Преглед изворни кода

Merge remote-tracking branch 'origin/dev' into dev

# Conflicts:
#	.env.development
wuguojie пре 1 година
родитељ
комит
a701dbdc14
74 измењених фајлова са 12451 додато и 228 уклоњено
  1. 1 1
      .env.development
  2. 11 12
      src/api/index.ts
  3. 112 0
      src/api/modules/agreement.ts
  4. 4 4
      src/api/modules/institution.ts
  5. 46 0
      src/api/modules/insurance.ts
  6. 2 1
      src/api/modules/lottery.ts
  7. 1 2
      src/api/modules/operation.ts
  8. 28 0
      src/api/modules/project.ts
  9. 8 8
      src/api/modules/role.ts
  10. 214 0
      src/api/types/agreementTypes.ts
  11. BIN
      src/assets/images/empty.png
  12. BIN
      src/assets/images/fee.png
  13. BIN
      src/assets/images/pdf.png
  14. 52 0
      src/assets/styles/globals.css
  15. 6 7
      src/components/LoginForm/index.vue
  16. 72 0
      src/components/SearchForm/index.vue
  17. 209 0
      src/components/TableTree/index.vue
  18. 303 0
      src/components/complexTable/index.vue
  19. 14 0
      src/components/empty/index.vue
  20. 356 0
      src/components/ruleCondition/index.vue
  21. 163 0
      src/components/ruleConditionContainer/index.vue
  22. 201 0
      src/layouts/components/DrivingType/index.vue
  23. 233 0
      src/layouts/components/InsuranceType/index.vue
  24. 0 1
      src/router/guards.ts
  25. 57 0
      src/router/modules/externalAgreement.ts
  26. 8 8
      src/router/modules/personnel.ts
  27. 109 0
      src/router/modules/quoteConfig.ts
  28. 22 6
      src/router/modules/representative.ts
  29. 10 2
      src/router/routes.ts
  30. 2 2
      src/settings.default.ts
  31. 2 2
      src/settings.ts
  32. 9 0
      src/types/auto-imports.d.ts
  33. 6 0
      src/types/components.d.ts
  34. 39 0
      src/utils/composables/dictService.ts
  35. 72 0
      src/utils/composables/fileProcessing.ts
  36. 429 0
      src/views/externalAgreement/agreement/agreementAdd.vue
  37. 727 0
      src/views/externalAgreement/agreement/agreementDetails.vue
  38. 479 0
      src/views/externalAgreement/agreement/agreementIndex.vue
  39. 83 0
      src/views/externalAgreement/agreement/components/agreementAuth.vue
  40. 485 0
      src/views/externalAgreement/agreement/components/contactPersonFeeDialog.vue
  41. 317 0
      src/views/externalAgreement/agreement/components/contactPersonFeeDialogEdit.vue
  42. 110 0
      src/views/externalAgreement/agreement/components/managerModal.vue
  43. 522 0
      src/views/externalAgreement/costManagement.vue
  44. 36 4
      src/views/marketingManagement/configurate.vue
  45. 8 2
      src/views/marketingManagement/lottery.vue
  46. 3 1
      src/views/operationManagement/channel.vue
  47. 44 40
      src/views/operationManagement/modules/addOperation.vue
  48. 2 1
      src/views/operationManagement/modules/addTeam.vue
  49. 1 1
      src/views/operationManagement/operation.vue
  50. 2 2
      src/views/operationManagement/operationDetail.vue
  51. 3 1
      src/views/operationManagement/team.vue
  52. 3 1
      src/views/operationManagement/telemarketing.vue
  53. 57 22
      src/views/personnel/memberManagement.vue
  54. 169 22
      src/views/personnel/modules/addOperation.vue
  55. 38 9
      src/views/personnel/modules/editInstitution.vue
  56. 99 39
      src/views/personnel/modules/memberMessage.vue
  57. 24 20
      src/views/personnel/roleManagement.vue
  58. 530 0
      src/views/quoteConfig/agreement/agreementAdd.vue
  59. 236 0
      src/views/quoteConfig/agreement/agreementCopy.vue
  60. 844 0
      src/views/quoteConfig/agreement/agreementDetails.vue
  61. 503 0
      src/views/quoteConfig/agreement/agreementIndex.vue
  62. 83 0
      src/views/quoteConfig/agreement/components/agreementAuth.vue
  63. 111 0
      src/views/quoteConfig/agreement/components/managerModal.vue
  64. 134 0
      src/views/quoteConfig/agreement/components/orderAccount.vue
  65. 431 0
      src/views/quoteConfig/agreement/components/ruleFeeDialog.vue
  66. 321 0
      src/views/quoteConfig/agreement/components/ruleFeeDialogEdit.vue
  67. 617 0
      src/views/quoteConfig/costManagement.vue
  68. 584 0
      src/views/quoteConfig/insuranceCompanyAccount/insuranceCompanyAccount.vue
  69. 310 0
      src/views/quoteConfig/insuranceCompanyAccount/modules/insuranceAccountAdd.vue
  70. 269 0
      src/views/quoteConfig/project/index.vue
  71. 551 0
      src/views/quoteConfig/project/modules/projectAdd.vue
  72. 396 0
      src/views/representative/externalCostApproval.vue
  73. 504 0
      src/views/representative/quoteApproval.vue
  74. 14 7
      src/views/representative/salesman.vue

+ 1 - 1
.env.development

@@ -1,7 +1,7 @@
 # 应用配置面板
 VITE_APP_SETTING = true
 # 页面标题
-VITE_APP_TITLE = 掌柜运营平台
+VITE_APP_TITLE = 租户平台
 # 接口请求地址,会设置到 axios 的 baseURL 参数上
 # VITE_APP_API_BASEURL = https://test.baoxianzhanggui.com/web-api #默认地址
 # VITE_APP_API_BASEURL = http://192.168.0.253:8001  #田智

+ 11 - 12
src/api/index.ts

@@ -14,13 +14,17 @@ import operationApi from './modules/operation.ts'
 import menuApi from './modules/menu.ts'
 import commonApi from "./modules/common.ts";
 import teamApi from "@/api/modules/team.ts";
+
+import advertisementApi from "@/api/modules/advertisement.ts"
 import representativeApi from "@/api/modules/representative.ts";
 import contentApi from "@/api/modules/content.ts";
-import advertisementApi from "@/api/modules/advertisement.ts"
+import agreementApi  from "@/api/modules/agreement";
+
 // import { useRouter } from 'vue-router'
 // const router = useRouter()
-import router from "@/router"
 import lotteryApi from '@/api/modules/lottery.ts';
+import insuranceApi from '@/api/modules/insurance.ts'
+import projectApi from '@/api/modules/project.ts'
 
 const axiosInstance:ApiInstance = axios.create({
   baseURL: (import.meta.env.DEV && import.meta.env.VITE_OPEN_PROXY === 'true') ? '/proxy/' : import.meta.env.VITE_APP_API_BASEURL,
@@ -67,21 +71,13 @@ axiosInstance.interceptors.response.use(
     }
     else {
       if (response.data.code === 401) {
-        // console.log(router)
-        localStorage.setItem("token",'')
-        // router.push({
-        //   path:"/login"
-        // })
-
-        // console.log(localStorage.getItem('token'))
         Message({
           message: '登录信息已过期,请重新登录!!!',
           type: 'error',
           duration: 2000,
         })
-        // useUserStore().logout()
+        useUserStore().logout()
       }
-      // useUserStore().logout()
     }
     return Promise.resolve(response.data)
   },
@@ -116,6 +112,9 @@ const api = {
   representativeApi: representativeApi(axiosInstance),
   lotteryApi: lotteryApi(axiosInstance),
   contentApi:contentApi(axiosInstance),
-  advertisementApi:advertisementApi(axiosInstance)
+  advertisementApi:advertisementApi(axiosInstance),
+  agreementApi:agreementApi(axiosInstance),
+  insuranceApi: insuranceApi(axiosInstance),
+  projectApi: projectApi(axiosInstance)
 }
 export default api

+ 112 - 0
src/api/modules/agreement.ts

@@ -0,0 +1,112 @@
+import { ApiInstance } from '../type'
+import * as Types from '../types/agreementTypes';
+const agreementApi = (axiosInstance: ApiInstance) => ({
+
+  /**
+   * @param 协议管理
+   */
+  //协议添加
+  agreementAdd: (data: Types.agreementAddRequest) => axiosInstance.post('agreement/agreementAdd', data),
+  //协议修改
+  agreementUpdate: (data: Types.agreementAddRequest) => axiosInstance.post('agreement/agreementEdit', data),
+  //分页查询
+  agreementList: (data: Types.pagelistRequest) => axiosInstance.post('agreement/agreementList', data),
+  //详情信息查询
+  agreementGet: (data: any) => axiosInstance.get(`agreement/agreementGet?id=${data}`),
+  //协议授权保存
+  authSave: (data: Types.authSaveRequest) => axiosInstance.post('agreement/authSave', data),
+  //根据机构获取后线人员列表
+  getUserByDeptId: (deptId?: string, searchValue?: string) => axiosInstance.get(`agreement/getUserByDeptId?deptId=${deptId}&searchValue=${searchValue}`),
+  //协议授权机构查询
+  agreementdeptTree: (id: string, deptName: string) => axiosInstance.get(`agreement/deptTree?id=${id}&deptName=${deptName}`),
+  //启用禁用
+  sysUpAndDown: (data: Types.sysUpAndDownRequest) => axiosInstance.post('agreement/sysUpAndDown', data),
+  //批量删除
+  agreementDelete: (ids: any) => axiosInstance.delete(`/agreement/agreementDelete?ids=${ids}`),
+  //获取保险公司数结构
+  findTree: (data: any) => axiosInstance.post('esmInsCompany/findTree', data),
+  //复制协议
+  agreementCopy: (data: any) => axiosInstance.post('/agreement/agreementCopy', data),
+  /**
+    * @param 承保规则
+    */
+  //协议承保规则分组属性列表
+  ruleAttrGroup: (data: any) => axiosInstance.post('pltAgreement/attr/attrGroup', data),
+  //获取地区车牌
+  getAreaLicense: (data: any) => axiosInstance.get(`plt/areaLicense/getAreaLicense?parentNumber=${data}`),
+  //添加承保规则
+  agreementRulesAdd: (data: any) => axiosInstance.post('agreementRules/rulesAdd', data),
+  //修改承保规则
+  agreementRulesEdit: (data: any) => axiosInstance.post('agreementRules/rulesEdit', data),
+  //承保规则列表
+  agreementRulesList: (id: any) => axiosInstance.get(`agreementRules/rulesList?agreementId=${id}`),
+  //承保规则详情
+  agreementRulesGet: (id: any) => axiosInstance.get(`agreementRules/rulesGet?ruleId=${id}`),
+  //删除承保规则
+  agreementRulesDelete: (id: any) => axiosInstance.delete(`agreementRules/rulesDelete?id=${id}`,),
+
+  /**
+   *  费用管理接口
+   */
+  //协议费用管理新增
+  ptlAgreementCostsave: (data: any) => axiosInstance.post('ptlAgreementCost/save', data),
+  //协议规则费用修改
+  AgreementFeeupdate: (data: any) => axiosInstance.post('ptlAgreementCost/update', data),
+  //协议规则费用列表查询
+  queryByAgreementId: (data: any) => axiosInstance.post('ptlAgreementCost/queryByAgreementId', data),
+  //费用管理分页列表
+  queryByFeePage: (data: any) => axiosInstance.post('ptlAgreementCost/pageList', data),
+  //协议费用批量删除
+  AgreementFeedeleteByIds: (data: any) => axiosInstance.post('ptlAgreementCost/deleteByIds', data),
+  //协议费用批量启用/禁用
+  AgreementFeeEnableByIds: (isEnabled: boolean, ids: any) => axiosInstance.post('ptlAgreementCost/enableDisableByIds?isEnabled=' + isEnabled, ids),
+  //协议费用管理详情查询
+  AgreementFeeById: (id: any) => axiosInstance.get(`ptlAgreementCost/queryById?id=${id}`),
+  //协议上传文件
+  agreementEditFile: (data: any) => axiosInstance.post('agreement/agreementEditFile', data),
+
+
+
+  /**
+   * 外部协议
+   */
+  //外部协议添加
+  contactAdd: (data: any) => axiosInstance.post('/agreement/contactAdd', data),
+  //外部协议修改
+  contactEdit: (data: any) => axiosInstance.post('/agreement/contactEdit', data),
+  //费用列表崔查询
+  costExternalList: (data: any) => axiosInstance.post('/ptlAgreementCostExternal/costExternalList', data),
+  //补录协议费用新增
+  ptlAgreementCostExternalsave: (data: any) => axiosInstance.post('/ptlAgreementCostExternal/save', data),
+  //补录协议费用修改
+  ptlAgreementCostExternalupdate: (data: any) => axiosInstance.post('ptlAgreementCostExternal/updateList', data),
+  //查询协议下对接人所有费用
+  selectByAgreementIdAll: (data: any) => axiosInstance.post('ptlAgreementCostExternal/selectByAgreementIdAll', data),
+  //删除对接人
+  deleteByPtlAgreementIdAndMobile: (data: any) => axiosInstance.post('ptlAgreementCostExternal/deleteByPtlAgreementIdAndMobile', data),
+  //费用管理分页列表
+  CostExternalFeePageList: (data: any) => axiosInstance.post('ptlAgreementCostExternal/pageList', data),
+  //单个费用修改
+  FeeupdateById: (data: any) => axiosInstance.post('/ptlAgreementCostExternal/updateById', data),
+  //单个费用查询
+  FeegetById: (id: any) => axiosInstance.get('/ptlAgreementCostExternal/getById?id=' + id),
+  //协议费用管理批量禁用/启用
+  enableDisableByIds: (isEnabled: boolean, ids: any) => axiosInstance.post('ptlAgreementCostExternal/enableDisableByIds?isEnabled=' + isEnabled, ids),
+  //批量删除
+  deleteById: (data: any) => axiosInstance.post('ptlAgreementCostExternal/deleteById', data),
+  //批量修改审核方式
+  approvalMethodAll: (auditMethod: string, ids: any) => axiosInstance.post('ptlAgreementCostExternal/approvalMethodAll?auditMethod=' + auditMethod, ids),
+
+
+  //费用审核
+  // 协议费用管理审核成功
+  auditSuccess: (data: any) => axiosInstance.post('/ptlAgreementCost/auditSuccess', data),
+  // 协议费用管理审核失败
+  auditFailed: (data:any) => axiosInstance.post(`/ptlAgreementCost/auditFailed`,data),
+  // 补录协议费用管理审核成功
+  ExternalauditSuccess: (data: any) => axiosInstance.post('/ptlAgreementCostExternal/auditSuccess', data),
+  // 补录协议费用管理审核失败
+  ExternalauditFailed: (data: any) => axiosInstance.post('/ptlAgreementCostExternal/auditFailed', data),
+
+})
+export default agreementApi

+ 4 - 4
src/api/modules/institution.ts

@@ -1,7 +1,7 @@
 import { ApiInstance } from '../type'
 interface institutionData {
-  name: string;
-  system: string,
+  name?: string;
+  system?: string,
   [key: string]: any; // 允许其他任意字段
 }
 const institutionApi = (axiosInstance: ApiInstance) => ({
@@ -9,7 +9,7 @@ const institutionApi = (axiosInstance: ApiInstance) => ({
   institutionAdd: (data: institutionData) => axiosInstance.post('/dept/add', data, {}),
 
   //机构树形列表
-  institutionList:(data: institutionData) => axiosInstance.post('/dept/getDeptTree', data, {}),
+  institutionList:(data?: institutionData) => axiosInstance.post('/dept/getDeptTree', data),
 
   //编辑
   institutionEdit: (data: institutionData) => axiosInstance.post('/dept/update', data, {}),
@@ -22,7 +22,7 @@ const institutionApi = (axiosInstance: ApiInstance) => ({
   //获取机构信息
   deptGet: (params: institutionData) => axiosInstance.get('/dept/deptGet', {params})
 
-  
+
 })
 
 export default institutionApi

+ 46 - 0
src/api/modules/insurance.ts

@@ -0,0 +1,46 @@
+import { ApiInstance } from '../type'
+
+export interface SearchInsurance {
+  pages: number,
+  size: number,
+  username?: string,
+  insCompanyId?: string,
+  quoteStatus?: string,  //  0:启用 1:禁用
+  useStatus?: string, // 0 正常 1 不可用 2 未知
+  userDept?: string
+}
+
+interface FieldsItem {
+  type: string,
+  field: string,
+  label: string,
+  value: string,
+  isEdit: boolean
+}
+export interface AddAgreementAttribution {
+  insCompanyId: string,
+  userDept: string,
+  loginUrl: string,
+  username: string,
+  password: string,
+  fields: FieldsItem[],
+  userAbbr?: string,
+}
+export interface UpdateAgreementAttribution extends AddAgreementAttribution {
+  id: string
+}
+
+const insuranceApi = (axiosInstance: ApiInstance) => ({
+  getAgreementAttributionList: (data: SearchInsurance) => axiosInstance.post('/manager/attribution/getAgreementAttributionList', data), // 列表查询
+  leftList: (data: any) => axiosInstance.get(`/esmInsCompany/gainTopList?type=${data}`), //获取顶级保险公司
+  getDeptTree: (data: { sysCode: string }) => axiosInstance.post('/dept/getDeptTree', data), // 归属
+  addAgreementAttribution: (data: AddAgreementAttribution) => axiosInstance.post('/manager/attribution/addAgreementAttribution', data), // 新增
+  getPtlInsAttributionTemplate: (data: { insCompanyId: string }) => axiosInstance.post('/manager/attributionTemplate/getPtlInsAttributionTemplate', data), // 配置
+  getLoginUrl: (data: { companyId: string }) => axiosInstance.post('/manager/attribution/getLoginUrl', data), // 登录地址
+  getAgreementAttribution: (data: { id: string }) => axiosInstance.post('/manager/attribution/getAgreementAttribution', data), // 详情
+  updateAgreementAttribution: (data: any) => axiosInstance.post('/manager/attribution/updateAgreementAttribution', data), // 编辑
+  enableAgreementAttribution: (data: { ids: string[] }) => axiosInstance.post('/manager/attribution/enableAgreementAttribution', data), // 启用
+  disableAgreementAttribution: (data: { ids: string[] }) => axiosInstance.post('/manager/attribution/disableAgreementAttribution', data), // 禁用
+  deleteAgreementAttribution: (data: { ids: string[] }) => axiosInstance.post('/manager/attribution/deleteAgreementAttribution', data)
+})
+export default insuranceApi

+ 2 - 1
src/api/modules/lottery.ts

@@ -47,7 +47,8 @@ const lotteryApi = (axiosInstance: ApiInstance) => ({
   deleteActivity: (data: { id: string }) => axiosInstance.post('/activity/deleteActivity', data),
   endActivity: (data: { id: string }) => axiosInstance.post('/activity/endActivity', data),
   getActivityRecord: (data: GetActivityRecord) => axiosInstance.post('/activity/getActivityRecord', data),
-  exportActivityRecord: (data: string[]) => axiosInstance.post('/activity/exportActivityRecord', data)
+  exportActivityRecord: (data: string[]) => axiosInstance.post('/activity/exportActivityRecord', data),
+  checkActivityDept: (data:{ deptIds: string[] }) => axiosInstance.post('/activity/checkActivityDept', data),
 })
 
 export default lotteryApi

+ 1 - 2
src/api/modules/operation.ts

@@ -13,7 +13,7 @@ export interface AddOperation {
   leaderId: string,
   referrerId: string,
 
-  sex?: string,
+  sex?: string | null,
   birthday?: string,
   nations?: string,
   koseki?: string,
@@ -35,7 +35,6 @@ export interface AddOperation {
   salaryLevel?: string,
   bankCardId?: string,
   bankName?: string,
-
   sfz1?: string,
   sfz2?: string,
   qualification?: string,

+ 28 - 0
src/api/modules/project.ts

@@ -0,0 +1,28 @@
+import { ApiInstance } from '../type'
+import {LocationQueryValue} from "vue-router";
+
+export interface GetSchemeList {
+  pages: number,
+  size: number,
+  companyId: string,
+  productId?: string,
+  schemeType?: string,
+  description?: string
+}
+
+const projectApi = (axiosInstance: ApiInstance) => ({
+  getBusinessInsurance: () => axiosInstance.get('/scheme/getBusinessInsurance'), // 险种
+  getScheme: (data: string | null | LocationQueryValue[]) => axiosInstance.get(`/scheme/getScheme?id=${data}`), // 详情
+  getSchemeList: (data: GetSchemeList) => axiosInstance.post('/scheme/getSchemeList', data), // 险种
+  jyxList: (data: { company: string }) => axiosInstance.post('/jyxConfig/jyxList', data), // 驾意险
+  saveScheme: (data: any) => axiosInstance.post('/scheme/saveScheme', data), // 保存方案
+  deleteScheme: (data: { id: string }) => axiosInstance.post('/scheme/deleteScheme', data), // 删除
+  getSchemeByCompany: (data: {
+    companyId: string,
+    productId: string,
+    schemeType: string
+  }) => axiosInstance.get(`/scheme/getSchemeByCompany?companyId=${data.companyId}&productId=${data.productId}&schemeType=${data.schemeType}`), // 新增时如果有方案获取对应方案
+  getCompanyList:() => axiosInstance.get('/scheme/getCompanyList')
+})
+
+export default projectApi

+ 8 - 8
src/api/modules/role.ts

@@ -4,31 +4,31 @@ interface roleData {
   desc: string;
   system: string,
   permsId: number[],
-  perms: string[]
+  perms: string[],
 }
 const roleApi = (axiosInstance: ApiInstance) => ({
   // 新增角色
-  roleAdd: (data: roleData) => axiosInstance.post('userRole/roleAdd', data, {}),
+  roleAdd: (data: any) => axiosInstance.post('userRole/roleAdd', data, {}),
 
   //角色列表
   roleList:(data: roleData) => axiosInstance.post('userRole/roleList', data, {}),
 
   //查询角色信息
-  roleInfo:(params: roleData) => axiosInstance.get('userRole/roleGet', {params}),
+  roleInfo:(params: any) => axiosInstance.get('userRole/roleGet', {params}),
 
   //编辑角色信息
-  roleUpdate:(data: roleData) => axiosInstance.post('userRole/roleUpdate', data,{}),
+  roleUpdate:(data: any) => axiosInstance.post('userRole/roleUpdate', data,{}),
 
   //角色删除检测
-  roleDeleteValid:(data: roleData) => axiosInstance.post('userRole/roleDeleteValid', data,{}),
+  roleDeleteValid:(data: any) => axiosInstance.post('userRole/roleDeleteValid', data),
 
   //角色删除检测
-  roleDelete:(data: roleData) => axiosInstance.post('userRole/roleDelete', data,{}),
+  roleDelete:(data: any) => axiosInstance.post('userRole/roleDelete', data,{}),
 
   //配置路由
   updateConfig:(data: any) => axiosInstance.post('system/updateConfig', data,{})
-  
- 
+
+
 })
 
 export default roleApi

+ 214 - 0
src/api/types/agreementTypes.ts

@@ -0,0 +1,214 @@
+/**
+ * AgreementAddParam,协议表新增dto
+ */
+export type agreementAddRequest = {
+  /**
+   * 协议id
+   */
+  id?: string;
+  /**
+   * 协议简称
+   */
+  agreementAbbreviation: string;
+  /**
+   * 协议描述
+   */
+  agreementName: string;
+  /**
+   * 协议名称
+   */
+  agreementTitle: string;
+  /**
+   * 协议类型 1-平台协议-平台结算 2-私有协议-自行结算 3-网销协议-自行结算
+   */
+  agreementType: string;
+  /**
+   * 保司账号id
+   */
+  attributionId: string;
+  /**
+   * 关联-保险公司id
+   */
+  companyId: string;
+  /**
+   * 对接管理人id
+   */
+  custodianId: string;
+  /**
+   * 协议归属id
+   */
+  deptId: string;
+  /**
+   * 对接人
+   */
+  dockingPerson: string;
+  /**
+   * 联系方式(对接人)
+   */
+  dockingPhone: string;
+  /**
+   * 协议失效时间
+   */
+  endDate: string;
+  /**
+   * 协议文件
+   */
+  fileId?: string;
+  /**
+   * 结算周期
+   */
+  moneyTime: number;
+  /**
+   * 协议生效时间
+   */
+  startDate: string;
+  /**
+   * 是否含增值税结算 0-否 1-是
+   */
+  withTax: string;
+  [property: string]: any;
+}
+/**
+ * AgreementParam,协议表分页查询dto
+ */
+export type pagelistRequest = {
+  /**
+   * 协议类型 1-平台协议-平台结算 2-私有协议-自行结算 3-网销协议-自行结算
+   */
+  agreementType?: string;
+  /**
+   * 出单账号
+   */
+  attributionName?: string;
+  /**
+   * 关联-保险公司id
+   */
+  companyId?: string;
+  /**
+   * 对接人(手机号,名字查询)
+   */
+  dockingPerson?: string;
+  /**
+   * 协议状态 0-启用 1-停用
+   */
+  isEnable?: string;
+  /**
+   * 失效结束时间
+   */
+  loseEndDate?: string;
+  /**
+   * 失效开始时间
+   */
+  loseStartDate?: string;
+  page?: Page;
+  pages?: number;
+  /**
+   * 协议搜索值
+   */
+  searchValue?: string;
+  size?: number;
+  /**
+   * 生效结束时间
+   */
+  takeEndDate?: string;
+  /**
+   * 生效开始时间
+   */
+  takeStartDate?: string;
+  /**
+   * 有效状态 0.生效中 1.已失效
+   */
+  validStatus?: string;
+  /**
+   * 是否含增值税结算 0-否 1-是
+   */
+  withTax?: string;
+  [property: string]: any;
+}
+/**
+ * Page
+ */
+export type Page = {
+  countId?: string;
+  current?: number;
+  maxLimit?: number;
+  optimizeCountSql?: PageObject;
+  optimizeJoinOfCountSql?: boolean;
+  orders?: OrderItem[];
+  pages?: number;
+  records?: { [key: string]: any }[];
+  searchCount?: PageObject;
+  size?: number;
+  total?: number;
+  [property: string]: any;
+}
+
+/**
+* PageObject
+*/
+export type PageObject = {
+  countId?: string;
+  current?: number;
+  maxLimit?: number;
+  optimizeCountSql?: PageObject;
+  optimizeJoinOfCountSql?: boolean;
+  orders?: OrderItem[];
+  pages?: number;
+  records?: { [key: string]: any }[];
+  searchCount?: PageObject;
+  size?: number;
+  total?: number;
+  [property: string]: any;
+}
+
+/**
+* OrderItem
+*/
+export type OrderItem = {
+  asc?: boolean;
+  column?: string;
+  [property: string]: any;
+}
+/**
+ * AgreementAuthParam,协议授权保存dto
+ */
+export type authSaveRequest = {
+  /**
+   * 机构id数组
+   */
+  deptIds: string[];
+  /**
+   * 协议id
+   */
+  id: string;
+  [property: string]: any;
+}
+
+export type agreementdeptTreeRequest = {
+  id: string;
+  [property: string]: any;
+}
+
+/**
+ * UpAndDownVo,启用/禁用实体
+ */
+export type sysUpAndDownRequest = {
+  /**
+   * id集合
+   */
+  ids: string[];
+  /**
+   * 操作类型
+   */
+  opType: string;
+  [property: string]: any;
+}
+/**
+ * 操作类型
+ */
+
+export type agreementDeleteRequest = {
+  ids: string[];
+  [property: string]: any;
+}
+

BIN
src/assets/images/empty.png


BIN
src/assets/images/fee.png


BIN
src/assets/images/pdf.png


+ 52 - 0
src/assets/styles/globals.css

@@ -224,11 +224,16 @@ textarea {
   margin-bottom: 15px;
 }
 
+.m-btn-20 {
+  margin-bottom: 20px;
+}
+
 .vh100 {
   height: calc(100vh - 200px);
 }
 
 .strong {
+  font-size: 14px;
   font-weight: bold;
 }
 
@@ -248,6 +253,26 @@ textarea {
   padding: 30px;
 }
 
+.m-top-20 {
+  margin-top: 20px;
+}
+
+.m-left-10 {
+  margin-left: 10px;
+}
+
+.m-left-20 {
+  margin-left: 20px;
+}
+
+.m-right-10 {
+  margin-right: 20px;
+}
+
+.m-right-20 {
+  margin-right: 20px;
+}
+
 /* 消息提示弹窗 */
 .el-message-box {
   padding: 32px 32px 24px;
@@ -282,3 +307,30 @@ textarea {
 }
 
 /* 消息提示弹窗 */
+
+/* dialog对话框样式 */
+.el-dialog {
+  padding: 0;
+}
+
+.el-dialog__header {
+  padding: 13px;
+  text-align: center;
+  border-bottom: 1px solid #eee;
+}
+
+.el-dialog__header .el-dialog__title {
+  font-weight: bold;
+  color: #333;
+}
+
+.el-dialog__body {
+  padding: 24px;
+}
+
+.el-dialog__footer {
+  padding: 13px 24px;
+  border-top: 1px solid #eee;
+}
+
+/* dialog对话框样式 */

+ 6 - 7
src/components/LoginForm/index.vue

@@ -2,7 +2,6 @@
 import type { FormInstance, FormRules } from "element-plus";
 import useUserStore from "@/store/modules/user";
 import { ref, useTemplateRef, onMounted,onUnmounted } from "vue";
-import { systemRoutes } from "@/router/routes";
 
 defineOptions({
   name: "LoginForm",
@@ -39,7 +38,7 @@ const form = ref({
   userName: props.userName ?? localStorage.login_account ?? "",
   passWord: "",
   captcha: "",
-  system: "",
+  system: "KK123",
   captchaId: 0,
   systemCode: "",
 });
@@ -100,7 +99,7 @@ function handleLogin() {
           loading.value = false;
           refreshCaptcha();
         });
-      
+
     }
   });
 }
@@ -153,7 +152,7 @@ function handleLogin() {
     :model="form"
     :rules="rules"
     class="min-h-500px w-full flex-col-stretch-center"
-    style="padding: 81px 75px"
+    style="padding: 81px 75px;"
     size="large"
   >
     <!-- <div class="mb-6">
@@ -210,7 +209,7 @@ function handleLogin() {
           </ElInput>
         </ElFormItem>
 
-        <ElFormItem prop="captcha" style="margin-bottom: 0">
+        <ElFormItem prop="captcha" style="margin-bottom: 0;">
           <ElCol :md="14" :sm="14" :xs="14">
             <ElInput
               v-model="form.captcha"
@@ -225,7 +224,7 @@ function handleLogin() {
           </ElCol>
           <ElCol :md="10" :sm="10" :xs="10" flex>
             <img
-              style="width: 100%; height: 32px; margin-left: 20px"
+              style="width: 100%; height: 32px; margin-left: 20px;"
               class="pointer"
               :src="captchaSrc"
               @click="refreshCaptcha"
@@ -248,7 +247,7 @@ function handleLogin() {
         :loading="loading"
         type="primary"
         @keydown.enter="handleEnter"
-        style="width: 100%"
+        style="width: 100%;"
         @click.prevent="handleLogin"
       >
         登录

+ 72 - 0
src/components/SearchForm/index.vue

@@ -0,0 +1,72 @@
+<template>
+  <ElForm :inline="false" :model="formInline" class="demo-form-inline" label-width="100px">
+    <ElRow :gutter="20">
+      <ElCol v-for="(field, index) in fields" :key="index" :md="8">
+        <ElFormItem :label="field.label">
+          <component v-if="field.type === 'input'" is="el-input" v-model="formInline[field.model]"
+            :placeholder="field.placeholder" clearable class="el-inputs" />
+          <component v-else-if="field.type === 'select'" is="el-select" v-model="formInline[field.model]"
+            :placeholder="field.placeholder" clearable class="el-inputs">
+            <el-option v-for="(item, index) in field.options" :key="index" :label="item.label" :value="item.value" />
+          </component>
+          <component v-else-if="field.type === 'cascader'" is="el-cascader" v-model="formInline[field.model]"
+            :options="field.options" :props="field.props" filterable clearable @change="field.onChange"
+            class="el-inputs" />
+          <component v-else-if="field.type === 'date'" is="el-date-picker" v-model="formInline[field.model]"
+            :type="field.dateType" range-separator="至" :start-placeholder="field.startplaceholder"
+            format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" time-format="HH:mm:ss"
+            :end-placeholder="field.endplaceholder" clearable @change="field.onChange" />
+        </ElFormItem>
+      </ElCol>
+      <ElCol :md="4" >
+        <ElFormItem>
+          <div class="flex j-end" style="width: 100%;">
+            <el-button type="primary" @click="handleSearch">
+              <template #icon>
+                <SvgIcon name="i-ep:search" />
+              </template>
+              查询
+            </el-button>
+            <el-button type="default" @click="handleReset">重置</el-button>
+          </div>
+        </ElFormItem>
+      </ElCol>
+    </ElRow>
+  </ElForm>
+</template>
+
+<script lang="ts" setup>
+import { defineProps } from "vue";
+
+const props = defineProps<{
+  fields: Array<{
+    label: string; // 字段标签
+    model: string; // v-model 绑定的字段名
+    type: string; // 字段类型 ('input', 'select', 'cascader', 'date')
+    placeholder?: string; // 占位符
+    options?: Array<{ [key: string]: any }>; // 选项列表
+    props?: any; // Cascader 组件的其他 props
+    onChange?: (value: any) => void; // 变化事件
+    dateType?: string; // 日期选择器类型
+    [key: string]: any,
+  }>; // 字段信息数组
+  formInline: any; // 表单数据
+  onSearch: () => void; // 查询事件
+  onReset: () => void; // 重置事件
+}>();
+onBeforeMount(() => {
+  console.log(props)
+})
+const handleSearch = () => {
+  props.onSearch(); // 调用父组件的查询事件
+};
+
+const handleReset = () => {
+  props.onReset(); // 调用父组件的重置事件
+};
+</script>
+<style lang="scss">
+.el-inputs {
+  width: 100% !important;
+}
+</style>

+ 209 - 0
src/components/TableTree/index.vue

@@ -0,0 +1,209 @@
+<template>
+  <div class="containerTable">
+    <div class="table_box">
+      <div class="table-title-btn">
+        <slot name="table-title-btn"></slot>
+      </div>
+      <el-table ref="TableTreeRef" class="custom-table" v-loading="props.loading"
+        :header-cell-style="{ 'background-color': '#EEF1F6', 'border-bottom': '1px solid #EEF1F6', 'font-size': '15px', 'color': '#333', 'padding': '10px 0' }"
+        :data="props.tableData" :height="tableHeight" row-key="id"
+         style="width: 100%;">
+        <!-- 空内容自定义 -->
+        <template #empty>
+          <div class="flex f-c a-c ">
+            <img src="../../assets/images/empty.png" alt="">
+            <span style="font-size: 14px;color: #909399;">暂无数据</span>
+          </div>
+        </template>
+        <el-table-column type="selection" v-if="showSelect" :selectable="selectable" width="55" />
+        <el-table-column v-if="showIndex" type="index" label="序号" width="55" />
+        <el-table-column v-for="column in columns" :key="column.prop" :prop="column.prop" :label="column.label"
+          :sortable="column.sortable ? 'custom' : false" :width="column.width" :align="column.align?column.align:'center'" >
+          <template #default="scope">
+            <RouterLink v-if="column.component === 'RouterLink'" :to="getRouterLinkTo(scope.row, column)" class="custom-link">
+              {{ scope.row[column.prop] }}
+            </RouterLink>
+            <el-tag v-else-if="column.component === 'Tag'" :type="getTagTypeAndText(scope.row, column).type">
+              {{ getTagTypeAndText(scope.row, column).text }}
+            </el-tag>
+            <ImagePreview v-else-if="column.component === 'Image'" :src="getImgTo(scope.row, column)" width="50px" height="50px"></ImagePreview>
+            <span v-else>
+              {{ getDisplayText(scope.row, column) }}
+            </span>
+          </template>
+          <template #header>
+            <slot name="search" :slotData="column"></slot>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" fixed="right" :width="props.columnwidth" align="center">
+          <template #default="scope">
+            <div class="flex j-c a-c">
+              <slot name="operation" class="operationClass" :operationData="scope.row"></slot>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { defineProps, ref, onMounted, defineEmits } from "vue";
+import type { TableInstance } from "element-plus";
+
+interface TableRow {
+  [key: string]: any;
+}
+interface Row {
+  [key: string]: any; // 假设Row是一个具有任意属性的对象
+}
+interface RouterLink {
+  name: string;
+}
+const emit = defineEmits([]);
+const props = defineProps<{
+  tableData: Array<TableRow>;
+  columnwidth: number,
+  loading?:boolean,
+  columns: {
+    prop: string;
+    label: string;
+    sortable?: boolean;
+    width?: string;
+    style?: string;
+    align?:string,
+    component?:string,
+    formatter?: (row: any, column: any, cellValue: any, index: number) => any;
+
+  }[];
+  showSelect: boolean;
+  showIndex: boolean;
+  tableheight?: (number | string),
+}>();
+const tableHeight = ref(props.tableheight); // 初始化表格高度
+
+const TableTreeRef = ref<TableInstance>();
+const TableData = ref<TableRow[]>();
+onMounted(() => {
+  let counter = 1;
+  TableData.value = props.tableData.map((row) => ({
+    ...row,
+    _edit: false, // 设置默认非编辑状态
+    counter: counter++
+
+  }));
+  setTimeout(() => {
+    const tableElement = TableTreeRef.value?.$el as HTMLElement;
+    if (tableElement) {
+      const offsetTop = tableElement.offsetTop; // 获取 offsetTop
+      let height = window.innerHeight;
+      console.log(height)
+      tableHeight.value = height - offsetTop - 300 + "px";
+    }
+  }, 10);
+
+
+});
+//空值处理
+const displayValue = (value: any) => {
+  return value === null || value === undefined || value === '' ? '-' : value;
+}
+const selectable = () => true;
+// 格式化内容配置
+function getRouterLinkTo(row: Row, column: any): RouterLink {
+  return column.formatter ? column.formatter(row, column, row[column.prop]) : { name: 'OrganizationAdd' };
+}
+function getImgTo(row: Row, column: any) {
+  return row[column.prop];
+}
+function getTagTypeAndText(row: Row, column: any) {
+  // 使用 formatter 返回一个对象,包含 type 和 text
+  return column.formatter ? column.formatter(row, column, row[column.prop]) : { type: 'primary', text: row[column.prop] };
+}
+function getDisplayText(row: Row, column: any): string {
+  return column.formatter ? column.formatter(row, column, row[column.prop]) :displayValue(row[column.prop]);
+}
+</script>
+<style lang="scss" scoped>
+.el-table .el-table__cell .routerlick > a {
+  color: red;
+}
+
+.containerTable {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  height: auto;
+}
+
+.table_box {
+  flex: 1;
+  margin-bottom: 10px;
+  overflow-y: auto;
+
+  .table_item_container {
+    padding: 0;
+    margin-left: 20px;
+
+    .table_item {
+      margin: 5px 0;
+    }
+  }
+
+  .table-title-btn {
+    margin-bottom: 20px;
+
+    :deep(.el-button) {
+      margin-right: 20px;
+    }
+  }
+}
+
+/* 自定义树状表格图标 */
+
+// /* 有子节点 且未展开 */
+// .custom-table :deep(.el-table__expand-icon) {
+//   display: flex;
+//   align-items: center;
+//   justify-content: center;
+//   width: 18px;
+//   height: 18px;
+//   background: url("../../assets/icons/caretRight.svg") no-repeat;
+//   background-size: auto;
+
+//   svg {
+//     display: none;
+//   }
+// }
+
+// /* 有子节点 且已展开 */
+// .custom-table :deep(.el-table__expand-icon--expanded) {
+//   display: inline-block;
+//   display: flex;
+//   align-items: center;
+//   justify-content: center;
+//   width: 18px;
+//   height: 18px;
+//   content: "";
+//   background: url("../../assets/icons/caretRight.svg") no-repeat;
+//   background-size: auto;
+//   transform: rotate(90deg);
+
+//   svg {
+//     display: none;
+//   }
+// }
+
+:deep(.el-table) {
+  .el-table__row--level-1 {
+    background-color: #eef1f6;
+  }
+}
+
+.operationClass {
+  .el-button {
+    margin: 0 7px;
+  }
+}
+</style>

+ 303 - 0
src/components/complexTable/index.vue

@@ -0,0 +1,303 @@
+<template>
+  <div class="containerTable">
+    <div class="table_box">
+      <div class="table-title-btn">
+        <slot name="table-title-btn"></slot>
+      </div>
+      <el-table ref="multipleTableRef"
+        :header-cell-style="{ 'background-color': '#F7F7F9', 'border-bottom': '1px solid #EEF1F6', 'font-size': '15px', 'color': '#333', 'padding': '10px 0' }"
+        :data="props.tableData" :height="tableHeight" :max-height="maxHeight" table-layout="fixed" style="width: 100%;"
+        @selection-change="selectionChange" @select-all="selectAllChange" :highlight-current-row="highlightCurrentRow"
+        @current-change="currentChange" v-loading="loading">
+        <!-- 空内容自定义 -->
+        <template #empty>
+          <div class="flex f-c a-c ">
+            <img src="../../assets/images/empty.png" alt="">
+            <span style="font-size: 14px;color: #909399;">暂无数据</span>
+          </div>
+        </template>
+        <el-table-column type="selection" v-if="showSelect" :selectable="selectable" width="55" fixed="left" />
+        <el-table-column type="expand" v-if="secondaryTable">
+          <template #default="props">
+            <div>
+              <el-table :data="props.row.children">
+                <el-table-column v-for="column in secondaryColumns" :key="column.prop" :prop="column.prop"
+                  :label="column.label" :sortable="column.sortable ? 'custom' : false" :width="column.width">
+                  <template #header>
+                    <slot name="search" :slotData="column"></slot>
+                  </template>
+                  <template #default="scope">
+                    <RouterLink v-if="column.component === 'RouterLink'" :to="getRouterLinkTo(scope.row, column)"
+                      class="custom-link">
+                      {{ scope.row[column.prop] }}
+                    </RouterLink>
+                    <el-tag v-else-if="column.component === 'Tag'" :type="getTagTypeAndText(scope.row, column).type">
+                      {{ getTagTypeAndText(scope.row, column).text }}
+                    </el-tag>
+                    <ImagePreview v-else-if="column.component === 'Image'" :src="getImgTo(scope.row, column)"
+                      width="50px" height="50px"></ImagePreview>
+                    <span v-else>
+                      {{ getDisplayText(scope.row, column) }}
+                    </span>
+                  </template>
+                </el-table-column>
+                <el-table-column label="操作" fixed="right" :width="props.columnwidth">
+                  <template #default="scope">
+                    <slot name="secondary" :secondaryData="scope.row"></slot>
+                  </template>
+                </el-table-column>
+              </el-table>
+            </div>
+
+          </template>
+        </el-table-column>
+        <el-table-column v-if="showIndex" type="index" label="序号" width="55" />
+        <el-table-column v-for="column in columns" :key="column.prop" :prop="column.prop" :label="column.label"
+          :sortable="column.sortable ? 'custom' : false" :width="column.width"
+          :align="column.align ? column.align : 'center'">
+          <template #header>
+            <slot name="search" :slotData="column"></slot>
+          </template>
+          <template #default="scope">
+            <RouterLink v-if="column.component === 'RouterLink'" :to="getRouterLinkTo(scope.row, column)"
+              class="custom-link">
+              {{ scope.row[column.prop] }}
+            </RouterLink>
+            <el-tag v-else-if="column.component === 'Tag'" :type="getTagTypeAndText(scope.row, column).type">
+              {{ getTagTypeAndText(scope.row, column).text }}
+            </el-tag>
+            <ImagePreview v-else-if="column.component === 'Image'" :src="getImgTo(scope.row, column)" width="50px"
+              height="50px"></ImagePreview>
+            <div v-else>
+              <div
+                v-if="typeof getDisplayText(scope.row, column) === 'string' && getDisplayText(scope.row, column).includes('\n')"
+                class="flex f-c ">
+                <span v-for="(line, index) in getDisplayText(scope.row, column).split('\n')" :key="index">{{ line
+                }}</span>
+              </div>
+              <span v-else>
+                {{ getDisplayText(scope.row, column) }}
+              </span>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" fixed="right" :width="props.columnwidth" v-if="props.showOperation" align="center">
+          <template #default="scope">
+            <div class="flex a-c j-c">
+              <slot name="operation" :operationData="scope.row"></slot>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+    <div class="page_box" v-if="props.pageInfo">
+      <el-pagination v-model:current-page="props.pageInfo.pageNum" v-model:page-size="props.pageInfo.pageSize"
+        :page-sizes="props.pageInfo.sizes" layout="total, sizes, prev, pager, next, jumper"
+        :total="props.pageInfo.total" @size-change="handleSizeChange" @current-change="handleCurrentChange" />
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { defineProps, ref, onMounted, defineEmits } from "vue";
+import type { TableInstance } from "element-plus";
+
+interface TableRow {
+  [key: string]: any;
+}
+const emit = defineEmits(['getList', 'selectAllChange', 'selectionChange', 'currentChange']);
+interface Props{
+  tableData: Array<TableRow>;
+  columnwidth: number,//操作栏宽度
+  secondaryTable?: boolean,//是否展示二级表格
+  highlightCurrentRow?: boolean,//是否可点击
+  showOperation?: boolean,//操作显示/隐藏
+  loading?:boolean,
+  columns: {
+    prop: string;
+    label: string;
+    sortable?: boolean;
+    width?: string;
+    style?: string;
+    align?: string,
+    component?: string,
+    formatter?: (row: any, column: any, cellValue: any, index: number) => any;
+
+  }[];
+  secondaryColumns?: {
+    prop: string;
+    label: string;
+    sortable?: boolean;
+    width?: string;
+    style?: string;
+    component?: string,
+    formatter?: (row: any, column: any, cellValue: any, index: number) => any;
+  }[];
+  pageInfo?: {
+    pageNum: number;
+    pageSize: number;
+    sizes: number[];
+    total: number;
+  };
+  showSelect: boolean;
+  showIndex: boolean;
+  tableheight?: (number | string),
+  maxHeight?: (number | string),
+  uniqueIdName?: string,
+};
+interface Row {
+  [key: string]: any; // 假设Row是一个具有任意属性的对象
+}
+interface RouterLink {
+  name: string;
+}
+const props = withDefaults(defineProps<Props>(), {
+  showOperation: true, // 默认值为 true
+});
+const loading=ref(false)
+const tableHeight = ref(props.tableheight); // 初始化表格高度
+const newSelectionList = ref<string[]>([]);
+const multipleTableRef = ref<TableInstance>();
+const TableData = ref<TableRow[]>();
+watch(props, () => {
+  if(props.loading!=undefined){
+    loading.value=props.loading;
+
+  }
+})
+onMounted(() => {
+  let counter = 1;
+  TableData.value = props.tableData.map((row) => ({
+    ...row,
+    _edit: false, // 设置默认非编辑状态
+    counter: counter++
+
+  }));
+  setTimeout(() => {
+    const tableElement = multipleTableRef.value?.$el as HTMLElement;
+    if (tableElement && !props.maxHeight) {
+      const offsetTop = tableElement.offsetTop; // 获取 offsetTop
+      let height = window.innerHeight;
+      tableHeight.value = height - offsetTop - 270 + "px";
+    }
+  }, 10);
+
+
+});
+const selectable = () => true;
+const handleSizeChange = (val: number) => {
+  if (props.pageInfo) {
+    props.pageInfo.pageSize = val
+    emit('getList', props.pageInfo);
+  }
+}
+//多选
+
+const selectAllChange = (value: any[]) => {
+
+  newSelectionList.value = [];
+  if (value.length > 0) {
+    value.map(val => {
+      newSelectionList.value.push(val.id)
+    })
+  } else {
+    newSelectionList.value = [];
+  }
+  emit('selectAllChange', newSelectionList.value)
+
+}
+//行点击事件
+const currentChange = (currentRow: any, _oldCurrentRow: any) => {
+  emit('currentChange', currentRow)
+}
+const selectionChange = (value: any[]) => {
+  newSelectionList.value = [];
+  if (value.length > 0) {
+    value.map(val => {
+      if (props.uniqueIdName) {
+        newSelectionList.value.push(val[props.uniqueIdName])
+      } else {
+        newSelectionList.value.push(val.id)
+      }
+    })
+  } else {
+    newSelectionList.value = [];
+  }
+  emit('selectionChange', newSelectionList.value)
+}
+const handleCurrentChange = (val: number) => {
+  if (props.pageInfo) {
+    props.pageInfo.pageNum = val
+    emit('getList', props.pageInfo);
+  }
+
+}
+//空值处理
+const displayValue = (value: any) => {
+  return value === null || value === undefined || value === '' ? '-' : value;
+}
+// 格式化内容配置
+function getRouterLinkTo(row: Row, column: any): RouterLink {
+  return column.formatter ? column.formatter(row, column, row[column.prop]) : { name: 'OrganizationAdd' };
+}
+function getImgTo(row: Row, column: any) {
+  return row[column.prop];
+}
+function getTagTypeAndText(row: Row, column: any) {
+  // 使用 formatter 返回一个对象,包含 type 和 text
+  return column.formatter ? column.formatter(row, column, row[column.prop]) : { type: 'primary', text: row[column.prop] };
+}
+function getDisplayText(row: Row, column: any): string {
+  return column.formatter ? column.formatter(row, column, row[column.prop]) : displayValue(row[column.prop]);
+}
+</script>
+<style lang="scss" scoped>
+.el-table .el-table__cell .routerlick > a {
+  color: red;
+}
+
+.containerTable {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  height: auto;
+}
+
+.custom-link {
+  color: #0083ff;
+
+  /* 设置文字颜色 */
+  text-decoration: none;
+
+  /* 去掉下划线 */
+}
+
+.table_box {
+  flex: 1;
+  margin-bottom: 10px;
+  overflow-y: auto;
+
+  .table_item_container {
+    padding: 0;
+    margin-left: 20px;
+
+    .table_item {
+      margin: 5px 0;
+    }
+  }
+
+  .table-title-btn {
+    margin-bottom: 20px;
+
+    :deep(.el-button) {
+      margin-right: 20px;
+    }
+  }
+}
+
+.page_box {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+}
+</style>

+ 14 - 0
src/components/empty/index.vue

@@ -0,0 +1,14 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <div class="flex f-c a-c " >
+      <img src="../../assets/images/empty.png" alt="">
+      <span style="font-size: 14px;color: #909399;">暂无数据</span>
+    </div>
+  </div>
+</template>
+
+<!-- 行为区域 -->
+<script lang='ts' setup>
+</script>
+<!-- 样式区域 -->

+ 356 - 0
src/components/ruleCondition/index.vue

@@ -0,0 +1,356 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <el-dialog v-model="props.ruleConditionVisible" title="规则条件" width="500" draggable @close="close">
+      <div class="Container flex f-c">
+        <div class="flex ">
+          <el-input v-model="attrName" style="margin-right: 20px;" clearable placeholder="搜索"
+            @keydown.enter="handleEnter" @clear="ruleAttr" />
+          <el-button @click="ruleAttr" type="primary">查找</el-button>
+        </div>
+        <div class="conditionSet flex ">
+          <div class="tabLeft">
+            <ul>
+              <li :class="{ active: item.name == activename }" v-for="(item, index) in typeList" :key="index"
+                @click="itemclick(item)">{{ item.name }}</li>
+            </ul>
+          </div>
+          <div class="tabRight flex f-c">
+            <el-checkbox-group v-model="checked.VehicleInformation" @change="change($event, 'VehicleInformation')"
+              v-for="(item, index) in dataStore.VehicleInformation" :key="index" v-if="activename == '车辆信息'">
+              <el-checkbox v-model="item.id" :label="item.attrName" :value="item.attrName" size="large" />
+            </el-checkbox-group>
+            <el-checkbox-group v-model="checked.VehicleOwnerInformation"
+              @change="change($event, 'VehicleOwnerInformation')"
+              v-for="(item, index) in dataStore.VehicleOwnerInformation" :key="index" v-if="activename == '车主信息'">
+              <el-checkbox v-model="item.id" :label="item.attrName" :value="item.attrName" size="large" />
+            </el-checkbox-group>
+            <el-checkbox-group v-model="checked.PolicyholderInformation"
+              @change="change($event, 'PolicyholderInformation')"
+              v-for="(item, index) in dataStore.PolicyholderInformation" :key="index" v-if="activename == '投保人信息'">
+              <el-checkbox v-model="item.id" :label="item.attrName" :value="item.attrName" size="large" />
+            </el-checkbox-group>
+            <el-checkbox-group v-model="checked.InsuredInformation" @change="change($event, 'InsuredInformation')"
+              v-for="(item, index) in dataStore.InsuredInformation" :key="index" v-if="activename == '被保人信息'">
+              <el-checkbox v-model="item.id" :label="item.attrName" :value="item.attrName" size="large" />
+            </el-checkbox-group>
+            <el-checkbox-group v-model="checked.InsuranceInformation" @change="change($event, 'InsuranceInformation')"
+              v-for="(item, index) in dataStore.InsuranceInformation" :key="index" v-if="activename == '商业险'">
+              <el-checkbox v-model="item.id" :label="item.attrName" :value="item.attrName" size="large" />
+            </el-checkbox-group>
+            <el-checkbox-group v-model="checked.InsuranceCostFactor" @change="change($event, 'InsuranceCostFactor')"
+              v-for="(item, index) in dataStore.InsuranceCostFactor" :key="index" v-if="activename == '保司费用因子'">
+              <el-checkbox v-model="item.id" :label="item.attrName" :value="item.attrName" size="large" />
+            </el-checkbox-group>
+          </div>
+        </div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="cancel">取消</el-button>
+          <el-button type="primary" @click="save">
+            保存
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+  </div>
+</template>
+
+<!-- 行为区域 -->
+<script lang='ts' setup>
+import { ref, reactive } from 'vue'
+import api from '@/api'
+import { defineProps, defineEmits } from "vue";
+const props = defineProps<{
+  ruleConditionVisible: boolean,
+  attrIdList?: any,
+}>();
+const emits = defineEmits(["cancel", 'save']);
+const cancel = () => {
+  emits("cancel")
+}
+const close = () => {
+  emits("cancel")
+}
+watch(props, () => {
+  if (props.attrIdList && props.attrIdList.length) {
+    attractivelist.value = props.attrIdList;//获取编辑数据
+    const matchedid = matchDataByIds(attractivelist.value);//
+    const matchedinfo = matchDataByInfos(attractivelist.value)
+    Object.keys(checked).forEach(key => { //复选框回显
+      checked[key as keyof checkeddata] = matchedid[key] || [];
+    });
+    Object.keys(checked).forEach(key => {//条件数据回显
+      checkeddata[key as keyof checkeddata] = matchedinfo[key] || [];
+    });
+
+  }
+
+})
+
+const save = () => {
+  const mergedData = [
+    ...checkeddata.VehicleInformation,
+    ...checkeddata.VehicleOwnerInformation,
+    ...checkeddata.PolicyholderInformation,
+    ...checkeddata.InsuredInformation,
+    ...checkeddata.InsuranceInformation,
+    ...checkeddata.InsuranceCostFactor,
+  ]
+  emits("save", mergedData)// 返回已选集合
+}
+const attractivelist = ref();
+const attrName = ref();
+const activename = ref('车辆信息');
+const checked = reactive<checkeddata>({
+  VehicleInformation: [],//车辆信息
+  VehicleOwnerInformation: [],//车主信息
+  PolicyholderInformation: [],//车主信息
+  InsuredInformation: [],//车主信息
+  InsuranceInformation: [], //车主信息
+  InsuranceCostFactor: [],//保司费用因子
+})
+const licensePlateRegion = ref();//车牌地区
+const licensePlateNumber = ref();//车牌号
+
+const matchDataByInfos = (idCollection: any) => {
+  const matchedData: any = {};
+  // 遍历 dataStore 的每个键
+  Object.keys(dataStore).forEach(key => {
+    // 过滤出匹配的项
+    matchedData[key as keyof dataStore] = dataStore[key as keyof dataStore].filter((item: any) => {
+      const match = idCollection.find((idObj: any) => idObj.attrName === item.attrName);
+      if (match) {
+        // 如果找到匹配项,将 minArray 和 min 赋值给 item
+        item.minArray = match.minArray;
+        item.min = match.min;
+        return true; // 保留该项
+      }
+      return false; // 不保留该项
+    });
+  });
+  return matchedData;
+};
+const matchDataByIds = (idCollection: any) => {
+  const matchedData: any = {};
+  // 提取 idCollection 中的 ID
+  const ids = idCollection.map((item: any) => item.attrName); // 提取 ID
+  // 遍历 dataStore 的每个键
+  Object.keys(dataStore).forEach(key => {
+    // 过滤出匹配的name回显复选框
+    matchedData[key as keyof dataStore] = dataStore[key as keyof dataStore].filter(item => ids.includes(item.attrName)).map(item => item.attrName);
+  });
+  return matchedData;
+};
+const typeList = ref([
+  {
+    name: "车辆信息",
+    type: 'VehicleInformation'
+  },
+  {
+    name: "车主信息",
+    type: 'VehicleOwnerInformation'
+
+  },
+  {
+    name: "投保人信息",
+    type: 'PolicyholderInformation'
+
+  },
+  {
+    name: "被保人信息",
+    type: 'InsuredInformation'
+
+  },
+  {
+    name: "商业险",
+    type: 'InsuranceInformation'
+
+  },
+  {
+    name: "保司费用因子",
+    type: 'InsuranceCostFactor'
+
+  }
+])
+const itemclick = (type: any) => {
+  activename.value = type.name;
+}
+const dataStore = reactive<dataStore>({
+  VehicleInformation: [],//车辆信息
+  VehicleOwnerInformation: [],//车主信息
+  PolicyholderInformation: [],//车主信息
+  InsuredInformation: [],//车主信息
+  InsuranceInformation: [], //车主信息
+  InsuranceCostFactor: [],//保司费用因子
+})
+interface checkeddata {
+  [key: string]: any[]
+}
+const checkeddata = reactive<checkeddata>({
+  VehicleInformation: [],//车辆信息
+  VehicleOwnerInformation: [],//车主信息
+  PolicyholderInformation: [],//车主信息
+  InsuredInformation: [],//车主信息
+  InsuranceInformation: [], //车主信息
+  InsuranceCostFactor: [],//保司费用因子
+});
+//已勾选的规则集合
+interface VehicleInformation {
+  attrName: string,
+  id: string,
+}[]
+interface dataStore {
+  VehicleInformation: VehicleInformation[],
+  VehicleOwnerInformation: VehicleInformation[],
+  PolicyholderInformation: VehicleInformation[],
+  InsuredInformation: VehicleInformation[],
+  InsuranceInformation: VehicleInformation[],
+  InsuranceCostFactor: VehicleInformation[],
+}
+
+onMounted(() => {
+    ruleAttr();
+    regionLicenseRegion('0');//车牌地区
+    regionLicenseNumber('14');//车牌号
+})
+
+const ruleAttr = () => {
+  api.agreementApi.ruleAttrGroup({ attrName: attrName.value }).then((res: any) => {
+    if (res.code == '200') {
+      dataStore.VehicleInformation = processArray(res.data.VehicleInformation);
+      dataStore.VehicleOwnerInformation = processArray(res.data.VehicleOwnerInformation);
+      dataStore.PolicyholderInformation = processArray(res.data.PolicyholderInformation);
+      dataStore.InsuredInformation = processArray(res.data.InsuredInformation);
+      dataStore.InsuranceInformation = processArray(res.data.InsuranceInformation);
+      dataStore.InsuranceCostFactor = processArray(res.data.InsuranceCostFactor);
+    } else {
+    }
+  });
+}
+//选择事件集中处理
+type title = keyof typeof dataStore;
+const change = (value: any, title: title) => {
+  // 使用动态属性访问
+  let arrayToUpdate = dataStore[title];//获取对应类型数组
+  if (arrayToUpdate) {
+    checkeddata[title] = [];//默认清空
+    arrayToUpdate = arrayToUpdate.map((val) => {
+      value.map((item: string) => {
+        if (val.attrName == item) {
+          checkeddata[title].push(val)//匹配相同值选中数据存入
+        }
+      })
+      return val;
+    });
+  } else {
+
+  }
+};
+// 遍历并处理每个数组
+const processArray = (array: any[]) => {
+  return array.map((val) => {
+    switch (val.attrType) {
+      case 'inputNumber':
+      case 'datetime':
+        if (!val.operator) {
+          val.operator = '1'; // 设置默认值
+        }
+        break;
+      default:
+        break;
+    }
+    if (val.attrCode == 'LicenseNo') {
+      val.dictLabal = licensePlateNumber.value;
+    }
+    if (val.attrCode == 'LicenseArea') {
+      val.dictLabal = licensePlateRegion.value;
+    }
+    val.isCheck = 0;
+    return val;
+  });
+};
+const handleEnter = (e: any) => {
+  if (e.keyCode == 13 || e.keyCode == 108) {
+    ruleAttr();
+  }
+}
+const regionLicenseRegion = (value: string) => {
+  api.agreementApi.getAreaLicense(value).then((res: any) => {
+    if (res.code == '200') {
+      let data: any = [];
+      res.data.map((val: any) => {
+        data.push({
+          code: val.areaNumber,
+          name: val.license,
+        })
+        return val;
+      })
+      licensePlateRegion.value = data;
+    } else {
+    }
+  });
+}
+const regionLicenseNumber = (value: string) => {
+  api.agreementApi.getAreaLicense(value).then((res: any) => {
+    if (res.code == '200') {
+      let data: any = [];
+      res.data.map((val: any) => {
+        data.push({
+          code: val.areaNumber,
+          name: val.license,
+        })
+        return val;
+      })
+      licensePlateNumber.value = data;
+    } else {
+    }
+  });
+}
+</script>
+<!-- 样式区域 -->
+<style lang='scss' scoped>
+.conditionSet {
+  padding: 20px 0;
+  margin-top: 10px;
+
+  .tabLeft {
+    width: 100px;
+
+    ul {
+      padding: 0;
+      margin: 0;
+      list-style: none;
+      border-right: 1px solid #dcdfe6;
+
+      li {
+        height: 40px;
+        font-weight: 500;
+        line-height: 40px;
+        text-align: center;
+        cursor: pointer;
+      }
+
+      li:hover {
+        color: #0083ff;
+        background: #f4faff;
+        transition: 0.3s;
+      }
+    }
+  }
+
+  .active {
+    color: #0083ff;
+    background: #f4faff;
+  }
+
+  .tabRight {
+    flex: 1;
+    max-height: 300px;
+    padding: 0 20px;
+    overflow-y: auto;
+  }
+}
+</style>

+ 163 - 0
src/components/ruleConditionContainer/index.vue

@@ -0,0 +1,163 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <div class="flex f-c a-start m-top-20" style="width: 100%;">
+      <el-row v-for="(item, index) in props.rulesAttrList" :key="index" class="roleClass flex a-c" style="width: 100%;">
+        <el-col :span="5">
+          <el-input v-model="item.attrName" disabled />
+        </el-col>
+        <el-col :span="3">
+          <el-select v-model="item.operator" placeholder="运算符" style="width: 100px; margin: 0 10px;"
+            :disabled="item.attrType !== 'inputNumber' && item.attrType !== 'datetime' && item.attrType !== 'inputTag' || disabled">
+            <template v-if="item.attrType == 'inputTag'">
+              <el-option label="包含" value="1"></el-option>
+              <el-option label="不包含" value="2"></el-option>
+            </template>
+            <template v-else>
+              <el-option v-for="(rulesDictitem, rulesDictindex) in item.rulesDict" :key="rulesDictindex"
+                :label="rulesDictitem.label" :value="rulesDictitem.value" />
+            </template>
+          </el-select>
+        </el-col>
+        <el-col :span="15">
+          <template v-if="item.attrType == 'radio'" >
+            <el-select v-model="item.min" placeholder="选择值" clearable filterable :disabled="disabled">
+              <el-option v-for="dictLabalitem in item.dictLabal" :key="dictLabalitem.id" :label="dictLabalitem.name"
+                :value="dictLabalitem.code" />
+            </el-select>
+          </template>
+          <template v-if="item.attrType == 'checkbox'" >
+            <el-select v-model="item.minArray" placeholder="选择值" clearable filterable multiple :disabled="disabled"
+              @change="change($event, index)">
+              <el-option v-for="dictLabalitem in item.dictLabal" :key="dictLabalitem.id" :label="dictLabalitem.name"
+                :value="dictLabalitem.name" />
+            </el-select>
+          </template>
+          <template v-if="item.attrType == 'inputNumber'">
+            <el-row class="flex a-c">
+              <template v-if="item.operator == '1' || item.operator == null">
+                <el-col :span="11">
+                  <el-input-number v-model="item.min" :disabled="disabled" style="width: 100%;"></el-input-number>
+                </el-col>
+                <el-col :span="2">
+                  <div class="flex a-c j-c " style="font-size: 14px;color: #333;">至</div>
+                </el-col>
+                <el-col :span="11">
+                  <el-input-number v-model="item.max" :disabled="disabled" style="width: 100%;"></el-input-number>
+                </el-col>
+              </template>
+              <template v-if="item.operator == '2'">
+                <el-col :span="24">
+                  <el-input-number v-model="item.min" :disabled="disabled" style="width: 100%;"></el-input-number>
+                </el-col>
+              </template>
+              <template v-if="item.operator == '3'">
+                <el-col :span="24">
+                  <el-input-number v-model="item.min" :disabled="disabled" style="width: 100%;"></el-input-number>
+                </el-col>
+              </template>
+              <template v-if="item.operator == '4'">
+                <el-col :span="24">
+                  <el-input-number v-model="item.max" :disabled="disabled" style="width: 100%;"></el-input-number>
+                </el-col>
+              </template>
+              <template v-if="item.operator == '5'">
+                <el-col :span="24">
+                  <el-input-number v-model="item.max" :disabled="disabled" style="width: 100%;"></el-input-number>
+                </el-col>
+              </template>
+            </el-row>
+          </template>
+          <template v-if="item.attrType == 'select'">
+            <el-select v-model="item.minArray" placeholder="选择值" clearable filterable multiple :disabled="disabled"
+              @change="change($event, index)">
+              <el-option v-for="dictLabalitem in item.dictLabal" :key="dictLabalitem.id" :label="dictLabalitem.name"
+                :value="item.attrName == '车辆类型' ? dictLabalitem.code : dictLabalitem.name" />
+            </el-select>
+          </template>
+          <template v-if="item.attrType == 'inputTag'">
+            <el-input-tag v-model="item.minArray" :disabled="disabled" @change="change($event, index)"
+              placeholder="输入内容后按回车键隔开" />
+          </template>
+          <template v-if="item.attrType == 'datetime'">
+            <el-row class="flex a-c">
+              <template v-if="item.operator == '1' || item.operator == null">
+                <el-col :span="11">
+                  <el-date-picker v-model="item.min" :disabled="disabled" type="datetime" format="YYYY-MM-DD HH:mm:ss"
+                    value-format="YYYY-MM-DD HH:mm:ss" time-format="HH:mm:ss" placeholder="选择日期时间" style="width: 100%;">
+                  </el-date-picker>
+                </el-col>
+                <el-col :span="2">
+                  <div class="flex a-c j-c " style="font-size: 14px;color: #333;">至</div>
+                </el-col>
+                <el-col :span="11">
+                  <el-date-picker v-model="item.max" :disabled="disabled" type="datetime" format="YYYY-MM-DD HH:mm:ss"
+                    value-format="YYYY-MM-DD HH:mm:ss" time-format="HH:mm:ss" placeholder="选择日期时间" style="width: 100%;">
+                  </el-date-picker>
+                </el-col>
+              </template>
+              <template v-if="item.operator == '2'">
+                <el-col :span="24">
+                  <el-date-picker v-model="item.min" :disabled="disabled" type="datetime" format="YYYY-MM-DD HH:mm:ss"
+                    value-format="YYYY-MM-DD HH:mm:ss" time-format="HH:mm:ss" placeholder="选择日期时间" style="width: 100%;">
+                  </el-date-picker>
+                </el-col>
+              </template>
+              <template v-if="item.operator == '3'">
+                <el-col :span="24"> <el-date-picker v-model="item.min" :disabled="disabled" type="datetime" format="YYYY-MM-DD HH:mm:ss"
+                    value-format="YYYY-MM-DD HH:mm:ss" time-format="HH:mm:ss" placeholder="选择日期时间" style="width: 100%;">
+                  </el-date-picker></el-col>
+              </template>
+              <template v-if="item.operator == '4'">
+                <el-col :span="24"><el-date-picker v-model="item.max" :disabled="disabled" type="datetime" format="YYYY-MM-DD HH:mm:ss"
+                    value-format="YYYY-MM-DD HH:mm:ss" time-format="HH:mm:ss" placeholder="选择日期时间" style="width: 100%;">
+                  </el-date-picker></el-col>
+              </template>
+              <template v-if="item.operator == '5'">
+                <el-col :span="24"> <el-date-picker v-model="item.max" :disabled="disabled" type="datetime" format="YYYY-MM-DD HH:mm:ss"
+                    value-format="YYYY-MM-DD HH:mm:ss" time-format="HH:mm:ss" placeholder="选择日期时间" style="width: 100%;">
+                  </el-date-picker></el-col>
+
+              </template>
+            </el-row>
+
+          </template>
+        </el-col>
+        <el-col :span="1">
+          <el-icon v-if="!disabled" color="#666" size="18" style="margin-left: 10px;" @click="attrDel(index)">
+            <Delete />
+          </el-icon>
+        </el-col>
+      </el-row>
+    </div>
+  </div>
+</template>
+
+<!-- 行为区域 -->
+<script lang='ts' setup>
+import { defineProps, defineEmits } from "vue";
+const props = defineProps<{
+  rulesAttrList: any[],//list集合
+  disabled?: boolean,
+}>();
+const emits = defineEmits(["Del", 'save', 'selectChange']);
+//右侧删除图标
+const attrDel = (index: number) => {
+  console.log(index)
+  emits('Del', index)
+}
+
+const change = (e: string[] | undefined, index: number) => {
+  console.log(e)
+  emits('selectChange', { e, index })
+}
+onMounted(() => {
+  console.log('规则数据', props.rulesAttrList)
+})
+</script>
+<!-- 样式区域 -->
+<style lang='scss' scoped>
+.roleClass {
+  margin-bottom: 10px;
+}
+</style>

+ 201 - 0
src/layouts/components/DrivingType/index.vue

@@ -0,0 +1,201 @@
+<template>
+  <div style="height: 400px">
+    <el-row :gutter="20" style="height: calc(100% - 30px); border: 1px solid #ccc; margin: 5px; border-radius: 5px; padding: 5px">
+      <el-col :span="12">
+        <el-input v-model="searchName" placeholder="输入驾意险名称查找" style="margin-bottom: 10px">
+<!--          <template #append>
+            <el-button type="primary" @click="handleSearchName">查找</el-button>
+          </template>-->
+        </el-input>
+<!--        <el-checkbox-group v-model="item.drivingIntentionId">
+          <el-checkbox style="display: block;" v-for="JYX in JYXList" :key="JYX.id" :value="JYX.id" :label="JYX.productName"></el-checkbox>
+        </el-checkbox-group>-->
+        <div class="insuranceLeft">
+          <div class="insuranceItem" v-for="(item, index) in handleSearchName" @click="handleType(item, index)">
+          <span class="checkIcon" :class="{ 'active': item.checked }">
+            <el-icon><Select /></el-icon>
+          </span>
+            <span>{{ item.productName }}</span>
+          </div>
+        </div>
+      </el-col>
+      <el-divider direction="vertical" style="height: 100%;"></el-divider>
+      <el-col :span="11">
+        <div style="display: flex; align-items: center; justify-content: space-between; margin: 8px 0">
+          <span>已选: {{ selectedList.length }}份驾意险</span>
+          <el-button link type="primary">清空</el-button>
+        </div>
+        <div class="selectedList">
+          <div style="display: flex; align-items: center; justify-content: space-between; margin: 10px" v-for="(JYX, index) in selectedList" :key="JYX.id">
+            <span>{{ JYX.productName }}</span>
+            <el-icon style="cursor: pointer;" @click="handleDel(JYX, index)"><Close /></el-icon>
+          </div>
+        </div>
+      </el-col>
+    </el-row>
+    <div class="footer">
+      <el-button plain @click="handleCancel">取消</el-button>
+      <el-button type="primary" @click="handleConfirm">保存</el-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+
+const emits = defineEmits(['close'])
+
+// 查找
+import api from "@/api";
+import {ElMessage} from "element-plus";
+
+let searchName = ref('')
+const handleSearchName = computed(() => {
+  return JYXList.value.filter(a => a.productName.includes(searchName.value))
+})
+
+// 驾意险
+let JYXList = ref([])
+// const initJYX = () => {
+//   api.projectApi.jyxList({
+//     company: '173881173559933834'
+//   }).then(res => {
+//     if(res.code == 200) {
+//       if(res.data && res.data.length > 0) {
+//         res.data.forEach(item => {
+//           item.checked = false
+//         })
+//       }
+//       JYXList.value = res.data
+//     } else {
+//       ElMessage({
+//         message: res.msg,
+//         type: 'warning'
+//       })
+//     }
+//   })
+// }
+
+// 选择驾意险
+let selectedList = ref([])
+const handleType = (item, index) => {
+  item.checked = !item.checked
+  selectedList.value = []
+  JYXList.value.forEach(sub => {
+    if(sub.checked) {
+      selectedList.value.push(sub)
+    }
+  })
+}
+// 删除
+const handleDel = (item, index) => {
+  selectedList.value.splice(index, 1)
+  JYXList.value?.forEach(sub => {
+    if(sub.id === item.id) {
+      sub.checked = false
+    }
+  })
+}
+
+// 编辑
+let editIndex = ref(0)
+const initEditData = (data, index) => {
+  editIndex.value = index
+  // JYXList.value.forEach(item => {
+  //   item.checked = false
+  // })
+  // let list = [...JYXList.value]
+  api.projectApi.jyxList({
+    company: '173881173559933834'
+  }).then(res => {
+    if(res.code == 200) {
+      if(res.data && res.data.length > 0) {
+        res.data.forEach(item => {
+          item.checked = false
+          item.drivingIntentionId = item.id
+          item.id = ''
+        })
+      }
+      JYXList.value = res.data
+      if(data && data.length > 0) {
+        data.forEach(item => {
+          item.checked = true
+        })
+        JYXList.value = res.data.map(a => {
+          let obj = data.find(b => b.drivingIntentionId === a.drivingIntentionId)
+          return obj || a
+        })
+        selectedList.value = JYXList.value.filter(a => a.checked)
+      }
+    } else {
+      ElMessage({
+        message: res.msg,
+        type: 'warning'
+      })
+    }
+  })
+}
+
+// 取消
+const handleCancel = () => {
+  emits('close', {
+    list: [],
+    index: editIndex.value
+  })
+}
+// 保存
+const handleConfirm = () => {
+  emits('close', {
+    list: JYXList.value,
+    index: editIndex.value
+  })
+}
+
+onMounted(() => {
+  // initJYX()
+})
+
+defineExpose({
+  initEditData
+})
+
+</script>
+
+<style scoped lang="scss">
+.insuranceLeft{
+  height: 100%;
+  overflow-y: auto;
+}
+.insuranceLeft{
+  width: calc(100% - 110px);
+  .insuranceItem{
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    margin: 10px 0;
+    span{
+      margin-left: 10px;
+    }
+    .checkIcon{
+      width: 14px;
+      height: 14px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      border: 1px solid #ccc;
+      border-radius: 2px;
+      font-size: 10px;
+      color: #fff;
+    }
+    .active{
+      color: #fff;
+      background: #3994FB;
+      border-color: #3994FB;
+    }
+  }
+}
+.footer{
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+</style>

+ 233 - 0
src/layouts/components/InsuranceType/index.vue

@@ -0,0 +1,233 @@
+<!--险种选择组件-->
+
+<template>
+  <div class="insurance" :style="[defineStyle]">
+    <div class="insuranceList">
+      <div class="insuranceLeft">
+        <div class="insuranceItem" v-for="(item, index) in insuranceTypeList" @click="handleType(item, index)">
+          <span class="checkIcon" :class="{ 'active': item.checked }">
+            <el-icon><Select /></el-icon>
+          </span>
+          <span>{{ item.kindName }}</span>
+        </div>
+      </div>
+      <el-divider direction="vertical" style="height: 100%;"></el-divider>
+      <div class="insuranceRight" v-show="choiceItem.insuranceId === item.insuranceId" v-for="(item, index) in insuranceTypeList">
+        <p class="account" :class="{'active': sub.checked}" v-for="sub in item.options" @click="handleSubType(item, sub, index)">{{ sub.label }}</p>
+      </div>
+    </div>
+    <div class="footer" >
+      <el-button plain @click="handleCancel">{{ props.cancelText }}</el-button>
+      <el-button type="primary" @click="handleConfirm">{{ props.confirmText }}</el-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import api from '@/api'
+import { ElMessage } from "element-plus";
+
+// props
+const props = withDefaults(
+  defineProps<{
+    width?: string | number,  // 弹框宽度
+    height?: string | number,  // 弹框高度
+    cancelText?: string, // 取消按钮文字
+    confirmText?: string, // 保存按钮文字
+  }>(),
+  {
+    width: 400,
+    height: 300,
+    cancelText: '取消',
+    confirmText: '保存'
+  }
+)
+
+// emits
+const emits = defineEmits(['close'])
+
+// 合并自定义样式
+const defineStyle = {
+  width:`${props.width}px`,
+  height: `${props.height}px`
+}
+
+// 方案类型
+let insuranceType = ref(null)
+
+// 当前选择的险种
+let choiceItem = ref({})
+// 初始化险种数据
+let insuranceTypeList = ref([])
+let typeList = ref([])
+// const initInsurance = () => {
+//   api.projectApi.getBusinessInsurance().then(res => {
+//     if(res.code == 200) {
+//       if(res.data&&res.data.length>0) {
+//         choiceItem.value = res.data[0]
+//         res.data.forEach(item => {
+//           item.checked = false
+//           item.options.forEach(sub => {
+//             sub.checked = false
+//           })
+//         })
+//         insuranceTypeList.value = JSON.parse(JSON.stringify(res.data))
+//         typeList.value = JSON.parse(JSON.stringify(res.data))
+//       }
+//     } else {
+//       ElMessage({
+//         message: res.msg,
+//         type: 'warning'
+//       })
+//     }
+//   })
+// }
+// 险种选择
+const handleType = (item, index) => {
+  choiceItem.value = item
+}
+// 投保金额选择
+const handleSubType =( item, sub, index) => {
+  if(insuranceType.value === 1) {
+    item.options.forEach(a => {
+      a.checked = false
+    })
+    sub.checked = true
+    item.checked = true
+  } else {
+    sub.checked = !sub.checked
+    let bool = item.options.some(a => a.checked)
+    item.checked = bool
+  }
+}
+
+// 取消
+const handleCancel = () => {
+  emits('close', {
+    list: [],
+    index: editIndex.value
+  })
+}
+// 保存
+const handleConfirm = () => {
+  let list = [...insuranceTypeList.value]
+  let checkedList = list.filter(a => a.checked)
+  emits('close', {
+    list: checkedList,
+    index: editIndex.value
+  })
+}
+
+// 修改数据
+let editIndex = ref(0)
+const initEditData = (data, index, type) => {
+  editIndex.value = index
+  insuranceType.value = type
+  api.projectApi.getBusinessInsurance().then(res => {
+    if(res.code == 200) {
+      if(res.data&&res.data.length>0) {
+        choiceItem.value = res.data[0]
+        res.data.forEach(item => {
+          item.checked = false
+          item.insuranceId = item.id
+          item.id = ''
+          item.options.forEach(sub => {
+            sub.checked = false
+          })
+        })
+        typeList.value = JSON.parse(JSON.stringify(res.data))
+        if(data && data.length > 0) {
+          data.forEach(item => {
+            item.checked = true
+            item.options.forEach(sub => {
+              if(item.optionId === sub.id) {
+                sub.checked = true
+              }
+            })
+          })
+          insuranceTypeList.value = res.data.map(a => {
+            let obj = data.find(b => b.insuranceId === a.insuranceId)
+            return obj || a
+          })
+        } else {
+          insuranceTypeList.value = res.data
+        }
+      }
+    } else {
+      ElMessage({
+        message: res.msg,
+        type: 'warning'
+      })
+    }
+  })
+}
+
+onMounted(() => {
+  // initInsurance()
+})
+
+defineExpose({
+  initEditData
+})
+
+</script>
+
+<style scoped lang="scss">
+.insurance{
+  margin: 0 auto;
+  .insuranceList{
+    height: calc(100% - 40px);
+    overflow: hidden;
+    display: flex;
+    justify-content: space-between;
+    margin-bottom: 10px;
+    .insuranceLeft, .insuranceRight{
+      height: 100%;
+      overflow-y: auto;
+    }
+    .insuranceLeft{
+      width: calc(100% - 110px);
+      .insuranceItem{
+        cursor: pointer;
+        display: flex;
+        align-items: center;
+        margin: 10px 0;
+        span{
+          margin-left: 10px;
+        }
+        .checkIcon{
+          width: 14px;
+          height: 14px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          border: 1px solid #ccc;
+          border-radius: 2px;
+          font-size: 10px;
+          color: #fff;
+        }
+        .active{
+          color: #fff;
+          background: #3994FB;
+          border-color: #3994FB;
+        }
+      }
+    }
+    .insuranceRight{
+      width: 70px;
+      .account{
+        text-align: center;
+        cursor: pointer;
+      }
+      .active{
+        color: #3994FB;
+      }
+    }
+  }
+  .footer{
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+}
+</style>

+ 0 - 1
src/router/guards.ts

@@ -89,7 +89,6 @@ function setupRoutes(router: Router) {
         }
         routeStore.setCurrentRemoveRoutes(removeRoutes)
         // 动态路由生成并注册后,重新进入当前路由
-        console.log(156789,localStorage.getItem("token"))
         if(localStorage.getItem("token")!=''){
           next({
             path: to.path,

+ 57 - 0
src/router/modules/externalAgreement.ts

@@ -0,0 +1,57 @@
+import type { RouteRecordRaw } from 'vue-router'
+
+const Layout = () => import('@/layouts/index.vue')
+
+const routes: RouteRecordRaw = {
+  path: '/externalAgreement/agreement',
+  component: Layout,
+  redirect: '/externalAgreement/agreement/agreementIndex',
+  name: 'externalAgreement',
+  meta: {
+    title: '外部协议',
+  },
+  children: [
+    {
+      path: 'agreementIndex',
+      name: 'ExternalagreementIndex',
+      component: () => import('@/views/externalAgreement/agreement/agreementIndex.vue'),
+      meta: {
+        title: '协议管理',
+      },
+      children:[
+        {
+          path: 'agreementAdd',
+          name: 'ExternalagreementAdd',
+          component: () => import('@/views/externalAgreement/agreement/agreementAdd.vue'),
+          meta: {
+            title: '协议添加',
+            menu:false,
+            activeMenu: '/externalAgreement/agreement/agreementIndex',
+          },
+        },
+
+        {
+          path: 'agreementDetails',
+          name: 'ExternalAgreementDetails',
+          component: () => import('@/views/externalAgreement/agreement/agreementDetails.vue'),
+          meta: {
+            title: '协议详情',
+            menu:false,
+            activeMenu: '/externalAgreement/agreement/agreementIndex',
+          },
+        },
+      ]
+    },
+    {
+      path: 'costManagement',
+      name: 'ExternalcostManagement',
+      component: () => import('@/views/externalAgreement/costManagement.vue'),
+      meta: {
+        title: '费用管理',
+        // auth:['role.add','role.view','role.delete','role.edit']
+      },
+    },
+  ],
+}
+
+export default routes

+ 8 - 8
src/router/modules/personnel.ts

@@ -26,22 +26,22 @@ const routes: RouteRecordRaw = {
           component: () => import('@/views/personnel/modules/memberMessage.vue'),
           meta: {
             title: '成员详情',
-            menu:false
-          }, 
+            menu:false,
+            activeMenu: '/personnel/member',
+          },
         },
         {
           path: 'memberEdit',
           name: 'personnelMemberEdit',
           component: () => import('@/views/personnel/modules/addOperation.vue'),
           meta: {
-            title: '成员管理',
-            menu:false
-          }, 
+            title: '成员添加',
+            menu:false,
+            activeMenu: '/personnel/member',
+          },
         },
       ]
     },
-    
-    
     {
       path: 'institution',
       name: 'personnelInstitution',
@@ -57,7 +57,7 @@ const routes: RouteRecordRaw = {
           meta: {
             title: '机构管理',
             menu:false
-          }, 
+          },
         },
       ]
     },

+ 109 - 0
src/router/modules/quoteConfig.ts

@@ -0,0 +1,109 @@
+import type { RouteRecordRaw } from 'vue-router'
+
+const Layout = () => import('@/layouts/index.vue')
+
+const routes: RouteRecordRaw = {
+  path: '/quoteConfig/agreement',
+  component: Layout,
+  redirect: '/quoteConfig/agreement/agreementIndex',
+  name: 'QuoteConfig',
+  meta: {
+    title: '报价配置',
+  },
+  children: [
+    {
+      path: 'agreementIndex',
+      name: 'AgreementIndex',
+      component: () => import('@/views/quoteConfig/agreement/agreementIndex.vue'),
+      meta: {
+        title: '协议管理',
+      },
+      children:[
+        {
+          path: 'agreementAdd',
+          name: 'AgreementAdd',
+          component: () => import('@/views/quoteConfig/agreement/agreementAdd.vue'),
+          meta: {
+            title: '协议添加',
+            menu:false,
+            activeMenu: '/quoteConfig/agreement/agreementIndex',
+          },
+        },
+        {
+          path: 'agreementCopy',
+          name: 'AgreementCopy',
+          component: () => import('@/views/quoteConfig/agreement/agreementCopy.vue'),
+          meta: {
+            title: '复制协议',
+            menu:false,
+            activeMenu: '/quoteConfig/agreement/agreementIndex',
+          },
+        },
+        {
+          path: 'agreementDetails',
+          name: 'AgreementDetails',
+          component: () => import('@/views/quoteConfig/agreement/agreementDetails.vue'),
+          meta: {
+            title: '协议详情',
+            menu:false,
+            activeMenu: '/quoteConfig/agreement/agreementIndex',
+          },
+        },
+      ]
+    },
+
+    {
+      path: 'costManagement',
+      name: 'CostManagement',
+      component: () => import('@/views/quoteConfig/costManagement.vue'),
+      meta: {
+        title: '费用管理',
+        // auth:['role.add','role.view','role.delete','role.edit']
+      },
+    },
+    {
+      path: 'insuranceCompanyAccount',
+      name: 'InsuranceCompanyAccount',
+      component: () => import('@/views/quoteConfig/insuranceCompanyAccount/insuranceCompanyAccount.vue'),
+      meta: {
+        title: '保司账号',
+        // auth:['role.add','role.view','role.delete','role.edit']
+      },
+      children: [
+        {
+          path: 'insuranceAccountAdd',
+          name: 'InsuranceAccountAdd',
+          component: () => import('@/views/quoteConfig/insuranceCompanyAccount/modules/insuranceAccountAdd.vue'),
+          meta: {
+            title: '保司账号',
+            menu:false,
+            auth: []
+          },
+        }
+      ]
+    },
+    {
+      path: 'project',
+      name: 'Project',
+      component: () => import('@/views/quoteConfig/project/index.vue'),
+      meta: {
+        title: '方案管理',
+        // auth:['role.add','role.view','role.delete','role.edit']
+      },
+      children: [
+        {
+          path: 'projectAdd',
+          name: 'ProjectAdd',
+          component: () => import('@/views/quoteConfig/project/modules/projectAdd.vue'),
+          meta: {
+            title: '方案管理',
+            menu:false,
+            auth: []
+          },
+        }
+      ]
+    },
+  ],
+}
+
+export default routes

+ 22 - 6
src/router/modules/representative.ts

@@ -27,10 +27,26 @@ const routes: RouteRecordRaw = {
           meta: {
             title: '成员详情',
             menu:false
-          }, 
+          },
         },
       ]
-      
+
+    },
+    {
+      path: 'quoteApproval',
+      name: 'QuoteApproval',
+      component: () => import('@/views/representative/quoteApproval.vue'),
+      meta: {
+        title: '报价费用审核',
+      },
+    },
+    {
+      path: 'externalCostApproval',
+      name: 'ExternalCostApproval',
+      component: () => import('@/views/representative/externalCostApproval.vue'),
+      meta: {
+        title: '外部费用审核',
+      },
     },
     {
       path: 'salesman',
@@ -48,13 +64,13 @@ const routes: RouteRecordRaw = {
           meta: {
             title: '业务员详情',
             menu:false
-          }, 
+          },
         },
       ]
-      
+
     },
-    
-    
+
+
   ],
 }
 

+ 10 - 2
src/router/routes.ts

@@ -8,7 +8,9 @@ import representative from "./modules/representative";
 import operationManagement from "./modules/operation";
 import personnel from "./modules/personnel";
 import content from "./modules/content"
-import marketingManagement from "./modules/marketing.ts";
+import marketingManagement from "./modules/marketing";
+import quoteConfig from "./modules/quoteConfig";//报价配置
+import externalAgreement from "./modules/externalAgreement";//外部协议
 // import {  } from "@/store/modules/user";
 // 固定路由(默认路由
 // console.log(useUserStore)
@@ -110,7 +112,13 @@ const asyncRoutes: Route.recordMainRaw[] = [
     },
     children: [content],
   },
-
+  {
+    meta: {
+      title: "报价配置",
+      icon: "i-uim:box",
+    },
+    children: [quoteConfig,externalAgreement],
+  },
   // {
   //   meta: {
   //     title: "租户运营",

+ 2 - 2
src/settings.default.ts

@@ -10,7 +10,7 @@ const globalSettingsDefault: RecursiveRequired<Settings.all> = {
     enablePermission: false,
     enableProgress: true,
     enableDynamicTitle: false,
-    routeBaseOn: 'frontend',
+    routeBaseOn: 'backend',
   },
   home: {
     enable: true,
@@ -21,7 +21,7 @@ const globalSettingsDefault: RecursiveRequired<Settings.all> = {
     enableMobileAdaptation: false,
   },
   menu: {
-    baseOn: 'frontend',
+    baseOn: 'backend',
     mode: 'side',
     switchMainMenuAndPageJump: false,
     subMenuUniqueOpened: true,

+ 2 - 2
src/settings.ts

@@ -31,10 +31,10 @@ const globalSettings: Settings.all = {
     enableHotkeys: false,
   },
   topbar: {
-    mode: 'static',
+    mode: 'fixed',
   },
   tabbar: {
-    enable: true,
+    enable: false,
     enableIcon: true,
     enableHotkeys: true,
   },

+ 9 - 0
src/types/auto-imports.d.ts

@@ -7,6 +7,7 @@
 export {}
 declare global {
   const EffectScope: typeof import('vue')['EffectScope']
+  const Fileonload: typeof import('../utils/composables/fileProcessing')['Fileonload']
   const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
   const computed: typeof import('vue')['computed']
   const convertToTreeData: typeof import('../utils/composables/routeUtils')['convertToTreeData']
@@ -16,16 +17,22 @@ declare global {
   const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
   const defineComponent: typeof import('vue')['defineComponent']
   const defineStore: typeof import('pinia')['defineStore']
+  const dictLists: typeof import('../utils/composables/dictService')['dictLists']
+  const dictionaryMatch: typeof import('../utils/composables/dictService')['dictionaryMatch']
+  const download: typeof import('../utils/composables/fileProcessing')['download']
   const effectScope: typeof import('vue')['effectScope']
+  const formatFileSize: typeof import('../utils/composables/fileProcessing')['formatFileSize']
   const getActivePinia: typeof import('pinia')['getActivePinia']
   const getCurrentInstance: typeof import('vue')['getCurrentInstance']
   const getCurrentScope: typeof import('vue')['getCurrentScope']
+  const getDict: typeof import('../utils/composables/dictService')['getDict']
   const h: typeof import('vue')['h']
   const inject: typeof import('vue')['inject']
   const isProxy: typeof import('vue')['isProxy']
   const isReactive: typeof import('vue')['isReactive']
   const isReadonly: typeof import('vue')['isReadonly']
   const isRef: typeof import('vue')['isRef']
+  const loadDicts: typeof import('../utils/composables/dictService')['loadDicts']
   const mapActions: typeof import('pinia')['mapActions']
   const mapGetters: typeof import('pinia')['mapGetters']
   const mapState: typeof import('pinia')['mapState']
@@ -51,10 +58,12 @@ declare global {
   const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
   const permissionDictionary: typeof import('../utils/composables/routeUtils')['permissionDictionary']
   const preprocessRoutes: typeof import('../utils/composables/routeUtils')['preprocessRoutes']
+  const processFileName: typeof import('../utils/composables/fileProcessing')['processFileName']
   const provide: typeof import('vue')['provide']
   const reactive: typeof import('vue')['reactive']
   const readonly: typeof import('vue')['readonly']
   const ref: typeof import('vue')['ref']
+  const removeUniqueIdentifier: typeof import('../utils/composables/fileProcessing')['removeUniqueIdentifier']
   const resolveComponent: typeof import('vue')['resolveComponent']
   const setActivePinia: typeof import('pinia')['setActivePinia']
   const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']

+ 6 - 0
src/types/components.d.ts

@@ -8,7 +8,9 @@ export {}
 declare module 'vue' {
   export interface GlobalComponents {
     Auth: typeof import('./../components/Auth/index.vue')['default']
+    ComplexTable: typeof import('./../components/complexTable/index.vue')['default']
     DialogSetDeptLeader: typeof import('./../components/DialogSetDeptLeader/index.vue')['default']
+    Empty: typeof import('./../components/empty/index.vue')['default']
     FileUpload: typeof import('./../components/FileUpload/index.vue')['default']
     FixedActionBar: typeof import('./../components/FixedActionBar/index.vue')['default']
     HButton: typeof import('./../layouts/ui-kit/HButton.vue')['default']
@@ -37,10 +39,14 @@ declare module 'vue' {
     ResetPasswordForm: typeof import('./../components/ResetPasswordForm/index.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
+    RuleCondition: typeof import('./../components/ruleCondition/index.vue')['default']
+    RuleConditionContainer: typeof import('./../components/ruleConditionContainer/index.vue')['default']
     SearchBar: typeof import('./../components/SearchBar/index.vue')['default']
+    SearchForm: typeof import('./../components/SearchForm/index.vue')['default']
     SvgIcon: typeof import('./../components/SvgIcon/index.vue')['default']
     SystemInfo: typeof import('./../components/SystemInfo/index.vue')['default']
     Table: typeof import('./../components/Table/index.vue')['default']
+    TableTree: typeof import('./../components/TableTree/index.vue')['default']
     Trend: typeof import('./../components/Trend/index.vue')['default']
   }
 }

+ 39 - 0
src/utils/composables/dictService.ts

@@ -0,0 +1,39 @@
+// dictService.ts
+import { ref } from 'vue';
+import api from '@/api';
+
+const dictLists = ref<{ [key: string]: any[] }>({});//多个字典集合
+
+// 加载多个字典
+const loadDicts = async (types: string[]): Promise<void> => {
+  const promises = types.map(async (type) => {
+    if (!dictLists.value[type + 'List']) {
+      try {
+        const res: any = await api.areaApi.systemList({ dictCode: type });
+        if (res.code == '200') {
+          dictLists.value[type + 'List'] = res.data.map((item: any) => {
+            return {
+              value: item.value,
+              label: item.label
+            };
+          });
+        }
+      } catch (error) {
+        console.error(`字典类型 ${type} 加载失败:`, error);
+      }
+    }
+  });
+
+  await Promise.all(promises);
+};
+
+// 获取字典数据
+const getDict = (type: string): any[] => {
+  return dictLists.value[type + 'List'] || [];
+};
+//字典name匹配
+const dictionaryMatch=(type:string,value:string| number)=>{
+  let name=dictLists.value[type + 'List'].find(val=>val.value==value);
+  return name;
+}
+export { dictLists, loadDicts, getDict ,dictionaryMatch};

+ 72 - 0
src/utils/composables/fileProcessing.ts

@@ -0,0 +1,72 @@
+// 图片/文件下载
+/**
+ * @download  下载方法
+ * @param url 下载地址
+ * @param name 文件名称
+ */
+const Fileonload = (url: any, name: any) => {
+  // 创建一个 XMLHttpRequest 对象
+  const xhr = new XMLHttpRequest();
+  xhr.open("GET", url, true);
+  xhr.responseType = "blob"; // 设置响应类型为 blob
+  xhr.onload = function () {
+    if (xhr.status === 200) {
+      const blob = xhr.response; // 获取响应的 blob 对象
+      const a = document.createElement("a"); // 生成一个 a 元素
+      const event = new MouseEvent("click"); // 创建一个单击事件
+      // 创建一个 URL 对象
+      const downloadUrl = URL.createObjectURL(blob);
+      a.href = downloadUrl; // 将生成的 URL 设置为 a.href 属性
+      a.download = name; // 设置文件名称
+      // 触发 a 的单击事件
+      a.dispatchEvent(event);
+      // 释放 URL 对象
+      URL.revokeObjectURL(downloadUrl);
+    } else {
+      console.error("下载失败,状态码:", xhr.status);
+    }
+  };
+  xhr.onerror = function () {
+    console.error("请求失败");
+  };
+  xhr.send(); // 发送请求
+};
+/**
+ *
+ * @param formatFileSize 文件大小单位转换
+ * @param size 文件size
+ */
+function formatFileSize(size: any) {
+  let Size = Number(size);
+  if (Size < 0) {
+    return "大小无效";
+  }
+
+  const units = ["B", "KB", "MB"];
+  let index = 0;
+
+  let formattedSize = Size;
+
+  while (formattedSize >= 1024 && index < units.length - 1) {
+    formattedSize /= 1024;
+    index++;
+  }
+
+  return `${formattedSize.toFixed(0)} ${units[index]}`;
+}
+
+/**
+ * @processFileName 处理文件名
+ * @param fileName 文件名称
+ * @returns
+ */
+// 提取文件名中下划线前面的部分
+const processFileName = (fileName: any) => {
+  const index = fileName?.lastIndexOf('_'); // 查找最后一个下划线的位置
+  if (index !== -1) {
+    return fileName.substring(0, index) + fileName.substring(fileName.lastIndexOf('.')); // 返回下划线前面的部分和文件后缀
+  } else {
+    return fileName; // 如果没有找到下划线,直接返回原文件名
+  }
+};
+export { Fileonload, formatFileSize, processFileName }

+ 429 - 0
src/views/externalAgreement/agreement/agreementAdd.vue

@@ -0,0 +1,429 @@
+<template>
+  <div>
+    <PageMain style="padding: 20px;margin-bottom: 100px;">
+      <ElRow :gutter="60">
+        <ElCol :md="24" :lg="24" style="padding-bottom: 10px;font-size: 14px;">
+          <span class="strong">协议信息</span>
+        </ElCol>
+        <el-form :model="agreementFrom" label-width="120px" :rules="agreementFromRules" label-position="top"
+          ref="agreementFromRef" style="max-width: 100%;" class="flex a-c f-wrap" :size="fromSize">
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="协议名称" prop="agreementTitle">
+              <el-input v-model="agreementFrom.agreementTitle" placeholder="输入协议名称" maxlength="50"
+                show-word-limit></el-input>
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="协议简称" prop="agreementAbbreviation">
+              <el-input v-model="agreementFrom.agreementAbbreviation" placeholder="输入简称" maxlength="50"
+                show-word-limit />
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="协议生效时间" prop="startDate">
+              <el-date-picker v-model="agreementFrom.startDate" style="width: 100%;" type="datetime"
+                placeholder="选择生效时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
+                time-format="HH:mm:ss" @change="handleStartDateChange" />
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="协议失效时间" prop="endDate">
+              <el-date-picker v-model="agreementFrom.endDate" style="width: 100%;" type="datetime" placeholder="选择失效时间"
+                format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" time-format="HH:mm:ss"
+                :disabled-date="disabledEndDate" />
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="是否含增值税结算" prop="withTax">
+              <el-radio-group v-model="agreementFrom.withTax">
+                <el-radio-button :label="item.label" :value="item.value" v-for="(item, index) in getDict('with_tax')"
+                  :key="index" />
+              </el-radio-group>
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="结算周期" prop="moneyTime">
+              <span style="margin: 0  20px;font-size: 18px;">T</span>
+              <el-input-number v-model="agreementFrom.moneyTime" :min="1" :max="999" />
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="12"></ElCol>
+          <ElCol :md="24" :lg="12">
+            <el-form-item label="协议描述" prop="agreementName">
+              <el-input v-model="agreementFrom.agreementName" placeholder="请输入" type="textarea" maxlength="500"
+                show-word-limit />
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="24" style="padding-bottom: 10px;font-size: 14px;">
+            <ElDivider></ElDivider>
+            <span class="strong">其他信息</span>
+          </ElCol>
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="协议归属" prop="deptId">
+              <el-cascader v-model="agreementFrom.deptId" style="width: 100%;" filterable clearable
+                :options="deptOptions" :props="parentIdprops" :show-all-levels="false" @change="deptIdChange" />
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="保险公司" prop="companyId">
+              <el-cascader v-model="agreementFrom.companyId" style="width: 100%;" filterable clearable
+                :options="companyOptions" :props="companyIdprops" :show-all-levels="false" @change="companyIdChange" />
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="协议类型" prop="agreementType">
+              <el-select v-model="agreementFrom.agreementType" placeholder="选择协议类型">
+                <el-option :label="item.label" :value="item.value" v-for="(item, index) in getDict('agreement_type')"
+                  :key="index" />
+              </el-select>
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="管理人" prop="custodianId">
+              <el-input v-model="managerName" @click="managerModalVisible = true">{{ agreementFrom.custodianId
+              }}</el-input>
+            </el-form-item>
+          </ElCol>
+        </el-form>
+      </ElRow>
+
+    </PageMain>
+    <FixedActionBar>
+      <ElButton type="primary" @click="submitForm(agreementFromRef)">
+        保存
+      </ElButton>
+      <ElButton @click="router.back()">
+        取消
+      </ElButton>
+    </FixedActionBar>
+    <!-- 选择管理人 -->
+    <managerModal :managerModal="managerModalVisible" :deptOptions="deptOptions" :managerlist="managerlist"
+      @save="managerModalsave" @NodeClick="managerModalNodeClick" @query="managerModalQuery" @cancel="managerModalVisible = false" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive } from 'vue'
+import { FormRules, FormInstance, ComponentSize, ElMessage } from 'element-plus';
+import { loadDicts, getDict } from '@/utils/composables/dictService';//字典组件
+import { useRoute, useRouter } from 'vue-router';
+import managerModal from "./components/managerModal.vue";
+import api from '@/api';
+const parentIdprops = {
+  expandTrigger: 'hover' as const,
+  value: 'id',
+  label: 'name',
+  checkStrictly: true,
+}
+const companyIdprops = {
+  expandTrigger: 'hover' as const,
+  value: 'id',
+  label: 'nameSimple',
+  checkStrictly: true,
+}
+interface AgreementForm {
+  id?: string; // 协议 ID
+  agreementTitle: string; // 协议名称
+  agreementAbbreviation: string; // 协议简称
+  startDate: string; // 生效时间,通常为 ISO 格式的日期字符串
+  endDate: string; // 失效时间,通常为 ISO 格式的日期字符串
+  withTax: string; // 是否含增值税结算
+  moneyTime: any; // 结算周期
+  agreementName: string; // 协议描述
+  deptId: string; // 协议归属
+  companyId: string; // 保险公司 ID
+  agreementType: string; // 协议类型
+  custodianId: string; // 管理人 ID
+
+}
+const route = useRoute();
+const router = useRouter();
+const id = ref(route.query.id as string);//id参数
+const deptOptions = ref([])//机构数据
+const companyOptions = ref()//保险公司数据
+const agreementFromRef = ref<FormInstance>()
+const fromSize = ref<ComponentSize>('large')
+const agreementFrom: AgreementForm = reactive({
+  id: "",
+  agreementTitle: "",//协议名称
+  agreementAbbreviation: "",//协议简称
+  startDate: "",//生效时间
+  endDate: "",//失效时间
+  withTax: "",//是否含增值税结算
+  moneyTime: 0,//结算周期
+  agreementName: "",//协议描述
+  deptId: "",//协议归属
+  companyId: "",//保险公司id
+  agreementType: "",//协议类型
+  custodianId: "",//管理人id
+
+})
+const validatepassword = (_rule: any, value: any, callback: any) => {
+  // 定义正则表达式,例如:只允许字母、数字,长度在4到10个字符之间
+  const codeRegex = /^(?:(?:\+|00)86)?1\d{10}$/;
+  // 判断是否为空
+  if (!value) {
+    return callback(new Error('请输入手机号'));
+  }
+
+  // 验证是否符合正则表达式
+  if (!codeRegex.test(value)) {
+    return callback(new Error('请输入正确的手机号'));
+  }
+  // 验证通过
+  callback();
+};
+
+const agreementFromRules = ref<FormRules>({
+  agreementTitle: [
+    { required: true, trigger: 'blur', message: '请输入协议名称' },
+  ],
+  agreementAbbreviation: [
+    { required: true, trigger: 'blur', message: '请输入协议简称' },
+  ],
+  startDate: [
+    { required: true, trigger: 'change', message: '选择生效时间' },
+  ],
+  endDate: [
+    { required: true, trigger: 'change', message: '选择失效时间' },
+  ],
+  withTax: [
+    { required: true, trigger: 'change', message: '请选择是否含增值税结算' },
+  ],
+  moneyTime: [
+    { required: true, trigger: 'blur', message: '请输入结算周期' },
+  ],
+  agreementName: [
+    { required: true, trigger: 'blur', message: '请输入协议描述' },
+  ],
+  deptId: [
+    { required: true, trigger: 'change', message: '请选择协议归属' },
+  ],
+  companyId: [
+    { required: true, trigger: 'change', message: '请选择保险公司' },
+  ],
+  agreementType: [
+    { required: true, trigger: 'blur', message: '请选择协议类型' },
+  ],
+  custodianId: [
+    { required: true, trigger: 'blur', message: '请选择管理人' },
+  ],
+  dockingPerson: [
+    { required: true, trigger: 'blur', message: '请输入对接人' },
+  ],
+  dockingPhone: [
+    { required: true, trigger: 'blur', message: '请输入对接人手机号' },
+    { validator: validatepassword, trigger: 'blur' }
+  ],
+  attributionId: [
+    { required: true, trigger: 'change', message: '请选择出单账号' },
+  ],
+
+});
+onBeforeMount(async () => {
+  await loadDicts(['agreement_type', 'with_tax']);//获取多个字典
+  getDeptTree();//归属机构
+  company();//获取保险公司列表
+  if (id.value) {
+    agreementFrom.id = id.value;
+    detailsInfo();//详情
+  }
+
+});
+//协议详情
+const detailsInfo = () => {
+  api.agreementApi.agreementGet(id.value).then((res: any) => {
+    if (res.code == '200') {
+      // 更新 agreementFrom 的属性
+      Object.keys(agreementFrom).forEach((key) => {
+        if (res.data.agreement.hasOwnProperty(key)) {
+          // 使用类型断言,确保类型匹配
+          agreementFrom[key as keyof AgreementForm] = res.data.agreement[key] as AgreementForm[keyof AgreementForm];
+        }
+      });
+    } else {
+    }
+  });
+}
+const companyIdChange = (value: any) => {
+  if (value) {
+    agreementFrom.companyId = value[value.length - 1]
+  }
+}
+const deptIdChange = (value: any) => {
+  if (value) {
+    agreementFrom.deptId = value[value.length - 1]
+  }
+}
+// 禁用失效时间选择
+const disabledEndDate = (date: any) => {
+  const startDate = new Date(agreementFrom.startDate);
+  return startDate && date < startDate; // 禁用小于生效时间的日期
+};
+
+// 处理生效时间变化
+const handleStartDateChange = (value: any) => {
+  // 如果生效时间改变,检查失效时间是否需要更新
+  if (agreementFrom.endDate && new Date(agreementFrom.endDate) < new Date(value)) {
+    agreementFrom.endDate = ''; // 清空失效时间
+  }
+};
+
+//嫌憎编辑提交事件
+const submitForm = async (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  await formEl.validate((valid) => {
+    if (valid) {
+      if (id.value) {
+        api.agreementApi.contactEdit({ ...agreementFrom }).then((res: any) => {
+          if (res.code == '200') {
+            ElMessage({
+              type: 'success',
+              message: res.msg,
+              plain: true,
+            })
+            router.back()
+          } else {
+          }
+        });
+      } else {
+        api.agreementApi.contactAdd({ ...agreementFrom }).then((res: any) => {
+          if (res.code == '200') {
+            ElMessage({
+              type: 'success',
+              message: res.msg,
+              plain: true,
+            })
+            router.back()
+          } else {
+          }
+        });
+      }
+    } else {
+
+    }
+  })
+}
+const getDeptTree = () => {
+  api.institutionApi.institutionList({}).then((res: any) => {
+    if (res.code == '200') {
+      deptOptions.value = res.data;
+      agreementFrom.deptId = res.data[0].id;
+    } else {
+    }
+  });
+}
+const company = () => {
+  api.agreementApi.findTree({ name: "" }).then((res: any) => {
+    if (res.code == '200') {
+      companyOptions.value = res.data;
+    } else {
+      ElMessage({
+        type: 'error',
+        message: res.msg,
+        plain: true,
+      })
+    }
+  });
+}
+//选择管理人
+interface managerlist {
+  id: string,
+  name: string,
+  deptName: string,
+}
+const managerModalVisible = ref(false)
+const managerlist = ref<managerlist[]>([]);//机构向下人员列表
+const managerName = ref();//管理员姓名  回显使用
+//选中机构获取人员
+const managerModalNodeClick = (id: string) => {
+  console.log(id)
+  api.agreementApi.getUserByDeptId(id,'').then((res: any) => {
+    if (res.code == '200') {
+      managerlist.value = res.data;
+    }
+  });
+}
+const managerModalQuery=(searchValue:string)=>{
+  api.agreementApi.getUserByDeptId('',searchValue).then((res: any) => {
+    if (res.code == '200') {
+      managerlist.value = res.data;
+    }
+  });
+}
+//选择管理人保存事件
+const managerModalsave = (id: string, name: string, deptName: string) => {
+  agreementFrom.custodianId = id;
+  managerName.value = name + ' - ' + deptName;
+  managerModalVisible.value = false;
+
+}
+
+</script>
+<style lang="scss" scoped>
+.disabled :deep(.el-upload--picture-card) {
+  display: none;
+}
+
+.agreementNotice {
+  width: max-content;
+  padding: 18px 24px;
+  border: 1px solid #eee;
+  border-radius: 4px;
+
+  &:hover {
+    background-color: #fafafa;
+  }
+
+  img {
+    width: 40px;
+    height: 40px;
+    margin-right: 20px;
+  }
+
+  span:first-child {
+    margin-bottom: 2px;
+    font-size: 14px;
+    color: #333;
+  }
+
+  span:last-child {
+    font-size: 14px;
+    color: #666;
+  }
+
+  .fileBtn {
+    margin-left: 40px;
+
+    .btn-icon {
+      margin: 0 10px;
+      font-size: 20px;
+      color: #999;
+      cursor: pointer;
+
+      &:hover {
+        color: #00a0f4;
+      }
+    }
+  }
+}
+
+.image-view {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 9999;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 100%;
+  background: rgb(0 0 0 / 60%);
+
+  img {
+    width: 800px;
+    height: 800px;
+    border-radius: 5px;
+  }
+}
+</style>

+ 727 - 0
src/views/externalAgreement/agreement/agreementDetails.vue

@@ -0,0 +1,727 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <PageMain>
+      <div class="headers">
+        <div class="flex f-c a-start">
+          <div class="flex j-s a-c" style="width: 100%;">
+            <span class="title">{{ agreementInfo.agreementTitle }}</span>
+            <div>
+              <el-button type="default" @click="agreementAuth">协议授权</el-button>
+              <el-button type="default" plain @click="agreementEdit">编辑</el-button>
+              <el-dropdown trigger="click" @command="dropdownCommand" style="margin-left: 10px;">
+                <el-button plain>
+                  更多 <el-icon class="el-icon--right">
+                    <CaretBottom />
+                  </el-icon></el-button>
+                <template #dropdown>
+                  <el-dropdown-menu>
+                    <el-dropdown-item command="启用" v-if="agreementInfo.isEnable == '1'">启用</el-dropdown-item>
+                    <el-dropdown-item command="禁用" v-else>禁用</el-dropdown-item>
+                    <el-dropdown-item command="删除">删除</el-dropdown-item>
+                    <el-dropdown-item command="分配管理人">复制协议</el-dropdown-item>
+                  </el-dropdown-menu>
+                </template>
+              </el-dropdown>
+            </div>
+          </div>
+          <div style="margin-top: 10px;" class="flex">
+            <el-tag type="primary" style="margin-right: 20px;">{{
+              getDict('is_enable').find(val => val.value == agreementInfo.isEnable)?.label}}</el-tag>
+            <el-tag type="success">{{getDict('valid_status').find(val => val.value == agreementInfo.validStatus)?.label
+              }}</el-tag>
+          </div>
+        </div>
+        <el-descriptions :column="3" style="margin-top: 10px;">
+          <el-descriptions-item label="协议号:">{{ agreementInfo.agreementCode }}</el-descriptions-item>
+          <el-descriptions-item label="协议名称:">{{ agreementInfo.agreementTitle }}</el-descriptions-item>
+          <el-descriptions-item label="归属机构:">{{ agreementInfo.deptName }}</el-descriptions-item>
+          <el-descriptions-item label="授权机构:">{{ authDeptlength }}</el-descriptions-item>
+          <el-descriptions-item label="添加人:">
+            {{ agreementInfo.createBy }}
+          </el-descriptions-item>
+          <el-descriptions-item label="添加时间:">
+            {{ agreementInfo.createTime }}
+          </el-descriptions-item>
+        </el-descriptions>
+      </div>
+    </PageMain>
+    <PageMain class="agreement ">
+      <el-tabs v-model="activeName" class="demo-tabs">
+        <el-tab-pane label="协议信息" name="agreementInfo">
+          <el-descriptions title="基本信息" class="m-top-20" :column="3">
+            <el-descriptions-item label="协议名称:">{{ agreementInfo.agreementTitle
+            }}</el-descriptions-item>
+            <el-descriptions-item label="协议简称:">{{ agreementInfo.agreementAbbreviation
+            }}</el-descriptions-item>
+            <el-descriptions-item label="协议生效时间:">{{ agreementInfo.startDate }}</el-descriptions-item>
+            <el-descriptions-item label="协议失效时间:">{{ agreementInfo.endDate }}</el-descriptions-item>
+            <el-descriptions-item label="是否含增值税结算:">
+              {{ agreementInfo.withTax }}
+            </el-descriptions-item>
+            <el-descriptions-item label="结算周期:">T{{ agreementInfo.moneyTime }} </el-descriptions-item>
+            <el-descriptions-item label="协议描述:">{{ agreementInfo.agreementName }}</el-descriptions-item>
+          </el-descriptions>
+          <el-descriptions title="其他信息" class="m-top-20" :column="3">
+            <el-descriptions-item label="协议归属:">{{ agreementInfo.deptName }} </el-descriptions-item>
+            <el-descriptions-item label="保险公司:"> {{ agreementInfo.companyId }}</el-descriptions-item>
+            <el-descriptions-item label="协议类型:">{{ agreementInfo.agreementType }} </el-descriptions-item>
+            <el-descriptions-item label="管理人:"> {{ agreementInfo.custodianId }}</el-descriptions-item>
+            <el-descriptions-item label="对接人:"> {{ agreementInfo.dockingPerson }} </el-descriptions-item>
+            <el-descriptions-item label="对接人手机号:"> {{ agreementInfo.dockingPhone }} </el-descriptions-item>
+          </el-descriptions>
+        </el-tab-pane>
+        <el-tab-pane label="费用管理" name="costManagement">
+          <span class="strong m-right-20">对接人</span>
+          <el-button type="primary" @click="addUnderwritingRule" plain>
+            添加对接人
+            <template #icon>
+              <el-icon>
+                <Plus />
+              </el-icon>
+            </template>
+          </el-button>
+          <div class="flex f-c  RuleContainer" v-if="contactPersonList">
+            <div class="list-item" v-for="(item, index) in contactPersonList" :key="index">
+              <div class="header-title flex f-c ">
+                <div class="flex a-c j-s">
+                  <div class="flex a-c">
+                    <img src="../../../assets/images/fee.png" alt="">
+                    <span class="strong m-left-10">{{ item.contactPerson
+                    }}</span>
+                    <span class="strong m-left-10">{{ item.contactMobile }}</span>
+                  </div>
+                  <div class="flex">
+                    <el-button link type="primary" :icon="Delete"
+                      @click="contactPersonDel(item.contactMobile)">删除</el-button>
+                    <el-button link type="primary" :icon="Edit" @click="contactPersonEdit(item)">编辑</el-button>
+                    <el-button v-if="!item.contactPersonShow" link type="default"
+                      @click="item.contactPersonShow = !item.contactPersonShow">展开<el-icon class="m-left-10">
+                        <ArrowDownBold />
+                      </el-icon> </el-button>
+                    <el-button v-else link type="default"
+                      @click="item.contactPersonShow = !item.contactPersonShow">收起<el-icon class="m-left-10">
+                        <ArrowUpBold />
+                      </el-icon></el-button>
+                  </div>
+                </div>
+              </div>
+              <div class="header-content" v-if="item.contactPersonShow">
+                <ComplexTable :tableData="item.costExternalListVo" :columns="columns" :maxHeight="400"
+                  :showIndex="false" :columnwidth="200" :showSelect="false">
+                  <template v-slot:operation="scope">
+                    <el-button type="primary" link @click="itemEdit(scope.operationData.id)"> 编辑
+                    </el-button>
+                    <el-button type="primary" link @click="itemDel(scope.operationData.id)"> 删除
+                    </el-button>
+                  </template>
+                </ComplexTable>
+              </div>
+            </div>
+          </div>
+          <Empty v-else />
+        </el-tab-pane>
+      </el-tabs>
+    </PageMain>
+    <!-- 协议授权弹窗 -->
+    <auth-Drawer :Drawer="agreementAuthDrawer" @cancel="agreementAuthDrawer = false" @save="DrawerSave"
+      @query="Drawerquery" :deptlist="deptauthList" :checkedKeys="checkedKeys" :checkedIds="checkedIds"></auth-Drawer>
+    <!-- 规则费用抽屉组件 -->
+    <contactPersonFeeDialog v-show="addcontactPersonShow" :ruleFeeVisible="addcontactPersonShow"
+      :title='contactPersontitle' :agreementInfo="agreementInfo"  @save="contactPersonSave"
+      :contactPersonEditInfo="contactPersonEditInfo" addMethod="details" @cancel="addcontactPersonShow = false"  />
+    <!-- 规则费用编辑组件 -->
+    <contactPersonFeeDialogEdit :ruleFeeVisible="ruleFeeEditVisible" :headerTitle="ruleFeeHeaderTitle"
+      :agreementInfo="agreementInfo"  :costitemEditInfo="costitemEditInfo" @save="ruleFeeEditSave"
+      @cancel="ruleFeeEditVisible = false" />
+  </div>
+</template>
+<!-- 行为区域 -->
+<script lang='ts' setup>
+import api from '@/api';
+import { CaretBottom } from '@element-plus/icons-vue'
+import { ElMessage, ElMessageBox, } from 'element-plus'
+import { ref, reactive } from 'vue'
+import { useRoute, useRouter } from 'vue-router';
+import { loadDicts, getDict } from '@/utils/composables/dictService';//字典组件
+import authDrawer from "./components/agreementAuth.vue";
+import contactPersonFeeDialog from "./components/contactPersonFeeDialog.vue";
+import contactPersonFeeDialogEdit from "./components/contactPersonFeeDialogEdit.vue";
+import { Delete, Edit } from '@element-plus/icons-vue'
+interface Agreement {
+  [key: string]: string | number; // 协议名称
+}
+const agreementInfo = ref<Agreement>({}); // 初始值为 null
+const route = useRoute();
+const router = useRouter();
+const id = ref(route.query.id as string);//id参数
+const activeName = ref('costManagement')//默认标签页
+const authDeptlength = ref<number>(0);
+onBeforeMount(async () => {
+  await loadDicts(['agreement_type', 'valid_status', 'is_enable', 'with_tax', 'audit_status', 'insurance_type']);//获取多个字典
+
+  if (id.value) {
+    detailsInfo();//协议信息
+    costExternalList();//对接人列表
+  }
+})
+//协议详情
+const detailsInfo = () => {
+  api.agreementApi.agreementGet(id.value).then((res: any) => {
+    if (res.code == '200') {
+      agreementInfo.value = res.data.agreement;
+
+      authDeptlength.value = res.data.dept.length;
+      imageUrl.value = res.data.agreement.fileUrl;
+    } else {
+    }
+  });
+}
+
+//编辑跳转
+const agreementEdit = () => {
+  router.push({
+    name: 'ExternalagreementAdd',
+    query: {
+      id: id.value
+    }
+  })
+}
+//协议授权
+//检索事件
+const imageUrl = ref('');//协议文件
+const agreementAuthDrawer = ref(false)//授权弹窗
+const deptauthList = ref([]);//授权机构列表
+const checkedKeys = ref<string[]>([])//授权机构回显数据
+const checkedIds = ref<string[]>([])//授权机构回显id
+const Drawerquery = (name: any) => {
+  agreementAuth(name)
+}
+//授权保存
+const DrawerSave = (list: any) => {
+  api.agreementApi.authSave({ deptIds: list, id: id.value }).then((res: any) => {
+    if (res.code == '200') {
+      ElMessage({
+        type: 'success',
+        message: res.msg,
+        plain: true,
+      })
+      agreementAuthDrawer.value = false;
+    } else {
+    }
+  });
+}
+//协议授权机构获取
+const agreementAuth = (deptName: any) => {
+  let name = typeof deptName === 'string' ? deptName : '';
+  api.agreementApi.agreementdeptTree(id.value, name).then((res: any) => {
+    if (res.code == '200') {
+      agreementAuthDrawer.value = true;
+      deptauthList.value = res.data;
+      checkedKeys.value = getUncheckedIds(res.data);
+      checkedIds.value = getUncheckedIds(res.data);
+    } else {
+    }
+  });
+}
+// 递归回显数据
+function getUncheckedIds(data: any[]): string[] {
+  let ids: string[] = [];
+
+  data.forEach((item) => {
+    if (item.isCheck === 1) {
+      ids.push(item.id); // 如果 isCheck 为 0,添加 id
+    }
+    if (item.children && item.children.length > 0) {
+      // 如果有子项,递归调用
+      ids = ids.concat(getUncheckedIds(item.children));
+    }
+  });
+
+  return ids;
+}
+
+//更多操作集合
+const dropdownCommand = (e: any) => {
+  switch (e) {
+    case '启用':
+      api.agreementApi.sysUpAndDown({ ids: [id.value], opType: '0' }).then((res: any) => {
+        if (res.code == '200') {
+          ElMessage({
+            type: 'success',
+            message: res.msg,
+            plain: true,
+          })
+          router.push({
+            name: 'reload',
+          })
+        } else {
+        }
+      });
+      break;
+    case '禁用':
+      ElMessageBox.confirm(
+        '确定要禁用该协议吗,禁用后,该协议不在支持报价出单,并且已核保待缴费,待支付的订单,都将失效,请确认是否禁用?',
+        '禁用协议',
+        {
+          confirmButtonText: '确定禁用',
+          type: 'error',
+          center: true,
+          confirmButtonClass: 'el-button--danger',
+        }
+      )
+        .then(() => {
+          api.agreementApi.sysUpAndDown({ ids: [id.value], opType: '1' }).then((res: any) => {
+            if (res.code == '200') {
+              ElMessage({
+                type: 'success',
+                message: res.msg,
+                plain: true,
+              })
+              router.push({
+                name: 'reload',
+              })
+            }
+          });
+        })
+
+      break;
+    case '删除':
+      ElMessageBox.confirm(
+        '确定要删除该协议吗,删除后,该协议不在支持报价出单,并且已核保待缴费,待支付的订单,都将失效,请确认是否删除?',
+        '删除协议',
+        {
+          confirmButtonText: '确定删除',
+          type: 'error',
+          center: true,
+          confirmButtonClass: 'el-button--danger',
+        }
+      )
+        .then(() => {
+          api.agreementApi.agreementDelete({ ids: [id.value] }).then((res: any) => {
+            if (res.code == '200') {
+              ElMessage({
+                type: 'success',
+                message: res.msg,
+                plain: true,
+              })
+              router.back()
+            }
+          });
+        })
+      break;
+    case '复制协议':
+      break;
+    default:
+      break;
+  }
+
+}
+
+//  添加承保规则
+const addcontactPersonShow = ref(false);//添加对接人
+const contactPersonList = ref();//协议下对接人费用
+const contactPersontitle = ref();//弹窗title
+const addUnderwritingRule = () => {//添加对接人
+  addcontactPersonShow.value = true;
+  contactPersontitle.value = '添加';
+}
+
+//对接人列表
+const costExternalList = () => {
+  api.agreementApi.costExternalList({ ptlAgreementId: id.value }).then((res: any) => {
+    if (res.code == '200') {
+      res.data.map((val: any) => {
+        val.contactPersonShow = false;
+        return val;
+      })
+      contactPersonList.value = res.data;
+    } else {
+    }
+  });
+}
+//修改对接人所有费用
+interface contactPersonEditInfo{
+  ptlAgreementId: string,
+  contactPerson: string,
+  contactMobile: string,
+  ptlAgreementCostExternalAddList: any[],
+}
+const contactPersonEditInfo = reactive<contactPersonEditInfo>({
+  ptlAgreementId: "",//协议id
+  contactPerson: "",//对接人
+  contactMobile: "",//手机号
+  ptlAgreementCostExternalAddList: [],//费用集合
+})
+const contactPersonEdit = (item: any) => {
+  api.agreementApi.selectByAgreementIdAll({ ptlAgreementId: id.value, contactPerson: item.contactPerson, contactMobile: item.contactMobile }).then((res: any) => {
+    if (res.code == '200') {
+      const dict = getDict('insurance_type');//获取险种字典
+      const difference = getObjectArrayDifference(dict, res.data, "label", 'productName');//获取编辑数据和字典数组差集
+      const differenceArray:any = dataCompletion(difference);//差集对象赋值
+      contactPersonEditInfo.ptlAgreementId = id.value;
+      contactPersonEditInfo.contactPerson = item.contactPerson;
+      contactPersonEditInfo.contactMobile = item.contactMobile;
+      contactPersonEditInfo.ptlAgreementCostExternalAddList =[...res.data,...differenceArray];
+      contactPersontitle.value = '编辑';
+      addcontactPersonShow.value = true;
+    } else {
+    }
+  });
+}
+//匹配编辑和新增数组差集
+const getObjectArrayDifference = (arr1: any, arr2: any, key: string, key1: string) => {
+  console.log(arr1, arr2)
+  return arr1.filter((item1: any) =>
+    !arr2.some((item2: any) => item2[key1] === item1[key])
+  );
+}
+/**
+ * @param dataCompletion 编辑数据补全
+ * @param list 差集数组
+ */
+const dataCompletion = (list: any) => {
+  let array = list.map((val: any) => {
+    const filteredList: any = filterCostTypeList(val.label);//根据险种匹配列表
+    let list = JSON.parse(JSON.stringify(filteredList)); // 深拷贝
+    return {
+      productName: val.label,
+      productId: val.value,
+      effectiveTime: "",//生效时间
+      failureTime: "",//失效时间
+      auditMethod: "",//审核方式
+      isInquiry: "0",//是否线询
+      costDescribe: "",//费用描述
+      costExternalTypeAdds: list,
+    }
+  })
+  return array;
+}
+
+// 过滤方法
+const filterCostTypeList = (name: string) => {
+  const ptlAgreementCostTypeList = [
+    {
+      costType: "交强险",
+      inlet: 0,
+      export: 0,
+      totalCost: 0,
+      other: 0,
+      level1: 0,
+      level5: 0,
+    },
+    {
+      costType: "商业险",
+      inlet: 0,
+      export: 0,
+      totalCost: 0,
+      other: 0,
+      level1: 0,
+      level5: 0,
+    },
+    {
+      costType: "驾意险",
+      inlet: 0,
+      export: 0,
+      totalCost: 0,
+      other: 0,
+      level1: 0,
+      level5: 0,
+    }
+  ];
+  return ptlAgreementCostTypeList.filter(item => {
+    if (name === "单交" && item.costType === "商业险") {
+      return false; // 当 name 为 "单交" 且 costType 为 "商业险" 时,返回 false,不包含该项
+    }
+    if (name === "单三" && item.costType === "商业险") {
+      return false; // 当 name 为 "单交" 且 costType 为 "商业险" 时,返回 false,不包含该项
+    }
+    if (name === "单商" && item.costType === "交强险") {
+      return false; // 当 name 为 "商业" 且 costType 为 "交强险" 时,返回 false,不包含该项
+    }
+    return true; // 其他情况返回 true,保留该项
+  });
+};
+//删除规则
+const contactPersonDel = (contactMobile: string) => {
+  ElMessageBox.confirm(
+    '确定要删除该对接人吗,删除后,该对接人的费用也将同步删除,请确认是否删除?',
+    '删除对接人',
+    {
+      confirmButtonText: '确定删除',
+      type: 'error',
+      center: true,
+      confirmButtonClass: 'el-button--danger',
+    }
+  )
+    .then(() => {
+      api.agreementApi.deleteByPtlAgreementIdAndMobile({ ptlAgreementId: id.value, contactMobile: contactMobile }).then((res: any) => {
+        if (res.code == '200') {
+          costExternalList();
+          ElMessage({
+            type: 'success',
+            message: res.msg,
+            plain: true,
+          })
+        }
+      });
+    })
+}
+//添加费用
+const ruleFeeEditVisible = ref(false);//编辑弹窗
+const ruleFeeHeaderTitle = ref();//编辑弹窗title
+const costitemEditInfo = ref();
+const columns = [
+  { prop: "productName", label: "险种", width: '200' },
+  {
+    prop: "isInquiry", label: "是否现询", width: '200', formatter: (row: any) => {
+      if (row.isInquiry == '0') {
+        return "是"
+      } else {
+        return "否"
+      }
+    }
+  },
+  {
+    prop: "costExternalTypeAdds", label: "交强险费用", width: '300', formatter: (row: any) => {
+      let info = row.costExternalTypeAdds.find((val: any) => val.costType == '交强险');
+      if (info) {
+        return `入口:${info.inlet.toFixed(2)}% 出口:${info.export.toFixed(2)}%\n一级:${info.level1.toFixed(2)}% 五级:${info.level5.toFixed(2)}%`
+      }else {
+        return '-'
+      }
+    }
+  },
+  {
+    prop: "costExternalTypeAdds", label: "商业险费用", width: '300', formatter: (row: any) => {
+      let info = row.costExternalTypeAdds.find((val: any) => val.costType == '商业险');
+      if (info) {
+        return `入口:${info.inlet.toFixed(2)}% 出口:${info.export.toFixed(2)}%\n一级:${info.level1.toFixed(2)}% 五级:${info.level5.toFixed(2)}%`
+      }else {
+        return '-'
+      }
+    }
+  },
+  {
+    prop: "costExternalTypeAdds", label: "驾意险费用", width: '300', formatter: (row: any) => {
+      let info = row.costExternalTypeAdds.find((val: any) => val.costType == '驾意险');
+      if (info) {
+        return `入口:${info.inlet.toFixed(2)}% 出口:${info.export.toFixed(2)}%\n一级:${info.level1.toFixed(2)}% 五级:${info.level5.toFixed(2)}%`
+      }else {
+        return '-'
+      }
+    }
+  },
+
+  {
+    prop: "effectiveTime", label: "有效期", width: '200',
+    formatter: (row: any) => {
+      return `起${row.effectiveTime}\n止${row.failureTime}`
+    }
+  },
+  {
+    prop: "isEnabled", label: "费用状态", width: '200', component: "Tag",
+    formatter: (row: any) => {
+      let name = getDict('is_enable').find(val => val.value == row.isEnabled).label
+      if (name) {
+        return { type: 'danger', text: name };
+      } else {
+        return { type: 'primary', text: name };
+      }
+    }
+  },
+  {
+    prop: "effective", label: "有效状态", width: '200',
+    component: "Tag",
+    formatter: (row: any) => {
+      if (row.effective) {
+        return { type: 'danger', text: '有效' };
+      } else {
+        return { type: 'danger', text: '无效' };
+      }
+    }
+  },
+];
+
+//费用提交
+const contactPersonSave = (data: object) => {
+  if (contactPersontitle.value == '编辑') {
+    api.agreementApi.ptlAgreementCostExternalupdate(data).then((res: any) => {
+      if (res.code == '200') {
+        addcontactPersonShow.value = false;
+        costExternalList();
+      } else {
+      }
+    });
+  } else {
+    api.agreementApi.ptlAgreementCostExternalsave(data).then((res: any) => {
+      if (res.code == '200') {
+        addcontactPersonShow.value = false;
+        costExternalList();
+      } else {
+      }
+    });
+  }
+
+}
+//费用编辑
+const ruleFeeEditSave = (data: object) => {
+  api.agreementApi.FeeupdateById(data).then((res: any) => {
+    if (res.code == '200') {
+      ElMessage({
+        type: 'success',
+        message: res.msg,
+        plain: true,
+      })
+      ruleFeeEditVisible.value=false;
+      costExternalList();
+    } else {
+    }
+  });
+}
+//编辑删除费用
+const itemEdit = (id: string) => {
+  api.agreementApi.FeegetById(id).then((res: any) => {
+    if (res.code == '200') {
+      ruleFeeHeaderTitle.value = res.data.productName;
+      costitemEditInfo.value = res.data;
+      ruleFeeEditVisible.value = true;
+    } else {
+    }
+  });
+}
+
+//删除费用
+const itemDel = (id: string) => {
+  ElMessageBox.confirm(
+    '确定要删除该费用,请确认是否删除?',
+    '删除费用',
+    {
+      confirmButtonText: '确定删除',
+      type: 'error',
+      center: true,
+      confirmButtonClass: 'el-button--danger',
+    }
+  )
+    .then(() => {
+      api.agreementApi.deleteById([id]).then((res: any) => {
+        if (res.code == '200') {
+          costExternalList();
+          ElMessage({
+            type: 'success',
+            message: res.msg,
+            plain: true,
+          })
+        } else {
+        }
+      });
+    })
+}
+</script>
+<!-- 样式区域 -->
+<style lang='scss' scoped>
+:deep(.demo-tabs .el-tabs__item) {
+  height: 60px;
+  font-size: 20px;
+  font-weight: bold !important;
+}
+
+:deep(.ruleFee .el-tabs__item) {
+  height: 40px;
+  font-size: 14px;
+  font-weight: 500 !important;
+}
+
+:deep(.agreement .p-5) {
+  padding-top: 0;
+}
+
+:deep(.el-descriptions__cell) {
+  width: 33.33%;
+}
+
+:deep(.my-label) {
+  font-weight: bold !important;
+}
+
+.headers {
+  .title {
+    font-size: 20px;
+    font-weight: bold;
+    color: #333;
+  }
+}
+
+.RuleContainer {
+  margin-top: 20px;
+
+  .list-item {
+    padding: 18px 16px;
+    margin-bottom: 20px;
+    border: 1px solid #e5e5e5;
+    border-radius: 10px;
+
+    .empty {
+      font-size: 14px;
+      color: #666;
+      text-align: center;
+    }
+  }
+}
+
+.agreementNotice {
+  width: max-content;
+  padding: 18px 24px;
+  border: 1px solid #eee;
+  border-radius: 4px;
+
+  &:hover {
+    background-color: #fafafa;
+  }
+
+  img {
+    width: 40px;
+    height: 40px;
+    margin-right: 20px;
+  }
+
+  span:first-child {
+    margin-bottom: 2px;
+    font-size: 14px;
+    color: #333;
+  }
+
+  span:last-child {
+    font-size: 14px;
+    color: #666;
+  }
+
+  .fileBtn {
+    margin-left: 40px;
+
+    .btn-icon {
+      margin: 0 10px;
+      font-size: 20px;
+      color: #999;
+      cursor: pointer;
+
+      &:hover {
+        color: #00a0f4;
+      }
+    }
+  }
+}
+
+.title {
+  display: flex;
+
+  &::before {
+    display: inline-block;
+    width: 4px;
+    height: 18px;
+    margin-right: 20px;
+    content: "";
+    background-color: #0083ff;
+  }
+}
+</style>

+ 479 - 0
src/views/externalAgreement/agreement/agreementIndex.vue

@@ -0,0 +1,479 @@
+<template>
+  <div>
+    <PageMain class="vh100">
+      <SearchForm :fields="fields" :formInline="formInline" :onSearch="agreementQuery" :onReset="resetForm" />
+      <ComplexTable :tableData="tableData" :columns="columns" :showIndex="false" :columnwidth="200" :showSelect="true"
+        :pageInfo="pageInfo" @getList="getList" @selectionChange="selectionChange">
+        <template v-slot:table-title-btn>
+          <RouterLink :to="{ name: 'ExternalagreementAdd' }">
+            <el-button type="primary">
+              添加协议
+              <template #icon>
+                <el-icon>
+                  <Plus />
+                </el-icon>
+              </template>
+            </el-button>
+          </RouterLink>
+          <el-dropdown placement="bottom-start">
+            <el-button > 批量管理<el-icon>
+                <arrow-down />
+              </el-icon>
+            </el-button>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item :disabled="!sysUpAndDownList.length" @click="batchDel">删除</el-dropdown-item>
+                <el-dropdown-item :disabled="!sysUpAndDownList.length" @click="batchdisable">禁用</el-dropdown-item>
+                <el-dropdown-item :disabled="!sysUpAndDownList.length" @click="batchenable">启用</el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </template>
+        <template v-slot:operation="scope">
+          <el-button type="primary" link @click="agreemenUpdate(scope.operationData)"> 详情
+          </el-button>
+          <el-dropdown placement="bottom-start" trigger="click">
+            <span class="el-dropdown-link">
+              更 多
+            </span>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item @click="enable(scope.operationData.id)">启用</el-dropdown-item>
+                <el-dropdown-item @click="disable(scope.operationData.id)">禁用</el-dropdown-item>
+                <el-dropdown-item @click="del(scope.operationData.id)">删除</el-dropdown-item>
+                <el-dropdown-item @click="agreemenCopy(scope.operationData.id)">复制协议</el-dropdown-item>
+                <el-dropdown-item @click="batchAuthorization(scope.operationData.id)">协议授权</el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </template>
+        <template v-slot:search="scope">
+          {{ scope.slotData.label }}
+        </template>
+      </ComplexTable>
+    </PageMain>
+    <auth-Drawer :Drawer="agreementAuthDrawer" @cancel=" agreementAuthDrawer = false" @save="DrawerSave"
+      @query="Drawerquery" :deptlist="deptauthList" :checkedKeys="checkedKeys" :checkedIds="checkedIds"></auth-Drawer>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive } from 'vue'
+import { RouterLink, useRouter } from "vue-router";
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { loadDicts, getDict } from '@/utils/composables/dictService';//字典组件
+import authDrawer from "./components/agreementAuth.vue";
+import api from '@/api';
+interface PageInfo {
+  pageNum: number;
+  pageSize: number;
+  sizes: number[];
+  total: number;
+}
+onBeforeMount(async () => {
+  await loadDicts(['agreement_type', 'valid_status', 'is_enable', 'with_tax']);//获取多个字典
+  findpage();//列表数据
+  getDeptTree();
+  agreement_type.value = getDict('agreement_type');//协议类型
+  valid_status.value = getDict('valid_status');//生效状态
+  is_enable.value = getDict('is_enable');//协议状态
+  with_tax.value = getDict('with_tax')//是否含税
+});
+
+const parentIdprops = {
+  expandTrigger: 'hover' as const,
+  value: 'id',
+  label: 'name',
+  checkStrictly: true,
+}
+const tableData = ref([
+]);
+const sysUpAndDownList = ref<string[]>([])//选中id
+const provinceoptions = ref<string[]>([])//省市区回显数据
+const deptOptions = ref()//机构数据
+const agreement_type = ref();
+const valid_status = ref();
+const is_enable = ref();
+const with_tax = ref();
+const router = useRouter();
+const pageInfo = ref({
+  pageNum: 1,
+  pageSize: 20,
+  sizes: [10, 20, 30, 40, 50],
+  total: 100,
+});
+const formInline = reactive({
+  searchValue: '',//协议信息
+  deptId: "",//归属
+  companyId: '',//保险公司id
+  agreementType: '',//协议类型
+  validStatus: "",//有效状态
+  isEnable: "",//协议状态
+  withTax: "",
+  dockingPerson: "",
+  attributionName: "",
+  takeStartDate: "",
+  takeEndDate: "",
+  loseStartDate: "",
+  loseEndDate: "",
+  isExternal:'1',
+  value1:[],
+  value2:[],
+})
+
+//表格字段数据
+const fields = ref([
+  { label: "协议信息", model: "searchValue", type: "input", placeholder: "输入代码,名称,简称查找" },
+  {
+    label: "协议归属", model: "deptId", type: "cascader", options: deptOptions, props: parentIdprops, onChange: (value: any) => {
+      formInline.deptId = value[value.length - 1];
+    }
+  },
+  {
+    label: "保险公司", model: "companyId", type: "select", placeholder: "选择保险公司",
+  },
+  {
+    label: "协议类型", model: "agreementType", type: "select", placeholder: "选择保险公司", options: agreement_type,
+  },
+  {
+    label: "生效状态", model: "validStatus", type: "select", placeholder: "选择生效状态", options: valid_status,
+  },
+  {
+    label: "协议状态", model: "isEnable", type: "select", placeholder: "选择协议状态", options: is_enable,
+  },
+  {
+    label: "是否含税结算", model: "withTax", type: "select", placeholder: "请选择", options: with_tax,
+  },
+  {
+    label: "生效时间", model: "value1", type: "date", dateType: "datetimerange", startplaceholder: "开始时间", endplaceholder: "结束时间",
+    onChange: (value: any) => {
+      formInline.takeStartDate = value[0];
+      formInline.takeEndDate = value[1];
+    }
+  },
+  {
+    label: "失效时间", model: "value2", type: "date", dateType: "datetimerange", startplaceholder: "开始时间", endplaceholder: "结束时间",
+    onChange: (value: any) => {
+      formInline.loseStartDate = value[0];
+      formInline.loseEndDate = value[1];
+    }
+  },
+]);
+
+//数据查询
+function findpage() {
+  api.agreementApi.agreementList({ pages: pageInfo.value.pageNum, size: pageInfo.value.pageSize, ...formInline }).then((res: any) => {
+    if (res.code == '200') {
+      tableData.value = res.data.records;
+      pageInfo.value.pageNum = res.data.current;
+      pageInfo.value.pageSize = res.data.size;
+      pageInfo.value.total = res.data.total;
+    } else {
+    }
+  });
+}
+const columns = [
+  { prop: "agreementCode", label: "协议编号", width: '200', },
+  {
+    prop: "agreementTitle", label: "协议名称", width: '200',
+  },
+  { prop: "agreementAbbreviation", label: "协议简称", width: '120', },
+  {
+    prop: "deptName", label: "归属", width: '120',
+  },
+  {
+    prop: "agreementType", label: "协议类型", width: '200', formatter: (row: any) => {
+      let name = getDict('agreement_type').find(val => val.value == row.agreementType).label
+      return name
+    }
+  },
+  {
+    prop: "isEnable", label: "协议状态", width: '100',
+    component: "Tag",
+    formatter: (row: any) => {
+      let name = getDict('is_enable').find(val => val.value == row.isEnable).label
+      if (name) {
+        return { type: 'primary', text: name };
+      } else {
+        return { type: 'primary', text: name };
+      }
+    }
+
+  },
+  {
+    prop: "validStatus", label: "生效状态", width: '120', component: "Tag",
+    formatter: (row: any) => {
+      let name = getDict('valid_status').find(val => val.value == row.validStatus).label
+      if (name) {
+        return { type: 'danger', text: name };
+      } else {
+        return { type: 'danger', text: name };
+      }
+    }
+  },
+  {
+    prop: "withTax", label: "是否含税结算", width: '180',
+    formatter: (row: any) => {
+      let name = getDict('with_tax').find(val => val.value == row.withTax).label
+      return name
+    }
+  },
+  {
+    prop: "moneyTime", label: "结算周期", width: '120',
+    formatter: (row: any) => {
+      return 'T' + row.moneyTime
+    }
+  },
+  { prop: "company", label: "保险公司", width: '200' },
+  { prop: "custodianId", label: "管理人", width: '200' },
+  { prop: "startDate", label: "生效时间", width: '200' },
+  { prop: "endDate", label: "失效时间", width: '200' },
+  { prop: "createTime", label: "添加时间", width: '200' },
+  { prop: "createBy", label: "添加人", width: '200' },
+];
+//分页事件
+function getList(item: PageInfo) {
+  pageInfo.value.pageNum = item.pageNum;
+  pageInfo.value.pageSize = item.pageSize;
+  findpage();
+
+};
+//机构授权
+const agreementAuthDrawer = ref(false)//授权弹窗
+const deptauthList = ref([]);//授权机构列表
+const checkedKeys = ref<string[]>([])//授权机构回显数据
+const checkedIds = ref<string[]>([])//授权机构回显id
+//授权机构检索
+const Drawerquery = (name: any) => {
+  agreementAuth(name)
+}
+//授权保存
+const DrawerSave = (list: any) => {
+  api.agreementApi.authSave({ deptIds: list, id: agreementId.value }).then((res: any) => {
+    if (res.code == '200') {
+      ElMessage({
+        type: 'success',
+        message: res.msg,
+        plain: true,
+      })
+      agreementAuthDrawer.value = false;
+    } else {
+    }
+  });
+}
+//协议授权
+const agreementAuth = (deptName: any) => {
+  let name = typeof deptName === 'string' ? deptName : '';
+  api.agreementApi.agreementdeptTree(agreementId.value, name).then((res: any) => {
+    if (res.code == '200') {
+      agreementAuthDrawer.value = true;
+      deptauthList.value = res.data;
+      checkedKeys.value = getUncheckedIds(res.data);
+      checkedIds.value = getUncheckedIds(res.data);
+    } else {
+    }
+  });
+}
+// 定义递归函数
+function getUncheckedIds(data: any[]): string[] {
+  let ids: string[] = [];
+
+  data.forEach((item) => {
+    if (item.isCheck === 1) {
+      ids.push(item.id); // 如果 isCheck 为 0,添加 id
+    }
+    if (item.children && item.children.length > 0) {
+      // 如果有子项,递归调用
+      ids = ids.concat(getUncheckedIds(item.children));
+    }
+  });
+
+  return ids;
+}
+
+function agreemenUpdate(item: any) {
+  router.push({
+    name: 'ExternalAgreementDetails',
+    query: {
+      id: item.id
+    }
+  })
+}
+
+//多选事件
+const selectionChange = (val: any) => {
+  sysUpAndDownList.value = val;
+}
+//协议授权
+const agreementId = ref()
+const batchAuthorization = (value: string) => {
+  agreementId.value = value;
+  api.agreementApi.agreementdeptTree(value, '').then((res: any) => {
+    if (res.code == '200') {
+      agreementAuthDrawer.value = true;
+      deptauthList.value = res.data;
+      checkedKeys.value = getUncheckedIds(res.data);
+      checkedIds.value = getUncheckedIds(res.data);
+    } else {
+    }
+  });
+
+}
+//复制协议
+const agreemenCopy = (id:string) => {
+  router.push({
+    name: 'AgreementCopy',
+    query: {
+      id: id
+    }
+  })
+}
+//禁用
+const disable = (value: string) => {
+  ElMessageBox.confirm(
+    '确定要禁用该协议吗,禁用后,该协议不在支持报价出单,并且已核保待缴费,待支付的订单,都将失效,请确认是否禁用?',
+    '禁用协议',
+    {
+      confirmButtonText: '确定禁用',
+      type: 'error',
+      center: true,
+      confirmButtonClass: 'el-button--danger',
+    }
+  )
+    .then(() => {
+      sysUpAndDown('1', [value]);
+    })
+}
+//启用
+const enable = (value: string) => {
+  sysUpAndDown('0', [value]);
+}
+const del = (value: string) => {
+  ElMessageBox.confirm(
+    '确定要删除该协议吗,删除后,该协议不在支持报价出单,并且已核保待缴费,待支付的订单,都将失效,请确认是否删除?',
+    '删除协议',
+    {
+      confirmButtonText: '确定删除',
+      type: 'error',
+      center: true,
+      confirmButtonClass: 'el-button--danger',
+    }
+  )
+    .then(() => {
+      api.agreementApi.agreementDelete([value]).then((res: any) => {
+        if (res.code == '200') {
+          ElMessage({
+            type: 'success',
+            message: res.msg,
+            plain: true,
+          })
+          router.push({
+            name: 'reload',
+          })
+        }
+      });
+    })
+}
+//批量删除
+const batchDel = () => {
+  ElMessageBox.confirm(
+    '确定要删除该协议吗,删除后,该协议不在支持报价出单,并且已核保待缴费,待支付的订单,都将失效,请确认是否删除?',
+    '删除协议',
+    {
+      confirmButtonText: '确定删除',
+      type: 'error',
+      center: true,
+      confirmButtonClass: 'el-button--danger',
+    }
+  )
+    .then(() => {
+      api.agreementApi.agreementDelete(sysUpAndDownList.value).then((res: any) => {
+        if (res.code == '200') {
+          ElMessage({
+            type: 'success',
+            message: res.msg,
+            plain: true,
+          })
+          router.push({
+            name: 'reload',
+          })
+        }
+      });
+    })
+
+}
+//批量禁用
+const batchdisable = () => {
+  ElMessageBox.confirm(
+    '确定要禁用该协议吗,禁用后,该协议不在支持报价出单,并且已核保待缴费,待支付的订单,都将失效,请确认是否禁用?',
+    '禁用协议',
+    {
+      confirmButtonText: '确定禁用',
+      type: 'error',
+      center: true,
+      confirmButtonClass: 'el-button--danger',
+    }
+  )
+    .then(async () => {
+      await sysUpAndDown('1', undefined);
+    })
+    .catch(() => {
+    })
+
+
+}
+//批量启用
+const batchenable = async () => {
+  await sysUpAndDown('0', undefined);
+}
+const agreementQuery = () => {
+  findpage();
+}
+const sysUpAndDown = async (type: string, list?: string[]) => {
+  await api.agreementApi.sysUpAndDown({ ids: list ? list : sysUpAndDownList.value, opType: type }).then((res: any) => {
+    if (res.code == '200') {
+      findpage();
+      ElMessage({
+        type: 'success',
+        message: res.msg,
+        plain: true,
+      })
+    }
+  });
+}
+const resetForm = () => {
+  formInline.searchValue = '';
+  provinceoptions.value = [];
+  findpage();
+}
+const getDeptTree = () => {
+  api.institutionApi.institutionList({}).then((res: any) => {
+    if (res.code == '200') {
+      deptOptions.value = res.data;
+    } else {
+    }
+  });
+}
+</script>
+
+<style lang="scss" scoped>
+.demo-form-inline .el-input {
+  --el-input-width: 220px;
+}
+
+.demo-form-inline .el-select {
+  --el-select-width: 220px;
+}
+
+:deep(.el-dropdown-link) {
+  display: flex;
+  align-items: center;
+  margin-left: 15px;
+  color: var(--el-color-primary);
+  cursor: pointer;
+}
+
+.el-inputs {
+  width: 100% !important;
+}
+</style>

+ 83 - 0
src/views/externalAgreement/agreement/components/agreementAuth.vue

@@ -0,0 +1,83 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <el-drawer v-model="props.Drawer" :direction="direction" @open="DrawerOpen" :close-on-click-modal="false">
+      <template #header>
+        <h4>参与机构</h4>
+      </template>
+      <template #default>
+        <div class="flex f-c">
+          <div>
+            <el-input v-model="deptName" class="m-right-20" style="width: 240px;" placeholder="输入名称"
+              :prefix-icon="Search" />
+            <el-button @click="query">查询</el-button>
+          </div>
+          <el-checkbox v-model="checkedALL" label="全选" size="large" />
+          <el-tree :data="props.deptlist" show-checkbox node-key="id" ref="treeRef" :props="defaultProps"
+            :default-expand-all="true" @check="handleCheck" :default-checked-keys="props.checkedKeys"
+            :check-strictly="true" />
+        </div>
+      </template>
+      <template #footer>
+        <div>
+          <el-button @click="cancel">取消</el-button>
+          <el-button type="primary" @click="save">保存</el-button>
+        </div>
+      </template>
+    </el-drawer>
+  </div>
+</template>
+
+<!-- 行为区域 -->
+<script lang="ts" setup>
+import { defineProps, defineEmits } from "vue";
+import { DrawerProps } from 'element-plus';
+import { Search } from '@element-plus/icons-vue'
+const props = defineProps<{
+  Drawer: boolean,
+  deptlist: string[]
+  checkedKeys: string[]
+  checkedIds: string[]
+}>();
+const emits = defineEmits(["cancel", 'save', 'DrawerOpen', 'query']);
+const defaultProps = {
+  children: 'children',
+  label: 'name',
+}
+const direction = ref<DrawerProps['direction']>('rtl')
+const deptName = ref('');
+
+
+const checkedALL = ref(false)
+const ids = ref()//id集合
+const DrawerOpen = () => {
+
+}
+watch(props, () => {
+  if (props.Drawer) {
+    ids.value = props.checkedIds;
+  }
+
+})
+const cancel = () => {
+  emits('cancel')
+}
+const save = () => {
+    emits('save', ids.value)
+}
+const query = () => {
+  emits('query', deptName.value)
+
+}
+const handleCheck = (
+  _checkedNodes: any[],
+  checkedInfo: {
+    checkedKeys: any[];
+    checkedNodes: any[];
+    halfCheckedKeys: any[];
+    halfCheckedNodes: any[];
+  }
+) => {
+  ids.value = checkedInfo.checkedKeys;
+};
+</script>

+ 485 - 0
src/views/externalAgreement/agreement/components/contactPersonFeeDialog.vue

@@ -0,0 +1,485 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <el-drawer v-model="props.ruleFeeVisible" :direction="direction" :close-on-click-modal="false" size="50%"
+      @open="draweropen" @close="close">
+      <template #header>
+        <h4>{{ props.title }}费用</h4>
+      </template>
+      <template #default>
+        <ElRow :gutter="20" class="m-top-20">
+          <el-form :model="ruleFeeinfo" label-width="120px" ref="formRef" style="width: 100%;" :rules="feerules"
+            class="flex a-c f-wrap" :size="fromSize">
+            <ElCol :md="24" :lg="18">
+              <el-form-item label="协议" :prop="addMethod == 'list' ? 'ptlAgreementId' : ''">
+                <el-select v-if="addMethod == 'list'" v-model="ruleFeeinfo.ptlAgreementId" placeholder="选择协议"
+                  :disabled="props.title != '添加'">
+                  <el-option v-for="item in agreementselect" :key="item.id" :label="item.agreementTitle"
+                    :value="item.id" />
+                </el-select>
+                <el-select v-else v-model="agreementActiveId" placeholder="选择协议" :disabled="props.title != '添加'">
+                  <el-option v-for="item in agreementselect" :key="item.id" :label="item.agreementTitle"
+                    :value="item.id" />
+                </el-select>
+              </el-form-item>
+            </ElCol>
+            <ElCol :md="24" :lg="18">
+              <el-form-item label="快速配置"
+                v-if="agreementActiveId || ruleFeeinfo.ptlAgreementId || props.agreementInfo?.id">
+                <el-select v-model="contactPersonListid" placeholder="选择对接人" @visible-change="contactvisiblechange"
+                  :disabled="props.title != '添加'" @change="contactPersonChange">
+                  <el-option v-for="item in contactPersonList" :key="item.contactMobile" :label="item.contactPerson"
+                    :value="item.contactMobile" />
+                </el-select>
+                <span style="font-size: 12px;color: #aaa;">选择已存在的对接人,直接复制该对接人的费用信息;也可在下方输入框手动配置</span>
+              </el-form-item>
+            </ElCol>
+            <ElCol :md="24" :lg="12">
+              <el-form-item label="对接人姓名" prop="contactPerson">
+                <el-input v-model="ruleFeeinfo.contactPerson" placeholder="请输入对接人姓名" maxlength="50" show-word-limit />
+              </el-form-item>
+            </ElCol>
+            <ElCol :md="24" :lg="12">
+              <el-form-item label="对接人手机号" prop="contactMobile">
+                <el-input v-model="ruleFeeinfo.contactMobile" placeholder="请输入手机号" maxlength="11" show-word-limit />
+              </el-form-item>
+            </ElCol>
+          </el-form>
+        </ElRow>
+        <div style="padding-left: 40px;">
+          <el-tabs v-model="activeName" type="card" @tab-click="hledclick" style="position: relative;"
+            class="ruleFeetabs">
+            <el-tab-pane v-for="(item, index) in ruleFeeinfo.ptlAgreementCostExternalAddList" :key="index"
+              :label="item.productName" :name="item.productName">
+              <ElRow :gutter="20" class="m-top-20">
+                <el-form :model="item" label-width="80px" ref="feeFormRef" style="width: 100%;" :rules="feerules"
+                  class="flex a-c f-wrap" :size="fromSize">
+                  <ElCol :md="24" :lg="12">
+                    <el-form-item label="生效时间" prop="effectiveTime">
+                      <el-date-picker v-model="item.effectiveTime" style="width: 100%;" type="datetime"
+                        placeholder="选择生效时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
+                        time-format="HH:mm:ss" @change="handleStartDateChange($event, index)" />
+                    </el-form-item>
+                  </ElCol>
+                  <ElCol :md="24" :lg="12">
+                    <el-form-item label="失效时间" prop="failureTime">
+                      <el-date-picker v-model="item.failureTime" style="width: 100%;" type="datetime"
+                        placeholder="选择失效时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
+                        time-format="HH:mm:ss" :disabled-date="(date: any) => disabledEndDate(date, index)" />
+                    </el-form-item>
+                  </ElCol>
+                  <ElCol :md="24" :lg="12">
+                    <el-form-item label="审核方式" prop="auditMethod">
+                      <el-select v-model="item.auditMethod" placeholder="选择审核方式">
+                        <el-option label="自动审核" value="0" />
+                        <el-option label="手动审核" value="1" />
+                      </el-select>
+                    </el-form-item>
+                  </ElCol>
+                  <ElCol :md="24" :lg="12">
+                    <el-form-item label="是否现询" prop="isInquiry">
+                      <el-radio-group v-model="item.isInquiry">
+                        <el-radio value="0" size="large">是</el-radio>
+                        <el-radio value="1" size="large">否</el-radio>
+                      </el-radio-group>
+                    </el-form-item>
+                  </ElCol>
+                  <ElCol :md="24" :lg="24">
+                    <el-form-item label="费用描述" prop="costDescribe">
+                      <el-input v-model="item.costDescribe" maxlength="500" placeholder="请输入费用描述" show-word-limit
+                        type="textarea" />
+                    </el-form-item>
+                  </ElCol>
+                </el-form>
+              </ElRow>
+
+              <el-table :data="item.costExternalTypeAdds" style="width: 100%;" border
+                :header-cell-style="{ 'background-color': '#F7F7F9', 'border-bottom': '1px solid #EEF1F6', 'font-size': '15px', 'color': '#333', 'padding': '10px 0' }">
+                <template #empty>
+                  <div class="flex f-c a-c ">
+                    <img src="../../assets/images/empty.png" alt="">
+                    <span style="font-size: 14px;color: #909399;">暂无数据</span>
+                  </div>
+                </template>
+                <el-table-column prop="costType" label="类型"></el-table-column>
+                <el-table-column prop="inlet" label="入口比例" width="180" align="center">
+                  <template #default="scope">
+                    <el-input-number v-model="scope.row.inlet" :precision="2" controls-position="right"
+                      style="width: 100%;" :min="0" :max="100">
+                      <template #suffix>%</template>
+                    </el-input-number>
+                  </template>
+                </el-table-column>
+                <el-table-column prop="export" label="出口比例" width="180" align="center">
+                  <template #default="scope">
+                    <el-input-number v-model="scope.row.export" :precision="2" controls-position="right"
+                      style="width: 100%;" :min="0" :max="100">
+                      <template #suffix>%</template>
+                    </el-input-number>
+                  </template>
+                </el-table-column>
+                <el-table-column prop="totalCost" label="总费用比例" width="180" align="center">
+                  <template #default="scope">
+                    <el-input-number v-model="scope.row.totalCost" :precision="2" controls-position="right"
+                      style="width: 100%;" :min="0" :max="100">
+                      <template #suffix>%</template>
+                    </el-input-number>
+                  </template>
+                </el-table-column>
+                <el-table-column prop="other" label="其他费用比例" width="180" align="center">
+                  <template #default="scope">
+                    <el-input-number v-model="scope.row.other" :precision="2" controls-position="right"
+                      style="width: 100%;" :min="0" :max="100">
+                      <template #suffix>%</template>
+                    </el-input-number>
+                  </template>
+                </el-table-column>
+                <el-table-column prop="level1" label="1级比例" width="180" align="center">
+                  <template #default="scope">
+                    <el-input-number v-model="scope.row.level1" :precision="2" controls-position="right"
+                      style="width: 100%;" :min="0" :max="100">
+                      <template #suffix>%</template>
+                    </el-input-number>
+                  </template>
+                </el-table-column>
+                <el-table-column prop="level5" label="5级比例" width="180" align="center">
+                  <template #default="scope">
+                    <el-input-number v-model="scope.row.level5" :precision="2" controls-position="right"
+                      style="width: 100%;" :min="0" :max="100">
+                      <template #suffix>%</template>
+                    </el-input-number>
+                  </template>
+                </el-table-column>
+              </el-table>
+            </el-tab-pane>
+          </el-tabs>
+        </div>
+      </template>
+      <template #footer>
+        <div class="flex a-c j-c">
+          <el-button @click="cancel">取消</el-button>
+          <el-button type="primary" @click="save(feeFormRef)">提交审核</el-button>
+        </div>
+      </template>
+    </el-drawer>
+  </div>
+</template>
+
+<!-- 行为区域 -->
+<script lang='ts' setup>
+import { ref, } from 'vue'
+import { DrawerProps, ComponentSize, FormInstance, FormRules, ElMessage } from 'element-plus';
+import { loadDicts, getDict } from '@/utils/composables/dictService';//字典组件
+import { defineProps, defineEmits } from "vue";
+import api from '@/api';
+const direction = ref<DrawerProps['direction']>('rtl')
+const fromSize = ref<ComponentSize>('default')
+const activeName = ref('单交')
+const props = defineProps<{
+  ruleFeeVisible: boolean,
+  agreementInfo?: any,
+  addMethod: string,
+  contactPersonEditInfo?: any,
+  title?: string,
+}>();
+interface ruleFeeinfo {
+  ptlAgreementId: string,
+  contactPerson: string,
+  contactMobile: string,
+  ptlAgreementCostExternalAddList: any[],
+}
+
+const emits = defineEmits(["cancel", 'save']);
+const ruleFeeinfo = reactive<ruleFeeinfo>({
+  ptlAgreementId: "",//协议id
+  contactPerson: "",//对接人
+  contactMobile: "",//手机号
+  ptlAgreementCostExternalAddList: [],//费用集合
+});//规则费用集合
+const insuranceTypeIndex = ref(0);//险种区域下标
+const agreementActiveId = ref();//选中协议id
+const agreementselect = ref();
+const feeFormRef = ref<FormInstance>()
+const formRef = ref<FormInstance>();
+const feerules = ref<FormRules>({
+  ptlAgreementId: [
+    { required: true, trigger: 'change', message: '请选择' },
+  ],
+  contactPerson: [
+    { required: true, trigger: 'blur', message: '请输入对接人姓名' },
+  ],
+  contactMobile: [
+    { required: true, trigger: 'blur', message: '请输入手机号' },
+  ],
+  effectiveTime: [
+    { required: true, trigger: 'change', message: '请选择生效时间' },
+  ],
+  failureTime: [
+    { required: true, trigger: 'change', message: '请选择失效时间' },
+  ],
+  auditMethod: [
+    { required: true, trigger: 'change', message: '请选择审核方式' },
+  ],
+  isInquiry: [
+    { required: true, trigger: 'change', message: '请选择是否线询' },
+  ],
+})
+watch(props, () => {
+  if (props.contactPersonEditInfo && props.contactPersonEditInfo.contactPerson) {
+    Object.assign(ruleFeeinfo, props.contactPersonEditInfo);
+    activeName.value = ruleFeeinfo.ptlAgreementCostExternalAddList[0].productName;
+  }
+})
+onMounted(async () => {
+  await loadDicts(['insurance_type'])
+  const dict = getDict('insurance_type');
+  agreementList();
+  ruleFeeinfo.ptlAgreementCostExternalAddList = dict.map((val: any) => {
+    const filteredList: any = filterCostTypeList(val.label);//根据险种匹配列表
+    let list = JSON.parse(JSON.stringify(filteredList)); // 深拷贝
+    return {
+      productName: val.label,
+      productId: val.value,
+      effectiveTime: "",//生效时间
+      failureTime: "",//失效时间
+      auditMethod: "0",//审核方式
+      isInquiry: "0",//是否线询
+      costDescribe: "",//费用描述
+      costExternalTypeAdds: list,
+    }
+  })
+})
+// 协议列表
+const agreementList = () => {
+  api.agreementApi.agreementList({ pages: 1, size: 100, isExternal: '1' }).then((res: any) => {
+    if (res.code == '200') {
+      agreementselect.value = res.data.records;
+    } else {
+    }
+  });
+}
+//对接人列表
+const contactPersonList = ref();
+const contactPersonListid = ref();
+//对接人下拉列表
+const costExternalList = () => {
+  api.agreementApi.costExternalList({ ptlAgreementId: agreementActiveId.value ? agreementActiveId.value : props.agreementInfo?.id }).then((res: any) => {
+    if (res.code == '200') {
+      contactPersonList.value = res.data;
+    } else {
+    }
+  });
+}
+//选择对接人
+const contactPersonChange = (value: string) => {
+  let info = contactPersonList.value.find((val: any) => val.contactMobile == value);
+  ruleFeeinfo.ptlAgreementCostExternalAddList = info.costExternalListVo;
+  activeName.value = ruleFeeinfo.ptlAgreementCostExternalAddList[0].productName;
+
+}
+//下拉框出现事件
+const contactvisiblechange = (visible: boolean) => {
+  if (visible) {
+    costExternalList();
+  }
+}
+// 禁用失效时间选择
+const disabledEndDate = (date: any, index: number) => {
+  let start = ruleFeeinfo.ptlAgreementCostExternalAddList[index]?.effectiveTime;
+  const startDate = new Date(start);
+  return startDate && date < startDate; // 禁用小于生效时间的日期
+};
+
+// 处理生效时间变化
+/**
+ * @param index  险种下标
+ * @param Costindex 费用下标
+ */
+const handleStartDateChange = (value: any, index: number,) => {
+  let endDate = ruleFeeinfo.ptlAgreementCostExternalAddList[index]?.failureTime;
+  // 如果生效时间改变,检查失效时间是否需要更新
+  if (endDate && new Date(endDate) < new Date(value)) {
+    ruleFeeinfo.ptlAgreementCostExternalAddList[index].failureTime = ''; // 清空失效时间
+  }
+};
+const cancel = () => {
+  emits("cancel")
+}
+const close=()=>{
+  emits("cancel")
+}
+const validateContactPerson = () => {
+  if (formRef.value) {
+    formRef.value.validateField(['contactPerson', 'contactMobile', 'ptlAgreementId'], (valid) => {
+      if (valid) {
+        console.log('对接人验证通过');
+      } else {
+        ElMessage({
+          type: 'error',
+          message: '对接人信息必填',
+          plain: true,
+        });
+      }
+    });
+  }
+};
+const save = async (formEl: FormInstance | undefined | any) => {
+  validateContactPerson();
+  if (!formEl) return
+  const validationPromises = ruleFeeinfo.ptlAgreementCostExternalAddList.map((formRef: any, index: any) => {
+    // 轮询验证所有表单校验
+    return new Promise((resolve, reject) => {
+      if (formRef) {
+        formEl[index].validate((valid: boolean) => {
+          if (valid) {
+
+            resolve(true);
+          } else {
+            reject(index);
+          }
+        });
+      } else {
+        reject(index);
+      }
+    });
+  });
+
+  try {
+    await Promise.all(validationPromises);
+    // 所有表单验证通过后执行提交逻辑
+    if (props.addMethod == 'details') {
+      ruleFeeinfo.ptlAgreementId = agreementActiveId.value ? agreementActiveId.value : props.agreementInfo.id;
+    }
+    emits("save", ruleFeeinfo)
+  } catch (error) {
+    const invalidIndex: any = error; // 获取未通过校验的表单索引
+    const invalidForm = ruleFeeinfo.ptlAgreementCostExternalAddList[invalidIndex]; // 获取未通过校验的表单对象
+    activeName.value = invalidForm.productName;
+    ElMessage({
+      type: 'error',
+      message: `${ruleFeeinfo.ptlAgreementCostExternalAddList[invalidIndex].productName}费用未填写完整`,
+      plain: true,
+    });
+  }
+};
+// 过滤方法
+const ptlAgreementCostTypeList = [
+  {
+    costType: "交强险",
+    inlet: 0,
+    export: 0,
+    totalCost: 0,
+    other: 0,
+    level1: 0,
+    level5: 0,
+  },
+  {
+    costType: "商业险",
+    inlet: 0,
+    export: 0,
+    totalCost: 0,
+    other: 0,
+    level1: 0,
+    level5: 0,
+  },
+  {
+    costType: "驾意险",
+    inlet: 0,
+    export: 0,
+    totalCost: 0,
+    other: 0,
+    level1: 0,
+    level5: 0,
+  }
+];
+
+const filterCostTypeList = (name: string) => {
+  return ptlAgreementCostTypeList.filter(item => {
+    if (name === "单交" && item.costType === "商业险") {
+      return false; // 当 name 为 "单交" 且 costType 为 "商业险" 时,返回 false,不包含该项
+    }
+    if (name === "单三" && item.costType === "商业险") {
+      return false; // 当 name 为 "单交" 且 costType 为 "商业险" 时,返回 false,不包含该项
+    }
+    if (name === "单商" && item.costType === "交强险") {
+      return false; // 当 name 为 "商业" 且 costType 为 "交强险" 时,返回 false,不包含该项
+    }
+    return true; // 其他情况返回 true,保留该项
+  });
+};
+const hledclick = (pane: any) => {
+  insuranceTypeIndex.value = pane.index;
+  activeName.value = pane.props.name;
+}
+const draweropen = () => {
+  if(formRef.value){
+    formRef.value.resetFields();
+  }
+  contactPersonListid.value='';
+  ruleFeeinfo.ptlAgreementId = "";
+  ruleFeeinfo.contactPerson = "";
+  ruleFeeinfo.contactMobile = "";
+  ruleFeeinfo.ptlAgreementCostExternalAddList.map((val: any) => {
+    val.effectiveTime = "";
+    val.failureTime = "";
+    val.costDescribe = "";
+    val.costExternalTypeAdds.map((item: any) => {
+      for (let key in item) {
+        if(item[key]!='costType'){
+          item[key] = 0;
+        }
+      }
+      return item
+    })
+    return val;
+  })
+}
+</script>
+<!-- 样式区域 -->
+<style lang='scss' scoped>
+:deep(.el-drawer__header) {
+  padding-top: 0;
+  margin-bottom: 0;
+  border-bottom: 1px solid #f2f2f2;
+}
+
+.agreement {
+  width: 100%;
+  padding: 15px;
+  font-size: 14px;
+  color: #333;
+  border-radius: 4px;
+
+  p {
+    margin: 0;
+    margin-top: 10px;
+  }
+}
+
+.feeItem {
+  width: 100%;
+  border: 1px solid #e5e5e5;
+  border-radius: 8px;
+
+  .header {
+    padding: 12px 14px;
+
+    .title {
+      display: flex;
+
+      &::before {
+        display: inline-block;
+        width: 4px;
+        height: 18px;
+        margin-right: 20px;
+        content: "";
+        background-color: #0083ff;
+      }
+    }
+  }
+}
+
+:deep(.ruleFeetabs>.el-tabs__content) {
+  position: static;
+}
+</style>

+ 317 - 0
src/views/externalAgreement/agreement/components/contactPersonFeeDialogEdit.vue

@@ -0,0 +1,317 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <el-drawer v-model="props.ruleFeeVisible" :direction="direction" :close-on-click-modal="false" size="50%"
+      @close="close">
+      <template #header>
+        <h4>{{ props.headerTitle }}费用编辑</h4>
+      </template>
+      <template #default>
+        <div class="rejectionReason flex a-start" v-if="costitemEditInfo.reviewDescription">
+          <el-icon color="red" class="m-right-20"><CircleClose /></el-icon>
+          <div class="flex f-c">
+            <span class="strong" style="color: #333;">审核不通过</span>
+            <span >审核原因:{{costitemEditInfo. reviewDescription }}</span>
+            <div class="flex a-c ">
+              <span class="m-right-20">审核人:{{ costitemEditInfo.auditPerson }}</span>
+              <span >审核时间:{{costitemEditInfo.auditTime }}</span>
+            </div>
+          </div>
+        </div>
+        <ElRow :gutter="20" class="m-top-20">
+          <el-form :model="contactPersonEditInfo" label-width="120px" ref="contactPersonFormRef" :disabled="disabledShow" style="width: 100%;"
+            :rules="feerules" class="flex a-c f-wrap" :size="fromSize">
+            <ElCol :md="24" :lg="12">
+              <el-form-item label="对接人姓名" prop="contactPerson">
+                <el-input v-model="contactPersonEditInfo.contactPerson"  placeholder="请输入对接人姓名" />
+              </el-form-item>
+            </ElCol>
+            <ElCol :md="24" :lg="12">
+              <el-form-item label="对接人手机号" prop="contactMobile">
+                <el-input v-model="contactPersonEditInfo.contactMobile" placeholder="请输入手机号" />
+              </el-form-item>
+            </ElCol>
+            <ElCol :md="24" :lg="12">
+              <el-form-item label="生效时间" prop="effectiveTime">
+                <el-date-picker v-model="contactPersonEditInfo.effectiveTime" style="width: 100%;" type="datetime"
+                  placeholder="选择生效时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
+                  time-format="HH:mm:ss" @change="handleStartDateChange($event)" />
+              </el-form-item>
+            </ElCol>
+            <ElCol :md="24" :lg="12">
+              <el-form-item label="失效时间" prop="failureTime">
+                <el-date-picker v-model="contactPersonEditInfo.failureTime" style="width: 100%;" type="datetime"
+                  placeholder="选择失效时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
+                  time-format="HH:mm:ss" :disabled-date="(date: any) => disabledEndDate(date)" />
+              </el-form-item>
+            </ElCol>
+            <ElCol :md="24" :lg="12">
+              <el-form-item label="审核方式" prop="auditMethod">
+                <el-select v-model="contactPersonEditInfo.auditMethod" placeholder="选择审核方式">
+                  <el-option label="自动审核" value="0" />
+                  <el-option label="手动审核" value="1" />
+                </el-select>
+              </el-form-item>
+            </ElCol>
+            <ElCol :md="24" :lg="12">
+              <el-form-item label="是否现询" prop="isInquiry">
+                <el-radio-group v-model="contactPersonEditInfo.isInquiry">
+                  <el-radio value="0" size="large">是</el-radio>
+                  <el-radio value="1" size="large">否</el-radio>
+                </el-radio-group>
+              </el-form-item>
+            </ElCol>
+            <ElCol :md="24" :lg="24">
+              <el-form-item label="费用描述" prop="costDescribe">
+                <el-input v-model="contactPersonEditInfo.costDescribe" maxlength="500" placeholder="请输入费用描述"
+                  show-word-limit type="textarea" />
+              </el-form-item>
+            </ElCol>
+          </el-form>
+        </ElRow>
+
+        <el-table :data="contactPersonEditInfo.costExternalTypeAdds" style="width: 100%;" border
+          :header-cell-style="{ 'background-color': '#F7F7F9', 'border-bottom': '1px solid #EEF1F6', 'font-size': '15px', 'color': '#333', 'padding': '10px 0' }">
+          <template #empty>
+            <div class="flex f-c a-c ">
+              <img src="../../assets/images/empty.png" alt="">
+              <span style="font-size: 14px;color: #909399;">暂无数据</span>
+            </div>
+          </template>
+          <el-table-column prop="costType" label="类型"></el-table-column>
+          <el-table-column prop="inlet" label="入口比例" width="180" align="center">
+            <template #default="scope">
+              <el-input-number v-model="scope.row.inlet" :disabled="disabledShow" :precision="2" controls-position="right" style="width: 100%;"
+                :min="0" :max="100">
+                <template #suffix>%</template>
+              </el-input-number>
+            </template>
+          </el-table-column>
+          <el-table-column prop="export" label="出口比例" width="180" align="center">
+            <template #default="scope">
+              <el-input-number v-model="scope.row.export" :disabled="disabledShow" :precision="2" controls-position="right" style="width: 100%;"
+                :min="0" :max="100">
+                <template #suffix>%</template>
+              </el-input-number>
+            </template>
+          </el-table-column>
+          <el-table-column prop="totalCost" label="总费用比例" width="180" align="center">
+            <template #default="scope">
+              <el-input-number v-model="scope.row.totalCost" :disabled="disabledShow" :precision="2" controls-position="right"
+                style="width: 100%;" :min="0" :max="100">
+                <template #suffix>%</template>
+              </el-input-number>
+            </template>
+          </el-table-column>
+          <el-table-column prop="other" label="其他费用比例" width="180" align="center">
+            <template #default="scope">
+              <el-input-number v-model="scope.row.other" :disabled="disabledShow" :precision="2" controls-position="right" style="width: 100%;"
+                :min="0" :max="100">
+                <template #suffix>%</template>
+              </el-input-number>
+            </template>
+          </el-table-column>
+          <el-table-column prop="level1" label="1级比例" width="180" align="center">
+            <template #default="scope">
+              <el-input-number v-model="scope.row.level1" :disabled="disabledShow" :precision="2" controls-position="right" style="width: 100%;"
+                :min="0" :max="100">
+                <template #suffix>%</template>
+              </el-input-number>
+            </template>
+          </el-table-column>
+          <el-table-column prop="level5" label="5级比例" width="180" align="center">
+            <template #default="scope">
+              <el-input-number v-model="scope.row.level5" :disabled="disabledShow" :precision="2" controls-position="right" style="width: 100%;"
+                :min="0" :max="100">
+                <template #suffix>%</template>
+              </el-input-number>
+            </template>
+          </el-table-column>
+        </el-table>
+      </template>
+      <template #footer>
+        <div class="flex a-c j-c" v-if="!disabledShow">
+          <el-button @click="cancel">取消</el-button>
+          <el-button type="primary" @click="save(contactPersonFormRef)">提交审核</el-button>
+        </div>
+        <div class="flex f-c " v-if="!costitemEditInfo?.reviewDescription">
+          <el-checkbox v-model="automatic" label="审核后,自动跳支审核下一条费用" size="large" />
+          <div class="flex a-c">
+            <el-button @click="rejectOperation">审核驳回</el-button>
+            <el-button type="primary" @click="auditOperation">审核通过</el-button>
+          </div>
+        </div>
+      </template>
+    </el-drawer>
+  </div>
+</template>
+
+<!-- 行为区域 -->
+<script lang='ts' setup>
+import { ref, } from 'vue'
+import { DrawerProps, ComponentSize, FormInstance, FormRules, ElMessage,ElMessageBox } from 'element-plus';
+import { defineProps, defineEmits } from "vue";
+const direction = ref<DrawerProps['direction']>('rtl')
+const fromSize = ref<ComponentSize>('default')
+const props = defineProps<{
+  ruleFeeVisible: boolean,
+  headerTitle?: string,
+  costitemEditInfo?: any,
+  operationType?: string,//操作类型
+
+}>();
+const emits = defineEmits(["cancel", 'save', 'auditOperation', 'rejectOperation']);
+watch(props, () => {
+  contactPersonEditInfo.value = props.costitemEditInfo;
+  //审核和驳回操作不可编辑
+  if (props.operationType == 'audit' || props.operationType == 'rejected') {
+    disabledShow.value = true;
+  } else {
+    disabledShow.value = false;
+
+  }
+})
+const disabledShow = ref(false);
+const automatic = ref(false);//是否连续审核
+const contactPersonEditInfo = ref();//规则费用集合
+const contactPersonFormRef = ref<FormInstance>()
+const feerules = ref<FormRules>({
+  contactPerson: [
+    { required: true, trigger: 'blur', message: '请输入对接人姓名' },
+  ],
+  contactMobile: [
+    { required: true, trigger: 'blur', message: '请输入手机号' },
+  ],
+  effectiveTime: [
+    { required: true, trigger: 'change', message: '请选择生效时间' },
+  ],
+  failureTime: [
+    { required: true, trigger: 'change', message: '请选择失效时间' },
+  ],
+  auditMethod: [
+    { required: true, trigger: 'change', message: '请选择审核方式' },
+  ],
+  isInquiry: [
+    { required: true, trigger: 'change', message: '请选择是否线询' },
+  ],
+})
+// 禁用失效时间选择
+const disabledEndDate = (date: any) => {
+  let start = contactPersonEditInfo.value.effectiveTime;
+  const startDate = new Date(start);
+  return startDate && date < startDate; // 禁用小于生效时间的日期
+};
+
+// 处理生效时间变化
+/**
+ * @param index  险种下标
+ * @param Costindex 费用下标
+ */
+const handleStartDateChange = (value: any) => {
+  let endDate = contactPersonEditInfo.value.failureTime;
+  // 如果生效时间改变,检查失效时间是否需要更新
+  if (endDate && new Date(endDate) < new Date(value)) {
+    contactPersonEditInfo.value.failureTime = ''; // 清空失效时间
+  }
+};
+const cancel = () => {
+  emits("cancel")
+}
+const close = () => {
+  emits("cancel")
+}
+const save = async (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  await formEl.validate((valid) => {
+    if (valid) {
+      emits('save', contactPersonEditInfo.value)
+    } else {
+      ElMessage({
+        type: 'error',
+        message: '信息不完整',
+        plain: true,
+      })
+    }
+  })
+}
+//审核驳回操作
+const rejectOperation = () => {
+  //驳回弹窗
+  ElMessageBox.prompt('', '审核不通过', {
+    confirmButtonText: '确定',
+    type: 'warning',
+    center: true,
+    inputType: "textarea",
+    inputPlaceholder: "请输入不通过原因",
+    inputPattern: /^[\u4e00-\u9fa5,。!?、;:“”‘’《》【】()——]+$/,
+    inputErrorMessage: '请输入中文',
+  })
+    .then(({ value }) => {
+      emits("rejectOperation",automatic.value,contactPersonEditInfo.value.id,value)
+    })
+}
+//审核通过操作
+const auditOperation = () => {
+  emits("auditOperation", automatic.value,contactPersonEditInfo.value.id)
+}
+</script>
+<!-- 样式区域 -->
+<style lang='scss' scoped>
+:deep(.el-drawer__header) {
+  padding-top: 0;
+  margin-bottom: 0;
+  border-bottom: 1px solid #f2f2f2;
+}
+
+.agreement {
+  width: 100%;
+  padding: 15px;
+  font-size: 14px;
+  color: #333;
+  border-radius: 4px;
+
+  p {
+    margin: 0;
+    margin-top: 10px;
+  }
+}
+
+.feeItem {
+  width: 100%;
+  border: 1px solid #e5e5e5;
+  border-radius: 8px;
+
+  .header {
+    padding: 12px 14px;
+
+    .title {
+      display: flex;
+
+      &::before {
+        display: inline-block;
+        width: 4px;
+        height: 18px;
+        margin-right: 20px;
+        content: "";
+        background-color: #0083ff;
+      }
+    }
+  }
+}
+
+.rejectionReason {
+  padding: 15px;
+  background: #fff1f0;
+  border: 1px solid #ffa39e;
+  border-radius: 5px;
+
+  span {
+    margin-bottom: 10px;
+    color: #666;
+  }
+}
+
+:deep(.ruleFeetabs>.el-tabs__content) {
+  position: static;
+}
+</style>

+ 110 - 0
src/views/externalAgreement/agreement/components/managerModal.vue

@@ -0,0 +1,110 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <el-dialog v-model="props.managerModal" title="选择管理人" width="40%"  @close="cancel">
+      <div class="flex a-c">
+        <el-input v-model="searchValue" style="width: 240px;margin-right: 10px;" clearable placeholder="输入姓名,手机号,工号查找" />
+        <el-button type="primary" @click='searchValueclick'>查询</el-button>
+      </div>
+      <div class="deptlist flex ">
+        <div class="area" style="border-right: 1px solid #dcdfe6;" v-show="!searchValueShow">
+          <el-tree style="max-width: 600px;" :data="props.deptOptions" :props="parentIdprops" :highlight-current="true"
+            @node-click="handleNodeClick" />
+        </div>
+        <div class="area">
+          <el-radio-group v-model="userId">
+            <div class="flex f-c">
+              <el-radio :value="item.id" v-for="(item,index) in managerlist" @change="radioChange" :key="index" class="m-btn-20">
+                <div>{{ item.name }}</div>
+                <div>{{ item.id }}</div>
+              </el-radio>
+            </div>
+          </el-radio-group>
+        </div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="cancel">取消</el-button>
+          <el-button type="primary" @click="save">
+            确定
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<!-- 行为区域 -->
+<script lang='ts' setup>
+import api from '@/api';
+import { ref, reactive } from 'vue'
+import { defineProps, defineEmits } from "vue";
+
+interface Tree {
+  id: string
+  children?: Tree[]
+}
+interface managerlist{
+  id:string,
+  name:string,
+  deptName:string,
+}
+const props = defineProps<{
+  managerModal: boolean,
+  deptOptions: string[]
+  managerlist: managerlist[],
+}>();
+const parentIdprops = {
+  expandTrigger: 'hover' as const,
+  value: 'id',
+  label: 'name',
+  checkStrictly: true,
+}
+const emits = defineEmits(["cancel", 'save', 'DrawerOpen', 'query', 'NodeClick','radioChange']);
+const userId = ref()
+const username = ref()
+const deptName=ref();
+const searchValue=ref();
+const searchValueShow=ref(false);
+watch(searchValue,()=>{
+  if(!searchValue.value){
+  searchValueShow.value=false;
+  }
+})
+const handleNodeClick = (data: Tree) => {
+  emits('NodeClick', data.id)
+}
+const cancel = () => {
+  emits('cancel')
+}
+const save = () => {
+  emits('save',userId.value,username.value,deptName.value)
+}
+
+const searchValueclick=()=>{
+  searchValueShow.value=true;
+  emits('query',searchValue.value)
+}
+const radioChange=(value:any)=>{
+  let name=props.managerlist.find(val=>val.id==value)?.name;
+  let deptname=props.managerlist.find(val=>val.id==value)?.deptName;
+  username.value=name;
+  deptName.value=deptname;
+}
+</script>
+<!-- 样式区域 -->
+<style lang='scss' scoped>
+.deptlist {
+  height: 400px;
+  margin-top: 20px;
+  border: 1px solid #dcdfe6;
+  border-radius: 5px;
+
+  .area {
+    flex: 1;
+    height: 100%;
+    padding: 10px;
+    overflow: auto;
+  }
+}
+</style>

+ 522 - 0
src/views/externalAgreement/costManagement.vue

@@ -0,0 +1,522 @@
+<template>
+  <div>
+    <PageMain class="vh100">
+      <SearchForm :fields="fields" :formInline="formInline" :onSearch="agreementQuery" :onReset="resetForm" />
+      <ComplexTable :tableData="tableData" :columns="columns" :showIndex="false" :columnwidth="200" :showSelect="true"
+        :pageInfo="pageInfo" @getList="getList" @selectionChange="selectionChange">
+        <template v-slot:table-title-btn>
+          <el-button type="primary" @click="agreementSelection">
+            添加费用
+            <template #icon>
+              <el-icon>
+                <Plus />
+              </el-icon>
+            </template>
+          </el-button>
+          <el-dropdown placement="bottom-start">
+            <el-button> 批量管理<el-icon>
+                <arrow-down />
+              </el-icon>
+            </el-button>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item :disabled="!sysUpAndDownList.length" @click="batchDel">删除</el-dropdown-item>
+                <el-dropdown-item :disabled="!sysUpAndDownList.length" @click="batchdisable">禁用</el-dropdown-item>
+                <el-dropdown-item :disabled="!sysUpAndDownList.length" @click="batchenable">启用</el-dropdown-item>
+                <el-dropdown-item :disabled="!sysUpAndDownList.length" @click="batchUpdate">批量编辑</el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </template>
+        <template v-slot:operation="scope">
+          <el-button type="primary" link @click="agreemenUpdate(scope.operationData.id)"> 编辑
+          </el-button>
+          <el-dropdown placement="bottom-start" trigger="click">
+            <span class="el-dropdown-link">
+              更 多
+            </span>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item @click="enable(scope.operationData.id)">启用</el-dropdown-item>
+                <el-dropdown-item @click="disable(scope.operationData.id)">禁用</el-dropdown-item>
+                <el-dropdown-item @click="del(scope.operationData.id)">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </template>
+      </ComplexTable>
+    </PageMain>
+    <el-dialog v-model="auditMethodVisible" title="选择协议" width="30%">
+
+      <ElRow :gutter="20" class="m-top-20">
+        <el-form :model="Editinfo" ref="FormRef" style="width: 100%;" :rules="rules" class="flex a-c f-wrap"
+          :size="fromSize">
+          <ElCol :md="24" :lg="24">
+            <el-form-item label="审核方式" prop="auditMethod">
+              <el-select v-model="Editinfo.auditMethod" placeholder="选择审核方式">
+                <el-option label="自动审核" value="0" />
+                <el-option label="手动审核" value="1" />
+              </el-select>
+            </el-form-item>
+          </ElCol>
+        </el-form>
+      </ElRow>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="auditMethodVisible = false">取消</el-button>
+          <el-button type="primary" @click="agreementSelectionConfirm(FormRef)">
+            确定
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+    <!-- 费用添加组件 -->
+    <contactPersonFeeDialog v-show="addcontactPersonShow" :ruleFeeVisible="addcontactPersonShow"
+      :title='contactPersontitle'   @save="contactPersonSave"
+       addMethod="list" @cancel="addcontactPersonShow = false"  />
+      <!-- 费用编辑组件 -->
+      <contactPersonFeeDialogEdit :ruleFeeVisible="ruleFeeEditVisible" :headerTitle="ruleFeeHeaderTitle"
+       :costitemEditInfo="costitemEditInfo" @save="ruleFeeEditSave"
+      @cancel="ruleFeeEditVisible = false" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive } from 'vue'
+import { useRoute, useRouter } from 'vue-router';
+import { ElMessage, ElMessageBox, FormRules, FormInstance, ComponentSize } from 'element-plus'
+import { loadDicts, getDict } from '@/utils/composables/dictService';//字典组件
+import contactPersonFeeDialogEdit from "./agreement/components/contactPersonFeeDialogEdit.vue";
+import contactPersonFeeDialog from "./agreement/components/contactPersonFeeDialog.vue";
+
+const FormRef = ref<FormInstance>()
+const fromSize = ref<ComponentSize>('large')//表单大小
+import api from '@/api';
+interface PageInfo {
+  pageNum: number;
+  pageSize: number;
+  sizes: number[];
+  total: number;
+}
+onBeforeMount(async () => {
+  await loadDicts(['valid_status', 'is_enable', 'audit_status', 'insurance_type']);//获取多个字典
+  if (id.value) {
+    formInline.agreement = id.value;
+  }
+  findpage();//列表数据
+  valid_status.value = getDict('valid_status');//生效状态
+  is_enable.value = getDict('is_enable');//协议状态
+  insurance_type.value = getDict('insurance_type')
+  audit_status.value = getDict('audit_status')
+
+});
+const tableData = ref([]);
+const sysUpAndDownList = ref<string[]>([])//选中id
+const insurance_type = ref();
+const valid_status = ref();
+const audit_status = ref();
+const is_enable = ref();
+const route = useRoute();
+const router = useRouter();
+const id = ref(route.query.id as string);//id参数
+const pageInfo = ref({
+  pageNum: 1,
+  pageSize: 20,
+  sizes: [10, 20, 30, 40, 50],
+  total: 100,
+});
+const formInline = reactive({
+  agreement: '',//协议信息
+  productId: "",//归属
+  rulesName: '',//保险公司id
+  costRules: '',//协议类型
+  agreementStatus: "",//有效状态
+  auditStatus: "",//协议状态
+  validState: "",
+  effectiveTime: [],
+  failureTime: [],
+})
+const rules = ref<FormRules>({
+  auditMethod: [
+    { required: true, trigger: 'blur', message: '请选择审核方式' },
+  ],
+
+})
+//表格字段数据
+const fields = ref([
+  { label: "所属协议", model: "agreement", type: "input", placeholder: "输入代码,名称,简称查找" },
+  {
+    label: "险种", model: "productId", type: "select", options: insurance_type,
+  },
+  {
+    label: "承保规则", model: "rulesName", type: "input", placeholder: "输入业务规则查找",
+  },
+  {
+    label: "费用规则", model: "costRules", type: "input", placeholder: "输入费用规则关键字查找",
+  },
+
+  {
+    label: "费用状态", model: "agreementStatus", type: "select", placeholder: "选择状态", options: is_enable,
+  },
+  {
+    label: "审核状态", model: "auditStatus", type: "select", placeholder: "选择状态", options: audit_status,
+  },
+  {
+    label: "有效状态", model: "validState", type: "select", placeholder: "请选择状态", options: valid_status,
+  },
+  {
+    label: "生效时间", model: "effectiveTime", type: "date", dateType: "datetimerange", startplaceholder: "开始时间", endplaceholder: "结束时间",
+    onChange: (value: any) => {
+      console.log(value)
+      formInline.effectiveTime = value;
+    }
+  },
+  {
+    label: "失效时间", model: "failureTime", type: "date", dateType: "datetimerange", startplaceholder: "开始时间", endplaceholder: "结束时间",
+    onChange: (value: any) => {
+      formInline.failureTime = value;
+    }
+  },
+]);
+
+//费用数据查询
+function findpage() {
+  api.agreementApi.CostExternalFeePageList({ pages: pageInfo.value.pageNum, size: pageInfo.value.pageSize, ...formInline }).then((res: any) => {
+    if (res.code == '200') {
+      tableData.value = res.data.records;
+      pageInfo.value.pageNum = res.data.current;
+      pageInfo.value.pageSize = res.data.size;
+      pageInfo.value.total = res.data.total;
+    } else {
+    }
+  });
+}
+
+const columns = [
+  {
+    prop: "agreementAbbreviation", label: "协议信息", width: '300',
+    formatter: (row: any) => {
+      return `${row.agreementCode}\n${row.agreementAbbreviation}`
+    }
+  },
+  { prop: "productName", label: "险种", width: '120', },
+  {
+    prop: "contactPerson", label: "对接人", width: '200', formatter: (row: any) => {
+      return `${row.contactPerson} ${row.contactMobile}`
+    }
+  },
+  {
+    prop: "isInquiry", label: "是否现询", width: '120', formatter: (row: any) => {
+      if (row.isInquiry == '0') {
+        return '是'
+      } else {
+        return "否"
+      }
+    }
+  },
+  {
+    prop: "auditMethod", label: "审核方式", width: '120', formatter: (row: any) => {
+      if (row.auditMethod == '0') {
+        return '自动审核'
+      } else {
+        return "手动审核"
+      }
+    }
+  },
+  {
+    prop: "isEnabled", label: "费用状态", width: '100', component: "Tag",
+    formatter: (row: any) => {
+      let name = getDict('is_enable').find(val => val.value == row.isEnabled).label
+      if (name) {
+        return { type: 'primary', text: name };
+      }
+    }
+  },
+  {
+    prop: "auditStatus", label: "审核状态", width: '120', component: "Tag",
+    formatter: (row: any) => {
+      let name = getDict('audit_status').find(val => val.value == row.auditStatus).label
+      if (name) {
+        return { type: 'warning', text: name };
+      }
+    }
+  },
+  { prop: "effectiveTime", label: "生效时间", width: '200' },
+  { prop: "failureTime", label: "失效时间", width: '200' },
+  {
+    prop: "costExternalTypeAdds", label: "交强险费用", width: '300', formatter: (row: any) => {
+      let info = row.costExternalTypeAdds.find((val: any) => val.costType == '交强险');
+      if (info) {
+        return `入口:${info.inlet.toFixed(2)}% 出口:${info.export.toFixed(2)}%\n一级:${info.level1?.toFixed(2)}% 五级:${info.level5?.toFixed(2)}%`
+      } else {
+        return '-'
+      }
+    }
+  },
+  {
+    prop: "costExternalTypeAdds", label: "商业险费用", width: '300', formatter: (row: any) => {
+      let info = row.costExternalTypeAdds.find((val: any) => val.costType == '商业险');
+      if (info) {
+        return `入口:${info.inlet.toFixed(2)}% 出口:${info.export.toFixed(2)}%\n一级:${info.level1?.toFixed(2)}% 五级:${info.level5?.toFixed(2)}%`
+      } else {
+        return '-'
+      }
+    }
+  },
+  {
+    prop: "costExternalTypeAdds", label: "驾意险费用", width: '300', formatter: (row: any) => {
+      let info = row.costExternalTypeAdds.find((val: any) => val.costType == '驾意险');
+      if (info) {
+        return `入口:${info.inlet.toFixed(2)}% 出口:${info.export.toFixed(2)}%\n一级:${info.level1?.toFixed(2)}% 五级:${info.level5?.toFixed(2)}%`
+      } else {
+        return '-'
+      }
+    }
+  },
+];
+//分页事件
+function getList(item: PageInfo) {
+  pageInfo.value.pageNum = item.pageNum;
+  pageInfo.value.pageSize = item.pageSize;
+  findpage();
+
+};
+// 费用添加
+
+const addcontactPersonShow = ref(false);//添加对接人
+const contactPersontitle = ref();//弹窗title
+const agreementSelection = () => {//添加对接人
+  addcontactPersonShow.value = true;
+  contactPersontitle.value = '添加';
+}
+
+//费用提交
+const contactPersonSave = (data: object) => {
+  console.log(data);
+    api.agreementApi.ptlAgreementCostExternalsave(data).then((res: any) => {
+      if (res.code == '200') {
+        addcontactPersonShow.value = false;
+        findpage();
+      } else {
+      }
+    });
+}
+// 费用编辑
+const ruleFeeEditVisible=ref(false);
+const ruleFeeHeaderTitle = ref();//编辑弹窗title
+const costitemEditInfo = ref();
+const agreemenUpdate = async (id: any) => {
+  api.agreementApi.FeegetById(id).then((res: any) => {
+    if (res.code == '200') {
+      ruleFeeHeaderTitle.value = res.data.productName;
+      costitemEditInfo.value = res.data;
+      ruleFeeEditVisible.value = true;
+    } else {
+    }
+  });
+}
+//费用编辑
+const ruleFeeEditSave = (data: object) => {
+  api.agreementApi.FeeupdateById(data).then((res: any) => {
+    if (res.code == '200') {
+      ElMessage({
+        type: 'success',
+        message: res.msg,
+        plain: true,
+      })
+      ruleFeeEditVisible.value=false;
+      findpage();
+    } else {
+    }
+  });
+}
+//多选事件
+const selectionChange = (val: any) => {
+  sysUpAndDownList.value = val;
+}
+//禁用
+const disable = (value: string) => {
+  ElMessageBox.confirm(
+    '确定要禁用该协议吗,禁用后,该协议不在支持报价出单,并且已核保待缴费,待支付的订单,都将失效,请确认是否禁用?',
+    '禁用协议',
+    {
+      confirmButtonText: '确定禁用',
+      type: 'error',
+      center: true,
+      confirmButtonClass: 'el-button--danger',
+    }
+  )
+    .then(() => {
+      sysUpAndDown(false, [value]);
+    })
+}
+//启用
+const enable = (value: string) => {
+  sysUpAndDown(true, [value]);
+}
+const del = (value: string) => {
+  ElMessageBox.confirm(
+    '确定要删除该费用吗,删除后,该费用不在支持新的补录订单?',
+    '删除费用',
+    {
+      confirmButtonText: '确定删除',
+      type: 'error',
+      center: true,
+      confirmButtonClass: 'el-button--danger',
+    }
+  )
+    .then(() => {
+      api.agreementApi.deleteById([value]).then((res: any) => {
+        if (res.code == '200') {
+          ElMessage({
+            type: 'success',
+            message: res.msg,
+            plain: true,
+          })
+          router.push({
+            name: 'reload',
+          })
+        }
+      });
+    })
+}
+//批量删除
+const batchDel = () => {
+  ElMessageBox.confirm(
+    '确定要删除该费用吗,删除后,该费用不在支持新的补录订单?',
+    '删除费用',
+    {
+      confirmButtonText: '确定删除',
+      type: 'error',
+      center: true,
+      confirmButtonClass: 'el-button--danger',
+    }
+  )
+    .then(() => {
+      api.agreementApi.deleteById(sysUpAndDownList.value).then((res: any) => {
+        if (res.code == '200') {
+          ElMessage({
+            type: 'success',
+            message: res.msg,
+            plain: true,
+          })
+          router.push({
+            name: 'reload',
+          })
+        }
+      });
+    })
+
+}
+//批量禁用
+const batchdisable = () => {
+  ElMessageBox.confirm(
+    '确定要禁用该费用吗,禁用后,该费用不在支持新的补录订单,请确认是否禁用?',
+    '禁用费用',
+    {
+      confirmButtonText: '确定禁用',
+      type: 'error',
+      center: true,
+      confirmButtonClass: 'el-button--danger',
+    }
+  )
+    .then(async () => {
+      await sysUpAndDown(false, undefined);
+    })
+    .catch(() => {
+    })
+
+
+}
+//批量启用
+const batchenable = async () => {
+  await sysUpAndDown(true, undefined);
+}
+const agreementQuery = () => {
+  findpage();
+}
+const sysUpAndDown = async (type: boolean, list?: string[]) => {
+  await api.agreementApi.enableDisableByIds(type, list ? list : sysUpAndDownList.value).then((res: any) => {
+    if (res.code == '200') {
+      findpage();
+      ElMessage({
+        type: 'success',
+        message: res.msg,
+        plain: true,
+      })
+    }
+  });
+}
+// 批量编辑
+const batchUpdate = () => {
+  auditMethodVisible.value = true;
+
+}
+const resetForm = () => {
+  formInline.agreement = '';//协议信息
+  formInline.productId = "";//归属
+  formInline.rulesName = '';//保险公司id
+  formInline.costRules = '';//协议类型
+  formInline.agreementStatus = "";//有效状态
+  formInline.auditStatus = "";//协议状态
+  formInline.validState = "";
+  formInline.effectiveTime = [];
+  formInline.failureTime = [];
+  findpage();
+}
+//批量修改弹窗
+const auditMethodVisible = ref(false);
+
+const agreementSelectionConfirm = async (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  await formEl.validate((valid) => {
+    if (valid) {
+      api.agreementApi.approvalMethodAll(Editinfo.auditMethod, sysUpAndDownList.value).then((res: any) => {
+        if (res.code == '200') {
+          ElMessage({
+            type: 'success',
+            message: res.msg,
+            plain: true,
+          })
+          auditMethodVisible.value=false;
+          findpage();
+        } else {
+        }
+      });
+    } else {
+      ElMessage({
+        type: 'error',
+        message: '信息不完整',
+        plain: true,
+      })
+    }
+  })
+}
+// 批量编辑
+const Editinfo = reactive({
+  auditMethod: "",
+});
+</script>
+
+<style lang="scss" scoped>
+.demo-form-inline .el-input {
+  --el-input-width: 220px;
+}
+
+.demo-form-inline .el-select {
+  --el-select-width: 220px;
+}
+
+:deep(.el-dropdown-link) {
+  display: flex;
+  align-items: center;
+  margin-left: 15px;
+  color: var(--el-color-primary);
+  cursor: pointer;
+}
+
+.el-inputs {
+  width: 100% !important;
+}
+</style>

+ 36 - 4
src/views/marketingManagement/configurate.vue

@@ -41,6 +41,7 @@
                         placeholder="请选择"
                         format="YYYY-MM-DD"
                         value-format="YYYY-MM-DD"
+                        :disabled-date="disabledDateStart"
                         style="width: 100%"
                       />
                     </el-form-item>
@@ -53,7 +54,7 @@
                         placeholder="请选择"
                         format="YYYY-MM-DD"
                         value-format="YYYY-MM-DD"
-                        :disabled-date="disabledDate"
+                        :disabled-date="disabledDateEnd"
                         style="width: 100%"
                       />
                     </el-form-item>
@@ -244,8 +245,11 @@ const basicRules = ref<FormRules>({
   ]
 })
 
-const disabledDate = (time: Date) => {
-  return time.getTime() < Date.now()
+const disabledDateStart = (time: Date) => {
+  return time.getTime() < Date.now() - 8.64e7
+}
+const disabledDateEnd = (time: Date) => {
+  return time.getTime() < new Date(basicForm.value.actStartTime) - 8.64e7
 }
 
 let deptData = ref([])
@@ -308,7 +312,35 @@ const confirmFirst = async (BasicFormRef: FormInstance) => {
   await BasicFormRef.validate((valid) => {
     if(valid) {
       if(basicForm.value.deptIds.length>0) {
-        active.value = 1
+        api.lotteryApi.checkActivityDept({
+          deptIds: basicForm.value.deptIds
+        }).then(res => {
+          if(res.code == 200) {
+            if(res.data == '1') {
+              ElMessageBox({
+                title: '提示',
+                message: '选择的参与机构存在有效的抽奖活动,发布后新的抽奖活动将覆盖机构的抽奖活动,请确认是否继续发布',
+                type: 'warning',
+                showCancelButton: true,
+                confirmButtonText: '继续发布',
+                cancelButtonText: '取消',
+              }).then((  action ) => {
+                if(action === 'confirm') {
+                  active.value = 1
+                } else {
+                  active.value = 0
+                }
+              })
+            } else {
+              active.value = 1
+            }
+          } else {
+            ElMessage({
+              message: res.msg,
+              type: 'warning'
+            })
+          }
+        })
       } else {
         ElMessage({
           message: '请勾选参与机构!',

+ 8 - 2
src/views/marketingManagement/lottery.vue

@@ -37,8 +37,12 @@
         </template>
       </el-table-column>
       <el-table-column prop="actDept" label="所属机构" width="300"></el-table-column>
-      <el-table-column prop="f" label="参与范围" width="300"></el-table-column>
-      <el-table-column prop="g" label="参与用户" width="150"></el-table-column>
+      <el-table-column prop="depts" label="参与范围" width="300"></el-table-column>
+      <el-table-column prop="userCount" label="参与用户" width="150">
+        <template #default="scope">
+          {{ scope.row.userCount }}个
+        </template>
+      </el-table-column>
       <el-table-column prop="actStatus" label="状态" width="150">
         <template #default="scope">
           {{ scope.row.actStatus == '1'?'未开始':scope.row.actStatus == '2'?'进行中':scope.row.actStatus == '3'?'已结束':'' }}
@@ -165,9 +169,11 @@ let pageSize = ref(10)
 let total = ref(0)
 const handleSizeChange = (pageSize) => {
   pageSize.value = pageSize
+  initData()
 }
 const handleCurrentChange = (page) => {
   currentPage.value = page
+  initData()
 }
 
 const createActive = () => {

+ 3 - 1
src/views/operationManagement/channel.vue

@@ -205,9 +205,11 @@ let pageSize = ref(10)
 let total = ref(0)
 const handleSizeChange = (size) => {
   pageSize.value = size
+  initTeam()
 }
 const handleCurrentChange = (page) => {
   currentPage.value = page
+  initTeam()
 }
 // 初始化列表
 let teamInfo = ref({
@@ -241,7 +243,7 @@ const treeNodeCommend = (item, e) => {
     case '编辑':
       addTeamShow.value = true
       nextTick(() => {
-        AddTeamRef.value.initEdit(item, true)
+        AddTeamRef.value.initEdit(item, true, 2)
       })
       break;
     case '删除':

+ 44 - 40
src/views/operationManagement/modules/addOperation.vue

@@ -29,7 +29,7 @@
                 </el-form-item>
               </el-col>
               <el-col :span="10">
-                <el-form-item prop="deptId" label="所属团队" v-if="operationForm.typeAttr!='6'">
+                <el-form-item prop="organizationId" label="所属团队" v-if="operationForm.typeAttr!='6'">
                   <el-select v-model="operationForm.organizationId" placeholder="请选择所属团队" @change="deptChange">
                     <el-option v-for="item in teamList" :key="item.id" :value="item.id" :label="item.name"></el-option>
                   </el-select>
@@ -463,10 +463,12 @@ import { ElMessage } from "element-plus";
 
 interface OperationFormProps extends AddOperation {
   organization: string
+  organizationId: string,
   leaderNumber: string,
   referrerNumber: string,
   address?: string[],
-  id?: string
+  id?: string,
+  // [key: string]: any;
 }
 
 let emits = defineEmits(['closeDialog'])
@@ -476,48 +478,47 @@ const OperationFormRef = ref<FormInstance>()
 let operationForm = ref<OperationFormProps>({
   typeAttr: '',
   deptId: '',
-  organizationId: '',
   name: '',
   mobile: '',
   identity: '',
   identityTimeStart: '',
   identityTimeEnd: '',
   leaderId: '',
-  leaderNumber: '',
   referrerId: '',
+  sex: '',
+  birthday: '',
+  nations: '',
+  koseki: '',
+  maritalStatus: '',
+  politicsStatus: '',
+  health: '',
+  province: '',
+  city: '',
+  district: '',
+  liveAddress: '',
+  postalCode: '',
+  education: '',
+  graduationTime: '',
+  school: '',
+  isComputer: '',
+  emergencyContactName: '',
+  emergencyContactTel: '',
+  salary: '',
+  salaryLevel: '',
+  bankCardId: '',
+  bankName: '',
+  sfz1: '',
+  sfz2: '',
+  qualification: '',
+  contract: '',
+  applicant: '',
+  salaryTable: '',
+  leaveTable: '',
+  organizationId: '',
+  organization: '',
+  leaderNumber: '',
   referrerNumber: '',
-
-  sex: null,
-  birthday: null,
-  nations: null,
-  koseki: null,
-  maritalStatus: null,
-  politicsStatus: null,
-  health: null,
-  province: null,
-  city: null,
-  district: null,
   address: [],
-  liveAddress: null,
-  postalCode: null,
-  education: null,
-  graduationTime: null,
-  school: null,
-  isComputer: null,
-  emergencyContactName: null,
-  emergencyContactTel: null,
-  salary: null,
-  salaryLevel: null,
-  bankCardId: null,
-  bankName: null,
-
-  sfz1: null,
-  sfz2: null,
-  qualification: null,
-  contract: null,
-  applicant: null,
-  salaryTable: null,
-  leaveTable: null
 })
 // 机构数据渲染格式
 const cascaderProps = {
@@ -530,7 +531,7 @@ const operationRules = ref<FormRules>({
   typeAttr: [
     { required: true, trigger: "change", message: "请选择业务员类型" }
   ],
-  deptId: [
+  organizationId: [
     { required: true, trigger: "change", message: "请选择所属团队" }
   ],
   name: [
@@ -637,10 +638,10 @@ const disabledDateEnd = (time: Date) => {
 // 编辑时初始化数据
 // 编辑的id
 let editId = ref('')
-const initEditData = (id) => {
-  if(id) {
+const initEditData = (id, ids) => {
+  if(ids) {
     editId.value = id
-    api.operationApi.operationDetail(id).then(res => {
+    api.operationApi.operationDetail(ids).then(res => {
       if(res?.code == 200) {
         let formData = res.data
         initTeamData(formData?.typeAttr==='5'?'1':formData?.typeAttr==='8'?'2':formData?.typeAttr==='7'?'3':'')
@@ -1094,6 +1095,7 @@ const typeAttrChange = async (e) => {
   operationForm.value.leaderNumber = ''
   operationForm.value.referrerId = ''
   operationForm.value.referrerNumber = ''
+  operationForm.value.organizationId = ''
   if(e != '6') {
     initTeamData(e==='5'?'1':e==='8'?'2':e==='7'?'3':'')
   } else {
@@ -1112,6 +1114,7 @@ const deptChange = (e) => {
 // 机构切换
 const deptIdChange = (e) => {
   if(e) {
+    operationForm.value.deptId = e[e.length - 1]
     getUser(e[e.length - 1])
     getRecommend(e[e.length - 1])
   }
@@ -1160,7 +1163,8 @@ onMounted(() => {
 })
 
 defineExpose({
-  initEditData
+  initEditData,
+  operationForm
 })
 
 

+ 2 - 1
src/views/operationManagement/modules/addTeam.vue

@@ -72,9 +72,10 @@ let teamRules = ref<FormRules>({
 
 // 编辑初始化数据
 let isEdit = ref(false)
-const initEdit = (data, bool) => {
+const initEdit = (data, bool, type) => {
   isEdit.value = bool
   teamForm.value = data
+  teamForm.value.attribute = type
   institutionList()
   getUser(data.deptId)
 }

+ 1 - 1
src/views/operationManagement/operation.vue

@@ -542,7 +542,7 @@ const tableDataCommand = (scope, e) => {
     case '编辑':
       dialogShow.value = true
       nextTick(() => {
-        AddOperationRef.value.initEditData(scope.row.userId)
+        AddOperationRef.value.initEditData(scope.row.userId, scope.row.id)
       })
       title.value = '编辑'
       break;

+ 2 - 2
src/views/operationManagement/operationDetail.vue

@@ -269,7 +269,7 @@ const closeAllocation = (type: boolean) => {
 let activeName = ref('msg')
 
 // 银行卡信息
-let bankCardInfo = ref(null)
+let bankCardInfo = ref({})
 let bankTitle = ref('添加银行卡')
 // 添加银行卡弹框
 let bankCardDialogShow = ref(false)
@@ -437,7 +437,7 @@ const imageView = (type) => {
 // 详情
 let dataDetail = ref(null)
 const initDetail = () => {
-  let id = myRoute.query?.id
+  let id = myRoute.query?.ids
   api.operationApi.operationDetail(id).then(res => {
     if(res?.code == 200) {
       dataDetail.value = res.data

+ 3 - 1
src/views/operationManagement/team.vue

@@ -205,9 +205,11 @@ let pageSize = ref(10)
 let total = ref(0)
 const handleSizeChange = (size) => {
   pageSize.value = size
+  initTeam()
 }
 const handleCurrentChange = (page) => {
   currentPage.value = page
+  initTeam()
 }
 // 初始化列表
 let teamInfo = ref({
@@ -241,7 +243,7 @@ const treeNodeCommend = (item, e) => {
     case '编辑':
       addTeamShow.value = true
       nextTick(() => {
-        AddTeamRef.value.initEdit(item, true)
+        AddTeamRef.value.initEdit(item, true, 1)
       })
       break;
     case '删除':

+ 3 - 1
src/views/operationManagement/telemarketing.vue

@@ -205,9 +205,11 @@ let pageSize = ref(10)
 let total = ref(0)
 const handleSizeChange = (size) => {
   pageSize.value = size
+  initTeam()
 }
 const handleCurrentChange = (page) => {
   currentPage.value = page
+  initTeam()
 }
 // 初始化列表
 let teamInfo = ref({
@@ -241,7 +243,7 @@ const treeNodeCommend = (item, e) => {
     case '编辑':
       addTeamShow.value = true
       nextTick(() => {
-        AddTeamRef.value.initEdit(item, true)
+        AddTeamRef.value.initEdit(item, true, 3)
       })
       break;
     case '删除':

+ 57 - 22
src/views/personnel/memberManagement.vue

@@ -61,10 +61,10 @@
       >
         <template v-slot:table-title-btn>
           <div>
-            <el-select v-model="status" style="width: 100px;">
+            <el-select v-model="status" style="width: 100px;" @change="statusChange()">
               <!-- status -->
               <el-option
-                :label="'全部'" :value="'all'"
+                :label="'全部'" :value="'-1'"
               ></el-option>
               <el-option v-for="item in statusList" :key="item.id"
                 :label="item.label" :value="item.value"
@@ -86,9 +86,9 @@
               </el-button>
               <template #dropdown>
                 <el-dropdown-menu>
-                  <el-dropdown-item>批量删除</el-dropdown-item>
-                  <el-dropdown-item>批量禁用</el-dropdown-item>
-                  <el-dropdown-item>批量启用</el-dropdown-item>
+                  <el-dropdown-item :disabled="checkTableIdList.length>0  ? false:true" v-on:click="delsUser">批量删除</el-dropdown-item>
+                  <!-- <el-dropdown-item>批量禁用</el-dropdown-item>
+                  <el-dropdown-item>批量启用</el-dropdown-item> -->
                 </el-dropdown-menu>
               </template>
             </el-dropdown>
@@ -269,7 +269,7 @@ onMounted(async () => {
 
 //状态字典
 const statusList = ref<dictType[]>([]);
-const status=ref('all')
+const status=ref('-1')
 //机构列表
 
 type tableType = {
@@ -331,19 +331,9 @@ function expandTree(){
   }
 }
 
-const formAccessibility = reactive({
-  fullName: "",
-  firstName: "",
-  lastName: "",
-  phone: "",
-});
+
 const ruleFormRef = ref<FormInstance>();
-const form = reactive({
-  name: "",
-  role: "",
-  password: "",
-  phone: "",
-});
+
 
 interface RuleForm {
   name: string;
@@ -439,8 +429,17 @@ const pageInfo =reactive( {
 });
 function showAllMember(){
   deptItem.value={}
+  pageInfo.pageNum = 1;
+  pageInfo.pageSize = 10;
+  getUserList()
+}
+
+function statusChange(){
+  pageInfo.pageNum = 1;
+  pageInfo.pageSize = 10;
   getUserList()
 }
+
 const getList = (item: PageInfo) => {
   pageInfo.pageNum = item.pageNum;
   pageInfo.pageSize = item.pageSize;
@@ -450,7 +449,9 @@ const getList = (item: PageInfo) => {
 const getUserList= () => {
   api.userApi.memberList({
     ...pageInfo,
-    deptId:deptItem.value.id
+    deptId:deptItem.value.id,
+    status:status.value,
+    isMember:1
   }).then((res:any)=>{
     
     if(res.code===200){
@@ -460,7 +461,7 @@ const getUserList= () => {
   })
 };
 const editor = (item: any) => {
-  console.log(item)
+ 
   router.push({
     path:"/personnel/member/memberMessage",
     query:{
@@ -647,13 +648,13 @@ function delUser(item:any) {
           .then(() => {
             api.userApi
               .userDelete({
-                id: [operationForm.value.id],
+                ids: [operationForm.value.id],
                 
               })
               .then((res: any) => {
                 if(res.code==200){
                   ElMessage.success(res.msg)
-                  
+                  getUserList()
                 }
               });
             // console.log(123);
@@ -664,6 +665,40 @@ function delUser(item:any) {
       }
     });
 }
+//批量删除成员
+function delsUser() {
+
+    ElMessageBox.confirm(
+      "账号删除后,该账号将无法使用访问本企业,请确定是否删除 ",
+      "确认要删除账号吗?",
+      {
+        confirmButtonText: "确认删除",
+        cancelButtonText: "取消",
+        type: "warning",
+        center:true
+      }
+    )
+      .then(() => {
+        api.userApi
+          .userDelete({
+            ids: checkTableIdList.value,
+
+          })
+          .then((res: any) => {
+            if(res.code==200){
+              ElMessage.success(res.msg)
+              
+            }
+          });
+        // console.log(123);
+      })
+      .catch(() => {
+        ElMessage.warning("用户取消操作");
+      });
+      
+    
+}
+
 //确认弹框提交信息
 const receiveInfo = ref<transfer>({});
 function confirmDialogInfo(item: any) {

+ 169 - 22
src/views/personnel/modules/addOperation.vue

@@ -433,8 +433,8 @@
                   <el-icon class="btn-icon" @click="imageView(1)"
                     ><View
                   /></el-icon>
-                  <el-icon class="btn-icon"><Download /></el-icon>
-                  <el-icon class="btn-icon"><Delete /></el-icon>
+                  <el-icon class="btn-icon" @click="downloadImage(1)"><Download /></el-icon>
+                  <el-icon class="btn-icon" @click="deleteImage(1)"><Delete /></el-icon>
                 </div>
               </div>
             </el-form-item>
@@ -470,8 +470,8 @@
                   <el-icon class="btn-icon" @click="imageView(2)"
                     ><View
                   /></el-icon>
-                  <el-icon class="btn-icon"><Download /></el-icon>
-                  <el-icon class="btn-icon"><Delete /></el-icon>
+                  <el-icon class="btn-icon" @click="downloadImage(2)"><Download /></el-icon>
+                  <el-icon class="btn-icon" @click="deleteImage(2)"><Delete /></el-icon>
                 </div>
               </div>
             </el-form-item>
@@ -507,8 +507,8 @@
                   <el-icon class="btn-icon" @click="imageView(3)"
                     ><View
                   /></el-icon>
-                  <el-icon class="btn-icon"><Download /></el-icon>
-                  <el-icon class="btn-icon"><Delete /></el-icon>
+                  <el-icon class="btn-icon" @click="downloadImage(3)"><Download /></el-icon>
+                  <el-icon class="btn-icon" @click="deleteImage(3)"><Delete /></el-icon>
                 </div>
               </div>
             </el-form-item>
@@ -544,8 +544,8 @@
                   <el-icon class="btn-icon" @click="imageView(4)"
                     ><View
                   /></el-icon>
-                  <el-icon class="btn-icon"><Download /></el-icon>
-                  <el-icon class="btn-icon"><Delete /></el-icon>
+                  <el-icon class="btn-icon" @click="downloadImage(4)"><Download /></el-icon>
+                  <el-icon class="btn-icon" @click="deleteImage(4)"><Delete /></el-icon>
                 </div>
               </div>
             </el-form-item>
@@ -581,8 +581,8 @@
                   <el-icon class="btn-icon" @click="imageView(5)"
                     ><View
                   /></el-icon>
-                  <el-icon class="btn-icon"><Download /></el-icon>
-                  <el-icon class="btn-icon"><Delete /></el-icon>
+                  <el-icon class="btn-icon" @click="downloadImage(5)"><Download /></el-icon>
+                  <el-icon class="btn-icon" @click="deleteImage(5)"><Delete /></el-icon>
                 </div>
               </div>
             </el-form-item>
@@ -618,8 +618,8 @@
                   <el-icon class="btn-icon" @click="imageView(6)"
                     ><View
                   /></el-icon>
-                  <el-icon class="btn-icon"><Download /></el-icon>
-                  <el-icon class="btn-icon"><Delete /></el-icon>
+                  <el-icon class="btn-icon" @click="downloadImage(6)"><Download /></el-icon>
+                  <el-icon class="btn-icon" @click="deleteImage(6)"><Delete /></el-icon>
                 </div>
               </div>
             </el-form-item>
@@ -655,8 +655,8 @@
                   <el-icon class="btn-icon" @click="imageView(7)"
                     ><View
                   /></el-icon>
-                  <el-icon class="btn-icon"><Download /></el-icon>
-                  <el-icon class="btn-icon"><Delete /></el-icon>
+                  <el-icon class="btn-icon" @click="downloadImage(7)"><Download /></el-icon>
+                  <el-icon class="btn-icon" @click="deleteImage(7)"><Delete /></el-icon>
                 </div>
               </div>
             </el-form-item>
@@ -727,6 +727,71 @@ onMounted(async () => {
       // console.log(res);
       if (res.code === 200) {
         operationForm.value = res.data;
+        let formData = res.data
+        
+        if(formData.fileList) {
+          
+          
+          let sfz1 = formData.fileList.find(item => item.fileType == 'sfz1')
+          let sfz2 = formData.fileList.find(item => item.fileType == 'sfz2')
+          
+          let qualification = formData.fileList.find(item => item.fileType == 'bankCard')
+          let contractImg = formData.fileList.find(item => item.fileType == 'contract')
+          let applicantImg = formData.fileList.find(item => item.fileType == 'applicant')
+          let salaryTable = formData.fileList.find(item => item.fileType == 'salaryTable')
+          let leaveTable = formData.fileList.find(item => item.fileType == 'leaveTable')
+         
+         
+         
+
+          if(sfz1) {
+            operationForm.value.sfz1 = sfz1.id
+            IDCardFrontName.value = sfz1.fileName
+            IDCardFront.value = sfz1.fileUrl
+            sfz1.value=sfz1.id
+          }
+          if(sfz2) {
+            operationForm.value.sfz2 = sfz2.id
+            IDCardBackName.value = sfz2.fileName
+            IDCardBack.value = sfz2.fileUrl
+            sfz2.value=sfz2.id
+
+          }
+          if(qualification) {
+            operationForm.value.bankCard = qualification.id
+            bankName.value = qualification.fileName
+            bankSize.value = qualification.fileUrl
+            bankCard.value=qualification.id
+          }
+          if(contractImg) {
+            operationForm.value.contract = contract.id
+            contractName.value = contractImg.fileName
+            contract.value = contractImg.fileUrl
+            contractId.value=ontract.id
+
+
+          }
+          if(applicantImg) {
+            operationForm.value.applicant = applicant.id
+            applicantName.value = applicantImg.fileName
+            applicant.value = applicantImg.fileUrl
+            applicantId.value=applicant.id
+
+          }
+          if(salaryTable) {
+            operationForm.value.salaryTable = salaryTable.id
+            decidePayName.value = salaryTable.fileName
+            decidePay.value = salaryTable.fileUrl
+            salaryTable.value=salaryTable.id
+
+          }
+          if(leaveTable) {
+            operationForm.value.leaveTable = leaveTable.id
+            dimissionName.value = leaveTable.fileName
+            dimission.value = leaveTable.fileUrl
+            leaveTable.value = leaveTable.id;
+          }
+        }
         if(res.data.address==null){
           operationForm.value.address=[]
         }
@@ -901,10 +966,11 @@ function deptChange(e: any) {
   // console.log(e)
   if (e != undefined) {
     let deptId = e[e?.length - 1];
-    operationForm.deptId = deptId;
+    operationForm.value.deptId = deptId;
   } else {
-    operationForm.deptId = "";
+    operationForm.value.deptId = "";
   }
+  // console.log(operationForm.value.deptId)
 }
 
 //数组去重
@@ -966,6 +1032,87 @@ const uploadImage = (file, type) => {
   });
 };
 
+
+// 图片下载
+const download = (url, name) => {
+  let image = new Image();
+  // 解决跨域 Canvas 污染问题
+  image.setAttribute("crossOrigin", "anonymous");
+  image.onload = function () {
+    let canvas = document.createElement("canvas");
+    canvas.width = image.width;
+    canvas.height = image.height;
+    let context = canvas.getContext("2d");
+    context.drawImage(image, 0, 0, image.width, image.height);
+    let url = canvas.toDataURL("image/png"); //得到图片的base64编码数据
+    let a = document.createElement("a"); // 生成一个a元素
+    let event = new MouseEvent("click"); // 创建一个单击事件
+    a.download = name; // 设置图片名称
+    a.href = url; // 将生成的URL设置为a.href属性
+    a.dispatchEvent(event); // 触发a的单击事件
+  }
+  image.src = url
+}
+
+const downloadImage = (type) => {
+  switch (type) {
+    case 1:
+      download(IDCardFront.value,IDCardFrontName.value)
+      break;
+    case 2:
+      download(IDCardBack.value,IDCardBackName.value)
+      break;
+    case 3:
+      download(bankSize.value,bankName.value)
+      break;
+    case 4:
+      download(contract.value,contractName.value)
+      break;
+    case 5:
+      download(applicant.value,applicantName.value)
+      break;
+    case 6:
+      download(decidePay.value,decidePayName.value)
+      break;
+    case 7:
+      download(dimission.value,dimissionName.value)
+      break;
+  }
+}
+// 图片删除
+const deleteImage = (type) => {
+  switch (type) {
+    case 1:
+      IDCardFront.value = ''
+      IDCardFrontName.value = ''
+      break;
+    case 2:
+      IDCardBack.value = ''
+      IDCardBackName.value = ''
+      break;
+    case 3:
+      bankSize.value = ''
+      bankName.value = ''
+      break;
+    case 4:
+      contract.value = ''
+      contractName.value = ''
+      break;
+    case 5:
+      applicant.value = ''
+      applicantName.value = ''
+      break;
+    case 6:
+      decidePay.value = ''
+      decidePayName.value = ''
+      break;
+    case 7:
+      dimission.value = ''
+      dimissionName.value = ''
+      break;
+  }
+}
+
 // 人事档案
 let recordShow = ref(false);
 
@@ -1010,13 +1157,13 @@ const onSubmit = async (OperationFormRef: FormInstance, type: number) => {
         province:operationForm.value.address.length?operationForm.value.address[0]:'',
         city:operationForm.value.address.length?operationForm.value.address[1]:'',
         district:operationForm.value.address.length?operationForm.value.address[2]:'',
-        deptId:operationForm.value.deptId[operationForm.value.deptId.length-1],
+        deptId:operationForm.value.deptId,
         typeAttr:2,
         sfz1:sfz1.value,
         sfz2:sfz2.value,
         bankCard:bankCard.value,
-        contractId:contractId.value,
-        applicantId:applicantId.value,
+        contract:contractId.value,
+        applicant:applicantId.value,
         salaryTable:salaryTable.value,
         leaveTable:leaveTable.value,
         
@@ -1033,13 +1180,13 @@ const onSubmit = async (OperationFormRef: FormInstance, type: number) => {
         province:operationForm.value.address.length?operationForm.value.address[0]:'',
         city:operationForm.value.address.length?operationForm.value.address[1]:'',
         district:operationForm.value.address.length?operationForm.value.address[2]:'',
-        deptId:operationForm.value.deptId[operationForm.value.deptId.length-1],
+        deptId:operationForm.value.deptId,
         typeAttr:2,
         sfz1:sfz1.value,
         sfz2:sfz2.value,
         bankCard:bankCard.value,
-        contractId:contractId.value,
-        applicantId:applicantId.value,
+        contract:contractId.value,
+        applicant:applicantId.value,
         salaryTable:salaryTable.value,
         leaveTable:leaveTable.value,
         id:operationForm.value.id

+ 38 - 9
src/views/personnel/modules/editInstitution.vue

@@ -118,7 +118,7 @@
       <el-button type="primary" @click="confirm(ruleFormRef)">
         保 存
       </el-button>
-      
+
     </div>
   </div>
 </template>
@@ -156,17 +156,23 @@ onMounted(() => {
     // form.parentId=[route.query.id]
   }
 
-  setTimeout(() => {
-    disabledTree(tableData.value);
-  }, 1000);
+  if(route.query.id) {
+    setTimeout(() => {
+      disabledEditTree(tableData.value);
+    }, 1000);
+  } else {
+    setTimeout(() => {
+      disabledAddTree(tableData.value);
+    }, 1000);
+  }
 });
 
 //禁用机构选择
-function disabledTree(list: tableType) {
+function disabledEditTree(list: tableType) {
   for (let i = 0; i < list.length; i++) {
     const node = list[i];
     if (route.query.id) {
-      if (list[i].id != route.query.id) {
+      if (list[i].id === route.query.id || list[i].parentId == '0') {
         list[i].disabled = true;
       }
     } else {
@@ -174,7 +180,20 @@ function disabledTree(list: tableType) {
     }
 
     if (node.children && node.children.length > 0) {
-      disabledTree(node.children);
+      disabledEditTree(node.children);
+    }
+  }
+}
+const disabledAddTree = (list: tableType) => {
+  for (let i = 0; i < list.length; i++) {
+    const node = list[i];
+    if (list[i].parentId == '0') {
+      list[i].disabled = true;
+    } else {
+      list[i].disabled = false;
+    }
+    if (node.children && node.children.length > 0) {
+      disabledAddTree(node.children);
     }
   }
 }
@@ -256,6 +275,17 @@ const tableProps = ref({
   label: "name",
   children: "children",
   checkStrictly: true,
+  // displayRender({node}) {
+  //   // 返回 VNode,实现复杂样式
+  //   return h(
+  //     'span',
+  //     null,
+  //     [
+  //       node.label,
+  //       h('span', {style: 'color: red;'}, '(自定义样式)'),
+  //     ]
+  //   );
+  // },
 });
 
 const getAreaList = () => {
@@ -307,11 +337,10 @@ const getInstitutionList = () => {
     // if(res.code===200){
 
     // }
-    tableData.value = res.data;
+    tableData.value = res.data
   });
   // pageInfo.pageSize=item.pageSize
 };
-
 const confirm = async (formEl: FormInstance | undefined) => {
   if (!formEl) return;
   await formEl.validate((valid, fields) => {

+ 99 - 39
src/views/personnel/modules/memberMessage.vue

@@ -1,10 +1,10 @@
 <template>
   <div class="message-info">
     <div class="error"></div>
-    <div style="background-color: #fff; padding: 15px; box-sizing: border-box">
+    <div style=" box-sizing: border-box; padding: 15px;background-color: #fff;">
       <el-row :gutter="24">
         <el-col :span="20">
-          <div style="display: flex; align-items: center">
+          <div style="display: flex; align-items: center;">
             <div class="img-title">
               {{ pinyin.getFullChars(operationForm.name).substring(0, 1) }}
             </div>
@@ -13,7 +13,7 @@
         </el-col>
         <el-col :span="4">
           <el-button type="primary" @click="editMember">编辑</el-button>
-          <el-dropdown style="margin-left: 15px">
+          <el-dropdown style="margin-left: 15px;">
             <el-button type="primary"> 更多 </el-button>
             <template #dropdown>
               <el-dropdown-menu>
@@ -71,7 +71,7 @@
       :model="operationForm"
       ref="OperationFormRef"
       label-width="120"
-      style="background-color: #fff; padding: 20px; box-sizing: border-box"
+      style=" box-sizing: border-box; padding: 20px;background-color: #fff;"
     >
       <el-tabs v-model="activeName" class="demo-tabs">
         <el-tab-pane label="基本信息" name="first">
@@ -344,7 +344,7 @@
                         :show-file-list="false"
                         accept=".png, .jpg, .jpeg"
                         @change="uploadImage($event, 1)"
-                        style="display: flex; align-items: center"
+                        style="display: flex; align-items: center;"
                       >
                         <el-icon class="btn-icon"><Upload /></el-icon>
                       </el-upload>
@@ -381,7 +381,7 @@
                         :show-file-list="false"
                         accept=".png, .jpg, .jpeg"
                         @change="uploadImage($event, 2)"
-                        style="display: flex; align-items: center"
+                        style="display: flex; align-items: center;"
                       >
                         <el-icon class="btn-icon"><Upload /></el-icon>
                       </el-upload>
@@ -418,7 +418,7 @@
                         :show-file-list="false"
                         accept=".png, .jpg, .jpeg"
                         @change="uploadImage($event, 3)"
-                        style="display: flex; align-items: center"
+                        style="display: flex; align-items: center;"
                       >
                         <el-icon class="btn-icon"><Upload /></el-icon>
                       </el-upload>
@@ -455,7 +455,7 @@
                         :show-file-list="false"
                         accept=".png, .jpg, .jpeg"
                         @change="uploadImage($event, 4)"
-                        style="display: flex; align-items: center"
+                        style="display: flex; align-items: center;"
                       >
                         <el-icon class="btn-icon"><Upload /></el-icon>
                       </el-upload>
@@ -492,7 +492,7 @@
                         :show-file-list="false"
                         accept=".png, .jpg, .jpeg"
                         @change="uploadImage($event, 5)"
-                        style="display: flex; align-items: center"
+                        style="display: flex; align-items: center;"
                       >
                         <el-icon class="btn-icon"><Upload /></el-icon>
                       </el-upload>
@@ -529,7 +529,7 @@
                         :show-file-list="false"
                         accept=".png, .jpg, .jpeg"
                         @change="uploadImage($event, 6)"
-                        style="display: flex; align-items: center"
+                        style="display: flex; align-items: center;"
                       >
                         <el-icon class="btn-icon"><Upload /></el-icon>
                       </el-upload>
@@ -566,7 +566,7 @@
                         :show-file-list="false"
                         accept=".png, .jpg, .jpeg"
                         @change="uploadImage($event, 7)"
-                        style="display: flex; align-items: center"
+                        style="display: flex; align-items: center;"
                       >
                         <el-icon class="btn-icon"><Upload /></el-icon>
                       </el-upload>
@@ -603,7 +603,7 @@
     >
       <el-row :gutter="24">
         <el-col :span="20">
-          <div style="display: flex; align-items: center">
+          <div style="display: flex; align-items: center;">
             <div class="img-title">
               {{ pinyin.getFullChars(operationForm.name).substring(0, 1) }}
             </div>
@@ -611,12 +611,12 @@
           </div>
         </el-col>
       </el-row>
-      <p style="margin-left: 50px">工号:{{ transferInfo.userId }}</p>
-      <p style="margin-left: 50px">所在机构:{{ transferInfo.deptName }}</p>
+      <p style="margin-left: 50px;">工号:{{ transferInfo.userId }}</p>
+      <p style="margin-left: 50px;">所在机构:{{ transferInfo.deptName }}</p>
       <el-divider v-show="leave"/>
       <!-- 资源转移--->
-      <p style="margin-left: 50px" v-show="!leave">需要转移的资源</p>
-      <el-table :data="[{}]" style="width: 100%" v-show="!leave">
+      <p style="margin-left: 50px;" v-show="!leave">需要转移的资源</p>
+      <el-table :data="[{}]" style="width: 100%;" v-show="!leave">
         <el-table-column prop="date" label="资源" width="120">
           <template #default> 业务员 </template>
         </el-table-column>
@@ -642,7 +642,7 @@
         <el-radio value="1" size="large">转移给其他成员</el-radio>
         <el-radio value="0" size="large">不转移</el-radio>
       </el-radio-group> -->
-    
+
       <template #footer>
         <div class="dialog-footer">
           <el-button @click="dialogVisible = false">取消</el-button>
@@ -670,13 +670,13 @@
       <div>
         <el-input
           v-model="filterText"
-          style="width: 240px"
+          style="width: 240px;"
           placeholder="请输入机构名称查找"
           :prefix-icon="Search"
         />
         <el-tree
           ref="treeRef"
-          style="max-width: 600px"
+          style="max-width: 600px;"
           class="filter-tree"
           :data="tableData"
           :props="tableProps"
@@ -689,7 +689,7 @@
             <span class="custom-tree-node">
               <span>{{ node.label }}</span>
               <span>
-                <a style="color: #00a0f4">
+                <a style="color: #00a0f4;">
                   {{ node.label == deptItem.name ? "已选" : "" }}
                 </a>
               </span>
@@ -764,6 +764,55 @@ onMounted(async () => {
       console.log(res);
       if (res.code === 200) {
         operationForm.value = res.data;
+        let formData = res.data
+
+        if(formData.fileList) {
+
+
+          let sfz1 = formData.fileList.find(item => item.fileType == 'sfz1')
+          let sfz2 = formData.fileList.find(item => item.fileType == 'sfz2')
+
+          let qualification = formData.fileList.find(item => item.fileType == 'bankCard')
+          let contractImg = formData.fileList.find(item => item.fileType == 'contract')
+          let applicantImg = formData.fileList.find(item => item.fileType == 'applicant')
+          let salaryTable = formData.fileList.find(item => item.fileType == 'salaryTable')
+          let leaveTable = formData.fileList.find(item => item.fileType == 'leaveTable')
+          if(sfz1) {
+            operationForm.value.sfz1 = sfz1.id
+            IDCardFrontName.value = sfz1.fileName
+            IDCardFront.value = sfz1.fileUrl
+          }
+          if(sfz2) {
+            operationForm.value.sfz2 = sfz2.id
+            IDCardBackName.value = sfz2.fileName
+            IDCardBack.value = sfz2.fileUrl
+          }
+          if(qualification) {
+            operationForm.value.qualification = qualification.id
+            bankName.value = qualification.fileName
+            bankSize.value = qualification.fileUrl
+          }
+          if(contractImg) {
+            operationForm.value.contract = contract.id
+            contractName.value = contractImg.fileName
+            contract.value = contractImg.fileUrl
+          }
+          if(applicantImg) {
+            operationForm.value.applicant = applicant.id
+            applicantName.value = applicantImg.fileName
+            applicant.value = applicantImg.fileUrl
+          }
+          if(salaryTable) {
+            operationForm.value.salaryTable = salaryTable.id
+            decidePayName.value = salaryTable.fileName
+            decidePay.value = salaryTable.fileUrl
+          }
+          if(leaveTable) {
+            operationForm.value.leaveTable = leaveTable.id
+            dimissionName.value = leaveTable.fileName
+            dimission.value = leaveTable.fileUrl
+          }
+        }
       }
     });
 });
@@ -944,11 +993,11 @@ const uploadImage = (file: any, type: number) => {
   let url = window.URL.createObjectURL(files); //转临时路径
   const params = new FormData(); // 声明formData数据类型
   params.append("file", files);
- 
+
   // params.append("multipartFile", files);
   // params.append("type", "image");
   api.areaApi.fileUpload(params).then((res: any) => {
-    console.log(res);
+
     if (res.code === 200) {
       switch (type) {
         case 1:
@@ -1142,7 +1191,7 @@ function confirmDialogInfo(item: any) {
 
 //资源转移确认
 function confirmTransfer() {
- 
+
     api.userApi
     .transfer({
       originalUserId: transferInfo.value.userId,
@@ -1168,8 +1217,8 @@ function confirmTransfer() {
     //     dialogVisible.value = false;
     //   }
     // });
-  
-  
+
+
 }
 
 //变更机构弹框
@@ -1250,7 +1299,7 @@ function delUser() {
             api.userApi
               .userDelete({
                 id: [operationForm.value.id],
-                
+
               })
               .then((res: any) => {
                 if(res.code==200){
@@ -1270,76 +1319,87 @@ function delUser() {
 
 <style scoped lang="scss">
 .message-info {
+  box-sizing: border-box;
   // background: #fff;
   padding: 15px;
-  box-sizing: border-box;
+
   h4 {
     margin: 0;
   }
 }
+
 .img-title {
-  border-radius: 50%;
-  width: 30px;
-  height: 30px;
-  color: #fff;
-  background: #00a0f4;
   display: flex;
   align-items: center;
   justify-content: center;
+  width: 30px;
+  height: 30px;
   margin-right: 20px;
+  color: #fff;
+  background: #00a0f4;
+  border-radius: 50%;
 }
+
 .fileInfo {
   display: flex;
   align-items: center;
   justify-content: flex-start;
   padding: 2px 20px;
-  border-radius: 5px;
   margin: 0 10px;
+  border-radius: 5px;
+
   &:hover {
     background: #ccc;
   }
+
   .fileName {
     width: 200px;
     overflow: hidden;
     text-overflow: ellipsis;
     white-space: nowrap;
   }
+
   .fileBtn {
     display: flex;
     align-items: center;
+
     .btn-icon {
       margin: 0 10px;
       font-size: 16px;
       cursor: pointer;
+
       &:hover {
         color: #00a0f4;
       }
     }
   }
 }
+
 .image-view {
   position: absolute;
   top: 0;
   left: 0;
-  width: 100%;
-  height: 100%;
-  background: rgba(0, 0, 0, 0.6);
+  z-index: 9999;
   display: flex;
   align-items: center;
   justify-content: center;
-  z-index: 9999;
+  width: 100%;
+  height: 100%;
+  background: rgb(0 0 0 / 60%);
+
   img {
     width: 800px;
     height: 800px;
     border-radius: 5px;
   }
 }
+
 .custom-tree-node {
-  flex: 1;
   display: flex;
+  flex: 1;
   align-items: center;
   justify-content: space-between;
-  font-size: 14px;
   padding-right: 8px;
+  font-size: 14px;
 }
 </style>

+ 24 - 20
src/views/personnel/roleManagement.vue

@@ -16,7 +16,7 @@
                 </template>
               </ElInput>
             </ElCol>
-            <ElCol :md="20" style="margin-top: 20px">
+            <ElCol :md="20" style="margin-top: 20px;">
               <ElButton type="primary" @click="addRole">
                 <el-icon><Plus /></el-icon>
                 添加</ElButton
@@ -58,7 +58,7 @@
             :rules="roleRules"
             label-width="auto"
             size=""
-            style="max-width: 500px"
+            style="max-width: 500px;"
           >
             <ElFormItem prop="name" label="角色名称">
               <ElInput
@@ -72,7 +72,7 @@
             <ElFormItem prop="deptId" label="所属机构">
               <el-cascader
                 clearable
-                style="width: 100%"
+                style="width: 100%;"
                 v-model="roleInfo.deptId"
                 :options="tableData"
                 :props="tableProps"
@@ -90,21 +90,21 @@
             </ElFormItem>
           </ElForm>
           <ElDivider></ElDivider>
-          <div style="min-height: 500px">
+          <div style="min-height: 500px;">
             <h5>
               角色权限<span
-                style="color: #ccc; font-size: 12px; font-weight: 400"
+                style=" font-size: 12px; font-weight: 400;color: #ccc;"
                 >(该角色享有的后台模块功能权限剩余数据权限)</span
               >
             </h5>
             <el-tabs
               :tab-position="'left'"
-              style="height: 500px"
+              style="height: 500px;"
               class="demo-tabs"
             >
               <el-tab-pane label="菜单/操作权限">
                 <ElTree
-                  style="max-height: 440px; overflow-y: auto"
+                  style="max-height: 440px; overflow-y: auto;"
                   :data="treeData"
                   :props="defaultProps"
                   show-checkbox
@@ -126,7 +126,7 @@
                   <el-radio value="3" size="large">指定机构</el-radio>
                 </el-radio-group>
                 <ElTree
-                  style="max-height: 440px; overflow-y: auto"
+                  style="max-height: 440px; overflow-y: auto;"
                   :data="deptTreeData"
                   :props="deptProps"
                   show-checkbox
@@ -304,7 +304,7 @@ function chooseRole(item: any) {
         deptTree.value!.setCheckedKeys([], false);
         roleInfo.dataMark = '1';
       }
-      
+
       roleInfo.deptIds = res.data.dataScope?.deptIds;
     });
   operationType.value = "edit";
@@ -361,7 +361,7 @@ function roleSubmit() {
           dataMark: roleInfo.dataMark,
         },
       })
-      .then((res) => {
+      .then((_res) => {
         // console.log(res);
         ElMessage.success("操作成功");
         getRoleList();
@@ -374,7 +374,7 @@ function deleteRole() {
     .roleDeleteValid({
       id: roleId.value,
     })
-    .then((res) => {
+    .then((res:any) => {
       console.log(res);
       if (res.code == 200) {
         ElMessageBox.confirm("确认要删除当前数据吗?", "提示", {
@@ -461,7 +461,6 @@ function disabledTree(list: any[], id: string) {
       } else {
         list[i].disabled = false;
       }
-
       //
     } else {
       list[i].disabled = false;
@@ -499,12 +498,12 @@ onBeforeMount(() => {
   const processedRoutes = preprocessRoutes(asyncRoutes);
 
   treeData.value = convertToTreeData(0, processedRoutes);
-  
+
   api.roleApi.updateConfig({
     sysConfig:JSON.stringify(processedRoutes),
     sysPerms:JSON.stringify(treeData.value)
   }).then(res=>{
-    
+
   })
 
 });
@@ -531,19 +530,21 @@ onMounted(() => {
 .active {
   background: #b4b2b2;
 }
+
 p {
   margin: 0;
 }
+
 .role-item {
-  padding: 0 10px;
   box-sizing: border-box;
-  cursor: pointer;
+  padding: 0 10px;
   margin-top: 10px;
+  cursor: pointer;
 
   > p {
     span {
-      color: #b4b2b2;
       font-size: 10px;
+      color: #b4b2b2;
     }
   }
   // >p:last-child{
@@ -551,19 +552,22 @@ p {
 
   // }
 }
+
 .on {
-  background: rgb(242, 242, 242);
-  padding: 0 10px;
   box-sizing: border-box;
+  padding: 0 10px;
   margin-top: 10px;
   cursor: pointer;
+  background: rgb(242 242 242);
+
   > p {
     span {
-      color: #b4b2b2;
       font-size: 10px;
+      color: #b4b2b2;
     }
   }
 }
+
 :deep(.p-5) {
   padding: 0.2rem !important;
 }

+ 530 - 0
src/views/quoteConfig/agreement/agreementAdd.vue

@@ -0,0 +1,530 @@
+<template>
+  <div>
+    <PageMain style="padding: 20px;margin-bottom: 100px;">
+      <ElRow :gutter="60">
+        <ElCol :md="24" :lg="24" style="padding-bottom: 10px;font-size: 14px;">
+          <span class="strong">协议信息</span>
+        </ElCol>
+        <el-form :model="agreementFrom" label-width="120px" :rules="agreementFromRules" label-position="top"
+          ref="agreementFromRef" style="max-width: 100%;" class="flex a-c f-wrap" :size="fromSize">
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="协议名称" prop="agreementTitle">
+              <el-input v-model="agreementFrom.agreementTitle" placeholder="输入协议名称" maxlength="50"
+                show-word-limit></el-input>
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="协议简称" prop="agreementAbbreviation">
+              <el-input v-model="agreementFrom.agreementAbbreviation" placeholder="输入简称" maxlength="50"
+                show-word-limit />
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="协议生效时间" prop="startDate">
+              <el-date-picker v-model="agreementFrom.startDate" style="width: 100%;" type="datetime"
+                placeholder="选择生效时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
+                time-format="HH:mm:ss" @change="handleStartDateChange" />
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="协议失效时间" prop="endDate">
+              <el-date-picker v-model="agreementFrom.endDate" style="width: 100%;" type="datetime" placeholder="选择失效时间"
+                format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" time-format="HH:mm:ss"
+                :disabled-date="disabledEndDate" />
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="是否含增值税结算" prop="withTax">
+              <el-radio-group v-model="agreementFrom.withTax">
+                <el-radio-button :label="item.label" :value="item.value" v-for="(item, index) in getDict('with_tax')"
+                  :key="index" />
+              </el-radio-group>
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="结算周期" prop="moneyTime">
+              <span style="margin: 0  20px;font-size: 18px;">T</span>
+              <el-input-number v-model="agreementFrom.moneyTime" :min="1" :max="999" />
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="12"></ElCol>
+          <ElCol :md="24" :lg="12">
+            <el-form-item label="协议描述" prop="agreementName">
+              <el-input v-model="agreementFrom.agreementName" placeholder="请输入" type="textarea" maxlength="500"
+                show-word-limit />
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="24" style="padding-bottom: 10px;font-size: 14px;">
+            <ElDivider></ElDivider>
+            <span class="strong">其他信息</span>
+          </ElCol>
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="协议归属" prop="deptId">
+              <el-cascader v-model="agreementFrom.deptId" style="width: 100%;" filterable clearable
+                :options="deptOptions" :props="parentIdprops" :show-all-levels="false" @change="deptIdChange" />
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="保险公司" prop="companyId">
+              <el-cascader v-model="agreementFrom.companyId" style="width: 100%;" filterable clearable
+                :options="companyOptions" :props="companyIdprops" :show-all-levels="false" @change="companyIdChange" />
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="协议类型" prop="agreementType">
+              <el-select v-model="agreementFrom.agreementType" placeholder="选择协议类型">
+                <el-option :label="item.label" :value="item.value" v-for="(item, index) in getDict('agreement_type')"
+                  :key="index" />
+              </el-select>
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="管理人" prop="custodianId">
+              <el-input v-model="managerName" @click="managerModalVisible = true">{{ agreementFrom.custodianId
+              }}</el-input>
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="对接人" prop="dockingPerson">
+              <el-input v-model="agreementFrom.dockingPerson" placeholder="输入对接人姓名">
+              </el-input>
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="对接人手机号" prop="dockingPhone">
+              <el-input v-model="agreementFrom.dockingPhone" placeholder="输入对接人手机号" maxlength="11" show-word-limit />
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="6">
+            <el-form-item label="出单账号" prop="attributionId">
+              <el-input v-model="attributionName" placeholder="请选择" type="textarea"
+                @click="orderAccountVisible = true" />
+            </el-form-item>
+          </ElCol>
+          <ElCol :md="24" :lg="12">
+            <el-form-item label="协议文件">
+              <div class="flex f-c a-start">
+                <el-upload action="" v-if="!imageUrl" :auto-upload="false" :show-file-list="false"
+                  accept=".pdf, .doc, .docx, .xls, .xlsx" @change="uploadImage($event)">
+                  <el-button type="primary" size="default" plain>
+                    <template #icon>
+                      <el-icon>
+                        <Upload />
+                      </el-icon>
+                    </template>
+                    上传</el-button>
+                </el-upload>
+                <div class="agreementNotice flex a-c j-s" v-else>
+                  <div class="flex  a-c">
+                    <img src="../../../assets/images/pdf.png" alt="">
+                    <div class="flex f-c j-s">
+                      <span style="line-height: 19px;">{{ imageName }}</span>
+                      <span style="line-height: 19px;">{{ fileSize }}</span>
+                    </div>
+                  </div>
+                  <div class="fileBtn flex a-c">
+                    <el-upload :show-file-list="false" :auto-upload="false" accept=".pdf, .doc, .docx, .xls, .xlsx"
+                      @change="uploadImage($event)" style="display: flex; align-items: center;">
+                      <el-icon class="btn-icon">
+                        <Refresh />
+                      </el-icon>
+                    </el-upload>
+                    <el-icon class="btn-icon" @click="Fileonload(imageUrl, imageName)">
+                      <Download />
+                    </el-icon>
+                    <el-icon class="btn-icon" @click="deleteImage">
+                      <Delete />
+                    </el-icon>
+                  </div>
+                </div>
+                <span style="font-size: 14px;color: #999;">支持pdf,doc,docx,xls,xlsx格式文件,最大不超过50MB</span>
+              </div>
+            </el-form-item>
+          </ElCol>
+        </el-form>
+      </ElRow>
+
+    </PageMain>
+    <FixedActionBar>
+      <ElButton type="primary" @click="submitForm(agreementFromRef)">
+        保存
+      </ElButton>
+      <ElButton @click="router.back()">
+        取消
+      </ElButton>
+    </FixedActionBar>
+    <!-- 选择管理人 -->
+    <managerModal :managerModal="managerModalVisible" :deptOptions="deptOptions" :managerlist="managerlist"
+      @save="managerModalsave" @NodeClick="managerModalNodeClick" @query="managerModalQuery"
+      @cancel="managerModalVisible = false" />
+    <!-- 选择出单账号 -->
+    <orderAccount :orderAccount="orderAccountVisible" @cancel="orderAccountVisible = false" @save="orderAccountSave" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive } from 'vue'
+import { FormRules, FormInstance, ComponentSize, ElMessage } from 'element-plus';
+import { loadDicts, getDict } from '@/utils/composables/dictService';//字典组件
+import { Fileonload, formatFileSize, processFileName } from '@/utils/composables/fileProcessing';//文件处理工具
+import { useRoute, useRouter } from 'vue-router';
+import managerModal from "./components/managerModal.vue";
+import orderAccount from "./components/orderAccount.vue";
+import api from '@/api';
+const parentIdprops = {
+  expandTrigger: 'hover' as const,
+  value: 'id',
+  label: 'name',
+  checkStrictly: true,
+}
+const companyIdprops = {
+  expandTrigger: 'hover' as const,
+  value: 'id',
+  label: 'nameSimple',
+  checkStrictly: true,
+}
+interface AgreementForm {
+  id?: string; // 协议 ID
+  agreementTitle: string; // 协议名称
+  agreementAbbreviation: string; // 协议简称
+  startDate: string; // 生效时间,通常为 ISO 格式的日期字符串
+  endDate: string; // 失效时间,通常为 ISO 格式的日期字符串
+  withTax: string; // 是否含增值税结算
+  moneyTime: any; // 结算周期
+  agreementName: string; // 协议描述
+  deptId: string; // 协议归属
+  companyId: string; // 保险公司 ID
+  agreementType: string; // 协议类型
+  custodianId: string; // 管理人 ID
+  dockingPerson: string; // 对接人
+  dockingPhone: string; // 对接人手机号
+  attributionId: string; // 出单账号 ID
+  fileId?: string; // 文件 ID
+}
+const route = useRoute();
+const router = useRouter();
+const id = ref(route.query.id as string);//id参数
+const deptOptions = ref([])//机构数据
+const companyOptions = ref()//保险公司数据
+const agreementFromRef = ref<FormInstance>()
+const fromSize = ref<ComponentSize>('large')
+const imageName = ref('');//文件名称
+const imageUrl = ref('');//文件地址
+const fileSize = ref();//文件大小
+const agreementFrom: AgreementForm = reactive({
+  id: "",
+  agreementTitle: "",//协议名称
+  agreementAbbreviation: "",//协议简称
+  startDate: "",//生效时间
+  endDate: "",//失效时间
+  withTax: "",//是否含增值税结算
+  moneyTime: 0,//结算周期
+  agreementName: "",//协议描述
+  deptId: "",//协议归属
+  companyId: "",//保险公司id
+  agreementType: "",//协议类型
+  custodianId: "",//管理人id
+  dockingPerson: "",//对接人
+  dockingPhone: "",//对接人手机号
+  attributionId: "",//出单账号id
+  fileId: '',//文件
+})
+const validatepassword = (_rule: any, value: any, callback: any) => {
+  // 定义正则表达式,例如:只允许字母、数字,长度在4到10个字符之间
+  const codeRegex = /^(?:(?:\+|00)86)?1\d{10}$/;
+  // 判断是否为空
+  if (!value) {
+    return callback(new Error('请输入手机号'));
+  }
+
+  // 验证是否符合正则表达式
+  if (!codeRegex.test(value)) {
+    return callback(new Error('请输入正确的手机号'));
+  }
+  // 验证通过
+  callback();
+};
+
+const agreementFromRules = ref<FormRules>({
+  agreementTitle: [
+    { required: true, trigger: 'blur', message: '请输入协议名称' },
+  ],
+  agreementAbbreviation: [
+    { required: true, trigger: 'blur', message: '请输入协议简称' },
+  ],
+  startDate: [
+    { required: true, trigger: 'change', message: '选择生效时间' },
+  ],
+  endDate: [
+    { required: true, trigger: 'change', message: '选择失效时间' },
+  ],
+  withTax: [
+    { required: true, trigger: 'change', message: '请选择是否含增值税结算' },
+  ],
+  moneyTime: [
+    { required: true, trigger: 'blur', message: '请输入结算周期' },
+  ],
+  agreementName: [
+    { required: true, trigger: 'blur', message: '请输入协议描述' },
+  ],
+  deptId: [
+    { required: true, trigger: 'change', message: '请选择协议归属' },
+  ],
+  companyId: [
+    { required: true, trigger: 'change', message: '请选择保险公司' },
+  ],
+  agreementType: [
+    { required: true, trigger: 'blur', message: '请选择协议类型' },
+  ],
+  custodianId: [
+    { required: true, trigger: 'blur', message: '请选择管理人' },
+  ],
+  dockingPerson: [
+    { required: true, trigger: 'blur', message: '请输入对接人' },
+  ],
+  dockingPhone: [
+    { required: true, trigger: 'blur', message: '请输入对接人手机号' },
+    { validator: validatepassword, trigger: 'blur' }
+  ],
+  attributionId: [
+    { required: true, trigger: 'change', message: '请选择出单账号' },
+  ],
+
+});
+onBeforeMount(async () => {
+  await loadDicts(['agreement_type', 'with_tax']);//获取多个字典
+  getDeptTree();//归属机构
+  company();//获取保险公司列表
+  if (id.value) {
+    agreementFrom.id = id.value;
+    detailsInfo();//详情
+  }
+
+});
+//协议详情
+const detailsInfo = () => {
+  api.agreementApi.agreementGet(id.value).then((res: any) => {
+    if (res.code == '200') {
+      // 更新 agreementFrom 的属性
+      Object.keys(agreementFrom).forEach((key) => {
+        if (res.data.agreement.hasOwnProperty(key)) {
+          // 使用类型断言,确保类型匹配
+          agreementFrom[key as keyof AgreementForm] = res.data.agreement[key] as AgreementForm[keyof AgreementForm];
+        }
+      });
+    } else {
+    }
+  });
+}
+const companyIdChange = (value: any) => {
+  if (value) {
+    agreementFrom.companyId = value[value.length - 1]
+  }
+}
+const deptIdChange = (value: any) => {
+  if (value) {
+    agreementFrom.deptId = value[value.length - 1]
+  }
+}
+// 禁用失效时间选择
+const disabledEndDate = (date: any) => {
+  const startDate = new Date(agreementFrom.startDate);
+  return startDate && date < startDate; // 禁用小于生效时间的日期
+};
+
+// 处理生效时间变化
+const handleStartDateChange = (value: any) => {
+  // 如果生效时间改变,检查失效时间是否需要更新
+  if (agreementFrom.endDate && new Date(agreementFrom.endDate) < new Date(value)) {
+    agreementFrom.endDate = ''; // 清空失效时间
+  }
+};
+const uploadImage = async (file: any) => {
+  let files = file.raw
+  const params = new FormData(); // 声明formData数据类型
+  params.append("file", files);
+  api.commonApi.fileUpload(params).then((res: any) => {
+    if (res.code == '200') {
+      imageName.value = res.data.fileName;
+      imageUrl.value = res.data.downloadUrl;
+      imageName.value = processFileName(res.data.fileName)
+      fileSize.value = formatFileSize(res.data.fileSize)
+      agreementFrom.fileId = res.data.id;
+    } else {
+    }
+  });
+}
+// 图片删除
+const deleteImage = () => {
+  imageName.value = ''
+  imageUrl.value = ''
+}
+//嫌憎编辑提交事件
+const submitForm = async (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  await formEl.validate((valid) => {
+    if (valid) {
+      if (id.value) {
+        api.agreementApi.agreementUpdate({ ...agreementFrom }).then((res: any) => {
+          if (res.code == '200') {
+            ElMessage({
+              type: 'success',
+              message: res.msg,
+              plain: true,
+            })
+            router.back()
+          } else {
+          }
+        });
+      } else {
+        api.agreementApi.agreementAdd({ ...agreementFrom }).then((res: any) => {
+          if (res.code == '200') {
+            ElMessage({
+              type: 'success',
+              message: res.msg,
+              plain: true,
+            })
+            router.back()
+          } else {
+          }
+        });
+      }
+    } else {
+
+    }
+  })
+}
+const getDeptTree = () => {
+  api.institutionApi.institutionList({}).then((res: any) => {
+    if (res.code == '200') {
+      deptOptions.value = res.data;
+      agreementFrom.deptId = res.data[0].id;
+    } else {
+    }
+  });
+}
+const company = () => {
+  api.agreementApi.findTree({ name: "" }).then((res: any) => {
+    if (res.code == '200') {
+      companyOptions.value = res.data;
+    } else {
+      ElMessage({
+        type: 'error',
+        message: res.msg,
+        plain: true,
+      })
+    }
+  });
+}
+//选择管理人
+interface managerlist {
+  id: string,
+  userId:string,
+  name: string,
+  deptName: string,
+}
+const managerModalVisible = ref(false)
+const managerlist = ref<managerlist[]>([]);//机构向下人员列表
+const managerName = ref();//管理员姓名  回显使用
+//选中机构获取人员
+const managerModalNodeClick = (id: string) => {
+  api.agreementApi.getUserByDeptId(id, '').then((res: any) => {
+    if (res.code == '200') {
+      managerlist.value = res.data;
+    }
+  });
+}
+const managerModalQuery = (searchValue: string) => {
+  api.agreementApi.getUserByDeptId('', searchValue).then((res: any) => {
+    if (res.code == '200') {
+      managerlist.value = res.data;
+    }
+  });
+}
+//选择管理人保存事件
+const managerModalsave = (id: string, name: string, deptName: string) => {
+  agreementFrom.custodianId = id;
+  managerName.value = name + ' - ' + deptName;
+  managerModalVisible.value = false;
+
+}
+//选择出单账号
+const orderAccountVisible = ref(false);
+const attributionName = ref();
+const attributionUserAbbr = ref();
+//保存事件
+const orderAccountSave = (id: string, name: string, userAbbr: string) => {
+  console.log(id, name, userAbbr)
+  agreementFrom.attributionId = id;
+  attributionName.value = `用户名:${name}\n简称:${userAbbr}`;
+  attributionUserAbbr.value = userAbbr;
+  orderAccountVisible.value = false;
+}
+</script>
+<style lang="scss" scoped>
+.disabled :deep(.el-upload--picture-card) {
+  display: none;
+}
+
+.agreementNotice {
+  width: max-content;
+  padding: 18px 24px;
+  border: 1px solid #eee;
+  border-radius: 4px;
+
+  &:hover {
+    background-color: #fafafa;
+  }
+
+  img {
+    width: 40px;
+    height: 40px;
+    margin-right: 20px;
+  }
+
+  span:first-child {
+    margin-bottom: 2px;
+    font-size: 14px;
+    color: #333;
+  }
+
+  span:last-child {
+    font-size: 14px;
+    color: #666;
+  }
+
+  .fileBtn {
+    margin-left: 40px;
+
+    .btn-icon {
+      margin: 0 10px;
+      font-size: 20px;
+      color: #999;
+      cursor: pointer;
+
+      &:hover {
+        color: #00a0f4;
+      }
+    }
+  }
+}
+
+.image-view {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 9999;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 100%;
+  background: rgb(0 0 0 / 60%);
+
+  img {
+    width: 800px;
+    height: 800px;
+    border-radius: 5px;
+  }
+}
+</style>

+ 236 - 0
src/views/quoteConfig/agreement/agreementCopy.vue

@@ -0,0 +1,236 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <PageMain>
+      <div class="headers">
+        <span class="strong">天勤恒邦吕梁协议 - 非晋M - 0136</span>
+        <el-descriptions :column="2" style="margin-top: 10px;">
+          <el-descriptions-item label="协议号:">{{ agreementInfo.agreementCode }}</el-descriptions-item>
+          <el-descriptions-item label="协议名称:">{{ agreementInfo.agreementTitle }}</el-descriptions-item>
+          <el-descriptions-item label="归属机构:">{{ agreementInfo.deptName }}</el-descriptions-item>
+          <el-descriptions-item label="授权机构:">{{ authDeptlength }}</el-descriptions-item>
+          <el-descriptions-item label="添加人:">
+            {{ agreementInfo.createBy }}
+          </el-descriptions-item>
+          <el-descriptions-item label="添加时间:">
+            {{ agreementInfo.createTime }}
+          </el-descriptions-item>
+        </el-descriptions>
+      </div>
+      <div class="content m-top-20 flex a-c">
+        <span>出单费用{{ feeData.length }}条</span>
+        <div class="grayBlock m-right-10 m-left-20 flex a-c j-s">
+          <span class="m-right-20" style="line-height: 14px;">有效状态</span>
+          <el-dropdown trigger="click" @command="validStateCommand">
+            <el-button link style="font-size: 14px;color: #333;"> {{ valid_statusName ? valid_statusName : '全部'
+            }}<el-icon>
+                <arrow-down />
+              </el-icon>
+            </el-button>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item :command="item.label" v-for="(item, index) in getDict('valid_status')" :key="index">{{
+                  item.label }}</el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </div>
+        <div class="grayBlock m-right-10 m-left-20 flex a-c j-s">
+          <span class="m-right-20" style="line-height: 14px;">费用状态</span>
+          <el-dropdown placement="bottom-start" @command="agreementStatusCommand">
+            <el-button link style="font-size: 14px;color: #333;">{{ is_enableName ? is_enableName : '全部' }}<el-icon>
+                <arrow-down />
+              </el-icon>
+            </el-button>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item :command="item.label" v-for="(item, index) in is_enable" :key="index">{{ item.label
+                }}</el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </div>
+      </div>
+      <ComplexTable :tableData="feeData" :columns="columns" :showIndex="false" :columnwidth="200" :showOperation="false"
+        :showSelect="true" uniqueIdName="costId" @selectionChange="selectionChange">
+      </ComplexTable>
+
+    </PageMain>
+    <FixedActionBar>
+      <ElButton @click="router.back()">
+        取消
+      </ElButton>
+      <ElButton type="primary" @click="agreementcopy" :disabled="!sysUpAndDownList.length">
+        复制协议
+      </ElButton>
+    </FixedActionBar>
+  </div>
+</template>
+
+<!-- 行为区域 -->
+<script lang='ts' setup>
+import { ref, reactive } from 'vue'
+import api from '@/api'
+import { useRoute, useRouter } from 'vue-router';
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { loadDicts, getDict } from '@/utils/composables/dictService';//字典组件
+const route = useRoute();
+const router = useRouter();
+const id = ref(route.query.id as string);//id参数
+interface Agreement {
+  [key: string]: string | number; // 协议名称
+}
+const agreementInfo = ref<Agreement>({}); // 初始值为 null
+const authDeptlength = ref<number>(0);
+const feeData = ref([]);//费用数据
+const valid_status = ref();
+const valid_statusName = ref();
+const is_enable = ref();
+const is_enableName = ref();
+const sysUpAndDownList = ref<string[]>([])//选中id
+const formInline = reactive({
+  validState: '',//协议信息
+  agreementStatus: "",//归属
+})
+const columns = [
+
+  {
+    prop: "costRules", label: "规则明细", width: '500',
+  },
+  {
+    prop: "productName", label: "保险产品", width: '120',
+  },
+  {
+    prop: "isEnabled", label: "费用状态", width: '100', component: "Tag",
+    formatter: (row: any) => {
+      let name = getDict('is_enable').find(val => val.value == row.isEnabled).label
+      if (name) {
+        return { type: 'primary', text: name };
+      }
+    }
+  },
+  {
+    prop: "approved", label: "有效状态", width: '120', component: "Tag", formatter: (row: any) => {
+      if (row.isValid == '0') {
+        return { type: 'primary', text: '生效中' }
+      } else {
+        return { type: 'danger', text: '已失效' }
+
+      }
+
+    }
+  },
+  { prop: "effectiveTime", label: "生效时间", width: '200' },
+  { prop: "failureTime", label: "失效时间", width: '200' },
+  {
+    prop: "ruleConditions", label: "交强费用", width: '300', formatter: (row: any) => {
+      let info = row.ruleConditions.find((val: any) => val.costType == '交强险');
+      if (info) {
+        return `入口:${info.inlet.toFixed(2)}% 出口:${info.export.toFixed(2)}%\n加投:${info.addThrow.toFixed(2)}% 手续费:${info.commission.toFixed(2)}%`
+      } else {
+        return '-';
+      }
+    }
+  },
+  {
+    prop: "ruleConditions", label: "商业费用", width: '300', formatter: (row: any) => {
+      let info = row.ruleConditions.find((val: any) => val.costType == '商业险');
+      if (info) {
+        return `入口:${info.inlet.toFixed(2)}% 出口:${info.export.toFixed(2)}%\n加投:${info.addThrow.toFixed(2)}% 手续费:${info.commission.toFixed(2)}%`
+      } else {
+        return '-';
+      }
+    }
+  },
+  {
+    prop: "ruleConditions", label: "驾意险费用", width: '300', formatter: (row: any) => {
+      let info = row.ruleConditions.find((val: any) => val.costType == '驾意险');
+      if (info) {
+        return `入口:${info.inlet.toFixed(2)}% 出口:${info.export.toFixed(2)}%\n加投:${info.addThrow.toFixed(2)}% 手续费:${info.commission.toFixed(2)}%`
+      } else {
+        return '-';
+      }
+    }
+  },
+];
+onBeforeMount(async () => {
+  await loadDicts(['valid_status', 'is_enable']);//获取多个字典
+  if (id.value) {
+    detailsInfo();//协议信息
+    findpage();
+    valid_status.value = getDict('valid_status')
+    is_enable.value = getDict('is_enable')
+  }
+})
+//协议详情
+const detailsInfo = () => {
+  api.agreementApi.agreementGet(id.value).then((res: any) => {
+    if (res.code == '200') {
+      res.data.agreement.fileName = processFileName(res.data.agreement.fileName)
+      res.data.agreement.fileSize = formatFileSize(res.data.agreement.fileSize);
+      agreementInfo.value = res.data.agreement;
+      authDeptlength.value = res.data.dept.length;
+    } else {
+    }
+  });
+}
+//费用数据查询
+function findpage() {
+  api.agreementApi.queryByFeePage({ pages: 1, size: 100, ...formInline }).then((res: any) => {
+    if (res.code == '200') {
+      feeData.value = res.data.records;
+    } else {
+    }
+  });
+}
+//费用复制
+const agreementcopy = () => {
+  api.agreementApi.agreementCopy({id:id.value,costIds:sysUpAndDownList.value}).then((res: any) => {
+    if (res.code == '200') {
+      ElMessage({
+        type: 'success',
+        message: res.msg,
+        plain: true,
+      })
+      router.back()
+    } else {
+    }
+  });
+}
+//多选事件
+const selectionChange = (val: any) => {
+  console.log(val)
+  sysUpAndDownList.value = val;
+}
+const validStateCommand = (e: any) => {
+  let info = valid_status.value.find((val: any) => val.label == e);
+  valid_statusName.value = info.label;
+  formInline.validState = info.value;
+  findpage();
+
+}
+const agreementStatusCommand = (e: any) => {
+  let info = is_enable.value.find((val: any) => val.label == e);
+  is_enableName.value = info.label;
+  formInline.agreementStatus = info.value;
+  findpage();
+}
+</script>
+<!-- 样式区域 -->
+<style lang='scss' scoped>
+.headers {
+  border-bottom: 1px solid #f2f2f2;
+}
+
+.grayBlock {
+  padding: 8px 10px;
+  font-size: 14px;
+  color: #666;
+  background: #f2f2f2;
+  border-radius: 4px;
+}
+
+:deep(.el-descriptions__cell) {
+  width: 33.33%;
+}
+</style>

+ 844 - 0
src/views/quoteConfig/agreement/agreementDetails.vue

@@ -0,0 +1,844 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <PageMain>
+      <div class="headers">
+        <div class="flex f-c a-start">
+          <div class="flex j-s a-c" style="width: 100%;">
+            <span class="title">{{ agreementInfo.agreementTitle }}</span>
+            <div>
+              <el-button type="default" @click="agreementAuth">协议授权</el-button>
+              <el-button type="default" plain @click="agreementEdit">编辑</el-button>
+              <el-dropdown trigger="click" @command="dropdownCommand" style="margin-left: 10px;">
+                <el-button plain>
+                  更多 <el-icon class="el-icon--right">
+                    <CaretBottom />
+                  </el-icon></el-button>
+                <template #dropdown>
+                  <el-dropdown-menu>
+                    <el-dropdown-item command="启用" v-if="agreementInfo.isEnable == '1'">启用</el-dropdown-item>
+                    <el-dropdown-item command="禁用" v-else>禁用</el-dropdown-item>
+                    <el-dropdown-item command="删除">删除</el-dropdown-item>
+                    <el-dropdown-item command="分配管理人">复制协议</el-dropdown-item>
+                  </el-dropdown-menu>
+                </template>
+              </el-dropdown>
+            </div>
+          </div>
+          <div style="margin-top: 10px;" class="flex">
+            <el-tag type="primary" style="margin-right: 20px;">{{
+              getDict('is_enable').find(val => val.value == agreementInfo.isEnable)?.label}}</el-tag>
+            <el-tag type="success">{{getDict('valid_status').find(val => val.value == agreementInfo.validStatus)?.label
+              }}</el-tag>
+          </div>
+        </div>
+        <el-descriptions :column="3" style="margin-top: 10px;">
+          <el-descriptions-item label="协议号:">{{ agreementInfo.agreementCode }}</el-descriptions-item>
+          <el-descriptions-item label="协议名称:">{{ agreementInfo.agreementTitle }}</el-descriptions-item>
+          <el-descriptions-item label="归属机构:">{{ agreementInfo.deptName }}</el-descriptions-item>
+          <el-descriptions-item label="授权机构:">{{ authDeptlength }}</el-descriptions-item>
+          <el-descriptions-item label="添加人:">
+            {{ agreementInfo.createBy }}
+          </el-descriptions-item>
+          <el-descriptions-item label="添加时间:">
+            {{ agreementInfo.createTime }}
+          </el-descriptions-item>
+        </el-descriptions>
+      </div>
+    </PageMain>
+    <PageMain class="agreement ">
+      <el-tabs v-model="activeName" class="demo-tabs">
+        <el-tab-pane label="协议信息" name="agreementInfo">
+          <el-descriptions title="基本信息" class="m-top-20" :column="3">
+            <el-descriptions-item label="协议名称:">{{ agreementInfo.agreementTitle
+            }}</el-descriptions-item>
+            <el-descriptions-item label="协议简称:">{{ agreementInfo.agreementAbbreviation
+            }}</el-descriptions-item>
+            <el-descriptions-item label="协议生效时间:">{{ agreementInfo.startDate }}</el-descriptions-item>
+            <el-descriptions-item label="协议失效时间:">{{ agreementInfo.endDate }}</el-descriptions-item>
+            <el-descriptions-item label="是否含增值税结算:">
+              {{ agreementInfo.withTax }}
+            </el-descriptions-item>
+            <el-descriptions-item label="结算周期:">T{{ agreementInfo.moneyTime }} </el-descriptions-item>
+            <el-descriptions-item label="协议描述:">{{ agreementInfo.agreementName }}</el-descriptions-item>
+          </el-descriptions>
+          <el-descriptions title="其他信息" class="m-top-20" :column="3">
+            <el-descriptions-item label="协议归属:">{{ agreementInfo.deptName }} </el-descriptions-item>
+            <el-descriptions-item label="保险公司:"> {{ agreementInfo.companyId }}</el-descriptions-item>
+            <el-descriptions-item label="协议类型:">{{ agreementInfo.agreementType }} </el-descriptions-item>
+            <el-descriptions-item label="管理人:"> {{ agreementInfo.custodianId }}</el-descriptions-item>
+            <el-descriptions-item label="对接人:"> {{ agreementInfo.dockingPerson }} </el-descriptions-item>
+            <el-descriptions-item label="对接人手机号:"> {{ agreementInfo.dockingPhone }} </el-descriptions-item>
+            <el-descriptions-item label="出单账号:"> {{ agreementInfo.attributionId }} </el-descriptions-item>
+          </el-descriptions>
+          <span class="strong" style="font-size: 16px;color: #303133;">协议文件</span>
+          <span class="strong m-top-20 m-btn-10" style="display: block;">协议费用政策告知单</span>
+          <div class="agreementNotice flex a-c j-s">
+            <div class="flex  a-c">
+              <img src="../../../assets/images/pdf.png" alt="">
+              <div class="flex f-c j-s">
+                <span>{{ agreementInfo.fileName }}</span>
+                <span>{{ agreementInfo.fileSize }}</span>
+              </div>
+            </div>
+            <div class="fileBtn flex a-c">
+              <el-upload :show-file-list="false" :auto-upload="false" accept=".pdf, .doc, .docx, .xls, .xlsx"
+                @change="uploadImage($event)" style="display: flex; align-items: center;">
+                <el-icon class="btn-icon">
+                  <Refresh />
+                </el-icon>
+              </el-upload>
+              <el-icon class="btn-icon" @click="Fileonload(agreementInfo.fileUrl, agreementInfo.fileName)">
+                <Download />
+              </el-icon>
+            </div>
+          </div>
+        </el-tab-pane>
+        <el-tab-pane label="费用管理" name="costManagement">
+          <span class="strong m-right-20">规则与费用</span>
+          <el-button type="primary" @click="addUnderwritingRule" plain>
+            添加承保规则
+            <template #icon>
+              <el-icon>
+                <Plus />
+              </el-icon>
+            </template>
+          </el-button>
+          <div class="flex f-c  RuleContainer" v-if="RulesList">
+            <div class="list-item" v-for="(item, index) in RulesList" :key="index">
+              <div class="header-title flex f-c ">
+                <div class="flex a-c j-s">
+                  <div class="flex a-c">
+                    <img src="../../../assets/images/fee.png" alt="">
+                    <span style="margin-right: 30px;font-size: 16px;" class="strong m-left-10">承保规则一{{ index + 1
+                      }}</span>
+                  </div>
+                  <div class="flex">
+                    <el-button link type="primary" :icon="Delete" @click="ruleDel(item.id)">删除</el-button>
+                    <el-button link type="primary" :icon="Edit" @click="ruleEdit(item.id)">编辑</el-button>
+                    <el-button v-if="!item.ruleFeeShow" link type="default"
+                      @click="item.ruleFeeShow = !item.ruleFeeShow">展开<el-icon class="m-left-10">
+                        <ArrowDownBold />
+                      </el-icon> </el-button>
+                    <el-button v-else link type="default" @click="item.ruleFeeShow = !item.ruleFeeShow">收起<el-icon
+                        class="m-left-10">
+                        <ArrowUpBold />
+                      </el-icon></el-button>
+                  </div>
+                </div>
+                <p class=" m-top-10" style="font-size: 14px;">规则描述:{{ item.ruleDescription }}</p>
+              </div>
+              <div class="header-content" v-if="item.ruleFeeShow">
+                <el-button plain type="primary" @click=" ruleFeeAdd(item)" class="m-top-20 m-btn-20">添加费用
+                  <template #icon>
+                    <el-icon>
+                      <Plus />
+                    </el-icon>
+                  </template>
+                </el-button>
+                <el-tabs :tab-position="ruleFeePosition" class="ruleFee" v-if="item.costVoList">
+                  <el-tab-pane v-for="(costitem, costindex) in item.costVoList" :key="costindex"
+                    :label="costitem.productName" :name="costitem.productName">
+                    <span class="strong">单交费用({{ costitem.ptlAgreementCostVoList.length }}条)</span>
+                    <ComplexTable :tableData="costitem.ptlAgreementCostVoList" :columns="columns" :maxHeight="250"
+                      :showIndex="false" :columnwidth="200" :showSelect="false">
+                      <template v-slot:operation="scope">
+                        <el-button type="primary" link @click="costitemEdit(scope.operationData.id, item)"> 编辑
+                        </el-button>
+                        <el-button type="primary" link @click="costitemDel(scope.operationData.id)"> 删除
+                        </el-button>
+                      </template>
+                    </ComplexTable>
+                  </el-tab-pane>
+                </el-tabs>
+                <Empty v-else />
+              </div>
+            </div>
+          </div>
+          <Empty v-else />
+        </el-tab-pane>
+      </el-tabs>
+    </PageMain>
+    <el-drawer v-model="addUnderwritingRuleShow" :direction="direction" :close-on-click-modal="false" size="50%">
+      <template #header>
+        <h4>添加承保规则</h4>
+      </template>
+      <template #default>
+        <div class="flex a-c j-s ">
+          <span class="strong title" style="margin-right: 20px;">规则条件</span>
+          <el-button type="primary" link @click="ruleConditionVisible = true">
+            添加条件
+            <template #icon>
+              <el-icon>
+                <Plus />
+              </el-icon>
+            </template>
+          </el-button>
+        </div>
+        <!-- 规则选择容器 -->
+        <rule-Condition-Container :rulesAttrList="rulesAttrListInfo.rulesAttrList" @Del="rulesAttrDel"
+          @selectChange="rulesAttrselectChange" />
+      </template>
+      <template #footer>
+        <div class="flex a-c j-c">
+          <el-button @click="addUnderwritingRuleShow = false">取消</el-button>
+          <el-button type="primary" @click="UnderwritingRuleSave">保存</el-button>
+        </div>
+      </template>
+    </el-drawer>
+    <!-- 协议授权弹窗 -->
+    <auth-Drawer :Drawer="agreementAuthDrawer" @cancel="agreementAuthDrawer = false" @save="DrawerSave"
+      @query="Drawerquery" :deptlist="deptauthList" :checkedKeys="checkedKeys" :checkedIds="checkedIds"></auth-Drawer>
+    <!-- 规则条件弹窗 -->
+    <RuleCondition v-if="ruleConditionVisible" :ruleConditionVisible="ruleConditionVisible"
+      @cancel="ruleConditionVisible = false" @save="ruleConditionSave" :attrIdList="rulesAttrListInfo.rulesAttrList" />
+    <!-- 规则费用抽屉组件 -->
+    <ruleFeeDialog v-if="ruleFeeVisible" :ruleFeeVisible="ruleFeeVisible" :agreementInfo="agreementInfo"
+      :RulesInfo="RulesInfo" @save="ruleFeeSave" @cancel="ruleFeeVisible = false" />
+    <!-- 规则费用编辑组件 -->
+    <ruleFeeDialogEdit :ruleFeeVisible="ruleFeeEditVisible" :headerTitle="ruleFeeHeaderTitle"
+      :agreementInfo="agreementInfo" :RulesInfo="RulesInfo" :costitemEditInfo="costitemEditInfo" @save="ruleFeeEditSave"
+      @cancel="ruleFeeEditVisible = false" />
+  </div>
+</template>
+<!-- 行为区域 -->
+<script lang='ts' setup>
+import api from '@/api';
+import { CaretBottom } from '@element-plus/icons-vue'
+import { ElMessage, ElMessageBox, DrawerProps, TabsInstance } from 'element-plus'
+import { ref, reactive } from 'vue'
+import { useRoute, useRouter } from 'vue-router';
+import { loadDicts, getDict } from '@/utils/composables/dictService';//字典组件
+import { Fileonload, formatFileSize,processFileName } from '@/utils/composables/fileProcessing';//文件处理工具
+import authDrawer from "./components/agreementAuth.vue";
+import ruleFeeDialog from "./components/ruleFeeDialog.vue";
+import ruleFeeDialogEdit from "./components/ruleFeeDialogEdit.vue";
+import { Delete, Edit } from '@element-plus/icons-vue'
+interface Agreement {
+  [key: string]: string | number; // 协议名称
+}
+const agreementInfo = ref<Agreement>({}); // 初始值为 null
+const route = useRoute();
+const router = useRouter();
+const id = ref(route.query.id as string);//id参数
+const activeName = ref('agreementInfo')//默认标签页
+const authDeptlength = ref<number>(0);
+onBeforeMount(async () => {
+  await loadDicts(['agreement_type', 'valid_status', 'is_enable', 'with_tax', 'audit_status']);//获取多个字典
+
+  if (id.value) {
+    detailsInfo();//协议信息
+    agreementRulesList();//承保规则
+    regionLicenseRegion('0');//车牌地区
+    regionLicenseNumber('14');//车牌号
+    rulesAttrListInfo.agreementId = id.value;//赋值协议id
+  }
+})
+//协议详情
+const detailsInfo = () => {
+  api.agreementApi.agreementGet(id.value).then((res: any) => {
+    if (res.code == '200') {
+      res.data.agreement.fileName=processFileName(res.data.agreement.fileName)
+      res.data.agreement.fileSize=formatFileSize(res.data.agreement.fileSize);
+      agreementInfo.value = res.data.agreement;
+
+      authDeptlength.value = res.data.dept.length;
+      imageUrl.value = res.data.agreement.fileUrl;
+    } else {
+    }
+  });
+}
+
+//编辑跳转
+const agreementEdit = () => {
+  router.push({
+    name: 'AgreementAdd',
+    query: {
+      id: id.value
+    }
+  })
+}
+//协议授权
+//检索事件
+const imageUrl = ref('');//协议文件
+const agreementAuthDrawer = ref(false)//授权弹窗
+const deptauthList = ref([]);//授权机构列表
+const checkedKeys = ref<string[]>([])//授权机构回显数据
+const checkedIds = ref<string[]>([])//授权机构回显id
+const Drawerquery = (name: any) => {
+  agreementAuth(name)
+}
+//授权保存
+const DrawerSave = (list: any) => {
+  api.agreementApi.authSave({ deptIds: list, id: id.value }).then((res: any) => {
+    if (res.code == '200') {
+      ElMessage({
+        type: 'success',
+        message: res.msg,
+        plain: true,
+      })
+      agreementAuthDrawer.value = false;
+    } else {
+    }
+  });
+}
+//协议授权机构获取
+const agreementAuth = (deptName: any) => {
+  let name = typeof deptName === 'string' ? deptName : '';
+  api.agreementApi.agreementdeptTree(id.value, name).then((res: any) => {
+    if (res.code == '200') {
+      agreementAuthDrawer.value = true;
+      deptauthList.value = res.data;
+      checkedKeys.value = getUncheckedIds(res.data);
+      checkedIds.value = getUncheckedIds(res.data);
+    } else {
+    }
+  });
+}
+// 递归回显数据
+function getUncheckedIds(data: any[]): string[] {
+  let ids: string[] = [];
+
+  data.forEach((item) => {
+    if (item.isCheck === 1) {
+      ids.push(item.id); // 如果 isCheck 为 0,添加 id
+    }
+    if (item.children && item.children.length > 0) {
+      // 如果有子项,递归调用
+      ids = ids.concat(getUncheckedIds(item.children));
+    }
+  });
+
+  return ids;
+}
+
+//更多操作集合
+const dropdownCommand = (e: any) => {
+  switch (e) {
+    case '启用':
+      api.agreementApi.sysUpAndDown({ ids: [id.value], opType: '0' }).then((res: any) => {
+        if (res.code == '200') {
+          ElMessage({
+            type: 'success',
+            message: res.msg,
+            plain: true,
+          })
+          router.push({
+            name: 'reload',
+          })
+        } else {
+        }
+      });
+      break;
+    case '禁用':
+      ElMessageBox.confirm(
+        '确定要禁用该协议吗,禁用后,该协议不在支持报价出单,并且已核保待缴费,待支付的订单,都将失效,请确认是否禁用?',
+        '禁用协议',
+        {
+          confirmButtonText: '确定禁用',
+          type: 'error',
+          center: true,
+          confirmButtonClass: 'el-button--danger',
+        }
+      )
+        .then(() => {
+          api.agreementApi.sysUpAndDown({ ids: [id.value], opType: '1' }).then((res: any) => {
+            if (res.code == '200') {
+              ElMessage({
+                type: 'success',
+                message: res.msg,
+                plain: true,
+              })
+              router.push({
+                name: 'reload',
+              })
+            }
+          });
+        })
+
+      break;
+    case '删除':
+      ElMessageBox.confirm(
+        '确定要删除该协议吗,删除后,该协议不在支持报价出单,并且已核保待缴费,待支付的订单,都将失效,请确认是否删除?',
+        '删除协议',
+        {
+          confirmButtonText: '确定删除',
+          type: 'error',
+          center: true,
+          confirmButtonClass: 'el-button--danger',
+        }
+      )
+        .then(() => {
+          api.agreementApi.agreementDelete({ ids: [id.value] }).then((res: any) => {
+            if (res.code == '200') {
+              ElMessage({
+                type: 'success',
+                message: res.msg,
+                plain: true,
+              })
+              router.back()
+            }
+          });
+        })
+      break;
+    case '复制协议':
+      break;
+    default:
+      break;
+  }
+
+}
+const licensePlateRegion = ref();//车牌地区
+const licensePlateNumber = ref();//车牌号
+const regionLicenseRegion = (value: string) => {
+  api.agreementApi.getAreaLicense(value).then((res: any) => {
+    if (res.code == '200') {
+      let data: any = [];
+      res.data.map((val: any) => {
+        data.push({
+          code: val.areaNumber,
+          name: val.license,
+        })
+        return val;
+      })
+      licensePlateRegion.value = data;
+    } else {
+    }
+  });
+}
+const regionLicenseNumber = (value: string) => {
+  api.agreementApi.getAreaLicense(value).then((res: any) => {
+    if (res.code == '200') {
+      let data: any = [];
+      res.data.map((val: any) => {
+        data.push({
+          code: val.areaNumber,
+          name: val.license,
+        })
+        return val;
+      })
+      licensePlateNumber.value = data;
+    } else {
+    }
+  });
+}
+//  添加承保规则
+const RulesList = ref();//承保规则数据
+const addUnderwritingRuleShow = ref(false);//添加规则抽屉
+
+const ruleFeePosition = ref<TabsInstance['tabPosition']>('left')
+const direction = ref<DrawerProps['direction']>('rtl')
+const addUnderwritingRule = () => {
+  addUnderwritingRuleShow.value = true;
+}
+//承保规则保存事件
+const UnderwritingRuleSave = () => {
+  if (rulesAttrListInfo.id) {//判断id是否存在
+    api.agreementApi.agreementRulesEdit(rulesAttrListInfo).then((res: any) => {
+      if (res.code == '200') {
+        ElMessage({
+          type: 'success',
+          message: res.msg,
+          plain: true,
+        })
+        addUnderwritingRuleShow.value = false;
+        router.push({
+          name: 'reload',
+        })
+      } else {
+      }
+    });
+  } else {
+    api.agreementApi.agreementRulesAdd(rulesAttrListInfo).then((res: any) => {
+      if (res.code == '200') {
+        ElMessage({
+          type: 'success',
+          message: res.msg,
+          plain: true,
+        })
+        addUnderwritingRuleShow.value = false;
+        router.push({
+          name: 'reload',
+        })
+      } else {
+      }
+    });
+  }
+
+}
+//获取承保规则列表
+const agreementRulesList = () => {
+  api.agreementApi.agreementRulesList(id.value).then((res: any) => {
+    if (res.code == '200') {
+      res.data.map((val: any) => {
+        val.ruleFeeShow = false;
+        return val;
+      })
+      RulesList.value = res.data;
+
+    } else {
+    }
+  });
+}
+//修改规则
+const ruleEdit = (id: string) => {
+  rulesAttrListInfo.id = id;
+  addUnderwritingRuleShow.value = true;
+  api.agreementApi.agreementRulesGet(id).then((res: any) => {
+    if (res.code == '200') {
+      rulesAttrListInfo.rulesAttrList = processArray(res.data.costsAttrLinks);
+    } else {
+    }
+  });
+}
+//添加车牌和车牌地区
+const processArray = (array: any[]) => {
+  return array.map((val) => {
+    switch (val.attrType) {
+      case 'inputNumber':
+      case 'datetime':
+        if (!val.operator) {
+          val.operator = '1'; // 设置默认值
+        }
+        break;
+      default:
+        break;
+    }
+    if (val.attrCode == 'LicenseNo') {
+      val.dictLabal = licensePlateNumber.value;
+    }
+    if (val.attrCode == 'LicenseArea') {
+      val.dictLabal = licensePlateRegion.value;
+    }
+    return val;
+  });
+};
+//删除规则
+const ruleDel = (id: string) => {
+  ElMessageBox.confirm(
+    '确定要删除该承保规则吗,删除后,该承保规则的费用也将同步删除,请确认是否删除?',
+    '删除承保规则',
+    {
+      confirmButtonText: '确定删除',
+      type: 'error',
+      center: true,
+      confirmButtonClass: 'el-button--danger',
+    }
+  )
+    .then(() => {
+      api.agreementApi.agreementRulesDelete(id).then((res: any) => {
+        if (res.code == '200') {
+          agreementRulesList();
+          ElMessage({
+            type: 'success',
+            message: res.msg,
+            plain: true,
+          })
+        }
+      });
+    })
+}
+//添加费用
+const ruleFeeVisible = ref(false);//规则费用弹窗
+const ruleFeeEditVisible = ref(false);//编辑弹窗
+const ruleFeeHeaderTitle = ref();//编辑弹窗title
+const costitemEditInfo = ref();
+const RulesInfo = ref({});
+const columns = [
+  { prop: "costRules", label: "费用规则", width: '200', },
+  {
+    prop: "ptlAgreementCostTypeList", label: "交强险费用", width: '300', formatter: (row: any) => {
+      let info = row.ptlAgreementCostTypeList.find((val: any) => val.costType == '交强险');
+      if (info) {
+        return `入口:${info.inlet.toFixed(2)}% 出口:${info.export.toFixed(2)}%\n加投:${info.addThrow.toFixed(2)}% 手续费:${info.commission.toFixed(2)}%`
+      }
+    }
+  },
+  {
+    prop: "agreementAbbreviation", label: "商业险费用", width: '300', formatter: (row: any) => {
+      let info = row.ptlAgreementCostTypeList.find((val: any) => val.costType == '商业险');
+      if (info) {
+        return `入口:${info.inlet.toFixed(2)}% 出口:${info.export.toFixed(2)}%\n加投:${info.addThrow.toFixed(2)}% 手续费:${info.commission.toFixed(2)}%`
+      }
+    }
+  },
+  {
+    prop: "deptName", label: "驾意险费用", width: '300', formatter: (row: any) => {
+      let info = row.ptlAgreementCostTypeList.find((val: any) => val.costType == '驾意险');
+      if (info) {
+        return `入口:${info.inlet.toFixed(2)}% 出口:${info.export.toFixed(2)}%\n加投:${info.addThrow.toFixed(2)}% 手续费:${info.commission.toFixed(2)}%`
+      }
+    }
+  },
+  {
+    prop: "auditMethod", label: "审核方式", width: '200',
+    formatter: (row: any) => {
+      if (row.auditMethod == '0') {
+        return '自动审核'
+      } else {
+        return '手动审核'
+      }
+    }
+  },
+  {
+    prop: "effectiveTime", label: "有效期", width: '200',
+    formatter: (row: any) => {
+      return `起${row.effectiveTime}\n止${row.failureTime}`
+    }
+  },
+  {
+    prop: "isEnabled", label: "费用状态", width: '200', component: "Tag",
+    formatter: (row: any) => {
+      let name = getDict('is_enable').find(val => val.value == row.isEnabled).label
+      if (name) {
+        return { type: 'danger', text: name };
+      } else {
+        return { type: 'primary', text: name };
+      }
+    }
+  },
+  {
+    prop: "approved", label: "审核状态", width: '200', component: "Tag",
+    formatter: (row: any) => {
+      let name = getDict('audit_status').find(val => val.value == row.approved).label
+      if (name) {
+        return { type: 'warning', text: name };
+      }
+    }
+  },
+  {
+    prop: "isValid", label: "有效状态", width: '200',
+    component: "Tag",
+    formatter: (row: any) => {
+      let name = getDict('valid_status').find(val => val.value == row.isValid).label
+      if (name) {
+        return { type: 'danger', text: name };
+      }
+    }
+  },
+];
+const ruleFeeAdd = (item: any) => {
+  RulesInfo.value = item;
+  ruleFeeVisible.value = true;
+}
+//费用提交
+const ruleFeeSave = (data: object) => {
+
+  api.agreementApi.ptlAgreementCostsave(data).then((res: any) => {
+    if (res.code == '200') {
+      ruleFeeVisible.value = false;
+    } else {
+    }
+  });
+}
+const ruleFeeEditSave = (data: object) => {
+  api.agreementApi.AgreementFeeupdate(data).then((res: any) => {
+    if (res.code == '200') {
+      ElMessage({
+        type: 'success',
+        message: res.msg,
+        plain: true,
+      })
+    } else {
+    }
+  });
+}
+//编辑删除费用
+const costitemEdit =  (id: string, item: any) => {
+  RulesInfo.value = item;
+   api.agreementApi.AgreementFeeById(id).then((res: any) => {
+    if (res.code == '200') {
+      res.data.ruleConditions = processArray(res.data.ruleConditions);
+      ruleFeeHeaderTitle.value = res.data.productName;
+      costitemEditInfo.value = res.data;
+      ruleFeeEditVisible.value = true;
+    } else {
+    }
+  });
+}
+
+//删除费用
+const costitemDel = (id: string) => {
+  ElMessageBox.confirm(
+    '确定要删除该费用,请确认是否删除?',
+    '删除费用',
+    {
+      confirmButtonText: '确定删除',
+      type: 'error',
+      center: true,
+      confirmButtonClass: 'el-button--danger',
+    }
+  )
+    .then(() => {
+      api.agreementApi.AgreementFeedeleteByIds([id]).then((res: any) => {
+        if (res.code == '200') {
+          agreementRulesList();
+          ElMessage({
+            type: 'success',
+            message: res.msg,
+            plain: true,
+          })
+        } else {
+        }
+      });
+    })
+}
+interface rulesAttrListInfo {
+  id: string,
+  agreementId: string,
+  rulesAttrList: any[],
+}
+const ruleConditionVisible = ref(false);//规则条件弹窗
+const rulesAttrListInfo = reactive<rulesAttrListInfo>({
+  id: '',
+  agreementId: "",
+  rulesAttrList: []
+});//规则条件列表数据
+const ruleConditionSave = (list: string[]) => {//获取规则条件选中集合
+
+  rulesAttrListInfo.rulesAttrList = list;
+  ruleConditionVisible.value = false;
+}
+//条件删除
+const rulesAttrDel = (index: number) => {
+  rulesAttrListInfo.rulesAttrList.splice(index, 1)
+}
+//多选框事件数据格式处理
+const rulesAttrselectChange = (item: any) => {
+  rulesAttrListInfo.rulesAttrList[item.index].min = '';
+  item.e.map((ele: any) => {
+    rulesAttrListInfo.rulesAttrList[item.index].min += ele + ',';
+  })
+  // 去掉最后一个逗号
+  if (rulesAttrListInfo.rulesAttrList[item.index].min.endsWith(',')) {
+    rulesAttrListInfo.rulesAttrList[item.index].min = rulesAttrListInfo.rulesAttrList[item.index].min.slice(0, -1);
+  }
+}
+//协议文件处理
+//上传
+const uploadImage =  (file: any) => {
+  let files = file.raw
+  const params = new FormData(); // 声明formData数据类型
+  params.append("file", files);
+  api.commonApi.fileUpload(params).then((res: any) => {
+    if (res.code == '200') {
+      // imageName.value = processFileName(res.data.fileName);
+      imageUrl.value = res.data.downloadUrl;
+      api.agreementApi.agreementEditFile({ id: id.value, fileId: res.data.id }).then((res: any) => {
+        if (res.code == '200') {
+
+        }
+      });
+    } else {
+    }
+  });
+
+}
+</script>
+<!-- 样式区域 -->
+<style lang='scss' scoped>
+:deep(.demo-tabs .el-tabs__item) {
+  height: 60px;
+  font-size: 20px;
+  font-weight: bold !important;
+}
+
+:deep(.ruleFee .el-tabs__item) {
+  height: 40px;
+  font-size: 14px;
+  font-weight: 500 !important;
+}
+
+:deep(.agreement .p-5) {
+  padding-top: 0;
+}
+
+:deep(.el-descriptions__cell) {
+  width: 33.33%;
+}
+
+:deep(.my-label) {
+  font-weight: bold !important;
+}
+
+.headers {
+  .title {
+    font-size: 20px;
+    font-weight: bold;
+    color: #333;
+  }
+}
+
+.RuleContainer {
+  margin-top: 20px;
+
+  .list-item {
+    padding: 18px 16px;
+    margin-bottom: 20px;
+    border: 1px solid #e5e5e5;
+    border-radius: 10px;
+
+    .empty {
+      font-size: 14px;
+      color: #666;
+      text-align: center;
+    }
+  }
+}
+
+.agreementNotice {
+  width: max-content;
+  padding: 18px 24px;
+  border: 1px solid #eee;
+  border-radius: 4px;
+
+  &:hover {
+    background-color: #fafafa;
+  }
+
+  img {
+    width: 40px;
+    height: 40px;
+    margin-right: 20px;
+  }
+
+  span:first-child {
+    margin-bottom: 2px;
+    font-size: 14px;
+    color: #333;
+  }
+
+  span:last-child {
+    font-size: 14px;
+    color: #666;
+  }
+
+  .fileBtn {
+    margin-left: 40px;
+
+    .btn-icon {
+      margin: 0 10px;
+      font-size: 20px;
+      color: #999;
+      cursor: pointer;
+
+      &:hover {
+        color: #00a0f4;
+      }
+    }
+  }
+}
+
+.title {
+  display: flex;
+
+  &::before {
+    display: inline-block;
+    width: 4px;
+    height: 18px;
+    margin-right: 20px;
+    content: "";
+    background-color: #0083ff;
+  }
+}
+</style>

+ 503 - 0
src/views/quoteConfig/agreement/agreementIndex.vue

@@ -0,0 +1,503 @@
+<template>
+  <div>
+    <PageMain class="vh100">
+      <SearchForm :fields="fields" :formInline="formInline" :onSearch="agreementQuery" :onReset="resetForm" />
+      <ComplexTable :tableData="tableData" :columns="columns" :showIndex="false" :columnwidth="200" :showSelect="true"
+        :pageInfo="pageInfo" @getList="getList" @selectionChange="selectionChange">
+        <template v-slot:table-title-btn>
+          <RouterLink :to="{ name: 'AgreementAdd' }">
+            <el-button type="primary">
+              添加协议
+              <template #icon>
+                <el-icon>
+                  <Plus />
+                </el-icon>
+              </template>
+            </el-button>
+          </RouterLink>
+          <el-dropdown placement="bottom-start">
+            <el-button > 批量管理<el-icon>
+                <arrow-down />
+              </el-icon>
+            </el-button>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item :disabled="!sysUpAndDownList.length" @click="batchDel">删除</el-dropdown-item>
+                <el-dropdown-item :disabled="!sysUpAndDownList.length" @click="batchdisable">禁用</el-dropdown-item>
+                <el-dropdown-item :disabled="!sysUpAndDownList.length" @click="batchenable">启用</el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </template>
+        <template v-slot:operation="scope">
+          <el-button type="primary" link @click="agreemenUpdate(scope.operationData)"> 详情
+          </el-button>
+          <el-dropdown placement="bottom-start" trigger="click">
+            <span class="el-dropdown-link">
+              更 多
+            </span>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item @click="enable(scope.operationData.id)">启用</el-dropdown-item>
+                <el-dropdown-item @click="disable(scope.operationData.id)">禁用</el-dropdown-item>
+                <el-dropdown-item @click="del(scope.operationData.id)">删除</el-dropdown-item>
+                <el-dropdown-item @click="agreemenCopy(scope.operationData.id)">复制协议</el-dropdown-item>
+                <el-dropdown-item @click="batchAuthorization(scope.operationData.id)">协议授权</el-dropdown-item>
+                <el-dropdown-item @click="cost(scope.operationData.id)">费用管理</el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </template>
+        <template v-slot:search="scope">
+          {{ scope.slotData.label }}
+        </template>
+      </ComplexTable>
+    </PageMain>
+    <auth-Drawer :Drawer="agreementAuthDrawer" @cancel=" agreementAuthDrawer = false" @save="DrawerSave"
+      @query="Drawerquery" :deptlist="deptauthList" :checkedKeys="checkedKeys" :checkedIds="checkedIds"></auth-Drawer>
+
+
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive } from 'vue'
+import { RouterLink, useRouter } from "vue-router";
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { loadDicts, getDict } from '@/utils/composables/dictService';//字典组件
+import authDrawer from "./components/agreementAuth.vue";
+import api from '@/api';
+interface PageInfo {
+  pageNum: number;
+  pageSize: number;
+  sizes: number[];
+  total: number;
+}
+onBeforeMount(async () => {
+  await loadDicts(['agreement_type', 'valid_status', 'is_enable', 'with_tax']);//获取多个字典
+  findpage();//列表数据
+  getDeptTree();
+  agreement_type.value = getDict('agreement_type');//协议类型
+  valid_status.value = getDict('valid_status');//生效状态
+  is_enable.value = getDict('is_enable');//协议状态
+  with_tax.value = getDict('with_tax')//是否含税
+});
+
+const parentIdprops = {
+  expandTrigger: 'hover' as const,
+  value: 'id',
+  label: 'name',
+  checkStrictly: true,
+}
+const tableData = ref([
+]);
+const sysUpAndDownList = ref<string[]>([])//选中id
+const provinceoptions = ref<string[]>([])//省市区回显数据
+const deptOptions = ref()//机构数据
+const agreement_type = ref();
+const valid_status = ref();
+const is_enable = ref();
+const with_tax = ref();
+const router = useRouter();
+const pageInfo = ref({
+  pageNum: 1,
+  pageSize: 20,
+  sizes: [10, 20, 30, 40, 50],
+  total: 100,
+});
+const formInline = reactive({
+  searchValue: '',//协议信息
+  deptId: "",//归属
+  companyId: '',//保险公司id
+  agreementType: '',//协议类型
+  validStatus: "",//有效状态
+  isEnable: "",//协议状态
+  withTax: "",
+  dockingPerson: "",
+  attributionName: "",
+  takeStartDate: "",
+  takeEndDate: "",
+  loseStartDate: "",
+  loseEndDate: "",
+  isExternal:'0',
+  value1:[],
+  value2:[],
+})
+
+//表格字段数据
+const fields = ref([
+  { label: "协议信息", model: "searchValue", type: "input", placeholder: "输入代码,名称,简称查找" },
+  {
+    label: "协议归属", model: "deptId", type: "cascader", options: deptOptions, props: parentIdprops, onChange: (value: any) => {
+      formInline.deptId = value[value.length - 1];
+    }
+  },
+  {
+    label: "保险公司", model: "companyId", type: "select", placeholder: "选择保险公司",
+  },
+  {
+    label: "协议类型", model: "agreementType", type: "select", placeholder: "选择保险公司", options: agreement_type,
+  },
+  {
+    label: "生效状态", model: "validStatus", type: "select", placeholder: "选择生效状态", options: valid_status,
+  },
+  {
+    label: "协议状态", model: "isEnable", type: "select", placeholder: "选择协议状态", options: is_enable,
+  },
+  {
+    label: "是否含税结算", model: "withTax", type: "select", placeholder: "请选择", options: with_tax,
+  },
+  {
+    label: "对接人", model: "dockingPerson", type: "input", placeholder: "输入姓名,手机号查找"
+  },
+  {
+    label: "出单账号", model: "attributionName", type: "input", placeholder: "选择出单账号"
+  },
+  {
+    label: "生效时间", model: "value1", type: "date", dateType: "datetimerange", startplaceholder: "开始时间", endplaceholder: "结束时间",
+    onChange: (value: any) => {
+      console.log(value)
+      formInline.takeStartDate = value[0];
+      formInline.takeEndDate = value[1];
+    }
+  },
+  {
+    label: "失效时间", model: "value2", type: "date", dateType: "datetimerange", startplaceholder: "开始时间", endplaceholder: "结束时间",
+    onChange: (value: any) => {
+      formInline.loseStartDate = value[0];
+      formInline.loseEndDate = value[1];
+    }
+  },
+]);
+
+//数据查询
+function findpage() {
+  api.agreementApi.agreementList({ pages: pageInfo.value.pageNum, size: pageInfo.value.pageSize, ...formInline }).then((res: any) => {
+    if (res.code == '200') {
+      tableData.value = res.data.records;
+      pageInfo.value.pageNum = res.data.current;
+      pageInfo.value.pageSize = res.data.size;
+      pageInfo.value.total = res.data.total;
+    } else {
+    }
+  });
+}
+const columns = [
+  { prop: "agreementCode", label: "协议编号", width: '200', },
+  {
+    prop: "agreementTitle", label: "协议名称", width: '200',
+  },
+  { prop: "agreementAbbreviation", label: "协议简称", width: '120', },
+  {
+    prop: "deptName", label: "归属", width: '120',
+  },
+  {
+    prop: "agreementType", label: "协议类型", width: '200', formatter: (row: any) => {
+      let name = getDict('agreement_type').find(val => val.value == row.agreementType).label
+      return name
+    }
+  },
+  {
+    prop: "isEnable", label: "协议状态", width: '100',
+    component: "Tag",
+    formatter: (row: any) => {
+      let name = getDict('is_enable').find(val => val.value == row.isEnable).label
+      if (name) {
+        return { type: 'primary', text: name };
+      } else {
+        return { type: 'primary', text: name };
+      }
+    }
+
+  },
+  {
+    prop: "validStatus", label: "生效状态", width: '120', component: "Tag",
+    formatter: (row: any) => {
+      let name = getDict('valid_status').find(val => val.value == row.validStatus).label
+      if (name) {
+        return { type: 'danger', text: name };
+      } else {
+        return { type: 'danger', text: name };
+      }
+    }
+  },
+  {
+    prop: "withTax", label: "是否含税结算", width: '180',
+    formatter: (row: any) => {
+      let name = getDict('with_tax').find(val => val.value == row.withTax).label
+      return name
+    }
+  },
+  {
+    prop: "moneyTime", label: "结算周期", width: '120',
+    formatter: (row: any) => {
+      return 'T' + row.moneyTime
+    }
+  },
+  { prop: "username", label: "出单账号", width: '220',
+  formatter: (row: any) => {
+      return `用户名:${row.username}\n简称:${row.userAbbr}`
+    }
+  },
+  { prop: "company", label: "保险公司", width: '200' },
+  { prop: "dockingPerson", label: "对接人", width: '200' },
+  { prop: "custodianId", label: "管理人", width: '200' },
+  { prop: "startDate", label: "生效时间", width: '200' },
+  { prop: "endDate", label: "失效时间", width: '200' },
+  { prop: "createTime", label: "添加时间", width: '200' },
+  { prop: "createBy", label: "添加人", width: '200' },
+  { prop: "fileName", label: "协议文件", width: '200' },
+];
+//分页事件
+function getList(item: PageInfo) {
+  pageInfo.value.pageNum = item.pageNum;
+  pageInfo.value.pageSize = item.pageSize;
+  findpage();
+
+};
+//机构授权
+const agreementAuthDrawer = ref(false)//授权弹窗
+const deptauthList = ref([]);//授权机构列表
+const checkedKeys = ref<string[]>([])//授权机构回显数据
+const checkedIds = ref<string[]>([])//授权机构回显id
+//授权机构检索
+const Drawerquery = (name: any) => {
+  agreementAuth(name)
+}
+//授权保存
+const DrawerSave = (list: any) => {
+  api.agreementApi.authSave({ deptIds: list, id: agreementId.value }).then((res: any) => {
+    if (res.code == '200') {
+      ElMessage({
+        type: 'success',
+        message: res.msg,
+        plain: true,
+      })
+      agreementAuthDrawer.value = false;
+    } else {
+    }
+  });
+}
+//协议授权
+const agreementAuth = (deptName: any) => {
+  let name = typeof deptName === 'string' ? deptName : '';
+  api.agreementApi.agreementdeptTree(agreementId.value, name).then((res: any) => {
+    if (res.code == '200') {
+      agreementAuthDrawer.value = true;
+      deptauthList.value = res.data;
+      checkedKeys.value = getUncheckedIds(res.data);
+      checkedIds.value = getUncheckedIds(res.data);
+    } else {
+    }
+  });
+}
+// 定义递归函数
+function getUncheckedIds(data: any[]): string[] {
+  let ids: string[] = [];
+
+  data.forEach((item) => {
+    if (item.isCheck === 1) {
+      ids.push(item.id); // 如果 isCheck 为 0,添加 id
+    }
+    if (item.children && item.children.length > 0) {
+      // 如果有子项,递归调用
+      ids = ids.concat(getUncheckedIds(item.children));
+    }
+  });
+
+  return ids;
+}
+
+function agreemenUpdate(item: any) {
+  router.push({
+    name: 'AgreementDetails',
+    query: {
+      id: item.id
+    }
+  })
+}
+const cost=(id:string)=>{
+  router.push({
+    name: 'CostManagement',
+    query: {
+      id: id
+    }
+  })
+}
+//多选事件
+const selectionChange = (val: any) => {
+  sysUpAndDownList.value = val;
+}
+//协议授权
+const agreementId = ref()
+const batchAuthorization = (value: string) => {
+  agreementId.value = value;
+  api.agreementApi.agreementdeptTree(value, '').then((res: any) => {
+    if (res.code == '200') {
+      agreementAuthDrawer.value = true;
+      deptauthList.value = res.data;
+      checkedKeys.value = getUncheckedIds(res.data);
+      checkedIds.value = getUncheckedIds(res.data);
+    } else {
+    }
+  });
+
+}
+//复制协议
+const agreemenCopy = (id:string) => {
+  router.push({
+    name: 'AgreementCopy',
+    query: {
+      id: id
+    }
+  })
+}
+//禁用
+const disable = (value: string) => {
+  ElMessageBox.confirm(
+    '确定要禁用该协议吗,禁用后,该协议不在支持报价出单,并且已核保待缴费,待支付的订单,都将失效,请确认是否禁用?',
+    '禁用协议',
+    {
+      confirmButtonText: '确定禁用',
+      type: 'error',
+      center: true,
+      confirmButtonClass: 'el-button--danger',
+    }
+  )
+    .then(() => {
+      sysUpAndDown('1', [value]);
+    })
+}
+//启用
+const enable = (value: string) => {
+  sysUpAndDown('0', [value]);
+}
+const del = (value: string) => {
+  ElMessageBox.confirm(
+    '确定要删除该协议吗,删除后,该协议不在支持报价出单,并且已核保待缴费,待支付的订单,都将失效,请确认是否删除?',
+    '删除协议',
+    {
+      confirmButtonText: '确定删除',
+      type: 'error',
+      center: true,
+      confirmButtonClass: 'el-button--danger',
+    }
+  )
+    .then(() => {
+      api.agreementApi.agreementDelete([value]).then((res: any) => {
+        if (res.code == '200') {
+          ElMessage({
+            type: 'success',
+            message: res.msg,
+            plain: true,
+          })
+          router.push({
+            name: 'reload',
+          })
+        }
+      });
+    })
+}
+//批量删除
+const batchDel = () => {
+  ElMessageBox.confirm(
+    '确定要删除该协议吗,删除后,该协议不在支持报价出单,并且已核保待缴费,待支付的订单,都将失效,请确认是否删除?',
+    '删除协议',
+    {
+      confirmButtonText: '确定删除',
+      type: 'error',
+      center: true,
+      confirmButtonClass: 'el-button--danger',
+    }
+  )
+    .then(() => {
+      api.agreementApi.agreementDelete(sysUpAndDownList.value).then((res: any) => {
+        if (res.code == '200') {
+          ElMessage({
+            type: 'success',
+            message: res.msg,
+            plain: true,
+          })
+          router.push({
+            name: 'reload',
+          })
+        }
+      });
+    })
+
+}
+//批量禁用
+const batchdisable = () => {
+  ElMessageBox.confirm(
+    '确定要禁用该协议吗,禁用后,该协议不在支持报价出单,并且已核保待缴费,待支付的订单,都将失效,请确认是否禁用?',
+    '禁用协议',
+    {
+      confirmButtonText: '确定禁用',
+      type: 'error',
+      center: true,
+      confirmButtonClass: 'el-button--danger',
+    }
+  )
+    .then(async () => {
+      await sysUpAndDown('1', undefined);
+    })
+    .catch(() => {
+    })
+
+
+}
+//批量启用
+const batchenable = async () => {
+  await sysUpAndDown('0', undefined);
+}
+const agreementQuery = () => {
+  findpage();
+}
+const sysUpAndDown = async (type: string, list?: string[]) => {
+  await api.agreementApi.sysUpAndDown({ ids: list ? list : sysUpAndDownList.value, opType: type }).then((res: any) => {
+    if (res.code == '200') {
+      findpage();
+      ElMessage({
+        type: 'success',
+        message: res.msg,
+        plain: true,
+      })
+    }
+  });
+}
+const resetForm = () => {
+  formInline.searchValue = '';
+  provinceoptions.value = [];
+  findpage();
+}
+const getDeptTree = () => {
+  api.institutionApi.institutionList({}).then((res: any) => {
+    if (res.code == '200') {
+      deptOptions.value = res.data;
+    } else {
+    }
+  });
+}
+</script>
+
+<style lang="scss" scoped>
+.demo-form-inline .el-input {
+  --el-input-width: 220px;
+}
+
+.demo-form-inline .el-select {
+  --el-select-width: 220px;
+}
+
+:deep(.el-dropdown-link) {
+  display: flex;
+  align-items: center;
+  margin-left: 15px;
+  color: var(--el-color-primary);
+  cursor: pointer;
+}
+
+.el-inputs {
+  width: 100% !important;
+}
+</style>

+ 83 - 0
src/views/quoteConfig/agreement/components/agreementAuth.vue

@@ -0,0 +1,83 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <el-drawer v-model="props.Drawer" :direction="direction" @open="DrawerOpen" :close-on-click-modal="false">
+      <template #header>
+        <h4>参与机构</h4>
+      </template>
+      <template #default>
+        <div class="flex f-c">
+          <div>
+            <el-input v-model="deptName" class="m-right-20" style="width: 240px;" placeholder="输入名称"
+              :prefix-icon="Search" />
+            <el-button @click="query">查询</el-button>
+          </div>
+          <el-checkbox v-model="checkedALL" label="全选" size="large" />
+          <el-tree :data="props.deptlist" show-checkbox node-key="id" ref="treeRef" :props="defaultProps"
+            :default-expand-all="true" @check="handleCheck" :default-checked-keys="props.checkedKeys"
+            :check-strictly="true" />
+        </div>
+      </template>
+      <template #footer>
+        <div>
+          <el-button @click="cancel">取消</el-button>
+          <el-button type="primary" @click="save">保存</el-button>
+        </div>
+      </template>
+    </el-drawer>
+  </div>
+</template>
+
+<!-- 行为区域 -->
+<script lang="ts" setup>
+import { defineProps, defineEmits } from "vue";
+import { DrawerProps } from 'element-plus';
+import { Search } from '@element-plus/icons-vue'
+const props = defineProps<{
+  Drawer: boolean,
+  deptlist: string[]
+  checkedKeys: string[]
+  checkedIds: string[]
+}>();
+const emits = defineEmits(["cancel", 'save', 'DrawerOpen', 'query']);
+const defaultProps = {
+  children: 'children',
+  label: 'name',
+}
+const direction = ref<DrawerProps['direction']>('rtl')
+const deptName = ref('');
+
+
+const checkedALL = ref(false)
+const ids = ref()//id集合
+const DrawerOpen = () => {
+
+}
+watch(props, () => {
+  if (props.Drawer) {
+    ids.value = props.checkedIds;
+  }
+
+})
+const cancel = () => {
+  emits('cancel')
+}
+const save = () => {
+    emits('save', ids.value)
+}
+const query = () => {
+  emits('query', deptName.value)
+
+}
+const handleCheck = (
+  _checkedNodes: any[],
+  checkedInfo: {
+    checkedKeys: any[];
+    checkedNodes: any[];
+    halfCheckedKeys: any[];
+    halfCheckedNodes: any[];
+  }
+) => {
+  ids.value = checkedInfo.checkedKeys;
+};
+</script>

+ 111 - 0
src/views/quoteConfig/agreement/components/managerModal.vue

@@ -0,0 +1,111 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <el-dialog v-model="props.managerModal" title="选择管理人" width="40%"  @close="cancel">
+      <div class="flex a-c">
+        <el-input v-model="searchValue" style="width: 240px;margin-right: 10px;" clearable placeholder="输入姓名,手机号,工号查找" />
+        <el-button type="primary" @click='searchValueclick'>查询</el-button>
+      </div>
+      <div class="deptlist flex ">
+        <div class="area" style="border-right: 1px solid #dcdfe6;" v-show="!searchValueShow">
+          <el-tree style="max-width: 600px;" :data="props.deptOptions" :props="parentIdprops" :highlight-current="true"
+            @node-click="handleNodeClick" />
+        </div>
+        <div class="area">
+          <el-radio-group v-model="userId">
+            <div class="flex f-c">
+              <el-radio :value="item.userId" v-for="(item,index) in managerlist" @change="radioChange" :key="index" class="m-btn-20">
+                <div>{{ item.name }}</div>
+                <div>{{ item.userId }}</div>
+              </el-radio>
+            </div>
+          </el-radio-group>
+        </div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="cancel">取消</el-button>
+          <el-button type="primary" @click="save">
+            确定
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<!-- 行为区域 -->
+<script lang='ts' setup>
+import api from '@/api';
+import { ref, reactive } from 'vue'
+import { defineProps, defineEmits } from "vue";
+
+interface Tree {
+  id: string
+  children?: Tree[]
+}
+interface managerlist{
+  id:string,
+  userId:string,
+  name:string,
+  deptName:string,
+}
+const props = defineProps<{
+  managerModal: boolean,
+  deptOptions: string[]
+  managerlist: managerlist[],
+}>();
+const parentIdprops = {
+  expandTrigger: 'hover' as const,
+  value: 'id',
+  label: 'name',
+  checkStrictly: true,
+}
+const emits = defineEmits(["cancel", 'save', 'DrawerOpen', 'query', 'NodeClick','radioChange']);
+const userId = ref()
+const username = ref()
+const deptName=ref();
+const searchValue=ref();
+const searchValueShow=ref(false);
+watch(searchValue,()=>{
+  if(!searchValue.value){
+  searchValueShow.value=false;
+  }
+})
+const handleNodeClick = (data: Tree) => {
+  emits('NodeClick', data.id)
+}
+const cancel = () => {
+  emits('cancel')
+}
+const save = () => {
+  emits('save',userId.value,username.value,deptName.value)
+}
+
+const searchValueclick=()=>{
+  searchValueShow.value=true;
+  emits('query',searchValue.value)
+}
+const radioChange=(value:any)=>{
+  let name=props.managerlist.find(val=>val.userId==value)?.name;
+  let deptname=props.managerlist.find(val=>val.userId==value)?.deptName;
+  username.value=name;
+  deptName.value=deptname;
+}
+</script>
+<!-- 样式区域 -->
+<style lang='scss' scoped>
+.deptlist {
+  height: 400px;
+  margin-top: 20px;
+  border: 1px solid #dcdfe6;
+  border-radius: 5px;
+
+  .area {
+    flex: 1;
+    height: 100%;
+    padding: 10px;
+    overflow: auto;
+  }
+}
+</style>

+ 134 - 0
src/views/quoteConfig/agreement/components/orderAccount.vue

@@ -0,0 +1,134 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <el-dialog v-model="props.orderAccount" title="选择出单账号" width="50%" @close="close">
+      <div class="flex a-c j-s ">
+        <div class="flex a-c">
+          <el-input v-model="searchValue" style="width: 240px;margin-right: 10px;" clearable
+            placeholder="输入姓名,手机号,工号查找" />
+          <el-button type="primary">查询</el-button>
+        </div>
+        <div>
+          <el-button type="default" @click="addAccount">添加账号
+            <template #icon>
+              <el-icon>
+                <Plus />
+              </el-icon>
+            </template>
+          </el-button>
+          <el-button type="default" @click="refresh">刷新</el-button>
+        </div>
+      </div>
+      <ComplexTable :tableData="tableData" :columns="columns" :showIndex="false" :columnwidth="200" :showSelect="false"
+        :showOperation="false" :pageInfo="pageInfo" :maxHeight="400" @getList="getList" :highlightCurrentRow="true"
+        :loading="loding" @currentChange="currentChange">
+      </ComplexTable>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="cancel">取消</el-button>
+          <el-button type="primary" @click="save" :disabled="!activeInfo">
+            确定
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<!-- 行为区域 -->
+<script lang='ts' setup>
+import { ref,  } from 'vue'
+import api from '@/api'
+import {  useRouter } from "vue-router";
+import { defineProps, defineEmits } from "vue";
+interface PageInfo {
+  pageNum: number;
+  pageSize: number;
+  sizes: number[];
+  total: number;
+}
+const pageInfo = ref({
+  pageNum: 1,
+  pageSize: 20,
+  sizes: [10, 20, 30, 40, 50],
+  total: 100,
+});
+const router = useRouter();
+const props = defineProps<{
+  orderAccount: boolean,
+}>();
+const emits = defineEmits(["cancel", 'save']);
+const searchValue = ref();
+const activeInfo = ref();//选中对象
+const tableData = ref([
+]);
+const loding = ref(false);
+const columns = [
+  {
+    prop: "username", label: "账号信息", align: 'left', formatter: (row: any) => {
+      return `用户名:${row.username}\n简称:${row.userAbbr}`
+    }
+  },
+  {
+    prop: "insCompanyName", label: "保险公司",
+  },
+  { prop: "url", label: "系统", },
+  { prop: "deptName", label: "归属", },
+];
+//分页事件
+function getList(item: PageInfo) {
+  pageInfo.value.pageNum = item.pageNum;
+  pageInfo.value.pageSize = item.pageSize;
+  findpage();
+
+};
+onMounted(() => {
+  findpage();
+})
+//数据查询
+function findpage() {
+  api.insuranceApi.getAgreementAttributionList({ pages: pageInfo.value.pageNum, size: pageInfo.value.pageSize, username: searchValue.value }).then((res: any) => {
+    if (res.code == '200') {
+      tableData.value = res.data.records;
+      pageInfo.value.pageNum = res.data.current;
+      pageInfo.value.pageSize = res.data.size;
+      pageInfo.value.total = res.data.total;
+    } else {
+    }
+  });
+}
+const currentChange = (info: object) => {
+  console.log(info)
+  activeInfo.value = info;
+}
+const cancel = () => {
+  emits("cancel")
+}
+const close = () => {
+  emits('cancel')
+}
+const save = () => {
+  emits('save', activeInfo.value.id, activeInfo.value.username, activeInfo.value.userAbbr)
+}
+//打开新标签页
+const addAccount = () => {
+  const url = router.resolve({
+    name: 'InsuranceAccountAdd',
+  }).href; // 获取目标路由的完整 URL
+  window.open(url, '_blank'); // 在新标签页中打开
+
+}
+const refresh = () => {
+  loding.value = true;
+  api.insuranceApi.getAgreementAttributionList({ pages: 1, size: 20, username: searchValue.value }).then((res: any) => {
+    if (res.code == '200') {
+      tableData.value = res.data.records;
+      pageInfo.value.pageNum = res.data.current;
+      pageInfo.value.pageSize = res.data.size;
+      pageInfo.value.total = res.data.total;
+      loding.value = false;
+
+    }
+  });
+}
+</script>

+ 431 - 0
src/views/quoteConfig/agreement/components/ruleFeeDialog.vue

@@ -0,0 +1,431 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <el-drawer v-model="props.ruleFeeVisible" :direction="direction" :close-on-click-modal="false" size="57%" @close="close">
+      <template #header>
+        <h4>添加费用</h4>
+      </template>
+      <template #default>
+        <div class="agreement ">
+          <ElRow :gutter="20">
+            <ElCol :md="10"><span><span style="color: #666;">协议号:</span> {{ props.agreementInfo.agreementCode }}</span>
+            </ElCol>
+            <ElCol :md="10"><span><span style="color: #666;">协议名称:</span>{{ props.agreementInfo.agreementTitle }}</span>
+            </ElCol>
+            <ElCol :md="24">
+              <p><span style="color: #666;">核保规则:</span>{{ props.RulesInfo.ruleDescription }}</p>
+            </ElCol>
+          </ElRow>
+        </div>
+        <div class="flex f-c m-top-20">
+          <!-- <span class="strong">险种</span> -->
+          <el-tabs v-model="activeName" type="card" @tab-click="hledclick" style="position: relative;"
+            class="ruleFeetabs">
+            <el-button type="default" @click="feeItemAdd(insuranceTypeIndex)"
+              style="position: absolute;top: 0;right: 0;z-index: 1;">
+              添加费用
+              <template #icon>
+                <el-icon>
+                  <Plus />
+                </el-icon>
+              </template>
+            </el-button>
+            <el-tab-pane v-for="(item, index) in ruleFeeinfo" :key="index" :label="item.productName"
+              :name="item.productName">
+              <div style="max-height: 590px;overflow-y: auto;" v-if="item.ptlAgreementCostAddList.length">
+                <div class="feeItem m-top-20" v-for="(Costitem, Costindex) in item.ptlAgreementCostAddList"
+                  :key="Costindex">
+                  <div class="flex j-s a-c header">
+                    <span class="strong title">出单费用{{ Costindex + 1 }}</span>
+                    <div class="flex a-c">
+                      <el-icon style=" margin-right: 10px;color: #666;" @click="feeItemDel(index, Costindex)">
+                        <Delete />
+                      </el-icon>
+                      <el-button v-if="!Costitem.ruleFeeShow" link type="default"
+                        @click="Costitem.ruleFeeShow = !Costitem.ruleFeeShow">展开<el-icon class="m-left-10">
+                          <ArrowDownBold />
+                        </el-icon> </el-button>
+                      <el-button v-else link type="default"
+                        @click="Costitem.ruleFeeShow = !Costitem.ruleFeeShow">收起<el-icon class="m-left-10">
+                          <ArrowUpBold />
+                        </el-icon></el-button>
+                    </div>
+                  </div>
+                  <div style="max-height: 500px;padding: 20px 14px;overflow-y: auto;border-top: 1px solid #e5e5e5;"
+                    v-if="Costitem.ruleFeeShow">
+                    <ElRow :gutter="20" class="m-top-20">
+                      <el-form :model="Costitem" :id="Costitem.fromID" label-width="80px" ref="feeFormRef"
+                        style="width: 100%;" :rules="feerules" class="flex a-c f-wrap" :size="fromSize">
+                        <ElCol :md="24" :lg="12">
+                          <el-form-item label="生效时间" prop="effectiveTime">
+                            <el-date-picker v-model="Costitem.effectiveTime" style="width: 100%;" type="datetime"
+                              placeholder="选择生效时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
+                              time-format="HH:mm:ss" @change="handleStartDateChange($event, index, Costindex)" />
+                          </el-form-item>
+                        </ElCol>
+                        <ElCol :md="24" :lg="12">
+                          <el-form-item label="失效时间" prop="failureTime">
+                            <el-date-picker v-model="Costitem.failureTime" style="width: 100%;" type="datetime"
+                              placeholder="选择失效时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
+                              time-format="HH:mm:ss"
+                              :disabled-date="(date: any) => disabledEndDate(date, index, Costindex)" />
+                          </el-form-item>
+                        </ElCol>
+                        <ElCol :md="24" :lg="12">
+                          <el-form-item label="审核方式" prop="auditMethod">
+                            <el-select v-model="Costitem.auditMethod" placeholder="选择审核方式">
+                              <el-option label="自动审核" value="0" />
+                              <el-option label="手动审核" value="1" />
+                            </el-select>
+                          </el-form-item>
+                        </ElCol>
+                        <ElCol :md="24" :lg="24">
+                          <el-form-item label="费用描述" prop="costDescribe">
+                            <el-input v-model="Costitem.costDescribe" maxlength="500" placeholder="请输入费用描述"
+                              show-word-limit type="textarea" />
+                          </el-form-item>
+                        </ElCol>
+                      </el-form>
+                    </ElRow>
+                    <!-- 规则选择容器 -->
+                    <div class="flex a-start">
+                      <ElCol :md="24" :lg="2">
+                        <span class="strong m-right-20">规则条件</span>
+                      </ElCol>
+                      <ElCol :md="24" :lg="22">
+                        <el-button type="default" @click="addRuleCondition(index, Costindex)">
+                          添加条件
+                          <template #icon>
+                            <el-icon>
+                              <Plus />
+                            </el-icon>
+                          </template>
+                        </el-button>
+                        <rule-Condition-Container :rulesAttrList="Costitem.ruleConditions" @Del="rulesAttrDel"
+                          @selectChange="rulesAttrselectChange" />
+                      </ElCol>
+                    </div>
+                    <el-table :data="Costitem.ptlAgreementCostTypeList" style="width: 100%;" border
+                      :header-cell-style="{ 'background-color': '#F7F7F9', 'border-bottom': '1px solid #EEF1F6', 'font-size': '15px', 'color': '#333', 'padding': '10px 0' }">
+                      <template #empty>
+                        <div class="flex f-c a-c ">
+                          <img src="../../assets/images/empty.png" alt="">
+                          <span style="font-size: 14px;color: #909399;">暂无数据</span>
+                        </div>
+                      </template>
+                      <el-table-column prop="costType" label="类型"></el-table-column>
+                      <el-table-column prop="inlet" label="入口比例" width="180" align="center">
+                        <template #default="scope">
+                          <el-input-number v-model="scope.row.inlet" :precision="2" controls-position="right"
+                            style="width: 100%;" :min="0" :max="100">
+                            <template #suffix>%</template>
+                          </el-input-number>
+                        </template>
+                      </el-table-column>
+                      <el-table-column prop="export" label="出口比例" width="180" align="center">
+                        <template #default="scope">
+                          <el-input-number v-model="scope.row.export" :precision="2" controls-position="right"
+                            style="width: 100%;" :min="0" :max="100">
+                            <template #suffix>%</template>
+                          </el-input-number>
+                        </template>
+                      </el-table-column>
+                      <el-table-column prop="commission" label="手续费比例" width="180" align="center">
+                        <template #default="scope">
+                          <el-input-number v-model="scope.row.commission" :precision="2" controls-position="right"
+                            style="width: 100%;" :min="0" :max="100">
+                            <template #suffix>%</template>
+                          </el-input-number>
+                        </template>
+                      </el-table-column>
+                      <el-table-column prop="addThrow" label="加投比例" width="180" align="center">
+                        <template #default="scope">
+                          <el-input-number v-model="scope.row.addThrow" :precision="2" controls-position="right"
+                            style="width: 100%;" :min="0" :max="100">
+                            <template #suffix>%</template>
+                          </el-input-number>
+                        </template>
+                      </el-table-column>
+                    </el-table>
+                  </div>
+                </div>
+              </div>
+              <Empty v-else />
+            </el-tab-pane>
+          </el-tabs>
+        </div>
+      </template>
+      <template #footer>
+        <div class="flex a-c j-c">
+          <el-button @click="cancel">取消</el-button>
+          <el-button type="primary" @click="save(feeFormRef)">提交审核</el-button>
+        </div>
+      </template>
+    </el-drawer>
+    <!-- 规则条件弹窗 -->
+    <RuleCondition :ruleConditionVisible="ruleConditionVisible" @cancel="ruleConditionVisible = false"
+      @save="ruleConditionSave" :attrIdList="echoResponse" />
+  </div>
+</template>
+
+<!-- 行为区域 -->
+<script lang='ts' setup>
+import { ref, } from 'vue'
+import { DrawerProps, ComponentSize, FormInstance, FormRules, ElMessage } from 'element-plus';
+import { loadDicts, getDict } from '@/utils/composables/dictService';//字典组件
+import { defineProps, defineEmits } from "vue";
+const direction = ref<DrawerProps['direction']>('rtl')
+const fromSize = ref<ComponentSize>('default')
+
+const activeName = ref('单交')
+const props = defineProps<{
+  ruleFeeVisible: boolean,
+  agreementInfo: any,
+  RulesInfo: any,
+}>();
+const emits = defineEmits(["cancel", 'save']);
+const ruleFeeinfo = ref();//规则费用集合
+const ruleConditionVisible = ref(false);//规则条件弹窗
+const insuranceTypeIndex = ref(0);//险种区域下标
+const feeIndex = ref(0);//费用区域下标
+
+const feeFormRef = ref<FormInstance>()
+const feerules = ref<FormRules>({
+  effectiveTime: [
+    { required: true, trigger: 'change', message: '请选择生效时间' },
+  ],
+  failureTime: [
+    { required: true, trigger: 'change', message: '请选择失效时间' },
+  ],
+  auditMethod: [
+    { required: true, trigger: 'change', message: '请选择审核方式' },
+  ],
+
+})
+onMounted(async () => {
+  await loadDicts(['insurance_type'])
+  const dict = getDict('insurance_type');
+  ruleFeeinfo.value = dict.map((val: any) => {
+    return {
+      productName: val.label,
+      productId: val.value,
+      ptlAgreementId: "",//协议id
+      coverRuleId: "",//规则id
+      ptlAgreementCostAddList: [],//费用集合
+    }
+  })
+})
+// 禁用失效时间选择
+const disabledEndDate = (date: any, index: number, Costindex: number) => {
+  let start = ruleFeeinfo.value[index].ptlAgreementCostAddList[Costindex]?.effectiveTime;
+  const startDate = new Date(start);
+  return startDate && date < startDate; // 禁用小于生效时间的日期
+};
+
+// 处理生效时间变化
+/**
+ * @param index  险种下标
+ * @param Costindex 费用下标
+ */
+const handleStartDateChange = (value: any, index: number, Costindex: number) => {
+  let endDate = ruleFeeinfo.value[index].ptlAgreementCostAddList[Costindex]?.failureTime;
+  // 如果生效时间改变,检查失效时间是否需要更新
+  if (endDate && new Date(endDate) < new Date(value)) {
+    ruleFeeinfo.value[index].ptlAgreementCostAddList[Costindex].failureTime = ''; // 清空失效时间
+  }
+};
+const echoResponse = ref();//
+//条件删除
+const rulesAttrDel = (index: number) => {
+  ruleFeeinfo.value[insuranceTypeIndex.value].ptlAgreementCostAddList[feeIndex.value].ruleConditions.splice(index, 1)
+}
+//多选框事件数据格式处理
+const rulesAttrselectChange = (item: any) => {
+  ruleFeeinfo.value[insuranceTypeIndex.value].ptlAgreementCostAddList[feeIndex.value].ruleConditions[item.index].min = "";
+  item.e.map((ele: any) => {
+    ruleFeeinfo.value[insuranceTypeIndex.value].ptlAgreementCostAddList[feeIndex.value].ruleConditions[item.index].min += ele + ',';
+  })
+  // 去掉最后一个逗号
+  if (ruleFeeinfo.value[insuranceTypeIndex.value].ptlAgreementCostAddList[feeIndex.value].ruleConditions[item.index].min.endsWith(',')) {
+    ruleFeeinfo.value[insuranceTypeIndex.value].ptlAgreementCostAddList[feeIndex.value].ruleConditions[item.index].min = ruleFeeinfo.value[insuranceTypeIndex.value].ptlAgreementCostAddList[feeIndex.value].ruleConditions[item.index].min.slice(0, -1);
+  }
+}
+//获取到的规则条件列表数据
+const ruleConditionSave = (list: string[]) => {//获取规则条件选中集合
+  ruleFeeinfo.value[insuranceTypeIndex.value].ptlAgreementCostAddList[feeIndex.value].ruleConditions = list;
+  echoResponse.value = ruleFeeinfo.value[insuranceTypeIndex.value].ptlAgreementCostAddList[feeIndex.value].ruleConditions;
+  ruleConditionVisible.value = false;
+}
+const cancel = () => {
+  emits("cancel")
+}
+const close=()=>{
+  emits("cancel")
+}
+const save = async (formEl: FormInstance | undefined | any) => {
+  if (!formEl) return
+  const validationPromises = ruleFeeinfo.value[insuranceTypeIndex.value].ptlAgreementCostAddList.map((formRef: any, index: any) => {
+    // 轮询验证所有表单校验
+    return new Promise((resolve, reject) => {
+      if (formRef) {
+        formEl[index].validate((valid: boolean) => {
+          if (valid) {
+            if (!ruleFeeinfo.value[insuranceTypeIndex.value].ptlAgreementCostAddList[index].ruleConditions.length) {
+              ElMessage({
+                type: 'error',
+                message: `${ruleFeeinfo.value[insuranceTypeIndex.value].productName}费用${index + 1}至少添加一条规则`,
+                plain: true,
+              });
+            } else {
+              resolve(true);
+            }
+          } else {
+            reject(index);
+          }
+        });
+      } else {
+        reject(index);
+      }
+    });
+  });
+
+  try {
+    await Promise.all(validationPromises);
+    // 所有表单验证通过后执行提交逻辑
+    ruleFeeinfo.value.map((val: any) => {
+      val.ptlAgreementId = props.agreementInfo.id;
+      val.coverRuleId = props.RulesInfo.id;
+      return val;
+    })
+    emits("save", ruleFeeinfo.value)
+  } catch (error) {
+    const invalidIndex: any = error; // 获取未通过校验的表单索引
+    // const invalidForm = ruleFeeinfo.value[insuranceTypeIndex.value].ptlAgreementCostAddList[invalidIndex]; // 获取未通过校验的表单对象
+    const invalidFormElement = document.getElementById(`from-${insuranceTypeIndex.value}-${invalidIndex}`); // 获取未通过校验的表单元素
+    if (invalidFormElement) {
+      invalidFormElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); // 定位到未通过校验的表单区域
+    }
+    ElMessage({
+      type: 'error',
+      message: `${ruleFeeinfo.value[insuranceTypeIndex.value].productName}费用${invalidIndex + 1}未填写完整`,
+      plain: true,
+    });
+  }
+};
+//添加规则条件
+const addRuleCondition = (index: number, Costindex: number) => {
+  insuranceTypeIndex.value = index;
+  feeIndex.value = Costindex;
+  ruleConditionVisible.value = true;
+}
+//对应险种下添加费用
+const feeItemAdd = (index: number) => {
+  let length = ruleFeeinfo.value[index].ptlAgreementCostAddList.length;
+  const filteredList: any = filterCostTypeList(activeName.value);//根据险种匹配列表
+  let list = JSON.parse(JSON.stringify(filteredList)); // 深拷贝
+  insuranceTypeIndex.value = index;
+  ruleFeeinfo.value[index].ptlAgreementCostAddList.push({
+    fromID: `from-${index}-${length}`,
+    effectiveTime: "",//生效时间
+    failureTime: "",//生效时间
+    auditMethod: "",//审核方式
+    costDescribe: "",//费用描述
+    ruleConditions: [],//规则集合
+    ptlAgreementCostTypeList: list,
+    ruleFeeShow: length ? false : true,
+
+  })
+}
+// 过滤方法
+const ptlAgreementCostTypeList = [
+  {
+    costType: "交强险",
+    inlet: 0,
+    export: 0,
+    commission: 0,
+    addThrow: 0,
+  },
+  {
+    costType: "商业险",
+    inlet: 0,
+    export: 0,
+    commission: 0,
+    addThrow: 0,
+  },
+  {
+    costType: "驾意险",
+    inlet: 0,
+    export: 0,
+    commission: 0,
+    addThrow: 0,
+  }
+];
+
+const filterCostTypeList = (name: string) => {
+  return ptlAgreementCostTypeList.filter(item => {
+    if (name === "单交" && item.costType === "商业险") {
+      return false; // 当 name 为 "单交" 且 costType 为 "商业险" 时,返回 false,不包含该项
+    }
+    if (name === "单商" && item.costType === "交强险") {
+      return false; // 当 name 为 "商业" 且 costType 为 "交强险" 时,返回 false,不包含该项
+    }
+    return true; // 其他情况返回 true,保留该项
+  });
+};
+const hledclick = (pane: any) => {
+  insuranceTypeIndex.value = pane.index;
+  activeName.value = pane.props.name;
+}
+// 费用删除
+const feeItemDel = (index: number, Costindex: number) => {
+  ruleFeeinfo.value[index].ptlAgreementCostAddList.splice(Costindex, 1);
+}
+</script>
+<!-- 样式区域 -->
+<style lang='scss' scoped>
+:deep(.el-drawer__header) {
+  padding-top: 0;
+  margin-bottom: 0;
+  border-bottom: 1px solid #f2f2f2;
+}
+
+.agreement {
+  width: 100%;
+  padding: 15px;
+  font-size: 14px;
+  color: #333;
+  border-radius: 4px;
+
+  p {
+    margin: 0;
+    margin-top: 10px;
+  }
+}
+
+.feeItem {
+  width: 100%;
+  border: 1px solid #e5e5e5;
+  border-radius: 8px;
+
+  .header {
+    padding: 12px 14px;
+
+    .title {
+      display: flex;
+
+      &::before {
+        display: inline-block;
+        width: 4px;
+        height: 18px;
+        margin-right: 20px;
+        content: "";
+        background-color: #0083ff;
+      }
+    }
+  }
+}
+
+:deep(.ruleFeetabs>.el-tabs__content) {
+  position: static;
+}
+</style>

+ 321 - 0
src/views/quoteConfig/agreement/components/ruleFeeDialogEdit.vue

@@ -0,0 +1,321 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <el-drawer v-model="props.ruleFeeVisible" :direction="direction" :close-on-click-modal="false" size="57%" @close="close">
+      <template #header>
+        <h4>{{ props.headerTitle }}费用编辑</h4>
+      </template>
+      <template #default>
+        <div class="rejectionReason flex a-start" v-if="costitemEditInfo.reviewDescription">
+          <el-icon color="red" class="m-right-20"><CircleClose /></el-icon>
+          <div class="flex f-c">
+            <span class="strong" style="color: #333;">审核不通过</span>
+            <span >审核原因:{{costitemEditInfo. reviewDescription }}</span>
+            <div class="flex a-c ">
+              <span class="m-right-20">审核人:{{ costitemEditInfo.auditPerson }}</span>
+              <span >审核时间:{{costitemEditInfo.auditTime }}</span>
+            </div>
+          </div>
+        </div>
+        <div class="agreement ">
+          <ElRow :gutter="20">
+            <ElCol :md="10"><span><span style="color: #666;">协议号:</span> {{ props.agreementInfo.agreementCode }}</span>
+            </ElCol>
+            <ElCol :md="10"><span><span style="color: #666;">协议名称:</span>{{ props.agreementInfo.agreementTitle }}</span>
+            </ElCol>
+            <ElCol :md="24">
+              <p><span style="color: #666;">核保规则:</span>{{ props.RulesInfo.ruleDescription }}</p>
+            </ElCol>
+          </ElRow>
+          <ElRow :gutter="20" class="m-top-20">
+            <el-form :model="costitemEditInfo" label-width="80px" ref="productFormRef" style="width: 100%;"
+              :disabled="disabledShow" class="flex a-c f-wrap" :size="fromSize">
+              <ElCol :md="24" :lg="12">
+                <el-form-item label="生效时间" prop="effectiveTime">
+                  <el-date-picker v-model="costitemEditInfo.effectiveTime" style="width: 100%;" type="datetime"
+                    placeholder="选择生效时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
+                    time-format="HH:mm:ss" @change="handleStartDateChange($event)" />
+                </el-form-item>
+              </ElCol>
+              <ElCol :md="24" :lg="12">
+                <el-form-item label="失效时间" prop="failureTime">
+                  <el-date-picker v-model="costitemEditInfo.failureTime" style="width: 100%;" type="datetime"
+                    placeholder="选择失效时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
+                    time-format="HH:mm:ss" :disabled-date="(date: any) => disabledEndDate(date)" />
+                </el-form-item>
+              </ElCol>
+              <ElCol :md="24" :lg="12">
+                <el-form-item label="审核方式" prop="auditMethod">
+                  <el-select v-model="costitemEditInfo.auditMethod" placeholder="选择审核方式">
+                    <el-option label="自动审核" value="0" />
+                    <el-option label="手动审核" value="1" />
+                  </el-select>
+                </el-form-item>
+              </ElCol>
+              <ElCol :md="24" :lg="24">
+                <el-form-item label="费用描述" prop="costDescribe">
+                  <el-input v-model="costitemEditInfo.costDescribe" maxlength="500" placeholder="请输入费用描述"
+                    show-word-limit type="textarea" />
+                </el-form-item>
+              </ElCol>
+            </el-form>
+          </ElRow>
+          <!-- 规则选择容器 -->
+          <div class="flex a-start">
+            <ElCol :md="24" :lg="2">
+              <span class="strong m-right-20">规则条件</span>
+            </ElCol>
+            <ElCol :md="24" :lg="22">
+              <el-button type="primary" :disabled="disabledShow" plain @click="ruleConditionVisible = true">
+                添加条件
+                <template #icon>
+                  <el-icon>
+                    <Plus />
+                  </el-icon>
+                </template>
+              </el-button>
+              <rule-Condition-Container :rulesAttrList="costitemEditInfo.ruleConditions" @Del="rulesAttrDel"
+                @selectChange="rulesAttrselectChange" :disabled="disabledShow" />
+            </ElCol>
+          </div>
+          <el-table :data="costitemEditInfo.ptlAgreementCostTypeList" class="m-top-20" style="width: 100%;" border
+            :header-cell-style="{ 'background-color': '#F7F7F9', 'border-bottom': '1px solid #EEF1F6', 'font-size': '15px', 'color': '#333', 'padding': '10px 0' }">
+            <template #empty>
+              <div class="flex f-c a-c ">
+                <img src="../../assets/images/empty.png" alt="">
+                <span style="font-size: 14px;color: #909399;">暂无数据</span>
+              </div>
+            </template>
+            <el-table-column prop="costType" label="类型"></el-table-column>
+            <el-table-column prop="inlet" label="入口比例" width="180" align="center">
+              <template #default="scope">
+                <el-input-number v-model="scope.row.inlet" :disabled="disabledShow" :precision="2"
+                  controls-position="right" style="width: 100%;" :min="0" :max="100">
+                  <template #suffix>%</template>
+                </el-input-number>
+              </template>
+            </el-table-column>
+            <el-table-column prop="export" label="出口比例" width="180" align="center">
+              <template #default="scope">
+                <el-input-number v-model="scope.row.export" :disabled="disabledShow" :precision="2"
+                  controls-position="right" style="width: 100%;" :min="0" :max="100">
+                  <template #suffix>%</template>
+                </el-input-number>
+              </template>
+            </el-table-column>
+            <el-table-column prop="commission" label="手续费比例" width="180" align="center">
+              <template #default="scope">
+                <el-input-number v-model="scope.row.commission" :disabled="disabledShow" :precision="2"
+                  controls-position="right" style="width: 100%;" :min="0" :max="100">
+                  <template #suffix>%</template>
+                </el-input-number>
+              </template>
+            </el-table-column>
+            <el-table-column prop="addThrow" label="加投比例" width="180" align="center">
+              <template #default="scope">
+                <el-input-number v-model="scope.row.addThrow" :disabled="disabledShow" :precision="2"
+                  controls-position="right" style="width: 100%;" :min="0" :max="100">
+                  <template #suffix>%</template>
+                </el-input-number>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+
+      </template>
+      <template #footer>
+        <div class="flex a-c j-c" v-if="!disabledShow">
+          <el-button @click="cancel">取消</el-button>
+          <el-button type="primary" @click="save">提交审核</el-button>
+        </div>
+        <div class="flex f-c " v-if="!costitemEditInfo?.reviewDescription">
+          <el-checkbox v-model="automatic" label="审核后,自动跳支审核下一条费用" size="large" />
+          <div class="flex a-c">
+            <el-button @click="rejectOperation">审核驳回</el-button>
+            <el-button type="primary" @click="auditOperation">审核通过</el-button>
+          </div>
+        </div>
+      </template>
+    </el-drawer>
+    <!-- 规则条件弹窗 -->
+    <RuleCondition :ruleConditionVisible="ruleConditionVisible" @cancel="ruleConditionVisible = false"
+      @save="ruleConditionSave" :attrIdList="ruleConditions" />
+
+  </div>
+</template>
+
+<!-- 行为区域 -->
+<script lang='ts' setup>
+import { ref, } from 'vue'
+import { DrawerProps, ComponentSize, ElMessageBox } from 'element-plus';
+import { loadDicts } from '@/utils/composables/dictService';//字典组件
+import { defineProps, defineEmits } from "vue";
+const direction = ref<DrawerProps['direction']>('rtl')
+const fromSize = ref<ComponentSize>('default')
+
+const props = defineProps<{
+  ruleFeeVisible: boolean,
+  agreementInfo: any,//协议信息
+  RulesInfo: any,//费用信息
+  costitemEditInfo: any,//编辑回显数据
+  headerTitle?: string,//抽屉name
+  operationType?: string,//操作类型
+}>();
+const emits = defineEmits(["cancel", 'save', 'auditOperation', 'rejectOperation']);
+const ruleConditions = ref();
+const ruleConditionVisible = ref(false);//规则条件弹窗
+const costitemEditInfo = ref();//编辑费用数据
+const disabledShow = ref(false);//是否可编辑
+const automatic = ref(false);//是否连续审核
+onMounted(async () => {
+  await loadDicts(['insurance_type'])
+})
+watch(props, () => {
+  if (props.costitemEditInfo) {
+    costitemEditInfo.value = props.costitemEditInfo;
+    ruleConditions.value = props.costitemEditInfo.ruleConditions;
+  }
+  //审核和驳回操作不可编辑
+  if (props.operationType == 'audit' || props.operationType == 'rejected') {
+    disabledShow.value = true;
+  } else {
+    disabledShow.value = false;
+
+  }
+})
+
+
+// 禁用失效时间选择
+const disabledEndDate = (date: any,) => {
+  let start = costitemEditInfo.value.effectiveTime;
+  const startDate = new Date(start);
+  return startDate && date < startDate; // 禁用小于生效时间的日期
+};
+
+// 处理生效时间变化
+const handleStartDateChange = (value: any,) => {
+  let endDate = costitemEditInfo.value.failureTime;
+  // 如果生效时间改变,检查失效时间是否需要更新
+  if (endDate && new Date(endDate) < new Date(value)) {
+    costitemEditInfo.value.failureTime = ''; // 清空失效时间
+  }
+};
+
+//条件删除
+const rulesAttrDel = (index: number) => {
+  costitemEditInfo.value.ruleConditions.splice(index, 1)
+}
+//多选框事件数据格式处理
+const rulesAttrselectChange = (item: any) => {
+  costitemEditInfo.value.ruleConditions[item.index].min = "";
+  item.e.map((ele: any) => {
+    costitemEditInfo.value.ruleConditions[item.index].min += ele + ',';
+  })
+  // 去掉最后一个逗号
+  if (costitemEditInfo.value.ruleConditions[item.index].min.endsWith(',')) {
+    costitemEditInfo.value.ruleConditions[item.index].min = costitemEditInfo.value.ruleConditions[item.index].min.slice(0, -1);
+  }
+}
+//获取到的规则条件列表数据
+const ruleConditionSave = (list: string[]) => {//获取规则条件选中集合
+  console.log(list)
+  costitemEditInfo.value.ruleConditions = list;
+  ruleConditionVisible.value = false;
+}
+const cancel = () => {
+  emits("cancel")
+}
+const close=()=>{
+  emits("cancel")
+}
+const save = () => {
+  console.log(costitemEditInfo.value)
+  // return;
+  emits("save", costitemEditInfo.value)
+}
+
+
+
+//审核驳回操作
+const rejectOperation = () => {
+  //驳回弹窗
+  ElMessageBox.prompt('', '审核不通过', {
+    confirmButtonText: '确定',
+    type: 'warning',
+    center: true,
+    inputType: "textarea",
+    inputPlaceholder: "请输入不通过原因",
+    inputPattern: /^[\u4e00-\u9fa5,。!?、;:“”‘’《》【】()——]+$/,
+    inputErrorMessage: '请输入中文',
+  })
+    .then(({ value }) => {
+      emits("rejectOperation",automatic.value,costitemEditInfo.value.id,value)
+    })
+}
+//审核通过操作
+const auditOperation = () => {
+  emits("auditOperation", automatic.value,costitemEditInfo.value.id)
+}
+</script>
+<!-- 样式区域 -->
+<style lang='scss' scoped>
+:deep(.el-drawer__header) {
+  padding-top: 0;
+  margin-bottom: 0;
+  font-size: 20px;
+  color: #333;
+  border-bottom: 1px solid #f2f2f2;
+}
+
+.agreement {
+  width: 100%;
+  padding: 24px;
+  font-size: 14px;
+  color: #333;
+  border-radius: 4px;
+
+  p {
+    margin: 0;
+    margin-top: 10px;
+  }
+}
+
+.feeItem {
+  width: 100%;
+  border: 1px solid #e5e5e5;
+  border-radius: 8px;
+
+  .header {
+    padding: 12px 14px;
+
+    .title {
+      display: flex;
+
+      &::before {
+        display: inline-block;
+        width: 4px;
+        height: 18px;
+        margin-right: 20px;
+        content: "";
+        background-color: #0083ff;
+      }
+    }
+  }
+}
+
+.rejectionReason {
+  padding: 15px;
+  background: #fff1f0;
+  border: 1px solid #ffa39e;
+  border-radius: 5px;
+
+  span {
+    margin-bottom: 10px;
+    color: #666;
+  }
+}
+
+:deep(.ruleFeetabs>.el-tabs__content) {
+  position: static;
+}
+</style>

+ 617 - 0
src/views/quoteConfig/costManagement.vue

@@ -0,0 +1,617 @@
+<template>
+  <div>
+    <PageMain class="vh100">
+      <SearchForm :fields="fields" :formInline="formInline" :onSearch="agreementQuery" :onReset="resetForm" />
+      <ComplexTable :tableData="tableData" :columns="columns" :showIndex="false" :columnwidth="200" :showSelect="true"
+        :pageInfo="pageInfo" @getList="getList" @selectionChange="selectionChange" uniqueIdName="costId">
+        <template v-slot:table-title-btn>
+          <el-button type="primary" @click="agreementSelection()">
+            添加费用
+            <template #icon>
+              <el-icon>
+                <Plus />
+              </el-icon>
+            </template>
+          </el-button>
+          <el-dropdown placement="bottom-start">
+            <el-button> 批量管理<el-icon>
+                <arrow-down />
+              </el-icon>
+            </el-button>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item :disabled="!sysUpAndDownList.length" @click="batchDel">删除</el-dropdown-item>
+                <el-dropdown-item :disabled="!sysUpAndDownList.length" @click="batchdisable">禁用</el-dropdown-item>
+                <el-dropdown-item :disabled="!sysUpAndDownList.length" @click="batchenable">启用</el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </template>
+        <template v-slot:operation="scope">
+          <el-button type="primary" link @click="agreemenUpdate(scope.operationData)"> 编辑
+          </el-button>
+          <el-dropdown placement="bottom-start" trigger="click">
+            <span class="el-dropdown-link">
+              更 多
+            </span>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item @click="enable(scope.operationData.costId)">启用</el-dropdown-item>
+                <el-dropdown-item @click="disable(scope.operationData.costId)">禁用</el-dropdown-item>
+                <el-dropdown-item @click="del(scope.operationData.costId)">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </template>
+      </ComplexTable>
+    </PageMain>
+    <el-dialog v-model="agreementSelectionVisible" title="选择协议" width="50%">
+      <div class="Selection flex ">
+        <div class="agreement flex f-c">
+          <div class="flex a-c">
+            <el-input v-model="agreementQueryName" style="width: 240px;margin-right: 10px;"
+              placeholder="输入协议名称,简称,编码查找" />
+            <el-button type="primary">查询</el-button>
+          </div>
+          <div class="flex f-c m-top-20" style="max-height: 400px;overflow-y: auto;">
+            <div class="agreementItem flex f-c " :class="{ active: agritem.id == AgreementId }"
+              v-for="(agritem, agrindex) in agreementList" :key="agrindex" @click="ItemClick(agritem)">
+              <span class="m-btn-10">{{ agritem.agreementCode }}</span>
+              <span>{{ agritem.agreementTitle }}</span>
+            </div>
+          </div>
+        </div>
+        <div class="rule flex f-c">
+          <span class="strong">承保规则</span>
+          <div class="flex f-c m-top-20" style="max-height: 400px;overflow-y: auto;">
+            <el-radio-group v-model="ruleId">
+              <div class="flex f-c  ">
+                <el-radio :value="item.id" v-for="(item, index) in ruleList" :key="index" @change="ruleItemclick"
+                  style="margin: 20px 0;">
+                  <p class="white-space">{{ item.ruleDescription }}</p>
+                </el-radio>
+              </div>
+            </el-radio-group>
+          </div>
+        </div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="agreementSelectionVisible = false">取消</el-button>
+          <el-button type="primary" @click="agreementSelectionConfirm">
+            确定
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+    <!-- 规则费用抽屉组件 -->
+    <ruleFeeDialog v-if="ruleFeeVisible" :ruleFeeVisible="ruleFeeVisible" :agreementInfo="AgreementInfo"
+      :RulesInfo="ruleInfo" @save="ruleFeeSave" @cancel="ruleFeeVisible = false" />
+    <!-- 规则费用编辑组件 -->
+    <ruleFeeDialogEdit :ruleFeeVisible="ruleFeeEditVisible" :headerTitle="ruleFeeHeaderTitle"
+      :agreementInfo="AgreementInfo" :RulesInfo="ruleInfo" :costitemEditInfo="costitemEditInfo" @save="ruleFeeEditSave"
+      @cancel="ruleFeeEditVisible = false" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive } from 'vue'
+import { useRoute, useRouter } from 'vue-router';
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { loadDicts, getDict } from '@/utils/composables/dictService';//字典组件
+import ruleFeeDialog from "./agreement/components/ruleFeeDialog.vue";
+import ruleFeeDialogEdit from "./agreement/components/ruleFeeDialogEdit.vue";
+import api from '@/api';
+interface PageInfo {
+  pageNum: number;
+  pageSize: number;
+  sizes: number[];
+  total: number;
+}
+onBeforeMount(async () => {
+  await loadDicts(['valid_status', 'is_enable', 'audit_status', 'insurance_type']);//获取多个字典
+  if(id.value){
+    formInline.agreement=id.value;
+  }
+  findpage();//列表数据
+  getDeptTree();
+  agreement();
+  valid_status.value = getDict('valid_status');//生效状态
+  is_enable.value = getDict('is_enable');//协议状态
+  insurance_type.value = getDict('insurance_type')
+  audit_status.value = getDict('audit_status')
+
+});
+const tableData = ref([
+]);
+const agreementQueryName = ref();//选择协议查询
+const agreementList = ref();//协议列表
+const sysUpAndDownList = ref<string[]>([])//选中id
+const deptOptions = ref()//机构数据
+const insurance_type = ref();
+const valid_status = ref();
+const audit_status = ref();
+const is_enable = ref();
+const route = useRoute();
+const router = useRouter();
+const id = ref(route.query.id as string);//id参数
+const pageInfo = ref({
+  pageNum: 1,
+  pageSize: 20,
+  sizes: [10, 20, 30, 40, 50],
+  total: 100,
+});
+const formInline = reactive({
+  agreement: '',//协议信息
+  productId: "",//归属
+  rulesName: '',//保险公司id
+  costRules: '',//协议类型
+  agreementStatus: "",//有效状态
+  auditStatus: "",//协议状态
+  validState: "",
+  effectiveTime: [],
+  failureTime: [],
+})
+
+//表格字段数据
+const fields = ref([
+  { label: "所属协议", model: "agreement", type: "input", placeholder: "输入代码,名称,简称查找" },
+  {
+    label: "险种", model: "productId", type: "select", options: insurance_type,
+  },
+  {
+    label: "承保规则", model: "rulesName", type: "input", placeholder: "输入业务规则查找",
+  },
+  {
+    label: "费用规则", model: "costRules", type: "input", placeholder: "输入费用规则关键字查找",
+  },
+
+  {
+    label: "费用状态", model: "agreementStatus", type: "select", placeholder: "选择状态", options: is_enable,
+  },
+  {
+    label: "审核状态", model: "auditStatus", type: "select", placeholder: "选择状态", options: audit_status,
+  },
+  {
+    label: "有效状态", model: "validState", type: "select", placeholder: "请选择状态", options: valid_status,
+  },
+  {
+    label: "生效时间", model: "effectiveTime", type: "date", dateType: "datetimerange", startplaceholder: "开始时间", endplaceholder: "结束时间",
+    onChange: (value: any) => {
+      console.log(value)
+      formInline.effectiveTime = value;
+    }
+  },
+  {
+    label: "失效时间", model: "failureTime", type: "date", dateType: "datetimerange", startplaceholder: "开始时间", endplaceholder: "结束时间",
+    onChange: (value: any) => {
+      formInline.failureTime = value;
+    }
+  },
+]);
+
+//费用数据查询
+function findpage() {
+  api.agreementApi.queryByFeePage({ pages: pageInfo.value.pageNum, size: pageInfo.value.pageSize, ...formInline }).then((res: any) => {
+    if (res.code == '200') {
+      tableData.value = res.data.records;
+      pageInfo.value.pageNum = res.data.current;
+      pageInfo.value.pageSize = res.data.size;
+      pageInfo.value.total = res.data.total;
+    } else {
+    }
+  });
+}
+//协议列表查询
+function agreement() {
+  api.agreementApi.agreementList({ pages: pageInfo.value.pageNum, size: pageInfo.value.pageSize, searchValue: agreementQueryName.value }).then((res: any) => {
+    if (res.code == '200') {
+      agreementList.value = res.data.records;
+    } else {
+    }
+  });
+}
+const columns = [
+  {
+    prop: "agreementAbbreviation", label: "协议信息", width: '300',
+    formatter: (row: any) => {
+      return `${row.agreementCode}\n${row.agreementAbbreviation}`
+    }
+  },
+  {
+    prop: "ruleDescription", label: "承保规则", width: '500',
+  },
+  { prop: "productName", label: "险种", width: '120', },
+  {
+    prop: "costRules", label: "费用规则", width: '500',
+  },
+  {
+    prop: "isEnabled", label: "费用状态", width: '100', component: "Tag",
+    formatter: (row: any) => {
+      let name = getDict('is_enable').find(val => val.value == row.isEnabled).label
+      if (name) {
+        return { type: 'primary', text: name };
+      }
+    }
+  },
+  {
+    prop: "approved", label: "审核状态", width: '120', component: "Tag",
+    formatter: (row: any) => {
+      let name = getDict('audit_status').find(val => val.value == row.approved).label
+      if (name) {
+        return { type: 'warning', text: name };
+      }
+    }
+  },
+  { prop: "effectiveTime", label: "生效时间", width: '200' },
+  { prop: "failureTime", label: "失效时间", width: '200' },
+  {
+    prop: "ruleConditions", label: "交强费用", width: '300', formatter: (row: any) => {
+      let info = row.ruleConditions.find((val: any) => val.costType == '交强险');
+      if (info) {
+        return `入口:${info.inlet.toFixed(2)}% 出口:${info.export.toFixed(2)}%\n加投:${info.addThrow.toFixed(2)}% 手续费:${info.commission.toFixed(2)}%`
+      } else {
+        return '-';
+      }
+    }
+  },
+  {
+    prop: "ruleConditions", label: "商业费用", width: '300', formatter: (row: any) => {
+      let info = row.ruleConditions.find((val: any) => val.costType == '商业险');
+      if (info) {
+        return `入口:${info.inlet.toFixed(2)}% 出口:${info.export.toFixed(2)}%\n加投:${info.addThrow.toFixed(2)}% 手续费:${info.commission.toFixed(2)}%`
+      } else {
+        return '-';
+      }
+    }
+  },
+  {
+    prop: "ruleConditions", label: "驾意险费用", width: '300', formatter: (row: any) => {
+      let info = row.ruleConditions.find((val: any) => val.costType == '驾意险');
+      if (info) {
+        return `入口:${info.inlet.toFixed(2)}% 出口:${info.export.toFixed(2)}%\n加投:${info.addThrow.toFixed(2)}% 手续费:${info.commission.toFixed(2)}%`
+      } else {
+        return '-';
+      }
+    }
+  },
+];
+//分页事件
+function getList(item: PageInfo) {
+  pageInfo.value.pageNum = item.pageNum;
+  pageInfo.value.pageSize = item.pageSize;
+  findpage();
+
+};
+
+const agreemenUpdate = async (item: any) => {
+  ruleFeeHeaderTitle.value = item.productName;
+  AgreementInfo.value = { agreementCode: item.agreementCode, agreementTitle: item.agreementAbbreviation };
+  ruleInfo.value = { ruleDescription: item.ruleDescription };
+  await regionLicenseRegion('0');//车牌地区
+  await regionLicenseNumber('14');//车牌号
+  await api.agreementApi.AgreementFeeById(item.costId).then((res: any) => {
+    if (res.code == '200') {
+      res.data.ruleConditions = processArray(res.data.ruleConditions);
+      costitemEditInfo.value = res.data;
+      ruleFeeEditVisible.value = true;
+    } else {
+    }
+  });
+}
+const ruleFeeEditSave = (data: object) => {
+  api.agreementApi.AgreementFeeupdate(data).then((res: any) => {
+    if (res.code == '200') {
+      ElMessage({
+        type: 'success',
+        message: res.msg,
+        plain: true,
+      })
+      ruleFeeEditVisible.value = false;
+      findpage();
+    } else {
+    }
+  });
+}
+//添加车牌和车牌地区
+const processArray = (array: any[]) => {
+  return array.map((val) => {
+    switch (val.attrType) {
+      case 'inputNumber':
+      case 'datetime':
+        if (!val.operator) {
+          val.operator = '1'; // 设置默认值
+        }
+        break;
+      default:
+        break;
+    }
+    if (val.attrCode == 'LicenseNo') {
+      val.dictLabal = licensePlateNumber.value;
+    }
+    if (val.attrCode == 'LicenseArea') {
+      val.dictLabal = licensePlateRegion.value;
+    }
+    return val;
+  });
+};
+const licensePlateRegion = ref();//车牌地区
+const licensePlateNumber = ref();//车牌号
+const regionLicenseRegion = (value: string) => {
+  api.agreementApi.getAreaLicense(value).then((res: any) => {
+    if (res.code == '200') {
+      let data: any = [];
+      res.data.map((val: any) => {
+        data.push({
+          code: val.areaNumber,
+          name: val.license,
+        })
+        return val;
+      })
+      licensePlateRegion.value = data;
+    } else {
+    }
+  });
+}
+const regionLicenseNumber = (value: string) => {
+  api.agreementApi.getAreaLicense(value).then((res: any) => {
+    if (res.code == '200') {
+      let data: any = [];
+      res.data.map((val: any) => {
+        data.push({
+          code: val.areaNumber,
+          name: val.license,
+        })
+        return val;
+      })
+      licensePlateNumber.value = data;
+    } else {
+    }
+  });
+}
+//多选事件
+const selectionChange = (val: any) => {
+  console.log(val)
+  sysUpAndDownList.value = val;
+}
+//禁用
+const disable = (value: string) => {
+  console.log(value)
+  ElMessageBox.confirm(
+    '确定要禁用该协议吗,禁用后,该协议不在支持报价出单,并且已核保待缴费,待支付的订单,都将失效,请确认是否禁用?',
+    '禁用协议',
+    {
+      confirmButtonText: '确定禁用',
+      type: 'error',
+      center: true,
+      confirmButtonClass: 'el-button--danger',
+    }
+  )
+    .then(() => {
+      sysUpAndDown(false, [value]);
+    })
+}
+//启用
+const enable = (value: string) => {
+  sysUpAndDown(true, [value]);
+}
+const del = (value: string) => {
+  ElMessageBox.confirm(
+    '确定要删除该协议吗,删除后,该协议不在支持报价出单,并且已核保待缴费,待支付的订单,都将失效,请确认是否删除?',
+    '删除协议',
+    {
+      confirmButtonText: '确定删除',
+      type: 'error',
+      center: true,
+      confirmButtonClass: 'el-button--danger',
+    }
+  )
+    .then(() => {
+      api.agreementApi.AgreementFeedeleteByIds([value]).then((res: any) => {
+        if (res.code == '200') {
+          ElMessage({
+            type: 'success',
+            message: res.msg,
+            plain: true,
+          })
+          router.push({
+            name: 'reload',
+          })
+        }
+      });
+    })
+}
+//批量删除
+const batchDel = () => {
+  ElMessageBox.confirm(
+    '确定要删除该协议吗,删除后,该协议不在支持报价出单,并且已核保待缴费,待支付的订单,都将失效,请确认是否删除?',
+    '删除协议',
+    {
+      confirmButtonText: '确定删除',
+      type: 'error',
+      center: true,
+      confirmButtonClass: 'el-button--danger',
+    }
+  )
+    .then(() => {
+      api.agreementApi.agreementDelete(sysUpAndDownList.value).then((res: any) => {
+        if (res.code == '200') {
+          ElMessage({
+            type: 'success',
+            message: res.msg,
+            plain: true,
+          })
+          router.push({
+            name: 'reload',
+          })
+        }
+      });
+    })
+
+}
+//批量禁用
+const batchdisable = () => {
+  ElMessageBox.confirm(
+    '确定要禁用该协议吗,禁用后,该协议不在支持报价出单,并且已核保待缴费,待支付的订单,都将失效,请确认是否禁用?',
+    '禁用协议',
+    {
+      confirmButtonText: '确定禁用',
+      type: 'error',
+      center: true,
+      confirmButtonClass: 'el-button--danger',
+    }
+  )
+    .then(async () => {
+      await sysUpAndDown(false, undefined);
+    })
+    .catch(() => {
+    })
+
+
+}
+//批量启用
+const batchenable = async () => {
+  await sysUpAndDown(true, undefined);
+}
+const agreementQuery = () => {
+  findpage();
+}
+const sysUpAndDown = async (type: boolean, list?: string[]) => {
+  await api.agreementApi.AgreementFeeEnableByIds(type, list ? list : sysUpAndDownList.value).then((res: any) => {
+    if (res.code == '200') {
+      findpage();
+      ElMessage({
+        type: 'success',
+        message: res.msg,
+        plain: true,
+      })
+    }
+  });
+}
+const resetForm = () => {
+  formInline.agreement = '';//协议信息
+  formInline.productId = "";//归属
+  formInline.rulesName = '';//保险公司id
+  formInline.costRules = '';//协议类型
+  formInline.agreementStatus = "";//有效状态
+  formInline.auditStatus = "";//协议状态
+  formInline.validState = "";
+  formInline.effectiveTime = [];
+  formInline.failureTime = [];
+  findpage();
+}
+const getDeptTree = () => {
+  api.institutionApi.institutionList({}).then((res: any) => {
+    if (res.code == '200') {
+      deptOptions.value = res.data;
+    } else {
+    }
+  });
+}
+//选择协议弹窗
+const agreementSelectionVisible = ref(false);
+const ruleFeeVisible = ref(false);
+const ruleFeeEditVisible = ref(false);//编辑弹窗
+const ruleFeeHeaderTitle = ref();//编辑弹窗title
+const costitemEditInfo = ref();//编辑对象
+const AgreementInfo = ref();//协议对象
+const ruleInfo = ref();//规则对象
+const AgreementId = ref();//规则id
+const ruleId = ref();//规则id
+const ruleList = ref();//协议规则列表
+const agreementSelection = () => {
+  agreementSelectionVisible.value = true;
+}
+const ItemClick = (item: any) => {
+  AgreementInfo.value = item;
+  AgreementId.value = item.id;
+  api.agreementApi.agreementRulesList(AgreementId.value).then((res: any) => {
+    if (res.code == '200') {
+      ruleList.value = res.data;
+    }
+  });
+}
+//
+const ruleItemclick = (value: string | number | boolean | undefined) => {
+  let info = ruleList.value.find((val: any) => val.id == value);
+  ruleInfo.value = info;
+}
+const agreementSelectionConfirm = () => {
+  agreementSelectionVisible.value = false;
+  ruleFeeVisible.value = true;
+}
+//规则弹窗回调方法
+const ruleFeeSave = (data: object) => {
+  console.log(data)
+  api.agreementApi.ptlAgreementCostsave(data).then((res: any) => {
+    if (res.code == '200') {
+      ElMessage({
+        type: 'success',
+        message: res.msg,
+        plain: true,
+      })
+      ruleFeeVisible.value = false;
+      findpage();
+    } else {
+    }
+  });
+}
+</script>
+
+<style lang="scss" scoped>
+.demo-form-inline .el-input {
+  --el-input-width: 220px;
+}
+
+.demo-form-inline .el-select {
+  --el-select-width: 220px;
+}
+
+:deep(.el-dropdown-link) {
+  display: flex;
+  align-items: center;
+  margin-left: 15px;
+  color: var(--el-color-primary);
+  cursor: pointer;
+}
+
+.el-inputs {
+  width: 100% !important;
+}
+
+.Selection {
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+
+  .agreement {
+    width: 40%;
+    padding: 15px;
+    border-right: 1px solid #dcdfe6;
+
+    .agreementItem {
+      padding: 10px;
+      border-radius: 5px;
+
+      &:hover {
+        background: #f2f2f2;
+      }
+    }
+  }
+
+  .active {
+    color: #1890ff;
+    background: #f2f2f2;
+  }
+
+  .rule {
+    width: 60%;
+    padding: 15px;
+
+    .white-space {
+      display: inline-block;
+      margin-bottom: 10px;
+      white-space: normal;
+    }
+  }
+}
+</style>

+ 584 - 0
src/views/quoteConfig/insuranceCompanyAccount/insuranceCompanyAccount.vue

@@ -0,0 +1,584 @@
+<template>
+  <div class="insuranceAccount">
+    <el-form :model="insuranceForm" ref="InsuranceFormRefs" label-width="70">
+      <el-row :gutter="29">
+        <el-col :span="8">
+          <el-form-item label="账号信息">
+            <el-input v-model="insuranceForm.username" placeholder="输入用户名,简称查找" clearable></el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="保险公司">
+            <el-select v-model="insuranceForm.insCompanyId" placeholder="请选择" clearable>
+              <el-option v-for="item in bankOption" :key="item.companyCode" :value="item.companyCode" :label="item.name"></el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="归属">
+            <el-cascader
+              style="width: 100%"
+              v-model="insuranceForm.userDept"
+              placeholder="请选择"
+              :options="deptOption"
+              :props="cascaderProps"
+              clearable
+            ></el-cascader>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="8">
+          <el-form-item label="可用状态">
+            <el-select v-model="insuranceForm.useStatus" placeholder="请选择" clearable>
+              <el-option key="0" label="正常" value="0"></el-option>
+              <el-option key="1" label="不可用" value="1"></el-option>
+              <el-option key="2" label="未知" value="2"></el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="报价状态">
+            <el-select v-model="insuranceForm.quoteStatus" placeholder="请选择" clearable>
+              <el-option key="0" label="启用" value="0"></el-option>
+              <el-option key="1" label="禁用" value="1"></el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <div style="display: flex; justify-content: flex-end">
+            <el-button type="primary" @click="search">查询</el-button>
+            <el-button plain @click="reset">重置</el-button>
+          </div>
+        </el-col>
+      </el-row>
+    </el-form>
+    <div class="btns">
+      <el-button type="primary" icon="plus" @click="addInsuranceAccount">添加账号</el-button>&nbsp;
+      <el-dropdown @command="batchCommand">
+        <el-button plain>批量管理</el-button>
+        <template #dropdown>
+          <el-dropdown-menu>
+            <el-dropdown-item command="删除">删除</el-dropdown-item>
+            <el-dropdown-item command="启用">启用</el-dropdown-item>
+            <el-dropdown-item command="禁用">禁用</el-dropdown-item>
+            <el-dropdown-item command="账号授权">账号授权</el-dropdown-item>
+          </el-dropdown-menu>
+        </template>
+      </el-dropdown>
+    </div>
+    <el-table
+      :data="tableData"
+      ref="insuranceDataRefs"
+      @selectionChange="tableSelectChange"
+      height="520"
+    >
+      <el-table-column type="selection" width="80"></el-table-column>
+      <el-table-column prop="username" label="账号信息" width="300">
+        <template #default="scope">
+          <div>用户名:{{ scope.row.username }}</div>
+          <div>简称:{{ scope.row.userAbbr }}</div>
+        </template>
+      </el-table-column>
+      <el-table-column prop="insCompanyName" label="保险公司"></el-table-column>
+      <el-table-column prop="url" label="账号登录地址"></el-table-column>
+      <el-table-column prop="deptName" label="归属"></el-table-column>
+      <el-table-column prop="useStatus">
+        <template #header>
+          <div style="display: flex; align-items: center">
+            可用状态&nbsp;
+            <el-tooltip
+              class="box-item"
+              effect="dark"
+              content="<span>使用该账号报价后,保司返回的账号可用结果</br>
+              正常:说明该账号可使用</br>
+              不可用:说明该账号在最近1次的报价时,保司返回的账号状态</br>
+              未知:超过7天未使用该账号报价出单</span>"
+              placement="top-start"
+              raw-content
+            >
+              <el-icon size="14"><QuestionFilled /></el-icon>
+            </el-tooltip>
+          </div>
+        </template>
+        <template #default="scope">
+          <div class="tableState">
+            <span :class="{'state-use': scope.row.useStatus==='0','state-noUse': scope.row.useStatus==='1','state-noKnow': scope.row.useStatus==='2', }"></span>{{ scope.row.useStatus==='0'?'正常':scope.row.useStatus==='1'?'不可用':scope.row.useStatus==='2'?'未知':'' }}
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column prop="quoteStatus" label="报价状态">
+        <template #default="scope">
+          <div class="tableState">
+            <span :class="{'state-use': scope.row.quoteStatus==='0','state-noUse': scope.row.quoteStatus==='1'}"></span>{{ scope.row.quoteStatus==='0'?'启用':scope.row.quoteStatus==='1'?'禁用':''  }}
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column prop="userDesc" label="账号描述"></el-table-column>
+      <el-table-column label="操作">
+        <template #default="scope">
+          <div style="display: flex; align-items: center">
+            <el-button link type="primary" @click="editInsuranceAccount(scope.row)">编辑</el-button>
+            <el-dropdown @command="tableDataCommand(scope, $event)">
+              <el-button link type="primary">更多</el-button>
+              <template #dropdown>
+                <el-dropdown-item command="删除">删除</el-dropdown-item>
+                <el-dropdown-item command="启用">启用</el-dropdown-item>
+                <el-dropdown-item command="禁用">禁用</el-dropdown-item>
+                <el-dropdown-item command="账号授权">账号授权</el-dropdown-item>
+              </template>
+            </el-dropdown>
+          </div>
+        </template>
+      </el-table-column>
+    </el-table>
+    <div class="paginationBox">
+      <el-pagination
+        v-model:current-page="currentPage"
+        v-model:page-size="pageSize"
+        :page-sizes="[10, 20, 30, 40]"
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="total"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+      ></el-pagination>
+    </div>
+    <el-drawer
+      v-model="accreditDrawer"
+      title="账号授权"
+      :size="800"
+    >
+      <el-row :gutter="10" style="height: 100%;">
+        <el-col :span="8" style="height: 100%;">
+          <el-input prefix-icon="search" @keyup.enter="searchInsurance"></el-input>
+          <div class="insuranceItem" v-for="item in insuranceList" :key="item.id">{{ item.name }}</div>
+        </el-col>
+        <el-divider direction="vertical" style="height: 100%;"></el-divider>
+        <el-col :span="15" style="height: 100%;">
+          <el-checkbox @change="insuranceSelectAll">全选</el-checkbox>
+          <el-tree
+            ref="insuranceTreeRefs"
+            :data="insuranceTree"
+            show-checkbox
+            default-expand-all
+            node-key="id"
+          />
+        </el-col>
+      </el-row>
+    </el-drawer>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ElMessage, ElMessageBox } from "element-plus";
+import { useRouter } from 'vue-router'
+import api from '@/api'
+
+const myRouter = useRouter()
+
+let insuranceForm = ref({})
+
+// 列表数据
+let tableData = ref([
+  {
+    id: '111'
+  }
+])
+// 查询
+const search = () => {
+  initData()
+}
+// 重置
+const reset = () => {
+  insuranceForm.value = {}
+  initData()
+}
+// 新增账号
+const addInsuranceAccount = () => {
+  myRouter.push({
+    path: '/quoteConfig/agreement/insuranceCompanyAccount/insuranceAccountAdd',
+  })
+}
+// 编辑账号
+const editInsuranceAccount = (row) => {
+  myRouter.push({
+    path: '/quoteConfig/agreement/insuranceCompanyAccount/insuranceAccountAdd',
+    query: {
+      id: row.id
+    }
+  })
+}
+// 批量操作
+const batchCommand = (e) => {
+  if(selectRows.value.length == 0) {
+    ElMessage({
+      message: '请选择需要操作的数据!',
+      type: 'warning'
+    })
+    return
+  }
+  switch (e) {
+    case '删除':
+      ElMessageBox({
+        title: '确定删除保司账号吗?',
+        message: '保司账号删除后不可恢复,请谨慎操作',
+        type: 'warning',
+        showCancelButton: true,
+        confirmButtonText: '确定删除',
+        cancelButtonText: '取消',
+      }).then((  action ) => {
+        if(action === 'confirm') {
+          api.insuranceApi.deleteAgreementAttribution({ ids: [...selectRows.value] }).then(res => {
+            if(res.code == 200) {
+              initData()
+              ElMessage({
+                message: res.msg,
+                type: 'success'
+              })
+            } else {
+              ElMessage({
+                message: res.msg,
+                type: 'warning'
+              })
+            }
+          })
+        }
+      })
+      break;
+    case '禁用':
+      ElMessageBox({
+        title: '禁用账号',
+        message: '确定要禁用该保司账号吗,禁用后当前租户/机构无法正常使用此账号报价出单',
+        type: 'warning',
+        showCancelButton: true,
+        confirmButtonText: '确定禁用',
+        cancelButtonText: '取消',
+      }).then((  action ) => {
+        if(action === 'confirm') {
+          api.insuranceApi.disableAgreementAttribution({ ids: [...selectRows.value] }).then(res => {
+            if(res.code == 200) {
+              initData()
+              ElMessage({
+                message: res.msg,
+                type: 'success'
+              })
+            } else {
+              ElMessage({
+                message: res.msg,
+                type: 'warning'
+              })
+            }
+          })
+        }
+      })
+      break;
+    case '启用':
+      // 启用的逻辑
+      api.insuranceApi.enableAgreementAttribution({ ids: [...selectRows.value] }).then(res => {
+        if(res.code == 200) {
+          initData()
+          ElMessage({
+            message: res.msg,
+            type: 'success'
+          })
+        } else {
+          ElMessage({
+            message: res.msg,
+            type: 'warning'
+          })
+        }
+      })
+      break;
+    case '账号授权':
+      // 账号授权的逻辑
+      accreditDrawer.value = true
+      break;
+  }
+}
+// 单条数据操作
+const tableDataCommand = (scope, e) => {
+  switch (e) {
+    case '删除':
+      ElMessageBox({
+        title: '确定删除保司账号吗?',
+        message: '保司账号删除后不可恢复,请谨慎操作',
+        type: 'warning',
+        showCancelButton: true,
+        confirmButtonText: '确定删除',
+        cancelButtonText: '取消',
+      }).then((  action ) => {
+        if(action === 'confirm') {
+          api.insuranceApi.deleteAgreementAttribution({ ids: [scope.row.id] }).then(res => {
+            if(res.code == 200) {
+              initData()
+              ElMessage({
+                message: res.msg,
+                type: 'success'
+              })
+            } else {
+              ElMessage({
+                message: res.msg,
+                type: 'warning'
+              })
+            }
+          })
+        }
+      })
+      break;
+    case '禁用':
+      ElMessageBox({
+        title: '禁用账号',
+        message: '确定要禁用该保司账号吗,禁用后当前租户/机构无法正常使用此账号报价出单',
+        type: 'warning',
+        showCancelButton: true,
+        confirmButtonText: '确定禁用',
+        cancelButtonText: '取消',
+      }).then((  action ) => {
+        if(action === 'confirm') {
+          api.insuranceApi.disableAgreementAttribution({ ids: [scope.row.id] }).then(res => {
+            if(res.code == 200) {
+              initData()
+              ElMessage({
+                message: res.msg,
+                type: 'success'
+              })
+            } else {
+              ElMessage({
+                message: res.msg,
+                type: 'warning'
+              })
+            }
+          })
+        }
+      })
+      break;
+    case '启用':
+      api.insuranceApi.enableAgreementAttribution({ ids: [scope.row.id] }).then(res => {
+        if(res.code == 200) {
+          initData()
+          ElMessage({
+            message: res.msg,
+            type: 'success'
+          })
+        } else {
+          ElMessage({
+            message: res.msg,
+            type: 'warning'
+          })
+        }
+      })
+      break;
+    case '账号授权':
+      // 账号授权的逻辑
+      accreditDrawer.value = true
+      break;
+  }
+}
+// 表格多选
+let selectRows = ref([])
+const tableSelectChange = (rows) => {
+  selectRows.value = []
+  if(rows.length>0) {
+    rows.forEach(item => {
+      selectRows.value.push(item.id)
+    })
+  }
+}
+// 分页功能
+let currentPage = ref(0)
+let pageSize = ref(10)
+let total = ref(0)
+
+const handleSizeChange = (size) => {
+  pageSize.value = size
+  initData()
+}
+const handleCurrentChange = (page) => {
+  currentPage.value = page
+  initData()
+}
+
+// 授权
+let accreditDrawer = ref(false)
+let insuranceList = ref([
+  {
+    name: '保司1',
+    id: '1'
+  },{
+    name: '保司2',
+    id: '2'
+  }
+])
+let insuranceTree = ref([
+  {
+    id: 1,
+    label: 'Level one 1',
+    children: [
+      {
+        id: 4,
+        label: 'Level two 1-1',
+        children: [
+          {
+            id: 9,
+            label: 'Level three 1-1-1',
+          },
+          {
+            id: 10,
+            label: 'Level three 1-1-2',
+          },
+        ],
+      },
+    ],
+  },
+  {
+    id: 2,
+    label: 'Level one 2',
+    children: [
+      {
+        id: 5,
+        label: 'Level two 2-1',
+      },
+      {
+        id: 6,
+        label: 'Level two 2-2',
+      },
+    ],
+  },
+  {
+    id: 3,
+    label: 'Level one 3',
+    children: [
+      {
+        id: 7,
+        label: 'Level two 3-1',
+      },
+      {
+        id: 8,
+        label: 'Level two 3-2',
+      },
+    ],
+  }
+])
+const treeProps = {
+
+}
+const searchInsurance = () => {
+
+}
+const insuranceSelectAll = (e) => {
+
+}
+
+// 列表数据初始化
+const initData = () => {
+  api.insuranceApi.getAgreementAttributionList({
+    ...insuranceForm.value,
+    pages: currentPage.value,
+    size: pageSize.value
+  }).then(res => {
+    if(res.code == 200) {
+      tableData.value = res.data.records
+      total.value = res.data.total
+    } else {
+      ElMessage({
+        message: res.msg,
+        type: 'warning'
+      })
+    }
+  })
+}
+// 保险公司列表
+let bankOption = ref([])
+const initCompany = () => {
+  api.insuranceApi.leftList('').then(res => {
+    if(res.code == 200) {
+      bankOption.value = res.data
+    } else {
+      ElMessage({
+        message: res.msg,
+        type: 'warning'
+      })
+    }
+  })
+}
+
+// 归属
+let deptOption = ref([])
+const cascaderProps = {
+  value: 'id',
+  label: 'name',
+  children: 'children',
+  checkStrictly: true,
+}
+const initDept = () => {
+  api.insuranceApi.getDeptTree({ sysCode: localStorage.getItem('sysCode')}).then(res => {
+    if(res.code == 200) {
+      deptOption.value = res.data
+    } else {
+      ElMessage({
+        message: res.msg,
+        type: 'warning'
+      })
+    }
+  })
+}
+
+onMounted(() => {
+  initData()
+  initCompany()
+  initDept()
+})
+
+</script>
+
+<style scoped lang="scss">
+.insuranceAccount{
+  width: 100%;
+  height: 100%;
+  background: white;
+  padding: 20px;
+  .btns{
+    margin: 10px 0 20px;
+  }
+  .paginationBox{
+    display: flex;
+    justify-content: center;
+    margin-top: 20px;
+  }
+  .insuranceItem{
+    padding: 5px 10px;
+    border-radius: 5px;
+    cursor: pointer;
+    margin: 10px 0;
+    &:hover{
+      background: #00a3ec;
+      color: white;
+    }
+  }
+  .tableState{
+    display: flex;
+    align-items: center;
+    .state-use{
+      width: 6px;
+      height: 6px;
+      background: #00a3ec;
+      border-radius: 50%;
+      margin-right: 10px;
+    }
+    .state-noUse{
+      width: 6px;
+      height: 6px;
+      background: red;
+      border-radius: 50%;
+      margin-right: 10px;
+    }
+    .state-noKnow{
+      width: 6px;
+      height: 6px;
+      background: orange;
+      border-radius: 50%;
+      margin-right: 10px;
+    }
+  }
+}
+</style>

+ 310 - 0
src/views/quoteConfig/insuranceCompanyAccount/modules/insuranceAccountAdd.vue

@@ -0,0 +1,310 @@
+<template>
+  <div class="insuranceAccountAdd">
+    <el-form :model="insuranceAccountForm" :rules="insuranceAccountRules" ref="insuranceAccountRefs" label-width="130" style="height: 700px">
+      <el-descriptions title="基本信息">
+        <el-descriptions-item>
+          <template #default>
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="保险公司" prop="insCompanyId">
+                  <el-select v-model="insuranceAccountForm.insCompanyId" placeholder="请选择" clearable @change="companyChange" filterable>
+                    <el-option v-for="item in bankOption" :key="item.companyCode" :value="item.companyCode" :label="item.name"></el-option>
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="账号归属" prop="userDept">
+                  <el-cascader
+                    style="width: 100%"
+                    v-model="insuranceAccountForm.userDept"
+                    placeholder="请选择"
+                    :options="deptOption"
+                    :props="cascaderProps"
+                    clearable
+                  ></el-cascader>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="报价对接方式" prop="apiType">
+                  <el-select v-model="insuranceAccountForm.apiType" placeholder="请选择" clearable @change="companyChange" filterable>
+                    <el-option key="1" value="1" label="直连"></el-option>
+                    <el-option key="2" value="2" label="非直连"></el-option>
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="账号登录地址" prop="loginUrl">
+                  <el-select v-model="insuranceAccountForm.loginUrl" placeholder="请选择" clearable>
+                    <el-option v-for="item in loginUrlOption" :key="item.id" :label="item.url" :value="item.id"></el-option>
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="用户名" prop="username">
+                  <el-input v-model="insuranceAccountForm.username" placeholder="请填写用户名" clearable></el-input>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="密码" prop="password">
+                  <el-input v-model="insuranceAccountForm.password" placeholder="请填写密码" clearable></el-input>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="账号简称" prop="userAbbr">
+                  <el-input v-model="insuranceAccountForm.userAbbr" placeholder="请填写账号简称,不设置系统会默认账号简称" clearable></el-input>
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </template>
+        </el-descriptions-item>
+      </el-descriptions>
+      <el-divider></el-divider>
+      <el-descriptions title="账号配置">
+        <el-descriptions-item>
+          <template #default>
+            <el-row :gutter="20">
+              <el-col :span="12" v-for="item in fielsOption" :key="item.field">
+                <el-form-item :label="item.label" :prop="item.field">
+                  <el-input v-model="insuranceAccountForm[item.field]" placeholder="请填写配置字段"></el-input>
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </template>
+        </el-descriptions-item>
+      </el-descriptions>
+    </el-form>
+    <div class="buts">
+      <el-button plain @click="cancel(insuranceAccountRefs)">取消</el-button>
+      <el-button type="primary" @click="submit(insuranceAccountRefs)">保存</el-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { type FormRules, ElMessage, type FormInstance } from 'element-plus'
+import api from '@/api'
+import { useRoute, useRouter } from 'vue-router'
+
+const myRoute = useRoute()
+const myRouter = useRouter()
+
+const insuranceAccountRefs = ref(null)
+
+let insuranceAccountForm = ref({})
+let insuranceAccountRules = ref<FormRules>({
+  insCompanyId: [
+    { required: true, trigger: "change", message: "请选择保险公司"  }
+  ],
+  userDept: [
+    { required: true, trigger: "change", message: "请选择账号归属"  }
+  ],
+  loginUrl: [
+    { required: true, trigger: "change", message: "请选择账号登录地址"  }
+  ],
+  username: [
+    { required: true, trigger: "blur", message: "请填写用户名"  }
+  ],
+  password: [
+    { required: true, trigger: "blur", message: "请填写密码"  }
+  ],
+  apiType: [
+    { required: true, trigger: "change", message: "请选择报价对接方式" }
+  ]
+})
+
+// 保险公司列表
+let bankOption = ref([])
+const initCompany = () => {
+  api.insuranceApi.leftList('').then(res => {
+    if(res.code == 200) {
+      bankOption.value = res.data
+    } else {
+      ElMessage({
+        message: res.msg,
+        type: 'warning'
+      })
+    }
+  })
+}
+
+// 归属
+let deptOption = ref([])
+const cascaderProps = {
+  value: 'id',
+  label: 'name',
+  children: 'children',
+  checkStrictly: true,
+}
+const initDept = () => {
+  api.insuranceApi.getDeptTree({ sysCode: localStorage.getItem('sysCode')}).then(res => {
+    if(res.code == 200) {
+      deptOption.value = res.data
+    } else {
+      ElMessage({
+        message: res.msg,
+        type: 'warning'
+      })
+    }
+  })
+}
+// 获取配置
+const companyChange = (e) => {
+  getPtlInsAttributionTemplate(e, insuranceAccountRefs.value)
+  getLoginUrl(e)
+}
+// 获取登录地址
+let loginUrlOption = ref([])
+const getLoginUrl = (id) => {
+  api.insuranceApi.getLoginUrl({ companyId: id }).then(res => {
+    if(res.code == 200) {
+      loginUrlOption.value = res.data
+    } else {
+      ElMessage({
+        message: res.msg,
+        type: 'warning'
+      })
+    }
+  })
+}
+// 配置项
+let fielsOption = ref([])
+const getPtlInsAttributionTemplate = (id, formRef) => {
+  api.insuranceApi.getPtlInsAttributionTemplate({ insCompanyId: id }).then(res => {
+    if(res.code == 200) {
+      if(res.data.fields&&res.data.fields.length>0) {
+        fielsOption.value = res.data.fields
+        res.data.fields.forEach(item => {
+          insuranceAccountRules.value[item.field] = [
+            { required: true, trigger: "blur", message: `请填写${item.label}` }
+          ]
+        })
+        setTimeout(() => {
+          formRef.clearValidate()
+        }, 50)
+      }
+    } else {
+      ElMessage({
+        message: res.msg,
+        type: 'warning'
+      })
+    }
+  })
+}
+
+const cancel = (formRef) => {
+  formRef.resetFields()
+}
+const submit = async (formRef) => {
+  if(!formRef) {
+    formRef.resetFields()
+    return
+  }
+  await formRef.validate((valid) => {
+    if(valid) {
+      let formData = {
+        insCompanyId: insuranceAccountForm.value.insCompanyId,
+        userDept: insuranceAccountForm.value.userDept,
+        loginUrl: insuranceAccountForm.value.loginUrl,
+        username: insuranceAccountForm.value.username,
+        password: insuranceAccountForm.value.password,
+        userAbbr: insuranceAccountForm.value.userAbbr,
+        apiType: insuranceAccountForm.value.apiType,
+        fields: []
+      }
+      formData.userDept = typeof formData.userDept==='string'?formData.userDept:formData.userDept[formData.userDept.length-1]
+      fielsOption.value.forEach(item => {
+        let obj = {...item}
+        obj.value = insuranceAccountForm.value[item.field]
+        formData.fields.push(obj)
+      })
+      if(myRoute.query.id) {
+        api.insuranceApi.updateAgreementAttribution({
+          ...formData,
+          id: myRoute.query.id
+        }).then(res => {
+          if(res.code == 200) {
+            ElMessage({
+              message: res.msg,
+              type: 'success'
+            })
+            myRouter.push({
+              path: '/quoteConfig/agreement/insuranceCompanyAccount'
+            })
+          } else {
+            ElMessage({
+              message: res.msg,
+              type: 'warning'
+            })
+          }
+        })
+      } else {
+        api.insuranceApi.addAgreementAttribution(formData).then(res => {
+          if(res.code == 200) {
+            ElMessage({
+              message: res.msg,
+              type: 'success'
+            })
+            myRouter.push({
+              path: '/quoteConfig/agreement/insuranceCompanyAccount'
+            })
+          } else {
+            ElMessage({
+              message: res.msg,
+              type: 'warning'
+            })
+          }
+        })
+      }
+    }
+  })
+}
+
+// 详情
+const initEditData = () => {
+  if(myRoute.query.id) {
+    api.insuranceApi.getAgreementAttribution({ id: myRoute.query.id }).then(res => {
+      if(res.code == 200) {
+        insuranceAccountForm.value = {...res.data}
+        getPtlInsAttributionTemplate(insuranceAccountForm.value.insCompanyId, insuranceAccountRefs.value)
+        getLoginUrl(insuranceAccountForm.value.insCompanyId)
+        if(res.data.fields.length>0) {
+          res.data.fields.forEach(item => {
+            insuranceAccountForm.value[item.field] = item.value
+          })
+          res.data.fields.forEach(item => {
+            insuranceAccountRules.value[item.field] = [
+              { required: true, trigger: "blur", message: `请填写${item.label}` }
+            ]
+          })
+          fielsOption.value = {...res.data.fields}
+        }
+      } else {
+        ElMessage({
+          message: res.msg,
+          type: 'warning'
+        })
+      }
+    })
+  }
+}
+
+onMounted(() => {
+  initCompany()
+  initDept()
+  initEditData()
+})
+
+</script>
+
+<style scoped lang="scss">
+.insuranceAccountAdd{
+  width: 100%;
+  height: 100%;
+  background: white;
+  padding: 20px;
+  .buts{
+    display: flex;
+    justify-content: center;
+  }
+}
+</style>

+ 269 - 0
src/views/quoteConfig/project/index.vue

@@ -0,0 +1,269 @@
+<template>
+  <div class="project">
+    <el-row :gutter="20" style="height: 100%; padding: 10px">
+      <el-col :span="4">
+        <el-input placeholder="请输入保险公司名称" v-model="company" prefix-icon="search" @keyup.enter="searchCompany"></el-input>
+        <div class="companyList">
+          <div class="companyItem" :class="{'active': companyIndex === index}" v-for="(item, index) in filterList" @click="companyChose(item, index)">{{ item.nameSimple }}</div>
+        </div>
+      </el-col>
+      <el-col :span="20">
+        <el-row :gutter="20" align="middle">
+          <el-col :span="20">
+            <h4>险种方案配置</h4>
+            <p>配置各家保险公司险种方案套餐,报价时根据规则信息匹配符合报价车辆的保司与保司内的方案</p>
+          </el-col>
+          <el-col :span="4">
+            <el-button icon="plus" type="primary" @click="addProject">添加新方案</el-button>
+          </el-col>
+        </el-row>
+        <el-divider></el-divider>
+        <el-form :model="projectForm" ref="projectRefs">
+          <el-row :gutter="20">
+            <el-col :span="4">
+              <el-form-item label="方案类型">
+                <el-select clearable v-model="projectForm.schemeType" placeholder="请选择" @change="search">
+                  <el-option key="1" value="1" label="基础方案"></el-option>
+                  <el-option key="2" value="2" label="自选方案"></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="4">
+              <el-form-item label="险种">
+                <el-select clearable v-model="projectForm.productId" placeholder="请选择" @change="search">
+                  <el-option v-for="item in insuranceType" :key="item.value" :value="item.value" :label="item.label"></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="4">
+              <el-form-item>
+                <el-input clearable prefix-icon="search" placeholder="输入规则描述查找" @keyup.enter="search" v-model="projectForm.ruleDesc"></el-input>
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </el-form>
+        <el-table
+          :data="tableData"
+          height="600"
+        >
+          <el-table-column label="方案名称" prop="schemeName"></el-table-column>
+          <el-table-column label="方案描述" prop="description"></el-table-column>
+          <el-table-column label="方案类型" prop="schemeType">
+            <template #default="scope">
+              {{ scope.row.schemeType == '1'?'基础方案':scope.row.schemeType == '1'?'自选方案':'' }}
+            </template>
+          </el-table-column>
+          <el-table-column label="险种" prop="productName"></el-table-column>
+          <el-table-column label="驾意险" prop="drivingIntentionNum"></el-table-column>
+          <el-table-column label="规则描述" prop="ruleDesc"></el-table-column>
+          <el-table-column label="操作">
+            <template #default="scope">
+              <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
+              <el-button link type="primary" @click="handleDel(scope.row)">删除</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+        <div class="paginationBox">
+          <el-pagination
+            v-model:current-page="currentPage"
+            v-model:page-size="pageSize"
+            :page-sizes="[10, 20, 30, 40]"
+            layout="total, sizes, prev, pager, next, jumper"
+            :total="total"
+            @size-change="handleSizeChange"
+            @current-change="handleCurrentChange"
+          ></el-pagination>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup lang="ts">
+import api from '@/api'
+import {ElMessage, ElMessageBox} from "element-plus";
+import { useRouter, useRoute } from 'vue-router'
+
+const myRouter = useRouter()
+const muRoute = useRoute()
+
+const searchCompany = () => {
+  currentPage.value = 1
+  pageSize.value = 10
+  initData()
+}
+let company = ref('')
+
+const filterList = computed(() => {
+  return companyOption.value.filter(a => a.nameSimple.includes(company.value))
+})
+
+const search = () => {
+  initData()
+}
+// 保险公司
+let companyOption = ref([])
+let companyIndex = ref(0)
+let companyId = ref('')
+const initCompany = () => {
+  api.projectApi.getCompanyList().then(res => {
+    if(res.code == 200) {
+      companyOption.value = res.data
+      companyId.value = companyOption.value[companyIndex.value].id
+      initData()
+    } else {
+      ElMessage({
+        message: res.msg,
+        type: 'warning'
+      })
+    }
+  })
+}
+const companyChose = (item, index) => {
+  companyIndex.value = index
+  companyId.value = item.companyCode
+  initData()
+}
+
+const addProject = () => {
+  myRouter.push({
+    path: '/quoteConfig/agreement/project/projectAdd'
+  })
+}
+
+let projectForm = ref({})
+let tableData = ref([])
+// 分页数据
+let currentPage = ref(0)
+let pageSize = ref(10)
+let total = ref(0)
+const initData = () => {
+  api.projectApi.getSchemeList({
+    pages: currentPage.value,
+    size: pageSize.value,
+    companyId: companyId.value,
+    productId: projectForm.value.productId,
+    schemeType: projectForm.value.schemeType,
+    ruleDesc: projectForm.value.ruleDesc
+  }).then(res => {
+    if(res.code == 200) {
+      tableData.value = res.data.records
+      total.value = res.data.total
+    } else {
+      ElMessage({
+        message: res.msg,
+        type: 'warning'
+      })
+    }
+  })
+}
+// 险种类型
+let insuranceType = ref([])
+const initType = () => {
+  api.commonApi.personInfo('insurance_type').then(res => {
+    if(res.code == 200) {
+      insuranceType.value = res.data
+    } else {
+      ElMessage({
+        message: res.msg,
+        type: 'warning'
+      })
+    }
+  })
+}
+const handleSizeChange = (size) => {
+  pageSize.value = size
+  initData()
+}
+const handleCurrentChange = (page) => {
+  currentPage.value = page
+  initData()
+}
+
+// 编辑
+const handleEdit = (row) => {
+    myRouter.push({
+      path: '/quoteConfig/agreement/project/projectAdd',
+      query: {
+        id: row.id
+      }
+    })
+}
+// 删除
+const handleDel = (row) => {
+  ElMessageBox({
+    title: '删除方案',
+    message: '确定要删除该方案吗?',
+    type: 'warning',
+    showCancelButton: true,
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+  }).then(( action ) => {
+    if(action === 'confirm') {
+      api.projectApi.deleteScheme({
+        id: row.id
+      }).then(res => {
+        if(res.code == 200) {
+          ElMessage({
+            message: res.msg,
+            type: 'success'
+          })
+          initData()
+        } else {
+          ElMessage({
+            message: res.msg,
+            type: 'warning'
+          })
+        }
+      })
+    }
+  }).catch(( action ) => {
+
+  })
+}
+
+onMounted(() => {
+  // initData()
+  initCompany()
+  initType()
+})
+
+</script>
+
+<style scoped lang="scss">
+.project{
+  width: 100%;
+  height: 100%;
+  background: white;
+  h4,p{
+    margin: 5px 0;
+  }
+  .companyList{
+    height: 700px;
+    overflow-y: auto;
+    margin-top: 20px;
+    .companyItem{
+      padding: 10px;
+      cursor: pointer;
+      margin: 10px 0;
+      width: 100%;
+      overflow: hidden;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+      &:hover{
+        background: #f0f0f0;
+      }
+    }
+    .active{
+      padding: 10px;
+      background: #F0F0F0;
+      color: #00a3ec;
+    }
+  }
+  .paginationBox{
+    display: flex;
+    justify-content: center;
+    margin-top: 20px;
+  }
+}
+</style>

+ 551 - 0
src/views/quoteConfig/project/modules/projectAdd.vue

@@ -0,0 +1,551 @@
+<template>
+  <div class="projectAdd">
+    <el-form :model="projectAddFrom" :rules="projectAddRules" ref="ProjectAddRef">
+      <el-descriptions title="基本信息">
+        <el-descriptions-item>
+          <el-form-item label="保险公司" prop="companyId">
+            <el-select :disabled="isEdit" v-model="projectAddFrom.companyId" placeholder="请选择" style="width: 40%" @change="initProduct">
+              <el-option v-for="item in companyOption" :key="item.id" :value="item.id" :label="item.nameSimple"></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="方案类型" prop="schemeType">
+            <el-radio-group :disabled="isEdit" v-model="projectAddFrom.schemeType" @change="schemeTypeChange">
+              <el-radio :value="1">基础方案</el-radio>
+              <el-radio :value="2">自选方案</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="险种类型" prop="productId">
+            <el-radio-group :disabled="isEdit" v-model="projectAddFrom.productId" @change="initProduct">
+              <el-radio-button v-for="item in insuranceType" :key="item.value" :value="item.value">{{ item.label }}</el-radio-button>
+            </el-radio-group>
+          </el-form-item>
+          <div style="display: flex;">
+            <span style="width: 10%"><a style="color: red">*</a>规则条件</span>
+<!--            <div class="ruleList">
+              <div class="ruleItem">
+                <el-input placeholder="请填写" style="width: 300px"></el-input>&nbsp;
+                <el-select style="width: 100px">
+                  <el-option key="1" value="1" label="="></el-option>
+                  <el-option key="2" value="2" label=">"></el-option>
+                  <el-option key="3" value="3" label="<"></el-option>
+                </el-select>&nbsp;
+                <el-input placeholder="请填写" style="width: 300px"></el-input>&nbsp;
+                <el-icon><CircleCloseFilled /></el-icon>&nbsp;
+              </div>
+            </div>-->
+            <div style="width: 60%">
+              <rule-condition-container :rulesAttrList="rulesAttrList"></rule-condition-container>
+            </div>
+            <rule-condition :ruleConditionVisible="rulesShow" :attrIdList="rulesAttrList" @save="saveRule"></rule-condition>
+          </div>
+          <el-button style="margin: 10px 10%" plain type="primary" icon="plus" @click="addRules">添加条件</el-button>
+          <el-descriptions>
+            <template #title>
+              <div style="display: flex; align-items: center">
+                <span>方案配置</span>
+                <el-button v-show="!isEdit" type="primary" icon="plus" style="margin-left: 20px;" @click="addProject">添加新方案</el-button>
+              </div>
+            </template>
+            <el-descriptions-item>
+              <div class="configuration" v-for="(item, index) in projectAddFrom.schemeList">
+                <el-form-item label="方案名称" prop="schemeName">
+                  <el-input v-model="item.schemeName"></el-input>
+                </el-form-item>
+                <el-form-item label="方案描述" prop="description">
+                  <el-input type="textarea" v-model="item.description" maxlength="500"></el-input>
+                </el-form-item>
+                <div>
+                  <div style="display: flex; align-items: center; margin-bottom: 10px">
+                    <span style="margin-right: 20px">驾意险</span>
+                    <el-button link icon="plus" type="primary" @click="handleDriving(item, index)">选择驾意险</el-button>
+                  </div>
+                  <el-table
+                    :data="item.drivingList"
+                  >
+                    <el-table-column label="名称" prop="productName">
+                      <template #default="scope">
+                        <a>{{ scope.row.productName }}</a>
+                      </template>
+                    </el-table-column>
+                    <el-table-column label="客户端状态" prop="selectedStatus">
+                      <template #default="scope">
+                        <el-select v-model="scope.row.selectedStatus">
+                          <el-option label="默认不选中" key="1" value="1"></el-option>
+                          <el-option label="默认选中" key="2" value="2"></el-option>
+                          <el-option label="强制选中" key="3" value="3"></el-option>
+                        </el-select>
+                      </template>
+                    </el-table-column>
+                    <el-table-column label="份数" prop="quantity">
+                      <template #default="scope">
+                        <el-input v-model="scope.row.quantity" style="width: 100px;"></el-input>份
+                      </template>
+                    </el-table-column>
+                    <el-table-column label="操作">
+                      <template #default="scope">
+                        <el-button link type="primary" @click="drivingTypeDel(item, scope.row)">删除</el-button>
+                      </template>
+                    </el-table-column>
+                  </el-table>
+                </div>
+                <div>
+                  <div style="display: flex; align-items: center; margin: 10px 0">
+                    <span style="margin-right: 20px">险种</span>
+                    <el-button link icon="plus" type="primary" @click="handlePopover(item, index)">选择险种</el-button>
+<!--                    <el-popover placement="bottom-end" trigger="click" width="500" v-model:visible="insuranceShow" @show="handlePopover(item)">
+                      <template #reference>
+                        <el-button link icon="plus" type="primary">选择险种</el-button>
+                      </template>
+
+                    </el-popover>-->
+                  </div>
+                  <el-table
+                    :data="item.tableData"
+                  >
+                    <el-table-column label="险种" prop="kindName">
+                      <template #default="scope">
+                        <a>{{ scope.row.kindName }}</a>
+                      </template>
+                    </el-table-column>
+                    <el-table-column label="保额" prop="optionId">
+                      <template #default="scope">
+                        <el-select v-if="projectAddFrom.schemeType === 2" v-model="scope.row.moneyId" style="width: 150px" multiple>
+                          <el-option v-for="m in scope.row.options" :label="m.label" :key="m.id" :value="m.id"></el-option>
+                        </el-select>
+                        <el-select v-else v-model="scope.row.moneyId" style="width: 150px" @change="">
+                          <el-option v-for="m in scope.row.options" :label="m.label" :key="m.id" :value="m.id"></el-option>
+                        </el-select>
+                      </template>
+                    </el-table-column>
+                    <el-table-column label="操作">
+                      <template #default="scope">
+                        <el-button link type="primary" @click="insuranceTypeDel(item, scope.row)">删除</el-button>
+                      </template>
+                    </el-table-column>
+                  </el-table>
+                </div>
+              </div>
+            </el-descriptions-item>
+          </el-descriptions>
+        </el-descriptions-item>
+      </el-descriptions>
+    </el-form>
+    <div class="btns">
+      <el-button plain @click="quit">取消</el-button>
+      <el-button type="primary" @click="save(ProjectAddRef)">保存</el-button>
+    </div>
+    <el-dialog
+      v-model="drivingShow"
+      title="选择驾意险"
+      width="700"
+    >
+      <driving ref="DrivingTypeRef" @close="handleDrivingClose"></driving>
+    </el-dialog>
+    <el-dialog
+      v-model="insuranceShow"
+      title="选择险种"
+      width="500"
+    >
+      <insurance ref="InsuranceTypeRef" @close="handleClose" ></insurance>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+
+import api from "@/api";
+import { ElMessage, FormInstance, FormRules } from "element-plus";
+import { useRouter, useRoute } from 'vue-router'
+import insurance from '../../../../layouts/components/InsuranceType/index.vue'
+import driving from '../../../../layouts/components/DrivingType/index.vue'
+import rule from '../../../../components/ruleConditionContainer/index.vue'
+
+const myRouter = useRouter()
+const myRoute = useRoute()
+const ProjectAddRef = ref<FormInstance>()
+const InsuranceTypeRef = ref(null)
+const DrivingTypeRef = ref(null)
+
+// 是否编辑
+let isEdit = ref(false)
+let projectAddFrom = ref({
+  ruleAttrs: [],
+  schemeList: [{
+    schemeName: '',
+    description: '',
+    tableData: [],
+    drivingList: [],
+    schemeInsuranceList: [],
+    drivingIntentionList: []
+  }]
+})
+let projectAddRules = ref<FormRules>({
+  companyId: [
+    { required: true, trigger: "change", message: "请选择保险公司" }
+  ],
+  schemeType: [
+    { required: true, trigger: "change", message: "请选择方案类型" }
+  ],
+  productId: [
+    { required: true, trigger: "change", message: "请选择险种类型" }
+  ]
+})
+
+// 新增的时候初始化已有的方案
+const initProduct = () => {
+  api.projectApi.getSchemeByCompany({
+    companyId: projectAddFrom.value.companyId,
+    productId: projectAddFrom.value.productId,
+    schemeType: projectAddFrom.value.schemeType
+  }).then(res => {
+    if(res.code == 200) {
+      if(res.data && res.data.schemeList.length > 0) {
+        res.data.schemeList.forEach(item => {
+          item.drivingList = item.drivingIntentionList
+          item.tableData = item.schemeInsuranceList.map(sub => {
+            let obj = insuranceTypeList.value.find(a => a.id === sub.insuranceId)
+            return {
+              ...sub,
+              optionId: res.data.schemeType===1?`${sub.optionId[0]}`:sub.optionId,
+              moneyId: res.data.schemeType===1?`${sub.optionId[0]}`:sub.optionId,
+              options: obj.options
+            }
+          })
+        })
+        projectAddFrom.value = res.data
+        rulesAttrList.value = res.data.ruleAttrs
+      }
+    } else {
+      projectAddFrom.value.ruleAttrs = []
+      projectAddFrom.value.schemeList  = [{
+        schemeName: '',
+        description: '',
+        tableData: [],
+        drivingList: [],
+        schemeInsuranceList: [],
+        drivingIntentionList: []
+      }]
+      ElMessage({
+        message: res.msg,
+        type: 'warning'
+      })
+    }
+  })
+}
+
+// 险种
+let insuranceTypeList = ref([])
+const initInsurance = () => {
+  api.projectApi.getBusinessInsurance().then(res => {
+    if(res.code == 200) {
+      // res.data.forEach(item => {
+      //   item.options.forEach(sub => {
+      //     sub.id = sub.value
+      //   })
+      // })
+      insuranceTypeList.value = res.data
+    } else {
+      ElMessage({
+        message: res.msg,
+        type: 'warning'
+      })
+    }
+  })
+}
+
+// 方案类型切换
+const schemeTypeChange = (e) => {
+  projectAddFrom.value.ruleAttrs = []
+  projectAddFrom.value.schemeList  = [{
+    schemeName: '',
+    description: '',
+    tableData: [],
+    drivingList: [],
+    schemeInsuranceList: [],
+    drivingIntentionList: []
+  }]
+  initProduct()
+  // rulesAttrList.value = defaultAttrList.value
+}
+
+// 保险公司
+let companyOption = ref([])
+const initCompany = () => {
+  api.projectApi.getCompanyList().then(res => {
+    if(res.code == 200) {
+      companyOption.value = res.data
+    } else {
+      ElMessage({
+        message: res.msg,
+        type: 'warning'
+      })
+    }
+  })
+}
+
+// 新增方案
+const addProject = () => {
+  projectAddFrom.value.schemeList.push({
+    schemeName: '',
+    description: '',
+    tableData: [],
+    drivingList: [],
+    schemeInsuranceList: [],
+    drivingIntentionList: []
+  })
+}
+
+// 规则
+let rulesShow = ref(false)
+const addRules = () => {
+  rulesShow.value = true
+}
+let rulesAttrList = ref([])
+let defaultAttrList = ref([])
+const saveRule = (data) => {
+  if(data && data.length > 0) {
+    rulesAttrList.value = data
+  }
+  rulesShow.value = false
+}
+
+// 险种类型
+let insuranceType = ref([])
+const initType = () => {
+  api.commonApi.personInfo('insurance_type').then(res => {
+    if(res.code == 200) {
+      insuranceType.value = res.data
+      projectAddFrom.value.productId = insuranceType.value[0].value
+    } else {
+      ElMessage({
+        message: res.msg,
+        type: 'warning'
+      })
+    }
+  })
+}
+
+// 险种
+let insuranceShow = ref(false)
+// let insuranceList = ref([])
+// let checkInsuranceList = ref([])
+// let checkSubInsuranceList = ref([])
+const handleClose = ( obj ) => {
+  if(obj.list && obj.list.length > 0) {
+    if(projectAddFrom.value.schemeType === 2) {
+      obj.list.forEach(a => {
+        a.optionId = []
+        a.options.forEach(b => {
+          if(b.checked) {
+            a.optionId.push(b.id)
+          }
+        })
+        a.moneyId = a.optionId
+      })
+    } else {
+      obj.list.forEach(a => {
+        a.optionId = a.options.find(b => b.checked)?.id
+        a.moneyId = a.optionId
+      })
+    }
+    projectAddFrom.value.schemeList[obj.index].tableData = obj.list
+  }
+  insuranceShow.value = false
+}
+
+// 险种删除
+const insuranceTypeDel = (item, row) => {
+  let data = item.tableData.filter(a => a.id != row.id)
+  item.tableData = [...data]
+}
+
+// 驾意险
+let drivingShow = ref(false)
+const handleDriving = (item, index) => {
+  drivingShow.value = true
+  nextTick(() => {
+    DrivingTypeRef.value.initEditData(item.drivingList, index)
+  })
+}
+const handleDrivingClose = ( obj ) => {
+  if(obj.list && obj.list.length > 0) {
+    let selectList = obj.list.filter(a => a.checked)
+    selectList.forEach(sub => {
+      sub.drivingIntentionId = sub.drivingIntentionId
+      sub.selectedStatus = sub.selectedStatus?sub.selectedStatus:'1'
+      sub.quantity = sub.quantity?sub.quantity:'1'
+    })
+    projectAddFrom.value.schemeList[obj.index].drivingList = selectList
+  }
+  drivingShow.value = false
+}
+const drivingTypeDel = (item, row) => {
+  let data = item.drivingList.filter(a => a.id != row.id)
+  item.drivingList = [...data]
+}
+
+const handlePopover = (item, index) => {
+  insuranceShow.value = true
+  nextTick(() => {
+    InsuranceTypeRef.value.initEditData(item.tableData, index, projectAddFrom.value.schemeType)
+  })
+}
+
+// 规则
+const initAttr = () => {
+  api.agreementApi.ruleAttrGroup({ attrName: '' }).then(res => {
+    if(res.code == 200) {
+      let list = res.data?.VehicleInformation?.filter(e => e.attrName === '能源类型' || e.attrName === '使用性质' || e.attrName === '车辆类型')
+      rulesAttrList.value = [...list]
+      defaultAttrList.value = [...list]
+    } else {
+      ElMessage({
+        message: res.msg,
+        type: 'warning'
+      })
+    }
+  })
+}
+
+// 编辑
+const initEdit = () => {
+  api.projectApi.getScheme(myRoute.query.id).then(res => {
+    if(res.code == 200) {
+      if(res.data && res.data.schemeList.length > 0) {
+        res.data.schemeList.forEach(item => {
+          item.drivingList = item.drivingIntentionList
+          item.tableData = item.schemeInsuranceList.map(sub => {
+            let obj = insuranceTypeList.value.find(a => a.id === sub.insuranceId)
+            return {
+              ...sub,
+              optionId: res.data.schemeType===1?`${sub.optionId[0]}`:sub.optionId,
+              moneyId: res.data.schemeType===1?`${sub.optionId[0]}`:sub.optionId,
+              options: obj.options
+            }
+          })
+        })
+        projectAddFrom.value = res.data
+        rulesAttrList.value = res.data.ruleAttrs
+      }
+    } else {
+      ElMessage({
+        message: res.msg,
+        type: 'warning'
+      })
+    }
+  })
+}
+
+// 取消
+const quit = () => {
+  myRouter.push({
+    path: '/quoteConfig/agreement/project'
+  })
+}
+// 保存
+const save = async (ProjectAddRef: FormInstance | undefined) => {
+  if(!ProjectAddRef) {
+    ProjectAddRef.resetFields()
+    return
+  }
+  await ProjectAddRef.validate((valid) => {
+    if(valid) {
+      let formData = {...projectAddFrom.value}
+      formData.ruleAttrs = [...rulesAttrList.value]
+      formData.schemeList.forEach(item => {
+        item.drivingIntentionList = []
+        item.schemeInsuranceList = []
+        item.drivingList.forEach(sub => {
+          let obj = {
+            id: sub.id,
+            drivingIntentionId: sub.drivingIntentionId,
+            selectedStatus: sub.selectedStatus,
+            quantity: sub.quantity
+          }
+          item.drivingIntentionList.push(obj)
+        })
+        item.tableData.forEach(ins => {
+          let obj = {
+            id: ins.id,
+            insuranceId: ins.insuranceId,
+            optionId: projectAddFrom.value.schemeType === 1?[ins.moneyId]:projectAddFrom.value.schemeType === 2?ins.moneyId:[]
+          }
+          // ins.options.forEach(sub => {
+          //   if(sub.checked) {
+          //     obj.optionId.push(sub.id)
+          //   }
+          // })
+          item.schemeInsuranceList.push(obj)
+        })
+      })
+      api.projectApi.saveScheme({...formData}).then(res => {
+        if(res.code == 200) {
+          ElMessage({
+            message: res.msg,
+            type: 'success'
+          })
+          myRouter.push({
+            path: '/quoteConfig/agreement/project'
+          })
+        } else {
+          ElMessage({
+            message: res.msg,
+            type: 'warning'
+          })
+        }
+      })
+    } else {
+      ElMessage({
+        message: '请正确填写相关信息!',
+        type: 'warning'
+      })
+    }
+  })
+}
+
+onMounted(() => {
+  initInsurance()
+  initType()
+  initCompany()
+  // initAttr()
+  if(myRoute.query.id) {
+    isEdit.value = true
+    initEdit()
+  } else {
+    isEdit.value = false
+    initAttr()
+  }
+  // initInsurance()
+})
+
+</script>
+
+<style scoped lang="scss">
+.projectAdd{
+  width: 100%;
+  height: 100%;
+  background: white;
+  padding: 20px;
+  .ruleList{
+    width: 90%;
+    .ruleItem{
+      display: flex;
+      align-items: center;
+      justify-content: flex-start;
+      margin: 10px 0;
+    }
+  }
+  .configuration{
+    width: 100%;
+    background: #f0f0f0;
+    padding: 20px 100px;
+    margin-bottom: 10px;
+    border-radius: 5px;
+  }
+  .btns{
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+}
+</style>

+ 396 - 0
src/views/representative/externalCostApproval.vue

@@ -0,0 +1,396 @@
+<template>
+  <div>
+    <PageMain class="vh100">
+      <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
+        <el-tab-pane :label="`待审核(${pendingReviewlength})`" name="pendingReview">
+        </el-tab-pane>
+        <el-tab-pane :label="`审核不通过(${reviewRejectedlength})`" name="reviewRejected">
+        </el-tab-pane>
+      </el-tabs>
+      <SearchForm :fields="fields" :formInline="formInline" :onSearch="agreementQuery" :onReset="resetForm" />
+      <ComplexTable :tableData="tableData" :columns="columns" :showIndex="false" :columnwidth="200" :showSelect="true"
+        :pageInfo="pageInfo" @getList="getList" @selectionChange="selectionChange">
+        <template v-slot:table-title-btn>
+          <el-dropdown placement="bottom-start">
+            <el-button> 批量管理<el-icon>
+                <arrow-down />
+              </el-icon>
+            </el-button>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item :disabled="!sysUpAndDownList.length" @click="batchaudit">批量审核</el-dropdown-item>
+                <el-dropdown-item :disabled="!sysUpAndDownList.length" @click="batchrejected">批量驳回</el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </template>
+        <template v-slot:operation="scope">
+          <el-button v-if="scope.operationData.auditStatus == '0'" type="primary"  link @click="agreemenUpdate(scope.operationData.id)"> 审核
+          </el-button>
+          <el-button v-if="scope.operationData.auditStatus == '2'" type="primary" link
+            @click="agreemenUpdate(scope.operationData.id)"> 详情
+          </el-button>
+        </template>
+      </ComplexTable>
+    </PageMain>
+    <!-- 费用编辑组件 -->
+    <contactPersonFeeDialogEdit :ruleFeeVisible="ruleFeeEditVisible" :headerTitle="ruleFeeHeaderTitle"
+      :costitemEditInfo="costitemEditInfo" @save="ruleFeeEditSave" @cancel="ruleFeeEditVisible = false"
+      operationType="audit" @rejectOperation="rejectOperation" @auditOperation="auditOperation" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive } from 'vue'
+import { useRoute } from 'vue-router';
+import { ElMessage, ElMessageBox  } from 'element-plus'
+import { loadDicts, getDict } from '@/utils/composables/dictService';//字典组件
+import contactPersonFeeDialogEdit from "../externalAgreement/agreement/components/contactPersonFeeDialogEdit.vue";
+
+import api from '@/api';
+interface PageInfo {
+  pageNum: number;
+  pageSize: number;
+  sizes: number[];
+  total: number;
+}
+onBeforeMount(async () => {
+  await loadDicts(['valid_status', 'is_enable', 'audit_status', 'insurance_type']);//获取多个字典
+  if (id.value) {
+    formInline.agreement = id.value;
+  }
+  findpage();//列表数据
+  valid_status.value = getDict('valid_status');//生效状态
+  is_enable.value = getDict('is_enable');//协议状态
+  insurance_type.value = getDict('insurance_type')
+  audit_status.value = getDict('audit_status')
+
+});
+const activeName = ref('pendingReview')
+const pendingReviewlength = ref(0);
+const reviewRejectedlength = ref(0);
+const tableData = ref([]);
+const sysUpAndDownList = ref<string[]>([])//选中id
+const insurance_type = ref();
+const valid_status = ref();
+const audit_status = ref();
+const is_enable = ref();
+const route = useRoute();
+const id = ref(route.query.id as string);//id参数
+const pageInfo = ref({
+  pageNum: 1,
+  pageSize: 20,
+  sizes: [10, 20, 30, 40, 50],
+  total: 100,
+});
+const formInline = reactive({
+  agreement: '',//协议信息
+  productId: "",//归属
+  rulesName: '',//保险公司id
+  costRules: '',//协议类型
+  agreementStatus: "",//有效状态
+  auditStatus: "0",//协议状态
+  validState: "",
+  effectiveTime: [],
+  failureTime: [],
+})
+//表格字段数据
+const fields = ref([
+  { label: "所属协议", model: "agreement", type: "input", placeholder: "输入代码,名称,简称查找" },
+  {
+    label: "险种", model: "productId", type: "select", options: insurance_type,
+  },
+  {
+    label: "承保规则", model: "rulesName", type: "input", placeholder: "输入业务规则查找",
+  },
+  {
+    label: "费用规则", model: "costRules", type: "input", placeholder: "输入费用规则关键字查找",
+  },
+
+  {
+    label: "费用状态", model: "agreementStatus", type: "select", placeholder: "选择状态", options: is_enable,
+  },
+  {
+    label: "有效状态", model: "validState", type: "select", placeholder: "请选择状态", options: valid_status,
+  },
+  {
+    label: "生效时间", model: "effectiveTime", type: "date", dateType: "datetimerange", startplaceholder: "开始时间", endplaceholder: "结束时间",
+    onChange: (value: any) => {
+      console.log(value)
+      formInline.effectiveTime = value;
+    }
+  },
+  {
+    label: "失效时间", model: "failureTime", type: "date", dateType: "datetimerange", startplaceholder: "开始时间", endplaceholder: "结束时间",
+    onChange: (value: any) => {
+      formInline.failureTime = value;
+    }
+  },
+]);
+
+//费用数据查询
+function findpage() {
+  api.agreementApi.CostExternalFeePageList({ pages: pageInfo.value.pageNum, size: pageInfo.value.pageSize, ...formInline }).then((res: any) => {
+    if (res.code == '200') {
+      tableData.value = res.data.records;
+      pageInfo.value.pageNum = res.data.current;
+      pageInfo.value.pageSize = res.data.size;
+      pageInfo.value.total = res.data.total;
+      if (res.data.records[0]?.auditStatus == '0') {
+        pendingReviewlength.value = res.data.total;
+      } else {
+        reviewRejectedlength.value = res.data.total;
+      }
+    } else {
+    }
+  });
+}
+
+const columns = [
+  {
+    prop: "agreementAbbreviation", label: "协议信息", width: '300',
+    formatter: (row: any) => {
+      return `${row.agreementCode}\n${row.agreementAbbreviation}`
+    }
+  },
+  { prop: "productName", label: "险种", width: '120', },
+  {
+    prop: "contactPerson", label: "对接人", width: '200', formatter: (row: any) => {
+      return `${row.contactPerson} ${row.contactMobile}`
+    }
+  },
+  {
+    prop: "isInquiry", label: "是否现询", width: '120', formatter: (row: any) => {
+      if (row.isInquiry == '0') {
+        return '是'
+      } else {
+        return "否"
+      }
+    }
+  },
+  {
+    prop: "auditMethod", label: "审核方式", width: '120', formatter: (row: any) => {
+      if (row.auditMethod == '0') {
+        return '自动审核'
+      } else {
+        return "手动审核"
+      }
+    }
+  },
+  {
+    prop: "isEnabled", label: "费用状态", width: '100', component: "Tag",
+    formatter: (row: any) => {
+      let name = getDict('is_enable').find(val => val.value == row.isEnabled).label
+      if (name) {
+        return { type: 'primary', text: name };
+      }
+    }
+  },
+  {
+    prop: "auditStatus", label: "审核状态", width: '120', component: "Tag",
+    formatter: (row: any) => {
+      let name = getDict('audit_status').find(val => val.value == row.auditStatus).label
+      if (name) {
+        return { type: 'warning', text: name };
+      }
+    }
+  },
+  { prop: "effectiveTime", label: "生效时间", width: '200' },
+  { prop: "failureTime", label: "失效时间", width: '200' },
+  {
+    prop: "costExternalTypeAdds", label: "交强险费用", width: '300', formatter: (row: any) => {
+      let info = row.costExternalTypeAdds.find((val: any) => val.costType == '交强险');
+      if (info) {
+        return `入口:${info.inlet.toFixed(2)}% 出口:${info.export.toFixed(2)}%\n一级:${info.level1?.toFixed(2)}% 五级:${info.level5?.toFixed(2)}%`
+      } else {
+        return '-'
+      }
+    }
+  },
+  {
+    prop: "costExternalTypeAdds", label: "商业险费用", width: '300', formatter: (row: any) => {
+      let info = row.costExternalTypeAdds.find((val: any) => val.costType == '商业险');
+      if (info) {
+        return `入口:${info.inlet.toFixed(2)}% 出口:${info.export.toFixed(2)}%\n一级:${info.level1?.toFixed(2)}% 五级:${info.level5?.toFixed(2)}%`
+      } else {
+        return '-'
+      }
+    }
+  },
+  {
+    prop: "costExternalTypeAdds", label: "驾意险费用", width: '300', formatter: (row: any) => {
+      let info = row.costExternalTypeAdds.find((val: any) => val.costType == '驾意险');
+      if (info) {
+        return `入口:${info.inlet.toFixed(2)}% 出口:${info.export.toFixed(2)}%\n一级:${info.level1?.toFixed(2)}% 五级:${info.level5?.toFixed(2)}%`
+      } else {
+        return '-'
+      }
+    }
+  },
+];
+//分页事件
+function getList(item: PageInfo) {
+  pageInfo.value.pageNum = item.pageNum;
+  pageInfo.value.pageSize = item.pageSize;
+  findpage();
+
+};
+// 费用编辑
+const ruleFeeEditVisible = ref(false);
+const ruleFeeHeaderTitle = ref();//编辑弹窗title
+const costitemEditInfo = ref();
+const agreemenUpdate = async (id: any) => {
+  api.agreementApi.FeegetById(id).then((res: any) => {
+    if (res.code == '200') {
+      ruleFeeHeaderTitle.value = res.data.productName;
+      costitemEditInfo.value = res.data;
+      ruleFeeEditVisible.value = true;
+    } else {
+    }
+  });
+}
+//费用编辑
+const ruleFeeEditSave = (data: object) => {
+  api.agreementApi.FeeupdateById(data).then((res: any) => {
+    if (res.code == '200') {
+      ElMessage({
+        type: 'success',
+        message: res.msg,
+        plain: true,
+      })
+      ruleFeeEditVisible.value = false;
+      findpage();
+    } else {
+    }
+  });
+}
+//多选事件
+const selectionChange = (val: any) => {
+  sysUpAndDownList.value = val;
+}
+//批量删除
+const batchaudit = () => {
+  ElMessageBox.confirm(
+    '确定审核通过该费用吗?',
+    '审核通过',
+    {
+      confirmButtonText: '确定',
+      type: 'info',
+      center: true,
+      confirmButtonClass: 'el-button--primary',
+    }
+  )
+    .then(() => {
+      ExternalauditSuccess(sysUpAndDownList.value)
+    })
+
+}
+//批量驳回
+const batchrejected = () => {
+  ElMessageBox.prompt('', '审核不通过', {
+    confirmButtonText: '确定',
+    type: 'warning',
+    center: true,
+    inputType: "textarea",
+    inputPlaceholder: "请输入不通过原因",
+    inputPattern: /^[\u4e00-\u9fa5,。!?、;:“”‘’《》【】()——]+$/,
+    inputErrorMessage: '请输入中文',
+  })
+    .then(({ value }) => {
+      ExternalauditFailed(sysUpAndDownList.value, value)
+    })
+}
+//单一审核回调
+const auditOperation = (automatic: any, id: any) => {
+  ExternalauditSuccess([id], automatic)
+}
+//单一驳回回调
+const rejectOperation = (automatic: any, id: any, value: any) => {
+  ExternalauditFailed([id], value, automatic)
+}
+const agreementQuery = () => {
+  findpage();
+}
+const ExternalauditSuccess = async (list: string[], automatic?: boolean) => {
+  await api.agreementApi.ExternalauditSuccess({ ids: list, automatic: automatic }).then((res: any) => {
+    if (res.code == '200') {
+      findpage();
+      ElMessage({
+        type: 'success',
+        message: res.msg,
+        plain: true,
+      })
+      ruleFeeEditVisible.value = false;
+      if (res.data) {
+        let info: any = tableData.value.find((val: any) => val.id == res.data);
+        if (info) {
+          agreemenUpdate(info.id)
+        }
+      }
+    }
+  });
+}
+const ExternalauditFailed = async (list: string[], value: string, automatic?: boolean) => {
+  await api.agreementApi.ExternalauditFailed({ ids: list, reason: value, automatic: automatic }).then((res: any) => {
+    if (res.code == '200') {
+      findpage();
+      ElMessage({
+        type: 'success',
+        message: res.msg,
+        plain: true,
+      })
+      ruleFeeEditVisible.value = false;
+      if (res.data) {
+        let info: any = tableData.value.find((val: any) => val.id == res.data);
+        if (info) {
+          agreemenUpdate(info.id)
+        }
+      }
+    }
+  });
+}
+const resetForm = () => {
+  formInline.agreement = '';//协议信息
+  formInline.productId = "";//归属
+  formInline.rulesName = '';//保险公司id
+  formInline.costRules = '';//协议类型
+  formInline.agreementStatus = "";//有效状态
+  formInline.auditStatus = "";//协议状态
+  formInline.validState = "";
+  formInline.effectiveTime = [];
+  formInline.failureTime = [];
+  findpage();
+}
+// 批量编辑
+const handleClick = (pane: any) => {
+  if (pane.props.name == 'pendingReview') {
+    formInline.auditStatus = '0';
+    findpage()
+  } else {
+    formInline.auditStatus = '2';
+    findpage();
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.demo-form-inline .el-input {
+  --el-input-width: 220px;
+}
+
+.demo-form-inline .el-select {
+  --el-select-width: 220px;
+}
+
+:deep(.el-dropdown-link) {
+  display: flex;
+  align-items: center;
+  margin-left: 15px;
+  color: var(--el-color-primary);
+  cursor: pointer;
+}
+
+.el-inputs {
+  width: 100% !important;
+}
+</style>

+ 504 - 0
src/views/representative/quoteApproval.vue

@@ -0,0 +1,504 @@
+<template>
+  <div>
+    <PageMain class="vh100">
+      <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
+        <el-tab-pane :label="`待审核(${pendingReviewlength})`" name="pendingReview">
+        </el-tab-pane>
+        <el-tab-pane :label="`审核不通过(${reviewRejectedlength})`" name="reviewRejected">
+        </el-tab-pane>
+      </el-tabs>
+      <SearchForm :fields="fields" :formInline="formInline" :onSearch="agreementQuery" :onReset="resetForm" />
+      <ComplexTable :tableData="tableData" :columns="columns" :showIndex="false" :columnwidth="200" :showSelect="true"
+        :pageInfo="pageInfo" @getList="getList" @selectionChange="selectionChange" uniqueIdName="costId">
+        <template v-slot:table-title-btn>
+          <el-dropdown placement="bottom-start" v-if="activeName == 'pendingReview'">
+            <el-button> 批量审核<el-icon>
+                <arrow-down />
+              </el-icon>
+            </el-button>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item :disabled="!sysUpAndDownList.length" @click="batchaudit">批量审核</el-dropdown-item>
+                <el-dropdown-item :disabled="!sysUpAndDownList.length" @click="batchrejected">批量驳回</el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </template>
+        <template v-slot:operation="scope">
+          <el-button v-if="scope.operationData.approved == '0'" type="primary" link
+            @click="agreemenUpdate(scope.operationData)"> 审核
+          </el-button>
+          <el-button v-if="scope.operationData.approved == '2'" type="primary" link
+            @click="agreemenUpdate(scope.operationData)"> 详情
+          </el-button>
+        </template>
+      </ComplexTable>
+    </PageMain>
+
+    <!-- 规则费用编辑组件 -->
+    <ruleFeeDialogEdit :ruleFeeVisible="ruleFeeEditVisible" :headerTitle="ruleFeeHeaderTitle"
+      :agreementInfo="AgreementInfo" :RulesInfo="ruleInfo" :costitemEditInfo="costitemEditInfo" @save="ruleFeeEditSave"
+      @cancel="ruleFeeEditVisible = false" operationType="audit" @rejectOperation="rejectOperation" @auditOperation="auditOperation" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive } from 'vue'
+import { useRoute, } from 'vue-router';
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { loadDicts, getDict } from '@/utils/composables/dictService';//字典组件
+import ruleFeeDialogEdit from "../quoteConfig/agreement/components/ruleFeeDialogEdit.vue";
+import api from '@/api';
+interface PageInfo {
+  pageNum: number;
+  pageSize: number;
+  sizes: number[];
+  total: number;
+}
+onBeforeMount(async () => {
+  await loadDicts(['valid_status', 'is_enable', 'audit_status', 'insurance_type']);//获取多个字典
+  if (id.value) {
+    formInline.agreement = id.value;
+  }
+  findpage();//列表数据
+  getDeptTree();
+  agreement();
+  valid_status.value = getDict('valid_status');//生效状态
+  is_enable.value = getDict('is_enable');//协议状态
+  insurance_type.value = getDict('insurance_type')
+  audit_status.value = getDict('audit_status')
+
+});
+const tableData = ref([
+]);
+const activeName = ref('pendingReview')
+const agreementQueryName = ref();//选择协议查询
+const agreementList = ref();//协议列表
+const sysUpAndDownList = ref<string[]>([])//选中id
+const deptOptions = ref()//机构数据
+const insurance_type = ref();
+const valid_status = ref();
+const audit_status = ref();
+const is_enable = ref();
+const route = useRoute();
+const id = ref(route.query.id as string);//id参数
+const pendingReviewlength = ref(0);
+const reviewRejectedlength = ref(0);
+const pageInfo = ref({
+  pageNum: 1,
+  pageSize: 20,
+  sizes: [10, 20, 30, 40, 50],
+  total: 100,
+});
+const formInline = reactive({
+  agreement: '',//协议信息
+  productId: "",//归属
+  rulesName: '',//保险公司id
+  costRules: '',//协议类型
+  agreementStatus: "",//有效状态
+  auditStatus: "0",//审核状态
+  validState: "",
+  effectiveTime: [],
+  failureTime: [],
+})
+
+//表格字段数据
+const fields = ref([
+  { label: "所属协议", model: "agreement", type: "input", placeholder: "输入代码,名称,简称查找" },
+  {
+    label: "险种", model: "productId", type: "select", options: insurance_type,
+  },
+  {
+    label: "承保规则", model: "rulesName", type: "input", placeholder: "输入业务规则查找",
+  },
+  {
+    label: "费用规则", model: "costRules", type: "input", placeholder: "输入费用规则关键字查找",
+  },
+
+  {
+    label: "费用状态", model: "agreementStatus", type: "select", placeholder: "选择状态", options: is_enable,
+  },
+  {
+    label: "有效状态", model: "validState", type: "select", placeholder: "请选择状态", options: valid_status,
+  },
+  {
+    label: "生效时间", model: "effectiveTime", type: "date", dateType: "datetimerange", startplaceholder: "开始时间", endplaceholder: "结束时间",
+    onChange: (value: any) => {
+      console.log(value)
+      formInline.effectiveTime = value;
+    }
+  },
+  {
+    label: "失效时间", model: "failureTime", type: "date", dateType: "datetimerange", startplaceholder: "开始时间", endplaceholder: "结束时间",
+    onChange: (value: any) => {
+      formInline.failureTime = value;
+    }
+  },
+]);
+
+//费用数据查询
+function findpage() {
+  api.agreementApi.queryByFeePage({ pages: pageInfo.value.pageNum, size: pageInfo.value.pageSize, ...formInline }).then((res: any) => {
+    if (res.code == '200') {
+      tableData.value = res.data.records;
+      pageInfo.value.pageNum = res.data.current;
+      pageInfo.value.pageSize = res.data.size;
+      pageInfo.value.total = res.data.total;
+      if (res.data.records[0].approved == '0') {
+        pendingReviewlength.value = res.data.total;
+      } else {
+        reviewRejectedlength.value = res.data.total;
+      }
+    } else {
+    }
+  });
+}
+//协议列表查询
+function agreement() {
+  api.agreementApi.agreementList({ pages: pageInfo.value.pageNum, size: pageInfo.value.pageSize, searchValue: agreementQueryName.value }).then((res: any) => {
+    if (res.code == '200') {
+      agreementList.value = res.data.records;
+    } else {
+    }
+  });
+}
+const columns = [
+  {
+    prop: "agreementAbbreviation", label: "协议信息", width: '300',
+    formatter: (row: any) => {
+      return `${row.agreementCode}\n${row.agreementAbbreviation}`
+    }
+  },
+  {
+    prop: "ruleDescription", label: "承保规则", width: '500',
+  },
+  { prop: "productName", label: "险种", width: '120', },
+  {
+    prop: "costRules", label: "费用规则", width: '500',
+  },
+  {
+    prop: "isEnabled", label: "费用状态", width: '100', component: "Tag",
+    formatter: (row: any) => {
+      let name = getDict('is_enable').find(val => val.value == row.isEnabled).label
+      if (name) {
+        return { type: 'primary', text: name };
+      }
+    }
+  },
+  {
+    prop: "approved", label: "审核状态", width: '120', component: "Tag",
+    formatter: (row: any) => {
+      let name = getDict('audit_status').find(val => val.value == row.approved).label
+      if (name) {
+        return { type: 'warning', text: name };
+      }
+    }
+  },
+  { prop: "effectiveTime", label: "生效时间", width: '200' },
+  { prop: "failureTime", label: "失效时间", width: '200' },
+  {
+    prop: "ruleConditions", label: "交强费用", width: '300', formatter: (row: any) => {
+      let info = row.ruleConditions.find((val: any) => val.costType == '交强险');
+      if (info) {
+        return `入口:${info.inlet.toFixed(2)}% 出口:${info.export.toFixed(2)}%\n加投:${info.addThrow.toFixed(2)}% 手续费:${info.commission.toFixed(2)}%`
+      } else {
+        return '-';
+      }
+    }
+  },
+  {
+    prop: "ruleConditions", label: "商业费用", width: '300', formatter: (row: any) => {
+      let info = row.ruleConditions.find((val: any) => val.costType == '商业险');
+      if (info) {
+        return `入口:${info.inlet.toFixed(2)}% 出口:${info.export.toFixed(2)}%\n加投:${info.addThrow.toFixed(2)}% 手续费:${info.commission.toFixed(2)}%`
+      } else {
+        return '-';
+      }
+    }
+  },
+  {
+    prop: "ruleConditions", label: "驾意险费用", width: '300', formatter: (row: any) => {
+      let info = row.ruleConditions.find((val: any) => val.costType == '驾意险');
+      if (info) {
+        return `入口:${info.inlet.toFixed(2)}% 出口:${info.export.toFixed(2)}%\n加投:${info.addThrow.toFixed(2)}% 手续费:${info.commission.toFixed(2)}%`
+      } else {
+        return '-';
+      }
+    }
+  },
+];
+//分页事件
+function getList(item: PageInfo) {
+  pageInfo.value.pageNum = item.pageNum;
+  pageInfo.value.pageSize = item.pageSize;
+  findpage();
+
+};
+// 审核
+const agreemenUpdate = async (item: any) => {
+  ruleFeeHeaderTitle.value = item.productName;
+  AgreementInfo.value = { agreementCode: item.agreementCode, agreementTitle: item.agreementAbbreviation };
+  ruleInfo.value = { ruleDescription: item.ruleDescription };
+  await regionLicenseRegion('0');//车牌地区
+  await regionLicenseNumber('14');//车牌号
+  await api.agreementApi.AgreementFeeById(item.costId).then((res: any) => {
+    if (res.code == '200') {
+      res.data.ruleConditions = processArray(res.data.ruleConditions);
+      costitemEditInfo.value = res.data;
+      ruleFeeEditVisible.value = true;
+    } else {
+    }
+  });
+}
+//详情
+const ruleFeeEditSave = (data: object) => {
+  console.log()
+  api.agreementApi.AgreementFeeupdate(data).then((res: any) => {
+    if (res.code == '200') {
+      ElMessage({
+        type: 'success',
+        message: res.msg,
+        plain: true,
+      })
+      ruleFeeEditVisible.value = false;
+      findpage();
+    } else {
+    }
+  });
+}
+//添加车牌和车牌地区
+const processArray = (array: any[]) => {
+  return array.map((val) => {
+    switch (val.attrType) {
+      case 'inputNumber':
+      case 'datetime':
+        if (!val.operator) {
+          val.operator = '1'; // 设置默认值
+        }
+        break;
+      default:
+        break;
+    }
+    if (val.attrCode == 'LicenseNo') {
+      val.dictLabal = licensePlateNumber.value;
+    }
+    if (val.attrCode == 'LicenseArea') {
+      val.dictLabal = licensePlateRegion.value;
+    }
+    return val;
+  });
+};
+const licensePlateRegion = ref();//车牌地区
+const licensePlateNumber = ref();//车牌号
+const regionLicenseRegion = (value: string) => {
+  api.agreementApi.getAreaLicense(value).then((res: any) => {
+    if (res.code == '200') {
+      let data: any = [];
+      res.data.map((val: any) => {
+        data.push({
+          code: val.areaNumber,
+          name: val.license,
+        })
+        return val;
+      })
+      licensePlateRegion.value = data;
+    } else {
+    }
+  });
+}
+const regionLicenseNumber = (value: string) => {
+  api.agreementApi.getAreaLicense(value).then((res: any) => {
+    if (res.code == '200') {
+      let data: any = [];
+      res.data.map((val: any) => {
+        data.push({
+          code: val.areaNumber,
+          name: val.license,
+        })
+        return val;
+      })
+      licensePlateNumber.value = data;
+    } else {
+    }
+  });
+}
+//多选事件
+const selectionChange = (val: any) => {
+  console.log(val)
+  sysUpAndDownList.value = val;
+}
+
+//批量审核
+const batchaudit = () => {
+  ElMessageBox.confirm(
+    '确定审核通过该费用吗?',
+    '审核通过',
+    {
+      confirmButtonText: '确定',
+      type: 'info',
+      center: true,
+      confirmButtonClass: 'el-button--primary',
+    }
+  )
+    .then(() => {
+      auditSuccess(sysUpAndDownList.value)
+    })
+}
+//批量驳回
+const batchrejected = () => {
+  ElMessageBox.prompt('', '审核不通过', {
+    confirmButtonText: '确定',
+    type: 'warning',
+    center: true,
+    inputType: "textarea",
+    inputPlaceholder: "请输入不通过原因",
+    inputPattern: /^[\u4e00-\u9fa5,。!?、;:“”‘’《》【】()——]+$/,
+    inputErrorMessage: '请输入中文',
+  })
+    .then(({ value }) => {
+      auditFailed(sysUpAndDownList.value, value)
+    })
+}
+//单一审核回调
+const auditOperation=(automatic:any,id:any)=>{
+  auditSuccess([id],automatic)
+}
+//单一驳回回调
+const rejectOperation=(automatic:any,id:any,value:any)=>{
+  auditFailed([id], value,automatic)
+}
+const agreementQuery = () => {
+  findpage();
+}
+//审核不通过
+const auditFailed = async (list: string[], value: string,automatic?:boolean) => {
+  await api.agreementApi.auditFailed({ids:list, reason:value,automatic:automatic}).then((res: any) => {
+    if (res.code == '200') {
+      findpage();
+      ElMessage({
+        type: 'success',
+        message: res.msg,
+        plain: true,
+      })
+      ruleFeeEditVisible.value=false;
+      if(res.data){
+        let info=tableData.value.find((val:any)=>val.costId==res.data);
+        agreemenUpdate(info)
+      }
+    }
+  });
+}
+// 审核通过
+const auditSuccess = async (list: string[],automatic?:boolean) => {
+  await api.agreementApi.auditSuccess({ ids: list,automatic:automatic}).then((res: any) => {
+    if (res.code == '200') {
+      findpage();
+      ElMessage({
+        type: 'success',
+        message: res.msg,
+        plain: true,
+      })
+      ruleFeeEditVisible.value=false;
+      if(res.data){
+        let info=tableData.value.find((val:any)=>val.costId==res.data);
+        agreemenUpdate(info)
+      }
+    }
+  });
+}
+const resetForm = () => {
+  formInline.agreement = '';//协议信息
+  formInline.productId = "";//归属
+  formInline.rulesName = '';//保险公司id
+  formInline.costRules = '';//协议类型
+  formInline.agreementStatus = "";//有效状态
+  formInline.auditStatus = "";//协议状态
+  formInline.validState = "";
+  formInline.effectiveTime = [];
+  formInline.failureTime = [];
+  findpage();
+}
+const getDeptTree = () => {
+  api.institutionApi.institutionList({}).then((res: any) => {
+    if (res.code == '200') {
+      deptOptions.value = res.data;
+    } else {
+    }
+  });
+}
+//选择协议弹窗
+const ruleFeeEditVisible = ref(false);//编辑弹窗
+const ruleFeeHeaderTitle = ref();//编辑弹窗title
+const costitemEditInfo = ref();//编辑对象
+const AgreementInfo = ref();//协议对象
+const ruleInfo = ref();//规则对象
+
+const handleClick = (pane: any) => {
+  if (pane.props.name == 'pendingReview') {
+    formInline.auditStatus = '0';
+    findpage()
+  } else {
+    formInline.auditStatus = '2';
+    findpage();
+  }
+}
+
+</script>
+
+<style lang="scss" scoped>
+.demo-form-inline .el-input {
+  --el-input-width: 220px;
+}
+
+.demo-form-inline .el-select {
+  --el-select-width: 220px;
+}
+
+:deep(.el-dropdown-link) {
+  display: flex;
+  align-items: center;
+  margin-left: 15px;
+  color: var(--el-color-primary);
+  cursor: pointer;
+}
+
+.el-inputs {
+  width: 100% !important;
+}
+
+.Selection {
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+
+  .agreement {
+    width: 40%;
+    padding: 15px;
+    border-right: 1px solid #dcdfe6;
+
+    .agreementItem {
+      padding: 10px;
+      border-radius: 5px;
+
+      &:hover {
+        background: #f2f2f2;
+      }
+    }
+  }
+
+  .active {
+    color: #1890ff;
+    background: #f2f2f2;
+  }
+
+  .rule {
+    width: 60%;
+    padding: 15px;
+
+    .white-space {
+      display: inline-block;
+      margin-bottom: 10px;
+      white-space: normal;
+    }
+  }
+}
+</style>

+ 14 - 7
src/views/representative/salesman.vue

@@ -495,21 +495,24 @@ onMounted(() => {
   display: flex;
   align-items: center;
   justify-content: space-between;
+
   > div {
     display: flex;
   }
 }
+
 .institution {
-  background: #fff;
-  width: 100%;
   // height: 100%;
   // padding: 20px;
   box-sizing: border-box;
   display: flex;
+  width: 100%;
+  background: #fff;
+
   :deep(.demo-tabs) {
+    box-sizing: border-box;
     width: 100%;
     padding: 20px;
-    box-sizing: border-box;
   }
   // .institution-main {
   //   flex: 1;
@@ -518,23 +521,27 @@ onMounted(() => {
   //   box-sizing: border-box;
   // }
 }
+
 :deep(.el-dropdown-link) {
-  cursor: pointer;
-  color: var(--el-color-primary);
   display: flex;
   align-items: center;
   margin-left: 15px;
+  color: var(--el-color-primary);
+  cursor: pointer;
 }
+
 .custom-tree-node {
-  flex: 1;
   display: flex;
+  flex: 1;
   align-items: center;
   justify-content: space-between;
-  font-size: 14px;
   padding-right: 8px;
+  font-size: 14px;
 }
+
 :deep(.el-message-box__container) {
   width: 100% !important;
+
   .el-message-box__message {
     width: 100% !important;
   }