Explorar el Código

feat(product): 优化商品添加页面功能和交互体验

- 新增三级分类搜索下拉选择功能,支持远程搜索和快速定位
- 添加税率编码选择组件,完善税务相关信息录入
- 实现A10产品名称自动拼接功能,基于品牌名+规格型号+分类+发票规格生成
- 优化分类选择流程,添加分类层级验证和提示信息
- 替换原有的文件上传组件为统一的upload-image组件
- 移除主供应商和采购人员选择功能,改为从缓存自动获取
- 重构价格校验逻辑,添加价格关系验证(市场价>官网价)
- 优化商品详情表单布局,调整字段顺序和标签命名
- 添加分类路径显示功能,实时展示已选择的分类层级
- 完善表单验证规则,移除不必要的必填项和验证
- 优化编辑模式下的数据回显逻辑,提升用户体验
- 添加销量人气整数限制和价格负数防护机制
肖路 hace 2 meses
padre
commit
17d3768ca6
Se han modificado 37 ficheros con 7334 adiciones y 606 borrados
  1. 3 1
      package.json
  2. 63 0
      src/api/external/item/index.ts
  3. 161 0
      src/api/external/item/types.ts
  4. 165 0
      src/api/external/product/index.ts
  5. 435 0
      src/api/external/product/types.ts
  6. 67 0
      src/api/external/productBrand/index.ts
  7. 69 0
      src/api/external/productBrand/types.ts
  8. 93 0
      src/api/external/productCategory/index.ts
  9. 253 0
      src/api/external/productCategory/types.ts
  10. 51 0
      src/utils/common.ts
  11. 226 0
      src/utils/pptPlugin.js
  12. 1 1
      src/views/product/attributes/edit.vue
  13. 25 38
      src/views/product/attributes/index.vue
  14. 410 261
      src/views/product/base/add.vue
  15. 292 113
      src/views/product/base/index.vue
  16. 13 10
      src/views/product/base/review.vue
  17. 453 0
      src/views/product/base/selected.vue
  18. 583 0
      src/views/product/base/self.vue
  19. 54 33
      src/views/product/base/shelfReview.vue
  20. 11 10
      src/views/product/brand/edit.vue
  21. 2 2
      src/views/product/category/index.vue
  22. 36 25
      src/views/product/pool/index.vue
  23. 8 15
      src/views/product/pool/review.vue
  24. 5 5
      src/views/product/pool/review1.vue
  25. 76 30
      src/views/product/pool/reviewDetail.vue
  26. 505 0
      src/views/product/pool/selectedAudit.vue
  27. 506 0
      src/views/product/pool/selfAudit.vue
  28. 411 0
      src/views/product/poolAudit/index.vue
  29. 351 0
      src/views/product/poolAuditReview/index.vue
  30. 128 35
      src/views/product/poolLink/index.vue
  31. 6 6
      src/views/product/poolLink/index1.vue
  32. 1037 0
      src/views/product/poolLinkAudit/index.vue
  33. BIN
      src/views/product/poolLinkAudit/商品导入模版.xlsx
  34. 6 6
      src/views/product/priceInventory/index.vue
  35. 17 5
      src/views/product/protocolInfo/index.vue
  36. 32 10
      src/views/product/protocolInfo/productManage.vue
  37. 780 0
      src/views/product/protocolInfo/review.vue

+ 3 - 1
package.json

@@ -38,6 +38,7 @@
     "jsencrypt": "3.3.2",
     "nprogress": "0.2.0",
     "pinia": "3.0.2",
+    "pptxgenjs": "^4.0.1",
     "screenfull": "6.0.2",
     "vue": "3.5.13",
     "vue-cropper": "1.1.1",
@@ -45,7 +46,8 @@
     "vue-json-pretty": "2.4.0",
     "vue-router": "4.5.0",
     "vue-types": "6.0.0",
