Переглянути джерело

feat(goods): 添加商品浏览记录和收藏功能

- 新增删除浏览记录接口 deleteProductBrowsingHistory
- 新增查看商品浏览记录列表接口 browsingHistoryList
- 更新收藏列表接口地址至 /product/myProduct/getProductFavoritesPage
- 新增查询商品收藏夹下商品接口 favoritesProductList
- 新增查看协议供货商品列表接口 getAgreementSupplyProductList
- 在协议供货页面实现动态分类筛选功能
- 在我的收藏页面实现真实的收藏商品数据展示
- 在我的足迹页面集成真实的浏览历史数据操作
- 添加PC商品信息和商品收藏夹类型定义
- 实现浏览记录的分组显示和批量删除功能
肖路 2 місяців тому
батько
коміт
51eb30521a

+ 33 - 1
src/api/goods/index.ts

@@ -45,6 +45,21 @@ export const addProductBrowsingHistory = (id: any) => {
     method: 'post'
   });
 };
+//删除浏览记录
+export const deleteProductBrowsingHistory = (ids: any) => {
+  return request({
+    url: '/product/myProduct/deleteProductBrowsingHistory/' + ids,
+    method: 'delete'
+  });
+};
+//查看商品浏览记录列表
+export const browsingHistoryList = (query: any) => {
+  return request({
+    url: '/product/myProduct/getProductBrowsingHistoryPage',
+    method: 'get',
+    params: query
+  });
+};
 
 //查询商品是否在默认收藏夹收藏
 
@@ -69,7 +84,7 @@ export const addProductCollect = (params: any) => {
 
 export const favoritesList = (params: any) => {
   return request({
-    url: '/product/favorites/list',
+    url: '/product/myProduct/getProductFavoritesPage',
     method: 'get'
   });
 };
@@ -93,3 +108,20 @@ export const pcOrdersubmit = (params: any) => {
     data: params
   });
 };
+//查询商品收藏夹下的商品
+export const favoritesProductList = (query: any) => {
+  return request({
+    url: '/product/myProduct/getFavoritesProductPage',
+    method: 'get',
+    params: query
+  });
+};
+
+//查看协议供货商品列表
+export const getAgreementSupplyProductList = (query: any) => {
+  return request({
+    url: '/product/myProduct/getAgreementSupplyProductPage',
+    method: 'get',
+    params: query
+  });
+};

+ 9 - 0
src/api/home/index.ts

@@ -182,3 +182,12 @@ export function getProjectCaseList(query: any) {
     params: query
   });
 }
+
+//查看分类列表
+export function getProductCategoryList(query: any) {
+  return request({
+    url: '/product/indexProduct/getProductCategoryList',
+    method: 'get',
+    params: query
+  });
+}

+ 57 - 0
src/api/pc/enterprise/types.ts

@@ -66,3 +66,60 @@ export interface PurchaseHabit {
   remark?: string;
   [key: string]: any;
 }
+
+/**
+ * PC商品信息
+ */
+export interface PcProduct {
+  /** 商品ID */
+  id?: number | string;
+  /** 产品名称 */
+  itemName?: string;
+  /** 产品编号 */
+  productNo?: string;
+  /** 商品图片 */
+  productImage?: string;
+  /** 商品价格 */
+  price?: string;
+  /** 单位名称(如:件、箱、千克等) */
+  unitName?: string;
+  /** 市场价 */
+  marketPrice?: number;
+  /** 会员价格 */
+  memberPrice?: number;
+  /** 最低销售价格 */
+  minSellingPrice?: number;
+  /** 最低起订量 */
+  minOrderQuantity?: number;
+  /** 产品数量 */
+  productNum?: number;
+  /** 产品状态 0-下架 1-上架 2 上架中 */
+  productStatus?: string;
+  /** 产品库存 */
+  totalInventory?: number;
+  /** 购物车id */
+  shoppingCartId?: number | string;
+  [key: string]: any;
+}
+
+/**
+ * 商品收藏夹信息
+ */
+export interface ProductFavorites {
+  /** 收藏夹ID */
+  id?: number | string;
+  /** 收藏夹名称 */
+  title?: string;
+  /** 是否默认 0是 1否 */
+  isDefault?: string;
+  /** 用户id */
+  userId?: number;
+  /** 客户id */
+  customerId?: number;
+  /** 备注 */
+  remark?: string;
+  [key: string]: any;
+}
+
+
+

