설명 없음

Gogs 6f60ecf946 add redisain 4 주 전
app 2bb99b9064 add redisain 4 주 전
README.md 6f60ecf946 add redisain 4 주 전

README.md

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

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

Название: Интернет-магазин лакокрасочной продукции "Браер-Колор" с админ панелью, мудьтидоменной и мультиязычной структурой Тип: SPA (Single Page Application) с современным минималистичным дизайном Аналоги: Функциональность m-kraski.ru с дизайном https://braer-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 файлы):



использовать @import '../../index.styl' не нужно, приложение одностраничное.

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

Основной 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'


PouchDBService = require 'app/utils/pouch.coffee'

# Инициализация сервиса данных
pouchService = PouchDBService

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','<style  type="text/css">'+stylFns['app/index.styl']+'</style>')

document.head.insertAdjacentHTML('beforeend','<title></title>')


# Создание главного приложения Vue
app = Vue.createApp({
  data: ->
    return {
      theme: localStorage.getItem('theme') || 'light'
      companyName: 'Браер-Колор'
      loading: false
      cartItems: []
      user: null
      currentDomain: window.location.hostname
      languages: ['ru', 'en']
      currentLanguage: 'ru'
      currentDomainSettings: null
    }
  
  render: (new Function '_ctx', '_cache', renderFns['app/index.pug'])()
  
  computed:
    isAdmin: -> @user?.role == 'admin'
  
  methods:
    toggleTheme: ->
      @theme = if @theme == 'light' then 'dark' else 'light'
      localStorage.setItem 'theme', @theme
      document.documentElement.classList.toggle 'dark'
    
    setLanguage: (lang) ->
      if @languages.includes lang
        @currentLanguage = lang
        localStorage.setItem 'language', @lang
    
    loadDomainSettings: ->
      pouchService.getDocument("domain_settings:#{@currentDomain}")
        .then (settings) =>
          @currentDomainSettings = settings
          # Устанавливаем заголовок страницы
          if settings?.companyName
            document.title = settings.companyName
        .catch (error) =>
          console.log 'Настройки домена не найдены, используются значения по умолчанию'
          @currentDomainSettings = null
    
    showNotification: (message, type = 'success') ->
      # Реализация системы уведомлений
      console.log "#{type.toUpperCase()}: #{message}"
  
  mounted: ->
    # Инициализация темы
    if @theme == 'dark'
      document.documentElement.classList.add 'dark'
    
    # Инициализация PouchDB
    try
      await pouchService.init()
      console.log 'PouchDB инициализирован'
      # Загружаем настройки домена
      await @loadDomainSettings()
    catch error
      console.error 'Ошибка инициализации PouchDB:', error
    
    # Загрузка пользователя (если авторизован)
    @loadUserData()
  
  loadUserData: ->
    # Загрузка данных пользователя из localStorage или PouchDB
    userData = localStorage.getItem 'user'
    if userData
      try
        @user = JSON.parse userData
      catch
        @user = null
})

📊 Работа с данными (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
  • ВАЖНО всегда приводи полные листинги файлов
  • ВАЖНО делай верстку всех страниц адаптивной, корректно отображающейся на основных видах устройств
  • Соблюдайте синтаксис файлов: Код на Pug должен находиться только в .pug файлах, код на CoffeeScript - только в .coffee файлах.
  • Используйте правильные форматы для иконок: Для Vue-компонентов в CoffeeScript определяйте иконки как объекты с свойством template, содержащим валидный HTML.
  • Унифицируйте отступы: Настройте редактор кода на использование пробелов вместо табов для избежания проблем с индентированием.
  • ВАЖНО в место console.log используй debug.log
  • ВАЖНО использовать @import '../../index.styl' не нужно, приложение одностраничное.

2. Работа с данными

  • Селективная синхронизация: общие данные → локальная БД, пользовательские данные → удаленная БД
  • Умное кэширование: сначала локальный поиск, потом удаленный с сохранением в кэш
  • Версионный контроль design документов через хеши и семантическое версионирование

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

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

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

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

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

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

  • app/index.coffee
  • app/index.pug
  • app/index.styl
  • 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 Важно Всегда приводи только полные листинги файлов в стил файлах не используй @import '../../index.styl', только стили текущего элемента

Добавь в app/pages/Admin/Products управление категориями, и загрузки изображений к ним. также звгружай информацию из категорий из csv файла поле "Тип*", доделай окно редактирования товара

  • app/pages/Admin/index.coffee
  • app/pages/Admin/index.pug
  • app/pages/Admin/index.styl
  • 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