-    "vxe-table": "4.13.7"
+    "vxe-table": "4.13.7",
+    "xlsx": "^0.18.5"
   },
   "devDependencies": {
     "@iconify/json": "^2.2.276",

+ 63 - 0
src/api/external/item/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ItemVO, ItemForm, ItemQuery } from '@/api/external/item/types';
+
+/**
+ * 查询第三方对接项目管理列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listItem = (query?: ItemQuery): AxiosPromise<ItemVO[]> => {
+  return request({
+    url: '/external/item/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询第三方对接项目管理详细
+ * @param id
+ */
+export const getItem = (id: string | number): AxiosPromise<ItemVO> => {
+  return request({
+    url: '/external/item/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增第三方对接项目管理
+ * @param data
+ */
+export const addItem = (data: ItemForm) => {
+  return request({
+    url: '/external/item',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改第三方对接项目管理
+ * @param data
+ */
+export const updateItem = (data: ItemForm) => {
+  return request({
+    url: '/external/item',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除第三方对接项目管理
+ * @param id
+ */
+export const delItem = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/external/item/' + id,
+    method: 'delete'
+  });
+};

+ 161 - 0
src/api/external/item/types.ts

@@ -0,0 +1,161 @@
+export interface ItemVO {
+  /**
+   * 项目id
+   */
+  id: string | number;
+
+  /**
+   * 负责人id
+   */
+  purchaseId: string | number;
+
+  /**
+   * 项目logo
+   */
+  logo: string;
+
+  /**
+   * 项目表
+   */
+  itemName: string;
+
+  /**
+   * 项目key
+   */
+  itemKey: string;
+
+  /**
+   * 项目用户名
+   */
+  userName: string;
+
+  /**
+   * 项目密码
+   */
+  password: string;
+
+  /**
+   * 项目url
+   */
+  url: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface ItemForm extends BaseEntity {
+  /**
+   * 项目id
+   */
+  id?: string | number;
+
+  /**
+   * 负责人id
+   */
+  purchaseId?: string | number;
+
+  /**
+   * 项目logo
+   */
+  logo?: string;
+
+  /**
+   * 项目表
+   */
+  itemName?: string;
+
+  /**
+   * 项目key
+   */
+  itemKey?: string;
+
+  /**
+   * 项目用户名
+   */
+  userName?: string;
+
+  /**
+   * 项目密码
+   */
+  password?: string;
+
+  /**
+   * 项目url
+   */
+  url?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface ItemQuery extends PageQuery {
+
+  /**
+   * 负责人id
+   */
+  purchaseId?: string | number;
+
+  /**
+   * 项目logo
+   */
+  logo?: string;
+
+  /**
+   * 项目表
+   */
+  itemName?: string;
+
+  /**
+   * 项目key
+   */
+  itemKey?: string;
+
+  /**
+   * 项目用户名
+   */
+  userName?: string;
+
+  /**
+   * 项目密码
+   */
+  password?: string;
+
+  /**
+   * 项目url
+   */
+  url?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 165 - 0
src/api/external/product/index.ts

@@ -0,0 +1,165 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ProductVO, ProductForm,ThirdProductVO, ProductQuery } from '@/api/external/product/types';
+
+/**
+ * 查询对外部推送商品列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listProduct = (query?: ProductQuery): AxiosPromise<ProductVO[]> => {
+  return request({
+    url: '/external/product/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询对外部推送商品详细
+ * @param id
+ */
+export const getProduct = (id: string | number): AxiosPromise<ProductVO> => {
+  return request({
+    url: '/external/product/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增对外部推送商品
+ * @param data
+ */
+export const addProduct = (data: ProductForm) => {
+  return request({
+    url: '/external/product',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改对外部推送商品
+ * @param data
+ */
+export const updateProduct = (data: ProductForm) => {
+  return request({
+    url: '/external/product',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除对外部推送商品
+ * @param id
+ */
+export const delProduct = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/external/product/' + id,
+    method: 'delete'
+  });
+};
+
+/**
+ * 获取第三方商品列表
+ * @param query
+ * @returns {*}
+ */
+export const getThirdProductPage = (query?: ProductQuery): AxiosPromise<ThirdProductVO[]> => {
+  return request({
+    url: '/external/product/getThirdProductPage',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 根据商品ID查询对接信息
+ * @param productId
+ */
+export const getProductByProductId = (productId: string | number): AxiosPromise<ProductVO> => {
+  return request({
+    url: '/external/product/getByProductId/' + productId,
+    method: 'get'
+  });
+};
+
+/**
+ * 批量推送商品
+ * @param ids
+ */
+export const batchPushProduct = (ids: string | number | Array<string | number>) => {
+  return request({
+    url: `/external/product/batchPush/${ids}`,
+    method: 'post'
+  });
+};
+
+/**
+ * 商品上下架 状态变更
+ * @param data 审核信息(包含id、productStatus、shelfComments)
+ */
+export const shelfReview = (data: ProductForm) => {
+  return request({
+    url: '/external/product/shelfReview',
+    method: 'post',
+    data: data
+  });
+};
+
+export const batchInsertExternalProduct = (itemId: string | number, boList: any[]): AxiosPromise<any> => {
+  return request({
+    url: '/external/product/batch/insert',
+    method: 'post',
+    params: { itemId },
+    data: boList
+  });
+};
+
+/**
+ * 获取项目已关联的商品ID列表
+ * @param itemId
+ */
+export const getProjectProductIds = (itemId: string | number): AxiosPromise<(string | number)[]> => {
+  return request({
+    url: '/external/product/getProjectProductIds/' + itemId,
+    method: 'get'
+  });
+};
+
+/**
+ * 删除项目关联商品
+ * @param id
+ */
+export const delProjectProduct = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/external/product/project/' + id,
+    method: 'delete'
+  });
+};
+
+/**
+ * 批量审核项目商品
+ * @param data
+ */
+export const updateProjectProductAudit = (data: any[]) => {
+  return request({
+    url: '/external/product/batchReview',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 批量更新第三方价格
+ * @param data
+ */
+export const updateThirdPrice = (data: any[]) => {
+  return request({
+    url: '/external/product/updateProductPrice',
+    method: 'post',
+    data: data
+  });
+};

+ 435 - 0
src/api/external/product/types.ts

@@ -0,0 +1,435 @@
+export interface ProductVO {
+  /**
+   *
+   */
+  id: string | number;
+
+  /**
+   * 商品id
+   */
+  productId: string | number;
+
+  /**
+   * 项目id
+   */
+  itemId: string | number;
+
+  /**
+   * 分类id
+   */
+  categoryId: string | number;
+
+  /**
+   * 外部分类id
+   */
+  externalCategoryId: string | number;
+
+  /**
+   * 第三方价格
+   */
+  externalPrice: number;
+
+  /**
+   * 推送状态 0未推送,1已推送
+   */
+  pushStatus: number;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface ProductForm extends BaseEntity {
+  /**
+   *
+   */
+  id?: string | number;
+
+  /**
+   * 商品id
+   */
+  productId?: string | number;
+
+  /**
+   * 项目id
+   */
+  itemId?: string | number;
+
+  /**
+   * 分类id
+   */
+  categoryId?: string | number;
+
+  /**
+   * 外部分类id
+   */
+  externalCategoryId?: string | number;
+
+  /**
+   * 第三方价格
+   */
+  externalPrice?: number;
+
+  /**
+   * 推送状态 0未推送,1已推送
+   */
+  pushStatus?: number;
+
+  /**
+   * 商品状态:1=已上架,0=下架
+   */
+  productStatus?: string | number;
+
+  /**
+   * 上下架审核意见
+   */
+  shelfComments?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface ProductQuery extends PageQuery {
+
+  /**
+   * 商品id
+   */
+  productId?: string | number;
+
+  /**
+   * 项目id
+   */
+  itemId?: string | number;
+
+  /**
+   * 项目key
+   */
+  itemKey?: string;
+
+  /**
+   * 分类id
+   */
+  categoryId?: string | number;
+
+  /**
+   * 产品分类
+   */
+  productNo?: string;
+
+  /**
+   * 商品名称
+   */
+  itemName?: string;
+
+  /**
+   * 外部分类id
+   */
+  externalCategoryId?: string | number;
+
+  /**
+   * 第三方价格
+   */
+  externalPrice?: number;
+
+  /**
+   * 推送状态 0未推送,1已推送
+   */
+  pushStatus?: number;
+
+  /**
+   * 商品状态:1=已上架,0=下架
+   */
+  productStatus?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+export interface ThirdProductVO {
+  /**
+   * 主键,自增ID
+   */
+  id: string | number;
+
+  /**
+   * 产品编号
+   */
+  productNo: string;
+
+  /**
+   * 项目名称
+   */
+  itemName: string;
+
+  /**
+   * 品牌id
+   */
+  brandId: string | number;
+
+  /**
+   * 顶级分类id
+   */
+  topCategoryId: string | number;
+
+  /**
+   * 中级分类id
+   */
+  mediumCategoryId: string | number;
+
+  /**
+   * 底层分类id
+   */
+  bottomCategoryId: string | number;
+
+  /**
+   * 单位id
+   */
+  unitId: string | number;
+
+  /**
+   * 产品图片URL
+   */
+  productImage: string;
+
+  /**
+   * 产品图片URLUrl
+   */
+  productImageUrl: string;
+  /**
+   * 是否自营(1=是,0=否)
+   */
+  isSelf: string;
+
+  /**
+   * 商品类型 1=默认类型,2精选商品,3=停售商品
+   * */
+  productCategory?: number;
+
+  /**
+   * 产品审核状态 0=待采购审核,1=审核通过,2=驳回,3=待营销审核
+   */
+  productReviewStatus: string;
+
+  /**
+   * 首页推荐:1=推荐,0=不推荐
+   */
+  homeRecommended: string;
+
+  /**
+   * 分类推荐:1=推荐,0=不推荐
+   */
+  categoryRecommendation: string;
+
+  /**
+   * 购物车推荐:1=推荐,0=不推荐
+   */
+  cartRecommendation: string;
+
+  /**
+   * 推荐产品顺序
+   */
+  recommendedProductOrder: number;
+
+  /**
+   * 是否热门:1=是,0=否
+   */
+  isPopular: string;
+
+  /**
+   * 是否新品:1=是,0=否
+   */
+  isNew: string;
+
+  /**
+   * 商品状态:1=已上架,0=下架,2=上架中
+   */
+  productStatus: string;
+
+  /**
+   * 数据来源
+   */
+  dataSource: string;
+
+  /**
+   * 市场价
+   */
+  marketPrice: number;
+
+  /**
+   * 官网价
+   */
+  memberPrice: number;
+
+  /**
+   * 最低销售价格
+   */
+  minSellingPrice: number;
+
+  /**
+   * 采购价格
+   */
+  purchasingPrice: number;
+
+  /**
+   * 暂估毛利率
+   */
+  tempGrossMargin: number;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+  /**
+   * 主库简介
+   */
+  mainLibraryIntro?: string;
+
+  /**
+   * 售后服务
+   */
+  afterSalesService?: string;
+
+  /**
+   * 服务保障 支持多选,分隔 (存储服务保障ID列表,如: "1,2,3")
+   */
+  serviceGuarantee?: string;
+
+  /**
+   * 安装服务 - 免费安装
+   */
+  freeInstallation?: string;
+
+  /**
+   * 市场价
+   */
+  midRangePrice?: number;
+
+  /**
+   * 平档价
+   */
+  standardPrice?: number;
+
+  /**
+   * 最低售价
+   */
+  certificatePrice?: number;
+
+  /**
+   * 售价验证量
+   */
+  priceVerificationQuantity?: string;
+
+  /**
+   * 采购价
+   */
+  purchasePrice?: number;
+
+  /**
+   *最高采购价
+   */
+  estimatedPurchasePrice?: number;
+
+  /**
+   * 产品性质
+   */
+  productNature?: string;
+
+  /**
+   * 采购人员
+   */
+  purchasingPersonnel?: string;
+
+  /**
+   * 旧属性类型
+   */
+  oldAttributeType?: string;
+
+  /**
+   * 录入套数
+   */
+  entrySetCount?: string;
+
+  /**
+   * 商品主图
+   */
+  mainImage?: string;
+
+  /**
+   * 商品详情 - 电脑端
+   */
+  pcDetail?: string;
+
+  /**
+   * 商品详情 - 移动端
+   */
+  mobileDetail?: string;
+
+  /**
+   * 税率
+   */
+  taxRate?: number;
+
+  /**
+   * 币种
+   */
+  currency?: string;
+
+  /**
+   * 最低起订量
+   */
+  minOrderQuantity?: number;
+
+  /**
+   * 审核意见
+   */
+  reviewComments?: string;
+
+  /**
+   * 商品属性值(JSON字符串)
+   */
+  attributesList?: string;
+
+  /**
+   * 品牌名称
+   */
+  brandName?: string;
+
+  /**
+   * 分类名称
+   */
+  categoryName?: string;
+
+  externalCategoryName?: string;
+
+  /**
+   * 单位名称
+   */
+  unitName?: string;
+
+  /**
+   * 可用库存数
+   */
+  availableStock?: number;
+
+  /**
+   * 第三方价格
+   */
+  externalPrice?: number;
+}
+
+
+
+

+ 67 - 0
src/api/external/productBrand/index.ts

@@ -0,0 +1,67 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ProductBrandVO, ProductBrandForm, ProductBrandQuery } from '@/api/external/productBrand/types';
+
+/**
+ * 查询第三方产品品牌信息列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listProductBrand = (query?: ProductBrandQuery): AxiosPromise<ProductBrandVO[]> => {
+  return request({
+    url: '/external/productBrand/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询第三方产品品牌信息详细
+ * @param id
+ * @param itemId
+ */
+export const getProductBrand = (id: string | number, itemId?: string): AxiosPromise<ProductBrandVO> => {
+  return request({
+    url: '/external/productBrand/' + id,
+    method: 'get',
+    params: { itemId }
+  });
+};
+
+/**
+ * 新增第三方产品品牌信息
+ * @param data
+ */
+export const addProductBrand = (data: ProductBrandForm) => {
+  return request({
+    url: '/external/productBrand',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改第三方产品品牌信息
+ * @param data
+ */
+export const updateProductBrand = (data: ProductBrandForm) => {
+  return request({
+    url: '/external/productBrand',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除第三方产品品牌信息
+ * @param id
+ * @param itemId
+ */
+export const delProductBrand = (id: string | number | Array<string | number>, itemId?: string) => {
+  return request({
+    url: '/external/productBrand/' + id,
+    method: 'delete',
+    params: { itemId }
+  });
+};

+ 69 - 0
src/api/external/productBrand/types.ts

@@ -0,0 +1,69 @@
+export interface ProductBrandVO {
+  /**
+   * 第三方品牌id
+   */
+  id: string | number;
+
+  /**
+   * 官方品牌id
+   */
+  productBrandId: string | number;
+
+  /**
+   * 官方品牌名称
+   */
+  brandName: string;
+
+  /**
+   * 第三方品牌名称
+   */
+  thirdPartyBrandName: string;
+}
+
+export interface ProductBrandForm extends BaseEntity {
+  /**
+   * 第三方品牌id
+   */
+  id?: string | number;
+
+  /**
+   * 官方品牌id
+   */
+  productBrandId?: string | number;
+
+  /**
+   * 官方品牌名称
+   */
+  brandName?: string;
+
+  /**
+   * 第三方品牌名称
+   */
+  thirdPartyBrandName?: string;
+}
+
+export interface ProductBrandQuery extends PageQuery {
+
+  /**
+   * 官方品牌名称
+   */
+  brandName?: string;
+
+  /**
+   * 第三方品牌名称
+   */
+  thirdPartyBrandName?: string;
+
+  /**
+   * 项目key
+   */
+  itemKey?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}
+
+
+

+ 93 - 0
src/api/external/productCategory/index.ts

@@ -0,0 +1,93 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ProductCategoryVO, ProductCategoryForm, ProductCategoryQuery } from '@/api/external/productCategory/types';
+
+/**
+ * 查询产品分类列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listProductCategory = (query?: Partial<ProductCategoryQuery>): AxiosPromise<ProductCategoryVO[]> => {
+  return request({
+    url: '/external/productCategory/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询产品分类详细
+ * @param id
+ * @param itemId
+ */
+export const getProductCategory = (id: string | number, itemId?: string): AxiosPromise<ProductCategoryVO> => {
+  return request({
+    url: '/external/productCategory/' + id,
+    method: 'get',
+    params: { itemId }
+  });
+};
+
+/**
+ * 新增产品分类
+ * @param data
+ */
+export const addProductCategory = (data: ProductCategoryForm) => {
+  return request({
+    url: '/external/productCategory',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改产品分类
+ * @param data
+ */
+export const updateProductCategory = (data: ProductCategoryForm) => {
+  return request({
+    url: '/external/productCategory',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除产品分类
+ * @param id
+ * @param itemId
+ */
+export const delProductCategory = (id: string | number | Array<string | number>, itemId?: string) => {
+  return request({
+    url: '/external/productCategory/' + id,
+    method: 'delete',
+    params: { itemId }
+  });
+};
+
+/**
+ * 查询产品分类列表(排除指定节点及其子节点)
+ * @param id
+ * @param itemId
+ */
+export const listProductCategoryExcludeChild = (id: string | number, itemId?: string): AxiosPromise<ProductCategoryVO[]> => {
+  return request({
+    url: '/external/productCategory/list/exclude/' + id,
+    method: 'get',
+    params: { itemId }
+  });
+};
+
+/**
+ * 查询产品分类树结构
+ * @param query
+ * @returns {*}
+ */
+export const getProductCategoryTree = (query?: Partial<ProductCategoryQuery>): AxiosPromise<ProductCategoryVO[]> => {
+  return request({
+    url: '/external/productCategory/categoryTree',
+    method: 'get',
+    params: query
+  });
+};

+ 253 - 0
src/api/external/productCategory/types.ts

@@ -0,0 +1,253 @@
+export interface ProductCategoryVO {
+  /**
+   * 第三方系统id
+   */
+  id: string | number;
+
+  /**
+   * 项目id
+   */
+  itemId: string | number;
+
+  /**
+   * 原系统分类id
+   */
+  productCategoryId: string | number;
+
+  /**
+   * 分类编号
+   */
+  categoryNo: string;
+
+  /**
+   * 分类名称
+   */
+  categoryName: string;
+
+  /**
+   * 父级分类ID
+   */
+  parentId: string | number;
+
+  /**
+   * 祖籍列表
+   */
+  ancestors: string;
+
+  /**
+   * 分类层级(1=一级,2=二级, 3三级)
+   */
+  classLevel: number;
+
+  /**
+   * 拼音码(用于快速检索)
+   */
+  pyCode: string;
+
+  /**
+   * 分类描述
+   */
+  classDescription: string;
+
+  /**
+   * 数据来源
+   */
+  dataSource: string;
+
+  /**
+   * 所属平台(0=Web, 1=小程序)
+   */
+  platform: number;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+  /**
+   * 是否有子节点
+   */
+  hasChildren?: boolean;
+
+  /**
+   * 子节点
+   */
+  children?: ProductCategoryVO[];
+
+  /**
+   * 折扣率
+   */
+  discountRate?: number;
+
+}
+
+export interface ProductCategoryForm extends BaseEntity {
+  /**
+   * 第三方系统id
+   */
+  id?: string | number;
+
+  /**
+   * 项目id
+   */
+  itemId?: string | number;
+
+  /**
+   * 原系统分类id
+   */
+  productCategoryId?: string | number;
+
+  /**
+   * 分类编号
+   */
+  categoryNo?: string;
+
+  /**
+   * 分类名称
+   */
+  categoryName?: string;
+
+  /**
+   * 父级分类ID
+   */
+  parentId?: string | number;
+
+  /**
+   * 祖籍列表
+   */
+  ancestors?: string;
+
+  /**
+   * 分类层级(1=一级,2=二级, 3三级)
+   */
+  classLevel?: number;
+
+  /**
+   * 拼音码(用于快速检索)
+   */
+  pyCode?: string;
+
+  /**
+   * 分类描述
+   */
+  classDescription?: string;
+
+  /**
+   * 数据来源
+   */
+  dataSource?: string;
+
+  /**
+   * 所属平台(0=Web, 1=小程序)
+   */
+  platform?: number;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+  /**
+   * 折扣率
+   */
+  discountRate?: number;
+
+}
+
+export interface ProductCategoryDiscountForm {
+  /**
+   * 分类ID
+   */
+  id: string | number;
+
+  /**
+   * 项目ID
+   */
+  itemId?: string | number;
+
+  /**
+   * 分类名称
+   */
+  categoryName?: string;
+
+  /**
+   * 折扣率
+   */
+  discountRate?: number;
+}
+
+export interface ProductCategoryQuery extends PageQuery {
+  /**
+   * 项目id
+   */
+  itemId?: string | number;
+
+  /**
+   * 原系统分类id
+   */
+  productCategoryId?: string | number;
+
+  /**
+   * 分类编号
+   */
+  categoryNo?: string;
+
+  /**
+   * 分类名称
+   */
+  categoryName?: string;
+
+  /**
+   * 父级分类ID
+   */
+  parentId?: string | number;
+
+  /**
+   * 祖籍列表
+   */
+  ancestors?: string;
+
+  /**
+   * 分类层级(1=一级,2=二级, 3三级)
+   */
+  classLevel?: number;
+
+  /**
+   * 拼音码(用于快速检索)
+   */
+  pyCode?: string;
+
+  /**
+   * 分类描述
+   */
+  classDescription?: string;
+
+  /**
+   * 数据来源
+   */
+  dataSource?: string;
+
+  /**
+   * 所属平台(0=Web, 1=小程序)
+   */
+  platform?: number;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+  /**
+   * 项目key
+   */
+  itemKey?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}
+
+
+
+

+ 51 - 0
src/utils/common.ts

@@ -0,0 +1,51 @@
+/**
+ * 判断是否是url
+ * @param str
+ * @returns
+ */
+export function isUrl(str: string): boolean {
+  return str.indexOf('http://') != -1 || str.indexOf('https://') != -1;
+}
+
+const isArray = (value: any) => {
+  if (typeof Array.isArray === 'function') {
+    return Array.isArray(value);
+  }
+  return Object.prototype.toString.call(value) === '[object Array]';
+};
+
+/**
+ * 图片输出
+ * @param path
+ * @returns
+ */
+export function img(path: string): string {
+  let imgDomain = import.meta.env.VITE_IMG_DOMAIN || location.origin;
+
+  if (typeof path == 'string' && path.startsWith('/')) path = path.replace(/^\//, '');
+  if (typeof imgDomain == 'string' && imgDomain.endsWith('/')) imgDomain = imgDomain.slice(0, -1);
+  if (path) {
+    return isUrl(path) ? path : `${imgDomain}/${path}`;
+  }
+}
+
+/**
+ * @description 深度克隆
+ * @param {object} obj 需要深度克隆的对象
+ * @returns {*} 克隆后的对象或者原值(不是对象)
+ */
+export function deepClone(obj: any) {
+  // 对常见的“非”值,直接返回原来值
+  if ([null, undefined, NaN, false].includes(obj)) return obj;
+  if (typeof obj !== 'object' && typeof obj !== 'function') {
+    // 原始类型直接返回
+    return obj;
+  }
+  const o = isArray(obj) ? [] : {};
+  for (const i in obj) {
+    if (obj.hasOwnProperty(i)) {
+      o[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i];
+    }
+  }
+  return o;
+}

+ 226 - 0
src/utils/pptPlugin.js

@@ -0,0 +1,226 @@
+import pptxgen from "pptxgenjs";
+
+/**
+ * 将任意图片 URL(含相对路径)转为 base64 data URL
+ * pptxgenjs 在浏览器端必须使用 base64 或完整 http 地址才能嵌入图片
+ */
+const toBase64 = (src) => {
+  return new Promise((resolve) => {
+    if (!src) { resolve(null); return; }
+    if (src.startsWith('data:')) { resolve(src); return; }
+
+    // 优先用 fetch 获取 Blob → base64(支持同源相对路径 & 允许CORS的外链)
+    const fullUrl = src.startsWith('http') ? src : (window.location.origin + (src.startsWith('/') ? src : '/' + src));
+    fetch(fullUrl)
+      .then(r => r.blob())
+      .then(blob => {
+        const reader = new FileReader();
+        reader.onload = () => resolve(reader.result);
+        reader.onerror = () => resolve(null);
+        reader.readAsDataURL(blob);
+      })
+      .catch(() => {
+        // fetch 失败时降级用 canvas(跨域图片会被 taint,可能失败)
+        const img = new Image();
+        img.crossOrigin = 'anonymous';
+        img.onload = () => {
+          try {
+            const c = document.createElement('canvas');
+            c.width = img.width; c.height = img.height;
+            c.getContext('2d').drawImage(img, 0, 0);
+            resolve(c.toDataURL('image/png'));
+          } catch { resolve(null); }
+        };
+        img.onerror = () => resolve(null);
+        img.src = fullUrl;
+      });
+  });
+};
+
+/** 1×1 灰色像素,作为图片加载失败时的占位符 */
+const PLACEHOLDER_B64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQI12NgYGBgAAAABQABpfZFQAAAAABJRU5ErkJggg==';
+
+export const generatePPT = async (template, products) => {
+  if (!products || products.length === 0) return;
+
+  const pres    = new pptxgen();
+  const MC      = (template.themeColor || '#C00000').replace('#', '');
+  const WHITE   = 'FFFFFF';
+  const title   = template.title || template.name || '商品展示方案';
+
+  // ── 把所有图片提前并行转 base64 ──────────────────────────────────────
+  const [coverB64, logoB64, ...productImgB64s] = await Promise.all([
+    toBase64(template.cover),
+    toBase64(template.logo),
+    ...products.map(p => toBase64(p.image))
+  ]);
+
+  // ── 母版(内页底纹)─────────────────────────────────────────────────
+  pres.defineSlideMaster({
+    title: 'MASTER',
+    background: { color: 'FFFFFF' },
+    objects: [
+      // 顶栏底色
+      { shape: { type: pres.ShapeType.rect, x: 0, y: 0, w: '100%', h: 0.6, fill: { color: 'F8F9FA' } } },
+      // 顶栏下面的细线
+      { shape: { type: pres.ShapeType.rect, x: 0, y: 0.6, w: '100%', h: 0.04, fill: { color: MC } } },
+      // 右下角极浅三角装饰
+      { shape: { type: pres.ShapeType.rtTriangle, x: 8.6, y: 4.5, w: 1.4, h: 1.4, fill: { color: MC, transparency: 92 } } },
+      // 底条
+      { shape: { type: pres.ShapeType.rect, x: 0, y: 5.4, w: '100%', h: 0.1, fill: { color: MC, transparency: 70 } } }
+    ]
+  });
+
+  // ══════════════════════════ 1. 封面 ══════════════════════════════════
+  const cover = pres.addSlide();
+  cover.background = { color: MC };
+
+  if (coverB64) {
+    cover.addImage({ data: coverB64, x: 0, y: 0, w: '100%', h: '100%', sizing: { type: 'cover' } });
+    cover.addShape(pres.ShapeType.rect, { x: 0, y: 0, w: '100%', h: '100%', fill: { color: '000000', transparency: 50 } });
+  } else {
+    cover.addShape(pres.ShapeType.rightTriangle, { x: 0, y: 0, w: 5, h: 4, fill: { color: WHITE, transparency: 90 } });
+    cover.addShape(pres.ShapeType.oval, { x: 7, y: 3, w: 4, h: 4, fill: { color: '000000', transparency: 82 } });
+  }
+
+  if (logoB64) {
+    cover.addImage({ data: logoB64, x: 0.5, y: 0.4, w: 1.4, h: 0.7, sizing: { type: 'contain' } });
+  }
+  cover.addText(title, {
+    x: 1, y: 2.0, w: 8, h: 1.6,
+    fontSize: 48, bold: true, color: WHITE, align: 'center',
+    shadow: { type: 'outer', opacity: 0.4 }
+  });
+  cover.addText('PRODUCT SHOWCASE · 优选商品展示方案', {
+    x: 1, y: 3.5, w: 8, h: 0.5,
+    fontSize: 14, color: 'EEEEEE', align: 'center', letterSpacing: 1.5
+  });
+  cover.addText(`发布日期:${new Date().toLocaleDateString()}`, {
+    x: 0, y: 5.0, w: '100%', h: 0.4,
+    fontSize: 11, color: WHITE, align: 'center'
+  });
+
+  // ══════════════════════════ 2. 商品内页 ══════════════════════════════
+  const itemsPerPage = template.itemsPerPage || 1;
+  const totalPages   = Math.ceil(products.length / itemsPerPage);
+
+  for (let i = 0; i < totalPages; i++) {
+    const slide        = pres.addSlide({ masterName: 'MASTER' });
+    const pageProducts = products.slice(i * itemsPerPage, (i + 1) * itemsPerPage);
+
+    // 内页顶栏
+    if (logoB64) {
+      slide.addImage({ data: logoB64, x: 0.2, y: 0.08, w: 1.1, h: 0.44, sizing: { type: 'contain' } });
+    }
+    slide.addText(title, {
+      x: logoB64 ? 1.45 : 0.2, y: 0.1, w: 6.5, h: 0.42,
+      fontSize: 16, color: MC, bold: true
+    });
+    slide.addText(`Page ${i + 1}`, { x: 8.5, y: 0.12, w: 1, h: 0.3, fontSize: 11, color: '999999', align: 'right' });
+
+    // 左侧竖装饰条
+    slide.addShape(pres.ShapeType.rect, { x: 0, y: 0.64, w: 0.12, h: 4.8, fill: { color: MC } });
+
+    if (itemsPerPage === 1) {
+      // ──── 1 商品 / 页版式 ────────────────────────────────────────────
+      const p    = pageProducts[0];
+      const imgB = productImgB64s[i] || PLACEHOLDER_B64;
+
+      if (imgB) {
+        slide.addImage({
+          data: imgB,
+          x: 0.5, y: 0.9, w: 4.7, h: 3.8,
+          sizing: { type: 'cover', w: 4.7, h: 3.8 }
+        });
+        slide.addShape(pres.ShapeType.rect, {
+          x: 0.5, y: 0.9, w: 4.7, h: 3.8,
+          fill: { type: 'none' }, line: { color: 'E0E0E0', width: 1 }
+        });
+      } else {
+        slide.addShape(pres.ShapeType.rect, {
+          x: 0.5, y: 0.9, w: 4.7, h: 3.8, fill: { type: 'solid', color: 'F0F0F0' }, line: { color: 'E0E0E0', width: 1 }
+        });
+      }
+
+      const sx = 5.5;
+      // 商品名 - 自动换行,字号缩小到22确保60字也不溢出
+      slide.addText(p.name, {
+        x: sx, y: 0.9, w: 3.9, h: 1.4,
+        fontSize: 20, bold: true, color: '222222',
+        wrap: true, valign: 'top'
+      });
+      // 移除原硬编码装饰横线,直接放置下续属性使排版自然衔接
+      slide.addText(`商品69码:${p.code}`, { x: sx, y: 2.3, w: 3.9, h: 0.35, fontSize: 13, color: '666666' });
+      slide.addText(`商品规格:${p.spec}`,  { x: sx, y: 2.7, w: 3.9, h: 0.35, fontSize: 13, color: '666666' });
+
+      // 售价标签整体上移避免分离感
+      slide.addShape(pres.ShapeType.roundRect, { x: sx, y: 3.5, w: 2.8, h: 0.75, fill: { color: MC }, rectRadius: 0.12 });
+      slide.addText(`售价: ¥${p.price}`, {
+        x: sx, y: 3.5, w: 2.8, h: 0.75,
+        fontSize: 22, color: WHITE, align: 'center', bold: true
+      });
+
+    } else {
+      // ──── 2 商品 / 页交错版式 ────────────────────────────────────────
+      if (pageProducts.length === 2) {
+        slide.addShape(pres.ShapeType.line, { x: 0.4, y: 3.0, w: 9.1, h: 0, line: { color: 'DDDDDD', width: 1, dashType: 'dash' } });
+      }
+
+      for (let j = 0; j < pageProducts.length; j++) {
+        const p       = pageProducts[j];
+        const imgIdx  = i * itemsPerPage + j;
+        const imgB    = productImgB64s[imgIdx] || PLACEHOLDER_B64;
+        const isUpper = j === 0;
+        const yBase   = isUpper ? 0.75 : 3.1;
+
+        const imgX  = isUpper ? 0.4  : 6.6;
+        const txtX  = isUpper ? 3.4  : 0.4;
+        const txtAl = isUpper ? 'left' : 'right';
+
+        if (imgB) {
+          slide.addImage({
+            data: imgB,
+            x: imgX, y: yBase, w: 2.7, h: 2.05,
+            sizing: { type: 'cover', w: 2.7, h: 2.05 }
+          });
+          slide.addShape(pres.ShapeType.rect, {
+            x: imgX, y: yBase, w: 2.7, h: 2.05,
+            fill: { type: 'none' }, line: { color: 'E0E0E0', width: 1 }
+          });
+        } else {
+          slide.addShape(pres.ShapeType.rect, {
+            x: imgX, y: yBase, w: 2.7, h: 2.05, fill: { type: 'solid', color: 'F5F5F5' }, line: { color: 'EEEEEE', width: 1 }
+          });
+        }
+
+        // 文字区
+        slide.addText(p.name, {
+          x: txtX, y: yBase + 0.05, w: 3.1, h: 0.95,
+          fontSize: 15, bold: true, color: '222222', align: txtAl, wrap: true, valign: 'top'
+        });
+        slide.addText(`69码: ${p.code}  |  规格: ${p.spec}`, {
+          x: txtX, y: yBase + 1.05, w: 3.1, h: 0.4,
+          fontSize: 11, color: '888888', align: txtAl
+        });
+
+        const priceX = isUpper ? txtX : txtX + 1.1;
+        slide.addShape(pres.ShapeType.roundRect, { x: priceX, y: yBase + 1.55, w: 2.0, h: 0.44, fill: { color: 'F5F5F5' }, rectRadius: 0.08, line: { color: MC, width: 1 } });
+        slide.addText(`¥ ${p.price}`, { x: priceX, y: yBase + 1.55, w: 2.0, h: 0.44, fontSize: 16, bold: true, color: MC, align: 'center' });
+      }
+    }
+  }
+
+  // ══════════════════════════ 3. 尾页 ══════════════════════════════════
+  const end = pres.addSlide();
+  end.background = { color: '1A1A1A' };
+  end.addShape(pres.ShapeType.rect, { x: 0, y: 0, w: '22%', h: '100%', fill: { color: MC } });
+  end.addShape(pres.ShapeType.line, { x: 2.8, y: 1.4, w: 6, h: 0, line: { color: '444444', width: 1.5 } });
+  end.addText('THANK YOU', { x: 2.8, y: 1.8, w: 7, h: 1.5, fontSize: 60, bold: true, color: MC });
+  end.addText('感谢您的观看 · 期待与您携手共进', { x: 2.8, y: 3.3, w: 7, h: 0.5, fontSize: 18, color: 'BBBBBB', letterSpacing: 2 });
+  if (logoB64) {
+    end.addImage({ data: logoB64, x: 2.8, y: 4.3, w: 1.4, h: 0.7, sizing: { type: 'contain' } });
+  }
+
+  // 导出
+  await pres.writeFile({ fileName: `${title}_${Date.now()}.pptx` });
+};

+ 1 - 1
src/views/product/attributes/edit.vue

@@ -61,7 +61,7 @@
         </el-form-item>
 
         <el-form-item label="品牌LOGO" prop="brandLogo">
-          <image-upload v-model="extendFormData.brandLogo" />
+          <upload-image v-model="extendFormData.brandLogo" />
         </el-form-item>
 
         <el-form-item label="品牌故事" prop="brandStory">

+ 25 - 38
src/views/product/attributes/index.vue

@@ -11,19 +11,12 @@
               <el-tree-select
                 v-model="queryParams.categoryId"
                 :data="categoryOptions"
-                :props="{ value: 'id', label: 'label', children: 'children' }"
-                check-strictly
-                :render-after-expand="false"
-                show-checkbox
-                :check-on-click-node="true"
-                node-key="id"
-                clearable
+                :props="{ value: 'id', label: 'label', children: 'children' } as any"
+                value-key="id"
                 placeholder="请选择商品类别"
-              >
-                <template #default="{ data }">
-                  <span>{{ getCategoryFullPath(data.id) }}</span>
-                </template>
-              </el-tree-select>
+                clearable
+                check-strictly
+              />
             </el-form-item>
             <el-form-item>
               <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -112,18 +105,12 @@
           <el-tree-select
             v-model="form.categoryId"
             :data="categoryOptions"
-            :props="{ value: 'id', label: 'label', children: 'children' }"
-            check-strictly
-            :render-after-expand="false"
+            :props="{ value: 'id', label: 'label', children: 'children' } as any"
+            value-key="id"
+            placeholder="请选择关联类别"
             clearable
-            placeholder="请选择关联类别(必须选择第三级)"
-          >
-            <template #default="{ node, data }">
-              <span :style="{ color: !node.isLeaf && node.level < 3 ? '#999' : '' }">
-                {{ getCategoryFullPath(data.id) }}
-              </span>
-            </template>
-          </el-tree-select>
+            check-strictly
+          />
         </el-form-item>
         <el-form-item label="是否可选" prop="isOptional">
           <el-radio-group v-model="form.isOptional">
@@ -153,7 +140,7 @@
             <el-radio :value="0">否</el-radio>
           </el-radio-group>
         </el-form-item>
-        <el-form-item label="可选值列表" prop="attributesList" v-if="form.entryMethod === 'select'">
+        <el-form-item label="可选值列表" prop="attributesList" v-if="form.entryMethod === '2'">
           <el-input 
             v-model="form.attributesList" 
             type="textarea" 
@@ -360,24 +347,24 @@ const handleSelectionChange = (selection: AttributesVO[]) => {
 
 /** 新增按钮操作 */
 const handleAdd = () => {
-  router.push({
-    path: '/product/attributes/edit',
-    query: {
-      type: 'add'
-    }
-  });
+  dialog.visible = true;
+  dialog.title = '添加产品属性定义(用于动态属性配置)';
 }
 
 /** 修改按钮操作 */
 const handleUpdate = async (row?: AttributesVO) => {
-  const _id = row?.id || ids.value[0];
-  router.push({
-    path: '/product/attributes/edit',
-    query: {
-      id: _id,
-      type: 'edit'
-    }
-  });
+  dialog.visible = true;
+  dialog.title = '修改产品属性定义(用于动态属性配置)';
+  if (row) {
+    form.value = {
+      ...row,
+      isOptional: row.isOptional !== undefined && row.isOptional !== null ? Number(row.isOptional) : initFormData.isOptional,
+      isFilter: row.isFilter !== undefined && row.isFilter !== null ? Number(row.isFilter) : initFormData.isFilter,
+      required: row.required !== undefined && row.required !== null ? Number(row.required) as any : initFormData.required,
+    } as AttributesForm;
+  } else {
+    form.value = { ...initFormData };
+  }
 }
 
 /** 提交按钮 */

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 410 - 261
src/views/product/base/add.vue


+ 292 - 113
src/views/product/base/index.vue

@@ -16,25 +16,19 @@
                 </el-form-item>
               </el-col>
               <el-col :span="6">
-                <el-form-item label="商品品牌" prop="brandName">
+                <el-form-item label="商品品牌" prop="brandId">
                   <el-select
-                    v-model="queryParams.brandName"
+                    v-model="queryParams.brandId"
                     placeholder="请输入品牌名称搜索"
                     filterable
                     remote
                     clearable
                     :remote-method="handleBrandSearch"
                     :loading="brandLoading"
-                    value-key="brandName"
                     style="width: 100%"
                     @keyup.enter="handleQuery"
                   >
-                    <el-option
-                      v-for="item in brandOptions"
-                      :key="item.id"
-                      :label="item.brandName"
-                      :value="item.brandName"
-                    />
+                    <el-option v-for="item in brandOptions" :key="item.id" :label="item.brandName" :value="item.id" />
                   </el-select>
                 </el-form-item>
               </el-col>
@@ -61,8 +55,8 @@
               <el-col :span="6">
                 <el-form-item label="是否自营" prop="isSelf">
                   <el-select v-model="queryParams.isSelf" placeholder="请选择" clearable>
-                    <el-option label="是" value="1" />
-                    <el-option label="否" value="0" />
+                    <el-option label="是" :value="1" />
+                    <el-option label="否" :value="0" />
                   </el-select>
                 </el-form-item>
               </el-col>
@@ -82,6 +76,7 @@
                     <el-option label="已上架" :value="1" />
                     <el-option label="下架" :value="0" />
                     <el-option label="上架中" :value="2" />
+                    <el-option label="驳回上架" :value="3" />
                   </el-select>
                 </el-form-item>
               </el-col>
@@ -106,12 +101,21 @@
           >条</span
         >
         <span class="mx-2">【上架/总数({{ statistics.onSale || 0 }}/{{ statistics.total || 0 }})】</span>
-        <span class="mx-2">审核状态: 待审核<span class="text-red-600">{{ statistics.waitAudit || 0 }}</span>条,通过<span class="text-green-600">{{ statistics.auditPass || 0 }}</span>条,驳回<span class="text-orange-600">{{ statistics.auditReject || 0 }}</span>条</span>
-        <span class="mx-2">上下架状态: 已上架<span class="text-green-600">{{ statistics.onSale || 0 }}</span>条,下架<span class="text-gray-600">{{ statistics.offSale || 0 }}</span>条</span>
+        <span class="mx-2"
+          >审核状态: 待审核<span class="text-red-600">{{ statistics.waitAudit || 0 }}</span
+          >条,通过<span class="text-green-600">{{ statistics.auditPass || 0 }}</span
+          >条,驳回<span class="text-orange-600">{{ statistics.auditReject || 0 }}</span
+          >条</span
+        >
+        <span class="mx-2"
+          >上下架状态: 已上架<span class="text-green-600">{{ statistics.onSale || 0 }}</span
+          >条,下架<span class="text-gray-600">{{ statistics.offSale || 0 }}</span
+          >条</span
+        >
         <div class="ml-auto flex gap-2">
           <el-button type="primary" icon="Plus" @click="handleAdd">商品新增</el-button>
-          <el-button type="warning" icon="Check" @click="handleGoReview">商品审核</el-button>
-          <el-button plain>批量操作</el-button>
+          <!-- <el-button type="warning" icon="Check" @click="handleGoReview">商品审核</el-button> -->
+          <!--          <el-button plain>批量操作</el-button>-->
           <el-button plain icon="Download" @click="handleExport">导出</el-button>
           <el-button circle icon="Refresh" @click="getList"></el-button>
         </div>
@@ -136,10 +140,10 @@
             <div class="text-left">
               <div style="white-space: normal; word-break: break-all; line-height: 1.4">{{ scope.row.itemName }}</div>
               <div class="text-gray-500" style="font-size: 12px">品牌: {{ scope.row.brandName || '-' }}</div>
+              <div class="text-gray-500" style="font-size: 12px">分类: {{ scope.row.categoryName || '-' }}</div>
             </div>
           </template>
         </el-table-column>
-        <el-table-column label="商品分类" align="center" prop="categoryName" width="120" />
         <el-table-column label="单位" align="center" prop="unitName" width="60" />
         <el-table-column label="SKU价格" align="center" width="120">
           <template #default="scope">
@@ -149,93 +153,115 @@
                 <span class="text-red-500">¥{{ scope.row.marketPrice || '0.00' }}</span>
               </div>
               <div>
-                <span class="text-gray-500">会员价:</span>
+                <span class="text-gray-500">官网价:</span>
                 <span class="text-red-500">¥{{ scope.row.memberPrice || '0.00' }}</span>
               </div>
-              <div>
-                <span class="text-gray-500">最低价:</span>
-                <span class="text-red-500">¥{{ scope.row.minSellingPrice || '0.00' }}</span>
-              </div>
+<!--              <div>-->
+<!--                <span class="text-gray-500">最低价:</span>-->
+<!--                <span class="text-red-500">¥{{ scope.row.minSellingPrice || '0.00' }}</span>-->
+<!--              </div>-->
             </div>
           </template>
         </el-table-column>
-        <el-table-column label="成本情况" align="center" width="150">
-          <template #default="scope">
-            <div class="text-left" style="font-size: 12px">
-              <div>
-                <span class="text-gray-500">采购价:</span>
-                <span>¥{{ scope.row.purchasingPrice || '0.00' }}</span>
-              </div>
-              <div>
-                <span class="text-gray-500">暂估毛利率:</span>
-                <span>{{ scope.row.tempGrossMargin || '0.0000' }}%</span>
-              </div>
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column label="数据来源" align="center" prop="dataSource" width="80">
-          <template #default="scope">
-            <span>{{ scope.row.dataSource || '-' }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column label="是否自营" align="center" width="80">
-          <template #default="scope">
-            <el-tag v-if="scope.row.isSelf === 1" type="success">是</el-tag>
-            <el-tag v-else-if="scope.row.isSelf === 0" type="info">否</el-tag>
-            <span v-else>-</span>
-          </template>
-        </el-table-column>
-        <el-table-column label="审核状态" align="center" prop="productReviewStatus" width="90">
-          <template #default="scope">
-            <span v-if="scope.row.productReviewStatus === 0">待采购审核</span>
-            <span v-else-if="scope.row.productReviewStatus === 1">审核通过</span>
-            <span v-else-if="scope.row.productReviewStatus === 2">驳回</span>
-            <span v-else-if="scope.row.productReviewStatus === 3">待营销审核</span>
-            <span v-else>-</span>
-          </template>
-        </el-table-column>
-        <el-table-column label="上下架状态" align="center" prop="productStatus" width="100">
-          <template #default="scope">
-            <el-tag v-if="scope.row.productStatus === 1" type="success">已上架</el-tag>
-            <el-tag v-else-if="scope.row.productStatus === 0" type="warning">下架</el-tag>
-            <el-tag v-else-if="scope.row.productStatus === 2" type="info">上架中</el-tag>
-            <el-tag v-else type="info">未知</el-tag>
-          </template>
-        </el-table-column>
+<!--        <el-table-column label="库存情况" align="center" width="140">-->
+<!--          <template #default="scope">-->
+<!--            <div class="text-left" style="font-size: 12px">-->
+<!--              <div>-->
+<!--                <span class="text-gray-500">总库存:</span>-->
+<!--                <span>{{ scope.row.totalInventory ?? '-' }}</span>-->
+<!--              </div>-->
+<!--              <div>-->
+<!--                <span class="text-gray-500">可用库存:</span>-->
+<!--                <span>{{ scope.row.nowInventory ?? '-' }}</span>-->
+<!--              </div>-->
+<!--              <div>-->
+<!--                <span class="text-gray-500">虚拟库存:</span>-->
+<!--                <span>{{ scope.row.virtualInventory ?? '-' }}</span>-->
+<!--              </div>-->
+<!--            </div>-->
+<!--          </template>-->
+<!--        </el-table-column>-->
+<!--        <el-table-column label="成本情况" align="center" width="150">-->
+<!--          <template #default="scope">-->
+<!--            <div class="text-left" style="font-size: 12px">-->
+<!--              <div>-->
+<!--                <span class="text-gray-500">采购价:</span>-->
+<!--                <span>¥{{ scope.row.purchasingPrice || '0.00' }}</span>-->
+<!--              </div>-->
+<!--              <div>-->
+<!--                <span class="text-gray-500">毛利率:</span>-->
+<!--                <span>{{ scope.row.memberPrice ? (((scope.row.memberPrice - (scope.row.purchasingPrice || 0)) / scope.row.memberPrice) * 100).toFixed(2) : '0.00' }}%</span>-->
+<!--              </div>-->
+<!--            </div>-->
+<!--          </template>-->
+<!--        </el-table-column>-->
+<!--        <el-table-column label="数据来源" align="center" prop="dataSource" width="80">-->
+<!--          <template #default="scope">-->
+<!--            <span>{{ scope.row.dataSource || '-' }}</span>-->
+<!--          </template>-->
+<!--        </el-table-column>-->
+<!--        <el-table-column label="是否自营" align="center" width="80">-->
+<!--          <template #default="scope">-->
+<!--            <el-tag v-if="scope.row.isSelf === 1" type="success">是</el-tag>-->
+<!--            <el-tag v-else-if="scope.row.isSelf === 0" type="info">否</el-tag>-->
+<!--            <span v-else>-</span>-->
+<!--          </template>-->
+<!--        </el-table-column>-->
+<!--        <el-table-column label="审核状态" align="center" prop="productReviewStatus" width="90">-->
+<!--          <template #default="scope">-->
+<!--            <span v-if="scope.row.productReviewStatus === 0">待采购审核</span>-->
+<!--            <span v-else-if="scope.row.productReviewStatus === 1">审核通过</span>-->
+<!--            <span v-else-if="scope.row.productReviewStatus === 2">驳回</span>-->
+<!--            <span v-else-if="scope.row.productReviewStatus === 3">待营销审核</span>-->
+<!--            <span v-else>-</span>-->
+<!--          </template>-->
+<!--        </el-table-column>-->
+<!--        <el-table-column label="上下架状态" align="center" prop="productStatus" width="100">-->
+<!--          <template #default="scope">-->
+<!--            <el-tag v-if="scope.row.productStatus === 1" type="success">已上架</el-tag>-->
+<!--            <el-tag v-else-if="scope.row.productStatus === 0" type="warning">下架</el-tag>-->
+<!--            <el-tag v-else-if="scope.row.productStatus === 2" type="info">上架中</el-tag>-->
+<!--            <el-tag v-else type="info">未知</el-tag>-->
+<!--          </template>-->
+<!--        </el-table-column>-->
         <el-table-column label="操作" align="center" width="150" fixed="right">
           <template #default="scope">
             <!-- 待审核状态:只显示编辑 -->
-            <div v-if="scope.row.productReviewStatus !== 1" class="flex gap-1 justify-center">
-              <el-link type="primary" :underline="false" @click="handleUpdate(scope.row)">编辑</el-link>
-            </div>
-
-            <!-- 审核通过 -->
-            <div v-else-if="scope.row.productReviewStatus === 1" class="flex flex-col gap-1">
-              <!-- 下架状态:编辑、上架、停售、修改库存 -->
-              <div v-if="scope.row.productStatus === 0" class="flex gap-1 justify-center">
-                <el-link type="primary" :underline="false" @click="handleUpdate(scope.row)">编辑</el-link>
-                <el-link type="success" :underline="false" @click="handleShelf(scope.row)">上架</el-link>
-                <el-link type="danger" :underline="false" @click="handleDiscontinue(scope.row)">停售</el-link>
-              </div>
-              <div v-if="scope.row.productStatus === 0" class="flex gap-1 justify-center">
-                <el-link type="primary" :underline="false" @click="handleSupply(scope.row)">修改库存</el-link>
-              </div>
-
-              <!-- 上架状态:编辑、下架、停售、修改库存 -->
-              <div v-else-if="scope.row.productStatus === 1" class="flex gap-1 justify-center">
-                <el-link type="primary" :underline="false" @click="handleUpdate(scope.row)">编辑</el-link>
-                <el-link type="warning" :underline="false" @click="handleShelf(scope.row)">下架</el-link>
-                <el-link type="danger" :underline="false" @click="handleDiscontinue(scope.row)">停售</el-link>
-              </div>
-              <div v-else-if="scope.row.productStatus === 1" class="flex gap-1 justify-center">
-                <el-link type="primary" :underline="false" @click="handleSupply(scope.row)">修改库存</el-link>
-              </div>
-            </div>
+<!--            <div v-if="scope.row.productReviewStatus !== 1" class="flex gap-1 justify-center">-->
+<!--              <el-link type="primary" :underline="false" @click="handleUpdate(scope.row)">编辑</el-link>-->
+<!--            </div>-->
+
+<!--            &lt;!&ndash; 审核通过 &ndash;&gt;-->
+<!--            <div v-else-if="scope.row.productReviewStatus === 1" class="flex flex-col gap-1">-->
+<!--              &lt;!&ndash; 下架状态:编辑、上架、停售、修改库存 &ndash;&gt;-->
+<!--              <div v-if="scope.row.productStatus === 0" class="flex gap-1 justify-center">-->
+<!--                <el-link type="primary" :underline="false" @click="handleUpdate(scope.row)">编辑</el-link>-->
+<!--                <el-link type="success" :underline="false" @click="handleShelf(scope.row)">上架</el-link>-->
+<!--                <el-link type="danger" :underline="false" @click="handleDiscontinue(scope.row)">停售</el-link>-->
+<!--              </div>-->
+
+<!--              &lt;!&ndash; 上架状态:编辑、下架、停售、修改库存 &ndash;&gt;-->
+<!--              <div v-else-if="scope.row.productStatus === 1" class="flex gap-1 justify-center">-->
+<!--                <el-link type="primary" :underline="false" @click="handleUpdate(scope.row)">编辑</el-link>-->
+<!--                <el-link type="warning" :underline="false" @click="handleShelf(scope.row)">下架</el-link>-->
+<!--                <el-link type="danger" :underline="false" @click="handleDiscontinue(scope.row)">停售</el-link>-->
+<!--              </div>-->
+<!--              <div v-if="scope.row.productStatus === 1 && scope.row.isSelf === 0" class="flex gap-1 justify-center">-->
+<!--                <el-link type="info" :underline="false" @click="handleAddToSelfPool(scope.row)">加入自营池</el-link>-->
+<!--              </div>-->
+<!--              &lt;!&ndash; 自营商品显示加入精品池按钮 &ndash;&gt;-->
+<!--              <div v-if="scope.row.productStatus === 1 && scope.row.isSelf === 1 && scope.row.productCategory === 1" class="flex gap-1 justify-center">-->
+<!--                <el-link type="warning" :underline="false" @click="handleAddToExquisitePool(scope.row)">加入精品池</el-link>-->
+<!--              </div>-->
+<!--            </div>-->
 
             <!-- 其他状态(待提交、审核驳回等):显示编辑 -->
-            <div v-else class="flex gap-1 justify-center">
+            <div class="flex gap-1 justify-center">
               <el-link type="primary" :underline="false" @click="handleUpdate(scope.row)">编辑</el-link>
             </div>
+<!--            <div class="flex gap-1 justify-center">-->
+<!--              <el-link type="primary" :underline="false" @click="handleSupply(scope.row)">修改库存</el-link>-->
+<!--            </div>-->
           </template>
         </el-table-column>
       </el-table>
@@ -251,11 +277,46 @@
         @pagination="getList"
       />
     </el-card>
+    <!-- 库存修改弹框 -->
+    <el-dialog v-model="inventoryDialog.visible" title="修改库存" width="500px" :close-on-click-modal="false">
+      <div v-loading="inventoryDialog.loading">
+        <el-form ref="inventoryFormRef" :model="inventoryForm" :rules="inventoryRules" label-width="110px">
+          <el-form-item label="虚拟库存" prop="virtualInventory">
+            <el-input-number
+              v-model="inventoryForm.virtualInventory"
+              :min="0"
+              :precision="0"
+              controls-position="right"
+              style="width: 100%"
+              placeholder="请输入虚拟库存"
+            />
+          </el-form-item>
+        </el-form>
+      </div>
+      <template #footer>
+        <el-button @click="inventoryDialog.visible = false">取消</el-button>
+        <el-button type="primary" :loading="inventoryDialog.submitLoading" @click="submitInventory">确定</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="Base" lang="ts">
-import { listBase, getBase, delBase, brandList, categoryTree, shelfReview, changeProductType, getProductStatusCount } from '@/api/product/base';
+import {
+  listBase,
+  getBase,
+  delBase,
+  brandList,
+  updateBase,
+  categoryTree,
+  shelfReview,
+  changeProductType,
+  getProductStatusCount
+} from '@/api/product/base';
+import { generatePPT } from '@/utils/pptPlugin';
+import { addProductSelf } from '@/api/product/productSelf';
+import { addProductExquisite } from '@/api/product/productExquisite';
+import { PriceInventoryForm } from '@/api/product/priceInventory/types';
 import { BaseVO, BaseQuery, BaseForm, StatusCountVo } from '@/api/product/base/types';
 import { BrandVO } from '@/api/product/brand/types';
 import { listBrand } from '@/api/product/brand';
@@ -328,7 +389,7 @@ const data = reactive<PageData<BaseForm, BaseQuery>>({
     pageSize: 10,
     productNo: undefined,
     itemName: undefined,
-    brandName: undefined,
+    brandId: undefined,
     productTag: undefined,
     purchaseNature: undefined,
     supplierType: undefined,
@@ -366,11 +427,11 @@ const data = reactive<PageData<BaseForm, BaseQuery>>({
 
 const { queryParams, form, rules } = toRefs(data);
 
-
 /** 查询产品基础信息列表 */
 const getList = async () => {
   loading.value = true;
   try {
+    initRouteParams();
     const params = { ...queryParams.value };
     const currentPageNum = queryParams.value.pageNum;
 
@@ -512,32 +573,67 @@ const handleDelete = async (row?: BaseVO) => {
 };
 
 /** 导出按钮操作 */
-const handleExport = () => {
-  proxy?.download(
-    'product/base/export',
-    {
-      ...queryParams.value
-    },
-    `base_${new Date().getTime()}.xlsx`
-  );
+const handleExport = async () => {
+  // 检查是否有选中的商品
+  if (ids.value.length === 0) {
+    proxy?.$modal.msgWarning('请先选择要导出的商品');
+    return;
+  }
+
+  // 获取选中商品的完整信息
+  const selectedProducts = baseList.value.filter(item => ids.value.includes(item.id));
+
+  if (selectedProducts.length === 0) {
+    proxy?.$modal.msgWarning('未找到选中的商品信息');
+    return;
+  }
+
+  // 转换为 generatePPT 需要的格式
+  const products = selectedProducts.map(item => ({
+    image: item.productImage || item.productImageUrl || '',
+    name: item.itemName || '',
+    code: item.productNo || '',
+    spec: item.specification || item.packagingSpec || '-',
+    price: item.minSellingPrice || item.memberPrice || 0
+  }));
+
+  // 默认模板配置
+  const template = {
+    name: '商品展示方案',
+    title: '商品展示方案',
+    themeColor: '#C00000',
+    itemsPerPage: 1, // 每页1个商品,展示更详细
+    cover: '',
+    logo: ''
+  };
+
+  try {
+    proxy?.$modal.loading('正在生成PPT...');
+    await generatePPT(template, products);
+    proxy?.$modal.closeLoading();
+    proxy?.$modal.msgSuccess('PPT导出成功');
+  } catch (error) {
+    proxy?.$modal.closeLoading();
+    console.error('PPT导出失败:', error);
+    proxy?.$modal.msgError('PPT导出失败');
+  }
 };
 
 /** 查看商品详情 */
 const handleView = (row: BaseVO) => {
-  console.log('查看商品', row);
-  // TODO: 实现查看详情逻辑
+  const url = `https://item.xiaoluwebsite.xyz/item?id=${row.id}`;
+  window.open(url, '_blank');
 };
 
 /** 上下架操作 */
 const handleShelf = async (row: BaseVO) => {
-  // productStatus字段定义为string类型:1=已上架,0=下架,2=上架中
-  const isOnShelf = row.productStatus === '1';
+  const isOnShelf = row.productStatus === 1;
   const action = isOnShelf ? '下架' : '上架';
   await proxy?.$modal.confirm(`确认${action}该商品吗?`);
 
   try {
     // 上架:状态改为2(上架中),下架:状态改为0(下架)
-    const productStatus = isOnShelf ? '0' : '2';
+    const productStatus = isOnShelf ? 0 : 2;
     await shelfReview({
       id: row.id,
       productStatus: productStatus,
@@ -557,16 +653,71 @@ const handlePrice = (row: BaseVO) => {
   // TODO: 打开价格设置对话框
 };
 
-/** 供货存管理 */
-const handleSupply = (row: BaseVO) => {
-  console.log('供货存管理', row);
-  // TODO: 打开供货存管理对话框
+/** 库存修改弹框 */
+const inventoryDialog = reactive({
+  visible: false,
+  loading: false,
+  submitLoading: false
+});
+
+const inventoryFormRef = ref<ElFormInstance>();
+
+const inventoryForm = reactive<PriceInventoryForm>({
+  productId: undefined,
+  totalInventory: undefined,
+  nowInventory: undefined,
+  virtualInventory: undefined
+});
+
+const inventoryRules = {
+  totalInventory: [{ required: true, message: '总库存不能为空', trigger: 'blur' }],
+  nowInventory: [{ required: true, message: '当前可用库存不能为空', trigger: 'blur' }],
+  virtualInventory: [{ required: true, message: '虚拟库存不能为空', trigger: 'blur' }]
+};
+
+/** 打开库存修改弹框 */
+const handleSupply = async (row: BaseVO) => {
+  inventoryForm.id = row.id;
+  inventoryForm.totalInventory = undefined;
+  inventoryForm.nowInventory = undefined;
+  inventoryForm.virtualInventory = undefined;
+  inventoryDialog.loading = true;
+  inventoryDialog.visible = true;
+  try {
+    const res = await getBase(row.id);
+    if (res.data) {
+      inventoryForm.totalInventory = res.data.totalInventory;
+      inventoryForm.nowInventory = res.data.nowInventory;
+      inventoryForm.virtualInventory = res.data.virtualInventory;
+    }
+  } catch (error) {
+    console.error('获取库存信息失败:', error);
+  } finally {
+    inventoryDialog.loading = false;
+  }
+};
+
+/** 提交库存修改 */
+const submitInventory = async () => {
+  await inventoryFormRef.value?.validate();
+  inventoryDialog.submitLoading = true;
+  try {
+    await updateBase({ ...inventoryForm });
+    proxy?.$modal.msgSuccess('库存修改成功');
+    inventoryDialog.visible = false;
+    await getList();
+  } catch (error) {
+    console.error('库存修改失败:', error);
+    proxy?.$modal.msgError('库存修改失败');
+  } finally {
+    inventoryDialog.submitLoading = false;
+  }
 };
 
 /** 停售操作 */
 const handleDiscontinue = async (row: BaseVO) => {
   await proxy?.$modal.confirm('确认停售该商品吗?停售后商品将无法正常售卖。');
-  
+
   try {
     // 调用停售API,将商品类型改为3(停售商品)
     await changeProductType({
@@ -581,6 +732,34 @@ const handleDiscontinue = async (row: BaseVO) => {
   }
 };
 
+/** 加入自营池操作 */
+const handleAddToSelfPool = async (row: BaseVO) => {
+  await proxy?.$modal.confirm('确认将该商品加入自营池吗?');
+
+  try {
+    await addProductSelf({ productId: row.id, auditStatus: 1 });
+    proxy?.$modal.msgSuccess('加入自营池成功');
+    await getList();
+  } catch (error) {
+    console.error('加入自营池失败:', error);
+    proxy?.$modal.msgError('加入自营池失败');
+  }
+};
+
+/** 加入精品池操作 */
+const handleAddToExquisitePool = async (row: BaseVO) => {
+  await proxy?.$modal.confirm('确认将该商品加入精品池吗?');
+
+  try {
+    await addProductExquisite({ productId: row.id, auditStatus: 1 });
+    proxy?.$modal.msgSuccess('加入精品池成功');
+    await getList();
+  } catch (error) {
+    console.error('加入精品池失败:', error);
+    proxy?.$modal.msgError('加入精品池失败');
+  }
+};
+
 /** 跳转到商品审核页面 */
 const handleGoReview = () => {
   router.push({
@@ -602,7 +781,7 @@ const loadBrandOptions = async (keyword?: string) => {
   brandLoading.value = true;
   try {
     const res = await listBrand({ pageNum: 1, pageSize: 100, brandName: keyword });
-    brandOptions.value =  res.rows || [];
+    brandOptions.value = res.rows || [];
   } catch (error) {
     console.error('加载品牌列表失败:', error);
   } finally {

+ 13 - 10
src/views/product/base/review.vue

@@ -117,13 +117,13 @@
         </el-table-column>
         <el-table-column label="SKU价格" align="center" width="180">
           <template #default="scope">
-            <div class="text-left" style="font-size: 12px;">
+            <div class="text-left" style="font-size: 12px">
               <div>
                 <span class="text-gray-500">市场价:</span>
                 <span class="text-red-500">¥{{ scope.row.marketPrice || '0.00' }}</span>
               </div>
               <div>
-                <span class="text-gray-500">会员价:</span>
+                <span class="text-gray-500">官网价:</span>
                 <span class="text-red-500">¥{{ scope.row.memberPrice || '0.00' }}</span>
               </div>
               <div>
@@ -142,7 +142,7 @@
               </div>
               <div>
                 <span class="text-gray-500">暂估毛利率:</span>
-                <span>{{ scope.row.tempGrossMargin || '0.0000' }}%</span>
+                <span>{{ scope.row.tempGrossMargin || '0.00' }}%</span>
               </div>
             </div>
           </template>
@@ -155,9 +155,9 @@
         <el-table-column label="商品状态" align="center" width="100">
           <template #default="scope">
             <el-tag v-if="scope.row.productReviewStatus === 0" type="info">待采购审核</el-tag>
-            <el-tag v-else-if="scope.row.productReviewStatus === 1" type="warning">审核通过</el-tag>
-            <el-tag v-else-if="scope.row.productReviewStatus === 2" type="success">驳回</el-tag>
-            <el-tag v-else-if="scope.row.productReviewStatus === 3" type="danger">待营销审核</el-tag>
+            <el-tag v-else-if="scope.row.productReviewStatus === 1" type="success">审核通过</el-tag>
+            <el-tag v-else-if="scope.row.productReviewStatus === 2" type="danger">驳回</el-tag>
+            <el-tag v-else-if="scope.row.productReviewStatus === 3" type="warning">待营销审核</el-tag>
             <span v-else>-</span>
           </template>
         </el-table-column>
@@ -222,8 +222,9 @@
         </el-form-item>
         <el-form-item label="审核结果" prop="reviewStatus">
           <el-radio-group v-model="reviewForm.reviewStatus">
-            <el-radio :label="2">审核通过</el-radio>
-            <el-radio :label="3">审核驳回</el-radio>
+            <el-radio v-if="reviewForm.productReviewStatus === 0" :label="3">采购审核通过</el-radio>
+            <el-radio v-if="reviewForm.productReviewStatus === 3" :label="1">营销审核通过</el-radio>
+            <el-radio :label="2">审核驳回</el-radio>
           </el-radio-group>
         </el-form-item>
         <el-form-item label="审核意见" prop="reviewComments">
@@ -444,7 +445,8 @@ const handlePurchaseReview = (row: BaseVO) => {
     productNo: row.productNo,
     itemName: row.itemName,
     reviewStatus: 3,
-    reviewComments: ''
+    reviewComments: '',
+    productReviewStatus: row.productReviewStatus
   };
 }
 
@@ -457,7 +459,8 @@ const handleMarketingReview = (row: BaseVO) => {
     productNo: row.productNo,
     itemName: row.itemName,
     reviewStatus: 1,
-    reviewComments: ''
+    reviewComments: '',
+    productReviewStatus: row.productReviewStatus
   };
 };
 

+ 453 - 0
src/views/product/base/selected.vue

@@ -0,0 +1,453 @@
+<template>
+  <div class="app-container">
+    <!-- 搜索区域 -->
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" label-width="100px">
+            <el-row :gutter="20">
+              <el-col :span="6">
+                <el-form-item label="商品编号" prop="productNo">
+                  <el-input v-model="queryParams.productNo" placeholder="请输入商品编号" clearable @keyup.enter="handleQuery" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="商品名称" prop="itemName">
+                  <el-input v-model="queryParams.itemName" placeholder="请输入商品名称" clearable @keyup.enter="handleQuery" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="商品类别" prop="bottomCategoryId">
+                  <el-tree-select
+                    v-model="queryParams.bottomCategoryId"
+                    :data="categoryOptions"
+                    :props="{ value: 'id', label: 'label', children: 'children' } as any"
+                    value-key="id"
+                    placeholder="请选择商品类别"
+                    clearable
+                    check-strictly
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="商品品牌" prop="brandName">
+                  <el-select-v2
+                    v-model="queryParams.brandName"
+                    :options="brandOptionsFormatted"
+                    placeholder="请选择商品品牌"
+                    clearable
+                    filterable
+                    :loading="brandLoading"
+                    @visible-change="handleBrandVisibleChange"
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="审核状态" prop="auditStatus">
+                  <el-select
+                    v-model="queryParams.auditStatus"
+                    placeholder="请选择审核状态"
+                    clearable
+                  >
+                    <el-option label="待审核" :value="1" />
+                    <el-option label="审核通过" :value="2" />
+                    <el-option label="审核驳回" :value="3" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item>
+                  <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                  <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <!-- 审核商品信息列表 -->
+    <el-card shadow="never" class="table-card">
+      <template #header>
+        <div class="flex items-center justify-between">
+          <span class="font-semibold">审核商品信息列表</span>
+          <div class="flex gap-2">
+            <el-button circle icon="Refresh" @click="getList"></el-button>
+          </div>
+        </div>
+      </template>
+
+      <el-table v-loading="loading" border :data="baseList" :height="tableHeight" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="商品图片" align="center" prop="productImage" width="100" >
+          <template #default="scope">
+            <image-preview :src="scope.row.productImage" :width="60" :height="60"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品编号" align="center" prop="productNo" width="120" >
+          <template #default="scope">
+            <el-link type="primary" @click="handleView(scope.row)">{{ scope.row.productNo }}</el-link>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品信息" align="center" min-width="250" show-overflow-tooltip>
+          <template #default="scope">
+            <div class="text-left">
+              <div>{{ scope.row.itemName }}</div>
+              <div class="text-gray-500" style="font-size: 12px;">品牌: {{ scope.row.brandName || '-' }}</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="基本情况" align="center" width="180">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px;">
+              <div>
+                <span class="text-gray-500">商品分类:</span>
+                <span>{{ scope.row.categoryName || '-' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">单位:</span>
+                <span>{{ scope.row.unitName || '-' }}</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="SKU价格" align="center" width="180">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px">
+              <div>
+                <span class="text-gray-500">市场价:</span>
+                <span class="text-red-500">¥{{ scope.row.marketPrice || '0.00' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">官网价:</span>
+                <span class="text-red-500">¥{{ scope.row.memberPrice || '0.00' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">最低价:</span>
+                <span class="text-red-500">¥{{ scope.row.minSellingPrice || '0.00' }}</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="成本预算" align="center" width="150">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px;">
+              <div>
+                <span class="text-gray-500">采购价:</span>
+                <span>¥{{ scope.row.purchasingPrice || '0.00' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">暂估毛利率:</span>
+                <span>{{ scope.row.tempGrossMargin || '0.00' }}%</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品来源" align="center" width="100">
+          <template #default="scope">
+            <span>{{ scope.row.dataSource || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品状态" align="center" width="100">
+          <template #default="scope">
+            <el-tag v-if="scope.row.auditStatus === 1" type="warning">待审核</el-tag>
+            <el-tag v-else-if="scope.row.auditStatus === 2" type="success">审核通过</el-tag>
+            <el-tag v-else-if="scope.row.auditStatus === 3" type="danger">审核驳回</el-tag>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="审核意见" align="center" width="180" show-overflow-tooltip>
+          <template #default="scope">
+            <span>{{ scope.row.auditReason || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="150" fixed="right" class-name="border-left">
+          <template #default="scope">
+            <div class="flex gap-1 justify-center">
+              <el-link type="primary" :underline="false" @click="handleView(scope.row)">详情</el-link>
+              <el-link type="primary" :underline="false" @click="handleUpdate(scope.row)">编辑</el-link>
+              <el-link v-if="scope.row.auditStatus === 2" type="warning" :underline="false" @click="handleOffShelf(scope.row)">下架</el-link>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 游标分页控制 -->
+      <pagination
+        v-show="baseList.length > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        v-model:way="queryParams.way"
+        :cursor-mode="true"
+        :has-more="hasMore"
+        @pagination="getList"
+      />
+    </el-card>
+
+
+  </div>
+</template>
+
+<script setup name="BaseReview" lang="ts">
+import { getGoodProductPage, brandList, categoryTree } from '@/api/product/base';
+import { BaseVO, BaseQuery } from '@/api/product/base/types';
+import { updateProductExquisite } from '@/api/product/productExquisite';
+import { BrandVO } from '@/api/product/brand/types';
+import { categoryTreeVO } from '@/api/product/category/types';
+import { useRouter, useRoute } from 'vue-router';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const router = useRouter();
+const route = useRoute();
+
+const baseList = ref<BaseVO[]>([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const brandOptions = ref<BrandVO[]>([]);
+const brandLoading = ref(false);
+const brandOptionsFormatted = computed(() => {
+  return brandOptions.value.slice(0, 500).map((item) => ({
+    label: item.brandName,
+    value: item.brandName // review.vue使用brandName作为value
+  }));
+});
+const categoryOptions = ref<categoryTreeVO[]>([]);
+const hasMore = ref(true); // 是否还有更多数据
+const pageHistory = ref([]);
+
+// 动态计算表格高度
+const tableHeight = computed(() => {
+  // 基础高度 = 视口高度 - 顶部导航(84) - 容器padding(16) - 搜索区域 - 卡片header(60) - 分页器(60)
+  const baseHeight = window.innerHeight - 84 - 16;
+  const searchHeight = showSearch.value ? 150 : 10; // 搜索区域高度
+  const cardHeaderHeight = 60; // 卡片header高度
+  const paginationHeight = 60; // 分页器高度
+  return baseHeight - searchHeight - cardHeaderHeight - paginationHeight;
+});
+
+const queryFormRef = ref<ElFormInstance>();
+
+const queryParams = ref<BaseQuery & { auditStatus?: number }>({
+  pageNum: 1,
+  pageSize: 10,
+  way: undefined,
+  productNo: undefined,
+  itemName: undefined,
+  brandName: undefined,
+  purchaseNature: undefined,
+  bottomCategoryId: undefined,
+  isSelf: undefined,
+  auditStatus: undefined,
+  productStatus: undefined,
+  lastSeenId: undefined // 游标分页的lastSeenId
+});
+
+
+/** 查询商品列表 */
+const getList = async () => {
+  loading.value = true;
+  try {
+    const params = { ...queryParams.value };
+    const currentPageNum = queryParams.value.pageNum;
+
+    // 第一页不需要游标参数
+    if (currentPageNum === 1) {
+      delete params.lastSeenId;
+      delete params.way;
+    } else {
+      // way参数:0=上一页,1=下一页
+      if (queryParams.value.way === 0) {
+        // 上一页:使用目标页(即当前显示页)的firstId
+        const nextPageHistory = pageHistory.value[currentPageNum];
+        if (nextPageHistory) {
+          params.firstSeenId = nextPageHistory.firstId;
+          params.way = 0;
+        }
+      } else {
+        // 下一页:使用前一页的lastId作为lastSeenId
+        const prevPageHistory = pageHistory.value[currentPageNum - 1];
+        if (prevPageHistory) {
+          params.lastSeenId = prevPageHistory.lastId;
+          params.way = 1;
+        }
+      }
+    }
+
+    const res = await getGoodProductPage(params);
+    baseList.value = res.rows || [];
+
+    // 判断是否还有更多数据
+    hasMore.value = baseList.value.length === queryParams.value.pageSize;
+
+    // 记录当前页的第一个id和最后一个id
+    if (baseList.value.length > 0) {
+      const firstItem = baseList.value[0];
+      const lastItem = baseList.value[baseList.value.length - 1];
+      //如果长度小于currentPageNum则创建
+
+      if (pageHistory.value.length <= currentPageNum) {
+        pageHistory.value[currentPageNum] = {
+          firstId: firstItem.id,
+          lastId: lastItem.id
+        };
+      }
+    }
+    total.value = res.total || 0;
+  } catch (error) {
+    console.error('获取列表失败:', error);
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 初始化路由参数 */
+const initRouteParams = () => {
+  // 从路由参数中获取筛选条件
+  if (route.query.auditStatus) {
+    queryParams.value.auditStatus = Number(route.query.auditStatus);
+  }
+  if (route.query.brandName) {
+    queryParams.value.brandName = route.query.brandName as string;
+  }
+  if (route.query.bottomCategoryId) {
+    queryParams.value.bottomCategoryId = route.query.bottomCategoryId as string;
+  }
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  queryParams.value.lastSeenId = undefined;
+  pageHistory.value = [0]; // 重置页面历史
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  queryParams.value.lastSeenId = undefined;
+  pageHistory.value = [0]; // 重置页面历史
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: BaseVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'product/base/export',
+    {
+      ...queryParams.value
+    },
+    `base_review_${new Date().getTime()}.xlsx`
+  );
+};
+
+/** 查看商品详情 */
+const handleView = (row: BaseVO) => {
+  const url = `https://item.xiaoluwebsite.xyz/item?id=${row.id}`;
+  window.open(url, '_blank');
+};
+
+/** 编辑商品 */
+const handleUpdate = (row: BaseVO) => {
+  router.push(`/product/base/edit/${row.id}`);
+};
+
+/** 下架操作 */
+const handleOffShelf = async (row: BaseVO) => {
+  await proxy?.$modal.confirm('确认下架该精选商品吗?');
+  try {
+    await updateProductExquisite({
+      id: row.exquisiteId,
+      auditStatus: 3,
+      auditReason: '下架操作'
+    });
+    proxy?.$modal.msgSuccess('下架成功');
+    await getList();
+  } catch (error) {
+    console.error('下架失败:', error);
+    proxy?.$modal.msgError('下架失败');
+  }
+};
+
+/** 查询品牌列表(实时请求,每次只加载500条) */
+const getBrandList = async () => {
+  try {
+    brandLoading.value = true;
+    const res = await brandList({ pageNum: 1, pageSize: 500 });
+    brandOptions.value = res.data || [];
+  } catch (error) {
+    console.error('获取品牌列表失败:', error);
+  } finally {
+    brandLoading.value = false;
+  }
+};
+
+/** 处理品牌下拉框显示/隐藏 */
+const handleBrandVisibleChange = (visible: boolean) => {
+  if (visible && brandOptions.value.length === 0) {
+    getBrandList();
+  }
+};
+
+/** 查询分类树 */
+const getCategoryTree = async () => {
+  const res = await categoryTree();
+  categoryOptions.value = res.data || [];
+};
+
+onMounted(() => {
+  getCategoryTree();
+  initRouteParams();
+  getList();
+});
+</script>
+
+<style scoped lang="scss">
+.app-container {
+  padding: 8px;
+  height: calc(100vh - 84px);
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.table-card {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+
+  :deep(.el-card__body) {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+  }
+
+  :deep(.el-table) {
+    flex: 1;
+  }
+
+  // 确保固定列左侧有边框
+  :deep(.el-table__fixed-right) {
+    box-shadow: -1px 0 0 var(--el-table-border-color) !important;
+  }
+
+  // 固定列的单元格左边框
+  :deep(.el-table__fixed-right .el-table__cell) {
+    border-left: 1px solid var(--el-table-border-color) !important;
+  }
+}
+</style>

+ 583 - 0
src/views/product/base/self.vue

@@ -0,0 +1,583 @@
+<template>
+  <div class="app-container">
+    <!-- 搜索区域 -->
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" label-width="100px">
+            <el-row :gutter="20">
+              <el-col :span="6">
+                <el-form-item label="商品编号" prop="productNo">
+                  <el-input v-model="queryParams.productNo" placeholder="请输入商品编号" clearable @keyup.enter="handleQuery" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="商品名称" prop="itemName">
+                  <el-input v-model="queryParams.itemName" placeholder="请输入商品名称" clearable @keyup.enter="handleQuery" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="商品类别" prop="bottomCategoryId">
+                  <el-tree-select
+                    v-model="queryParams.bottomCategoryId"
+                    :data="categoryOptions"
+                    :props="{ value: 'id', label: 'label', children: 'children' } as any"
+                    value-key="id"
+                    placeholder="请选择商品类别"
+                    clearable
+                    check-strictly
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="商品品牌" prop="brandName">
+                  <el-select-v2
+                    v-model="queryParams.brandName"
+                    :options="brandOptionsFormatted"
+                    placeholder="请选择商品品牌"
+                    clearable
+                    filterable
+                    :loading="brandLoading"
+                    @visible-change="handleBrandVisibleChange"
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="审核状态" prop="auditStatus">
+                  <el-select
+                    v-model="queryParams.auditStatus"
+                    placeholder="请选择审核状态"
+                    clearable
+                  >
+                    <el-option label="待审核" :value="1" />
+                    <el-option label="审核通过" :value="2" />
+                    <el-option label="审核驳回" :value="3" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item>
+                  <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                  <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <!-- 审核商品信息列表 -->
+    <el-card shadow="never" class="table-card">
+      <template #header>
+        <div class="flex items-center justify-between">
+          <span class="font-semibold">审核商品信息列表</span>
+          <div class="flex gap-2">
+
+            <el-button circle icon="Refresh" @click="getList"></el-button>
+          </div>
+        </div>
+      </template>
+
+      <el-table v-loading="loading" border :data="baseList" :height="tableHeight" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="商品图片" align="center" prop="productImage" width="100" >
+          <template #default="scope">
+            <image-preview :src="scope.row.productImage" :width="60" :height="60"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品编号" align="center" prop="productNo" width="120" >
+          <template #default="scope">
+            <el-link type="primary" @click="handleView(scope.row)">{{ scope.row.productNo }}</el-link>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品信息" align="center" min-width="250" show-overflow-tooltip>
+          <template #default="scope">
+            <div class="text-left">
+              <div>{{ scope.row.itemName }}</div>
+              <div class="text-gray-500" style="font-size: 12px;">品牌: {{ scope.row.brandName || '-' }}</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="基本情况" align="center" width="180">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px;">
+              <div>
+                <span class="text-gray-500">商品分类:</span>
+                <span>{{ scope.row.categoryName || '-' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">单位:</span>
+                <span>{{ scope.row.unitName || '-' }}</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="SKU价格" align="center" width="180">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px">
+              <div>
+                <span class="text-gray-500">市场价:</span>
+                <span class="text-red-500">¥{{ scope.row.marketPrice || '0.00' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">官网价:</span>
+                <span class="text-red-500">¥{{ scope.row.memberPrice || '0.00' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">最低价:</span>
+                <span class="text-red-500">¥{{ scope.row.minSellingPrice || '0.00' }}</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="成本预算" align="center" width="150">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px;">
+              <div>
+                <span class="text-gray-500">采购价:</span>
+                <span>¥{{ scope.row.purchasingPrice || '0.00' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">暂估毛利率:</span>
+                <span>{{ scope.row.tempGrossMargin || '0.00' }}%</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品来源" align="center" width="100">
+          <template #default="scope">
+            <span>{{ scope.row.dataSource || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品状态" align="center" width="100">
+          <template #default="scope">
+            <el-tag v-if="scope.row.auditStatus === 1" type="warning">待审核</el-tag>
+            <el-tag v-else-if="scope.row.auditStatus === 2" type="success">审核通过</el-tag>
+            <el-tag v-else-if="scope.row.auditStatus === 3" type="danger">审核驳回</el-tag>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="审核意见" align="center" width="180" show-overflow-tooltip>
+          <template #default="scope">
+            <span>{{ scope.row.auditReason || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="150" fixed="right" class-name="border-left">
+          <template #default="scope">
+            <!-- 待审核状态:只显示编辑 -->
+            <div v-if="scope.row.auditStatus === 1" class="flex gap-1 justify-center">
+              <el-link type="primary" :underline="false" @click="handleUpdate(scope.row)">编辑</el-link>
+            </div>
+
+            <!-- 审核通过 -->
+            <div v-else-if="scope.row.auditStatus === 2" class="flex flex-col gap-1">
+              <!-- 下架状态:编辑、上架、停售 -->
+              <div v-if="scope.row.productStatus === 0" class="flex gap-1 justify-center">
+                <el-link type="primary" :underline="false" @click="handleUpdate(scope.row)">编辑</el-link>
+                <el-link type="success" :underline="false" @click="handleShelf(scope.row)">上架</el-link>
+                <el-link type="danger" :underline="false" @click="handleDiscontinue(scope.row)">停售</el-link>
+              </div>
+
+              <!-- 上架状态:编辑、下架、停售 -->
+              <div v-else-if="scope.row.productStatus === 1" class="flex gap-1 justify-center">
+                <el-link type="primary" :underline="false" @click="handleUpdate(scope.row)">编辑</el-link>
+                <el-link type="warning" :underline="false" @click="handleShelf(scope.row)">下架</el-link>
+                <el-link type="danger" :underline="false" @click="handleDiscontinue(scope.row)">停售</el-link>
+              </div>
+            </div>
+
+            <!-- 审核驳回:显示编辑 -->
+            <div v-else-if="scope.row.auditStatus === 3" class="flex gap-1 justify-center">
+              <el-link type="primary" :underline="false" @click="handleUpdate(scope.row)">编辑</el-link>
+            </div>
+
+            <!-- 其他状态:显示编辑 -->
+            <div v-else class="flex gap-1 justify-center">
+              <el-link type="primary" :underline="false" @click="handleUpdate(scope.row)">编辑</el-link>
+            </div>
+            <div class="flex gap-1 justify-center">
+              <el-link type="primary" :underline="false" @click="handleSupply(scope.row)">修改库存</el-link>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 游标分页控制 -->
+      <pagination
+        v-show="baseList.length > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        v-model:way="queryParams.way"
+        :cursor-mode="true"
+        :has-more="hasMore"
+        @pagination="getList"
+      />
+    </el-card>
+
+    <!-- 库存修改弹框 -->
+    <el-dialog v-model="inventoryDialog.visible" title="修改库存" width="500px" :close-on-click-modal="false">
+      <div v-loading="inventoryDialog.loading">
+        <el-form ref="inventoryFormRef" :model="inventoryForm" :rules="inventoryRules" label-width="110px">
+          <el-form-item label="虚拟库存" prop="virtualInventory">
+            <el-input-number
+              v-model="inventoryForm.virtualInventory"
+              :min="0"
+              :precision="0"
+              controls-position="right"
+              style="width: 100%"
+              placeholder="请输入虚拟库存"
+            />
+          </el-form-item>
+        </el-form>
+      </div>
+      <template #footer>
+        <el-button @click="inventoryDialog.visible = false">取消</el-button>
+        <el-button type="primary" :loading="inventoryDialog.submitLoading" @click="submitInventory">确定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="BaseReview" lang="ts">
+import { getSelfProductPage, brandList, categoryTree, getBase, updateBase, shelfReview, changeProductType } from '@/api/product/base';
+import { BaseVO, BaseQuery, BaseForm } from '@/api/product/base/types';
+import { BrandVO } from '@/api/product/brand/types';
+import { categoryTreeVO } from '@/api/product/category/types';
+import { useRouter, useRoute } from 'vue-router';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const router = useRouter();
+const route = useRoute();
+
+const baseList = ref<BaseVO[]>([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const brandOptions = ref<BrandVO[]>([]);
+const brandLoading = ref(false);
+const brandOptionsFormatted = computed(() => {
+  return brandOptions.value.slice(0, 500).map((item) => ({
+    label: item.brandName,
+    value: item.brandName // review.vue使用brandName作为value
+  }));
+});
+const categoryOptions = ref<categoryTreeVO[]>([]);
+const hasMore = ref(true); // 是否还有更多数据
+const pageHistory = ref([]);
+
+// 动态计算表格高度
+const tableHeight = computed(() => {
+  // 基础高度 = 视口高度 - 顶部导航(84) - 容器padding(16) - 搜索区域 - 卡片header(60) - 分页器(60)
+  const baseHeight = window.innerHeight - 84 - 16;
+  const searchHeight = showSearch.value ? 150 : 10; // 搜索区域高度
+  const cardHeaderHeight = 60; // 卡片header高度
+  const paginationHeight = 60; // 分页器高度
+  return baseHeight - searchHeight - cardHeaderHeight - paginationHeight;
+});
+
+const queryFormRef = ref<ElFormInstance>();
+const inventoryFormRef = ref<ElFormInstance>();
+
+// 库存修改弹框
+const inventoryDialog = reactive({
+  visible: false,
+  loading: false,
+  submitLoading: false
+});
+
+const inventoryForm = reactive<any>({
+  id: undefined,
+  totalInventory: undefined,
+  nowInventory: undefined,
+  virtualInventory: undefined
+});
+
+const inventoryRules = {
+  totalInventory: [{ required: true, message: '总库存不能为空', trigger: 'blur' }],
+  nowInventory: [{ required: true, message: '当前可用库存不能为空', trigger: 'blur' }],
+  virtualInventory: [{ required: true, message: '虚拟库存不能为空', trigger: 'blur' }]
+};
+
+const queryParams = ref<BaseQuery & { auditStatus?: number }>({
+  pageNum: 1,
+  pageSize: 10,
+  way: undefined,
+  productNo: undefined,
+  itemName: undefined,
+  brandName: undefined,
+  purchaseNature: undefined,
+  bottomCategoryId: undefined,
+  isSelf: undefined,
+  auditStatus: undefined,
+  productStatus: undefined,
+  lastSeenId: undefined // 游标分页的lastSeenId
+});
+
+
+/** 查询商品列表 */
+const getList = async () => {
+  loading.value = true;
+  try {
+    const params = { ...queryParams.value };
+    const currentPageNum = queryParams.value.pageNum;
+
+    // 第一页不需要游标参数
+    if (currentPageNum === 1) {
+      delete params.lastSeenId;
+      delete params.way;
+    } else {
+      // way参数:0=上一页,1=下一页
+      if (queryParams.value.way === 0) {
+        // 上一页:使用目标页(即当前显示页)的firstId
+        const nextPageHistory = pageHistory.value[currentPageNum];
+        if (nextPageHistory) {
+          params.firstSeenId = nextPageHistory.firstId;
+          params.way = 0;
+        }
+      } else {
+        // 下一页:使用前一页的lastId作为lastSeenId
+        const prevPageHistory = pageHistory.value[currentPageNum - 1];
+        if (prevPageHistory) {
+          params.lastSeenId = prevPageHistory.lastId;
+          params.way = 1;
+        }
+      }
+    }
+
+    const res = await getSelfProductPage(params);
+    baseList.value = res.rows || [];
+
+    // 判断是否还有更多数据
+    hasMore.value = baseList.value.length === queryParams.value.pageSize;
+
+    // 记录当前页的第一个id和最后一个id
+    if (baseList.value.length > 0) {
+      const firstItem = baseList.value[0];
+      const lastItem = baseList.value[baseList.value.length - 1];
+      //如果长度小于currentPageNum则创建
+
+      if (pageHistory.value.length <= currentPageNum) {
+        pageHistory.value[currentPageNum] = {
+          firstId: firstItem.id,
+          lastId: lastItem.id
+        };
+      }
+    }
+    total.value = res.total || 0;
+  } catch (error) {
+    console.error('获取列表失败:', error);
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 初始化路由参数 */
+const initRouteParams = () => {
+  // 从路由参数中获取筛选条件
+  if (route.query.auditStatus) {
+    queryParams.value.auditStatus = Number(route.query.auditStatus);
+  }
+  if (route.query.brandName) {
+    queryParams.value.brandName = route.query.brandName as string;
+  }
+  if (route.query.bottomCategoryId) {
+    queryParams.value.bottomCategoryId = route.query.bottomCategoryId as string;
+  }
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  queryParams.value.lastSeenId = undefined;
+  pageHistory.value = [0]; // 重置页面历史
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  queryParams.value.lastSeenId = undefined;
+  pageHistory.value = [0]; // 重置页面历史
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: BaseVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'product/base/export',
+    {
+      ...queryParams.value
+    },
+    `base_review_${new Date().getTime()}.xlsx`
+  );
+};
+
+/** 查看商品详情 */
+const handleView = (row: BaseVO) => {
+  router.push(`/product/base/detail/${row.id}`);
+};
+
+/** 编辑商品 */
+const handleUpdate = (row: BaseVO) => {
+  router.push(`/product/base/edit/${row.id}`);
+};
+
+/** 上下架操作 */
+const handleShelf = async (row: BaseVO) => {
+  const isOnShelf = row.productStatus === 1;
+  const action = isOnShelf ? '下架' : '上架';
+  await proxy?.$modal.confirm(`确认${action}该商品吗?`);
+
+  try {
+    const productStatus = isOnShelf ? 0 : 2;
+    await shelfReview({
+      id: row.id,
+      productStatus: productStatus,
+      shelfComments: `${action}操作`
+    });
+    proxy?.$modal.msgSuccess(`${action}成功`);
+    await getList();
+  } catch (error) {
+    console.error(`${action}失败:`, error);
+    proxy?.$modal.msgError(`${action}失败`);
+  }
+};
+
+/** 停售操作 */
+const handleDiscontinue = async (row: BaseVO) => {
+  await proxy?.$modal.confirm('确认停售该商品吗?停售后商品将无法正常售卖。');
+
+  try {
+    await changeProductType({
+      id: row.id,
+      productCategory: 3
+    });
+    proxy?.$modal.msgSuccess('停售成功');
+    await getList();
+  } catch (error) {
+    console.error('停售失败:', error);
+    proxy?.$modal.msgError('停售失败');
+  }
+};
+
+/** 打开库存修改弹框 */
+const handleSupply = async (row: BaseVO) => {
+  inventoryForm.id = row.id;
+  inventoryForm.totalInventory = undefined;
+  inventoryForm.nowInventory = undefined;
+  inventoryForm.virtualInventory = undefined;
+  inventoryDialog.loading = true;
+  inventoryDialog.visible = true;
+  try {
+    const res = await getBase(row.id);
+    if (res.data) {
+      const data = res.data as any;
+      inventoryForm.totalInventory = data.totalInventory;
+      inventoryForm.nowInventory = data.nowInventory;
+      inventoryForm.virtualInventory = data.virtualInventory;
+    }
+  } catch (error) {
+    console.error('获取库存信息失败:', error);
+  } finally {
+    inventoryDialog.loading = false;
+  }
+};
+
+/** 提交库存修改 */
+const submitInventory = async () => {
+  await inventoryFormRef.value?.validate();
+  inventoryDialog.submitLoading = true;
+  try {
+    await updateBase({ ...inventoryForm });
+    proxy?.$modal.msgSuccess('库存修改成功');
+    inventoryDialog.visible = false;
+    await getList();
+  } catch (error) {
+    console.error('库存修改失败:', error);
+    proxy?.$modal.msgError('库存修改失败');
+  } finally {
+    inventoryDialog.submitLoading = false;
+  }
+};
+
+/** 查询品牌列表(实时请求,每次只加载500条) */
+const getBrandList = async () => {
+  try {
+    brandLoading.value = true;
+    const res = await brandList({ pageNum: 1, pageSize: 500 });
+    brandOptions.value = res.data || [];
+  } catch (error) {
+    console.error('获取品牌列表失败:', error);
+  } finally {
+    brandLoading.value = false;
+  }
+};
+
+/** 处理品牌下拉框显示/隐藏 */
+const handleBrandVisibleChange = (visible: boolean) => {
+  if (visible && brandOptions.value.length === 0) {
+    getBrandList();
+  }
+};
+
+/** 查询分类树 */
+const getCategoryTree = async () => {
+  const res = await categoryTree();
+  categoryOptions.value = res.data || [];
+};
+
+onMounted(() => {
+  getCategoryTree();
+  initRouteParams();
+  getList();
+});
+</script>
+
+<style scoped lang="scss">
+.app-container {
+  padding: 8px;
+  height: calc(100vh - 84px);
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.table-card {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+
+  :deep(.el-card__body) {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+  }
+
+  :deep(.el-table) {
+    flex: 1;
+  }
+
+  // 确保固定列左侧有边框
+  :deep(.el-table__fixed-right) {
+    box-shadow: -1px 0 0 var(--el-table-border-color) !important;
+  }
+
+  // 固定列的单元格左边框
+  :deep(.el-table__fixed-right .el-table__cell) {
+    border-left: 1px solid var(--el-table-border-color) !important;
+  }
+}
+</style>

+ 54 - 33
src/views/product/base/shelfReview.vue

@@ -30,16 +30,35 @@
                 </el-form-item>
               </el-col>
               <el-col :span="6">
-                <el-form-item label="商品品牌" prop="brandName">
-                  <el-select-v2
-                    v-model="queryParams.brandName"
-                    :options="brandOptionsFormatted"
-                    placeholder="请选择商品品牌"
-                    clearable
+                <el-form-item label="商品品牌" prop="brandId">
+                  <el-select
+                    v-model="queryParams.brandId"
+                    placeholder="请输入品牌名称搜索"
                     filterable
+                    remote
+                    clearable
+                    :remote-method="handleBrandSearch"
                     :loading="brandLoading"
-                    @visible-change="handleBrandVisibleChange"
-                  />
+                    style="width: 100%"
+                  >
+                    <el-option
+                      v-for="item in brandOptions"
+                      :key="item.id"
+                      :label="item.brandName"
+                      :value="item.id"
+                    />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+
+              <el-col :span="6">
+                <el-form-item label="上下架状态" prop="productStatus">
+                  <el-select v-model="queryParams.productStatus" placeholder="请选择" clearable>
+                    <el-option label="已上架" :value="1" />
+<!--                    <el-option label="下架" :value="0" />-->
+                    <el-option label="上架中" :value="2" />
+                    <el-option label="驳回上架" :value="3" />
+                  </el-select>
                 </el-form-item>
               </el-col>
 
@@ -110,7 +129,7 @@
                 <span class="text-red-500">¥{{ scope.row.marketPrice || '0.00' }}</span>
               </div>
               <div>
-                <span class="text-gray-500">会员价:</span>
+                <span class="text-gray-500">官网价:</span>
                 <span class="text-red-500">¥{{ scope.row.memberPrice || '0.00' }}</span>
               </div>
               <div>
@@ -129,7 +148,7 @@
               </div>
               <div>
                 <span class="text-gray-500">暂估毛利率:</span>
-                <span>{{ scope.row.tempGrossMargin || '0.0000' }}%</span>
+                <span>{{ scope.row.tempGrossMargin || '0.00' }}%</span>
               </div>
             </div>
           </template>
@@ -144,6 +163,7 @@
             <el-tag v-if="scope.row.productStatus === '2' || scope.row.productStatus === 2" type="warning">上架中</el-tag>
             <el-tag v-else-if="scope.row.productStatus === '1' || scope.row.productStatus === 1" type="success">已上架</el-tag>
             <el-tag v-else-if="scope.row.productStatus === '0' || scope.row.productStatus === 0" type="info">已下架</el-tag>
+            <el-tag v-else-if="scope.row.productStatus === '3' || scope.row.productStatus === 3" type="info">驳回上架</el-tag>
             <span v-else>-</span>
           </template>
         </el-table-column>
@@ -199,7 +219,7 @@
         <el-form-item label="审核结果" prop="productStatus">
           <el-radio-group v-model="reviewForm.productStatus">
             <el-radio :label="1">通过上架</el-radio>
-            <el-radio :label="0">驳回下架</el-radio>
+            <el-radio :label="3">驳回下架</el-radio>
           </el-radio-group>
         </el-form-item>
         <el-form-item label="审核意见" prop="shelfComments">
@@ -220,6 +240,7 @@
 import { listBase, getBase, shelfReview, brandList, categoryTree } from '@/api/product/base';
 import { BaseVO, BaseQuery, BaseForm } from '@/api/product/base/types';
 import { BrandVO } from '@/api/product/brand/types';
+import { listBrand } from '@/api/product/brand';
 import { categoryTreeVO } from '@/api/product/category/types';
 import { useRouter, useRoute } from 'vue-router';
 
@@ -236,12 +257,7 @@ const multiple = ref(true);
 const total = ref(0);
 const brandOptions = ref<BrandVO[]>([]);
 const brandLoading = ref(false);
-const brandOptionsFormatted = computed(() => {
-  return brandOptions.value.slice(0, 500).map((item) => ({
-    label: item.brandName,
-    value: item.brandName // review.vue使用brandName作为value
-  }));
-});
+let brandSearchTimer: ReturnType<typeof setTimeout> | null = null;
 const categoryOptions = ref<categoryTreeVO[]>([]);
 const hasMore = ref(true); // 是否还有更多数据
 const pageHistory = ref([]);
@@ -286,12 +302,13 @@ const queryParams = ref<BaseQuery>({
   way: undefined,
   productNo: undefined,
   itemName: undefined,
-  brandName: undefined,
+  brandId: undefined,
   purchaseNature: undefined,
   bottomCategoryId: undefined,
   isSelf: undefined,
   productReviewStatus: 1, // 只查询审核通过的数据
-  productStatus: 2, // 用于筛选商品状态
+  productStatus: undefined, // 用于筛选商品状态
+  isShelfAudit: 1,
   lastSeenId: undefined // 游标分页的lastSeenId
 });
 
@@ -306,6 +323,7 @@ const getList = async () => {
     // 强制只查询审核通过的数据
     params.productReviewStatus = 1;
 
+
     // 如果没有选择商品状态,默认查询上架中和已上架的数据
     // 后端需要支持多状态查询,这里通过不传productStatus让后端返回所有状态,前端再过滤
     // 或者后端支持传入多个状态值
@@ -366,8 +384,8 @@ const initRouteParams = () => {
   if (route.query.productStatus) {
     queryParams.value.productStatus = Number(route.query.productStatus);
   }
-  if (route.query.brandName) {
-    queryParams.value.brandName = route.query.brandName as string;
+  if (route.query.brandId) {
+    queryParams.value.brandId = route.query.brandId as string;
   }
   if (route.query.bottomCategoryId) {
     queryParams.value.bottomCategoryId = route.query.bottomCategoryId as string;
@@ -410,7 +428,8 @@ const handleExport = () => {
 
 /** 查看商品详情 */
 const handleView = (row: BaseVO) => {
-  router.push(`/product/base/detail/${row.id}`);
+  const url = `https://item.xiaoluwebsite.xyz/item?id=${row.id}`;
+  window.open(url, '_blank');
 };
 
 /** 编辑商品 */
@@ -457,24 +476,25 @@ const submitReview = async () => {
   await getList();
 };
 
-/** 查询品牌列表(实时请求,每次只加载500条) */
-const getBrandList = async () => {
+/** 加载品牌选项(默认100条) */
+const loadBrandOptions = async (keyword?: string) => {
+  brandLoading.value = true;
   try {
-    brandLoading.value = true;
-    const res = await brandList({ pageNum: 1, pageSize: 500 });
-    brandOptions.value = res.data || [];
+    const res = await listBrand({ pageNum: 1, pageSize: 100, brandName: keyword });
+    brandOptions.value = res.rows || [];
   } catch (error) {
-    console.error('获取品牌列表失败:', error);
+    console.error('加载品牌列表失败:', error);
   } finally {
     brandLoading.value = false;
   }
 };
 
-/** 处理品牌下拉框显示/隐藏 */
-const handleBrandVisibleChange = (visible: boolean) => {
-  if (visible && brandOptions.value.length === 0) {
-    getBrandList();
-  }
+/** 品牌远程搜索(防抖) */
+const handleBrandSearch = (query: string) => {
+  if (brandSearchTimer) clearTimeout(brandSearchTimer);
+  brandSearchTimer = setTimeout(() => {
+    loadBrandOptions(query || undefined);
+  }, 300);
 };
 
 /** 查询分类树 */
@@ -486,6 +506,7 @@ const getCategoryTree = async () => {
 onMounted(() => {
   getCategoryTree();
   initRouteParams();
+  loadBrandOptions();
   getList();
 });
 </script>

+ 11 - 10
src/views/product/brand/edit.vue

@@ -32,12 +32,12 @@
           </el-col>
           <el-col :span="12">
             <el-form-item label="品牌推荐系数" prop="recommendValue">
-              <el-input-number 
-                v-model="form.recommendValue" 
-                :min="0" 
-                :max="9999" 
-                controls-position="right" 
-                style="width: 100%" 
+              <el-input-number
+                v-model="form.recommendValue"
+                :min="0"
+                :max="9999"
+                controls-position="right"
+                style="width: 100%"
                 placeholder="请输入推荐系数"
               />
             </el-form-item>
@@ -49,7 +49,7 @@
         </el-form-item>
 
         <el-form-item label="品牌LOGO" prop="brandLogo">
-          <image-upload v-model="form.brandLogo" />
+          <upload-image v-model="form.brandLogo" :limit="1" width="120px" height="120px" imageText="添加图片" />
         </el-form-item>
 
         <el-form-item label="品牌故事" prop="brandStory">
@@ -77,18 +77,18 @@
         <el-row :gutter="20">
           <el-col :span="12">
             <el-form-item label="上传营业执照" prop="license">
-              <image-upload v-model="form.license" />
+              <upload-image v-model="form.license" :limit="1" width="120px" height="120px" imageText="添加图片" />
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="上传商标注册证" prop="registrationCertificate">
-              <image-upload v-model="form.registrationCertificate" />
+              <upload-image v-model="form.registrationCertificate" :limit="1" width="120px" height="120px" imageText="添加图片" />
             </el-form-item>
           </el-col>
         </el-row>
 
         <el-form-item label="到期时间" prop="expireTime">
-          <el-date-picker 
+          <el-date-picker
             v-model="form.expireTime"
             type="datetime"
             value-format="YYYY-MM-DD HH:mm:ss"
@@ -131,6 +131,7 @@
 <script setup lang="ts" name="BrandEdit">
 import { getBrand, addBrand, updateBrand } from '@/api/product/brand';
 import { BrandForm } from '@/api/product/brand/types';
+import UploadImage from '@/components/upload-image/index.vue';
 
 const route = useRoute();
 const router = useRouter();

+ 2 - 2
src/views/product/category/index.vue

@@ -126,8 +126,8 @@
           <el-col :span="12">
             <el-form-item label="平台" prop="platform">
               <el-select v-model="form.platform" placeholder="请选择所属平台" style="width: 100%">
-                <el-option label="工业品" :value="0" />
-                <el-option label="PC端" :value="1" />
+                <el-option label="PC端" :value="0" />
+                <el-option label="工业品" :value="1" />
               </el-select>
             </el-form-item>
           </el-col>

+ 36 - 25
src/views/product/pool/index.vue

@@ -4,13 +4,13 @@
       <div v-show="showSearch" class="mb-[10px]">
         <el-card shadow="hover">
           <el-form ref="queryFormRef" :model="queryParams" :inline="true">
-            <el-form-item label="商品池名称" prop="name">
-              <el-input v-model="queryParams.name" placeholder="请输入分类商品池名称" clearable @keyup.enter="handleQuery" />
+            <el-form-item label="商品池名称" prop="name" :label-width="100">
+              <el-input v-model="queryParams.name" placeholder="请输入营销商品池名称" clearable @keyup.enter="handleQuery" />
             </el-form-item>
             <el-form-item>
               <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
               <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-              <el-button type="primary" icon="Plus" @click="handleAdd" v-hasPermi="['product:pool:add']">新增</el-button>
+
             </el-form-item>
           </el-form>
         </el-card>
@@ -18,25 +18,36 @@
     </transition>
 
     <el-card shadow="never">
+      <template #header>
+              <el-row :gutter="10" class="mb8" justify="space-between">
+                <el-col :span="1.5">
+                  <span class="table-title">营销商品池信息列表</span>
+                </el-col>
+                <el-col :span="1.5">
+                  <el-button type="primary" icon="Plus" @click="handleAdd" v-hasPermi="['product:pool:add']">新增</el-button>
+                </el-col>
+              </el-row>
+      </template>
 
 
       <el-table v-loading="loading" border :data="poolList">
-        
-        <el-table-column label="分类商品池名称" align="center" prop="name" />
-        <el-table-column label="产品池类型" align="center" prop="type">
+
+        <el-table-column label="营销商品池名称" align="center" prop="name" />
+        <!-- <el-table-column label="产品池类型" align="center" prop="type">
           <template #default="scope">
             {{ getPoolTypeLabel(scope.row.type) }}
           </template>
-        </el-table-column>
-        <el-table-column label="商品数量" align="center" prop="productCount" />
+        </el-table-column> -->
+<!--        <el-table-column label="商品数量" align="center" prop="productCount" />-->
         <el-table-column label="创建时间" align="center" prop="createTime" width="180" />
-        <el-table-column label="待申请" align="center" prop="waitApplyCount" />
-        <el-table-column label="待审核" align="center" prop="waitReviewCount" />
-        <el-table-column label="已通过" align="center" prop="approvedCount" />
-        <el-table-column label="已驳回" align="center" prop="rejectedCount" />
+        <el-table-column label="备注" align="center" prop="remark" width="180" />
+        <!-- <el-table-column label="待申请" align="center" prop="waitApplyCount" /> -->
+        <!-- <el-table-column label="待审核" align="center" prop="waitReviewCount" /> -->
+        <el-table-column label="商品数量" align="center" prop="approvedCount" />
+        <!-- <el-table-column label="已驳回" align="center" prop="rejectedCount" /> -->
         <el-table-column label="操作" align="center" width="260">
           <template #default="scope">
-            <el-link type="primary" @click="handleModifyPool(scope.row)" v-hasPermi="['product:pool:edit']">修改分类商品池售卖</el-link>
+            <el-link type="primary" @click="handleModifyPool(scope.row)" v-hasPermi="['product:pool:edit']">修改营销商品池售卖</el-link>
             <el-divider direction="vertical" />
             <el-link type="primary" @click="handlePoolManage(scope.row)" v-hasPermi="['product:pool:edit']">商品池管理</el-link>
           </template>
@@ -48,10 +59,10 @@
     <!-- 添加或修改产品池对话框 -->
     <el-dialog title="项目/平台商品信息" v-model="dialog.visible" width="500px" append-to-body>
       <el-form ref="poolFormRef" :model="form" :rules="rules" label-width="110px">
-        <el-form-item label="项目/平台名称:" prop="name">
+        <el-form-item label="商品池名称:" prop="name">
           <el-input v-model="form.name" placeholder="办公设备精选池" />
         </el-form-item>
-        <el-form-item label="产品池类型:" prop="type">
+        <!-- <el-form-item label="产品池类型:" prop="type">
           <el-select v-model="form.type" placeholder="请选择产品池类型" style="width: 100%">
             <el-option
               v-for="item in poolTypeOptions"
@@ -60,20 +71,20 @@
               :value="item.value"
             />
           </el-select>
-        </el-form-item>
+        </el-form-item> -->
         <el-form-item label="状态:" prop="isShow">
-          <el-switch 
-            v-model="form.isShow" 
-            active-value="1" 
+          <el-switch
+            v-model="form.isShow"
+            active-value="1"
             inactive-value="0"
             active-text="启用"
           />
         </el-form-item>
         <el-form-item label="描述:" prop="remark">
-          <el-input 
-            v-model="form.remark" 
-            type="textarea" 
-            placeholder="请输入内容" 
+          <el-input
+            v-model="form.remark"
+            type="textarea"
+            placeholder="请输入内容"
             maxlength="200"
             show-word-limit
             :rows="4"
@@ -98,7 +109,7 @@ const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
 /** 产品池类型选项 */
 const poolTypeOptions = [
-  { label: '普通产品池', value: 0 },
+  { label: '自营产品池', value: 0 },
   { label: '精选产品池', value: 1 },
   { label: '协议产品池', value: 2 },
   { label: '项目产品池', value: 3 },
@@ -218,7 +229,7 @@ const handleStatusChange = async (row: PoolVO) => {
   }
 }
 
-/** 修改分类商品池售卖 */
+/** 修改营销商品池售卖 */
 const handleModifyPool = async (row: PoolVO) => {
   reset();
   const res = await getPool(row.id);

+ 8 - 15
src/views/product/pool/review.vue

@@ -61,7 +61,7 @@
         </el-table-column>
         <!-- <el-table-column label="创建人" align="center" prop="createBy" />
         <el-table-column label="审核人" align="center" prop="reviewBy" /> -->
-        <el-table-column label="待申请" align="center" prop="waitApplyCount" />
+        <!-- <el-table-column label="待申请" align="center" prop="waitApplyCount" /> -->
         <el-table-column label="待审核" align="center" prop="waitReviewCount" />
         <el-table-column label="已通过" align="center" prop="approvedCount" />
         <el-table-column label="已驳回" align="center" prop="rejectedCount" />
@@ -73,19 +73,11 @@
               <el-divider direction="vertical" />
               <el-link type="primary" @click="handleReview(scope.row)">去审核</el-link>
             </template>
-            <!-- 已驳回:查看 -->
-            <template v-else-if="queryParams.productReviewStatus === '3'">
-              <el-link type="primary" @click="handleView(scope.row)">查看</el-link>
-            </template>
-            <!-- 已通过:查看 -->
-            <template v-else-if="queryParams.productReviewStatus === '2'">
-              <el-link type="primary" @click="handleView(scope.row)">查看</el-link>
-            </template>
             <!-- 待申请:查看、申请入池单、修改入池 -->
             <template v-else-if="queryParams.productReviewStatus === '0'">
               <el-link type="primary" @click="handleView(scope.row)">查看</el-link>
-              <el-divider direction="vertical" />
-              <el-link type="primary" @click="handleApply(scope.row)">申请入池单</el-link>
+              <!-- <el-divider direction="vertical" />
+              <el-link type="primary" @click="handleApply(scope.row)">申请入池单</el-link> -->
               <el-divider direction="vertical" />
               <el-link type="primary" @click="handleModify(scope.row)">修改入池</el-link>
             </template>
@@ -141,7 +133,7 @@ const { product_review_status } = toRefs<any>(proxy?.useDict('product_review_sta
 
 /** 产品池类型选项 */
 const poolTypeOptions = [
-  { label: '普通产品池', value: 0 },
+  { label: '自营产品池', value: 0 },
   { label: '精选产品池', value: 1 },
   { label: '协议产品池', value: 2 },
   { label: '项目产品池', value: 3 },
@@ -280,7 +272,7 @@ const handleView = async (row: PoolVO) => {
 const handleReview = (row: PoolVO) => {
   proxy?.$router.push({
     name: 'PoolReviewDetail',
-    query: { poolId: row.id, poolName: row.name, type: 'edit', productReviewStatus: route.query.productReviewStatus as string }
+    query: { poolId: row.id, poolName: row.name, type: 'edit' }
   });
 }
 
@@ -309,7 +301,8 @@ const submitReview = () => {
 }
 
 /** 申请入池单 */
-const handleApply = async (row: PoolVO) => {
+const
+handleApply = async (row: PoolVO) => {
   try {
     await proxy?.$modal.confirm('确认要申请该产品池入池吗?');
     await updatePool({
@@ -330,7 +323,7 @@ const handleApply = async (row: PoolVO) => {
 const handleModify = (row: PoolVO) => {
   proxy?.$router.push({
     path: '/product/poolLink',
-    query: { poolId: row.id, poolName: row.name, type: 'edit', productReviewStatus: route.query.productReviewStatus as string }
+    query: { poolId: row.id, poolName: row.name, type: 'edit'}
   });
 }
 

+ 5 - 5
src/views/product/pool/review1.vue

@@ -37,7 +37,7 @@
                     <span class="text-red-500">¥{{ scope.row.marketPrice || '0.00' }}</span>
                   </div>
                   <div>
-                    <span class="text-gray-500">平台售价:</span>
+                    <span class="text-gray-500">官网价:</span>
                     <span class="text-red-500">¥{{ scope.row.platformPrice || '0.00' }}</span>
                   </div>
                   <div>
@@ -61,7 +61,7 @@
                 </div>
               </template>
             </el-table-column>
-            <el-table-column label="项目/平台价" align="center" prop="productPrice" width="120">
+            <el-table-column label="项目/官网价" align="center" prop="productPrice" width="120">
               <template #default="scope">
                 ¥{{ scope.row.productPrice || '0.00' }}
               </template>
@@ -111,7 +111,7 @@
                     <span class="text-red-500">¥{{ scope.row.marketPrice || '0.00' }}</span>
                   </div>
                   <div>
-                    <span class="text-gray-500">平台售价:</span>
+                    <span class="text-gray-500">官网价:</span>
                     <span class="text-red-500">¥{{ scope.row.platformPrice || '0.00' }}</span>
                   </div>
                   <div>
@@ -135,7 +135,7 @@
                 </div>
               </template>
             </el-table-column>
-            <el-table-column label="项目/平台价" align="center" prop="productPrice" width="120">
+            <el-table-column label="项目/官网价" align="center" prop="productPrice" width="120">
               <template #default="scope">
                 ¥{{ scope.row.productPrice || '0.00' }}
               </template>
@@ -164,7 +164,7 @@ const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
 /** 产品池类型选项 */
 const poolTypeOptions = [
-  { label: '普通产品池', value: 0 },
+  { label: '自营产品池', value: 0 },
   { label: '精选产品池', value: 1 },
   { label: '协议产品池', value: 2 },
   { label: '项目产品池', value: 3 },

+ 76 - 30
src/views/product/pool/reviewDetail.vue

@@ -24,7 +24,24 @@
               </el-col>
               <el-col :span="6">
                 <el-form-item label="商品品牌" prop="brandId">
-                  <el-input v-model="queryParams.brandId" placeholder="请选择" clearable />
+                  <el-select
+                    v-model="queryParams.brandId"
+                    placeholder="请输入品牌名称搜索"
+                    filterable
+                    remote
+                    clearable
+                    :remote-method="handleBrandSearch"
+                    :loading="brandLoading"
+                    style="width: 100%"
+                    @keyup.enter="handleQuery"
+                  >
+                    <el-option
+                      v-for="item in brandOptions"
+                      :key="item.id"
+                      :label="item.brandName"
+                      :value="item.id"
+                    />
+                  </el-select>
                 </el-form-item>
               </el-col>
               <el-col :span="6">
@@ -43,23 +60,19 @@
                   <el-tree-select
                     v-model="queryParams.categoryId"
                     :data="categoryOptions"
-                    :props="{ value: 'id', label: 'label', children: 'children' }"
-                    check-strictly
-                    :render-after-expand="false"
-                    clearable
+                    :props="{ value: 'id', label: 'label', children: 'children' } as any"
+                    value-key="id"
                     placeholder="请选择商品类别"
-                  >
-                    <template #default="{ data }">
-                      <span>{{ getCategoryFullPath(data.id) }}</span>
-                    </template>
-                  </el-tree-select>
-                </el-form-item>
-              </el-col>
-              <el-col :span="6">
-                <el-form-item label="创建供应商" prop="supplier">
-                  <el-input v-model="queryParams.supplier" placeholder="请选择创建供应商" clearable />
+                    clearable
+                    check-strictly
+                  />
                 </el-form-item>
               </el-col>
+<!--              <el-col :span="6">-->
+<!--                <el-form-item label="创建供应商" prop="supplier">-->
+<!--                  <el-input v-model="queryParams.supplier" placeholder="请选择创建供应商" clearable />-->
+<!--                </el-form-item>-->
+<!--              </el-col>-->
               <el-col :span="6">
                 <el-form-item label="入池时间" prop="dateRange">
                   <el-date-picker
@@ -88,16 +101,16 @@
         <div class="flex justify-between items-center">
           <span class="font-bold">商品列表信息列表</span>
           <div class="flex gap-2">
-            <el-button type="primary" icon="Plus" @click="handleAddProduct">添加商品</el-button>
+            <!-- <el-button type="primary" icon="Plus" @click="handleAddProduct">添加商品</el-button> -->
             <el-button type="warning" plain :disabled="!hasReviewableItems" @click="handleBatchReview">批量审核</el-button>
-            <el-button type="danger" plain :disabled="selectedPoolLinks.length === 0" @click="handleBatchRemove">批量移出</el-button>
-            <el-button type="primary" plain :disabled="!hasSubmittableItems" @click="handleSubmitReview">提交审核</el-button>
+            <!-- <el-button type="danger" plain :disabled="selectedPoolLinks.length === 0" @click="handleBatchRemove">批量移出</el-button>
+            <el-button type="primary" plain :disabled="!hasSubmittableItems" @click="handleSubmitReview">提交审核</el-button> -->
           </div>
         </div>
       </template>
 
       <el-table v-loading="loading" border :data="productList" @selection-change="handlePoolLinkSelectionChange">
-        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column type="selection" width="55" align="center" :selectable="canSelectForReview" />
         <el-table-column type="index" label="序号" width="60" align="center" />
         <el-table-column label="商品编号" align="center" prop="productNo" width="120" />
         <el-table-column label="商品图片" align="center" prop="productImageUrl" width="100">
@@ -105,7 +118,7 @@
             <image-preview :src="scope.row.productImageUrl" :width="60" :height="60"/>
           </template>
         </el-table-column>
-        <el-table-column label="商品信息" align="center" width="200">
+        <el-table-column label="商品信息" align="center" min-width="200">
           <template #default="scope">
             <div class="text-left" style="font-size: 12px;">
               <div>{{ scope.row.itemName }}</div>
@@ -130,7 +143,7 @@
                 <span class="text-red-500">¥{{ scope.row.marketPrice || '0.00' }}</span>
               </div>
               <div>
-                <span class="text-gray-500">平台售价:</span>
+                <span class="text-gray-500">官网价:</span>
                 <span class="text-red-500">¥{{ scope.row.platformPrice || '0.00' }}</span>
               </div>
               <div>
@@ -154,7 +167,7 @@
             </div>
           </template>
         </el-table-column>
-        <el-table-column label="项目平台价" align="center" prop="platformPrice" width="100" />
+        <!-- <el-table-column label="项目官网价" align="center" prop="platformPrice" width="100" /> -->
         <el-table-column label="商品状态" align="center" width="80">
           <template #default="scope">
             <el-tag v-if="scope.row.productStatus === '1'" type="success">已上架</el-tag>
@@ -163,7 +176,7 @@
             <el-tag v-else type="info">未知</el-tag>
           </template>
         </el-table-column>
-        <el-table-column label="审核状态" align="center" width="100">
+        <el-table-column label="产品审核状态" align="center" width="100">
           <template #default="scope">
             <el-tag v-if="scope.row.productReviewStatus === '0'" type="info">待提交</el-tag>
             <el-tag v-else-if="scope.row.productReviewStatus === '1'" type="warning">待审核</el-tag>
@@ -172,16 +185,16 @@
             <el-tag v-else type="info">未知</el-tag>
           </template>
         </el-table-column>
+        <el-table-column label="审核原因" align="center" prop="reviewReason" width="150" show-overflow-tooltip />
         <el-table-column label="入池时间" align="center" prop="createTime" width="120" />
-        <el-table-column label="创建供应商" align="center" prop="supplier" width="100" />
-        <el-table-column label="操作" align="center" width="120" fixed="right">
+        <!-- <el-table-column label="操作" align="center" width="120" fixed="right">
           <template #default="scope">
             <div class="flex flex-col gap-1">
               <el-link type="primary" :underline="false" @click="handlePriceMaintain(scope.row)">价格维护</el-link>
               <el-link type="primary" :underline="false" @click="handleStockMaintain(scope.row)">修改库存</el-link>
             </div>
           </template>
-        </el-table-column>
+        </el-table-column> -->
       </el-table>
 
       <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
@@ -196,7 +209,7 @@
         <el-form-item label="市场价" prop="marketPrice">
           <el-input-number v-model="priceDialog.form.marketPrice" :precision="2" :min="0" controls-position="right" style="width: 100%" />
         </el-form-item>
-        <el-form-item label="平台售价" prop="platformPrice">
+        <el-form-item label="官网价" prop="platformPrice">
           <el-input-number v-model="priceDialog.form.platformPrice" :precision="2" :min="0" controls-position="right" style="width: 100%" />
         </el-form-item>
         <el-form-item label="最低售价" prop="minPrice">
@@ -316,7 +329,7 @@
                   <span>¥{{ scope.row.midRangePrice || '0.00' }}</span>
                 </div>
                 <div>
-                  <span class="text-gray-500">平台价:</span>
+                  <span class="text-gray-500">官网价:</span>
                   <span class="text-red-500">¥{{ scope.row.standardPrice || '0.00' }}</span>
                 </div>
                 <div>
@@ -372,6 +385,8 @@ import { categoryTree, listBase } from '@/api/product/base';
 import { BaseVO, BaseQuery } from '@/api/product/base/types';
 import { listPoolLink, batchAddProducts, BatchAddProductData, batchReview, reSubmit, editPrice, editStock, PoolLinkForm } from '@/api/product/poolLink';
 import { PoolLinkQuery, PoolLinkVO } from '@/api/product/poolLink/types';
+import { listBrand } from '@/api/product/brand';
+import { BrandVO } from '@/api/product/brand/types';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const router = useRouter();
@@ -407,6 +422,10 @@ const queryParams = ref({
 const categoryOptions = ref<any[]>([]);
 const categoryMap = ref<Map<string | number, any>>(new Map());
 
+const brandOptions = ref<BrandVO[]>([]);
+const brandLoading = ref(false);
+let brandSearchTimer: ReturnType<typeof setTimeout> | null = null;
+
 // 添加商品对话框
 const addProductDialog = reactive({
   visible: false,
@@ -429,6 +448,27 @@ const addProductQuery = ref({
 const selectedProducts = ref<BaseVO[]>([]);
 const addProductTableRef = ref<any>();
 
+/** 加载品牌选项(默认100条) */
+const loadBrandOptions = async (keyword?: string) => {
+  brandLoading.value = true;
+  try {
+    const res = await listBrand({ pageNum: 1, pageSize: 100, brandName: keyword });
+    brandOptions.value = res.rows || [];
+  } catch (error) {
+    console.error('加载品牌列表失败:', error);
+  } finally {
+    brandLoading.value = false;
+  }
+};
+
+/** 品牌远程搜索(防抖) */
+const handleBrandSearch = (query: string) => {
+  if (brandSearchTimer) clearTimeout(brandSearchTimer);
+  brandSearchTimer = setTimeout(() => {
+    loadBrandOptions(query || undefined);
+  }, 300);
+};
+
 /** 获取分类树 */
 const getCategoryTree = async () => {
   try {
@@ -624,7 +664,7 @@ const handleBatchAdd = async () => {
       poolId: queryParams.value.poolId ,
       products: selectedProducts.value.map(product => ({
         productId: product.id ,
-        agreementPrice: product.standardPrice || product.midRangePrice // 使用平台价或市场价作为协议价
+        agreementPrice: product.standardPrice || product.midRangePrice // 使用官网价或市场价作为协议价
       }))
     };
 
@@ -650,7 +690,7 @@ const handleAddSingleProduct = async (row: BaseVO) => {
       poolId: queryParams.value.poolId ,
       products: [{
         productId: row.id ,
-        agreementPrice: row.standardPrice || row.midRangePrice // 使用平台价或市场价作为协议价
+        agreementPrice: row.standardPrice || row.midRangePrice // 使用官网价或市场价作为协议价
       }]
     };
 
@@ -682,6 +722,11 @@ const hasReviewableItems = computed(() => {
   return selectedPoolLinks.value.some(item => item.productReviewStatus === '1');
 });
 
+/** 判断商品是否可以被选择用于审核(只有待审核状态的才能被选择) */
+const canSelectForReview = (row: PoolLinkVO) => {
+  return row.productReviewStatus === '1';
+};
+
 /** 是否有可提交审核的项(待提交或驳回状态) */
 const hasSubmittableItems = computed(() => {
   return selectedPoolLinks.value.some(item => item.productReviewStatus === '0' || item.productReviewStatus === '3');
@@ -834,6 +879,7 @@ const submitStockForm = async () => {
 
 onMounted(() => {
   getCategoryTree();
+  loadBrandOptions();
   getList();
 });
 </script>

+ 505 - 0
src/views/product/pool/selectedAudit.vue

@@ -0,0 +1,505 @@
+<template>
+  <div class="app-container">
+    <!-- 搜索区域 -->
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" label-width="100px">
+            <el-row :gutter="20">
+              <el-col :span="6">
+                <el-form-item label="商品编号" prop="productNo">
+                  <el-input v-model="queryParams.productNo" placeholder="请输入商品编号" clearable @keyup.enter="handleQuery" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="商品名称" prop="itemName">
+                  <el-input v-model="queryParams.itemName" placeholder="请输入商品名称" clearable @keyup.enter="handleQuery" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="商品类别" prop="bottomCategoryId">
+                  <el-tree-select
+                    v-model="queryParams.bottomCategoryId"
+                    :data="categoryOptions"
+                    :props="{ value: 'id', label: 'label', children: 'children' } as any"
+                    value-key="id"
+                    placeholder="请选择商品类别"
+                    clearable
+                    check-strictly
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="商品品牌" prop="brandName">
+                  <el-select-v2
+                    v-model="queryParams.brandName"
+                    :options="brandOptionsFormatted"
+                    placeholder="请选择商品品牌"
+                    clearable
+                    filterable
+                    :loading="brandLoading"
+                    @visible-change="handleBrandVisibleChange"
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="审核状态" prop="auditStatus">
+                  <el-select
+                    v-model="queryParams.auditStatus"
+                    placeholder="请选择审核状态"
+                    clearable
+                  >
+                    <el-option label="待审核" :value="1" />
+                    <el-option label="审核通过" :value="2" />
+                    <el-option label="审核驳回" :value="3" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item>
+                  <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                  <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <!-- 审核商品信息列表 -->
+    <el-card shadow="never" class="table-card">
+      <template #header>
+        <div class="flex items-center justify-between">
+          <span class="font-semibold">审核商品信息列表</span>
+          <div class="flex gap-2">
+            <el-button circle icon="Refresh" @click="getList"></el-button>
+          </div>
+        </div>
+      </template>
+
+      <el-table v-loading="loading" border :data="baseList" :height="tableHeight" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="商品图片" align="center" prop="productImage" width="100" >
+          <template #default="scope">
+            <image-preview :src="scope.row.productImage" :width="60" :height="60"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品编号" align="center" prop="productNo" width="120" >
+          <template #default="scope">
+            <el-link type="primary" @click="handleView(scope.row)">{{ scope.row.productNo }}</el-link>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品信息" align="center" min-width="250" show-overflow-tooltip>
+          <template #default="scope">
+            <div class="text-left">
+              <div>{{ scope.row.itemName }}</div>
+              <div class="text-gray-500" style="font-size: 12px;">品牌: {{ scope.row.brandName || '-' }}</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="基本情况" align="center" width="180">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px;">
+              <div>
+                <span class="text-gray-500">商品分类:</span>
+                <span>{{ scope.row.categoryName || '-' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">单位:</span>
+                <span>{{ scope.row.unitName || '-' }}</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="SKU价格" align="center" width="180">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px">
+              <div>
+                <span class="text-gray-500">市场价:</span>
+                <span class="text-red-500">¥{{ scope.row.marketPrice || '0.00' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">官网价:</span>
+                <span class="text-red-500">¥{{ scope.row.memberPrice || '0.00' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">最低价:</span>
+                <span class="text-red-500">¥{{ scope.row.minSellingPrice || '0.00' }}</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="成本预算" align="center" width="150">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px;">
+              <div>
+                <span class="text-gray-500">采购价:</span>
+                <span>¥{{ scope.row.purchasingPrice || '0.00' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">暂估毛利率:</span>
+                <span>{{ scope.row.tempGrossMargin || '0.00' }}%</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品来源" align="center" width="100">
+          <template #default="scope">
+            <span>{{ scope.row.dataSource || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品状态" align="center" width="100">
+          <template #default="scope">
+            <el-tag v-if="scope.row.auditStatus === 1" type="warning">待审核</el-tag>
+            <el-tag v-else-if="scope.row.auditStatus === 2" type="success">审核通过</el-tag>
+            <el-tag v-else-if="scope.row.auditStatus === 3" type="danger">审核驳回</el-tag>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="审核意见" align="center" width="180" show-overflow-tooltip>
+          <template #default="scope">
+            <span>{{ scope.row.auditReason || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="100" fixed="right" class-name="border-left">
+          <template #default="scope">
+            <div class="flex justify-center">
+              <!-- 只有待审核状态才显示审核按钮 -->
+              <el-link v-if="scope.row.auditStatus === 1" type="primary" :underline="false" @click="handleAudit(scope.row)">审核</el-link>
+              <span v-else>-</span>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 游标分页控制 -->
+      <pagination
+        v-show="baseList.length > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        v-model:way="queryParams.way"
+        :cursor-mode="true"
+        :has-more="hasMore"
+        @pagination="getList"
+      />
+    </el-card>
+
+    <!-- 审核对话框 -->
+    <el-dialog v-model="reviewDialog.visible" :title="reviewDialog.title" width="600px" append-to-body>
+      <el-form ref="reviewFormRef" :model="reviewForm" :rules="reviewRules" label-width="100px">
+        <el-form-item label="商品编号">
+          <el-input v-model="reviewForm.productNo" disabled />
+        </el-form-item>
+        <el-form-item label="商品名称">
+          <el-input v-model="reviewForm.itemName" disabled />
+        </el-form-item>
+        <el-form-item label="审核结果" prop="auditStatus">
+          <el-radio-group v-model="reviewForm.auditStatus">
+            <el-radio :value="2">通过</el-radio>
+            <el-radio :value="3">驳回</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="审核意见" prop="auditReason">
+          <el-input v-model="reviewForm.auditReason" type="textarea" :rows="4" placeholder="请输入审核意见" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="reviewDialog.visible = false">取消</el-button>
+          <el-button type="primary" @click="submitReview">确定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="BaseReview" lang="ts">
+import { getGoodProductPage, brandList, categoryTree } from '@/api/product/base';
+import { BaseVO, BaseQuery } from '@/api/product/base/types';
+import { updateProductExquisite } from '@/api/product/productExquisite';
+import { ProductExquisiteForm } from '@/api/product/productExquisite/types';
+import { BrandVO } from '@/api/product/brand/types';
+import { categoryTreeVO } from '@/api/product/category/types';
+import { useRouter, useRoute } from 'vue-router';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const router = useRouter();
+const route = useRoute();
+
+const baseList = ref<BaseVO[]>([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const brandOptions = ref<BrandVO[]>([]);
+const brandLoading = ref(false);
+const brandOptionsFormatted = computed(() => {
+  return brandOptions.value.slice(0, 500).map((item) => ({
+    label: item.brandName,
+    value: item.brandName // review.vue使用brandName作为value
+  }));
+});
+const categoryOptions = ref<categoryTreeVO[]>([]);
+const hasMore = ref(true); // 是否还有更多数据
+const pageHistory = ref([]);
+
+// 动态计算表格高度
+const tableHeight = computed(() => {
+  // 基础高度 = 视口高度 - 顶部导航(84) - 容器padding(16) - 搜索区域 - 卡片header(60) - 分页器(60)
+  const baseHeight = window.innerHeight - 84 - 16;
+  const searchHeight = showSearch.value ? 150 : 10; // 搜索区域高度
+  const cardHeaderHeight = 60; // 卡片header高度
+  const paginationHeight = 60; // 分页器高度
+  return baseHeight - searchHeight - cardHeaderHeight - paginationHeight;
+});
+
+const queryFormRef = ref<ElFormInstance>();
+const reviewFormRef = ref<ElFormInstance>();
+
+// 审核对话框
+const reviewDialog = reactive({
+  visible: false,
+  title: '上架审核'
+});
+
+// 审核表单
+const reviewForm = ref<any>({
+  productId: undefined,
+  productNo: '',
+  itemName: '',
+  auditStatus: 2,
+  auditReason: ''
+});
+
+// 审核表单验证规则
+const reviewRules = ref({
+  auditStatus: [{ required: true, message: '请选择审核结果', trigger: 'change' }],
+  auditReason: [{ required: true, message: '请输入审核意见', trigger: 'blur' }]
+});
+
+const queryParams = ref<BaseQuery & { auditStatus?: number }>({
+  pageNum: 1,
+  pageSize: 10,
+  way: undefined,
+  productNo: undefined,
+  itemName: undefined,
+  brandName: undefined,
+  purchaseNature: undefined,
+  bottomCategoryId: undefined,
+  isSelf: undefined,
+  auditStatus: undefined,
+  productStatus: undefined,
+  lastSeenId: undefined // 游标分页的lastSeenId
+});
+
+
+/** 查询商品列表 */
+const getList = async () => {
+  loading.value = true;
+  try {
+    const params = { ...queryParams.value };
+    const currentPageNum = queryParams.value.pageNum;
+
+    // 第一页不需要游标参数
+    if (currentPageNum === 1) {
+      delete params.lastSeenId;
+      delete params.way;
+    } else {
+      // way参数:0=上一页,1=下一页
+      if (queryParams.value.way === 0) {
+        // 上一页:使用目标页(即当前显示页)的firstId
+        const nextPageHistory = pageHistory.value[currentPageNum];
+        if (nextPageHistory) {
+          params.firstSeenId = nextPageHistory.firstId;
+          params.way = 0;
+        }
+      } else {
+        // 下一页:使用前一页的lastId作为lastSeenId
+        const prevPageHistory = pageHistory.value[currentPageNum - 1];
+        if (prevPageHistory) {
+          params.lastSeenId = prevPageHistory.lastId;
+          params.way = 1;
+        }
+      }
+    }
+
+    const res = await getGoodProductPage(params);
+    baseList.value = res.rows || [];
+
+    // 判断是否还有更多数据
+    hasMore.value = baseList.value.length === queryParams.value.pageSize;
+
+    // 记录当前页的第一个id和最后一个id
+    if (baseList.value.length > 0) {
+      const firstItem = baseList.value[0];
+      const lastItem = baseList.value[baseList.value.length - 1];
+      //如果长度小于currentPageNum则创建
+
+      if (pageHistory.value.length <= currentPageNum) {
+        pageHistory.value[currentPageNum] = {
+          firstId: firstItem.id,
+          lastId: lastItem.id
+        };
+      }
+    }
+    total.value = res.total || 0;
+  } catch (error) {
+    console.error('获取列表失败:', error);
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 初始化路由参数 */
+const initRouteParams = () => {
+  // 从路由参数中获取筛选条件
+  if (route.query.auditStatus) {
+    queryParams.value.auditStatus = Number(route.query.auditStatus);
+  }
+  if (route.query.brandName) {
+    queryParams.value.brandName = route.query.brandName as string;
+  }
+  if (route.query.bottomCategoryId) {
+    queryParams.value.bottomCategoryId = route.query.bottomCategoryId as string;
+  }
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  queryParams.value.lastSeenId = undefined;
+  pageHistory.value = [0]; // 重置页面历史
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  queryParams.value.lastSeenId = undefined;
+  pageHistory.value = [0]; // 重置页面历史
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: BaseVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'product/base/export',
+    {
+      ...queryParams.value
+    },
+    `base_review_${new Date().getTime()}.xlsx`
+  );
+};
+
+/** 查看商品详情 */
+const handleView = (row: BaseVO) => {
+  router.push(`/product/base/detail/${row.id}`);
+};
+
+/** 审核商品 */
+const handleAudit = (row: BaseVO) => {
+  reviewDialog.visible = true;
+  reviewDialog.title = '上架审核';
+  reviewForm.value = {
+    productId: row.id,
+    productNo: row.productNo,
+    itemName: row.itemName,
+    auditStatus: 2,
+    auditReason: ''
+  };
+};
+
+/** 提交审核 */
+const submitReview = async () => {
+  await reviewFormRef.value?.validate();
+  const data: ProductExquisiteForm = {
+    productId: reviewForm.value.productId,
+    auditStatus: Number(reviewForm.value.auditStatus),
+    auditReason: reviewForm.value.auditReason
+  };
+  await updateProductExquisite(data);
+  proxy?.$modal.msgSuccess(reviewForm.value.auditStatus === 2 ? '审核通过' : '审核驳回');
+  reviewDialog.visible = false;
+  await getList();
+};
+
+/** 查询品牌列表(实时请求,每次只加载500条) */
+const getBrandList = async () => {
+  try {
+    brandLoading.value = true;
+    const res = await brandList({ pageNum: 1, pageSize: 500 });
+    brandOptions.value = res.data || [];
+  } catch (error) {
+    console.error('获取品牌列表失败:', error);
+  } finally {
+    brandLoading.value = false;
+  }
+};
+
+/** 处理品牌下拉框显示/隐藏 */
+const handleBrandVisibleChange = (visible: boolean) => {
+  if (visible && brandOptions.value.length === 0) {
+    getBrandList();
+  }
+};
+
+/** 查询分类树 */
+const getCategoryTree = async () => {
+  const res = await categoryTree();
+  categoryOptions.value = res.data || [];
+};
+
+onMounted(() => {
+  getCategoryTree();
+  initRouteParams();
+  getList();
+});
+</script>
+
+<style scoped lang="scss">
+.app-container {
+  padding: 8px;
+  height: calc(100vh - 84px);
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.table-card {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+
+  :deep(.el-card__body) {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+  }
+
+  :deep(.el-table) {
+    flex: 1;
+  }
+
+  // 确保固定列左侧有边框
+  :deep(.el-table__fixed-right) {
+    box-shadow: -1px 0 0 var(--el-table-border-color) !important;
+  }
+
+  // 固定列的单元格左边框
+  :deep(.el-table__fixed-right .el-table__cell) {
+    border-left: 1px solid var(--el-table-border-color) !important;
+  }
+}
+</style>

+ 506 - 0
src/views/product/pool/selfAudit.vue

@@ -0,0 +1,506 @@
+<template>
+  <div class="app-container">
+    <!-- 搜索区域 -->
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" label-width="100px">
+            <el-row :gutter="20">
+              <el-col :span="6">
+                <el-form-item label="商品编号" prop="productNo">
+                  <el-input v-model="queryParams.productNo" placeholder="请输入商品编号" clearable @keyup.enter="handleQuery" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="商品名称" prop="itemName">
+                  <el-input v-model="queryParams.itemName" placeholder="请输入商品名称" clearable @keyup.enter="handleQuery" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="商品类别" prop="bottomCategoryId">
+                  <el-tree-select
+                    v-model="queryParams.bottomCategoryId"
+                    :data="categoryOptions"
+                    :props="{ value: 'id', label: 'label', children: 'children' } as any"
+                    value-key="id"
+                    placeholder="请选择商品类别"
+                    clearable
+                    check-strictly
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="商品品牌" prop="brandName">
+                  <el-select-v2
+                    v-model="queryParams.brandName"
+                    :options="brandOptionsFormatted"
+                    placeholder="请选择商品品牌"
+                    clearable
+                    filterable
+                    :loading="brandLoading"
+                    @visible-change="handleBrandVisibleChange"
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="审核状态" prop="auditStatus">
+                  <el-select
+                    v-model="queryParams.auditStatus"
+                    placeholder="请选择审核状态"
+                    clearable
+                  >
+                    <el-option label="待审核" :value="1" />
+                    <el-option label="审核通过" :value="2" />
+                    <el-option label="审核驳回" :value="3" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item>
+                  <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                  <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <!-- 审核商品信息列表 -->
+    <el-card shadow="never" class="table-card">
+      <template #header>
+        <div class="flex items-center justify-between">
+          <span class="font-semibold">审核商品信息列表</span>
+          <div class="flex gap-2">
+
+            <el-button circle icon="Refresh" @click="getList"></el-button>
+          </div>
+        </div>
+      </template>
+
+      <el-table v-loading="loading" border :data="baseList" :height="tableHeight" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="商品图片" align="center" prop="productImage" width="100" >
+          <template #default="scope">
+            <image-preview :src="scope.row.productImage" :width="60" :height="60"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品编号" align="center" prop="productNo" width="120" >
+          <template #default="scope">
+            <el-link type="primary" @click="handleView(scope.row)">{{ scope.row.productNo }}</el-link>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品信息" align="center" min-width="250" show-overflow-tooltip>
+          <template #default="scope">
+            <div class="text-left">
+              <div>{{ scope.row.itemName }}</div>
+              <div class="text-gray-500" style="font-size: 12px;">品牌: {{ scope.row.brandName || '-' }}</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="基本情况" align="center" width="180">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px;">
+              <div>
+                <span class="text-gray-500">商品分类:</span>
+                <span>{{ scope.row.categoryName || '-' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">单位:</span>
+                <span>{{ scope.row.unitName || '-' }}</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="SKU价格" align="center" width="180">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px">
+              <div>
+                <span class="text-gray-500">市场价:</span>
+                <span class="text-red-500">¥{{ scope.row.marketPrice || '0.00' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">官网价:</span>
+                <span class="text-red-500">¥{{ scope.row.memberPrice || '0.00' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">最低价:</span>
+                <span class="text-red-500">¥{{ scope.row.minSellingPrice || '0.00' }}</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="成本预算" align="center" width="150">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px;">
+              <div>
+                <span class="text-gray-500">采购价:</span>
+                <span>¥{{ scope.row.purchasingPrice || '0.00' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">暂估毛利率:</span>
+                <span>{{ scope.row.tempGrossMargin || '0.00' }}%</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品来源" align="center" width="100">
+          <template #default="scope">
+            <span>{{ scope.row.dataSource || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品状态" align="center" width="100">
+          <template #default="scope">
+            <el-tag v-if="scope.row.auditStatus === 1" type="warning">待审核</el-tag>
+            <el-tag v-else-if="scope.row.auditStatus === 2" type="success">审核通过</el-tag>
+            <el-tag v-else-if="scope.row.auditStatus === 3" type="danger">审核驳回</el-tag>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="审核意见" align="center" width="180" show-overflow-tooltip>
+          <template #default="scope">
+            <span>{{ scope.row.auditReason || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="100" fixed="right" class-name="border-left">
+          <template #default="scope">
+            <div class="flex justify-center">
+              <!-- 只有待审核状态才显示审核按钮 -->
+              <el-link v-if="scope.row.auditStatus === 1" type="primary" :underline="false" @click="handleAudit(scope.row)">审核</el-link>
+              <span v-else>-</span>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 游标分页控制 -->
+      <pagination
+        v-show="baseList.length > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        v-model:way="queryParams.way"
+        :cursor-mode="true"
+        :has-more="hasMore"
+        @pagination="getList"
+      />
+    </el-card>
+
+    <!-- 审核对话框 -->
+    <el-dialog v-model="reviewDialog.visible" :title="reviewDialog.title" width="600px" append-to-body>
+      <el-form ref="reviewFormRef" :model="reviewForm" :rules="reviewRules" label-width="100px">
+        <el-form-item label="商品编号">
+          <el-input v-model="reviewForm.productNo" disabled />
+        </el-form-item>
+        <el-form-item label="商品名称">
+          <el-input v-model="reviewForm.itemName" disabled />
+        </el-form-item>
+        <el-form-item label="审核结果" prop="auditStatus">
+          <el-radio-group v-model="reviewForm.auditStatus">
+            <el-radio :value="2">通过</el-radio>
+            <el-radio :value="3">驳回</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="审核意见" prop="auditReason">
+          <el-input v-model="reviewForm.auditReason" type="textarea" :rows="4" placeholder="请输入审核意见" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="reviewDialog.visible = false">取消</el-button>
+          <el-button type="primary" @click="submitReview">确定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="BaseReview" lang="ts">
+import { getSelfProductPage, brandList, categoryTree } from '@/api/product/base';
+import { BaseVO, BaseQuery } from '@/api/product/base/types';
+import { updateProductSelf } from '@/api/product/productSelf';
+import { ProductSelfForm } from '@/api/product/productSelf/types';
+import { BrandVO } from '@/api/product/brand/types';
+import { categoryTreeVO } from '@/api/product/category/types';
+import { useRouter, useRoute } from 'vue-router';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const router = useRouter();
+const route = useRoute();
+
+const baseList = ref<BaseVO[]>([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const brandOptions = ref<BrandVO[]>([]);
+const brandLoading = ref(false);
+const brandOptionsFormatted = computed(() => {
+  return brandOptions.value.slice(0, 500).map((item) => ({
+    label: item.brandName,
+    value: item.brandName // review.vue使用brandName作为value
+  }));
+});
+const categoryOptions = ref<categoryTreeVO[]>([]);
+const hasMore = ref(true); // 是否还有更多数据
+const pageHistory = ref([]);
+
+// 动态计算表格高度
+const tableHeight = computed(() => {
+  // 基础高度 = 视口高度 - 顶部导航(84) - 容器padding(16) - 搜索区域 - 卡片header(60) - 分页器(60)
+  const baseHeight = window.innerHeight - 84 - 16;
+  const searchHeight = showSearch.value ? 150 : 10; // 搜索区域高度
+  const cardHeaderHeight = 60; // 卡片header高度
+  const paginationHeight = 60; // 分页器高度
+  return baseHeight - searchHeight - cardHeaderHeight - paginationHeight;
+});
+
+const queryFormRef = ref<ElFormInstance>();
+const reviewFormRef = ref<ElFormInstance>();
+
+// 审核对话框
+const reviewDialog = reactive({
+  visible: false,
+  title: '上架审核'
+});
+
+// 审核表单
+const reviewForm = ref<any>({
+  productId: undefined,
+  productNo: '',
+  itemName: '',
+  auditStatus: 2,
+  auditReason: ''
+});
+
+// 审核表单验证规则
+const reviewRules = ref({
+  auditStatus: [{ required: true, message: '请选择审核结果', trigger: 'change' }],
+  auditReason: [{ required: true, message: '请输入审核意见', trigger: 'blur' }]
+});
+
+const queryParams = ref<BaseQuery & { auditStatus?: number }>({
+  pageNum: 1,
+  pageSize: 10,
+  way: undefined,
+  productNo: undefined,
+  itemName: undefined,
+  brandName: undefined,
+  purchaseNature: undefined,
+  bottomCategoryId: undefined,
+  isSelf: undefined,
+  auditStatus: undefined,
+  productStatus: undefined,
+  lastSeenId: undefined // 游标分页的lastSeenId
+});
+
+
+/** 查询商品列表 */
+const getList = async () => {
+  loading.value = true;
+  try {
+    const params = { ...queryParams.value };
+    const currentPageNum = queryParams.value.pageNum;
+
+    // 第一页不需要游标参数
+    if (currentPageNum === 1) {
+      delete params.lastSeenId;
+      delete params.way;
+    } else {
+      // way参数:0=上一页,1=下一页
+      if (queryParams.value.way === 0) {
+        // 上一页:使用目标页(即当前显示页)的firstId
+        const nextPageHistory = pageHistory.value[currentPageNum];
+        if (nextPageHistory) {
+          params.firstSeenId = nextPageHistory.firstId;
+          params.way = 0;
+        }
+      } else {
+        // 下一页:使用前一页的lastId作为lastSeenId
+        const prevPageHistory = pageHistory.value[currentPageNum - 1];
+        if (prevPageHistory) {
+          params.lastSeenId = prevPageHistory.lastId;
+          params.way = 1;
+        }
+      }
+    }
+
+    const res = await getSelfProductPage(params);
+    baseList.value = res.rows || [];
+
+    // 判断是否还有更多数据
+    hasMore.value = baseList.value.length === queryParams.value.pageSize;
+
+    // 记录当前页的第一个id和最后一个id
+    if (baseList.value.length > 0) {
+      const firstItem = baseList.value[0];
+      const lastItem = baseList.value[baseList.value.length - 1];
+      //如果长度小于currentPageNum则创建
+
+      if (pageHistory.value.length <= currentPageNum) {
+        pageHistory.value[currentPageNum] = {
+          firstId: firstItem.id,
+          lastId: lastItem.id
+        };
+      }
+    }
+    total.value = res.total || 0;
+  } catch (error) {
+    console.error('获取列表失败:', error);
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 初始化路由参数 */
+const initRouteParams = () => {
+  // 从路由参数中获取筛选条件
+  if (route.query.auditStatus) {
+    queryParams.value.auditStatus = Number(route.query.auditStatus);
+  }
+  if (route.query.brandName) {
+    queryParams.value.brandName = route.query.brandName as string;
+  }
+  if (route.query.bottomCategoryId) {
+    queryParams.value.bottomCategoryId = route.query.bottomCategoryId as string;
+  }
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  queryParams.value.lastSeenId = undefined;
+  pageHistory.value = [0]; // 重置页面历史
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  queryParams.value.lastSeenId = undefined;
+  pageHistory.value = [0]; // 重置页面历史
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: BaseVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'product/base/export',
+    {
+      ...queryParams.value
+    },
+    `base_review_${new Date().getTime()}.xlsx`
+  );
+};
+
+/** 查看商品详情 */
+const handleView = (row: BaseVO) => {
+  router.push(`/product/base/detail/${row.id}`);
+};
+
+/** 审核商品 */
+const handleAudit = (row: BaseVO) => {
+  reviewDialog.visible = true;
+  reviewDialog.title = '上架审核';
+  reviewForm.value = {
+    productId: row.id,
+    productNo: row.productNo,
+    itemName: row.itemName,
+    auditStatus: 2,
+    auditReason: ''
+  };
+};
+
+/** 提交审核 */
+const submitReview = async () => {
+  await reviewFormRef.value?.validate();
+  const data: ProductSelfForm = {
+    productId: reviewForm.value.productId,
+    auditStatus: Number(reviewForm.value.auditStatus),
+    auditReason: reviewForm.value.auditReason
+  };
+  await updateProductSelf(data);
+  proxy?.$modal.msgSuccess(reviewForm.value.auditStatus === 2 ? '审核通过' : '审核驳回');
+  reviewDialog.visible = false;
+  await getList();
+};
+
+/** 查询品牌列表(实时请求,每次只加载500条) */
+const getBrandList = async () => {
+  try {
+    brandLoading.value = true;
+    const res = await brandList({ pageNum: 1, pageSize: 500 });
+    brandOptions.value = res.data || [];
+  } catch (error) {
+    console.error('获取品牌列表失败:', error);
+  } finally {
+    brandLoading.value = false;
+  }
+};
+
+/** 处理品牌下拉框显示/隐藏 */
+const handleBrandVisibleChange = (visible: boolean) => {
+  if (visible && brandOptions.value.length === 0) {
+    getBrandList();
+  }
+};
+
+/** 查询分类树 */
+const getCategoryTree = async () => {
+  const res = await categoryTree();
+  categoryOptions.value = res.data || [];
+};
+
+onMounted(() => {
+  getCategoryTree();
+  initRouteParams();
+  getList();
+});
+</script>
+
+<style scoped lang="scss">
+.app-container {
+  padding: 8px;
+  height: calc(100vh - 84px);
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.table-card {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+
+  :deep(.el-card__body) {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+  }
+
+  :deep(.el-table) {
+    flex: 1;
+  }
+
+  // 确保固定列左侧有边框
+  :deep(.el-table__fixed-right) {
+    box-shadow: -1px 0 0 var(--el-table-border-color) !important;
+  }
+
+  // 固定列的单元格左边框
+  :deep(.el-table__fixed-right .el-table__cell) {
+    border-left: 1px solid var(--el-table-border-color) !important;
+  }
+}
+</style>

+ 411 - 0
src/views/product/poolAudit/index.vue

@@ -0,0 +1,411 @@
+<template>
+  <div class="p-2">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+            <el-form-item label="产品池名称" prop="name" label-width="100px">
+              <el-input v-model="queryParams.name" placeholder="请输入产品池名称" clearable @keyup.enter="handleQuery" style="width:200px" />
+            </el-form-item>
+            <el-form-item label="申请时间">
+              <el-date-picker
+                v-model="applyTimeRange"
+                type="daterange"
+                range-separator="至"
+                start-placeholder="开始时间"
+                end-placeholder="结束时间"
+                value-format="YYYY-MM-DD"
+                style="width:280px"
+              />
+            </el-form-item>
+            <el-form-item label="审核时间">
+              <el-date-picker
+                v-model="auditTimeRange"
+                type="daterange"
+                range-separator="至"
+                start-placeholder="开始时间"
+                end-placeholder="结束时间"
+                value-format="YYYY-MM-DD"
+                style="width:280px"
+              />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never">
+      <template #header>
+        <div class="flex items-center justify-between">
+          <span>产品池信息列表</span>
+          
+          <div class="ml-auto flex gap-2">
+              <el-button v-if="productReviewStatus==='0'" type="primary" plain icon="Plus" @click="handleAdd">新增入池单</el-button>
+              <!-- <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> -->
+          </div>
+        </div>
+
+
+      </template>
+
+      <el-table v-loading="loading" border :data="poolAuditList">
+        <el-table-column label="产品池名称" align="center" prop="name" />
+        <el-table-column label="创建人" align="center" prop="createByName" />
+        <el-table-column label="审核人" align="center" prop="auditByName" />
+        <el-table-column label="状态" align="center" prop="productReviewStatus" width="120">
+          <template #default="scope">
+            <el-tag :type="reviewStatusTagType(scope.row.productReviewStatus)">
+              {{ reviewStatusLabel(scope.row.productReviewStatus) }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width" min-width="200">
+          <template #default="scope">
+            <!-- 审核通过或已驳回:只显示查看入池单 -->
+            <template v-if="scope.row.productReviewStatus == '2' || scope.row.productReviewStatus == '3'">
+              <el-button link type="primary" @click="handleAudit(scope.row)">查看入池单</el-button>
+            </template>
+            <!-- 其他状态:显示原有操作 -->
+            <template v-else>
+              <el-button link type="primary" @click="handleView(scope.row)">查看</el-button>
+              <el-button
+                v-if="scope.row.productReviewStatus == '0' || scope.row.productReviewStatus === 0"
+                link type="primary"
+                @click="handleApply(scope.row)"
+              >申请入池单</el-button>
+              <el-button
+                v-if="scope.row.productReviewStatus == '1' || scope.row.productReviewStatus === 1"
+                link type="warning"
+                @click="handleAudit(scope.row)"
+              >去审核</el-button>
+              <el-button
+                v-if="scope.row.productReviewStatus == '0' || scope.row.productReviewStatus === 0"
+                link type="primary"
+                @click="handleUpdate(scope.row)"
+              >修改入池单</el-button>
+            </template>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </el-card>
+
+    <!-- 添加或修改产品池入池单对话框 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="700px" append-to-body>
+      <el-form ref="poolAuditFormRef" :model="form" :rules="viewMode ? {} : rules" label-width="100px">
+        <el-form-item label="池名称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入池名称" :disabled="viewMode" />
+        </el-form-item>
+        <!-- 普通/营销产品池:选择产品池 -->
+        <el-form-item v-if="poolType === 0 || poolType === 4" label="选择产品池" prop="poolId">
+          <el-select v-model="form.poolId" placeholder="请选择产品池" clearable filterable style="width:100%" :disabled="viewMode">
+            <el-option v-for="item in poolOptions" :key="item.id" :label="item.name" :value="item.id" />
+          </el-select>
+        </el-form-item>
+        <!-- 协议产品池:选择协议 -->
+        <el-form-item v-if="poolType === 2" label="选择协议" prop="protocolId">
+          <el-select v-model="form.protocolId" placeholder="请选择协议" clearable filterable style="width:100%" :disabled="viewMode">
+            <el-option
+              v-for="item in protocolOptions"
+              :key="item.id"
+              :label="`${item.protocolNo} - ${item.customerName}`"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+        <!-- 项目产品池:选择项目 -->
+        <el-form-item v-if="poolType === 3" label="选择项目" prop="itemId">
+          <el-select v-model="form.itemId" placeholder="请选择项目" clearable filterable style="width:100%" :disabled="viewMode">
+            <el-option v-for="item in itemOptions" :key="item.id" :label="item.itemName" :value="item.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" :disabled="viewMode" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <template v-if="!viewMode">
+            <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+            <el-button @click="cancel">取 消</el-button>
+          </template>
+          <template v-else>
+            <el-button @click="cancel">关 闭</el-button>
+          </template>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="PoolAudit" lang="ts">
+import { listPoolAudit, getPoolAudit, delPoolAudit, addPoolAudit, updatePoolAudit, applyPoolAudit } from '@/api/product/poolAudit';
+import { PoolAuditVO, PoolAuditQuery, PoolAuditForm } from '@/api/product/poolAudit/types';
+import { listPool } from '@/api/product/pool';
+import { PoolVO } from '@/api/product/pool/types';
+import { listInfo } from '@/api/product/protocolInfo';
+import { InfoVO } from '@/api/product/protocolInfo/types';
+import { listItem } from '@/api/external/item';
+import { ItemVO } from '@/api/external/item/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const route = useRoute();
+const router = useRouter();
+
+/** 从路由获取产品池类型:0普通,1精选,2协议,3项目,4营销 */
+const poolType = computed(() => Number(route.query.type ?? -1));
+const productReviewStatus = computed(() => route.query.productReviewStatus as string ?? "0");
+
+/** 下拉选项数据 */
+const poolOptions = ref<PoolVO[]>([]);
+const protocolOptions = ref<InfoVO[]>([]);
+const itemOptions = ref<ItemVO[]>([]);
+
+/** 日期范围 */
+const applyTimeRange = ref<[string, string] | null>(null);
+const auditTimeRange = ref<[string, string] | null>(null);
+
+/** 查看模式 */
+const viewMode = ref(false);
+
+/** 审核状态映射 */
+const reviewStatusMap: Record<string, { label: string; type: string }> = {
+  '0': { label: '待提交', type: 'info' },
+  '1': { label: '待审核', type: 'warning' },
+  '2': { label: '审核通过', type: 'success' },
+  '3': { label: '审核驳回', type: 'danger' },
+};
+
+const reviewStatusLabel = (status: string | number) => {
+  return reviewStatusMap[String(status)]?.label ?? '未知';
+};
+
+const reviewStatusTagType = (status: string | number): 'info' | 'warning' | 'success' | 'danger' | 'primary' => {
+  return (reviewStatusMap[String(status)]?.type ?? 'info') as 'info' | 'warning' | 'success' | 'danger' | 'primary';
+};
+
+/** 根据 type 加载对应下拉选项 */
+const loadSelectOptions = async () => {
+  const type = poolType.value;
+  if (type === 0 || type === 4) {
+    const res = await listPool({ type } as any);
+    poolOptions.value = res.rows ?? [];
+  } else if (type === 2) {
+    const res = await listInfo();
+    protocolOptions.value = res.rows ?? [];
+  } else if (type === 3) {
+    const res = await listItem();
+    itemOptions.value = res.rows ?? [];
+  }
+};
+
+const poolAuditList = ref<PoolAuditVO[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+
+const queryFormRef = ref<ElFormInstance>();
+const poolAuditFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: PoolAuditForm = {
+  id: undefined,
+  name: undefined,
+  poolId: undefined,
+  itemId: undefined,
+  type: poolType.value,
+  protocolId: undefined,
+  customerId: undefined,
+  productReviewStatus: undefined,
+  reviewReason: undefined,
+  remark: undefined,
+}
+const data = reactive<PageData<PoolAuditForm, PoolAuditQuery>>({
+  form: {...initFormData},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    name: undefined,
+    poolId: undefined,
+    itemId: undefined,
+    type: poolType.value,
+    protocolId: undefined,
+    customerId: undefined,
+    productReviewStatus: productReviewStatus.value,
+    reviewReason: undefined,
+    createByName: undefined,
+    auditByName: undefined,
+    params: {
+    }
+  },
+  rules: {
+    poolId: [
+      { required: true, message: "池id不能为空", trigger: "blur" }
+    ],
+    itemId: [
+      { required: true, message: "项目id不能为空", trigger: "blur" }
+    ],
+    type: [
+      { required: true, message: "产品池类型不能为空", trigger: "change" }
+    ],
+    protocolId: [
+      { required: true, message: "协议id不能为空", trigger: "blur" }
+    ],
+    customerId: [
+      { required: true, message: "客户id不能为空", trigger: "blur" }
+    ],
+    reviewReason: [
+      { required: true, message: "审核原因不能为空", trigger: "blur" }
+    ],
+    remark: [
+      { required: true, message: "备注不能为空", trigger: "blur" }
+    ],
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询产品池审核列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listPoolAudit(queryParams.value);
+  poolAuditList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+}
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+}
+
+/** 表单重置 */
+const reset = () => {
+  form.value = {...initFormData};
+  poolAuditFormRef.value?.resetFields();
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  // 处理申请时间范围
+  if (applyTimeRange.value) {
+    queryParams.value.params.beginCreateTime = applyTimeRange.value[0];
+    queryParams.value.params.endCreateTime = applyTimeRange.value[1];
+  } else {
+    queryParams.value.params.beginCreateTime = undefined;
+    queryParams.value.params.endCreateTime = undefined;
+  }
+  // 处理审核时间范围
+  if (auditTimeRange.value) {
+    queryParams.value.params.beginAuditTime = auditTimeRange.value[0];
+    queryParams.value.params.endAuditTime = auditTimeRange.value[1];
+  } else {
+    queryParams.value.params.beginAuditTime = undefined;
+    queryParams.value.params.endAuditTime = undefined;
+  }
+  getList();
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  applyTimeRange.value = null;
+  auditTimeRange.value = null;
+  handleQuery();
+}
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: PoolAuditVO[]) => {
+  ids.value = selection.map(item => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  router.push({ path: '/product/poolLinkAudit' });
+}
+
+/** 查看按钮操作 */
+const handleView = async (row: PoolAuditVO) => {
+  reset();
+  viewMode.value = true;
+  const res = await getPoolAudit(row.id);
+  Object.assign(form.value, res.data);
+  await loadSelectOptions();
+  dialog.visible = true;
+  dialog.title = "查看入池单";
+}
+
+/** 修改按钮操作 */
+const handleUpdate = (row?: PoolAuditVO) => {
+  const _id = row?.id || ids.value[0];
+  router.push({ path: '/product/poolLinkAudit', query: { id: _id } });
+}
+
+/** 去审核 */
+const handleAudit = (row: PoolAuditVO) => {
+  router.push({ path: '/product/poolAuditReview', query: { id: row.id } });
+}
+
+/** 申请入池单(待提交 -> 待审核) */
+const handleApply = async (row: PoolAuditVO) => {
+  await proxy?.$modal.confirm(`是否确认申请“${row.name}”入池单?确定后状态将变为待审核。`);
+  await applyPoolAudit(row.id);
+  proxy?.$modal.msgSuccess("申请入池单成功");
+  await getList();
+}
+
+/** 提交按钮 */
+const submitForm = () => {
+  poolAuditFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updatePoolAudit(form.value).finally(() =>  buttonLoading.value = false);
+      } else {
+        await addPoolAudit(form.value).finally(() =>  buttonLoading.value = false);
+      }
+      proxy?.$modal.msgSuccess("操作成功");
+      dialog.visible = false;
+      await getList();
+    }
+  });
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: PoolAuditVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('是否确认删除产品池审核编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
+  await delPoolAudit(_ids);
+  proxy?.$modal.msgSuccess("删除成功");
+  await getList();
+}
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download('product/poolAudit/export', {
+    ...queryParams.value
+  }, `poolAudit_${new Date().getTime()}.xlsx`)
+}
+
+onMounted(() => {
+  getList();
+});
+</script>

+ 351 - 0
src/views/product/poolAuditReview/index.vue

@@ -0,0 +1,351 @@
+<template>
+  <div class="p-2">
+    <!-- 入池清单信息 -->
+    <el-card shadow="hover" class="mb-[10px]" v-loading="auditInfoLoading">
+      <template #header>
+        <span class="font-bold">入池清单信息</span>
+      </template>
+      <el-descriptions :column="3" border>
+        <el-descriptions-item label="编号">{{ auditInfo?.id }}</el-descriptions-item>
+        <el-descriptions-item label="产品池名称">{{ auditInfo?.poolName || auditInfo?.name }}</el-descriptions-item>
+        <el-descriptions-item label="申请时间">{{ auditInfo?.createTime }}</el-descriptions-item>
+        <el-descriptions-item label="审核时间">{{ auditInfo?.auditTime || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="状态">
+          <el-tag :type="getStatusTagType(auditInfo?.productReviewStatus)">{{ getStatusLabel(auditInfo?.productReviewStatus) }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="创建人">{{ auditInfo?.createByName }}</el-descriptions-item>
+        <el-descriptions-item label="审核人">{{ auditInfo?.auditByName || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="备注">{{ auditInfo?.remark || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="驳回意见">{{ auditInfo?.reviewReason || '-' }}</el-descriptions-item>
+      </el-descriptions>
+    </el-card>
+
+    <!-- 入池清单 -->
+    <el-card shadow="never">
+      <template #header>
+        <span class="font-bold">入池清单</span>
+      </template>
+      <div class="mb-4" v-if="!auditInfoLoading && !isReadOnly">
+        <el-button type="danger" icon="Remove" @click="handleRemoveFromPoolListBatch">移出入池清单</el-button>
+      </div>
+          <el-table
+            ref="poolTableRef"
+            v-loading="listLoading"
+            :data="poolList"
+            border
+            @selection-change="handlePoolSelectionChange"
+          >
+            <el-table-column type="selection" width="55" align="center" />
+            <el-table-column type="index" label="序号" width="60" align="center" />
+            <el-table-column label="商品编号" align="center" prop="productNo" width="120" />
+            <el-table-column label="商品图片" align="center" prop="productImage" width="100">
+              <template #default="scope">
+                <image-preview :src="scope.row.productImage" :width="60" :height="60"/>
+              </template>
+            </el-table-column>
+            <el-table-column label="商品信息" align="center" min-width="180">
+              <template #default="scope">
+                <div class="text-left" style="font-size: 12px;">
+                  <div>{{ scope.row.itemName }}</div>
+                  <div class="text-gray-500">品牌:{{ scope.row.brandName || '-' }}</div>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column label="单位" align="center" prop="unitName" width="80" />
+            <el-table-column label="SKU价格" align="center" width="150">
+              <template #default="scope">
+                <div class="text-left" style="font-size: 12px;">
+                  <div>
+                    <span class="text-gray-500">市场价:</span>
+                    <span class="text-red-500">¥{{ scope.row.marketPrice || '0.00' }}</span>
+                  </div>
+                  <div>
+                    <span class="text-gray-500">官网价:</span>
+                    <span class="text-red-500">¥{{ scope.row.memberPrice || '0.00' }}</span>
+                  </div>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column label="成本数据" align="center" width="130">
+              <template #default="scope">
+                <div class="text-left" style="font-size: 12px;">
+                  <div>
+                    <span class="text-gray-500">采购价:</span>
+                    <span>¥{{ scope.row.purchasePrice || '0.00' }}</span>
+                  </div>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column label="项目/平台价" align="center" width="100">
+              <template #default="scope">
+                <span class="text-red-500">¥{{ scope.row.agreementPrice ?? scope.row.minSellingPrice ?? '0.00' }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="商品状态" align="center" width="80">
+              <template #default="scope">
+                <el-tag v-if="scope.row.productStatus === '1'" type="success">上架</el-tag>
+                <el-tag v-else type="warning">下架</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="创建供应商" align="center" width="100">
+              <template #default="scope">
+                <span>{{ getSupplierName(scope.row.supplier) }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column v-if="!auditInfoLoading && !isReadOnly" label="操作" align="center" width="80" fixed="right">
+              <template #default="scope">
+                <el-link type="danger" :underline="false" @click="handleRemoveFromPoolList(scope.row)">移除</el-link>
+              </template>
+            </el-table-column>
+          </el-table>
+      <pagination
+        v-show="listTotal > 0"
+        :total="listTotal"
+        v-model:page="listQuery.pageNum"
+        v-model:limit="listQuery.pageSize"
+        @pagination="getPoolList"
+      />
+    </el-card>
+
+    <!-- 底部操作按钮 -->
+    <div v-if="!auditInfoLoading && !isReadOnly" class="fixed bottom-0 left-0 right-0 bg-white border-t p-4 flex justify-center gap-4 z-10">
+      <el-button type="danger" size="large" @click="handleReject">驳 回</el-button>
+      <el-button type="primary" size="large" @click="handlePass">通 过</el-button>
+    </div>
+
+    <!-- 驳回原因对话框 -->
+    <el-dialog title="驳回原因" v-model="rejectDialog.visible" width="500px" append-to-body>
+      <el-form :model="rejectDialog.form" label-width="80px">
+        <el-form-item label="驳回原因" required>
+          <el-input
+            v-model="rejectDialog.form.reason"
+            type="textarea"
+            :rows="4"
+            placeholder="请输入驳回原因"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="rejectDialog.visible = false">取 消</el-button>
+        <el-button type="primary" @click="confirmReject">确 定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="PoolAuditReview" lang="ts">
+import { useRouter, useRoute } from 'vue-router';
+import { getPoolAudit, updatePoolAudit, getPoolAuditProductPage, batchAudit } from '@/api/product/poolAudit';
+import { PoolAuditVO } from '@/api/product/poolAudit/types';
+import { delPoolLinkAudit } from '@/api/product/poolLinkAudit';
+import { PoolLinkVO } from '@/api/product/poolLink/types';
+import { listInfo } from '@/api/customer/supplierInfo';
+import { InfoVO } from '@/api/customer/supplierInfo/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const router = useRouter();
+const route = useRoute();
+
+const poolAuditId = computed(() => (route.params.id || route.query.id) as string | number);
+
+// 审核信息
+const auditInfoLoading = ref(false);
+const auditInfo = ref<(PoolAuditVO & { createTime?: string; auditTime?: string }) | undefined>();
+
+// 入池清单
+const listLoading = ref(false);
+const poolList = ref<PoolLinkVO[]>([]);
+const listTotal = ref(0);
+const listQuery = ref({
+  pageNum: 1,
+  pageSize: 10
+});
+const selectedPoolProducts = ref<PoolLinkVO[]>([]);
+const poolTableRef = ref<any>();
+
+// 供应商
+const supplierOptions = ref<InfoVO[]>([]);
+
+/** 是否只读(审核通过或已驳回状态) */
+const isReadOnly = computed(() => {
+  const status = auditInfo.value?.productReviewStatus;
+  return status === '2' || status === '3';
+});
+
+// 驳回对话框
+const rejectDialog = reactive({
+  visible: false,
+  form: {
+    reason: ''
+  }
+});
+
+/** 获取审核状态标签文字 */
+const getStatusLabel = (status?: string): string => {
+  const map: Record<string, string> = { '0': '待提交', '1': '待审核', '2': '审核通过', '3': '审核驳回' };
+  return status !== undefined ? (map[status] || status) : '-';
+};
+
+/** 获取审核状态标签类型 */
+const getStatusTagType = (status?: string): 'success' | 'warning' | 'info' | 'danger' | 'primary' => {
+  const map: Record<string, 'success' | 'warning' | 'info' | 'danger' | 'primary'> = {
+    '0': 'info', '1': 'warning', '2': 'success', '3': 'danger'
+  };
+  return status !== undefined ? (map[status] || 'primary') : 'primary';
+};
+
+/** 获取供应商名称 */
+const getSupplierName = (supplierId: string | number | undefined): string => {
+  if (!supplierId) return '-';
+  const supplier = supplierOptions.value.find(item => item.id === supplierId);
+  return supplier?.enterpriseName || supplier?.shortName || '-';
+};
+
+/** 加载审核信息 */
+const loadAuditInfo = async () => {
+  if (!poolAuditId.value) return;
+  auditInfoLoading.value = true;
+  try {
+    const res = await getPoolAudit(poolAuditId.value);
+    auditInfo.value = res.data as any;
+  } catch (error) {
+    console.error('加载审核信息失败:', error);
+  } finally {
+    auditInfoLoading.value = false;
+  }
+};
+
+/** 获取供应商列表 */
+const getSupplierList = async () => {
+  try {
+    const res = await listInfo();
+    supplierOptions.value = res.data || res.rows || [];
+  } catch (error) {
+    console.error('获取供应商列表失败:', error);
+  }
+};
+
+/** 获取入池清单列表 */
+const getPoolList = async () => {
+  listLoading.value = true;
+  try {
+    const params = {
+      pageNum: listQuery.value.pageNum,
+      pageSize: listQuery.value.pageSize,
+      poolAuditId: poolAuditId.value
+    };
+    const res = await getPoolAuditProductPage(params);
+    poolList.value = (res.rows || res.data || []) as any;
+    listTotal.value = res.total || 0;
+  } catch (error) {
+    console.error('获取入池清单列表失败:', error);
+    poolList.value = [];
+    listTotal.value = 0;
+  } finally {
+    listLoading.value = false;
+  }
+};
+
+/** 入池清单选择变化 */
+const handlePoolSelectionChange = (selection: PoolLinkVO[]) => {
+  selectedPoolProducts.value = selection;
+};
+
+/** 批量移出入池清单 */
+const handleRemoveFromPoolListBatch = async () => {
+  if (selectedPoolProducts.value.length === 0) {
+    proxy?.$modal.msgWarning('请先选择要移出的商品');
+    return;
+  }
+  await proxy?.$modal.confirm(`确认要将 ${selectedPoolProducts.value.length} 个商品移出入池清单吗?`);
+  try {
+    const promises = selectedPoolProducts.value.map(item =>
+      delPoolLinkAudit((item as any).poolAuditProductId)
+    );
+    await Promise.all(promises);
+    proxy?.$modal.msgSuccess('移出成功');
+    selectedPoolProducts.value = [];
+    if (poolTableRef.value) {
+      poolTableRef.value.clearSelection();
+    }
+    await getPoolList();
+  } catch (error) {
+    console.error('移出入池清单失败:', error);
+    proxy?.$modal.msgError('移出入池清单失败');
+  }
+};
+
+/** 从入池清单移除单个 */
+const handleRemoveFromPoolList = async (row: PoolLinkVO) => {
+  await proxy?.$modal.confirm('确认要从入池清单移除该商品吗?');
+  try {
+    await delPoolLinkAudit((row as any).poolAuditProductId);
+    proxy?.$modal.msgSuccess('移除成功');
+    await getPoolList();
+  } catch (error) {
+    console.error('移除失败:', error);
+    proxy?.$modal.msgError('移除失败');
+  }
+};
+
+/** 驳回 */
+const handleReject = () => {
+  rejectDialog.form.reason = '';
+  rejectDialog.visible = true;
+};
+
+/** 确认驳回 */
+const confirmReject = async () => {
+  if (!rejectDialog.form.reason?.trim()) {
+    proxy?.$modal.msgWarning('请输入驳回原因');
+    return;
+  }
+  try {
+    await updatePoolAudit({
+      id: auditInfo.value?.id,
+      productReviewStatus: '3',
+      reviewReason: rejectDialog.form.reason
+    });
+    proxy?.$modal.msgSuccess('驳回成功');
+    rejectDialog.visible = false;
+    router.back();
+  } catch (error) {
+    console.error('驳回失败:', error);
+  }
+};
+
+/** 通过 */
+const handlePass = async () => {
+  if (poolList.value.length === 0) {
+    proxy?.$modal.msgWarning('入池清单为空,请先将商品加入入池清单');
+    return;
+  }
+  await proxy?.$modal.confirm('确认审核通过该入池申请吗?');
+  try {
+    // 获取入池清单中所有商品的productId
+    const productIds = poolList.value.map(item => item.productId);
+    // 调用批量审核接口
+    await batchAudit({
+      poolAuditId: poolAuditId.value,
+      productIds: productIds,
+      auditStatus: '2' // 审核通过
+    });
+    proxy?.$modal.msgSuccess('审核通过');
+    router.back();
+  } catch (error) {
+    console.error('审核通过失败:', error);
+  }
+};
+
+onMounted(() => {
+  loadAuditInfo();
+  getSupplierList();
+  getPoolList();
+});
+</script>
+
+<style scoped lang="scss">
+.p-2 {
+  padding-bottom: 80px; // 为底部固定按钮留出空间
+}
+</style>

+ 128 - 35
src/views/product/poolLink/index.vue

@@ -24,7 +24,24 @@
               </el-col>
               <el-col :span="6">
                 <el-form-item label="商品品牌" prop="brandId">
-                  <el-input v-model="queryParams.brandId" placeholder="请选择" clearable />
+                  <el-select
+                    v-model="queryParams.brandId"
+                    placeholder="请输入品牌名称搜索"
+                    filterable
+                    remote
+                    clearable
+                    :remote-method="handleBrandSearch"
+                    :loading="brandLoading"
+                    style="width: 100%"
+                    @keyup.enter="handleQuery"
+                  >
+                    <el-option
+                      v-for="item in brandOptions"
+                      :key="item.id"
+                      :label="item.brandName"
+                      :value="item.id"
+                    />
+                  </el-select>
                 </el-form-item>
               </el-col>
               <el-col :span="6">
@@ -38,25 +55,28 @@
             </el-row>
             <el-row :gutter="20">
               <el-col :span="6">
-                <el-form-item label="商品类别" prop="categoryId">
+                <el-form-item label="商品分类" prop="bottomCategoryId">
                   <el-tree-select
-                    v-model="queryParams.categoryId"
+                    v-model="queryParams.bottomCategoryId"
                     :data="categoryOptions"
-                    :props="{ value: 'id', label: 'label', children: 'children' }"
-                    check-strictly
-                    :render-after-expand="false"
+                    :props="{ value: 'id', label: 'label', children: 'children' } as any"
+                    value-key="id"
+                    placeholder="请选择商品分类"
                     clearable
-                    placeholder="请选择商品类别"
-                  >
-                    <template #default="{ data }">
-                      <span>{{ getCategoryFullPath(data.id) }}</span>
-                    </template>
-                  </el-tree-select>
+                    check-strictly
+                  />
                 </el-form-item>
               </el-col>
               <el-col :span="6">
                 <el-form-item label="创建供应商" prop="supplier">
-                  <el-input v-model="queryParams.supplier" placeholder="请选择创建供应商" clearable />
+                  <el-select v-model="queryParams.supplier" placeholder="请选择创建供应商" clearable filterable>
+                    <el-option
+                      v-for="item in supplierOptions"
+                      :key="item.id"
+                      :label="item.enterpriseName || item.shortName"
+                      :value="item.id"
+                    />
+                  </el-select>
                 </el-form-item>
               </el-col>
               <el-col :span="6">
@@ -86,7 +106,8 @@
         <div class="flex justify-between items-center">
           <span class="font-bold">商品列表信息列表</span>
           <div class="flex gap-2">
-            <el-button type="primary" icon="Plus" @click="handleAddProduct">添加商品</el-button>
+            <!-- <el-button type="primary" icon="Plus" @click="handleAddProduct">添加商品</el-button> -->
+                        <el-button type="danger" icon="Delete" @click="handleClearPool">清空产品池</el-button>
           </div>
         </div>
       </template>
@@ -94,9 +115,9 @@
       <el-table v-loading="loading" border :data="productList">
         <el-table-column type="index" label="序号" width="60" align="center" />
         <el-table-column label="商品编号" align="center" prop="productNo" width="120" />
-        <el-table-column label="商品图片" align="center" prop="productImageUrl" width="100">
+        <el-table-column label="商品图片" align="center" prop="productImage" width="100">
           <template #default="scope">
-            <image-preview :src="scope.row.productImageUrl" :width="60" :height="60"/>
+            <image-preview :src="scope.row.productImage" :width="60" :height="60"/>
           </template>
         </el-table-column>
         <el-table-column label="商品信息" align="center" min-width="200">
@@ -124,7 +145,7 @@
                 <span class="text-red-500">¥{{ scope.row.marketPrice || '0.00' }}</span>
               </div>
               <div>
-                <span class="text-gray-500">平台售价:</span>
+                <span class="text-gray-500">官网价:</span>
                 <span class="text-red-500">¥{{ scope.row.platformPrice || '0.00' }}</span>
               </div>
               <div>
@@ -148,7 +169,7 @@
             </div>
           </template>
         </el-table-column>
-        <el-table-column label="项目平台价" align="center" prop="platformPrice" width="100" />
+        <!-- <el-table-column label="项目官网价" align="center" prop="platformPrice" width="100" /> -->
         <el-table-column label="商品状态" align="center" width="80">
           <template #default="scope">
             <el-tag v-if="scope.row.productStatus === '1'" type="success">上架</el-tag>
@@ -156,12 +177,27 @@
           </template>
         </el-table-column>
         <el-table-column label="入池时间" align="center" prop="createTime" width="120" />
-        <el-table-column label="创建供应商" align="center" prop="supplier" width="100" />
+        <el-table-column label="创建供应商" align="center" width="100">
+          <template #default="scope">
+            <span>{{ getSupplierName(scope.row.supplier) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="产品审核状态" align="center" width="100">
+          <template #default="scope">
+            <el-tag v-if="scope.row.productReviewStatus === '0'" type="info">待提交</el-tag>
+            <el-tag v-else-if="scope.row.productReviewStatus === '1'" type="warning">待审核</el-tag>
+            <el-tag v-else-if="scope.row.productReviewStatus === '2'" type="success">审核通过</el-tag>
+            <el-tag v-else-if="scope.row.productReviewStatus === '3'" type="danger">审核驳回</el-tag>
+            <el-tag v-else type="info">未知</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="审核原因" align="center" prop="reviewReason" width="150" show-overflow-tooltip />
         <el-table-column label="操作" align="center" width="120" fixed="right">
           <template #default="scope">
             <div class="flex flex-col gap-1">
-              <el-link type="primary" :underline="false" @click="handlePriceMaintain(scope.row)">价格维护</el-link>
-              <el-link type="primary" :underline="false" @click="handleInventoryMaintain(scope.row)">修改库存</el-link>
+              <!-- <el-link v-if="scope.row.productReviewStatus === '3'" type="primary" :underline="false" @click="handleResubmit(scope.row)">重新提交</el-link> -->
+              <!-- <el-link type="primary" :underline="false" @click="handlePriceMaintain(scope.row)">价格维护</el-link>
+              <el-link type="primary" :underline="false" @click="handleInventoryMaintain(scope.row)">修改库存</el-link> -->
               <el-link type="danger" :underline="false" @click="handleRemoveProduct(scope.row)">移除商品池</el-link>
             </div>
           </template>
@@ -180,7 +216,7 @@
         <el-form-item label="市场价" prop="marketPrice">
           <el-input-number v-model="priceDialog.form.marketPrice" :precision="2" :min="0" controls-position="right" style="width: 100%" />
         </el-form-item>
-        <el-form-item label="平台售价" prop="platformPrice">
+        <el-form-item label="官网价" prop="platformPrice">
           <el-input-number v-model="priceDialog.form.platformPrice" :precision="2" :min="0" controls-position="right" style="width: 100%" />
         </el-form-item>
         <el-form-item label="最低售价" prop="minPrice">
@@ -245,9 +281,9 @@
         >
           <el-table-column type="selection" width="55" align="center" />
           <el-table-column label="商品编号" align="center" prop="productNo" width="120" />
-          <el-table-column label="商品图片" align="center" prop="productImageUrl" width="100">
+          <el-table-column label="商品图片" align="center" prop="productImage" width="100">
             <template #default="scope">
-              <image-preview :src="scope.row.productImageUrl" :width="60" :height="60"/>
+              <image-preview :src="scope.row.productImage" :width="60" :height="60"/>
             </template>
           </el-table-column>
           <el-table-column label="商品信息" align="center" min-width="200">
@@ -281,7 +317,7 @@
                   <span>¥{{ scope.row.midRangePrice || '0.00' }}</span>
                 </div>
                 <div>
-                  <span class="text-gray-500">平台价:</span>
+                  <span class="text-gray-500">官网价:</span>
                   <span class="text-red-500">¥{{ scope.row.standardPrice || '0.00' }}</span>
                 </div>
                 <div>
@@ -308,7 +344,7 @@
               </div>
             </template>
           </el-table-column>
-          <el-table-column label="协议价" align="center" prop="agreementPrice" width="100" />
+          <!-- <el-table-column label="协议价" align="center" prop="agreementPrice" width="100" /> -->
           <el-table-column label="操作" align="center" width="100" fixed="right">
             <template #default="scope">
               <el-link type="primary" :underline="false" @click="handleAddSingleProduct(scope.row)">加入清单</el-link>
@@ -335,8 +371,12 @@
 import { useRouter, useRoute } from 'vue-router';
 import { categoryTree, listBase } from '@/api/product/base';
 import { BaseVO, BaseQuery } from '@/api/product/base/types';
-import { listPoolLink, batchAddProducts, BatchAddProductData, editPrice, editStock, delPoolLink, PoolLinkForm } from '@/api/product/poolLink';
+import { listPoolLink, batchAddProducts, BatchAddProductData, editPrice, editStock, delPoolLink, PoolLinkForm, reSubmit, clearPool } from '@/api/product/poolLink';
 import { PoolLinkQuery, PoolLinkVO } from '@/api/product/poolLink/types';
+import { listInfo } from '@/api/customer/supplierInfo';
+import { InfoVO } from '@/api/customer/supplierInfo/types';
+import { listBrand } from '@/api/product/brand';
+import { BrandVO } from '@/api/product/brand/types';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const router = useRouter();
@@ -357,13 +397,17 @@ const queryParams = ref({
   itemName: undefined,
   brandId: undefined,
   productStatus: undefined,
-  categoryId: undefined,
+  bottomCategoryId: undefined,
   supplier: undefined,
   dateRange: undefined,
 });
 
 const categoryOptions = ref<any[]>([]);
 const categoryMap = ref<Map<string | number, any>>(new Map());
+const supplierOptions = ref<InfoVO[]>([]); // 供应商选项
+const brandOptions = ref<BrandVO[]>([]); // 品牌选项
+const brandLoading = ref(false); // 品牌加载状态
+let brandSearchTimer: ReturnType<typeof setTimeout> | null = null; // 品牌搜索防抖定时器
 
 // 添加商品对话框
 const addProductDialog = reactive({
@@ -403,6 +447,44 @@ const getCategoryTree = async () => {
   }
 };
 
+/** 获取供应商列表 */
+const getSupplierList = async () => {
+  try {
+    const res = await listInfo();
+    supplierOptions.value = res.data || res.rows|| [];
+  } catch (error) {
+    console.error('获取供应商列表失败:', error);
+  }
+};
+
+/** 获取供应商名称 */
+const getSupplierName = (supplierId: string | number | undefined): string => {
+  if (!supplierId) return '-';
+  const supplier = supplierOptions.value.find(item => item.id === supplierId);
+  return supplier?.enterpriseName || supplier?.shortName || '-';
+};
+
+/** 加载品牌选项(默认100条) */
+const loadBrandOptions = async (keyword?: string) => {
+  brandLoading.value = true;
+  try {
+    const res = await listBrand({ pageNum: 1, pageSize: 100, brandName: keyword });
+    brandOptions.value = res.rows || [];
+  } catch (error) {
+    console.error('加载品牌列表失败:', error);
+  } finally {
+    brandLoading.value = false;
+  }
+};
+
+/** 品牌远程搜索(防抖) */
+const handleBrandSearch = (query: string) => {
+  if (brandSearchTimer) clearTimeout(brandSearchTimer);
+  brandSearchTimer = setTimeout(() => {
+    loadBrandOptions(query || undefined);
+  }, 300);
+};
+
 /** 构建分类映射 */
 const buildCategoryMap = (categories: any[], parentPath = '') => {
   categories.forEach(category => {
@@ -431,7 +513,7 @@ const getList = async () => {
       productNo: queryParams.value.productNo,
       itemName: queryParams.value.itemName,
       brandId: queryParams.value.brandId,
-      categoryId: queryParams.value.categoryId,
+      bottomCategoryId: queryParams.value.bottomCategoryId,
       productStatus: queryParams.value.productStatus,
       supplier: queryParams.value.supplier
     };
@@ -439,8 +521,8 @@ const getList = async () => {
     // 处理日期范围
     if (queryParams.value.dateRange && queryParams.value.dateRange.length === 2) {
       query.params = {
-        beginCreateTime: queryParams.value.dateRange[0],
-        endCreateTime: queryParams.value.dateRange[1]
+        beginCreateTime: proxy?.parseTime(queryParams.value.dateRange[0], '{y}-{m}-{d} {h}:{i}:{s}') || queryParams.value.dateRange[0],
+        endCreateTime: proxy?.parseTime(queryParams.value.dateRange[1], '{y}-{m}-{d} {h}:{i}:{s}') || queryParams.value.dateRange[1]
       };
     }
 
@@ -475,8 +557,8 @@ const resetQuery = () => {
 
 /** 清空产品池 */
 const handleClearPool = async () => {
-  await proxy?.$modal.confirm('确认要清空该产品池的所有商品吗?');
-  // TODO: 调用清空API
+  await proxy?.$modal.confirm('确认要清空该产品池的所有商品吗?此操作不可恢复!');
+  await clearPool(queryParams.value.poolId);
   proxy?.$modal.msgSuccess('清空成功');
   await getList();
 }
@@ -595,7 +677,8 @@ const handleBatchAdd = async () => {
       poolId: queryParams.value.poolId,
       products: selectedProducts.value.map(product => ({
         productId: product.id,
-        agreementPrice: product.standardPrice || product.midRangePrice // 使用平台价或市场价作为协议价
+        agreementPrice: product.standardPrice || product.midRangePrice // 使用官网价或市场价作为协议价
+
       }))
     };
 
@@ -621,7 +704,7 @@ const handleAddSingleProduct = async (row: BaseVO) => {
       poolId: queryParams.value.poolId,
       products: [{
         productId: row.id,
-        agreementPrice: row.standardPrice || row.midRangePrice // 使用平台价或市场价作为协议价
+        agreementPrice: row.standardPrice || row.midRangePrice // 使用官网价或市场价作为协议价
       }]
     };
 
@@ -731,6 +814,14 @@ const submitStockForm = async () => {
   await getList();
 };
 
+/** 重新提交审核 */
+const handleResubmit = async (row: PoolLinkVO) => {
+  await proxy?.$modal.confirm('确认要重新提交审核吗?');
+  await reSubmit([{ id: row.id }]);
+  proxy?.$modal.msgSuccess('重新提交成功');
+  await getList();
+};
+
 /** 移除商品池 */
 const handleRemoveProduct = async (row: PoolLinkVO) => {
   await proxy?.$modal.confirm('确认要移除该商品吗?');
@@ -741,7 +832,9 @@ const handleRemoveProduct = async (row: PoolLinkVO) => {
 
 onMounted(() => {
   getCategoryTree();
+  getSupplierList();
   getList();
+  loadBrandOptions();
 });
 </script>
 

+ 6 - 6
src/views/product/poolLink/index1.vue

@@ -130,7 +130,7 @@
                 <span class="text-red-500">¥{{ scope.row.marketPrice || '0.00' }}</span>
               </div>
               <div>
-                <span class="text-gray-500">平台售价:</span>
+                <span class="text-gray-500">官网价:</span>
                 <span class="text-red-500">¥{{ scope.row.platformPrice || '0.00' }}</span>
               </div>
               <div>
@@ -154,7 +154,7 @@
             </div>
           </template>
         </el-table-column>
-        <el-table-column label="项目平台价" align="center" prop="platformPrice" width="100" />
+        <el-table-column label="项目官网价" align="center" prop="platformPrice" width="100" />
         <el-table-column label="商品状态" align="center" width="80">
           <template #default="scope">
             <el-tag v-if="scope.row.productStatus === '1'" type="success">已上架</el-tag>
@@ -196,7 +196,7 @@
         <el-form-item label="市场价" prop="marketPrice">
           <el-input-number v-model="priceDialog.form.marketPrice" :precision="2" :min="0" controls-position="right" style="width: 100%" />
         </el-form-item>
-        <el-form-item label="平台售价" prop="platformPrice">
+        <el-form-item label="官网价" prop="platformPrice">
           <el-input-number v-model="priceDialog.form.platformPrice" :precision="2" :min="0" controls-position="right" style="width: 100%" />
         </el-form-item>
         <el-form-item label="最低售价" prop="minPrice">
@@ -316,7 +316,7 @@
                   <span>¥{{ scope.row.midRangePrice || '0.00' }}</span>
                 </div>
                 <div>
-                  <span class="text-gray-500">平台价:</span>
+                  <span class="text-gray-500">官网价:</span>
                   <span class="text-red-500">¥{{ scope.row.standardPrice || '0.00' }}</span>
                 </div>
                 <div>
@@ -624,7 +624,7 @@ const handleBatchAdd = async () => {
       poolId: queryParams.value.poolId ,
       products: selectedProducts.value.map(product => ({
         productId: product.id ,
-        agreementPrice: product.standardPrice || product.midRangePrice // 使用平台价或市场价作为协议价
+        agreementPrice: product.standardPrice || product.midRangePrice // 使用官网价或市场价作为协议价
       }))
     };
 
@@ -650,7 +650,7 @@ const handleAddSingleProduct = async (row: BaseVO) => {
       poolId: queryParams.value.poolId ,
       products: [{
         productId: row.id ,
-        agreementPrice: row.standardPrice || row.midRangePrice // 使用平台价或市场价作为协议价
+        agreementPrice: row.standardPrice || row.midRangePrice // 使用官网价或市场价作为协议价
       }]
     };
 

+ 1037 - 0
src/views/product/poolLinkAudit/index.vue

@@ -0,0 +1,1037 @@
+<template>
+  <div class="p-2">
+    <!-- 返回按钮 -->
+    <div class="mb-4 flex items-center">
+      <el-button link icon="ArrowLeft" @click="goBack">返回</el-button>
+    </div>
+
+    <!-- 申请入池表单头部 -->
+    <el-card shadow="hover" class="mb-[10px]">
+      <el-form :model="auditForm" label-width="100px">
+        <!-- 第一行:产品池类型 + 产品册选择 -->
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="产品池类型" prop="type">
+              <el-select v-model="auditForm.type" placeholder="请选择产品池类型" style="width: 100%">
+                <el-option label="自营产品池" value="0" />
+                <el-option label="精选产品池" value="1" />
+                <el-option label="协议产品池" value="2" />
+                <el-option label="项目产品池" value="3" />
+                <el-option label="营销产品池" value="4" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <!-- type 4:营销产品池 -->
+            <el-form-item v-if="auditForm.type === '4'" label="产品池" prop="poolId">
+              <el-select v-model="auditForm.poolId" placeholder="请选择产品池" clearable filterable style="width: 100%">
+                <el-option v-for="item in poolOptions" :key="item.id" :label="item.name" :value="item.id" />
+              </el-select>
+            </el-form-item>
+            <!-- type 2:协议产品池 -->
+            <el-form-item v-else-if="auditForm.type === '2'" label="协议" prop="protocolId">
+              <el-select v-model="auditForm.protocolId" placeholder="请选择协议" clearable filterable style="width: 100%">
+                <el-option
+                  v-for="item in protocolOptions"
+                  :key="item.id"
+                  :label="`${item.protocolNo} - ${item.customerName}`"
+                  :value="item.id"
+                />
+              </el-select>
+            </el-form-item>
+            <!-- type 3:项目产品池 -->
+            <el-form-item v-else-if="auditForm.type === '3'" label="项目" prop="itemId">
+              <el-select v-model="auditForm.itemId" placeholder="请选择项目" clearable filterable style="width: 100%">
+                <el-option v-for="item in itemOptions" :key="item.id" :label="item.itemName" :value="item.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <!-- 第二行:备注 -->
+        <el-row>
+          <el-col :span="16">
+            <el-form-item label="备注" prop="remark">
+              <el-input v-model="auditForm.remark" placeholder="请输入备注" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <!-- 第三行:附件 -->
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="附件" prop="attachment">
+              <file-upload v-model="auditForm.attachment" :file-size="20" :limit="5" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </el-card>
+
+    <el-card shadow="never">
+      <template #header>
+        <div class="flex justify-between items-center">
+          <span class="font-bold">入池清单</span>
+          <div class="flex gap-2">
+            <el-button icon="Upload" @click="handleImport">导入商品</el-button>
+            <el-button type="primary" icon="Plus" @click="handleAddProduct">添加商品</el-button>
+            <el-button type="danger" icon="Delete" @click="handleClearPool">清空产品池</el-button>
+          </div>
+        </div>
+      </template>
+
+      <el-table v-loading="loading" border :data="productList">
+        <el-table-column type="index" label="序号" width="60" align="center" />
+        <el-table-column label="商品编号" align="center" prop="productNo" width="120" />
+        <el-table-column label="商品图片" align="center" prop="productImage" width="100">
+          <template #default="scope">
+            <image-preview :src="scope.row.productImage" :width="60" :height="60"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品信息" align="center" min-width="200">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px;">
+              <div>{{ scope.row.itemName }}</div>
+              <div class="text-gray-500">品牌:{{ scope.row.brandName || '-' }}</div>
+              <div class="text-gray-500">库存:{{ scope.row.stock || '999' }}</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品类别" align="center" width="150">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px;">
+              <div>{{ scope.row.categoryName || getCategoryFullPath(scope.row.bottomCategoryId) || '-' }}</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="单位" align="center" prop="unitName" width="80" />
+        <el-table-column label="SKU价格" align="center" width="150">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px;">
+              <div>
+                <span class="text-gray-500">市场价:</span>
+                <span class="text-red-500">¥{{ scope.row.marketPrice || '0.00' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">官网价:</span>
+                <span class="text-red-500">¥{{ scope.row.memberPrice || '0.00' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">最低售价:</span>
+                <span class="text-red-500">¥{{ scope.row.minSellingPrice || '0.00' }}</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column v-if="auditForm.type === '2'" label="协议价" align="center" width="140">
+          <template #default="scope">
+            <el-input-number
+              v-model="tempAgreementPrices[scope.row.id]"
+              :min="0"
+              :precision="2"
+              :controls="false"
+              size="small"
+              style="width: 120px"
+              placeholder="请输入协议价"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column label="成本数据" align="center" width="150">
+          <template #default="scope">
+            <div class="text-left" style="font-size: 12px;">
+              <div>
+                <span class="text-gray-500">采购价:</span>
+                <span>¥{{ scope.row.purchasePrice || '0.00' }}</span>
+              </div>
+              <div>
+                <span class="text-gray-500">暂估毛利率:</span>
+                <span>{{ scope.row.grossMargin ?? scope.row.tempGrossMargin ?? '0.00' }}%</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品状态" align="center" width="80">
+          <template #default="scope">
+            <el-tag v-if="String(scope.row.productStatus) === '1'" type="success">上架</el-tag>
+            <el-tag v-else type="warning">下架</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="入池时间" align="center" prop="createTime" width="120" />
+        <el-table-column label="创建供应商" align="center" width="100">
+          <template #default="scope">
+            <span>{{ getSupplierName(scope.row.supplier) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="120" fixed="right">
+          <template #default="scope">
+            <div class="flex flex-col gap-1">
+              <el-link type="danger" :underline="false" @click="handleRemoveProduct(scope.row)">移除商品池</el-link>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </el-card>
+
+    <!-- 底部操作按钮 -->
+    <div class="mt-4 flex justify-end gap-2">
+      <el-button @click="goBack">取消</el-button>
+      <el-button type="primary" @click="handleSubmitAudit">确定</el-button>
+    </div>
+
+    <!-- 导入商品对话框 -->
+    <el-dialog v-model="importDialog.open" title="导入商品" width="400px" append-to-body @close="handleImportDialogClose">
+      <el-upload
+        ref="uploadRef"
+        :limit="1"
+        accept=".xlsx, .xls"
+        :auto-upload="false"
+        :before-upload="() => false"
+        :on-change="handleFileChange"
+        :on-remove="() => (importDialog.selectedFile = null)"
+        drag
+      >
+        <el-icon class="el-icon--upload"><i-ep-upload-filled /></el-icon>
+        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+        <template #tip>
+          <div class="text-center el-upload__tip">
+            <span>仅允许导入 xls、xlsx 格式文件。</span>
+            <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="downloadTemplate">下载模板</el-link>
+          </div>
+        </template>
+      </el-upload>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitFileForm" :loading="importDialog.isUploading">确 定</el-button>
+          <el-button @click="importDialog.open = false">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 添加商品对话框 -->
+    <el-dialog title="添加商品" v-model="addProductDialog.visible" width="1400px" append-to-body top="5vh">
+      <div class="add-product-dialog">
+        <!-- 搜索区域 -->
+        <el-form :model="addProductQuery" :inline="true" class="mb-4">
+          <el-form-item>
+            <el-button type="primary" icon="Plus" @click="handleBatchAdd">加入清单</el-button>
+          </el-form-item>
+          <el-form-item label="商品名称:">
+            <el-input v-model="addProductQuery.itemName" placeholder="商品名称" clearable style="width: 200px" />
+          </el-form-item>
+          <el-form-item label="商品编号:">
+            <el-input v-model="addProductQuery.productNo" placeholder="商品编号" clearable style="width: 200px" />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleSearchProducts">搜索</el-button>
+          </el-form-item>
+        </el-form>
+
+        <!-- 商品列表 -->
+        <el-table
+          ref="addProductTableRef"
+          v-loading="addProductDialog.loading"
+          :data="addProductDialog.productList"
+          border
+          @selection-change="handleSelectionChange"
+          max-height="500"
+        >
+          <el-table-column type="selection" width="55" align="center" />
+          <el-table-column label="商品编号" align="center" prop="productNo" width="120" />
+          <el-table-column label="商品图片" align="center" prop="productImage" width="100">
+            <template #default="scope">
+              <image-preview :src="scope.row.productImage" :width="60" :height="60"/>
+            </template>
+          </el-table-column>
+          <el-table-column label="商品信息" align="center" min-width="200">
+            <template #default="scope">
+              <div class="text-left" style="font-size: 12px;">
+                <div>{{ scope.row.itemName }}</div>
+                <div class="text-gray-500">品牌:{{ scope.row.brandName || '雅唐' }}</div>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="商品分类" align="center" width="150">
+            <template #default="scope">
+              <div class="text-left" style="font-size: 12px;">
+                <div>{{ getCategoryName(scope.row) }}</div>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="单位" align="center" width="100">
+            <template #default="scope">
+              <div class="text-left" style="font-size: 12px;">
+                <div>单位:{{ scope.row.unitName || '件' }}</div>
+                <div>起订量:{{ scope.row.minOrderQuantity || 1 }}</div>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="SKU价格" align="center" width="150">
+            <template #default="scope">
+              <div class="text-left" style="font-size: 12px;">
+                <div>
+                  <span class="text-gray-500">市场价:</span>
+                  <span>¥{{ scope.row.midRangePrice || '0.00' }}</span>
+                </div>
+                <div>
+                  <span class="text-gray-500">官网价:</span>
+                  <span class="text-red-500">¥{{ scope.row.standardPrice || '0.00' }}</span>
+                </div>
+                <div>
+                  <span class="text-gray-500">最低价:</span>
+                  <span>¥{{ scope.row.certificatePrice || '0.00' }}</span>
+                </div>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column v-if="auditForm.type === '2'" label="协议价" align="center" width="140">
+            <template #default="scope">
+              <el-input-number
+                v-model="priceInputMap[scope.row.id]"
+                :min="0"
+                :precision="2"
+                :controls="false"
+                size="small"
+                style="width: 120px"
+                placeholder="请输入协议价"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="库存情况" align="center" width="150">
+            <template #default="scope">
+              <div class="text-left" style="font-size: 12px;">
+                <div class="text-red-500">库存总数:{{ scope.row.stock || 0 }}</div>
+                <div>现有库存:{{ scope.row.availableStock || 0 }}</div>
+                <div>虚拟库存:{{ scope.row.virtualStock || 0 }}</div>
+                <div class="text-orange-500">[现有库存不足时]</div>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="供应情况" align="center" width="150">
+            <template #default="scope">
+              <div class="text-left" style="font-size: 12px;">
+                <div>供应商数量:{{ scope.row.supplierCount || 0 }}</div>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" align="center" width="100" fixed="right">
+            <template #default="scope">
+              <el-link type="primary" :underline="false" @click="handleAddSingleProduct(scope.row)" :disabled="tempProductIds.includes(scope.row.id)">加入清单</el-link>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <!-- 游标分页控制 -->
+        <pagination
+          v-show="addProductDialog.productList.length > 0"
+          v-model:page="addProductQuery.pageNum"
+          v-model:limit="addProductQuery.pageSize"
+          v-model:way="addProductQuery.way"
+          :cursor-mode="true"
+          :has-more="addProductHasMore"
+          @pagination="getProductList"
+        />
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="PoolLinkAudit" lang="ts">
+import { useRouter, useRoute } from 'vue-router';
+import { categoryTree, listBase } from '@/api/product/base';
+import { BaseVO, BaseQuery } from '@/api/product/base/types';
+import { getPoolAudit, updatePoolAudit, addPoolAudit } from '@/api/product/poolAudit';
+import { getPoolLinkAudit } from '@/api/product/poolLinkAudit';
+import { listInfo } from '@/api/customer/supplierInfo';
+import { InfoVO } from '@/api/customer/supplierInfo/types';
+import { listBrand } from '@/api/product/brand';
+import { BrandVO } from '@/api/product/brand/types';
+import { PoolAuditVO, PoolAuditForm } from '@/api/product/poolAudit/types';
+import { getItem, listItem } from '@/api/external/item';
+import { ItemVO } from '@/api/external/item/types';
+import { getInfo as getProtocolInfo, listInfo as listProtocolInfo } from '@/api/product/protocolInfo';
+import { InfoVO as ProtocolInfoVO } from '@/api/product/protocolInfo/types';
+import { getPool, listPool } from '@/api/product/pool';
+import { PoolVO } from '@/api/product/pool/types';
+import * as XLSX from 'xlsx';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const router = useRouter();
+const route = useRoute();
+
+const productList = ref<BaseVO[]>([]);
+const loading = ref(false);
+const total = ref(0);
+
+const auditInfoLoading = ref(false);
+const auditInfo = ref<(PoolAuditVO & { createTime?: string; auditTime?: string }) | undefined>();
+const itemInfo = ref<ItemVO | undefined>();
+const protocolInfo = ref<ProtocolInfoVO | undefined>();
+const poolInfo = ref<PoolVO | undefined>();
+
+// 申请入池表单数据
+const auditForm = reactive<PoolAuditForm>({
+  type: undefined,
+  name: undefined,
+  poolId: undefined,
+  protocolId: undefined,
+  itemId: undefined,
+  remark: undefined,
+  attachment: undefined,
+  productIds: []
+});
+
+/** 下拉选项数据 */
+const poolOptions = ref<PoolVO[]>([]);
+const protocolOptions = ref<ProtocolInfoVO[]>([]);
+const itemOptions = ref<ItemVO[]>([]);
+
+/** 根据产品池类型加载对应下拉选项 */
+const loadSelectOptions = async (type?: string | number) => {
+  const t = String(type ?? auditForm.type ?? '');
+  if ( t === '4') {
+    const res = await listPool({ type: Number(t) } as any);
+    poolOptions.value = res.rows ?? [];
+  } else if (t === '2') {
+    const res = await listProtocolInfo();
+    protocolOptions.value = res.rows ?? [];
+  } else if (t === '3') {
+    const res = await listItem();
+    itemOptions.value = res.rows ?? [];
+  }
+};
+
+// 回显时屏蔽 watch 副作用
+const isInitializing = ref(false);
+
+/** 切换产品池类型时,清空关联选择并重新加载选项 */
+watch(() => auditForm.type, (newType) => {
+  if (isInitializing.value) return;
+  auditForm.poolId = undefined;
+  auditForm.protocolId = undefined;
+  auditForm.itemId = undefined;
+  auditForm.name = undefined;
+  if (newType !== undefined && newType !== null) {
+    loadSelectOptions(newType);
+  }
+});
+
+const queryParams = ref({
+  pageNum: 1,
+  pageSize: 10,
+  poolAuditId: (route.params.id || route.query.id ) as string | number,
+  productNo: undefined,
+  itemName: undefined,
+  brandId: undefined,
+  productStatus: undefined,
+  bottomCategoryId: undefined,
+  supplier: undefined,
+  dateRange: undefined,
+});
+
+const categoryOptions = ref<any[]>([]);
+const categoryMap = ref<Map<string | number, any>>(new Map());
+const supplierOptions = ref<InfoVO[]>([]);
+const brandOptions = ref<BrandVO[]>([]);
+const brandLoading = ref(false);
+let brandSearchTimer: ReturnType<typeof setTimeout> | null = null;
+
+// 导入商品对话框
+const importDialog = reactive({
+  open: false,
+  isUploading: false,
+  selectedFile: null as File | null
+});
+const uploadRef = ref<any>();
+
+// 添加商品对话框
+const addProductDialog = reactive({
+  visible: false,
+  loading: false,
+  productList: [] as BaseVO[],
+  total: 0,
+});
+
+// 游标分页相关变量
+const addProductHasMore = ref(true);
+const addProductPageHistory = ref<Array<{ firstId: string | number; lastId: string | number }>>([]);
+
+// 添加商品查询参数
+const addProductQuery = ref<BaseQuery>({
+  pageNum: 1,
+  pageSize: 10,
+  way: undefined,
+  productNo: undefined,
+  itemName: undefined,
+  lastSeenId: undefined,
+});
+
+// 选中的商品
+const selectedProducts = ref<BaseVO[]>([]);
+const addProductTableRef = ref<any>();
+// 协议价输入映射(type=2时使用,项为对话框内商品id)
+const priceInputMap = reactive<Record<string | number, number | undefined>>({});
+
+// 临时展示用:本地暂存的入池商品ID列表
+const tempProductIds = ref<Array<string | number>>([]);
+// 临时展示用:本地暂存的协议价(type=2)
+const tempAgreementPrices = reactive<Record<string | number, number | undefined>>({})
+
+/** 获取审核状态标签文字 */
+const getStatusLabel = (status?: string): string => {
+  const map: Record<string, string> = { '0': '待提交', '1': '待审核', '2': '审核通过', '3': '审核驳回' };
+  return status !== undefined ? (map[status] || status) : '-';
+};
+
+/** 获取审核状态标签类型 */
+const getStatusTagType = (status?: string): 'success' | 'warning' | 'info' | 'danger' | 'primary' => {
+  const map: Record<string, 'success' | 'warning' | 'info' | 'danger' | 'primary'> = {
+    '0': 'info', '1': 'warning', '2': 'success', '3': 'danger'
+  };
+  return status !== undefined ? (map[status] || 'primary') : 'primary';
+};
+
+/** 加载审核池信息(编辑回显) */
+const loadAuditInfo = async () => {
+  const id = route.params.id || route.query.id || route.query.poolAuditId;
+  if (!id) return;
+  auditInfoLoading.value = true;
+  try {
+    const res = await getPoolLinkAudit(id as string | number);
+    const data = res.data as any;
+    auditInfo.value = data;
+
+    // 回显表单,先设标志位防止 watch 清空字段
+    isInitializing.value = true;
+    auditForm.type = data.type !== undefined && data.type !== null ? String(data.type) : undefined;
+    auditForm.name = data.name;
+    auditForm.poolId = data.poolId;
+    auditForm.protocolId = data.protocolId;
+    auditForm.itemId = data.itemId;
+    auditForm.remark = data.remark;
+    auditForm.attachment = data.attachment;
+    isInitializing.value = false;
+
+    // 加载对应下拉选项
+    if (auditForm.type) {
+      await loadSelectOptions(auditForm.type);
+    }
+
+    // 从 products 初始化商品 ID 列表和协议价
+    const products: Array<{ productId: string | number; agreementPrice?: number }> = data.products || [];
+    tempProductIds.value = products.map((p: any) => p.productId);
+    products.forEach((p: any) => {
+      if (p.agreementPrice !== undefined && p.agreementPrice !== null) {
+        tempAgreementPrices[p.productId] = p.agreementPrice;
+      }
+    });
+
+    // 刷新商品列表
+    await getList();
+
+    // 加载关联详情信息
+    const type = auditForm.type;
+    if (type === '3' && data.itemId) {
+      const itemRes = await getItem(data.itemId);
+      itemInfo.value = itemRes.data;
+    } else if (type === '2' && data.protocolId) {
+      const protocolRes = await getProtocolInfo(data.protocolId);
+      protocolInfo.value = protocolRes.data;
+    } else if ((type === '0' || type === '4') && data.poolId) {
+      const poolRes = await getPool(data.poolId);
+      poolInfo.value = poolRes.data;
+    }
+  } catch (error) {
+    console.error('加载信息失败:', error);
+  } finally {
+    auditInfoLoading.value = false;
+  }
+};
+
+/** 获取分类树 */
+const getCategoryTree = async () => {
+  try {
+    const res = await categoryTree();
+    categoryOptions.value = res.data || [];
+    buildCategoryMap(categoryOptions.value);
+  } catch (error) {
+    console.error('获取分类树失败:', error);
+  }
+};
+
+/** 获取供应商列表 */
+const getSupplierList = async () => {
+  try {
+    const res = await listInfo();
+    supplierOptions.value = res.data || res.rows || [];
+  } catch (error) {
+    console.error('获取供应商列表失败:', error);
+  }
+};
+
+/** 获取供应商名称 */
+const getSupplierName = (supplierId: string | number | undefined): string => {
+  if (!supplierId) return '-';
+  const supplier = supplierOptions.value.find(item => item.id === supplierId);
+  return supplier?.enterpriseName || supplier?.shortName || '-';
+};
+
+/** 加载品牌选项(默认100条) */
+const loadBrandOptions = async (keyword?: string) => {
+  brandLoading.value = true;
+  try {
+    const res = await listBrand({ pageNum: 1, pageSize: 100, brandName: keyword });
+    brandOptions.value = res.rows || [];
+  } catch (error) {
+    console.error('加载品牌列表失败:', error);
+  } finally {
+    brandLoading.value = false;
+  }
+};
+
+/** 品牌远程搜索(防抖) */
+const handleBrandSearch = (query: string) => {
+  if (brandSearchTimer) clearTimeout(brandSearchTimer);
+  brandSearchTimer = setTimeout(() => {
+    loadBrandOptions(query || undefined);
+  }, 300);
+};
+
+/** 构建分类映射 */
+const buildCategoryMap = (categories: any[], parentPath = '') => {
+  categories.forEach(category => {
+    const fullPath = parentPath ? `${parentPath} > ${category.label}` : category.label;
+    categoryMap.value.set(category.id, { ...category, fullPath });
+    if (category.children && category.children.length > 0) {
+      buildCategoryMap(category.children, fullPath);
+    }
+  });
+};
+
+/** 获取分类完整路径 */
+const getCategoryFullPath = (categoryId: string | number): string => {
+  const category = categoryMap.value.get(categoryId);
+  return category?.fullPath || '';
+};
+
+/** 查询商品列表(将本地暂存的商品ID传给后端查询) */
+const getList = async () => {
+  // 没有临时商品时直接显示空列表
+  if (tempProductIds.value.length === 0) {
+    productList.value = [];
+    total.value = 0;
+    return;
+  }
+  loading.value = true;
+  try {
+    const params: any = {
+      pageNum: queryParams.value.pageNum,
+      pageSize: queryParams.value.pageSize,
+      ids: tempProductIds.value,
+    };
+    const res = await listBase(params);
+    productList.value = (res.rows || res.data || []) as any;
+    total.value = res.total || tempProductIds.value.length;
+  } catch (error) {
+    console.error('获取商品列表失败:', error);
+    productList.value = [];
+    total.value = 0;
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 返回 */
+const goBack = () => {
+  router.back();
+};
+
+/** 搜索 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+
+/** 申请入池(待提交 -> 待审核) */
+const handleApplyPool = async () => {
+  await proxy?.$modal.confirm('确认要申请入池吗?提交后状态将变为待审核。');
+  const id = auditInfo.value?.id;
+  if (!id) return;
+  await updatePoolAudit({ id, productReviewStatus: '1' });
+  proxy?.$modal.msgSuccess('申请成功,状态已变为待审核');
+  await loadAuditInfo();
+};
+
+/** 确定按钮 - 提交产品池审核单据 */
+const handleSubmitAudit = async () => {
+  if (!auditForm.type) {
+    proxy?.$modal.msgWarning('请选择产品池类型');
+    return;
+  }
+  if ((auditForm.type === '0' || auditForm.type === '4') && !auditForm.poolId) {
+    proxy?.$modal.msgWarning('请选择产品池');
+    return;
+  }
+  if (auditForm.type === '2' && !auditForm.protocolId) {
+    proxy?.$modal.msgWarning('请选择协议');
+    return;
+  }
+  if (auditForm.type === '3' && !auditForm.itemId) {
+    proxy?.$modal.msgWarning('请选择项目');
+    return;
+  }
+  if (auditForm.type === '1' && !auditForm.name) {
+    proxy?.$modal.msgWarning('请输入产品池名称');
+    return;
+  }
+  if (tempProductIds.value.length === 0) {
+    proxy?.$modal.msgWarning('请先添加商品到入池清单');
+    return;
+  }
+
+  await proxy?.$modal.confirm('确认要提交产品池审核单据吗?');
+
+  try {
+    // 提交整个审核单据(包含本地暂存的商品ID及协议价)
+    const submitData: PoolAuditForm = {
+      ...auditForm,
+      products: tempProductIds.value.map(id => ({
+            productId: id,
+            negotiatedPrice: tempAgreementPrices[id]
+          })),    
+      productReviewStatus: '1' // 提交后状态为待审核
+    };
+
+    await addPoolAudit(submitData);
+    proxy?.$modal.msgSuccess('提交成功');
+    goBack();
+  } catch (error) {
+    console.error('提交失败:', error);
+  }
+};
+
+/** 清空入池清单 */
+const handleClearPool = async () => {
+  await proxy?.$modal.confirm('确认要清空入池清单中的所有商品吗?');
+  tempProductIds.value = [];
+  Object.keys(tempAgreementPrices).forEach(key => delete tempAgreementPrices[key]);
+  productList.value = [];
+  total.value = 0;
+  proxy?.$modal.msgSuccess('清空成功');
+};
+
+/** 导入商品按钮 */
+const handleImport = () => {
+  importDialog.open = true;
+  importDialog.isUploading = false;
+  importDialog.selectedFile = null;
+  nextTick(() => {
+    uploadRef.value?.clearFiles();
+  });
+};
+
+/** 关闭导入对话框时清理 */
+const handleImportDialogClose = () => {
+  importDialog.selectedFile = null;
+  importDialog.isUploading = false;
+  uploadRef.value?.clearFiles();
+};
+
+/** 下载导入模板 */
+const downloadTemplate = () => {
+  const url = new URL('./商品导入模版.xlsx', import.meta.url).href;
+  const a = document.createElement('a');
+  a.href = url;
+  a.download = '商品导入模版.xlsx';
+  document.body.appendChild(a);
+  a.click();
+  document.body.removeChild(a);
+};
+
+/** 选择文件时暂存 */
+const handleFileChange = (file: any) => {
+  importDialog.selectedFile = file.raw as File;
+};
+
+/** 解析 Excel 文件,读取商品编号和协议价格两列 */
+const parseExcelData = (file: File): Promise<Array<{ productNo: string; negotiatedPrice?: number }>> => {
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader();
+    reader.onload = (e) => {
+      try {
+        const data = new Uint8Array(e.target!.result as ArrayBuffer);
+        const workbook = XLSX.read(data, { type: 'array' });
+        const sheet = workbook.Sheets[workbook.SheetNames[0]];
+        const rows = XLSX.utils.sheet_to_json<any[]>(sheet, { header: 1 });
+        const headerRow: any[] = rows[0] || [];
+        // 查找「商品编号」列索引
+        let noColIndex = headerRow.findIndex((h: any) => String(h).trim() === '商品编号');
+        if (noColIndex === -1) noColIndex = 0;
+        // 查找「协议价格」列索引
+        let priceColIndex = headerRow.findIndex((h: any) => String(h).trim() === '协议价格');
+        const result: Array<{ productNo: string; negotiatedPrice?: number }> = [];
+        rows.slice(1).forEach((row: any[]) => {
+          const val = row[noColIndex];
+          if (val !== undefined && val !== null && String(val).trim() !== '') {
+            const negotiatedPrice = priceColIndex !== -1 && row[priceColIndex] !== undefined && row[priceColIndex] !== null
+              ? Number(row[priceColIndex])
+              : undefined;
+            result.push({
+              productNo: String(val).trim(),
+              negotiatedPrice: !isNaN(negotiatedPrice as number) ? negotiatedPrice : undefined
+            });
+          }
+        });
+        resolve(result);
+      } catch (err) {
+        reject(err);
+      }
+    };
+    reader.onerror = reject;
+    reader.readAsArrayBuffer(file);
+  });
+};
+
+/** 提交文件:前端解析 Excel,再查询匹配商品 ID */
+const submitFileForm = async () => {
+  if (!importDialog.selectedFile) {
+    proxy?.$modal.msgWarning('请先选择要导入的文件');
+    return;
+  }
+  importDialog.isUploading = true;
+  try {
+    const parsedRows = await parseExcelData(importDialog.selectedFile);
+    if (parsedRows.length === 0) {
+      proxy?.$modal.msgWarning('未在文件中找到有效的商品编号,请检查模板格式');
+      return;
+    }
+    const productNos = parsedRows.map(r => r.productNo);
+    // 按商品编号批量查询商品,获取商品对象
+    const res = await listBase({ productNos, pageNum: 1, pageSize: productNos.length } as any);
+    const products: BaseVO[] = res.rows || res.data || [];
+    if (products.length === 0) {
+      proxy?.$modal.msgWarning(`共读取 ${productNos.length} 个编号,但未匹配到任何商品`);
+      return;
+    }
+    // 建立 productNo -> negotiatedPrice 映射
+    const priceMap = new Map<string, number | undefined>();
+    parsedRows.forEach(r => priceMap.set(r.productNo, r.negotiatedPrice));
+    const isProtocol = auditForm.type === '2';
+    let addedCount = 0;
+    products.forEach((p) => {
+      if (!tempProductIds.value.includes(p.id)) {
+        tempProductIds.value.push(p.id);
+        addedCount++;
+      }
+      // 无论是否已存在,有协议价时都更新
+      if (isProtocol && p.productNo) {
+        const price = priceMap.get(p.productNo);
+        if (price !== undefined) {
+          tempAgreementPrices[p.id] = price;
+        }
+      }
+    });
+    const skipped = productNos.length - products.length;
+    importDialog.open = false;
+    const msg = skipped > 0
+      ? `成功导入 ${addedCount} 个商品,${skipped} 个编号未匹配`
+      : `成功导入 ${addedCount} 个商品到入池清单`;
+    proxy?.$modal.msgSuccess(msg);
+    await getList();
+  } catch (err) {
+    console.error('导入失败:', err);
+    proxy?.$modal.msgError('解析文件失败,请检查文件格式');
+  } finally {
+    importDialog.isUploading = false;
+  }
+};
+
+/** 添加商品 */
+const handleAddProduct = () => {
+  addProductDialog.visible = true;
+  addProductQuery.value = {
+    pageNum: 1,
+    pageSize: 10,
+    way: undefined,
+    productNo: undefined,
+    itemName: undefined,
+    lastSeenId: undefined,
+  };
+  addProductPageHistory.value = [];
+  addProductHasMore.value = true;
+  selectedProducts.value = [];
+  // 清空协议价映射
+  Object.keys(priceInputMap).forEach(key => delete priceInputMap[key]);
+  getProductList();
+};
+
+/** 获取商品列表 */
+const getProductList = async () => {
+  addProductDialog.loading = true;
+  try {
+    const params = { ...addProductQuery.value };
+    const currentPageNum = addProductQuery.value.pageNum;
+
+    if (currentPageNum === 1) {
+      delete params.lastSeenId;
+      delete params.firstSeenId;
+      delete params.way;
+    } else {
+      if (addProductQuery.value.way === 0) {
+        const nextPageHistory = addProductPageHistory.value[currentPageNum];
+        if (nextPageHistory) {
+          params.firstSeenId = nextPageHistory.firstId;
+          params.way = 0;
+        }
+      } else {
+        const prevPageHistory = addProductPageHistory.value[currentPageNum - 1];
+        if (prevPageHistory) {
+          params.lastSeenId = prevPageHistory.lastId;
+          params.way = 1;
+        }
+      }
+    }
+
+    const res = await listBase(params);
+    if (res.rows) {
+      addProductDialog.productList = res.rows;
+      addProductDialog.total = res.total || 0;
+    } else if (res.data) {
+      addProductDialog.productList = Array.isArray(res.data) ? res.data : [];
+      addProductDialog.total = addProductDialog.productList.length;
+    } else {
+      addProductDialog.productList = [];
+      addProductDialog.total = 0;
+    }
+
+    addProductHasMore.value = addProductDialog.productList.length === addProductQuery.value.pageSize;
+
+    if (addProductDialog.productList.length > 0) {
+      const firstItem = addProductDialog.productList[0];
+      const lastItem = addProductDialog.productList[addProductDialog.productList.length - 1];
+      if (addProductPageHistory.value.length <= currentPageNum) {
+        addProductPageHistory.value[currentPageNum] = {
+          firstId: firstItem.id,
+          lastId: lastItem.id
+        };
+      }
+    }
+    // 初始化协议价(只对type=2有意义,默认用官网价)
+    if (auditInfo.value?.type === '2') {
+      addProductDialog.productList.forEach(product => {
+        if (priceInputMap[product.id] === undefined) {
+          priceInputMap[product.id] = product.standardPrice ?? product.midRangePrice ?? 0;
+        }
+      });
+    }
+  } catch (error) {
+    console.error('获取商品列表失败:', error);
+    addProductDialog.productList = [];
+    addProductDialog.total = 0;
+  } finally {
+    addProductDialog.loading = false;
+  }
+};
+
+/** 搜索商品 */
+const handleSearchProducts = () => {
+  addProductQuery.value.pageNum = 1;
+  addProductQuery.value.lastSeenId = undefined;
+  addProductPageHistory.value = [];
+  addProductHasMore.value = true;
+  getProductList();
+};
+
+/** 选择变化 */
+const handleSelectionChange = (selection: BaseVO[]) => {
+  selectedProducts.value = selection;
+};
+
+/** 批量加入清单(本地暂存,不调接口) */
+const handleBatchAdd = async () => {
+  if (selectedProducts.value.length === 0) {
+    proxy?.$modal.msgWarning('请先选择要添加的商品');
+    return;
+  }
+
+  const isProtocol = auditForm.type === '2';
+  let addedCount = 0;
+
+  selectedProducts.value.forEach(product => {
+    if (!tempProductIds.value.includes(product.id)) {
+      tempProductIds.value.push(product.id);
+      // 存储协议价(type=2时取用户输入的值,否则取官网价)
+      tempAgreementPrices[product.id] = isProtocol
+        ? (priceInputMap[product.id] ?? product.standardPrice ?? product.midRangePrice)
+        : undefined;
+      addedCount++;
+    }
+  });
+
+  if (addedCount === 0) {
+    proxy?.$modal.msgWarning('选中的商品已全部在入池清单中');
+    return;
+  }
+
+  proxy?.$modal.msgSuccess(`成功添加 ${addedCount} 个商品到入池清单`);
+  addProductDialog.visible = false;
+  selectedProducts.value = [];
+  if (addProductTableRef.value) {
+    addProductTableRef.value.clearSelection();
+  }
+  await getList();
+};
+
+/** 添加单个商品(本地暂存,不调接口) */
+const handleAddSingleProduct = async (row: BaseVO) => {
+  if (tempProductIds.value.includes(row.id)) {
+    proxy?.$modal.msgWarning('该商品已在入池清单中');
+    return;
+  }
+
+  const isProtocol = auditForm.type === '2';
+  tempProductIds.value.push(row.id);
+  tempAgreementPrices[row.id] = isProtocol
+    ? (priceInputMap[row.id] ?? row.standardPrice ?? row.midRangePrice)
+    : undefined;
+
+  proxy?.$modal.msgSuccess('添加成功');
+  await getList();
+};
+
+/** 获取分类名称 */
+const getCategoryName = (row: BaseVO): string => {
+  if (row.bottomCategoryId) {
+    return getCategoryFullPath(row.bottomCategoryId);
+  }
+  return '-';
+};
+
+/** 移除商品(本地移除) */
+const handleRemoveProduct = async (row: BaseVO) => {
+  await proxy?.$modal.confirm('确认要移除该商品吗?');
+  const idx = tempProductIds.value.indexOf(row.id);
+  if (idx !== -1) {
+    tempProductIds.value.splice(idx, 1);
+    delete tempAgreementPrices[row.id];
+  }
+  proxy?.$modal.msgSuccess('移除成功');
+  await getList();
+};
+
+onMounted(() => {
+  loadAuditInfo();
+  getCategoryTree();
+  getSupplierList();
+  getList();
+  loadBrandOptions();
+});
+</script>
+
+<style scoped lang="scss">
+.add-product-dialog {
+  :deep(.el-form--inline .el-form-item) {
+    margin-right: 10px;
+  }
+}
+</style>

BIN
src/views/product/poolLinkAudit/商品导入模版.xlsx


+ 6 - 6
src/views/product/priceInventory/index.vue

@@ -7,8 +7,8 @@
             <el-form-item label="市场价格" prop="marketPrice">
               <el-input v-model="queryParams.marketPrice" placeholder="请输入市场价格" clearable @keyup.enter="handleQuery" />
             </el-form-item>
-            <el-form-item label="会员价格" prop="memberPrice">
-              <el-input v-model="queryParams.memberPrice" placeholder="请输入会员价格" clearable @keyup.enter="handleQuery" />
+            <el-form-item label="官网价" prop="memberPrice">
+              <el-input v-model="queryParams.memberPrice" placeholder="请输入官网价" clearable @keyup.enter="handleQuery" />
             </el-form-item>
             <el-form-item label="最低销售价格" prop="minSellingPrice">
               <el-input v-model="queryParams.minSellingPrice" placeholder="请输入最低销售价格" clearable @keyup.enter="handleQuery" />
@@ -72,7 +72,7 @@
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="关联 product_base.id" align="center" prop="productId" v-if="true" />
         <el-table-column label="市场价格" align="center" prop="marketPrice" />
-        <el-table-column label="会员价格" align="center" prop="memberPrice" />
+        <el-table-column label="官网价" align="center" prop="memberPrice" />
         <el-table-column label="最低销售价格" align="center" prop="minSellingPrice" />
         <el-table-column label="采购价格" align="center" prop="purchasingPrice" />
         <el-table-column label="最高采购价格" align="center" prop="maxPurchasePrice" />
@@ -103,8 +103,8 @@
         <el-form-item label="市场价格" prop="marketPrice">
           <el-input v-model="form.marketPrice" placeholder="请输入市场价格" />
         </el-form-item>
-        <el-form-item label="会员价格" prop="memberPrice">
-          <el-input v-model="form.memberPrice" placeholder="请输入会员价格" />
+        <el-form-item label="官网价" prop="memberPrice">
+          <el-input v-model="form.memberPrice" placeholder="请输入官网价" />
         </el-form-item>
         <el-form-item label="最低销售价格" prop="minSellingPrice">
           <el-input v-model="form.minSellingPrice" placeholder="请输入最低销售价格" />
@@ -210,7 +210,7 @@ const data = reactive<PageData<PriceInventoryForm, PriceInventoryQuery>>({
       { required: true, message: "市场价格不能为空", trigger: "blur" }
     ],
     memberPrice: [
-      { required: true, message: "会员价格不能为空", trigger: "blur" }
+      { required: true, message: "官网价不能为空", trigger: "blur" }
     ],
     minSellingPrice: [
       { required: true, message: "最低销售价格不能为空", trigger: "blur" }

+ 17 - 5
src/views/product/protocolInfo/index.vue

@@ -56,17 +56,18 @@
           </template>
         </el-table-column>
         <el-table-column label="商品数量" align="center" prop="productNum" width="100" />
-        <el-table-column label="状态" align="center" prop="approvalStatus" width="80">
+        <!-- <el-table-column label="状态" align="center" prop="approvalStatus" width="80">
           <template #default="scope">
             <span>{{ getStatusLabel(scope.row.protocolStatus) }}</span>
           </template>
-        </el-table-column>
+        </el-table-column> -->
         <el-table-column label="操作" align="center" fixed="right" width="300" class-name="small-padding fixed-width">
           <template #default="scope">
             <el-button link type="primary" @click="handleDetail(scope.row)" >基本信息</el-button>
-            <el-button link type="primary" @click="handleProductManage(scope.row)" >商品管理</el-button>
-            <el-button link type="primary" @click="handleInvalid(scope.row)" v-if="scope.row.protocolStatus === 1" >失效</el-button>
-            <el-button link type="primary" @click="handleAudit(scope.row)" v-if="scope.row.protocolStatus === 0" >审核</el-button>
+            <el-button v-if="isAudit === 0" link type="primary" @click="handleProductManage(scope.row)" >商品管理</el-button>
+            <el-button v-if="isAudit === 1" link type="primary" @click="handleGoReview(scope.row)" >去审核</el-button>
+            <!-- <el-button link type="primary" @click="handleInvalid(scope.row)" v-if="scope.row.protocolStatus === 1" >失效</el-button>
+            <el-button link type="primary" @click="handleAudit(scope.row)" v-if="scope.row.protocolStatus === 0" >审核</el-button> -->
           </template>
         </el-table-column>
       </el-table>
@@ -178,6 +179,10 @@ import { CustomerInfoVO } from '@/api/customer/customerInfo/types';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const router = useRouter();
+const route = useRoute();
+
+// 从路由获取isAudit参数
+const isAudit = ref(Number(route.query.isAudit) || 0);
 
 const infoList = ref<InfoVO[]>([]);
 const customerList = ref<CustomerInfoVO[]>([]);
@@ -434,6 +439,13 @@ const handleProductManage = (row: InfoVO) => {
     query: { protocolId: row.id }
   });
 }
+/** 去审核 */
+const handleGoReview = (row: InfoVO) => {
+  router.push({
+    path: '/product/protocolInfo/review',
+    query: { protocolId: row.id }
+  });
+}
 
 /** 失效操作 */
 const handleInvalid = async (row: InfoVO) => {

+ 32 - 10
src/views/product/protocolInfo/productManage.vue

@@ -25,9 +25,9 @@
             <el-form-item label="产品名称" prop="productName">
               <el-input v-model="queryParams.productName" placeholder="请输入产品名称" clearable @keyup.enter="handleQuery" />
             </el-form-item>
-            <el-form-item label="商品品牌" prop="brandId">
-              <el-select v-model="queryParams.brandId" placeholder="请选择" clearable style="width: 200px">
-                <el-option v-for="item in brandList" :key="item.id" :label="item.brandName" :value="item.id" />
+            <el-form-item label="商品品牌" prop="brandName">
+              <el-select v-model="queryParams.brandName" placeholder="请选择" clearable style="width: 200px">
+                <el-option v-for="item in brandList" :key="item.id" :label="item.brandName" :value="item.brandName" />
               </el-select>
             </el-form-item>
             <el-form-item label="商品状态" prop="productStatus">
@@ -36,12 +36,19 @@
                 <el-option label="下架" value="0" />
               </el-select>
             </el-form-item>
+            <!-- <el-form-item label="审核状态" prop="auditStatus">
+              <el-select v-model="queryParams.auditStatus" placeholder="请选择" clearable style="width: 200px">
+                <el-option label="待审核" :value="1" />
+                <el-option label="审核通过" :value="2" />
+                <el-option label="审核驳回" :value="3" />
+              </el-select>
+            </el-form-item> -->
             <el-form-item>
               <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
               <el-button type="primary" icon="Refresh" @click="resetQuery">重置</el-button>
-              <el-button type="primary" icon="Plus" @click="handleAdd">添加商品</el-button>
+              <!-- <el-button type="primary" icon="Plus" @click="handleAdd">添加商品</el-button>
               <el-button type="primary" icon="Upload" @click="handleImport">导入商品</el-button>
-              <el-button type="primary" icon="Download" @click="handleExport">导出商品</el-button>
+              <el-button type="primary" icon="Download" @click="handleExport">导出商品</el-button> -->
             </el-form-item>
           </el-form>
         </el-card>
@@ -75,11 +82,11 @@
         </el-table-column>
         <el-table-column label="产品类别" align="center" prop="categoryName" width="100" />
         <el-table-column label="单位" align="center" prop="unitName" width="80" />
-        <el-table-column label="Sku价格" align="center" width="140">
+        <el-table-column label="Sku价格" align="center" width="170">
           <template #default="scope">
             <div class="text-left">
               <div>市场价: ¥{{ scope.row.marketPrice || 0 }}</div>
-              <div class="text-red-500">平台价: ¥{{ scope.row.memberPrice || 0 }}</div>
+              <div class="text-red-500">官网价: ¥{{ scope.row.memberPrice || 0 }}</div>
               <div>标准成本: ¥{{ scope.row.purchasingPrice || 0 }}</div>
               <div>最低售价: ¥{{ scope.row.minSellingPrice || 0 }}</div>
             </div>
@@ -93,6 +100,19 @@
             </span>
           </template>
         </el-table-column>
+        <!-- <el-table-column label="审核状态" align="center" width="100">
+          <template #default="scope">
+            <el-tag v-if="scope.row.auditStatus === 1" type="warning">待审核</el-tag>
+            <el-tag v-else-if="scope.row.auditStatus === 2" type="success">审核通过</el-tag>
+            <el-tag v-else-if="scope.row.auditStatus === 3" type="danger">审核驳回</el-tag>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="审核意见" align="center" width="150" show-overflow-tooltip>
+          <template #default="scope">
+            <span>{{ scope.row.auditReason || '-' }}</span>
+          </template>
+        </el-table-column> -->
         <el-table-column label="协议供货价" align="center" width="140">
           <template #default="scope">
             <el-input-number
@@ -192,7 +212,7 @@
                   <span>¥{{ scope.row.midRangePrice || scope.row.marketPrice || '0.00' }}</span>
                 </div>
                 <div>
-                  <span class="text-gray-500">平台价:</span>
+                  <span class="text-gray-500">官网价:</span>
                   <span class="text-red-500">¥{{ scope.row.standardPrice || scope.row.memberPrice || '0.00' }}</span>
                 </div>
                 <div>
@@ -277,7 +297,7 @@ const queryParams = ref<ProductsQuery>({
   protocolNo: undefined,
   productNo: undefined,
   productName: undefined,
-  brandId: undefined,
+  brandName: undefined,
   productStatus: undefined
 });
 
@@ -396,13 +416,15 @@ const resetQuery = () => {
   queryFormRef.value?.resetFields();
   // 保留协议编号
   const pNo = queryParams.value.protocolNo;
+  const protocolId = queryParams.value.protocolId
   queryParams.value = {
     pageNum: 1,
     pageSize: 10,
     protocolNo: pNo,
+    protocolId: protocolId || undefined,
     productNo: undefined,
     productName: undefined,
-    brandId: undefined,
+    brandName: undefined,
     productStatus: undefined
   };
   handleQuery();

+ 780 - 0
src/views/product/protocolInfo/review.vue

@@ -0,0 +1,780 @@
+<template>
+  <div class="p-2">
+    <!-- 头部信息 -->
+    <el-card shadow="hover" class="mb-[10px]">
+      <div class="flex items-center">
+        <el-button link type="primary" @click="handleBack">
+          <el-icon class="mr-1"><ArrowLeft /></el-icon>
+          返回
+        </el-button>
+        <el-divider direction="vertical" />
+        <span class="mr-4">客户编号: {{ protocolInfo.customerNo }}</span>
+        <span class="mr-4">客户名称: {{ protocolInfo.customerName }}</span>
+        <span>截止日期: {{ protocolInfo.endTime }}</span>
+      </div>
+    </el-card>
+
+    <!-- 搜索区域 -->
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+            <el-form-item label="产品编号" prop="productNo">
+              <el-input v-model="queryParams.productNo" placeholder="请输入产品编号" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="产品名称" prop="productName">
+              <el-input v-model="queryParams.productName" placeholder="请输入产品名称" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="商品品牌" prop="brandName">
+              <el-select v-model="queryParams.brandName" placeholder="请选择" clearable style="width: 200px">
+                <el-option v-for="item in brandList" :key="item.id" :label="item.brandName" :value="item.brandName" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="商品状态" prop="productStatus">
+              <el-select v-model="queryParams.productStatus" placeholder="请选择" clearable style="width: 200px">
+                <el-option label="上架" value="1" />
+                <el-option label="下架" value="0" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="审核状态" prop="auditStatus">
+              <el-select v-model="queryParams.auditStatus" placeholder="请选择" clearable style="width: 200px">
+                <el-option label="待审核" :value="1" />
+                <el-option label="审核通过" :value="2" />
+                <el-option label="审核驳回" :value="3" />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button type="primary" icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <!-- 列表区域 -->
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8" justify="space-between">
+          <el-col :span="1.5">
+            <span class="table-title">协议商品详细信息列表</span>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button
+              type="primary"
+              icon="Check"
+              :disabled="selectedProducts.length > 0"
+              @click="handleBatchAudit"
+            >批量审核</el-button>
+          </el-col>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" border :data="productsList" @selection-change="handleTableSelectionChange">
+        <el-table-column type="selection" width="55" align="center" :selectable="checkSelectableForAudit" />
+        <el-table-column label="产品编号" align="center" prop="productNo" width="120" />
+        <el-table-column label="商品图片" align="center" width="100">
+          <template #default="scope">
+            <image-preview :src="scope.row.productImage" :width="60" :height="60"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品名称" align="center" min-width="180">
+          <template #default="scope">
+            <div>
+              <div class="text-primary">{{ scope.row.itemName }}</div>
+              <div class="text-gray-400 text-sm">{{ scope.row.brandName }}</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="产品类别" align="center" prop="categoryName" width="100" />
+        <el-table-column label="单位" align="center" prop="unitName" width="80" />
+        <el-table-column label="Sku价格" align="center" width="140">
+          <template #default="scope">
+            <div class="text-left">
+              <div>市场价: ¥{{ scope.row.marketPrice || 0 }}</div>
+              <div class="text-red-500">官网价: ¥{{ scope.row.memberPrice || 0 }}</div>
+              <div>标准成本: ¥{{ scope.row.purchasingPrice || 0 }}</div>
+              <div>最低售价: ¥{{ scope.row.minSellingPrice || 0 }}</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="产品来源" align="center" prop="dataSource" width="100" />
+        <el-table-column label="状态" align="center" width="80">
+          <template #default="scope">
+            <span :class="scope.row.productStatus === '1' ? 'text-green-500' : 'text-red-500'">
+              {{ scope.row.productStatus === '1' ? '上架' : '下架' }}
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column label="审核状态" align="center" width="100">
+          <template #default="scope">
+            <el-tag v-if="scope.row.auditStatus === 1" type="warning">待审核</el-tag>
+            <el-tag v-else-if="scope.row.auditStatus === 2" type="success">审核通过</el-tag>
+            <el-tag v-else-if="scope.row.auditStatus === 3" type="danger">审核驳回</el-tag>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="审核意见" align="center" width="150" show-overflow-tooltip>
+          <template #default="scope">
+            <span>{{ scope.row.auditReason || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="协议供货价" align="center" width="140">
+          <template #default="scope">
+            <el-input-number
+              v-model="scope.row.agreementPrice"
+              :min="0"
+              :precision="2"
+              size="small"
+              controls-position="right"
+              @change="handlePriceChange(scope.row)"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column label="供货价毛利率" align="center" width="120">
+          <template #default="scope">
+            {{ calculateMargin(scope.row) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" fixed="right" width="80">
+          <template #default="scope">
+            <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination
+        v-show="total > 0"
+        :total="total"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </el-card>
+
+    <!-- 批量审核对话框 -->
+    <el-dialog v-model="auditDialog.visible" title="批量审核" width="600px" append-to-body>
+      <el-form ref="auditFormRef" :model="auditForm" :rules="auditRules" label-width="100px">
+        <el-form-item label="审核结果" prop="auditStatus">
+          <el-radio-group v-model="auditForm.auditStatus">
+            <el-radio :value="2">审核通过</el-radio>
+            <el-radio :value="3">审核驳回</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="审核意见" prop="auditReason">
+          <el-input v-model="auditForm.auditReason" type="textarea" :rows="4" placeholder="请输入审核意见" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="auditDialog.visible = false">取消</el-button>
+          <el-button type="primary" @click="submitBatchAudit">确定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 添加商品对话框 -->
+    <el-dialog title="添加商品" v-model="addProductDialog.visible" width="1400px" append-to-body top="5vh">
+      <div class="add-product-dialog">
+        <!-- 搜索区域 -->
+        <el-form :model="addProductQuery" :inline="true" class="mb-4">
+          <el-form-item>
+            <el-button type="primary" icon="Plus" @click="handleBatchAdd">加入清单</el-button>
+          </el-form-item>
+          <el-form-item label="商品名称:">
+            <el-input v-model="addProductQuery.itemName" placeholder="商品名称" clearable style="width: 200px" />
+          </el-form-item>
+          <el-form-item label="商品编号:">
+            <el-input v-model="addProductQuery.productNo" placeholder="商品编号" clearable style="width: 200px" />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleSearchProducts">搜索</el-button>
+          </el-form-item>
+        </el-form>
+
+        <!-- 商品列表 -->
+        <el-table
+          ref="addProductTableRef"
+          v-loading="addProductDialog.loading"
+          :data="addProductDialog.productList"
+          border
+          @selection-change="handleSelectionChange"
+          max-height="500"
+        >
+          <el-table-column type="selection" width="55" align="center" :selectable="checkSelectable" />
+          <el-table-column label="商品编号" align="center" prop="productNo" width="120" />
+          <el-table-column label="商品图片" align="center" prop="productImageUrl" width="100">
+            <template #default="scope">
+              <image-preview :src="scope.row.productImage " :width="60" :height="60"/>
+            </template>
+          </el-table-column>
+          <el-table-column label="商品信息" align="center" min-width="200">
+            <template #default="scope">
+              <div class="text-left" style="font-size: 12px;">
+                <div>{{ scope.row.itemName }}</div>
+                <div class="text-gray-500">品牌:{{ scope.row.brandName || '-' }}</div>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="商品分类" align="center" width="150">
+            <template #default="scope">
+              <div class="text-left" style="font-size: 12px;">
+                <div>{{ scope.row.categoryName || '-' }}</div>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="单位" align="center" width="100">
+            <template #default="scope">
+              <div class="text-left" style="font-size: 12px;">
+                <div>单位:{{ scope.row.unitName || '-' }}</div>
+                <div>起订量:{{ scope.row.minOrderQuantity || 1 }}</div>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="SKU价格" align="center" width="150">
+            <template #default="scope">
+              <div class="text-left" style="font-size: 12px;">
+                <div>
+                  <span class="text-gray-500">市场价:</span>
+                  <span>¥{{ scope.row.midRangePrice || scope.row.marketPrice || '0.00' }}</span>
+                </div>
+                <div>
+                  <span class="text-gray-500">官网价:</span>
+                  <span class="text-red-500">¥{{ scope.row.standardPrice || scope.row.memberPrice || '0.00' }}</span>
+                </div>
+                <div>
+                  <span class="text-gray-500">最低价:</span>
+                  <span>¥{{ scope.row.certificatePrice || scope.row.minSellingPrice || '0.00' }}</span>
+                </div>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="状态" align="center" width="100">
+            <template #default="scope">
+              <el-tag v-if="isProductAdded(scope.row.id)" type="info">已添加</el-tag>
+              <el-tag v-else-if="scope.row.productStatus === '1'" type="success">上架</el-tag>
+              <el-tag v-else type="warning">下架</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" align="center" width="100" fixed="right">
+            <template #default="scope">
+              <el-link
+                v-if="!isProductAdded(scope.row.id)"
+                type="primary"
+                :underline="false"
+                @click="handleAddSingleProduct(scope.row)"
+              >加入清单</el-link>
+              <span v-else class="text-gray-400">已添加</span>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <!-- 分页 -->
+        <pagination
+          v-show="addProductDialog.productList.length > 0"
+          v-model:page="addProductQuery.pageNum"
+          v-model:limit="addProductQuery.pageSize"
+          v-model:way="addProductQuery.way"
+          :cursor-mode="true"
+          :has-more="addProductHasMore"
+          @pagination="getProductListForAdd"
+        />
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="ProductManage" lang="ts">
+import { listProducts, delProducts, updateProducts, addProducts, getProtocolProductIds, updateProductsPrice, updateProductsAudit } from '@/api/product/products';
+import { ProductsQuery, ProductsForm } from '@/api/product/products/types';
+import { getInfo } from '@/api/product/protocolInfo';
+import { listBrand } from '@/api/product/brand';
+import { BrandVO } from '@/api/product/brand/types';
+import { BaseVO, BaseQuery } from '@/api/product/base/types';
+import { listBase } from '@/api/product/base';
+import { ArrowLeft } from '@element-plus/icons-vue';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const router = useRouter();
+const route = useRoute();
+
+// 协议ID
+const protocolId = ref<string | number>('');
+// 协议编号
+const protocolNo = ref<string>('');
+// 协议信息
+const protocolInfo = ref<any>({
+  customerNo: '',
+  customerName: '',
+  endTime: ''
+});
+
+const productsList = ref<BaseVO[]>([]);
+const brandList = ref<BrandVO[]>([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const total = ref(0);
+
+const queryFormRef = ref<ElFormInstance>();
+
+const queryParams = ref<ProductsQuery>({
+  pageNum: 1,
+  pageSize: 10,
+  protocolId: route.query.protocolId as string | number,
+  protocolNo: undefined,
+  productNo: undefined,
+  productName: undefined,
+  brandName: undefined,
+  productStatus: undefined
+});
+
+// 已关联的产品ID列表
+const addedProductIds = ref<Set<string | number>>(new Set());
+
+// 添加商品对话框
+const addProductDialog = reactive({
+  visible: false,
+  loading: false,
+  productList: [] as BaseVO[],
+  total: 0
+});
+
+// 游标分页相关变量
+const addProductHasMore = ref(true);
+const addProductPageHistory = ref<Array<{ firstId: string | number; lastId: string | number }>>([]);
+
+// 添加商品查询参数
+const addProductQuery = ref<BaseQuery>({
+  pageNum: 1,
+  pageSize: 10,
+  way: undefined,
+  productNo: undefined,
+  itemName: undefined,
+  lastSeenId: undefined
+});
+
+// 选中的商品(添加商品弹窗用)
+const selectedProducts = ref<BaseVO[]>([]);
+const addProductTableRef = ref<any>();
+
+// 列表选中的商品(批量审核用)
+const tableSelectedProducts = ref<BaseVO[]>([]);
+
+// 审核对话框
+const auditDialog = reactive({
+  visible: false
+});
+
+// 审核表单
+const auditFormRef = ref<ElFormInstance>();
+const auditForm = ref({
+  auditStatus: 2,
+  auditReason: '',
+  protocolId: route.query.protocolId as string | number
+});
+
+// 审核表单验证规则
+const auditRules = ref({
+  auditStatus: [{ required: true, message: '请选择审核结果', trigger: 'change' }],
+  auditReason: [{ required: true, message: '请输入审核意见', trigger: 'blur' }]
+});
+
+/** 查询协议商品列表 */
+const getList = async () => {
+  loading.value = true;
+  try {
+    const res = await listProducts(queryParams.value);
+    productsList.value = res.rows || [];
+    total.value = res.total || 0;
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 加载协议信息 */
+const loadProtocolInfo = async () => {
+  if (!protocolId.value) return;
+  const res = await getInfo(protocolId.value);
+  if (res.data) {
+    protocolInfo.value = {
+      customerNo: res.data.customerNo || '',
+      customerName: res.data.customerName || '',
+      endTime: res.data.endTime ? parseTime(res.data.endTime, '{y}/{m}/{d} {h}:{i}:{s}') : ''
+    };
+    // 设置协议编号用于查询
+    protocolNo.value = res.data.protocolNo || '';
+    queryParams.value.protocolNo = res.data.protocolNo;
+  }
+};
+
+/** 加载品牌列表 */
+const loadBrandList = async () => {
+  const res = await listBrand({ pageNum: 1, pageSize: 1000 });
+  brandList.value = res.rows || [];
+};
+
+/** 加载已关联的产品ID列表 */
+const loadAddedProductIds = async () => {
+  const protocolId = route.query.protocolId as string | number;
+  try {
+    const res = await getProtocolProductIds(protocolId);
+    addedProductIds.value = new Set(res.data || []);
+  } catch (error) {
+    console.error('获取已关联产品ID失败:', error);
+    addedProductIds.value = new Set();
+  }
+};
+
+/** 判断商品是否已添加 */
+const isProductAdded = (productId: string | number): boolean => {
+  return addedProductIds.value.has(String(productId)) || addedProductIds.value.has(Number(productId));
+};
+
+/** 检查行是否可选择 */
+const checkSelectable = (row: BaseVO): boolean => {
+  return !isProductAdded(row.id);
+};
+
+/** 格式化时间 */
+const parseTime = (time: any, pattern?: string) => {
+  if (!time) return '';
+  const date = new Date(time);
+  const fmt = pattern || '{y}-{m}-{d} {h}:{i}:{s}';
+  const o: Record<string, number> = {
+    y: date.getFullYear(),
+    m: date.getMonth() + 1,
+    d: date.getDate(),
+    h: date.getHours(),
+    i: date.getMinutes(),
+    s: date.getSeconds()
+  };
+  return fmt.replace(/{([ymdhis])+}/g, (match, key) => {
+    const val = o[key];
+    return val.toString().padStart(2, '0');
+  });
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  // 保留协议编号
+  const pNo = queryParams.value.protocolNo;
+  const protocolId = queryParams.value.protocolId
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10,
+    protocolNo: pNo,
+    protocolId: protocolId || undefined,
+    productNo: undefined,
+    productName: undefined,
+    brandName: undefined,
+    productStatus: undefined
+  };
+  handleQuery();
+};
+
+/** 返回按钮 */
+const handleBack = () => {
+  router.back();
+};
+
+/** 添加商品按钮 */
+const handleAdd = async () => {
+  addProductDialog.visible = true;
+  // 重置查询条件
+  addProductQuery.value = {
+    pageNum: 1,
+    pageSize: 10,
+    way: undefined,
+    productNo: undefined,
+    itemName: undefined,
+    lastSeenId: undefined
+  };
+  // 重置游标分页状态
+  addProductPageHistory.value = [];
+  addProductHasMore.value = true;
+  selectedProducts.value = [];
+  // 先加载已关联的产品ID
+  await loadAddedProductIds();
+  // 再加载商品列表
+  getProductListForAdd();
+};
+
+/** 获取商品列表用于添加 */
+const getProductListForAdd = async () => {
+  addProductDialog.loading = true;
+  try {
+    const params = { ...addProductQuery.value };
+    const currentPageNum = addProductQuery.value.pageNum;
+
+    // 第一页不需要游标参数
+    if (currentPageNum === 1) {
+      delete params.lastSeenId;
+      delete params.firstSeenId;
+      delete params.way;
+    } else {
+      // way参数:0=上一页,1=下一页
+      if (addProductQuery.value.way === 0) {
+        // 上一页:使用目标页的firstId
+        const nextPageHistory = addProductPageHistory.value[currentPageNum];
+        if (nextPageHistory) {
+          params.firstSeenId = nextPageHistory.firstId;
+          params.way = 0;
+        }
+      } else {
+        // 下一页:使用前一页的lastId作为lastSeenId
+        const prevPageHistory = addProductPageHistory.value[currentPageNum - 1];
+        if (prevPageHistory) {
+          params.lastSeenId = prevPageHistory.lastId;
+          params.way = 1;
+        }
+      }
+    }
+
+    const res = await listBase(params);
+    // 兼容两种返回结构
+    if (res.rows) {
+      addProductDialog.productList = res.rows;
+      addProductDialog.total = res.total || 0;
+    } else if (res.data) {
+      addProductDialog.productList = Array.isArray(res.data) ? res.data : [];
+      addProductDialog.total = addProductDialog.productList.length;
+    } else {
+      addProductDialog.productList = [];
+      addProductDialog.total = 0;
+    }
+
+    // 判断是否还有更多数据
+    addProductHasMore.value = addProductDialog.productList.length === addProductQuery.value.pageSize;
+
+    // 记录当前页的第一个id和最后一个id
+    if (addProductDialog.productList.length > 0) {
+      const firstItem = addProductDialog.productList[0];
+      const lastItem = addProductDialog.productList[addProductDialog.productList.length - 1];
+      // 如果长度小于currentPageNum则创建
+      if (addProductPageHistory.value.length <= currentPageNum) {
+        addProductPageHistory.value[currentPageNum] = {
+          firstId: firstItem.id,
+          lastId: lastItem.id
+        };
+      }
+    }
+  } catch (error) {
+    console.error('获取商品列表失败:', error);
+    addProductDialog.productList = [];
+    addProductDialog.total = 0;
+  } finally {
+    addProductDialog.loading = false;
+  }
+};
+
+/** 搜索商品 */
+const handleSearchProducts = () => {
+  addProductQuery.value.pageNum = 1;
+  addProductQuery.value.lastSeenId = undefined;
+  addProductPageHistory.value = []; // 重置页面历史
+  addProductHasMore.value = true;
+  getProductListForAdd();
+};
+
+/** 选择变化(添加商品弹窗用) */
+const handleSelectionChange = (selection: BaseVO[]) => {
+  selectedProducts.value = selection;
+};
+
+/** 表格选择变化(批量审核用) */
+const handleTableSelectionChange = (selection: BaseVO[]) => {
+  tableSelectedProducts.value = selection;
+};
+
+/** 检查行是否可选择(批量审核用,只有待审核状态才能选择) */
+const checkSelectableForAudit = (row: BaseVO): boolean => {
+  return row.auditStatus === 1;
+};
+
+/** 批量审核按钮 */
+const handleBatchAudit = () => {
+  if (tableSelectedProducts.value.length === 0) {
+    proxy?.$modal.msgWarning('请先选择要审核的商品');
+    return;
+  }
+  auditDialog.visible = true;
+  auditForm.value = {
+    protocolId: route.query.protocolId as string | number,
+    auditStatus: 2,
+    auditReason: ''
+  };
+};
+
+/** 提交批量审核 */
+const submitBatchAudit = async () => {
+  await auditFormRef.value?.validate();
+  try {
+    const data = tableSelectedProducts.value.map(item => ({
+      id: item.id,
+      productId: item.id,
+      protocolId: auditForm.value.protocolId,
+      auditStatus: auditForm.value.auditStatus,
+      auditReason: auditForm.value.auditReason
+    }));
+    await updateProductsAudit(data);
+    proxy?.$modal.msgSuccess(auditForm.value.auditStatus === 2 ? '审核通过' : '审核驳回');
+    auditDialog.visible = false;
+    await getList();
+  } catch (error) {
+    console.error('批量审核失败:', error);
+    proxy?.$modal.msgError('审核失败');
+  }
+};
+
+/** 批量加入清单 */
+const handleBatchAdd = async () => {
+  if (selectedProducts.value.length === 0) {
+    proxy?.$modal.msgWarning('请先选择要添加的商品');
+    return;
+  }
+
+  try {
+    // 逐个添加商品
+    for (const product of selectedProducts.value) {
+      const data: ProductsForm = {
+        protocolNo: protocolNo.value,
+        protocolId: route.query.protocolId as string | number,
+        productNo: product.productNo,
+        productId: product.id,
+        agreementPrice: product.standardPrice || product.midRangePrice || product.memberPrice || 0,
+        customerId: protocolInfo.value.customerId,
+        dataSource: product.dataSource
+      };
+      await addProducts(data);
+    }
+
+    proxy?.$modal.msgSuccess(`成功添加 ${selectedProducts.value.length} 个商品`);
+    addProductDialog.visible = false;
+    // 清空选中项
+    selectedProducts.value = [];
+    if (addProductTableRef.value) {
+      addProductTableRef.value.clearSelection();
+    }
+    // 刷新列表
+    await loadAddedProductIds();
+    await getList();
+  } catch (error) {
+    console.error('添加商品失败:', error);
+    proxy?.$modal.msgError('添加商品失败');
+  }
+};
+
+/** 添加单个商品 */
+const handleAddSingleProduct = async (row: BaseVO) => {
+  try {
+    const data: ProductsForm = {
+      protocolNo: protocolNo.value,
+      protocolId: route.query.protocolId as string | number,
+      productNo: row.productNo,
+      productId: row.id,
+      agreementPrice: row.standardPrice || row.midRangePrice || row.memberPrice || 0,
+      customerId: protocolInfo.value.customerId,
+      dataSource: row.dataSource
+    };
+    await addProducts(data);
+
+    proxy?.$modal.msgSuccess('添加成功');
+    // 更新已添加的产品ID列表
+    addedProductIds.value.add(row.id);
+    // 刷新主列表
+    await getList();
+  } catch (error) {
+    console.error('添加商品失败:', error);
+    proxy?.$modal.msgError('添加商品失败');
+  }
+};
+
+/** 导入商品按钮 */
+const handleImport = () => {
+  proxy?.$modal.msgWarning('导入商品功能待实现');
+};
+
+/** 导出商品按钮 */
+const handleExport = () => {
+  proxy?.download('product/products/export', {
+    ...queryParams.value
+  }, `products_${new Date().getTime()}.xlsx`);
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row: any) => {
+  await proxy?.$modal.confirm('是否确认删除该商品?');
+  await delProducts(row.id);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+/** 协议供货价变更 */
+const handlePriceChange = async (row: any) => {
+  try {
+    await updateProductsPrice({
+      agreementPrice: row.agreementPrice,
+      productId: row.id,
+      protocolId: route.query.protocolId as string | number
+    });
+    proxy?.$modal.msgSuccess('价格更新成功');
+  } catch (error) {
+    proxy?.$modal.msgError('价格更新失败');
+  }
+};
+
+/** 计算供货价毛利率 */
+const calculateMargin = (row: any) => {
+  if (!row.agreementPrice || !row.purchasingPrice || row.purchasingPrice === 0) {
+    return '-';
+  }
+  const margin = ((row.agreementPrice - row.purchasingPrice) / row.agreementPrice * 100).toFixed(2)+'%';
+  return margin;
+};
+
+onMounted(() => {
+  // 从路由参数获取协议ID
+  protocolId.value = route.query.protocolId as string || route.params.id as string || '';
+  if (protocolId.value) {
+    loadProtocolInfo();
+    loadBrandList();
+    getList();
+  } else {
+    proxy?.$modal.msgError('缺少协议ID参数');
+    router.back();
+  }
+});
+</script>
+
+<style scoped>
+.text-primary {
+  color: #409eff;
+}
+.text-gray-400 {
+  color: #909399;
+}
+.text-gray-500 {
+  color: #909399;
+}
+.text-red-500 {
+  color: #f56c6c;
+}
+.text-green-500 {
+  color: #67c23a;
+}
+.text-sm {
+  font-size: 12px;
+}
+.add-product-dialog {
+  :deep(.el-form--inline .el-form-item) {
+    margin-right: 10px;
+  }
+}
+</style>

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio