index.pug 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. div(class="admin-products")
  2. div(class="admin-products__header")
  3. h1(class="admin-products__title") Управление товарами
  4. div(class="admin-products__actions")
  5. button(
  6. @click="showProductModal = true"
  7. class="admin-products__btn admin-products__btn--primary"
  8. ) Добавить товар
  9. button(
  10. @click="showImportModal = true"
  11. class="admin-products__btn admin-products__btn--secondary"
  12. ) Импорт из CSV
  13. button(
  14. @click="showCategoriesModal = true"
  15. class="admin-products__btn admin-products__btn--secondary"
  16. ) Управление категориями
  17. div(class="admin-products__content")
  18. div(class="admin-products__filters")
  19. div(class="admin-products__filter-group")
  20. label(class="admin-products__label") Поиск
  21. input(
  22. v-model="searchQuery"
  23. type="text"
  24. class="admin-products__input"
  25. placeholder="Название или артикул..."
  26. )
  27. div(class="admin-products__filter-group")
  28. label(class="admin-products__label") Категория
  29. select(v-model="selectedCategory" class="admin-products__select")
  30. option(value="") Все категории
  31. option(
  32. v-for="category in categories"
  33. :key="category._id"
  34. :value="category._id"
  35. ) {{ category.name }}
  36. div(class="admin-products__filter-group")
  37. label(class="admin-products__label") Статус
  38. select(v-model="selectedStatus" class="admin-products__select")
  39. option(value="") Все
  40. option(value="active") Активные
  41. option(value="inactive") Неактивные
  42. div(class="admin-products__list")
  43. div(class="admin-products__table-container")
  44. table(class="admin-products__table")
  45. thead
  46. tr
  47. th(class="admin-products__th") Изобр.
  48. th(class="admin-products__th") Название
  49. th(class="admin-products__th") Артикул
  50. th(class="admin-products__th") Цена
  51. th(class="admin-products__th") Категория
  52. th(class="admin-products__th") Статус
  53. th(class="admin-products__th") Действия
  54. tbody
  55. tr(
  56. v-for="product in filteredProducts"
  57. :key="product._id"
  58. class="admin-products__tr"
  59. )
  60. td(class="admin-products__td")
  61. img(
  62. v-if="product.image"
  63. :src="product.image"
  64. :alt="product.name"
  65. class="admin-products__image"
  66. )
  67. div(v-else class="admin-products__no-image") Нет
  68. td(class="admin-products__td")
  69. div(class="admin-products__name") {{ product.name }}
  70. td(class="admin-products__td") {{ product.sku }}
  71. td(class="admin-products__td")
  72. div(class="admin-products__price") {{ formatPrice(product.price) }}
  73. div(
  74. v-if="product.oldPrice"
  75. class="admin-products__old-price"
  76. ) {{ formatPrice(product.oldPrice) }}
  77. td(class="admin-products__td") {{ getCategoryName(product.category) }}
  78. td(class="admin-products__td")
  79. span(
  80. :class="getStatusClass(product.active)"
  81. ) {{ product.active ? 'Активен' : 'Неактивен' }}
  82. td(class="admin-products__td")
  83. div(class="admin-products__action-buttons")
  84. button(
  85. @click="editProduct(product)"
  86. class="admin-products__action-btn admin-products__action-btn--edit"
  87. ) Редакт.
  88. button(
  89. @click="toggleProductStatus(product)"
  90. class="admin-products__action-btn admin-products__action-btn--toggle"
  91. ) {{ product.active ? 'Выкл.' : 'Вкл.' }}
  92. button(
  93. @click="deleteProduct(product._id)"
  94. class="admin-products__action-btn admin-products__action-btn--delete"
  95. ) Удалить
  96. // Модальное окно редактирования товара
  97. div(v-if="showProductModal" class="admin-products__modal")
  98. div(class="admin-products__modal-content")
  99. h3(class="admin-products__modal-title") {{ editingProduct ? 'Редактирование' : 'Добавление' }} товара
  100. div(class="admin-products__modal-form")
  101. div(class="admin-products__form-group")
  102. label(class="admin-products__label") Название товара *
  103. input(
  104. v-model="productForm.name"
  105. type="text"
  106. class="admin-products__input"
  107. placeholder="Введите название товара"
  108. required
  109. )
  110. div(class="admin-products__form-group")
  111. label(class="admin-products__label") Артикул *
  112. input(
  113. v-model="productForm.sku"
  114. type="text"
  115. class="admin-products__input"
  116. placeholder="Артикул товара"
  117. required
  118. )
  119. div(class="admin-products__form-group")
  120. label(class="admin-products__label") Категория
  121. select(v-model="productForm.category" class="admin-products__select")
  122. option(value="") Выберите категорию
  123. option(
  124. v-for="category in categories"
  125. :key="category._id"
  126. :value="category._id"
  127. ) {{ category.name }}
  128. div(class="admin-products__form-group")
  129. label(class="admin-products__label") Цена *
  130. input(
  131. v-model="productForm.price"
  132. type="number"
  133. class="admin-products__input"
  134. placeholder="0.00"
  135. min="0"
  136. step="0.01"
  137. required
  138. )
  139. div(class="admin-products__form-group")
  140. label(class="admin-products__label") Старая цена
  141. input(
  142. v-model="productForm.oldPrice"
  143. type="number"
  144. class="admin-products__input"
  145. placeholder="0.00"
  146. min="0"
  147. step="0.01"
  148. )
  149. div(class="admin-products__form-group")
  150. label(class="admin-products__label") Бренд
  151. input(
  152. v-model="productForm.brand"
  153. type="text"
  154. class="admin-products__input"
  155. placeholder="Бренд производителя"
  156. )
  157. div(class="admin-products__form-group")
  158. label(class="admin-products__label") Описание
  159. textarea(
  160. v-model="productForm.description"
  161. class="admin-products__textarea"
  162. placeholder="Описание товара"
  163. rows="4"
  164. )
  165. div(class="admin-products__form-group")
  166. label(class="admin-products__label") Изображение товара
  167. div(class="admin-products__image-upload")
  168. div(v-if="productForm.image" class="admin-products__image-preview")
  169. img(:src="productForm.image" :alt="productForm.name" class="admin-products__preview-image")
  170. button(
  171. @click="removeProductImage"
  172. class="admin-products__btn admin-products__btn--danger"
  173. ) Удалить
  174. div(v-else class="admin-products__image-placeholder") Изображение не загружено
  175. input(
  176. type="file"
  177. ref="productImageInput"
  178. @change="onProductImageUpload"
  179. accept="image/*"
  180. class="admin-products__file-input"
  181. id="product-image-upload"
  182. )
  183. label(for="product-image-upload" class="admin-products__btn admin-products__btn--secondary") Выбрать изображение
  184. div(class="admin-products__form-group")
  185. label(class="admin-products__checkbox-label")
  186. input(
  187. v-model="productForm.active"
  188. type="checkbox"
  189. class="admin-products__checkbox"
  190. )
  191. span Активный товар
  192. div(class="admin-products__form-group")
  193. label(class="admin-products__label") Домены
  194. div(class="admin-products__domains-list")
  195. label(
  196. v-for="domain in availableDomains"
  197. :key="domain._id"
  198. class="admin-products__domain-label"
  199. )
  200. input(
  201. type="checkbox"
  202. :value="domain.domain"
  203. v-model="productForm.domains"
  204. class="admin-products__domain-checkbox"
  205. )
  206. span {{ domain.domain }}
  207. div(class="admin-products__modal-actions")
  208. button(
  209. @click="saveProduct"
  210. class="admin-products__btn admin-products__btn--primary"
  211. ) {{ editingProduct ? 'Обновить' : 'Создать' }}
  212. button(
  213. @click="showProductModal = false"
  214. class="admin-products__btn admin-products__btn--secondary"
  215. ) Отмена
  216. // Модальное окно импорта
  217. div(v-if="showImportModal" class="admin-products__modal")
  218. div(class="admin-products__modal-content")
  219. h3(class="admin-products__modal-title") Импорт товаров из CSV
  220. div(class="admin-products__import-form")
  221. div(class="admin-products__form-group")
  222. label(class="admin-products__label") Выберите CSV файл
  223. input(
  224. type="file"
  225. @change="onFileSelect"
  226. accept=".csv"
  227. class="admin-products__file-input"
  228. )
  229. p(class="admin-products__help-text") Поддерживается формат CSV с разделителем ; и кодировкой UTF-8
  230. div(v-if="selectedFile" class="admin-products__file-info")
  231. p Выбран файл: {{ selectedFile.name }}
  232. button(
  233. @click="importProducts"
  234. :disabled="importing"
  235. class="admin-products__btn admin-products__btn--primary"
  236. ) {{ importing ? 'Импорт...' : 'Начать импорт' }}
  237. div(v-if="importResults" class="admin-products__import-results")
  238. h4(v-if="importResults.success" class="admin-products__success") Импорт успешно завершен!
  239. h4(v-else class="admin-products__error") Ошибка импорта
  240. p Обработано товаров: {{ importResults.processed }}
  241. p(v-if="importResults.errors && importResults.errors.length")
  242. strong Ошибки:
  243. ul
  244. li(v-for="error in importResults.errors" :key="error") {{ error }}
  245. p(v-if="importResults.error") Ошибка: {{ importResults.error }}
  246. div(class="admin-products__modal-actions")
  247. button(
  248. @click="showImportModal = false"
  249. class="admin-products__btn admin-products__btn--secondary"
  250. ) Закрыть
  251. // Модальное окно управления категориями
  252. div(v-if="showCategoriesModal" class="admin-products__modal")
  253. div(class="admin-products__modal-content admin-products__modal-content--large")
  254. h3(class="admin-products__modal-title") Управление категориями
  255. div(class="admin-products__categories-tabs")
  256. button(
  257. @click="categoriesActiveTab = 'list'"
  258. :class="getCategoriesTabClass('list')"
  259. ) Список категорий
  260. button(
  261. @click="categoriesActiveTab = 'import'"
  262. :class="getCategoriesTabClass('import')"
  263. ) Импорт категорий
  264. // Список категорий
  265. div(v-if="categoriesActiveTab === 'list'" class="admin-products__categories-list")
  266. div(class="admin-products__categories-header")
  267. button(
  268. @click="showCategoryModal = true"
  269. class="admin-products__btn admin-products__btn--primary"
  270. ) Добавить категорию
  271. div(class="admin-products__categories-grid")
  272. div(
  273. v-for="category in categories"
  274. :key="category._id"
  275. class="admin-products__category-item"
  276. )
  277. div(class="admin-products__category-preview")
  278. img(
  279. v-if="category.image"
  280. :src="category.image"
  281. :alt="category.name"
  282. class="admin-products__category-image"
  283. )
  284. div(v-else class="admin-products__category-no-image") Нет изображения
  285. div(class="admin-products__category-info")
  286. h4(class="admin-products__category-name") {{ category.name }}
  287. p(class="admin-products__category-description") {{ category.description || 'Без описания' }}
  288. div(class="admin-products__category-meta")
  289. span(class="admin-products__category-products") Товаров: {{ getCategoryProductCount(category._id) }}
  290. span(
  291. :class="getStatusClass(category.active)"
  292. ) {{ category.active ? 'Активна' : 'Неактивна' }}
  293. div(class="admin-products__category-actions")
  294. button(
  295. @click="editCategory(category)"
  296. class="admin-products__btn admin-products__btn--edit"
  297. ) Редактировать
  298. button(
  299. @click="deleteCategory(category._id)"
  300. class="admin-products__btn admin-products__btn--delete"
  301. ) Удалить
  302. // Импорт категорий
  303. div(v-if="categoriesActiveTab === 'import'" class="admin-products__categories-import")
  304. div(class="admin-products__form-group")
  305. label(class="admin-products__label") Выберите CSV файл категорий
  306. input(
  307. type="file"
  308. @change="onCategoriesFileSelect"
  309. accept=".csv"
  310. class="admin-products__file-input"
  311. )
  312. p(class="admin-products__help-text") Формат CSV с полями: name, slug, description, parentCategory, sortOrder, active
  313. div(v-if="selectedCategoriesFile" class="admin-products__file-info")
  314. p Выбран файл: {{ selectedCategoriesFile.name }}
  315. button(
  316. @click="importCategories"
  317. :disabled="importingCategories"
  318. class="admin-products__btn admin-products__btn--primary"
  319. ) {{ importingCategories ? 'Импорт...' : 'Импорт категорий' }}
  320. div(v-if="categoriesImportResults" class="admin-products__import-results")
  321. h4(v-if="categoriesImportResults.success" class="admin-products__success") Импорт категорий завершен!
  322. h4(v-else class="admin-products__error") Ошибка импорта категорий
  323. p Обработано категорий: {{ categoriesImportResults.processed }}
  324. p(v-if="categoriesImportResults.errors && categoriesImportResults.errors.length")
  325. strong Ошибки:
  326. ul
  327. li(v-for="error in categoriesImportResults.errors" :key="error") {{ error }}
  328. div(class="admin-products__modal-actions")
  329. button(
  330. @click="showCategoriesModal = false"
  331. class="admin-products__btn admin-products__btn--secondary"
  332. ) Закрыть
  333. // Модальное окно редактирования категории
  334. div(v-if="showCategoryModal" class="admin-products__modal")
  335. div(class="admin-products__modal-content")
  336. h3(class="admin-products__modal-title") {{ editingCategory ? 'Редактирование' : 'Добавление' }} категории
  337. div(class="admin-products__modal-form")
  338. div(class="admin-products__form-group")
  339. label(class="admin-products__label") Название категории *
  340. input(
  341. v-model="categoryForm.name"
  342. type="text"
  343. class="admin-products__input"
  344. placeholder="Введите название категории"
  345. required
  346. )
  347. div(class="admin-products__form-group")
  348. label(class="admin-products__label") URL slug *
  349. input(
  350. v-model="categoryForm.slug"
  351. type="text"
  352. class="admin-products__input"
  353. placeholder="url-slug"
  354. required
  355. )
  356. div(class="admin-products__form-group")
  357. label(class="admin-products__label") Описание
  358. textarea(
  359. v-model="categoryForm.description"
  360. class="admin-products__textarea"
  361. placeholder="Описание категории"
  362. rows="3"
  363. )
  364. div(class="admin-products__form-group")
  365. label(class="admin-products__label") Родительская категория
  366. select(v-model="categoryForm.parentCategory" class="admin-products__select")
  367. option(value="") Без родительской категории
  368. option(
  369. v-for="cat in categories.filter(c => c._id !== editingCategory?._id)"
  370. :key="cat._id"
  371. :value="cat._id"
  372. ) {{ cat.name }}
  373. div(class="admin-products__form-group")
  374. label(class="admin-products__label") Порядок сортировки
  375. input(
  376. v-model="categoryForm.sortOrder"
  377. type="number"
  378. class="admin-products__input"
  379. placeholder="0"
  380. min="0"
  381. )
  382. div(class="admin-products__form-group")
  383. label(class="admin-products__label") Изображение категории
  384. div(class="admin-products__image-upload")
  385. div(v-if="categoryForm.image" class="admin-products__image-preview")
  386. img(:src="categoryForm.image" :alt="categoryForm.name" class="admin-products__preview-image")
  387. button(
  388. @click="removeCategoryImage"
  389. class="admin-products__btn admin-products__btn--danger"
  390. ) Удалить
  391. div(v-else class="admin-products__image-placeholder") Изображение не загружено
  392. input(
  393. type="file"
  394. ref="categoryImageInput"
  395. @change="onCategoryImageUpload"
  396. accept="image/*"
  397. class="admin-products__file-input"
  398. id="category-image-upload"
  399. )
  400. label(for="category-image-upload" class="admin-products__btn admin-products__btn--secondary") Выбрать изображение
  401. div(class="admin-products__form-group")
  402. label(class="admin-products__label") Иконка для меню
  403. div(class="admin-products__image-upload")
  404. div(v-if="categoryForm.icon" class="admin-products__image-preview admin-products__image-preview--small")
  405. img(:src="categoryForm.icon" :alt="categoryForm.name" class="admin-products__preview-image")
  406. button(
  407. @click="removeCategoryIcon"
  408. class="admin-products__btn admin-products__btn--danger"
  409. ) Удалить
  410. div(v-else class="admin-products__image-placeholder") Иконка не загружена
  411. input(
  412. type="file"
  413. ref="categoryIconInput"
  414. @change="onCategoryIconUpload"
  415. accept="image/*"
  416. class="admin-products__file-input"
  417. id="category-icon-upload"
  418. )
  419. label(for="category-icon-upload" class="admin-products__btn admin-products__btn--secondary") Выбрать иконку
  420. div(class="admin-products__form-group")
  421. label(class="admin-products__checkbox-label")
  422. input(
  423. v-model="categoryForm.active"
  424. type="checkbox"
  425. class="admin-products__checkbox"
  426. )
  427. span Активная категория
  428. div(class="admin-products__form-group")
  429. label(class="admin-products__label") Домены
  430. div(class="admin-products__domains-list")
  431. label(
  432. v-for="domain in availableDomains"
  433. :key="domain._id"
  434. class="admin-products__domain-label"
  435. )
  436. input(
  437. type="checkbox"
  438. :value="domain.domain"
  439. v-model="categoryForm.domains"
  440. class="admin-products__domain-checkbox"
  441. )
  442. span {{ domain.domain }}
  443. div(class="admin-products__modal-actions")
  444. button(
  445. @click="saveCategory"
  446. class="admin-products__btn admin-products__btn--primary"
  447. ) {{ editingCategory ? 'Обновить' : 'Создать' }}
  448. button(
  449. @click="showCategoryModal = false"
  450. class="admin-products__btn admin-products__btn--secondary"
  451. ) Отмена