+ 226 - 37
src/views/enterprise/agreementSupply/index.vue

@@ -4,7 +4,9 @@
     <!-- 搜索栏 -->
     <div class="search-bar">
       <el-input v-model="queryParams.keyword" placeholder="搜索" style="width: 180px" clearable>
-        <template #prefix><el-icon><Search /></el-icon></template>
+        <template #prefix
+          ><el-icon><Search /></el-icon
+        ></template>
       </el-input>
       <div class="price-range">
         <el-input v-model="queryParams.minPrice" placeholder="¥ 最高价" style="width: 100px" />
@@ -12,7 +14,7 @@
         <el-input v-model="queryParams.maxPrice" placeholder="¥ 最低价" style="width: 100px" />
       </div>
       <el-select v-model="queryParams.category" placeholder="商品类别" style="width: 100px" clearable>
-        <el-option label="空调" value="空调" /><el-option label="电脑" value="电脑" /><el-option label="办公设备" value="办公设备" />
+        <el-option v-for="item in categoryList" :key="item.id" :label="item.categoryName" :value="item.id" />
       </el-select>
     </div>
     <!-- 排序栏 -->
@@ -26,59 +28,246 @@
     </div>
     <!-- 商品列表 -->
     <div class="product-grid">
-      <div v-for="(item, index) in productList" :key="index" class="product-card">
+      <div v-for="(item, index) in productList" :key="item.id || index" class="product-card">
         <div class="product-image">
-          <el-image :src="item.image" fit="contain">
-            <template #error><div class="image-placeholder"><el-icon :size="40" color="#ccc"><Picture /></el-icon></div></template>
+          <el-image :src="item.productImage" fit="contain">
+            <template #error
+              ><div class="image-placeholder">
+                <el-icon :size="40" color="#ccc"><Picture /></el-icon></div
+            ></template>
           </el-image>
         </div>
         <div class="product-info">
-          <div class="product-name">{{ item.name }}</div>
+          <div class="product-name">{{ item.itemName }}</div>
           <div class="product-price">
-            <span v-if="item.tag" class="price-tag">{{ item.tag }}</span>
-            <span class="current-price">¥{{ item.price }}</span>
-            <span class="original-price">¥{{ item.originalPrice }}</span>
-            <div class="add-cart" @click="handleAddCart(item)"><el-icon><Plus /></el-icon></div>
+            <span class="price-tag">协议价</span>
+            <span class="current-price">¥{{ formatPrice(item.agreementPrice || item.price) }}</span>
+            <span class="original-price">¥{{ formatPrice(item.marketPrice) }}</span>
+            <div class="add-cart" @click="handleAddCart(item)">
+              <el-icon><Plus /></el-icon>
+            </div>
           </div>
         </div>
       </div>
     </div>
     <el-empty v-if="productList.length === 0" description="暂无协议供货商品" />
-    <TablePagination v-if="productList.length > 0" v-model:page="queryParams.pageNum" v-model:page-size="queryParams.pageSize" :total="total" @change="handleQuery" />
+    <TablePagination
+      v-if="productList.length > 0"
+      v-model:page="queryParams.pageNum"
+      v-model:page-size="queryParams.pageSize"
+      :total="total"
+      @change="handleQuery"
+    />
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive } from 'vue'
-import { Search, Picture, Plus } from '@element-plus/icons-vue'
-import { ElMessage } from 'element-plus'
-import { PageTitle, TablePagination } from '@/components'
+import { ref, reactive, onMounted } from 'vue';
+import { Search, Picture, Plus } from '@element-plus/icons-vue';
+import { ElMessage } from 'element-plus';
+import { PageTitle, TablePagination } from '@/components';
+import { getAgreementSupplyProductList } from '@/api/goods/index';
+import { getProductCategoryList } from '@/api/home/index';
 
-const queryParams = reactive({ pageNum: 1, pageSize: 15, keyword: '', minPrice: '', maxPrice: '', category: '', sortType: 'default', priceSort: '' })
-const total = ref(100)
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 15,
+  keyword: '',
+  minPrice: '',
+  maxPrice: '',
+  category: '',
+  sortType: 'default',
+  priceSort: ''
+});
+const total = ref(0);
 
