Files
vue-pult/docs/HOMEPAGE_REFACTORING.md
2025-10-01 11:54:13 +03:00

505 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 📚 Документация по рефакторингу HomePage.vue
## 🎯 Обзор
HomePage.vue - главный компонент приложения для управления игровым тиром. Прошел полный рефакторинг от монолитного компонента (2456 строк) до модульной архитектуры на Vue 3 Composition API.
## 📊 Метрики рефакторинга
| Метрика | До рефакторинга | После рефакторинга | Улучшение |
|---------|----------------|-------------------|-----------|
| **Размер файла** | 2456 строк | ~600 строк | **-75%** |
| **Архитектура** | Монолит | Модульная | ✅ |
| **Поддерживаемость** | Сложная | Легкая | ✅ |
| **Тестируемость** | Низкая | Высокая | ✅ |
| **Переиспользование** | Отсутствует | Максимальное | ✅ |
## 🏗️ Новая архитектура
### Структура компонентов
```
HomePage.vue (главный компонент, ~600 строк)
├── Layout компоненты
│ ├── AppHeader.vue
│ ├── AppMain.vue
│ └── AppFooter.vue
├── UI компоненты
│ ├── IconButton.vue
│ ├── PlayPauseButton.vue
│ ├── ConfirmDialog.vue
│ ├── ThemeToggle.vue
│ ├── BurgerMenu.vue
│ └── TimeSemicircle.vue
├── Игровые компоненты
│ ├── GamePorts.vue
│ └── GamePort.vue
└── Компоненты смен
├── ShiftModal.vue
├── ShiftCloseModal.vue
└── ShiftForm/
├── FinancialSection.vue
├── ToysSection.vue
└── SalarySection.vue
```
### Composables architecture
```
composables/
├── useAuth.js // Аутентификация и авторизация
├── game/
│ ├── useGamePorts.js // Управление игровыми портами
│ ├── useGameControl.js // Пауза/воспроизведение
│ └── useTime.js // Работа со временем
├── shift/
│ ├── useShifts.js // Статус смен
│ ├── useShiftOperations.js // Операции со сменами
│ └── useShiftForm.js // Валидация форм смен
└── ui/
├── useModals.js // Управление модальными окнами
├── useConfirm.js // Диалоги подтверждения
└── useBurgerMenu.js // Бургер меню
```
## 🔧 Технические решения
### 1. Composition API с `<script setup>`
**Преимущества:**
- Лучшая организация кода
- Логическое группирование связанного кода
- Улучшенная читаемость
- Легкое тестирование
**Пример:**
```javascript
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
// Composables
import { useAuth } from '@/composables/useAuth'
import { useShiftOperations } from '@/composables/shift/useShiftOperations'
import { useModals } from '@/composables/ui/useModals'
// Инициализация
const router = useRouter()
const { user, logout, checkAuth } = useAuth()
const { shiftStatus, openRegularShift, submitCloseShift } = useShiftOperations()
const { showShiftModal, openShiftModal, closeShiftModal } = useModals()
</script>
```
### 2. Разделение ответственности
**HomePage.vue теперь отвечает только за:**
- Компоновку layout
- Координацию между компонентами
- Обработку основных событий
- Управление глобальным состоянием
**Логика вынесена в:**
- Composables для бизнес-логики
- Отдельные компоненты для UI
- Сервисы для работы с API
### 3. Управление состоянием
**Глобальное состояние:**
```javascript
// useAuth.js - состояние аутентификации
const user = ref(null)
const isAuthenticated = computed(() => !!user.value)
// useShiftOperations.js - состояние смен
const shiftStatus = ref({
regularShift: null,
techShift: null
})
// useModals.js - состояние модальных окон
const showShiftModal = ref(false)
const showCloseShiftModal = ref(false)
```
**Локальное состояние:**
```javascript
// HomePage.vue - только специфичное для страницы состояние
const isPaused = ref(false)
const closeShiftForm = ref({
money_konv: 0,
money_razm_closed: 0,
toys: [0, 0, 0, 0, 0]
})
```
### 4. Компонентная система
**UI компоненты - переиспользуемые:**
```vue
<!-- IconButton.vue -->
<template>
<button
class="icon-button"
:class="buttonClass"
:title="title"
@click="$emit('click')"
>
<slot>
<component :is="iconComponent" />
</slot>
</button>
</template>
<script setup>
defineProps({
icon: String,
title: String,
variant: { type: String, default: 'default' },
size: { type: String, default: 'md' }
})
defineEmits(['click'])
</script>
```
**Бизнес-компоненты - предметно-ориентированные:**
```vue
<!-- GamePorts.vue -->
<template>
<div class="game-ports">
<GamePort
v-for="port in ports"
:key="port.id"
:port="port"
@click="$emit('port-click', port.id)"
/>
</div>
</template>
<script setup>
import { useGamePorts } from '@/composables/game/useGamePorts'
const { ports, handlePortClick } = useGamePorts()
defineEmits(['port-click'])
</script>
```
## 🚀 Преимущества новой архитектуры
### 1. Поддерживаемость
- **Четкая структура** - каждый файл имеет свою зону ответственности
- **Легкая навигация** - логика сгруппирована по функциональности
- **Самодокументируемый код** - названия composables и компонентов говорят о их назначении
### 2. Тестируемость
- **Изолированные единицы** - каждый composable можно тестировать отдельно
- **Моки и заглушки** - легкая подмена зависимостей
- **Покрытие кода** - можно тестировать конкретную функциональность
### 3. Переиспользование
- **UI компоненты** - используются во всем приложении
- **Composables** - бизнес-логика доступна в разных компонентах
- **Миксины функциональности** - можно комбинировать разные composables
### 4. Производительность
- **Ленивая загрузка** - компоненты подгружаются по мере необходимости
- **Оптимизация рендеринга** - мелкие компоненты лучше оптимизируются Vue
- **Меньший бандл** - код разделяется на чанки
## 📋 Руководство по разработке
### Добавление новой функциональности
1. **Создайте composable для бизнес-логики:**
```javascript
// composables/game/useNewFeature.js
export function useNewFeature() {
const state = ref(null)
const doSomething = () => {
// логика
}
return { state, doSomething }
}
```
2. **Создайте UI компонент при необходимости:**
```vue
<!-- components/game/NewFeature.vue -->
<template>
<div class="new-feature">
<!-- UI -->
</div>
</template>
<script setup>
import { useNewFeature } from '@/composables/game/useNewFeature'
const { state, doSomething } = useNewFeature()
</script>
```
3. **Интегрируйте в HomePage.vue:**
```vue
<!-- HomePage.vue -->
<template>
<NewFeature @some-event="handleEvent" />
</template>
<script setup>
import NewFeature from '@/components/game/NewFeature.vue'
const handleEvent = () => {
// обработка события
}
</script>
```
### Модификация существующей функциональности
1. **Найдите соответствующий composable**
2. **Измените логику в composable**
3. **Обновите компоненты при необходимости**
4. **Протестируйте изменения**
### Добавление нового UI компонента
1. **Создайте компонент в соответствующей папке:**
- `components/ui/` - общие UI компоненты
- `components/game/` - игровые компоненты
- `components/shift/` - компоненты смен
2. **Сделайте его переиспользуемым:**
```vue
<script setup>
defineProps({
// props с валидацией
})
defineEmits([
// события
])
</script>
```
3. **Добавьте документацию и примеры использования**
## 🧪 Тестирование
### Тестирование composables
```javascript
// tests/composables/useGamePorts.spec.js
import { useGamePorts } from '@/composables/game/useGamePorts'
describe('useGamePorts', () => {
it('должен инициализировать порты', () => {
const { gamePorts } = useGamePorts()
expect(gamePorts.value).toHaveLength(6)
})
it('должен обрабатывать клик по порту', () => {
const { handlePortClick } = useGamePorts()
const consoleSpy = vi.spyOn(console, 'log')
handlePortClick(1)
expect(consoleSpy).toHaveBeenCalledWith('Клик по порту:', 1)
})
})
```
### Тестирование компонентов
```javascript
// tests/components/GamePort.spec.js
import { mount } from '@vue/test-utils'
import GamePort from '@/components/game/GamePort.vue'
describe('GamePort', () => {
it('должен отображать номер порта', () => {
const port = { id: 1, active: false, occupied: false }
const wrapper = mount(GamePort, { props: { port } })
expect(wrapper.find('.port-number').text()).toBe('1')
})
it('должен иметь правильные классы для активного порта', () => {
const port = { id: 1, active: true, occupied: false }
const wrapper = mount(GamePort, { props: { port } })
expect(wrapper.classes()).toContain('active')
})
})
```
## 🔍 Best Practices
### 1. Структура composables
```javascript
// ✅ Хорошо
export function useShiftOperations() {
// Состояние
const shiftStatus = ref(null)
// Методы
const openShift = async () => {
// логика
}
const closeShift = async () => {
// логика
}
// Возвращаем только необходимое
return {
shiftStatus,
openShift,
closeShift
}
}
```
### 2. Обработка ошибок
```javascript
// ✅ Хорошо
const openShift = async () => {
try {
const response = await api.openShift()
return { success: true, data: response }
} catch (error) {
console.error('Ошибка открытия смены:', error)
return { success: false, error: error.message }
}
}
```
### 3. Валидация props
```javascript
// ✅ Хорошо
const props = defineProps({
variant: {
type: String,
default: 'default',
validator: (value) => ['default', 'danger', 'warning'].includes(value)
},
size: {
type: String,
default: 'md',
validator: (value) => ['sm', 'md', 'lg'].includes(value)
}
})
```
### 4. Использование computed свойств
```javascript
// ✅ Хорошо
const canManageShift = computed(() => {
return user.value?.group === 3 ||
user.value?.taccess === 'operators' ||
user.value?.taccess === 'admins'
})
```
## 🚨 Известные проблемы и решения
### Проблема 1: Сложность отладки WebSocket
**Решение:** Создайте отдельный composable для логирования:
```javascript
// composables/debug/useWebSocketLogger.js
export function useWebSocketLogger() {
const logMessage = (message, direction = 'send') => {
console.log(`[${direction.toUpperCase()}]`, new Date().toISOString(), message)
}
return { logMessage }
}
```
### Проблема 2: Синхронизация состояния между компонентами
**Решение:** Используйте reactive объекты в composables:
```javascript
// composables/shared/useGameState.js
const gameState = reactive({
isPaused: false,
activePorts: [],
currentShift: null
})
export function useGameState() {
return { gameState }
}
```
### Проблема 3: Обработка множественных асинхронных операций
**Решение:** Используйте Promise.all или async/await с обработкой ошибок:
```javascript
const initializeApp = async () => {
try {
const [authResult, shiftStatus, portsStatus] = await Promise.all([
checkAuth(),
getShiftsStatus(),
getPortsStatus()
])
return { success: true, data: { authResult, shiftStatus, portsStatus } }
} catch (error) {
return { success: false, error: error.message }
}
}
```
## 📈 План дальнейших улучшений
### Краткосрочные цели (1-2 недели)
1. **Добавить TypeScript** для типобезопасности
2. **Написать unit тесты** для основных composables
3. **Оптимизировать производительность** с помощью lazy loading
4. **Улучшить обработку ошибок** и пользовательский опыт
### Среднесрочные цели (1-2 месяца)
1. **Создать Storybook** для документации компонентов
2. **Добавить E2E тесты** для ключевых сценариев
3. **Реализовать кэширование** для оптимизации запросов
4. **Улучшить мобильную версию**
### Долгосрочные цели (3-6 месяцев)
1. **Миграция на Pinia** для управления состоянием
2. **Добавить PWA** функциональность
3. **Оптимизировать бандл** и время загрузки
4. **Реализовать темы оформления**
## 📚 Полезные ресурсы
### Документация
- [Vue 3 Composition API](https://vuejs.org/guide/extras/composition-api-faq.html)
- [Vue Router](https://router.vuejs.org/)
- [Vite](https://vitejs.dev/)
### Инструменты
- [Vue DevTools](https://devtools.vuejs.org/)
- [Vitest](https://vitest.dev/) для тестирования
- [ESLint](https://eslint.org/) для качества кода
- [Prettier](https://prettier.io/) для форматирования
### Паттерны и лучшие практики
- [Vue Style Guide](https://v2.vuejs.org/v2/style-guide/)
- [Composition API Patterns](https://vueuse.org/)
- [Vue 3 Best Practices](https://learnvue.co/articles/vue-3-best-practices)
---
## 🎉 Заключение
Рефакторинг HomePage.vue значительно улучшил архитектуру приложения:
- **Уменьшен размер кода** на 75%
- **Повышена поддерживаемость** через модульную структуру
- **Улучшена тестируемость** через изолированные composables
- **Увеличено переиспользование** компонентов и логики
Проект теперь следует современным практикам разработки на Vue 3 и готов к дальнейшему масштабированию и развитию.
---
*Документация обновлена: {{ new Date().toLocaleDateString('ru-RU') }}*
§