@dongkboy 7 mēneši atpakaļ
vecāks
revīzija
5e28ed7f65
31 mainītis faili ar 6081 papildinājumiem un 27 dzēšanām
  1. 1 1
      .env.development
  2. 6 0
      package-lock.json
  3. 1 0
      package.json
  4. 0 14
      src/api/adManagement/placementPlan.ts
  5. 122 0
      src/api/channel/businessConfig.ts
  6. 115 0
      src/api/channel/distributionRecord.ts
  7. 13 0
      src/api/channel/isInvalidRecords.ts
  8. 70 0
      src/api/channel/management.ts
  9. 0 0
      src/api/channel/usageRecords.ts
  10. 83 0
      src/api/organization/institution.ts
  11. 62 0
      src/api/organization/member.ts
  12. 73 0
      src/api/organization/role.ts
  13. 1 1
      src/api/sys/menu.ts
  14. 1 1
      src/api/sys/model/userModel.ts
  15. 2 2
      src/api/sys/user.ts
  16. 3 1
      src/assets/style/common.less
  17. 4 3
      src/store/modules/user.ts
  18. 863 0
      src/views/channel/baseRightsConfig/baseRightsConfigAdd.vue
  19. 437 0
      src/views/channel/baseRightsConfig/baseRightsConfigList.vue
  20. 428 0
      src/views/channel/distributionDetails.vue
  21. 580 0
      src/views/channel/distributionRecord/add.vue
  22. 350 0
      src/views/channel/distributionRecord/details.vue
  23. 345 0
      src/views/channel/distributionRecord/pageList.vue
  24. 246 0
      src/views/channel/isInvalidRecords.vue
  25. 417 0
      src/views/channel/management.vue
  26. 261 0
      src/views/channel/usageRecords.vue
  27. 0 0
      src/views/organizationStructure/memberManagement/add.vue
  28. 725 0
      src/views/organizationStructure/memberManagement/pageList.vue
  29. 376 0
      src/views/organizationStructure/orgManagement.vue
  30. 491 0
      src/views/organizationStructure/roleManagement.vue
  31. 5 4
      src/views/system/loginmini/MiniLogin.vue

+ 1 - 1
.env.development

@@ -16,7 +16,6 @@ VITE_PROXY = [["/jeecgboot","http://192.168.0.115:8082/jeecg-boot"],["/upload","
 
 #后台接口全路径地址(必填)
 # VITE_GLOB_DOMAIN_URL=https://test.baoxianzhanggui.com/locallive-java  # 测试
-#
 VITE_GLOB_DOMAIN_URL=http://192.168.0.115:8082/jeecg-boot # 屈晨
 # VITE_GLOB_DOMAIN_URL=http://192.168.0.36:8080/jeecg-boot  # 魏瑞杰
 # VITE_GLOB_DOMAIN_URL=http://192.168.0.52:8080/jeecg-boot  # 贺礼霄
@@ -29,6 +28,7 @@ VITE_GLOB_API_URL=/jeecgboot
 # 接口前缀
 VITE_GLOB_API_URL_PREFIX=
 
+
 #微前端qiankun应用,命名必须以VITE_APP_SUB_开头,jeecg-app-1为子应用的项目名称,也是子应用的路由父路径
 VITE_APP_SUB_jeecg-app-1 = '//localhost:8092'
 

+ 6 - 0
package-lock.json

@@ -30,6 +30,7 @@
         "cropperjs": "^1.6.1",
         "crypto-js": "^4.2.0",
         "dayjs": "^1.11.10",
+        "decimal.js": "^10.6.0",
         "dom-align": "^1.12.4",
         "echarts": "^5.4.3",
         "emoji-mart-vue-fast": "^15.0.1",
@@ -6862,6 +6863,11 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/decimal.js": {
+      "version": "10.6.0",
+      "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
+      "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="
+    },
     "node_modules/decode-uri-component": {
       "version": "0.2.2",
       "resolved": "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz",

+ 1 - 0
package.json

@@ -42,6 +42,7 @@
     "cropperjs": "^1.6.1",
     "crypto-js": "^4.2.0",
     "dayjs": "^1.11.10",
+    "decimal.js": "^10.6.0",
     "dom-align": "^1.12.4",
     "echarts": "^5.4.3",
     "emoji-mart-vue-fast": "^15.0.1",

+ 0 - 14
src/api/adManagement/placementPlan.ts

@@ -87,17 +87,3 @@ export function queryTree() {
     isTransformResponse: false,
   },);
 }
-/**
- * 省市区街道联动收据
- * @param
- * @returns 
- */
-export function upload(data) {
-  return defHttp.post({
-    url: '/sys/common/upload',
-    data
-  },
-  {
-    isTransformResponse: false,
-  },);
-}

+ 122 - 0
src/api/channel/businessConfig.ts

