Browse Source

新增客户资料等模块

hurx 10 hours ago
parent
commit
9b7c8b9fed
36 changed files with 5807 additions and 66 deletions
  1. 63 0
      src/api/customer/customerFile/contract/index.ts
  2. 264 0
      src/api/customer/customerFile/contract/types.ts
  3. 16 0
      src/api/customer/customerFile/customerContact/types.ts
  4. 59 0
      src/api/customer/customerFile/customerInfo/index.ts
  5. 25 3
      src/api/customer/customerFile/customerInfo/types.ts
  6. 77 0
      src/api/customer/customerFile/purchaseHabit/index.ts
  7. 245 0
      src/api/customer/customerFile/purchaseHabit/types.ts
  8. 80 0
      src/api/customer/customerFile/shippingAddress/index.ts
  9. 290 0
      src/api/customer/customerFile/shippingAddress/types.ts
  10. 63 0
      src/api/customer/maintenanceType/index.ts
  11. 65 0
      src/api/customer/maintenanceType/types.ts
  12. 63 0
      src/api/customer/serverItem/index.ts
  13. 65 0
      src/api/customer/serverItem/types.ts
  14. 63 0
      src/api/customer/serverTime/index.ts
  15. 65 0
      src/api/customer/serverTime/types.ts
  16. 0 1
      src/views/customer/customerCategory/customerTag/index.vue
  17. 238 0
      src/views/customer/customerFile/customerContract/customerContractInfo.vue
  18. 245 0
      src/views/customer/customerFile/customerContract/customerCreateContract.vue
  19. 186 0
      src/views/customer/customerFile/customerContract/index.vue
  20. 10 12
      src/views/customer/customerFile/customerInfo/add.vue
  21. 8 5
      src/views/customer/customerFile/customerInfo/components/addContactDialog.vue
  22. 99 1
      src/views/customer/customerFile/customerInfo/edit.vue
  23. 247 30
      src/views/customer/customerFile/customerInfo/index.vue
  24. 815 0
      src/views/customer/customerFile/customerInfo/overview/baseInfo.vue
  25. 386 0
      src/views/customer/customerFile/customerInfo/overview/contactInfo.vue
  26. 421 0
      src/views/customer/customerFile/customerInfo/overview/contractManagement.vue
  27. 179 0
      src/views/customer/customerFile/customerInfo/overview/operationLog.vue
  28. 24 0
      src/views/customer/customerFile/customerInfo/overview/orgStructure.vue
  29. 359 0
      src/views/customer/customerFile/customerInfo/overview/procurementProfile.vue
  30. 334 0
      src/views/customer/customerFile/customerInfo/overview/shippingAddress.vue
  31. 261 0
      src/views/customer/customerFile/maintainInfo/add.vue
  32. 22 8
      src/views/customer/maintainInfo/add.vue
  33. 1 6
      src/views/customer/maintainInfo/index.vue
  34. 240 0
      src/views/customer/maintenanceType/index.vue
  35. 228 0
      src/views/customer/serverItem/index.vue
  36. 1 0
      src/views/customer/serverTime/index.vue

