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

16 KiB
Raw Blame History

📚 Документация по рефакторингу 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>

Преимущества:

  • Лучшая организация кода
  • Логическое группирование связанного кода
  • Улучшенная читаемость
  • Легкое тестирование

Пример:

<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. Управление состоянием

Глобальное состояние:

// 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)

Локальное состояние:

// HomePage.vue - только специфичное для страницы состояние
const isPaused = ref(false)
const closeShiftForm = ref({
  money_konv: 0,
  money_razm_closed: 0,
  toys: [0, 0, 0, 0, 0]
})

4. Компонентная система

UI компоненты - переиспользуемые:

<!-- 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>

Бизнес-компоненты - предметно-ориентированные:

<!-- 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 для бизнес-логики:
// composables/game/useNewFeature.js
export function useNewFeature() {
  const state = ref(null)
  
  const doSomething = () => {
    // логика
  }
  
  return { state, doSomething }
}
  1. Создайте UI компонент при необходимости:
<!-- 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>
  1. Интегрируйте в HomePage.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. Сделайте его переиспользуемым:

<script setup>
defineProps({
  // props с валидацией
})

defineEmits([
  // события
])
</script>
  1. Добавьте документацию и примеры использования

🧪 Тестирование

Тестирование composables

// 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)
  })
})

Тестирование компонентов

// 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

// ✅ Хорошо
export function useShiftOperations() {
  // Состояние
  const shiftStatus = ref(null)
  
  // Методы
  const openShift = async () => {
    // логика
  }
  
  const closeShift = async () => {
    // логика
  }
  
  // Возвращаем только необходимое
  return {
    shiftStatus,
    openShift,
    closeShift
  }
}

2. Обработка ошибок

// ✅ Хорошо
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

// ✅ Хорошо
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 свойств

// ✅ Хорошо
const canManageShift = computed(() => {
  return user.value?.group === 3 || 
         user.value?.taccess === 'operators' || 
         user.value?.taccess === 'admins'
})

🚨 Известные проблемы и решения

Проблема 1: Сложность отладки WebSocket

Решение: Создайте отдельный composable для логирования:

// 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:

// composables/shared/useGameState.js
const gameState = reactive({
  isPaused: false,
  activePorts: [],
  currentShift: null
})

export function useGameState() {
  return { gameState }
}

Проблема 3: Обработка множественных асинхронных операций

Решение: Используйте Promise.all или async/await с обработкой ошибок:

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. Реализовать темы оформления

📚 Полезные ресурсы

Документация

Инструменты

Паттерны и лучшие практики


🎉 Заключение

Рефакторинг HomePage.vue значительно улучшил архитектуру приложения:

  • Уменьшен размер кода на 75%
  • Повышена поддерживаемость через модульную структуру
  • Улучшена тестируемость через изолированные composables
  • Увеличено переиспользование компонентов и логики

Проект теперь следует современным практикам разработки на Vue 3 и готов к дальнейшему масштабированию и развитию.


Документация обновлена: {{ new Date().toLocaleDateString('ru-RU') }} §