- Клиентская часть Vue 3 + Vite - Серверная часть Node.js + WebSocket - Система авторизации и смен - Управление игровыми портами - Поддержка тем (светлая/темная) - Адаптивный дизайн 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
173 lines
11 KiB
Markdown
173 lines
11 KiB
Markdown
# План 1: Рефакторинг HomePage.vue (разделение UI/бизнес/сеть)
|
||
|
||
Документ описывает подробный план раскладывания `client/src/pages/HomePage.vue` (~1200 строк) на независимые компоненты и вынос бизнес‑логики/сетевых вызовов в composables. Цель — сохранить текущее поведение, повысить читаемость, переиспользуемость и упростить сопровождение.
|
||
|
||
## Текущее состояние и проблемы
|
||
- В одном файле смешаны: верстка, управление модалками, работа со сменами, сетевые вызовы WebSocket, вывод ошибок и индикаторы системного состояния.
|
||
- Повторяются вызовы `socket.sendMessage({ do: ... })`; протокол (`do`, `promise`) «просачивается» в UI.
|
||
- Сложно тестировать и вносить изменения — любая правка затрагивает большой файл.
|
||
|
||
## Целевая архитектура
|
||
Страница — тонкий контейнер, который собирает:
|
||
- Отдельные UI‑компоненты (layout, grid, модалки) через props/emit.
|
||
- Composables для состояния и бизнес‑логики.
|
||
- Тонкий слой WS‑API вместо прямых `sendMessage`.
|
||
|
||
### Новые компоненты (директории и обязанности)
|
||
- `components/layout/HeaderBar.vue`
|
||
- Отвечает за кнопки: пауза, выключение, выход, переключатель темы.
|
||
- Props: `isPaused: boolean`.
|
||
- Emits: `toggle-pause`, `request-shutdown`, `request-logout`, `open-shift-modal`.
|
||
- `components/layout/FooterBar.vue`
|
||
- Отображает индикаторы (ESP, link‑to‑server, sync) и счетчики.
|
||
- Props: `indicators: { esp:boolean, ext:boolean, sync:string, ... }`.
|
||
- Emits: по необходимости (`open-tech-shift`, и т.п.).
|
||
- `components/game/PortsGrid.vue`
|
||
- Отрисовывает сетку портов.
|
||
- Props: `ports: Array<{ id:number, active:boolean, occupied:boolean }>`.
|
||
- Emits: `click-port(id:number)`.
|
||
- `components/modals/ConfirmDialog.vue`
|
||
- Универсальная модалка подтверждения.
|
||
- Props: `modelValue:boolean`, `title:string`, `text:string`, `confirmLabel?:string`, `cancelLabel?:string`, `severity?:'info'|'warn'|'danger'`.
|
||
- Emits: `update:modelValue`, `confirm`, `cancel`.
|
||
- `components/modals/ShiftModal.vue`
|
||
- Форма открытия обычной смены.
|
||
- Props: `modelValue:boolean`, `form:any`.
|
||
- Emits: `update:modelValue`, `submit(form)`.
|
||
- `components/modals/CloseShiftModal.vue`
|
||
- Форма закрытия смены; валидация данных (касса/расходы/зарплаты).
|
||
- Props/Emits аналогично.
|
||
- `components/modals/TechShiftModal.vue`
|
||
- Открытие/закрытие тех. смены; простые поля + подтверждение.
|
||
|
||
### Новые composables
|
||
- `composables/useShift.js`
|
||
- Держит состояние смен: `regularShift`, `techShift`, `isPaused` и т.п.
|
||
- Методы: `openRegularShift`, `closeRegularShift(closedData)`, `openTechShift`, `closeTechShift`, `fetchShiftStatus`, `togglePause`.
|
||
- Обрабатывает ошибки и повторную авторизацию (если требуется) через `useAuth`.
|
||
- `composables/usePorts.js`
|
||
- Держит `gamePorts`, `selectedPort`, вычисления для сетки.
|
||
- Методы: `loadInfo()`, `selectPort(id)`, `handlePortClick(id)`.
|
||
- `composables/useIndicators.js`
|
||
- Держит индикаторы: `espConnected`, `extServerConnected`, `syncState`.
|
||
- Подписка на системные сообщения из слоя WS (без `window.*`).
|
||
- `api/wsApi.js`
|
||
- Единая обертка над WebSocket‑вызовами, скрывает `promise/do`.
|
||
- Методы: `info()`, `smenaOpen()`, `smenaClose(closedData)`, `tehOpen()`, `tehClose(closeObj)`, `ping()`.
|
||
|
||
### Файловая структура (предлагаемая)
|
||
```
|
||
client/src/
|
||
api/wsApi.js
|
||
components/
|
||
layout/HeaderBar.vue
|
||
layout/FooterBar.vue
|
||
game/PortsGrid.vue
|
||
modals/ConfirmDialog.vue
|
||
modals/ShiftModal.vue
|
||
modals/CloseShiftModal.vue
|
||
modals/TechShiftModal.vue
|
||
composables/
|
||
useShift.js
|
||
usePorts.js
|
||
useIndicators.js
|
||
```
|
||
|
||
## Детали реализации (ключевые моменты)
|
||
1) WS URL и поведение по умолчанию: читать `import.meta.env.VITE_WS_URL || 'ws://localhost:9000'` в низком слое (не в UI).
|
||
2) Убрать `window.systemMessageHandler`: организовать подписки внутри `useWebSocketGlobal` (или локальная шина событий) и передавать уведомления в `useIndicators`.
|
||
3) Ошибки: единый helper `notifyError(message)` (позже заменить на toast), без `alert` для прод.
|
||
4) Логи: в прод режиме (`import.meta.env.PROD`) минимизировать `console.log`.
|
||
|
||
## Пошаговый план (итерации с критерием готовности)
|
||
Итерация 0 — Подготовка
|
||
- Добавить `api/wsApi.js` с оберткой `request(do, payload, { timeout })` и методами `info/smenaOpen/smenaClose/tehOpen/tehClose/ping`.
|
||
- Критерий: все вызовы на странице можно заменить без изменения логики.
|
||
|
||
Итерация 1 — PortsGrid
|
||
- Вырезать сетку портов в `components/game/PortsGrid.vue`.
|
||
- Пробросить данные через props и клики через emit.
|
||
- В `HomePage.vue` слушать `click-port` и вызывать логику из страницы (временный мост).
|
||
- Критерий: визуально без изменений, клики работают как прежде.
|
||
|
||
Итерация 2 — Header/Footer
|
||
- Создать `layout/HeaderBar.vue`, `layout/FooterBar.vue`, перенести кнопки/индикаторы.
|
||
- Все действия экспонировать через emit, состояние — через props.
|
||
- Критерий: UI/поведение идентичны (пауза, выход/выключение, индикаторы).
|
||
|
||
Итерация 3 — Модалки
|
||
- Создать `ConfirmDialog.vue` и заменить подтверждения выхода/выключения.
|
||
- Создать `ShiftModal.vue`, `CloseShiftModal.vue`, `TechShiftModal.vue`.
|
||
- Критерий: открытие/закрытие модалок и сабмиты работают как прежде.
|
||
|
||
Итерация 4 — useShift
|
||
- Вынести всю логику смен (открыть/закрыть/тех) из страницы в `useShift` и перевести вызовы на `wsApi`.
|
||
- Критерий: HomePage не содержит прямых `sendMessage`; все операции смен — через `useShift`.
|
||
|
||
Итерация 5 — usePorts
|
||
- Перенести состояние и обработчики портов в `usePorts` (загрузка `info()`, выбор порта, computed состояния).
|
||
- Критерий: HomePage оперирует только вызовами `usePorts` и `PortsGrid`.
|
||
|
||
Итерация 6 — useIndicators
|
||
- Перенести индикаторы и подписки на системные сообщения в `useIndicators` (подключение к глобальному WS).
|
||
- Критерий: индикаторы меняются корректно, без глобальных хендлеров в `window`.
|
||
|
||
Итерация 7 — Уборка и полировка
|
||
- Привести строки к UTF‑8, убрать «битую» кодировку, разграничить `DEV/PROD`‑логи.
|
||
- Ограничить `HomePage.vue` до 150–200 строк `<script setup>`.
|
||
- Критерий: сборка `npm run build` успешна, консоль без новых ошибок.
|
||
|
||
## Спецификации props/emit (выдержки)
|
||
- HeaderBar.vue
|
||
- Props: `{ isPaused: Boolean }`
|
||
- Emits: `toggle-pause`, `request-shutdown`, `request-logout`, `open-shift-modal`
|
||
- FooterBar.vue
|
||
- Props: `{ indicators: Object }`
|
||
- PortsGrid.vue
|
||
- Props: `{ ports: Array, selectedId?: Number }`
|
||
- Emits: `click-port(id: Number)`
|
||
- ConfirmDialog.vue
|
||
- v-model: `modelValue:Boolean`; Emits: `update:modelValue`, `confirm`, `cancel`
|
||
|
||
## Пример wsApi (псевдокод)
|
||
```js
|
||
// client/src/api/wsApi.js
|
||
import { useWebSocketGlobal } from '@/composables/useWebSocketGlobal'
|
||
|
||
const request = async (doName, payload = {}, timeout = 10000) => {
|
||
const { getGlobalSocket } = useWebSocketGlobal()
|
||
const socket = await getGlobalSocket()
|
||
const message = { do: doName, ...payload }
|
||
return await socket.sendMessage(message, timeout)
|
||
}
|
||
|
||
export const wsApi = {
|
||
info: () => request('info'),
|
||
smenaOpen: () => request('smena', { enable: true }),
|
||
smenaClose: (closed) => request('smena', { enable: false, closed }),
|
||
tehOpen: () => request('teh-smena', { enable: true }),
|
||
tehClose: (closeObj) => request('teh-smena', { enable: false, closeObj }),
|
||
ping: () => request('ping', {}, 1000),
|
||
}
|
||
```
|
||
|
||
## Acceptance (приемка)
|
||
- Функциональные потоки: вход → открытие смены → работа с портами → закрытие смены; тех. смена — без регрессий.
|
||
- HomePage не содержит прямых WS‑вызовов, только обращения к composables/API.
|
||
- Визуальное поведение/верстка без отличий.
|
||
- Консоль без ошибок, DEV‑логи выключаемы в PROD.
|
||
|
||
## Риски и смягчение
|
||
- Риск рассинхронизации протокола WS: сначала вводим `wsApi`, затем переносим логику — минимальные изменения UI.
|
||
- Риск утраты поведения в модалках: сначала выделяем ConfirmDialog и покрываем кейсы выход/выключение, затем переносим формы смен.
|
||
- Риск сбоев из‑за «битых» строк: поэтапная замена строк UTF‑8 в изменяемых файлах.
|
||
|
||
## План отката
|
||
- Каждая итерация — отдельный коммит/PR. При регрессии — revert последней итерации.
|
||
|
||
## Оценка трудозатрат (грубая)
|
||
- Итерация 0–1: 4–6 часов (wsApi + PortsGrid).
|
||
- Итерация 2–3: 6–8 часов (Header/Footer + модалки).
|
||
- Итерация 4–6: 10–14 часов (composables, перенос логики).
|
||
- Итерация 7: 2–4 часа (полировка/приемка).
|