+ 63 - 0
src/api/customer/customerFile/contract/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ContractVO, ContractForm, ContractQuery } from '@/api/customer/customerFile/contract/types';
+
+/**
+ * 查询客户合同列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listContract = (query?: ContractQuery): AxiosPromise<ContractVO[]> => {
+  return request({
+    url: '/customer/contract/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询客户合同详细
+ * @param id
+ */
+export const getContract = (id: string | number): AxiosPromise<ContractVO> => {
+  return request({
+    url: '/customer/contract/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增客户合同
+ * @param data
+ */
+export const addContract = (data: ContractForm) => {
+  return request({
+    url: '/customer/contract',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改客户合同
+ * @param data
+ */
+export const updateContract = (data: ContractForm) => {
+  return request({
+    url: '/customer/contract',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除客户合同
+ * @param id
+ */
+export const delContract = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/customer/contract/' + id,
+    method: 'delete'
+  });
+};

+ 264 - 0
src/api/customer/customerFile/contract/types.ts

@@ -0,0 +1,264 @@
+export interface ContractVO {
+  /**
+   * ID
+   */
+  id: string | number;
+
+  /**
+   * 客户编号
+   */
+  customerNo: string;
+
+  /**
+   * 客户id
+   */
+  customerId: string | number;
+
+  /**
+   * 合同编号
+   */
+  contractNo: string;
+
+  /**
+   * 合同名称
+   */
+  contractName: string;
+
+  customerName: string;
+
+  /**
+   * 合同金额
+   */
+  contractAmount: string;
+
+  /**
+   * 客服负责人
+   */
+  customerLeader: string;
+
+  /**
+   * 项目负责人
+   */
+  projectLeader: string;
+
+  /**
+   * 合同类型
+   */
+  contractType: string;
+
+  /**
+   * 合同开始时间
+   */
+  startTime: string;
+
+  /**
+   * 合同结束时间
+   */
+  endTime: string;
+
+  /**
+   * 合同上传时间
+   */
+  uploadTime: string;
+
+  /**
+   * 结算方式
+   */
+  settlementType: string;
+
+  /**
+   * 合同状态
+   */
+  contractStatus: string;
+
+  /**
+   * 审批状态(如:0-待审批,1-已通过,2-已驳回等)
+   */
+  approvalStatus: string;
+
+  /**
+   * 附件路径或信息
+   */
+  annex: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+}
+
+export interface ContractForm extends BaseEntity {
+  /**
+   * ID
+   */
+  id?: string | number;
+
+  /**
+   * 客户编号
+   */
+  customerNo?: string;
+
+  customerName?: string;
+
+  /**
+   * 客户id
+   */
+  customerId?: string | number;
+
+  /**
+   * 合同编号
+   */
+  contractNo?: string;
+
+  /**
+   * 合同名称
+   */
+  contractName?: string;
+
+  /**
+   * 合同金额
+   */
+  contractAmount?: string;
+
+  /**
+   * 客服负责人
+   */
+  customerLeader?: string;
+
+  /**
+   * 项目负责人
+   */
+  projectLeader?: string;
+
+  /**
+   * 合同类型
+   */
+  contractType?: string;
+
+  /**
+   * 合同开始时间
+   */
+  startTime?: string;
+
+  /**
+   * 合同结束时间
+   */
+  endTime?: string;
+
+  /**
+   * 合同上传时间
+   */
+  uploadTime?: string;
+
+  /**
+   * 结算方式
+   */
+  settlementType?: string;
+
+  /**
+   * 合同状态
+   */
+  contractStatus?: string;
+
+  /**
+   * 审批状态(如:0-待审批,1-已通过,2-已驳回等)
+   */
+  approvalStatus?: string;
+
+  /**
+   * 附件路径或信息
+   */
+  annex?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+}
+
+export interface ContractQuery extends PageQuery {
+  /**
+   * 客户编号
+   */
+  customerNo?: string;
+
+  /**
+   * 客户id
+   */
+  customerId?: string | number;
+
+  /**
+   * 合同编号
+   */
+  contractNo?: string;
+
+  /**
+   * 合同名称
+   */
+  contractName?: string;
+
+  /**
+   * 合同金额
+   */
+  contractAmount?: string;
+
+  /**
+   * 客服负责人
+   */
+  customerLeader?: string;
+
+  /**
+   * 项目负责人
+   */
+  projectLeader?: string;
+
+  /**
+   * 合同类型
+   */
+  contractType?: string;
+
+  /**
+   * 合同开始时间
+   */
+  startTime?: string;
+
+  /**
+   * 合同结束时间
+   */
+  endTime?: string;
+
+  /**
+   * 合同上传时间
+   */
+  uploadTime?: string;
+
+  /**
+   * 结算方式
+   */
+  settlementType?: string;
+
+  /**
+   * 合同状态
+   */
+  contractStatus?: string;
+
+  /**
+   * 审批状态(如:0-待审批,1-已通过,2-已驳回等)
+   */
+  approvalStatus?: string;
+
+  /**
+   * 附件路径或信息
+   */
+  annex?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 16 - 0
src/api/customer/customerFile/customerContact/types.ts

@@ -14,6 +14,8 @@ export interface CustomerContactVO {
    */
   contactName: string;
 
+  customLoginName: string;
+
   /**
    * 手机号码
    */
@@ -39,11 +41,15 @@ export interface CustomerContactVO {
    */
   roleId: string | number;
 
+  deptId: string | number;
+
   /**
    * 是否主联系人:0=是,1=否
    */
   isPrimary: string;
 
+  birthday: Date;
+
   /**
    * 详细地址
    */
@@ -96,6 +102,8 @@ export interface CustomerContactForm extends BaseEntity {
    */
   contactName?: string;
 
+  customLoginName?: string;
+
   /**
    * 手机号码
    */
@@ -121,11 +129,17 @@ export interface CustomerContactForm extends BaseEntity {
    */
   roleId?: string | number;
 
+  deptId?: string | number;
+
+  email: string;
+
   /**
    * 是否主联系人:0=是,1=否
    */
   isPrimary?: string;
 
+  birthday?: Date;
+
   /**
    * 详细地址
    */
@@ -178,6 +192,8 @@ export interface CustomerContactQuery extends PageQuery {
    */
   phone?: string;
 
+  email?: string;
+
   /**
    * 办公电话
    */

+ 59 - 0
src/api/customer/customerFile/customerInfo/index.ts

@@ -61,3 +61,62 @@ export const delCustomerInfo = (id: string | number | Array<string | number>) =>
     method: 'delete'
   });
 };
+
+/**
+ * 状态修改
+ * @param id 客户id
+ * @param status 状态
+ */
+export function changeStatus(id: string, status: string) {
+  const data = {
+    id,
+    status
+  };
+  return request({
+    url: '/customer/customerInfo/changeStatus',
+    method: 'put',
+    data: data
+  });
+}
+
+/**
+ * 修改临时额度
+ * @param customerIds 客户id
+ * @param creditAmount 额度
+ */
+export function updateCreditAmount(customerIds: number[], creditAmount: number) {
+  const data = {
+    customerIds,
+    creditAmount
+  };
+  return request({
+    url: '/customer/customerInfo/updateCreditAmount',
+    method: 'put',
+    data: data
+  });
+}
+
+/**
+ * 修改临时额度
+ * @param customerIds 客户id
+ * @param tagIds 标签id
+ */
+export function setCustomerInfoTag(customerIds: number[], tagIds: number[]) {
+  const data = {
+    customerIds,
+    tagIds
+  };
+  return request({
+    url: '/customer/customerInfo/setCustomerInfoTag',
+    method: 'put',
+    data: data
+  });
+}
+
+export const listContractList = (query?: CustomerInfoQuery): AxiosPromise<CustomerInfoVO[]> => {
+  return request({
+    url: '/customer/customerInfo/contractList',
+    method: 'get',
+    params: query
+  });
+};

+ 25 - 3
src/api/customer/customerFile/customerInfo/types.ts

@@ -20,10 +20,15 @@ export interface CustomerInfoVO {
   belongCompanyId: string | number;
 
   /**
-   * 客户名称
+   * 公司名称
    */
   companyName: string;
 
+  /**
+   * 客户名称
+   */
+  customerName: string;
+
   /**
    * 工商名称
    */
@@ -171,10 +176,15 @@ export interface CustomerInfoForm extends BaseEntity {
   belongCompanyId?: string | number;
 
   /**
-   * 客户名称
+   * 公司名称
    */
   companyName?: string;
 
+  /**
+   * 客户名称
+   */
+  customerName?: string;
+
   /**
    * 工商名称
    */
@@ -313,10 +323,15 @@ export interface CustomerInfoQuery extends PageQuery {
   belongCompanyId?: string | number;
 
   /**
-   * 客户名称
+   * 公司名称
    */
   companyName?: string;
 
+  /**
+   * 客户名称
+   */
+  customerName?: string;
+
   /**
    * 工商名称
    */
@@ -352,6 +367,11 @@ export interface CustomerInfoQuery extends PageQuery {
    */
   customerLevelId?: string | number;
 
+  salesPersonId?: string | number;
+
+  serviceStaffId?: string | number;
+
+  belongingDepartmentId?: string | number;
   /**
    * 固定电话
    */
@@ -422,6 +442,8 @@ export interface CustomerInfoQuery extends PageQuery {
    */
   platformCode?: string;
 
+  customerTag?: string;
+
   /**
    * 日期范围参数
    */

+ 77 - 0
src/api/customer/customerFile/purchaseHabit/index.ts

@@ -0,0 +1,77 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { PurchaseHabitVO, PurchaseHabitForm, PurchaseHabitQuery } from '@/api/customer/customerFile/purchaseHabit/types';
+
+/**
+ * 查询客户采购习惯列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listPurchaseHabit = (query?: PurchaseHabitQuery): AxiosPromise<PurchaseHabitVO[]> => {
+  return request({
+    url: '/customer/purchaseHabit/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询客户采购习惯详细
+ * @param id
+ */
+export const getPurchaseHabit = (id: string | number): AxiosPromise<PurchaseHabitVO> => {
+  return request({
+    url: '/customer/purchaseHabit/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增客户采购习惯
+ * @param data
+ */
+export const addPurchaseHabit = (data: PurchaseHabitForm) => {
+  return request({
+    url: '/customer/purchaseHabit',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改客户采购习惯
+ * @param data
+ */
+export const updatePurchaseHabit = (data: PurchaseHabitForm) => {
+  return request({
+    url: '/customer/purchaseHabit',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除客户采购习惯
+ * @param id
+ */
+export const delPurchaseHabit = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/customer/purchaseHabit/' + id,
+    method: 'delete'
+  });
+};
+
+/**
+ * 新增客户采购习惯
+ * @param data
+ */
+export const getCustomerPurchaseHabitData = (customerNo: string | number): AxiosPromise<PurchaseHabitVO> => {
+  return request({
+    url: '/customer/purchaseHabit/getCustomerPurchaseHabitData',
+    method: 'get',
+    params: {
+      customerNo: customerNo
+    }
+  });
+};

+ 245 - 0
src/api/customer/customerFile/purchaseHabit/types.ts

@@ -0,0 +1,245 @@
+export interface PurchaseHabitVO {
+  /**
+   * ID
+   */
+  id: string | number;
+
+  /**
+   * 客户编号
+   */
+  customerNo: string;
+
+  /**
+   * 月度采购量
+   */
+  monthPurchase: number;
+
+  /**
+   * 年度采购量
+   */
+  yearPurchase: number;
+
+  /**
+   * 常驻负责人/对接人
+   */
+  permanentOfficer: string;
+
+  /**
+   * 选择的机型/型号
+   */
+  choiceModel: string;
+
+  /**
+   * 打印量
+   */
+  printAmount: string;
+
+  /**
+   * 是否购买原装耗材(0-是,1-否)
+   */
+  buyOriginal: string;
+
+  /**
+   * 是否购买技术服务(0-是,1-否)
+   */
+  technologyService: string;
+
+  /**
+   * 采购品类(主选项)
+   */
+  purchaseCategory: string;
+
+  /**
+   * 采购品类(其他补充)
+   */
+  otherCategory: string;
+
+  /**
+   * 适用场景(主选项)
+   */
+  adaptScene: string;
+
+  /**
+   * 适用场景(其他补充)
+   */
+  otherScene: string;
+
+  /**
+   * 定制化需求(主选项)
+   */
+  customizeDemand: string;
+
+  /**
+   * 定制化需求(其他补充)
+   */
+  otherCustomize: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+}
+
+export interface PurchaseHabitForm extends BaseEntity {
+  /**
+   * ID
+   */
+  id?: string | number;
+
+  /**
+   * 客户编号
+   */
+  customerNo?: string;
+
+  /**
+   * 月度采购量
+   */
+  monthPurchase?: number;
+
+  /**
+   * 年度采购量
+   */
+  yearPurchase?: number;
+
+  /**
+   * 常驻负责人/对接人
+   */
+  permanentOfficer?: string;
+
+  /**
+   * 选择的机型/型号
+   */
+  choiceModel?: string;
+
+  /**
+   * 打印量
+   */
+  printAmount?: string;
+
+  /**
+   * 是否购买原装耗材(0-是,1-否)
+   */
+  buyOriginal?: string;
+
+  /**
+   * 是否购买技术服务(0-是,1-否)
+   */
+  technologyService?: string;
+
+  /**
+   * 采购品类(主选项)
+   */
+  purchaseCategory?: string;
+
+  /**
+   * 采购品类(其他补充)
+   */
+  otherCategory?: string;
+
+  /**
+   * 适用场景(主选项)
+   */
+  adaptScene?: string;
+
+  /**
+   * 适用场景(其他补充)
+   */
+  otherScene?: string;
+
+  /**
+   * 定制化需求(主选项)
+   */
+  customizeDemand?: string;
+
+  /**
+   * 定制化需求(其他补充)
+   */
+  otherCustomize?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+}
+
+export interface PurchaseHabitQuery extends PageQuery {
+  /**
+   * 客户编号
+   */
+  customerNo?: string;
+
+  /**
+   * 月度采购量
+   */
+  monthPurchase?: number;
+
+  /**
+   * 年度采购量
+   */
+  yearPurchase?: number;
+
+  /**
+   * 常驻负责人/对接人
+   */
+  permanentOfficer?: string;
+
+  /**
+   * 选择的机型/型号
+   */
+  choiceModel?: string;
+
+  /**
+   * 打印量
+   */
+  printAmount?: string;
+
+  /**
+   * 是否购买原装耗材(0-是,1-否)
+   */
+  buyOriginal?: string;
+
+  /**
+   * 是否购买技术服务(0-是,1-否)
+   */
+  technologyService?: string;
+
+  /**
+   * 采购品类(主选项)
+   */
+  purchaseCategory?: string;
+
+  /**
+   * 采购品类(其他补充)
+   */
+  otherCategory?: string;
+
+  /**
+   * 适用场景(主选项)
+   */
+  adaptScene?: string;
+
+  /**
+   * 适用场景(其他补充)
+   */
+  otherScene?: string;
+
+  /**
+   * 定制化需求(主选项)
+   */
+  customizeDemand?: string;
+
+  /**
+   * 定制化需求(其他补充)
+   */
+  otherCustomize?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 80 - 0
src/api/customer/customerFile/shippingAddress/index.ts

@@ -0,0 +1,80 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ShippingAddressVO, ShippingAddressForm, ShippingAddressQuery } from '@/api/customer/customerFile/shippingAddress/types';
+
+/**
+ * 查询客户收货地址列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listShippingAddress = (query?: ShippingAddressQuery): AxiosPromise<ShippingAddressVO[]> => {
+  return request({
+    url: '/customer/shippingAddress/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询客户收货地址详细
+ * @param id
+ */
+export const getShippingAddress = (id: string | number): AxiosPromise<ShippingAddressVO> => {
+  return request({
+    url: '/customer/shippingAddress/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增客户收货地址
+ * @param data
+ */
+export const addShippingAddress = (data: ShippingAddressForm) => {
+  return request({
+    url: '/customer/shippingAddress',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改客户收货地址
+ * @param data
+ */
+export const updateShippingAddress = (data: ShippingAddressForm) => {
+  return request({
+    url: '/customer/shippingAddress',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除客户收货地址
+ * @param id
+ */
+export const delShippingAddress = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/customer/shippingAddress/' + id,
+    method: 'delete'
+  });
+};
+
+/**
+ * 设置默认地址
+ * @param id
+ * @param defaultAddress 状态
+ */
+export function changeDefaultAddress(id: string | number, defaultAddress: string) {
+  const data = {
+    id,
+    defaultAddress
+  };
+  return request({
+    url: '/customer/shippingAddress/changeDefaultAddress',
+    method: 'put',
+    data: data
+  });
+}

+ 290 - 0
src/api/customer/customerFile/shippingAddress/types.ts

@@ -0,0 +1,290 @@
+export interface ShippingAddressVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 客户id
+   */
+  customerId: string | number;
+
+  /**
+   * 收货地址编号
+   */
+  shippingAddressNo: string;
+
+  /**
+   * 收货人姓名
+   */
+  consignee: string;
+
+  /**
+   * 部门编号
+   */
+  deptId: string | number;
+
+  /**
+   * 联系电话
+   */
+  phone: string;
+
+  /**
+   * 详细地址
+   */
+  address: string;
+
+  /**
+   * 邮政编码
+   */
+  postal: string;
+
+  /**
+   * 是否默认地址(0-是,1-否)
+   */
+  defaultAddress: string;
+
+  /**
+   * 地址标签
+   */
+  addressLabel: string;
+
+  /**
+   * 省份编码
+   */
+  provincialNo: string;
+
+  /**
+   * 城市编码
+   */
+  cityNo: string;
+
+  /**
+   * 区县编码
+   */
+  countryNo: string;
+
+  /**
+   * 区县编码
+   */
+  provincialCityCountry: string;
+
+  /**
+   * 推送状态(0-已推送,1-未推送等)
+   */
+  pushStatus: string;
+
+  /**
+   * 序号/排序号
+   */
+  num: number;
+
+  /**
+   * 部门名称
+   */
+  deptName: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+}
+
+export interface ShippingAddressForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 客户id
+   */
+  customerId?: string | number;
+
+  /**
+   * 收货地址编号
+   */
+  shippingAddressNo?: string;
+
+  /**
+   * 收货人姓名
+   */
+  consignee?: string;
+
+  /**
+   * 部门编号
+   */
+  deptId?: string | number;
+
+  /**
+   * 联系电话
+   */
+  phone?: string;
+
+  /**
+   * 详细地址
+   */
+  address?: string;
+
+  /**
+   * 邮政编码
+   */
+  postal?: string;
+
+  /**
+   * 是否默认地址(0-是,1-否)
+   */
+  defaultAddress?: string;
+
+  /**
+   * 地址标签
+   */
+  addressLabel?: string;
+
+  /**
+   * 省份编码
+   */
+  provincialNo?: string;
+
+  /**
+   * 城市编码
+   */
+  cityNo?: string;
+
+  /**
+   * 区县编码
+   */
+  countryNo?: string;
+
+  /**
+   * 区县编码
+   */
+  provincialCityCountry?: string;
+
+  /**
+   * 推送状态(0-已推送,1-未推送等)
+   */
+  pushStatus?: string;
+
+  /**
+   * 序号/排序号
+   */
+  num?: number;
+
+  /**
+   * 部门名称
+   */
+  deptName?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+}
+
+export interface ShippingAddressQuery extends PageQuery {
+  /**
+   * 客户id
+   */
+  customerId?: string | number;
+
+  /**
+   * 收货地址编号
+   */
+  shippingAddressNo?: string;
+
+  /**
+   * 收货人姓名
+   */
+  consignee?: string;
+
+  /**
+   * 部门编号
+   */
+  deptId?: string | number;
+
+  /**
+   * 联系电话
+   */
+  phone?: string;
+
+  /**
+   * 详细地址
+   */
+  address?: string;
+
+  /**
+   * 邮政编码
+   */
+  postal?: string;
+
+  /**
+   * 是否默认地址(0-是,1-否)
+   */
+  defaultAddress?: string;
+
+  /**
+   * 地址标签
+   */
+  addressLabel?: string;
+
+  /**
+   * 省份编码
+   */
+  provincialNo?: string;
+
+  /**
+   * 城市编码
+   */
+  cityNo?: string;
+
+  /**
+   * 区县编码
+   */
+  countryNo?: string;
+
+  /**
+   * 区县编码
+   */
+  provincialCityCountry?: string;
+
+  /**
+   * 推送状态(0-已推送,1-未推送等)
+   */
+  pushStatus?: string;
+
+  /**
+   * 序号/排序号
+   */
+  num?: number;
+
+  /**
+   * 部门名称
+   */
+  deptName?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 63 - 0
src/api/customer/maintenanceType/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { MaintenanceTypeVO, MaintenanceTypeForm, MaintenanceTypeQuery } from '@/api/customer/maintenanceType/types';
+
+/**
+ * 查询维保类型列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listMaintenanceType = (query?: MaintenanceTypeQuery): AxiosPromise<MaintenanceTypeVO[]> => {
+  return request({
+    url: '/customer/maintenanceType/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询维保类型详细
+ * @param id
+ */
+export const getMaintenanceType = (id: string | number): AxiosPromise<MaintenanceTypeVO> => {
+  return request({
+    url: '/customer/maintenanceType/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增维保类型
+ * @param data
+ */
+export const addMaintenanceType = (data: MaintenanceTypeForm) => {
+  return request({
+    url: '/customer/maintenanceType',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改维保类型
+ * @param data
+ */
+export const updateMaintenanceType = (data: MaintenanceTypeForm) => {
+  return request({
+    url: '/customer/maintenanceType',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除维保类型
+ * @param id
+ */
+export const delMaintenanceType = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/customer/maintenanceType/' + id,
+    method: 'delete'
+  });
+};

+ 65 - 0
src/api/customer/maintenanceType/types.ts

@@ -0,0 +1,65 @@
+export interface MaintenanceTypeVO {
+  /**
+   * ID
+   */
+  id: string | number;
+
+  /**
+   * 类型名称
+   */
+  typeName: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+}
+
+export interface MaintenanceTypeForm extends BaseEntity {
+  /**
+   * ID
+   */
+  id?: string | number;
+
+  /**
+   * 类型名称
+   */
+  typeName?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+}
+
+export interface MaintenanceTypeQuery extends PageQuery {
+  /**
+   * 类型名称
+   */
+  typeName?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 63 - 0
src/api/customer/serverItem/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ServerItemVO, ServerItemForm, ServerItemQuery } from '@/api/customer/serverItem/types';
+
+/**
+ * 查询维保服务内容列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listServerItem = (query?: ServerItemQuery): AxiosPromise<ServerItemVO[]> => {
+  return request({
+    url: '/customer/serverItem/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询维保服务内容详细
+ * @param id
+ */
+export const getServerItem = (id: string | number): AxiosPromise<ServerItemVO> => {
+  return request({
+    url: '/customer/serverItem/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增维保服务内容
+ * @param data
+ */
+export const addServerItem = (data: ServerItemForm) => {
+  return request({
+    url: '/customer/serverItem',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改维保服务内容
+ * @param data
+ */
+export const updateServerItem = (data: ServerItemForm) => {
+  return request({
+    url: '/customer/serverItem',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除维保服务内容
+ * @param id
+ */
+export const delServerItem = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/customer/serverItem/' + id,
+    method: 'delete'
+  });
+};

+ 65 - 0
src/api/customer/serverItem/types.ts

@@ -0,0 +1,65 @@
+export interface ServerItemVO {
+  /**
+   * ID
+   */
+  id: string | number;
+
+  /**
+   * 服务内容
+   */
+  itemName: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+}
+
+export interface ServerItemForm extends BaseEntity {
+  /**
+   * ID
+   */
+  id?: string | number;
+
+  /**
+   * 服务内容
+   */
+  itemName?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+}
+
+export interface ServerItemQuery extends PageQuery {
+  /**
+   * 服务内容
+   */
+  itemName?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 63 - 0
src/api/customer/serverTime/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ServerTimeVO, ServerTimeForm, ServerTimeQuery } from '@/api/customer/serverTime/types';
+
+/**
+ * 查询维保服务时间列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listServerTime = (query?: ServerTimeQuery): AxiosPromise<ServerTimeVO[]> => {
+  return request({
+    url: '/customer/serverTime/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询维保服务时间详细
+ * @param id
+ */
+export const getServerTime = (id: string | number): AxiosPromise<ServerTimeVO> => {
+  return request({
+    url: '/customer/serverTime/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增维保服务时间
+ * @param data
+ */
+export const addServerTime = (data: ServerTimeForm) => {
+  return request({
+    url: '/customer/serverTime',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改维保服务时间
+ * @param data
+ */
+export const updateServerTime = (data: ServerTimeForm) => {
+  return request({
+    url: '/customer/serverTime',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除维保服务时间
+ * @param id
+ */
+export const delServerTime = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/customer/serverTime/' + id,
+    method: 'delete'
+  });
+};

+ 65 - 0
src/api/customer/serverTime/types.ts

@@ -0,0 +1,65 @@
+export interface ServerTimeVO {
+  /**
+   * ID
+   */
+  id: string | number;
+
+  /**
+   * 时间类型
+   */
+  timeName: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+}
+
+export interface ServerTimeForm extends BaseEntity {
+  /**
+   * ID
+   */
+  id?: string | number;
+
+  /**
+   * 时间类型
+   */
+  timeName?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+}
+
+export interface ServerTimeQuery extends PageQuery {
+  /**
+   * 时间类型
+   */
+  timeName?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 0 - 1
src/views/customer/customerCategory/customerTag/index.vue

@@ -14,7 +14,6 @@
 
       <el-table v-loading="loading" border :data="customerTagList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
-        <el-table-column label="ID" align="center" prop="id" v-if="true" />
         <el-table-column label="标签名称" align="center" prop="tagName" />
         <el-table-column label="匹配商品标签" align="center" prop="productTagIds">
           <template #default="scope">

+ 238 - 0
src/views/customer/customerFile/customerContract/customerContractInfo.vue

@@ -0,0 +1,238 @@
+<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="contractType">
+              <el-select
+                v-model="queryParams.contractType"
+                placeholder="请选择合同类型"
+                clearable
+                filterable
+                style="width: 200px"
+                @keyup.enter="handleQuery"
+              >
+                <el-option v-for="dict in contract_type" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="合同名称" prop="contractName">
+              <el-input v-model="queryParams.contractName" placeholder="请输入合同名称" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+
+            <el-form-item label="状态" prop="contractStatus">
+              <el-select
+                v-model="queryParams.contractStatus"
+                placeholder="请选择状态"
+                clearable
+                filterable
+                style="width: 200px"
+                @keyup.enter="handleQuery"
+              >
+                <el-option v-for="dict in contract_status" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </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>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="20"><span>合同详情信息列表</span> </el-col>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" border :data="contractList">
+        <el-table-column label="合同编号" align="center" prop="contractNo" />
+        <el-table-column label="合同名称" align="center" prop="contractName" />
+        <el-table-column label="合同类型" align="center" prop="contractType">
+          <template #default="scope">
+            <dict-tag :options="contract_type" :value="scope.row.contractType" />
+          </template>
+        </el-table-column>
+        <el-table-column label="金额(万)" align="center" prop="contractAmount" />
+        <el-table-column label="起始时间" align="center" prop="startTime" width="180">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="截至时间" align="center" prop="endTime" width="180">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="上传时间" align="center" prop="uploadTime" width="180">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.uploadTime, '{y}-{m}-{d}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="项目负责人" align="center" prop="projectLeader" width="180" />
+        <el-table-column label="附件管理" align="center" prop="annex">
+          <template>
+            <span>下载</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" align="center" prop="contractStatus" width="150">
+          <template #default="scope">
+            <dict-tag :options="contract_status" :value="scope.row.contractStatus" />
+          </template>
+        </el-table-column>
+
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-button link type="primary" icon="Edit" @click="handleManage(scope.row)" v-hasPermi="['customer:contract:edit']">管理</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>
+  </div>
+</template>
+
+<script setup name="CustomerContractInfo" lang="ts">
+import {} from '@/api/customer/customerFile/customerInfo';
+import { CustomerInfoQuery, CustomerInfoForm } from '@/api/customer/customerFile/customerInfo/types';
+import { listContract } from '@/api/customer/customerFile/contract';
+import { ContractVO, ContractQuery, ContractForm } from '@/api/customer/customerFile/contract/types';
+import { listIndustryCategory } from '@/api/customer/customerCategory/industryCategory';
+import { IndustryCategoryVO } from '@/api/customer/customerCategory/industryCategory/types';
+const router = useRouter();
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { contract_type, contract_status } = toRefs<any>(proxy?.useDict('contract_type', 'contract_status'));
+const industryCategoryList = ref<IndustryCategoryVO[]>([]);
+const contractList = ref<any[]>([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const total = ref(0);
+
+const queryFormRef = ref<ElFormInstance>();
+const maintainInfoFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: CustomerInfoForm = {
+  id: undefined,
+  customerNo: undefined,
+  belongCompanyId: undefined,
+  companyName: undefined,
+  customerName: undefined,
+  businessCustomerName: undefined,
+  shortName: undefined,
+  invoiceTypeId: undefined,
+  enterpriseScaleId: undefined,
+  customerTypeId: undefined,
+  industryCategoryId: undefined,
+  customerLevelId: undefined,
+  landline: undefined,
+  fax: undefined,
+  url: undefined,
+  postCode: undefined,
+  validityFromDate: undefined,
+  validityToDate: undefined,
+  invoiceTop: undefined,
+  address: undefined,
+  regProvincialNo: undefined,
+  regCityNo: undefined,
+  regCountyNo: undefined,
+  provincialCityCounty: undefined,
+  status: undefined,
+  remark: undefined
+};
+const route = useRoute();
+
+const data = reactive<any>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    customerId: undefined,
+    customerNo: undefined,
+    customerName: undefined,
+    contractType: undefined,
+    contractStatus: undefined,
+    contractName: undefined,
+    industryCategoryId: undefined,
+    belongingDepartmentId: undefined
+  }
+}) as any;
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询维保记录列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listContract(queryParams.value);
+  contractList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+//管理按钮
+const handleManage = (row: any) => {
+  router.push({
+    path: '/customer/customer-file/customer-create-contract',
+    query: {
+      id: row.id
+    }
+  });
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  maintainInfoFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 加载行业分类列表 */
+const loadIndustryCategories = async () => {
+  try {
+    const res = await listIndustryCategory(); // 只获取启用状态的行业
+    industryCategoryList.value = res.rows;
+  } catch (error) {
+    console.error('加载行业分类失败:', error);
+  }
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  // 重置时保留customerId
+  const customerId = queryParams.value.customerId;
+  queryParams.value.customerNo = undefined;
+  queryParams.value.customerName = undefined;
+  queryParams.value.industryCategoryId = undefined;
+  queryParams.value.belongingDepartmentId = undefined;
+  queryParams.value.customerId = customerId;
+  handleQuery();
+};
+
+onMounted(() => {
+  // 从路由获取customerId
+  if (route.query.customerId) {
+    queryParams.value.customerId = route.query.customerId as string | number;
+  }
+  getList();
+  loadIndustryCategories();
+});
+</script>

+ 245 - 0
src/views/customer/customerFile/customerContract/customerCreateContract.vue

@@ -0,0 +1,245 @@
+<template>
+  <div class="p-4">
+    <!-- 页面标题和返回按钮 -->
+    <div class="flex items-center gap-2 mb-4">
+      <el-button icon="ArrowLeft" @click="handleBack">返回</el-button>
+      <span class="text-lg font-medium">基本设置</span>
+    </div>
+
+    <el-card shadow="never" v-loading="loading">
+      <el-form ref="contractFormRef" :model="form" :rules="rules" label-width="100px">
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="合同类型" prop="contractType">
+              <el-select v-model="form.contractType" placeholder="请选择合同类型" class="w-full">
+                <el-option v-for="dict in contract_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="客户编号" prop="customerNo">
+              <el-input v-model="form.customerNo" disabled />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="合同编号" prop="contractNo">
+              <el-input v-model="form.contractNo" disabled />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="合同名称" prop="contractName">
+              <el-input v-model="form.contractName" placeholder="请输入合同名称" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="我方负责人" prop="projectLeader">
+              <el-input v-model="form.projectLeader" placeholder="请输入我方负责人" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="起始时间" prop="startTime">
+              <el-date-picker
+                v-model="form.startTime"
+                type="date"
+                value-format="YYYY-MM-DD"
+                placeholder="请选择起始时间"
+                class="w-full"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="截止时间" prop="endTime">
+              <el-date-picker
+                v-model="form.endTime"
+                type="date"
+                value-format="YYYY-MM-DD"
+                placeholder="请选择截止时间"
+                class="w-full"
+                style="width: 100%"
+                :disabled-date="disabledEndDate"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="上传时间" prop="uploadTime">
+              <el-date-picker
+                v-model="form.uploadTime"
+                type="date"
+                value-format="YYYY-MM-DD"
+                placeholder="请选择"
+                class="w-full"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="合同金额" prop="contractAmount">
+              <el-input v-model="form.contractAmount" placeholder="请输入合同金额" style="width: 95%" />万元
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="附件管理" prop="annex">
+              <el-upload></el-upload>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 保存按钮 -->
+        <el-row :gutter="20">
+          <el-col :span="24" class="text-left">
+            <el-button :loading="buttonLoading" type="primary" @click="submitForm">提交</el-button>
+          </el-col>
+        </el-row>
+      </el-form>
+    </el-card>
+  </div>
+</template>
+<script setup name="CustomerCreateContract" lang="ts">
+import { listContract, getContract, delContract, addContract, updateContract } from '@/api/customer/customerFile/contract';
+import { ContractVO, ContractQuery, ContractForm } from '@/api/customer/customerFile/contract/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { contract_type } = toRefs<any>(proxy?.useDict('contract_type'));
+const route = useRoute();
+const router = useRouter();
+
+const contractFormRef = ref<ElFormInstance>();
+const buttonLoading = ref(false);
+const loading = ref(false);
+const contractId = ref<string | number>('');
+
+const initFormData: ContractForm = {
+  id: undefined,
+  customerNo: undefined,
+  customerId: undefined,
+  customerName: undefined,
+  contractNo: undefined,
+  contractName: undefined,
+  contractAmount: undefined,
+  customerLeader: undefined,
+  projectLeader: undefined,
+  contractType: undefined,
+  startTime: undefined,
+  endTime: undefined,
+  uploadTime: undefined,
+  settlementType: undefined,
+  contractStatus: undefined,
+  approvalStatus: undefined,
+  annex: undefined,
+  remark: undefined
+};
+const data = reactive<PageData<ContractForm, ContractQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    customerNo: undefined,
+    customerId: undefined,
+    contractNo: undefined,
+    contractName: undefined,
+    contractAmount: undefined,
+    customerLeader: undefined,
+    projectLeader: undefined,
+    contractType: undefined,
+    startTime: undefined,
+    endTime: undefined,
+    uploadTime: undefined,
+    settlementType: undefined,
+    contractStatus: undefined,
+    approvalStatus: undefined,
+    annex: undefined,
+    platformCode: undefined,
+    params: {}
+  },
+  rules: {
+    contractName: [{ required: true, message: '合同名称不能为空', trigger: 'blur' }],
+    contractType: [{ required: true, message: '合同类型不能为空', trigger: 'change' }],
+    contractAmount: [{ required: true, message: '合同金额不能为空', trigger: 'blur' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 禁用截止日期:只能选择起始日期之后的日期 */
+const disabledEndDate = (time: Date) => {
+  if (!form.value.startTime) {
+    return false;
+  }
+  const startDate = new Date(form.value.startTime);
+  return time.getTime() < startDate.getTime();
+};
+
+/** 返回按钮 */
+const handleBack = () => {
+  router.back();
+};
+
+/** 加载合同数据 */
+const loadContractData = async () => {
+  if (!contractId.value) return;
+
+  try {
+    loading.value = true;
+    const res = await getContract(contractId.value);
+    if (res.data) {
+      Object.assign(form.value, res.data);
+    }
+  } catch (error) {
+    console.error('加载合同数据失败:', error);
+    proxy?.$modal.msgError('加载合同数据失败');
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 提交表单 */
+const submitForm = () => {
+  contractFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      try {
+        if (form.value.id) {
+          await updateContract(form.value);
+          proxy?.$modal.msgSuccess('保存成功');
+        } else {
+          await addContract(form.value);
+          proxy?.$modal.msgSuccess('创建成功');
+        }
+        // 保存成功后返回
+        router.back();
+      } catch (error) {
+        console.error('保存失败:', error);
+        proxy?.$modal.msgError('保存失败,请重试');
+      } finally {
+        buttonLoading.value = false;
+      }
+    }
+  });
+};
+
+onMounted(() => {
+  // 从路由获取合同ID(编辑模式)
+  if (route.query.id) {
+    contractId.value = route.query.id as string | number;
+    loadContractData();
+  }
+  // 从路由获取客户信息(新增模式)
+  if (route.query.customerNo) {
+    form.value.customerNo = route.query.customerNo as string;
+  }
+  if (route.query.customerId) {
+    form.value.customerId = route.query.customerId as string | number;
+  }
+  if (route.query.customerName) {
+    form.value.customerName = route.query.customerName as string;
+  }
+});
+</script>

+ 186 - 0
src/views/customer/customerFile/customerContract/index.vue

@@ -0,0 +1,186 @@
+<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="customerNo">
+              <el-input v-model="queryParams.customerNo" placeholder="请输入客户编号" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="客户名称" prop="customerNo">
+              <el-input v-model="queryParams.customerName" placeholder="请输入客户名称" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="归属部门" prop="belongingDepartmentId">
+              <el-select v-model="queryParams.belongingDepartmentId" placeholder="请选择归属部门" clearable filterable style="width: 200px">
+                <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-form-item label="所属行业" prop="industryCategoryId">
+              <el-select v-model="queryParams.industryCategoryId" placeholder="请选择行业" clearable filterable style="width: 200px">
+                <el-option v-for="industry in industryCategoryList" :key="industry.id" :label="industry.industryCategoryName" :value="industry.id" />
+              </el-select>
+            </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>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="20"><span>合同管理信息列表</span> </el-col>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" border :data="contractList">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="客户编号" align="center" prop="customerNo" />
+        <el-table-column label="客户名称" align="center" prop="customerName" />
+        <el-table-column label="归属部门" align="center" prop="deptName" />
+        <el-table-column label="所属行业" align="center" prop="industryCategory" />
+        <el-table-column label="合同数量" align="center" prop="contractCount" />
+        <el-table-column label="生效合同" align="center" prop="effectiveContract" />
+        <el-table-column label="失效合同" align="center" prop="invalidContract" />
+        <el-table-column label="作废合同" align="center" prop="voidedContract" />
+
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-button link type="primary" icon="Edit" @click="handleManage(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>
+  </div>
+</template>
+
+<script setup name="Contracts" lang="ts">
+import { listContractList } from '@/api/customer/customerFile/customerInfo';
+import { CustomerInfoQuery, CustomerInfoForm } from '@/api/customer/customerFile/customerInfo/types';
+import { listIndustryCategory } from '@/api/customer/customerCategory/industryCategory';
+import { IndustryCategoryVO } from '@/api/customer/customerCategory/industryCategory/types';
+import { info } from 'console';
+const router = useRouter();
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const industryCategoryList = ref<IndustryCategoryVO[]>([]);
+const contractList = ref<any[]>([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const total = ref(0);
+
+const queryFormRef = ref<ElFormInstance>();
+const maintainInfoFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: CustomerInfoForm = {
+  id: undefined,
+  customerNo: undefined,
+  belongCompanyId: undefined,
+  companyName: undefined,
+  customerName: undefined,
+  businessCustomerName: undefined,
+  shortName: undefined,
+  invoiceTypeId: undefined,
+  enterpriseScaleId: undefined,
+  customerTypeId: undefined,
+  industryCategoryId: undefined,
+  customerLevelId: undefined,
+  landline: undefined,
+  fax: undefined,
+  url: undefined,
+  postCode: undefined,
+  validityFromDate: undefined,
+  validityToDate: undefined,
+  invoiceTop: undefined,
+  address: undefined,
+  regProvincialNo: undefined,
+  regCityNo: undefined,
+  regCountyNo: undefined,
+  provincialCityCounty: undefined,
+  status: undefined,
+  remark: undefined
+};
+const data = reactive<any>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    customerNo: undefined,
+    customerName: undefined,
+    industryCategoryId: undefined,
+    belongingDepartmentId: undefined
+  }
+}) as any;
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询维保记录列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listContractList(queryParams.value);
+  contractList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+//管理按钮
+const handleManage = (row: any) => {
+  router.push({
+    path: '/customer/customer-file/customer-contract-info',
+    query: {
+      customerId: row.customerId
+    }
+  });
+};
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  maintainInfoFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 加载行业分类列表 */
+const loadIndustryCategories = async () => {
+  try {
+    const res = await listIndustryCategory(); // 只获取启用状态的行业
+    industryCategoryList.value = res.rows;
+  } catch (error) {
+    console.error('加载行业分类失败:', error);
+  }
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  queryParams.value = {};
+  handleQuery();
+};
+
+onMounted(() => {
+  getList();
+  loadIndustryCategories();
+});
+</script>

+ 10 - 12
src/views/customer/customerFile/customerInfo/add.vue

@@ -20,8 +20,8 @@
             </el-form-item>
           </el-col>
           <el-col :span="8">
-            <el-form-item label="客户名称" prop="companyName">
-              <el-input v-model="form.companyName" placeholder="请输入客户名称" />
+            <el-form-item label="客户名称" prop="customerName">
+              <el-input v-model="form.customerName" placeholder="请输入客户名称" />
             </el-form-item>
           </el-col>
           <el-col :span="8">
@@ -59,9 +59,7 @@
           <el-col :span="8">
             <el-form-item label="客户类别" prop="customerTypeId">
               <el-select v-model="form.customerTypeId" placeholder="请选择客户类别" class="w-full">
-                <el-option label="重点客户" value="1" />
-                <el-option label="普通客户" value="2" />
-                <el-option label="潜在客户" value="3" />
+                <el-option v-for="dict in customer_type" :key="dict.value" :label="dict.label" :value="dict.value" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -75,10 +73,7 @@
           <el-col :span="8">
             <el-form-item label="客户等级" prop="customerLevelId">
               <el-select v-model="form.customerLevelId" placeholder="请选择客户等级" class="w-full">
-                <el-option label="A级" value="1" />
-                <el-option label="B级" value="2" />
-                <el-option label="C级" value="3" />
-                <el-option label="D级" value="4" />
+                <el-option v-for="dict in customer_level" :key="dict.value" :label="dict.label" :value="dict.value" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -110,12 +105,12 @@
           </el-col>
           <el-col :span="8">
             <el-form-item label="开始时间" prop="validityFromDate">
-              <el-date-picker v-model="form.validityFromDate" type="date" placeholder="请选择开始时间" class="w-full" />
+              <el-date-picker v-model="form.validityFromDate" type="date" placeholder="请选择开始时间" class="w-full" style="width: 100%" />
             </el-form-item>
           </el-col>
           <el-col :span="8">
             <el-form-item label="结束时间" prop="validityToDate">
-              <el-date-picker v-model="form.validityToDate" type="date" placeholder="请选择结束时间" class="w-full" />
+              <el-date-picker v-model="form.validityToDate" type="date" placeholder="请选择结束时间" class="w-full" style="width: 100%" />
             </el-form-item>
           </el-col>
         </el-row>
@@ -283,7 +278,7 @@
           </el-col>
           <el-col :span="8">
             <el-form-item label="所属部门" prop="belongingDepartmentId">
-              <el-select v-model="salesForm.belongingDepartmentId" placeholder="请选择所属部门" class="w-full" filterable>
+              <el-select v-model="salesForm.belongingDepartmentId" placeholder="请选择所属部门" class="w-full" filterable disabled>
                 <el-option label="销售部" value="1" />
                 <el-option label="市场部" value="2" />
                 <el-option label="客服部" value="3" />
@@ -344,6 +339,9 @@ import { listIndustryCategory } from '@/api/customer/customerCategory/industryCa
 import type { EnterpriseScaleVO } from '@/api/customer/customerCategory/enterpriseScale/types';
 import type { IndustryCategoryVO } from '@/api/customer/customerCategory/industryCategory/types';
 
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { customer_type, customer_level } = toRefs<any>(proxy?.useDict('customer_type', 'customer_level'));
+
 const route = useRoute();
 const router = useRouter();
 

+ 8 - 5
src/views/customer/customerFile/customerInfo/components/addContactDialog.vue

@@ -40,9 +40,7 @@
         <el-col :span="12">
           <el-form-item label="性别" prop="gender">
             <el-radio-group v-model="form.gender">
-              <el-radio value="">保密</el-radio>
-              <el-radio value="0">男</el-radio>
-              <el-radio value="1">女</el-radio>
+              <el-radio v-for="dict in sys_user_sex" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
             </el-radio-group>
           </el-form-item>
         </el-col>
@@ -52,8 +50,7 @@
         <el-col :span="24">
           <el-form-item label="主联系人" prop="isPrimary">
             <el-radio-group v-model="form.isPrimary">
-              <el-radio value="0">是</el-radio>
-              <el-radio value="1">否</el-radio>
+              <el-radio v-for="dict in sys_platform_yes_no" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
             </el-radio-group>
           </el-form-item>
         </el-col>
@@ -63,6 +60,8 @@
         <el-col :span="24">
           <el-form-item label="详细地址" prop="addressDetail">
             <el-cascader v-model="codeArr" :options="regionData" placeholder="请选择" @change="handleChange" style="width: 100%"></el-cascader>
+          </el-form-item>
+          <el-form-item prop="addressDetail">
             <el-input v-model="form.addressDetail" type="textarea" :rows="3" placeholder="请输入详细地址" />
           </el-form-item>
         </el-col>
@@ -81,6 +80,8 @@
 <script setup lang="ts">
 import type { CustomerContactForm } from '@/api/customer/customerFile/customerContact/types';
 import { regionData } from 'element-china-area-data';
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { sys_platform_yes_no, sys_user_sex } = toRefs<any>(proxy?.useDict('sys_platform_yes_no', 'sys_user_sex'));
 interface Props {
   modelValue: boolean;
   editData?: CustomerContactForm;
@@ -112,6 +113,8 @@ const form = reactive<CustomerContactForm>({
   officePhoneTwo: '',
   gender: '',
   roleId: undefined,
+  deptId: undefined,
+  email: '',
   isPrimary: '1',
   addressDetail: '',
   addressProvince: '',

+ 99 - 1
src/views/customer/customerFile/customerInfo/edit.vue

@@ -1 +1,99 @@
-<template>测试</template>
+<template>
+  <div class="p-4">
+    <!-- 页面标题和按钮 -->
+    <div class="flex justify-between items-center mb-4">
+      <div class="flex items-center gap-2">
+        <el-button icon="ArrowLeft" @click="router.back()">返回</el-button>
+        <span class="text-lg font-medium">客户详情</span>
+      </div>
+    </div>
+
+    <!-- 菜单导航 -->
+    <el-card shadow="never" class="mb-4">
+      <el-menu :default-active="activeMenu" class="el-menu-demo" mode="horizontal" @select="handleSelect">
+        <el-menu-item index="baseInfo">基本信息</el-menu-item>
+        <el-menu-item index="orgStructure">企业组织结构</el-menu-item>
+        <el-menu-item index="contactInfo">联系人信息</el-menu-item>
+        <el-menu-item index="shippingAddress">收货地址</el-menu-item>
+        <el-menu-item index="contractManagement">合同管理</el-menu-item>
+        <el-menu-item index="procurementProfile">采购画像</el-menu-item>
+        <el-menu-item index="operationLog">日志管理</el-menu-item>
+      </el-menu>
+      <!-- 动态组件显示区域 -->
+      <component :is="currentComponent" :customer-id="customerId" :customer-no="customerNo" :customer-name="customerName" />
+    </el-card>
+  </div>
+</template>
+
+<script setup lang="ts" name="CustomerInfoEdit">
+const route = useRoute();
+const router = useRouter();
+
+const activeMenu = ref('baseInfo');
+const customerId = ref<string>('');
+const customerNo = ref<string>('');
+const customerName = ref<string>('');
+
+// 异步加载子组件
+const BaseInfo = defineAsyncComponent(() => import('@/views/customer/customerFile/customerInfo/overview/baseInfo.vue'));
+const OrgStructure = defineAsyncComponent(() => import('@/views/customer/customerFile/customerInfo/overview/orgStructure.vue'));
+const ContactInfo = defineAsyncComponent(() => import('@/views/customer/customerFile/customerInfo/overview/contactInfo.vue'));
+const ShippingAddress = defineAsyncComponent(() => import('@/views/customer/customerFile/customerInfo/overview/shippingAddress.vue'));
+const ContractManagement = defineAsyncComponent(() => import('@/views/customer/customerFile/customerInfo/overview/contractManagement.vue'));
+const ProcurementProfile = defineAsyncComponent(() => import('@/views/customer/customerFile/customerInfo/overview/procurementProfile.vue'));
+const OperationLog = defineAsyncComponent(() => import('@/views/customer/customerFile/customerInfo/overview/operationLog.vue'));
+
+// 组件映射
+const componentMap: Record<string, any> = {
+  baseInfo: BaseInfo,
+  orgStructure: OrgStructure,
+  contactInfo: ContactInfo,
+  shippingAddress: ShippingAddress,
+  contractManagement: ContractManagement,
+  procurementProfile: ProcurementProfile,
+  operationLog: OperationLog
+};
+
+const currentComponent = ref(componentMap['baseInfo']); // 默认显示基本信息
+
+// 菜单选择处理
+const handleSelect = (key: string) => {
+  activeMenu.value = key;
+  currentComponent.value = componentMap[key] || componentMap['baseInfo'];
+};
+
+// 初始化
+onMounted(async () => {
+  const id = route.query.id;
+  if (id) {
+    customerId.value = id as string;
+    // 加载客户基本信息获取客户编号
+    await loadCustomerNo(id as string);
+  }
+
+  // 如果URL中有tab参数,则显示对应的tab
+  const tab = route.query.tab;
+  if (tab && componentMap[tab as string]) {
+    activeMenu.value = tab as string;
+    currentComponent.value = componentMap[tab as string];
+  }
+});
+
+// 加载客户编号
+const loadCustomerNo = async (id: string) => {
+  try {
+    const { getCustomerInfo } = await import('@/api/customer/customerFile/customerInfo');
+    const res = await getCustomerInfo(id);
+    customerNo.value = res.data.customerNo || '';
+    customerName.value = res.data.customerName || '';
+  } catch (error) {
+    console.error('加载客户编号失败:', error);
+  }
+};
+</script>
+
+<style scoped>
+.el-menu-demo {
+  border-bottom: none;
+}
+</style>

+ 247 - 30
src/views/customer/customerFile/customerInfo/index.vue

@@ -15,24 +15,16 @@
               <el-input v-model="queryParams.customerNo" placeholder="请输入客户编码" clearable style="width: 200px" @keyup.enter="handleQuery" />
             </el-form-item>
             <el-form-item label="客户名称" prop="companyName">
-              <el-input v-model="queryParams.companyName" placeholder="请输入客户名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
+              <el-input v-model="queryParams.customerName" placeholder="请输入客户名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
             </el-form-item>
             <el-form-item label="客户等级" prop="customerLevelId">
               <el-select v-model="queryParams.customerLevelId" placeholder="请选择客户等级" clearable style="width: 200px">
-                <el-option label="A级" value="1" />
-                <el-option label="B级" value="2" />
-                <el-option label="C级" value="3" />
-                <el-option label="D级" value="4" />
+                <el-option v-for="dict in customer_level" :key="dict.value" :label="dict.label" :value="dict.value" />
               </el-select>
             </el-form-item>
             <el-form-item label="行业" prop="industryCategoryId">
               <el-select v-model="queryParams.industryCategoryId" placeholder="请选择行业" clearable filterable style="width: 200px">
-                <el-option label="制造业" value="1" />
-                <el-option label="服务业" value="2" />
-                <el-option label="贸易" value="3" />
-                <el-option label="科技" value="4" />
-                <el-option label="金融" value="5" />
-                <el-option label="其他" value="6" />
+                <el-option v-for="industry in industryCategoryList" :key="industry.id" :label="industry.industryCategoryName" :value="industry.id" />
               </el-select>
             </el-form-item>
             <el-form-item label="业务员" prop="salesPersonId">
@@ -61,18 +53,13 @@
             </el-form-item>
             <el-form-item label="客户标签" prop="customerTag">
               <el-select v-model="queryParams.customerTag" placeholder="请选择客户标签" clearable filterable style="width: 200px">
-                <el-option label="VIP客户" value="1" />
-                <el-option label="重点客户" value="2" />
-                <el-option label="优质客户" value="3" />
-                <el-option label="普通客户" value="4" />
-                <el-option label="潜在客户" value="5" />
+                <el-option v-for="tag in customerTagList" :key="tag.id" :label="tag.tagName" :value="tag.id" />
               </el-select>
             </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" plain icon="Plus" @click="handleAdd" v-hasPermi="['customer:customerInfo:add']">新增</el-button>
             </el-form-item>
           </el-form>
         </el-card>
@@ -82,15 +69,24 @@
     <el-card shadow="never">
       <template #header>
         <el-row :gutter="10" class="mb8">
-          <el-col :span="20"><span>客户信息列表</span> </el-col>
+          <el-col :span="17"><span>客户信息列表</span> </el-col>
           <el-col :span="1.5">
-            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['customer:customerInfo:edit']"
-              >修改</el-button
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['customer:customerInfo:add']">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="primary" plain :disabled="single" v-hasPermi="['customer:customerInfo:edit']">转移业务员</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="primary" plain :disabled="single" v-hasPermi="['customer:customerInfo:edit']">转移业务员</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="primary" plain :disabled="single" @click="updateCreditAmountBtn" v-hasPermi="['customer:customerInfo:edit']"
+              >修改临时额度</el-button
             >
           </el-col>
           <el-col :span="1.5">
-            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['customer:customerInfo:remove']"
-              >删除</el-button
+            <el-button type="primary" plain :disabled="single" @click="setCustomerInfoTagBtn" v-hasPermi="['customer:customerInfo:edit']"
+              >设置标签</el-button
             >
           </el-col>
           <el-col :span="1.5">
@@ -101,7 +97,7 @@
 
       <el-table v-loading="loading" border :data="customerInfoList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
-        <el-table-column label="客户名称" align="center" prop="companyName" min-width="200" show-overflow-tooltip />
+        <el-table-column label="客户名称" align="center" prop="customerName" min-width="200" show-overflow-tooltip />
         <el-table-column label="归属公司" align="center" prop="belongCompanyId" min-width="150">
           <template #default="scope">
             <span>{{ getCompanyName(scope.row.belongCompanyId) }}</span>
@@ -128,30 +124,92 @@
         <el-table-column label="信用额" align="center" prop="creditAmount" min-width="80"> </el-table-column>
         <el-table-column label="状态" align="center" prop="status" min-width="80">
           <template #default="scope">
-            <el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">
-              {{ scope.row.status === '0' ? '正常' : '停用' }}
-            </el-tag>
+            <dict-tag :options="sys_check_status" :value="scope.row.status" />
           </template>
         </el-table-column>
         <el-table-column label="操作" align="center" width="150" fixed="right">
           <template #default="scope">
             <el-button link type="primary" @click="handleUpdate(scope.row)" v-hasPermi="['customer:customerInfo:edit']">编辑</el-button>
-            <el-button link type="danger" @click="handleDelete(scope.row)" v-hasPermi="['customer:customerInfo:remove']">删除</el-button>
+            <el-button link type="primary" v-if="scope.row.status == '0'" @click="handleCheck(scope.row)" v-hasPermi="['customer:customerInfo:edit']"
+              >审核</el-button
+            >
+            <!-- <el-button link type="danger" @click="handleDelete(scope.row)" v-hasPermi="['customer:customerInfo:remove']">删除</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 title="审核" v-model="checkDialog.visible" width="500px" append-to-body>
+      <el-form ref="checkFormRef" :model="checkForm" :rules="checkRules" label-width="100px">
+        <el-form-item label="审核结果" prop="status">
+          <el-select v-model="checkForm.status" placeholder="请选择" class="w-full">
+            <el-option v-for="dict in filteredCheckStatus" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitCheck">确认</el-button>
+          <el-button @click="checkDialog.visible = false">取消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 修改临时额度对话框 -->
+    <el-dialog title="修改临时额度" v-model="creditDialog.visible" width="500px" append-to-body>
+      <el-form ref="creditFormRef" :model="creditForm" :rules="creditRules" label-width="100px">
+        <el-form-item label="临时额度" prop="temporaryAmount">
+          <el-input v-model="creditForm.temporaryAmount" placeholder="请输入临时额度" class="w-full" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitCreditAmount">确认</el-button>
+          <el-button @click="creditDialog.visible = false">取消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 设置标签对话框 -->
+    <el-dialog title="设置标签" v-model="tagDialog.visible" width="500px" append-to-body>
+      <el-checkbox-group v-model="selectedTags">
+        <el-checkbox v-for="tag in customerTagList" :key="tag.id" :value="tag.id">{{ tag.tagName }}</el-checkbox>
+      </el-checkbox-group>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitTags">确认</el-button>
+          <el-button @click="tagDialog.visible = false">取消</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="CustomerInfo" lang="ts">
-import { listCustomerInfo, getCustomerInfo, delCustomerInfo, addCustomerInfo, updateCustomerInfo } from '@/api/customer/customerFile/customerInfo';
+import {
+  listCustomerInfo,
+  getCustomerInfo,
+  delCustomerInfo,
+  addCustomerInfo,
+  updateCustomerInfo,
+  changeStatus,
+  updateCreditAmount,
+  setCustomerInfoTag
+} from '@/api/customer/customerFile/customerInfo';
 import { CustomerInfoVO, CustomerInfoQuery, CustomerInfoForm } from '@/api/customer/customerFile/customerInfo/types';
+import { listCustomerTag } from '@/api/customer/customerCategory/customerTag';
+import { CustomerTagVO } from '@/api/customer/customerCategory/customerTag/types';
+import { listIndustryCategory } from '@/api/customer/customerCategory/industryCategory';
+import { IndustryCategoryVO } from '@/api/customer/customerCategory/industryCategory/types';
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { sys_check_status, customer_type, customer_level } = toRefs<any>(proxy?.useDict('sys_check_status', 'customer_type', 'customer_level'));
 const router = useRouter();
 const customerInfoList = ref<CustomerInfoVO[]>([]);
+const customerTagList = ref<CustomerTagVO[]>([]);
+const industryCategoryList = ref<IndustryCategoryVO[]>([]);
 const buttonLoading = ref(false);
 const loading = ref(true);
 const showSearch = ref(true);
@@ -168,11 +226,67 @@ const dialog = reactive<DialogOption>({
   title: ''
 });
 
+// 审核对话框
+const checkDialog = reactive<DialogOption>({
+  visible: false,
+  title: '审核'
+});
+
+// 审核表单
+const checkForm = reactive({
+  customerId: undefined,
+  status: undefined
+});
+
+const checkFormRef = ref<ElFormInstance>();
+
+// 审核表单验证规则
+const checkRules = {
+  status: [{ required: true, message: '请选择审核结果', trigger: 'change' }]
+};
+
+// 过滤后的审核状态(去掉待审核)
+const filteredCheckStatus = computed(() => {
+  return sys_check_status.value?.filter((item: any) => item.value !== '0') || [];
+});
+
+// 修改临时额度对话框
+const creditDialog = reactive<DialogOption>({
+  visible: false,
+  title: '修改临时额度'
+});
+
+// 临时额度表单
+const creditForm = reactive({
+  customerIds: undefined,
+  temporaryAmount: undefined
+});
+
+const creditFormRef = ref<ElFormInstance>();
+
+// 临时额度表单验证规则
+const creditRules = {
+  temporaryAmount: [
+    { required: true, message: '请输入临时额度', trigger: 'blur' },
+    { pattern: /^\d+(\.\d{1,2})?$/, message: '请输入有效的金额', trigger: 'blur' }
+  ]
+};
+
+// 设置标签对话框
+const tagDialog = reactive<DialogOption>({
+  visible: false,
+  title: '设置标签'
+});
+
+// 选中的标签
+const selectedTags = ref<Array<string | number>>([]);
+
 const initFormData: CustomerInfoForm = {
   id: undefined,
   customerNo: undefined,
   belongCompanyId: undefined,
   companyName: undefined,
+  customerName: undefined,
   businessCustomerName: undefined,
   shortName: undefined,
   invoiceTypeId: undefined,
@@ -203,6 +317,7 @@ const data = reactive<PageData<CustomerInfoForm, CustomerInfoQuery>>({
     belongCompanyId: undefined,
     customerNo: undefined,
     companyName: undefined,
+    customerName: undefined,
     customerLevelId: undefined,
     industryCategoryId: undefined,
     salesPersonId: undefined,
@@ -302,7 +417,7 @@ const resetQuery = () => {
 /** 多选框选中数据 */
 const handleSelectionChange = (selection: CustomerInfoVO[]) => {
   ids.value = selection.map((item) => item.id);
-  single.value = selection.length != 1;
+  single.value = selection.length < 1;
   multiple.value = !selection.length;
 };
 
@@ -315,13 +430,93 @@ const handleAdd = () => {
 
 /** 修改按钮操作 */
 const handleUpdate = async (row?: CustomerInfoVO) => {
-  const _id = row?.customerNo || ids.value[0];
+  const _id = row?.id || ids.value[0];
   router.push({
     path: '/customer/customer-overview',
     query: { id: _id, status: 'edit' }
   });
 };
 
+/** 审核按钮操作 */
+const handleCheck = (row: CustomerInfoVO) => {
+  checkForm.customerId = row.id;
+  checkForm.status = undefined;
+  checkFormRef.value?.resetFields();
+  checkDialog.visible = true;
+};
+
+/** 提交审核 */
+const submitCheck = () => {
+  checkFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      try {
+        await changeStatus(checkForm.customerId, checkForm.status);
+        proxy?.$modal.msgSuccess('审核成功');
+        checkDialog.visible = false;
+        await getList();
+      } catch (error) {
+        proxy?.$modal.msgError('审核失败,请重试');
+      }
+    }
+  });
+};
+
+/** 修改临时额度按钮操作 */
+const updateCreditAmountBtn = () => {
+  if (ids.value.length !== 1) {
+    proxy?.$modal.msgWarning('请选择一条记录');
+    return;
+  }
+  creditForm.customerIds = ids;
+  creditForm.temporaryAmount = undefined;
+  creditFormRef.value?.resetFields();
+  creditDialog.visible = true;
+};
+
+/** 提交临时额度 */
+const submitCreditAmount = () => {
+  creditFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      try {
+        await updateCreditAmount(creditForm.customerIds, creditForm.temporaryAmount);
+        proxy?.$modal.msgSuccess('修改成功');
+        creditDialog.visible = false;
+        await getList();
+      } catch (error) {
+        console.error('修改临时额度失败:', error);
+        proxy?.$modal.msgError('修改失败,请重试');
+      }
+    }
+  });
+};
+/** 设置标签操作 */
+const setCustomerInfoTagBtn = () => {
+  if (ids.value.length === 0) {
+    proxy?.$modal.msgWarning('请至少选择一条记录');
+    return;
+  }
+  // 清空之前选中的标签
+  selectedTags.value = [];
+  tagDialog.visible = true;
+};
+
+/** 提交标签 */
+const submitTags = async () => {
+  if (selectedTags.value.length === 0) {
+    proxy?.$modal.msgWarning('请至少选择一个标签');
+    return;
+  }
+  try {
+    await setCustomerInfoTag(ids.value as number[], selectedTags.value as number[]);
+    proxy?.$modal.msgSuccess('设置成功');
+    tagDialog.visible = false;
+    await getList();
+  } catch (error) {
+    console.error('设置标签失败:', error);
+    proxy?.$modal.msgError('设置失败,请重试');
+  }
+};
+
 /** 提交按钮 */
 const submitForm = () => {
   customerInfoFormRef.value?.validate(async (valid: boolean) => {
@@ -359,7 +554,29 @@ const handleExport = () => {
   );
 };
 
+/** 加载客户标签列表 */
+const loadCustomerTags = async () => {
+  try {
+    const res = await listCustomerTag({ status: '0' }); // 只获取启用状态的标签
+    customerTagList.value = res.rows;
+  } catch (error) {
+    console.error('加载客户标签失败:', error);
+  }
+};
+
+/** 加载行业分类列表 */
+const loadIndustryCategories = async () => {
+  try {
+    const res = await listIndustryCategory({ status: '0' }); // 只获取启用状态的行业
+    industryCategoryList.value = res.rows;
+  } catch (error) {
+    console.error('加载行业分类失败:', error);
+  }
+};
+
 onMounted(() => {
   getList();
+  loadCustomerTags();
+  loadIndustryCategories();
 });
 </script>

+ 815 - 0
src/views/customer/customerFile/customerInfo/overview/baseInfo.vue

@@ -0,0 +1,815 @@
+<template>
+  <div class="p-4">
+    <!-- 企业基本信息 -->
+    <el-card shadow="never" class="mb-4">
+      <template #header>
+        <div class="flex justify-between items-center">
+          <span class="font-medium"
+            >企业基本信息 / <span style="color: #ff0033">客户编号:{{ customerNumber }}</span></span
+          >
+          <el-button type="primary" @click="handleSave">保存</el-button>
+        </div>
+      </template>
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="140px">
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="所属公司" prop="belongCompanyId">
+              <el-select v-model="form.belongCompanyId" placeholder="请选择所属公司" class="w-full" filterable>
+                <el-option label="公司A" value="1" />
+                <el-option label="公司B" value="2" />
+                <el-option label="公司C" value="3" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="客户名称" prop="customerName">
+              <el-input v-model="form.customerName" placeholder="请输入客户名称" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="工商名称" prop="businessCustomerName">
+              <el-input v-model="form.businessCustomerName" placeholder="请输入工商名称" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="企业简称" prop="shortName">
+              <el-input v-model="form.shortName" placeholder="请输入企业简称" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="开票类型" prop="invoiceTypeId">
+              <el-select v-model="form.invoiceTypeId" placeholder="请选择开票类型" class="w-full">
+                <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="8">
+            <el-form-item label="企业规模" prop="enterpriseScaleId">
+              <el-select v-model="form.enterpriseScaleId" placeholder="请选择企业规模" class="w-full" filterable>
+                <el-option v-for="item in enterpriseScaleList" :key="item.id" :label="item.enterpriseScaleName" :value="item.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="客户类别" prop="customerTypeId">
+              <el-select v-model="form.customerTypeId" placeholder="请选择客户类别" class="w-full">
+                <el-option v-for="dict in customer_type" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="行业类别" prop="industryCategoryId">
+              <el-select v-model="form.industryCategoryId" placeholder="请选择行业类别" class="w-full" filterable>
+                <el-option v-for="item in industryCategoryList" :key="item.id" :label="item.industryCategoryName" :value="item.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="客户等级" prop="customerLevelId">
+              <el-select v-model="form.customerLevelId" placeholder="请选择客户等级" class="w-full">
+                <el-option v-for="dict in customer_level" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="固定电话" prop="landline">
+              <el-input v-model="form.landline" placeholder="请输入固定电话" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="传真" prop="fax">
+              <el-input v-model="form.fax" placeholder="请输入传真" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="网址" prop="url">
+              <el-input v-model="form.url" placeholder="请输入网址" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="邮政编码" prop="postCode">
+              <el-input v-model="form.postCode" placeholder="请输入邮政编码" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="开始时间" prop="validityFromDate">
+              <el-date-picker
+                v-model="form.validityFromDate"
+                type="date"
+                placeholder="请选择开始时间"
+                class="w-full"
+                value-format="YYYY-MM-DD"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="结束时间" prop="validityToDate">
+              <el-date-picker
+                v-model="form.validityToDate"
+                type="date"
+                placeholder="请选择结束时间"
+                class="w-full"
+                value-format="YYYY-MM-DD"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="发票抬头" prop="invoiceTop">
+              <el-input v-model="form.invoiceTop" placeholder="请输入发票抬头" disabled />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="办公地址">
+              <el-cascader v-model="codeArr" :options="regionData" placeholder="请选择" @change="handleChange" style="width: 100%"></el-cascader>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item prop="address">
+              <el-input v-model="form.address" placeholder="请输入详细地址" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </el-card>
+
+    <!-- 工商信息 -->
+    <el-card shadow="never" class="mb-4">
+      <template #header>
+        <div class="flex justify-between items-center">
+          <span class="font-medium">工商信息</span>
+          <el-button type="primary" @click="handleUpdate">更新</el-button>
+        </div>
+      </template>
+
+      <el-form :model="businessInfo" label-width="140px">
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="企业工商名称">
+              <el-input v-model="businessInfo.businessCustomerName" disabled />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="社会信用代码">
+              <el-input v-model="businessInfo.socialCreditCode" disabled />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="法人姓名">
+              <el-input v-model="businessInfo.legalPersonName" disabled />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="注册资本">
+              <el-input v-model="businessInfo.registeredCapital" disabled />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="登记机关">
+              <el-input v-model="businessInfo.registrationAuthority" disabled />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="成立日期">
+              <el-input v-model="businessInfo.establishmentDate" disabled />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="吊销日期">
+              <el-input v-model="businessInfo.revocationDate" disabled />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="登记状态">
+              <el-input v-model="businessInfo.registrationStatus" disabled />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="实缴资本">
+              <el-input v-model="businessInfo.paidInCapital" disabled />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="16">
+            <el-form-item label="详细地址">
+              <el-input v-model="businessInfo.businessAddress" disabled />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="营业执照">
+              <div class="flex items-center gap-2">
+                <el-upload
+                  ref="uploadRef"
+                  :action="uploadUrl"
+                  :headers="uploadHeaders"
+                  :on-success="handleUploadSuccess"
+                  :on-error="handleUploadError"
+                  :before-upload="beforeUpload"
+                  :show-file-list="false"
+                  accept="image/*,.pdf"
+                >
+                  <el-button icon="Plus" :loading="uploadLoading">上传</el-button>
+                </el-upload>
+                <span v-if="businessInfo.businessLicense" class="text-sm text-gray-500">{{ businessInfo.businessLicense }}</span>
+                <el-button v-if="businessInfo.businessLicense" link type="primary" @click="previewLicense">预览</el-button>
+              </div>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </el-card>
+
+    <!-- 销售信息 -->
+    <el-card shadow="never" class="mb-4">
+      <template #header>
+        <div class="flex justify-between items-center">
+          <span class="font-medium">销售信息</span>
+          <el-button type="primary" @click="handleSave">保存</el-button>
+        </div>
+      </template>
+      <el-form :model="salesForm" label-width="140px">
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="业务人员" prop="salesPersonId">
+              <el-select v-model="salesForm.salesPersonId" placeholder="请选择业务人员" class="w-full" filterable>
+                <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">
+            <el-form-item label="客服人员" prop="serviceStaffId">
+              <el-select v-model="salesForm.serviceStaffId" placeholder="请选择客服人员" class="w-full" filterable>
+                <el-option label="客服A" value="1" />
+                <el-option label="客服B" value="2" />
+                <el-option label="客服C" value="3" />
+                <el-option label="客服D" value="4" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="所属部门" prop="belongingDepartmentId">
+              <el-select v-model="salesForm.belongingDepartmentId" placeholder="请选择所属部门" class="w-full" filterable disabled>
+                <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-row>
+
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="信用等级" prop="creditManagement">
+              <el-select v-model="salesForm.creditManagement" placeholder="请选择信用等级" class="w-full">
+                <el-option label="A级" value="A" />
+                <el-option label="B级" value="B" />
+                <el-option label="C级" value="C" />
+                <el-option label="D级" value="D" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="信用额度" prop="creditAmount">
+              <el-input v-model="salesForm.creditAmount" :controls="false" class="w-full" placeholder="请输入信用额度" style="width: 100%" disabled />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="可用额度" prop="remainingQuota">
+              <el-input
+                v-model="salesForm.remainingQuota"
+                :controls="false"
+                class="w-full"
+                placeholder="请输入可用额度"
+                style="width: 100%"
+                disabled
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="订单审核" prop="orderAudit">
+              <el-select v-model="salesForm.orderAudit" placeholder="请选择" class="w-full" filterable>
+                <el-option v-for="dict in order_check_way" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="支付密码" prop="creditPaymentPassword">
+              <el-input v-model="salesForm.creditPaymentPassword" type="password" placeholder="请输入支付密码" show-password />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="收款条件" prop="accountPeriod">
+              <el-input v-model="salesForm.accountPeriod" placeholder="请输入收款条件" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="结算方式" prop="payDays">
+              <el-select v-model="salesForm.creditManagement" placeholder="请选择结算方式" class="w-full"> </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </el-card>
+
+    <!-- 公司开票信息 -->
+    <el-card shadow="never" class="mb-4">
+      <template #header>
+        <div class="flex justify-between items-center">
+          <span class="font-medium">企业开票信息</span>
+          <el-button type="primary" @click="handleAddInvoice">新增</el-button>
+        </div>
+      </template>
+      <el-table :data="invoiceList" border>
+        <el-table-column type="index" label="序号" align="center" width="60" />
+        <el-table-column label="是否主账号" align="center" prop="isPrimaryAccount" min-width="120">
+          <template #default="{ row }">
+            <span>{{ row.isPrimaryAccount === '0' ? '是' : '否' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="开户行名称" align="center" prop="bankName" min-width="180" />
+        <el-table-column label="银行账户" align="center" prop="bankAccount" min-width="180" />
+        <el-table-column label="操作" align="center" width="150" fixed="right">
+          <template #default="{ row, $index }">
+            <el-button link type="primary" @click="handleEditInvoice(row, $index)">编辑</el-button>
+            <el-button link type="danger" @click="removeInvoice($index)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+
+    <!-- 添加/编辑开票信息对话框 -->
+    <add-invoice-dialog v-model="invoiceDialogVisible" :edit-data="currentInvoice" @confirm="handleInvoiceConfirm" />
+  </div>
+</template>
+
+<script setup lang="ts" name="BaseInfo">
+import { getCustomerInfo, updateCustomerInfo } from '@/api/customer/customerFile/customerInfo';
+import type { CustomerInfoForm } from '@/api/customer/customerFile/customerInfo/types';
+import type { BusinessInfoForm } from '@/api/customer/customerFile/businessInfo/types';
+import type { SalesInfoForm } from '@/api/customer/customerFile/salesInfo/types';
+import type { InvoiceInfoForm } from '@/api/customer/customerFile/invoiceInfo/types';
+import AddInvoiceDialog from '../components/addInvoiceDialog.vue';
+import { regionData } from 'element-china-area-data';
+import { listEnterpriseScale } from '@/api/customer/customerCategory/enterpriseScale';
+import { listIndustryCategory } from '@/api/customer/customerCategory/industryCategory';
+import type { EnterpriseScaleVO } from '@/api/customer/customerCategory/enterpriseScale/types';
+import type { IndustryCategoryVO } from '@/api/customer/customerCategory/industryCategory/types';
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { order_check_way, customer_type, customer_level } = toRefs<any>(proxy?.useDict('order_check_way', 'customer_type', 'customer_level'));
+// 接收父组件传递的props
+const props = defineProps<{
+  customerId?: string | number;
+  customerNo?: string;
+}>();
+
+const route = useRoute();
+const router = useRouter();
+
+const formRef = ref<any>(null);
+const submitLoading = ref(false);
+const customerId = ref<string | number>('');
+const customerNumber = ref('');
+const codeArr = ref([]);
+
+// 下拉框数据列表
+const enterpriseScaleList = ref<EnterpriseScaleVO[]>([]);
+const industryCategoryList = ref<IndustryCategoryVO[]>([]);
+
+// 企业基本信息(用于显示)
+const customerInfo = reactive<any>({
+  customerNo: '',
+  belongCompanyName: '',
+  companyName: '',
+  businessCustomerName: '',
+  shortName: '',
+  invoiceTypeName: '',
+  enterpriseScale: '',
+  customerTypeName: '',
+  industryCategory: '',
+  customerLevelName: '',
+  landline: '',
+  fax: '',
+  url: '',
+  postCode: '',
+  validityFromDate: '',
+  validityToDate: '',
+  invoiceTop: '',
+  provincialCityCounty: '',
+  address: ''
+});
+
+// 企业基本信息表单(用于编辑)
+const form = reactive<CustomerInfoForm>({
+  id: undefined,
+  customerNo: '',
+  belongCompanyId: undefined,
+  companyName: '',
+  businessCustomerName: '',
+  shortName: '',
+  invoiceTypeId: undefined,
+  enterpriseScaleId: undefined,
+  customerTypeId: undefined,
+  industryCategoryId: undefined,
+  customerLevelId: undefined,
+  landline: '',
+  fax: '',
+  url: '',
+  postCode: '',
+  validityFromDate: undefined,
+  validityToDate: undefined,
+  invoiceTop: '',
+  address: '',
+  regProvincialNo: '',
+  regCityNo: '',
+  regCountyNo: '',
+  provincialCityCounty: '',
+  status: '0',
+  remark: ''
+});
+
+// 工商信息(只读)
+const businessInfo = reactive<BusinessInfoForm>({
+  businessCustomerName: '',
+  socialCreditCode: '',
+  legalPersonName: '',
+  registeredCapital: '',
+  registrationAuthority: '',
+  establishmentDate: '',
+  revocationDate: '',
+  registrationStatus: '',
+  paidInCapital: undefined,
+  businessAddress: '',
+  businessLicense: '',
+  status: '0'
+});
+
+// 销售信息
+const salesForm = reactive<SalesInfoForm>({
+  salesPersonId: undefined,
+  serviceStaffId: undefined,
+  belongingDepartmentId: undefined,
+  creditManagement: undefined,
+  creditAmount: undefined,
+  remainingQuota: undefined,
+  orderAudit: undefined,
+  creditPaymentPassword: '',
+  accountPeriod: '',
+  payDays: undefined,
+  status: '0'
+});
+
+// 开票信息列表
+const invoiceList = ref<InvoiceInfoForm[]>([]);
+const invoiceDialogVisible = ref(false);
+const currentInvoice = ref<InvoiceInfoForm | undefined>(undefined);
+const currentInvoiceIndex = ref<number>(-1);
+
+// 营业执照上传相关
+const uploadRef = ref();
+const uploadLoading = ref(false);
+const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/common/upload';
+const uploadHeaders = ref({
+  Authorization: 'Bearer ' + localStorage.getItem('token')
+});
+
+// 表单验证规则
+const rules = {
+  companyName: [{ required: true, message: '请输入客户名称', trigger: 'blur' }]
+};
+
+// 初始化
+onMounted(async () => {
+  // 加载下拉框数据
+  await loadEnterpriseScaleList();
+  await loadIndustryCategoryList();
+
+  // 优先使用props传递的customerId,否则从路由获取
+  const id = props.customerId || route.query.id;
+  if (id) {
+    customerId.value = id as string;
+    await loadCustomerData(id as string);
+  }
+});
+
+// 监听props变化
+watch(
+  () => props.customerId,
+  (newId) => {
+    if (newId) {
+      customerId.value = newId;
+      loadCustomerData(newId);
+    }
+  }
+);
+
+// 加载企业规模列表
+const loadEnterpriseScaleList = async () => {
+  try {
+    const res = await listEnterpriseScale();
+    enterpriseScaleList.value = res.rows || [];
+  } catch (error) {
+    console.error('加载企业规模列表失败:', error);
+  }
+};
+
+// 加载行业类别列表
+const loadIndustryCategoryList = async () => {
+  try {
+    const res = await listIndustryCategory();
+    industryCategoryList.value = res.rows || [];
+  } catch (error) {
+    console.error('加载行业类别列表失败:', error);
+  }
+};
+
+// 加载客户数据
+const loadCustomerData = async (id: string) => {
+  try {
+    const res = await getCustomerInfo(id);
+    const data = res.data;
+
+    // 填充基本信息
+    customerInfo.customerNo = data.customerNo || '';
+    customerInfo.belongCompanyName = getCompanyName(data.belongCompanyId);
+    customerInfo.companyName = data.companyName || '';
+    customerInfo.businessCustomerName = data.businessCustomerName || '';
+    customerInfo.shortName = data.shortName || '';
+    customerInfo.invoiceTypeName = getInvoiceTypeName(data.invoiceTypeId);
+    customerInfo.enterpriseScale = data.enterpriseScale || '';
+    customerInfo.customerTypeName = getCustomerTypeName(data.customerTypeId);
+    customerInfo.industryCategory = data.industryCategory || '';
+    customerInfo.customerLevelName = getCustomerLevelName(data.customerLevelId);
+    customerInfo.landline = data.landline || '';
+    customerInfo.fax = data.fax || '';
+    customerInfo.url = data.url || '';
+    customerInfo.postCode = data.postCode || '';
+    customerInfo.validityFromDate = data.validityFromDate || '';
+    customerInfo.validityToDate = data.validityToDate || '';
+    customerInfo.invoiceTop = data.invoiceTop || '';
+    customerInfo.provincialCityCounty = data.provincialCityCounty || '';
+    customerInfo.address = data.address || '';
+
+    // 填充工商信息
+    if (data.customerBusinessInfoVo) {
+      Object.assign(businessInfo, data.customerBusinessInfoVo);
+    }
+
+    // 填充表单数据(用于编辑)
+    Object.assign(form, data);
+    customerNumber.value = data.customerNo || '';
+
+    // 如果有省市区编码,回显到级联选择器
+    if (data.regProvincialNo && data.regCityNo && data.regCountyNo) {
+      codeArr.value = [data.regProvincialNo, data.regCityNo, data.regCountyNo] as any;
+    }
+
+    // 填充销售信息
+    if (data.customerSalesInfoVo) {
+      Object.assign(salesForm, data.customerSalesInfoVo);
+    }
+
+    // 填充开票信息列表
+    if (data.customerInvoiceInfoVoList) {
+      invoiceList.value = data.customerInvoiceInfoVoList;
+    }
+  } catch (error) {
+    console.error('加载客户数据失败:', error);
+    ElMessage.error('加载客户数据失败');
+  }
+};
+
+// 格式化方法
+const getCompanyName = (id: string | number | undefined) => {
+  const map: Record<string, string> = { '1': '公司A', '2': '公司B', '3': '公司C' };
+  return map[String(id)] || '-';
+};
+
+const getInvoiceTypeName = (id: string | number | undefined) => {
+  const map: Record<string, string> = {
+    '1': '增值税专用发票',
+    '2': '增值税普通发票',
+    '3': '电子发票'
+  };
+  return map[String(id)] || '-';
+};
+
+const getCustomerTypeName = (id: string | number | undefined) => {
+  const map: Record<string, string> = {
+    '1': '重点客户',
+    '2': '普通客户',
+    '3': '潜在客户'
+  };
+  return map[String(id)] || '-';
+};
+
+const getCustomerLevelName = (id: string | number | undefined) => {
+  const map: Record<string, string> = { '1': 'A级', '2': 'B级', '3': 'C级', '4': 'D级' };
+  return map[String(id)] || '-';
+};
+
+const getSalesPersonName = (id: string | number | undefined) => {
+  const map: Record<string, string> = { '1': '张三', '2': '李四', '3': '王五', '4': '赵六' };
+  return map[String(id)] || '-';
+};
+
+const getServiceStaffName = (id: string | number | undefined) => {
+  const map: Record<string, string> = { '1': '客服A', '2': '客服B', '3': '客服C', '4': '客服D' };
+  return map[String(id)] || '-';
+};
+
+const getDepartmentName = (id: string | number | undefined) => {
+  const map: Record<string, string> = { '1': '销售部', '2': '市场部', '3': '客服部', '4': '技术部' };
+  return map[String(id)] || '-';
+};
+
+// 处理区域选择变化
+const handleChange = (val: string[]) => {
+  // 保存编码
+  form.regProvincialNo = val[0];
+  form.regCityNo = val[1];
+  form.regCountyNo = val[2];
+
+  // 根据编码获取名称
+  const names: string[] = [];
+  if (val[0]) {
+    const province = regionData.find((item: any) => item.value === val[0]);
+    if (province) {
+      names.push(province.label);
+
+      if (val[1] && province.children) {
+        const city = province.children.find((item: any) => item.value === val[1]);
+        if (city) {
+          names.push(city.label);
+
+          if (val[2] && city.children) {
+            const county = city.children.find((item: any) => item.value === val[2]);
+            if (county) {
+              names.push(county.label);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // 将省市区名称用斜杠连接
+  form.provincialCityCounty = names.join('/');
+};
+
+// 打开添加开票信息对话框
+const handleAddInvoice = () => {
+  currentInvoice.value = undefined;
+  currentInvoiceIndex.value = -1;
+  invoiceDialogVisible.value = true;
+};
+
+//更新工商信息
+const handleUpdate = () => {
+  // 触发上传
+  uploadRef.value?.$el.querySelector('input[type="file"]')?.click();
+};
+
+// 上传前验证
+const beforeUpload = (file: File) => {
+  const isImage = file.type.startsWith('image/');
+  const isPDF = file.type === 'application/pdf';
+  const isLt10M = file.size / 1024 / 1024 < 10;
+
+  if (!isImage && !isPDF) {
+    ElMessage.error('只能上传图片或PDF文件!');
+    return false;
+  }
+  if (!isLt10M) {
+    ElMessage.error('文件大小不能超过 10MB!');
+    return false;
+  }
+  uploadLoading.value = true;
+  return true;
+};
+
+// 上传成功回调
+const handleUploadSuccess = (response: any) => {
+  uploadLoading.value = false;
+  if (response.code === 200) {
+    businessInfo.businessLicense = response.data.url || response.data.fileName;
+    ElMessage.success('上传成功');
+
+    // TODO: 这里可以调用OCR识别接口,自动填充工商信息
+    // 如果后端提供了OCR识别接口,可以在这里调用
+    // recognizeBusinessLicense(response.data.url);
+  } else {
+    ElMessage.error(response.msg || '上传失败');
+  }
+};
+
+// 上传失败回调
+const handleUploadError = () => {
+  uploadLoading.value = false;
+  ElMessage.error('上传失败,请重试');
+};
+
+// 预览营业执照
+const previewLicense = () => {
+  if (businessInfo.businessLicense) {
+    window.open(businessInfo.businessLicense, '_blank');
+  }
+};
+
+// 编辑开票信息
+const handleEditInvoice = (row: InvoiceInfoForm, index: number) => {
+  currentInvoice.value = { ...row };
+  currentInvoiceIndex.value = index;
+  invoiceDialogVisible.value = true;
+};
+
+// 确认添加/编辑开票信息
+const handleInvoiceConfirm = (data: InvoiceInfoForm) => {
+  if (currentInvoiceIndex.value >= 0) {
+    // 编辑
+    invoiceList.value[currentInvoiceIndex.value] = data;
+  } else {
+    // 新增
+    invoiceList.value.push(data);
+  }
+};
+
+// 删除开票信息
+const removeInvoice = (index: number) => {
+  ElMessageBox.confirm('确定要删除该开票信息吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(() => {
+      invoiceList.value.splice(index, 1);
+      ElMessage.success('删除成功');
+    })
+    .catch(() => {});
+};
+
+// 保存按钮
+const handleSave = async () => {
+  try {
+    await formRef.value?.validate();
+    submitLoading.value = true;
+
+    // 组装提交数据
+    const submitData: CustomerInfoForm = {
+      ...form,
+      customerBusinessBo: businessInfo,
+      customerSalesInfoBo: salesForm,
+      customerInvoiceInfoBoList: invoiceList.value
+    };
+
+    await updateCustomerInfo(submitData);
+    ElMessage.success('保存成功');
+
+    // 重新加载数据
+    await loadCustomerData(customerId.value);
+  } catch (error) {
+    console.error('保存失败:', error);
+    ElMessage.error('保存失败,请重试');
+  } finally {
+    submitLoading.value = false;
+  }
+};
+</script>

+ 386 - 0
src/views/customer/customerFile/customerInfo/overview/contactInfo.vue

@@ -0,0 +1,386 @@
+<template>
+  <div class="p-2">
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="22">
+            <span>联系人信息列表</span>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAdd">添加联系人</el-button>
+          </el-col>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" border :data="customerContactList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="用户ID" align="center" prop="customerId" />
+        <el-table-column label="A10标识" align="center" />
+        <el-table-column label="员工姓名" align="center" prop="contactName" />
+        <el-table-column label="性别" align="center" prop="gender">
+          <template #default="scope">
+            <dict-tag :options="sys_user_sex" :value="scope.row.gender" />
+          </template>
+        </el-table-column>
+        <el-table-column label="手机号" align="center" prop="phone" />
+        <el-table-column label="自定义登录名" align="center" prop="customLoginName" />
+        <el-table-column label="部门" align="center" prop="" />
+        <el-table-column label="采购角色" align="center" prop="roleName" />
+        <el-table-column label="状态" align="center" prop="status">
+          <template #default="scope">
+            <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
+          </template>
+        </el-table-column>
+        <el-table-column label="主联系人" align="center" prop="isPrimary">
+          <template #default="scope">
+            <dict-tag :options="sys_platform_yes_no" :value="scope.row.isPrimary" />
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
+            <el-button link type="primary" icon="Delete" @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 :title="dialog.title" v-model="dialog.visible" width="900px" append-to-body>
+      <el-form ref="customerContactFormRef" :model="form" :rules="rules" label-width="120px">
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="联系人姓名" prop="contactName">
+              <el-input v-model="form.contactName" placeholder="请输入联系人姓名" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="手机号码" prop="phone">
+              <el-input v-model="form.phone" placeholder="请输入手机号码" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="办公电话" prop="officePhone">
+              <el-input v-model="form.officePhone" placeholder="请输入办公电话" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="自定义登录名" prop="customLoginName">
+              <el-input v-model="form.customLoginName" placeholder="请输入自定义登录名" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="所属部门" prop="deptId">
+              <el-select v-model="form.deptId" placeholder="请选择" class="w-full">
+                <el-option label="部门1" value="1" />
+                <el-option label="部门2" value="2" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="邮箱" prop="email">
+              <el-input v-model="form.email" placeholder="请输入邮箱" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="采购角色" prop="roleId">
+              <el-input v-model="form.roleId" placeholder="请输入采购角色" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="生日" prop="birthday">
+              <el-date-picker v-model="form.birthday" type="date" placeholder="请选择" class="w-full" value-format="YYYY-MM-DD" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="性别" prop="gender">
+              <el-radio-group v-model="form.gender">
+                <el-radio v-for="dict in sys_user_sex" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="启用状态" prop="status">
+              <el-switch v-model="form.status" active-value="0" inactive-value="1" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="详细地址" prop="codeArr">
+              <el-cascader v-model="codeArr" :options="regionData" placeholder="请选择" @change="handleChange" style="width: 100%"></el-cascader>
+            </el-form-item>
+            <el-form-item prop="addressDetail">
+              <el-input v-model="form.addressDetail" type="textarea" :rows="3" placeholder="请输入详细地址" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" :loading="buttonLoading" @click="submitForm">确认</el-button>
+          <el-button @click="cancel">取消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="CustomerContact" lang="ts">
+import {
+  listCustomerContact,
+  getCustomerContact,
+  delCustomerContact,
+  addCustomerContact,
+  updateCustomerContact
+} from '@/api/customer/customerFile/customerContact';
+import { CustomerContactVO, CustomerContactQuery, CustomerContactForm } from '@/api/customer/customerFile/customerContact/types';
+import { regionData } from 'element-china-area-data';
+// 接收父组件传递的props
+const props = defineProps<{
+  customerId?: string;
+  customerNo?: string;
+}>();
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { sys_platform_yes_no, sys_user_sex, sys_normal_disable } = toRefs<any>(
+  proxy?.useDict('sys_platform_yes_no', 'sys_user_sex', 'sys_normal_disable')
+);
+
+const customerContactList = ref<CustomerContactVO[]>([]);
+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 customerContactFormRef = ref<ElFormInstance>();
+const codeArr = ref([]);
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: CustomerContactForm = {
+  id: undefined,
+  customerId: undefined,
+  contactName: undefined,
+  phone: undefined,
+  customLoginName: undefined,
+  officePhone: undefined,
+  officePhoneTwo: undefined,
+  gender: '2',
+  roleId: undefined,
+  deptId: undefined,
+  email: undefined,
+  birthday: undefined,
+  isPrimary: undefined,
+  addressDetail: undefined,
+  addressProvince: undefined,
+  addressCity: undefined,
+  addressCounty: undefined,
+  provincialCityCounty: undefined,
+  status: '0',
+  remark: undefined
+};
+const data = reactive<PageData<CustomerContactForm, CustomerContactQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    customerId: undefined,
+    contactName: undefined,
+    phone: undefined,
+    officePhone: undefined,
+    officePhoneTwo: undefined,
+    gender: undefined,
+    roleId: undefined,
+    isPrimary: undefined,
+    addressDetail: undefined,
+    addressProvince: undefined,
+    addressCity: undefined,
+    addressCounty: undefined,
+    provincialCityCounty: undefined,
+    status: undefined,
+    platformCode: undefined,
+    params: {}
+  },
+  rules: {
+    contactName: [{ required: true, message: '联系人姓名不能为空', trigger: 'blur' }],
+    phone: [{ required: true, message: '手机号码不能为空', trigger: 'blur' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询客户联系人信息列表 */
+const getList = async () => {
+  loading.value = true;
+  // 使用props传递的customerId进行查询
+  if (props.customerId) {
+    queryParams.value.customerId = props.customerId;
+  }
+  const res = await listCustomerContact(queryParams.value);
+  customerContactList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  codeArr.value = [];
+  customerContactFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 处理区域选择变化 */
+const handleChange = (val: string[]) => {
+  // 保存编码
+  form.value.addressProvince = val[0];
+  form.value.addressCity = val[1];
+  form.value.addressCounty = val[2];
+
+  // 根据编码获取名称
+  const names: string[] = [];
+  if (val[0]) {
+    const province = regionData.find((item: any) => item.value === val[0]);
+    if (province) {
+      names.push(province.label);
+
+      if (val[1] && province.children) {
+        const city = province.children.find((item: any) => item.value === val[1]);
+        if (city) {
+          names.push(city.label);
+
+          if (val[2] && city.children) {
+            const county = city.children.find((item: any) => item.value === val[2]);
+            if (county) {
+              names.push(county.label);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // 将省市区名称用斜杠连接
+  form.value.provincialCityCounty = names.join('/');
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: CustomerContactVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  // 设置客户ID
+  if (props.customerId) {
+    form.value.customerId = props.customerId;
+  }
+  dialog.visible = true;
+  dialog.title = '添加客户联系人信息';
+};
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: CustomerContactVO) => {
+  reset();
+  const _id = row?.id || ids.value[0];
+  const res = await getCustomerContact(_id);
+  // 如果有省市区编码,回显到级联选择器
+  if (res.data.addressProvince && res.data.addressCity && res.data.addressCounty) {
+    codeArr.value = [res.data.addressProvince, res.data.addressCity, res.data.addressCounty] as any;
+  }
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = '修改客户联系人信息';
+};
+
+/** 提交按钮 */
+const submitForm = () => {
+  customerContactFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateCustomerContact(form.value).finally(() => (buttonLoading.value = false));
+      } else {
+        await addCustomerContact(form.value).finally(() => (buttonLoading.value = false));
+      }
+      proxy?.$modal.msgSuccess('操作成功');
+      dialog.visible = false;
+      await getList();
+    }
+  });
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: CustomerContactVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('是否确认删除客户联系人信息编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
+  await delCustomerContact(_ids);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'customer/customerContact/export',
+    {
+      ...queryParams.value
+    },
+    `customerContact_${new Date().getTime()}.xlsx`
+  );
+};
+
+onMounted(() => {
+  // 初始化时设置customerId
+  if (props.customerId) {
+    queryParams.value.customerId = props.customerId;
+  }
+  getList();
+});
+
+// 监听props变化
+watch(
+  () => props.customerId,
+  (newCustomerId) => {
+    if (newCustomerId) {
+      queryParams.value.customerId = newCustomerId;
+      getList();
+    }
+  }
+);
+</script>

+ 421 - 0
src/views/customer/customerFile/customerInfo/overview/contractManagement.vue

@@ -0,0 +1,421 @@
+<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="contractNo">
+              <el-input v-model="queryParams.contractNo" placeholder="请输入合同编号" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="合同名称" prop="contractName">
+              <el-input v-model="queryParams.contractName" placeholder="请输入合同名称" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="合同类型" prop="contractType">
+              <el-select v-model="queryParams.contractType" placeholder="请选择合同类型" clearable>
+                <el-option v-for="dict in contract_type" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="合同状态" prop="approvalStatus">
+              <el-select v-model="queryParams.approvalStatus" placeholder="请选择合同状态" clearable>
+                <el-option v-for="dict in contract_status" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </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>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="22">
+            <span>合同管理信息列表</span>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['customer:contract:add']">新增合同</el-button>
+          </el-col>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" border :data="contractList" @selection-change="handleSelectionChange">
+        <el-table-column label="合同编号" align="center" prop="contractNo" />
+        <el-table-column label="合同名称" align="center" prop="contractName" />
+        <el-table-column label="合同类型" align="center" prop="contractType">
+          <template #default="scope">
+            <dict-tag :options="contract_type" :value="scope.row.contractType" />
+          </template>
+        </el-table-column>
+        <el-table-column label="起始时间" align="center" prop="startTime" width="180">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="截至时间" align="center" prop="endTime" width="180">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="上传时间" align="center" prop="uploadTime" width="180">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.uploadTime, '{y}-{m}-{d}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" align="center" prop="approvalStatus">
+          <template #default="scope">
+            <dict-tag :options="contract_status" :value="scope.row.approvalStatus" />
+          </template>
+        </el-table-column>
+
+        <el-table-column label="附件管理" align="center" prop="annex" />
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['customer:contract:edit']">编辑</el-button>
+            <el-button link type="primary" icon="Edit" @click="handleReview(scope.row)" v-hasPermi="['customer:contract:edit']">查看</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 :title="dialog.title" v-model="dialog.visible" width="50%" append-to-body>
+      <el-form ref="contractFormRef" :model="form" :rules="rules" label-width="120px">
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="合同编号" prop="contractNo">
+              <el-input v-model="form.contractNo" disabled />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="合同名称" prop="contractName">
+              <el-input v-model="form.contractName" placeholder="请输入合同名称" :disabled="isViewMode" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="合同类型" prop="contractType">
+              <el-select v-model="form.contractType" placeholder="请选择合同类型" class="w-full" :disabled="isViewMode">
+                <el-option v-for="dict in contract_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="合同金额(万元)" prop="contractAmount">
+              <el-input v-model="form.contractAmount" placeholder="请输入合同金额" :disabled="isViewMode" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="客户编号" prop="customerNo">
+              <el-input v-model="form.customerNo" placeholder="请输入客户编号" disabled />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="客户名称" prop="customerName">
+              <el-input v-model="form.customerName" placeholder="请输入客户名称" :disabled="isViewMode" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="客户负责人" prop="customerLeader">
+              <el-input v-model="form.customerLeader" placeholder="请输入客户负责人" :disabled="isViewMode" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="我方负责人" prop="projectLeader">
+              <el-input v-model="form.projectLeader" placeholder="请输入我方负责人" :disabled="isViewMode" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="起始时间" prop="startTime">
+              <el-date-picker
+                v-model="form.startTime"
+                type="date"
+                value-format="YYYY-MM-DD"
+                placeholder="请选择起始时间"
+                class="w-full"
+                style="width: 100%"
+                :disabled="isViewMode"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="截止时间" prop="endTime">
+              <el-date-picker
+                v-model="form.endTime"
+                type="date"
+                value-format="YYYY-MM-DD"
+                placeholder="请选择截止时间"
+                class="w-full"
+                style="width: 100%"
+                :disabled="isViewMode"
+                :disabled-date="disabledEndDate"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="16">
+            <el-form-item label="附件管理" prop="annex">
+              <el-upload action="#" :auto-upload="false" :disabled="isViewMode">
+                <el-button type="primary" :disabled="isViewMode">选择文件</el-button>
+              </el-upload>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button v-if="!isViewMode" :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">{{ isViewMode ? '关 闭' : '取 消' }}</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="Contract" lang="ts">
+import { listContract, getContract, delContract, addContract, updateContract } from '@/api/customer/customerFile/contract';
+import { ContractVO, ContractQuery, ContractForm } from '@/api/customer/customerFile/contract/types';
+// 接收父组件传递的props
+const props = defineProps<{
+  customerId?: string;
+  customerNo?: string;
+  customerName?: string;
+}>();
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { contract_type, contract_status } = toRefs<any>(proxy?.useDict('contract_type', 'contract_status'));
+
+const contractList = ref<ContractVO[]>([]);
+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 contractFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+// 是否为查看模式
+const isViewMode = ref(false);
+
+const initFormData: ContractForm = {
+  id: undefined,
+  customerNo: undefined,
+  customerId: undefined,
+  customerName: undefined,
+  contractNo: undefined,
+  contractName: undefined,
+  contractAmount: undefined,
+  customerLeader: undefined,
+  projectLeader: undefined,
+  contractType: undefined,
+  startTime: undefined,
+  endTime: undefined,
+  uploadTime: undefined,
+  settlementType: undefined,
+  contractStatus: undefined,
+  approvalStatus: undefined,
+  annex: undefined,
+  remark: undefined
+};
+const data = reactive<PageData<ContractForm, ContractQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    customerNo: undefined,
+    customerId: undefined,
+    contractNo: undefined,
+    contractName: undefined,
+    contractAmount: undefined,
+    customerLeader: undefined,
+    projectLeader: undefined,
+    contractType: undefined,
+    startTime: undefined,
+    endTime: undefined,
+    uploadTime: undefined,
+    settlementType: undefined,
+    contractStatus: undefined,
+    approvalStatus: undefined,
+    annex: undefined,
+    platformCode: undefined,
+    params: {}
+  },
+  rules: {
+    customerNo: [{ required: true, message: '客户编号不能为空', trigger: 'blur' }],
+    contractAmount: [{ required: true, message: '合同金额不能为空', trigger: 'blur' }],
+    contractType: [{ required: true, message: '合同类型不能为空', trigger: 'change' }],
+    startTime: [{ required: true, message: '合同开始时间不能为空', trigger: 'blur' }],
+    endTime: [{ required: true, message: '合同结束时间不能为空', trigger: 'blur' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 禁用截止日期:只能选择起始日期之后的日期 */
+const disabledEndDate = (time: Date) => {
+  if (!form.value.startTime) {
+    return false; // 如果没有选择起始时间,不限制
+  }
+  // 将起始时间字符串转换为Date对象
+  const startDate = new Date(form.value.startTime);
+  // 截止日期必须大于起始日期
+  return time.getTime() < startDate.getTime();
+};
+
+/** 查询客户合同列表 */
+const getList = async () => {
+  loading.value = true;
+  // 使用props传递的customerId进行查询
+  if (props.customerId) {
+    queryParams.value.customerId = props.customerId;
+  }
+  const res = await listContract(queryParams.value);
+  contractList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  contractFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: ContractVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  isViewMode.value = false; // 取消查看模式
+  // 将props中的客户信息赋值给form
+  if (props.customerId) {
+    form.value.customerId = props.customerId;
+  }
+  if (props.customerNo) {
+    form.value.customerNo = props.customerNo;
+  }
+  if (props.customerName) {
+    form.value.customerName = props.customerName;
+  }
+  dialog.visible = true;
+  dialog.title = '添加客户合同';
+};
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: ContractVO) => {
+  reset();
+  isViewMode.value = false; // 取消查看模式
+  const _id = row?.id || ids.value[0];
+  const res = await getContract(_id);
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = '修改客户合同';
+};
+/** 查看按钮操作 */
+const handleReview = async (row?: ContractVO) => {
+  reset();
+  isViewMode.value = true; // 设置为查看模式
+  const _id = row?.id || ids.value[0];
+  const res = await getContract(_id);
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = '查看合同详情';
+};
+
+/** 提交按钮 */
+const submitForm = () => {
+  contractFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+
+      if (form.value.id) {
+        await updateContract(form.value).finally(() => (buttonLoading.value = false));
+      } else {
+        await addContract(form.value).finally(() => (buttonLoading.value = false));
+      }
+      proxy?.$modal.msgSuccess('操作成功');
+      dialog.visible = false;
+      await getList();
+    }
+  });
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: ContractVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('是否确认删除客户合同编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
+  await delContract(_ids);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'customer/contract/export',
+    {
+      ...queryParams.value
+    },
+    `contract_${new Date().getTime()}.xlsx`
+  );
+};
+
+onMounted(() => {
+  // 初始化时将customerId赋值给queryParams
+  if (props.customerId) {
+    queryParams.value.customerId = props.customerId;
+  }
+  getList();
+});
+
+// 监听props变化
+watch(
+  () => props.customerId,
+  (newId) => {
+    if (newId) {
+      queryParams.value.customerId = newId;
+      getList();
+    }
+  }
+);
+</script>

+ 179 - 0
src/views/customer/customerFile/customerInfo/overview/operationLog.vue

@@ -0,0 +1,179 @@
+<template>
+  <div class="p-2">
+    <el-card shadow="hover">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="22">
+            <span>日志管理信息列表</span>
+          </el-col>
+        </el-row>
+      </template>
+
+      <el-table
+        ref="operLogTableRef"
+        v-loading="loading"
+        :data="operlogList"
+        border
+        :default-sort="defaultSort"
+        @selection-change="handleSelectionChange"
+        @sort-change="handleSortChange"
+      >
+        <el-table-column type="selection" width="50" align="center" />
+        <el-table-column label="日志编号" align="center" prop="operId" />
+        <el-table-column label="操作日期" align="center" prop="operTime" width="180" sortable="custom" :sort-orders="['descending', 'ascending']">
+          <template #default="scope">
+            <span>{{ proxy.parseTime(scope.row.operTime) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作状态" align="center" prop="status">
+          <template #default="scope">
+            <dict-tag :options="sys_common_status" :value="scope.row.status" />
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" fixed="right" align="center" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-tooltip content="详细" placement="top">
+              <el-button v-hasPermi="['monitor:operlog:query']" link type="primary" icon="View" @click="handleView(scope.row)"> </el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
+    </el-card>
+    <!-- 操作日志详细 -->
+    <OperInfoDialog ref="operInfoDialogRef" />
+  </div>
+</template>
+
+<script setup name="Operlog" lang="ts">
+import { list, delOperlog, cleanOperlog } from '@/api/monitor/operlog';
+import { OperLogForm, OperLogQuery, OperLogVO } from '@/api/monitor/operlog/types';
+import OperInfoDialog from '../../../../monitor/operlog/oper-info-dialog.vue';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { sys_oper_type, sys_common_status } = toRefs<any>(proxy?.useDict('sys_oper_type', 'sys_common_status'));
+
+const operlogList = ref<OperLogVO[]>([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<number | string>>([]);
+const multiple = ref(true);
+const total = ref(0);
+const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
+const defaultSort = ref<any>({ prop: 'operTime', order: 'descending' });
+
+const operLogTableRef = ref<ElTableInstance>();
+const queryFormRef = ref<ElFormInstance>();
+
+const data = reactive<PageData<OperLogForm, OperLogQuery>>({
+  form: {
+    operId: undefined,
+    tenantId: undefined,
+    title: '',
+    businessType: 0,
+    businessTypes: undefined,
+    method: '',
+    requestMethod: '',
+    operatorType: 0,
+    operName: '',
+    deptName: '',
+    operUrl: '',
+    operIp: '',
+    operLocation: '',
+    operParam: '',
+    jsonResult: '',
+    status: 0,
+    errorMsg: '',
+    operTime: '',
+    costTime: 0
+  },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    operIp: '',
+    title: '',
+    operName: '',
+    businessType: '',
+    status: '',
+    orderByColumn: defaultSort.value.prop,
+    isAsc: defaultSort.value.order
+  },
+  rules: {}
+});
+
+const { queryParams, form } = toRefs(data);
+
+/** 查询登录日志 */
+const getList = async () => {
+  loading.value = true;
+  const res = await list(proxy?.addDateRange(queryParams.value, dateRange.value));
+  operlogList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+/** 操作日志类型字典翻译 */
+const typeFormat = (row: OperLogForm) => {
+  return proxy?.selectDictLabel(sys_oper_type.value, row.businessType);
+};
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+/** 重置按钮操作 */
+const resetQuery = () => {
+  dateRange.value = ['', ''];
+  queryFormRef.value?.resetFields();
+  queryParams.value.pageNum = 1;
+  operLogTableRef.value?.sort(defaultSort.value.prop, defaultSort.value.order);
+};
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: OperLogVO[]) => {
+  ids.value = selection.map((item) => item.operId);
+  multiple.value = !selection.length;
+};
+/** 排序触发事件 */
+const handleSortChange = (column: any) => {
+  queryParams.value.orderByColumn = column.prop;
+  queryParams.value.isAsc = column.order;
+  getList();
+};
+
+const operInfoDialogRef = ref<InstanceType<typeof OperInfoDialog>>();
+/** 详细按钮操作 */
+const handleView = (row: OperLogVO) => {
+  operInfoDialogRef.value.openDialog(row);
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: OperLogVO) => {
+  const operIds = row?.operId || ids.value;
+  await proxy?.$modal.confirm('是否确认删除日志编号为"' + operIds + '"的数据项?');
+  await delOperlog(operIds);
+  await getList();
+  proxy?.$modal.msgSuccess('删除成功');
+};
+
+/** 清空按钮操作 */
+const handleClean = async () => {
+  await proxy?.$modal.confirm('是否确认清空所有操作日志数据项?');
+  await cleanOperlog();
+  await getList();
+  proxy?.$modal.msgSuccess('清空成功');
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'monitor/operlog/export',
+    {
+      ...queryParams.value
+    },
+    `config_${new Date().getTime()}.xlsx`
+  );
+};
+onMounted(() => {
+  getList();
+});
+</script>

+ 24 - 0
src/views/customer/customerFile/customerInfo/overview/orgStructure.vue

@@ -0,0 +1,24 @@
+<template>
+  <div class="p-2">
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="22">
+            <span>企业组织结构信息列表</span>
+          </el-col>
+        </el-row>
+      </template>
+    </el-card>
+  </div>
+</template>
+
+<script setup name="OrgStructure" lang="ts">
+// 接收父组件传递的props
+const props = defineProps<{
+  customerId?: string;
+  customerNo?: string;
+  customerName?: string;
+}>();
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+</script>

+ 359 - 0
src/views/customer/customerFile/customerInfo/overview/procurementProfile.vue

@@ -0,0 +1,359 @@
+<template>
+  <div class="p-2">
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="22">
+            <span>企业采购习惯</span>
+          </el-col>
+        </el-row>
+      </template>
+
+      <el-form ref="purchaseHabitFormRef" :model="form" :rules="rules" label-width="140px">
+        <!-- 基本信息 -->
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="月度采购金额预估" prop="monthPurchase">
+              <el-input v-model="form.monthPurchase" placeholder="请输入月度采购金额预估" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="年度采购金额" prop="yearPurchase">
+              <el-input v-model="form.yearPurchase" placeholder="请输入年度采购金额" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 产品选择 -->
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="产品选型" prop="choiceModel">
+              <el-radio-group v-model="form.choiceModel">
+                <el-radio v-for="dict in product_types_choosing" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 日常打印量 -->
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="日常打印量" prop="printAmount">
+              <el-radio-group v-model="form.printAmount">
+                <el-radio v-for="dict in daily_print_volume" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 购买选项 -->
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="购买原装耗材" prop="buyOriginal">
+              <el-radio-group v-model="form.buyOriginal">
+                <el-radio v-for="dict in sys_platform_yes_no" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="专人进行技术服务" prop="technologyService">
+              <el-radio-group v-model="form.technologyService">
+                <el-radio v-for="dict in sys_platform_yes_no" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 主要办公采购类目 -->
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="主要办公采购类目" prop="purchaseCategory">
+              <el-checkbox-group v-model="purchaseCategoryArr">
+                <el-checkbox v-for="dict in purchase_item" :key="dict.value" :value="dict.value">
+                  {{ dict.label }}
+                </el-checkbox>
+              </el-checkbox-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item prop="otherCategory">
+              <el-input v-model="form.otherCategory" type="textarea" :rows="2" placeholder="其他采购品类" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 企业福利 -->
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="企业福利" prop="adaptScene">
+              <el-checkbox-group v-model="adaptSceneArr">
+                <el-checkbox v-for="dict in welfare_item" :key="dict.value" :value="dict.value">
+                  {{ dict.label }}
+                </el-checkbox>
+              </el-checkbox-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item prop="otherScene">
+              <el-input v-model="form.otherScene" type="textarea" :rows="2" placeholder="其他企业福利" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 产品定制需求 -->
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="产品定制需求" prop="customizeDemand">
+              <el-checkbox-group v-model="customizeDemandArr">
+                <el-checkbox v-for="dict in product_customization" :key="dict.value" :value="dict.value">
+                  {{ dict.label }}
+                </el-checkbox>
+              </el-checkbox-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item prop="otherCustomize">
+              <el-input v-model="form.otherCustomize" type="textarea" :rows="2" placeholder="其他定制化需求" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <!-- 提交按钮 -->
+        <el-row :gutter="20">
+          <el-col :span="24" class="text-center">
+            <el-button @click="goBack">取消</el-button>
+            <el-button :loading="buttonLoading" type="primary" @click="submitForm">保存</el-button>
+          </el-col>
+        </el-row>
+      </el-form>
+    </el-card>
+  </div>
+</template>
+
+<script setup name="PurchaseHabit" lang="ts">
+import {
+  listPurchaseHabit,
+  getPurchaseHabit,
+  delPurchaseHabit,
+  addPurchaseHabit,
+  updatePurchaseHabit,
+  getCustomerPurchaseHabitData
+} from '@/api/customer/customerFile/purchaseHabit';
+import { PurchaseHabitVO, PurchaseHabitQuery, PurchaseHabitForm } from '@/api/customer/customerFile/purchaseHabit/types';
+
+// 接收父组件传递的props
+const props = defineProps<{
+  customerId?: string;
+  customerNo?: string;
+  customerName?: string;
+}>();
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { welfare_item, sys_platform_yes_no, product_types_choosing, purchase_item, product_customization, daily_print_volume } = toRefs<any>(
+  proxy?.useDict('welfare_item', 'sys_platform_yes_no', 'product_types_choosing', 'purchase_item', 'product_customization', 'daily_print_volume')
+);
+
+const purchaseHabitList = ref<PurchaseHabitVO[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const router = useRouter();
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const adaptSceneArr = ref<Array<string | number>>([]);
+const purchaseCategoryArr = ref<Array<string | number>>([]);
+const customizeDemandArr = ref<Array<string | number>>([]);
+
+const queryFormRef = ref<ElFormInstance>();
+const purchaseHabitFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: PurchaseHabitForm = {
+  id: undefined,
+  customerNo: undefined,
+  monthPurchase: undefined,
+  yearPurchase: undefined,
+  permanentOfficer: undefined,
+  choiceModel: undefined,
+  printAmount: undefined,
+  buyOriginal: '1',
+  technologyService: '1',
+  purchaseCategory: undefined,
+  otherCategory: undefined,
+  adaptScene: undefined,
+  otherScene: undefined,
+  customizeDemand: undefined,
+  otherCustomize: undefined,
+  remark: undefined
+};
+const data = reactive<PageData<PurchaseHabitForm, PurchaseHabitQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    customerNo: undefined,
+    monthPurchase: undefined,
+    yearPurchase: undefined,
+    permanentOfficer: undefined,
+    choiceModel: undefined,
+    printAmount: undefined,
+    buyOriginal: undefined,
+    technologyService: undefined,
+    purchaseCategory: undefined,
+    otherCategory: undefined,
+    adaptScene: undefined,
+    otherScene: undefined,
+    customizeDemand: undefined,
+    otherCustomize: undefined,
+    platformCode: undefined,
+    params: {}
+  },
+  rules: {
+    buyOriginal: [{ required: true, message: '是否购买原装耗材不能为空', trigger: 'change' }],
+    technologyService: [{ required: true, message: '是否购买技术服务不能为空', trigger: 'change' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询客户采购习惯列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listPurchaseHabit(queryParams.value);
+  purchaseHabitList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 加载客户采购画像数据 */
+const loadCustomerPurchaseData = async () => {
+  if (!props.customerNo) return;
+
+  try {
+    loading.value = true;
+    const res = await getCustomerPurchaseHabitData(props.customerNo);
+    if (res.data) {
+      Object.assign(form.value, res.data);
+
+      // 将字符串转换为数组并赋值给对应的数组变量
+      if (form.value.purchaseCategory && typeof form.value.purchaseCategory === 'string') {
+        purchaseCategoryArr.value = form.value.purchaseCategory.split(',').filter((item: string) => item.trim());
+      } else {
+        purchaseCategoryArr.value = [];
+      }
+
+      if (form.value.adaptScene && typeof form.value.adaptScene === 'string') {
+        adaptSceneArr.value = form.value.adaptScene.split(',').filter((item: string) => item.trim());
+      } else {
+        adaptSceneArr.value = [];
+      }
+
+      if (form.value.customizeDemand && typeof form.value.customizeDemand === 'string') {
+        customizeDemandArr.value = form.value.customizeDemand.split(',').filter((item: string) => item.trim());
+      } else {
+        customizeDemandArr.value = [];
+      }
+    } else {
+      // 如果没有数据,设置客户编号并初始化数组
+      form.value.customerNo = props.customerNo;
+      purchaseCategoryArr.value = [];
+      adaptSceneArr.value = [];
+      customizeDemandArr.value = [];
+    }
+  } catch (error) {
+    console.error('加载采购画像数据失败:', error);
+    // 如果加载失败,设置客户编号并初始化数组
+    form.value.customerNo = props.customerNo;
+    purchaseCategoryArr.value = [];
+    adaptSceneArr.value = [];
+    customizeDemandArr.value = [];
+  } finally {
+    loading.value = false;
+  }
+};
+const goBack = () => {
+  router.push('/customer/customer-file/customer-list');
+};
+
+/** 提交按钮 */
+const submitForm = () => {
+  purchaseHabitFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+
+      // 将数组变量转换为逗号分隔的字符串并赋值给form字段
+      form.value.purchaseCategory = purchaseCategoryArr.value.join(',');
+      form.value.adaptScene = adaptSceneArr.value.join(',');
+      form.value.customizeDemand = customizeDemandArr.value.join(',');
+
+      try {
+        if (form.value.id) {
+          await updatePurchaseHabit(form.value);
+        } else {
+          await addPurchaseHabit(form.value);
+        }
+        proxy?.$modal.msgSuccess('保存成功');
+        // 重新加载数据
+        await loadCustomerPurchaseData();
+      } catch (error) {
+        console.error('保存失败:', error);
+        proxy?.$modal.msgError('保存失败,请重试');
+      } finally {
+        buttonLoading.value = false;
+      }
+    }
+  });
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: PurchaseHabitVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('是否确认删除客户采购习惯编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
+  await delPurchaseHabit(_ids);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'customer/purchaseHabit/export',
+    {
+      ...queryParams.value
+    },
+    `purchaseHabit_${new Date().getTime()}.xlsx`
+  );
+};
+
+onMounted(() => {
+  // 页面加载时根据客户编号查询采购画像数据
+  loadCustomerPurchaseData();
+});
+
+// 监听props变化
+watch(
+  () => props.customerNo,
+  (newCustomerNo) => {
+    if (newCustomerNo) {
+      loadCustomerPurchaseData();
+    }
+  }
+);
+</script>

+ 334 - 0
src/views/customer/customerFile/customerInfo/overview/shippingAddress.vue

@@ -0,0 +1,334 @@
+<template>
+  <div class="p-2">
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="22">
+            <span>收货地址信息列表</span>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Add" @click="handleAdd" v-hasPermi="['customer:shippingAddress:add']">添加地址</el-button>
+          </el-col>
+        </el-row>
+      </template>
+      <el-table v-loading="loading" border :data="shippingAddressList" @selection-change="handleSelectionChange">
+        <el-table-column label="收货地址编号" align="center" prop="shippingAddressNo" />
+        <el-table-column label="收货人" align="center" prop="consignee" />
+        <el-table-column label="部门名称" align="center" prop="deptName" />
+        <el-table-column label="手机号码" align="center" prop="phone" />
+        <el-table-column label="详细地址" align="center" prop="address" />
+        <el-table-column label="默认地址" align="center" prop="defaultAddress">
+          <template #default="scope">
+            <el-switch
+              v-model="scope.row.defaultAddress"
+              active-value="0"
+              inactive-value="1"
+              @change="handleDefaultAddressChange(scope.row)"
+            ></el-switch>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['customer:shippingAddress:edit']"
+              >编辑</el-button
+            >
+            <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['customer:shippingAddress:remove']"
+              >删除</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 :title="dialog.title" v-model="dialog.visible" width="650px" append-to-body>
+      <el-form ref="shippingAddressFormRef" :model="form" :rules="rules" label-width="110px">
+        <el-form-item label="收货人" prop="consignee">
+          <el-input v-model="form.consignee" placeholder="请输入收货人姓名" />
+        </el-form-item>
+        <el-form-item label="部门名称" prop="deptName">
+          <el-input v-model="form.deptName" placeholder="请输入部门名称" />
+        </el-form-item>
+        <el-form-item label="手机号码" prop="phone">
+          <el-input v-model="form.phone" placeholder="请输入联系电话" />
+        </el-form-item>
+        <el-form-item label="邮政编码" prop="postal">
+          <el-input v-model="form.postal" placeholder="请输入邮政编码" />
+        </el-form-item>
+        <el-form-item label="详细地址" prop="provincialCityCountry">
+          <el-cascader v-model="codeArr" :options="regionData" placeholder="请选择" @change="handleChange" style="width: 100%"></el-cascader>
+        </el-form-item>
+        <el-form-item prop="address">
+          <el-input v-model="form.address" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="ShippingAddress" lang="ts">
+import {
+  listShippingAddress,
+  getShippingAddress,
+  delShippingAddress,
+  addShippingAddress,
+  changeDefaultAddress,
+  updateShippingAddress
+} from '@/api/customer/customerFile/shippingAddress';
+import { ShippingAddressVO, ShippingAddressQuery, ShippingAddressForm } from '@/api/customer/customerFile/shippingAddress/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+import { regionData } from 'element-china-area-data';
+
+// 接收父组件传递的props
+const props = defineProps<{
+  customerId?: string;
+  customerNo?: string;
+}>();
+
+const shippingAddressList = ref<ShippingAddressVO[]>([]);
+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 codeArr = ref([]);
+const queryFormRef = ref<ElFormInstance>();
+const shippingAddressFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: ShippingAddressForm = {
+  id: undefined,
+  customerId: undefined,
+  shippingAddressNo: undefined,
+  consignee: undefined,
+  deptId: undefined,
+  phone: undefined,
+  address: undefined,
+  postal: undefined,
+  defaultAddress: undefined,
+  addressLabel: undefined,
+  provincialNo: undefined,
+  cityNo: undefined,
+  countryNo: undefined,
+  provincialCityCountry: undefined,
+  pushStatus: undefined,
+  num: undefined,
+  deptName: undefined,
+  status: undefined,
+  remark: undefined
+};
+const data = reactive<PageData<ShippingAddressForm, ShippingAddressQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    customerId: undefined,
+    shippingAddressNo: undefined,
+    consignee: undefined,
+    deptId: undefined,
+    phone: undefined,
+    address: undefined,
+    postal: undefined,
+    defaultAddress: undefined,
+    addressLabel: undefined,
+    provincialNo: undefined,
+    cityNo: undefined,
+    countryNo: undefined,
+    provincialCityCountry: undefined,
+    pushStatus: undefined,
+    num: undefined,
+    deptName: undefined,
+    status: undefined,
+    platformCode: undefined,
+    params: {}
+  },
+  rules: {
+    consignee: [{ required: true, message: '收货人姓名不能为空', trigger: 'blur' }],
+    provincialCityCountry: [{ required: true, message: '详细地址不能为空', trigger: 'blur' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 处理区域选择变化 */
+const handleChange = (val: string[]) => {
+  // 保存编码
+  form.value.provincialNo = val[0];
+  form.value.cityNo = val[1];
+  form.value.countryNo = val[2];
+
+  // 根据编码获取名称
+  const names: string[] = [];
+  if (val[0]) {
+    const province = regionData.find((item: any) => item.value === val[0]);
+    if (province) {
+      names.push(province.label);
+
+      if (val[1] && province.children) {
+        const city = province.children.find((item: any) => item.value === val[1]);
+        if (city) {
+          names.push(city.label);
+
+          if (val[2] && city.children) {
+            const county = city.children.find((item: any) => item.value === val[2]);
+            if (county) {
+              names.push(county.label);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // 将省市区名称用斜杠连接
+  form.value.provincialCityCountry = names.join('/');
+};
+
+/** 查询客户收货地址列表 */
+const getList = async () => {
+  loading.value = true;
+  // 使用props传递的customerId进行查询
+  if (props.customerId) {
+    queryParams.value.customerId = props.customerId;
+  }
+  const res = await listShippingAddress(queryParams.value);
+  shippingAddressList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 状态修改  */
+const handleDefaultAddressChange = async (row: ShippingAddressVO) => {
+  //   const text = row.status === '0' ? '是' : '否';
+  try {
+    // await proxy?.$modal.confirm('确认要"' + text + '"吗?');
+    await changeDefaultAddress(row.id, row.defaultAddress);
+    proxy?.$modal.msgSuccess('操作成功');
+  } catch (err) {
+    row.status = row.status === '0' ? '1' : '0';
+  }
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  shippingAddressFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: ShippingAddressVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  // 将props中的customerId赋值给form
+  if (props.customerId) {
+    form.value.customerId = props.customerId;
+  }
+  dialog.visible = true;
+  dialog.title = '添加客户收货地址';
+};
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: ShippingAddressVO) => {
+  reset();
+  const _id = row?.id || ids.value[0];
+  const res = await getShippingAddress(_id);
+  Object.assign(form.value, res.data);
+  codeArr.value = [res.data.provincialNo, res.data.cityNo, res.data.countryNo];
+  dialog.visible = true;
+  dialog.title = '修改客户收货地址';
+};
+
+/** 提交按钮 */
+const submitForm = () => {
+  shippingAddressFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateShippingAddress(form.value).finally(() => (buttonLoading.value = false));
+      } else {
+        await addShippingAddress(form.value).finally(() => (buttonLoading.value = false));
+      }
+      proxy?.$modal.msgSuccess('操作成功');
+      dialog.visible = false;
+      await getList();
+    }
+  });
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: ShippingAddressVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('是否确认删除客户收货地址编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
+  await delShippingAddress(_ids);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'customer/shippingAddress/export',
+    {
+      ...queryParams.value
+    },
+    `shippingAddress_${new Date().getTime()}.xlsx`
+  );
+};
+
+onMounted(() => {
+  // 初始化时将customerId赋值给queryParams
+  if (props.customerId) {
+    queryParams.value.customerId = props.customerId;
+  }
+  getList();
+});
+
+// 监听props变化
+watch(
+  () => props.customerId,
+  (newId) => {
+    if (newId) {
+      queryParams.value.customerId = newId;
+      getList();
+    }
+  }
+);
+</script>

+ 261 - 0
src/views/customer/customerFile/maintainInfo/add.vue

@@ -0,0 +1,261 @@
+<template>
+  <div class="p-4">
+    <!-- 页面标题和按钮 -->
+    <div class="flex justify-between items-center mb-4">
+      <div class="flex items-center gap-2">
+        <el-button icon="ArrowLeft" @click="router.back()">返回</el-button>
+        <span class="text-lg font-medium">{{ isEdit ? '编辑' : '新增' }}维保</span>
+      </div>
+      <el-button type="primary" @click="handleSubmit" :loading="submitLoading">保存</el-button>
+    </div>
+
+    <el-card shadow="never">
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="140px">
+        <!-- 企业基本信息 -->
+        <div class="text-base font-medium mb-4">企业基本信息</div>
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="企业名称" prop="companyName">
+              <el-select v-model="form.companyName" placeholder="请选择" class="w-full" filterable>
+                <el-option label="企业A" value="企业A" />
+                <el-option label="企业B" value="企业B" />
+                <el-option label="企业C" value="企业C" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="维保类型" prop="maintainType">
+              <el-select v-model="form.maintainType" placeholder="请选择" class="w-full">
+                <el-option label="类型A" value="类型A" />
+                <el-option label="类型B" value="类型B" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="合同编号" prop="contractNo">
+              <el-input v-model="form.contractNo" placeholder="" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="企业地址" prop="companyAddress">
+              <el-input v-model="form.companyAddress" placeholder="" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="资质类型" prop="qualificationType">
+              <el-input v-model="form.qualificationType" placeholder="" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="中介机构" prop="intermediaryOrg">
+              <el-input v-model="form.intermediaryOrg" placeholder="" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 维保信息 -->
+        <div class="text-base font-medium mb-4 mt-6">维保信息</div>
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="维保单位" prop="maintainUnit">
+              <el-select v-model="form.maintainUnit" placeholder="请选择" class="w-full">
+                <el-option label="维保单位A" value="维保单位A" />
+                <el-option label="维保单位B" value="维保单位B" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="维保人员" prop="maintainPerson">
+              <el-select v-model="form.maintainPerson" placeholder="请选择" class="w-full">
+                <el-option label="维保人员A" value="维保人员A" />
+                <el-option label="维保人员B" value="维保人员B" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 维保证书 -->
+        <div class="text-base font-medium mb-4 mt-6">维保证书</div>
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="维保人员证书" prop="maintainPersonCert">
+              <el-input v-model="form.maintainPersonCert" placeholder="请输入人员证书名称" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="维保人员证书编号" prop="maintainPersonCertNo">
+              <el-input v-model="form.maintainPersonCertNo" placeholder="请输入人员证书编号" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="维保证书有效期(月)" prop="maintainPersonCertValidity">
+              <el-input v-model="form.maintainPersonCertValidity" placeholder="请选择" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="维保单位证书" prop="maintainUnitCert">
+              <el-input v-model="form.maintainUnitCert" placeholder="请输入单位证书名称" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="维保单位证书编号" prop="maintainUnitCertNo">
+              <el-input v-model="form.maintainUnitCertNo" placeholder="请输入单位证书编号" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="维保单位证书有效期" prop="maintainUnitCertValidity">
+              <el-input v-model="form.maintainUnitCertValidity" placeholder="请输入单位证书有效期" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="工作单位证书" prop="workUnitCert">
+              <el-input v-model="form.workUnitCert" placeholder="请选择" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="工作单位证书编号" prop="workUnitCertNo">
+              <el-input v-model="form.workUnitCertNo" placeholder="请选择" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="月检次数" prop="monthlyInspectionCount">
+              <el-input v-model="form.monthlyInspectionCount" placeholder="请选择" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="维保类型" prop="maintainTypeRadio">
+              <el-radio-group v-model="form.maintainTypeRadio">
+                <el-radio value="1">新增维保</el-radio>
+                <el-radio value="2">换维保单位</el-radio>
+                <el-radio value="3">取消维保</el-radio>
+                <el-radio value="4">产品维保</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="维保内容" prop="maintainContent">
+              <el-input v-model="form.maintainContent" type="textarea" :rows="3" placeholder="请输入维保内容" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="附件路径" prop="attachmentPath">
+              <el-upload action="#" :auto-upload="false" :file-list="fileList" :on-change="handleFileChange">
+                <el-button type="primary">点击上传</el-button>
+              </el-upload>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </el-card>
+  </div>
+</template>
+
+<script setup lang="ts" name="MaintainInfoAdd">
+import { addMaintainInfo, updateMaintainInfo, getMaintainInfo } from '@/api/customer/customerFile/maintainInfo';
+import type { MaintainInfoForm } from '@/api/customer/customerFile/maintainInfo/types';
+
+const route = useRoute();
+const router = useRouter();
+
+const formRef = ref<any>(null);
+const submitLoading = ref(false);
+const isEdit = ref(false);
+const fileList = ref<any[]>([]);
+
+// 表单数据
+const form = reactive<MaintainInfoForm>({
+  companyName: '',
+  maintainType: '',
+  contractNo: '',
+  companyAddress: '',
+  qualificationType: '',
+  intermediaryOrg: '',
+  maintainUnit: '',
+  maintainPerson: '',
+  maintainPersonCert: '',
+  maintainPersonCertNo: '',
+  maintainPersonCertValidity: '',
+  maintainUnitCert: '',
+  maintainUnitCertNo: '',
+  maintainUnitCertValidity: '',
+  workUnitCert: '',
+  workUnitCertNo: '',
+  monthlyInspectionCount: '',
+  maintainTypeRadio: '1',
+  maintainContent: '',
+  attachmentPath: '',
+  status: '0'
+});
+
+// 表单验证规则
+const rules = {
+  companyName: [{ required: true, message: '请选择企业名称', trigger: 'change' }],
+  maintainType: [{ required: true, message: '请选择维保类型', trigger: 'change' }]
+};
+
+// 初始化
+onMounted(async () => {
+  const id = route.query.id;
+  if (id) {
+    isEdit.value = true;
+    await loadMaintainData(id);
+  }
+});
+
+// 加载维保数据
+const loadMaintainData = async (id: string | number) => {
+  try {
+    const res = await getMaintainInfo(id);
+    Object.assign(form, res.data);
+  } catch (error) {
+    console.error('加载维保数据失败:', error);
+    ElMessage.error('加载维保数据失败');
+  }
+};
+
+// 文件变化
+const handleFileChange = (file: any, fileList: any[]) => {
+  // 处理文件上传逻辑
+  console.log('文件变化:', file, fileList);
+};
+
+// 提交表单
+const handleSubmit = async () => {
+  try {
+    await formRef.value?.validate();
+    submitLoading.value = true;
+
+    if (isEdit.value) {
+      await updateMaintainInfo(form);
+      ElMessage.success('修改成功');
+    } else {
+      await addMaintainInfo(form);
+      ElMessage.success('添加成功');
+    }
+    router.back();
+  } catch (error) {
+    console.error('保存失败:', error);
+    ElMessage.error('保存失败,请重试');
+  } finally {
+    submitLoading.value = false;
+  }
+};
+</script>

+ 22 - 8
src/views/customer/maintainInfo/add.vue

@@ -203,7 +203,7 @@
           <el-col :span="24">
             <el-form-item label="服务内容" prop="serviceContent">
               <el-checkbox-group v-model="form.serviceContent">
-                <el-checkbox v-for="dict in service_content" :key="dict.value" :value="dict.value">{{ dict.label }}</el-checkbox>
+                <el-checkbox v-for="item in serviceContentList" :key="item.id" :value="item.id">{{ item.itemName }}</el-checkbox>
               </el-checkbox-group>
             </el-form-item>
           </el-col>
@@ -237,12 +237,12 @@ import { addMaintainInfo, updateMaintainInfo, getMaintainInfo } from '@/api/cust
 import { listCustomerInfo, getCustomerInfo } from '@/api/customer/customerFile/customerInfo';
 import { getEnterpriseScale } from '@/api/customer/customerCategory/enterpriseScale';
 import { getIndustryCategory } from '@/api/customer/customerCategory/industryCategory';
+import { listServerItem } from '@/api/customer/serverItem';
 import { CustomerInfoVO } from '@/api/customer/customerFile/customerInfo/types';
+import type { ServerItemVO } from '@/api/customer/serverItem/types';
 import type { MaintainInfoForm } from '@/api/customer/maintainInfo/types';
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
-const { service_content, service_time_type, maintenance_type } = toRefs<any>(
-  proxy?.useDict('service_content', 'service_time_type', 'maintenance_type')
-);
+const { service_time_type, maintenance_type } = toRefs<any>(proxy?.useDict('service_time_type', 'maintenance_type'));
 
 const route = useRoute();
 const router = useRouter();
@@ -253,6 +253,7 @@ const submitLoading = ref(false);
 const isEdit = ref(false);
 const fileList = ref<any[]>([]);
 const customerList = ref<CustomerInfoVO[]>([]);
+const serviceContentList = ref<ServerItemVO[]>([]);
 
 // 客户信息(选择企业后自动带出)
 const customerInfo = reactive({
@@ -303,6 +304,8 @@ const rules = {
 onMounted(async () => {
   // 加载客户列表
   await loadCustomerList();
+  // 加载服务内容列表
+  await loadServiceContentList();
   const _id = route.query.id;
   if (_id) {
     isEdit.value = true;
@@ -321,6 +324,17 @@ const loadCustomerList = async () => {
   }
 };
 
+// 加载服务内容列表
+const loadServiceContentList = async () => {
+  try {
+    const res = await listServerItem();
+    serviceContentList.value = res.rows || [];
+  } catch (error) {
+    console.error('加载服务内容列表失败:', error);
+    ElMessage.error('加载服务内容列表失败');
+  }
+};
+
 // 选择企业后加载企业信息
 const handleCustomerChange = async (customerId: string | number) => {
   if (!customerId) return;
@@ -371,12 +385,12 @@ const loadMaintainData = async (id: string | number) => {
   try {
     const res = await getMaintainInfo(id);
     const data = res.data;
-    
-    // 将服务内容字符串转换为数组
+
+    // 将服务内容字符串(ID列表)转换为数组
     if (data.serviceContent && typeof data.serviceContent === 'string') {
       data.serviceContent = data.serviceContent.split(',').filter((item: string) => item.trim());
     }
-    
+
     Object.assign(form, data);
 
     // 如果有客户ID,加载客户信息
@@ -401,7 +415,7 @@ const handleSubmit = async () => {
     await maintainFormRef.value?.validate();
     submitLoading.value = true;
 
-    // 准备提交数据,将 serviceContent 数组转换为逗号分隔的字符串
+    // 准备提交数据,将 serviceContent 数组(ID列表)转换为逗号分隔的字符串
     const submitData = {
       ...form,
       serviceContent: Array.isArray(form.serviceContent) ? form.serviceContent.join(',') : form.serviceContent

+ 1 - 6
src/views/customer/maintainInfo/index.vue

@@ -83,11 +83,7 @@
         <el-table-column label="维保次数限制" align="center" prop="maintenLimit" />
         <el-table-column label="维保次数" align="center" prop="maintainCount" />
         <el-table-column label="主要技术顾问" align="center" prop="technicalAdviser" />
-        <el-table-column label="主要维保项目" align="center" prop="serviceContent">
-          <template #default="scope">
-            <dict-tag :options="service_content" :value="scope.row.serviceContent ? scope.row.serviceContent.split(',') : []" />
-          </template>
-        </el-table-column>
+        <el-table-column label="主要维保项目" align="center" prop="serviceContentStr"> </el-table-column>
         <el-table-column label="状态" align="center" prop="status">
           <template #default="scope">
             <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
@@ -274,7 +270,6 @@ const submitForm = () => {
   maintainInfoFormRef.value?.validate(async (valid: boolean) => {
     if (valid) {
       buttonLoading.value = true;
-      form.value.serviceContent = form.value.serviceContent.join(',');
       if (form.value.id) {
         await updateMaintainInfo(form.value).finally(() => (buttonLoading.value = false));
       } else {

+ 240 - 0
src/views/customer/maintenanceType/index.vue

@@ -0,0 +1,240 @@
+<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="typeName">
+              <el-input v-model="queryParams.typeName" placeholder="请输入类型名称" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="平台标识" prop="platformCode">
+              <el-input v-model="queryParams.platformCode" 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-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['customer:maintenanceType:add']">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['customer:maintenanceType:edit']"
+              >修改</el-button
+            >
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['customer:maintenanceType:remove']"
+              >删除</el-button
+            >
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['customer:maintenanceType:export']">导出</el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" border :data="maintenanceTypeList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="类型名称" align="center" prop="typeName" />
+        <el-table-column label="备注" align="center" prop="remark" />
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-tooltip content="修改" placement="top">
+              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['customer:maintenanceType:edit']"></el-button>
+            </el-tooltip>
+            <el-tooltip content="删除" placement="top">
+              <el-button
+                link
+                type="primary"
+                icon="Delete"
+                @click="handleDelete(scope.row)"
+                v-hasPermi="['customer:maintenanceType:remove']"
+              ></el-button>
+            </el-tooltip>
+          </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="500px" append-to-body>
+      <el-form ref="maintenanceTypeFormRef" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="类型名称" prop="typeName">
+          <el-input v-model="form.typeName" placeholder="请输入类型名称" />
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="MaintenanceType" lang="ts">
+import {
+  listMaintenanceType,
+  getMaintenanceType,
+  delMaintenanceType,
+  addMaintenanceType,
+  updateMaintenanceType
+} from '@/api/customer/maintenanceType';
+import { MaintenanceTypeVO, MaintenanceTypeQuery, MaintenanceTypeForm } from '@/api/customer/maintenanceType/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const maintenanceTypeList = ref<MaintenanceTypeVO[]>([]);
+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 maintenanceTypeFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: MaintenanceTypeForm = {
+  id: undefined,
+  typeName: undefined,
+  status: undefined,
+  remark: undefined
+};
+const data = reactive<PageData<MaintenanceTypeForm, MaintenanceTypeQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    typeName: undefined,
+    status: undefined,
+    platformCode: undefined,
+    params: {}
+  },
+  rules: {
+    typeName: [{ required: true, message: '类型名称不能为空', trigger: 'blur' }],
+    status: [{ required: true, message: '状态不能为空', trigger: 'change' }],
+    remark: [{ required: true, message: '备注不能为空', trigger: 'blur' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询维保类型列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listMaintenanceType(queryParams.value);
+  maintenanceTypeList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  maintenanceTypeFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: MaintenanceTypeVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = '添加维保类型';
+};
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: MaintenanceTypeVO) => {
+  reset();
+  const _id = row?.id || ids.value[0];
+  const res = await getMaintenanceType(_id);
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = '修改维保类型';
+};
+
+/** 提交按钮 */
+const submitForm = () => {
+  maintenanceTypeFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateMaintenanceType(form.value).finally(() => (buttonLoading.value = false));
+      } else {
+        await addMaintenanceType(form.value).finally(() => (buttonLoading.value = false));
+      }
+      proxy?.$modal.msgSuccess('操作成功');
+      dialog.visible = false;
+      await getList();
+    }
+  });
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: MaintenanceTypeVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('是否确认删除维保类型编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
+  await delMaintenanceType(_ids);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'customer/maintenanceType/export',
+    {
+      ...queryParams.value
+    },
+    `maintenanceType_${new Date().getTime()}.xlsx`
+  );
+};
+
+onMounted(() => {
+  getList();
+});
+</script>

+ 228 - 0
src/views/customer/serverItem/index.vue

@@ -0,0 +1,228 @@
+<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="itemName">
+              <el-input v-model="queryParams.itemName" placeholder="请输入服务内容" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="平台标识" prop="platformCode">
+              <el-input v-model="queryParams.platformCode" 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-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['customer:serverItem:add']">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['customer:serverItem:edit']"
+              >修改</el-button
+            >
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['customer:serverItem:remove']"
+              >删除</el-button
+            >
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['customer:serverItem:export']">导出</el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" border :data="serverItemList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="服务内容" align="center" prop="itemName" />
+        <el-table-column label="备注" align="center" prop="remark" />
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-tooltip content="修改" placement="top">
+              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['customer:serverItem:edit']"></el-button>
+            </el-tooltip>
+            <el-tooltip content="删除" placement="top">
+              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['customer:serverItem:remove']"></el-button>
+            </el-tooltip>
+          </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="500px" append-to-body>
+      <el-form ref="serverItemFormRef" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="服务内容" prop="itemName">
+          <el-input v-model="form.itemName" placeholder="请输入服务内容" />
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="ServerItem" lang="ts">
+import { listServerItem, getServerItem, delServerItem, addServerItem, updateServerItem } from '@/api/customer/serverItem';
+import { ServerItemVO, ServerItemQuery, ServerItemForm } from '@/api/customer/serverItem/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const serverItemList = ref<ServerItemVO[]>([]);
+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 serverItemFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: ServerItemForm = {
+  id: undefined,
+  itemName: undefined,
+  status: undefined,
+  remark: undefined
+};
+const data = reactive<PageData<ServerItemForm, ServerItemQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    itemName: undefined,
+    status: undefined,
+    platformCode: undefined,
+    params: {}
+  },
+  rules: {
+    itemName: [{ required: true, message: '服务内容不能为空', trigger: 'blur' }],
+    status: [{ required: true, message: '状态不能为空', trigger: 'change' }],
+    remark: [{ required: true, message: '备注不能为空', trigger: 'blur' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询维保服务内容列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listServerItem(queryParams.value);
+  serverItemList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  serverItemFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: ServerItemVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = '添加维保服务内容';
+};
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: ServerItemVO) => {
+  reset();
+  const _id = row?.id || ids.value[0];
+  const res = await getServerItem(_id);
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = '修改维保服务内容';
+};
+
+/** 提交按钮 */
+const submitForm = () => {
+  serverItemFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateServerItem(form.value).finally(() => (buttonLoading.value = false));
+      } else {
+        await addServerItem(form.value).finally(() => (buttonLoading.value = false));
+      }
+      proxy?.$modal.msgSuccess('操作成功');
+      dialog.visible = false;
+      await getList();
+    }
+  });
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: ServerItemVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('是否确认删除维保服务内容编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
+  await delServerItem(_ids);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'customer/serverItem/export',
+    {
+      ...queryParams.value
+    },
+    `serverItem_${new Date().getTime()}.xlsx`
+  );
+};
+
+onMounted(() => {
+  getList();
+});
+</script>

+ 1 - 0
src/views/customer/serverTime/index.vue

@@ -0,0 +1 @@
+<template>测试</template>