index.coffee 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. document.head.insertAdjacentHTML('beforeend','<style type="text/css">'+stylFns['app/pages/Admin/Products/index.styl']+'</style>')
  2. PouchDB = require 'app/utils/pouch'
  3. Papa = require 'papaparse'
  4. module.exports =
  5. name: 'AdminProducts'
  6. render: (new Function '_ctx', '_cache', renderFns['app/pages/Admin/Products/index.pug'])()
  7. data: ->
  8. return {
  9. products: []
  10. categories: []
  11. searchQuery: ''
  12. selectedCategory: ''
  13. selectedStatus: ''
  14. showProductModal: false
  15. showImportModal: false
  16. editingProduct: null
  17. selectedFile: null
  18. importing: false
  19. importResults: null
  20. }
  21. computed:
  22. filteredProducts: ->
  23. products = @products
  24. # Фильтр по поиску
  25. if @searchQuery
  26. query = @searchQuery.toLowerCase()
  27. products = products.filter (product) =>
  28. product.name?.toLowerCase().includes(query) ||
  29. product.sku?.toLowerCase().includes(query)
  30. # Фильтр по категории
  31. if @selectedCategory
  32. products = products.filter (product) =>
  33. product.category == @selectedCategory
  34. # Фильтр по статусу
  35. if @selectedStatus == 'active'
  36. products = products.filter (product) => product.active
  37. else if @selectedStatus == 'inactive'
  38. products = products.filter (product) => !product.active
  39. return products
  40. methods:
  41. loadProducts: ->
  42. PouchDB.queryView('admin', 'products', { include_docs: true })
  43. .then (result) =>
  44. @products = result.rows.map (row) -> row.doc
  45. .catch (error) =>
  46. console.error 'Ошибка загрузки товаров:', error
  47. @showNotification 'Ошибка загрузки товаров', 'error'
  48. loadCategories: ->
  49. PouchDB.queryView('admin', 'categories', { include_docs: true })
  50. .then (result) =>
  51. @categories = result.rows.map (row) -> row.doc
  52. .catch (error) =>
  53. console.error 'Ошибка загрузки категорий:', error
  54. getCategoryName: (categoryId) ->
  55. category = @categories.find (cat) -> cat._id == categoryId
  56. category?.name || 'Без категории'
  57. editProduct: (product) ->
  58. @editingProduct = product
  59. @showProductModal = true
  60. toggleProductStatus: (product) ->
  61. updatedProduct = {
  62. ...product
  63. active: !product.active
  64. updatedAt: new Date().toISOString()
  65. }
  66. PouchDB.saveToRemote(updatedProduct)
  67. .then (result) =>
  68. @loadProducts()
  69. @showNotification 'Статус товара обновлен'
  70. .catch (error) =>
  71. console.error 'Ошибка обновления статуса:', error
  72. @showNotification 'Ошибка обновления статуса', 'error'
  73. deleteProduct: (productId) ->
  74. if confirm('Вы уверены, что хотите удалить этот товар?')
  75. PouchDB.getDocument(productId)
  76. .then (doc) ->
  77. PouchDB.saveToRemote({ ...doc, _deleted: true })
  78. .then (result) =>
  79. @loadProducts()
  80. @showNotification 'Товар удален'
  81. .catch (error) =>
  82. console.error 'Ошибка удаления товара:', error
  83. @showNotification 'Ошибка удаления товара', 'error'
  84. onFileSelect: (event) ->
  85. @selectedFile = event.target.files[0]
  86. @importResults = null
  87. importProducts: ->
  88. if !@selectedFile
  89. @showNotification 'Выберите файл для импорта', 'error'
  90. return
  91. @importing = true
  92. @importResults = null
  93. reader = new FileReader()
  94. reader.onload = (e) =>
  95. try
  96. results = Papa.parse e.target.result, {
  97. header: true
  98. delimiter: ';'
  99. skipEmptyLines: true
  100. encoding: 'UTF-8'
  101. }
  102. products = results.data.filter (row) =>
  103. row && row['Артикул*'] && row['Название товара'] && row['Цена, руб.*']
  104. couchProducts = products.map (product, index) =>
  105. @transformProductData(product, index)
  106. # Пакетное сохранение
  107. PouchDB.bulkDocs(couchProducts)
  108. .then (result) =>
  109. @importResults = { success: true, processed: couchProducts.length }
  110. @importing = false
  111. @loadProducts()
  112. @showNotification "Импортировано #{couchProducts.length} товаров"
  113. .catch (error) =>
  114. @importResults = { success: false, error: error.message, processed: 0 }
  115. @importing = false
  116. @showNotification "Ошибка импорта: #{error.message}", 'error'
  117. catch error
  118. @importResults = { success: false, error: error.message, processed: 0 }
  119. @importing = false
  120. @showNotification "Ошибка обработки файла: #{error.message}", 'error'
  121. reader.readAsText(@selectedFile, 'UTF-8')
  122. transformProductData: (product, index) ->
  123. # Базовые поля
  124. productData = {
  125. _id: "product:#{Date.now()}-#{index}"
  126. type: 'product'
  127. name: product['Название товара']
  128. sku: product['Артикул*']
  129. price: parseFloat(product['Цена, руб.*'].replace(/\s/g, '').replace(',', '.')) || 0
  130. active: true
  131. createdAt: new Date().toISOString()
  132. updatedAt: new Date().toISOString()
  133. }
  134. # Дополнительные поля
  135. if product['Цена до скидки, руб.']
  136. productData.oldPrice = parseFloat(product['Цена до скидки, руб.'].replace(/\s/g, '').replace(',', '.'))
  137. if product['Ссылка на главное фото*']
  138. productData.image = product['Ссылка на главное фото*']
  139. if product['Бренд*']
  140. productData.brand = product['Бренд*']
  141. if product['Тип*']
  142. productData.productType = product['Тип*']
  143. # Rich content преобразование
  144. if product['Rich-контент JSON']
  145. try
  146. richContent = JSON.parse(product['Rich-контент JSON'])
  147. productData.description = @richContentToMarkdown(richContent)
  148. catch
  149. productData.description = product['Аннотация'] || ''
  150. else
  151. productData.description = product['Аннотация'] || ''
  152. # Домены (все товары доступны на всех доменах по умолчанию)
  153. productData.domains = @availableDomains?.map((d) -> d.domain) || []
  154. return productData
  155. richContentToMarkdown: (richContent) ->
  156. # Простое преобразование rich content в markdown
  157. return JSON.stringify(richContent) # Временная реализация
  158. formatPrice: (price) ->
  159. return '0 ₽' if !price
  160. new Intl.NumberFormat('ru-RU', {
  161. style: 'currency'
  162. currency: 'RUB'
  163. minimumFractionDigits: 0
  164. }).format(price)
  165. getStatusClass: (isActive) ->
  166. baseClass = 'admin-products__status'
  167. if isActive
  168. return "#{baseClass} admin-products__status--active"
  169. else
  170. return "#{baseClass} admin-products__status--inactive"
  171. showNotification: (message, type = 'success') ->
  172. @$root.showNotification?(message, type) || debug.log("#{type}: #{message}")
  173. mounted: ->
  174. @loadProducts()
  175. @loadCategories()