Эх сурвалжийг харах

feat(product): 添加商品管理相关API接口和组件功能

- 新增自营、精品、项目商品列表获取接口
- 添加合同产品关联管理API接口
- 实现产品池审核流程相关接口
- 添加仓库库存明细管理功能
- 集成税收编码选择组件
- 扩展分页组件支持游标分页模式
- 添加图片上传组件及拖拽排序功能
- 优化商品分类查询接口
- 添加批量审核商品功能
- 实现商品池清空操作接口
肖路 2 долоо хоног өмнө
parent
commit
4c45868a50

+ 1 - 0
package.json

@@ -29,6 +29,7 @@
     "axios": "1.8.4",
     "axios": "1.8.4",
     "crypto-js": "4.2.0",
     "crypto-js": "4.2.0",
     "echarts": "5.6.0",
     "echarts": "5.6.0",
+    "element-china-area-data": "^6.1.0",
     "element-plus": "2.9.8",
     "element-plus": "2.9.8",
     "file-saver": "2.0.5",
     "file-saver": "2.0.5",
     "highlight.js": "11.9.0",
     "highlight.js": "11.9.0",

+ 32 - 0
src/api/product/base/index.ts

@@ -209,3 +209,35 @@ export const getTaxRateList = () => {
   });
   });
 };
 };
 
 
