| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564 |
- document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss">'+stylFns['app/pages/Admin/Products/index.styl']+'</style>')
- PouchDB = require 'app/utils/pouch'
- Papa = require 'papaparse'
- module.exports =
- name: 'AdminProducts'
-
- render: (new Function '_ctx', '_cache', renderFns['app/pages/Admin/Products/index.pug'])()
-
- data: ->
- return {
- products: []
- categories: []
- searchQuery: ''
- selectedCategory: ''
- selectedStatus: ''
- showProductModal: false
- showImportModal: false
- showCategoriesModal: false
- showCategoryModal: false
- editingProduct: null
- editingCategory: null
- selectedFile: null
- selectedCategoriesFile: null
- importing: false
- importingCategories: false
- importResults: null
- categoriesImportResults: null
- availableDomains: []
- categoriesActiveTab: 'list'
-
- productForm: {
- name: ''
- sku: ''
- category: ''
- price: 0
- oldPrice: 0
- brand: ''
- description: ''
- image: ''
- active: true
- domains: []
- }
-
- categoryForm: {
- name: ''
- slug: ''
- description: ''
- parentCategory: ''
- sortOrder: 0
- image: ''
- icon: ''
- active: true
- domains: []
- }
- }
-
- computed:
- filteredProducts: ->
- products = @products
-
- # Фильтр по поиску
- if @searchQuery
- query = @searchQuery.toLowerCase()
- products = products.filter (product) =>
- product.name?.toLowerCase().includes(query) ||
- product.sku?.toLowerCase().includes(query)
-
- # Фильтр по категории
- if @selectedCategory
- products = products.filter (product) =>
- product.category == @selectedCategory
-
- # Фильтр по статусу
- if @selectedStatus == 'active'
- products = products.filter (product) => product.active
- else if @selectedStatus == 'inactive'
- products = products.filter (product) => !product.active
-
- return products
-
- methods:
- loadProducts: ->
- PouchDB.queryView('admin', 'products', { include_docs: true })
- .then (result) =>
- @products = result.rows.map (row) -> row.doc
- .catch (error) =>
- console.error 'Ошибка загрузки товаров:', error
- @showNotification 'Ошибка загрузки товаров', 'error'
-
- loadCategories: ->
- PouchDB.queryView('admin', 'categories', { include_docs: true })
- .then (result) =>
- @categories = result.rows.map (row) -> row.doc
- .catch (error) =>
- console.error 'Ошибка загрузки категорий:', error
-
- loadDomains: ->
- PouchDB.queryView('admin', 'domain_settings', { include_docs: true })
- .then (result) =>
- @availableDomains = result.rows.map (row) -> row.doc
- .catch (error) =>
- console.error 'Ошибка загрузки доменов:', error
-
- getCategoryName: (categoryId) ->
- category = @categories.find (cat) -> cat._id == categoryId
- category?.name || 'Без категории'
-
- getCategoryProductCount: (categoryId) ->
- @products.filter((product) -> product.category == categoryId).length
-
- # Управление товарами
- editProduct: (product) ->
- @editingProduct = product
- @productForm = {
- name: product.name || ''
- sku: product.sku || ''
- category: product.category || ''
- price: product.price || 0
- oldPrice: product.oldPrice || 0
- brand: product.brand || ''
- description: product.description || ''
- image: product.image || ''
- active: product.active != false
- domains: product.domains || []
- }
- @showProductModal = true
-
- saveProduct: ->
- if !@productForm.name || !@productForm.sku || !@productForm.price
- @showNotification 'Заполните обязательные поля (Название, Артикул, Цена)', 'error'
- return
-
- productData = {
- type: 'product'
- ...@productForm
- updatedAt: new Date().toISOString()
- }
-
- if @editingProduct
- productData._id = @editingProduct._id
- productData._rev = @editingProduct._rev
- productData.createdAt = @editingProduct.createdAt
- else
- productData._id = "product:#{Date.now()}"
- productData.createdAt = new Date().toISOString()
-
- PouchDB.saveToRemote(productData)
- .then (result) =>
- @showProductModal = false
- @resetProductForm()
- @loadProducts()
- @showNotification 'Товар успешно сохранен'
- .catch (error) =>
- console.error 'Ошибка сохранения товара:', error
- @showNotification 'Ошибка сохранения товара', 'error'
-
- removeProductImage: ->
- @productForm.image = ''
-
- onProductImageUpload: (event) ->
- file = event.target.files[0]
- if file
- reader = new FileReader()
- reader.onload = (e) =>
- @productForm.image = e.target.result
- reader.readAsDataURL(file)
-
- # Управление категориями
- editCategory: (category) ->
- @editingCategory = category
- @categoryForm = {
- name: category.name || ''
- slug: category.slug || ''
- description: category.description || ''
- parentCategory: category.parentCategory || ''
- sortOrder: category.sortOrder || 0
- image: category.image || ''
- icon: category.icon || ''
- active: category.active != false
- domains: category.domains || []
- }
- @showCategoryModal = true
-
- saveCategory: ->
- if !@categoryForm.name || !@categoryForm.slug
- @showNotification 'Заполните обязательные поля (Название, URL slug)', 'error'
- return
-
- categoryData = {
- type: 'category'
- ...@categoryForm
- updatedAt: new Date().toISOString()
- }
-
- if @editingCategory
- categoryData._id = @editingCategory._id
- categoryData._rev = @editingCategory._rev
- categoryData.createdAt = @editingCategory.createdAt
- else
- categoryData._id = "category:#{Date.now()}"
- categoryData.createdAt = new Date().toISOString()
-
- PouchDB.saveToRemote(categoryData)
- .then (result) =>
- @showCategoryModal = false
- @resetCategoryForm()
- @loadCategories()
- @showNotification 'Категория успешно сохранена'
- .catch (error) =>
- console.error 'Ошибка сохранения категории:', error
- @showNotification 'Ошибка сохранения категории', 'error'
-
- removeCategoryImage: ->
- @categoryForm.image = ''
-
- removeCategoryIcon: ->
- @categoryForm.icon = ''
-
- onCategoryImageUpload: (event) ->
- file = event.target.files[0]
- if file
- reader = new FileReader()
- reader.onload = (e) =>
- @categoryForm.image = e.target.result
- reader.readAsDataURL(file)
-
- onCategoryIconUpload: (event) ->
- file = event.target.files[0]
- if file
- reader = new FileReader()
- reader.onload = (e) =>
- @categoryForm.icon = e.target.result
- reader.readAsDataURL(file)
-
- deleteCategory: (categoryId) ->
- if confirm('Вы уверены, что хотите удалить эту категорию?')
- PouchDB.getDocument(categoryId)
- .then (doc) ->
- PouchDB.saveToRemote({ ...doc, _deleted: true })
- .then (result) =>
- @loadCategories()
- @showNotification 'Категория удалена'
- .catch (error) =>
- console.error 'Ошибка удаления категории:', error
- @showNotification 'Ошибка удаления категории', 'error'
-
- # Импорт товаров
- onFileSelect: (event) ->
- @selectedFile = event.target.files[0]
- @importResults = null
-
- importProducts: ->
- if !@selectedFile
- @showNotification 'Выберите файл для импорта', 'error'
- return
-
- @importing = true
- @importResults = null
-
- reader = new FileReader()
- reader.onload = (e) =>
- try
- results = Papa.parse e.target.result, {
- 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)
-
- # Пакетное сохранение
- PouchDB.bulkDocs(couchProducts)
- .then (result) =>
- @importResults = {
- success: true,
- processed: couchProducts.length,
- errors: []
- }
- @importing = false
- @loadProducts()
- @loadCategories() # Перезагружаем категории, т.к. могли добавиться новые
- @showNotification "Импортировано #{couchProducts.length} товаров"
- .catch (error) =>
- @importResults = {
- success: false,
- error: error.message,
- processed: 0,
- errors: [error.message]
- }
- @importing = false
- @showNotification "Ошибка импорта: #{error.message}", 'error'
-
- catch error
- @importResults = {
- success: false,
- error: error.message,
- processed: 0,
- errors: [error.message]
- }
- @importing = false
- @showNotification "Ошибка обработки файла: #{error.message}", 'error'
-
- reader.readAsText(@selectedFile, 'UTF-8')
-
- transformProductData: (product, index) ->
- # Базовые поля
- productData = {
- _id: "product:#{Date.now()}-#{index}"
- type: 'product'
- name: product['Название товара']
- sku: product['Артикул*']
- price: parseFloat(product['Цена, руб.*'].replace(/\s/g, '').replace(',', '.')) || 0
- active: true
- createdAt: new Date().toISOString()
- updatedAt: new Date().toISOString()
- }
-
- # Обработка категории из поля "Тип*"
- if product['Тип*']
- categoryName = product['Тип*'].trim()
- # Ищем существующую категорию
- existingCategory = @categories.find (cat) ->
- cat.name?.toLowerCase() == categoryName.toLowerCase()
-
- if existingCategory
- productData.category = existingCategory._id
- else
- # Создаем новую категорию
- categoryId = "category:#{Date.now()}-#{index}"
- newCategory = {
- _id: categoryId
- type: 'category'
- name: categoryName
- slug: @generateSlug(categoryName)
- sortOrder: @categories.length
- active: true
- createdAt: new Date().toISOString()
- updatedAt: new Date().toISOString()
- domains: @availableDomains?.map((d) -> d.domain) || []
- }
- # Сохраняем новую категорию
- PouchDB.saveToRemote(newCategory)
- productData.category = categoryId
-
- # Дополнительные поля
- if product['Цена до скидки, руб.']
- productData.oldPrice = parseFloat(product['Цена до скидки, руб.'].replace(/\s/g, '').replace(',', '.'))
-
- if product['Ссылка на главное фото*']
- productData.image = product['Ссылка на главное фото*']
-
- if product['Бренд*']
- productData.brand = product['Бренд*']
-
- if product['Тип*']
- productData.productType = product['Тип*']
-
- # Rich content преобразование
- if product['Rich-контент JSON']
- try
- richContent = JSON.parse(product['Rich-контент JSON'])
- productData.description = @richContentToMarkdown(richContent)
- catch
- productData.description = product['Аннотация'] || ''
- else
- productData.description = product['Аннотация'] || ''
-
- # Домены
- productData.domains = @availableDomains?.map((d) -> d.domain) || []
-
- return productData
-
- # Импорт категорий
- onCategoriesFileSelect: (event) ->
- @selectedCategoriesFile = event.target.files[0]
- @categoriesImportResults = null
-
- importCategories: ->
- if !@selectedCategoriesFile
- @showNotification 'Выберите файл категорий для импорта', 'error'
- return
-
- @importingCategories = true
- @categoriesImportResults = null
-
- reader = new FileReader()
- reader.onload = (e) =>
- try
- results = Papa.parse e.target.result, {
- header: true
- delimiter: ','
- skipEmptyLines: true
- encoding: 'UTF-8'
- }
-
- categories = results.data.filter (row) =>
- row && row.name && row.slug
-
- couchCategories = categories.map (category, index) =>
- @transformCategoryData(category, index)
-
- # Пакетное сохранение категорий
- PouchDB.bulkDocs(couchCategories)
- .then (result) =>
- @categoriesImportResults = {
- success: true,
- processed: couchCategories.length,
- errors: []
- }
- @importingCategories = false
- @loadCategories()
- @showNotification "Импортировано #{couchCategories.length} категорий"
- .catch (error) =>
- @categoriesImportResults = {
- success: false,
- error: error.message,
- processed: 0,
- errors: [error.message]
- }
- @importingCategories = false
- @showNotification "Ошибка импорта категорий: #{error.message}", 'error'
-
- catch error
- @categoriesImportResults = {
- success: false,
- error: error.message,
- processed: 0,
- errors: [error.message]
- }
- @importingCategories = false
- @showNotification "Ошибка обработки файла категорий: #{error.message}", 'error'
-
- reader.readAsText(@selectedCategoriesFile, 'UTF-8')
-
- transformCategoryData: (category, index) ->
- categoryData = {
- _id: "category:import-#{Date.now()}-#{index}"
- type: 'category'
- name: category.name
- slug: category.slug
- description: category.description || ''
- parentCategory: category.parentCategory || ''
- sortOrder: parseInt(category.sortOrder) || @categories.length + index
- active: category.active != 'false'
- createdAt: new Date().toISOString()
- updatedAt: new Date().toISOString()
- domains: @availableDomains?.map((d) -> d.domain) || []
- }
-
- if category.image
- categoryData.image = category.image
-
- if category.icon
- categoryData.icon = category.icon
-
- return categoryData
-
- # Вспомогательные методы
- generateSlug: (text) ->
- text.toLowerCase()
- .replace(/\s+/g, '-')
- .replace(/[^\w\-]+/g, '')
- .replace(/\-\-+/g, '-')
- .replace(/^-+/, '')
- .replace(/-+$/, '')
-
- richContentToMarkdown: (richContent) ->
- # Простое преобразование rich content в markdown
- return JSON.stringify(richContent) # Временная реализация
-
- toggleProductStatus: (product) ->
- updatedProduct = {
- ...product
- active: !product.active
- updatedAt: new Date().toISOString()
- }
-
- PouchDB.saveToRemote(updatedProduct)
- .then (result) =>
- @loadProducts()
- @showNotification 'Статус товара обновлен'
- .catch (error) =>
- console.error 'Ошибка обновления статуса:', error
- @showNotification 'Ошибка обновления статуса', 'error'
-
- deleteProduct: (productId) ->
- if confirm('Вы уверены, что хотите удалить этот товар?')
- PouchDB.getDocument(productId)
- .then (doc) ->
- PouchDB.saveToRemote({ ...doc, _deleted: true })
- .then (result) =>
- @loadProducts()
- @showNotification 'Товар удален'
- .catch (error) =>
- console.error 'Ошибка удаления товара:', error
- @showNotification 'Ошибка удаления товара', 'error'
-
- resetProductForm: ->
- @editingProduct = null
- @productForm = {
- name: ''
- sku: ''
- category: ''
- price: 0
- oldPrice: 0
- brand: ''
- description: ''
- image: ''
- active: true
- domains: []
- }
-
- resetCategoryForm: ->
- @editingCategory = null
- @categoryForm = {
- name: ''
- slug: ''
- description: ''
- parentCategory: ''
- sortOrder: 0
- image: ''
- icon: ''
- active: true
- domains: []
- }
-
- getCategoriesTabClass: (tabId) ->
- baseClass = 'admin-products__categories-tab'
- isActive = @categoriesActiveTab == tabId
-
- if isActive
- return "#{baseClass} admin-products__categories-tab--active"
- else
- return baseClass
-
- formatPrice: (price) ->
- return '0 ₽' if !price
- new Intl.NumberFormat('ru-RU', {
- style: 'currency'
- currency: 'RUB'
- minimumFractionDigits: 0
- }).format(price)
-
- getStatusClass: (isActive) ->
- baseClass = 'admin-products__status'
- if isActive
- return "#{baseClass} admin-products__status--active"
- else
- return "#{baseClass} admin-products__status--inactive"
-
- showNotification: (message, type = 'success') ->
- @$root.showNotification?(message, type) || debug.log("#{type}: #{message}")
-
- mounted: ->
- @loadProducts()
- @loadCategories()
- @loadDomains()
|