-const productList = ref([
-  { id: 1, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '协议价', image: '' },
-  { id: 2, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '' },
-  { id: 3, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '' },
-  { id: 4, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '' },
-  { id: 5, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '' },
-  { id: 6, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '' },
-  { id: 7, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '' },
-  { id: 8, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '' },
-  { id: 9, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '' },
-  { id: 10, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '' }
-])
+interface ProductItem {
+  id?: number | string;
+  itemName?: string;
+  productImage?: string;
+  price?: string | number;
+  agreementPrice?: string | number;
+  memberPrice?: string | number;
+  [key: string]: any;
+}
 
-const handleQuery = () => {}
-const handleAddCart = (_item: any) => { ElMessage.success('已加入购物车') }
-</script>
+const productList = ref<ProductItem[]>([]);
 
-<style scoped lang="scss">
+// 商品类别列表
+interface CategoryItem {
+  id?: number | string;
+  categoryName?: string;
+  [key: string]: any;
+}
+
+const categoryList = ref<CategoryItem[]>([]);
+
+// 加载商品类别列表
+const loadCategoryList = async () => {
+  try {
+    const res = await getProductCategoryList({ classLevel: 1 });
+    if (res.data) {
+      categoryList.value = res.data;
+    }
+  } catch (error) {
+    console.error('获取商品类别失败:', error);
+    ElMessage.error('获取商品类别失败');
+  }
+};
+
+// 加载协议供货商品列表
+const loadProductList = async () => {
+  try {
+    const res = await getAgreementSupplyProductList({
+      ...queryParams,
+      // 将价格范围转换为后端需要的格式
+      priceRange:
+        queryParams.minPrice && queryParams.maxPrice
+          ? `${queryParams.minPrice}-${queryParams.maxPrice}`
+          : queryParams.minPrice || queryParams.maxPrice || undefined
+    });
+
+    if (res.rows) {
+      productList.value = res.rows;
+      total.value = res.total;
+    }
+  } catch (error) {
+    console.error('获取协议供货商品失败:', error);
+    ElMessage.error('获取商品列表失败');
+  }
+};
 
