|
@@ -16,6 +16,7 @@
|
|
|
- **Шаблонизатор:** Pug с Vue компонентами
|
|
- **Шаблонизатор:** Pug с Vue компонентами
|
|
|
- **Стилизация:** Stylus + CSS переменные + BEM методология
|
|
- **Стилизация:** Stylus + CSS переменные + BEM методология
|
|
|
- **Логика:** CoffeeScript + Vue.js 3 (runtime)
|
|
- **Логика:** CoffeeScript + Vue.js 3 (runtime)
|
|
|
|
|
+- **Важно** Vuejs в runtime режиме, не использовать template, шаблоны СТРОГО через "render: (new Function '_ctx', '_cache', renderFns['app/index.pug'])()" во всех компанентах
|
|
|
- **Маршрутизация:** Vue Router
|
|
- **Маршрутизация:** Vue Router
|
|
|
- **База данных:** PouchDB (клиент) + CouchDB (сервер)
|
|
- **База данных:** PouchDB (клиент) + CouchDB (сервер)
|
|
|
- **Анимации:** CSS transitions/transforms + Vue transitions
|
|
- **Анимации:** CSS transitions/transforms + Vue transitions
|
|
@@ -199,9 +200,12 @@ div(class="app" :class="{'theme-dark': theme === 'dark'}")
|
|
|
|
|
|
|
|
**app/index.coffee**
|
|
**app/index.coffee**
|
|
|
```coffee
|
|
```coffee
|
|
|
-# Глобальная инициализация debug в начале файла, сам debug уже глобально определён
|
|
|
|
|
|
|
+# Глобальная инициализация debug
|
|
|
globalThis.log = debug.log
|
|
globalThis.log = debug.log
|
|
|
|
|
|
|
|
|
|
+# Главный файл приложения
|
|
|
|
|
+log '🚀 Инициализация приложения Браер-Колор'
|
|
|
|
|
+
|
|
|
# Загрузка конфигурации
|
|
# Загрузка конфигурации
|
|
|
config = require 'app/config'
|
|
config = require 'app/config'
|
|
|
DataTypes = require 'app/types/data'
|
|
DataTypes = require 'app/types/data'
|
|
@@ -212,21 +216,38 @@ globalThis.renderFns = require 'pug.json'
|
|
|
globalThis.stylFns = require 'styl.json'
|
|
globalThis.stylFns = require 'styl.json'
|
|
|
|
|
|
|
|
|
|
|
|
|
-# Сервисы
|
|
|
|
|
-PouchDBService = require 'app/utils/pouch'
|
|
|
|
|
-DomainService = require 'app/services/DomainService'
|
|
|
|
|
|
|
+
|
|
|
|
|
+# Сервисы (пока заглушки)
|
|
|
|
|
+PouchDBService =
|
|
|
|
|
+ init: -> Promise.resolve()
|
|
|
|
|
+ getDocument: -> Promise.resolve(null)
|
|
|
|
|
+ saveToRemote: -> Promise.resolve()
|
|
|
|
|
+
|
|
|
|
|
+DomainService =
|
|
|
|
|
+ init: -> Promise.resolve()
|
|
|
|
|
+ loadDomainSettings: -> Promise.resolve(null)
|
|
|
|
|
+ getAvailableDomains: -> []
|
|
|
|
|
|
|
|
# Мета-теги
|
|
# Мета-теги
|
|
|
document.head.insertAdjacentHTML 'beforeend', '<meta charset="UTF-8">'
|
|
document.head.insertAdjacentHTML 'beforeend', '<meta charset="UTF-8">'
|
|
|
document.head.insertAdjacentHTML 'beforeend', '<meta name="viewport" content="width=device-width, initial-scale=1.0">'
|
|
document.head.insertAdjacentHTML 'beforeend', '<meta name="viewport" content="width=device-width, initial-scale=1.0">'
|
|
|
-document.head.insertAdjacentHTML 'beforeend', '<style type="text/css">' + stylFns['app/index.styl'] + '</style>'
|
|
|
|
|
-
|
|
|
|
|
-# Главное приложение Vue
|
|
|
|
|
|
|
+document.head.insertAdjacentHTML 'beforeend', '<title>Браер-Колор - Интернет-магазин лакокрасочной продукции</title>'
|
|
|
|
|
+
|
|
|
|
|
+# Добавление глобальных стилей
|
|
|
|
|
+if stylFns['app/index.styl']
|
|
|
|
|
+ styleElement = document.createElement('style')
|
|
|
|
|
+ styleElement.type = 'text/css'
|
|
|
|
|
+ styleElement.textContent = stylFns['app/index.styl']
|
|
|
|
|
+ document.head.appendChild(styleElement)
|
|
|
|
|
+else
|
|
|
|
|
+ log '⚠️ Глобальные стили не найдены'
|
|
|
|
|
+
|
|
|
|
|
+# Создание Vue приложения
|
|
|
app = Vue.createApp({
|
|
app = Vue.createApp({
|
|
|
data: ->
|
|
data: ->
|
|
|
{
|
|
{
|
|
|
- theme: localStorage.getItem('theme') or 'light'
|
|
|
|
|
- companyName: 'Браер-Колор'
|
|
|
|
|
|
|
+ theme: localStorage.getItem('theme') or config.defaultTheme
|
|
|
|
|
+ companyName: config.companyName
|
|
|
loading: false
|
|
loading: false
|
|
|
currentDomain: window.location.hostname
|
|
currentDomain: window.location.hostname
|
|
|
currentDomainSettings: null
|
|
currentDomainSettings: null
|
|
@@ -239,94 +260,405 @@ app = Vue.createApp({
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
computed:
|
|
computed:
|
|
|
- isAdmin: -> @user?.role == 'admin'
|
|
|
|
|
|
|
+ isAdmin: ->
|
|
|
|
|
+ @user?.role == 'admin'
|
|
|
|
|
+
|
|
|
domainConfig: ->
|
|
domainConfig: ->
|
|
|
- DomainService.getDomainConfig(@currentDomain)
|
|
|
|
|
|
|
+ DomainService.getDomainConfig?(@currentDomain) or {}
|
|
|
|
|
|
|
|
methods:
|
|
methods:
|
|
|
|
|
+ # Управление темой
|
|
|
toggleTheme: ->
|
|
toggleTheme: ->
|
|
|
@theme = if @theme == 'light' then 'dark' else 'light'
|
|
@theme = if @theme == 'light' then 'dark' else 'light'
|
|
|
localStorage.setItem 'theme', @theme
|
|
localStorage.setItem 'theme', @theme
|
|
|
document.documentElement.classList.toggle 'dark'
|
|
document.documentElement.classList.toggle 'dark'
|
|
|
@$emit EventTypes.THEME_CHANGED, @theme
|
|
@$emit EventTypes.THEME_CHANGED, @theme
|
|
|
|
|
+ log '🎨 Тема изменена:', @theme
|
|
|
|
|
|
|
|
|
|
+ # Управление языком
|
|
|
setLanguage: (lang) ->
|
|
setLanguage: (lang) ->
|
|
|
if @languages.includes lang
|
|
if @languages.includes lang
|
|
|
@currentLanguage = lang
|
|
@currentLanguage = lang
|
|
|
- localStorage.setItem 'language', @lang
|
|
|
|
|
|
|
+ localStorage.setItem 'language', @currentLanguage
|
|
|
@$emit EventTypes.LANGUAGE_CHANGED, lang
|
|
@$emit EventTypes.LANGUAGE_CHANGED, lang
|
|
|
|
|
+ log '🌐 Язык изменен:', lang
|
|
|
|
|
+ else
|
|
|
|
|
+ log '⚠️ Язык не поддерживается:', lang
|
|
|
|
|
+
|
|
|
|
|
+ # Смена домена
|
|
|
|
|
+ changeDomain: (domain) ->
|
|
|
|
|
+ log '🌐 Смена домена на:', domain
|
|
|
|
|
+ @currentDomain = domain
|
|
|
|
|
+ @loadDomainData()
|
|
|
|
|
|
|
|
|
|
+ # Переход в корзину
|
|
|
|
|
+ goToCart: ->
|
|
|
|
|
+ @$router.push '/cart'
|
|
|
|
|
+ log '🛒 Переход в корзину'
|
|
|
|
|
+
|
|
|
|
|
+ # Загрузка настроек домена
|
|
|
loadDomainData: ->
|
|
loadDomainData: ->
|
|
|
|
|
+ log '📡 Загрузка настроек домена:', @currentDomain
|
|
|
|
|
+
|
|
|
DomainService.loadDomainSettings(@currentDomain)
|
|
DomainService.loadDomainSettings(@currentDomain)
|
|
|
.then (settings) =>
|
|
.then (settings) =>
|
|
|
@currentDomainSettings = settings
|
|
@currentDomainSettings = settings
|
|
|
document.title = settings?.companyName or @companyName
|
|
document.title = settings?.companyName or @companyName
|
|
|
- log 'Настройки домена загружены', settings
|
|
|
|
|
|
|
+ log '✅ Настройки домена загружены', settings
|
|
|
.catch (error) =>
|
|
.catch (error) =>
|
|
|
- log 'Настройки домена не найдены, используются значения по умолчанию'
|
|
|
|
|
|
|
+ log '⚠️ Настройки домена не найдены, используются значения по умолчанию'
|
|
|
@currentDomainSettings = new DataTypes.DomainSettings()
|
|
@currentDomainSettings = new DataTypes.DomainSettings()
|
|
|
|
|
+ @currentDomainSettings.companyName = @companyName
|
|
|
|
|
|
|
|
|
|
+ # Управление корзиной
|
|
|
updateCart: (items) ->
|
|
updateCart: (items) ->
|
|
|
@cartItems = items
|
|
@cartItems = items
|
|
|
localStorage.setItem 'cart', JSON.stringify(items)
|
|
localStorage.setItem 'cart', JSON.stringify(items)
|
|
|
|
|
+ @$emit EventTypes.CART_UPDATE, items
|
|
|
|
|
+ log '🛒 Корзина обновлена:', items.length, 'товаров'
|
|
|
|
|
|
|
|
- showNotification: (message, type = 'success') ->
|
|
|
|
|
- notification = { id: Date.now(), message, type, visible: true }
|
|
|
|
|
|
|
+ # Уведомления
|
|
|
|
|
+ showNotification: (message, type = 'info') ->
|
|
|
|
|
+ notification = {
|
|
|
|
|
+ id: Date.now(),
|
|
|
|
|
+ message,
|
|
|
|
|
+ type,
|
|
|
|
|
+ visible: true,
|
|
|
|
|
+ timestamp: new Date()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
@notifications.push notification
|
|
@notifications.push notification
|
|
|
|
|
+ log '📢 Показано уведомление:', message
|
|
|
|
|
+
|
|
|
setTimeout (=>
|
|
setTimeout (=>
|
|
|
notification.visible = false
|
|
notification.visible = false
|
|
|
setTimeout (=>
|
|
setTimeout (=>
|
|
|
@notifications = @notifications.filter (n) -> n.id != notification.id
|
|
@notifications = @notifications.filter (n) -> n.id != notification.id
|
|
|
), 300
|
|
), 300
|
|
|
), 5000
|
|
), 5000
|
|
|
-
|
|
|
|
|
- mounted: -> # не используй async в определении методов и обработчиков событий
|
|
|
|
|
- # Инициализация темы
|
|
|
|
|
- if @theme == 'dark'
|
|
|
|
|
- document.documentElement.classList.add 'dark'
|
|
|
|
|
|
|
|
|
|
- # Инициализация сервисов
|
|
|
|
|
- try
|
|
|
|
|
- await PouchDBService.init()
|
|
|
|
|
- log 'PouchDB инициализирован'
|
|
|
|
|
|
|
+ # Закрытие уведомления
|
|
|
|
|
+ closeNotification: (id) ->
|
|
|
|
|
+ @notifications = @notifications.filter (notification) -> notification.id != id
|
|
|
|
|
+ log '📢 Уведомление закрыто:', id
|
|
|
|
|
+
|
|
|
|
|
+ # Загрузка пользователя
|
|
|
|
|
+ loadUserData: ->
|
|
|
|
|
+ userData = localStorage.getItem 'user'
|
|
|
|
|
+ if userData
|
|
|
|
|
+ try
|
|
|
|
|
+ @user = JSON.parse userData
|
|
|
|
|
+ log '👤 Пользователь загружен:', @user.username
|
|
|
|
|
+ catch error
|
|
|
|
|
+ log '❌ Ошибка загрузки пользователя:', error
|
|
|
|
|
+ @user = null
|
|
|
|
|
+ else
|
|
|
|
|
+ @user = null
|
|
|
|
|
+
|
|
|
|
|
+ # Загрузка корзины
|
|
|
|
|
+ loadCartData: ->
|
|
|
|
|
+ cartData = localStorage.getItem 'cart'
|
|
|
|
|
+ if cartData
|
|
|
|
|
+ try
|
|
|
|
|
+ @cartItems = JSON.parse cartData
|
|
|
|
|
+ log '🛒 Корзина загружена:', @cartItems.length, 'товаров'
|
|
|
|
|
+ catch error
|
|
|
|
|
+ log '❌ Ошибка загрузки корзины:', error
|
|
|
|
|
+ @cartItems = []
|
|
|
|
|
+ else
|
|
|
|
|
+ @cartItems = []
|
|
|
|
|
+
|
|
|
|
|
+ # Инициализация приложения
|
|
|
|
|
+ initializeApp: ->
|
|
|
|
|
+ log '🔧 Начало инициализации приложения'
|
|
|
|
|
+ @loading = true
|
|
|
|
|
|
|
|
- await DomainService.init()
|
|
|
|
|
- @availableDomains = DomainService.getAvailableDomains()
|
|
|
|
|
|
|
+ # Инициализация темы
|
|
|
|
|
+ if @theme == 'dark'
|
|
|
|
|
+ document.documentElement.classList.add 'dark'
|
|
|
|
|
+ log '🌙 Темная тема активирована'
|
|
|
|
|
+ else
|
|
|
|
|
+ log '☀️ Светлая тема активирована'
|
|
|
|
|
|
|
|
- await @loadDomainData()
|
|
|
|
|
- catch error
|
|
|
|
|
- log 'Ошибка инициализации:', error
|
|
|
|
|
-
|
|
|
|
|
- # Загрузка пользователя и корзины
|
|
|
|
|
- @loadUserData()
|
|
|
|
|
- @loadCartData()
|
|
|
|
|
|
|
+ # Последовательная инициализация сервисов
|
|
|
|
|
+ Promise.resolve()
|
|
|
|
|
+ .then =>
|
|
|
|
|
+ log '📦 Инициализация PouchDB...'
|
|
|
|
|
+ PouchDBService.init()
|
|
|
|
|
+ .then =>
|
|
|
|
|
+ log '🌐 Инициализация DomainService...'
|
|
|
|
|
+ DomainService.init()
|
|
|
|
|
+ .then =>
|
|
|
|
|
+ log '📡 Получение доступных доменов...'
|
|
|
|
|
+ @availableDomains = DomainService.getAvailableDomains()
|
|
|
|
|
+ .then =>
|
|
|
|
|
+ @loadDomainData()
|
|
|
|
|
+ .then =>
|
|
|
|
|
+ @loadUserData()
|
|
|
|
|
+ .then =>
|
|
|
|
|
+ @loadCartData()
|
|
|
|
|
+ .then =>
|
|
|
|
|
+ log '✅ Приложение успешно инициализировано'
|
|
|
|
|
+ @showNotification('Приложение готово к работе', 'success')
|
|
|
|
|
+ .catch (error) =>
|
|
|
|
|
+ log '❌ Ошибка инициализации приложения:', error
|
|
|
|
|
+ @showNotification('Ошибка загрузки приложения', 'error')
|
|
|
|
|
+ .finally =>
|
|
|
|
|
+ @loading = false
|
|
|
|
|
|
|
|
- loadUserData: ->
|
|
|
|
|
- userData = localStorage.getItem 'user'
|
|
|
|
|
- if userData
|
|
|
|
|
- try
|
|
|
|
|
- @user = JSON.parse userData
|
|
|
|
|
- catch
|
|
|
|
|
- @user = null
|
|
|
|
|
|
|
+ async mounted: ->
|
|
|
|
|
+ await @initializeApp()
|
|
|
|
|
|
|
|
- loadCartData: ->
|
|
|
|
|
- cartData = localStorage.getItem 'cart'
|
|
|
|
|
- if cartData
|
|
|
|
|
- try
|
|
|
|
|
- @cartItems = JSON.parse cartData
|
|
|
|
|
- catch
|
|
|
|
|
- @cartItems = []
|
|
|
|
|
-
|
|
|
|
|
- render: (new Function '_ctx', '_cache', renderFns['app/index.pug'])()
|
|
|
|
|
|
|
+ # Рендер функция из Pug
|
|
|
|
|
+ render: (new Function '_ctx', '_cache', globalThis.renderFns['app/index.pug'])()
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-# Глобальная обработка ошибок
|
|
|
|
|
|
|
+
|
|
|
|
|
+Router = require 'app/router/index.coffee'
|
|
|
|
|
+# Регистрация глобальных компонентов
|
|
|
|
|
+app.component('ui-button', require 'app/components/UI/Button/index.coffee')
|
|
|
|
|
+app.component('notification-container', require 'app/components/UI/Notification/index.coffee')
|
|
|
|
|
+app.component('app-loader', require 'app/components/UI/AppLoader/index.coffee')
|
|
|
|
|
+app.component('multilevel-menu', require 'app/components/Domain/MultilevelMenu/index.coffee')
|
|
|
|
|
+app.component('theme-toggle', require 'app/components/UI/ThemeToggle/index.coffee')
|
|
|
|
|
+app.component('language-toggle', require 'app/components/UI/LanguageToggle/index.coffee')
|
|
|
|
|
+app.component('cart-widget', require 'app/components/Domain/CartWidget/index.coffee')
|
|
|
|
|
+
|
|
|
|
|
+# Подключение роутера
|
|
|
|
|
+app.use Router
|
|
|
|
|
+
|
|
|
|
|
+# Глобальная обработка ошибок Vue
|
|
|
app.config.errorHandler = (err, vm, info) ->
|
|
app.config.errorHandler = (err, vm, info) ->
|
|
|
- log 'Vue ошибка:', err, info
|
|
|
|
|
|
|
+ log '💥 Vue ошибка:', err, info
|
|
|
|
|
+ console.error('Vue ошибка:', err, info)
|
|
|
|
|
+
|
|
|
|
|
+# Глобальная обработка предупреждений
|
|
|
|
|
+app.config.warnHandler = (msg, vm, trace) ->
|
|
|
|
|
+ log '⚠️ Vue предупреждение:', msg, trace
|
|
|
|
|
+
|
|
|
|
|
+# Монтирование приложения
|
|
|
|
|
+try
|
|
|
|
|
+ app.mount('body')
|
|
|
|
|
+ log '✅ Приложение успешно смонтировано'
|
|
|
|
|
+catch error
|
|
|
|
|
+ log '❌ Ошибка монтирования приложения:', error
|
|
|
|
|
+ console.error('Ошибка монтирования:', error)
|
|
|
|
|
|
|
|
-app.mount('body')
|
|
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
|
|
+Пример файла компонента, или страницы.Пиши стилевые файлы к элементам
|
|
|
|
|
+**Важно** не нужно делать Vue = require 'vue' или require 'VueRouter' они уже подключены глобально доступны
|
|
|
|
|
+
|
|
|
|
|
+** app/pages/Home/index.coffee**
|
|
|
|
|
+```
|
|
|
|
|
+# app/pages/Home/index.coffee
|
|
|
|
|
+
|
|
|
|
|
+# Добавление стилей страницы
|
|
|
|
|
+if globalThis.stylFns and globalThis.stylFns['app/pages/Home/index.styl']
|
|
|
|
|
+ styleElement = document.createElement('style')
|
|
|
|
|
+ styleElement.type = 'text/css'
|
|
|
|
|
+ styleElement.textContent = globalThis.stylFns['app/pages/Home/index.styl']
|
|
|
|
|
+ document.head.appendChild(styleElement)
|
|
|
|
|
+else
|
|
|
|
|
+ log '⚠️ Стили главной страницы не найдены'
|
|
|
|
|
+
|
|
|
|
|
+module.exports = {
|
|
|
|
|
+ # Импорт компонентов
|
|
|
|
|
+ components: {
|
|
|
|
|
+ 'product-grid': require 'app/components/Domain/ProductGrid/index.coffee'
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ props:
|
|
|
|
|
+ domainSettings:
|
|
|
|
|
+ type: Object
|
|
|
|
|
+ default: -> {}
|
|
|
|
|
+ language:
|
|
|
|
|
+ type: String
|
|
|
|
|
+ default: 'ru'
|
|
|
|
|
+
|
|
|
|
|
+ data: ->
|
|
|
|
|
+ {
|
|
|
|
|
+ welcomeText: 'Добро пожаловать в Браер-Колор'
|
|
|
|
|
+ loading: false
|
|
|
|
|
+ productsLoading: false
|
|
|
|
|
+ featuredProducts: []
|
|
|
|
|
+ features: [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: 'Качественные материалы'
|
|
|
|
|
+ description: 'Широкий ассортимент красок, грунтовок и лакокрасочных материалов от проверенных производителей'
|
|
|
|
|
+ }
|
|
|
|
|
+ {
|
|
|
|
|
+ title: 'Доставка по России'
|
|
|
|
|
+ description: 'Быстрая и надежная доставка в любой регион страны. Работаем с ведущими транспортными компаниями'
|
|
|
|
|
+ }
|
|
|
|
|
+ {
|
|
|
|
|
+ title: 'Профессиональные консультации'
|
|
|
|
|
+ description: 'Наши специалисты помогут подобрать оптимальные материалы для ваших задач и бюджета'
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ methods:
|
|
|
|
|
+ goToCatalog: ->
|
|
|
|
|
+ @$router.push '/catalog'
|
|
|
|
|
+
|
|
|
|
|
+ contactSupport: ->
|
|
|
|
|
+ @$emit 'show-notification', 'Форма обратной связи будет добавлена позже', 'info'
|
|
|
|
|
+
|
|
|
|
|
+ viewProduct: (productId) ->
|
|
|
|
|
+ @$router.push "/product/#{productId}"
|
|
|
|
|
+
|
|
|
|
|
+ loadFeaturedProducts: ->
|
|
|
|
|
+ @productsLoading = true
|
|
|
|
|
+ # Заглушка для загрузки товаров
|
|
|
|
|
+ setTimeout (=>
|
|
|
|
|
+ @featuredProducts = [
|
|
|
|
|
+ { id: 1, name: 'Грунтовка глубокого проникновения', price: 528, image: '' },
|
|
|
|
|
+ { id: 2, name: 'Краска акриловая белая', price: 890, image: '' },
|
|
|
|
|
+ { id: 3, name: 'Эмаль для металла', price: 670, image: '' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ @productsLoading = false
|
|
|
|
|
+ ), 1000
|
|
|
|
|
+
|
|
|
|
|
+ mounted: ->
|
|
|
|
|
+ log 'Главная страница загружена'
|
|
|
|
|
+ @loadFeaturedProducts()
|
|
|
|
|
+
|
|
|
|
|
+ render: (new Function '_ctx', '_cache', globalThis.renderFns['app/pages/Home/index.pug'])()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+# app/components/UI/Button/index.coffee
|
|
|
|
|
+
|
|
|
|
|
+# Добавление стилей компонента
|
|
|
|
|
+if globalThis.stylFns and globalThis.stylFns['app/components/UI/Button/index.styl']
|
|
|
|
|
+ styleElement = document.createElement('style')
|
|
|
|
|
+ styleElement.type = 'text/css'
|
|
|
|
|
+ styleElement.textContent = globalThis.stylFns['app/components/UI/Button/index.styl']
|
|
|
|
|
+ document.head.appendChild(styleElement)
|
|
|
|
|
+else
|
|
|
|
|
+ log '⚠️ Стили кнопки не найдены'
|
|
|
|
|
+
|
|
|
|
|
+module.exports =
|
|
|
|
|
+ name: 'ui-button'
|
|
|
|
|
+ props:
|
|
|
|
|
+ type:
|
|
|
|
|
+ type: String
|
|
|
|
|
+ default: 'primary'
|
|
|
|
|
+ validator: (value) ->
|
|
|
|
|
+ ['primary', 'secondary', 'success', 'danger', 'outline'].includes(value)
|
|
|
|
|
+ size:
|
|
|
|
|
+ type: String
|
|
|
|
|
+ default: 'medium'
|
|
|
|
|
+ validator: (value) ->
|
|
|
|
|
+ ['small', 'medium', 'large'].includes(value)
|
|
|
|
|
+ disabled:
|
|
|
|
|
+ type: Boolean
|
|
|
|
|
+ default: false
|
|
|
|
|
+ loading:
|
|
|
|
|
+ type: Boolean
|
|
|
|
|
+ default: false
|
|
|
|
|
+
|
|
|
|
|
+ methods:
|
|
|
|
|
+ handleClick: (event) ->
|
|
|
|
|
+ if not @disabled and not @loading
|
|
|
|
|
+ @$emit 'click', event
|
|
|
|
|
+
|
|
|
|
|
+ render: (new Function '_ctx', '_cache', globalThis.renderFns['app/components/UI/Button/index.pug'])()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+**app/router/index.coffee** пример, VueRouter определён глобально вызывать отдельно не нужно
|
|
|
|
|
+```coffee
|
|
|
|
|
+# app/router/index.coffee
|
|
|
|
|
+config = require 'app/config'
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+# Middleware для проверки прав доступа
|
|
|
|
|
+authGuard = (to, from, next) ->
|
|
|
|
|
+ log 'Проверка прав доступа для route:', to.path
|
|
|
|
|
+ # Здесь будет логика проверки пользователя из глобального состояния
|
|
|
|
|
+ if to.matched.some (record) -> record.meta.requiresAuth
|
|
|
|
|
+ # Проверка авторизации
|
|
|
|
|
+ next() # Временная заглушка - всегда разрешаем доступ
|
|
|
|
|
+ else
|
|
|
|
|
+ next()
|
|
|
|
|
+
|
|
|
|
|
+domainMiddleware = (to, from, next) ->
|
|
|
|
|
+ log 'Обработка динамического домена для route'
|
|
|
|
|
+ # Логика обработки домена будет интегрирована позже
|
|
|
|
|
+ next()
|
|
|
|
|
+
|
|
|
|
|
+router = VueRouter.createRouter({
|
|
|
|
|
+ history: VueRouter.createWebHistory()
|
|
|
|
|
+ routes: [
|
|
|
|
|
+ {
|
|
|
|
|
+ path: '/'
|
|
|
|
|
+ name: 'Home'
|
|
|
|
|
+ component: require 'app/pages/Home/index.coffee'
|
|
|
|
|
+ beforeEnter: [domainMiddleware]
|
|
|
|
|
+ }
|
|
|
|
|
+ {
|
|
|
|
|
+ path: '/catalog'
|
|
|
|
|
+ name: 'Catalog'
|
|
|
|
|
+ component: require 'app/pages/Catalog/index.coffee'
|
|
|
|
|
+ beforeEnter: [domainMiddleware]
|
|
|
|
|
+ }
|
|
|
|
|
+ {
|
|
|
|
|
+ path: '/catalog/:category?'
|
|
|
|
|
+ name: 'CatalogCategory'
|
|
|
|
|
+ component: require 'app/pages/Catalog/index.coffee'
|
|
|
|
|
+ beforeEnter: [domainMiddleware]
|
|
|
|
|
+ }
|
|
|
|
|
+ {
|
|
|
|
|
+ path: '/product/:id'
|
|
|
|
|
+ name: 'Product'
|
|
|
|
|
+ component: require 'app/pages/Product/index.coffee'
|
|
|
|
|
+ beforeEnter: [domainMiddleware]
|
|
|
|
|
+ }
|
|
|
|
|
+ {
|
|
|
|
|
+ path: '/cart'
|
|
|
|
|
+ name: 'Cart'
|
|
|
|
|
+ component: require 'app/pages/Cart/index.coffee'
|
|
|
|
|
+ beforeEnter: [domainMiddleware]
|
|
|
|
|
+ }
|
|
|
|
|
+ {
|
|
|
|
|
+ path: '/admin'
|
|
|
|
|
+ name: 'Admin'
|
|
|
|
|
+ component: require 'app/pages/Admin/index.coffee'
|
|
|
|
|
+ meta: { requiresAuth: true }
|
|
|
|
|
+ beforeEnter: [domainMiddleware, authGuard]
|
|
|
|
|
+ }
|
|
|
|
|
+ {
|
|
|
|
|
+ path: '/:pathMatch(.*)*'
|
|
|
|
|
+ name: 'NotFound'
|
|
|
|
|
+ component: require 'app/pages/NotFound/index.coffee'
|
|
|
|
|
+ beforeEnter: [domainMiddleware]
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+# Глобальные обработчики роутера
|
|
|
|
|
+router.beforeEach (to, from, next) ->
|
|
|
|
|
+ log "Переход с "+ from.path +" на "+to.path+""
|
|
|
|
|
+ next()
|
|
|
|
|
+
|
|
|
|
|
+router.afterEach (to, from) ->
|
|
|
|
|
+ log "Навигация завершена на "+to.path+""
|
|
|
|
|
+
|
|
|
|
|
+module.exports = router
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
## 🚨 КРИТИЧЕСКИЕ ПРАВИЛА РАЗРАБОТКИ
|
|
## 🚨 КРИТИЧЕСКИЕ ПРАВИЛА РАЗРАБОТКИ
|
|
|
|
|
|
|
|
### ❌ **ЗАПРЕЩЕНО:**
|
|
### ❌ **ЗАПРЕЩЕНО:**
|
|
@@ -921,6 +1253,32 @@ importFromCSV: (file, domain, onProgress) ->
|
|
|
- Напиши базовые файлы системы
|
|
- Напиши базовые файлы системы
|
|
|
- Система роутинга
|
|
- Система роутинга
|
|
|
- Глобальная конфигурация
|
|
- Глобальная конфигурация
|
|
|
|
|
+Добавлено подключение компонентов во все объекты где они используются:
|
|
|
|
|
+
|
|
|
|
|
+Главное приложение подключает все UI и Domain компоненты
|
|
|
|
|
+
|
|
|
|
|
+Страницы подключают используемые компоненты через components
|
|
|
|
|
+
|
|
|
|
|
+Роутер подключает все страницы приложения
|
|
|
|
|
+
|
|
|
|
|
+Создана полная структура компонентов:
|
|
|
|
|
+
|
|
|
|
|
+UI компоненты: Button, Notification, AppLoader, ThemeToggle, LanguageToggle
|
|
|
|
|
+
|
|
|
|
|
+Domain компоненты: MultilevelMenu, CartWidget, ProductGrid, ProductCard
|
|
|
|
|
+
|
|
|
|
|
+Страницы: Home, Catalog, Product, Cart, Admin, NotFound
|
|
|
|
|
+
|
|
|
|
|
+Настроена правильная иерархия зависимостей:
|
|
|
|
|
+
|
|
|
|
|
+Каждый компонент самостоятельно подключает свои стили
|
|
|
|
|
+
|
|
|
|
|
+Родительские компоненты регистрируют дочерние через components
|
|
|
|
|
+
|
|
|
|
|
+Главное приложение регистрирует глобальные компоненты
|
|
|
|
|
+
|
|
|
|
|
+Созданы все необходимые заглушки для полнофункционального приложения
|
|
|
|
|
+
|
|
|
|
|
|
|
|
### 🎯 БЛИЖАЙШИЕ ЗАДАЧИ
|
|
### 🎯 БЛИЖАЙШИЕ ЗАДАЧИ
|
|
|
|
|
|
|
@@ -967,40 +1325,32 @@ importFromCSV: (file, domain, onProgress) ->
|
|
|
- что сделано.
|
|
- что сделано.
|
|
|
- Напиши следущую задачу на выполнение (после завершения отладки, созданых файлов)
|
|
- Напиши следущую задачу на выполнение (после завершения отладки, созданых файлов)
|
|
|
|
|
|
|
|
|
|
+ ⚠️ ПРИОРИТЕТ
|
|
|
|
|
+
|
|
|
|
|
+ЭТАП 1.4: НАСТРОЙКА POUCHDB СЕРВИСА И ДИЗАЙН-ДОКУМЕНТОВ
|
|
|
|
|
+
|
|
|
|
|
+Реализовать полноценный PouchDB сервис:
|
|
|
|
|
+
|
|
|
|
|
+Подключение к CouchDB с аутентификацией
|
|
|
|
|
+
|
|
|
|
|
+Система синхронизации с фильтрацией по доменам
|
|
|
|
|
+
|
|
|
|
|
+Обработка ошибок и повторов
|
|
|
|
|
+
|
|
|
|
|
+Создать дизайн-документы CouchDB:
|
|
|
|
|
+
|
|
|
|
|
+Views для товаров, категорий, заказов
|
|
|
|
|
+
|
|
|
|
|
+Validate_doc_update функции
|
|
|
|
|
+
|
|
|
|
|
+Система индексов для поиска
|
|
|
|
|
+
|
|
|
|
|
+Разработать сервисы данных:
|
|
|
|
|
+
|
|
|
|
|
+ProductService для работы с товарами
|
|
|
|
|
+
|
|
|
|
|
+CategoryService для управления категориями
|
|
|
|
|
+
|
|
|
|
|
+DomainService для мультидоменности
|
|
|
|
|
|
|
|
-ЭТАП 1.2: СИСТЕМА РОУТИНГА И БАЗОВЫЕ СТРАНИЦЫ ⚠️ ПРИОРИТЕТ
|
|
|
|
|
- Создать Vue Router конфигурацию
|
|
|
|
|
-
|
|
|
|
|
- Файл маршрутизации с основными путями
|
|
|
|
|
-
|
|
|
|
|
- Обработка динамических доменов
|
|
|
|
|
-
|
|
|
|
|
- Middleware для проверки прав доступа
|
|
|
|
|
-
|
|
|
|
|
- Создать базовые страницы:
|
|
|
|
|
-
|
|
|
|
|
- Главная страница (/)
|
|
|
|
|
-
|
|
|
|
|
- Страница 404 (/*)
|
|
|
|
|
-
|
|
|
|
|
- Заглушки для основных разделов
|
|
|
|
|
-
|
|
|
|
|
- Разработать базовые UI компоненты:
|
|
|
|
|
-
|
|
|
|
|
- Кнопка (Button)
|
|
|
|
|
-
|
|
|
|
|
- Модальное окно (Modal)
|
|
|
|
|
-
|
|
|
|
|
- Уведомления (Notification)
|
|
|
|
|
-
|
|
|
|
|
- Индикатор загрузки (Loader)
|
|
|
|
|
-
|
|
|
|
|
- Настроить PouchDB сервис:
|
|
|
|
|
-
|
|
|
|
|
- Подключение к CouchDB
|
|
|
|
|
-
|
|
|
|
|
- Дизайн-документы
|
|
|
|
|
-
|
|
|
|
|
- Система синхронизации
|
|
|
|
|
-
|
|
|
|
|
Приоритет: Высокий ⚠️
|
|
Приоритет: Высокий ⚠️
|