index.vue 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. <template>
  2. <div class="page-container">
  3. <div class="page-header">
  4. <PageTitle title="我的收藏" />
  5. <el-button type="danger" link @click="handleAddCategory"
  6. ><el-icon><Plus /></el-icon>添加分类</el-button
  7. >
  8. </div>
  9. <!-- 分类Tab -->
  10. <StatusTabs v-model="activeCategory" :tabs="categoryTabs" type="pill" />
  11. <!-- 操作栏 -->
  12. <div class="action-bar">
  13. <el-checkbox v-model="selectAll" @change="handleSelectAll">全选</el-checkbox>
  14. <el-button type="danger" link @click="handleBatchAddCart">加入购物车</el-button>
  15. <el-button type="danger" link @click="handleBatchCancel">取消收藏</el-button>
  16. </div>
  17. <!-- 商品列表 -->
  18. <div class="product-grid">
  19. <ProductCard
  20. v-for="(item, index) in productList"
  21. :key="index"
  22. :product="item"
  23. v-model="item.checked"
  24. show-checkbox
  25. show-action
  26. show-add-cart
  27. action-text="取消收藏"
  28. @action="handleCancelCollection(item)"
  29. @add-cart="handleAddCart(item)"
  30. />
  31. </div>
  32. <el-empty v-if="productList.length === 0" description="暂无收藏商品" />
  33. <TablePagination
  34. v-if="productList.length > 0"
  35. v-model:page="queryParams.pageNum"
  36. v-model:page-size="queryParams.pageSize"
  37. :total="total"
  38. @change="handleQuery"
  39. />
  40. <!-- 添加分类弹窗 -->
  41. <el-dialog v-model="categoryDialogVisible" title="添加分类" width="400px">
  42. <el-form ref="categoryFormRef" :model="categoryForm" :rules="categoryRules">
  43. <el-form-item prop="name"><el-input v-model="categoryForm.name" placeholder="请输入分类名称" /></el-form-item>
  44. </el-form>
  45. <template #footer>
  46. <el-button @click="categoryDialogVisible = false">取消</el-button>
  47. <el-button type="danger" @click="handleSaveCategory">确定</el-button>
  48. </template>
  49. </el-dialog>
  50. </div>
  51. </template>
  52. <script setup lang="ts">
  53. import { ref, reactive, watch, onMounted } from 'vue';
  54. import { Plus } from '@element-plus/icons-vue';
  55. import { ElMessage, ElMessageBox, type CheckboxValueType } from 'element-plus';
  56. import { PageTitle, StatusTabs, ProductCard, TablePagination } from '@/components';
  57. import { favoritesList, favoritesProductList, cancelProductCollect, addProductShoppingCart } from '@/api/goods/index';
  58. const activeCategory = ref('all');
  59. const selectAll = ref(false);
  60. const categoryDialogVisible = ref(false);
  61. const categoryFormRef = ref();
  62. const loading = ref(false);
  63. const categoryTabs = ref<any[]>([{ key: 'all', label: '全部' }]);
  64. const categoryForm = reactive({ name: '' });
  65. const categoryRules = { name: [{ required: true, message: '请输入分类名称', trigger: 'blur' }] };
  66. const queryParams = reactive({ pageNum: 1, pageSize: 15, favoritesId: '' as any });
  67. const total = ref(0);
  68. const productList = ref<any[]>([]);
  69. /** 获取收藏夹列表(分类Tab) */
  70. const getFavoritesTabs = () => {
  71. favoritesList({}).then((res: any) => {
  72. if (res.code == 200) {
  73. const tabs = [{ key: 'all', label: '全部' }];
  74. if (res.rows && res.rows.length > 0) {
  75. res.rows.forEach((item: any) => {
  76. tabs.push({ key: String(item.id), label: item.title || '未命名' });
  77. });
  78. }
  79. categoryTabs.value = tabs;
  80. }
  81. });
  82. };
  83. /** 获取收藏商品列表 */
  84. const getProductList = () => {
  85. loading.value = true;
  86. const params: any = { pageNum: queryParams.pageNum, pageSize: queryParams.pageSize };
  87. if (activeCategory.value !== 'all') {
  88. params.favoritesId = activeCategory.value;
  89. }
  90. favoritesProductList(params)
  91. .then((res: any) => {
  92. if (res.code == 200) {
  93. productList.value = (res.rows || []).map((item: any) => ({
  94. ...item,
  95. name: item.itemName || item.productName || item.name || '',
  96. image: item.productImage || item.image || '',
  97. price: item.price || item.memberPrice || item.minSellingPrice || '',
  98. originalPrice: item.marketPrice || item.originalPrice || '',
  99. tag: item.tag || '',
  100. checked: false
  101. }));
  102. total.value = res.total || 0;
  103. }
  104. })
  105. .finally(() => {
  106. loading.value = false;
  107. });
  108. };
  109. const handleQuery = () => {
  110. selectAll.value = false;
  111. getProductList();
  112. };
  113. watch(activeCategory, () => {
  114. queryParams.pageNum = 1;
  115. handleQuery();
  116. });
  117. onMounted(() => {
  118. getFavoritesTabs();
  119. getProductList();
  120. });
  121. const handleSelectAll = (val: CheckboxValueType) => {
  122. productList.value.forEach((item) => {
  123. item.checked = !!val;
  124. });
  125. };
  126. const handleAddCategory = () => {
  127. categoryForm.name = '';
  128. categoryDialogVisible.value = true;
  129. };
  130. const handleSaveCategory = async () => {
  131. const valid = await categoryFormRef.value?.validate();
  132. if (!valid) return;
  133. // TODO: 调用后端新增收藏夹接口
  134. categoryTabs.value.push({ key: categoryForm.name, label: categoryForm.name });
  135. ElMessage.success('添加成功');
  136. categoryDialogVisible.value = false;
  137. getFavoritesTabs();
  138. };
  139. /** 单个加入购物车 */
  140. const handleAddCart = (item: any) => {
  141. addProductShoppingCart({ productId: item.id, productNum: 1 }).then((res: any) => {
  142. if (res.code == 200) {
  143. ElMessage.success('已加入购物车');
  144. }
  145. });
  146. };
  147. /** 批量加入购物车 */
  148. const handleBatchAddCart = () => {
  149. const selected = productList.value.filter((item) => item.checked);
  150. if (selected.length === 0) {
  151. ElMessage.warning('请先选择商品');
  152. return;
  153. }
  154. const promises = selected.map((item) => addProductShoppingCart({ productId: item.id, productNum: 1 }));
  155. Promise.all(promises).then(() => {
  156. ElMessage.success(`已将${selected.length}件商品加入购物车`);
  157. });
  158. };
  159. /** 单个取消收藏 */
  160. const handleCancelCollection = (item: any) => {
  161. ElMessageBox.confirm('确定要取消收藏该商品吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
  162. const params: any = { productId: item.id || item.productId };
  163. if (activeCategory.value !== 'all') {
  164. params.favoritesId = activeCategory.value;
  165. }
  166. cancelProductCollect(params).then((res: any) => {
  167. if (res.code == 200) {
  168. ElMessage.success('已取消收藏');
  169. getProductList();
  170. }
  171. });
  172. });
  173. };
  174. /** 批量取消收藏 */
  175. const handleBatchCancel = () => {
  176. const selected = productList.value.filter((item) => item.checked);
  177. if (selected.length === 0) {
  178. ElMessage.warning('请先选择商品');
  179. return;
  180. }
  181. ElMessageBox.confirm(`确定要取消收藏选中的${selected.length}件商品吗?`, '提示', {
  182. confirmButtonText: '确定',
  183. cancelButtonText: '取消',
  184. type: 'warning'
  185. }).then(() => {
  186. const promises = selected.map((item) => {
  187. const params: any = { productId: item.id || item.productId };
  188. if (activeCategory.value !== 'all') {
  189. params.favoritesId = activeCategory.value;
  190. }
  191. return cancelProductCollect(params);
  192. });
  193. Promise.all(promises).then(() => {
  194. selectAll.value = false;
  195. ElMessage.success('已取消收藏');
  196. getProductList();
  197. });
  198. });
  199. };
  200. </script>
  201. <style scoped lang="scss">
  202. .page-header {
  203. display: flex;
  204. justify-content: space-between;
  205. align-items: center;
  206. margin-bottom: 20px;
  207. :deep(.page-title) {
  208. margin-bottom: 0;
  209. }
  210. }
  211. .action-bar {
  212. display: flex;
  213. align-items: center;
  214. gap: 20px;
  215. padding: 10px 0;
  216. border-bottom: 1px solid #eee;
  217. margin-bottom: 15px;
  218. }
  219. .product-grid {
  220. display: grid;
  221. grid-template-columns: repeat(4, 1fr);
  222. gap: 15px;
  223. }
  224. </style>