@@ -0,0 +1,122 @@
+import { defHttp } from '/@/utils/http/axios';
+//渠道业务配置
+
+
+// 添加配置
+export function supplierConfigAdd(data) {
+  return defHttp.request({
+    url: '/channel/supplier-config',
+    method: 'post',
+    data
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+// id启用禁用 配置
+export function supplierConfigEnable(data) {
+  return defHttp.request({
+    url: '/channel/supplier-config/disable/enable',
+    method: 'put',
+    data
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+// id配置查询
+export function supplierConfigById(params) {
+  return defHttp.get({
+    url: '/channel/supplier-config/' + params,
+  },
+    {
+      isTransformResponse: false,
+    }
+  );
+}
+
+// id删除
+export function supplierConfigDel(id) {
+  return defHttp.request({
+    url: '/channel/supplier-config/' + id,
+    method: 'delete',
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+// 修改业务配置
+export function supplierConfigEdit(data, id) {
+  return defHttp.request({
+    url: '/channel/supplier-config/' + id,
+    method: 'put',
+    data
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+// 业务配置分页查询
+export function supplierConfigPageList(params) {
+  return defHttp.get({
+    url: '/channel/supplier-config/page',
+    params
+  });
+}
+
+// 获取业务配置渠道机构
+export function getChannelDeptList() {
+  return defHttp.get({
+    url: '/channel/institution/all',
+  },
+    {
+      isTransformResponse: false,
+    });
+}
+// 获取规则参数
+export function getchannelRulesParams() {
+  return defHttp.get({
+    url: '/channel/config/distribution/rules',
+  },
+    {
+      isTransformResponse: false,
+    });
+}
+// 配置参数-api接口配置字段
+export function channelapiConfigPermissions() {
+  return defHttp.get({
+    url: '/channel/config/interface/permissions',
+  },
+    {
+      isTransformResponse: false,
+    });
+}
+// 供应商列表
+export function localPurveyorInfoAllList() {
+  return defHttp.get({
+    url: '/merchant/localPurveyorInfo/allList',
+  },
+    {
+      isTransformResponse: false,
+    });
+}
+// 查询关键信息
+export function channelKeyInfo(params) {
+  return defHttp.get({
+    url: '/channel/key/information/' + params,
+  },
+    {
+      isTransformResponse: false,
+    }
+  );
+}
+// 查询关键信息
+export function regenerateAppId(params) {
+  return defHttp.get({
+    url: '/channel/key/information/regenerate/app-search/' + params,
+  },
+    {
+      isTransformResponse: false,
+    }
+  );
+}

+ 115 - 0
src/api/channel/distributionRecord.ts

@@ -0,0 +1,115 @@
+import { defHttp } from '/@/utils/http/axios';
+//渠道业务配置
+
+
+//发放记录添加
+export function addDistributionRecord(data) {
+  return defHttp.request({
+    url: '/channel/grantRecord/add',
+    method: 'post',
+    data,
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+//发放权益id作废
+export function cancelDistributionRecord(data) {
+  return defHttp.request({
+    url: '/channel/grantRecord/cancelByGrantRightsId',
+    method: 'post',
+    data,
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+//发放记录id作废
+export function cancelDistributionRecordByRecordId(data) {
+  return defHttp.request({
+    url: '/channel/grantRecord/cancelByRecordId',
+    method: 'post',
+    data,
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+// 发放记录分页列表查询
+export function getDistributionRecordList(params) {
+  return defHttp.get({
+    url: '/channel/grantRecord/list',
+    params,
+  }
+  );
+}
+// 发放记录详情查询
+export function getDistributionRecordDetail(params) {
+  return defHttp.get({
+    url: '/channel/grantRecord/queryById?id=' + params,
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+// 发放记录校验
+export function validateBeforeSubmit(data) {
+  return defHttp.post({
+    url: '/channel/grantRecord/validateBeforeSubmit',
+    method: 'post',
+    data,
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+// 机构id获取渠道权益包
+export function getChannelRightsByInstitution(params) {
+  return defHttp.get({
+    url: '/channel/rightsRela/queryChannelRightsByInstitution',
+    params,
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+
+
+
+
+// 发放明细
+// 发放明细分页列表
+export function getDistributionDetailList(params) {
+  return defHttp.get({
+    url: '/channel/grantDetail/list',
+    params,
+  }
+  );
+}
+// 导出接口
+export function grantDetailexportXls() {
+  return defHttp.get({
+    url: '/channel/grantDetail/exportXls',
+  }
+  );
+}
+// 明细作废
+export function cancelDistributionDetailByDetailId(data) {
+  return defHttp.request({
+    url: '/channel/grantDetail/cancel',
+    method: 'post',
+    data,
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+// 发放明细详情查询
+export function getDistributionDetailDetail(params) {
+  return defHttp.get({
+    url: '/channel/grantDetail/detail?detailId=' + params,
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}

+ 13 - 0
src/api/channel/isInvalidRecords.ts

@@ -0,0 +1,13 @@
+import { defHttp } from '/@/utils/http/axios';
+//渠道业务配置
+
+
+
+// 作废记录分页列表查询
+export function getCancelRecordList(params) {
+  return defHttp.get({
+    url: '/channel/cancelRecord/list',
+    params,
+  }
+  );
+}

+ 70 - 0
src/api/channel/management.ts

@@ -0,0 +1,70 @@
+import { defHttp } from '/@/utils/http/axios';
+
+//渠道管理
+
+/**
+ * 添加渠道
+ * @param data  表单数据
+ * @returns 
+ */
+export function managementAdd(data) {
+  return defHttp.request({
+    url: '/channel/management',
+    method: 'post',
+    data
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+/**
+ * 渠道修改
+ * @param
+ * @returns 
+ */
+export function managementEdit(data, id) {
+  return defHttp.request({
+    url: '/channel/management/' + id,
+    method: 'put',
+    data
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+/**
+ * 渠道分页查询
+ * @param
+ * @returns 
+ */
+export function managementPage(params) {
+  return defHttp.get({
+    url: '/channel/management/page',
+    params
+  });
+}
+
+/**
+ * 渠道详情查询
+ * @param 
+ * @returns 
+ */
+export function managementDetail(params) {
+  return defHttp.get({
+    url: '/channel/management/' + params,
+  },
+  {
+    isTransformResponse: false,
+  },);
+}
+// 渠道禁用
+export function isChannelEnabled(params) {
+  return defHttp.request({
+    url: '/channel/management/disable/enable',
+    method: 'put',
+    params
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}

+ 0 - 0
src/api/channel/usageRecords.ts


+ 83 - 0
src/api/organization/institution.ts

@@ -0,0 +1,83 @@
+import { defHttp } from '/@/utils/http/axios';
+
+//机构管理
+
+
+export function institutionAdd(data) {
+  return defHttp.request({
+    url: '/channel/institution',
+    method: 'post',
+    data
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+// 获取机构树
+export function institutionAll() {
+  return defHttp.get({
+    url: '/channel/institution/all',
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+// 机构列表树
+export function institutionList(params) {
+  return defHttp.get({
+    url: '/channel/institution/list',
+    params
+  });
+}
+// 机构管理-判断机构下是否还存在人
+export function institutionExists(params) {
+  return defHttp.get({
+    url: '/channel/institution/exists/' + params,
+  },
+  {
+    isTransformResponse: false,
+  },);
+}
+// 机构管理-通过机构id查询机构
+export function institutionQueryId(params) {
+  return defHttp.get({
+    url: '/channel/institution/' + params,
+  },
+  {
+    isTransformResponse: false,
+  },);
+}
+// 修改机构
+export function institutionUpdate(data,params) {
+  return defHttp.request({
+    url: '/channel/institution/' + params,
+    method: 'put',
+    data
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+// 删除机构
+export function institutionDelete(params) {
+  return defHttp.request({
+    url: '/channel/institution/' + params,
+    method: 'delete',
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+/**
+ * 省市区街道联动收据
+ * @param
+ * @returns 
+ */
+export function queryTree() {
+  return defHttp.post({
+    url: '/sys/area/queryTree',
+  },
+  {
+    isTransformResponse: false,
+  },);
+}

+ 62 - 0
src/api/organization/member.ts

@@ -0,0 +1,62 @@
+import { defHttp } from '/@/utils/http/axios';
+
+//机构管理
+
+//用户管理-添加用户
+export function userAdd(data) {
+  return defHttp.request({
+    url: '/channel/user',
+    method: 'post',
+    data
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+
+// 成员分页查询
+export function userPage(params) {
+  return defHttp.get({
+    url: '/channel/user/page',
+    params
+  });
+}
+
+// 用户id查询
+export function userQueryId(params) {
+  return defHttp.get({
+    url: '/channel/user/' + params,
+  },
+    {
+      isTransformResponse: false,
+    },);
+}
+// 修改用户
+export function userUpdate(data, params) {
+  return defHttp.request({
+    url: '/channel/user/' + params,
+    method: 'put',
+    data
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+// 删除用户
+export function userDelete(params) {
+  return defHttp.request({
+    url: '/channel/user/' + params,
+    method: 'delete',
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+// 机构列表树
+export function institutionList() {
+  return defHttp.get({
+    url: '/channel/institution/list',
+  }, {
+    isTransformResponse: false,
+  },);
+}

+ 73 - 0
src/api/organization/role.ts

@@ -0,0 +1,73 @@
+import { defHttp } from '/@/utils/http/axios';
+
+//角色管理
+
+//角色管理-添加角色
+export function roleAdd(data) {
+  return defHttp.request({
+    url: '/channel/role',
+    method: 'post',
+    data
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+// 角色管理-判断角色下是否存在用户
+export function roleExists(params) {
+  return defHttp.get({
+    url: '/channel/role/exists/' + params,
+  },
+  {
+    isTransformResponse: false,
+  });
+}
+// 角色管理-分页查询
+export function rolePage() {
+  return defHttp.get({
+    url: '/channel/role/list',
+    
+  }, {
+    isTransformResponse: false,
+  });
+}
+
+// 角色管理-通过角色id查询角色
+export function roleQueryId(params) {
+  return defHttp.get({
+    url: '/channel/role/' + params,
+  },
+    {
+      isTransformResponse: false,
+    },);
+}
+// 修改角色
+export function roleUpdate(data, params) {
+  return defHttp.request({
+    url: '/channel/role/' + params,
+    method: 'put',
+    data
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+// 删除角色
+export function roleDelete(params) {
+  return defHttp.request({
+    url: '/channel/role/' + params,
+    method: 'delete',
+  }, {
+    isTransformResponse: false,
+  },
+  );
+}
+
+// 获取菜单权限
+export function getRoleMenu() {
+  return defHttp.get({
+    url: '/channel/permission/tree',
+  }, {
+    isTransformResponse: false,
+  });
+}

+ 1 - 1
src/api/sys/menu.ts

@@ -2,7 +2,7 @@ import { defHttp } from '/@/utils/http/axios';
 import { getMenuListResultModel } from './model/menuModel';
 
 enum Api {
-  GetMenuList = '/sys/permission/getUserPermissionByToken',
+  GetMenuList = '/channel/permission/authentication/tree',
   // 【QQYUN-8487】
   // SwitchVue3Menu = '/sys/switchVue3Menu',
 }

+ 1 - 1
src/api/sys/model/userModel.ts

@@ -2,7 +2,7 @@
  * @description: Login interface parameters
  */
 export interface LoginParams {
-  username: string;
+  account: string;
   password: string;
 }
 

+ 2 - 2
src/api/sys/user.ts

@@ -13,7 +13,7 @@ import { ExceptionEnum } from "@/enums/exceptionEnum";
 const { createErrorModal } = useMessage();
 enum Api {
   Login = '/sys/login',
-  LoginNew = '/platformUser/localPlatformUser/login',
+  LoginNew = '/channel/auth/login',
   phoneLogin = '/sys/phoneLogin',
   Logout = '/sys/logout',
   GetUserInfo = '/sys/user/getUserInfo',
@@ -23,7 +23,7 @@ enum Api {
   // 3、系统安全模式
   GetPermCode = '/sys/permission/getPermCode',
   //新加的获取图形验证码的接口
-  getInputCode = '/sys/randomImage',
+  getInputCode = '/channel/auth/captcha',
   //获取短信验证码的接口
   getCaptcha = '/sys/sms',
   //注册接口

+ 3 - 1
src/assets/style/common.less

@@ -287,7 +287,9 @@
   display: flex;
 
 }
-
+.dis{
+  display: flex;
+}
 .f-wrap {
   flex-wrap: wrap;
 }

+ 4 - 3
src/store/modules/user.ts

@@ -152,10 +152,10 @@ export const useUserStore = defineStore({
         const result = await loginApiNew(loginParams, 'none');
         console.log(result);
         if (result.data.code == 200) {
-          const { token, userInfo } = result.data.result;
+          const { token } = result.data.result;
           // save token
           this.setToken(token);
-          this.setTenant(userInfo.loginTenantId);
+          // this.setTenant(userInfo.loginTenantId);
           return this.afterLoginAction(goHome, result.data.result);
         } else if (result.data.code == 511) {
           //账号异常,请联系客服解除异常。
@@ -207,11 +207,12 @@ export const useUserStore = defineStore({
     async afterLoginAction(goHome?: boolean, data?: any): Promise<any | null> {
       if (!this.getToken) return null;
       //获取用户信息
-      const userInfo = await this.getUserInfoAction();
+      // const userInfo = await this.getUserInfoAction();
       const sessionTimeout = this.sessionTimeout;
       if (sessionTimeout) {
         this.setSessionTimeout(false);
       } else {
+        console.log(11111)
         //update-begin---author:scott ---date::2024-02-21  for:【QQYUN-8326】登录不需要构建路由,进入首页有构建---
         // // 构建后台菜单路由
         // const permissionStore = usePermissionStore();

+ 863 - 0
src/views/channel/baseRightsConfig/baseRightsConfigAdd.vue

@@ -0,0 +1,863 @@
+<!-- 模板区域 -->
+<template>
+  <div class="body">
+    <a-spin :spinning="spinning">
+      <div class="card">
+        <a-form ref="formRef" :model="businessConfigForm" labelAlign="right" :rules="businessConfigFormRules"
+          :label-col="labelCol">
+          <div class="card-header dis a-c mb-2">
+            <div class="link"></div>
+            <span>基本信息</span>
+          </div>
+          <div class="horizontal-section">
+            <a-row>
+              <a-col :span="6">
+                <a-form-item label="渠道机构" name="institutionId">
+                  <a-tree-select v-model:value="businessConfigForm.institutionId" show-search style="width: 100%"
+                    :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }" placeholder="Please select" allow-clear
+                    tree-default-expand-all :tree-data="treeData" :field-names="{
+                      children: 'node',
+                      label: 'institutionName',
+                      value: 'id',
+                    }" tree-node-filter-prop="institutionName">
+                  </a-tree-select>
+                </a-form-item>
+              </a-col>
+              <a-col :span="6">
+                <a-form-item label="联系人" name="contact">
+                  <a-input v-model:value="businessConfigForm.contact" showCount maxlength="20" placeholder="请输入联系人" />
+                </a-form-item>
+              </a-col>
+              <a-col :span="6">
+                <a-form-item label="联系人手机号" name="contactPhone">
+                  <a-input v-model:value="businessConfigForm.contactPhone" showCount maxlength="11"
+                    placeholder="请输入联系人手机号" allowClear />
+                </a-form-item>
+              </a-col>
+              <a-col :span="6">
+                <a-form-item label="联系地址">
+                  <a-input v-model:value="businessConfigForm.contactAddr" placeholder="请输入联系地址" />
+                </a-form-item>
+              </a-col>
+              <a-col :span="6">
+                <a-form-item label="邮箱">
+                  <a-input v-model:value="businessConfigForm.email" placeholder="请输入邮箱" />
+                </a-form-item>
+              </a-col>
+            </a-row>
+          </div>
+          <div class="card-header dis a-c mb-2">
+            <div class="link"></div>
+            <span>权益发放场景配置</span>
+          </div>
+          <a-form-item label="对接方式" name="issueConnectionMethod">
+            <!-- 卡片单选 -->
+            <div class="flex card-Radio-container">
+              <div class="card-Radio dis f-c "
+                :class="{ 'card-Radio-active': item.value == businessConfigForm.issueConnectionMethod }"
+                @click="businessConfigForm.issueConnectionMethod = item.value"
+                v-for="item in issueConnectionMethodOptions" :key="item.value">
+                <span class="Method">{{ item.label }}</span>
+                <span class="annotation">{{ item.value == '1' ? '渠道使用H5页面,发放权益' : '渠道调用API接口,发放权益' }}</span>
+                <CheckCircleFilled v-if="item.value == businessConfigForm.issueConnectionMethod" />
+              </div>
+            </div>
+          </a-form-item>
+          <a-form-item label="权益查询配置" name="queryPermission">
+            <a-radio-group v-model:value="businessConfigForm.queryPermission">
+              <a-radio value="1">静默访问</a-radio>
+              <a-radio value="2">授权后访问</a-radio>
+            </a-radio-group>
+          </a-form-item>
+          <template v-if="businessConfigForm.issueConnectionMethod == '1'">
+            <a-col :span="8">
+              <a-form-item label="回调地址" name="callbackUrl">
+                <a-input v-model:value="businessConfigForm.callbackUrl" placeholder="请输入回调地址" />
+              </a-form-item>
+            </a-col>
+            <a-col :span="8">
+              <a-form-item label="权益查询页面位置精度" name="provinceCode">
+                <a-cascader v-model:value="queryTreeList"
+                  :field-names="{ label: 'areaCname', value: 'areaCode', children: 'children' }" :options="queryTree"
+                  placeholder="请选择位置查询精度" @change="selectAccuracy" change-on-select />
+              </a-form-item>
+            </a-col>
+            <a-col :span="8">
+              <a-form-item label="主题设置" name="issueThemeColor">
+                <a-popover trigger="click" overlayClassName="themeSettings">
+                  <template #content>
+                    <ChromePicker v-model="businessConfigForm.issueThemeColor" />
+                  </template>
+                  <div class="color-preview dis a-c j-c">
+                    <div class="colorBlock" :style="{ backgroundColor: businessConfigForm.issueThemeColor }"></div>
+                    <span>{{ businessConfigForm.issueThemeColor }}</span>
+                  </div>
+                </a-popover>
+              </a-form-item>
+            </a-col>
+            <a-form-item label="权益发放配置" name="channelRightsDistributionConfig">
+              <div class=" receivedUserInfo dis f-c">
+                <div class="dis a-c ">
+                  <a-popover trigger="click">
+                    <template #content>
+                      <a-checkbox-group v-model:value="rightsIssuanceRulesValue" class="dis f-c"
+                        @change="rightsIssuanceRulesChange">
+                        <a-checkbox :value="item.rulesId" name="type" v-for="(item, index) in rightsIssuanceRules"
+                          :disabled="item.checked" :key="index">{{ item.rulesName }}</a-checkbox>
+                      </a-checkbox-group>
+                    </template>
+                    <a-button type="link" preIcon="ant-design:plus-outlined" style="color: #018FFB;">添加字段</a-button>
+                  </a-popover>
+                  <span class="text">配置发放权益需要上传的接收人信息</span>
+                </div>
+              </div>
+            </a-form-item>
+            <div class="received ">
+              <a-space :size="[16, 16]" wrap>
+                <template v-for="(item, index) in businessConfigForm.channelRightsDistributionConfig" :key="index">
+                  <a-input-group compact>
+                    <a-input disabled :placeholder="item.rulesName" style="width: 150px" />
+                    <a-select v-model:value="item.isRequired" style="border-right: none;"
+                      @change="val => channelRightsDistributionconfigDel(val, item.rulesId)">
+                      <a-select-option :value="1">必填</a-select-option>
+                      <a-select-option :value="0">非必填</a-select-option>
+                      <a-select-option value="DEL" :disabled="item.checked">删除</a-select-option>
+                    </a-select>
+                  </a-input-group>
+                </template>
+              </a-space>
+            </div>
+          </template>
+          <template v-if="businessConfigForm.issueConnectionMethod == '2'">
+            <a-form-item label="接口权限">
+              <div class="dis f-c">
+                <div class="" v-for="item in businessConfigForm.issueChannelSceneApiConfigs">
+                  <a-checkbox class="mr-1 mb-1" v-model:checked="item.checked">{{ item.interfacePermissionName
+                    }}</a-checkbox>
+                  <div class="dis a-c mb-1">
+                    <span class="fontsize14 mr-1 ">每分钟最大调用次数</span>
+                    <a-input v-model:value="item.callCount" placeholder="请输入次数" class="w250">
+                      <template #suffix>
+                        次
+                      </template>
+                    </a-input>
+                  </div>
+                </div>
+              </div>
+            </a-form-item>
+          </template>
+          <div class="card-header dis a-c mb-2">
+            <div class="link"></div>
+            <span>消费者场景配置</span>
+          </div>
+          <a-form-item label="对接方式">
+            <!-- 卡片单选 -->
+            <div class="flex card-Radio-container">
+              <div class="card-Radio dis f-c "
+                :class="{ 'card-Radio-active': item.value == businessConfigForm.consumerConnectionMethod }"
+                @click="businessConfigForm.consumerConnectionMethod = item.value"
+                v-for="item in consumerConnectionMethodOptions" :key="item.value">
+                <span class="Method">{{ item.label }}</span>
+                <span class="annotation">{{ item.text }}</span>
+                <CheckCircleFilled v-if="item.value == businessConfigForm.consumerConnectionMethod" />
+              </div>
+            </div>
+          </a-form-item>
+          <template v-if="businessConfigForm.consumerConnectionMethod == '1'">
+            <a-form-item label="权益查询条件">
+              <div class="dis f-c table">
+                <div class="dis a-c ">
+                  <a-popover trigger="click">
+                    <template #content>
+                      <a-checkbox-group v-model:value="rightsInquiryRulesValue" class="dis f-c"
+                        @change="rightsInquiryRulesChange">
+                        <a-checkbox :value="item.rulesId" :disabled="item.checked"
+                          v-for="(item, index) in rightsInquiryRules" :key="index">{{
+                            item.rulesName }}</a-checkbox>
+                      </a-checkbox-group>
+                    </template>
+                    <a-button type="link" preIcon="ant-design:plus-outlined" style="color: #018FFB;">添加新条件</a-button>
+                  </a-popover>
+                  <span class="text">查询不到用户权益时,通过以下条件可查询到用户权益</span>
+                </div>
+                <a-table :dataSource="businessConfigForm.channelRightsInquiryRules" size="small"
+                  :columns="channelRightsInquiryRulesColumns" :pagination="false" rowClassName="rowClassName">
+                  <template #bodyCell="{ column, record }">
+                    <template v-if="column.dataIndex === 'operation'">
+                      <a-button type="link" @click="editRule(column, record)">编辑</a-button>
+                      <a-button type="link" @click="delRule(column, record)" :disabled="record.checked">删除</a-button>
+                    </template>
+                    <template v-if="column.dataIndex === 'rules'">
+                      <div v-if="record.isVerify == '1'">
+                        <span v-if="record.rules == '1'">全部字符校验</span>
+                        <span v-else-if="record.rules == '2'">校验前{{ record.number }}位{{ record.rulesName }}</span>
+                        <span v-else>校验后{{ record.number }}位{{ record.rulesName }}</span>
+                      </div>
+                      <div v-else>不校验</div>
+                    </template>
+                  </template>
+                </a-table>
+              </div>
+            </a-form-item>
+            <a-form-item label="主题设置">
+              <a-popover trigger="click" overlayClassName="themeSettings">
+                <template #content>
+                  <ChromePicker v-model="businessConfigForm.consumerThemeColor" />
+                </template>
+                <div class="color-preview dis a-c j-c">
+                  <div class="colorBlock" :style="{ backgroundColor: businessConfigForm.consumerThemeColor }"></div>
+                  <span>{{ businessConfigForm.consumerThemeColor }}</span>
+                </div>
+              </a-popover>
+            </a-form-item>
+            <a-form-item label="业务配置">
+              <a-radio-group v-model:value="businessConfigForm.businessConfig">
+                <a-radio :value="0">全部商户参与服务</a-radio>
+                <a-radio :value="1">部分商户参与服务</a-radio>
+              </a-radio-group>
+              <div class="" v-if="businessConfigForm.businessConfig == '1'">
+                <a-popover trigger="click" overlayClassName="channelSupplierConfiglist">
+                  <template #content>
+                    <a-checkbox-group v-model:value="channelSupplierConfiglistValue"
+                      @change="changeChannelSupplierConfigChange" class="dis f-c">
+                      <a-checkbox :value="item.id" v-for="(item, index) in channelSupplierConfiglist" :key="index">{{
+                        item.purveyorName || "--" }}</a-checkbox>
+                    </a-checkbox-group>
+                  </template>
+                  <a-button type="link" preIcon="ant-design:plus-outlined">选择商户供应商</a-button>
+                </a-popover>
+                <div class="dis a-c flex-wrap" style="gap: 10px;">
+                  <div class="merchantSupplier" v-for="(item, index) in businessConfigForm.channelSupplierConfig"
+                    :key="index">
+                    {{ item.supplierName }}
+                    <CloseOutlined @click="deleteSupplier(index)" />
+                  </div>
+                </div>
+              </div>
+            </a-form-item>
+          </template>
+          <template v-if="businessConfigForm.consumerConnectionMethod == '2'">
+            <a-form-item label="接口权限">
+              <div class="dis f-c">
+                <div class="" v-for="item in businessConfigForm.consumerChannelSceneApiConfigs">
+                  <a-checkbox class="mr-1 mb-1" v-model:checked="item.checked">{{ item.interfacePermissionName
+                    }}</a-checkbox>
+                  <div class="dis a-c mb-1">
+                    <span class="fontsize14 mr-1 ">每分钟最大调用次数</span>
+                    <a-input v-model:value="item.callCount" placeholder="请输入次数" class="w250">
+                      <template #suffix>
+                        次
+                      </template>
+                    </a-input>
+                  </div>
+                </div>
+              </div>
+            </a-form-item>
+          </template>
+          <template v-if="businessConfigForm.consumerConnectionMethod == '3'">
+            <a-form-item label="业务配置">
+              <div class="dis">
+                <a-alert show-icon message="选择使用生活服务,消费者的应用场景将使用“晋掌柜生活APP和小程序”供渠道的消费者使用" type="info" />
+              </div>
+            </a-form-item>
+          </template>
+        </a-form>
+      </div>
+      <!-- 操作栏 -->
+      <div class="footer flex a-c j-c">
+        <a-button @click="onCancel">取消</a-button>
+        <a-button @click="onSubmit" style="margin-left: 40px" type="primary">保存</a-button>
+      </div>
+    </a-spin>
+    <a-modal v-model:open="editRuleShow" title="编辑规则" @ok="editRuleSave" width="700px" :bodyStyle="{ padding: '16px' }">
+      <a-form ref="editRuleFormRef" :model="editRuleInfo" :label-col="RulelabelCol" :wrapper-col="RulewrapperCol"
+        style="width: 100%;">
+        <a-form-item label="查询条件">
+          <a-input v-model:value="editRuleInfo.rulesName" disabled autocomplete="off" placeholder="请输入" />
+        </a-form-item>
+        <a-form-item label="是否检验">
+          <a-radio-group v-model:value="editRuleInfo.isVerify">
+            <a-radio :value="1">是</a-radio>
+            <a-radio :value="0">否</a-radio>
+          </a-radio-group>
+        </a-form-item>
+        <a-form-item label="检验规则">
+          <a-radio-group v-model:value="editRuleInfo.rules" class="dis f-c" style="gap: 10px 0 ;">
+            <a-radio :value="1">全部字符校验</a-radio>
+            <div class="dis a-c flex">
+              <a-radio :value="2" style="flex: none;">校验接收人手机号前</a-radio>
+              <a-input v-model:value="editRuleInfo.number" :disabled="editRuleInfo.rules != '2'" placeholder="请输入"
+                style="width: 100px;" />
+              <span class="fontsize14 ml-1">位</span>
+            </div>
+            <div class="dis a-c flex">
+              <a-radio :value="3" style="flex: none;">校验接收人手机号后</a-radio>
+              <a-input v-model:value="editRuleInfo.number" :disabled="editRuleInfo.rules != '3'" placeholder="请输入"
+                style="width: 100px;" />
+              <span class="fontsize14 ml-1">位</span>
+            </div>
+          </a-radio-group>
+        </a-form-item>
+      </a-form>
+
+    </a-modal>
+  </div>
+</template>
+<!-- 行为区域 -->
+<script lang='ts' setup>
+import {
+  CheckCircleFilled,
+} from '@ant-design/icons-vue';
+import { reactive, ref, onMounted, watch } from 'vue';
+import type { UnwrapRef } from 'vue';
+import { useRouter, useRoute } from 'vue-router';
+import { CloseOutlined } from '@ant-design/icons-vue';
+import type { Rule, } from 'ant-design-vue/es/form';
+import * as businessConfigAPI from '/@/api/channel/businessConfig';
+import { type TreeSelectProps } from 'ant-design-vue';
+import * as placementPlanAPI from '/@/api/adManagement/placementPlan';
+import { ChromePicker } from 'vue-color'//颜色选择器
+import { message, Modal } from 'ant-design-vue';
+import { RefSymbol } from '@vue/reactivity';
+const router = useRouter();
+const route = useRoute();
+onMounted(async () => {
+  spinning.value = true;
+  try {
+    await regionLinkage();//省市区
+    await queryChannelOrg();//获取渠道机构
+    await getRulesParams();//获取规则参数
+    await getlocalPurveyorInfo();//获取供应商列表
+    await getapiConfigPermissions() //获取接口配置字段
+    if (route.query.id) {
+      businessConfigid.value = route.query.id
+      await getDetails()//详情
+    }
+    spinning.value = false;
+  } catch (error) {
+    spinning.value = false;
+  }
+})
+
+interface FormState {
+  [key: string]: any;//默认全类型
+}
+// 校验规则弹窗
+const formRef = ref();
+const spinning = ref(false)//加载中
+const editRuleShow = ref(false)//编辑规则弹窗
+const businessConfigid = ref()//业务配置id
+const RulelabelCol = { span: 5 };
+const RulewrapperCol = { span: 13 };
+const editRuleInfo = reactive({
+  isVerify: "",
+  number: "",
+  rules: "",
+  rulesId: "",
+  rulesName: "",
+});
+// 业务配置表单
+const businessConfigForm: UnwrapRef<FormState> = reactive({
+  institutionId: '',//机构id
+  contact: '',//联系人
+  contactPhone: '',//联系人电话
+  contactAddr: '',//联系人地址
+  email: '',//联系人邮箱  
+  issueConnectionMethod: '1',//发放对接方式  1 h5 2 api
+  consumerConnectionMethod: '1',//消费者对接方式 1 h5 2 api 3 原生(使用生活服务)
+  queryPermission: "1",//查询权益权限  1 静默访问 2 授权后访问
+  callbackUrl: '',//回调地址
+  provinceCode: '',//省份编码
+  cityCode: '',//城市编码
+  areaCode: '',//区县编码
+  issueThemeColor: '#0052d9',//权益发放主题颜色
+  consumerThemeColor: '#0052d9',//消费者主题颜色
+  businessConfig: "0",//业务配置 0 全部 1 部分
+  channelRightsDistributionConfig: [],//权益发放配置
+  channelRightsInquiryRules: [],//权益查询条件
+  issueChannelSceneApiConfigs: [],//权益发放接口权限
+  consumerChannelSceneApiConfigs: [],//消费者场景接口权限
+  channelSupplierConfig: [],//商户供应商配置
+});
+const channelRightsDistributionvalidator = async (value) => {
+  if (!businessConfigForm[value.field].some(item => item.isRequired === 1)) {
+    return Promise.reject('请至少添加一个必填字段');
+  }
+  return Promise.resolve();
+};
+const businessConfigFormRules: Record<string, Rule[]> = {
+  institutionId: [
+    { required: true, message: '请选择合作渠道机构', trigger: 'change' },
+  ],
+  contact: [
+    { required: true, message: '联系人姓名必填', trigger: 'blur' },
+  ],
+  contactPhone: [
+    { required: true, message: '联系人手机号必填', trigger: 'blur' },
+    {
+      pattern: /^[0-9]+$/,
+      message: '请输入正确手机号',
+    },
+    {
+      min: 11,
+      max: 11,
+      message: '请输入11位手机号',
+    },
+  ],
+  callbackUrl: [
+    { required: true, message: '回调地址必填', trigger: 'blur' },
+  ],
+  queryPermission: [
+    { required: true, message: '请选择查询权益权限', trigger: 'change' },
+  ],
+  issueConnectionMethod: [
+    { required: true, message: '请选择权益发放对接方式', trigger: 'change' },
+  ],
+  consumerConnectionMethod: [
+    { required: true, message: '请选择权益消费者对接方式', trigger: 'change' },
+  ],
+  provinceCode: [
+    { required: true, message: '请选择权益查询页面位置精度', trigger: 'change' },
+  ],
+  issueThemeColor: [
+    { required: true, message: '请选择权益发放主题颜色', trigger: 'change' },
+  ],
+  consumerThemeColor: [
+    { required: true, message: '请选择消费者主题颜色', trigger: 'change' },
+  ],
+  channelRightsDistributionConfig: [
+    { required: true, message: '请选择权益发放配置', trigger: 'change' },
+    { validator: channelRightsDistributionvalidator, trigger: 'change' }
+  ],
+};
+
+const channelRightsInquiryRulesColumns = [
+  {
+    title: '查询条件',
+    dataIndex: 'rulesName',
+    key: 'rulesName',
+    align: "center",
+    width: 200,
+
+  },
+  {
+    title: '检验规则',
+    dataIndex: 'rules',
+    key: 'rules',
+    align: "center",
+
+  },
+  {
+    title: '操作',
+    dataIndex: 'operation',
+    key: 'operation',
+    width: 200,
+    align: "center"
+  },
+]
+const labelCol = { style: { width: '160px' } };
+const queryTree = ref([]);//省市区数据
+const queryTreeList = ref();//省市区选中数据
+const rightsIssuanceRules = ref();//权益发放配置字段
+const rightsIssuanceRulesValue = ref([]);//权益发放配置选中值
+const rightsInquiryRules = ref();//权益查询条件列表
+const rightsInquiryRulesValue = ref([]);//权益查询条件选中值
+const channelSupplierConfiglist = ref();//商户供应商配置列表
+const channelSupplierConfiglistValue = ref(['1910251419283050497']);//商户供应商配置选中值
+
+const issueConnectionMethodOptions = ref([
+  {
+    label: 'H5链接',
+    value: '1',
+  },
+  {
+    label: 'API接入',
+    value: '2',
+  },
+]);// 对接方式选项
+const consumerConnectionMethodOptions = ref([
+  {
+    label: 'H5链接',
+    value: '1',
+    text: "渠道使用H5页面,满足消费者场景",
+  },
+  {
+    label: 'API接入',
+    value: '2',
+    text: "渠道调用API接口,满足消费者场景",
+  },
+  {
+    label: '原生(使用生活服务)',
+    value: '3',
+    text: "渠道使用生活服务应用,满足消费者场景",
+  },
+]);// 对接方式选项
+// 机构下拉选择树
+const treeData = ref<TreeSelectProps['treeData']>([]);//渠道机构数据
+watch(() => businessConfigForm.channelRightsDistributionConfig, (newValue) => {
+  // 权益发放配置必填项带入权益查询条件  businessConfigForm.channelRightsInquiryRules
+  if (newValue) {
+    if (rightsInquiryRules.value && rightsInquiryRules.value.length > 0) {
+      const List = newValue.filter(val => val.isRequired == 1);
+      const requiredRules = rightsInquiryRules.value.filter(item => List.some(id => id.rulesId === item.rulesId)).map(val => {
+        val.checked = true;
+        return val;
+      });
+      // 获取当前已有的查询规则
+      const currentRules = businessConfigForm.channelRightsInquiryRules || [];
+      // 合并逻辑:有就替换,没有就添加
+      requiredRules.forEach(requiredRule => {
+        const existingIndex = currentRules.findIndex(item => item.rulesId == requiredRule.rulesId);
+        if (existingIndex === -1) {
+          // 没有就加进去
+          currentRules.push(requiredRule);
+        } else {
+        }
+      });
+      // 更新数据
+      businessConfigForm.channelRightsInquiryRules = currentRules;
+    }
+  }
+}, { immediate: true, deep: true });
+
+const getDetails = () => {
+  businessConfigAPI.supplierConfigById(businessConfigid.value).then((res) => {
+    if (res.code == 200) {
+      Object.assign(businessConfigForm, res.result);
+      queryTreeList.value = [businessConfigForm.provinceCode, businessConfigForm.cityCode, businessConfigForm.areaCode]
+      rightsIssuanceRulesValue.value = businessConfigForm.channelRightsDistributionConfig.map(item => item.rulesId)
+      rightsInquiryRulesValue.value = businessConfigForm.channelRightsInquiryRules.map(item => item.rulesId)
+      rightsInquiryRules.value.map(item => {
+        businessConfigForm.channelRightsInquiryRules.map(val => {
+          if (item.rulesId === val.rulesId) {
+            item.checked = val.checked
+          }
+        })
+        return item;
+      })
+    }
+  })
+}
+const onSubmit = () => {
+  formRef.value
+    .validate()
+    .then(() => {
+      if (businessConfigid.value) {
+        businessConfigAPI.supplierConfigEdit(businessConfigForm, businessConfigid.value).then((res) => {
+          if (res.code == 200) {
+            message.success('修改成功');
+          } else {
+            message.error(res.message);
+          }
+        })
+      } else {
+        businessConfigAPI.supplierConfigAdd(businessConfigForm).then((res) => {
+          if (res.code == 200) {
+            message.success('添加成功');
+            setTimeout(() => {
+              window.close();
+            }, 1000);
+          } else {
+            message.error(res.message);
+          }
+        })
+      }
+
+    })
+    .catch(error => {
+      console.log('error', error);
+    });
+};
+const editRuleSave = () => {
+  businessConfigForm.channelRightsInquiryRules.map(item => {
+    if (item.rulesId === editRuleInfo.rulesId) {
+      Object.assign(item, editRuleInfo);
+    }
+    return item
+  });
+  editRuleShow.value = false;
+}
+// 获取省市区数据
+const regionLinkage = () => {
+  placementPlanAPI.queryTree().then((res) => {
+    if (res.code == 200) {
+      queryTree.value = res.result
+    }
+  });
+}
+// 获取业务配置渠道机构
+const queryChannelOrg = () => {
+  businessConfigAPI.getChannelDeptList().then((res) => {
+    if (res.code == 200) {
+      treeData.value = res.result
+    }
+  });
+}
+// 获取商户供应商
+const getlocalPurveyorInfo = () => {
+  businessConfigAPI.localPurveyorInfoAllList().then((res) => {
+    if (res.code == 200) {
+      channelSupplierConfiglist.value = res.result
+    }
+  });
+}
+// 获取接口配置字段
+const getapiConfigPermissions = () => {
+  businessConfigAPI.channelapiConfigPermissions().then((res) => {
+    if (res.code == 200) {
+      businessConfigForm.issueChannelSceneApiConfigs = res.result.issueInterfacePermissions;//权益发放接口权限
+      businessConfigForm.consumerChannelSceneApiConfigs = res.result.consumerInterfacePermissions;//消费者场景接口权限
+    }
+  });
+}
+// 获取规则参数
+const getRulesParams = () => {
+  businessConfigAPI.getchannelRulesParams().then((res) => {
+    if (res.code == 200) {
+      rightsIssuanceRules.value = res.result.rightsIssuanceRules;//权益发放字段列表 
+      rightsInquiryRules.value = res.result.rightsInquiryRules;//权益条件列表
+      // 新增时将必填项默认选中  编辑时不处理
+      if (!businessConfigid.value) {
+        businessConfigForm.channelRightsDistributionConfig = rightsIssuanceRules.value.filter((item) => item.checked)
+        rightsIssuanceRulesValue.value = rightsIssuanceRules.value.filter((item) => item.checked).map((item) => item.rulesId)
+      } else {
+        // 编辑时不处理
+      }
+    }
+  });
+}
+// 权益发放配置选中值改变
+const rightsIssuanceRulesChange = (val) => {
+  rightsIssuanceRulesValue.value = val;
+  businessConfigForm.channelRightsDistributionConfig = rightsIssuanceRules.value.filter(item => val.some(id => id === item.rulesId));
+}
+// 权益查询条件选中值改变
+const rightsInquiryRulesChange = (val) => {
+  rightsInquiryRulesValue.value = val;
+  businessConfigForm.channelRightsInquiryRules = rightsInquiryRules.value.filter(item => val.some(id => id === item.rulesId)).sort((a, b) => b.rulesId - a.rulesId);
+}
+// 商户供应商配置选中值改变
+const changeChannelSupplierConfigChange = (val) => {
+  businessConfigForm.channelSupplierConfig = channelSupplierConfiglist.value.filter(item => val.some(id => id === item.id)).map((value) => {
+    return {
+      supplierId: value.id,
+      supplierName: value.purveyorName,
+    }
+  });
+}
+// 删除权益发放配置
+const channelRightsDistributionconfigDel = (value, id) => {
+  if (value == 'DEL') {
+    // 删除 权益发放配置 中对应的项
+    const configIndex = businessConfigForm.channelRightsDistributionConfig.findIndex(item => item.rulesId == id);
+    if (configIndex !== -1) {
+      businessConfigForm.channelRightsDistributionConfig.splice(configIndex, 1);
+    }
+    // 删除 权益查询条件 中对应的项
+    const inquiryIndex = businessConfigForm.channelRightsInquiryRules.findIndex(item => item.rulesId == id);
+    if (inquiryIndex !== -1) {
+      businessConfigForm.channelRightsInquiryRules.splice(inquiryIndex, 1);
+      rightsInquiryRules.value.find(val=>val.rulesId == id).checked = false;//同步解除权益规则删除限制
+      rightsInquiryRulesValue.value = rightsInquiryRulesValue.value.filter(item => item !== id);//同步解除权益规则删除限制
+    }
+  }
+  if (value == 0) {
+    // 改为非必填,同步删除
+    const inquiryIndex = businessConfigForm.channelRightsInquiryRules.findIndex(item => item.rulesId == id);
+    if (inquiryIndex !== -1) {
+      businessConfigForm.channelRightsInquiryRules.splice(inquiryIndex, 1);
+      rightsInquiryRules.value.find(val=>val.rulesId == id).checked = false;//同步解除权益规则删除限制
+      rightsInquiryRulesValue.value = rightsInquiryRulesValue.value.filter(item => item !== id);//同步解除权益规则删除限制
+      
+    }
+
+  }
+}
+// 删除商户供应商配置
+const deleteSupplier = (index) => {
+  businessConfigForm.channelSupplierConfig.splice(index, 1);
+};
+// 选择位置精度
+async function selectAccuracy(value, selectedOptions) {
+  if (selectedOptions) {
+    businessConfigForm.provinceCode = selectedOptions[0]?.areaCode;
+    businessConfigForm.cityCode = selectedOptions[1]?.areaCode;
+    businessConfigForm.areaCode = selectedOptions[2]?.areaCode;
+  } else {
+
+  }
+}
+// 编辑规则
+const editRule = (column, record) => {
+  Object.assign(editRuleInfo, record);
+  editRuleShow.value = true;
+}
+const delRule = (column, record) => {
+  const inquiryIndex = businessConfigForm.channelRightsInquiryRules.findIndex(item => item.rulesId == record.rulesId);
+    if (inquiryIndex !== -1) {
+      businessConfigForm.channelRightsInquiryRules.splice(inquiryIndex, 1);
+      rightsInquiryRules.value.find(val=>val.rulesId == record.rulesId).checked = false;//同步解除权益规则删除限制
+      rightsInquiryRulesValue.value = rightsInquiryRulesValue.value.filter(item => item !== record.rulesId);//同步解除权益规则删除限制
+    }
+}
+// 新增渠道 - 带参数在新标签页中打开
+const onCancel = () => {
+  window.close();
+}
+</script>
+<!-- 样式区域 -->
+<style>
+.themeSettings .ant-popover-content .ant-popover-inner .ant-popover-inner-content {
+  padding: 0 !important;
+
+}
+
+.channelSupplierConfiglist .ant-popover-content .ant-popover-inner .ant-popover-inner-content {
+  overflow-y: auto;
+  width: 200px;
+  height: 200px;
+}
+</style>
+<style lang='less' scoped>
+.body {
+  padding: 10px 0px;
+  box-sizing: border-box;
+  height: 100%;
+
+  .card {
+    padding: 0 24px;
+    box-sizing: border-box;
+    background-color: #fff;
+    border-radius: 4px;
+    overflow-y: auto;
+    height: calc(100vh - 192px);
+  }
+}
+
+// 底部布局
+.footer {
+  position: fixed;
+  bottom: 0;
+  right: 0;
+  background: #fff;
+  width: calc(100vw - 210px);
+  height: 60px;
+  padding: 10px 30px;
+  box-sizing: border-box;
+
+  >span {
+    font-size: 16px;
+    color: #1890FF;
+  }
+}
+
+.card-header {
+  padding: 20px 0;
+  box-sizing: border-box;
+
+  .link {
+    width: 3px;
+    height: 20px;
+    border-radius: 0 5px 5px 0;
+    background-color: #018FFB;
+    margin-right: 7px;
+  }
+
+  font-size: 20px;
+  color: #333;
+  font-weight: bold;
+  border-bottom: 1px solid #E5E5E5;
+
+}
+
+.card-Radio-container {
+  gap: 0 24px;
+
+  .card-Radio {
+    position: relative;
+    width: max-content;
+    padding: 16px;
+    box-sizing: border-box;
+    background: #FFFFFF;
+    border-radius: 4px 4px 4px 4px;
+    border: 2px solid #E5E5E5;
+    cursor: pointer;
+
+    .Method {
+      font-size: 14px;
+      color: #333;
+      font-weight: bold;
+      line-height: 1.4;
+      margin-bottom: 4px;
+    }
+
+    .annotation {
+      font-size: 14px;
+      color: #999;
+    }
+
+    .anticon-check-circle {
+      position: absolute;
+      top: 7px;
+      right: 7px;
+      font-size: 21px;
+      color: #018FFB;
+    }
+  }
+
+  .card-Radio-active {
+    border: 2px solid #80C7FD;
+  }
+}
+
+.color-preview {
+  width: 120px;
+  padding: 3px 10px;
+  box-sizing: border-box;
+  border-radius: 4px 4px 4px 4px;
+  border: 1px solid #E5E5E5;
+
+  .colorBlock {
+    width: 16px;
+    height: 16px;
+  }
+
+  span {
+    font-size: 14px;
+    color: #333;
+    margin-left: 4px;
+  }
+}
+
+.text {
+  font-size: 14px;
+  color: #999;
+}
+
+.w250 {
+  width: 250px;
+}
+
+.fontsize14 {
+  font-size: 14px;
+  color: #333;
+}
+
+.table {
+  width: 800px;
+}
+
+.merchantSupplier {
+  padding: 6px 10px;
+  box-sizing: border-box;
+  background: #E9F4FF;
+  border-radius: 2px 2px 2px 2px;
+  width: auto;
+  font-size: 14px;
+  color: #018FFB;
+}
+
+.received {
+  margin-left: 160px;
+}
+</style>

+ 437 - 0
src/views/channel/baseRightsConfig/baseRightsConfigList.vue

@@ -0,0 +1,437 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <!--定义表格-->
+    <BasicTable @register="registerTable">
+      <!-- 表格顶部左侧操作 -->
+      <template #tableTitle>
+        <a-button type="primary" @click="channelConfigAdd" preIcon="ant-design:plus-outlined">创建渠道</a-button>
+      </template>
+      <!--操作栏-->
+      <template #action="{ record }">
+        <TableAction :actions="getTableAction(record)" :dropDownActions="dropDownAction(record)" />
+      </template>
+    </BasicTable>
+    <a-modal v-model:open="keyInfoShow" title="关键信息" width="700px" :footer="null" :bodyStyle="{ padding: '16px' }">
+      <div class=" keyInfo">
+        <div class="dis ">
+          <span class="label">AppID:</span>
+          <span class="value">{{ keyInfoData.appId }}</span>
+          <a-button type="link" ></a-button>
+        </div>
+        <div class="dis ">
+          <span class="label">AppSecret:</span>
+          <span class="value ">{{ keyInfoData.appSecret }}</span>
+          <a-button type="link" @click="regenerate">重新生成</a-button>
+        </div>
+        <div class="dis ">
+          <span class="label">AppKey:</span>
+          <span class="value">{{ keyInfoData.appKey }}</span>
+          <a-button type="link" @click="copyClick">复制</a-button>
+        </div>
+        <div class="dis ">
+          <span class="label">权益发放H5链接:</span>
+          <span class="value">{{ keyInfoData.rightsDistributionUrl }}</span>
+          <a-button type="link" @click="copyClick">复制</a-button>
+        </div>
+        <div class="dis ">
+          <span class="label">订单H5链接:</span>
+          <span class="value">{{ keyInfoData.orderUrl }}</span>
+          <a-button type="link" @click="copyClick">复制</a-button>
+        </div>
+        <div class="dis ">
+          <span class="label">我的券包H5链接:</span>
+          <span class="value">{{ keyInfoData.rollPackUrl }}</span>
+          <a-button type="link" @click="copyClick">复制</a-button>
+        </div>
+        <div class="dis ">
+          <span class="label">权益兑换H5链接:</span>
+          <span class="value">{{ keyInfoData.rightsRedemptionUrl }}</span>
+          <a-button type="link" @click="copyClick">复制</a-button>
+        </div>
+      </div>
+    </a-modal>
+  </div>
+</template>
+<!-- 行为区域 -->
+<script lang="ts" name="basic-table-demo" setup>
+import { ActionItem, BasicColumn, BasicTable, TableAction } from '/@/components/Table';
+import { useListPage } from '/@/hooks/system/useListPage';
+import { FormSchema } from '/@/components/Table';
+import { reactive, ref } from 'vue';
+import type { UnwrapRef } from 'vue';
+import type { Rule } from 'ant-design-vue/es/form';
+import * as channelAPI from '/@/api/channel/businessConfig';
+import { message, Modal } from 'ant-design-vue';
+import { useRouter, useRoute } from 'vue-router';
+//表单配置
+const router = useRouter();
+const formRef = ref();
+const channelId = ref(null);//渠道id
+const keyInfoShow = ref(false);//关键信息弹窗
+const keyInfoData = ref<ActionItem>({});//关键信息数据
+const channelConfigForm = reactive({
+  id: null,
+  pricingName: '',//计费模式名称
+  pricingCode: "",//计费模式编码
+  pricingUnit: "",//计费单位
+  minPrice: "",//最低起投金额
+  pricingDesc: "",//描述
+});
+
+const rules: Record<string, Rule[]> = {
+  pricingName: [
+    { required: true, message: '名称必填', trigger: 'blur' },
+  ],
+  pricingCode: [
+    { required: true, message: '编码必填', trigger: 'blur' },
+  ],
+  pricingUnit: [
+    { required: true, message: '必选', trigger: 'change' },
+  ],
+  minPrice: [
+    { required: true, message: '起投金额必填', trigger: 'blur' },
+  ],
+};
+
+//定义表格列字段
+const columns: BasicColumn[] = [
+  {
+    title: '渠道机构',
+    dataIndex: 'institutionName',
+    key: 'institutionName',
+    width: 200,
+  },
+  {
+    title: '权益发放场景',
+    dataIndex: 'issueConnectionMethod',
+    key: 'issueConnectionMethod',
+    customRender: ({ record }) => {
+      if (record.issueConnectionMethod == 1) {
+        return 'H5'
+      } else {
+        return 'api'
+      }
+    },
+  },
+  {
+    title: '消费者场景',
+    dataIndex: 'consumerConnectionMethod',
+    key: 'consumerConnectionMethod',
+    customRender: ({ record }) => {
+      if (record.issueConnectionMethod == 1) {
+        return 'H5'
+      } else if (record.consumerConnectionMethod == 2) {
+        return 'api'
+      } else {
+        return '原生'
+      }
+    },
+  },
+  {
+    title: '渠道归属',
+    dataIndex: 'channelName',
+    key: 'channelName',
+  },
+  {
+    title: '状态',
+    dataIndex: 'status',
+    key: 'status',
+    customRender: ({ record }) => {
+      if (record.status == 1) {
+        return '启用'
+      } else if (record.status == 0) {
+        return '禁用'
+      } else {
+        return '-'
+      }
+    },
+  },
+  {
+    title: '创建时间',
+    dataIndex: 'createTime',
+    key: 'createTime',
+  },
+  {
+    title: '联系人姓名',
+    dataIndex: 'contact',
+    key: 'contact',
+  },
+  {
+    title: '联系人手机号',
+    dataIndex: 'contactPhone',
+    key: 'contactPhone',
+  },
+];
+const searchFormSchema: FormSchema[] = [
+  {
+    label: '渠道名称',
+    field: 'pricingName',
+    component: 'Input',
+    colProps: {
+      span: 6,
+    },
+  },
+  {
+    label: '渠道所属',
+    field: 'pricingName',
+    component: 'Input',
+    colProps: {
+      span: 6,
+    },
+  },
+  {
+    label: '状态',
+    field: 'status',
+    component: 'Select',
+    colProps: {
+      span: 6,
+    },
+    componentProps: {
+      options: [
+        { label: "全部", value: null },
+        { label: "启用", value: 1 },
+        { label: "禁用", value: 0 },
+      ]
+    }
+  },
+  {
+    label: '日期范围',
+    field: 'dateRangeSelect',
+    component: 'RangeDate',
+    colProps: {
+      span: 6,
+    },
+    componentProps: {
+      format: 'YYYY-MM-DD',
+      placeholder: ['请选择开始日期', '请选择结束日期']
+    },
+  },
+  {
+    label: '联系人',
+    field: 'pricingName',
+    component: 'Input',
+    colProps: {
+      span: 6,
+    },
+  },
+];
+/** useListPage 是整个框架的核心用于表格渲染,里边封装了很多公共方法;
+ * 平台通过此封装,简化了代码,支持自定义扩展*/
+// 通过hook useListPage渲染表格(设置dataSource、columns、actionColumn等参数)
+const { tableContext } = useListPage({
+  designScope: 'basic-table-demo',
+  tableProps: {
+    title: '用户列表',
+    dataSource: [],
+    columns: columns,
+    formConfig: {
+      schemas: searchFormSchema,
+      showAdvancedButton: false,
+      // 将表单内时间区域的值映射成 2个字段, 分别是 joinTime_begin 和 joinTime_end
+      fieldMapToTime: [['dateRangeSelect', ['joinTime_begin', 'joinTime_end'], 'YYYY-MM-DD']],
+    },
+    api: channelAPI.supplierConfigPageList,
+    size: 'small',
+    actionColumn: {
+      width: 200,
+      fixed: 'right'
+
+    },
+  },
+});
+// BasicTable绑定注册
+const [registerTable, { reload }] = tableContext;
+/**
+ * 操作栏
+ */
+function getTableAction(record: ActionItem): ActionItem[] {
+  return [
+    {
+      label: '编辑',
+      onClick: channelConfigEdit.bind(null, record),
+    },
+
+  ];
+}
+/**
+ * 下拉操作栏
+ * @param record 行数据
+ */
+function dropDownAction(record: ActionItem): ActionItem[] {
+  return [
+    {
+      label: '启用',
+      onClick: channelConfigDisable.bind(null, record),
+      disabled: record.status == 1,
+    },
+    {
+      label: '禁用',
+      onClick: channelConfigDisable.bind(null, record),
+      disabled: record.status == 0,
+    },
+    {
+      label: '删除',
+      onClick: channelConfigDel.bind(null, record),
+    },
+    {
+      label: '关键信息',
+      onClick: getKeyInfo.bind(null, record),
+    },
+  ];
+}
+// 新增渠道 - 带参数在新标签页中打开
+const channelConfigAdd = () => {
+  // 定义要传递的参数
+  // 使用 router.resolve() 解析路由和参数
+  const routeInfo = router.resolve({
+    path: "/channel/baseRightsConfig/baseRightsConfigAdd",
+  });
+  // 构建完整的 URL
+  const fullUrl = `${window.location.origin}${routeInfo.href}`;
+  // 在新标签页中打开
+  window.open(fullUrl, '_blank');
+}
+// 关键信息弹窗
+const getKeyInfo = (record: ActionItem) => {
+  channelId.value = record.id;
+  // 查询关键信息
+  channelAPI.channelKeyInfo(record.id).then((res) => {
+    if (res.code == 200) {
+      keyInfoData.value = res.result;
+      keyInfoShow.value = true;
+    } else {
+      message.error(res.message);
+    }
+  })
+}
+/**
+ * 启用禁用
+ * @param record 行数据
+ */
+function channelConfigDisable(record: ActionItem) {
+  if (record.status == 0) {
+    channelAPI.supplierConfigEnable({ id: record.id, status: '1' }).then((res) => {
+      if (res.code == 200) {
+        reload();
+      } else {
+        message.error(res.message);
+      }
+    })
+  } else {
+    Modal.confirm({
+      title: '禁用渠道业务',
+      content: '确定要禁用该渠道业务吗,禁用后,该渠道的发放权益和消费者场景将全部关闭为不可用?',
+      okText: '确定禁用',
+      okType: 'danger',
+      cancelText: '取消',
+      onOk() {
+        channelAPI.supplierConfigEnable({ id: record.id, status: '0' }).then((res) => {
+          if (res.code == 200) {
+            reload();
+          } else {
+            message.error(res.message);
+          }
+        })
+      },
+      onCancel() {
+      },
+    });
+  }
+}
+/**
+ * 广告位编辑
+ * @param record 
+ */
+function channelConfigEdit(record: ActionItem) {
+  const routeInfo = router.resolve({
+    path: "/channel/baseRightsConfig/baseRightsConfigAdd",
+    query: {id: record.id}
+  });
+  // 构建完整的 URL
+  const fullUrl = `${window.location.origin}${routeInfo.href}`;
+  // 在新标签页中打开
+  window.open(fullUrl, '_blank');
+}
+// 删除
+const channelConfigDel = (record: ActionItem) => {
+  Modal.confirm({
+    title: '删除',
+    content: '确定要删除该渠道的业务吗?',
+    okText: '确定删除',
+    okType: 'danger',
+    cancelText: '取消',
+    onOk() {
+      channelAPI.supplierConfigDel(record.id).then((res) => {
+        if (res.code == 200) {
+          message.success(res.message);
+          reload();
+        } else {
+          message.error(res.message);
+        }
+      })
+    },
+    onCancel() {
+    },
+  });
+}
+
+// 文本复制
+function copyClick(value) {
+  // 创建input元素
+  const el = document.createElement('input');
+  // 给input元素赋值需要复制的文本
+  el.setAttribute('value', value);
+  // 将input元素插入页面
+  document.body.appendChild(el);
+  // 选中input元素的文本
+  el.select();
+  // 复制内容到剪贴板
+  document.execCommand('copy');
+  // 删除input元素
+  document.body.removeChild(el);
+  message.success('复制成功');
+};
+const regenerate = () => {
+  channelAPI.regenerateAppId(channelId.value).then((res) => {
+    if (res.code == 200) {
+      message.success('已生成');
+      channelAPI.channelKeyInfo(channelId.value).then((res) => {
+        if (res.code == 200) {
+          keyInfoData.value = res.result;
+        } else {
+          message.error(res.message);
+        }
+      })
+    } else {
+      message.error(res.message);
+    }
+  })
+}
+
+</script>
+<!-- 样式区域 -->
+<style lang='less' scoped>
+::v-deep(.jeecg-basic-table .ant-table-wrapper) {
+  padding: 20px;
+  height: calc(100% - 10px);
+}
+
+::v-deep(.ant-col) {
+  width: auto;
+}
+
+.keyInfo {
+  width: 100%;
+
+  .label {
+    width: 150px;
+  }
+
+  .value {
+    display: flex;
+    align-items: center;
+    flex: 1;
+  }
+}
+</style>

+ 428 - 0
src/views/channel/distributionDetails.vue

@@ -0,0 +1,428 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <!--定义表格-->
+    <BasicTable @register="registerTable">
+      <!-- 表格顶部左侧操作 -->
+      <template #tableTitle>
+        <a-button preIcon="ant-design:plus-outlined" @click="onExportXls">导出</a-button>
+      </template>
+      <template #bodyCell="{ column, record }">
+        <span v-if="column.key === 'useCount'" class="cursorstyle">
+          {{ record.useCount }}
+        </span>
+        <span v-if="column.key === 'cancelCount'" class="cursorstyle" @click="redirectToisInvalidRecords(record)">
+          {{ record.cancelCount }}
+        </span>
+        <a-tag v-if="column.key === 'status'" :bordered="false"
+          :color="record.status == '1' ? 'processing' : 'error'">{{ record.status == '1' ? '有效' : '已作废' }}</a-tag>
+        <template v-if="column.key === 'phone'">
+          <a-popover title="全部字段" placement="bottom" v-if="record.grantType == '3'">
+            <template #content>
+              <div class="dis f-c ">
+                <span>手机号:{{ record.receiver.phone || '-' }}</span>
+                <span>保单号:{{ record.receiver.policyNo || '-' }}</span>
+                <span>车架号:{{ record.receiver.vin || '-' }}</span>
+                <span>接收人姓名:{{ record.receiver.receiverName || '-' }}</span>
+                <span>车牌号:{{ record.receiver.plateNo || '-' }}</span>
+              </div>
+            </template>
+            <span>接收人信息</span>
+          </a-popover>
+          <a-popover title="全部字段" placement="bottom" v-else>
+            <template #content>
+              <p v-for="value in record.receiver.dynamicAttrs">{{ value.rulesName }}:{{ value.rulesValue }}</p>
+            </template>
+            <span>接收人信息</span>
+          </a-popover>
+        </template>
+      </template>
+      <!--操作栏-->
+      <template #action="{ record }">
+        <TableAction :actions="getTableAction(record)" />
+      </template>
+    </BasicTable>
+    <a-modal v-model:open="benefitDetailsShow" title="权益明细" width="1000px" :footer="null"
+      :bodyStyle="{ padding: '16px' }">
+      <a-descriptions :column="3">
+        <a-descriptions-item label="记录编号">{{ detailForm.recordId }}</a-descriptions-item>
+        <a-descriptions-item label="发放渠道">{{ detailForm.displayName }}</a-descriptions-item>
+        <a-descriptions-item label="发放方式">{{ pricingUnitMap() }}</a-descriptions-item>
+        <template v-if="[1, 2].includes(detailForm.grantType)">
+          <a-descriptions-item v-for="dynamicAttrsItem in detailForm.receiver.dynamicAttrs"
+            :label="dynamicAttrsItem.rulesName">{{ dynamicAttrsItem.rulesValue }}</a-descriptions-item>
+        </template>
+        <template v-else>
+          <a-descriptions-item label="手机号" v-if="detailForm.receiver && detailForm.receiver.phone">{{
+            detailForm.receiver.phone || '-' }}</a-descriptions-item>
+          <a-descriptions-item label="保单号" v-if="detailForm.receiver && detailForm.receiver.policyNo">{{
+            detailForm.receiver.policyNo || "-" }}</a-descriptions-item>
+          <a-descriptions-item label="车架号" v-if="detailForm.receiver && detailForm.receiver.vin">{{
+            detailForm.receiver.vin || "-" }}</a-descriptions-item>
+          <a-descriptions-item label="接收人姓名" v-if="detailForm.receiver && detailForm.receiver.receiverName">{{
+            detailForm.receiver.receiverName || "-" }}</a-descriptions-item>
+          <a-descriptions-item label="车牌号" v-if="detailForm.receiver && detailForm.receiver.plateNo">{{
+            detailForm.receiver.plateNo || "-" }}</a-descriptions-item>
+        </template>
+        <a-descriptions-item label="发券数量">{{ detailForm.grantCount }}</a-descriptions-item>
+        <a-descriptions-item label="发放金额">{{ detailForm.grantAmount }}</a-descriptions-item>
+        <a-descriptions-item label="发放人">{{ detailForm.grantUserName }}</a-descriptions-item>
+        <a-descriptions-item label="发放时间">{{ detailForm.createTime }}</a-descriptions-item>
+      </a-descriptions>
+      <a-table :dataSource="detailForm.detailItems" size="small" :columns="detailsColumns" :pagination="false">
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.dataIndex === 'status'">
+            <a-tag :bordered="false" color="error" v-if="record.status == '0'">已作废</a-tag>
+            <a-tag :bordered="false" color="warning" v-if="record.status == '1'">待使用</a-tag>
+            <a-tag :bordered="false" color="processing" v-if="record.status == '2'">已使用</a-tag>
+            <a-tag :bordered="false" color="error" v-if="record.status == '3'">已过期</a-tag>
+          </template>
+        </template>
+      </a-table>
+    </a-modal>
+  </div>
+</template>
+<!-- 行为区域 -->
+<script lang="ts" name="basic-table-demo" setup>
+import { ActionItem, BasicColumn, BasicTable, TableAction } from '/@/components/Table';
+import { useListPage } from '/@/hooks/system/useListPage';
+import { FormSchema } from '/@/components/Table';
+import { reactive, ref } from 'vue';
+import * as distributionRecordAPI from '/@/api/channel/distributionRecord';
+import { message, Modal } from 'ant-design-vue';
+import { useRouter, useRoute } from 'vue-router';
+import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
+import { createVNode } from 'vue';
+//表单配置
+const router = useRouter();
+const route = useRoute();
+// 从路由参数中获取 recordId
+const recordId = ref(route.query.id);
+// 权益详情弹窗
+const benefitDetailsShow = ref(false);
+const detailForm = ref();//发放明细详情
+const pricingUnitMap = () => {
+  const pricingUnitMap = {
+    "1": "API发放",
+    "2": "H5发放",
+    "3": "后台发放",
+  }
+  return pricingUnitMap[detailForm.value.grantType];
+}
+const detailsColumns = [
+  {
+    title: '权益名称',
+    dataIndex: 'rightsName',
+    key: 'rightsName',
+    align: "center",
+  },
+  {
+    title: '状态',
+    dataIndex: 'status',
+    key: 'status',
+    width: 150,
+    align: "center"
+
+  },
+  {
+    title: '时间',
+    dataIndex: 'createTime',
+    key: 'createTime',
+    align: "center",
+  },
+  {
+    title: '订单号',
+    dataIndex: 'orderNo',
+    key: 'orderNo',
+    align: "center",
+  },
+
+]
+//定义表格列字段
+const columns: BasicColumn[] = [
+  {
+    title: '记录编号',
+    dataIndex: 'recordId',
+    key: 'recordId',
+    width: 200,
+    ellipsis: true
+  },
+  {
+    title: '发放渠道',
+    dataIndex: 'displayName',
+    key: 'displayName',
+  },
+  {
+    title: '权益名称',
+    dataIndex: 'rightsName',
+    key: 'rightsName',
+  },
+  {
+    title: '状态',
+    dataIndex: 'status',
+    key: 'status',
+    customRender: ({ record }) => {
+      return record.status ? (record.status == '1' ? '生效' : '已作废') : '';
+    },
+  },
+  {
+    title: '发放方式',
+    dataIndex: 'grantType',
+    key: 'grantType',
+    customRender: ({ record }) => {
+      const pricingUnitMap = {
+        "1": "API发放",
+        "2": "H5发放",
+        "3": "后台发放",
+      }
+      return pricingUnitMap[record.grantType];
+    },
+  },
+  {
+    title: '数量',
+    dataIndex: 'grantCount',
+    key: 'grantCount',
+
+  },
+  {
+    title: '发放金额',
+    dataIndex: 'grantAmount',
+    key: 'grantAmount',
+  },
+  {
+    title: '已使用',
+    dataIndex: 'useCount',
+    key: 'useCount',
+  },
+  {
+    title: '已作废',
+    dataIndex: 'cancelCount',
+    key: 'cancelCount',
+  },
+  {
+    title: '接收人信息',
+    dataIndex: 'phone',
+    key: 'phone',
+  },
+  {
+    title: '发放人',
+    dataIndex: 'grantUserName',
+    key: 'grantUserName',
+  },
+  {
+    title: '发放时间',
+    dataIndex: 'createTime',
+    key: 'createTime',
+  },
+];
+const searchFormSchema: FormSchema[] = [
+  {
+    label: '记录编号',
+    field: 'recordId',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+    defaultValue: recordId.value, // 设置初始值
+  },
+  {
+    label: '发放渠道',
+    field: 'displayName',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+  },
+  {
+    label: '权益名称',
+    field: 'rightsName',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+  },
+  {
+    label: '发放方式',
+    field: 'grantType',
+    component: 'Select',
+    colProps: {
+      span: 7,
+    },
+    componentProps: {
+      options: [
+        { label: "API发放", value: 1 },
+        { label: "H5发放", value: 2 },
+        { label: "后台发放", value: 3 },
+      ]
+    }
+  },
+  {
+    label: '作废状态',
+    field: 'status',
+    component: 'Select',
+    colProps: {
+      span: 7,
+    },
+    componentProps: {
+      options: [
+        { label: "作废", value: 0 },
+        { label: "失效", value: 1 },
+      ]
+    }
+  },
+  {
+    label: '接收人保单号',
+    field: 'policyNo',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+  },
+  {
+    label: '发放人',
+    field: 'grantUserName',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+  },
+  {
+    label: '发放时间',
+    field: 'dateRangeSelect',
+    component: 'RangeDate',
+    colProps: {
+      span: 7,
+    },
+    componentProps: {
+      format: 'YYYY-MM-DD',
+      placeholder: ['请选择开始日期', '请选择结束日期']
+    },
+  },
+];
+/** useListPage 是整个框架的核心用于表格渲染,里边封装了很多公共方法;
+ * 平台通过此封装,简化了代码,支持自定义扩展*/
+// 通过hook useListPage渲染表格(设置dataSource、columns、actionColumn等参数)
+const { tableContext, onExportXls } = useListPage({
+  designScope: 'basic-table-demo',
+  tableProps: {
+    title: '用户列表',
+    dataSource: [],
+    columns: columns,
+    formConfig: {
+      schemas: searchFormSchema,
+      showAdvancedButton: false,
+      fieldMapToTime: [['dateRangeSelect', ['createTimeBegin', 'createTimeEnd'], 'YYYY-MM-DD HH:mm:ss']],
+    },
+    api: distributionRecordAPI.getDistributionDetailList,
+    size: 'small',
+    actionColumn: {
+      width: 200,
+      fixed: 'right'
+    },
+  },
+  exportConfig: {
+    name: '发放明细导出',
+    url: '/channel/grantDetail/exportXls',
+    method: 'get'
+  },
+});
+// BasicTable绑定注册
+const [registerTable, { getForm, reload }] = tableContext;
+/**
+ * 操作栏
+ */
+function getTableAction(record: ActionItem): ActionItem[] {
+  return [
+    {
+      label: '明细',
+      onClick: details.bind(null, record),
+    },
+
+    {
+      label: '作废',
+      onClick: distributionRecordDel.bind(null, record),
+    },
+  ];
+}
+// 权益作废
+const distributionRecordDel = (record: ActionItem) => {
+  const invalidReason = ref(''); // 作废原因
+  Modal.confirm({
+    title: '权益作废',
+    okText: '确定作废',
+    icon: createVNode(ExclamationCircleOutlined, { style: 'color: #ff4d4f' }),
+    content: createVNode('div', {}, [
+      createVNode('p', { style: 'margin-bottom: 10px;' }, '确定将该记录中发放的权益全部作废?'),
+      createVNode('textarea', {
+        value: invalidReason.value,
+        onInput: (e) => (invalidReason.value = e.target.value),
+        placeholder: '请输入作废原因(必填)',
+        style: 'width: 100%; padding: 8px; border: 1px solid #d9d9d9; border-radius: 2px;outline: none !important;color:#333',
+        rows: 4,
+      }),
+    ]),
+    onOk: () => {
+      if (invalidReason.value.length < 1) {
+        message.error('作废原因(必填)');
+        return Promise.reject();
+      }
+      distributionRecordAPI.cancelDistributionDetailByDetailId({
+        id: record.detailId,
+        cancelReason: invalidReason.value,
+      }).then(res => {
+        if (res.code === 200) {
+          message.success('作废成功');
+          reload();
+        } else {
+          message.error(res.message || '作废失败');
+        }
+      });
+    },
+  });
+}
+// 明细详情
+const details = (record: ActionItem) => {
+  // 定义要传递的参数
+  // 调用查询接口
+  distributionRecordAPI.getDistributionDetailDetail(record.detailId).then(res => {
+    if (res.code === 200) {
+      detailForm.value = res.result;
+      benefitDetailsShow.value = true;
+    } else {
+      message.error(res.message || '查询失败');
+    }
+  });
+}
+const redirectToisInvalidRecords = (record: ActionItem) => {
+  console.log(record)
+  // 定义要传递的参数
+  const params = {
+    receiverId: record.receiverId,
+    recordId: record.recordId,
+    rightsId: record.rightsId,
+  };
+  // 使用 router.resolve() 解析路由和参数
+  router.push({
+    path: "/channel/isInvalidRecords",
+    query: params
+  });
+
+}
+</script>
+<!-- 样式区域 -->
+<style lang='less' scoped>
+::v-deep(.jeecg-basic-table .ant-table-wrapper) {
+  padding: 20px;
+  height: calc(100% - 10px);
+}
+
+::v-deep(.ant-col) {
+  width: auto;
+}
+
+.cursorstyle {
+  font-size: 14px;
+  color: #018FFB;
+  cursor: pointer;
+}
+</style>

+ 580 - 0
src/views/channel/distributionRecord/add.vue

@@ -0,0 +1,580 @@
+<!-- 模板区域 -->
+<template>
+  <div class="body">
+    <div class="card">
+      <a-form ref="formRef" :model="distributionRecordForm" layout="vertical" labelAlign="right" :label-col="labelCol"
+        :rules="distributionRecordFormRules">
+        <div class="card-header dis a-c mb-2">
+          <div class="link"></div>
+          <span>基本信息</span>
+        </div>
+        <div class="horizontal-section">
+          <a-row :gutter="30">
+            <a-col :span="6">
+              <a-form-item label="发放渠道" name="institutionId">
+                <a-tree-select v-model:value="distributionRecordForm.institutionId" @change="institutionChange"
+                  show-search style="width: 100%" :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
+                  placeholder="Please select" allow-clear tree-default-expand-all :tree-data="treeData" :field-names="{
+                    children: 'node',
+                    label: 'institutionName',
+                    value: 'id',
+                  }" tree-node-filter-prop="institutionName">
+                </a-tree-select>
+              </a-form-item>
+            </a-col>
+            <a-col :span="6">
+              <a-form-item label="发放人" name="grantUserName">
+                <a-input v-model:value="distributionRecordForm.grantUserName" showCount maxlength="20"
+                  placeholder="请输入发放人" />
+              </a-form-item>
+            </a-col>
+            <a-col :span="6">
+              <a-form-item label="发放人手机号" name="grantUserPhone">
+                <a-input v-model:value="distributionRecordForm.grantUserPhone" showCount maxlength="11"
+                  placeholder="请输入发放人手机号" allowClear />
+              </a-form-item>
+            </a-col>
+            <a-col :span="12">
+              <a-form-item label="备注">
+                <a-textarea v-model:value="distributionRecordForm.remark" show-count :maxlength="500"
+                  placeholder="请输入内容...." />
+              </a-form-item>
+            </a-col>
+          </a-row>
+          <a-row :gutter="30">
+            <a-col :span="8">
+              <a-form-item label="接收人" name="fileUrl">
+                <a-upload-dragger v-model:fileList="fileList" name="file" :action="UploadUrl" @change="fileChange"
+                  accept=".xls,.xlsx">
+                  <p class="ant-upload-drag-icon">
+                    <inbox-outlined></inbox-outlined>
+                  </p>
+                  <template v-if="distributionRecordForm.fileUrl">
+                    <span class="fontsize14">{{ distributionRecordForm.fileUrl }}</span>
+                  </template>
+                  <template v-else>
+                    <p class="fontsize14">点击或将文件拖拽到这里上传,或者 <span style="color: #018FFB;">点击上传</span> </p>
+                  </template>
+                </a-upload-dragger>
+                <div class="dis a-c">
+                  <a-button type="link" @click="downloadTemplate">下载模版</a-button>
+                  <span class="fontsize14">仅允许导入“xls”或“xlsx”格式文件!最多导入1500人 </span>
+                </div>
+                <!-- 导入提示 -->
+                <div class="importPrompt" v-if="fileresult.failRows">
+                  <div class="dis a-c">
+                    <InfoCircleOutlined style="font-size: 18px;color: #FAA21E;" />
+                    <span class="fontsize14 ml-1">您将导入 <span style="color: #018FFB;">{{ fileresult.totalRows }}</span>
+                      条数据,其中 <span style="color: #018FFB;">{{ fileresult.successRows }}</span> 条正常导入;<span
+                        style="color: #FF4545;">{{
+                          fileresult.failRows }}</span>
+                      条不符合要求无法导入</span>
+                  </div>
+                  <a-button type="link" @click="downloadFailedFile">下载导入失败文件</a-button>
+                </div>
+              </a-form-item>
+            </a-col>
+          </a-row>
+        </div>
+        <div class="card-header dis a-c mb-2">
+          <div class="link"></div>
+          <span>权益礼包</span>
+        </div>
+        <a-table :dataSource="distributionRecordForm.rightsList" size="small" :columns="Columns" :pagination="false"
+          rowClassName="rowClassName">
+          <template #bodyCell="{ column, record, index }">
+            <template v-if="column.dataIndex === 'operation'">
+              <a-button type="link" @click="rightsListAdd(column, record)">添加</a-button>
+              <a-button type="link" @click="rightsListDel(column, record, index)" :disabled="index === 0">删除</a-button>
+            </template>
+            <template v-if="column.dataIndex === 'rightsContentName'">
+              <a-select v-model:value="record.rightsContentId" placeholder="权益搜索" style="width: 300px"
+                :options="entitlementPackages" :field-names="{ label: 'rightsContentName', value: 'rightsContentId' }">
+                <template #dropdownRender>
+                  <div class="benefit">
+                    <div class="header dis a-c j-s">
+                      <span style="width: 50%;">权益名称</span>
+                      <span style="width: 25%;">类型</span>
+                      <span style="width: 25%;">采购价</span>
+                    </div>
+                    <div class="custom-options">
+                      <div v-for="item in entitlementPackages" :key="item.rightsContentId"
+                        class="option-item dis a-c j-s" @click="handleSelect(item, index)">
+                        <span style="width: 50%;">{{ item.rightsContentName }}</span>
+                        <span style="width: 25%;">{{ item.rightsContentType == 1 ? '单独权益' : '权益包' }}</span>
+                        <span style="width: 25%;">{{ item.settlePrice || 0 }}元</span>
+                      </div>
+                    </div>
+                  </div>
+                </template>
+              </a-select>
+            </template>
+            <template v-if="column.dataIndex === 'rightsType'">
+              <span v-if="record.rightsType == '1'">单独权益</span>
+              <span v-else-if="record.rightsType == '2'">权益包</span>
+              <span v-else></span>
+            </template>
+            <template v-if="column.dataIndex === 'rightsCount'">
+              <a-input-number v-model:value="record.rightsCount" style="width: 100%" allowClear placeholder="请输入权益数量" />
+            </template>
+            <template v-if="column.dataIndex === 'totalPrice'">
+              <span>{{ Decimal(record.settlePrice || 0).times(record.rightsCount || 0) }}元</span>
+            </template>
+          </template>
+        </a-table>
+      </a-form>
+    </div>
+    <!-- 操作栏 -->
+    <div class="footer flex a-c j-c">
+      <a-button @click="onCancel">取消</a-button>
+      <a-button @click="onSubmit" style="margin-left: 40px" type="primary">保存</a-button>
+    </div>
+  </div>
+</template>
+<!-- 行为区域 -->
+<script lang='ts' setup>
+import { reactive, ref, onMounted } from 'vue';
+import type { UnwrapRef } from 'vue';
+import { useRouter, useRoute } from 'vue-router';
+import { InboxOutlined, InfoCircleOutlined, ExclamationCircleFilled } from '@ant-design/icons-vue';
+import type { Rule, } from 'ant-design-vue/es/form';
+import * as businessConfigAPI from '/@/api/channel/businessConfig';
+import { type TreeSelectProps } from 'ant-design-vue';
+import * as distributionRecordAPI from '/@/api/channel/distributionRecord';
+import { message, Modal } from 'ant-design-vue';
+import { Decimal } from "decimal.js";
+import { useGlobSetting } from '/@/hooks/setting';
+import { createVNode } from 'vue';
+const globSetting = useGlobSetting();
+const baseUploadUrl = globSetting.uploadUrl;
+const UploadUrl = `${baseUploadUrl}/channel/grantRecord/uploadReceiverFile`; // 上传地址
+
+onMounted(() => {
+  queryChannelOrg()//获取渠道机构数据
+})
+// 发放失败弹窗
+const failedToIssue = (failedFileUrl: string) => {
+  Modal.confirm({
+    title: '发放失败',
+    okText: '忽略,确认发放',
+    cancelText: '查看错误数据',
+    content: '错误信息,本次发放权益的金额大于导入文件内的结算金额,请确认是否继续发放。',
+    icon: createVNode(ExclamationCircleFilled, { style: 'color: #ff4d4f' }),
+    onOk: () => {
+      // 处理发放失败逻辑
+      console.log(distributionRecordForm)
+      return;
+      distributionRecordAPI.addDistributionRecord(distributionRecordForm).then(res => {
+        if (res.code === 200) {
+          message.success('发放记录已保存');
+        } else {
+          message.error(res.message || '发放失败记录保存失败');
+        }
+      });
+    },
+    onCancel: () => {
+      let Url = 'E:/workspace/opt/upFiles' + '/' + failedFileUrl;
+      // 处理查看错误数据逻辑
+      window.open(Url);
+    },
+  });
+}
+interface FormState {
+  [key: string]: any;//默认全类型
+}
+// 校验规则弹窗
+const fileList = ref([]);//
+const formRef = ref();
+const treeData = ref<TreeSelectProps['treeData']>([]);//渠道机构数据
+const fileresult = ref<FormState>({});//文件上传后数据
+const labelCol = { style: { width: '150px' } };
+const entitlementPackages = ref();//权益礼包列表数据
+// 业务配置表单
+const distributionRecordForm: UnwrapRef<FormState> = reactive({
+  institutionId: '',//机构id
+  grantUserName: '',//联系人
+  grantUserPhone: '',//联系人电话
+  remark: '',//备注
+  fileUrl: '',//接收人文件url
+  failedFileUrl: '',//发放失败文件url
+  rightsList: [
+    {
+      rightsContentId: "",//权益id
+      rightsContentName: "",//权益名称
+      rightsCount: 1,//权益数量
+      rightsType: null,//权益类型
+      settlePrice: "",//结算价
+      totalPrice: "",//总价
+    }
+  ],//权益礼包
+});
+
+const distributionRecordFormRules: Record<string, Rule[]> = {
+  institutionId: [
+    { required: true, message: '请选择合作渠道机构', trigger: 'change' },
+  ],
+  grantUserName: [
+    { required: true, message: '联系人姓名必填', trigger: 'blur' },
+  ],
+  grantUserPhone: [
+    { required: true, message: '联系人手机号必填', trigger: 'blur' },
+    {
+      pattern: /^[0-9]+$/,
+      message: '请输入正确手机号',
+    },
+    {
+      min: 11,
+      max: 11,
+      message: '请输入11位手机号',
+    },
+  ],
+
+  fileUrl: [
+    { required: true, message: '请上传发放人群', trigger: 'blur' },
+  ],
+};
+
+const Columns = [
+  {
+    title: '操作',
+    dataIndex: 'operation',
+    key: 'operation',
+    align: "center",
+    width: 200,
+
+  },
+  {
+    title: '权益名称',
+    dataIndex: 'rightsContentName',
+    key: 'rightsContentName',
+    align: "center",
+  },
+  {
+    title: '权益类型',
+    dataIndex: 'rightsType',
+    key: 'rightsType',
+    width: 200,
+    align: "center"
+  },
+  {
+    title: '结算价',
+    dataIndex: 'settlePrice',
+    key: 'settlePrice',
+    align: "center",
+    width: 200,
+  },
+  {
+    title: '数量',
+    dataIndex: 'rightsCount',
+    key: 'rightsCount',
+    align: "center",
+    width: 200,
+  },
+  {
+    title: '总价',
+    dataIndex: 'totalPrice',
+    key: 'totalPrice',
+    align: "center",
+    width: 200,
+  },
+]
+const onSubmit = () => {
+  formRef.value.validate().then(() => {
+    // 校验通过,提交表单数据
+    console.log('表单数据:', distributionRecordForm);
+    distributionRecordAPI.validateBeforeSubmit(distributionRecordForm).then(res => {
+      if (res.code === 200) {
+        if (res.result.success) {
+          console.log(distributionRecordForm)
+          return;
+          distributionRecordAPI.addDistributionRecord(distributionRecordForm).then(res => {
+            if (res.code === 200) {
+              message.success('发放成功');
+            } else {
+              message.error(res.message || '发放失败');
+            }
+          })
+        } else {
+          let Url = 'E:/workspace/opt/upFiles' + '/' + res.result.failedFileUrl;
+          distributionRecordForm.failedFileUrl = res.result.failedFileUrl;
+          failedToIssue(Url)
+        }
+      } else {
+        message.error(res.message || '校验失败');
+      }
+    })
+
+  }).catch(() => {
+    // 校验不通过,处理错误逻辑
+    console.log('校验失败');
+  });
+}
+const handleSelect = (val: any, index: number) => {
+  Object.assign(distributionRecordForm.rightsList[index], val)
+  distributionRecordForm.rightsList[index].rightsType = val.rightsContentType
+
+};
+
+
+// 添加权益
+const rightsListAdd = (column, record) => {
+  console.log(record)
+  if (distributionRecordForm.rightsList.length >= 20) {
+    message.error('最多添加20个权益')
+    return
+  } else {
+    distributionRecordForm.rightsList.push({
+      rightsContentId: "",
+      rightsCount: 1,//权益数量
+      rightsType: "",//权益类型
+    })
+  }
+}
+// 删除权益
+const rightsListDel = (column, record, index) => {
+  distributionRecordForm.rightsList.splice(index, 1)
+}
+//  机构改变时查询权益
+const institutionChange = (val: string) => {
+  getEntitlementPackages()
+};
+const getEntitlementPackages = () => {
+  distributionRecordAPI.getChannelRightsByInstitution({
+    institutionId: distributionRecordForm.institutionId,
+    name: "",
+  }).then(res => {
+    if (res.code === 200) {
+      entitlementPackages.value = res.result
+    } else {
+      message.error(res.msg || '权益查询失败');
+    }
+  })
+}
+// 获取业务配置渠道机构
+const queryChannelOrg = () => {
+  businessConfigAPI.getChannelDeptList().then((res) => {
+    if (res.code == 200) {
+      treeData.value = res.result
+    }
+  });
+}
+// 文件上传
+const fileChange = async (info) => {
+  if (info.file.status === 'done') {
+    if (info.file.response.code == '200') {
+      message.success(info.file.response.message);//文件上传成功
+      fileresult.value = info.file.response.result;
+      // let  url= import.meta.env.VITE_GLOB_DOMAIN_URL + '/' + info.file.response.result.fileUrl;
+      let fileUrl = 'E:/workspace/opt/upFiles' + '/' + info.file.response.result.fileUrl;
+      let failedFileUrl = 'E:/workspace/opt/upFiles' + '/' + info.file.response.result.failedFileUrl;
+      distributionRecordForm.fileUrl = fileUrl;
+      distributionRecordForm.failedFileUrl = failedFileUrl;
+    } else {
+      message.error(info.file.response.message);//文件上传失败
+      return
+    }
+  }
+}
+const onCancel = () => {
+  window.close();
+}
+// 下载模版
+const downloadTemplate = () => {
+  window.open(
+    'https://life.baoxianzhanggui.com/locallive-pro-java/channel/receiver/模板.xlsx',
+  );
+}
+const downloadFailedFile = () => {
+  window.open(
+    distributionRecordForm.failedFileUrl,
+  );
+}
+</script>
+<!-- 样式区域 -->
+
+<style lang='less' scoped>
+.body {
+  padding: 10px 0px;
+  box-sizing: border-box;
+  height: 100%;
+
+  .card {
+    padding: 0 24px;
+    box-sizing: border-box;
+    background-color: #fff;
+    border-radius: 4px;
+    overflow-y: auto;
+    height: calc(100vh - 192px);
+  }
+}
+
+// 底部布局
+.footer {
+  position: fixed;
+  bottom: 0;
+  right: 0;
+  background: #fff;
+  width: calc(100vw - 210px);
+  height: 60px;
+  padding: 10px 30px;
+  box-sizing: border-box;
+
+  >span {
+    font-size: 16px;
+    color: #1890FF;
+  }
+}
+
+.card-header {
+  padding: 20px 0;
+  box-sizing: border-box;
+
+  .link {
+    width: 3px;
+    height: 20px;
+    border-radius: 0 5px 5px 0;
+    background-color: #018FFB;
+    margin-right: 7px;
+  }
+
+  font-size: 20px;
+  color: #333;
+  font-weight: bold;
+  border-bottom: 1px solid #E5E5E5;
+
+}
+
+.card-Radio-container {
+  gap: 0 24px;
+
+  .card-Radio {
+    position: relative;
+    width: max-content;
+    padding: 16px;
+    box-sizing: border-box;
+    background: #FFFFFF;
+    border-radius: 4px 4px 4px 4px;
+    border: 2px solid #E5E5E5;
+    cursor: pointer;
+
+    .Method {
+      font-size: 14px;
+      color: #333;
+      font-weight: bold;
+      line-height: 1.4;
+      margin-bottom: 4px;
+    }
+
+    .annotation {
+      font-size: 14px;
+      color: #999;
+    }
+
+    .anticon-check-circle {
+      position: absolute;
+      top: 7px;
+      right: 7px;
+      font-size: 21px;
+      color: #018FFB;
+    }
+  }
+
+  .card-Radio-active {
+    border: 2px solid #80C7FD;
+  }
+}
+
+.color-preview {
+  width: 120px;
+  padding: 3px 10px;
+  box-sizing: border-box;
+  border-radius: 4px 4px 4px 4px;
+  border: 1px solid #E5E5E5;
+
+  .colorBlock {
+    width: 16px;
+    height: 16px;
+  }
+
+  span {
+    font-size: 14px;
+    color: #333;
+    margin-left: 4px;
+  }
+}
+
+.text {
+  font-size: 14px;
+  color: #999;
+}
+
+.w250 {
+  width: 250px;
+}
+
+.fontsize14 {
+  font-size: 14px;
+  color: #333;
+}
+
+.table {
+  width: 800px;
+}
+
+.merchantSupplier {
+  padding: 6px 10px;
+  box-sizing: border-box;
+  background: #E9F4FF;
+  border-radius: 2px 2px 2px 2px;
+  width: auto;
+  font-size: 14px;
+  color: #018FFB;
+}
+
+.received {
+  margin-left: 160px;
+}
+
+.importPrompt {
+  background: #FFFAEF;
+  border-radius: 4px 4px 4px 4px;
+  border: 1px solid #FAA21E;
+  padding: 16px;
+  box-sizing: border-box;
+}
+
+.benefit {
+  .header {
+    padding: 4px;
+    box-sizing: border-box;
+    background: #F7F7F9;
+    border-radius: 4px 4px 0px 0px;
+    font-size: 14px;
+    color: #333;
+    font-weight: bold;
+    border: 1px solid #EEEEEE;
+    text-align: center;
+  }
+
+  .custom-options {
+    max-height: 200px;
+    overflow-y: auto;
+  }
+
+  .option-item {
+    padding: 8px 6px;
+    width: 100%;
+    border-radius: 4px;
+
+    span {
+      text-align: center;
+
+    }
+  }
+
+  .option-item:hover {
+    background-color: #F7F7F9;
+  }
+
+}
+</style>

+ 350 - 0
src/views/channel/distributionRecord/details.vue

@@ -0,0 +1,350 @@
+<!-- 模板区域 -->
+<template>
+  <div class="body">
+    <div class="card">
+      <div class="item dis f-c">
+        <div class="card-header dis a-c ">
+          <div class="link"></div>
+          <span>基本信息</span>
+        </div>
+        <a-descriptions :column="4">
+          <a-descriptions-item label="记录编号">{{ info.id || '' }}</a-descriptions-item>
+          <a-descriptions-item label="发放渠道">{{ info.displayName || '' }}</a-descriptions-item>
+          <a-descriptions-item label="发放方式">{{ pricingUnitMap() }}</a-descriptions-item>
+          <a-descriptions-item label="发放金额">{{ info.totalGrantAmount || '' }}</a-descriptions-item>
+          <a-descriptions-item label="接收人数">
+            {{ info.totalReceiverCount || '' }}
+          </a-descriptions-item>
+          <a-descriptions-item label="发券数量">
+            {{ info.totalRightsCount || '' }}
+          </a-descriptions-item>
+          <a-descriptions-item label="发放人">
+            {{ info.grantUserName || '' }}
+          </a-descriptions-item>
+          <a-descriptions-item label="发放时间">
+            {{ info.createTime || '' }}
+          </a-descriptions-item>
+          <a-descriptions-item label="备注">
+            {{ info.remark || '' }}
+          </a-descriptions-item>
+        </a-descriptions>
+      </div>
+      <div class="item dis f-c">
+        <div class="card-header dis a-c ">
+          <div class="link"></div>
+          <span>接收人信息</span>
+        </div>
+        <a-steps progress-dot :current="1" direction="vertical" v-if="info.grantType == '3'">
+          <a-step>
+            <template #title>
+              <span class="a-steps-title">上传接收人文件</span>
+            </template>
+            <template #description>
+              <span style="color: #018FFB;">{{ info.fileUrl || '' }}</span>
+            </template>
+          </a-step>
+          <a-step v-if="info.failedFileUrl">
+            <template #title>
+              <span class="a-steps-title">识别接收人文件</span>
+            </template>
+            <template #description>
+              <!-- <div class="text">
+                导入 0 条数据,其中 0 条正常导入;5 条不符合要求无法导入</div> -->
+              <a-button type="link" preIcon="ant-design:download-outlined"
+                @click="downloadFailedFile">下载导入失败文件</a-button>
+            </template>
+          </a-step>
+          <a-step>
+            <template #title>
+              <span class="a-steps-title">发放权益</span>
+            </template>
+            <template #description>
+              <div class="custom-content">{{ info.createTime }}由管理员姓名发放</div>
+            </template>
+          </a-step>
+        </a-steps>
+        <a-descriptions :column="4" v-else>
+          <a-descriptions-item v-for="value in info.receiver?.dynamicAttrs" :label="value.rulesName">{{ value.rulesValue || '' }}</a-descriptions-item>
+        </a-descriptions>
+      </div>
+      <div class="card-header dis a-c ">
+        <div class="link"></div>
+        <span>发放权益</span>
+      </div>
+      <a-table :dataSource="info.rightsList" size="small" :columns="Columns" :pagination="false"
+        rowClassName="rowClassName">
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.dataIndex === 'rightsType'">
+            <span>{{ record.rightsType === '1' ? '权益' : '权益包' }}</span>
+          </template>
+          <template v-if="column.dataIndex === 'status'">
+            <a-tag :bordered="false" :color="record.status == '1' ? 'processing' : 'error'">{{ record.status == '1' ?
+              '正常' : '已作废' }}</a-tag>
+          </template>
+          <template v-if="column.dataIndex === 'settlePrice'">
+            <span>{{ Decimal(record.totalPrice || 0).div(record.grantCount || 0) }}</span>
+          </template>
+          <template v-if="column.dataIndex === 'operation'">
+            <a-button :disabled="record.status != '1'" type="link" @click="distributionRecordDel(record)">作废</a-button>
+          </template>
+        </template>
+      </a-table>
+      <div class="footer mt-2 mb-2" style="text-align: right;">
+        <div class="cell">
+          <span>发放人数</span>
+          <span>{{ info.totalReceiverCount || 0 }}</span>
+        </div>
+        <div class="cell">
+          <span>发券数量</span>
+          <span>{{ info.totalRightsCount || 0 }}</span>
+        </div>
+        <div class="cell">
+          <span>人均成本</span>
+          <span>¥{{ info.grantAmount || 0 }}</span>
+        </div>
+        <div class="cell">
+          <span>结算金额</span>
+          <span style="color: #FF8C21;">¥{{ info.totalGrantAmount || 0 }}</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<!-- 行为区域 -->
+<script lang='ts' setup>
+import { reactive, ref, onMounted, watch } from 'vue';
+import { useRouter, useRoute } from 'vue-router';
+import * as distributionRecordAPI from '/@/api/channel/distributionRecord';
+import { Decimal } from "decimal.js";
+import { message, Modal } from 'ant-design-vue';
+import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
+import { createVNode } from 'vue';
+onMounted(async () => {
+  await getDetails()//详情
+})
+const pricingUnitMap = () => {
+  const pricingUnitMap = {
+    "1": "API发放",
+    "2": "H5发放",
+    "3": "后台发放",
+  }
+  return pricingUnitMap[info.value.grantType];
+}
+interface DistributionRecordDetail {
+  [key: string]: any;//默认全类型
+}
+const router = useRouter();
+const route = useRoute();
+const distributionRecordId = ref(route.query.id)
+const info = ref<DistributionRecordDetail>({})  //详情信息
+const Columns = [
+  {
+    title: '权益名称',
+    dataIndex: 'rightsContentName',
+    key: 'rightsContentName',
+    align: "center",
+  },
+  {
+    title: '权益类型',
+    dataIndex: 'rightsType',
+    key: 'rightsType',
+    width: 200,
+    align: "center"
+
+  },
+  {
+    title: '状态',
+    dataIndex: 'status',
+    key: 'status',
+    align: "center",
+  },
+  {
+    title: '发放数量',
+    dataIndex: 'grantCount',
+    key: 'grantCount',
+    align: "center",
+    width: 200,
+
+  },
+  {
+    title: '使用数量',
+    dataIndex: 'usedCount',
+    key: 'usedCount',
+    align: "center",
+    width: 200,
+
+  },
+  {
+    title: '作废数量',
+    dataIndex: 'cancelledCount',
+    key: 'cancelledCount',
+    align: "center",
+    width: 200,
+
+  },
+
+  {
+    title: '结算价',
+    dataIndex: 'settlePrice',
+    key: 'settlePrice',
+    align: "center",
+    width: 200,
+
+  },
+  {
+    title: '总价',
+    dataIndex: 'totalPrice',
+    key: 'totalPrice',
+    align: "center",
+    width: 200,
+
+  },
+  {
+    title: '操作',
+    dataIndex: 'operation',
+    key: 'operation',
+    align: "center",
+    width: 150,
+
+  },
+]
+// 权益作废
+const distributionRecordDel = (record: any) => {
+  const invalidReason = ref(''); // 作废原因
+  Modal.confirm({
+    title: '权益作废',
+    okText: '确定作废',
+    icon: createVNode(ExclamationCircleOutlined, { style: 'color: #ff4d4f' }),
+    content: createVNode('div', {}, [
+      createVNode('p', { style: 'margin-bottom: 10px;' }, '确定将该记录中发放的权益全部作废?'),
+      createVNode('textarea', {
+        value: invalidReason.value,
+        onInput: (e) => (invalidReason.value = e.target.value),
+        placeholder: '请输入作废原因(必填)',
+        style: 'width: 100%; padding: 8px; border: 1px solid #d9d9d9; border-radius: 2px;outline: none !important;color:#333',
+        rows: 4,
+      }),
+    ]),
+    onOk: () => {
+      if (invalidReason.value.length < 1) {
+        message.error('作废原因(必填)');
+        return Promise.reject();
+      }
+      distributionRecordAPI.cancelDistributionRecord({
+        id: record.id,
+        cancelReason: invalidReason.value,
+      }).then(res => {
+        if (res.code === 200) {
+          message.success('作废成功');
+          getDetails();
+        } else {
+          message.error(res.msg || '作废失败');
+        }
+      });
+    },
+  });
+}
+// 详情接口
+const getDetails = async () => {
+  const res = await distributionRecordAPI.getDistributionRecordDetail(distributionRecordId.value)
+  if (res.code == 200) {
+    console.log(res)
+    info.value = res.result;
+  } else {
+    message.error(res.message);
+  }
+}
+// 下载导入失败文件
+const downloadFailedFile = () => {
+  window.open(
+    info.value.failedFileUrl,
+  );
+}
+</script>
+<!-- 样式区域 -->
+<style lang='less' scoped>
+.body {
+  padding: 10px 0px;
+  box-sizing: border-box;
+  height: 100%;
+
+  .card {
+    padding: 0 24px;
+    box-sizing: border-box;
+    background-color: #fff;
+    border-radius: 4px;
+    overflow-y: auto;
+    height: calc(100vh - 140px);
+
+    .item {
+      border-bottom: 1px solid #E5E5E5;
+    }
+
+    .card-header {
+      padding: 20px 0;
+      box-sizing: border-box;
+
+      .link {
+        width: 3px;
+        height: 20px;
+        border-radius: 0 5px 5px 0;
+        background-color: #018FFB;
+        margin-right: 7px;
+      }
+
+      font-size: 20px;
+      color: #333;
+      font-weight: bold;
+
+    }
+  }
+}
+
+.text {
+  font-size: 14px;
+  color: #999;
+}
+
+.w250 {
+  width: 250px;
+}
+
+.fontsize14 {
+  font-size: 14px;
+  color: #333;
+}
+
+.table {
+  width: 800px;
+}
+
+.footer {
+  text-align: right;
+
+  .cell {
+    padding: 6px 0;
+
+    >:first-child {
+      font-size: 14px;
+      color: #666;
+    }
+
+    >:last-child {
+      display: inline-block;
+      width: 100px;
+      font-size: 14px;
+      color: #333;
+    }
+  }
+
+  .cell:last-child {
+    font-weight: bold;
+  }
+}
+
+.a-steps-title {
+  font-weight: bold;
+  font-size: 14px;
+  color: #333;
+}
+</style>

+ 345 - 0
src/views/channel/distributionRecord/pageList.vue

@@ -0,0 +1,345 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <!--定义表格-->
+    <BasicTable @register="registerTable">
+      <!-- 表格顶部左侧操作 -->
+      <template #tableTitle>
+        <a-button type="primary" @click="distributionRecordAdd" preIcon="ant-design:plus-outlined">立即新建</a-button>
+      </template>
+      <template #bodyCell="{ column, record }">
+        <span v-if="column.key === 'id'" class="cursorstyle" @click="distributionRecordDetail(record)">
+          {{ record.id }}
+        </span>
+        <template v-if="column.key === 'fileName'">
+          <span v-if="record.grantType == '3'" class="cursorstyle" @click="recipientFileDownload(record.fileUrl)">
+            {{ record.fileName }}
+          </span>
+          <a-popover title="全部字段" placement="bottom" v-else>
+              <template #content>
+                <p v-for="value in record.receiver.dynamicAttrs">{{ value.rulesName }}:{{ value.rulesValue }}</p>
+              </template>
+              <span>{{record.receiver.dynamicAttrs[0].rulesName}}</span>
+            </a-popover>
+        </template>
+        <a-tag v-if="column.key === 'status'" :bordered="false"
+          :color="record.status == '1' ? 'processing' : 'error'">{{
+            record.status == '1' ? '有效' : '已作废' }}</a-tag>
+      </template>
+      <!--操作栏-->
+      <template #action="{ record }">
+        <TableAction :actions="getTableAction(record)" />
+      </template>
+    </BasicTable>
+  </div>
+</template>
+<!-- 行为区域 -->
+<script lang="ts" name="basic-table-demo" setup>
+import { ActionItem, BasicColumn, BasicTable, TableAction } from '/@/components/Table';
+import { useListPage } from '/@/hooks/system/useListPage';
+import { FormSchema } from '/@/components/Table';
+import { reactive, ref } from 'vue';
+import * as distributionRecordAPI from '/@/api/channel/distributionRecord';
+import { message, Modal } from 'ant-design-vue';
+import { useRouter, useRoute } from 'vue-router';
+import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
+import { createVNode } from 'vue';
+//表单配置
+const router = useRouter();
+//定义表格列字段
+const columns: BasicColumn[] = [
+  {
+    title: '记录编号',
+    dataIndex: 'id',
+    key: 'id',
+    width: 200,
+  },
+  {
+    title: '发放渠道',
+    dataIndex: 'displayName',
+    key: 'displayName',
+  },
+  {
+    title: '发放方式',
+    dataIndex: 'grantType',
+    key: 'grantType',
+    customRender: ({ record }) => {
+      const pricingUnitMap = {
+        "1": "API发放",
+        "2": "H5发放",
+        "3": "后台发放",
+      }
+      return pricingUnitMap[record.grantType];
+    },
+  },
+  {
+    title: '接收人',
+    dataIndex: 'fileName',
+    key: 'fileName',
+  },
+  {
+    title: '状态',
+    dataIndex: 'status',
+    key: 'status',
+    customRender: ({ record }) => {
+      return record.status ? (record.status == '1' ? '有效' : '已作废') : '';
+    },
+  },
+  {
+    title: '发放金额',
+    dataIndex: 'totalGrantAmount',
+    key: 'totalGrantAmount',
+  },
+  {
+    title: '覆盖人数',
+    dataIndex: 'totalReceiverCount',
+    key: 'totalReceiverCount',
+  },
+  {
+    title: '发券数量',
+    dataIndex: 'totalRightsCount',
+    key: 'totalRightsCount',
+  },
+  {
+    title: '使用数量',
+    dataIndex: 'useCount',
+    key: 'useCount',
+  },
+  {
+    title: '使用率',
+    dataIndex: 'usageRate',
+    key: 'usageRate',
+  },
+  {
+    title: '发放时间',
+    dataIndex: 'createTime',
+    key: 'createTime',
+  },
+  {
+    title: '发放人',
+    dataIndex: 'grantUserName',
+    key: 'grantUserName',
+  },
+  {
+    title: '发放人手机号',
+    dataIndex: 'grantUserPhone',
+    key: 'grantUserPhone',
+  },
+];
+const searchFormSchema: FormSchema[] = [
+  {
+    label: '记录编号',
+    field: 'id',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+  },
+  {
+    label: '发放渠道',
+    field: 'channelName',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+  },
+  {
+    label: '发放人',
+    field: 'grantUserName',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+  },
+  {
+    label: '发放方式',
+    field: 'grantType',
+    component: 'Select',
+    colProps: {
+      span: 7,
+    },
+    componentProps: {
+      options: [
+        { label: "API发放", value: 1 },
+        { label: "H5发放", value: 2 },
+        { label: "后台发放", value: 3 },
+      ]
+    }
+  },
+  {
+    label: '状态',
+    field: 'status',
+    component: 'Select',
+    colProps: {
+      span: 7,
+    },
+    componentProps: {
+      options: [
+        { label: "有效", value: 1 },
+        { label: "已作废", value: 0 },
+      ]
+    }
+  },
+  {
+    label: '发放时间',
+    field: 'dateRangeSelect',
+    component: 'RangeDate',
+    componentProps: {
+      showTime: true,
+      format: 'YYYY-MM-DD HH:mm:ss',
+      placeholder: ['请选择开始日期时间', '请选择结束日期时间']
+    },
+    colProps: {
+      span: 7,
+    },
+  },
+];
+/** useListPage 是整个框架的核心用于表格渲染,里边封装了很多公共方法;
+ * 平台通过此封装,简化了代码,支持自定义扩展*/
+// 通过hook useListPage渲染表格(设置dataSource、columns、actionColumn等参数)
+const { tableContext } = useListPage({
+  designScope: 'basic-table-demo',
+  tableProps: {
+    dataSource: [],
+    columns: columns,
+    formConfig: {
+      schemas: searchFormSchema,
+      showAdvancedButton: false,
+      // 将表单内时间区域的值映射成 2个字段, 分别是 joinTime_begin 和 joinTime_end
+      fieldMapToTime: [['dateRangeSelect', ['createTimeBegin', 'createTimeEnd'], 'YYYY-MM-DD HH:mm:ss']],
+    },
+    api: distributionRecordAPI.getDistributionRecordList,
+    size: 'small',
+    actionColumn: {
+      width: 200,
+      fixed: 'right'
+    },
+  },
+});
+// BasicTable绑定注册
+const [registerTable, { reload }] = tableContext;
+/**
+ * 操作栏
+ */
+function getTableAction(record: ActionItem): ActionItem[] {
+  return [
+    {
+      label: '明细',
+      onClick: detailInfo.bind(null, record),
+    },
+    {
+      label: '详情',
+      onClick: distributionRecordDetail.bind(null, record),
+    },
+    {
+      label: '作废',
+      onClick: distributionRecordDel.bind(null, record),
+    },
+  ];
+}
+// 下载接收人文件
+// 下载接收人文件
+const recipientFileDownload = (fileUrl: string) => {
+  window.open(fileUrl);
+}
+// 新增发放记录 - 带参数在新标签页中打开
+const distributionRecordAdd = (record: ActionItem) => {
+  // 定义要传递的参数
+  const params = {
+    id: record.id,
+  };
+  // 使用 router.resolve() 解析路由和参数
+  const routeInfo = router.resolve({
+    path: "/channel/distributionRecord/add",
+    query: params
+  });
+  // 构建完整的 URL
+  const fullUrl = `${window.location.origin}${routeInfo.href}`;
+  // 在新标签页中打开
+  window.open(fullUrl, '_blank');
+}
+// 权益作废
+const distributionRecordDel = (record: ActionItem) => {
+  const invalidReason = ref(''); // 作废原因
+  Modal.confirm({
+    title: '权益作废',
+    okText: '确定作废',
+    icon: createVNode(ExclamationCircleOutlined, { style: 'color: #ff4d4f' }),
+    content: createVNode('div', {}, [
+      createVNode('p', { style: 'margin-bottom: 10px;' }, '确定将该记录中发放的权益全部作废?'),
+      createVNode('textarea', {
+        value: invalidReason.value,
+        onInput: (e) => (invalidReason.value = e.target.value),
+        placeholder: '请输入作废原因(必填)',
+        style: 'width: 100%; padding: 8px; border: 1px solid #d9d9d9; border-radius: 2px;outline: none !important;color:#333',
+        rows: 4,
+      }),
+    ]),
+    onOk: () => {
+      if (invalidReason.value.length < 1) {
+        message.error('作废原因(必填)');
+        return Promise.reject();
+      }
+      distributionRecordAPI.cancelDistributionRecordByRecordId({
+        id: record.id,
+        cancelReason: invalidReason.value,
+      }).then(res => {
+        if (res.code === 200) {
+          message.success('作废成功');
+          reload();
+        } else {
+          message.error(res.msg || '作废失败');
+        }
+      });
+    },
+  });
+}
+// 明细
+const detailInfo = (record: ActionItem) => {
+  // 定义要传递的参数
+  const params = {
+    id: record.id,
+  };
+  // 使用 router.resolve() 解析路由和参数
+  const routeInfo = router.resolve({
+    path: "/channel/distributionDetails",
+    query: params
+  });
+  // 构建完整的 URL
+  const fullUrl = `${window.location.origin}${routeInfo.href}`;
+  // 在新标签页中打开
+  window.open(fullUrl, '_blank');
+}
+// 详情
+const distributionRecordDetail = (record: ActionItem) => {
+  const params = {
+    id: record.id,
+  };
+  // 使用 router.resolve() 解析路由和参数
+  const routeInfo = router.resolve({
+    path: "/channel/distributionRecord/details",
+    query: params
+  });
+  // 构建完整的 URL
+  const fullUrl = `${window.location.origin}${routeInfo.href}`;
+  // 在新标签页中打开
+  window.open(fullUrl, '_blank');
+}
+</script>
+<!-- 样式区域 -->
+<style lang='less' scoped>
+::v-deep(.jeecg-basic-table .ant-table-wrapper) {
+  padding: 20px;
+  height: calc(100% - 10px);
+}
+
+::v-deep(.ant-col) {
+  width: auto;
+}
+
+.cursorstyle {
+  font-size: 14px;
+  color: #018FFB;
+  cursor: pointer;
+}
+</style>

+ 246 - 0
src/views/channel/isInvalidRecords.vue

@@ -0,0 +1,246 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <!--定义表格-->
+    <BasicTable @register="registerTable">
+      <!-- 表格顶部左侧操作 -->
+      <template #tableTitle>
+        <a-button preIcon="ant-design:plus-outlined" @click="onExportXls">导出</a-button>
+      </template>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'phone'">
+          <a-popover title="全部字段" placement="bottom" v-if="record.grantType == '3'">
+            <template #content>
+              <div class="dis f-c ">
+                <span>手机号:{{ record.receiver.phone || '-' }}</span>
+                <span>保单号:{{ record.receiver.policyNo || '-' }}</span>
+                <span>车架号:{{ record.receiver.vin || '-' }}</span>
+                <span>接收人姓名:{{ record.receiver.receiverName || '-' }}</span>
+                <span>车牌号:{{ record.receiver.plateNo || '-' }}</span>
+              </div>
+            </template>
+            <span>{{record.phone || '接收人信息'}}</span>
+          </a-popover>
+          <a-popover title="全部字段" placement="bottom" v-else>
+            <template #content>
+              <p v-for="value in record.receiver.dynamicAttrs">{{ value.rulesName }}:{{ value.rulesValue }}</p>
+            </template>
+            <span>{{record.phone || '接收人信息'}}</span>
+          </a-popover>
+        </template>
+      </template>
+    </BasicTable>
+  </div>
+</template>
+<!-- 行为区域 -->
+<script lang="ts" name="basic-table-demo" setup>
+import { ActionItem, BasicColumn, BasicTable, TableAction } from '/@/components/Table';
+import { useListPage } from '/@/hooks/system/useListPage';
+import { FormSchema } from '/@/components/Table';
+import { reactive, ref } from 'vue';
+import * as isInvalidRecordsAPI from '/@/api/channel/isInvalidRecords';
+import { useRouter, useRoute } from 'vue-router';
+//表单配置
+const router = useRouter();
+const route = useRoute();
+// 权益详情弹窗
+//定义表格列字段
+const columns: BasicColumn[] = [
+  {
+    title: '记录编号',
+    dataIndex: 'recordId',
+    key: 'recordId',
+    width: 200,
+  },
+  {
+    title: '渠道',
+    dataIndex: 'displayName',
+    key: 'displayName',
+    ellipsis: true
+
+  },
+  {
+    title: '权益名称',
+    dataIndex: 'rightsName',
+    key: 'rightsName',
+    ellipsis: true,
+    customRender: ({ record }) => {
+      const pricingUnitMap = {
+        "1": "元/点击",
+        "2": "元/天",
+        "3": "元/笔销售",
+        "4": "元/千次曝光",
+      }
+      return pricingUnitMap[record.pricingUnit];
+    },
+  },
+  {
+    title: '发放方式',
+    dataIndex: 'grantType',
+    key: 'grantType',
+    customRender: ({ record }) => {
+      const pricingUnitMap = {
+        "1": "API发放",
+        "2": "H5发放",
+        "3": "后台发放",
+      }
+      return pricingUnitMap[record.grantType];
+    },
+  },
+  {
+    title: '发放数量',
+    dataIndex: 'grantCount',
+    key: 'grantCount',
+  },
+  {
+    title: '已作废',
+    dataIndex: 'cancelCount',
+    key: 'cancelCount',
+  },
+  {
+    title: '接收人信息',
+    dataIndex: 'phone',
+    key: 'phone',
+  },
+  {
+    title: '操作人',
+    dataIndex: 'cancelBy',
+    key: 'cancelBy',
+  },
+  {
+    title: '操作时间',
+    dataIndex: 'cancelTime',
+    key: 'cancelTime',
+  },
+  {
+    title: '作废原因',
+    dataIndex: 'cancelReason',
+    key: 'cancelReason',
+  }];
+const searchFormSchema: FormSchema[] = [
+  {
+    label: '记录编号',
+    field: 'recordId',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+    defaultValue: route.query.recordId, // 设置初始值
+  },
+  {
+    label: '权益名称',
+    field: 'rightsName',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+  },
+  {
+    label: '发放渠道',
+    field: 'channelName',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+  },
+  {
+    label: '操作人',
+    field: 'cancelBy',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+  },
+  {
+    label: '作废时间',
+    field: 'dateRangeSelect',
+    component: 'RangeDate',
+    colProps: {
+      span: 7,
+    },
+    componentProps: {
+      format: 'YYYY-MM-DD',
+      placeholder: ['开始日期', '结束日期']
+    },
+  },
+  {
+    label: '接收人保单号',
+    field: 'policyNo',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+  },
+  {
+    label: '接收人id',
+    field: 'receiverId',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+    show: false,
+    defaultValue: route.query.receiverId, // 设置初始值
+  },
+  {
+    label: '权益id',
+    field: 'rightsId',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+    show: false,
+    defaultValue: route.query.rightsId, // 设置初始值
+  },
+];
+const resetFunc = (): Promise<void> => {
+  return new Promise((resolve, reject) => {
+    const { setFieldsValue } = getForm();
+    setFieldsValue({ recordId: '', receiverId: '', rightsId: '' });
+    resolve();
+  });
+};
+/** useListPage 是整个框架的核心用于表格渲染,里边封装了很多公共方法;
+ * 平台通过此封装,简化了代码,支持自定义扩展*/
+// 通过hook useListPage渲染表格(设置dataSource、columns、actionColumn等参数)
+const { tableContext, onExportXls } = useListPage({
+  designScope: 'basic-table-demo',
+  tableProps: {
+    title: '用户列表',
+    dataSource: [],
+    columns: columns,
+    showActionColumn: false,
+    formConfig: {
+      schemas: searchFormSchema,
+      showAdvancedButton: false,
+      resetFunc: resetFunc,
+      fieldMapToTime: [['dateRangeSelect', ['cancelTimeBegin', 'cancelTimeEnd'], 'YYYY-MM-DD HH:mm:ss']],
+    },
+    api: isInvalidRecordsAPI.getCancelRecordList,
+    size: 'small',
+    actionColumn: {
+      width: 200,
+      fixed: 'right'
+    },
+  },
+  exportConfig: {
+    name: '作废记录导出',
+    url: '/channel/cancelRecord/exportXls',
+    method: 'get'
+  },
+});
+// BasicTable绑定注册
+const [registerTable, { getForm, reload }] = tableContext;
+
+
+</script>
+<!-- 样式区域 -->
+<style lang='less' scoped>
+::v-deep(.jeecg-basic-table .ant-table-wrapper) {
+  padding: 20px;
+  height: calc(100% - 10px);
+}
+
+::v-deep(.ant-col) {
+  width: auto;
+}
+</style>

+ 417 - 0
src/views/channel/management.vue

@@ -0,0 +1,417 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <!--定义表格-->
+    <BasicTable @register="registerTable">
+      <!-- 表格顶部左侧操作 -->
+      <template #tableTitle>
+        <a-button type="primary" preIcon="ant-design:plus-outlined" @click="channelCreate">创建渠道</a-button>
+      </template>
+      <!--操作栏-->
+      <template #action="{ record }">
+        <TableAction :actions="getTableAction(record)" />
+      </template>
+      <template #img="{ text }">
+        <img :src="text" alt="" style="width: 40px; height: 40px;">
+      </template>
+    </BasicTable>
+    <a-modal v-model:open="addChannelShow" title="添加渠道" @ok="onSubmit" :afterClose="adafterClose" width="700px"
+      :bodyStyle="{ padding: '16px' }">
+      <a-form ref="formRef" :model="ManagementForm" layout="vertical" :rules="rules">
+        <a-row :gutter="25">
+          <a-col :span="12">
+            <a-form-item label="渠道名称" name="name">
+              <a-input v-model:value="ManagementForm.name" showCount maxlength="50" autocomplete="off" allowClear
+                placeholder="输入50个字以内的渠道名称" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="管理员姓名" name="administratorName">
+              <a-input v-model:value="ManagementForm.administratorName" showCount maxlength="20" placeholder="输入管理员姓名"
+                allowClear autocomplete="off" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="管理员账号" name="administratorAccount">
+              <a-input v-model:value="ManagementForm.administratorAccount" showCount maxlength="20"
+                placeholder="输入管理员账号" allowClear autocomplete="off" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="登录密码" name="administratorPassword">
+              <a-input v-model:value="ManagementForm.administratorPassword" placeholder="输入登录密码" allowClear
+                autocomplete="off" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="联系人姓名">
+              <a-input v-model:value="ManagementForm.contact" showCount maxlength="20" placeholder="输入联系人姓名" allowClear
+                autocomplete="off" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="联系电话">
+              <a-input v-model:value="ManagementForm.contactPhone" showCount maxlength="11" placeholder="输入联系电话"
+                allowClear autocomplete="off" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="24">
+            <a-form-item label="渠道图标">
+              <div class="clearfix">
+                <a-upload v-model:file-list="fileList" :max-count="1" :action="UploadUrl" @change="fileChange"
+                  list-type="picture-card" @preview="handlePreview" accept=".jpg,.jpeg,.png">
+                  <div v-if="fileList.length < 1">
+                    <PlusOutlined />
+                    <div style="margin-top: 8px">Upload</div>
+                  </div>
+                </a-upload>
+              </div>
+            </a-form-item>
+          </a-col>
+        </a-row>
+      </a-form>
+    </a-modal>
+    <!-- 预览视图 -->
+    <a-modal :open="previewVisible" :title="previewTitle" :footer="null" @cancel="handleCancel">
+      <img alt="example" style="width: 100%" :src="previewImage" />
+    </a-modal>
+  </div>
+</template>
+<!-- 行为区域 -->
+<script lang="ts" name="basic-table-demo" setup>
+import { ActionItem, BasicColumn, BasicTable, TableAction } from '/@/components/Table';
+import { useListPage } from '/@/hooks/system/useListPage';
+import { FormSchema } from '/@/components/Table';
+import { reactive, ref } from 'vue';
+import * as channelAPI from '/@/api/channel/management';
+import { message, Modal } from 'ant-design-vue';
+import { PlusOutlined, ExclamationCircleOutlined } from '@ant-design/icons-vue';
+import { useRouter, useRoute } from 'vue-router';
+import type { Rule } from 'ant-design-vue/es/form';
+import type { UploadProps } from 'ant-design-vue';
+import { createVNode } from 'vue';
+
+//表单配置
+const router = useRouter();
+const formRef = ref();
+
+const labelCol = { span: 5 };
+const wrapperCol = { span: 13 };
+const ManagementForm = reactive({
+  id: "",
+  name: "",//渠道名称 
+  logo: "",//渠道图标
+  administratorAccount: "",//管理员账号
+  administratorName: "",//管理员姓名  
+  administratorPassword: "",//管理员登录密码  
+  contact: "",//联系人姓名
+  contactPhone: "",//联系电话
+});
+const fileList = ref([]);//文件显示列表
+const previewVisible = ref(false);//图片预览弹窗
+const previewImage = ref('');//图片路径
+const previewTitle = ref('');
+import { useGlobSetting } from '/@/hooks/setting';
+const globSetting = useGlobSetting();
+const baseUploadUrl = globSetting.uploadUrl;
+const UploadUrl = `${baseUploadUrl}/sys/common/upload`; // 上传地址
+const rules: Record<string, Rule[]> = {
+  name: [
+    { required: true, message: '渠道名称', trigger: 'blur' },
+  ],
+  administratorName: [
+    { required: true, message: '管理员姓名', trigger: 'blur' },
+  ],
+  administratorAccount: [
+    { required: true, message: '管理员账号', trigger: 'blur' },
+    {
+      pattern: /^[a-zA-Z0-9]+$/,
+      message: '密码只能包含字母和数字',
+    },
+
+  ],
+  administratorPassword: [
+    { required: true, message: '登录密码', trigger: 'blur' },
+    {
+      pattern: /^[a-zA-Z0-9]+$/,
+      message: '密码只能包含字母和数字',
+    },
+    {
+      min: 8,
+      max: 20,
+      message: '密码长度8~20位字符',
+    },
+  ],
+};
+const addChannelShow = ref(false);// 添加渠道弹窗
+const columns: BasicColumn[] = [//定义表格列字段
+  {
+    title: '渠道名称',
+    dataIndex: 'name',
+    key: 'name',
+    width: 200,
+    ellipsis: true
+  },
+  {
+    title: 'Logo',
+    dataIndex: 'logo',
+    key: 'logo',
+    slots: { customRender: 'img' }, // 指定该列用 img 插槽渲染
+  },
+  {
+    title: '联系人姓名',
+    dataIndex: 'contact',
+    key: 'contact',
+  },
+  {
+    title: '联系人手机号',
+    dataIndex: 'contactPhone',
+    key: 'contactPhone',
+  },
+  {
+    title: '下级机构',
+    dataIndex: 'status',
+    key: 'status',
+  },
+  {
+    title: '状态',
+    dataIndex: 'status',
+    key: 'status',
+    customRender: ({ record }) => {
+      if (record.status == 0) {
+        return '启用';
+      } else if (record.status == 1) {
+        return '禁用';
+      }
+
+    },
+  },
+  {
+    title: '创建人',
+    dataIndex: 'createTime',
+    key: 'createTime',
+  },
+  {
+    title: '创建时间',
+    dataIndex: 'createTime',
+    key: 'createTime',
+  },
+];
+const searchFormSchema: FormSchema[] = [
+  {
+    label: '渠道名称',
+    field: 'name',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+  },
+  {
+    label: '联系人',
+    field: 'contact',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+  },
+  {
+    label: '创建时间',
+    field: 'date',
+    component: 'RangeDate',
+    colProps: {
+      span: 7,
+    },
+    componentProps: {
+      format: 'YYYY-MM-DD',
+      placeholder: ['开始日期', '结束日期']
+    },
+  },
+];
+/** useListPage 是整个框架的核心用于表格渲染,里边封装了很多公共方法;
+ * 平台通过此封装,简化了代码,支持自定义扩展*/
+// 通过hook useListPage渲染表格(设置dataSource、columns、actionColumn等参数)
+const { tableContext } = useListPage({
+  designScope: 'basic-table-demo',
+  tableProps: {
+    title: '用户列表',
+    dataSource: [],
+    columns: columns,
+    formConfig: {
+      schemas: searchFormSchema,
+      showAdvancedButton: false,
+      fieldMapToTime: [['date', ['startDate', 'endDate'], 'YYYY-MM-DD']],
+    },
+    api: channelAPI.managementPage,
+    size: 'small',
+    actionColumn: {
+      width: 200,
+      fixed: 'right'
+    },
+  },
+});
+// BasicTable绑定注册
+const [registerTable, { getForm, reload }] = tableContext;
+/**
+ * 操作栏
+ */
+function getTableAction(record: ActionItem): ActionItem[] {
+  return [
+    {
+      label: '编辑',
+      onClick: channelEdit.bind(null, record),
+    },
+    {
+      label: record.status == 0 ? '禁用' : '启用',
+      onClick: channelEnabled.bind(null, record),
+
+    },
+  ];
+}
+// 文件上传
+const fileChange = async (info) => {
+  if (info.file.status === 'done') {
+    // 文件上传成功
+    const fileList = info.fileList.map((file) => {
+      if (file.response) {
+        // 拼接地址前缀
+        file.url = import.meta.env.VITE_GLOB_DOMAIN_URL + '/' + file.response.message;
+      }
+      return file.url;
+    });
+    ManagementForm.logo = fileList.join(",")//图片地址逗号拼接
+  }
+}
+//图片预览事件
+const handlePreview = async (file: UploadProps['fileList'][number]) => {
+  if (!file.url && !file.preview) {
+    file.preview = (await getBase64(file.originFileObj)) as string;
+  }
+  previewImage.value = file.url || file.preview;
+  previewVisible.value = true;
+  previewTitle.value = file.name || file.url.substring(file.url.lastIndexOf('/') + 1);
+};
+function getBase64(file: File) {
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader();
+    reader.readAsDataURL(file);
+    reader.onload = () => resolve(reader.result);
+    reader.onerror = error => reject(error);
+  });
+}
+const handleCancel = () => {
+  previewVisible.value = false;
+  previewTitle.value = '';
+  previewImage.value = "";
+};
+// 创建渠道
+const channelCreate = () => {
+  addChannelShow.value = true;
+
+}
+// 编辑
+const channelEdit = (record: ActionItem) => {
+  ManagementForm.id = record.id;
+  channelAPI.managementDetail(record.id).then((res: any) => {
+    console.log(res);
+    if (res.code == 200) {
+      Object.assign(ManagementForm, res.result)
+      let list = res.result.logo.split(',');
+      let data = list.map((val) => ({
+        url: val
+      }));
+      fileList.value = data;
+      addChannelShow.value = true;
+    } else {
+      message.error(res.message);
+    }
+  })
+  // 定义要传递的参数
+}
+// 启用/禁用
+const channelEnabled = (record: ActionItem) => {
+  if (record.status == 0) {
+    Modal.confirm({
+      title: '禁用渠道',
+      content: '确定要禁用该渠道吗',
+      okText: '确定禁用',
+      okType: 'danger',
+      cancelText: '取消',
+      icon: createVNode(ExclamationCircleOutlined, { style: 'color: #ff4d4f' }),
+      onOk() {
+        channelAPI.isChannelEnabled({ id: record.id, status: 1 }).then((res: any) => {
+          if (res.code == 200) {
+            message.success(res.message);
+            reload();
+          } else {
+            message.error(res.message);
+          }
+        })
+      },
+      onCancel() {
+      },
+    });
+  } else {
+    channelAPI.isChannelEnabled({ id: record.id, status: 0 }).then((res: any) => {
+      if (res.code == 200) {
+        message.success(res.message);
+        reload();
+      } else {
+        message.error(res.message);
+      }
+    })
+  }
+
+}
+//提交
+const onSubmit = () => {
+  formRef.value
+    .validate()
+    .then(() => {
+      if (ManagementForm.id) {
+        channelAPI.managementEdit(ManagementForm, ManagementForm.id).then((res: any) => {
+          if (res.code == 200) {
+            message.success(res.message);
+            addChannelShow.value = false;
+            formRef.value.resetFields();
+            reload();
+          } else {
+            message.error(res.message);
+          }
+        })
+      } else {
+        channelAPI.managementAdd(ManagementForm).then((res: any) => {
+          if (res.code == 200) {
+            message.success(res.message);
+            addChannelShow.value = false;
+            formRef.value.resetFields();
+            reload();
+          } else {
+            message.error(res.message);
+          }
+        })
+      }
+
+    })
+    .catch(error => {
+      console.log('error', error);
+    });
+};
+const adafterClose = () => {
+  formRef.value.resetFields();
+}
+</script>
+<!-- 样式区域 -->
+<style lang='less' scoped>
+::v-deep(.jeecg-basic-table .ant-table-wrapper) {
+  padding: 20px;
+  height: calc(100% - 10px);
+}
+
+::v-deep(.ant-col) {
+  width: auto;
+}
+
+::v-deep(.ant-form-item .ant-form-item-label) {
+  width: 140px !important;
+}
+</style>

+ 261 - 0
src/views/channel/usageRecords.vue

@@ -0,0 +1,261 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <!--定义表格-->
+    <BasicTable @register="registerTable">
+      <!-- 表格顶部左侧操作 -->
+      <template #tableTitle>
+        <a-button preIcon="ant-design:plus-outlined" @click="exportDetails">导出</a-button>
+      </template>
+      <!--操作栏-->
+      <template #action="{ record }">
+        <TableAction :actions="getTableAction(record)" />
+      </template>
+    </BasicTable>
+    <a-modal v-model:open="benefitDetailsShow" title="权益明细" width="700px" :footer="null"
+      :bodyStyle="{ padding: '16px' }">
+
+    </a-modal>
+  </div>
+</template>
+<!-- 行为区域 -->
+<script lang="ts" name="basic-table-demo" setup>
+import { ActionItem, BasicColumn, BasicTable, TableAction } from '/@/components/Table';
+import { useListPage } from '/@/hooks/system/useListPage';
+import { FormSchema } from '/@/components/Table';
+import { reactive, ref } from 'vue';
+import * as billingAPI from '/@/api/adManagement/billingModel';
+import { message, Modal } from 'ant-design-vue';
+import { useRouter, useRoute } from 'vue-router';
+//表单配置
+const router = useRouter();
+// 权益详情弹窗
+const benefitDetailsShow = ref(false);
+//定义表格列字段
+const columns: BasicColumn[] = [
+  {
+    title: '订单号',
+    dataIndex: 'pricingName',
+    key: 'pricingName',
+    width: 200,
+  },
+  {
+    title: '发放渠道',
+    dataIndex: 'pricingCode',
+    key: 'pricingCode',
+    ellipsis: true
+
+  },
+  {
+    title: '权益名称',
+    dataIndex: 'pricingUnit',
+    key: 'pricingUnit',
+    ellipsis: true,
+    customRender: ({ record }) => {
+      const pricingUnitMap = {
+        "1": "元/点击",
+        "2": "元/天",
+        "3": "元/笔销售",
+        "4": "元/千次曝光",
+      }
+      return pricingUnitMap[record.pricingUnit];
+    },
+  },
+  {
+    title: '数量',
+    dataIndex: 'minPrice',
+    key: 'minPrice',
+  },
+  {
+    title: '结算价',
+    dataIndex: 'status',
+    key: 'status',
+    customRender: ({ record }) => {
+      return record.status ? (record.status == '1' ? '启用' : '禁用') : '';
+    },
+  },
+  {
+    title: '核销商户',
+    dataIndex: 'createTime',
+    key: 'createTime',
+  },
+  {
+    title: '订单状态',
+    dataIndex: 'createTime',
+    key: 'createTime',
+  },
+  {
+    title: '服务凭证',
+    dataIndex: 'createTime',
+    key: 'createTime',
+  },
+  {
+    title: '接收人保单号',
+    dataIndex: 'createTime',
+    key: 'createTime',
+  },
+  {
+    title: '客户',
+    dataIndex: 'createTime',
+    key: 'createTime',
+  },
+  {
+    title: '手机号',
+    dataIndex: 'createTime',
+    key: 'createTime',
+  },
+  {
+    title: '下单时间',
+    dataIndex: 'createTime',
+    key: 'createTime',
+  },
+  {
+    title: '完成时间',
+    dataIndex: 'createTime',
+    key: 'createTime',
+  },
+];
+const searchFormSchema: FormSchema[] = [
+  {
+    label: '订单号',
+    field: 'pricingName',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+  },
+  {
+    label: '核销商户',
+    field: 'pricingName',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+  },
+  {
+    label: '权益名称',
+    field: 'status',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+  },
+  {
+    label: '发放渠道',
+    field: 'status',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+  },
+  {
+    label: '订单状态',
+    field: 'dateRangeSelect',
+    component: 'Select',
+    colProps: {
+      span: 7,
+    },
+    componentProps: {
+      options: [
+        { label: "全部", value: null },
+        { label: "启用", value: 1 },
+        { label: "禁用", value: 0 },
+      ]
+    }
+  },
+
+  {
+    label: '接收人保单号',
+    field: 'status',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+  },
+
+  {
+    label: '下单时间',
+    field: 'dateRangeSelect',
+    component: 'RangeDate',
+    colProps: {
+      span: 7,
+    },
+    componentProps: {
+      format: 'YYYY-MM-DD',
+      placeholder: ['开始日期', '结束日期']
+    },
+  },
+  {
+    label: '完成时间',
+    field: 'dateRangeSelect',
+    component: 'RangeDate',
+    colProps: {
+      span: 7,
+    },
+    componentProps: {
+      format: 'YYYY-MM-DD',
+      placeholder: ['开始日期', '结束日期']
+    },
+  },
+];
+/** useListPage 是整个框架的核心用于表格渲染,里边封装了很多公共方法;
+ * 平台通过此封装,简化了代码,支持自定义扩展*/
+// 通过hook useListPage渲染表格(设置dataSource、columns、actionColumn等参数)
+const { tableContext } = useListPage({
+  designScope: 'basic-table-demo',
+  tableProps: {
+    title: '用户列表',
+    dataSource: [],
+    columns: columns,
+    showActionColumn: false,
+    formConfig: {
+      schemas: searchFormSchema,
+      showAdvancedButton: false,
+    },
+    api: billingAPI.adPricingModelQueryPageList,
+    size: 'small',
+    actionColumn: {
+      width: 200,
+      fixed: 'right'
+    },
+  },
+});
+// BasicTable绑定注册
+const [registerTable, { getForm, reload }] = tableContext;
+/**
+ * 操作栏
+ */
+function getTableAction(record: ActionItem): ActionItem[] {
+  return [
+    {
+      label: '明细',
+      onClick: details.bind(null, record),
+    },
+    {
+      label: '作废',
+    },
+  ];
+}
+// 导出明细
+const exportDetails = () => {
+  let formValues = getForm().getFieldsValue()
+  console.log(formValues)
+}
+// 明细详情
+const details = (record: ActionItem) => {
+  // 定义要传递的参数
+  benefitDetailsShow.value = true;
+}
+
+</script>
+<!-- 样式区域 -->
+<style lang='less' scoped>
+::v-deep(.jeecg-basic-table .ant-table-wrapper) {
+  padding: 20px;
+  height: calc(100% - 10px);
+}
+
+::v-deep(.ant-col) {
+  width: auto;
+}
+</style>

+ 0 - 0
src/views/organizationStructure/memberManagement/add.vue


+ 725 - 0
src/views/organizationStructure/memberManagement/pageList.vue

@@ -0,0 +1,725 @@
+<!-- 模板区域 -->
+<template>
+  <div class="member-management dis ">
+    <!-- 左侧机构树  -->
+    <div class="organization-tree">
+      <a-input-search placeholder="成员名称查找" v-model:value="searchValue" enter-button />
+      <div class="m-top-10 m-btn-10 dis j-s">
+        <a-button style="width: 50%;" class="mr-2" type="primary" @click="pageJump">机构管理</a-button>
+        <a-button style="width: 50%;" @click="toggleExpand">
+          {{ isExpanded ? '全部收起' : '全部展开' }}
+        </a-button>
+      </div>
+      <a-tree class="draggable-tree" :blockNode="true" v-model:expandedKeys="expandedKeys" :tree-data="InstitutionList"
+        @select="handleTreeSelect" :field-names="{
+          children: 'children',
+          title: 'institutionName',
+          key: 'id',
+        }">
+        <template #title="{ dataRef }">
+          <div class="dis a-c j-s ">
+            {{ dataRef.institutionName }}
+            <a-dropdown placement="bottom" arrow>
+              <EllipsisOutlined style="color: #1890FF;" />
+              <template #overlay>
+                <a-menu trigger="click" @click="e => handleMenuClick(e, dataRef)">
+                  <a-menu-item key="添加子机构">
+                    <a-button type="link" size="small">添加子机构</a-button>
+                  </a-menu-item>
+                  <a-menu-item key="编辑">
+                    <a-button type="link" size="small">编辑</a-button>
+                  </a-menu-item>
+                  <a-menu-item key="删除">
+                    <a-button type="link" size="small">删除</a-button>
+                  </a-menu-item>
+                </a-menu>
+              </template>
+            </a-dropdown>
+          </div>
+        </template>
+      </a-tree>
+    </div>
+    <div class="distribution-record-table">
+      <!--定义表格-->
+      <BasicTable @register="registerTable">
+        <!-- 表格顶部左侧操作 -->
+        <template #tableTitle>
+          <span class="userCount" v-if="organizationName">{{ organizationName }}({{ number }}人)</span>
+          <a-button type="primary" style="margin-left: auto;" @click="addMember"
+            preIcon="ant-design:plus-outlined">添加成员</a-button>
+        </template>
+        <!--操作栏-->
+        <template #action="{ record }">
+          <TableAction :actions="getTableAction(record)" />
+        </template>
+      </BasicTable>
+    </div>
+    <a-modal v-model:open="addMemberShow" :title="addMemberShowTitle" @ok="submit" width="40%"
+      :bodyStyle="{ padding: '16px 24px' }" @cancel="resetForm">
+      <a-form ref="memberFormRef" :model="memberForm" :labelCol="labelCol" layout="vertical" :rules="memberRules">
+        <a-row :gutter="24">
+          <a-col :span="12">
+            <a-form-item label="姓名" prop="name">
+              <a-input v-model:value="memberForm.name" placeholder="请输入姓名" showCount maxlength="20" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="手机号" prop="phone">
+              <a-input v-model:value="memberForm.phone" :disabled="memberForm.id" placeholder="请输入手机号" showCount
+                maxlength="11" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="登录密码" :prop="memberForm.id ? '' : 'password'">
+              <a-input v-model:value="memberForm.password" :disabled="memberForm.id" placeholder="请输入登录密码" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="所属机构" prop="institutionId">
+              <a-tree-select v-model:value="memberForm.institutionId" show-search style="width: 100%"
+                :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }" placeholder="请选择所属机构" allow-clear
+                tree-default-expand-all :tree-data="InstitutionList" :field-names="{
+                  children: 'children',
+                  label: 'institutionName',
+                  value: 'id',
+                }" tree-node-filter-prop="institutionName">
+              </a-tree-select>
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row>
+          <a-col :span="12">
+            <a-form-item label="角色" prop="roles">
+              <a-select v-model:value="memberForm.roles" mode="multiple" placeholder="请选择角色">
+                <a-select-option v-for="item in roleList" :key="item.id" :value="item.id">{{ item.roleName
+                  }}</a-select-option>
+              </a-select>
+            </a-form-item>
+          </a-col>
+          <a-col :span="24">
+            <a-form-item label="用户头像">
+              <div class="clearfix">
+                <a-upload v-model:file-list="fileList" :max-count="1" :action="UploadUrl" @change="fileChange"
+                  list-type="picture-card" @preview="handlePreview" accept=".jpg,.jpeg,.png">
+                  <div v-if="fileList.length < 1">
+                    <PlusOutlined />
+                    <div style="margin-top: 8px">Upload</div>
+                  </div>
+                </a-upload>
+              </div>
+            </a-form-item>
+          </a-col>
+        </a-row>
+      </a-form>
+    </a-modal>
+    <!-- 添加机构弹窗 -->
+    <a-modal v-model:open="addOrgShow" title="添加机构" @ok="onOrgSubmit" width="600px" :bodyStyle="{ padding: '16px' }"
+      @cancel="afterClose">
+      <a-form ref="orgFormRef" :model="orgForm" :rules="orgrules" :label-col="labelCol" :wrapper-col="wrapperCol">
+        <a-form-item label="机构名称" name="institutionName">
+          <a-input v-model:value="orgForm.institutionName" allowClear placeholder="输入50个字以内的机构名称" />
+        </a-form-item>
+        <a-form-item label="上级机构" name="superiorId">
+          <a-tree-select v-model:value="orgForm.superiorId" show-search style="width: 100%"
+            :disabled="orgForm.superiorId == '0' ? true : false"
+            :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }" placeholder="请选择上级机构" allow-clear
+            tree-default-expand-all :tree-data="orgInstitutionList" :field-names="{
+              children: 'children',
+              label: 'institutionName',
+              value: 'id',
+            }" tree-node-filter-prop="institutionName">
+          </a-tree-select>
+        </a-form-item>
+        <a-form-item label="地区" name="provinceCode">
+          <a-cascader v-model:value="queryTreeList" change-on-select
+            :field-names="{ label: 'areaCname', value: 'areaCode', children: 'children' }" :options="queryTree"
+            placeholder="选择省市区" @change="selectedType" />
+        </a-form-item>
+        <a-form-item label="地址" name="addr">
+          <a-input v-model:value="orgForm.addr" allowClear placeholder="输入地址" />
+        </a-form-item>
+      </a-form>
+    </a-modal>
+    <!-- 预览视图 -->
+    <a-modal :open="previewVisible" :title="previewTitle" :footer="null" @cancel="handleCancel">
+      <img alt="example" style="width: 100%" :src="previewImage" />
+    </a-modal>
+  </div>
+</template>
+<!-- 行为区域 -->
+<script lang="ts" name="basic-table-demo" setup>
+import { ActionItem, BasicColumn, BasicTable, TableAction } from '/@/components/Table';
+import { useListPage } from '/@/hooks/system/useListPage';
+import type { TreeProps } from 'ant-design-vue';
+import { reactive, ref, onMounted } from 'vue';
+import * as memberAPI from '/@/api/organization/member';
+import * as roleAPI from '/@/api/organization/role';
+import * as institutionAPI from '/@/api/organization/institution';
+
+import { message, Modal } from 'ant-design-vue';
+import { useRouter, useRoute } from 'vue-router';
+import { EllipsisOutlined, PlusOutlined } from '@ant-design/icons-vue';
+import type { UnwrapRef } from 'vue';
+import { useGlobSetting } from '/@/hooks/setting';
+import type { UploadProps } from 'ant-design-vue';
+import type { Rule } from 'ant-design-vue/es/form';
+onMounted(() => {
+  getInstitutionList();//获取机构列表
+  getRoleList();//获取角色列表
+  regionLinkage();//获取省市区数据
+});
+//表单配置
+const router = useRouter();
+
+const addMemberShowTitle = ref('添加成员');//添加成员弹窗标题
+const searchValue = ref('');//搜索值
+const memberFormRef = ref();//表单实例
+const isExpanded = ref(false);//是否展开所有节点
+const addMemberShow = ref(false);//添加成员弹窗是否显示
+const organizationName = ref('');//机构名称
+const InstitutionList = ref([]);//机构列表
+const roleList = ref();//角色列表
+const institutionId = ref('');//机构id
+const number = ref('');//机构人数
+const labelCol = { style: { width: '80px' } };
+const wrapperCol = { span: 20 };
+const fileList = ref([]);//文件显示列表
+const previewVisible = ref(false);//图片预览弹窗
+const previewImage = ref('');//图片路径
+const previewTitle = ref('');
+const globSetting = useGlobSetting();
+const baseUploadUrl = globSetting.uploadUrl;
+const UploadUrl = `${baseUploadUrl}/sys/common/upload`; // 上传地址
+interface FormState {
+  [key: string]: any;
+}
+const memberForm: UnwrapRef<FormState> = reactive({
+  id: "",//id
+  name: '',//姓名
+  phone: '',//手机号
+  password: '',//登录密码
+  roles: [],//角色
+  institutionId: '',//机构id
+  avatar: '',//用户头像
+});
+const memberRules: Record<string, Rule[]> = {
+  name: [
+    { required: true, message: '请输入姓名', trigger: 'blur' },
+  ],
+  phone: [
+    { required: true, message: '请输入手机号', trigger: 'blur' },
+  ],
+  password: [
+    { required: true, message: '请输入密码', trigger: 'blur' },
+    {
+      pattern: /^[a-zA-Z0-9]+$/,
+      message: '密码只能包含字母和数字',
+    },
+    {
+      min: 8,
+      max: 20,
+      message: '密码长度8~20位字符',
+    },
+  ],
+  roles: [
+    { required: true, message: '请选择角色', trigger: 'change' },
+  ],
+  institutionId: [
+    { required: true, message: '所属机构', trigger: 'blur' },
+  ],
+};
+//添加成员
+const addMember = () => {
+  addMemberShowTitle.value = '添加成员';
+  if (!institutionId.value) {
+    message.error('请选择所属机构');
+    return;
+  }
+  addMemberShow.value = true;
+}
+// 文件上传
+const fileChange = async (info) => {
+  if (info.file.status === 'done') {
+    // 文件上传成功
+    const fileList = info.fileList.map((file) => {
+      if (file.response) {
+        // 拼接地址前缀
+        file.url = import.meta.env.VITE_GLOB_DOMAIN_URL + '/' + file.response.message;
+      }
+      return file.url;
+    });
+    memberForm.avatar = fileList[0]//图片地址逗号拼接
+  }
+}
+//图片预览事件
+const handlePreview = async (file: UploadProps['fileList'][number]) => {
+  if (!file.url && !file.preview) {
+    file.preview = (await getBase64(file.originFileObj)) as string;
+  }
+  previewImage.value = file.url || file.preview;
+  previewVisible.value = true;
+  previewTitle.value = file.name || file.url.substring(file.url.lastIndexOf('/') + 1);
+};
+function getBase64(file: File) {
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader();
+    reader.readAsDataURL(file);
+    reader.onload = () => resolve(reader.result);
+    reader.onerror = error => reject(error);
+  });
+}
+
+
+// 切换展开/收起所有节点
+const toggleExpand = () => {
+  // 切换状态
+  isExpanded.value = !isExpanded.value;
+  if (isExpanded.value) {
+    expandedKeys.value = getAllKeys(InstitutionList.value);
+  } else {
+    expandedKeys.value = [];
+  }
+};
+// 递归获取所有树节点的key
+const getAllKeys = (tree: TreeProps['treeData'], keys: string[] = []): string[] => {
+  if (!tree) return keys;
+  tree.forEach(node => {
+    if (node.id) {
+      keys.push(node.id as string);
+    }
+    if (node.children && node.children.length) {
+      getAllKeys(node.children, keys);
+    }
+  });
+  return keys;
+};
+const expandedKeys = ref<string[]>();
+//定义表格列字段
+const columns: BasicColumn[] = [
+  {
+    title: '姓名',
+    dataIndex: 'name',
+    key: 'name',
+    width: 200,
+  },
+  {
+    title: '手机号',
+    dataIndex: 'phone',
+    key: 'phone',
+    customRender: ({ record }) => {
+      if (record.phone) {
+        return record.phone;
+      } else {
+        return '-';
+      }
+    },
+  },
+  {
+    title: '所属机构',
+    dataIndex: 'institutionName',
+    key: 'institutionName',
+  },
+  {
+    title: '角色',
+    dataIndex: 'roleName',
+    key: 'roleName',
+    customRender: ({ record }) => {
+      if (record.roleName) {
+        return record.roleName;
+      } else {
+        return '-';
+      }
+    },
+  },
+];
+/** useListPage 是整个框架的核心用于表格渲染,里边封装了很多公共方法;
+ * 平台通过此封装,简化了代码,支持自定义扩展*/
+// 通过hook useListPage渲染表格(设置dataSource、columns、actionColumn等参数)
+const { tableContext } = useListPage({
+  designScope: 'basic-table-demo',
+  tableProps: {
+    dataSource: [],
+    columns: columns,
+    api: memberAPI.userPage,
+    beforeFetch: (params) => {
+      params.institutionId = institutionId.value;
+      return params;
+    },
+    size: 'small',
+    actionColumn: {
+      width: 200,
+      fixed: 'right'
+    },
+  },
+});
+// BasicTable绑定注册
+const [registerTable, { reload }] = tableContext;
+/**
+ * 操作栏
+ */
+function getTableAction(record: ActionItem): ActionItem[] {
+  return [
+    {
+      label: '编辑',
+      onClick: edit.bind(null, record),
+    },
+    {
+      label: '删除',
+      onClick: del.bind(null, record),
+    },
+  ];
+}
+// 树节点选择事件
+const handleTreeSelect = (selectedKeys: string[], info: any) => {
+  console.log('selectedKeys:', selectedKeys);
+  console.log('info:', info.node.dataRef);
+  institutionId.value = info.node.dataRef.id;
+  number.value = info.node.dataRef.number;
+  memberForm.institutionId = info.node.dataRef.id;
+  organizationName.value = info.node.dataRef.institutionName;
+  reload();
+
+};
+/**
+ * 编辑
+ */
+const edit = (record: ActionItem) => {
+  addMemberShowTitle.value = '编辑成员';
+  memberAPI.userQueryId(record.id).then((res) => {
+    if (res.code == 200) {
+      Object.assign(memberForm, { ...res.result, id: record.id });
+      if (res.result.avatar) {
+        let list = res.result.avatar?.split(',');
+        let data = list.map((val) => ({
+          url: val
+        }));
+        fileList.value = data;
+      }
+      addMemberShow.value = true;
+
+    } else {
+      message.error(res.message);
+    }
+  });
+}
+const del = (record: ActionItem) => {
+  Modal.confirm({
+    title: '确定要删除该成员吗?',
+    okText: '确认',
+    cancelText: '取消',
+    onOk() {
+      memberAPI.userDelete(record.id).then((res) => {
+        if (res.code == 200) {
+          message.success('删除成功');
+          reload();
+        } else {
+          message.error(res.message);
+        }
+      });
+    },
+    onCancel() {
+    },
+  });
+}
+const handleCancel = () => {
+  previewVisible.value = false;
+  previewTitle.value = '';
+  previewImage.value = "";
+};
+// 提交表单
+const submit = () => {
+
+  memberFormRef.value.validate().then(() => {
+    // 提交表单数据
+    if (memberForm.id) {
+      memberAPI.userUpdate(memberForm, memberForm.id).then((res) => {
+        if (res.code == 200) {
+          message.success('编辑成功');
+          addMemberShow.value = false;
+          reload();
+        } else {
+          message.error(res.message);
+        }
+      });
+    } else {
+      memberAPI.userAdd(memberForm).then((res) => {
+        if (res.code == 200) {
+          message.success('添加成功');
+          addMemberShow.value = false;
+          reload();
+        } else {
+          message.error(res.message);
+        }
+      });
+    }
+    // ...
+  }).catch(() => {
+    console.log('表单校验失败');
+  });
+}
+// 获取机构列表
+const getInstitutionList = () => {
+  memberAPI.institutionList().then((res) => {
+    if (res.code == 200) {
+      InstitutionList.value = res.result;
+    }
+  });
+}
+const getInstitutionAll = () => {
+  institutionAPI.institutionAll().then((res) => {
+    if (res.code == 200) {
+      orgInstitutionList.value = res.result;
+    }
+  });
+}
+
+// 获取角色列表
+const getRoleList = () => {
+  roleAPI.rolePage().then((res) => {
+    if (res.code == 200) {
+      console.log(res.result);
+      roleList.value = res.result;
+    }
+  });
+}
+// 跳转机构管理页面
+const pageJump = () => {
+  router.push({
+    path: "/organizationStructure/orgManagement",
+  });
+}
+// 重置表单
+const resetForm = () => {
+  memberFormRef.value.resetFields();
+  fileList.value = [];
+}
+
+// 机构树菜单
+const handleMenuClick = (e: any, item: any) => {
+  console.log(item)
+  switch (e.key) {
+    case '添加子机构':
+      orgresetForm();
+      getInstitutionAll();
+      orgForm.superiorId = item.id;
+      addOrgShow.value = true;
+      break;
+    case '编辑':
+      getInstitutionAll();
+      institutionAPI.institutionQueryId(item.id).then((res) => {
+        if (res.code == 200) {
+          Object.assign(orgForm, { ...res.result, id: item.id });
+          queryTreeList.value = [orgForm.provinceCode, orgForm.cityCode, orgForm.areaCode].filter(Boolean);
+          // 从机构列表中删除当前机构及其所有子机构
+          removeInstitutionAndChildren(item.id, orgInstitutionList.value);
+          addOrgShow.value = true;
+        }
+      });
+      break;
+    case '删除':
+      institutionAPI.institutionExists(item.id).then((res) => {
+        if (res.code == 200 && res.result) {
+          Modal.confirm({
+            title: '确定要删除该机构吗?',
+            okText: '确定删除',
+            cancelText: '取消',
+            onOk() {
+              institutionAPI.institutionDelete(item.id).then((res) => {
+                if (res.code == 200) {
+                  message.success('删除成功');
+                  getInstitutionList();
+                } else {
+                  message.error(res.message);
+                }
+              })
+            },
+            onCancel() {
+            },
+          });
+        } else {
+          Modal.error({
+            title: '无法删除',
+            content: '删除角色前,请先移除角色内的所有成员',
+          });
+        }
+      });
+      break;
+  }
+}
+// .机构管理代码  构造的 机构代码复写
+const addOrgShow = ref(false);//添加机构弹窗显示
+const orgFormRef = ref();//机构表单实例
+const orgInstitutionList = ref([]);//机构列表
+const orgForm: UnwrapRef<FormState> = reactive({
+  id: "",//机构id 
+  addr: "",//地址
+  areaCode: "",//地区编码
+  areaName: "",//地区名称
+  cityCode: "",//城市编码
+  cityName: "",//城市名称
+  institutionName: "",//机构名称
+  provinceCode: "",//省份编码
+  provinceName: "",//省份名称
+  superiorId: ""//上级机构
+});
+const orgrules: Record<string, Rule[]> = {
+  institutionName: [
+    { required: true, message: '机构名称', trigger: 'blur' },
+  ],
+  superiorId: [
+    { required: true, message: '请选择上级机构', trigger: 'change' },
+  ],
+  provinceCode: [
+    { required: true, message: '请选择地区', trigger: 'change' },
+  ],
+  addr: [
+    { required: true, message: '请输入地址', trigger: 'blur' },
+  ],
+};
+// 递归删除机构及其所有子机构
+const removeInstitutionAndChildren = (id: string, list: any[]): boolean => {
+  for (let i = 0; i < list.length; i++) {
+    const item = list[i];
+    if (item.id === id) {
+      // 找到目标机构,从当前列表中删除
+      list.splice(i, 1);
+      return true;
+    }
+    if (item.children && item.children.length > 0) {
+      // 在子机构中查找并删除
+      if (removeInstitutionAndChildren(id, item.children)) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+const onOrgSubmit = () => {
+  orgFormRef.value.validate().then(() => {
+    console.log(orgForm)
+    // 提交表单数据
+    if (orgForm.id) {
+      institutionAPI.institutionUpdate(orgForm, orgForm.id).then((res) => {
+        if (res.code == 200) {
+          message.success('修改成功');
+          addOrgShow.value = false;
+          getInstitutionList();
+        } else {
+          message.error(res.message);
+        }
+      })
+    } else {
+      institutionAPI.institutionAdd(orgForm).then((res) => {
+        if (res.code == 200) {
+          message.success('添加成功');
+          addOrgShow.value = false;
+          getInstitutionList();
+        } else {
+          message.error(res.message);
+        }
+      })
+    }
+    // ...
+  }).catch(() => {
+    console.log('表单校验失败');
+  });
+}
+const queryTree = ref([]);//省市区数据
+const queryTreeList = ref();//省市区选中数据
+
+const regionLinkage = () => {
+  institutionAPI.queryTree().then((res) => {
+    if (res.code == 200) {
+      queryTree.value = res.result
+    }
+  });
+}
+// 地区选择
+async function selectedType(value, selectedOptions) {
+  if (selectedOptions) {
+    orgForm.provinceCode = selectedOptions[0]?.areaCode;
+    orgForm.provinceName = selectedOptions[0]?.areaCname;
+    orgForm.cityCode = selectedOptions[1]?.areaCode;
+    orgForm.cityName = selectedOptions[1]?.areaCname;
+    orgForm.areaCode = selectedOptions[2]?.areaCode;
+    orgForm.areaName = selectedOptions[2]?.areaCname;
+  } else {
+    orgForm.provinceCode = "";
+    orgForm.provinceName = "";
+    orgForm.cityCode = "";
+    orgForm.cityName = "";
+    orgForm.areaCode = "";
+    orgForm.areaName = "";
+  }
+}
+const afterClose = () => {
+  orgresetForm();
+}
+const orgresetForm = () => {
+  // 清空表单数据
+  Object.assign(orgForm, {
+    id: "",
+    addr: "",
+    areaCode: "",
+    areaName: "",
+    cityCode: "",
+    cityName: "",
+    institutionName: "",
+    provinceCode: "",
+    provinceName: "",
+    superiorId: ""
+  });
+  console.log(orgForm)
+  queryTreeList.value = [];
+}
+</script>
+<!-- 样式区域 -->
+<style lang='less' scoped>
+::v-deep(.jeecg-basic-table .ant-table-wrapper) {
+  padding: 20px;
+  height: calc(100% - 10px);
+}
+
+::v-deep(.ant-col) {
+  width: auto;
+}
+
+.cursorstyle {
+  font-size: 14px;
+  color: #018FFB;
+  cursor: pointer;
+}
+
+.member-management {
+  box-sizing: border-box;
+  height: 100%;
+  height: calc(100vh - 140px);
+  margin: 10px;
+
+
+}
+
+.organization-tree {
+  flex: 1;
+  box-sizing: border-box;
+  border-radius: 4px;
+  overflow-y: auto;
+  padding: 24px;
+  background-color: #fff;
+  border-radius: 4px;
+  margin-right: 15px;
+}
+
+.distribution-record-table {
+  width: 80%;
+  background-color: #fff;
+  border-radius: 4px;
+}
+
+.userCount {
+  font-size: 18px;
+  color: #333;
+  font-weight: bold;
+}
+</style>

+ 376 - 0
src/views/organizationStructure/orgManagement.vue

@@ -0,0 +1,376 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <!--定义表格-->
+    <BasicTable @register="registerTable">
+      <!-- 表格顶部左侧操作 -->
+      <template #tableTitle>
+        <a-button preIcon="ant-design:plus-outlined" @click="addOrg">添加机构</a-button>
+      </template>
+      <!--操作栏-->
+      <template #action="{ record }">
+        <TableAction :actions="getTableAction(record)" :dropDownActions="dropDownAction(record)" />
+      </template>
+    </BasicTable>
+    <a-modal v-model:open="addOrgShow" title="添加机构" @ok="onSubmit" width="600px" :bodyStyle="{ padding: '16px' }"
+      @cancel="afterClose">
+      <a-form ref="formRef" :model="orgForm" :rules="rules" :label-col="labelCol" :wrapper-col="wrapperCol">
+        <a-form-item label="机构名称" name="institutionName">
+          <a-input v-model:value="orgForm.institutionName" allowClear placeholder="输入50个字以内的机构名称" />
+        </a-form-item>
+        <a-form-item label="上级机构" name="superiorId">
+          <a-tree-select v-model:value="orgForm.superiorId" show-search style="width: 100%" :disabled="orgForm.superiorId=='0'?true:false"
+            :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }" placeholder="请选择上级机构" allow-clear
+            tree-default-expand-all :tree-data="InstitutionList" :field-names="{
+              children: 'children',
+              label: 'institutionName',
+              value: 'id',
+            }" tree-node-filter-prop="institutionName">
+          </a-tree-select>
+        </a-form-item>
+        <a-form-item label="地区" name="provinceCode">
+          <a-cascader v-model:value="queryTreeList" change-on-select
+            :field-names="{ label: 'areaCname', value: 'areaCode', children: 'children' }" :options="queryTree"
+            placeholder="选择省市区" @change="selectedType" />
+        </a-form-item>
+        <a-form-item label="地址" name="addr">
+          <a-input v-model:value="orgForm.addr" allowClear placeholder="输入地址" />
+        </a-form-item>
+      </a-form>
+    </a-modal>
+  </div>
+</template>
+
+<!-- 行为区域 -->
+<script lang="ts" name="basic-table-demo" setup>
+import { ActionItem, BasicColumn, BasicTable, TableAction } from '/@/components/Table';
+import { useListPage } from '/@/hooks/system/useListPage';
+import { FormSchema } from '/@/components/Table';
+import { reactive, ref, onMounted } from 'vue';
+import * as institutionAPI from '/@/api/organization/institution';
+import { useRouter, useRoute } from 'vue-router';
+import type { Rule } from 'ant-design-vue/es/form';
+import { message, Modal } from 'ant-design-vue';
+import type { UnwrapRef } from 'vue';
+//表单配置
+const router = useRouter();
+const addOrgShow = ref(false);//添加机构弹窗显示
+const formRef = ref();
+const labelCol = { style: { width: '80px' } };
+const wrapperCol = { span: 20 };
+const queryTree = ref([]);//省市区数据
+const queryTreeList = ref();//省市区选中数据
+const InstitutionList = ref([]);//机构列表
+
+onMounted(() => {
+  regionLinkage();
+  getInstitutionList();
+});
+interface FormState {
+  [key: string]: string;
+}
+// 机构表单
+const orgForm:UnwrapRef<FormState> = reactive({
+  id: "",//机构id 
+  addr: "",//地址
+  areaCode: "",//地区编码
+  areaName: "",//地区名称
+  cityCode: "",//城市编码
+  cityName: "",//城市名称
+  institutionName: "",//机构名称
+  provinceCode: "",//省份编码
+  provinceName: "",//省份名称
+  superiorId: ""//上级机构
+});
+
+const rules: Record<string, Rule[]> = {
+  institutionName: [
+    { required: true, message: '机构名称', trigger: 'blur' },
+  ],
+  superiorId: [
+    { required: true, message: '请选择上级机构', trigger: 'change' },
+  ],
+  provinceCode: [
+    { required: true, message: '请选择地区', trigger: 'change' },
+  ],
+  addr: [
+    { required: true, message: '请输入地址', trigger: 'blur' },
+  ],
+};
+
+// 定义表格列字段
+const columns: BasicColumn[] = [
+  {
+    title: '机构名称',
+    dataIndex: 'institutionName',
+    key: 'institutionName',
+    width: 200,
+  },
+  {
+    title: '人数',
+    dataIndex: 'number',
+    key: 'number',
+    ellipsis: true
+  },
+  {
+    title: '所属地区',
+    dataIndex: 'addr',
+    key: 'addr',
+  }
+];
+
+const searchFormSchema: FormSchema[] = [
+  {
+    label: '机构名称',
+    field: 'institutionName',
+    component: 'Input',
+    colProps: {
+      span: 7,
+    },
+  },
+];
+
+/** useListPage 是整个框架的核心用于表格渲染,里边封装了很多公共方法;
+ * 平台通过此封装,简化了代码,支持自定义扩展*/
+// 通过hook useListPage渲染表格(设置dataSource、columns、actionColumn等参数)
+const { tableContext } = useListPage({
+  designScope: 'basic-table-demo',
+  tableProps: {
+    title: '用户列表',
+    isTreeTable: true,
+    dataSource: [],
+    columns: columns,
+    formConfig: {
+      schemas: searchFormSchema,
+      showAdvancedButton: false,
+    },
+    api: institutionAPI.institutionList,
+    size: 'small',
+    actionColumn: {
+      width: 200,
+      fixed: 'right'
+    },
+  },
+});
+
+// BasicTable绑定注册
+const [registerTable, { getForm, reload }] = tableContext;
+
+/**
+ * 操作栏
+ */
+function getTableAction(record: ActionItem): ActionItem[] {
+  return [
+    {
+      label: '添加子机构',
+      onClick: addSubOrg.bind(null, record),
+    },
+  ];
+}
+
+/**
+ * 下拉操作栏
+ * @param record 行数据
+ */
+function dropDownAction(record: ActionItem): ActionItem[] {
+  return [
+    {
+      label: '编辑',
+      onClick: memberEdit.bind(null, record),
+    },
+    {
+      label: '删除',
+      onClick: memberDel.bind(null, record),
+    },
+  ];
+}
+
+// 地区选择
+async function selectedType(value, selectedOptions) {
+  if (selectedOptions) {
+    orgForm.provinceCode = selectedOptions[0]?.areaCode;
+    orgForm.provinceName = selectedOptions[0]?.areaCname;
+    orgForm.cityCode = selectedOptions[1]?.areaCode;
+    orgForm.cityName = selectedOptions[1]?.areaCname;
+    orgForm.areaCode = selectedOptions[2]?.areaCode;
+    orgForm.areaName = selectedOptions[2]?.areaCname;
+  } else {
+    orgForm.provinceCode = "";
+    orgForm.provinceName = "";
+    orgForm.cityCode = "";
+    orgForm.cityName = "";
+    orgForm.areaCode = "";
+    orgForm.areaName = "";
+  }
+}
+
+// 获取省市区数据
+const regionLinkage = () => {
+  institutionAPI.queryTree().then((res) => {
+    if (res.code == 200) {
+      queryTree.value = res.result
+    }
+  });
+}
+
+// 获取机构列表
+const getInstitutionList = () => {
+  institutionAPI.institutionAll().then((res) => {
+    if (res.code == 200) {
+      InstitutionList.value = res.result;
+    }
+  });
+}
+
+// 添加机构
+const addOrg = () => {
+  // 重置表单
+  resetForm();
+  // 重新获取最新的机构列表(包括所有机构)
+  getInstitutionList();
+  addOrgShow.value = true;
+}
+
+const addSubOrg = (record: ActionItem) => {
+  // 重置表单
+  resetForm();
+  getInstitutionList();
+  institutionAPI.institutionQueryId(record.id).then((res) => {
+    if (res.code == 200) {
+      orgForm.superiorId = record.id;
+    }
+  });
+  addOrgShow.value = true;
+}
+
+// 递归删除机构及其所有子机构
+const removeInstitutionAndChildren = (id: string, list: any[]): boolean => {
+  for (let i = 0; i < list.length; i++) {
+    const item = list[i];
+    if (item.id === id) {
+      // 找到目标机构,从当前列表中删除
+      list.splice(i, 1);
+      return true;
+    }
+    if (item.children && item.children.length > 0) {
+      // 在子机构中查找并删除
+      if (removeInstitutionAndChildren(id, item.children)) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+// 编辑机构
+const memberEdit = (record: ActionItem) => {
+getInstitutionList();
+  institutionAPI.institutionQueryId(record.id).then((res) => {
+    if (res.code == 200) {
+      Object.assign(orgForm, { ...res.result, id: record.id });
+      queryTreeList.value = [orgForm.provinceCode, orgForm.cityCode, orgForm.areaCode].filter(Boolean);
+      
+      // 从机构列表中删除当前机构及其所有子机构
+      removeInstitutionAndChildren(record.id, InstitutionList.value);
+      
+      addOrgShow.value = true;
+    }
+  });
+}
+
+// 删除机构
+const memberDel = (record: ActionItem) => {
+  institutionAPI.institutionExists(record.id).then((res) => {
+    if (res.code == 200 && res.result) {
+      Modal.confirm({
+        title: '确定要删除该机构吗?',
+        okText: '确定删除',
+        cancelText: '取消',
+        onOk() {
+          institutionAPI.institutionDelete(record.id).then((res) => {
+            if (res.code == 200) {
+              message.success('删除成功');
+              reload();
+            } else {
+              message.error(res.message);
+            }
+          })
+        },
+        onCancel() {
+        },
+      });
+    } else {
+      Modal.error({
+        title: '无法删除',
+        content: '删除角色前,请先移除角色内的所有成员',
+      });
+    }
+  });
+}
+
+// 重置表单
+const resetForm = () => {
+  // 清空表单数据
+  Object.assign(orgForm, {
+    id: "",
+    addr: "",
+    areaCode: "",
+    areaName: "",
+    cityCode: "",
+    cityName: "",
+    institutionName: "",
+    provinceCode: "",
+    provinceName: "",
+    superiorId: ""
+  });
+  console.log(orgForm)
+  queryTreeList.value = [];
+}
+
+// 新增机构弹窗关闭后,清空表单数据
+const afterClose = () => {
+  resetForm();
+}
+const onSubmit = () => {
+  formRef.value.validate().then(() => {
+    console.log('表单校验通过');
+    // 提交表单数据
+    if (orgForm.id) {
+      institutionAPI.institutionUpdate(orgForm, orgForm.id).then((res) => {
+        if (res.code == 200) {
+          message.success('修改成功');
+          addOrgShow.value = false;
+          reload();
+        } else {
+          message.error(res.message);
+        }
+      })
+    } else {
+      institutionAPI.institutionAdd(orgForm).then((res) => {
+        if (res.code == 200) {
+          message.success('添加成功');
+          addOrgShow.value = false;
+          reload();
+        } else {
+          message.error(res.message);
+        }
+      })
+    }
+    // ...
+  }).catch(() => {
+    console.log('表单校验失败');
+  });
+}
+</script>
+
+<!-- 样式区域 -->
+<style lang='less' scoped>
+::v-deep(.jeecg-basic-table .ant-table-wrapper) {
+  padding: 20px;
+  height: calc(100% - 10px);
+}
+
+::v-deep(.ant-col) {
+  width: auto;
+}
+</style>

+ 491 - 0
src/views/organizationStructure/roleManagement.vue

@@ -0,0 +1,491 @@
+<!-- 模板区域 -->
+<template>
+  <div class="member-management dis ">
+    <!-- 角色列表  -->
+    <div class="organization-role  dis f-c a-c">
+      <a-input-search placeholder="角色名称查找" enter-button />
+      <div class="m-top-20 m-btn-10 roleList">
+        <div class="roleItem dis f-c a-start" v-for="item in roleList"
+          :class="{ 'roleItem-selected': selectedRole === item.id }" @click="selectRole(item.id)">
+          <span class="title">{{ item.roleName }}</span>
+          <span class="content">{{ item.description }}</span>
+        </div>
+      </div>
+      <a-button type="primary" preIcon="ant-design:plus-outlined" @click="roleAdd">添加角色</a-button>
+    </div>
+    <div class="roleDetail">
+      <div class="header dis">
+        <span>角色管理</span>
+        <a-button type="primary" @click="roleSubmit" style="margin-right:10px ;margin-left: auto;">{{ roleForm.id ? '编辑'
+          : '保存' }}</a-button>
+        <a-button v-if="roleForm.id" @click="roleDelete">删除</a-button>
+      </div>
+      <!-- 角色基本信息表单 -->
+      <div class="form">
+        <span class="form-title ">基本信息</span>
+        <a-form ref="formRef" class="m-top-20 " style="width: 50%;" :model="roleForm" :rules="roleRules"
+          :label-col="labelCol">
+          <a-form-item label="角色名称" name="roleName">
+            <a-input v-model:value="roleForm.roleName" allowClear placeholder="输入20个字以内的角色名称" />
+          </a-form-item>
+          <a-form-item label="所属机构" name="institutionId">
+            <a-tree-select v-model:value="roleForm.institutionId" show-search style="width: 100%"
+              :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }" placeholder="请选择上级机构" allow-clear
+              tree-default-expand-all :tree-data="InstitutionList" :field-names="{
+                children: 'children',
+                label: 'institutionName',
+                value: 'id',
+              }" tree-node-filter-prop="institutionName">
+            </a-tree-select>
+          </a-form-item>
+          <a-form-item label="角色描述" name="description">
+            <a-textarea v-model:value="roleForm.description" show-count :maxlength="500" placeholder="请输入内容" />
+          </a-form-item>
+        </a-form>
+      </div>
+      <!-- 角色权限 -->
+      <div class="rolePermission m-top-20 ">
+        <div class="Permission-title ">
+          <span>角色权限</span>
+          <span class="permission-tip">(该角色享有的后台模块功能权限剩余数据权限)</span>
+        </div>
+        <a-tabs v-model:activeKey="activeKey" tabPosition="left" class="m-top-20">
+          <a-tab-pane key="1" tab="菜单操作权限" class="draggable-tree">
+            <a-tree :blockNode="true" checkable :autoExpandParent="true" v-model:checkedKeys="menuSelectedKeys"
+              :tree-data="menuTreeData" :field-names="{
+                children: 'children',
+                title: 'name',
+                key: 'id',
+              }" @check="menuTreeCheck"></a-tree>
+          </a-tab-pane>
+          <a-tab-pane key="2" tab="组织架构权限" class="draggable-tree">
+            <a-radio-group v-model:value="roleForm.dataMark" name="radioGroup">
+              <a-radio :value="1">可见本机构下的数据</a-radio>
+              <a-radio :value="2">可见本机构及下级机构数据</a-radio>
+              <a-radio :value="3">指定机构</a-radio>
+            </a-radio-group>
+            <a-tree :blockNode="true" checkable :checkStrictly="true" :autoExpandParent="true"
+              v-model:checkedKeys="orgSelectedKeys" :tree-data="processedInstitutionList" :field-names="{
+                children: 'children',
+                title: 'institutionName',
+                key: 'id',
+              }" @check="orgTreeCheck"></a-tree>
+          </a-tab-pane>
+        </a-tabs>
+      </div>
+    </div>
+
+  </div>
+</template>
+<!-- 行为区域 -->
+<script lang="ts" name="basic-table-demo" setup>
+import { reactive, ref, onMounted, computed, watch } from 'vue';
+import * as roleAPI from '/@/api/organization/role';
+import * as institutionAPI from '/@/api/organization/institution';
+import { message, Modal } from 'ant-design-vue';
+import { useRouter, useRoute } from 'vue-router';
+import type { UnwrapRef } from 'vue';
+import type { Rule } from 'ant-design-vue/es/form';
+import { useUserStore } from '/@/store/modules/user';
+
+onMounted(async () => {
+  await getInstitutionList();//获取机构列表
+  await getMenuPermission();//获取菜单权限
+  await getRoleList();//获取角色列表
+  // 初始化机构树选中状态
+  orgSelectedKeys.value = treeDefaultSelectedKeys.value;
+  roleForm.institutionIdList = treeDefaultSelectedKeys.value;
+})
+
+//表单配置
+const userStore = useUserStore();
+const router = useRouter();
+const formRef = ref();//表单实例
+const labelCol = { style: { width: '80px' } };
+const activeKey = ref('1');
+const orgvalue = ref('1');
+const menuSelectedKeys = ref<string[]>([]);
+const orgSelectedKeys = ref<string[]>();
+const selectedRole = ref(); // 新增:选中的角色
+const InstitutionList = ref([]); //机构权限列表
+const menuTreeData = ref([]);//菜单权限列表
+const roleList = ref<any>();//角色列表
+const institutionId = ref((userStore.getLoginInfo.userInfo as any).institutionId);//登录人机构id
+
+interface FormState {
+  [key: string]: any;
+}
+const roleForm: UnwrapRef<FormState> = reactive({
+  id: "",//角色id
+  dataMark: 1,//  	组织机构权限 1 可见本机构下的数据 2 可见本机构及下级数据 3 指定机构
+  description: "",//角色描述
+  institutionId: "",//所属机构id
+  institutionIdList: [],//指定机构id列表
+  permissionIdList: [],//菜单权限id列表
+  roleName: ""//角色名称
+});
+const roleRules: Record<string, Rule[]> = {
+  roleName: [
+    { required: true, message: '请输入姓名', trigger: 'blur' },
+  ],
+  institutionId: [
+    { required: true, message: '请选择所属机构', trigger: 'change' },
+  ],
+
+};
+
+// 添加角色
+const roleAdd = () => {
+  // 重置表单
+  resetForm();
+  roleForm.institutionId = institutionId.value;
+  // 检查是否已存在新增角色条目
+  const hasNewRole = roleList.value.some(item => !item.id || item.roleName === "新增角色");
+  // 如果不存在,则添加
+  if (!hasNewRole) {
+    roleList.value.unshift({
+      roleName: "新增角色",
+      description: "-----"
+    });
+  }
+
+  // 自动选中新增角色
+  selectedRole.value = "";
+};
+// 角色提交
+const roleSubmit = () => {
+  // 验证表单
+  formRef.value.validate().then(() => {
+    // 提交表单数据
+    if (roleForm.id) {
+      roleAPI.roleUpdate(roleForm, roleForm.id).then((res) => {
+        if (res.code == 200) {
+          message.success("修改成功");
+          // 刷新角色列表
+          getRoleList();
+          // 清空新增角色条目
+          roleList.value = roleList.value.filter(item => item.id);
+          // 重置表单
+          resetForm();
+        }
+      });
+    } else {
+      roleAPI.roleAdd(roleForm).then((res) => {
+        if (res.code == 200) {
+          message.success("添加成功");
+          // 刷新角色列表
+          getRoleList();
+          // 清空新增角色条目
+          roleList.value = roleList.value.filter(item => item.id);
+          // 重置表单
+          resetForm();
+        }
+      });
+    }
+  });
+}
+// 删除角色
+const roleDelete = () => {
+  roleAPI.roleExists(roleForm.id).then((res) => {
+    if (res.code == 200 && res.success) {
+      Modal.confirm({
+        title: '删除角色',
+        content: '确定要删除该角色吗?',
+        okText: '确认删除',
+        cancelText: '取消',
+        onOk() {
+          roleAPI.roleDelete(roleForm.id).then((res) => {
+            if (res.code == 200) {
+              message.success("删除成功");
+              // 刷新角色列表
+              getRoleList();
+              resetForm();
+            }
+          });
+        },
+      });
+
+    } else {
+      Modal.error({
+        title: '无法删除',
+        content: '删除角色前,请先移除角色内的所有成员',
+      });
+    }
+  });
+
+}
+// 菜单树节点选择事件
+const menuTreeCheck = (selectedKeys: string[], info: any) => {
+  roleForm.permissionIdList = selectedKeys;
+};
+const orgTreeCheck = (selectedKeys: string[], info: any) => {
+  roleForm.institutionIdList = selectedKeys.checked;
+};
+// 新增:选择角色
+const selectRole = (index) => {
+  selectedRole.value = index;
+  roleAPI.roleQueryId(index).then((res) => {
+    if (res.code == 200) {
+      Object.assign(roleForm, { ...res.result, id: index });
+      menuSelectedKeys.value = roleForm.permissionIdList;
+      orgSelectedKeys.value = roleForm.institutionIdList;
+    } else {
+      message.error(res.message);
+    }
+  });
+};
+// 获取机构列表
+const getInstitutionList = () => {
+  institutionAPI.institutionAll().then((res) => {
+    if (res.code == 200) {
+      InstitutionList.value = res.result;
+    }
+  });
+}
+// 获取菜单权限
+const getMenuPermission = () => {
+  roleAPI.getRoleMenu().then((res) => {
+    if (res.code == 200) {
+      menuTreeData.value = res.result;
+    }
+  });
+}
+// 获取角色列表
+const getRoleList = () => {
+  roleAPI.rolePage().then((res) => {
+    if (res.code == 200) {
+      roleList.value = res.result;
+    }
+  });
+}
+const resetForm = () => {
+  Object.assign(roleForm, {
+    id: "",
+    dataMark: 1,
+    description: "",
+    institutionId: "",
+    institutionIdList: [],
+    permissionIdList: [],
+    roleName: ""
+  });
+  menuSelectedKeys.value = [];
+  orgSelectedKeys.value = [];
+  orgSelectedKeys.value = treeDefaultSelectedKeys.value;
+
+}
+// 查找当前用户所在机构
+function findUserInstitution(tree: any[], userId: string | number): any | null {
+  for (const node of tree) {
+    if (node.id === userId) {
+      return node;
+    }
+    if (node.children && node.children.length > 0) {
+      const found = findUserInstitution(node.children, userId);
+      if (found) return found;
+    }
+  }
+  return null;
+}
+
+// 新增:递归获取机构的所有下级ID
+function getAllChildrenKeys(node: any, result: string[] = []): string[] {
+  if (node.children && node.children.length > 0) {
+    for (const child of node.children) {
+      result.push(child.id);
+      getAllChildrenKeys(child, result);
+    }
+  }
+  return result;
+}
+
+// 新增:计算属性 - 机构树默认选中的ID列表
+const treeDefaultSelectedKeys = computed(() => {
+  // 确保InstitutionList和institutionId都有值
+  if (!InstitutionList.value || InstitutionList.value.length === 0 || !institutionId.value) {
+    return [institutionId.value]; // 至少返回当前机构ID
+  }
+
+  const currentInstitution = findUserInstitution(InstitutionList.value, institutionId.value);
+  // 如果找不到当前机构,直接返回当前机构ID
+  if (!currentInstitution) return [institutionId.value];
+  switch (roleForm.dataMark) {
+    case 1: // 可见本机构
+      return [institutionId.value];
+    case 2: // 可见本机构及下级
+      return [institutionId.value, ...getAllChildrenKeys(currentInstitution)];
+    case 3: // 指定机构
+      return orgSelectedKeys.value;
+    default:
+      return [institutionId.value]; // 默认返回当前机构ID
+  }
+});
+// 新增:计算属性 - 处理后的机构树数据(添加disabled字段)
+const processedInstitutionList = computed(() => {
+  if (roleForm.dataMark === 3) return InstitutionList.value; // 指定机构模式下不禁用任何节点
+
+  const currentInstitution = findUserInstitution(InstitutionList.value, institutionId.value);
+  if (!currentInstitution) return InstitutionList.value;
+
+  // 获取允许选中的机构ID
+  const allowedKeys = treeDefaultSelectedKeys.value;
+
+  // 递归处理机构树,添加disabled字段
+  const processTree = (tree: any[]) => {
+    return tree.map(node => {
+      const newNode = { ...node };
+      // 设置节点的disabled状态
+      newNode.disabled = !allowedKeys.includes(node.id);
+
+      // 递归处理子节点
+      if (node.children && node.children.length > 0) {
+        newNode.children = processTree(node.children);
+      }
+
+      return newNode;
+    });
+  };
+
+  return processTree(InstitutionList.value);
+});
+
+//监听dataMark变化,更新选中的机构列表
+watch(
+  () => roleForm.dataMark,
+  (newValue) => {
+    console.log(treeDefaultSelectedKeys.value)
+    // 更新选中的机构
+    orgSelectedKeys.value = treeDefaultSelectedKeys.value;//更新选中的机构
+    roleForm.institutionIdList = treeDefaultSelectedKeys.value;//更新表单机构id列表
+  }
+);
+
+
+
+</script>
+<!-- 样式区域 -->
+<style lang='less' scoped>
+::v-deep(.jeecg-basic-table .ant-table-wrapper) {
+  padding: 20px;
+  height: calc(100% - 10px);
+}
+
+::v-deep(.ant-col) {
+  width: auto;
+}
+
+.cursorstyle {
+  font-size: 14px;
+  color: #018FFB;
+  cursor: pointer;
+}
+
+.member-management {
+  box-sizing: border-box;
+  height: 100%;
+  height: calc(100vh - 140px);
+  margin: 10px;
+
+
+}
+
+.organization-role {
+  flex: 1;
+  box-sizing: border-box;
+  border-radius: 4px;
+  padding: 24px;
+  background-color: #fff;
+  border-radius: 4px;
+  margin-right: 15px;
+
+  .roleList {
+    width: 100%;
+    overflow-y: auto;
+    height: calc(100% - 100px);
+  }
+
+  .roleItem {
+    padding: 10px;
+    cursor: pointer;
+    margin-bottom: 10px;
+    user-select: none;
+
+    .title {
+      font-size: 16px;
+      color: #333;
+      font-weight: bold;
+    }
+
+    .content {
+      margin-top: 8px;
+      font-size: 14px;
+      color: #666;
+    }
+
+    &:hover {
+      background-color: #f5f5f5;
+      border-radius: 4px;
+    }
+
+    // 新增:选中状态样式
+    &.roleItem-selected {
+      background-color: #f0faff;
+      border-radius: 4px;
+
+    }
+  }
+
+}
+
+.roleDetail {
+  width: 80%;
+  background-color: #fff;
+  border-radius: 4px;
+  padding: 24px;
+
+  .header {
+    width: 100%;
+    font-size: 20px;
+    color: #333;
+    font-weight: bold;
+    padding-bottom: 20px;
+    border-bottom: 1px solid #eee;
+  }
+
+  .form {
+    .form-title {
+      font-size: 18px;
+      color: #333;
+      font-weight: bold;
+    }
+
+    padding-top: 20px;
+    border-bottom: 1px solid #eee;
+  }
+
+  .rolePermission {
+    .Permission-title {
+      font-size: 18px;
+      color: #333;
+      font-weight: bold;
+
+      .permission-tip {
+        font-size: 14px;
+        color: #666;
+        font-weight: 400;
+      }
+    }
+
+  }
+}
+
+.userCount {
+  font-size: 18px;
+  color: #333;
+  font-weight: bold;
+}
+
+.draggable-tree {
+  height: 320px;
+  overflow-y: auto;
+}
+</style>

+ 5 - 4
src/views/system/loginmini/MiniLogin.vue

@@ -39,7 +39,7 @@
                       <div class="aui-inputClear">
                         <i class="icon icon-code"></i>
                         <a-form-item>
-                          <a-input class="fix-auto-fill" :placeholder="t('sys.login.userName')" v-model:value="formData.username" />
+                          <a-input class="fix-auto-fill" :placeholder="t('sys.login.userName')" v-model:value="formData.account" />
                         </a-form-item>
                       </div>
                       <div class="aui-inputClear">
@@ -199,7 +199,7 @@
   //账号登录表单字段
   const formData = reactive<any>({
     inputCode: '',
-    username: '',
+    account: '',
     password: '',
   });
   //手机登录表单字段
@@ -255,6 +255,7 @@
    * 账号或者手机登录
    */
   async function loginHandleClick() {
+    console.log(1)
     if (unref(activeIndex) === 'accountLogin') {
       accountLogin();
     } else {
@@ -264,7 +265,7 @@
   }
 
   async function accountLogin() {
-    if (!formData.username) {
+    if (!formData.account) {
       createMessage.warn(t('sys.login.accountPlaceholder'));
       return;
     }
@@ -277,7 +278,7 @@
       const { userInfo } = await userStore.login(
         toRaw({
           password: formData.password,
-          username: formData.username,
+          account: formData.account,
           captcha: formData.inputCode,
           checkKey: randCodeData.checkKey,
           mode: 'none', //不要默认的错误提示