+/**
+ * 获取自营商品列表
+ * */
+export const getSelfProductPage = (query?: BaseQuery): AxiosPromise<BaseVO[]> => {
+  return request({
+    url: '/product/base/getSelfProductPage',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 获取精品商品列表
+ * */
+export const getGoodProductPage = (query?: BaseQuery): AxiosPromise<BaseVO[]> => {
+  return request({
+    url: '/product/base/getGoodProductPage',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 获取项目商品列表
+ * */
+export const getProjectProductPage = (query?: BaseQuery): AxiosPromise<BaseVO[]> => {
+  return request({
+    url: '/product/base/getItemProductPage',
+    method: 'get',
+    params: query
+  });
+};

+ 20 - 5
src/api/product/base/types.ts

@@ -96,7 +96,7 @@ export interface BaseVO {
   /**
   /**
    * 商品状态:1=已上架,0=下架,2=上架中
    * 商品状态:1=已上架,0=下架,2=上架中
    */
    */
-  productStatus: string;
+  productStatus: number;
 
 
   /**
   /**
    * 数据来源
    * 数据来源
@@ -109,7 +109,7 @@ export interface BaseVO {
   marketPrice: number;
   marketPrice: number;
 
 
   /**
   /**
-   * 会员价格
+   * 官网价
    */
    */
   memberPrice: number;
   memberPrice: number;
 
 
@@ -179,7 +179,7 @@ export interface BaseVO {
   purchasePrice?: number;
   purchasePrice?: number;
 
 
   /**
   /**
-   * 暂估采购价
+   *最高采购价
    */
    */
   estimatedPurchasePrice?: number;
   estimatedPurchasePrice?: number;
 
 
@@ -248,6 +248,16 @@ export interface BaseVO {
    */
    */
   imageUrl?: string;
   imageUrl?: string;
 
 
+  /**
+   * 审核状态 1=待审核,2=审核通过,3=审核驳回
+   */
+  auditStatus?: number;
+
+  /**
+   * 审核意见
+   */
+  auditReason?: string;
+
 }
 }
 
 
 export interface BaseForm extends BaseEntity {
 export interface BaseForm extends BaseEntity {
@@ -344,7 +354,7 @@ export interface BaseForm extends BaseEntity {
   /**
   /**
    * 商品状态:1=已上架,0=下架,2=上架中
    * 商品状态:1=已上架,0=下架,2=上架中
    */
    */
-  productStatus?: string;
+  productStatus?: string | number;
 
 
   /**
   /**
    * 数据来源
    * 数据来源
@@ -482,7 +492,7 @@ export interface BaseForm extends BaseEntity {
   purchasePrice?: number;
   purchasePrice?: number;
 
 
   /**
   /**
-   * 暂估采购价
+   *最高采购价
    */
    */
   estimatedPurchasePrice?: number;
   estimatedPurchasePrice?: number;
 
 
@@ -726,6 +736,11 @@ export interface BaseQuery extends PageQuery {
      * 日期范围参数
      * 日期范围参数
      */
      */
     params?: any;
     params?: any;
+
+  /**
+   * 商品ID列表(按指定ID集合查询)
+   */
+  ids?: Array<string | number>;
 }
 }
 /**
 /**
  * 状态数量统计视图对象
  * 状态数量统计视图对象

+ 11 - 1
src/api/product/category/index.ts

@@ -65,7 +65,7 @@ export const delCategory = (id: string | number | Array<string | number>) => {
 /**
 /**
  * 查询产品分类列表(排除节点)
  * 查询产品分类列表(排除节点)
  * @param id
  * @param id
-  */
+ */
 export const listCategoryExcludeChild = (id: string | number): AxiosPromise<CategoryVO[]> => {
 export const listCategoryExcludeChild = (id: string | number): AxiosPromise<CategoryVO[]> => {
   return request({
   return request({
     url: '/product/category/list/exclude/' + id,
     url: '/product/category/list/exclude/' + id,
@@ -73,6 +73,16 @@ export const listCategoryExcludeChild = (id: string | number): AxiosPromise<Cate
   });
   });
 };
 };
 
 
+/**
+ * 获取产品分类列表
+ */
+export const getProductCategoryList = () => {
+  return request({
+    url: '/product/category/getProductCategoryList',
+    method: 'get'
+  });
+};
+
 /**
 /**
  * 设置分类审核员
  * 设置分类审核员
  * @param id 分类ID
  * @param id 分类ID

+ 63 - 0
src/api/product/contracproduct/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ContracproductVO, ContracproductForm, ContracproductQuery } from '@/api/product/contracproduct/types';
+
+/**
+ * 查询合同产品关联列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listContracproduct = (query?: ContracproductQuery): AxiosPromise<ContracproductVO[]> => {
+  return request({
+    url: '/product/contracproduct/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询合同产品关联详细
+ * @param id
+ */
+export const getContracproduct = (id: string | number): AxiosPromise<ContracproductVO> => {
+  return request({
+    url: '/product/contracproduct/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增合同产品关联
+ * @param data
+ */
+export const addContracproduct = (data: ContracproductForm) => {
+  return request({
+    url: '/product/contracproduct',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改合同产品关联
+ * @param data
+ */
+export const updateContracproduct = (data: ContracproductForm) => {
+  return request({
+    url: '/product/contracproduct',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除合同产品关联
+ * @param id
+ */
+export const delContracproduct = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/product/contracproduct/' + id,
+    method: 'delete'
+  });
+};

+ 146 - 0
src/api/product/contracproduct/types.ts

@@ -0,0 +1,146 @@
+export interface ContracproductVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 合同供货编号
+   */
+  contractSupplyNo: string;
+
+  /**
+   * 产品编号
+   */
+  productNo: string;
+
+  /**
+   * 产品id
+   */
+  productId: string | number;
+
+  /**
+   * 供货周期(单位:天/月,根据业务定义)
+   */
+  supplyCycle: number;
+
+  /**
+   * 库存属性
+   */
+  inventoryProperties: string;
+
+  /**
+   * 最小供货量
+   */
+  minSupply: number;
+
+  /**
+   * 报价(支持大文本)
+   */
+  offerPrice: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status: string;
+
+}
+
+export interface ContracproductForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 合同供货编号
+   */
+  contractSupplyNo?: string;
+
+  /**
+   * 产品编号
+   */
+  productNo?: string;
+
+  /**
+   * 产品id
+   */
+  productId?: string | number;
+
+  /**
+   * 供货周期(单位:天/月,根据业务定义)
+   */
+  supplyCycle?: number;
+
+  /**
+   * 库存属性
+   */
+  inventoryProperties?: string;
+
+  /**
+   * 最小供货量
+   */
+  minSupply?: number;
+
+  /**
+   * 报价(支持大文本)
+   */
+  offerPrice?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+}
+
+export interface ContracproductQuery extends PageQuery {
+
+  /**
+   * 合同供货编号
+   */
+  contractSupplyNo?: string;
+
+  /**
+   * 产品编号
+   */
+  productNo?: string;
+
+  /**
+   * 产品id
+   */
+  productId?: string | number;
+
+  /**
+   * 供货周期(单位:天/月,根据业务定义)
+   */
+  supplyCycle?: number;
+
+  /**
+   * 库存属性
+   */
+  inventoryProperties?: string;
+
+  /**
+   * 最小供货量
+   */
+  minSupply?: number;
+
+  /**
+   * 报价(支持大文本)
+   */
+  offerPrice?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 63 - 0
src/api/product/contractproduct/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ContracproductVO, ContracproductForm, ContracproductQuery } from '@/api/product/contractproduct/types';
+
+/**
+ * 查询合同产品关联列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listContracproduct = (query?: ContracproductQuery): AxiosPromise<ContracproductVO[]> => {
+  return request({
+    url: '/product/contracproduct/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询合同产品关联详细
+ * @param id
+ */
+export const getContracproduct = (id: string | number): AxiosPromise<ContracproductVO> => {
+  return request({
+    url: '/product/contracproduct/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增合同产品关联
+ * @param data
+ */
+export const addContracproduct = (data: ContracproductForm) => {
+  return request({
+    url: '/product/contracproduct',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改合同产品关联
+ * @param data
+ */
+export const updateContracproduct = (data: ContracproductForm) => {
+  return request({
+    url: '/product/contracproduct',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除合同产品关联
+ * @param id
+ */
+export const delContracproduct = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/product/contracproduct/' + id,
+    method: 'delete'
+  });
+};

+ 146 - 0
src/api/product/contractproduct/types.ts

@@ -0,0 +1,146 @@
+export interface ContracproductVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 合同供货编号
+   */
+  contractSupplyNo: string;
+
+  /**
+   * 产品编号
+   */
+  productNo: string;
+
+  /**
+   * 产品id
+   */
+  productId: string | number;
+
+  /**
+   * 供货周期(单位:天/月,根据业务定义)
+   */
+  supplyCycle: number;
+
+  /**
+   * 库存属性
+   */
+  inventoryProperties: string;
+
+  /**
+   * 最小供货量
+   */
+  minSupply: number;
+
+  /**
+   * 报价(支持大文本)
+   */
+  offerPrice: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status: string;
+
+}
+
+export interface ContracproductForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 合同供货编号
+   */
+  contractSupplyNo?: string;
+
+  /**
+   * 产品编号
+   */
+  productNo?: string;
+
+  /**
+   * 产品id
+   */
+  productId?: string | number;
+
+  /**
+   * 供货周期(单位:天/月,根据业务定义)
+   */
+  supplyCycle?: number;
+
+  /**
+   * 库存属性
+   */
+  inventoryProperties?: string;
+
+  /**
+   * 最小供货量
+   */
+  minSupply?: number;
+
+  /**
+   * 报价(支持大文本)
+   */
+  offerPrice?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+}
+
+export interface ContracproductQuery extends PageQuery {
+
+  /**
+   * 合同供货编号
+   */
+  contractSupplyNo?: string;
+
+  /**
+   * 产品编号
+   */
+  productNo?: string;
+
+  /**
+   * 产品id
+   */
+  productId?: string | number;
+
+  /**
+   * 供货周期(单位:天/月,根据业务定义)
+   */
+  supplyCycle?: number;
+
+  /**
+   * 库存属性
+   */
+  inventoryProperties?: string;
+
+  /**
+   * 最小供货量
+   */
+  minSupply?: number;
+
+  /**
+   * 报价(支持大文本)
+   */
+  offerPrice?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 3 - 3
src/api/product/pool/types.ts

@@ -20,7 +20,7 @@ export interface PoolVO {
   categoryId: string | number;
   categoryId: string | number;
 
 
   /**
   /**
-   * 产品池类型 0普通产品池,1精选产品池,2协议产品池,3项目产品池,4分类产品池
+   * 产品池类型 0自营产品池,1精选产品池,2协议产品池,3项目产品池,4分类产品池
    */
    */
   type: number;
   type: number;
 
 
@@ -113,7 +113,7 @@ export interface PoolForm extends BaseEntity {
   categoryId: string | number;
   categoryId: string | number;
 
 
   /**
   /**
-   * 产品池类型 0普通产品池,1精选产品池,2协议产品池,3项目产品池,4分类产品池
+   * 产品池类型 0自营产品池,1精选产品池,2协议产品池,3项目产品池,4分类产品池
    */
    */
   type: number;
   type: number;
 
 
@@ -162,7 +162,7 @@ export interface PoolQuery extends PageQuery {
   categoryId: string | number;
   categoryId: string | number;
 
 
   /**
   /**
-   * 产品池类型 0普通产品池,1精选产品池,2协议产品池,3项目产品池,4分类产品池
+   * 产品池类型 0自营产品池,1精选产品池,2协议产品池,3项目产品池,4分类产品池
    */
    */
   type: number;
   type: number;
 
 

+ 120 - 0
src/api/product/poolAudit/index.ts

@@ -0,0 +1,120 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { PoolAuditVO, PoolAuditForm, PoolAuditQuery } from '@/api/product/poolAudit/types';
+import { BaseVO, BaseQuery } from '@/api/product/base/types';
+
+/**
+ * 查询产品池审核列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listPoolAudit = (query?: PoolAuditQuery): AxiosPromise<PoolAuditVO[]> => {
+  return request({
+    url: '/product/poolAudit/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询产品池审核详细
+ * @param id
+ */
+export const getPoolAudit = (id: string | number): AxiosPromise<PoolAuditVO> => {
+  return request({
+    url: '/product/poolAudit/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增产品池审核
+ * @param data
+ */
+export const addPoolAudit = (data: PoolAuditForm) => {
+  return request({
+    url: '/product/poolAudit',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改产品池审核
+ * @param data
+ */
+export const updatePoolAudit = (data: PoolAuditForm) => {
+  return request({
+    url: '/product/poolAudit',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除产品池审核
+ * @param id
+ */
+export const delPoolAudit = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/product/poolAudit/' + id,
+    method: 'delete'
+  });
+};
+
+/**
+ * 获取审核池里的商品列表
+ * @param query
+ * @returns {*}
+ * */
+export const getPoolAuditProductPage = (query?: BaseQuery & PageQuery): AxiosPromise<BaseVO[]> => {
+  return request({
+    url: '/product/poolAudit/getPoolAuditProductPage',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 批量审核产品池商品
+ * @param data
+ */
+export interface PoolAuditBatchData {
+  poolAuditId: string | number;
+  productIds: Array<string | number>;
+  auditStatus: string | number;
+  reason?: string;
+}
+
+export const batchAudit = (data: PoolAuditBatchData) => {
+  return request({
+    url: '/product/poolAudit/batchAudit',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 申请入池单(待提交 -> 待审核)
+ * @param id
+ */
+export const applyPoolAudit = (id: string | number) => {
+  return request({
+    url: '/product/poolAudit/apply/' + id,
+    method: 'put'
+  });
+};
+
+/**
+ * 清空审核池
+ * @param id
+ */
+export const clearPool = (id: string | number) => {
+  return request({
+    url: '/product/poolAudit/clearPool/' + id,
+    method: 'delete'
+  });
+};
+
+

+ 265 - 0
src/api/product/poolAudit/types.ts

@@ -0,0 +1,265 @@
+export interface PoolAuditVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 池名称
+   */
+  name: string;
+
+  /**
+   * 池id
+   */
+  poolId: string | number;
+
+  /**
+   * 项目id
+   */
+  itemId: string | number;
+
+  /**
+   * 产品池类型 0自营产品池,1精选产品池,2协议产品池,3项目产品池,4营销产品池
+   */
+  type: string;
+
+  /**
+   * 协议id
+   */
+  protocolId: string | number;
+
+  /**
+   * 客户id
+   */
+  customerId: string | number;
+
+  /**
+   * 产品审核状态 0=待提交,1=待审核,2=审核通过,3=审核驳回
+   */
+  productReviewStatus: string;
+
+  /**
+   * 审核原因
+   */
+  reviewReason: string;
+
+  /**
+   * 创建人名称
+   */
+  createByName?: string;
+
+  /**
+   * 审核人名称
+   */
+  auditByName?: string;
+
+  /**
+   * 项目/协议/自营产品池名称(根据类型回显)
+   */
+  poolName?: string;
+
+  /**
+   * 审核人 ID
+   */
+  auditUserId?: string | number;
+
+  /**
+   * 创建人 ID
+   */
+  createBy?: string | number;
+
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface PoolAuditForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 池名称
+   */
+  name?: string;
+
+  /**
+   * 池id
+   */
+  poolId?: string | number;
+
+  /**
+   * 项目id
+   */
+  itemId?: string | number;
+
+  /**
+   * 产品池类型 0自营产品池,1精选产品池,2协议产品池,3项目产品池,4营销产品池
+   */
+  type?: string | number;
+
+  /**
+   * 协议id
+   */
+  protocolId?: string | number;
+
+  /**
+   * 客户id
+   */
+  customerId?: string | number;
+
+  /**
+   * 产品审核状态 0=待提交,1=待审核,2=审核通过,3=审核驳回
+   */
+  productReviewStatus?: string;
+
+  /**
+   * 审核原因
+   */
+  reviewReason?: string;
+
+  /**
+   * 创建人名称
+   */
+  createByName?: string;
+
+  /**
+   * 审核人名称
+   */
+  auditByName?: string;
+
+  /**
+   * 项目/协议/自营产品池名称(根据类型回显)
+   */
+  poolName?: string;
+
+  /**
+   * 审核人 ID
+   */
+  auditUserId?: string | number;
+
+  /**
+   * 创建人 ID
+   */
+  createBy?: string | number;
+
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+  /**
+   * 附加信息
+   */
+  additional?: string;
+
+  /**
+   * 附件(ossId逗号分隔)
+   */
+  attachment?: string;
+
+  /**
+   * 商品ID列表
+   */
+  productIds?: Array<string | number>;
+
+  /**
+   * 商品列表(含协议价,提交时使用)
+   */
+  products?: Array<{
+    productId: string | number;
+    agreementPrice?: number;
+  }>;
+
+}
+
+export interface PoolAuditQuery extends PageQuery {
+  /**
+   * 搜索文本(池名称)
+   */
+  searchText?: string;
+
+  /**
+   * 池名称
+   */
+  name?: string;
+
+  /**
+   * 池 id
+   */
+  poolId?: string | number;
+
+  /**
+   * 项目 id
+   */
+  itemId?: string | number;
+
+  /**
+   * 产品池类型 0 自营产品池,1 精选产品池,2 协议产品池,3 项目产品池,4 营销产品池
+   */
+  type?: string | number;
+
+  /**
+   * 协议 id
+   */
+  protocolId?: string | number;
+
+  /**
+   * 客户 id
+   */
+  customerId?: string | number;
+
+  /**
+   * 产品审核状态 0=待提交,1=待审核,2=审核通过,3=审核驳回
+   */
+  productReviewStatus?: string | number;
+
+  /**
+   * 审核原因
+   */
+  reviewReason?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+  /**
+   * 创建人名称
+   */
+  createByName?: string;
+
+  /**
+   * 审核人名称
+   */
+  auditByName?: string;
+
+  /**
+   * 项目/协议/自营产品池名称(根据类型回显)
+   */
+  poolName?: string;
+
+  /**
+   * 审核人 ID
+   */
+  auditUserId?: string | number;
+
+  /**
+   * 创建人 ID
+   */
+  createBy?: string | number;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}
+
+
+

+ 11 - 0
src/api/product/poolLink/index.ts

@@ -130,3 +130,14 @@ export const editStock = (data: PoolLinkForm) => {
     data: data
     data: data
   });
   });
 };
 };
+
+/**
+ * 清空产品池
+ * @param poolId 产品池ID
+ */
+export const clearPool = (poolId: string | number) => {
+  return request({
+    url: '/product/poolLink/clear/' + poolId,
+    method: 'delete'
+  });
+};

+ 19 - 4
src/api/product/poolLink/types.ts

@@ -15,7 +15,7 @@ export interface PoolLinkVO {
   poolName?: string;
   poolName?: string;
 
 
   /**
   /**
-   * 产品池类型 0普通产品池,1精选产品池,2协议产品池,3项目产品池,4分类产品池
+   * 产品池类型 0自营产品池,1精选产品池,2协议产品池,3项目产品池,4分类产品池
    */
    */
   type?: number;
   type?: number;
 
 
@@ -100,7 +100,7 @@ export interface PoolLinkVO {
   marketPrice?: number;
   marketPrice?: number;
 
 
   /**
   /**
-   * 平台售
+   * 官网
    */
    */
   platformPrice?: number;
   platformPrice?: number;
 
 
@@ -128,6 +128,16 @@ export interface PoolLinkVO {
    * 供应商
    * 供应商
    */
    */
   supplier?: string;
   supplier?: string;
+
+  /**
+   * 协议价格
+   */
+  negotiatedPrice?: number;
+
+  /**
+   * 审核池商品关联ID(用于移除)
+   */
+  poolAuditProductId?: string | number;
 }
 }
 
 
 export interface PoolLinkForm extends BaseEntity {
 export interface PoolLinkForm extends BaseEntity {
@@ -243,9 +253,14 @@ export interface PoolLinkQuery extends PageQuery {
   brandId?: string | number;
   brandId?: string | number;
 
 
   /**
   /**
-   * 分类ID
+   * 品牌名称
+   */
+  brandName?: string;
+
+  /**
+   * 底部分类ID
    */
    */
-  categoryId?: string | number;
+  bottomCategoryId?: string | number;
 
 
   /**
   /**
    * 商品状态:1=已上架,0=下架,2=上架中
    * 商品状态:1=已上架,0=下架,2=上架中

+ 83 - 0
src/api/product/poolLinkAudit/index.ts

@@ -0,0 +1,83 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { PoolLinkAuditVO, PoolLinkAuditForm, PoolLinkAuditQuery } from '@/api/product/poolLinkAudit/types';
+
+/**
+ * 查询产品池和产品关联审核列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listPoolLinkAudit = (query?: PoolLinkAuditQuery): AxiosPromise<PoolLinkAuditVO[]> => {
+  return request({
+    url: '/product/poolLinkAudit/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询产品池和产品关联审核详细
+ * @param id
+ */
+export const getPoolLinkAudit = (id: string | number): AxiosPromise<PoolLinkAuditVO> => {
+  return request({
+    url: '/product/poolLinkAudit/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增产品池和产品关联审核
+ * @param data
+ */
+export const addPoolLinkAudit = (data: PoolLinkAuditForm) => {
+  return request({
+    url: '/product/poolLinkAudit',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改产品池和产品关联审核
+ * @param data
+ */
+export const updatePoolLinkAudit = (data: PoolLinkAuditForm) => {
+  return request({
+    url: '/product/poolLinkAudit',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除产品池和产品关联审核
+ * @param id
+ */
+export const delPoolLinkAudit = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/product/poolLinkAudit/' + id,
+    method: 'delete'
+  });
+};
+
+/**
+ * 批量添加商品到商品池
+ * @param data 批量添加参数
+ */
+export interface BatchAddProductData {
+  poolId: string | number;
+  products: Array<{
+    productId: string | number;
+    agreementPrice?: number;
+  }>;
+}
+
+export const batchAddProducts = (data: BatchAddProductData) => {
+  return request({
+    url: '/product/poolLinkAudit/batchAdd',
+    method: 'post',
+    data: data
+  });
+};

+ 176 - 0
src/api/product/poolLinkAudit/types.ts

@@ -0,0 +1,176 @@
+export interface PoolLinkAuditVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 所属池ID
+   */
+  poolId: string | number;
+
+  /**
+   * 审核池id
+   */
+  poolAuditId: string | number;
+
+  /**
+   * 产品id
+   */
+  productId: string | number;
+
+  /**
+   * 产品价格
+   */
+  productPrice: number;
+
+  /**
+   * 协议价格
+   */
+  negotiatedPrice: number;
+
+  /**
+   * 分类id
+   */
+  categoryId: string | number;
+
+  /**
+   * 产品审核状态 0=待提交,1=待审核,2=审核通过,3=审核驳回
+   */
+  productReviewStatus: string;
+
+  /**
+   * 审核原因
+   */
+  reviewReason: string;
+
+  /**
+   * 审核人
+   */
+  reviewer: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface PoolLinkAuditForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 所属池ID
+   */
+  poolId?: string | number;
+
+  /**
+   * 审核池id
+   */
+  poolAuditId?: string | number;
+
+  /**
+   * 产品id
+   */
+  productId?: string | number;
+
+  /**
+   * 产品价格
+   */
+  productPrice?: number;
+
+  /**
+   * 协议价格
+   */
+  negotiatedPrice?: number;
+
+  /**
+   * 分类id
+   */
+  categoryId?: string | number;
+
+  /**
+   * 产品审核状态 0=待提交,1=待审核,2=审核通过,3=审核驳回
+   */
+  productReviewStatus?: string;
+
+  /**
+   * 审核原因
+   */
+  reviewReason?: string;
+
+  /**
+   * 审核人
+   */
+  reviewer?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface PoolLinkAuditQuery extends PageQuery {
+
+  /**
+   * 所属池ID
+   */
+  poolId?: string | number;
+
+  /**
+   * 审核池id
+   */
+  poolAuditId?: string | number;
+
+  /**
+   * 产品id
+   */
+  productId?: string | number;
+
+  /**
+   * 产品价格
+   */
+  productPrice?: number;
+
+  /**
+   * 协议价格
+   */
+  negotiatedPrice?: number;
+
+  /**
+   * 分类id
+   */
+  categoryId?: string | number;
+
+  /**
+   * 产品审核状态 0=待提交,1=待审核,2=审核通过,3=审核驳回
+   */
+  productReviewStatus?: string;
+
+  /**
+   * 审核原因
+   */
+  reviewReason?: string;
+
+  /**
+   * 审核人
+   */
+  reviewer?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 3 - 3
src/api/product/priceInventory/types.ts

@@ -10,7 +10,7 @@ export interface PriceInventoryVO {
   marketPrice: number;
   marketPrice: number;
 
 
   /**
   /**
-   * 会员价格
+   * 官网价
    */
    */
   memberPrice: number;
   memberPrice: number;
 
 
@@ -78,7 +78,7 @@ export interface PriceInventoryForm extends BaseEntity {
   marketPrice?: number;
   marketPrice?: number;
 
 
   /**
   /**
-   * 会员价格
+   * 官网价
    */
    */
   memberPrice?: number;
   memberPrice?: number;
 
 
@@ -142,7 +142,7 @@ export interface PriceInventoryQuery extends PageQuery {
   marketPrice?: number;
   marketPrice?: number;
 
 
   /**
   /**
-   * 会员价格
+   * 官网价
    */
    */
   memberPrice?: number;
   memberPrice?: number;
 
 

+ 63 - 0
src/api/product/productExquisite/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ProductExquisiteVO, ProductExquisiteForm, ProductExquisiteQuery } from '@/api/product/productExquisite/types';
+
+/**
+ * 查询精品商品池列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listProductExquisite = (query?: ProductExquisiteQuery): AxiosPromise<ProductExquisiteVO[]> => {
+  return request({
+    url: '/product/productExquisite/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询精品商品池详细
+ * @param productId
+ */
+export const getProductExquisite = (productId: string | number): AxiosPromise<ProductExquisiteVO> => {
+  return request({
+    url: '/product/productExquisite/' + productId,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增精品商品池
+ * @param data
+ */
+export const addProductExquisite = (data: ProductExquisiteForm) => {
+  return request({
+    url: '/product/productExquisite',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改精品商品池
+ * @param data
+ */
+export const updateProductExquisite = (data: ProductExquisiteForm) => {
+  return request({
+    url: '/product/productExquisite',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除精品商品池
+ * @param productId
+ */
+export const delProductExquisite = (productId: string | number | Array<string | number>) => {
+  return request({
+    url: '/product/productExquisite/' + productId,
+    method: 'delete'
+  });
+};

+ 71 - 0
src/api/product/productExquisite/types.ts

@@ -0,0 +1,71 @@
+export interface ProductExquisiteVO {
+  /**
+   * 
+   */
+  productId: string | number;
+
+  /**
+   * 产品审核状态:0=待提交,1=待审核,2=审核通过,3=审核驳回
+   */
+  auditStatus: number;
+
+  /**
+   * 审核意见
+   */
+  auditReason: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface ProductExquisiteForm extends BaseEntity {
+  /**
+   * 
+   */
+  productId?: string | number;
+
+  /**
+   * 产品审核状态:0=待提交,1=待审核,2=审核通过,3=审核驳回
+   */
+  auditStatus?: number;
+
+  /**
+   * 审核意见
+   */
+  auditReason?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface ProductExquisiteQuery extends PageQuery {
+
+  /**
+   * 产品审核状态:0=待提交,1=待审核,2=审核通过,3=审核驳回
+   */
+  auditStatus?: number;
+
+  /**
+   * 审核意见
+   */
+  auditReason?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 63 - 0
src/api/product/productSelf/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ProductSelfVO, ProductSelfForm, ProductSelfQuery } from '@/api/product/productSelf/types';
+
+/**
+ * 查询自营商品池列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listProductSelf = (query?: ProductSelfQuery): AxiosPromise<ProductSelfVO[]> => {
+  return request({
+    url: '/product/productSelf/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询自营商品池详细
+ * @param productId
+ */
+export const getProductSelf = (productId: string | number): AxiosPromise<ProductSelfVO> => {
+  return request({
+    url: '/product/productSelf/' + productId,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增自营商品池
+ * @param data
+ */
+export const addProductSelf = (data: ProductSelfForm) => {
+  return request({
+    url: '/product/productSelf',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改自营商品池
+ * @param data
+ */
+export const updateProductSelf = (data: ProductSelfForm) => {
+  return request({
+    url: '/product/productSelf',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除自营商品池
+ * @param productId
+ */
+export const delProductSelf = (productId: string | number | Array<string | number>) => {
+  return request({
+    url: '/product/productSelf/' + productId,
+    method: 'delete'
+  });
+};

+ 71 - 0
src/api/product/productSelf/types.ts

@@ -0,0 +1,71 @@
+export interface ProductSelfVO {
+  /**
+   * 
+   */
+  productId: string | number;
+
+  /**
+   * 产品审核状态:0=待提交,1=待审核,2=审核通过,3=审核驳回
+   */
+  auditStatus: number;
+
+  /**
+   * 审核意见
+   */
+  auditReason: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface ProductSelfForm extends BaseEntity {
+  /**
+   * 
+   */
+  productId?: string | number;
+
+  /**
+   * 产品审核状态:0=待提交,1=待审核,2=审核通过,3=审核驳回
+   */
+  auditStatus?: number;
+
+  /**
+   * 审核意见
+   */
+  auditReason?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface ProductSelfQuery extends PageQuery {
+
+  /**
+   * 产品审核状态:0=待提交,1=待审核,2=审核通过,3=审核驳回
+   */
+  auditStatus?: number;
+
+  /**
+   * 审核意见
+   */
+  auditReason?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 13 - 1
src/api/product/products/index.ts

@@ -1,6 +1,6 @@
 import request from '@/utils/request';
 import request from '@/utils/request';
 import { AxiosPromise } from 'axios';
 import { AxiosPromise } from 'axios';
-import { ProductsVO, ProductsForm, ProductsQuery } from '@/api/product/protocolProducts/types';
+import { ProductsVO, ProductsForm, ProductsQuery } from '@/api/product/products/types';
 import { BaseQuery, BaseVO } from '@/api/product/base/types';
 import { BaseQuery, BaseVO } from '@/api/product/base/types';
 
 
 /**
 /**
@@ -88,3 +88,15 @@ export const updateProductsPrice = (data: ProductsForm) => {
     data: data
     data: data
   })
   })
 }
 }
+
+/**
+ * 批量审核协议商品
+ * @param data 审核参数列表
+ */
+export const updateProductsAudit = (data: { id: string | number; auditStatus: number; auditReason: string }[]) => {
+  return request({
+    url: '/product/protocolProducts/batchAudit',
+    method: 'post',
+    data: data
+  })
+}

+ 8 - 0
src/api/product/products/types.ts

@@ -170,6 +170,10 @@ export interface ProductsQuery extends PageQuery {
    * 商品品牌id
    * 商品品牌id
    */
    */
   brandId?: string | number;
   brandId?: string | number;
+  /**
+   * 商品品牌名称
+   */
+  brandName?: string;
   /**
   /**
    * 商品状态 0-下架 1-上架
    * 商品状态 0-下架 1-上架
    */
    */
@@ -178,6 +182,10 @@ export interface ProductsQuery extends PageQuery {
    * 商品id
    * 商品id
    */
    */
   productId?: string | number;
   productId?: string | number;
+  /**
+   * 审核状态 1=待审核,2=审核通过,3=审核驳回
+   */
+  auditStatus?: number;
 
 
     /**
     /**
    * 日期范围参数
    * 日期范围参数

+ 63 - 0
src/api/product/warehouseInventory/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { WarehouseInventoryVO, WarehouseInventoryForm, WarehouseInventoryQuery } from '@/api/product/warehouseInventory/types';
+
+/**
+ * 查询仓库库存明细列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listWarehouseInventory = (query?: WarehouseInventoryQuery): AxiosPromise<WarehouseInventoryVO[]> => {
+  return request({
+    url: '/product/warehouseInventory/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询仓库库存明细详细
+ * @param id
+ */
+export const getWarehouseInventory = (id: string | number): AxiosPromise<WarehouseInventoryVO> => {
+  return request({
+    url: '/product/warehouseInventory/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增仓库库存明细
+ * @param data
+ */
+export const addWarehouseInventory = (data: WarehouseInventoryForm) => {
+  return request({
+    url: '/product/warehouseInventory',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改仓库库存明细
+ * @param data
+ */
+export const updateWarehouseInventory = (data: WarehouseInventoryForm) => {
+  return request({
+    url: '/product/warehouseInventory',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除仓库库存明细
+ * @param id
+ */
+export const delWarehouseInventory = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/product/warehouseInventory/' + id,
+    method: 'delete'
+  });
+};

+ 176 - 0
src/api/product/warehouseInventory/types.ts

@@ -0,0 +1,176 @@
+export interface WarehouseInventoryVO {
+  /**
+   * ID
+   */
+  id: string | number;
+
+  /**
+   * 商品ID
+   */
+  productId: string | number;
+
+  /**
+   * SKU ID
+   */
+  skuId: string | number;
+
+  /**
+   * 规格型号
+   */
+  specModel: string;
+
+  /**
+   * 仓库ID
+   */
+  warehouseId: string | number;
+
+  /**
+   * 仓库编号
+   */
+  warehouseNo: string;
+
+  /**
+   * 仓库名称
+   */
+  warehouseName: string;
+
+  /**
+   * 可用库存
+   */
+  nowInventory: number;
+
+  /**
+   * 锁定库存
+   */
+  lockInventory: number;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface WarehouseInventoryForm extends BaseEntity {
+  /**
+   * ID
+   */
+  id?: string | number;
+
+  /**
+   * 商品ID
+   */
+  productId?: string | number;
+
+  /**
+   * SKU ID
+   */
+  skuId?: string | number;
+
+  /**
+   * 规格型号
+   */
+  specModel?: string;
+
+  /**
+   * 仓库ID
+   */
+  warehouseId?: string | number;
+
+  /**
+   * 仓库编号
+   */
+  warehouseNo?: string;
+
+  /**
+   * 仓库名称
+   */
+  warehouseName?: string;
+
+  /**
+   * 可用库存
+   */
+  nowInventory?: number;
+
+  /**
+   * 锁定库存
+   */
+  lockInventory?: number;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface WarehouseInventoryQuery extends PageQuery {
+
+  /**
+   * 商品ID
+   */
+  productId?: string | number;
+
+  /**
+   * SKU ID
+   */
+  skuId?: string | number;
+
+  /**
+   * 规格型号
+   */
+  specModel?: string;
+
+  /**
+   * 仓库ID
+   */
+  warehouseId?: string | number;
+
+  /**
+   * 仓库编号
+   */
+  warehouseNo?: string;
+
+  /**
+   * 仓库名称
+   */
+  warehouseName?: string;
+
+  /**
+   * 可用库存
+   */
+  nowInventory?: number;
+
+  /**
+   * 锁定库存
+   */
+  lockInventory?: number;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 63 - 0
src/api/system/taxCode/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { TaxCodeVO, TaxCodeForm, TaxCodeQuery } from '@/api/system/taxCode/types';
+
+/**
+ * 查询税收编码列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listTaxCode = (query?: TaxCodeQuery): AxiosPromise<TaxCodeVO[]> => {
+  return request({
+    url: '/system/taxCode/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询税收编码详细
+ * @param id
+ */
+export const getTaxCode = (id: string | number): AxiosPromise<TaxCodeVO> => {
+  return request({
+    url: '/system/taxCode/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增税收编码
+ * @param data
+ */
+export const addTaxCode = (data: TaxCodeForm) => {
+  return request({
+    url: '/system/taxCode',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改税收编码
+ * @param data
+ */
+export const updateTaxCode = (data: TaxCodeForm) => {
+  return request({
+    url: '/system/taxCode',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除税收编码
+ * @param id
+ */
+export const delTaxCode = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/system/taxCode/' + id,
+    method: 'delete'
+  });
+};

+ 161 - 0
src/api/system/taxCode/types.ts

@@ -0,0 +1,161 @@
+export interface TaxCodeVO {
+  /**
+   * 
+   */
+  id: string | number;
+
+  /**
+   * 父级id
+   */
+  parentId: string | number;
+
+  /**
+   * 货物和劳务名称
+   */
+  name: string;
+
+  /**
+   * 商品和服务分类简称
+   */
+  abbreviation: string;
+
+  /**
+   * 税收编码
+   */
+  taxationNo: string;
+
+  /**
+   * 合并编码
+   */
+  mergeNo: string;
+
+  /**
+   * 上级编码
+   */
+  parentNo: string;
+
+  /**
+   * 祖级列表
+   */
+  ancestors: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface TaxCodeForm extends BaseEntity {
+  /**
+   * 
+   */
+  id?: string | number;
+
+  /**
+   * 父级id
+   */
+  parentId?: string | number;
+
+  /**
+   * 货物和劳务名称
+   */
+  name?: string;
+
+  /**
+   * 商品和服务分类简称
+   */
+  abbreviation?: string;
+
+  /**
+   * 税收编码
+   */
+  taxationNo?: string;
+
+  /**
+   * 合并编码
+   */
+  mergeNo?: string;
+
+  /**
+   * 上级编码
+   */
+  parentNo?: string;
+
+  /**
+   * 祖级列表
+   */
+  ancestors?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface TaxCodeQuery extends PageQuery {
+
+  /**
+   * 父级id
+   */
+  parentId?: string | number;
+
+  /**
+   * 货物和劳务名称
+   */
+  name?: string;
+
+  /**
+   * 商品和服务分类简称
+   */
+  abbreviation?: string;
+
+  /**
+   * 税收编码
+   */
+  taxationNo?: string;
+
+  /**
+   * 合并编码
+   */
+  mergeNo?: string;
+
+  /**
+   * 上级编码
+   */
+  parentNo?: string;
+
+  /**
+   * 祖级列表
+   */
+  ancestors?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 74 - 3
src/components/Pagination/index.vue

@@ -1,6 +1,33 @@
 <template>
 <template>
   <div :class="{ hidden: hidden }" class="pagination-container">
   <div :class="{ hidden: hidden }" class="pagination-container">
+    <!-- 游标分页模式 -->
+    <div v-if="cursorMode" class="cursor-pagination">
+      <el-button
+        :disabled="currentPage === 1"
+        @click="handlePrevPage"
+      >
+        上一页
+      </el-button>
+      <span class="page-info">第 {{ currentPage }} 页</span>
+      <el-button
+        :disabled="!hasMore"
+        @click="handleNextPage"
+      >
+        下一页
+      </el-button>
+      <el-select v-model="pageSize" @change="handleSizeChange" style="width: 100px" class="ml-2">
+        <el-option
+          v-for="size in pageSizes"
+          :key="size"
+          :label="`${size}条/页`"
+          :value="size"
+        />
+      </el-select>
+    </div>
+
+    <!-- 传统分页模式 -->
     <el-pagination
     <el-pagination
+      v-else
       v-model:current-page="currentPage"
       v-model:current-page="currentPage"
       v-model:page-size="pageSize"
       v-model:page-size="pageSize"
       :background="background"
       :background="background"
@@ -22,6 +49,7 @@ const props = defineProps({
   total: propTypes.number,
   total: propTypes.number,
   page: propTypes.number.def(1),
   page: propTypes.number.def(1),
   limit: propTypes.number.def(20),
   limit: propTypes.number.def(20),
+  way: propTypes.number.def(1),
   pageSizes: { type: Array<number>, default: () => [10, 20, 30, 50] },
   pageSizes: { type: Array<number>, default: () => [10, 20, 30, 50] },
   // 移动端页码按钮的数量端默认值5
   // 移动端页码按钮的数量端默认值5
   pagerCount: propTypes.number.def(document.body.clientWidth < 992 ? 5 : 7),
   pagerCount: propTypes.number.def(document.body.clientWidth < 992 ? 5 : 7),
@@ -29,10 +57,14 @@ const props = defineProps({
   background: propTypes.bool.def(true),
   background: propTypes.bool.def(true),
   autoScroll: propTypes.bool.def(true),
   autoScroll: propTypes.bool.def(true),
   hidden: propTypes.bool.def(false),
   hidden: propTypes.bool.def(false),
-  float: propTypes.string.def('right')
+  float: propTypes.string.def('right'),
+  // 游标分页模式
+  cursorMode: propTypes.bool.def(false),
+  // 是否还有更多数据(游标分页使用)
+  hasMore: propTypes.bool.def(true)
 });
 });
 
 
-const emit = defineEmits(['update:page', 'update:limit', 'pagination']);
+const emit = defineEmits(['update:page', 'update:limit', 'update:way', 'pagination']);
 const currentPage = computed({
 const currentPage = computed({
   get() {
   get() {
     return props.page;
     return props.page;
@@ -50,7 +82,7 @@ const pageSize = computed({
   }
   }
 });
 });
 function handleSizeChange(val: number) {
 function handleSizeChange(val: number) {
-  if (currentPage.value * val > props.total) {
+  if (!props.cursorMode && currentPage.value * val > props.total) {
     currentPage.value = 1;
     currentPage.value = 1;
   }
   }
   emit('pagination', { page: currentPage.value, limit: val });
   emit('pagination', { page: currentPage.value, limit: val });
@@ -64,6 +96,28 @@ function handleCurrentChange(val: number) {
     scrollTo(0, 800);
     scrollTo(0, 800);
   }
   }
 }
 }
+// 游标分页:下一页
+function handleNextPage() {
+  if (props.hasMore) {
+    currentPage.value += 1;
+    emit('update:way', 1);
+    emit('pagination', { page: currentPage.value, limit: pageSize.value, way: 1 });
+    if (props.autoScroll) {
+      scrollTo(0, 800);
+    }
+  }
+}
+// 游标分页:上一页
+function handlePrevPage() {
+  if (currentPage.value > 1) {
+    currentPage.value -= 1;
+    emit('update:way', 0);
+    emit('pagination', { page: currentPage.value, limit: pageSize.value, way: 0 });
+    if (props.autoScroll) {
+      scrollTo(0, 800);
+    }
+  }
+}
 </script>
 </script>
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>
@@ -71,6 +125,23 @@ function handleCurrentChange(val: number) {
   .el-pagination {
   .el-pagination {
     float: v-bind(float);
     float: v-bind(float);
   }
   }
+
+  .cursor-pagination {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 16px;
+    float: v-bind(float);
+
+    .page-info {
+      font-size: 14px;
+      color: #606266;
+    }
+
+    .ml-2 {
+      margin-left: 8px;
+    }
+  }
 }
 }
 .pagination-container.hidden {
 .pagination-container.hidden {
   display: none;
   display: none;

+ 272 - 0
src/components/TaxCodeSelect/index.vue

@@ -0,0 +1,272 @@
+<template>
+  <div>
+    <el-dialog
+      v-model="dialog.visible.value"
+      :title="dialog.title.value"
+      width="1200px"
+      append-to-body
+      destroy-on-close
+    >
+      <div class="tax-code-container">
+        <!-- 左侧树 -->
+        <div class="tax-tree-panel">
+          <el-tree
+            ref="treeRef"
+            :props="treeProps"
+            node-key="id"
+            highlight-current
+            lazy
+            :load="loadNode"
+            :expand-on-click-node="false"
+            @node-click="handleNodeClick"
+            @node-dblclick="handleTreeDblClick"
+          />
+        </div>
+
+        <!-- 右侧列表 -->
+        <div class="tax-list-panel">
+          <!-- 搜索框 -->
+          <div class="search-bar">
+            <el-input
+              v-model="searchKeyword"
+              placeholder="请输入名称或编码"
+              prefix-icon="Search"
+              clearable
+              @keyup.enter="handleSearch"
+              @clear="handleSearch"
+              style="width: 280px"
+            />
+            <el-button icon="Search" @click="handleSearch" style="margin-left:8px" />
+          </div>
+
+          <!-- 表格 -->
+          <el-table
+            v-loading="loading"
+            :data="listData"
+            border
+            highlight-current-row
+            height="360px"
+            @row-dblclick="handleRowDblClick"
+          >
+            <el-table-column label="编码" align="center" prop="taxationNo" min-width="110">
+              <template #default="{ row }">
+                <el-link type="primary" :underline="false">{{ row.taxationNo }}</el-link>
+              </template>
+            </el-table-column>
+            <el-table-column label="合并编码" align="center" prop="mergeNo" min-width="130" show-overflow-tooltip>
+              <template #default="{ row }">
+                <el-link type="primary" :underline="false">{{ row.mergeNo }}</el-link>
+              </template>
+            </el-table-column>
+            <el-table-column label="名称" align="center" prop="name" min-width="120" show-overflow-tooltip>
+              <template #default="{ row }">
+                <el-link type="primary" :underline="false">{{ row.name }}</el-link>
+              </template>
+            </el-table-column>
+            <el-table-column label="简称" align="center" prop="abbreviation" min-width="100" show-overflow-tooltip>
+              <template #default="{ row }">
+                <el-link type="primary" :underline="false">{{ row.abbreviation }}</el-link>
+              </template>
+            </el-table-column>
+            <!-- <el-table-column label="说明" align="center" prop="remark" min-width="150" show-overflow-tooltip />
+            <el-table-column label="税率" align="center" prop="taxRate" width="80" />
+            <el-table-column label="用户选择比例" align="center" prop="selectRatio" width="110" /> -->
+          </el-table>
+
+          <!-- 分页 -->
+          <pagination
+            v-show="total > 0"
+            :total="total"
+            v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize"
+            @pagination="getList"
+          />
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { listTaxCode, getTaxCode } from '@/api/system/taxCode';
+import { TaxCodeVO, TaxCodeQuery } from '@/api/system/taxCode/types';
+import useDialog from '@/hooks/useDialog';
+
+const emit = defineEmits<{
+  (e: 'select', row: TaxCodeVO): void;
+}>();
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const dialog = useDialog({ title: '税收编码选择  请双击选择税收编码' });
+
+const treeRef = ref<ElTreeInstance>();
+const treeProps = { label: 'name' };
+const currentEchoId = ref<string | number | undefined>();
+
+const listData = ref<TaxCodeVO[]>([]);
+const loading = ref(false);
+const total = ref(0);
+const searchKeyword = ref('');
+
+const queryParams = ref<TaxCodeQuery>({
+  pageNum: 1,
+  pageSize: 10,
+  parentId: undefined,
+  name: undefined,
+  taxationNo: undefined,
+  params: {}
+});
+
+/** 懒加载树节点 */
+const loadNode = async (node: any, resolve: (data: any[]) => void) => {
+  const parentId = node.level === 0 ? 0 : node.data.id;
+  try {
+    const res = await listTaxCode({ parentId, pageNum: 1, pageSize: 9999, params: {} });
+    resolve(res.rows ?? []);
+  } catch {
+    resolve([]);
+  }
+};
+
+/** 等待节点出现在树中并展开 */
+const expandNodeById = (id: string | number): Promise<void> => {
+  return new Promise(resolve => {
+    const tryExpand = (retry = 0) => {
+      const node = treeRef.value?.getNode(id);
+      if (node) {
+        node.expand(() => resolve(), false);
+      } else if (retry < 30) {
+        setTimeout(() => tryExpand(retry + 1), 100);
+      } else {
+        resolve();
+      }
+    };
+    tryExpand();
+  });
+};
+
+/** 根据 id 回显:展开祖先路径并高亮目标节点 */
+const echoById = async (id: string | number) => {
+  try {
+    const res = await getTaxCode(id);
+    const data = res.data;
+    // ancestors 格式如 "0,100001,100002",按逗号分割并过滤掉根节点 0
+    const ancestors = (data.ancestors ?? '')
+      .split(',')
+      .filter((a: string) => a && a !== '0');
+    for (const ancId of ancestors) {
+      await expandNodeById(ancId);
+    }
+    await nextTick();
+    treeRef.value?.setCurrentKey(id);
+  } catch (e) {
+    console.warn('taxCode echo failed', e);
+  }
+};
+
+/** 查询右侧列表 */
+const getList = async () => {
+  loading.value = true;
+  try {
+    const res = await listTaxCode(queryParams.value);
+    listData.value = res.rows ?? [];
+    total.value = res.total ?? 0;
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 单击树节点 — 刷新右侧列表 */
+const handleNodeClick = (node: any) => {
+  queryParams.value.parentId = node.id;
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 双击树节点 — 叶子节点直接选中 */
+const handleTreeDblClick = (data: any, node: any) => {
+  if (node.isLeaf) {
+    emit('select', data as TaxCodeVO);
+    dialog.closeDialog();
+  }
+};
+
+/** 搜索 */
+const handleSearch = () => {
+  const kw = searchKeyword.value?.trim();
+  queryParams.value.name = kw || undefined;
+  queryParams.value.taxationNo = undefined;
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 双击行选择 */
+const handleRowDblClick = (row: TaxCodeVO) => {
+  emit('select', row);
+  dialog.closeDialog();
+};
+
+/** 对外暴露打开方法,可传入 id 用于回显 */
+const open = (id?: string | number) => {
+  currentEchoId.value = id;
+  searchKeyword.value = '';
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10,
+    parentId: undefined,
+    name: undefined,
+    taxationNo: undefined,
+    params: {}
+  };
+  dialog.openDialog();
+};
+
+watch(
+  () => dialog.visible.value,
+  async (val) => {
+    if (val) {
+      await getList();
+      if (currentEchoId.value) {
+        await echoById(currentEchoId.value);
+      }
+    } else {
+      listData.value = [];
+      total.value = 0;
+    }
+  }
+);
+
+defineExpose({ open, close: dialog.closeDialog });
+</script>
+
+<style scoped>
+.tax-code-container {
+  display: flex;
+  gap: 16px;
+  min-height: 460px;
+}
+
+.tax-tree-panel {
+  width: 300px;
+  flex-shrink: 0;
+  border: 1px solid var(--el-border-color);
+  border-radius: 4px;
+  padding: 8px 0;
+  overflow-y: auto;
+  max-height: 480px;
+}
+
+.tax-list-panel {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  min-width: 0;
+}
+
+.search-bar {
+  display: flex;
+  align-items: center;
+}
+</style>

+ 1212 - 0
src/components/upload-image/index.vue

@@ -0,0 +1,1212 @@
+<template>
+  <div>
+    <div class="flex flex-wrap">
+      <template v-if="limit == 1">
+        <div
+          class="rounded cursor-pointer overflow-hidden relative border border-solid border-color image-wrap mr-[10px]"
+          :class="{ 'rounded-full': type == 'avatar' }"
+          :style="style"
+        >
+          <div class="w-full h-full relative" v-if="imagesData && imagesData.length > 0 && imagesData[0] != ''">
+            <div class="w-full h-full flex items-center justify-center">
+              <el-image class="w-full h-full" :src="imagesData[0].indexOf('data:image') != -1 ? imagesData[0] : img(imagesData[0])"></el-image>
+            </div>
+            <div class="absolute z-[1] flex items-center justify-center w-full h-full inset-0 bg-black bg-opacity-60 operation">
+              <icon name="element ZoomIn" color="#fff" size="18px" class="mr-[10px]" @click="previewImage(imagesData, 0)" />
+              <icon name="element Delete" color="#fff" size="18px" @click.stop="removeImage" />
+            </div>
+          </div>
+          <div class="w-full h-full flex items-center justify-center flex-col content-wrap" v-else @click="openDialog">
+            <icon name="element Plus" size="20px" color="var(--el-text-color-secondary)" />
+            <div class="leading-none text-xs mt-[10px] text-secondary">{{ imageText || '上传图片' }}</div>
+          </div>
+        </div>
+      </template>
+      <template v-else>
+        <div class="flex flex-wrap" ref="imgListRef">
+          <template v-for="(item, index) in imagesData" :key="item + index">
+            <div
+              v-if="item && item != ''"
+              class="rounded cursor-pointer overflow-hidden relative border border-solid border-color image-wrap mr-[10px] mb-[10px]"
+              :style="style"
+            >
+              <div class="w-full h-full relative">
+                <div class="w-full h-full flex items-center justify-center">
+                  <el-image :src="img(item)" fit="contain"></el-image>
+                </div>
+                <div class="absolute z-[1] flex flex-col items-center justify-center w-full h-full inset-0 bg-black bg-opacity-60 operation">
+                  <div class="flex items-center justify-center mb-[6px]">
+                    <icon name="element ZoomIn" color="#fff" size="18px" class="mr-[10px]" @click.stop="previewImage(imagesData, index)" />
+                    <icon name="element Delete" color="#fff" size="18px" @click.stop="removeImage(index)" />
+                  </div>
+                  <div class="flex items-center justify-center gap-[8px]">
+                    <el-icon
+                      :size="16"
+                      :style="{ color: Number(index) === 0 ? 'rgba(255,255,255,0.3)' : '#fff', cursor: Number(index) === 0 ? 'not-allowed' : 'pointer' }"
+                      :title="'向左移动'"
+                      @click.stop="Number(index) > 0 && moveImage(Number(index), Number(index) - 1)"
+                    ><ArrowLeft /></el-icon>
+                    <el-icon
+                      :size="16"
+                      :style="{ color: Number(index) === imagesData.length - 1 ? 'rgba(255,255,255,0.3)' : '#fff', cursor: Number(index) === imagesData.length - 1 ? 'not-allowed' : 'pointer' }"
+                      :title="'向右移动'"
+                      @click.stop="Number(index) < imagesData.length - 1 && moveImage(Number(index), Number(index) + 1)"
+                    ><ArrowRight /></el-icon>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </template>
+          <div
+            class="rounded cursor-pointer overflow-hidden relative border border-solid border-color image-wrap mr-[10px] mb-[10px]"
+            :style="style"
+            v-if="imagesData.length < limit"
+          >
+            <div class="w-full h-full flex items-center justify-center flex-col content-wrap" @click="openDialog">
+              <icon name="element Plus" size="20px" color="var(--el-text-color-secondary)" />
+              <div class="leading-none text-xs mt-[10px] text-secondary">{{ imageText || '上传图片' }}</div>
+            </div>
+          </div>
+        </div>
+      </template>
+    </div>
+    <!-- 选择图片 -->
+    <el-dialog
+      v-model="dialogVisible"
+      title="选择图片"
+      width="1400"
+      :close-on-click-modal="false"
+      class="file-selector-dialog"
+      :before-close="closeDialog"
+    >
+      <div class="dialog-bos">
+        <!-- 工具栏 -->
+        <div class="toolbar">
+          <div class="toolbar-left">
+            <el-upload
+              ref="uploadRef"
+              :action="uploadUrl"
+              :headers="uploadHeaders"
+              :before-upload="beforeUpload"
+              :on-success="onUploadSuccess"
+              :on-error="onUploadError"
+              :show-file-list="false"
+              :accept="getUploadFileAccept()"
+            >
+              <el-button type="primary">
+                <el-icon><Plus /></el-icon>
+                上传图片
+              </el-button>
+            </el-upload>
+          </div>
+          <div class="toolbar-right">
+            <el-input v-model="queryParams.name" placeholder="请输入图片名称" style="width: 200px" clearable @input="handleSearch">
+              <template #prefix>
+                <el-icon><Search /></el-icon>
+              </template>
+            </el-input>
+            <div class="view-toggle">
+              <el-button :type="viewMode === 'grid' ? 'primary' : 'default'" size="small" @click="viewMode = 'grid'">
+                <el-icon><Grid /></el-icon>
+              </el-button>
+              <el-button :type="viewMode === 'list' ? 'primary' : 'default'" size="small" @click="viewMode = 'list'">
+                <el-icon><List /></el-icon>
+              </el-button>
+            </div>
+          </div>
+        </div>
+        <div class="content-wrapper">
+          <!-- 左侧分类导航 -->
+          <div class="sidebar">
+            <!-- 全部文件 -->
+            <div @click="handleTreeNodeClick({ id: '' })" class="category-item" :class="{ active: queryParams.categoryId == '' }">
+              <el-icon class="category-icon"><Folder /></el-icon>
+              <span>全部图片</span>
+              <el-dropdown trigger="click" @click.stop class="node-actions">
+                <el-icon class="more-icon"><MoreFilled /></el-icon>
+                <template #dropdown>
+                  <el-dropdown-menu>
+                    <el-dropdown-item @click="openClassify({}, 'add')" command="addRoot">添加分类</el-dropdown-item>
+                  </el-dropdown-menu>
+                </template>
+              </el-dropdown>
+            </div>
+            <!-- 树形控件 -->
+            <el-tree
+              ref="categoryTreeRef"
+              :data="filteredCategoryTree"
+              :props="treeProps"
+              :expand-on-click-node="false"
+              :current-node-key="queryParams.categoryId"
+              node-key="id"
+              @node-click="handleTreeNodeClick"
+              class="category-tree"
+            >
+              <template #default="{ data }">
+                <div class="tree-node-content">
+                  <el-icon class="category-icon"><Folder /></el-icon>
+                  <span class="node-label">{{ data.name }}</span>
+                  <el-dropdown trigger="click" @click.stop class="node-actions pr-[10px]">
+                    <el-icon class="more-icon"><MoreFilled /></el-icon>
+                    <template #dropdown>
+                      <el-dropdown-menu>
+                        <el-dropdown-item @click="openClassify(data, 'add')">添加分类</el-dropdown-item>
+                        <el-dropdown-item @click="openClassify(data, 'edit')">编辑分类</el-dropdown-item>
+                        <el-dropdown-item @click="closeClassify(data)">删除分类</el-dropdown-item>
+                      </el-dropdown-menu>
+                    </template>
+                  </el-dropdown>
+                </div>
+              </template>
+            </el-tree>
+          </div>
+          <!-- 右侧文件展示区 -->
+          <div class="content-area">
+            <!-- 网格视图 -->
+            <div v-if="viewMode === 'grid'" class="file-grid">
+              <div
+                v-for="(file, index) in fileList"
+                :key="index"
+                class="file-item"
+                :class="{
+                  selected: selectedFiles.some((row: any) => row.id == file.id)
+                }"
+                @click="toggleFileSelection(file)"
+              >
+                <div class="file-wrapper">
+                  <el-image :src="getImageUrl(file)" fit="cover" class="file-thumbnail" :preview-disabled="true" lazy>
+                    <template #error>
+                      <div class="file-error">
+                        <el-icon size="24" color="#c0c4cc">
+                          <Picture />
+                        </el-icon>
+                      </div>
+                    </template>
+                    <template #placeholder>
+                      <div class="file-loading">
+                        <el-icon size="24" color="#409eff">
+                          <Loading />
+                        </el-icon>
+                      </div>
+                    </template>
+                  </el-image>
+                  <div class="file-checkbox">
+                    <el-checkbox :model-value="selectedFiles.some((row: any) => row.id == file.id)" />
+                  </div>
+                </div>
+                <div class="file-info">
+                  <div class="file-name">{{ file.name || file.originalName }}</div>
+                  <div class="file-actions">
+                    <el-button
+                      link
+                      size="small"
+                      @click.stop="
+                        previewImage(
+                          fileList.map((row: any) => row.url),
+                          index
+                        )
+                      "
+                      >预览</el-button
+                    >
+                    <el-button link size="small" @click.stop="handleRename(file)">重命名</el-button>
+                  </div>
+                </div>
+              </div>
+            </div>
+            <!-- 列表视图 -->
+            <div v-else class="file-list">
+              <!-- @selection-change="handleSelectionChange" -->
+              <el-table height="600" v-loading="loading" :data="fileList" style="width: 100%">
+                <!-- <el-table-column type="selection" width="55" /> -->
+                <el-table-column label="预览" width="80">
+                  <template #default="{ row }">
+                    <el-image :src="getImageUrl(row)" style="width: 50px; height: 50px; border-radius: 4px" fit="cover" :preview-disabled="true" lazy>
+                      <template #error>
+                        <div class="list-image-error">
+                          <el-icon size="20" color="#c0c4cc">
+                            <Picture />
+                          </el-icon>
+                        </div>
+                      </template>
+                      <template #placeholder>
+                        <div class="list-image-loading">
+                          <el-icon size="20" color="#409eff">
+                            <Loading />
+                          </el-icon>
+                        </div>
+                      </template>
+                    </el-image>
+                  </template>
+                </el-table-column>
+                <el-table-column label="文件名" prop="name" min-width="200">
+                  <template #default="{ row }">
+                    {{ row.name || row.originalName }}
+                  </template>
+                </el-table-column>
+                <el-table-column label="大小" width="100">
+                  <template #default="{ row }">
+                    {{ formatFileSize(row.size) }}
+                  </template>
+                </el-table-column>
+                <el-table-column label="类型" prop="type" width="120" />
+                <el-table-column label="上传时间" width="180">
+                  <template #default="{ row }">
+                    {{ formatTime(row.createTime) }}
+                  </template>
+                </el-table-column>
+                <el-table-column label="操作" width="150" fixed="right">
+                  <template #default="scope">
+                    <el-button
+                      link
+                      @click="
+                        previewImage(
+                          fileList.map((row: any) => row.url),
+                          scope.$index
+                        )
+                      "
+                      >预览</el-button
+                    >
+                    <el-button link @click="handleRename(scope.row)">重命名</el-button>
+                  </template>
+                </el-table-column>
+              </el-table>
+            </div>
+            <!-- 分页 -->
+            <div class="pagination">
+              <el-pagination
+                v-model:current-page="queryParams.pageNum"
+                v-model:page-size="queryParams.pageSize"
+                :total="total"
+                :page-sizes="[21, 28, 35, 42]"
+                layout="total, sizes, prev, pager, next, jumper"
+                @size-change="getList"
+                @current-change="getList"
+              />
+            </div>
+          </div>
+        </div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="closeDialog">取消</el-button>
+          <el-button type="primary" @click="confirmSelection" :disabled="selectedFiles.length > 0 ? false : true">
+            确认选择({{ selectedFiles.length }})
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 添加编辑分类 -->
+    <el-dialog v-model="classifyDialog.dialog" :title="classifyDialog.title" width="600">
+      <el-form ref="categoryFormRef" :model="categoryForm" :rules="categoryRules" label-width="100px">
+        <!-- 分类名称 -->
+        <el-form-item label="分类名称" prop="name" required>
+          <el-input v-model="categoryForm.name" placeholder="请输入分类名称" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="classifyDialog.dialog = false">取消</el-button>
+          <el-button type="primary" @click="submitCategory" :loading="classifyDialog.loading">确定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 重命名对话框 -->
+    <el-dialog v-model="renameDialogVisible" title="重命名文件" width="400px" append-to-body :close-on-click-modal="false">
+      <el-form ref="renameFormRef" :model="renameForm" label-width="80px">
+        <el-form-item label="原文件名">
+          <el-input v-model="renameForm.originalName" readonly />
+        </el-form-item>
+        <el-form-item
+          label="新文件名"
+          prop="name"
+          :rules="[
+            { required: true, message: '请输入新文件名', trigger: 'blur' },
+            { min: 1, max: 100, message: '文件名长度在 1 到 100 个字符', trigger: 'blur' }
+          ]"
+        >
+          <el-input v-model="renameForm.name" placeholder="请输入新文件名" />
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="renameDialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="submitRename">确定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 图片放大 -->
+    <el-image-viewer
+      :url-list="previewImageList"
+      v-if="imageViewer.show"
+      @close="imageViewer.show = false"
+      :initial-index="imageViewer.index"
+      :zoom-rate="1"
+    />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue';
+import { img } from '@/utils/common';
+import { listFileInfo, delFileInfo, addFileInfo, updateDownloadCount, updateFileInfo } from '@/api/file/info';
+import { listFileCategoryTree, addFileCategory, updateFileCategory, delFileCategory } from '@/api/file/category';
+import { globalHeaders } from '@/utils/request';
+const props = defineProps({
+  type: {
+    type: String,
+    default: 'image'
+  },
+  modelValue: {
+    type: String || Array,
+    default: ''
+  },
+  width: {
+    type: String,
+    default: '100px'
+  },
+  height: {
+    type: String,
+    default: '100px'
+  },
+  imageText: {
+    type: String
+  },
+  limit: {
+    type: Number,
+    default: 1
+  }
+});
+const imagesData = ref<any>([]);
+const previewImageList = ref<any>([]);
+watch(
+  () => props.modelValue,
+  () => {
+    if (props.limit == 1) {
+      imagesData.value = [props.modelValue];
+    } else {
+      if (Array.isArray(props.modelValue)) {
+        imagesData.value = props.modelValue;
+      } else {
+        imagesData.value = props.modelValue.split(',');
+        imagesData.value = imagesData.value.filter((item: any) => item !== '');
+      }
+    }
+  },
+  { immediate: true }
+);
+
+const emit = defineEmits(['update:modelValue', 'change']);
+
+const dialogVisible = ref<any>(false);
+const viewMode = ref<any>('grid');
+// 图片列表
+const loading = ref<any>(false);
+const fileList = ref([]);
+const total = ref(0);
+const selectedFiles = ref([]);
+
+// 查询参数
+const queryParams = ref<any>({
+  pageNum: 1,
+  pageSize: 20,
+  name: null,
+  categoryId: '',
+  categoryType: 1
+});
+
+// 分类相关数据
+const filteredCategoryTree = ref<any>([]);
+const categoryTreeRef = ref<any>(null);
+const treeProps = {
+  label: 'name',
+  children: 'children'
+};
+const classifyDialog = ref<any>({
+  dialog: false,
+  title: '添加分类',
+  loading: false
+});
+// 分类表单数据
+const categoryFormRef = ref<any>(null);
+const categoryForm = ref({
+  id: null,
+  name: '',
+  code: '',
+  parentId: null,
+  type: 1,
+  sort: 1,
+  description: '',
+  status: 0
+});
+// 分类表单验证规则
+const categoryRules = ref<any>({
+  name: [
+    { required: true, message: '请输入分类名称', trigger: 'blur' },
+    { min: 2, max: 50, message: '分类名称长度在 2 到 50 个字符', trigger: 'blur' }
+  ]
+});
+
+// 重命名相关数据
+const renameDialogVisible = ref(false);
+const renameForm = ref({
+  id: null,
+  name: '',
+  originalName: '',
+  currentFile: null
+});
+const renameFormRef = ref();
+
+// 上传配置
+const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload');
+const uploadHeaders = ref(globalHeaders());
+const uploadFileList = ref([]);
+
+// 打开弹窗
+const openDialog = () => {
+  getCategoryTree();
+  getList();
+  selectedFiles.value = [];
+  dialogVisible.value = true;
+};
+
+//关闭弹窗
+const closeDialog = () => {
+  dialogVisible.value = false;
+};
+
+//确定
+const confirmSelection = () => {
+  if (props.limit == 1) {
+    emit('update:modelValue', selectedFiles.value[0].url);
+    emit('change', selectedFiles.value[0]);
+  } else {
+    const result = selectedFiles.value.map((item: any) => item.url);
+    let resultArray = [];
+    let resultString = '';
+    if (Array.isArray(props.modelValue)) {
+      resultArray = [...result, ...imagesData.value];
+      emit('update:modelValue', resultArray);
+      emit('change', resultArray);
+    } else {
+      resultString = imagesData.value + ',' + result.join(',');
+      emit('update:modelValue', resultString);
+      emit('change', resultString);
+    }
+  }
+  closeDialog();
+};
+
+// 搜索文件
+const handleSearch = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+// 获取文件列表
+const getList = async () => {
+  try {
+    loading.value = true;
+    const response = (await listFileInfo(queryParams.value)) as any;
+    const data = response?.data ?? response;
+    if (data && data.rows) {
+      fileList.value = data.rows as any[];
+      total.value = data.total || 0;
+    } else {
+      fileList.value = [];
+      total.value = 0;
+    }
+  } catch (error) {
+    console.error('获取文件列表失败:', error);
+    ElMessage.error('获取文件列表失败');
+    fileList.value = [];
+    total.value = 0;
+  } finally {
+    loading.value = false;
+  }
+};
+
+const handleSelectionChange = (res: any) => {};
+// 切换文件选择状态
+const toggleFileSelection = (res: any) => {
+  if (props.limit == 1) {
+    selectedFiles.value = [res];
+  } else {
+    // 多选模式,实现切换逻辑
+    const index = selectedFiles.value.findIndex((item: any) => item.id === res.id);
+    // 计算当前总选择数量(已选文件 + 已上传图片)
+    const currentTotalCount = selectedFiles.value.length + imagesData.value.length;
+    if (index > -1) {
+      // 如果已选中,则取消选中(删除)
+      selectedFiles.value.splice(index, 1);
+    } else {
+      // 如果未选中,检查是否超过限制
+      if (currentTotalCount >= props.limit) {
+        ElMessage.warning(`最多只能选择 ${props.limit} 张图片`);
+        return;
+      }
+      // 未超过限制,添加选中
+      selectedFiles.value.push(res);
+    }
+  }
+};
+
+// 获取图片URL
+const getImageUrl = (file) => {
+  // 优先使用url字段
+  if (file.url) {
+    // 如果是完整的URL,直接返回
+    if (file.url.startsWith('http://') || file.url.startsWith('https://')) {
+      return file.url;
+    }
+    // 如果是相对路径,添加基础URL
+    if (file.url.startsWith('/')) {
+      return import.meta.env.VITE_APP_BASE_API + file.url;
+    }
+    return file.url;
+  }
+
+  // 备选方案:使用path字段
+  if (file.path) {
+    if (file.path.startsWith('http://') || file.path.startsWith('https://')) {
+      return file.path;
+    }
+    if (file.path.startsWith('/')) {
+      return import.meta.env.VITE_APP_BASE_API + file.path;
+    }
+    return file.path;
+  }
+
+  // 如果都没有,返回空字符串,触发错误处理
+  return '';
+};
+
+// 获取分类树
+const getCategoryTree = () => {
+  listFileCategoryTree().then((res) => {
+    if (res.code == 200) {
+      if (res.data.length > 0) {
+        res.data.forEach((item: any) => {
+          if (item.type == 1 && item.code == 'IMAGE') {
+            filteredCategoryTree.value = item.children;
+          }
+        });
+      }
+    }
+  });
+};
+
+// 添加分类
+const openClassify = (res: any, type: any) => {
+  console.log(res, 'res');
+  if (type == 'add') {
+    classifyDialog.value.title = '添加分类';
+    categoryForm.value.name = '';
+    categoryForm.value.id = null;
+    if (res.id) {
+      categoryForm.value.parentId = res.id;
+    } else {
+      categoryForm.value.parentId = 1;
+    }
+  } else {
+    classifyDialog.value.title = '编辑分类';
+    categoryForm.value.name = res.name;
+    categoryForm.value.id = res.id;
+    categoryForm.value.parentId = null;
+  }
+
+  classifyDialog.value.dialog = true;
+};
+
+// 新增编辑分类
+const submitCategory = async () => {
+  try {
+    await categoryFormRef.value?.validate();
+    classifyDialog.value.loading = true;
+
+    const categoryData = {
+      ...categoryForm.value,
+      tenantId: '000000'
+    };
+
+    if (categoryForm.value.id) {
+      await updateFileCategory(categoryData);
+    } else {
+      await addFileCategory(categoryData);
+    }
+
+    ElMessage.success(categoryForm.value.id ? '更新成功' : '添加成功');
+    classifyDialog.value.loading = false;
+    getCategoryTree();
+  } catch (error) {
+    console.error('保存分类失败:', error);
+    ElMessage.error('保存分类失败');
+  } finally {
+    classifyDialog.value.loading = false;
+    classifyDialog.value.dialog = false;
+  }
+};
+
+// 删除分类
+const closeClassify = async (data: any) => {
+  if (data.children && data.children.length > 0) {
+    ElMessage.warning('该分类下有子分类,请先删除子分类');
+    return;
+  }
+  try {
+    await ElMessageBox.confirm(`确定要删除分类"${data.name}"吗?`, '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    } as any);
+    await delFileCategory(data.id);
+    ElMessage.success('删除成功');
+    getCategoryTree();
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error('删除分类失败:', error);
+      ElMessage.error('删除分类失败');
+    }
+  }
+};
+
+// 树节点点击事件
+const handleTreeNodeClick = (res: any) => {
+  queryParams.value.categoryId = res.id;
+  if (res.id == '') {
+    categoryTreeRef.value.setCurrentKey(null);
+  }
+  handleSearch();
+};
+
+// 图片放大
+const imageViewer = reactive({
+  show: false,
+  index: 0
+});
+
+const previewImage = (list: any, index: any) => {
+  previewImageList.value = list;
+  imageViewer.index = index ? index : 0;
+  imageViewer.show = true;
+};
+
+/**
+ * 移动图片位置
+ * @param fromIndex 原位置
+ * @param toIndex 目标位置
+ */
+const moveImage = (fromIndex: number, toIndex: number) => {
+  const list = [...imagesData.value];
+  const [item] = list.splice(fromIndex, 1);
+  list.splice(toIndex, 0, item);
+  if (Array.isArray(props.modelValue)) {
+    emit('update:modelValue', list);
+    emit('change', list);
+  } else {
+    emit('update:modelValue', list.join(','));
+    emit('change', list.join(','));
+  }
+};
+
+/**
+ * 删除图片
+ * @param index
+ */
+const removeImage = (index?: any) => {
+  if (props.limit == 1) {
+    emit('update:modelValue', '');
+    emit('change', '');
+  } else {
+    const list = [...imagesData.value];
+    list.splice(index, 1);
+    if (Array.isArray(props.modelValue)) {
+      emit('update:modelValue', list);
+      emit('change', list);
+    } else {
+      emit('update:modelValue', list.join(','));
+      emit('change', list.join(','));
+    }
+  }
+};
+
+// 重命名文件
+const handleRename = (file: any) => {
+  renameForm.value = {
+    id: file.id,
+    name: '',
+    originalName: file.name || file.originalName || '',
+    currentFile: file // 保存完整的文件信息
+  };
+  renameDialogVisible.value = true;
+};
+
+// 提交重命名
+const submitRename = async () => {
+  try {
+    await renameFormRef.value?.validate();
+
+    if (!renameForm.value.name.trim()) {
+      ElMessage.error('请输入新文件名');
+      return;
+    }
+
+    if (renameForm.value.name === renameForm.value.originalName) {
+      ElMessage.warning('新文件名与原文件名相同');
+      return;
+    }
+
+    // 调用重命名API
+    const file = renameForm.value.currentFile;
+    await updateFileInfo({
+      id: renameForm.value.id,
+      name: renameForm.value.name.trim(),
+      originalName: file.originalName,
+      path: file.path,
+      url: file.url,
+      size: file.size,
+      type: file.type,
+      extension: file.extension,
+      categoryId: file.categoryId,
+      description: file.description
+    });
+
+    ElMessage.success('重命名成功');
+    renameDialogVisible.value = false;
+
+    // 刷新文件列表
+    handleSearch();
+
+    console.log('重命名文件:', {
+      id: renameForm.value.id,
+      oldName: renameForm.value.originalName,
+      newName: renameForm.value.name.trim()
+    });
+  } catch (error) {
+    console.error('重命名失败:', error);
+    ElMessage.error('重命名失败');
+  }
+};
+
+// 格式化文件大小
+const formatFileSize = (size) => {
+  if (!size) return '0 B';
+  const units = ['B', 'KB', 'MB', 'GB'];
+  let index = 0;
+  let fileSize = size;
+  while (fileSize >= 1024 && index < units.length - 1) {
+    fileSize /= 1024;
+    index++;
+  }
+  return fileSize.toFixed(2) + ' ' + units[index];
+};
+
+// 格式化时间
+const formatTime = (time) => {
+  if (!time) return '';
+  return new Date(time).toLocaleString();
+};
+
+// 上传前检查
+const beforeUpload = (file) => {
+  // 获取准确的MIME类型
+  const actualMimeType = getFileMimeType(file);
+  const fileName = file.name || '';
+  const extension = fileName.split('.').pop()?.toLowerCase();
+
+  let isValidType = false;
+  let fileTypeText = '';
+
+  isValidType = actualMimeType.startsWith('image/') || ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'].includes(extension);
+  fileTypeText = '图片';
+
+  if (!isValidType) {
+    ElMessage.error(`只能上传${fileTypeText}文件! 检测到的文件类型: ${actualMimeType}`);
+    return false;
+  }
+
+  // 检查文件大小 (50MB)
+  const isLtSize = file.size / 1024 / 1024 < 50;
+  if (!isLtSize) {
+    ElMessage.error('上传文件大小不能超过 50MB!');
+    return false;
+  }
+
+  // 非图片文件或非图片分类,直接上传
+  return true;
+};
+
+// 上传成功
+const onUploadSuccess = (response, file) => {
+  if (response.code === 200) {
+    // 获取文件MIME类型和扩展名
+    const mimeType = getFileMimeType(file);
+    const fileName = file.name || '';
+    const extension = fileName.split('.').pop()?.toLowerCase() || '';
+    const datas = {
+      categoryId: queryParams.value.categoryId,
+      categoryType: 1,
+      description: '',
+      downloadCount: 0,
+      extension: extension,
+      isPublic: 1,
+      name: file.name,
+      originalName: file.name,
+      ossId: response.data?.ossId || '',
+      path: response.data?.url || '',
+      size: file.size,
+      status: 0,
+      type: mimeType,
+      uploadStatus: 1,
+      url: response.data?.url || '',
+      viewCount: 0
+    };
+    addFileInfo(datas).then((res) => {
+      if (res.code == 200) {
+        ElMessage.success('文件上传成功');
+        handleSearch();
+      }
+    });
+  } else {
+    ElMessage.error('上传失败:' + response.msg);
+  }
+};
+
+// 上传失败
+const onUploadError = (error) => {
+  console.error('上传失败:', error);
+  ElMessage.error('上传失败');
+};
+
+// 获取上传文件接受类型
+const getUploadFileAccept = () => {
+  return '.jpg,.jpeg,.png,.gif,.bmp,.webp';
+};
+
+// 获取文件MIME类型
+const getFileMimeType = (file) => {
+  // 优先使用浏览器检测的MIME类型
+  if (file.type) {
+    return file.type;
+  }
+
+  // 如果浏览器无法检测,根据文件扩展名推断
+  const fileName = file.name || '';
+  const extension = fileName.split('.').pop()?.toLowerCase();
+
+  const mimeTypeMap = {
+    // 图片类型
+    'jpg': 'image/jpeg',
+    'jpeg': 'image/jpeg',
+    'png': 'image/png',
+    'gif': 'image/gif',
+    'bmp': 'image/bmp',
+    'webp': 'image/webp',
+    'svg': 'image/svg+xml',
+
+    // 视频类型
+    'mp4': 'video/mp4',
+    'avi': 'video/x-msvideo',
+    'mov': 'video/quicktime',
+    'wmv': 'video/x-ms-wmv',
+    'flv': 'video/x-flv',
+    'mkv': 'video/x-matroska',
+    'webm': 'video/webm',
+
+    // 音频类型
+    'mp3': 'audio/mpeg',
+    'wav': 'audio/wav',
+    'flac': 'audio/flac',
+    'aac': 'audio/aac',
+    'ogg': 'audio/ogg',
+    'm4a': 'audio/mp4',
+
+    // 文档类型
+    'pdf': 'application/pdf',
+    'doc': 'application/msword',
+    'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+    'xls': 'application/vnd.ms-excel',
+    'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+    'ppt': 'application/vnd.ms-powerpoint',
+    'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+    'txt': 'text/plain',
+
+    // 压缩文件
+    'zip': 'application/zip',
+    'rar': 'application/vnd.rar',
+    '7z': 'application/x-7z-compressed',
+    'tar': 'application/x-tar',
+    'gz': 'application/gzip',
+
+    // 其他常见类型
+    'json': 'application/json',
+    'xml': 'application/xml',
+    'csv': 'text/csv',
+    'html': 'text/html',
+    'css': 'text/css',
+    'js': 'application/javascript'
+  };
+
+  return mimeTypeMap[extension] || 'application/octet-stream';
+};
+
+const style = computed(() => {
+  return {
+    width: props.width,
+    height: props.height
+  };
+});
+</script>
+
+<style lang="scss" scoped>
+.image-wrap {
+  .operation {
+    display: none;
+  }
+
+  &:hover {
+    .operation {
+      display: flex;
+    }
+  }
+}
+.border-color {
+  border-color: #e5e7eb;
+}
+
+.dialog-bos {
+  height: 750px;
+  background: #f5f7fa;
+  padding: 10px;
+  border-radius: 0 0 8px 8px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+  margin-left: 0px !important;
+  display: flex;
+  flex-direction: column;
+  .toolbar {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 10px;
+    padding: 16px 20px;
+    background: white;
+    .toolbar-left {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+    }
+    .toolbar-right {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      .view-toggle {
+        display: flex;
+        gap: 0;
+      }
+    }
+  }
+  .content-wrapper {
+    display: flex;
+    gap: 20px;
+    height: 0;
+    flex: 1;
+    .sidebar {
+      width: 280px;
+      background: white;
+      border-radius: 8px;
+      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+      overflow: auto;
+      display: flex;
+      flex-direction: column;
+      height: 100%;
+      padding: 10px;
+      .category-item {
+        display: flex;
+        align-items: center;
+        padding: 0px 15px;
+        border-radius: 6px;
+        cursor: pointer;
+        margin-bottom: 8px;
+        background: #f5f7fa;
+        transition: background-color 0.3s;
+        height: 40px;
+        .category-icon {
+          margin-right: 10px;
+          font-size: 18px;
+        }
+        &.active {
+          background: #409eff;
+          color: white;
+          .more-icon {
+            color: rgba(255, 255, 255, 0.8);
+            &:hover {
+              color: white;
+              background: rgba(255, 255, 255, 0.2);
+            }
+          }
+        }
+      }
+      /* 树形控件样式 */
+      .category-tree {
+        background: transparent;
+        :deep(.el-tree-node__content) {
+          height: 40px;
+          border-radius: 6px;
+          margin-bottom: 4px;
+          background: #f5f7fa;
+          transition: background-color 0.3s;
+        }
+        :deep(.el-tree-node__content:hover) {
+          background: #ebeef5;
+        }
+        :deep(.el-tree-node.is-current > .el-tree-node__content) {
+          background: #409eff;
+          color: white;
+          .more-icon {
+            color: rgba(255, 255, 255, 0.8);
+            &:hover {
+              color: white;
+              background: rgba(255, 255, 255, 0.2);
+            }
+          }
+        }
+        :deep(.el-tree-node.is-current > .el-tree-node__content:hover) {
+          background: #66b1ff;
+        }
+      }
+      .tree-node-content {
+        display: flex;
+        align-items: center;
+        width: 100%;
+        padding: 0 5px;
+        .category-icon {
+          margin-right: 8px;
+          font-size: 16px;
+        }
+        .node-label {
+          flex: 1;
+          font-size: 14px;
+        }
+      }
+      .node-actions {
+        margin-left: auto;
+        .more-icon {
+          font-size: 16px;
+          color: #909399;
+          cursor: pointer;
+          transition: color 0.3s;
+          border-radius: 2px;
+          &:hover {
+            color: #409eff;
+            background: rgba(64, 158, 255, 0.1);
+          }
+        }
+      }
+    }
+
+    .content-area {
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+      height: 100%;
+      overflow: hidden;
+      .file-grid {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 15px 10px;
+        padding: 10px;
+        flex: 1;
+        overflow-y: auto;
+        min-height: 0;
+        .file-item {
+          flex: 0 0 calc((100% - 30px) / 4);
+          overflow: hidden;
+          position: relative;
+          cursor: pointer;
+          border: 1px solid #ebeef5;
+          border-radius: 8px;
+          box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+          transition:
+            transform 0.3s ease,
+            box-shadow 0.3s ease;
+          height: 223px;
+          &:hover {
+            transform: translateY(-5px);
+            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
+          }
+          &.selected {
+            border: 2px solid #409eff;
+            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
+          }
+          .file-wrapper {
+            position: relative;
+            width: 100%;
+            height: 150px; /* Fixed height for grid view */
+            overflow: hidden;
+            .file-thumbnail {
+              width: 100%;
+              height: 100%;
+              object-fit: cover;
+              .file-error {
+                display: flex;
+                justify-content: center;
+                align-items: center;
+                width: 100%;
+                height: 100%;
+                background: #f5f7fa;
+                color: #c0c4cc;
+              }
+
+              .file-loading {
+                display: flex;
+                justify-content: center;
+                align-items: center;
+                width: 100%;
+                height: 100%;
+                background: #f0f9ff;
+                color: #409eff;
+              }
+            }
+            .file-checkbox {
+              position: absolute;
+              top: 8px;
+              right: 8px;
+              z-index: 10;
+            }
+          }
+          .file-info {
+            padding: 5px 5px 10px 5px;
+            background: #f5f7fa;
+            border-top: 1px solid #ebeef5;
+            border-radius: 0 0 8px 8px;
+            .file-name {
+              font-size: 14px;
+              color: #303133;
+              overflow: hidden;
+              text-overflow: ellipsis;
+              white-space: nowrap;
+              margin-bottom: 5px;
+            }
+            .file-actions {
+              display: flex;
+              justify-content: space-around;
+              gap: 8px;
+            }
+          }
+        }
+      }
+    }
+
+    .pagination {
+      display: flex;
+      justify-content: center;
+      padding: 10px;
+      background: white;
+      flex-shrink: 0;
+    }
+  }
+}
+</style>