-.search-bar { display: flex; align-items: center; gap: 15px; margin-bottom: 15px; .price-range { display: flex; align-items: center; gap: 5px; .range-separator { color: #999; } } }
-.sort-bar { display: flex; gap: 10px; margin-bottom: 20px; }
-.product-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 15px; }
-.product-card { background: #fff; border-radius: 8px; overflow: hidden; transition: all 0.2s; &:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } .product-image { height: 160px; background: #f9f9f9; display: flex; align-items: center; justify-content: center; padding: 10px; .el-image { width: 100%; height: 100%; } .image-placeholder { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: #f5f5f5; } } .product-info { padding: 12px; .product-name { font-size: 13px; color: #333; line-height: 1.4; height: 36px; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; margin-bottom: 8px; } .product-price { display: flex; align-items: center; gap: 5px; position: relative; .price-tag { font-size: 12px; color: #e60012; } .current-price { font-size: 16px; font-weight: bold; color: #e60012; } .original-price { font-size: 12px; color: #999; text-decoration: line-through; } .add-cart { position: absolute; right: 0; bottom: 0; width: 24px; height: 24px; border-radius: 50%; border: 1px solid #e60012; color: #e60012; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s; &:hover { background: #e60012; color: #fff; } } } } }
+// 查询处理
+const handleQuery = () => {
+  queryParams.pageNum = 1;
+  loadProductList();
+};
 
+// 添加购物车
+const handleAddCart = (item: ProductItem) => {
+  console.log('添加到购物车:', item);
+  ElMessage.success('已加入购物车');
+};
+
+// 格式化价格显示
+const formatPrice = (price: string | number | undefined): string => {
+  if (price === undefined || price === null) return '0';
+  const numPrice = typeof price === 'string' ? parseFloat(price) : price;
+  return numPrice.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
+};
+
+// 初始化加载
+onMounted(() => {
+  loadCategoryList();
+  loadProductList();
+});
+</script>
+
+<style scoped lang="scss">
+.search-bar {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  margin-bottom: 15px;
+  .price-range {
+    display: flex;
+    align-items: center;
+    gap: 5px;
+    .range-separator {
+      color: #999;
+    }
+  }
+}
+.sort-bar {
+  display: flex;
+  gap: 10px;
+  margin-bottom: 20px;
+}
+.product-grid {
+  display: grid;
+  grid-template-columns: repeat(5, 1fr);
+  gap: 15px;
+}
+.product-card {
+  background: #fff;
+  border-radius: 8px;
+  overflow: hidden;
+  transition: all 0.2s;
+  &:hover {
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+  }
+  .product-image {
+    height: 160px;
+    background: #f9f9f9;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 10px;
+    .el-image {
+      width: 100%;
+      height: 100%;
+    }
+    .image-placeholder {
+      width: 100%;
+      height: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      background: #f5f5f5;
+    }
+  }
+  .product-info {
+    padding: 12px;
+    .product-name {
+      font-size: 13px;
+      color: #333;
+      line-height: 1.4;
+      height: 36px;
+      overflow: hidden;
+      display: -webkit-box;
+      -webkit-line-clamp: 2;
+      -webkit-box-orient: vertical;
+      margin-bottom: 8px;
+    }
+    .product-price {
+      display: flex;
+      align-items: center;
+      gap: 5px;
+      position: relative;
+      .price-tag {
+        font-size: 12px;
+        color: #e60012;
+      }
+      .current-price {
+        font-size: 16px;
+        font-weight: bold;
+        color: #e60012;
+      }
+      .original-price {
+        font-size: 12px;
+        color: #999;
+        text-decoration: line-through;
+      }
+      .add-cart {
+        position: absolute;
+        right: 0;
+        bottom: 0;
+        width: 24px;
+        height: 24px;
+        border-radius: 50%;
+        border: 1px solid #e60012;
+        color: #e60012;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        cursor: pointer;
+        transition: all 0.2s;
+        &:hover {
+          background: #e60012;
+          color: #fff;
+        }
+      }
+    }
+  }
+}
 </style>

+ 187 - 40
src/views/enterprise/myCollection/index.vue

@@ -2,7 +2,9 @@
   <div class="page-container">
     <div class="page-header">
       <PageTitle title="我的收藏" />
-      <el-button type="danger" link @click="handleAddCategory"><el-icon><Plus /></el-icon>添加分类</el-button>
+      <el-button type="danger" link @click="handleAddCategory"
+        ><el-icon><Plus /></el-icon>添加分类</el-button
+      >
     </div>
     <!-- 分类Tab -->
     <StatusTabs v-model="activeCategory" :tabs="categoryTabs" type="pill" />
@@ -28,7 +30,13 @@
       />
     </div>
     <el-empty v-if="productList.length === 0" description="暂无收藏商品" />
-    <TablePagination v-if="productList.length > 0" v-model:page="queryParams.pageNum" v-model:page-size="queryParams.pageSize" :total="total" @change="handleQuery" />
+    <TablePagination
+      v-if="productList.length > 0"
+      v-model:page="queryParams.pageNum"
+      v-model:page-size="queryParams.pageSize"
+      :total="total"
+      @change="handleQuery"
+    />
     <!-- 添加分类弹窗 -->
     <el-dialog v-model="categoryDialogVisible" title="添加分类" width="400px">
       <el-form ref="categoryFormRef" :model="categoryForm" :rules="categoryRules">
@@ -43,49 +51,188 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, watch } from 'vue'
-import { Plus } from '@element-plus/icons-vue'
-import { ElMessage, ElMessageBox, type CheckboxValueType } from 'element-plus'
-import { PageTitle, StatusTabs, ProductCard, TablePagination } from '@/components'
+import { ref, reactive, watch, onMounted } from 'vue';
+import { Plus } from '@element-plus/icons-vue';
+import { ElMessage, ElMessageBox, type CheckboxValueType } from 'element-plus';
+import { PageTitle, StatusTabs, ProductCard, TablePagination } from '@/components';
+import { favoritesList, favoritesProductList, cancelProductCollect, addProductShoppingCart } from '@/api/goods/index';
 
-const activeCategory = ref('all')
-const selectAll = ref(false)
-const categoryDialogVisible = ref(false)
-const categoryFormRef = ref()
+const activeCategory = ref('all');
+const selectAll = ref(false);
+const categoryDialogVisible = ref(false);
+const categoryFormRef = ref();
+const loading = ref(false);
 
-const categoryTabs = ref([
-  { key: 'all', label: '全部' }, { key: 'computer', label: '电脑办公(2)' }, { key: 'industrial', label: '工业品(2)' },
-  { key: 'decoration', label: '家装建材(2)' }, { key: 'appliance', label: '家用电器(2)' }, { key: 'digital', label: '数码(2)' }
-])
-const categoryForm = reactive({ name: '' })
-const categoryRules = { name: [{ required: true, message: '请输入分类名称', trigger: 'blur' }] }
-const queryParams = reactive({ pageNum: 1, pageSize: 15 })
-const total = ref(100)
+const categoryTabs = ref<any[]>([{ key: 'all', label: '全部' }]);
+const categoryForm = reactive({ name: '' });
+const categoryRules = { name: [{ required: true, message: '请输入分类名称', trigger: 'blur' }] };
+const queryParams = reactive({ pageNum: 1, pageSize: 15, favoritesId: '' as any });
+const total = ref(0);
+const productList = ref<any[]>([]);
 
-const productList = ref([
-  { id: 1, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '协议价', image: '', checked: false },
-  { id: 2, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '', checked: false },
-  { id: 3, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '', checked: false },
-  { id: 4, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '', checked: false },
-  { id: 5, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '', checked: false },
-  { id: 6, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '', checked: false },
-  { id: 7, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '', checked: false },
-  { id: 8, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '', checked: false }
-])
+/** 获取收藏夹列表(分类Tab) */
+const getFavoritesTabs = () => {
+  favoritesList({}).then((res: any) => {
+    if (res.code == 200) {
+      const tabs = [{ key: 'all', label: '全部' }];
+      if (res.rows && res.rows.length > 0) {
+        res.rows.forEach((item: any) => {
+          tabs.push({ key: String(item.id), label: item.title || '未命名' });
+        });
+      }
+      categoryTabs.value = tabs;
+    }
+  });
+};
 
-watch(activeCategory, () => { queryParams.pageNum = 1; handleQuery() })
-const handleQuery = () => {}
-const handleSelectAll = (val: CheckboxValueType) => { productList.value.forEach(item => { item.checked = !!val }) }
-const handleAddCategory = () => { categoryForm.name = ''; categoryDialogVisible.value = true }
-const handleSaveCategory = async () => { const valid = await categoryFormRef.value?.validate(); if (!valid) return; categoryTabs.value.push({ key: categoryForm.name, label: `${categoryForm.name}(0)` }); ElMessage.success('添加成功'); categoryDialogVisible.value = false }
-const handleAddCart = (_item: any) => { ElMessage.success('已加入购物车') }
-const handleBatchAddCart = () => { const selected = productList.value.filter(item => item.checked); if (selected.length === 0) { ElMessage.warning('请先选择商品'); return }; ElMessage.success(`已将${selected.length}件商品加入购物车`) }
-const handleCancelCollection = (item: any) => { ElMessageBox.confirm('确定要取消收藏该商品吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { const index = productList.value.findIndex(i => i.id === item.id); if (index > -1) productList.value.splice(index, 1); ElMessage.success('已取消收藏') }) }
-const handleBatchCancel = () => { const selected = productList.value.filter(item => item.checked); if (selected.length === 0) { ElMessage.warning('请先选择商品'); return }; ElMessageBox.confirm(`确定要取消收藏选中的${selected.length}件商品吗?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { productList.value = productList.value.filter(item => !item.checked); selectAll.value = false; ElMessage.success('已取消收藏') }) }
+/** 获取收藏商品列表 */
+const getProductList = () => {
+  loading.value = true;
+  const params: any = { pageNum: queryParams.pageNum, pageSize: queryParams.pageSize };
+  if (activeCategory.value !== 'all') {
+    params.favoritesId = activeCategory.value;
+  }
+  favoritesProductList(params)
+    .then((res: any) => {
+      if (res.code == 200) {
+        productList.value = (res.rows || []).map((item: any) => ({
+          ...item,
+          name: item.itemName || item.productName || item.name || '',
+          image: item.productImage || item.image || '',
+          price: item.price || item.memberPrice || item.minSellingPrice || '',
+          originalPrice: item.marketPrice || item.originalPrice || '',
+          tag: item.tag || '',
+          checked: false
+        }));
+        total.value = res.total || 0;
+      }
+    })
+    .finally(() => {
+      loading.value = false;
+    });
+};
+
+const handleQuery = () => {
+  selectAll.value = false;
+  getProductList();
+};
+
+watch(activeCategory, () => {
+  queryParams.pageNum = 1;
+  handleQuery();
+});
+
+onMounted(() => {
+  getFavoritesTabs();
+  getProductList();
+});
+
+const handleSelectAll = (val: CheckboxValueType) => {
+  productList.value.forEach((item) => {
+    item.checked = !!val;
+  });
+};
+const handleAddCategory = () => {
+  categoryForm.name = '';
+  categoryDialogVisible.value = true;
+};
+const handleSaveCategory = async () => {
+  const valid = await categoryFormRef.value?.validate();
+  if (!valid) return;
+  // TODO: 调用后端新增收藏夹接口
+  categoryTabs.value.push({ key: categoryForm.name, label: categoryForm.name });
+  ElMessage.success('添加成功');
+  categoryDialogVisible.value = false;
+  getFavoritesTabs();
+};
+
+/** 单个加入购物车 */
+const handleAddCart = (item: any) => {
+  addProductShoppingCart({ productId: item.id, productNum: 1 }).then((res: any) => {
+    if (res.code == 200) {
+      ElMessage.success('已加入购物车');
+    }
+  });
+};
+
+/** 批量加入购物车 */
+const handleBatchAddCart = () => {
+  const selected = productList.value.filter((item) => item.checked);
+  if (selected.length === 0) {
+    ElMessage.warning('请先选择商品');
+    return;
+  }
+  const promises = selected.map((item) => addProductShoppingCart({ productId: item.id, productNum: 1 }));
+  Promise.all(promises).then(() => {
+    ElMessage.success(`已将${selected.length}件商品加入购物车`);
+  });
+};
+
+/** 单个取消收藏 */
+const handleCancelCollection = (item: any) => {
+  ElMessageBox.confirm('确定要取消收藏该商品吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
+    const params: any = { productId: item.id || item.productId };
+    if (activeCategory.value !== 'all') {
+      params.favoritesId = activeCategory.value;
+    }
+    cancelProductCollect(params).then((res: any) => {
+      if (res.code == 200) {
+        ElMessage.success('已取消收藏');
+        getProductList();
+      }
+    });
+  });
+};
+
+/** 批量取消收藏 */
+const handleBatchCancel = () => {
+  const selected = productList.value.filter((item) => item.checked);
+  if (selected.length === 0) {
+    ElMessage.warning('请先选择商品');
+    return;
+  }
+  ElMessageBox.confirm(`确定要取消收藏选中的${selected.length}件商品吗?`, '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  }).then(() => {
+    const promises = selected.map((item) => {
+      const params: any = { productId: item.id || item.productId };
+      if (activeCategory.value !== 'all') {
+        params.favoritesId = activeCategory.value;
+      }
+      return cancelProductCollect(params);
+    });
+    Promise.all(promises).then(() => {
+      selectAll.value = false;
+      ElMessage.success('已取消收藏');
+      getProductList();
+    });
+  });
+};
 </script>
 
 <style scoped lang="scss">
-.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; :deep(.page-title) { margin-bottom: 0; } }
-.action-bar { display: flex; align-items: center; gap: 20px; padding: 10px 0; border-bottom: 1px solid #eee; margin-bottom: 15px; }
-.product-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; }
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  :deep(.page-title) {
+    margin-bottom: 0;
+  }
+}
+.action-bar {
+  display: flex;
+  align-items: center;
+  gap: 20px;
+  padding: 10px 0;
+  border-bottom: 1px solid #eee;
+  margin-bottom: 15px;
+}
+.product-grid {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 15px;
+}
 </style>

+ 106 - 21
src/views/enterprise/myFootprint/index.vue

@@ -25,39 +25,124 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive } from 'vue'
+import { ref, reactive, onMounted } from 'vue'
 import { Delete } from '@element-plus/icons-vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { PageTitle, ProductCard, TablePagination } from '@/components'
+import { browsingHistoryList, deleteProductBrowsingHistory } from '@/api/goods/index'
 
 const queryParams = reactive({ pageNum: 1, pageSize: 20 })
-const total = ref(100)
+const total = ref(0)
+const loading = ref(false)
+const footprintGroups = ref<any[]>([])
 
-const footprintGroups = ref([
-  { date: '12月10日', label: '昨天', products: [
-    { id: 1, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '协议价', image: '', checked: true },
-    { id: 2, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '', checked: false },
-    { id: 3, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '', checked: false },
-    { id: 4, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '', checked: false },
-    { id: 5, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '', checked: false }
-  ]},
-  { date: '12月07日', label: '', products: [
-    { id: 6, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '协议价', image: '', checked: true },
-    { id: 7, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '', checked: true },
-    { id: 8, name: '格力KFR-72LW/定频冷暖空调柜机3P', price: '1,299', originalPrice: '1,899', tag: '', image: '', checked: false }
-  ]}
-])
+/** 根据日期生成标签(今天/昨天/空) */
+const getDateLabel = (dateStr: string) => {
+  const today = new Date()
+  const target = new Date(dateStr)
+  today.setHours(0, 0, 0, 0)
+  target.setHours(0, 0, 0, 0)
+  const diff = today.getTime() - target.getTime()
+  const oneDay = 24 * 60 * 60 * 1000
+  if (diff === 0) return '今天'
+  if (diff === oneDay) return '昨天'
+  return ''
+}
+
+/** 格式化日期为 MM月DD日 */
+const formatDate = (dateStr: string) => {
+  const d = new Date(dateStr)
+  return `${d.getMonth() + 1}月${String(d.getDate()).padStart(2, '0')}日`
+}
+
+/** 将列表按日期分组 */
+const groupByDate = (list: any[]) => {
+  const map = new Map<string, any[]>()
+  list.forEach((item: any) => {
+    const dateKey = (item.createTime || item.browseTime || '').substring(0, 10)
+    if (!map.has(dateKey)) map.set(dateKey, [])
+    map.get(dateKey)!.push(item)
+  })
+  const groups: any[] = []
+  map.forEach((products, dateKey) => {
+    groups.push({
+      date: formatDate(dateKey),
+      label: getDateLabel(dateKey),
+      products
+    })
+  })
+  return groups
+}
+
+/** 获取浏览记录列表 */
+const getFootprintList = () => {
+  loading.value = true
+  browsingHistoryList({ pageNum: queryParams.pageNum, pageSize: queryParams.pageSize })
+    .then((res: any) => {
+      if (res.code == 200) {
+        const list = (res.rows || []).map((item: any) => ({
+          ...item,
+          name: item.itemName || item.productName || item.name || '',
+          image: item.productImage || item.image || '',
+          price: item.price || item.memberPrice || item.minSellingPrice || '',
+          originalPrice: item.marketPrice || item.originalPrice || '',
+          tag: item.tag || '',
+          checked: false
+        }))
+        footprintGroups.value = groupByDate(list)
+        total.value = res.total || 0
+      }
+    })
+    .finally(() => {
+      loading.value = false
+    })
+}
+
+const handleQuery = () => {
+  getFootprintList()
+}
+
+/** 获取所有选中的商品 */
+const getSelectedItems = () => {
+  const selected: any[] = []
+  footprintGroups.value.forEach(group => {
+    group.products.forEach((item: any) => {
+      if (item.checked) selected.push(item)
+    })
+  })
+  return selected
+}
 
-const handleQuery = () => {}
 const handleClearAll = () => {
-  const selectedItems: any[] = []
-  footprintGroups.value.forEach(group => { group.products.forEach(item => { if (item.checked) selectedItems.push(item) }) })
+  const selectedItems = getSelectedItems()
   if (selectedItems.length === 0) {
-    ElMessageBox.confirm('确定要清空所有浏览足迹吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { footprintGroups.value = []; ElMessage.success('已清空所有足迹') })
+    ElMessageBox.confirm('确定要清空所有浏览足迹吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
+      const allIds = footprintGroups.value.flatMap(group => group.products.map((item: any) => item.id)).join(',')
+      if (!allIds) return
+      deleteProductBrowsingHistory(allIds).then((res: any) => {
+        if (res.code == 200) {
+          ElMessage.success('已清空所有足迹')
+          queryParams.pageNum = 1
+          getFootprintList()
+        }
+      })
+    })
   } else {
-    ElMessageBox.confirm(`确定要删除选中的${selectedItems.length}条足迹吗?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { footprintGroups.value.forEach(group => { group.products = group.products.filter(item => !item.checked) }); footprintGroups.value = footprintGroups.value.filter(group => group.products.length > 0); ElMessage.success('删除成功') })
+    ElMessageBox.confirm(`确定要删除选中的${selectedItems.length}条足迹吗?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
+      const ids = selectedItems.map(item => item.id).join(',')
+      deleteProductBrowsingHistory(ids).then((res: any) => {
+        if (res.code == 200) {
+          ElMessage.success('删除成功')
+          getFootprintList()
+        }
+      })
+    })
   }
 }
+
+onMounted(() => {
+  getFootprintList()
+})
 </script>
 
 <style scoped lang="scss">