docs: replace cursor rules with CLAUDE.md
Migrate project guidance from .cursor/ to a single CLAUDE.md covering architecture, backend/frontend conventions, commands and PHP 7.4 constraint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,64 +0,0 @@
|
|||||||
# Cursor AI Agents Configuration
|
|
||||||
|
|
||||||
## Роли и правила поведения ИИ
|
|
||||||
|
|
||||||
### Основная роль: Senior Full-Stack Developer
|
|
||||||
|
|
||||||
Вы - опытный full-stack разработчик, специализирующийся на:
|
|
||||||
|
|
||||||
- OpenCart модульной разработке
|
|
||||||
- Кастомных фреймворках (OpenCart Framework)
|
|
||||||
- PHP 7.4+ с современными практиками
|
|
||||||
- Vue.js 3 (Composition API)
|
|
||||||
- Telegram Mini App разработке
|
|
||||||
|
|
||||||
### Правила работы с кодом
|
|
||||||
|
|
||||||
1. **Всегда используй существующие паттерны проекта**
|
|
||||||
2. **Не создавай дубликаты - используй существующие утилиты**
|
|
||||||
3. **Следуй соглашениям именования проекта**
|
|
||||||
4. **Тестируй изменения перед коммитом**
|
|
||||||
5. **Документируй публичные API**
|
|
||||||
6. **Комментарии только на английском языке и только если они действительно оправданы**
|
|
||||||
|
|
||||||
### Правила коммитов
|
|
||||||
|
|
||||||
1. **Следование Conventional Commits**
|
|
||||||
- Используй префиксы: `feat:`, `fix:`, `chore:`, `refactor:`, `style:`, `test:`, `docs:`
|
|
||||||
- Формат: `<type>: <subject>` (первая строка до 72 символов)
|
|
||||||
- После пустой строки - подробное описание изменений
|
|
||||||
|
|
||||||
2. **Язык коммитов**
|
|
||||||
- Все коммиты на **английском языке**
|
|
||||||
- Подробное описание изменений в теле коммита
|
|
||||||
- Перечисляй все измененные файлы и ключевые изменения
|
|
||||||
|
|
||||||
3. **Примеры правильных коммитов**
|
|
||||||
```
|
|
||||||
feat: add setting to control category products button visibility
|
|
||||||
|
|
||||||
- Add show_category_products_button field to StoreDTO
|
|
||||||
- Update SettingsSerializerService to support new field
|
|
||||||
- Add setting in admin panel on 'Store' tab with toggle
|
|
||||||
- Pass setting to SPA through SettingsHandler
|
|
||||||
- Button displays only for categories with child categories
|
|
||||||
- Add default value true to configuration
|
|
||||||
```
|
|
||||||
|
|
||||||
### Запрещено
|
|
||||||
|
|
||||||
- Хардкод значений (используй конфиги/настройки)
|
|
||||||
- Игнорирование обработки ошибок
|
|
||||||
- Создание циклических зависимостей
|
|
||||||
|
|
||||||
Для разработки FrontEnd используй:
|
|
||||||
|
|
||||||
- Vue.js 3 (Composition API)
|
|
||||||
- Старайся избегать функций watch там, где это возможно и где можно сделать более красиво.
|
|
||||||
- Для frontend/admin используй Tailwind 4 с префиксом `tw:`.
|
|
||||||
- Для frontend/spa используй Tailwind 4 без префикса.
|
|
||||||
- Для frontend/admin используй иконки от FontAwesome 4, потому что это уже встроено в OpenCart 3.
|
|
||||||
- Для frontend/admin используй компоненты VuePrime 4.
|
|
||||||
- Для frontend/spa используй Daisy UI.
|
|
||||||
- Чтобы получить название стандартной таблицы OpenCart, используй хелпер `db_table`, либо добавляй константу DB_PREFIX перед названием таблицы. Так ты получишь название таблицы с префиксом.
|
|
||||||
- Все таблицы моего модуля TeleCart начинаются с префикса `telecart_`. Примеры миграций лежат в `module/oc_telegram_shop/upload/oc_telegram_shop/database/migrations`
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"rules": {
|
|
||||||
"preferCompositionAPI": true,
|
|
||||||
"strictTypes": true,
|
|
||||||
"noHardcodedValues": true,
|
|
||||||
"useDependencyInjection": true
|
|
||||||
},
|
|
||||||
"paths": {
|
|
||||||
"telecart_module": "module/oc_telegram_shop/upload/oc_telegram_shop",
|
|
||||||
"frontendAdmin": "frontend/admin",
|
|
||||||
"telegramShopSpa": "frontend/spa",
|
|
||||||
"migrations": "module/oc_telegram_shop/upload/oc_telegram_shop/database/migrations",
|
|
||||||
"telecartHandlers": "module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers",
|
|
||||||
"adminHandlers": "module/oc_telegram_shop/upload/oc_telegram_shop/bastion/Handlers",
|
|
||||||
"models": "module/oc_telegram_shop/upload/oc_telegram_shop/src/Models",
|
|
||||||
"framework": "module/oc_telegram_shop/upload/oc_telegram_shop/framework"
|
|
||||||
},
|
|
||||||
"naming": {
|
|
||||||
"classes": "PascalCase",
|
|
||||||
"methods": "camelCase",
|
|
||||||
"variables": "camelCase",
|
|
||||||
"constants": "UPPER_SNAKE_CASE",
|
|
||||||
"files": "PascalCase for classes, kebab-case for others",
|
|
||||||
"tables": "snake_case with telecart_ prefix"
|
|
||||||
},
|
|
||||||
"php": {
|
|
||||||
"version": "7.4+",
|
|
||||||
"preferVersion": "7.4+",
|
|
||||||
"psr12": true
|
|
||||||
},
|
|
||||||
"javascript": {
|
|
||||||
"version": "ES2020+",
|
|
||||||
"framework": "Vue 3 Composition API",
|
|
||||||
"stateManagement": "Pinia",
|
|
||||||
"uiLibrary": "PrimeVue (admin), Tailwind (spa)"
|
|
||||||
},
|
|
||||||
"database": {
|
|
||||||
"queryBuilder": true,
|
|
||||||
"migrations": true,
|
|
||||||
"tablePrefix": "telecart_",
|
|
||||||
"noForeignKeys": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
## TeleCart Pulse Heartbeat Telemetry
|
|
||||||
|
|
||||||
### Цель
|
|
||||||
Раз в час отправлять телеметрию (heartbeat) на TeleCart Pulse, чтобы фиксировать состояние магазина и версии окружения без участия пользователя.
|
|
||||||
|
|
||||||
### Backend (`module/oc_telegram_shop/upload/oc_telegram_shop`)
|
|
||||||
- `framework/TeleCartPulse/TeleCartPulseService.php`
|
|
||||||
- Новый метод `handleHeartbeat()` собирает данные: домен (через `Utils::getCurrentDomain()`), username бота (через `TelegramService::getMe()`), версии PHP, модуля (из `composer.json`), OpenCart (`VERSION` и `VERSION_CORE`), текущий UTC timestamp.
|
|
||||||
- Последний успешный пинг кешируется (ключ `telecart_pulse_heartbeat`, TTL 1 час) через существующий `CacheInterface`.
|
|
||||||
- Подпись heartbeat выполняется через отдельный `PayloadSigner`, который использует секрет `pulse.heartbeat_secret`/`PULSE_HEARTBEAT_SECRET`. Логируются предупреждения при ошибках кеша/бота/подписи.
|
|
||||||
- Отправка идет на эндпоинт `heartbeat` с таймаутом 2 секунды и заголовком `X-TELECART-VERSION`, взятым из `composer.json`.
|
|
||||||
- `framework/TeleCartPulse/TeleCartPulseServiceProvider.php`
|
|
||||||
- Регистрирует основной `PayloadSigner` (по `pulse.api_key`) и отдельный heartbeat signer (по `pulse.heartbeat_secret` или `PULSE_HEARTBEAT_SECRET`), инжектит `LoggerInterface`.
|
|
||||||
- `src/Handlers/TelemetryHandler.php` + `src/routes.php`
|
|
||||||
- Добавлен маршрут `heartbeat`, который вызывает `handleHeartbeat()` и возвращает `{ status: "ok" }`. Логгер пишет warning при проблемах.
|
|
||||||
|
|
||||||
### Frontend (`frontend/spa`)
|
|
||||||
- `src/utils/ftch.js`: новая функция `heartbeat()` вызывает `api_action=heartbeat`.
|
|
||||||
- `src/stores/Pulse.js`: добавлен action `heartbeat`, использующий новую API-функцию и логирующий результат.
|
|
||||||
- `src/main.js`: после `pulse.ingest(...)` вызывается `pulse.heartbeat()` без блокировки цепочки.
|
|
||||||
|
|
||||||
### Конфигурация / ENV
|
|
||||||
- `PULSE_API_HOST` — базовый URL TeleCart Pulse (используется и для events, и для heartbeat).
|
|
||||||
- `PULSE_TIMEOUT` — общий таймаут HTTP (для heartbeat принудительно 2 секунды).
|
|
||||||
- `PULSE_HEARTBEAT_SECRET` (или `pulse.heartbeat_secret` в настройках) — общий секрет для подписания heartbeat. Обязателен, иначе heartbeat не будет отправляться.
|
|
||||||
- `pulse.api_key` — прежний API ключ, используется только для event-инджеста.
|
|
||||||
|
|
||||||
### Поведение
|
|
||||||
1. Frontend (SPA) вызывает `heartbeat` при инициализации приложения (fire-and-forget).
|
|
||||||
2. Backend проверяет кеш. Если часа еще не прошло, `handleHeartbeat()` возвращает без запросов.
|
|
||||||
3. При необходимости собираются данные, подписываются через heartbeat signer и отправляются POST-запросом на `/heartbeat`.
|
|
||||||
4. Любые сбои (bot info, подпись, HTTP) логируются как warning, чтобы не тревожить пользователей.
|
|
||||||
|
|
||||||
### TODO / Возможные улучшения
|
|
||||||
- При необходимости вынести heartbeat запуск в крон/CLI, чтобы не зависеть от фронтенда.
|
|
||||||
- Добавить метрики успешности heartbeat в админку.
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
# Промпты для генерации API
|
|
||||||
|
|
||||||
## Создание нового API endpoint
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай новый API endpoint [ENDPOINT_NAME] для [DESCRIPTION]:
|
|
||||||
|
|
||||||
1. Handler в [HANDLER_PATH]:
|
|
||||||
- Метод handle() принимает Request
|
|
||||||
- Валидация входных данных
|
|
||||||
- Использование Service для бизнес-логики
|
|
||||||
- Возврат JsonResponse с правильной структурой
|
|
||||||
- Обработка ошибок с логированием
|
|
||||||
|
|
||||||
2. Service в [SERVICE_PATH]:
|
|
||||||
- Бизнес-логика
|
|
||||||
- Работа с Model
|
|
||||||
- Валидация данных
|
|
||||||
- Обработка исключений
|
|
||||||
|
|
||||||
3. Model в [MODEL_PATH] (если нужен):
|
|
||||||
- Методы для работы с БД
|
|
||||||
- Использование Query Builder
|
|
||||||
- Типизация методов
|
|
||||||
|
|
||||||
4. Route в routes.php:
|
|
||||||
- Добавь маршрут с правильным именем
|
|
||||||
|
|
||||||
5. Миграция (если нужна новая таблица):
|
|
||||||
- Создай миграцию в database/migrations/
|
|
||||||
- Используй фиксированный префикс telecart_
|
|
||||||
- Добавь индексы где необходимо
|
|
||||||
|
|
||||||
Следуй архитектуре MVC-L проекта и используй существующие паттерны.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание CRUD API
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай полный CRUD API для сущности [ENTITY_NAME]:
|
|
||||||
|
|
||||||
1. Handler с методами:
|
|
||||||
- list() - список с пагинацией и фильтрацией
|
|
||||||
- get() - получение одной записи
|
|
||||||
- create() - создание
|
|
||||||
- update() - обновление
|
|
||||||
- delete() - удаление
|
|
||||||
|
|
||||||
2. Service с бизнес-логикой для всех операций
|
|
||||||
|
|
||||||
3. Model с методами:
|
|
||||||
- findAll() - список
|
|
||||||
- findById() - по ID
|
|
||||||
- create() - создание
|
|
||||||
- update() - обновление
|
|
||||||
- delete() - удаление
|
|
||||||
|
|
||||||
4. DTO для валидации данных
|
|
||||||
|
|
||||||
5. Миграция для таблицы [TABLE_NAME]
|
|
||||||
|
|
||||||
6. Routes для всех endpoints
|
|
||||||
|
|
||||||
Используй серверную пагинацию, фильтрацию и сортировку для list().
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание Admin API endpoint
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай Admin API endpoint [ENDPOINT_NAME] в bastion/Handlers/:
|
|
||||||
|
|
||||||
1. Handler в bastion/Handlers/[HANDLER_NAME].php:
|
|
||||||
- Используй Request для получения параметров
|
|
||||||
- Валидация данных
|
|
||||||
- Работа через Service
|
|
||||||
- Возврат JsonResponse с структурой { data: { data: [...], totalRecords: ... } }
|
|
||||||
- Обработка ошибок
|
|
||||||
|
|
||||||
2. Service в bastion/Services/ (если нужен):
|
|
||||||
- Бизнес-логика для админки
|
|
||||||
- Работа с Models
|
|
||||||
|
|
||||||
3. Route в bastion/routes.php
|
|
||||||
|
|
||||||
4. Frontend компонент (если нужен UI):
|
|
||||||
- Vue компонент в frontend/admin/src/views/
|
|
||||||
- Используй PrimeVue компоненты
|
|
||||||
- Серверная пагинация/фильтрация
|
|
||||||
- Обработка ошибок с toast уведомлениями
|
|
||||||
|
|
||||||
Следуй существующим паттернам проекта.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание Frontend API клиента
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай функцию для работы с API endpoint [ENDPOINT_NAME]:
|
|
||||||
|
|
||||||
1. В frontend/[admin|spa]/src/utils/http.js:
|
|
||||||
- Функция api[Method] для вызова endpoint
|
|
||||||
- Правильная обработка ошибок
|
|
||||||
- Возврат структурированного ответа
|
|
||||||
|
|
||||||
2. Использование:
|
|
||||||
- В компонентах через import
|
|
||||||
- Обработка loading states
|
|
||||||
- Toast уведомления для ошибок
|
|
||||||
|
|
||||||
Следуй существующим паттернам в http.js.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание миграции
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай миграцию для таблицы [TABLE_NAME]:
|
|
||||||
|
|
||||||
1. Файл: database/migrations/[TIMESTAMP]_[DESCRIPTION].php
|
|
||||||
2. Используй фиксированный префикс telecart_ для таблицы
|
|
||||||
3. Добавь все необходимые поля с правильными типами
|
|
||||||
4. Добавь индексы для часто используемых полей
|
|
||||||
5. Используй utf8mb4_unicode_ci collation
|
|
||||||
6. Используй InnoDB engine
|
|
||||||
7. Добавь created_at и updated_at timestamps
|
|
||||||
8. Не создавай foreign keys (используй только индексы)
|
|
||||||
|
|
||||||
Следуй структуре существующих миграций.
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
# Правила оформления Changelog для документации
|
|
||||||
|
|
||||||
## Общие требования
|
|
||||||
|
|
||||||
- **Формат**: Markdown
|
|
||||||
- **Структура**: Одна версия = одна страница
|
|
||||||
- **Стиль**: Профессиональный, лаконичный, без маркетинговых лозунгов
|
|
||||||
- **Язык**: Русский
|
|
||||||
- **Целевая аудитория**: Разработчики и владельцы магазинов
|
|
||||||
- **Содержимое**: Только ключевые изменения, без лишних технических деталей
|
|
||||||
|
|
||||||
## Структура страницы
|
|
||||||
|
|
||||||
### Вводный абзац
|
|
||||||
- Краткое описание релиза (1-2 предложения)
|
|
||||||
- Дополнить 1-2 предложениями о ключевых изменениях
|
|
||||||
- Упоминать основные фичи и улучшения
|
|
||||||
|
|
||||||
### Разделы (строго в этом порядке)
|
|
||||||
|
|
||||||
1. **🚀 Добавлено** - новые функции и возможности
|
|
||||||
2. **✨ Улучшено** - улучшения существующих функций
|
|
||||||
3. **🐞 Исправлено** - исправленные ошибки
|
|
||||||
4. **⚠️ Несовместимые изменения** - изменения без обратной совместимости
|
|
||||||
5. **🔧 Технические изменения** - технические изменения (отдельный раздел)
|
|
||||||
|
|
||||||
### Правила для разделов
|
|
||||||
|
|
||||||
- Раздел **НЕ** добавлять, если в нём нет пунктов
|
|
||||||
- Использовать маркдаун-списки, без нумерации
|
|
||||||
- Не добавлять ссылок, если они явно не указаны
|
|
||||||
- Не добавлять лишних пояснений, выводов и заключений
|
|
||||||
- Выводить только содержимое Markdown-файла, без комментариев
|
|
||||||
|
|
||||||
## Разделение изменений
|
|
||||||
|
|
||||||
### Бизнес-логика и процессы
|
|
||||||
Разделы "Добавлено", "Улучшено", "Исправлено", "Несовместимые изменения" содержат только изменения, связанные с:
|
|
||||||
- Бизнес-процессами магазина
|
|
||||||
- Пользовательским опытом
|
|
||||||
- Функциональностью для владельцев магазинов
|
|
||||||
- Продающими фичами
|
|
||||||
|
|
||||||
### Технические изменения
|
|
||||||
Все технические изменения выносятся в отдельный раздел **🔧 Технические изменения**:
|
|
||||||
- Без подразделов, всё в одном списке
|
|
||||||
- Только самые ключевые технические изменения
|
|
||||||
- Не расписывать детали, которые не интересны пользователю
|
|
||||||
|
|
||||||
## Стиль написания
|
|
||||||
|
|
||||||
### Терминология
|
|
||||||
- Английские термины писать по-английски (например: "navbar", а не "навбар")
|
|
||||||
- Избегать технических терминов в бизнес-разделах (например: "customer_id" → "автоматическое связывание покупателей")
|
|
||||||
|
|
||||||
### Описания
|
|
||||||
|
|
||||||
**Для ключевых продающих фич:**
|
|
||||||
- Давать подробное описание с деталями
|
|
||||||
- Указывать возможности и преимущества
|
|
||||||
- Подчеркивать ценность для пользователя
|
|
||||||
|
|
||||||
**Для технических изменений:**
|
|
||||||
- Кратко, только ключевое
|
|
||||||
- Без лишних деталей
|
|
||||||
|
|
||||||
**Для обычных функций:**
|
|
||||||
- Лаконично, но информативно
|
|
||||||
- Фокус на пользе для пользователя
|
|
||||||
|
|
||||||
## Порядок размещения
|
|
||||||
|
|
||||||
### В разделе "Добавлено"
|
|
||||||
Ключевые продающие фичи размещать **первыми**:
|
|
||||||
1. Система конфигурации главной страницы через блоки
|
|
||||||
2. Конструктор форм на базе FormKit
|
|
||||||
3. Интеграция с Яндекс.Метрикой
|
|
||||||
4. Политика конфиденциальности
|
|
||||||
5. Поддержка купонов и подарочных сертификатов
|
|
||||||
6. Остальные функции
|
|
||||||
|
|
||||||
## Примеры правильных формулировок
|
|
||||||
|
|
||||||
### ✅ Хорошо
|
|
||||||
- "Автоматическое связывание покупателей Telegram с покупателями OpenCart: система автоматически находит и связывает покупателей из Telegram-магазина с существующими покупателями в OpenCart по email или номеру телефона, создавая единую базу клиентов"
|
|
||||||
|
|
||||||
### ❌ Плохо
|
|
||||||
- "Сохранение customer_id вместе с заказом для связи с клиентами OpenCart: автоматическая связь заказов из Telegram с покупателями в OpenCart, единая база клиентов"
|
|
||||||
|
|
||||||
### ✅ Хорошо
|
|
||||||
- "Navbar с логотипом и названием приложения"
|
|
||||||
|
|
||||||
### ❌ Плохо
|
|
||||||
- "Навбар с логотипом и названием приложения"
|
|
||||||
|
|
||||||
## Что не включать
|
|
||||||
|
|
||||||
- Внутренние детали разработки (тесты, статический анализ, обфускация)
|
|
||||||
- Технические детали, не интересные пользователям
|
|
||||||
- Избыточные технические описания
|
|
||||||
- Маркетинговые лозунги и призывы
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
# Промпты для документирования
|
|
||||||
|
|
||||||
## Документирование класса
|
|
||||||
|
|
||||||
```
|
|
||||||
Добавь PHPDoc документацию для класса [CLASS_NAME]:
|
|
||||||
1. Описание класса и его назначения
|
|
||||||
2. @package тег
|
|
||||||
3. @author тег
|
|
||||||
4. Документация для всех публичных методов
|
|
||||||
5. Документация для публичных свойств
|
|
||||||
6. Примеры использования где уместно
|
|
||||||
```
|
|
||||||
|
|
||||||
## Документирование метода
|
|
||||||
|
|
||||||
```
|
|
||||||
Добавь PHPDoc для метода [METHOD_NAME]:
|
|
||||||
1. Описание метода
|
|
||||||
2. @param для всех параметров с типами
|
|
||||||
3. @return с типом возвращаемого значения
|
|
||||||
4. @throws для всех исключений
|
|
||||||
5. Примеры использования если сложная логика
|
|
||||||
```
|
|
||||||
|
|
||||||
## Документирование API endpoint
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай документацию для API endpoint [ENDPOINT_NAME]:
|
|
||||||
1. Описание назначения
|
|
||||||
2. HTTP метод и путь
|
|
||||||
3. Параметры запроса (query/body)
|
|
||||||
4. Формат ответа (JSON структура)
|
|
||||||
5. Коды ошибок
|
|
||||||
6. Примеры запросов/ответов
|
|
||||||
7. Требования к авторизации
|
|
||||||
```
|
|
||||||
|
|
||||||
## Документирование Vue компонента
|
|
||||||
|
|
||||||
```
|
|
||||||
Добавь документацию для Vue компонента [COMPONENT_NAME]:
|
|
||||||
1. Описание компонента
|
|
||||||
2. Props с типами и описаниями
|
|
||||||
3. Emits с описаниями
|
|
||||||
4. Slots если есть
|
|
||||||
5. Примеры использования
|
|
||||||
6. Зависимости от других компонентов
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание README
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай README.md для [MODULE/COMPONENT]:
|
|
||||||
1. Описание назначения
|
|
||||||
2. Установка/настройка
|
|
||||||
3. Использование с примерами
|
|
||||||
4. API документация
|
|
||||||
5. Конфигурация
|
|
||||||
6. Troubleshooting
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
# Промпты для рефакторинга
|
|
||||||
|
|
||||||
## Общий рефакторинг
|
|
||||||
|
|
||||||
```
|
|
||||||
Проанализируй код в файле [FILE_PATH] и выполни рефакторинг:
|
|
||||||
1. Убери дублирование кода
|
|
||||||
2. Улучши читаемость
|
|
||||||
3. Примени принципы SOLID
|
|
||||||
4. Добавь обработку ошибок где необходимо
|
|
||||||
5. Улучши типизацию
|
|
||||||
6. Добавь документацию для публичных методов
|
|
||||||
7. Убедись что код следует архитектуре MVC-L проекта
|
|
||||||
8. Используй существующие утилиты и сервисы проекта вместо создания новых
|
|
||||||
```
|
|
||||||
|
|
||||||
## Рефакторинг Handler
|
|
||||||
|
|
||||||
```
|
|
||||||
Рефакторинг Handler [HANDLER_NAME]:
|
|
||||||
1. Вынеси бизнес-логику в отдельный Service
|
|
||||||
2. Добавь валидацию входных данных
|
|
||||||
3. Улучши обработку ошибок с логированием
|
|
||||||
4. Используй DTO для передачи данных
|
|
||||||
5. Добавь PHPDoc комментарии
|
|
||||||
6. Убедись что используется Dependency Injection
|
|
||||||
7. Оптимизируй запросы к БД если необходимо
|
|
||||||
```
|
|
||||||
|
|
||||||
## Рефакторинг Model
|
|
||||||
|
|
||||||
```
|
|
||||||
Рефакторинг Model [MODEL_NAME]:
|
|
||||||
1. Убедись что все запросы используют Query Builder
|
|
||||||
2. Добавь методы для частых операций (findBy, findAll, create, update)
|
|
||||||
3. Добавь валидацию данных перед сохранением
|
|
||||||
4. Улучши типизацию методов
|
|
||||||
5. Добавь PHPDoc комментарии
|
|
||||||
6. Используй транзакции для сложных операций
|
|
||||||
```
|
|
||||||
|
|
||||||
## Рефакторинг Vue компонента
|
|
||||||
|
|
||||||
```
|
|
||||||
Рефакторинг Vue компонента [COMPONENT_NAME]:
|
|
||||||
1. Вынеси логику в composable функции
|
|
||||||
2. Улучши типизацию props и emits
|
|
||||||
3. Оптимизируй computed properties
|
|
||||||
4. Добавь обработку ошибок
|
|
||||||
5. Улучши структуру template
|
|
||||||
6. Добавь loading states
|
|
||||||
7. Используй существующие утилиты проекта
|
|
||||||
```
|
|
||||||
|
|
||||||
## Удаление дублирования
|
|
||||||
|
|
||||||
```
|
|
||||||
Найди и устрани дублирование кода в:
|
|
||||||
- [FILE_PATH_1]
|
|
||||||
- [FILE_PATH_2]
|
|
||||||
- [FILE_PATH_3]
|
|
||||||
|
|
||||||
Создай общие утилиты/сервисы где необходимо, следуя архитектуре проекта.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Улучшение производительности
|
|
||||||
|
|
||||||
```
|
|
||||||
Проанализируй производительность кода в [FILE_PATH]:
|
|
||||||
1. Оптимизируй запросы к БД (используй индексы, избегай N+1)
|
|
||||||
2. Добавь кэширование где уместно
|
|
||||||
3. Оптимизируй алгоритмы
|
|
||||||
4. Уменьши количество запросов к API
|
|
||||||
5. Используй ленивую загрузку на фронтенде
|
|
||||||
```
|
|
||||||
|
|
||||||
## Улучшение безопасности
|
|
||||||
|
|
||||||
```
|
|
||||||
Улучши безопасность кода в [FILE_PATH]:
|
|
||||||
1. Добавь валидацию всех входных данных
|
|
||||||
2. Используй prepared statements (Query Builder)
|
|
||||||
3. Добавь CSRF защиту где необходимо
|
|
||||||
4. Валидируй права доступа
|
|
||||||
5. Санитизируй выходные данные
|
|
||||||
6. Добавь rate limiting где необходимо
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
# Промпты для тестирования
|
|
||||||
|
|
||||||
## Создание unit теста
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай unit тест для [CLASS_NAME] в tests/Unit/:
|
|
||||||
|
|
||||||
1. Используй PHPUnit
|
|
||||||
2. Покрой все публичные методы
|
|
||||||
3. Тестируй успешные сценарии
|
|
||||||
4. Тестируй обработку ошибок
|
|
||||||
5. Используй моки для зависимостей
|
|
||||||
6. Следуй структуре существующих тестов
|
|
||||||
7. Используй TestCase базовый класс проекта
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание integration теста
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай integration тест для [FEATURE_NAME] в tests/Integration/:
|
|
||||||
|
|
||||||
1. Тестируй полный flow от запроса до ответа
|
|
||||||
2. Используй тестовую БД
|
|
||||||
3. Очищай данные после тестов
|
|
||||||
4. Тестируй реальные сценарии использования
|
|
||||||
5. Проверяй валидацию данных
|
|
||||||
6. Проверяй обработку ошибок
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание Vue компонент теста
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай тест для Vue компонента [COMPONENT_NAME] в frontend/[admin|spa]/tests/:
|
|
||||||
|
|
||||||
1. Используй Vitest
|
|
||||||
2. Тестируй рендеринг компонента
|
|
||||||
3. Тестируй props
|
|
||||||
4. Тестируй события (emits)
|
|
||||||
5. Тестируй пользовательские взаимодействия
|
|
||||||
6. Используй моки для API вызовов
|
|
||||||
7. Следуй структуре существующих тестов
|
|
||||||
```
|
|
||||||
|
|
||||||
## Покрытие тестами
|
|
||||||
|
|
||||||
```
|
|
||||||
Проанализируй покрытие тестами для [FILE_PATH]:
|
|
||||||
1. Определи какие методы не покрыты тестами
|
|
||||||
2. Создай тесты для критичных методов
|
|
||||||
3. Убедись что тестируются граничные случаи
|
|
||||||
4. Добавь тесты для обработки ошибок
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
# Архитектурные правила
|
|
||||||
|
|
||||||
## OpenCart Framework Architecture
|
|
||||||
|
|
||||||
### MVC-L Pattern
|
|
||||||
|
|
||||||
Проект использует модифицированный паттерн MVC-L (Model-View-Controller-Language):
|
|
||||||
|
|
||||||
- **Model**: Классы в `src/Models/` - работа с данными, доступ к БД
|
|
||||||
- **View**: Vue компоненты на фронтенде, JSON ответы на бэкенде
|
|
||||||
- **Controller**: Handlers в `src/Handlers/` и `bastion/Handlers/`
|
|
||||||
- **Language**: Переводчик в `framework/Translator/`
|
|
||||||
|
|
||||||
### Dependency Injection
|
|
||||||
|
|
||||||
Все зависимости внедряются через Container:
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Правильно
|
|
||||||
public function __construct(
|
|
||||||
private Builder $builder,
|
|
||||||
private TelegramCustomer $telegramCustomerModel
|
|
||||||
) {}
|
|
||||||
|
|
||||||
// ❌ Неправильно
|
|
||||||
public function __construct() {
|
|
||||||
$this->builder = new Builder(...);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Service Providers
|
|
||||||
|
|
||||||
Регистрация сервисов через Service Providers:
|
|
||||||
|
|
||||||
```php
|
|
||||||
class MyServiceProvider extends ServiceProvider
|
|
||||||
{
|
|
||||||
public function register(): void
|
|
||||||
{
|
|
||||||
$this->app->singleton(MyService::class, function ($app) {
|
|
||||||
return new MyService($app->get(Dependency::class));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Routes
|
|
||||||
|
|
||||||
Маршруты определяются в `routes.php`:
|
|
||||||
|
|
||||||
```php
|
|
||||||
return [
|
|
||||||
'actionName' => [HandlerClass::class, 'methodName'],
|
|
||||||
];
|
|
||||||
```
|
|
||||||
|
|
||||||
### Handlers (Controllers)
|
|
||||||
|
|
||||||
Handlers обрабатывают HTTP запросы:
|
|
||||||
|
|
||||||
```php
|
|
||||||
class MyHandler
|
|
||||||
{
|
|
||||||
public function handle(Request $request): JsonResponse
|
|
||||||
{
|
|
||||||
// Валидация
|
|
||||||
// Бизнес-логика через Services
|
|
||||||
// Возврат JsonResponse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Models
|
|
||||||
|
|
||||||
Models работают с данными:
|
|
||||||
|
|
||||||
```php
|
|
||||||
class MyModel
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private ConnectionInterface $database,
|
|
||||||
private Builder $builder
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function findById(int $id): ?array
|
|
||||||
{
|
|
||||||
return $this->builder->newQuery()
|
|
||||||
->from($this->tableName)
|
|
||||||
->where('id', '=', $id)
|
|
||||||
->firstOrNull();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Services
|
|
||||||
|
|
||||||
Services содержат бизнес-логику:
|
|
||||||
|
|
||||||
```php
|
|
||||||
class MyService
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private MyModel $model
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function doSomething(array $data): array
|
|
||||||
{
|
|
||||||
// Бизнес-логика
|
|
||||||
return $this->model->create($data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Migrations
|
|
||||||
|
|
||||||
Миграции в `database/migrations/`:
|
|
||||||
|
|
||||||
```php
|
|
||||||
return new class extends Migration {
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$this->database->statement('CREATE TABLE ...');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Query Builder
|
|
||||||
|
|
||||||
Всегда используй Query Builder вместо прямых SQL:
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Правильно
|
|
||||||
$query = $this->builder->newQuery()
|
|
||||||
->select(['id', 'name'])
|
|
||||||
->from('table_name')
|
|
||||||
->where('status', '=', 'active')
|
|
||||||
->get();
|
|
||||||
|
|
||||||
// ❌ Неправильно
|
|
||||||
$result = $this->database->query("SELECT * FROM table_name WHERE status = 'active'");
|
|
||||||
```
|
|
||||||
|
|
||||||
### Frontend Architecture
|
|
||||||
|
|
||||||
#### Admin Panel (Vue 3)
|
|
||||||
|
|
||||||
- Composition API
|
|
||||||
- Pinia для state management
|
|
||||||
- PrimeVue для UI компонентов
|
|
||||||
- Axios для HTTP запросов
|
|
||||||
- Vue Router для навигации
|
|
||||||
|
|
||||||
#### SPA (Telegram Mini App)
|
|
||||||
|
|
||||||
- Composition API
|
|
||||||
- Pinia stores
|
|
||||||
- Tailwind CSS для стилей
|
|
||||||
- Telegram WebApp API
|
|
||||||
- Vue Router
|
|
||||||
|
|
||||||
### Naming Conventions
|
|
||||||
|
|
||||||
- **Classes**: PascalCase (`TelegramCustomerService`)
|
|
||||||
- **Methods**: camelCase (`getCustomers`)
|
|
||||||
- **Variables**: camelCase (`$customerData`)
|
|
||||||
- **Constants**: UPPER_SNAKE_CASE (`MAX_RETRIES`)
|
|
||||||
- **Files**: PascalCase для классов, kebab-case для остального
|
|
||||||
- **Tables**: snake_case с префиксом `telecart_`
|
|
||||||
|
|
||||||
### Error Handling
|
|
||||||
|
|
||||||
Всегда обрабатывай ошибки:
|
|
||||||
|
|
||||||
```php
|
|
||||||
try {
|
|
||||||
$result = $this->service->doSomething();
|
|
||||||
} catch (SpecificException $e) {
|
|
||||||
$this->logger->error('Error message', ['exception' => $e]);
|
|
||||||
throw new UserFriendlyException('User message');
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
Используй конфигурационные файлы в `configs/`:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$config = $this->app->getConfigValue('app.setting_name');
|
|
||||||
```
|
|
||||||
|
|
||||||
### Caching
|
|
||||||
|
|
||||||
Используй Cache Service для кэширования:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$cache = $this->app->get(CacheInterface::class);
|
|
||||||
$value = $cache->get('key', function() {
|
|
||||||
return expensiveOperation();
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
# FormBuilder System Context
|
|
||||||
|
|
||||||
## Architectural Overview
|
|
||||||
The FormBuilder ecosystem is a strictly typed Vue 3 application module designed to generate standard FormKit Schema JSON. It eschews internal DTOs in favor of direct schema manipulation.
|
|
||||||
|
|
||||||
### Core Components
|
|
||||||
1. **FormBuilderView (`views/FormBuilderView.vue`)**:
|
|
||||||
* **Role**: Smart container / Data fetcher.
|
|
||||||
* **Responsibility**: Fetches form data from API (`GET /api/admin/forms/{alias}`), handles loading states, and passes data to `FormBuilder`.
|
|
||||||
* **Contract**: Expects API response `{ data: { schema: Array, is_custom: Boolean, ... } }`.
|
|
||||||
|
|
||||||
2. **FormBuilder (`components/FormBuilder/FormBuilder.vue`)**:
|
|
||||||
* **Role**: Main Orchestrator / State Owner.
|
|
||||||
* **Responsibility**: Manages `v-model` (schema), mode switching (Visual/Code/Preview), and provides state to children.
|
|
||||||
* **State Management**: Uses `defineModel` for `formFields` (schema) and `isCustom` (mode flag). Uses `provide('formFields')` and `provide('selectedFieldId')` for deep dependency injection.
|
|
||||||
* **Modes**:
|
|
||||||
* **Visual**: Drag-and-drop interface using `vuedraggable`.
|
|
||||||
* **Code**: Direct JSON editing of the FormKit schema. Sets `isCustom = true`.
|
|
||||||
* **Preview**: Renders the current schema using `FormKit`.
|
|
||||||
|
|
||||||
3. **FormCanvas (`components/FormBuilder/FormCanvas.vue`)**:
|
|
||||||
* **Role**: Visual Editor Surface.
|
|
||||||
* **Responsibility**: Renders the draggable list of fields.
|
|
||||||
* **Implementation**: Uses `vuedraggable` bound to `formFields`.
|
|
||||||
* **UX**: Implements "Ghost" and "Drag" classes for visual feedback. Handles selection logic.
|
|
||||||
|
|
||||||
4. **FieldsPanel (`components/FormBuilder/FieldsPanel.vue`)**:
|
|
||||||
* **Role**: Component Palette.
|
|
||||||
* **Responsibility**: Source of truth for available field types.
|
|
||||||
* **Implementation**: Uses `vuedraggable` with `pull: 'clone', put: false` to spawn new fields.
|
|
||||||
|
|
||||||
5. **FieldSettings (`components/FormBuilder/FieldSettings.vue`)**:
|
|
||||||
* **Role**: Property Editor.
|
|
||||||
* **Responsibility**: Edits properties of the `selectedFieldId`.
|
|
||||||
* **Constraint**: Must use PrimeVue components for all inputs.
|
|
||||||
|
|
||||||
## Data Flow & Invariants
|
|
||||||
1. **Schema Authority**: The FormKit Schema JSON is the single source of truth. There is no "internal model" separate from the schema.
|
|
||||||
2. **Reactivity**:
|
|
||||||
* `formFields` is an Array of Objects.
|
|
||||||
* Mutations must preserve reactivity. When using `v-model` or `provide/inject`, ensure array methods (splice, push, filter) are used correctly or replace the entire array reference if needed to trigger watchers.
|
|
||||||
* **Immutability**: `useFormFields` composable uses immutable patterns (returning new array references) to ensure `defineModel` in parent detects changes.
|
|
||||||
3. **Mode Logic**:
|
|
||||||
* Switching to **Code** mode sets `isCustom = true`.
|
|
||||||
* Switching to **Visual** mode sets `isCustom = false`.
|
|
||||||
* **Safety**: Switching modes triggers JSON validation. Invalid JSON prevents mode switch.
|
|
||||||
4. **Drag and Drop**:
|
|
||||||
* Powered by `vuedraggable` (Sortable.js).
|
|
||||||
* **Clone Logic**: `FieldsPanel` clones from `availableFields`. `FormCanvas` receives the clone.
|
|
||||||
* **ID Generation**: Unique IDs are generated upon cloning/addition to ensure key stability.
|
|
||||||
|
|
||||||
## Naming & Conventions
|
|
||||||
* **Tailwind**: Use `tw:` prefix for all utility classes (e.g., `tw:flex`, `tw:p-4`).
|
|
||||||
* **Components**: PrimeVue components are the standard UI kit (Button, Panel, InputText, etc.).
|
|
||||||
* **Icons**: FontAwesome (`fa fa-*`).
|
|
||||||
* **Files**: PascalCase for components (`FormBuilder.vue`), camelCase for logic (`useFormFields.js`).
|
|
||||||
|
|
||||||
## Integration Rules
|
|
||||||
* **Backend**: The backend stores the JSON blob directly. `FormBuilder` does not transform data before save; it emits the raw schema.
|
|
||||||
* **API**: `useFormsStore` handles API communication.
|
|
||||||
|
|
||||||
## Pitfalls & Warnings
|
|
||||||
* **vuedraggable vs @formkit/drag-and-drop**: We strictly use `vuedraggable`. Do not re-introduce `@formkit/drag-and-drop`.
|
|
||||||
* **Watchers**: Avoid `watch` where `computed` or event handlers suffice, to prevent infinite loops in bidirectional data flow.
|
|
||||||
* **Tailwind Config**: Do not use `@apply` with `tw:` prefixed classes in `<style>` blocks; standard CSS properties should be used if custom classes are needed.
|
|
||||||
|
|
||||||
## Future Modifications
|
|
||||||
* **Adding Fields**: Update `constants/availableFields.js` and ensure `utils/fieldHelpers.js` supports the new type.
|
|
||||||
* **Validation**: FormKit validation rules string (e.g., "required|email") is edited as a raw string in `FieldSettings`. Complex validation builders would require a new UI component.
|
|
||||||
|
|
||||||
@@ -1,332 +0,0 @@
|
|||||||
# JavaScript/TypeScript Code Style Rules
|
|
||||||
|
|
||||||
## JavaScript Version
|
|
||||||
|
|
||||||
- ES2020+ features
|
|
||||||
- Modern async/await
|
|
||||||
- Optional chaining (`?.`)
|
|
||||||
- Nullish coalescing (`??`)
|
|
||||||
- Template literals
|
|
||||||
|
|
||||||
## Code Style
|
|
||||||
|
|
||||||
### Variable Declarations
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Используй const по умолчанию
|
|
||||||
const customers = [];
|
|
||||||
const totalRecords = 0;
|
|
||||||
|
|
||||||
// ✅ let только когда нужно переназначение
|
|
||||||
let currentPage = 1;
|
|
||||||
currentPage = 2;
|
|
||||||
|
|
||||||
// ❌ Не используй var
|
|
||||||
var oldVariable = 'bad';
|
|
||||||
```
|
|
||||||
|
|
||||||
### Arrow Functions
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Предпочтительно для коротких функций
|
|
||||||
const filtered = items.filter(item => item.isActive);
|
|
||||||
|
|
||||||
// ✅ Для методов объектов
|
|
||||||
const api = {
|
|
||||||
get: async (url) => {
|
|
||||||
return await fetch(url);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ✅ Для сложной логики - обычные функции
|
|
||||||
function complexCalculation(data) {
|
|
||||||
// много строк кода
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Template Literals
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Предпочтительно
|
|
||||||
const message = `User ${userId} not found`;
|
|
||||||
const url = `${baseUrl}/api/${endpoint}`;
|
|
||||||
|
|
||||||
// ❌ Не используй конкатенацию
|
|
||||||
const message = 'User ' + userId + ' not found';
|
|
||||||
```
|
|
||||||
|
|
||||||
### Optional Chaining
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Используй optional chaining
|
|
||||||
const name = user?.profile?.name;
|
|
||||||
const count = data?.items?.length ?? 0;
|
|
||||||
|
|
||||||
// ❌ Избегай длинных проверок
|
|
||||||
const name = user && user.profile && user.profile.name;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Nullish Coalescing
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Используй ?? для значений по умолчанию
|
|
||||||
const page = params.page ?? 1;
|
|
||||||
const name = user.name ?? 'Unknown';
|
|
||||||
|
|
||||||
// ❌ Не используй || для чисел/булевых
|
|
||||||
const page = params.page || 1; // 0 будет заменено на 1
|
|
||||||
```
|
|
||||||
|
|
||||||
### Destructuring
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Используй деструктуризацию
|
|
||||||
const { data, totalRecords } = response.data;
|
|
||||||
const [first, second] = items;
|
|
||||||
|
|
||||||
// ✅ В параметрах функций
|
|
||||||
function processUser({ id, name, email }) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ С значениями по умолчанию
|
|
||||||
const { page = 1, limit = 20 } = params;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Async/Await
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Предпочтительно
|
|
||||||
async function loadCustomers() {
|
|
||||||
try {
|
|
||||||
const response = await apiGet('getCustomers', params);
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ❌ Избегай .then() цепочек
|
|
||||||
function loadCustomers() {
|
|
||||||
return apiGet('getCustomers', params)
|
|
||||||
.then(response => response.data)
|
|
||||||
.catch(error => console.error(error));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Vue.js 3 Composition API
|
|
||||||
|
|
||||||
### Script Setup
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Используй <script setup>
|
|
||||||
import { ref, computed, onMounted } from 'vue';
|
|
||||||
import { apiGet } from '@/utils/http.js';
|
|
||||||
|
|
||||||
const customers = ref([]);
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
const totalRecords = computed(() => customers.value.length);
|
|
||||||
|
|
||||||
async function loadCustomers() {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const result = await apiGet('getCustomers');
|
|
||||||
customers.value = result.data || [];
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadCustomers();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Reactive State
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Используй ref для примитивов
|
|
||||||
const count = ref(0);
|
|
||||||
const name = ref('');
|
|
||||||
|
|
||||||
// ✅ Используй reactive для объектов
|
|
||||||
import { reactive } from 'vue';
|
|
||||||
const state = reactive({
|
|
||||||
customers: [],
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
|
|
||||||
// ✅ Или ref для объектов (предпочтительно)
|
|
||||||
const state = ref({
|
|
||||||
customers: [],
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Computed Properties
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Используй computed для производных значений
|
|
||||||
const filteredCustomers = computed(() => {
|
|
||||||
return customers.value.filter(c => c.isActive);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ❌ Не используй методы для вычислений
|
|
||||||
function filteredCustomers() {
|
|
||||||
return customers.value.filter(c => c.isActive);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Props
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Определяй props с типами
|
|
||||||
const props = defineProps({
|
|
||||||
customerId: {
|
|
||||||
type: Number,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
showDetails: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Emits
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Определяй emits
|
|
||||||
const emit = defineEmits(['update', 'delete']);
|
|
||||||
|
|
||||||
function handleUpdate() {
|
|
||||||
emit('update', data);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Pinia Stores
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Используй setup stores
|
|
||||||
import { defineStore } from 'pinia';
|
|
||||||
import { ref, computed } from 'vue';
|
|
||||||
|
|
||||||
export const useCustomersStore = defineStore('customers', () => {
|
|
||||||
const customers = ref([]);
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
const totalRecords = computed(() => customers.value.length);
|
|
||||||
|
|
||||||
async function loadCustomers() {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const result = await apiGet('getCustomers');
|
|
||||||
customers.value = result.data || [];
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
customers,
|
|
||||||
loading,
|
|
||||||
totalRecords,
|
|
||||||
loadCustomers
|
|
||||||
};
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Всегда обрабатывай ошибки
|
|
||||||
async function loadData() {
|
|
||||||
try {
|
|
||||||
const result = await apiGet('endpoint');
|
|
||||||
if (result.success) {
|
|
||||||
return result.data;
|
|
||||||
} else {
|
|
||||||
throw new Error(result.error);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load data:', error);
|
|
||||||
toast.error('Не удалось загрузить данные');
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Naming Conventions
|
|
||||||
|
|
||||||
### Variables and Functions
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ camelCase
|
|
||||||
const customerData = {};
|
|
||||||
const totalRecords = 0;
|
|
||||||
function loadCustomers() {}
|
|
||||||
|
|
||||||
// ✅ Константы UPPER_SNAKE_CASE
|
|
||||||
const MAX_RETRIES = 3;
|
|
||||||
const API_BASE_URL = '/api';
|
|
||||||
```
|
|
||||||
|
|
||||||
### Components
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<!-- ✅ PascalCase для компонентов -->
|
|
||||||
<CustomerCard />
|
|
||||||
<ProductsList />
|
|
||||||
```
|
|
||||||
|
|
||||||
### Files
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ kebab-case для файлов
|
|
||||||
// customers-view.vue
|
|
||||||
// http-utils.js
|
|
||||||
// customer-service.js
|
|
||||||
```
|
|
||||||
|
|
||||||
## Imports
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Группируй импорты
|
|
||||||
// 1. Vue core
|
|
||||||
import { ref, computed, onMounted } from 'vue';
|
|
||||||
|
|
||||||
// 2. Third-party
|
|
||||||
import { apiGet } from '@/utils/http.js';
|
|
||||||
import { useToast } from 'primevue';
|
|
||||||
|
|
||||||
// 3. Local components
|
|
||||||
import CustomerCard from '@/components/CustomerCard.vue';
|
|
||||||
|
|
||||||
// 4. Types (если TypeScript)
|
|
||||||
import type { Customer } from '@/types';
|
|
||||||
```
|
|
||||||
|
|
||||||
## TypeScript (где используется)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ✅ Используй типы
|
|
||||||
interface Customer {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
email?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCustomer(id: number): Promise<Customer> {
|
|
||||||
return apiGet(`customers/${id}`);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
# PHP Code Style Rules
|
|
||||||
|
|
||||||
## PHP Version
|
|
||||||
|
|
||||||
Проект поддерживает PHP 7.4+
|
|
||||||
|
|
||||||
## PSR Standards
|
|
||||||
|
|
||||||
- **PSR-1**: Basic Coding Standard
|
|
||||||
- **PSR-4**: Autoloading Standard
|
|
||||||
- **PSR-12**: Extended Coding Style
|
|
||||||
|
|
||||||
## Code Style
|
|
||||||
|
|
||||||
### Type Declarations
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Правильно - строгая типизация
|
|
||||||
public function getCustomers(Request $request): JsonResponse
|
|
||||||
{
|
|
||||||
$id = (int) $request->get('id');
|
|
||||||
return new JsonResponse(['data' => $customers]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ❌ Неправильно - без типов
|
|
||||||
public function getCustomers($request)
|
|
||||||
{
|
|
||||||
return ['data' => $customers];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Nullable Types
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Правильно
|
|
||||||
public function findById(?int $id): ?array
|
|
||||||
{
|
|
||||||
if ($id === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return $this->query->where('id', '=', $id)->firstOrNull();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Strict Types
|
|
||||||
|
|
||||||
Всегда используй `declare(strict_types=1);`:
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Services;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Array Syntax
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Предпочтительно - короткий синтаксис
|
|
||||||
$array = ['key' => 'value'];
|
|
||||||
|
|
||||||
// ❌ Не использовать
|
|
||||||
$array = array('key' => 'value');
|
|
||||||
```
|
|
||||||
|
|
||||||
### String Interpolation
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Предпочтительно
|
|
||||||
$message = "User {$userId} not found";
|
|
||||||
|
|
||||||
// ✅ Альтернатива
|
|
||||||
$message = sprintf('User %d not found', $userId);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Arrow Functions (PHP 7.4+)
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Для простых операций
|
|
||||||
$filtered = array_filter($items, fn($item) => $item->isActive());
|
|
||||||
|
|
||||||
// ❌ Для сложной логики - используй обычные функции
|
|
||||||
```
|
|
||||||
|
|
||||||
### Nullsafe Operator (PHP 8.0+)
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Для PHP 7.4
|
|
||||||
$name = $user && $user->profile ? $user->profile->name : null;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Naming Conventions
|
|
||||||
|
|
||||||
### Classes
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ PascalCase
|
|
||||||
class TelegramCustomerService {}
|
|
||||||
class UserRepository {}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Methods
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ camelCase
|
|
||||||
public function getCustomers(): array {}
|
|
||||||
public function saveOrUpdate(array $data): array {}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Variables
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ camelCase
|
|
||||||
$customerData = [];
|
|
||||||
$totalRecords = 0;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Constants
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ UPPER_SNAKE_CASE
|
|
||||||
private const MAX_RETRIES = 3;
|
|
||||||
public const DEFAULT_PAGE_SIZE = 20;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Private Properties
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ camelCase с модификатором доступа
|
|
||||||
private string $tableName;
|
|
||||||
private Builder $builder;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
### PHPDoc
|
|
||||||
|
|
||||||
```php
|
|
||||||
/**
|
|
||||||
* @throws ValidationException Если параметры невалидны
|
|
||||||
*/
|
|
||||||
public function getCustomers(Request $request): JsonResponse
|
|
||||||
{
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Inline Comments
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Полезные комментарии
|
|
||||||
// Применяем фильтры для подсчета общего количества записей
|
|
||||||
$countQuery = $this->buildCountQuery($filters);
|
|
||||||
|
|
||||||
// ❌ Очевидные комментарии
|
|
||||||
// Получаем данные
|
|
||||||
$data = $this->getData();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
### Exceptions
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Специфичные исключения
|
|
||||||
if (!$userId) {
|
|
||||||
throw new InvalidArgumentException('User ID is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ Логирование
|
|
||||||
try {
|
|
||||||
$result = $this->service->process();
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$this->logger->error('Processing failed', [
|
|
||||||
'exception' => $e,
|
|
||||||
'context' => $context,
|
|
||||||
]);
|
|
||||||
throw new ProcessingException('Failed to process', 0, $e);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Query Builder Usage
|
|
||||||
|
|
||||||
### Always Use Query Builder
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Правильно
|
|
||||||
$customers = $this->builder->newQuery()
|
|
||||||
->select(['id', 'name', 'email'])
|
|
||||||
->from('telecart_customers')
|
|
||||||
->where('status', '=', 'active')
|
|
||||||
->orderBy('created_at', 'DESC')
|
|
||||||
->get();
|
|
||||||
|
|
||||||
// В крайних случаях можно использовать прямые SQL
|
|
||||||
$result = $this->database->query("SELECT * FROM telecart_customers");
|
|
||||||
```
|
|
||||||
|
|
||||||
### Parameter Binding
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Query Builder автоматически биндит параметры
|
|
||||||
$query->where('name', 'LIKE', "%{$search}%");
|
|
||||||
|
|
||||||
// ❌ Никогда не конкатенируй значения в SQL, избегай SQL Injection.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Array Access
|
|
||||||
|
|
||||||
### Safe Array Access
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Используй Arr::get()
|
|
||||||
use Openguru\OpenCartFramework\Support\Arr;
|
|
||||||
|
|
||||||
$value = Arr::get($data, 'key', 'default');
|
|
||||||
|
|
||||||
// ❌ Небезопасно
|
|
||||||
$value = $data['key']; // может вызвать ошибку
|
|
||||||
```
|
|
||||||
|
|
||||||
## Return Types
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Всегда указывай return type
|
|
||||||
public function getData(): array {}
|
|
||||||
public function findById(int $id): ?array {}
|
|
||||||
public function process(): void {}
|
|
||||||
|
|
||||||
// ❌ Без типа
|
|
||||||
public function getData() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Visibility Modifiers
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Всегда указывай модификатор доступа
|
|
||||||
private string $tableName;
|
|
||||||
protected Builder $builder;
|
|
||||||
public function getData(): array {}
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,370 +0,0 @@
|
|||||||
# Vue.js 3 Rules
|
|
||||||
|
|
||||||
## Component Structure
|
|
||||||
|
|
||||||
### Template
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<!-- ✅ Логическая структура -->
|
|
||||||
<div class="container">
|
|
||||||
<header>
|
|
||||||
<h2>{{ title }}</h2>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<DataTable :value="items" />
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<Button @click="handleSave">Save</Button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Script Setup
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Всегда используй <script setup>
|
|
||||||
import { ref, computed, onMounted } from 'vue';
|
|
||||||
import DataTable from 'primevue/datatable';
|
|
||||||
import { apiGet } from '@/utils/http.js';
|
|
||||||
|
|
||||||
// Props
|
|
||||||
const props = defineProps({
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Emits
|
|
||||||
const emit = defineEmits(['update', 'delete']);
|
|
||||||
|
|
||||||
// State
|
|
||||||
const items = ref([]);
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
// Computed
|
|
||||||
const totalItems = computed(() => items.value.length);
|
|
||||||
|
|
||||||
// Methods
|
|
||||||
async function loadItems() {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const result = await apiGet('getItems');
|
|
||||||
items.value = result.data || [];
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lifecycle
|
|
||||||
onMounted(() => {
|
|
||||||
loadItems();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Styles
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<style scoped>
|
|
||||||
/* ✅ Используй scoped стили */
|
|
||||||
.container {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ✅ Используй :deep() для доступа к дочерним компонентам */
|
|
||||||
:deep(.p-datatable) {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Component Naming
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<!-- ✅ PascalCase для компонентов -->
|
|
||||||
<CustomerCard />
|
|
||||||
<ProductsList />
|
|
||||||
<OrderDetails />
|
|
||||||
|
|
||||||
<!-- ✅ kebab-case в шаблоне тоже работает -->
|
|
||||||
<customer-card />
|
|
||||||
```
|
|
||||||
|
|
||||||
## Props
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Всегда определяй типы и валидацию
|
|
||||||
const props = defineProps({
|
|
||||||
customerId: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
validator: (value) => value > 0
|
|
||||||
},
|
|
||||||
showDetails: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
items: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Emits
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Определяй emits с типами
|
|
||||||
const emit = defineEmits<{
|
|
||||||
update: [id: number, data: object];
|
|
||||||
delete: [id: number];
|
|
||||||
cancel: [];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
// ✅ Или с валидацией
|
|
||||||
const emit = defineEmits({
|
|
||||||
update: (id: number, data: object) => {
|
|
||||||
if (id > 0 && typeof data === 'object') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
console.warn('Invalid emit arguments');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Reactive State
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ ref для примитивов
|
|
||||||
const count = ref(0);
|
|
||||||
const name = ref('');
|
|
||||||
|
|
||||||
// ✅ ref для объектов (предпочтительно)
|
|
||||||
const customer = ref({
|
|
||||||
id: null,
|
|
||||||
name: '',
|
|
||||||
email: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
// ✅ reactive только если нужно
|
|
||||||
import { reactive } from 'vue';
|
|
||||||
const state = reactive({
|
|
||||||
items: [],
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Computed Properties
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Используй computed для производных значений
|
|
||||||
const filteredItems = computed(() => {
|
|
||||||
return items.value.filter(item => item.isActive);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ✅ Computed с getter/setter
|
|
||||||
const fullName = computed({
|
|
||||||
get: () => `${firstName.value} ${lastName.value}`,
|
|
||||||
set: (value) => {
|
|
||||||
const parts = value.split(' ');
|
|
||||||
firstName.value = parts[0];
|
|
||||||
lastName.value = parts[1];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Event Handlers
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<!-- ✅ Используй kebab-case для событий -->
|
|
||||||
<Button @click="handleClick" />
|
|
||||||
<Input @input="handleInput" />
|
|
||||||
<Form @submit.prevent="handleSubmit" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
// ✅ Именуй обработчики с префиксом handle
|
|
||||||
function handleClick() {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleInput(event) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSubmit() {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Conditional Rendering
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<!-- ✅ Используй v-if для условного рендеринга -->
|
|
||||||
<div v-if="loading">
|
|
||||||
<LoadingSpinner />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ✅ v-show для частых переключений -->
|
|
||||||
<div v-show="hasItems">
|
|
||||||
<ItemsList :items="items" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ✅ v-else для альтернатив -->
|
|
||||||
<div v-else>
|
|
||||||
<EmptyState />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Lists
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<!-- ✅ Всегда используй :key -->
|
|
||||||
<div v-for="item in items" :key="item.id">
|
|
||||||
{{ item.name }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ✅ Для индексов -->
|
|
||||||
<div v-for="(item, index) in items" :key="`item-${index}`">
|
|
||||||
{{ item.name }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Form Handling
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<form @submit.prevent="handleSubmit">
|
|
||||||
<!-- ✅ Используй v-model -->
|
|
||||||
<InputText v-model="form.name" />
|
|
||||||
<Textarea v-model="form.description" />
|
|
||||||
|
|
||||||
<!-- ✅ Для кастомных компонентов -->
|
|
||||||
<CustomInput v-model="form.email" />
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const form = ref({
|
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
email: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleSubmit() {
|
|
||||||
// Валидация и отправка
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## PrimeVue Components
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<!-- ✅ Используй PrimeVue компоненты в админке -->
|
|
||||||
<DataTable
|
|
||||||
:value="customers"
|
|
||||||
:loading="loading"
|
|
||||||
paginator
|
|
||||||
:rows="20"
|
|
||||||
@page="onPage"
|
|
||||||
>
|
|
||||||
<Column field="name" header="Name" sortable />
|
|
||||||
</DataTable>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Styling
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<style scoped>
|
|
||||||
/* ✅ Используй scoped -->
|
|
||||||
.container {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ✅ :deep() для дочерних компонентов */
|
|
||||||
:deep(.p-datatable) {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ✅ :slotted() для слотов */
|
|
||||||
:slotted(.header) {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Composition Functions
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Выноси сложную логику в composables
|
|
||||||
import { useCustomers } from '@/composables/useCustomers.js';
|
|
||||||
|
|
||||||
const {
|
|
||||||
customers,
|
|
||||||
loading,
|
|
||||||
loadCustomers,
|
|
||||||
totalRecords
|
|
||||||
} = useCustomers();
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadCustomers();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
import { useToast } from 'primevue';
|
|
||||||
|
|
||||||
const toast = useToast();
|
|
||||||
|
|
||||||
async function loadData() {
|
|
||||||
try {
|
|
||||||
const result = await apiGet('endpoint');
|
|
||||||
if (result.success) {
|
|
||||||
data.value = result.data;
|
|
||||||
} else {
|
|
||||||
toast.add({
|
|
||||||
severity: 'error',
|
|
||||||
summary: 'Ошибка',
|
|
||||||
detail: result.error
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error:', error);
|
|
||||||
toast.add({
|
|
||||||
severity: 'error',
|
|
||||||
summary: 'Ошибка',
|
|
||||||
detail: 'Не удалось загрузить данные'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
136
CLAUDE.md
Normal file
136
CLAUDE.md
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
TeleCart — коммерческий модуль для OpenCart, превращающий магазин в Telegram Mini App. Это монорепозиторий из трёх
|
||||||
|
частей:
|
||||||
|
|
||||||
|
- **PHP-модуль OpenCart** — `module/oc_telegram_shop/upload/` (вся бизнес-логика и собственный фреймворк).
|
||||||
|
- **SPA** — `frontend/spa/` (Telegram Mini App, витрина для покупателей).
|
||||||
|
- **Admin** — `frontend/admin/` (панель управления настройками модуля).
|
||||||
|
|
||||||
|
`src/` в корне — это распакованная установка OpenCart (генерируется скриптами, **не редактировать**; код модуля
|
||||||
|
«линкуется» в неё через `make link`).
|
||||||
|
|
||||||
|
## Где живёт код модуля
|
||||||
|
|
||||||
|
Сердце проекта — `module/oc_telegram_shop/upload/oc_telegram_shop/` (далее «корень модуля»). Его структура:
|
||||||
|
|
||||||
|
| Каталог | Namespace | Назначение |
|
||||||
|
|------------------------|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `framework/` | `Openguru\OpenCartFramework\` | Собственный Laravel-подобный фреймворк: Container (DI), Router, QueryBuilder, Cache, Migrations, Telegram, Http, Translator |
|
||||||
|
| `src/` | `App\` | API витрины/SPA (catalog). Handlers, Services, Models, DTO, Filters, ServiceProviders |
|
||||||
|
| `bastion/` | `Bastion\` | API админ-панели. Handlers, Services, Tasks, ScheduledTasks |
|
||||||
|
| `console/` | `Console\` | CLI-команды (Symfony Console), точка входа `upload/cli.php` |
|
||||||
|
| `database/migrations/` | — | Миграции БД |
|
||||||
|
| `configs/` | — | `app.php`, `maintenance.php`, `schedule.php` |
|
||||||
|
| `tests/` | `Tests\` | `Unit/`, `Integration/`, `Telegram/` |
|
||||||
|
|
||||||
|
**Маршрутизация.** Запросы приходят через action-параметр и резолвятся по двум картам:
|
||||||
|
|
||||||
|
- `src/routes.php` — действия SPA (`products`, `getCart`, `checkout`, `webhook`, …)
|
||||||
|
- `bastion/routes.php` — действия админки (`getSettingsForm`, `getTelegramCustomers`, `getDashboardStats`, …)
|
||||||
|
|
||||||
|
Каждая запись — `'actionName' => [HandlerClass::class, 'method']`.
|
||||||
|
|
||||||
|
**Точки входа OpenCart** (тонкие обёртки, грузят autoload и поднимают `App\ApplicationFactory`):
|
||||||
|
|
||||||
|
- `module/.../upload/catalog/controller/extension/tgshop/handle.php` — SPA API
|
||||||
|
- `module/.../upload/admin/controller/extension/module/tgshop.php` — админка
|
||||||
|
|
||||||
|
В продакшене модуль собирается в `oc_telegram_shop.phar` и подключается из `system/library/oc_telegram_shop/`; в dev —
|
||||||
|
из исходников через `vendor/autoload.php`.
|
||||||
|
|
||||||
|
## Архитектурные правила (бэкенд)
|
||||||
|
|
||||||
|
Паттерн — MVC-L поверх собственного фреймворка. Соблюдай строго:
|
||||||
|
|
||||||
|
- **Совместимость с PHP 7.4.** Минимальные требования OpenCart и модуля — PHP 7.4 (`composer.json`: `^7.4`). Не используй синтаксис и функции PHP 8.0+ (named arguments, `match`, constructor property promotion, `enum`, `readonly`, nullsafe `?->`, union types и т.п.).
|
||||||
|
- **Dependency Injection через конструктор.** Никогда не создавай зависимости через `new` внутри классов — внедряй их.
|
||||||
|
Регистрация сервисов идёт в Service Providers (`src/ServiceProviders/`) через `$this->app->singleton(...)`.
|
||||||
|
- **Query Builder вместо сырого SQL.** Используй `$this->builder->newQuery()->from(...)->where(...)->get()`. Прямой
|
||||||
|
SQL — только в крайних случаях; никогда не конкатенируй значения в запрос (SQL injection).
|
||||||
|
- **Слои:** Handler (валидация + вызов сервиса + `JsonResponse`) → Service (бизнес-логика) → Model (доступ к данным). Не
|
||||||
|
смешивай.
|
||||||
|
- **Строгая типизация:** `declare(strict_types=1)`, типы аргументов и возвращаемых значений, nullable где нужно. PSR-12.
|
||||||
|
- **Безопасный доступ к массивам:** `Arr::get($data, 'key', $default)` вместо `$data['key']`.
|
||||||
|
- **Таблицы:** префикс OpenCart (`oc_`) + собственные таблицы модуля.
|
||||||
|
|
||||||
|
## Архитектура фронтенда
|
||||||
|
|
||||||
|
Оба приложения — Vue 3, **только `<script setup>` + Composition API**, Pinia (setup-stores), Vue Router.
|
||||||
|
|
||||||
|
- **SPA** (`frontend/spa/`): Tailwind CSS, FormKit, Swiper, `ofetch`. Сборка →
|
||||||
|
`module/.../upload/image/catalog/tgshopspa/`. Тесты — Vitest.
|
||||||
|
- **Admin** (`frontend/admin/`): PrimeVue (основной UI-кит), Tailwind/DaisyUI, axios, CodeMirror, `vuedraggable`.
|
||||||
|
Сборка → `module/.../upload/admin/view/javascript/telecart/`.
|
||||||
|
|
||||||
|
Конвенции: компоненты — PascalCase, файлы логики — camelCase; обработчики событий с префиксом `handle`; `computed` для
|
||||||
|
производных значений; `??` (не `||`) для дефолтов чисел/булевых; сложную логику выносить в composables. Ошибки API
|
||||||
|
ловить и показывать через toast (admin: `useToast` из PrimeVue).
|
||||||
|
|
||||||
|
**FormBuilder** (admin) — отдельная подсистема визуального конструктора форм. Единственный источник истины — FormKit
|
||||||
|
Schema JSON (без промежуточных DTO). Drag-and-drop строго на `vuedraggable` (не `@formkit/drag-and-drop`).
|
||||||
|
Tailwind-классы там идут с префиксом `tw:`.
|
||||||
|
|
||||||
|
## Команды
|
||||||
|
|
||||||
|
Все `make`-цели выполняются **внутри Docker-контейнера `web`** — нужен запущенный `docker compose`.
|
||||||
|
|
||||||
|
**Старт / окружение:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make setup # первичная установка: скачать OpenCart, установить, поднять, link
|
||||||
|
make start # поднять контейнеры
|
||||||
|
make dev # link + параллельно vite dev для SPA и Admin
|
||||||
|
make dev-spa # только SPA (vite)
|
||||||
|
make dev-admin # только Admin (vite)
|
||||||
|
make link # «слинковать» код модуля в установку OpenCart (после правок структуры)
|
||||||
|
make ssh # шелл в контейнер в каталоге модуля
|
||||||
|
```
|
||||||
|
|
||||||
|
**Тесты (PHP, PHPUnit в контейнере):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make test # все тесты (--testdox)
|
||||||
|
make test-unit # tests/Unit
|
||||||
|
make test-integration # tests/Integration
|
||||||
|
make test-telegram # tests/Telegram
|
||||||
|
make test-coverage # HTML-покрытие в coverage/
|
||||||
|
```
|
||||||
|
|
||||||
|
Запуск одного теста — через ssh в контейнер:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make ssh
|
||||||
|
./vendor/bin/phpunit --filter testMethodName tests/Unit/SomeTest.php
|
||||||
|
```
|
||||||
|
|
||||||
|
**Тесты фронтенда (SPA):** `cd frontend/spa && npm run test` (Vitest), `npm run test:run` для однократного прогона.
|
||||||
|
|
||||||
|
**Линтеры / статический анализ:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make lint # PHPStan (level 1) по src, bastion, framework, console
|
||||||
|
make phpcs # PHP_CodeSniffer PSR-12 (проверка)
|
||||||
|
make phpcbf # PHP_CodeSniffer PSR-12 (автофикс)
|
||||||
|
```
|
||||||
|
|
||||||
|
Admin-фронтенд: `cd frontend/admin && npm run lint` (oxlint + eslint), `npm run format` (prettier).
|
||||||
|
|
||||||
|
**Сборка / релиз:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make phar # собрать oc_telegram_shop.phar (через docker/build.dockerfile)
|
||||||
|
make ocmod # применить OCMOD-модификации
|
||||||
|
make cli ARGS="..." # запустить console-команду модуля
|
||||||
|
make changelog # сгенерировать CHANGELOG из conventional commits
|
||||||
|
make release # changelog + commit релиза
|
||||||
|
```
|
||||||
|
|
||||||
|
## Коммиты
|
||||||
|
|
||||||
|
Используются **Conventional Commits** (`feat:`, `fix:`, `chore:`, `build(deps):` …) — CHANGELOG и версии генерируются
|
||||||
|
автоматически. **Не делать `git commit`/`git push` без явного указания пользователя.**
|
||||||
Reference in New Issue
Block a user