Sin descripción

Gogs a93ce6a7ac next Readme update hace 1 mes
README.md a93ce6a7ac next Readme update hace 1 mes

README.md

Промт для разработки проекта "Браер-Колор"

🎯 Контекст проекта

Название: Интернет-магазин лакокрасочной продукции "Браер-Колор" с админ панелью, мудьтидоменной и мультиязычной структурой Тип: SPA (Single Page Application) с современным минималистичным дизайном Аналоги: Функциональность m-kraski.ru с дизайном impasto-color.ru

🛠 Технический стек

Frontend

  • Шаблонизатор: Pug (включая Vue компоненты)
  • Стилизация: Tailwind CSS (исключительно через классы) в файлах styl, d качестве имён классов использоваьть методологию BEM
  • Логика: CoffeeScript с Vue.js 3
  • Маршрутизация: Vue Router
  • Анимации: Утилиты Tailwind (transition-, transform-, hover:)
  • База данных: PouchDB(локальный кешь, открытых данных) с синхронизацией CouchDB (центральное хранилище данных, и сессий пользователей.)

Архитектура

app/
├── index.pug (основной layout)
├── index.coffee (инициализация Vue и роутера)
├── index.styl (файл хранения описания классов по системе BEM с использованием Tailwind)
├── utils/
│   └── pouch.coffee (сервис работы с PouchDB)
├── design/
│   ├── admin.coffee (design документы админки)
│   └── site.coffee (design документы сайта)
└── pages/
    ├── Admin/ (админ-панель)
    │   ├── Layout/ (общий layout админки)
    │   ├── Slider/ (управление слайдами)
    │   ├── Products/ (управление товарами)
    │   ├── Clients/ (управление клиентами)
    │   ├── Blog/ (управление блогом с Markdown)
    │   ├── Routes/ (управление маршрутами)
    │   └── Settings/ (настройки системы)
    └── [пользовательские страницы]

💻 ПРИМЕРЫ КОДА И ПОДХОДЫ

🎨 Дизайн-система и организация стилей

Важна должна храниться в БД привязанная к конкретному домену/доменам theme.config.coffee:

module.exports = {
  colors: {
    primary: {
      50: '#fef2f2', 100: '#fee2e2', 200: '#fecaca', 300: '#fca5a5',
      400: '#f87171', 500: '#ef4444', 600: '#dc2626', 700: '#b91c1c',
      800: '#991b1b', 900: '#7f1d1d'
    }
    accent: {
      50: '#f0f9ff', 100: '#e0f2fe', 200: '#bae6fd', 300: '#7dd3fc',
      400: '#38bdf8', 500: '#0ea5e9', 600: '#0284c7', 700: '#0369a1',
      800: '#075985', 900: '#0c4a6e'
    }
  },
  typography: {
    fonts: {
      sans: ['Inter', 'ui-sans-serif', 'system-ui']
    },
    sizes: {
      xs: '0.75rem', sm: '0.875rem', base: '1rem', lg: '1.125rem',
      xl: '1.25rem', '2xl': '1.5rem', '3xl': '1.875rem'
    }
  }
}

Организация стилей (.styl файлы):

// Использование @css для Tailwind классов
@css {
  .admin--sidebar {
    @apply w-64 bg-white dark:bg-gray-800 shadow-md h-screen fixed;
  }
  .menu-item--active {
    @apply bg-primary-100 text-primary-700 dark:bg-primary-900 dark:text-primary-300;
  }
}

🏗️ Структура компонентов

Основной layout (app/index.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):

# Подключение файлов
globalThis.renderFns = require 'pug.json'
globalThis.stylFns   = require 'styl.json'
globalThis.themeConfig = require 'app/theme.config.coffee'

# Создание Vue приложения
app = Vue.createApp({
  data: ->
    return {
      theme: 'light'
      companyName: 'Браер-Колор'
      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):

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

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

document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss">'+stylFns['app/pages/Admin/Slider/index.styl']+'</style>')

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:

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

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. Обработка ошибок

  • Глобальная обработка ошибок, с максимальной детализацией, и возможностью отключения их вывода, в продакшн релизе.
  • Пользовательские уведомления об ошибках

4. Производительность

  • Ленивая загрузка компонентов через Vue Router
  • Кэширование данных в локальной PouchDB
  • Пакетная обработка при импорте больших объемов данных, показывать количество обработанных документов.

🚀 Состояние разработки

✅ Реализовано

Анализировать реализованный код, по git репозитарию https://gogs.osvoj.ru/oleg/s5l.ru-crm.git Проверяй промт и изменения в нём по адресу https://gogs.osvoj.ru/oleg/s5l.ru-crm/raw/master/README.md

🚧 В процессе

Начать разработку с полного листинга файлов app/index.coffee app/index.pug app/index.styl app/utils/pouch.db app/desing/admin.coffee app/desing/site.coffee