# Промт для разработки проекта "Браер-Колор" ## 🎯 Контекст проекта **Название:** Интернет-магазин лакокрасочной продукции "Браер-Колор" с админ панелью, мудьтидоменной и мультиязычной структурой **Тип:** SPA (Single Page Application) с современным минималистичным дизайном **Аналоги:** Функциональность m-kraski.ru с дизайном impasto-color.ru ## 🛠 Технический стек ### Frontend - **Шаблонизатор:** Pug (включая Vue компоненты) - **Стилизация:** Stylus в файлах styl, в качестве имён классов использоваьть методологию BEM - **Логика:** CoffeeScript с Vue.js 3 - **Маршрутизация:** Vue Router - **Анимации:** Утилиты Stylus (transition-, transform-, hover: focus: down: ) или предложи другой вариант - **База данных:** PouchDB(локальный кешь, открытых данных) с синхронизацией CouchDB (центральное хранилище данных, и сессий пользователей.) ### Архитектура ``` app/ ├── index.pug (основной layout) ├── index.coffee (инициализация Vue и роутера) ├── index.styl (файл хранения описания классов по системе BEM ) ├── utils/ │ └── pouch.coffee (сервис работы с PouchDB) ├── design/ │ ├── admin.coffee (design документы админки) │ └── site.coffee (design документы сайта) └── pages/ ├── Admin/ (админ-панель) │ ├── Layout/ (общий layout админки) │ ├── Slider/ (управление слайдами) │ ├── Products/ (управление товарами) │ ├── Clients/ (управление клиентами) │ ├── Blog/ (управление блогом с Markdown) │ ├── Routes/ (управление маршрутами) │ └── Settings/ (настройки системы) └── [пользовательские страницы] ``` ## 💻 ПРИМЕРЫ КОДА И ПОДХОДЫ ### 🎨 Дизайн-система и организация стилей **Организация стилей (.styl файлы):** ```stylus ``` использовать @import '../../index.styl' не нужно, приложение одностраничное. ### 🏗️ Структура компонентов **Основной layout (app/index.pug):** ```pug div(class="") header(class="header") nav(class="header-nav") div(class="header-nav-blok") div(class="header-nav--name") {{ kompname }} div(class="header-nav--menu") multilevelmenu themetoggle main router-view(v-slot='{ Component }') transition(name='page-slide' mode='out-in') component(:is='Component') ``` Важно: не используй многострочные вычисляемые атрибуты, приводит к ошибке. Важно: мета данные добавляются через app/index.coffe базовым тегоьм для vuejs является body, app/index.pug начинается с div, теги html, head, body ЗАПРЕЩЕНО использовать. Важно: все тексты в клиенской части беруться их документа настройки домена. в которых должна быть предусмотрена мультиязычность, документы могут иметь произвольную структуру, создаваемую в админке. **Инициализация приложения (app/index.coffee):** ```coffee # Подключение файлов globalThis.renderFns = require 'pug.json' globalThis.stylFns = require 'styl.json' # Создание Vue приложения app = Vue.createApp({ data: -> return { theme: 'light' loading: false cartItems: [] user: null } render: (new Function '_ctx', '_cache', renderFns['app/index.pug'])() methods: toggleTheme: -> @theme = if @theme == 'light' then 'dark' else 'light' localStorage.setItem 'theme', @theme document.documentElement.classList.toggle 'dark' }) # Настройка маршрутизатора router = VueRouter.createRouter({ history: VueRouter.createWebHistory() routes: [ { path: '/', component: require 'app/pages/Home' } { path: '/admin' component: require 'app/pages/Admin' redirect: '/admin/Settings' children: [ { path: 'slider', component: require 'app/pages/Admin/Slider' } { path: 'products', component: require 'app/pages/Admin/Products' } { path: 'clients', component: require 'app/pages/Admin/Clients' } { path: 'blog', component: require 'app/pages/Admin/Blog' } { path: 'routes', component: require 'app/pages/Admin/Routes' } { path: 'settings', component: require 'app/pages/Admin/Settings' } ] } # Динамические маршруты для статей блога { path: '/blog/:slug', component: require './pages/BlogArticle' } # Пользовательские маршруты { path: '/contacts', component: require './pages/Contacts' } { path: '/about', component: require './pages/About' } ] }) app.use(router) app.mount('#app') ``` ### 📊 Работа с данными (PouchDB) **PouchDBService (app/utils/pouch.coffee):** ```coffee class PouchDBService constructor: (options = {}) -> {@localDbName, @remoteDbUrl, @userFilter, @appVersion} = options @localDb = null @remoteDb = null @initialized = false init: -> return Promise.resolve() if @initialized try @localDb = new PouchDB(@localDbName || 'braer_color_cache') await @ensureRemoteDatabase() await @loadDesignDocs() await @ensureDesignDocs() # Настройка селективной синхронизации PouchDB.sync(@remoteDb, @localDb, { live: true, retry: true, filter: (doc) => @shouldSyncDocument(doc) }) @initialized = true return Promise.resolve() catch error console.error('Ошибка инициализации PouchDB:', error) return Promise.reject(error) # Умное получение документа - сначала локально, потом удаленно getDocument: (docId) -> @ensureInit() try return await @localDb.get(docId) catch localError if localError.status == 404 try doc = await @remoteDb.get(docId) await @localDb.put(doc) # Кэшируем для будущих запросов return doc catch remoteError throw remoteError else throw localError # Сохранение только в удаленную БД с проверкой существования saveToRemote: (doc) -> @ensureInit() try existingDoc = await @remoteDb.get(doc._id) doc._rev = existingDoc._rev return await @remoteDb.put(doc) catch error if error.status == 404 return await @remoteDb.put(doc) else throw error # Проверка необходимости синхронизации документа shouldSyncDocument: (doc) -> # Всегда синхронизируем общие данные if doc.type in ['product', 'category', 'settings', 'hero_slide', 'blog_article', 'route'] return true # Для пользовательских данных проверяем принадлежность if doc.type in ['order', 'user_data', 'cart'] return doc.userId == @userFilter?.userId return false module.exports = new PouchDBService({ localDbName: 'braer_color_cache' remoteDbUrl: 'http://localhost:5984/braer_color_shop' userFilter: { userId: 'current_user_id' } appVersion: '1.0.0' }) ``` Выжно: при реализации кода, проверяй наличие баз данных, если их нет то создай, также проверяй наличие _desing документов, если их нет создай. ### 🎛️ Компоненты админ-панели Учти возможность многодоменной разработки, и ограничения доступа в зависимости от используемого домена на клиенской части, и в админке, в зависимости от прав администратора. **Layout админки (app/pages/Admin/index.pug):** ```pug div(class="mainadmin-block") .admin-sidebar .p-4 h1(class="mainadmin-block--h1") Панель администратора nav(class="mainadmin-block--nav") a( v-for="item in menuItems" :key="item.id" :href="item.path" @click.prevent="navigateTo(item.path)" :class="getMenuItemClass(item.id)" ) .flex.items-center.space-x-3 component(:is="item.icon" class="w-5 h-5") span {{ item.name }} .admin-content router-view ``` **Пример компонента админки (app/pages/Admin/Slider/index.coffee):** ```coffee document.head.insertAdjacentHTML('beforeend','') PouchDB = require 'app/utils/pouch' module.exports = name: 'AdminSlider' render: (new Function '_ctx', '_cache', renderFns['app/pages/Admin/Slider/index.pug'])() data: -> slides: [] showSlideModal: false currentSlide: { title: '' subtitle: '' image: '' buttonText: 'В каталог' buttonLink: '/catalog' active: true order: 0 } mounted: -> @loadSlides() methods: loadSlides: -> PouchDB.queryView('admin', 'slides', { descending: false }) .then (result) => @slides = result.rows.map (row) -> row.doc .catch (error) => console.error 'Ошибка загрузки слайдов:', error @showNotification 'Ошибка загрузки слайдов', 'error' saveSlide: -> slideData = { type: 'hero_slide' ...@currentSlide updatedAt: new Date().toISOString() } if !slideData._id slideData._id = "hero_slide:#{Date.now()}" slideData.createdAt = new Date().toISOString() slideData.order = @slides.length PouchDB.saveToRemote(slideData) .then (result) => @loadSlides() # Перезагружаем через view @showSlideModal = false @resetCurrentSlide() @showNotification 'Слайд успешно сохранен' .catch (error) => console.error 'Ошибка сохранения слайда:', error @showNotification 'Ошибка сохранения слайда', 'error' ``` ### 📝 Design документы **admin.coffee:** ```coffee module.exports = { _id: '_design/admin', version: '1.1.0', appVersion: '1.0.0', hash: 'generated_hash_here', views: { slides: { map: ((doc) -> if doc.type == 'hero_slide' emit(doc.order, { _id: doc._id, title: doc.title, active: doc.active, order: doc.order }) ).toString() }, products: { map: ((doc) -> if doc.type == 'product' emit([doc.category, doc.createdAt], { _id: doc._id, name: doc.name, price: doc.price, active: doc.active }) ).toString() } } } ``` ### 🔄 Импорт товаров из CSV **Метод импорта (app/pages/Admin/Products/index.coffee):** ```coffee importProducts: -> if !@selectedFile @showNotification 'Выберите файл для импорта', 'error' return @importing = true @importResults = null @readFile(@selectedFile) .then (text) => results = Papa.parse(text, { header: true delimiter: ';' skipEmptyLines: true encoding: 'UTF-8' }) products = results.data.filter (row) => row && row['Артикул*'] && row['Название товара'] && row['Цена, руб.*'] couchProducts = products.map (product, index) => @transformProductData(product, index) # Пакетное сохранение в удаленную БД return PouchDB.bulkDocs(couchProducts) .then (result) => @importResults = { success: true, processed: couchProducts.length } @showNotification "Импортировано #{couchProducts.length} товаров" @loadProducts() # Перезагружаем список .catch (error) => console.error 'Ошибка импорта:', error @importResults = { success: false, error: error.message } @showNotification "Ошибка импорта: #{error.message}", 'error' .finally => @importing = false ``` Важно: при полной реализации используй пример csv файла, При этом учти что количество полей в документе может меняться. выдели из них основные, остальные загружай по факту как свойства товара. пример csv: №;Артикул*;Название товара;Цена, руб.*;Цена до скидки, руб.;НДС, %*;Рассрочка;Баллы за отзывы;SKU;Штрихкод (Серийный номер / EAN);Вес в упаковке, г*;Ширина упаковки, мм*;Высота упаковки, мм*;Длина упаковки, мм*;Ссылка на главное фото*;Ссылки на дополнительные фото;Ссылки на фото 360;Артикул фото;Бренд*;Название модели (для объединения в одну карточку)*;Единиц в одном товаре;Цвет товара;Название цвета;Тип*;Класс опасности товара*;Степень блеска покрытия;Работы;Вес товара, г;Количество товара в УЕИ;#Хештеги;Аннотация;Rich-контент JSON;Название группы;Образец цвета;Партномер;Гарантия;Страна-изготовитель;Комплектация;ТН ВЭД коды ЕАЭС;Срок годности в днях;Количество заводских упаковок;Вид краски;Объем, л;Время высыхания, часов;Вес, кг;Расход, л/м2;Назначение грунтовки;Рекомендуемое количество слоев;Расход, кг/м2;Область применения состава;Количество компонентов;Особенности ЛКМ;Макс. температура эксплуатации, С°;Материал основания;Основа краски;Основа грунтовки;Способ нанесения;Форма выпуска средства;Назначение;Тип помещения;Возможность колеровки;Вид выпуска товара;Тип растворителя;Эффект краски;Марка эмали;Можно мыть;Базис;Аэрозоль;Помещение;Название модели для шаблона наименования;Ошибка;Предупреждение 1;4673764201943;Грунтовка глубокого проникновения для стен под обои и покраску ЭкоКрас 1кг;528,00;1 056,00;20;Да;Нет;;4673764201943;1000;100;55;250;https://cdn1.ozone.ru/s3/multimedia-1-o/7663357104.jpg;"https://cdn1.ozone.ru/s3/multimedia-1-8/7663357124.jpg https://cdn1.ozone.ru/s3/multimedia-1-w/7663352504.jpg https://cdn1.ozone.ru/s3/multimedia-1-r/7663357179.jpg https://cdn1.ozone.ru/s3/multimedia-1-b/7663352483.jpg https://cdn1.ozone.ru/s3/multimedia-1-c/7663352556.jpg https://cdn1.ozone.ru/s3/multimedia-1-t/7663352537.jpg https://cdn1.ozone.ru/s3/multimedia-1-o/7663352496.jpg https://cdn1.ozone.ru/s3/multimedia-1-k/7663352492.jpg https://cdn1.ozone.ru/s3/multimedia-1-y/7663365970.jpg https://cdn1.ozone.ru/s3/multimedia-1-m/7663365922.jpg https://cdn1.ozone.ru/s3/multimedia-1-1/7663365937.jpg https://cdn1.ozone.ru/s3/multimedia-1-p/7663352533.jpg";;;ЭкоКрас;4673764201943;1;прозрачный;;Грунтовка;Не опасен;;"Внутренние;Наружные";;;#Грунтовка #ГрунтДляСтен #АкриловаяГрунтовка #СтроительныеМатериалы #ГлубокогоПроникновения #УниверсальнаяГрунтовка #АдгезионнаяГрунтовка #АнтисептическаяГрунтовка #ДляВнутреннихРабот #ДляНаружныхРабот #ДляБетона #ДляГипсокартона #ДляДерева #Быстросохнущая #Водостойкая #Укрепляющая #Противогрибковая #БелаяГрунтовка #ПодОбои #ПодШтукатурку #ПодПокраску #ПодПлитку #ДляРовныхСтен #РемонтДома #ОтделкаПомещений #СтроительныеРаботы #КачественныйРемонт #СтроительныеТовары #профессиональная_грунтовка #грунтовка_концентрат_премиум;"Премиум грунтовка глубокого проникновения для подготовки поверхностей перед финишной отделкой. Грунтовка глубокого проникновения – это универсальное средство, она проникает глубоко в структуру бетона, кирпича, штукатурки, дерева и других материалов, укрепляя их и улучшая адгезию для последующего нанесения краски, обоев или плитки. Грунтовка для стен применяется для укрепления штукатурки и шпатлевок, препятствует появлению высолов создавая щелочестойкий слой. Идеально подходит для стен, потолков и полов в любых помещениях – от ванной и кухни до жилых комнат. Благодаря жидкой консистенции и акриловой основе, грунтовка быстро впитывается, связывая пыль и предотвращая осыпание старых покрытий. Средство подходит для наружных работ, так как устойчива к перепадам температур и влажности. Может использоваться под наливные полы, фактурную штукатурку и кафель. Белый или прозрачный состав не оставляет разводов и не влияет на цвет финишного покрытия. Высокое содержание полимеров позволяет получить слабо впитывающие поверхности, даже в случае осыпающихся, осмеливающихся поверхностей (известковые покрытия, песчанно-цементные штукатурки слабого качества, давно окрашенные водоэмульсионными красками поверхности). Расход 0,05-0,15 кг на 1 м 2 в один раз. Наносить в один или два слоя в зависимости от состояния подложки. Второй слой наносить непосредственно за первым для более глубокого проникновения или же после полного высыхания первого слоя. Быстро высыхает, не имеет резкого запаха и безопасна для внутренних работ. Применяется перед покраской, поклейкой обоев или укладкой плитки, обеспечивая долговечность и качество ремонта. Незаменима для обработки бетонных, известковых и гипсовых оснований, а также при работе с волма слоем. Используйте грунтовку глубокого проникновения – и ваши отделочные работы будут выполнены на профессиональном уровне! ";"{ ""content"": [ { ""widgetName"": ""raTextBlock"", ""title"": { ""items"": [ { ""type"": ""text"", ""content"": ""Грунтовка глубокого проникновения PRIMER 1:4 концентрат ImPasto 1кг"" } ], ""size"": ""size5"", ""color"": ""color1"" }, ""theme"": ""primary"", ""padding"": ""type2"", ""gapSize"": ""m"", ""text"": { ""size"": ""size2"", ""align"": ""left"", ""color"": ""color1"", ""items"": [ { ""type"": ""text"", ""content"": ""Премиум грунтовка глубокого проникновения для подготовки поверхностей перед финишной отделкой. Грунтовка глубокого проникновения – это универсальное средство, она проникает глубоко в структуру бетона, кирпича, штукатурки, дерева и других материалов, укрепляя их и улучшая адгезию для последующего нанесения краски, обоев или плитки.\r"" }, { ""type"": ""br"" }, { ""type"": ""text"", ""content"": ""\r"" }, { ""type"": ""br"" }, { ""type"": ""text"", ""content"": ""Грунтовка для стен применяется для укрепления штукатурки и шпатлевок, препятствует появлению высолов создавая щелочестойкий слой. Идеально подходит для стен, потолков и полов в любых помещениях – от ванной и кухни до жилых комнат. Благодаря жидкой консистенции и акриловой основе, грунтовка быстро впитывается, связывая пыль и предотвращая осыпание старых покрытий. \r"" }, { ""type"": ""br"" }, { ""type"": ""text"", ""content"": ""\r"" }, { ""type"": ""br"" }, { ""type"": ""text"", ""content"": ""Средство подходит для наружных работ, так как устойчива к перепадам температур и влажности. Может использоваться под наливные полы, фактурную штукатурку и кафель. Белый или прозрачный состав не оставляет разводов и не влияет на цвет финишного покрытия.\r"" }, { ""type"": ""br"" }, { ""type"": ""text"", ""content"": ""\r"" }, { ""type"": ""br"" }, { ""type"": ""text"", ""content"": ""Высокое содержание полимеров позволяет получить слабо впитывающие поверхности, даже в случае осыпающихся, осмеливающихся поверхностей (известковые покрытия, песчанно-цементные штукатурки слабого качества, давно окрашенные водоэмульсионными красками поверхности). Расход 0,05-0,15 кг на 1 м 2 в один раз. Наносить в один или два слоя в зависимости от состояния подложки. Второй слой наносить непосредственно за первым для более глубокого проникновения или же после полного высыхания первого слоя. Быстро высыхает, не имеет резкого запаха и безопасна для внутренних работ."" } ] } } ], ""version"": 0.3 }";экокрас;https://cdn1.ozone.ru/s3/multimedia-1-q/7218190898.jpg;;2 года;Россия;Грунтовка глубокого проникновения ЭкоКрас 1кг- 1шт;;990;1;;1;24;1;0,05;"Глубокого проникновения;Обеспыливающая;Пропиточная;Укрепляющая;Универсальная";;;"По бетону;Для фасадов;Для хобби и творчества;Для швов;По кирпичу;По штукатурке;Универсальная";;;;"Бетон;Газобетон;Кирпич;Пенобетон;Штукатурка";;Акриловая;"Валик;Кисть;Краскопульт;Пистолет";Готовый раствор;;"С повышенной влажностью;С умеренной влажностью;Сухое";;;;;;;;;;;; При обработке csv загружай изображения в базу данных как attachment, также сохраняй ричконтент преобразовывая в markdown, и выводи его в качестве описания товара при его наличии. формат ссылок на attachment файлы "/d/[ИМЯ БД]/[id doc]/[имя файла]" Важно: каждый товар должен быть привязан к домену/доменам (может одновременно быть доступен на разных доменах), тоже касается статей блога. ## 🎯 КЛЮЧЕВЫЕ ПОДХОДЫ И ПАТТЕРНЫ ### 1. **Организация компонентов** - Каждый компонент в отдельной папке с `index.pug`, `index.coffee`, `index.styl` - Использование render функций: `(new Function '_ctx', '_cache', renderFns[...])()` - Централизованная дизайн-система в `theme.config.coffee` - ВАЖНО всегда приводи полные листинги файлов - ВАЖНО делай верстку всех страниц адаптивной, корректно отображающейся на основных видах устройств ### 2. **Работа с данными** - Селективная синхронизация: общие данные → локальная БД, пользовательские данные → удаленная БД - Умное кэширование: сначала локальный поиск, потом удаленный с сохранением в кэш - Версионный контроль design документов через хеши и семантическое версионирование ### 3. **Обработка ошибок** - Глобальная обработка ошибок, с максимальной детализацией, и возможностью отключения их вывода, в продакшн релизе. - Пользовательские уведомления об ошибках - ВАЖНО в место console.log используй debug.log ### 4. **Производительность** - Ленивая загрузка компонентов через Vue Router - Кэширование данных в локальной PouchDB - Пакетная обработка при импорте больших объемов данных, показывать количество обработанных документов. ## 🚀 Состояние разработки ### ✅ Реализовано - app/index.coffee - app/index.pug - app/index.styl - app/theme.config.coffee - app/utils/pouch.db - app/desing/admin.coffee - app/desing/site.coffee - app/pages/Home/index.coffee - app/pages/Home/index.pug - app/pages/Home/index.styl - app/pages/Admin/index.coffee - app/pages/Admin/index.pug - app/pages/Admin/index.styl - app/pages/Admin/Settings/index.coffee - app/pages/Admin/Settings/index.pug - app/pages/Admin/Settings/index.styl ### 🚧 В процессе Анализировать реализованный код, по git репозитарию https://gogs.osvoj.ru/oleg/s5l.ru-crm.git Проверяй промт и изменения в нём по адресу https://gogs.osvoj.ru/oleg/s5l.ru-crm/raw/master/README.md Написать файлы - app/pages/Admin/Slider/index.coffee - app/pages/Admin/Slider/index.pug - app/pages/Admin/Slider/index.styl - app/pages/Admin/Products/index.coffee - app/pages/Admin/Products/index.pug - app/pages/Admin/Products/index.styl - app/pages/Admin/Blog/index.coffee - app/pages/Admin/Blog/index.pug - app/pages/Admin/Blog/index.styl