Commit b289da6c45ccaa284fe8c2645e1d8d6099a1ee7b

Authored by xp.Huang
2 parents d88d2390 5f11983c

Merge branch 'feat/account-role-manage' into 'main_dev'

# Conflicts:
#   src/views/report/config/components/SelectDevice.vue
Showing 61 changed files with 4310 additions and 105 deletions
@@ -8,10 +8,10 @@ VITE_GLOB_PUBLIC_PATH = / @@ -8,10 +8,10 @@ VITE_GLOB_PUBLIC_PATH = /
8 # Please note that no line breaks 8 # Please note that no line breaks
9 9
10 # 本地 10 # 本地
11 -VITE_PROXY = [["/api","http://localhost:8080/api"],["/thingskit-scada","http://localhost:5173/thingskit-scada"],["/large-designer", "http://localhost:5555/large-designer/"]] 11 +VITE_PROXY = [["/api","http://222.180.200.114:30480/api"],["/thingskit-scada","http://localhost:5173/thingskit-scada"],["/large-designer", "http://localhost:5555/large-designer/"]]
12 12
13 # 实时数据的ws地址 13 # 实时数据的ws地址
14 -VITE_GLOB_WEB_SOCKET = ws://localhost:8080/api/ws/plugins/telemetry?token= 14 +VITE_GLOB_WEB_SOCKET = ws://222.180.200.114:30480/api/ws/plugins/telemetry?token=
15 15
16 # Delete console 16 # Delete console
17 VITE_GLOB_DROP_CONSOLE = true 17 VITE_GLOB_DROP_CONSOLE = true
@@ -44,7 +44,7 @@ export interface DeviceList { @@ -44,7 +44,7 @@ export interface DeviceList {
44 44
45 export type queryPageParams = BasicPageParams & { 45 export type queryPageParams = BasicPageParams & {
46 name?: Nullable<string>; 46 name?: Nullable<string>;
47 - organizationId?: Nullable<number>; 47 + organizationId?: string;
48 isTemplate?: number; 48 isTemplate?: number;
49 }; 49 };
50 50
@@ -57,6 +57,21 @@ enum DeviceManagerApi { @@ -57,6 +57,21 @@ enum DeviceManagerApi {
57 * @description 批量变更产品 57 * @description 批量变更产品
58 */ 58 */
59 BATCH_UPDATE_PRODUCT = '/device/batch/update', 59 BATCH_UPDATE_PRODUCT = '/device/batch/update',
  60 +
  61 + /**
  62 + * @description 批量公开设备
  63 + */
  64 + BATCH_PUBLIC_DEVICE = '/batch/customer/public/device/',
  65 +
  66 + /**
  67 + * @description 批量私有设备
  68 + */
  69 + BATCH_PRIVATE_DEVICE = '/batch/customer/device/',
  70 +
  71 + /**
  72 + * @description 批量修改设备
  73 + */
  74 + BATCH_UPDATE_DEVICE = '/device/batch/update',
60 } 75 }
61 76
62 export const devicePage = (params: DeviceQueryParam) => { 77 export const devicePage = (params: DeviceQueryParam) => {
@@ -398,3 +413,43 @@ export const doBatchUpdateProduct = (params: { deviceIds: string[]; deviceProfil @@ -398,3 +413,43 @@ export const doBatchUpdateProduct = (params: { deviceIds: string[]; deviceProfil
398 { joinPrefix: false } 413 { joinPrefix: false }
399 ); 414 );
400 }; 415 };
  416 +
  417 +/**
  418 + *
  419 + * @param deviceIds tbDeviceId分割的字符串
  420 + * @returns
  421 + */
  422 +export const doBatchPublicDevice = (deviceIds: string) => {
  423 + return defHttp.post(
  424 + {
  425 + url: `${DeviceManagerApi.BATCH_PUBLIC_DEVICE}?deviceIds=${deviceIds}`,
  426 + },
  427 + { joinPrefix: false }
  428 + );
  429 +};
  430 +
  431 +/**
  432 + *
  433 + * @param deviceIds tbDeviceId分割的字符串
  434 + * @returns
  435 + */
  436 +export const doBatchPrivateDevice = (deviceIds: string) => {
  437 + return defHttp.post(
  438 + {
  439 + url: `${DeviceManagerApi.BATCH_PRIVATE_DEVICE}?deviceIds=${deviceIds}`,
  440 + },
  441 + { joinPrefix: false }
  442 + );
  443 +};
  444 +
  445 +/**
  446 + *
  447 + * @param orgId
  448 + * @returns
  449 + */
  450 +export const doBatchUpdateDevice = (orgId: string, data: Recordable[]) => {
  451 + return defHttp.post({
  452 + url: `${DeviceManagerApi.BATCH_UPDATE_DEVICE}?orgId=${orgId}`,
  453 + data,
  454 + });
  455 +};
@@ -53,6 +53,7 @@ export interface DeviceModel { @@ -53,6 +53,7 @@ export interface DeviceModel {
53 customerId?: string; 53 customerId?: string;
54 alias?: string; 54 alias?: string;
55 tbDeviceId?: string; 55 tbDeviceId?: string;
  56 + customerName?: string;
56 } 57 }
57 58
58 export interface DeviceProfileModel { 59 export interface DeviceProfileModel {
@@ -207,6 +208,9 @@ export interface DeviceRecord { @@ -207,6 +208,9 @@ export interface DeviceRecord {
207 manufacturer: string; 208 manufacturer: string;
208 streamMode: string; 209 streamMode: string;
209 }; 210 };
  211 + customerName?: string;
  212 + customerId?: string;
  213 + gatewayId?: string;
210 } 214 }
211 215
212 export interface DeviceModelOfMatterAttrs { 216 export interface DeviceModelOfMatterAttrs {
  1 +import { AccountParams } from './model/systemModel';
  2 +import { defHttp } from '/@/utils/http/axios';
  3 +enum Api {
  4 + GET_TENANT_List = '/user/tenant/page',
  5 + ADD_TANANT_LIST = '/user/save_common_tenant',
  6 +}
  7 +
  8 +export const getTenantList = (params: AccountParams) => {
  9 + const url = Api.GET_TENANT_List;
  10 + return defHttp.get({ url: url, params });
  11 +};
  12 +export const addTenantList = (params: any) => {
  13 + const url = Api.ADD_TANANT_LIST;
  14 + return defHttp.post({ url: url, params });
  15 +};
@@ -94,12 +94,17 @@ export const getAllRoleList = async (params?: RoleParams) => { @@ -94,12 +94,17 @@ export const getAllRoleList = async (params?: RoleParams) => {
94 }; 94 };
95 95
96 // 过滤角色列表----根据登录的账号所属的角色-过滤掉其他平台类型的角色 96 // 过滤角色列表----根据登录的账号所属的角色-过滤掉其他平台类型的角色
97 -export const filterRoleList = async () => {  
98 - const res = await defHttp.post<RoleListGetResultModel>({ url: Api.GetAllRoleList }); 97 +export const filterRoleList = async (params?: { roleType: string }) => {
  98 + const res = await defHttp.post<RoleListGetResultModel>({
  99 + url: Api.GetAllRoleList,
  100 + params: params,
  101 + });
99 const userInfo: any = getAuthCache(USER_INFO_KEY); 102 const userInfo: any = getAuthCache(USER_INFO_KEY);
100 const role = userInfo.roles[0]; 103 const role = userInfo.roles[0];
101 const options = res.filter((item) => { 104 const options = res.filter((item) => {
102 - if (role === 'SYS_ADMIN' || role === 'PLATFORM_ADMIN') { 105 + if (params?.roleType && params.roleType === 'TENANT_ADMIN') {
  106 + return item;
  107 + } else if (role === 'SYS_ADMIN' || role === 'PLATFORM_ADMIN') {
103 return item.roleType === 'PLATFORM_ADMIN'; 108 return item.roleType === 'PLATFORM_ADMIN';
104 } else { 109 } else {
105 return item.roleType === 'CUSTOMER_USER'; 110 return item.roleType === 'CUSTOMER_USER';
@@ -178,7 +183,7 @@ export const resetPassword = (params: ChangeAccountParams) => @@ -178,7 +183,7 @@ export const resetPassword = (params: ChangeAccountParams) =>
178 * 清除密码 183 * 清除密码
179 * @param params 184 * @param params
180 */ 185 */
181 -export const clearUserPassword = (userId: string) => 186 +export const clearUserPassword = (userId: string, level: number) =>
182 defHttp.post({ 187 defHttp.post({
183 - url: Api.RESET_USER_PASSWORD + userId, 188 + url: Api.RESET_USER_PASSWORD + userId + '/' + level,
184 }); 189 });
@@ -15,6 +15,7 @@ export interface GetTaskListParamsType { @@ -15,6 +15,7 @@ export interface GetTaskListParamsType {
15 15
16 export interface CreateTaskRecordType { 16 export interface CreateTaskRecordType {
17 name: string; 17 name: string;
  18 + organizationId: string;
18 targetType: TaskTargetEnum; 19 targetType: TaskTargetEnum;
19 executeTarget: { 20 executeTarget: {
20 organizationId?: string; 21 organizationId?: string;
@@ -49,7 +49,7 @@ @@ -49,7 +49,7 @@
49 watch( 49 watch(
50 () => props.params, 50 () => props.params,
51 () => { 51 () => {
52 - isFirstLoaded.value && fetch(); 52 + fetch();
53 }, 53 },
54 { deep: true } 54 { deep: true }
55 ); 55 );
@@ -102,7 +102,11 @@ @@ -102,7 +102,11 @@
102 class="flex-auto" 102 class="flex-auto"
103 :disabled="controlDisableStatus" 103 :disabled="controlDisableStatus"
104 /> 104 />
105 - <Popconfirm v-model:visible="popconfirmVisible" v-bind="getPopConfirmProps"> 105 + <Popconfirm
  106 + :disabled="disabled"
  107 + v-model:visible="popconfirmVisible"
  108 + v-bind="getPopConfirmProps"
  109 + >
106 <Button type="primary" :disabled="disabled"> 110 <Button type="primary" :disabled="disabled">
107 <Icon 111 <Icon
108 :icon="controlDisableStatus ? 'ant-design:lock-outlined' : 'ant-design:unlock-outlined'" 112 :icon="controlDisableStatus ? 'ant-design:lock-outlined' : 'ant-design:unlock-outlined'"
@@ -3,4 +3,5 @@ export enum MessageEnum { @@ -3,4 +3,5 @@ export enum MessageEnum {
3 IS_EMAIL = 'EMAIL_MESSAGE', 3 IS_EMAIL = 'EMAIL_MESSAGE',
4 IS_DINGTALK = 'DING_TALK_MESSAGE', 4 IS_DINGTALK = 'DING_TALK_MESSAGE',
5 IS_VOICE = 'VOICE_MESSAGE', 5 IS_VOICE = 'VOICE_MESSAGE',
  6 + IS_ENTERPRISE_WECHAT = 'ENTERPRISE_WECHAT_MESSAGE',
6 } 7 }
@@ -8,22 +8,26 @@ @@ -8,22 +8,26 @@
8 '清除未确认: 只可处理,已经清除', 8 '清除未确认: 只可处理,已经清除',
9 '清除已确认: 不需要做处理和清除', 9 '清除已确认: 不需要做处理和清除',
10 ] --> 10 ] -->
11 - <a-button  
12 - type="primary"  
13 - class="mr-4"  
14 - @click="handleAlarm"  
15 - v-if="alarmStatus !== AlarmStatus.ACTIVE_ACK && alarmStatus !== AlarmStatus.CLEARED_ACK"  
16 - >  
17 - 处理  
18 - </a-button>  
19 - <a-button  
20 - danger  
21 - type="primary"  
22 - @click="clearAlarm"  
23 - v-if="alarmStatus === AlarmStatus.ACTIVE_ACK"  
24 - >  
25 - 清除  
26 - </a-button> 11 + <Authority value="api:yt:alarm:single:handle">
  12 + <a-button
  13 + type="primary"
  14 + class="mr-4"
  15 + @click="handleAlarm"
  16 + v-if="alarmStatus !== AlarmStatus.ACTIVE_ACK && alarmStatus !== AlarmStatus.CLEARED_ACK"
  17 + >
  18 + 处理
  19 + </a-button>
  20 + </Authority>
  21 + <Authority value="api:yt:alarm:single:clear">
  22 + <a-button
  23 + danger
  24 + type="primary"
  25 + @click="clearAlarm"
  26 + v-if="alarmStatus === AlarmStatus.ACTIVE_ACK"
  27 + >
  28 + 清除
  29 + </a-button>
  30 + </Authority>
27 </div> 31 </div>
28 </BasicDrawer> 32 </BasicDrawer>
29 </template> 33 </template>
@@ -36,11 +40,13 @@ @@ -36,11 +40,13 @@
36 import { alarmLevel, statusType } from '/@/views/device/list/config/detail.config'; 40 import { alarmLevel, statusType } from '/@/views/device/list/config/detail.config';
37 import { BasicDrawer, useDrawerInner } from '/@/components/Drawer'; 41 import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
38 import { AlarmStatus } from '/@/enums/alarmEnum'; 42 import { AlarmStatus } from '/@/enums/alarmEnum';
  43 + import { Authority } from '/@/components/Authority';
39 export default defineComponent({ 44 export default defineComponent({
40 name: 'AlarmDetailDrawer', 45 name: 'AlarmDetailDrawer',
41 components: { 46 components: {
42 BasicForm, 47 BasicForm,
43 BasicDrawer, 48 BasicDrawer,
  49 + Authority,
44 }, 50 },
45 emits: ['success', 'register'], 51 emits: ['success', 'register'],
46 setup(_, { emit }) { 52 setup(_, { emit }) {
@@ -19,12 +19,16 @@ @@ -19,12 +19,16 @@
19 </Button> 19 </Button>
20 </template> 20 </template>
21 <template #toolbar> 21 <template #toolbar>
22 - <Button @click="handleBatchClear" type="primary" :disabled="getCanBatchClear">  
23 - <span>批量清除</span>  
24 - </Button>  
25 - <Button @click="handleBatchAck" type="primary" :disabled="getCanBatchAck">  
26 - <span>批量处理</span>  
27 - </Button> 22 + <Authority value="api:yt:alarm:batch:clear">
  23 + <Button @click="handleBatchClear" type="primary" :disabled="getCanBatchClear">
  24 + <span>批量清除</span>
  25 + </Button>
  26 + </Authority>
  27 + <Authority value="api:yt:alarm:batch:handle">
  28 + <Button @click="handleBatchAck" type="primary" :disabled="getCanBatchAck">
  29 + <span>批量处理</span>
  30 + </Button>
  31 + </Authority>
28 </template> 32 </template>
29 </BasicTable> 33 </BasicTable>
30 <AlarmDetailDrawer @register="registerDetailDrawer" @success="handleSuccess" /> 34 <AlarmDetailDrawer @register="registerDetailDrawer" @success="handleSuccess" />
@@ -49,6 +53,7 @@ @@ -49,6 +53,7 @@
49 import { AlarmLogItem } from '/@/api/device/model/deviceConfigModel'; 53 import { AlarmLogItem } from '/@/api/device/model/deviceConfigModel';
50 import { AlarmStatus } from '/@/enums/alarmEnum'; 54 import { AlarmStatus } from '/@/enums/alarmEnum';
51 import { useMessage } from '/@/hooks/web/useMessage'; 55 import { useMessage } from '/@/hooks/web/useMessage';
  56 + import { Authority } from '/@/components/Authority';
52 57
53 const props = defineProps<{ 58 const props = defineProps<{
54 deviceId?: string; 59 deviceId?: string;
@@ -10,6 +10,7 @@ @@ -10,6 +10,7 @@
10 <BasicForm @register="registerForm"> 10 <BasicForm @register="registerForm">
11 <!-- 模板选择 --> 11 <!-- 模板选择 -->
12 <template #templateId="{ model }"> 12 <template #templateId="{ model }">
  13 + <span style="display: none">{{ getFieldOrg(model['organizationId']) }}</span>
13 <Select 14 <Select
14 v-model:value="model['templateId']" 15 v-model:value="model['templateId']"
15 placeholder="请选择模板" 16 placeholder="请选择模板"
@@ -35,7 +36,7 @@ @@ -35,7 +36,7 @@
35 </BasicDrawer> 36 </BasicDrawer>
36 </template> 37 </template>
37 <script lang="ts"> 38 <script lang="ts">
38 - import { defineComponent, ref, computed, unref, Ref, onMounted, nextTick } from 'vue'; 39 + import { defineComponent, ref, computed, unref, Ref, onMounted, nextTick, watch } from 'vue';
39 import { BasicForm, useForm } from '/@/components/Form'; 40 import { BasicForm, useForm } from '/@/components/Form';
40 import { Select } from 'ant-design-vue'; 41 import { Select } from 'ant-design-vue';
41 import { formSchema, PC_DEFAULT_CONTENT, PHONE_DEFAULT_CONTENT, Platform } from './center.data'; 42 import { formSchema, PC_DEFAULT_CONTENT, PHONE_DEFAULT_CONTENT, Platform } from './center.data';
@@ -61,11 +62,12 @@ @@ -61,11 +62,12 @@
61 62
62 const selectDeviceProfileRef = ref<InstanceType<typeof SelectDeviceProfile>>(); 63 const selectDeviceProfileRef = ref<InstanceType<typeof SelectDeviceProfile>>();
63 64
64 - const [registerForm, { validate, setFieldsValue, resetFields, updateSchema }] = useForm({  
65 - labelWidth: 120,  
66 - schemas: formSchema,  
67 - showActionButtonGroup: false,  
68 - }); 65 + const [registerForm, { validate, setFieldsValue, resetFields, updateSchema, clearValidate }] =
  66 + useForm({
  67 + labelWidth: 120,
  68 + schemas: formSchema,
  69 + showActionButtonGroup: false,
  70 + });
69 71
70 const updateEnableTemplate = async (enable: number, disabled: boolean) => { 72 const updateEnableTemplate = async (enable: number, disabled: boolean) => {
71 await setFieldsValue({ 73 await setFieldsValue({
@@ -90,6 +92,14 @@ @@ -90,6 +92,14 @@
90 }; 92 };
91 }, 93 },
92 }); 94 });
  95 + await updateSchema({
  96 + field: 'organizationId',
  97 + componentProps: () => {
  98 + return {
  99 + disabled,
  100 + };
  101 + },
  102 + });
93 }; 103 };
94 104
95 const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => { 105 const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
@@ -107,6 +117,7 @@ @@ -107,6 +117,7 @@
107 ]; 117 ];
108 } 118 }
109 if (data.record.organizationDTO) { 119 if (data.record.organizationDTO) {
  120 + organizationIdStr.value = data.record['organizationId'];
110 await setFieldsValue(data.record); 121 await setFieldsValue(data.record);
111 } else { 122 } else {
112 Reflect.deleteProperty(data.record, 'organizationId'); 123 Reflect.deleteProperty(data.record, 'organizationId');
@@ -135,16 +146,41 @@ @@ -135,16 +146,41 @@
135 //新增修改 146 //新增修改
136 const templateDisabled = ref(false); 147 const templateDisabled = ref(false);
137 onMounted(() => { 148 onMounted(() => {
138 - getTemplate({  
139 - page: 1,  
140 - pageSize: 30,  
141 - }); 149 + getTemplate(
  150 + {
  151 + page: 1,
  152 + pageSize: 30,
  153 + },
  154 + null
  155 + );
142 }); 156 });
143 157
144 const selectTemplateOptions: Ref<any[]> = ref([]); 158 const selectTemplateOptions: Ref<any[]> = ref([]);
145 159
146 - const getTemplate = async (params: queryPageParams) => {  
147 - const { items } = await getPage({ ...params, isTemplate: 1 }); 160 + const organizationIdStr = ref('');
  161 +
  162 + const getFieldOrg = (org: string) => (organizationIdStr.value = org);
  163 +
  164 + watch(
  165 + () => organizationIdStr.value,
  166 + (newValue) => {
  167 + getTemplate(
  168 + {
  169 + page: 1,
  170 + pageSize: 30,
  171 + },
  172 + newValue
  173 + );
  174 + if (!isUpdate.value) {
  175 + setFieldsValue({
  176 + templateId: null,
  177 + });
  178 + }
  179 + }
  180 + );
  181 +
  182 + const getTemplate = async (params: queryPageParams, organizationId) => {
  183 + const { items } = await getPage({ ...params, isTemplate: 1, organizationId });
148 selectTemplateOptions.value = items.map((item) => ({ 184 selectTemplateOptions.value = items.map((item) => ({
149 ...item, 185 ...item,
150 label: item.name, 186 label: item.name,
@@ -154,6 +190,7 @@ @@ -154,6 +190,7 @@
154 const selectOptions: Ref<any[]> = ref([]); 190 const selectOptions: Ref<any[]> = ref([]);
155 191
156 const handleTemplateChange = async (_, option) => { 192 const handleTemplateChange = async (_, option) => {
  193 + clearValidate('templateId');
157 const { productAndDevice } = option; 194 const { productAndDevice } = option;
158 setFieldsValue({ platform: option?.platform }); 195 setFieldsValue({ platform: option?.platform });
159 await nextTick(); 196 await nextTick();
@@ -249,6 +286,7 @@ @@ -249,6 +286,7 @@
249 createPickerSearch, 286 createPickerSearch,
250 selectDeviceProfileRef, 287 selectDeviceProfileRef,
251 templateDisabled, 288 templateDisabled,
  289 + getFieldOrg,
252 }; 290 };
253 }, 291 },
254 }); 292 });
@@ -36,7 +36,7 @@ const updateProductHelpMessage = [ @@ -36,7 +36,7 @@ const updateProductHelpMessage = [
36 '修改设备产品时,如果"大屏"、"组态"、"看板"、"场景联动"、"数据流转"等有关联设备,请自行调整并保存。', 36 '修改设备产品时,如果"大屏"、"组态"、"看板"、"场景联动"、"数据流转"等有关联设备,请自行调整并保存。',
37 ]; 37 ];
38 38
39 -const updateOrgHelpMessage = [ 39 +export const updateOrgHelpMessage = [
40 '注意:', 40 '注意:',
41 '1、修改设备组织时,如果"大屏"、"组态"、"看板"、"场景联动"、"数据流转"等有关联设备,请自行调整并保存。', 41 '1、修改设备组织时,如果"大屏"、"组态"、"看板"、"场景联动"、"数据流转"等有关联设备,请自行调整并保存。',
42 '2、网关设备在修改组织时,其关联网关子设备组织归属于网关组织及以下不用修改,否则将同步更新网关子设备组织为网关组织。', 42 '2、网关设备在修改组织时,其关联网关子设备组织归属于网关组织及以下不用修改,否则将同步更新网关子设备组织为网关组织。',
@@ -314,6 +314,12 @@ export const step1Schemas: FormSchema[] = [ @@ -314,6 +314,12 @@ export const step1Schemas: FormSchema[] = [
314 ifShow: false, 314 ifShow: false,
315 }, 315 },
316 { 316 {
  317 + field: 'customerId',
  318 + label: '用来判断编辑时禁用组织修改',
  319 + component: 'Input',
  320 + ifShow: false,
  321 + },
  322 + {
317 field: 'organizationId', 323 field: 'organizationId',
318 label: '所属组织', 324 label: '所属组织',
319 component: 'LockControlGroup', 325 component: 'LockControlGroup',
@@ -327,6 +333,7 @@ export const step1Schemas: FormSchema[] = [ @@ -327,6 +333,7 @@ export const step1Schemas: FormSchema[] = [
327 return { 333 return {
328 component: 'OrgTreeSelect', 334 component: 'OrgTreeSelect',
329 defaultLockStatus: !!formModel?.isUpdate, 335 defaultLockStatus: !!formModel?.isUpdate,
  336 + disabled: !!formModel?.customerId,
330 componentProps: { 337 componentProps: {
331 apiTreeSelectProps: { 338 apiTreeSelectProps: {
332 params: { 339 params: {
1 import { formatToDateTime } from '/@/utils/dateUtil'; 1 import { formatToDateTime } from '/@/utils/dateUtil';
2 -import { FormSchema } from '/@/components/Form'; 2 +import { FormSchema, useComponentRegister } from '/@/components/Form';
3 import { BasicColumn } from '/@/components/Table'; 3 import { BasicColumn } from '/@/components/Table';
4 import { DeviceTypeEnum } from '/@/api/device/model/deviceModel'; 4 import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';
5 import { getCustomerList } from '/@/api/device/deviceManager'; 5 import { getCustomerList } from '/@/api/device/deviceManager';
@@ -7,12 +7,15 @@ import { DescItem } from '/@/components/Description/index'; @@ -7,12 +7,15 @@ import { DescItem } from '/@/components/Description/index';
7 import moment from 'moment'; 7 import moment from 'moment';
8 import { CSSProperties, h } from 'vue'; 8 import { CSSProperties, h } from 'vue';
9 import { Button, Tooltip } from 'ant-design-vue'; 9 import { Button, Tooltip } from 'ant-design-vue';
10 -import { TypeEnum } from './data'; 10 +import { TypeEnum, updateOrgHelpMessage } from './data';
11 import { PageEnum } from '/@/enums/pageEnum'; 11 import { PageEnum } from '/@/enums/pageEnum';
12 import { useGo } from '/@/hooks/web/usePage'; 12 import { useGo } from '/@/hooks/web/usePage';
13 import { useAuthDeviceDetail } from '../hook/useAuthDeviceDetail'; 13 import { useAuthDeviceDetail } from '../hook/useAuthDeviceDetail';
14 import { findDictItemByCode } from '/@/api/system/dict'; 14 import { findDictItemByCode } from '/@/api/system/dict';
15 import { isNullOrUnDef } from '/@/utils/is'; 15 import { isNullOrUnDef } from '/@/utils/is';
  16 +import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect';
  17 +
  18 +useComponentRegister('OrgTreeSelect', OrgTreeSelect);
16 19
17 // 设备详情的描述 20 // 设备详情的描述
18 export const descSchema = (emit: EmitType): DescItem[] => { 21 export const descSchema = (emit: EmitType): DescItem[] => {
@@ -407,3 +410,28 @@ export const customerForm: FormSchema[] = [ @@ -407,3 +410,28 @@ export const customerForm: FormSchema[] = [
407 colProps: { span: 12 }, 410 colProps: { span: 12 },
408 }, 411 },
409 ]; 412 ];
  413 +
  414 +export const orgForm: FormSchema[] = [
  415 + {
  416 + field: 'sensorOrganizationId',
  417 + label: '依据网关设备请求的组织数组',
  418 + component: 'Input',
  419 + ifShow: false,
  420 + },
  421 + {
  422 + field: 'organizationId',
  423 + component: 'OrgTreeSelect',
  424 + helpMessage: updateOrgHelpMessage,
  425 + label: '选择组织',
  426 + required: true,
  427 + componentProps: ({ formModel }) => {
  428 + return {
  429 + apiTreeSelectProps: {
  430 + params: {
  431 + organizationId: formModel?.sensorOrganizationId,
  432 + },
  433 + },
  434 + };
  435 + },
  436 + },
  437 +];
  1 +<template>
  2 + <BasicModal
  3 + v-bind="$attrs"
  4 + @register="registerModal"
  5 + title="批量修改设备"
  6 + :canFullscreen="false"
  7 + centered
  8 + @ok="dispatchCustomer"
  9 + @cancel="resetFields"
  10 + :minHeight="300"
  11 + okText="确认"
  12 + >
  13 + <BasicForm @register="registerForm" />
  14 + </BasicModal>
  15 +</template>
  16 +
  17 +<script lang="ts">
  18 + import { defineComponent, ref, nextTick } from 'vue';
  19 + import { BasicModal, useModalInner } from '/@/components/Modal';
  20 + import { BasicForm, useForm } from '/@/components/Form';
  21 + import { orgForm } from '../../config/detail.config';
  22 + import { doBatchUpdateDevice, getGATEWAY } from '/@/api/device/deviceManager';
  23 + import { useMessage } from '/@/hooks/web/useMessage';
  24 +
  25 + export default defineComponent({
  26 + name: 'AlarmDetailModal',
  27 + components: {
  28 + BasicModal,
  29 + BasicForm,
  30 + },
  31 + emits: ['reload', 'register'],
  32 + setup(_, { emit }) {
  33 + const { createMessage } = useMessage();
  34 +
  35 + const record = ref([]);
  36 +
  37 + const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
  38 + await nextTick();
  39 + record.value = data;
  40 + // 如果是网关子设备
  41 + const deviceTypeFilterSensor = data
  42 + .filter((filterItem) => filterItem.deviceType === 'SENSOR')
  43 + .map((mapItem) => mapItem.tbDeviceId);
  44 + // 根据gatewayId获取网关所属组织
  45 + for (let item of deviceTypeFilterSensor) {
  46 + await getGATEWAYByTbDeviceId(item);
  47 + }
  48 + });
  49 +
  50 + const getGATEWAYByTbDeviceId = async (tbDeviceId: string) => {
  51 + const result = await getGATEWAY(tbDeviceId);
  52 + if (!result) return;
  53 + setFieldsValue({
  54 + sensorOrganizationId: result.organizationId,
  55 + });
  56 + };
  57 +
  58 + const [registerForm, { validate, resetFields, setFieldsValue }] = useForm({
  59 + labelWidth: 100,
  60 + showActionButtonGroup: false,
  61 + schemas: orgForm,
  62 + });
  63 +
  64 + // 分配组织
  65 + const dispatchCustomer = async () => {
  66 + setModalProps({ loading: true });
  67 + try {
  68 + const value = await validate();
  69 + if (!value) return;
  70 + await doBatchUpdateDevice(value.organizationId, record.value);
  71 + closeModal();
  72 + resetFields();
  73 + emit('reload');
  74 + } finally {
  75 + setModalProps({ loading: false });
  76 + createMessage.success('操作成功');
  77 + }
  78 + };
  79 +
  80 + return {
  81 + registerModal,
  82 + registerForm,
  83 + dispatchCustomer,
  84 + resetFields,
  85 + };
  86 + },
  87 + });
  88 +</script>
@@ -418,6 +418,7 @@ @@ -418,6 +418,7 @@
418 code: data?.code, 418 code: data?.code,
419 addressCode: data?.code, 419 addressCode: data?.code,
420 isUpdate: unref(isUpdate1), 420 isUpdate: unref(isUpdate1),
  421 + customerId: data?.customerId,
421 }); 422 });
422 } 423 }
423 // 父组件调用获取字段值的方法 424 // 父组件调用获取字段值的方法
1 <script lang="ts" setup> 1 <script lang="ts" setup>
  2 + import { ref } from 'vue';
2 import { TaskCard } from '/@/views/task/center/components/TaskCard'; 3 import { TaskCard } from '/@/views/task/center/components/TaskCard';
3 import { formSchemas } from '/@/views/task/center/config'; 4 import { formSchemas } from '/@/views/task/center/config';
4 import { getTaskCenterList } from '/@/api/task'; 5 import { getTaskCenterList } from '/@/api/task';
@@ -29,6 +30,8 @@ @@ -29,6 +30,8 @@
29 }, 30 },
30 }); 31 });
31 32
  33 + const fromDeviceDetail = ref(true);
  34 +
32 const handleRunTask = (record: TaskRecordType) => { 35 const handleRunTask = (record: TaskRecordType) => {
33 openModal(true, record); 36 openModal(true, record);
34 }; 37 };
@@ -50,7 +53,12 @@ @@ -50,7 +53,12 @@
50 /> 53 />
51 </template> 54 </template>
52 </BasicCardList> 55 </BasicCardList>
53 - <RunTaskModal :reload="reload" @register="registerRunTaskModal" /> 56 + <RunTaskModal
  57 + :tbDeviceId="tbDeviceId"
  58 + :fromOrigin="fromDeviceDetail"
  59 + :reload="reload"
  60 + @register="registerRunTaskModal"
  61 + />
54 </section> 62 </section>
55 <!-- </PageWrapper> --> 63 <!-- </PageWrapper> -->
56 </template> 64 </template>
@@ -23,13 +23,14 @@ @@ -23,13 +23,14 @@
23 > 23 >
24 <AuthDropDown 24 <AuthDropDown
25 v-if="authBtn(role)" 25 v-if="authBtn(role)"
26 - :disabled="!isExistOption" 26 + :disabled="isPublicAndPrivateFlag || !isExistOption"
27 :dropMenuList="[ 27 :dropMenuList="[
28 { 28 {
29 text: '删除设备', 29 text: '删除设备',
30 auth: DeviceListAuthEnum.DELETE, 30 auth: DeviceListAuthEnum.DELETE,
31 icon: 'ant-design:delete-outlined', 31 icon: 'ant-design:delete-outlined',
32 event: '', 32 event: '',
  33 + disabled: !batchPrivateFlag,
33 popconfirm: { 34 popconfirm: {
34 title: '您确定要批量删除数据', 35 title: '您确定要批量删除数据',
35 onConfirm: () => handleDelete(), 36 onConfirm: () => handleDelete(),
@@ -40,6 +41,7 @@ @@ -40,6 +41,7 @@
40 auth: DeviceListAuthEnum.ASSIGN, 41 auth: DeviceListAuthEnum.ASSIGN,
41 icon: 'mdi:account-arrow-left', 42 icon: 'mdi:account-arrow-left',
42 event: '', 43 event: '',
  44 + disabled: !batchPrivateFlag,
43 onClick: handleBatchAssign.bind(null), 45 onClick: handleBatchAssign.bind(null),
44 }, 46 },
45 { 47 {
@@ -47,12 +49,35 @@ @@ -47,12 +49,35 @@
47 auth: DeviceListAuthEnum.UPDATE_PRODUCT, 49 auth: DeviceListAuthEnum.UPDATE_PRODUCT,
48 icon: 'clarity:note-edit-line', 50 icon: 'clarity:note-edit-line',
49 event: '', 51 event: '',
50 - disabled: batchUpdateProductFlag, 52 + disabled: !batchPrivateFlag || batchUpdateProductFlag,
51 onClick: handelOpenBatchUpdateProductModal, 53 onClick: handelOpenBatchUpdateProductModal,
52 }, 54 },
  55 + {
  56 + text: '批量公开',
  57 + icon: 'ant-design:wallet-outlined',
  58 + event: '',
  59 + disabled: !batchPrivateFlag,
  60 + onClick: handleBatchPublic.bind(null),
  61 + },
  62 + {
  63 + text: '批量私有',
  64 + icon: 'ant-design:wallet-outlined',
  65 + event: '',
  66 + disabled: batchPrivateFlag,
  67 + onClick: handleBatchPrivate.bind(null),
  68 + },
  69 + {
  70 + text: '批量修改组织',
  71 + icon: 'ant-design:wallet-outlined',
  72 + event: '',
  73 + disabled: !batchPrivateFlag || batchSensorFlag || diffBatchSensorFlag,
  74 + onClick: handleBatchOrg.bind(null),
  75 + },
53 ]" 76 ]"
54 > 77 >
55 - <Button type="primary" :disabled="!isExistOption">批量操作</Button> 78 + <Button type="primary" :disabled="isPublicAndPrivateFlag || !isExistOption"
  79 + >批量操作</Button
  80 + >
56 </AuthDropDown> 81 </AuthDropDown>
57 </Authority> 82 </Authority>
58 </template> 83 </template>
@@ -225,6 +250,7 @@ @@ -225,6 +250,7 @@
225 250
226 <DeviceModal @register="registerModal" @success="handleSuccess" @reload="handleSuccess" /> 251 <DeviceModal @register="registerModal" @success="handleSuccess" @reload="handleSuccess" />
227 <CustomerModal @register="registerCustomerModal" @reload="handleReload" /> 252 <CustomerModal @register="registerCustomerModal" @reload="handleReload" />
  253 + <OrgModal @register="registerOrgModal" @reload="handleReload" />
228 254
229 <BatchImportModal @register="registerImportModal" @import-finally="handleImportFinally" /> 255 <BatchImportModal @register="registerImportModal" @import-finally="handleImportFinally" />
230 256
@@ -254,6 +280,8 @@ @@ -254,6 +280,8 @@
254 getGATEWAY, 280 getGATEWAY,
255 privateDevice, 281 privateDevice,
256 publicDevice, 282 publicDevice,
  283 + doBatchPublicDevice,
  284 + doBatchPrivateDevice,
257 } from '/@/api/device/deviceManager'; 285 } from '/@/api/device/deviceManager';
258 import { PageEnum } from '/@/enums/pageEnum'; 286 import { PageEnum } from '/@/enums/pageEnum';
259 import { useGo } from '/@/hooks/web/usePage'; 287 import { useGo } from '/@/hooks/web/usePage';
@@ -264,6 +292,7 @@ @@ -264,6 +292,7 @@
264 import { useDrawer } from '/@/components/Drawer'; 292 import { useDrawer } from '/@/components/Drawer';
265 import DeviceDetailDrawer from './cpns/modal/DeviceDetailDrawer.vue'; 293 import DeviceDetailDrawer from './cpns/modal/DeviceDetailDrawer.vue';
266 import CustomerModal from './cpns/modal/CustomerModal.vue'; 294 import CustomerModal from './cpns/modal/CustomerModal.vue';
  295 + import OrgModal from './cpns/modal/OrgModal.vue';
267 import BatchImportModal from './cpns/modal/BatchImportModal/index.vue'; 296 import BatchImportModal from './cpns/modal/BatchImportModal/index.vue';
268 import { useMessage } from '/@/hooks/web/useMessage'; 297 import { useMessage } from '/@/hooks/web/useMessage';
269 import { USER_INFO_KEY } from '/@/enums/cacheEnum'; 298 import { USER_INFO_KEY } from '/@/enums/cacheEnum';
@@ -292,6 +321,7 @@ @@ -292,6 +321,7 @@
292 const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo); 321 const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);
293 const [registerModal, { openModal }] = useModal(); 322 const [registerModal, { openModal }] = useModal();
294 const [registerCustomerModal, { openModal: openCustomerModal }] = useModal(); 323 const [registerCustomerModal, { openModal: openCustomerModal }] = useModal();
  324 + const [registerOrgModal, { openModal: openOrgodal }] = useModal();
295 const [registerDetailDrawer, { openDrawer }] = useDrawer(); 325 const [registerDetailDrawer, { openDrawer }] = useDrawer();
296 const [registerTbDetailDrawer, { openDrawer: openTbDeviceDrawer }] = useDrawer(); 326 const [registerTbDetailDrawer, { openDrawer: openTbDeviceDrawer }] = useDrawer();
297 const [registerGatewayDetailDrawer, { openDrawer: openGatewayDetailDrawer }] = useDrawer(); 327 const [registerGatewayDetailDrawer, { openDrawer: openGatewayDetailDrawer }] = useDrawer();
@@ -326,6 +356,14 @@ @@ -326,6 +356,14 @@
326 356
327 const batchUpdateProductFlag = ref(true); 357 const batchUpdateProductFlag = ref(true);
328 358
  359 + const isPublicAndPrivateFlag = ref(false);
  360 +
  361 + const batchPrivateFlag = ref(true);
  362 +
  363 + const batchSensorFlag = ref(false);
  364 +
  365 + const diffBatchSensorFlag = ref(false);
  366 +
329 const [ 367 const [
330 registerTable, 368 registerTable,
331 { 369 {
@@ -376,7 +414,7 @@ @@ -376,7 +414,7 @@
376 rowSelection: { 414 rowSelection: {
377 type: 'checkbox', 415 type: 'checkbox',
378 getCheckboxProps: (record: DeviceModel) => { 416 getCheckboxProps: (record: DeviceModel) => {
379 - return { disabled: !!record.customerId }; 417 + return { disabled: !!record.customerId && record.customerName !== 'Public' };
380 }, 418 },
381 onSelect(_record, _selected, selectedRows) { 419 onSelect(_record, _selected, selectedRows) {
382 const [firstItem] = selectedRows as DeviceRecord[]; 420 const [firstItem] = selectedRows as DeviceRecord[];
@@ -384,6 +422,19 @@ @@ -384,6 +422,19 @@
384 batchUpdateProductFlag.value = 422 batchUpdateProductFlag.value =
385 !selectedRows.length || 423 !selectedRows.length ||
386 !selectedRows.every((item) => (item as DeviceRecord).deviceType === deviceType); 424 !selectedRows.every((item) => (item as DeviceRecord).deviceType === deviceType);
  425 +
  426 + batchPrivateFlag.value = selectedRows.some((item: DeviceRecord) => !item?.customerId);
  427 + const filterSensor = selectedRows.map((filterItem: DeviceRecord) => filterItem.deviceType);
  428 + batchSensorFlag.value =
  429 + filterSensor.includes('SENSOR') &&
  430 + (filterSensor.includes('DIRECT_CONNECTION') || filterSensor.includes('GATEWAY')); // 网关子和直连设备或者网关设备,则禁用组织修改
  431 + const filterGatewayId = selectedRows
  432 + .filter((filterItem: DeviceRecord) => filterItem.deviceType === 'SENSOR')
  433 + .map((mapItem: DeviceRecord) => mapItem.gatewayId);
  434 + diffBatchSensorFlag.value = [...new Set(filterGatewayId)].length > 1; // 数组长度大于1,说明选择的网关子设备所属网关为多个,则禁用组织修改
  435 + isPublicAndPrivateFlag.value =
  436 + selectedRows.some((item: DeviceRecord) => !item?.customerId) &&
  437 + selectedRows.some((item: DeviceRecord) => item?.customerAdditionalInfo?.isPublic);
387 }, 438 },
388 onSelectAll(_selected, selectedRows) { 439 onSelectAll(_selected, selectedRows) {
389 const [firstItem] = selectedRows as DeviceRecord[]; 440 const [firstItem] = selectedRows as DeviceRecord[];
@@ -391,6 +442,19 @@ @@ -391,6 +442,19 @@
391 batchUpdateProductFlag.value = 442 batchUpdateProductFlag.value =
392 !selectedRows.length || 443 !selectedRows.length ||
393 !selectedRows.every((item) => (item as DeviceRecord).deviceType === deviceType); 444 !selectedRows.every((item) => (item as DeviceRecord).deviceType === deviceType);
  445 +
  446 + batchPrivateFlag.value = selectedRows.some((item) => !item?.customerId);
  447 + const filterSensor = selectedRows.map((filterItem) => filterItem.deviceType);
  448 + batchSensorFlag.value =
  449 + filterSensor.includes('SENSOR') &&
  450 + (filterSensor.includes('DIRECT_CONNECTION') || filterSensor.includes('GATEWAY')); // 网关子和直连设备或者网关设备,则禁用组织修改
  451 + const filterGatewayId = selectedRows
  452 + .filter((filterItem: DeviceRecord) => filterItem.deviceType === 'SENSOR')
  453 + .map((mapItem: DeviceRecord) => mapItem.gatewayId);
  454 + diffBatchSensorFlag.value = [...new Set(filterGatewayId)].length > 1; // 数组长度大于1,说明选择的网关子设备所属网关为多个,则禁用组织修改
  455 + isPublicAndPrivateFlag.value =
  456 + selectedRows.some((item) => !item?.customerId) &&
  457 + selectedRows.some((item) => item?.customerAdditionalInfo?.isPublic);
394 }, 458 },
395 }, 459 },
396 }); 460 });
@@ -492,6 +556,39 @@ @@ -492,6 +556,39 @@
492 openCustomerModal(true, options); 556 openCustomerModal(true, options);
493 }; 557 };
494 558
  559 + const handleBatchOrg = () => {
  560 + const options = getSelectRows();
  561 + openOrgodal(true, options);
  562 + };
  563 +
  564 + // 批量公开设备
  565 + const handleBatchPublic = async () => {
  566 + setLoading(true);
  567 + try {
  568 + const options = getSelectRows();
  569 + const tbDeviceIdJoinStr = options.map((item) => item.tbDeviceId).join(',');
  570 + const res = await doBatchPublicDevice(tbDeviceIdJoinStr);
  571 + createMessage.success(res);
  572 + } finally {
  573 + handleReload();
  574 + setLoading(false);
  575 + }
  576 + };
  577 +
  578 + // 批量私有设备
  579 + const handleBatchPrivate = async () => {
  580 + setLoading(true);
  581 + try {
  582 + const options = getSelectRows();
  583 + const tbDeviceIdJoinStr = options.map((item) => item.tbDeviceId).join(',');
  584 + const res = await doBatchPrivateDevice(tbDeviceIdJoinStr);
  585 + createMessage.success(res);
  586 + } finally {
  587 + handleReload();
  588 + setLoading(false);
  589 + }
  590 + };
  591 +
495 const handleDelete = async (record?: DeviceRecord) => { 592 const handleDelete = async (record?: DeviceRecord) => {
496 let ids: string[] = []; 593 let ids: string[] = [];
497 if (record) { 594 if (record) {
@@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
17 import { BasicDrawer, useDrawerInner } from '/@/components/Drawer'; 17 import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
18 import { saveOrEditMessageConfig } from '/@/api/message/config'; 18 import { saveOrEditMessageConfig } from '/@/api/message/config';
19 import { useMessage } from '/@/hooks/web/useMessage'; 19 import { useMessage } from '/@/hooks/web/useMessage';
  20 + import { MessageEnum } from '/@/enums/messageEnum';
20 21
21 export default defineComponent({ 22 export default defineComponent({
22 name: 'ConfigDrawer', 23 name: 'ConfigDrawer',
@@ -85,6 +86,12 @@ @@ -85,6 +86,12 @@
85 accessKeyId: values.accessKeyId, 86 accessKeyId: values.accessKeyId,
86 accessKeySecret: values.accessKeySecret, 87 accessKeySecret: values.accessKeySecret,
87 }; 88 };
  89 + } else if (values.messageType === MessageEnum.IS_ENTERPRISE_WECHAT) {
  90 + config = {
  91 + agentId: values.agentId,
  92 + corpId: values.corpId,
  93 + corpSecret: values.corpSecret,
  94 + };
88 } 95 }
89 Reflect.set(values, 'config', config); 96 Reflect.set(values, 'config', config);
90 let saveMessage = '添加成功'; 97 let saveMessage = '添加成功';
@@ -85,6 +85,9 @@ export const isDingtalk = (type: string) => { @@ -85,6 +85,9 @@ export const isDingtalk = (type: string) => {
85 export const isVoice = (type: string) => { 85 export const isVoice = (type: string) => {
86 return type === MessageEnum.IS_VOICE; 86 return type === MessageEnum.IS_VOICE;
87 }; 87 };
  88 +
  89 +export const isEnterpriseWechat = (type: string) => type === MessageEnum.IS_ENTERPRISE_WECHAT;
  90 +
88 export const messageTypeIsTencentCloud = (type: string) => { 91 export const messageTypeIsTencentCloud = (type: string) => {
89 return type === 'TENCENT_CLOUD'; 92 return type === 'TENCENT_CLOUD';
90 }; 93 };
@@ -105,13 +108,20 @@ export const formSchema: FormSchema[] = [ @@ -105,13 +108,20 @@ export const formSchema: FormSchema[] = [
105 label: '消息类型', 108 label: '消息类型',
106 required: true, 109 required: true,
107 component: 'ApiSelect', 110 component: 'ApiSelect',
108 - componentProps: {  
109 - api: findDictItemByCode,  
110 - params: {  
111 - dictCode: 'message_type',  
112 - },  
113 - labelField: 'itemText',  
114 - valueField: 'itemValue', 111 + componentProps({ formActionType }) {
  112 + return {
  113 + api: findDictItemByCode,
  114 + params: {
  115 + dictCode: 'message_type',
  116 + },
  117 + labelField: 'itemText',
  118 + valueField: 'itemValue',
  119 + onChange() {
  120 + formActionType.setFieldsValue({
  121 + platformType: null,
  122 + });
  123 + },
  124 + };
115 }, 125 },
116 }, 126 },
117 { 127 {
@@ -124,13 +134,22 @@ export const formSchema: FormSchema[] = [ @@ -124,13 +134,22 @@ export const formSchema: FormSchema[] = [
124 api: async (params: Recordable) => { 134 api: async (params: Recordable) => {
125 try { 135 try {
126 const result = await findDictItemByCode(params as any); 136 const result = await findDictItemByCode(params as any);
127 - if (isMessage(Reflect.get(formModel, 'messageType'))) 137 + const messageType = Reflect.get(formModel, 'messageType');
  138 + if (isMessage(messageType))
128 return result.filter( 139 return result.filter(
129 - (item) => item.itemValue !== 'DING_TALK' && item.itemValue !== 'ALI_VOICE' 140 + (item) =>
  141 + item.itemValue !== 'DING_TALK' &&
  142 + item.itemValue !== 'ALI_VOICE' &&
  143 + item.itemValue !== 'ENTERPRISE_WECHAT'
130 ); 144 );
131 - if (isDingtalk(Reflect.get(formModel, 'messageType'))) 145 +
  146 + if (isEnterpriseWechat(messageType)) {
  147 + return result.filter((item) => item.itemValue === 'ENTERPRISE_WECHAT');
  148 + }
  149 +
  150 + if (isDingtalk(messageType))
132 return result.filter((item) => item.itemValue === 'DING_TALK'); 151 return result.filter((item) => item.itemValue === 'DING_TALK');
133 - if (isVoice(Reflect.get(formModel, 'messageType'))) 152 + if (isVoice(messageType))
134 return result.filter((item) => item.itemValue === 'ALI_VOICE'); 153 return result.filter((item) => item.itemValue === 'ALI_VOICE');
135 } catch (e) { 154 } catch (e) {
136 // eslint-disable-next-line no-console 155 // eslint-disable-next-line no-console
@@ -145,10 +164,15 @@ export const formSchema: FormSchema[] = [ @@ -145,10 +164,15 @@ export const formSchema: FormSchema[] = [
145 valueField: 'itemValue', 164 valueField: 'itemValue',
146 }; 165 };
147 }, 166 },
148 - ifShow: ({ values }) =>  
149 - isMessage(Reflect.get(values, 'messageType')) ||  
150 - isDingtalk(Reflect.get(values, 'messageType')) ||  
151 - isVoice(Reflect.get(values, 'messageType')), 167 + ifShow: ({ values }) => {
  168 + const messageType = Reflect.get(values, 'messageType');
  169 + return (
  170 + isMessage(messageType) ||
  171 + isDingtalk(messageType) ||
  172 + isVoice(messageType) ||
  173 + isEnterpriseWechat(messageType)
  174 + );
  175 + },
152 }, 176 },
153 { 177 {
154 field: 'appId', 178 field: 'appId',
@@ -283,7 +307,10 @@ export const formSchema: FormSchema[] = [ @@ -283,7 +307,10 @@ export const formSchema: FormSchema[] = [
283 label: 'agentId', 307 label: 'agentId',
284 component: 'InputPassword', 308 component: 'InputPassword',
285 required: true, 309 required: true,
286 - ifShow: ({ values }) => isDingtalk(Reflect.get(values, 'messageType')), 310 + ifShow: ({ values }) => {
  311 + const messageType = Reflect.get(values, 'messageType');
  312 + return isDingtalk(messageType) || isEnterpriseWechat(messageType);
  313 + },
287 componentProps: { 314 componentProps: {
288 maxLength: 36, 315 maxLength: 36,
289 placeholder: '请输入agentId', 316 placeholder: '请输入agentId',
@@ -311,6 +338,27 @@ export const formSchema: FormSchema[] = [ @@ -311,6 +338,27 @@ export const formSchema: FormSchema[] = [
311 }, 338 },
312 }, 339 },
313 { 340 {
  341 + field: 'corpId',
  342 + label: 'corpId',
  343 + component: 'InputPassword',
  344 + required: true,
  345 + ifShow: ({ values }) => isEnterpriseWechat(Reflect.get(values, 'messageType')),
  346 + componentProps: {
  347 + maxLength: 18,
  348 + placeholder: '请输入corpId',
  349 + },
  350 + },
  351 + {
  352 + field: 'corpSecret',
  353 + label: 'corpSecret',
  354 + component: 'InputPassword',
  355 + required: true,
  356 + ifShow: ({ values }) => isEnterpriseWechat(Reflect.get(values, 'messageType')),
  357 + componentProps: {
  358 + placeholder: '请输入corpSecret',
  359 + },
  360 + },
  361 + {
314 field: 'status', 362 field: 'status',
315 label: '状态', 363 label: '状态',
316 component: 'RadioButtonGroup', 364 component: 'RadioButtonGroup',
@@ -43,7 +43,7 @@ @@ -43,7 +43,7 @@
43 :actions="[ 43 :actions="[
44 { 44 {
45 label: '发送', 45 label: '发送',
46 - auth: 'api:yt:template:sendEmail:post', 46 + auth: 'api:yt:template:send:post',
47 icon: 'ant-design:send-outlined', 47 icon: 'ant-design:send-outlined',
48 onClick: handleModal.bind(null, record), 48 onClick: handleModal.bind(null, record),
49 }, 49 },
@@ -9,6 +9,7 @@ @@ -9,6 +9,7 @@
9 :disabled="disabled" 9 :disabled="disabled"
10 mode="multiple" 10 mode="multiple"
11 labelInValue 11 labelInValue
  12 + :max-tag-count="selectDeviceMaxCount"
12 /> 13 />
13 <template v-for="(item, index) in deviceList" :key="item.value"> 14 <template v-for="(item, index) in deviceList" :key="item.value">
14 <SelectAttributes 15 <SelectAttributes
@@ -55,7 +56,10 @@ @@ -55,7 +56,10 @@
55 }; 56 };
56 57
57 const handleDeviceChange = (_, options) => { 58 const handleDeviceChange = (_, options) => {
58 - if (options.length > selectDeviceMaxCount.value) return createMessage.warn('最大限制选择5个'); 59 + if (options.length > selectDeviceMaxCount.value) {
  60 + createMessage.warn(`限制选择设备数为${selectDeviceMaxCount.value}`);
  61 + return;
  62 + }
59 deviceList.value = options; 63 deviceList.value = options;
60 }; 64 };
61 65
@@ -34,13 +34,14 @@ @@ -34,13 +34,14 @@
34 34
35 const getValue = async () => { 35 const getValue = async () => {
36 const record = (await validateFields()) || {}; 36 const record = (await validateFields()) || {};
37 - const { type, remark } = record; 37 + const { type, remark, organizationId } = record;
38 const datasourceType = Reflect.get(record, BasicInfoFormField.DATA_SOURCE_TYPE); 38 const datasourceType = Reflect.get(record, BasicInfoFormField.DATA_SOURCE_TYPE);
39 const convertDevices = Reflect.get(record, BasicInfoFormField.DATA_SOURCE_DEVICE); 39 const convertDevices = Reflect.get(record, BasicInfoFormField.DATA_SOURCE_DEVICE);
40 const convertProducts = Reflect.get(record, BasicInfoFormField.DATA_SOURCE_PRODUCT); 40 const convertProducts = Reflect.get(record, BasicInfoFormField.DATA_SOURCE_PRODUCT);
41 return { 41 return {
42 type, 42 type,
43 remark, 43 remark,
  44 + organizationId,
44 datasourceType, 45 datasourceType,
45 datasourceContent: { 46 datasourceContent: {
46 convertProducts, 47 convertProducts,
1 -import { FormSchema } from '/@/components/Form'; 1 +import { FormSchema, useComponentRegister } from '/@/components/Form';
2 import { findDictItemByCode } from '/@/api/system/dict'; 2 import { findDictItemByCode } from '/@/api/system/dict';
3 import { getDeviceProfile } from '/@/api/alarm/position'; 3 import { getDeviceProfile } from '/@/api/alarm/position';
4 import { BasicInfoFormField, DataSourceType } from '../enum'; 4 import { BasicInfoFormField, DataSourceType } from '../enum';
5 import { DeviceRecord } from '/@/api/device/model/deviceModel'; 5 import { DeviceRecord } from '/@/api/device/model/deviceModel';
6 import { useMessage } from '/@/hooks/web/useMessage'; 6 import { useMessage } from '/@/hooks/web/useMessage';
  7 +import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect';
  8 +
  9 +useComponentRegister('OrgTreeSelect', OrgTreeSelect);
7 10
8 export const stepConfig = ['选择流转方式', '完善配置参数']; 11 export const stepConfig = ['选择流转方式', '完善配置参数'];
9 12
@@ -57,6 +60,13 @@ export const modeForm = (disabled: boolean): FormSchema[] => { @@ -57,6 +60,13 @@ export const modeForm = (disabled: boolean): FormSchema[] => {
57 }, 60 },
58 }, 61 },
59 { 62 {
  63 + required: true,
  64 + field: BasicInfoFormField.ORGANIZATION_ID,
  65 + label: '所属组织',
  66 + colProps: { span: 24 },
  67 + component: 'OrgTreeSelect',
  68 + },
  69 + {
60 field: BasicInfoFormField.DATA_SOURCE_PRODUCT, 70 field: BasicInfoFormField.DATA_SOURCE_PRODUCT,
61 label: '数据源产品', 71 label: '数据源产品',
62 component: 'TransferModal', 72 component: 'TransferModal',
@@ -108,9 +118,10 @@ export const modeForm = (disabled: boolean): FormSchema[] => { @@ -108,9 +118,10 @@ export const modeForm = (disabled: boolean): FormSchema[] => {
108 componentProps: ({ formModel }) => { 118 componentProps: ({ formModel }) => {
109 const convertConfigId = formModel[BasicInfoFormField.CONVERT_CONFIG_ID]; 119 const convertConfigId = formModel[BasicInfoFormField.CONVERT_CONFIG_ID];
110 const deviceProfileIds = formModel[BasicInfoFormField.DATA_SOURCE_PRODUCT]; 120 const deviceProfileIds = formModel[BasicInfoFormField.DATA_SOURCE_PRODUCT];
  121 + const organizationId = formModel[BasicInfoFormField.ORGANIZATION_ID];
111 return { 122 return {
112 disabled, 123 disabled,
113 - params: { convertConfigId, deviceProfileIds }, 124 + params: { convertConfigId, deviceProfileIds, organizationId },
114 transformValue: handleGroupDevice, 125 transformValue: handleGroupDevice,
115 openModalValidate: () => { 126 openModalValidate: () => {
116 if (!deviceProfileIds || !deviceProfileIds?.length) { 127 if (!deviceProfileIds || !deviceProfileIds?.length) {
@@ -8,6 +8,7 @@ export enum BasicInfoFormField { @@ -8,6 +8,7 @@ export enum BasicInfoFormField {
8 DATA_SOURCE_PRODUCT = 'datasourceProduct', 8 DATA_SOURCE_PRODUCT = 'datasourceProduct',
9 DATA_SOURCE_DEVICE = 'datasourceDevice', 9 DATA_SOURCE_DEVICE = 'datasourceDevice',
10 CONVERT_CONFIG_ID = 'convertConfigId', 10 CONVERT_CONFIG_ID = 'convertConfigId',
  11 + ORGANIZATION_ID = 'organizationId',
11 } 12 }
12 13
13 export enum DataSourceType { 14 export enum DataSourceType {
@@ -30,7 +30,7 @@ @@ -30,7 +30,7 @@
30 auth: 'api:yt:sceneLinkage:update', 30 auth: 'api:yt:sceneLinkage:update',
31 icon: 'clarity:note-edit-line', 31 icon: 'clarity:note-edit-line',
32 onClick: handleEdit.bind(null, record), 32 onClick: handleEdit.bind(null, record),
33 - ifShow: record.creator === userId && record.status !== 1, 33 + ifShow: record.status !== 1,
34 }, 34 },
35 ]" 35 ]"
36 :drop-down-actions="[ 36 :drop-down-actions="[
@@ -39,7 +39,7 @@ @@ -39,7 +39,7 @@
39 auth: 'api:yt:sceneLinkage:delete', 39 auth: 'api:yt:sceneLinkage:delete',
40 icon: 'ant-design:delete-outlined', 40 icon: 'ant-design:delete-outlined',
41 color: 'error', 41 color: 'error',
42 - ifShow: record.creator === userId && record.status !== 1, 42 + ifShow: record.status !== 1,
43 popConfirm: { 43 popConfirm: {
44 title: '是否确认删除', 44 title: '是否确认删除',
45 confirm: handleDeleteOrBatchDelete.bind(null, record), 45 confirm: handleDeleteOrBatchDelete.bind(null, record),
@@ -82,8 +82,6 @@ @@ -82,8 +82,6 @@
82 import { useBatchDelete } from '/@/hooks/web/useBatchDelete'; 82 import { useBatchDelete } from '/@/hooks/web/useBatchDelete';
83 import { Switch, Popconfirm, Tag } from 'ant-design-vue'; 83 import { Switch, Popconfirm, Tag } from 'ant-design-vue';
84 import { columns, searchFormSchema } from './config/config.data'; 84 import { columns, searchFormSchema } from './config/config.data';
85 - import { USER_INFO_KEY } from '/@/enums/cacheEnum';  
86 - import { getAuthCache } from '/@/utils/auth';  
87 import { SceneLinkageDrawer } from './components/SceneLinkageDrawer'; 85 import { SceneLinkageDrawer } from './components/SceneLinkageDrawer';
88 import { useMessage } from '/@/hooks/web/useMessage'; 86 import { useMessage } from '/@/hooks/web/useMessage';
89 import { Authority } from '/@/components/Authority'; 87 import { Authority } from '/@/components/Authority';
@@ -91,8 +89,6 @@ @@ -91,8 +89,6 @@
91 import { SceneLinkageDrawerDataType } from './components/SceneLinkageDrawer/type'; 89 import { SceneLinkageDrawerDataType } from './components/SceneLinkageDrawer/type';
92 import { DataActionModeEnum } from '/@/enums/toolEnum'; 90 import { DataActionModeEnum } from '/@/enums/toolEnum';
93 91
94 - const userInfo: any = getAuthCache(USER_INFO_KEY);  
95 - const userId = userInfo.userId;  
96 const { hasPermission } = usePermission(); 92 const { hasPermission } = usePermission();
97 93
98 const [registerDrawer, { openDrawer }] = useDrawer(); 94 const [registerDrawer, { openDrawer }] = useDrawer();
@@ -128,6 +128,13 @@ export const formSchema: FormSchema[] = [ @@ -128,6 +128,13 @@ export const formSchema: FormSchema[] = [
128 }, 128 },
129 }, 129 },
130 { 130 {
  131 + required: true,
  132 + field: 'organizationId',
  133 + label: '所属组织',
  134 + colProps: { span: 24 },
  135 + component: 'OrgTreeSelect',
  136 + },
  137 + {
131 field: 'remark', 138 field: 'remark',
132 label: '说明', 139 label: '说明',
133 component: 'InputTextArea', 140 component: 'InputTextArea',
@@ -8,9 +8,8 @@ @@ -8,9 +8,8 @@
8 8
9 <script> 9 <script>
10 import { defineComponent } from 'vue'; 10 import { defineComponent } from 'vue';
11 - import { useRoute } from 'vue-router'; 11 + import { useRoute, useRouter } from 'vue-router';
12 import { PageWrapper } from '/@/components/Page'; 12 import { PageWrapper } from '/@/components/Page';
13 - import { useGo } from '/@/hooks/web/usePage';  
14 import { Description } from '../../../components/Description'; 13 import { Description } from '../../../components/Description';
15 import { getAccountInfo } from '../../../api/system/system'; 14 import { getAccountInfo } from '../../../api/system/system';
16 import { accountSchema } from './account.detail.data'; 15 import { accountSchema } from './account.detail.data';
@@ -22,7 +21,8 @@ @@ -22,7 +21,8 @@
22 components: { PageWrapper, Description }, 21 components: { PageWrapper, Description },
23 setup() { 22 setup() {
24 const route = useRoute(); 23 const route = useRoute();
25 - const go = useGo(); 24 +
  25 + const ROUTER = useRouter();
26 const { setTitle, close } = useTabs(); 26 const { setTitle, close } = useTabs();
27 const [register, { setDescProps }] = useDescription({ 27 const [register, { setDescProps }] = useDescription({
28 title: '账号基础信息', 28 title: '账号基础信息',
@@ -52,7 +52,8 @@ @@ -52,7 +52,8 @@
52 function goBack() { 52 function goBack() {
53 // 本例的效果时点击返回始终跳转到账号列表页,实际应用时可返回上一页 53 // 本例的效果时点击返回始终跳转到账号列表页,实际应用时可返回上一页
54 close(); 54 close();
55 - go('/system/account'); 55 + ROUTER.go(-1);
  56 + // go('/system/account');
56 } 57 }
57 return { goBack, accountSchema, accountData, register }; 58 return { goBack, accountSchema, accountData, register };
58 }, 59 },
@@ -245,7 +245,10 @@ @@ -245,7 +245,10 @@
245 delete postData.email; 245 delete postData.email;
246 } 246 }
247 } 247 }
248 - await SaveOrUpdateUserInfo(postData as any, unref(isUpdate)); 248 + await SaveOrUpdateUserInfo(
  249 + { ...postData, roleType: 'PLATFORM_ADMIN' } as any,
  250 + unref(isUpdate)
  251 + );
249 closeModal(); 252 closeModal();
250 emit('success'); 253 emit('success');
251 createMessage.success(unref(isUpdate) ? '编辑成功' : '新增成功'); 254 createMessage.success(unref(isUpdate) ? '编辑成功' : '新增成功');
  1 +<template>
  2 + <div>
  3 + <PageWrapper :title="`用户资料`" contentBackground @back="goBack">
  4 + <Description size="middle" @register="register" />
  5 + </PageWrapper>
  6 + </div>
  7 +</template>
  8 +
  9 +<script>
  10 + import { defineComponent } from 'vue';
  11 + import { useRoute } from 'vue-router';
  12 + import { PageWrapper } from '/@/components/Page';
  13 + import { useGo } from '/@/hooks/web/usePage';
  14 + import { Description } from '/@/components/Description';
  15 + import { getAccountInfo } from '/@/api/system/system';
  16 + import { accountSchema } from './account.detail.data';
  17 + import { useDescription } from '/@/components/Description';
  18 + import { useTabs } from '/@/hooks/web/useTabs';
  19 + const accountData = {};
  20 + export default defineComponent({
  21 + name: 'AccountDetail',
  22 + components: { PageWrapper, Description },
  23 + setup() {
  24 + const route = useRoute();
  25 + const go = useGo();
  26 + const { setTitle, close } = useTabs();
  27 + const [register, { setDescProps }] = useDescription({
  28 + title: '账号基础信息',
  29 + data: accountData,
  30 + schema: accountSchema,
  31 + column: 3,
  32 + });
  33 + getAccountInfo(route.params?.id).then((result) => {
  34 + Reflect.set(accountData, 'remark', result.remark);
  35 + Reflect.set(accountData, 'realName', result.realName);
  36 + Reflect.set(accountData, 'phoneNumber', result.phoneNumber);
  37 + Reflect.set(accountData, 'email', result.email);
  38 + Reflect.set(accountData, 'username', result.username);
  39 + Reflect.set(
  40 + accountData,
  41 + 'enabled',
  42 + result.enabled ? '正常' : !result.enabled ? '禁用' : '已过期'
  43 + );
  44 + Reflect.set(accountData, 'accountExpireTime', result.accountExpireTime);
  45 + Reflect.set(accountData, 'createTime', result.createTime);
  46 + Reflect.set(accountData, 'updateTime', result.updateTime);
  47 + // 设置Tab的标题(不会影响页面标题)
  48 + setTitle('详情:用户' + result.realName);
  49 + setDescProps(accountData);
  50 + });
  51 + // 页面左侧点击返回链接时的操作
  52 + function goBack() {
  53 + // 本例的效果时点击返回始终跳转到账号列表页,实际应用时可返回上一页
  54 + close();
  55 + go('/system/account');
  56 + }
  57 + return { goBack, accountSchema, accountData, register };
  58 + },
  59 + });
  60 +</script>
  61 +
  62 +<style scoped>
  63 + .vben-collapse-container {
  64 + background-color: white !important;
  65 + }
  66 +</style>
  1 +<template>
  2 + <BasicModal
  3 + width="650px"
  4 + v-bind="$attrs"
  5 + @register="registerModal"
  6 + :title="getTitle"
  7 + @ok="handleSubmit"
  8 + >
  9 + <div style="height: 50vh">
  10 + <BasicForm @register="registerForm">
  11 + <template #organizationId="{ model, field }">
  12 + <Button type="link" @click="handleOpenCreate" style="padding: 0; z-index: 9999"
  13 + >新增组织
  14 + </Button>
  15 + <BasicTree
  16 + v-if="organizationTreeData.length"
  17 + v-model:value="model[field]"
  18 + :treeData="organizationTreeData"
  19 + :checked-keys="checkGroup"
  20 + :expandedKeys="treeExpandData"
  21 + ref="basicTreeRef"
  22 + @check="handleCheckClick"
  23 + @unSelectAll="handleUnSelectAll"
  24 + @strictlyStatus="handleStrictlyStatus"
  25 + checkable
  26 + toolbar
  27 + @change="handleTreeSelect"
  28 + />
  29 + </template>
  30 + <template #roleSlot="{ model, field }">
  31 + <a-select
  32 + mode="multiple"
  33 + allowClear
  34 + placeholder="请选择角色"
  35 + v-model:value="model[field]"
  36 + @change="handleRoleSelect"
  37 + :options="roleOptions.map((item) => ({ value: item.value, label: item.label }))"
  38 + >
  39 + <template #dropdownRender="{ menuNode: menu }">
  40 + <v-nodes :vnodes="menu" />
  41 + <a-divider style="margin: 4px 0" />
  42 + <div @click="handleOpenRole" style="padding: 4px 0; cursor: pointer">
  43 + <plus-outlined />
  44 + 新增角色
  45 + </div>
  46 + </template>
  47 + </a-select>
  48 + </template>
  49 + </BasicForm>
  50 +
  51 + <OrganizationDrawer @register="registerDrawer" @success="handleReload" />
  52 + </div>
  53 + </BasicModal>
  54 + <RoleDrawer @register="registerRoleDrawer" @success="handleSuccess" />
  55 +</template>
  56 +<script lang="ts">
  57 + import { defineComponent, ref, computed, unref, reactive, onMounted } from 'vue';
  58 + import { BasicModal, useModalInner } from '/@/components/Modal';
  59 + import { BasicForm, useForm } from '/@/components/Form/index';
  60 + import { accountFormSchema } from './account.data';
  61 + import { Button } from 'ant-design-vue';
  62 + import {
  63 + findCurrentUserRelation,
  64 + SaveOrUpdateUserInfo,
  65 + filterRoleList,
  66 + } from '/@/api/system/system';
  67 + import { BasicTree, TreeItem, CheckKeys, CheckEvent } from '/@/components/Tree';
  68 + import { findCurrentUserGroups } from '/@/api/system/group';
  69 + import { RoleOrOrganizationParam } from '/@/api/system/model/systemModel';
  70 + import { useMessage } from '/@/hooks/web/useMessage';
  71 + import { copyTransTreeFun } from '/@/utils/fnUtils';
  72 + import { TOption } from '/@/views/rule/linkedge/config/config.data';
  73 + import { PlusOutlined } from '@ant-design/icons-vue';
  74 + import { useDrawer } from '/@/components/Drawer';
  75 + import RoleDrawer from '../../role/CustomRoleDrawer.vue';
  76 + import OrganizationDrawer from '/@/views/system/organization/OrganizationDrawer.vue';
  77 +
  78 + export default defineComponent({
  79 + name: 'AccountModal',
  80 + components: {
  81 + BasicModal,
  82 + BasicForm,
  83 + Button,
  84 + BasicTree,
  85 + OrganizationDrawer,
  86 + PlusOutlined,
  87 + RoleDrawer,
  88 + VNodes: (_, { attrs }) => {
  89 + return attrs.vnodes;
  90 + },
  91 + },
  92 + emits: ['success', 'register'],
  93 + setup(_, { emit }) {
  94 + const roleOptions = ref<TOption[]>([]);
  95 + const isUpdate = ref(true);
  96 + const rowId = ref('');
  97 + const organizationTreeData = ref<TreeItem[]>([]);
  98 + const basicTreeRef = ref();
  99 + const checkGroup = ref<string[]>([]);
  100 + const treeExpandData = ref([]);
  101 + const olderPhoneNumber = ref();
  102 + const postData = reactive({});
  103 + const singleEditPostPhoneNumber = reactive({
  104 + phoneNumber: '',
  105 + });
  106 + const checkedKeysWithHalfChecked = ref<(string | number)[]>([]);
  107 + const getRoleList = async () => {
  108 + const res = await filterRoleList();
  109 + roleOptions.value = res.map((m) => {
  110 + return {
  111 + label: m.name,
  112 + value: m.id,
  113 + };
  114 + });
  115 + };
  116 + onMounted(async () => {
  117 + await getRoleList();
  118 + });
  119 + const [registerRoleDrawer, { openDrawer }] = useDrawer();
  120 +
  121 + const handleOpenRole = () => {
  122 + openDrawer(true, {
  123 + isUpdate: false,
  124 + });
  125 + };
  126 + const clearValidateByField = (field: string) => {
  127 + clearValidate(field);
  128 + };
  129 + const handleRoleSelect = (e) => {
  130 + if (e?.length > 0) clearValidateByField('roleIds');
  131 + else validateFields(['roleIds']);
  132 + };
  133 + const handleTreeSelect = (e) => {
  134 + if (e) clearValidateByField('organizationIds');
  135 + };
  136 + const handleSuccess = async () => {
  137 + await getRoleList();
  138 + };
  139 + const [
  140 + registerForm,
  141 + {
  142 + setFieldsValue,
  143 + updateSchema,
  144 + resetFields,
  145 + validate,
  146 + getFieldsValue,
  147 + clearValidate,
  148 + validateFields,
  149 + },
  150 + ] = useForm({
  151 + labelWidth: 100,
  152 + schemas: accountFormSchema,
  153 + showActionButtonGroup: false,
  154 + actionColOptions: {
  155 + span: 18,
  156 + },
  157 + });
  158 + //获取所有父级id
  159 + function findForAllId(data = [], arr = []) {
  160 + for (const item of data) {
  161 + arr.push(item.id);
  162 + }
  163 + return arr;
  164 + }
  165 +
  166 + const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
  167 + await resetFields();
  168 + setModalProps({ confirmLoading: false });
  169 + isUpdate.value = !!data?.isUpdate;
  170 + const groupListModel = await findCurrentUserGroups();
  171 + if (!unref(organizationTreeData).length) {
  172 + copyTransTreeFun(groupListModel);
  173 + organizationTreeData.value = groupListModel;
  174 + const getAllIds = findForAllId(organizationTreeData.value as any, []);
  175 + //设置要展开的id
  176 + treeExpandData.value = getAllIds;
  177 + }
  178 + if (unref(isUpdate)) {
  179 + rowId.value = data.record.id;
  180 + const roleParams = new RoleOrOrganizationParam(rowId.value, true, false);
  181 + olderPhoneNumber.value = data.record.phoneNumber;
  182 + singleEditPostPhoneNumber.phoneNumber = data.record.phoneNumber;
  183 + findCurrentUserRelation(roleParams).then((result) => {
  184 + Reflect.set(data.record, 'roleIds', result);
  185 + Reflect.set(data.record, 'password', '******');
  186 + setFieldsValue(data.record);
  187 + });
  188 + const organizationParams = new RoleOrOrganizationParam(rowId.value, false, true);
  189 + checkGroup.value = await findCurrentUserRelation(organizationParams);
  190 + }
  191 + await updateSchema([
  192 + {
  193 + field: 'username',
  194 + dynamicDisabled: unref(isUpdate),
  195 + },
  196 + {
  197 + field: 'password',
  198 + ifShow: !unref(isUpdate),
  199 + },
  200 + ]);
  201 + });
  202 + const getTitle = computed(() => (!unref(isUpdate) ? '新增客户账号' : '编辑客户账号'));
  203 +
  204 + async function handleSubmit() {
  205 + setModalProps({ confirmLoading: true });
  206 + try {
  207 + const { createMessage } = useMessage();
  208 + if (unref(isUpdate)) {
  209 + Object.assign(postData, singleEditPostPhoneNumber);
  210 + }
  211 + const values = await validate([
  212 + 'id',
  213 + 'username',
  214 + 'realName',
  215 + 'password',
  216 + 'roleIds',
  217 + 'email',
  218 + 'accountExpireTime',
  219 + 'enabled',
  220 + 'remark',
  221 + 'organizationIds',
  222 + olderPhoneNumber.value === getFieldsValue().phoneNumber ? '' : 'phoneNumber',
  223 + ]);
  224 + let treeCheckedKeys: string[] | CheckKeys =
  225 + (unref(basicTreeRef)?.getCheckedKeys() as string[] | CheckKeys) || [];
  226 + //fix 取消层级独立后(unref(treeRef)?.getCheckedKeys() as string[])的数据不是数组,是{checked:[],halfChecked:[]}对象,迭代报错
  227 + if (!Array.isArray(treeCheckedKeys)) {
  228 + treeCheckedKeys = treeCheckedKeys?.checked;
  229 + }
  230 + const organizationIds = [
  231 + ...new Set([...unref(checkedKeysWithHalfChecked), ...treeCheckedKeys]),
  232 + ];
  233 + values.accountExpireTime =
  234 + typeof values.accountExpireTime != 'undefined' && values.accountExpireTime != null
  235 + ? values.accountExpireTime.format('YYYY-MM-DD HH:mm:ss')
  236 + : null;
  237 + values.organizationIds = organizationIds;
  238 + Object.assign(postData, values);
  239 + if (unref(isUpdate)) {
  240 + if (values.email == '') {
  241 + delete postData.email;
  242 + }
  243 + } else {
  244 + if (values.email == '') {
  245 + delete postData.email;
  246 + }
  247 + }
  248 + if (!Reflect.get(values, 'accountExpireTime')) {
  249 + Reflect.deleteProperty(postData, 'accountExpireTime');
  250 + }
  251 + await SaveOrUpdateUserInfo(postData as any, unref(isUpdate));
  252 + closeModal();
  253 + emit('success');
  254 + createMessage.success(unref(isUpdate) ? '编辑成功' : '新增成功');
  255 + } finally {
  256 + setTimeout(() => {
  257 + setModalProps({ confirmLoading: false });
  258 + }, 300);
  259 + }
  260 + }
  261 + // 取消全部的时候清除回显时获取的
  262 + const handleUnSelectAll = () => {
  263 + checkedKeysWithHalfChecked.value = [];
  264 + };
  265 +
  266 + const strictlyStatus = ref(false); //层级关联或独立的状态 false为层级关联 true为层级独立
  267 +
  268 + const handleStrictlyStatus = (status) => (strictlyStatus.value = status);
  269 +
  270 + const handleCheckClick = (selectedKeys: CheckKeys, event: CheckEvent) => {
  271 + //fix 取消层级独立后selectedKeys不是数组,是{checked:[],halfChecked:[]}对象 迭代报错
  272 + // 层级独立
  273 + if (strictlyStatus.value) {
  274 + if (!Array.isArray(selectedKeys)) {
  275 + selectedKeys = selectedKeys?.checked;
  276 + event.halfCheckedKeys = [];
  277 + }
  278 + } else {
  279 + // 层级关联
  280 + event.halfCheckedKeys = [];
  281 + }
  282 + checkedKeysWithHalfChecked.value = [
  283 + ...selectedKeys,
  284 + ...(event.halfCheckedKeys as string[]),
  285 + ];
  286 + };
  287 +
  288 + const [registerDrawer, { openDrawer: addOpenDrawer }] = useDrawer();
  289 +
  290 + const handleOpenCreate = () => {
  291 + addOpenDrawer(true, { isUpdate: false });
  292 + };
  293 + const handleReload = async () => {
  294 + const groupListModel = await findCurrentUserGroups();
  295 + copyTransTreeFun(groupListModel);
  296 + organizationTreeData.value = groupListModel;
  297 + };
  298 +
  299 + return {
  300 + registerModal,
  301 + registerForm,
  302 + handleSubmit,
  303 + getTitle,
  304 + organizationTreeData,
  305 + checkGroup,
  306 + basicTreeRef,
  307 + treeExpandData,
  308 + roleOptions,
  309 + registerRoleDrawer,
  310 + handleOpenRole,
  311 + handleSuccess,
  312 + handleRoleSelect,
  313 + handleTreeSelect,
  314 + handleCheckClick,
  315 + handleUnSelectAll,
  316 + handleStrictlyStatus,
  317 + handleOpenCreate,
  318 + registerDrawer,
  319 + handleReload,
  320 + };
  321 + },
  322 + });
  323 +</script>
  324 +<style scoped lang="less">
  325 + :deep(.vben-basic-tree) {
  326 + width: 100% !important;
  327 + margin-top: -28px !important;
  328 + padding: 0;
  329 + }
  330 +
  331 + :deep(.is-unflod) {
  332 + display: none !important;
  333 + }
  334 +
  335 + :deep(.is-flod) {
  336 + display: none !important;
  337 + }
  338 +</style>
  1 +import { isAccountExist, IsPhoneExist } from '/@/api/system/system';
  2 +import { BasicColumn } from '/@/components/Table';
  3 +import { FormSchema } from '/@/components/Table';
  4 +import { emailRule, ChineseRegexp, EmailRegexp, phoneRegexp } from '/@/utils/rules';
  5 +
  6 +let olderPhoneNumber;
  7 +
  8 +export const columns: BasicColumn[] = [
  9 + {
  10 + title: '用户名',
  11 + dataIndex: 'username',
  12 + width: 120,
  13 + },
  14 + {
  15 + title: '姓名',
  16 + dataIndex: 'realName',
  17 + width: 120,
  18 + },
  19 + {
  20 + title: '手机号码',
  21 + dataIndex: 'phoneNumber',
  22 + width: 120,
  23 + },
  24 + {
  25 + title: '邮箱',
  26 + dataIndex: 'email',
  27 + width: 120,
  28 + },
  29 + {
  30 + title: '创建时间',
  31 + dataIndex: 'createTime',
  32 + width: 180,
  33 + },
  34 + {
  35 + title: '状态',
  36 + dataIndex: 'userStatusEnum',
  37 + width: 120,
  38 + slots: { customRender: 'status' },
  39 + },
  40 +];
  41 +
  42 +export const searchFormSchema: FormSchema[] = [
  43 + {
  44 + field: 'username',
  45 + label: '用户名',
  46 + component: 'Input',
  47 + colProps: { span: 8 },
  48 + componentProps: {
  49 + maxLength: 255,
  50 + placeholder: '请输入用户名',
  51 + },
  52 + },
  53 + {
  54 + field: 'realName',
  55 + label: '姓名',
  56 + component: 'Input',
  57 + colProps: { span: 8 },
  58 + componentProps: {
  59 + maxLength: 255,
  60 + placeholder: '请输入姓名',
  61 + },
  62 + },
  63 +];
  64 +
  65 +export const accountFormSchema: FormSchema[] = [
  66 + {
  67 + field: 'id',
  68 + label: 'id',
  69 + component: 'Input',
  70 + show: false,
  71 + componentProps: {
  72 + maxLength: 36,
  73 + },
  74 + },
  75 + {
  76 + field: 'username',
  77 + label: '用户名',
  78 + component: 'Input',
  79 + colProps: { span: 12 },
  80 + dynamicDisabled: false,
  81 + componentProps: {
  82 + maxLength: 36,
  83 + placeholder: '请输入用户名',
  84 + },
  85 + dynamicRules: ({ values }) => {
  86 + return [
  87 + {
  88 + required: true,
  89 + validator(_, value) {
  90 + return new Promise((resolve, reject) => {
  91 + if (value == '') {
  92 + reject('请输入用户名');
  93 + } else if (ChineseRegexp.test(value)) {
  94 + reject('用户名不能含有中文');
  95 + } else if (EmailRegexp.test(value)) {
  96 + reject('用户名不能为电子邮箱格式');
  97 + } else {
  98 + if (values.username != undefined && values.id == undefined) {
  99 + isAccountExist(value).then(({ data }) => {
  100 + if (data != null) {
  101 + reject('用户名已存在');
  102 + } else {
  103 + resolve();
  104 + }
  105 + });
  106 + } else {
  107 + resolve();
  108 + }
  109 + }
  110 + });
  111 + },
  112 + },
  113 + ];
  114 + },
  115 + },
  116 + {
  117 + field: 'password',
  118 + label: '密码',
  119 + component: 'InputPassword',
  120 + required: true,
  121 + colProps: { span: 12 },
  122 + },
  123 + {
  124 + field: 'realName',
  125 + label: '姓名',
  126 + component: 'Input',
  127 + colProps: { span: 12 },
  128 + required: true,
  129 + componentProps: {
  130 + maxLength: 10,
  131 + },
  132 + },
  133 + {
  134 + label: '角色',
  135 + field: 'roleIds',
  136 + component: 'Select',
  137 + colProps: { span: 12 },
  138 + slot: 'roleSlot',
  139 + rules: [
  140 + {
  141 + required: true,
  142 + message: '请选择角色',
  143 + type: 'array',
  144 + },
  145 + ],
  146 + },
  147 + {
  148 + label: '手机号',
  149 + field: 'phoneNumber',
  150 + component: 'Input',
  151 + colProps: { span: 12 },
  152 + dynamicRules: ({ values }) => {
  153 + return [
  154 + {
  155 + required: true,
  156 + validator(_, value) {
  157 + return new Promise((resolve, reject) => {
  158 + if (value == '') {
  159 + reject('请输入手机号');
  160 + } else if (!phoneRegexp.test(value)) {
  161 + reject('请输入正确的手机号');
  162 + } else {
  163 + if (values.phoneNumber != undefined) {
  164 + // 此处可以用防抖函数优化性能
  165 + IsPhoneExist(value).then(({ data }) => {
  166 + if (data != null) {
  167 + reject('手机号已存在');
  168 + } else {
  169 + resolve();
  170 + }
  171 + });
  172 + } else {
  173 + resolve();
  174 + }
  175 + }
  176 + });
  177 + },
  178 + },
  179 + ];
  180 + },
  181 + componentProps({ formActionType }) {
  182 + const { clearValidate } = formActionType;
  183 + return {
  184 + maxlength: 11,
  185 + onChange(value) {
  186 + if (value == olderPhoneNumber) {
  187 + clearValidate('phoneNumber');
  188 + }
  189 + },
  190 + };
  191 + },
  192 + },
  193 + {
  194 + label: '邮箱',
  195 + field: 'email',
  196 + component: 'Input',
  197 + colProps: { span: 12 },
  198 + rules: emailRule,
  199 + },
  200 + {
  201 + field: 'accountExpireTime',
  202 + label: '有效期',
  203 + component: 'DatePicker',
  204 + colProps: { span: 12 },
  205 + componentProps: {
  206 + showTime: true,
  207 + format: 'YYYY-MM-DD HH:mm:ss',
  208 + },
  209 + },
  210 + {
  211 + field: 'enabled',
  212 + label: '状态',
  213 + component: 'RadioButtonGroup',
  214 + colProps: { span: 12 },
  215 + defaultValue: true,
  216 + componentProps: {
  217 + options: [
  218 + { label: '启用', value: true },
  219 + { label: '禁用', value: false },
  220 + ],
  221 + },
  222 + },
  223 + {
  224 + field: 'remark',
  225 + label: '备注',
  226 + component: 'InputTextArea',
  227 + colProps: { span: 24 },
  228 + componentProps: {
  229 + maxLength: 255,
  230 + placeholder: '请输入备注',
  231 + },
  232 + },
  233 + {
  234 + field: 'organizationIds',
  235 + label: '所属组织',
  236 + component: 'Input',
  237 + slot: 'organizationId',
  238 + required: true,
  239 + },
  240 +];
  1 +import { DescItem } from '/@/components/Description';
  2 +
  3 +export const accountSchema: DescItem[] = [
  4 + {
  5 + field: 'realName',
  6 + label: '用户姓名',
  7 + },
  8 + {
  9 + field: 'phoneNumber',
  10 + label: '手机号',
  11 + },
  12 + {
  13 + field: 'email',
  14 + label: '邮箱',
  15 + },
  16 + {
  17 + field: 'username',
  18 + label: '账号',
  19 + },
  20 + {
  21 + field: 'enabled',
  22 + label: '状态',
  23 + },
  24 + {
  25 + field: 'accountExpireTime',
  26 + label: '有效期',
  27 + },
  28 + {
  29 + field: 'createTime',
  30 + label: '创建时间',
  31 + },
  32 + {
  33 + field: 'updateTime',
  34 + label: '更新时间',
  35 + },
  36 + {
  37 + field: 'remark',
  38 + label: '备注',
  39 + },
  40 +];
  1 +<template>
  2 + <div>
  3 + <PageWrapper dense contentFullHeight contentClass="flex">
  4 + <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" />
  5 + <BasicTable
  6 + style="flex: auto"
  7 + :clickToRowSelect="false"
  8 + @register="registerTable"
  9 + class="w-3/4 xl:w-4/5"
  10 + >
  11 + <template #toolbar>
  12 + <Authority value="api:yt:user:post">
  13 + <a-button type="primary" @click="handleCreate">新增客户账号</a-button>
  14 + </Authority>
  15 + <Authority value="api:yt:user:delete">
  16 + <Popconfirm
  17 + title="您确定要批量删除数据"
  18 + ok-text="确定"
  19 + cancel-text="取消"
  20 + @confirm="handleDeleteOrBatchDelete(null)"
  21 + >
  22 + <a-button color="error" :disabled="hasBatchDelete"> 批量删除 </a-button>
  23 + </Popconfirm>
  24 + </Authority>
  25 + </template>
  26 + <template #status="{ record }">
  27 + <Tag
  28 + :color="
  29 + record.userStatusEnum === 'NORMAL'
  30 + ? 'green'
  31 + : record.userStatusEnum === 'DISABLED'
  32 + ? 'red'
  33 + : 'orange'
  34 + "
  35 + >
  36 + {{
  37 + record.userStatusEnum === 'NORMAL'
  38 + ? '正常'
  39 + : record.userStatusEnum === 'DISABLED'
  40 + ? '已禁用'
  41 + : '已过期'
  42 + }}
  43 + </Tag>
  44 + </template>
  45 + <template #action="{ record }">
  46 + <TableAction
  47 + :actions="[
  48 + {
  49 + label: '进入',
  50 + icon: 'ant-design:login-outlined',
  51 + tooltip: `以${!isAdmin(role) ? '客户' : '平台'}用户身份登录`,
  52 + onClick: handleLoginCustomAdmin.bind(null, record),
  53 + },
  54 + {
  55 + label: '用户详情',
  56 + auth: 'api:yt:user:get',
  57 + icon: 'clarity:info-standard-line',
  58 + tooltip: '用户详情',
  59 + onClick: handleView.bind(null, record),
  60 + ifShow: record.level != 0,
  61 + },
  62 + {
  63 + label: '编辑',
  64 + auth: 'api:yt:user:update',
  65 + icon: 'clarity:note-edit-line',
  66 + tooltip: '编辑',
  67 + onClick: handleEdit.bind(null, record),
  68 + ifShow: record.level != 0,
  69 + },
  70 + ]"
  71 + :drop-down-actions="[
  72 + {
  73 + label: '删除',
  74 + auth: 'api:yt:user:delete',
  75 + icon: 'ant-design:delete-outlined',
  76 + color: 'error',
  77 + tooltip: '删除',
  78 + ifShow: record.level != 0,
  79 + popConfirm: {
  80 + title: '是否确认删除',
  81 + confirm: handleDeleteOrBatchDelete.bind(null, record),
  82 + },
  83 + },
  84 + {
  85 + label: '清除密码',
  86 + auth: 'api:yt:user:resetPassword',
  87 + icon: 'ant-design:delete-outlined',
  88 + color: 'error',
  89 + tooltip: '清除密码',
  90 + popConfirm: {
  91 + title: '是否确认清除密码',
  92 + confirm: handleClearPassword.bind(null, record),
  93 + },
  94 + },
  95 + ]"
  96 + />
  97 + </template>
  98 + </BasicTable>
  99 + <AccountModal @register="registerModal" @success="handleSuccess" />
  100 + </PageWrapper>
  101 + </div>
  102 +</template>
  103 +<script lang="ts">
  104 + import { defineComponent, reactive, nextTick } from 'vue';
  105 + import { BasicTable, useTable, TableAction } from '/@/components/Table';
  106 + import { deleteUser, getAccountList } from '/@/api/system/system';
  107 + import { PageWrapper } from '/@/components/Page';
  108 + import { useResetOrganizationTree, OrganizationIdTree } from '/@/views/common/organizationIdTree';
  109 + import { Tag, Popconfirm } from 'ant-design-vue';
  110 + import { useModal } from '/@/components/Modal';
  111 + import AccountModal from './AccountModal.vue';
  112 + import { columns, searchFormSchema } from './account.data';
  113 + import { useGo } from '/@/hooks/web/usePage';
  114 + import { useBatchDelete } from '/@/hooks/web/useBatchDelete';
  115 + import { Authority } from '/@/components/Authority';
  116 + import { getAuthCache } from '/@/utils/auth';
  117 + import { USER_INFO_KEY } from '/@/enums/cacheEnum';
  118 + import { isAdmin } from '/@/enums/roleEnum';
  119 + import { TenantListItemRecord } from '/@/api/tenant/tenantInfo';
  120 + import { useFastEnter } from '/@/hooks/business/useFastEnter';
  121 + import { clearUserPassword } from '/@/api/system/system';
  122 + import { useMessage } from '/@/hooks/web/useMessage';
  123 +
  124 + export default defineComponent({
  125 + name: 'AccountManagement',
  126 + components: {
  127 + BasicTable,
  128 + PageWrapper,
  129 + OrganizationIdTree,
  130 + AccountModal,
  131 + TableAction,
  132 + Tag,
  133 + Authority,
  134 + Popconfirm,
  135 + },
  136 + setup() {
  137 + const { createMessage } = useMessage();
  138 + const userInfo: any = getAuthCache(USER_INFO_KEY);
  139 + const role: string = userInfo?.roles[0];
  140 +
  141 + const go = useGo();
  142 + const [registerModal, { openModal }] = useModal();
  143 + let searchInfo = reactive<Recordable>({});
  144 + const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);
  145 + const [registerTable, { reload, setProps }] = useTable({
  146 + title: '账号列表',
  147 + api: getAccountList,
  148 + rowKey: 'id',
  149 + columns,
  150 + searchInfo,
  151 + formConfig: {
  152 + labelWidth: 120,
  153 + schemas: searchFormSchema,
  154 + // autoSubmitOnEnter: true,
  155 + resetFunc: resetFn,
  156 + },
  157 + useSearchForm: true,
  158 + showTableSetting: true,
  159 + bordered: true,
  160 + actionColumn: {
  161 + width: 240,
  162 + title: '操作',
  163 + dataIndex: 'action',
  164 + slots: { customRender: 'action' },
  165 + fixed: 'right',
  166 + },
  167 + });
  168 + const { hasBatchDelete, handleDeleteOrBatchDelete, selectionOptions } = useBatchDelete(
  169 + deleteUser,
  170 + handleSuccess,
  171 + setProps
  172 + );
  173 + nextTick(() => {
  174 + setProps(selectionOptions);
  175 + });
  176 +
  177 + function handleCreate() {
  178 + openModal(true, {
  179 + isUpdate: false,
  180 + });
  181 + }
  182 + function handleEdit(record: Recordable) {
  183 + openModal(true, {
  184 + record,
  185 + isUpdate: true,
  186 + });
  187 + }
  188 + function handleSuccess() {
  189 + reload();
  190 + }
  191 +
  192 + function handleSelect(organization) {
  193 + searchInfo.organizationId = organization;
  194 + reload();
  195 + }
  196 +
  197 + function handleView(record: Recordable) {
  198 + go('/system/account_detail/' + record.id);
  199 + }
  200 +
  201 + async function handleLoginCustomAdmin(record: TenantListItemRecord) {
  202 + try {
  203 + useFastEnter(record, go);
  204 + } catch (error) {
  205 + } finally {
  206 + }
  207 + }
  208 +
  209 + const handleClearPassword = async (record: Recordable) => {
  210 + const { id } = record;
  211 + if (!id) return;
  212 + const { message } = await clearUserPassword(id, 3);
  213 + createMessage.success(message);
  214 + };
  215 +
  216 + return {
  217 + handleLoginCustomAdmin,
  218 + registerTable,
  219 + registerModal,
  220 + handleCreate,
  221 + handleEdit,
  222 + handleSuccess,
  223 + handleSelect,
  224 + handleView,
  225 + organizationIdTreeRef,
  226 + hasBatchDelete,
  227 + handleDeleteOrBatchDelete,
  228 + isAdmin,
  229 + role,
  230 + handleClearPassword,
  231 + };
  232 + },
  233 + });
  234 +</script>
@@ -209,7 +209,7 @@ @@ -209,7 +209,7 @@
209 const handleClearPassword = async (record: Recordable) => { 209 const handleClearPassword = async (record: Recordable) => {
210 const { id } = record; 210 const { id } = record;
211 if (!id) return; 211 if (!id) return;
212 - const { message } = await clearUserPassword(id); 212 + const { message } = await clearUserPassword(id, 1);
213 createMessage.success(message); 213 createMessage.success(message);
214 }; 214 };
215 215
  1 +<template>
  2 + <BasicDrawer
  3 + v-bind="$attrs"
  4 + @register="registerDrawer"
  5 + showFooter
  6 + :title="getTitle"
  7 + width="500px"
  8 + @ok="handleSubmit"
  9 + >
  10 + <BasicForm @register="registerForm">
  11 + <template #menu>
  12 + <Spin :spinning="spinning">
  13 + <BasicTree
  14 + v-if="treeData.length"
  15 + checkable
  16 + toolbar
  17 + ref="treeRef"
  18 + :treeData="treeData"
  19 + @check="handleCheckClick"
  20 + :replace-fields="{ title: 'name', key: 'id' }"
  21 + :checkedKeys="roleMenus"
  22 + title="菜单分配"
  23 + />
  24 + </Spin>
  25 + </template>
  26 + </BasicForm>
  27 + </BasicDrawer>
  28 +</template>
  29 +<script lang="ts">
  30 + import { defineComponent, ref, computed, unref, nextTick } from 'vue';
  31 + import { BasicForm, useForm } from '/@/components/Form/index';
  32 + import { formSchema, KeysTypeEnum, RoleMenuDictEnum } from './role.data';
  33 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  34 + import { BasicTree, TreeActionType, TreeItem } from '/@/components/Tree';
  35 + import { useMessage } from '/@/hooks/web/useMessage';
  36 + const { t } = useI18n(); // 加载国际化
  37 + // 加载菜单数据
  38 + import { getMeMenuList, getMenusIdsByRoleId } from '/@/api/sys/menu';
  39 + import { useI18n } from '/@/hooks/web/useI18n';
  40 + import { MenuRecord } from '/@/api/sys/model/menuModel';
  41 + import { saveOrUpdateRoleInfoWithMenu } from '/@/api/system/system';
  42 + import { findDictItemByCode } from '/@/api/system/dict';
  43 + import { RoleEnum } from '/@/enums/roleEnum';
  44 + import { Spin } from 'ant-design-vue';
  45 + import { useRole } from '/@/hooks/business/useRole';
  46 + import { RoleListItem } from '/@/api/system/model/systemModel';
  47 +
  48 + type TreeData = MenuRecord & TreeItem;
  49 +
  50 + export default defineComponent({
  51 + name: 'RoleDrawer',
  52 + components: { BasicDrawer, BasicForm, BasicTree, Spin },
  53 + emits: ['success', 'register'],
  54 + setup(_, { emit }) {
  55 + const isUpdate = ref<boolean>(true);
  56 + const treeData = ref<TreeData[]>([]);
  57 + const roleMenus = ref<string[]>([]);
  58 + const roleId = ref<string>('');
  59 + const treeRef = ref<Nullable<TreeActionType>>();
  60 + const checked = ref<string[]>([]); //需要选中的节点
  61 + const spinning = ref(false);
  62 + const checkedKeysWithHalfChecked = ref<string[]>([]);
  63 +
  64 + const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
  65 + labelWidth: 100,
  66 + schemas: formSchema,
  67 + showActionButtonGroup: false,
  68 + });
  69 +
  70 + const transformName = (data: TreeData[]) => {
  71 + return data.map((item) => {
  72 + item.name = t(item.name);
  73 + if (item.children && item.children.length) {
  74 + item.children = transformName(item.children as unknown as TreeData[]);
  75 + }
  76 + return item;
  77 + });
  78 + };
  79 +
  80 + const { isTenantAdmin, isSysadmin, getRole } = useRole();
  81 + const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(
  82 + async (data: { isUpdate: boolean; record: RoleListItem }) => {
  83 + resetFields();
  84 + roleId.value = '';
  85 + // 在打开弹窗时清除所有选择的菜单
  86 + treeRef.value && treeRef.value?.setCheckedKeys([]);
  87 + isUpdate.value = data.isUpdate;
  88 + let roleType = unref(getRole) || RoleEnum.SYS_ADMIN;
  89 +
  90 + // 租户管理员创建角色时 菜单分配为客户菜单
  91 + if (unref(isTenantAdmin)) {
  92 + roleType = RoleEnum.TENANT_ADMIN;
  93 + }
  94 +
  95 + try {
  96 + spinning.value = true;
  97 + // 需要在setFieldsValue之前先填充treeData,否则Tree组件可能会报key not exist警告
  98 +
  99 + // if (!unref(treeData).length) {
  100 + // 获取全部的菜单
  101 + const menuListModel = await getMeMenuList();
  102 + treeData.value = transformName(menuListModel as unknown as TreeData[]);
  103 + // }
  104 +
  105 + const keys = await getPermissionByRole(roleType);
  106 + const { keyType } = RoleMenuDictEnum[roleType];
  107 + treeData.value = filterPermissionTreeData(
  108 + unref(treeData) as unknown as TreeData[],
  109 + keys,
  110 + keyType
  111 + );
  112 +
  113 + // 如果编辑的是超级管理员 则不再过滤平台管理员禁用的权限
  114 + // 如果是超级管理员创建角色 创建角色属于平台管理员 因此过滤平台管理员禁用的权限
  115 + if (data?.record?.roleType !== RoleEnum.SYS_ADMIN && unref(isSysadmin)) {
  116 + const keys = await getPermissionByRole(RoleEnum.PLATFORM_ADMIN);
  117 + const { keyType } = RoleMenuDictEnum[RoleEnum.PLATFORM_ADMIN];
  118 + treeData.value = filterPermissionTreeData(
  119 + unref(treeData) as unknown as TreeData[],
  120 + keys,
  121 + keyType
  122 + );
  123 + }
  124 +
  125 + // 更新
  126 + if (unref(isUpdate)) {
  127 + checked.value = [];
  128 + roleId.value = data.record.id;
  129 +
  130 + //通过角色id去获取角色对应的菜单的ids
  131 + checkedKeysWithHalfChecked.value = roleMenus.value = await getMenusIdsByRoleId(
  132 + data.record.id
  133 + );
  134 + excludeHalfCheckedKeys(unref(treeData));
  135 + await nextTick();
  136 + unref(treeRef)?.setCheckedKeys(roleMenus.value);
  137 + setFieldsValue(data.record);
  138 + } else {
  139 + }
  140 + } catch (error) {
  141 + throw error;
  142 + } finally {
  143 + spinning.value = false;
  144 + }
  145 + }
  146 + );
  147 +
  148 + const getTitle = computed(() => (!unref(isUpdate) ? '新增角色' : '编辑角色'));
  149 +
  150 + async function handleSubmit() {
  151 + setDrawerProps({ confirmLoading: true });
  152 + const { createMessage } = useMessage();
  153 + try {
  154 + const values = await validate();
  155 + const treeCheckedKeys: string[] = (unref(treeRef)?.getCheckedKeys() as string[]) || [];
  156 + const menu = [...new Set([...unref(checkedKeysWithHalfChecked), ...treeCheckedKeys])];
  157 + const req = {
  158 + id: roleId.value,
  159 + name: values.name,
  160 + remark: values.remark,
  161 + status: values.status,
  162 + menu,
  163 + roleType: 'TENANT_ADMIN',
  164 + };
  165 + if (req.menu == undefined) return createMessage.error('请勾选权限菜单');
  166 + saveOrUpdateRoleInfoWithMenu(req).then(() => {
  167 + closeDrawer();
  168 + emit('success');
  169 + createMessage.success(`${unref(isUpdate) ? '编辑' : '新增'}成功`);
  170 + });
  171 + } finally {
  172 + setTimeout(() => {
  173 + setDrawerProps({ confirmLoading: false });
  174 + }, 300);
  175 + }
  176 + }
  177 +
  178 + const getPermissionByRole = async (roleType: RoleEnum) => {
  179 + try {
  180 + const { key } = RoleMenuDictEnum[roleType];
  181 + const res = await findDictItemByCode({ dictCode: key });
  182 + return res.map((item) => item.itemValue);
  183 + } catch (error) {}
  184 + return [];
  185 + };
  186 +
  187 + const filterPermissionTreeData = (
  188 + data: MenuRecord[],
  189 + permissionKeys: string[],
  190 + keysType: KeysTypeEnum
  191 + ): TreeData[] => {
  192 + const permissionCompare = (
  193 + data: MenuRecord[],
  194 + permissionKeys: string[],
  195 + keysType: KeysTypeEnum
  196 + ): TreeData[] => {
  197 + return data.filter((item) => {
  198 + item.name = t(item.name);
  199 + const findFlag = permissionKeys.includes(item.permission);
  200 +
  201 + item.show = findFlag ? keysType === KeysTypeEnum.ENABLED : undefined;
  202 +
  203 + if (item.children && item.children.length) {
  204 + if (item.show) return true;
  205 + if (item.show === undefined) {
  206 + item.children = permissionCompare(item.children, permissionKeys, keysType);
  207 + item.show = item.children.some((item) =>
  208 + keysType === KeysTypeEnum.ENABLED
  209 + ? item.show
  210 + : item.show === undefined
  211 + ? true
  212 + : item.show
  213 + );
  214 + return item.show;
  215 + }
  216 + }
  217 +
  218 + return keysType === KeysTypeEnum.ENABLED
  219 + ? item.show
  220 + : item.show === undefined
  221 + ? true
  222 + : item.show;
  223 + }) as unknown as TreeData[];
  224 + };
  225 +
  226 + return permissionCompare(data, permissionKeys, keysType);
  227 + };
  228 +
  229 + const excludeHalfCheckedKeys = (treeData: MenuRecord[]) => {
  230 + const needExcludeKeys: string[] = [];
  231 + const query = (data: MenuRecord[]) => {
  232 + data.forEach((item) => {
  233 + item.checked = roleMenus.value.includes(item.id);
  234 + if (item.children && item.children.length) {
  235 + query(item.children);
  236 + item.checked = item.children.every((item) => item.checked);
  237 + }
  238 + if (!item.checked) {
  239 + needExcludeKeys.push(item.id);
  240 + }
  241 + });
  242 + };
  243 + query(treeData);
  244 + roleMenus.value = unref(roleMenus).filter((key) => !needExcludeKeys.includes(key));
  245 + return needExcludeKeys;
  246 + };
  247 +
  248 + const handleCheckClick = (selectedKeys: string[], event: CheckEvent) => {
  249 + checkedKeysWithHalfChecked.value = [
  250 + ...selectedKeys,
  251 + ...(event.halfCheckedKeys as string[]),
  252 + ];
  253 + };
  254 +
  255 + return {
  256 + spinning,
  257 + registerDrawer,
  258 + registerForm,
  259 + getTitle,
  260 + handleSubmit,
  261 + treeData,
  262 + roleMenus,
  263 + treeRef,
  264 + handleCheckClick,
  265 + };
  266 + },
  267 + });
  268 +</script>
  269 +
  270 +<style scoped lang="less">
  271 + :deep(.vben-basic-tree) {
  272 + width: 100% !important;
  273 + }
  274 +
  275 + :deep(.is-unflod) {
  276 + display: none !important;
  277 + }
  278 +
  279 + :deep(.is-flod) {
  280 + display: none !important;
  281 + }
  282 +</style>
  1 +<template>
  2 + <BasicModal
  3 + width="650px"
  4 + v-bind="$attrs"
  5 + @register="registerModal"
  6 + :title="getTitle"
  7 + @ok="handleSubmit"
  8 + >
  9 + <div style="height: 40vh">
  10 + <BasicForm @register="registerForm">
  11 + <template #organizationId="{ model, field }">
  12 + <Button type="link" @click="handleOpenCreate" style="padding: 0; z-index: 9999"
  13 + >新增组织
  14 + </Button>
  15 + <BasicTree
  16 + v-if="organizationTreeData.length"
  17 + v-model:value="model[field]"
  18 + :treeData="organizationTreeData"
  19 + :checked-keys="checkGroup"
  20 + :expandedKeys="treeExpandData"
  21 + ref="basicTreeRef"
  22 + @check="handleCheckClick"
  23 + @unSelectAll="handleUnSelectAll"
  24 + @strictlyStatus="handleStrictlyStatus"
  25 + checkable
  26 + toolbar
  27 + @change="handleTreeSelect"
  28 + />
  29 + </template>
  30 + <template #roleSlot="{ model, field }">
  31 + <a-select
  32 + mode="multiple"
  33 + allowClear
  34 + placeholder="请选择角色"
  35 + v-model:value="model[field]"
  36 + @change="handleRoleSelect"
  37 + :options="roleOptions.map((item) => ({ value: item.value, label: item.label }))"
  38 + >
  39 + <template #dropdownRender="{ menuNode: menu }">
  40 + <v-nodes :vnodes="menu" />
  41 + <a-divider style="margin: 4px 0" />
  42 + <div @click="handleOpenRole" style="padding: 4px 0; cursor: pointer">
  43 + <plus-outlined />
  44 + 新增角色
  45 + </div>
  46 + </template>
  47 + </a-select>
  48 + </template>
  49 + </BasicForm>
  50 +
  51 + <OrganizationDrawer @register="registerDrawer" @success="handleReload" />
  52 + </div>
  53 + </BasicModal>
  54 + <RoleDrawer @register="registerRoleDrawer" @success="handleSuccess" />
  55 +</template>
  56 +<script lang="ts">
  57 + import { defineComponent, ref, computed, unref, reactive, onMounted } from 'vue';
  58 + import { BasicModal, useModalInner } from '/@/components/Modal';
  59 + import { BasicForm, useForm } from '/@/components/Form/index';
  60 + import { accountFormSchema } from './config';
  61 + import { Button } from 'ant-design-vue';
  62 + import { findCurrentUserRelation, filterRoleList } from '/@/api/system/system';
  63 + import { addTenantList } from '/@/api/system/account';
  64 + import { BasicTree, TreeItem, CheckKeys, CheckEvent } from '/@/components/Tree';
  65 + import { findCurrentUserGroups } from '/@/api/system/group';
  66 + import { RoleOrOrganizationParam } from '/@/api/system/model/systemModel';
  67 + import { useMessage } from '/@/hooks/web/useMessage';
  68 + import { copyTransTreeFun } from '/@/utils/fnUtils';
  69 + import { TOption } from '/@/views/rule/linkedge/config/config.data';
  70 + import { PlusOutlined } from '@ant-design/icons-vue';
  71 + import { useDrawer } from '/@/components/Drawer';
  72 + import RoleDrawer from './RoleDrawer.vue';
  73 + import OrganizationDrawer from '/@/views/system/organization/OrganizationDrawer.vue';
  74 + import { useUserStore } from '/@/store/modules/user';
  75 + import { IsPhoneExist } from '/@/api/system/system';
  76 + import { phoneRegexp } from '/@/utils/rules';
  77 +
  78 + export default defineComponent({
  79 + name: 'TenantModal',
  80 + components: {
  81 + BasicModal,
  82 + BasicForm,
  83 + Button,
  84 + BasicTree,
  85 + OrganizationDrawer,
  86 + PlusOutlined,
  87 + RoleDrawer,
  88 + VNodes: (_, { attrs }) => {
  89 + return attrs.vnodes;
  90 + },
  91 + },
  92 + emits: ['success', 'register'],
  93 + setup(_, { emit }) {
  94 + const [registerRoleDrawer, { openDrawer }] = useDrawer();
  95 + const { createMessage } = useMessage();
  96 + const userInfo = useUserStore();
  97 +
  98 + const roleOptions = ref<TOption[]>([]);
  99 + const isAdd = ref(true);
  100 + const rowId = ref('');
  101 + const organizationTreeData = ref<TreeItem[]>([]);
  102 + const basicTreeRef = ref();
  103 + const checkGroup = ref<string[]>([]);
  104 + const treeExpandData = ref([]);
  105 + const olderPhoneNumber = ref();
  106 + const singleEditPostPhoneNumber = reactive({
  107 + phoneNumber: '',
  108 + });
  109 + const checkedKeysWithHalfChecked = ref<(string | number)[]>([]);
  110 + const getRoleList = async () => {
  111 + const res = await filterRoleList({ roleType: 'TENANT_ADMIN' });
  112 + roleOptions.value = res.map((m) => {
  113 + return {
  114 + label: m.name,
  115 + value: m.id,
  116 + };
  117 + });
  118 + };
  119 +
  120 + onMounted(async () => {
  121 + await getRoleList();
  122 + });
  123 + const handleOpenRole = () => {
  124 + openDrawer(true, {
  125 + isAdd: false,
  126 + });
  127 + };
  128 + const clearValidateByField = (field: string) => {
  129 + clearValidate(field);
  130 + };
  131 + const handleRoleSelect = (e) => {
  132 + if (e?.length > 0) clearValidateByField('roleIds');
  133 + else validateFields(['roleIds']);
  134 + };
  135 + const handleTreeSelect = (e) => {
  136 + if (e) clearValidateByField('organizationIds');
  137 + };
  138 + const handleSuccess = async () => {
  139 + await getRoleList();
  140 + };
  141 + const [
  142 + registerForm,
  143 + {
  144 + setFieldsValue,
  145 + updateSchema,
  146 + resetFields,
  147 + validate,
  148 + getFieldsValue,
  149 + clearValidate,
  150 + validateFields,
  151 + },
  152 + ] = useForm({
  153 + labelWidth: 100,
  154 + schemas: accountFormSchema,
  155 + showActionButtonGroup: false,
  156 + actionColOptions: {
  157 + span: 18,
  158 + },
  159 + });
  160 + //获取所有父级id
  161 + function findForAllId(data = [], arr = []) {
  162 + for (const item of data) {
  163 + arr.push(item.id);
  164 + }
  165 + return arr;
  166 + }
  167 +
  168 + const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
  169 + await resetFields();
  170 + setModalProps({ confirmLoading: false });
  171 + isAdd.value = !!data?.isAdd;
  172 + const groupListModel = await findCurrentUserGroups();
  173 + if (!unref(organizationTreeData).length) {
  174 + copyTransTreeFun(groupListModel);
  175 + organizationTreeData.value = groupListModel;
  176 + const getAllIds = findForAllId(organizationTreeData.value as any, []);
  177 + //设置要展开的id
  178 + treeExpandData.value = getAllIds;
  179 + }
  180 +
  181 + if (!unref(isAdd)) {
  182 + rowId.value = data.record.id;
  183 + const roleParams = new RoleOrOrganizationParam(rowId.value, true, false);
  184 + olderPhoneNumber.value = data.record.phoneNumber;
  185 + singleEditPostPhoneNumber.phoneNumber = data.record.phoneNumber;
  186 + findCurrentUserRelation(roleParams).then((result) => {
  187 + Reflect.set(data.record, 'roleIds', result);
  188 + setFieldsValue(data.record);
  189 + });
  190 + updateSchema([
  191 + {
  192 + field: 'phoneNumber',
  193 + dynamicRules: () => {
  194 + return [
  195 + {
  196 + required: true,
  197 + validator(_, value) {
  198 + return new Promise((resolve, reject) => {
  199 + if (value == '') {
  200 + reject('请输入手机号');
  201 + } else if (!phoneRegexp.test(value)) {
  202 + reject('请输入正确的手机号');
  203 + } else {
  204 + resolve();
  205 + }
  206 + });
  207 + },
  208 + },
  209 + ];
  210 + },
  211 + },
  212 + ]);
  213 + const organizationParams = new RoleOrOrganizationParam(rowId.value, false, true);
  214 + checkGroup.value = await findCurrentUserRelation(organizationParams);
  215 + } else {
  216 + updateSchema([
  217 + {
  218 + field: 'phoneNumber',
  219 + dynamicRules: ({ values }) => {
  220 + return [
  221 + {
  222 + required: true,
  223 + validator(_, value) {
  224 + return new Promise((resolve, reject) => {
  225 + if (value == '') {
  226 + reject('请输入手机号');
  227 + } else if (!phoneRegexp.test(value)) {
  228 + reject('请输入正确的手机号');
  229 + } else {
  230 + if (values.phoneNumber != undefined) {
  231 + // 此处可以用防抖函数优化性能
  232 + IsPhoneExist(value).then(({ data }) => {
  233 + if (data != null) {
  234 + reject('手机号已存在');
  235 + } else {
  236 + resolve();
  237 + }
  238 + });
  239 + } else {
  240 + resolve();
  241 + }
  242 + }
  243 + });
  244 + },
  245 + },
  246 + ];
  247 + },
  248 + },
  249 + ]);
  250 + }
  251 + await updateSchema([
  252 + {
  253 + field: 'username',
  254 + dynamicDisabled: !unref(isAdd),
  255 + },
  256 + ]);
  257 + });
  258 + const getTitle = computed(() => (unref(isAdd) ? '新增管理员账号' : '编辑管理员账号'));
  259 +
  260 + async function handleSubmit() {
  261 + setModalProps({ confirmLoading: true });
  262 + try {
  263 + const values = getFieldsValue();
  264 + if (!('organizationIds' in values)) {
  265 + createMessage.error('组织必选');
  266 + }
  267 + await validate();
  268 + await addTenantList({ ...values, level: 4, tenantId: userInfo.getUserInfo.tenantId! });
  269 + createMessage.success(unref(isAdd) ? '新增成功' : '编辑成功');
  270 + closeModal();
  271 + emit('success');
  272 + } finally {
  273 + setModalProps({ confirmLoading: false });
  274 + }
  275 + }
  276 + // 取消全部的时候清除回显时获取的
  277 + const handleUnSelectAll = () => {
  278 + checkedKeysWithHalfChecked.value = [];
  279 + };
  280 +
  281 + const strictlyStatus = ref(false); //层级关联或独立的状态 false为层级关联 true为层级独立
  282 +
  283 + const handleStrictlyStatus = (status) => (strictlyStatus.value = status);
  284 +
  285 + const handleCheckClick = (selectedKeys: CheckKeys, event: CheckEvent) => {
  286 + //fix 取消层级独立后selectedKeys不是数组,是{checked:[],halfChecked:[]}对象 迭代报错
  287 + // 层级独立
  288 + if (strictlyStatus.value) {
  289 + if (!Array.isArray(selectedKeys)) {
  290 + selectedKeys = selectedKeys?.checked;
  291 + event.halfCheckedKeys = [];
  292 + }
  293 + } else {
  294 + // 层级关联
  295 + event.halfCheckedKeys = [];
  296 + }
  297 + checkedKeysWithHalfChecked.value = [
  298 + ...selectedKeys,
  299 + ...(event.halfCheckedKeys as string[]),
  300 + ];
  301 + };
  302 +
  303 + const [registerDrawer, { openDrawer: addOpenDrawer }] = useDrawer();
  304 +
  305 + const handleOpenCreate = () => {
  306 + addOpenDrawer(true, { isAdd: false });
  307 + };
  308 + const handleReload = async () => {
  309 + const groupListModel = await findCurrentUserGroups();
  310 + copyTransTreeFun(groupListModel);
  311 + organizationTreeData.value = groupListModel;
  312 + };
  313 +
  314 + return {
  315 + registerModal,
  316 + registerForm,
  317 + handleSubmit,
  318 + getTitle,
  319 + organizationTreeData,
  320 + checkGroup,
  321 + basicTreeRef,
  322 + treeExpandData,
  323 + roleOptions,
  324 + registerRoleDrawer,
  325 + handleOpenRole,
  326 + handleSuccess,
  327 + handleRoleSelect,
  328 + handleTreeSelect,
  329 + handleCheckClick,
  330 + handleUnSelectAll,
  331 + handleStrictlyStatus,
  332 + handleOpenCreate,
  333 + registerDrawer,
  334 + handleReload,
  335 + };
  336 + },
  337 + });
  338 +</script>
  339 +<style scoped lang="less">
  340 + :deep(.vben-basic-tree) {
  341 + width: 100% !important;
  342 + margin-top: -28px !important;
  343 + padding: 0;
  344 + }
  345 +
  346 + :deep(.is-unflod) {
  347 + display: none !important;
  348 + }
  349 +
  350 + :deep(.is-flod) {
  351 + display: none !important;
  352 + }
  353 +</style>
  1 +import { IsPhoneExist, isAccountExist } from '/@/api/system/system';
  2 +import { BasicColumn, FormSchema } from '/@/components/Table';
  3 +import { ChineseRegexp, EmailRegexp, emailRule, phoneRegexp } from '/@/utils/rules';
  4 +
  5 +let olderPhoneNumber;
  6 +
  7 +export const columns: BasicColumn[] = [
  8 + {
  9 + title: '用户名',
  10 + dataIndex: 'username',
  11 + width: 120,
  12 + },
  13 + {
  14 + title: '姓名',
  15 + dataIndex: 'realName',
  16 + width: 120,
  17 + },
  18 + {
  19 + title: '手机号码',
  20 + dataIndex: 'phoneNumber',
  21 + width: 120,
  22 + },
  23 + {
  24 + title: '邮箱',
  25 + dataIndex: 'email',
  26 + width: 120,
  27 + },
  28 + {
  29 + title: '状态',
  30 + dataIndex: 'userStatusEnum',
  31 + width: 120,
  32 + slots: { customRender: 'status' },
  33 + },
  34 +];
  35 +
  36 +export const searchFormSchema: FormSchema[] = [
  37 + {
  38 + field: 'username',
  39 + label: '用户名',
  40 + component: 'Input',
  41 + colProps: { span: 8 },
  42 + componentProps: {
  43 + maxLength: 255,
  44 + placeholder: '请输入用户名',
  45 + },
  46 + },
  47 + {
  48 + field: 'realName',
  49 + label: '姓名',
  50 + component: 'Input',
  51 + colProps: { span: 8 },
  52 + componentProps: {
  53 + maxLength: 255,
  54 + placeholder: '请输入姓名',
  55 + },
  56 + },
  57 +];
  58 +
  59 +export const accountFormSchema: FormSchema[] = [
  60 + {
  61 + field: 'id',
  62 + label: 'id',
  63 + component: 'Input',
  64 + show: false,
  65 + componentProps: {
  66 + maxLength: 36,
  67 + },
  68 + },
  69 + {
  70 + field: 'username',
  71 + label: '用户名',
  72 + component: 'Input',
  73 + colProps: { span: 12 },
  74 + dynamicDisabled: false,
  75 + componentProps: {
  76 + maxLength: 36,
  77 + placeholder: '请输入用户名',
  78 + },
  79 + dynamicRules: ({ values }) => {
  80 + return [
  81 + {
  82 + required: true,
  83 + validator(_, value) {
  84 + return new Promise((resolve, reject) => {
  85 + if (value == '') {
  86 + reject('请输入用户名');
  87 + } else if (ChineseRegexp.test(value)) {
  88 + reject('用户名不能含有中文');
  89 + } else if (EmailRegexp.test(value)) {
  90 + reject('用户名不能为电子邮箱格式');
  91 + } else {
  92 + if (values.username != undefined && values.id == undefined) {
  93 + isAccountExist(value).then(({ data }) => {
  94 + if (data != null) {
  95 + reject('用户名已存在');
  96 + } else {
  97 + resolve();
  98 + }
  99 + });
  100 + } else {
  101 + resolve();
  102 + }
  103 + }
  104 + });
  105 + },
  106 + },
  107 + ];
  108 + },
  109 + },
  110 + // {
  111 + // field: 'password',
  112 + // label: '密码',
  113 + // component: 'InputPassword',
  114 + // required: true,
  115 + // colProps: { span: 12 },
  116 + // },
  117 + {
  118 + field: 'realName',
  119 + label: '真实姓名',
  120 + component: 'Input',
  121 + colProps: { span: 12 },
  122 + required: true,
  123 + componentProps: {
  124 + maxLength: 10,
  125 + },
  126 + },
  127 +
  128 + {
  129 + label: '手机号',
  130 + field: 'phoneNumber',
  131 + component: 'Input',
  132 + colProps: { span: 12 },
  133 + dynamicRules: ({ values }) => {
  134 + return [
  135 + {
  136 + required: true,
  137 + validator(_, value) {
  138 + return new Promise((resolve, reject) => {
  139 + if (value == '') {
  140 + reject('请输入手机号');
  141 + } else if (!phoneRegexp.test(value)) {
  142 + reject('请输入正确的手机号');
  143 + } else {
  144 + if (values.phoneNumber != undefined) {
  145 + // 此处可以用防抖函数优化性能
  146 + IsPhoneExist(value).then(({ data }) => {
  147 + if (data != null) {
  148 + reject('手机号已存在');
  149 + } else {
  150 + resolve();
  151 + }
  152 + });
  153 + } else {
  154 + resolve();
  155 + }
  156 + }
  157 + });
  158 + },
  159 + },
  160 + ];
  161 + },
  162 + componentProps({ formActionType }) {
  163 + const { clearValidate } = formActionType;
  164 + return {
  165 + maxlength: 11,
  166 + onChange(value) {
  167 + if (value == olderPhoneNumber) {
  168 + clearValidate('phoneNumber');
  169 + }
  170 + },
  171 + };
  172 + },
  173 + },
  174 + {
  175 + label: '角色',
  176 + field: 'roleIds',
  177 + component: 'Select',
  178 + colProps: { span: 12 },
  179 + slot: 'roleSlot',
  180 + rules: [
  181 + {
  182 + required: true,
  183 + message: '请选择角色',
  184 + type: 'array',
  185 + },
  186 + ],
  187 + },
  188 + {
  189 + label: '邮箱',
  190 + field: 'email',
  191 + component: 'Input',
  192 + colProps: { span: 12 },
  193 + rules: emailRule,
  194 + },
  195 + // {
  196 + // field: 'accountExpireTime',
  197 + // label: '有效期',
  198 + // component: 'DatePicker',
  199 + // colProps: { span: 12 },
  200 + // componentProps: {
  201 + // showTime: true,
  202 + // format: 'YYYY-MM-DD HH:mm:ss',
  203 + // },
  204 + // },
  205 + {
  206 + field: 'enabled',
  207 + label: '状态',
  208 + component: 'RadioButtonGroup',
  209 + colProps: { span: 12 },
  210 + defaultValue: true,
  211 + componentProps: {
  212 + options: [
  213 + { label: '启用', value: true },
  214 + { label: '禁用', value: false },
  215 + ],
  216 + },
  217 + },
  218 + // {
  219 + // field: 'remark',
  220 + // label: '备注',
  221 + // component: 'InputTextArea',
  222 + // colProps: { span: 24 },
  223 + // componentProps: {
  224 + // maxLength: 255,
  225 + // placeholder: '请输入备注',
  226 + // },
  227 + // },
  228 + {
  229 + field: 'organizationIds',
  230 + label: '所属组织',
  231 + component: 'Input',
  232 + slot: 'organizationId',
  233 + required: true,
  234 + },
  235 +];
  1 +<template>
  2 + <div>
  3 + <PageWrapper dense contentFullHeight contentClass="flex">
  4 + <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" />
  5 +
  6 + <BasicTable
  7 + style="flex: auto"
  8 + :clickToRowSelect="false"
  9 + @register="registerTable"
  10 + class="w-3/4 xl:w-4/5"
  11 + >
  12 + <template #toolbar>
  13 + <Authority value="api:yt:user:saveCommonTenant:post">
  14 + <a-button type="primary" @click="handleCreate">新增管理员账号</a-button>
  15 + </Authority>
  16 + <Authority value="api:yt:admin:user:deleteTenantAdmin:delete">
  17 + <Popconfirm
  18 + title="您确定要批量删除数据"
  19 + ok-text="确定"
  20 + cancel-text="取消"
  21 + @confirm="handleDeleteOrBatchDelete(null)"
  22 + >
  23 + <a-button color="error" :disabled="hasBatchDelete"> 批量删除 </a-button>
  24 + </Popconfirm>
  25 + </Authority>
  26 + </template>
  27 + <template #status="{ record }">
  28 + <Tag
  29 + :color="
  30 + record.userStatusEnum === 'NORMAL'
  31 + ? 'green'
  32 + : record.userStatusEnum === 'DISABLED'
  33 + ? 'red'
  34 + : 'orange'
  35 + "
  36 + >
  37 + {{
  38 + record.userStatusEnum === 'NORMAL'
  39 + ? '正常'
  40 + : record.userStatusEnum === 'DISABLED'
  41 + ? '已禁用'
  42 + : '已过期'
  43 + }}
  44 + </Tag>
  45 + </template>
  46 + <template #action="{ record }">
  47 + <TableAction
  48 + :actions="[
  49 + {
  50 + label: '进入',
  51 + icon: 'ant-design:login-outlined',
  52 + tooltip: `以${!isAdmin(role) ? '租户' : '平台'}用户身份登录`,
  53 + onClick: handleLoginCustomAdmin.bind(null, record),
  54 + },
  55 + {
  56 + label: '编辑',
  57 + auth: 'api:yt:user:saveCommonTenant:post',
  58 + icon: 'clarity:note-edit-line',
  59 + tooltip: '编辑',
  60 + onClick: handleEdit.bind(null, record),
  61 + ifShow: record.level != 0,
  62 + },
  63 + {
  64 + label: '删除',
  65 + auth: 'api:yt:admin:user:deleteTenantAdmin:delete',
  66 + icon: 'ant-design:delete-outlined',
  67 + color: 'error',
  68 + tooltip: '删除',
  69 + ifShow: record.level != 0,
  70 + popConfirm: {
  71 + title: '是否确认删除',
  72 + confirm: handleDeleteOrBatchDelete.bind(null, record),
  73 + },
  74 + },
  75 + ]"
  76 + :drop-down-actions="[
  77 + {
  78 + label: '清除密码',
  79 + auth: 'api:yt:user:resetPassword',
  80 + icon: 'ant-design:delete-outlined',
  81 + color: 'error',
  82 + tooltip: '清除密码',
  83 + popConfirm: {
  84 + title: '是否确认清除密码',
  85 + confirm: handleClearPassword.bind(null, record),
  86 + },
  87 + },
  88 + ]"
  89 + />
  90 + </template>
  91 + </BasicTable>
  92 + <TenantModal @register="registerModal" @success="handleSuccess" />
  93 + </PageWrapper>
  94 + </div>
  95 +</template>
  96 +
  97 +<script lang="ts" setup>
  98 + import { BasicTable, useTable, TableAction } from '/@/components/Table';
  99 + import { getTenantList } from '/@/api/system/account';
  100 + import { columns, searchFormSchema } from './config';
  101 + import { useResetOrganizationTree, OrganizationIdTree } from '/@/views/common/organizationIdTree';
  102 + import { reactive, nextTick } from 'vue';
  103 + import { Authority } from '/@/components/Authority';
  104 + import { useUserStore } from '/@/store/modules/user';
  105 + import { PageWrapper } from '/@/components/Page';
  106 + import { useGo } from '/@/hooks/web/usePage';
  107 + import { Tag, Popconfirm } from 'ant-design-vue';
  108 + import TenantModal from './TenantModal.vue';
  109 + import { useModal } from '/@/components/Modal';
  110 + import { useFastEnter } from '/@/hooks/business/useFastEnter';
  111 + import { TenantListItemRecord } from '/@/api/tenant/tenantInfo';
  112 + import { isAdmin } from '/@/enums/roleEnum';
  113 + import { getAuthCache } from '/@/utils/auth';
  114 + import { useBatchDelete } from '/@/hooks/web/useBatchDelete';
  115 + import { deleteTenantAdmin } from '/@/api/tenant/tenantApi';
  116 + import { useMessage } from '/@/hooks/web/useMessage';
  117 +
  118 + import { USER_INFO_KEY } from '/@/enums/cacheEnum';
  119 + import { clearUserPassword } from '/@/api/system/system';
  120 +
  121 + const searchInfo = reactive<Recordable>({});
  122 + const go = useGo();
  123 +
  124 + const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);
  125 +
  126 + const userRole: any = getAuthCache(USER_INFO_KEY);
  127 + const role: string = userRole?.roles[0];
  128 + const { createMessage } = useMessage();
  129 +
  130 + const userInfo = useUserStore();
  131 + const [registerTable, { reload, setProps }] = useTable({
  132 + title: '管理号账号列表',
  133 + columns,
  134 + api: getTenantList,
  135 + formConfig: {
  136 + labelWidth: 100,
  137 + schemas: searchFormSchema,
  138 + resetFunc: resetFn,
  139 + },
  140 +
  141 + beforeFetch: (params) => {
  142 + return {
  143 + ...params,
  144 + tenantId: userInfo.getUserInfo.tenantId!,
  145 + };
  146 + },
  147 + useSearchForm: true,
  148 + showTableSetting: true,
  149 + bordered: true,
  150 + showIndexColumn: false,
  151 + rowKey: 'id',
  152 + searchInfo: searchInfo,
  153 + clickToRowSelect: false,
  154 + actionColumn: {
  155 + width: 200,
  156 + title: '操作',
  157 + slots: { customRender: 'action' },
  158 + fixed: 'right',
  159 + },
  160 + });
  161 + const { hasBatchDelete, handleDeleteOrBatchDelete, selectionOptions } = useBatchDelete(
  162 + deleteTenantAdmin,
  163 + handleSuccess,
  164 + setProps
  165 + );
  166 + nextTick(() => {
  167 + setProps(selectionOptions);
  168 + });
  169 +
  170 + const [registerModal, { openModal }] = useModal();
  171 +
  172 + function handleSelect(organization) {
  173 + searchInfo.organizationId = organization;
  174 + reload();
  175 + }
  176 +
  177 + const handleCreate = () => {
  178 + openModal(true, {
  179 + isAdd: true,
  180 + });
  181 + };
  182 + const handleLoginCustomAdmin = async (record: TenantListItemRecord) => {
  183 + try {
  184 + useFastEnter(record, go);
  185 + } catch (errpr) {
  186 + } finally {
  187 + }
  188 + };
  189 + const handleEdit = (record: Recordable) => {
  190 + openModal(true, {
  191 + isAdd: false,
  192 + record,
  193 + });
  194 + };
  195 +
  196 + const handleClearPassword = async (record: Recordable) => {
  197 + const { id } = record;
  198 + if (!id) return;
  199 + const { message } = await clearUserPassword(id, 4);
  200 + createMessage.success(message);
  201 + handleSuccess();
  202 + };
  203 + // const handleDeleteOrBatchDelete = (record: Recordable) => {
  204 + // deleteTenantAdmin([record.id]).then(() => {
  205 + // createMessage.success('删除成功');
  206 + // handleSuccess();
  207 + // });
  208 + // };
  209 + // const handleClearPassword = async (record: Recordable) => {
  210 + // const { id } = record;
  211 + // if (!id) return;
  212 + // const { message } = await clearUserPassword(id);
  213 + // createMessage.success(message);
  214 + // };
  215 + function handleSuccess() {
  216 + reload();
  217 + }
  218 +</script>
  1 +import { BasicColumn } from '/@/components/Table';
  2 +import { FormSchema } from '/@/components/Table';
  3 +import { DictEnum } from '/@/enums/dictEnum';
  4 +import { RoleEnum } from '/@/enums/roleEnum';
  5 +
  6 +export enum KeysTypeEnum {
  7 + DISABLED = 'disabled',
  8 + ENABLED = 'enabled',
  9 +}
  10 +
  11 +export const RoleMenuDictEnum: Recordable<{ key: string; keyType: KeysTypeEnum }> = {
  12 + [RoleEnum.PLATFORM_ADMIN]: {
  13 + key: DictEnum.DISABLED_PLATFORM_ADMIN_AUTH,
  14 + keyType: KeysTypeEnum.DISABLED,
  15 + },
  16 + [RoleEnum.SYS_ADMIN]: { key: DictEnum.ENABLED_SYSADMIN_AUTH, keyType: KeysTypeEnum.ENABLED },
  17 + [RoleEnum.TENANT_ADMIN]: { key: DictEnum.DISABLED_TENANT_AUTH, keyType: KeysTypeEnum.DISABLED },
  18 + [RoleEnum.CUSTOMER_USER]: { key: DictEnum.DISABLE_CUSTOMER_AUTH, keyType: KeysTypeEnum.DISABLED },
  19 +};
  20 +
  21 +export const columns: BasicColumn[] = [
  22 + {
  23 + title: '角色名称',
  24 + dataIndex: 'name',
  25 + width: 200,
  26 + },
  27 + {
  28 + title: '角色Code',
  29 + dataIndex: 'code',
  30 + width: 200,
  31 + },
  32 + {
  33 + title: '状态',
  34 + dataIndex: 'status',
  35 + width: 120,
  36 + slots: { customRender: 'status' },
  37 + },
  38 +
  39 + {
  40 + title: '备注',
  41 + dataIndex: 'remark',
  42 + width: 240,
  43 + },
  44 + {
  45 + title: '创建时间',
  46 + dataIndex: 'createTime',
  47 + width: 180,
  48 + },
  49 +];
  50 +
  51 +export const searchFormSchema: FormSchema[] = [
  52 + {
  53 + field: 'roleName',
  54 + label: '角色名称',
  55 + component: 'Input',
  56 + colProps: { span: 6 },
  57 + componentProps: {
  58 + maxLength: 255,
  59 + },
  60 + },
  61 + {
  62 + field: 'status',
  63 + label: '状态',
  64 + component: 'Select',
  65 + componentProps: {
  66 + options: [
  67 + { label: '启用', value: 1 },
  68 + { label: '停用', value: 0 },
  69 + ],
  70 + },
  71 + colProps: { span: 6 },
  72 + },
  73 +];
  74 +
  75 +export const formSchema: FormSchema[] = [
  76 + {
  77 + field: 'name',
  78 + label: '角色名称',
  79 + required: true,
  80 + component: 'Input',
  81 + componentProps: {
  82 + maxLength: 255,
  83 + placeholder: '请输入角色名称',
  84 + },
  85 + },
  86 + {
  87 + field: 'status',
  88 + label: '状态',
  89 + component: 'RadioButtonGroup',
  90 + defaultValue: 1,
  91 + componentProps: {
  92 + options: [
  93 + { label: '启用', value: 1 },
  94 + { label: '停用', value: 0 },
  95 + ],
  96 + },
  97 + },
  98 + {
  99 + label: '备注',
  100 + field: 'remark',
  101 + component: 'InputTextArea',
  102 + componentProps: {
  103 + maxLength: 255,
  104 + placeholder: '请输入备注',
  105 + },
  106 + },
  107 + {
  108 + label: '',
  109 + field: 'menu',
  110 + slot: 'menu',
  111 + component: 'Input',
  112 + },
  113 +];
@@ -25,6 +25,7 @@ @@ -25,6 +25,7 @@
25 label: '编辑', 25 label: '编辑',
26 auth: 'api:yt:organization:update', 26 auth: 'api:yt:organization:update',
27 icon: 'clarity:note-edit-line', 27 icon: 'clarity:note-edit-line',
  28 + disabled: record.isRoot && getIsChildTenant,
28 onClick: handleEdit.bind(null, record), 29 onClick: handleEdit.bind(null, record),
29 }, 30 },
30 { 31 {
@@ -32,6 +33,7 @@ @@ -32,6 +33,7 @@
32 auth: 'api:yt:organization:delete', 33 auth: 'api:yt:organization:delete',
33 icon: 'ant-design:delete-outlined', 34 icon: 'ant-design:delete-outlined',
34 color: 'error', 35 color: 'error',
  36 + disabled: record.isRoot && getIsChildTenant,
35 popConfirm: { 37 popConfirm: {
36 title: getDeleteTitle(), //是否确认删除//getDeleteTitle() 38 title: getDeleteTitle(), //是否确认删除//getDeleteTitle()
37 confirm: handleDeleteOrBatchDelete.bind(null, record), 39 confirm: handleDeleteOrBatchDelete.bind(null, record),
@@ -45,7 +47,7 @@ @@ -45,7 +47,7 @@
45 </div> 47 </div>
46 </template> 48 </template>
47 <script lang="ts"> 49 <script lang="ts">
48 - import { computed, defineComponent, nextTick } from 'vue'; 50 + import { computed, defineComponent, nextTick, unref } from 'vue';
49 import { BasicTable, useTable, TableAction } from '/@/components/Table'; 51 import { BasicTable, useTable, TableAction } from '/@/components/Table';
50 // 加载自定义侧边弹出框 组件 52 // 加载自定义侧边弹出框 组件
51 import { useDrawer } from '/@/components/Drawer'; 53 import { useDrawer } from '/@/components/Drawer';
@@ -56,6 +58,8 @@ @@ -56,6 +58,8 @@
56 import { useBatchDelete } from '/@/hooks/web/useBatchDelete'; 58 import { useBatchDelete } from '/@/hooks/web/useBatchDelete';
57 import { Authority } from '/@/components/Authority'; 59 import { Authority } from '/@/components/Authority';
58 import { Popconfirm } from 'ant-design-vue'; 60 import { Popconfirm } from 'ant-design-vue';
  61 + import { getJwtToken } from '/@/utils/auth';
  62 + import jwtDecode from 'jwt-decode';
59 63
60 export default defineComponent({ 64 export default defineComponent({
61 name: 'DeptManagement', 65 name: 'DeptManagement',
@@ -65,6 +69,11 @@ @@ -65,6 +69,11 @@
65 const { t } = useI18n(); // 加载国际化 69 const { t } = useI18n(); // 加载国际化
66 const getI18n = computed(() => t('routes.common.organization.toolCreateOrganization')); 70 const getI18n = computed(() => t('routes.common.organization.toolCreateOrganization'));
67 71
  72 + const getIsChildTenant = computed(() => {
  73 + const token = getJwtToken();
  74 + return !!(jwtDecode(token as string) as Recordable)?.isCommonTenant;
  75 + });
  76 +
68 const [registerTable, { reload, expandAll, setProps }] = useTable({ 77 const [registerTable, { reload, expandAll, setProps }] = useTable({
69 title: t('routes.common.organization.toolOrganizationList'), 78 title: t('routes.common.organization.toolOrganizationList'),
70 api: getOrganizationList, 79 api: getOrganizationList,
@@ -76,6 +85,14 @@ @@ -76,6 +85,14 @@
76 showTableSetting: true, 85 showTableSetting: true,
77 bordered: true, 86 bordered: true,
78 showIndexColumn: false, 87 showIndexColumn: false,
  88 + afterFetch: (data: Recordable[]) => {
  89 + if (data && data.length) {
  90 + data.forEach((item) => {
  91 + item.isRoot = true;
  92 + });
  93 + }
  94 + return data;
  95 + },
79 actionColumn: { 96 actionColumn: {
80 width: 200, 97 width: 200,
81 title: t('routes.common.common.operation'), //操作 98 title: t('routes.common.common.operation'), //操作
@@ -91,7 +108,15 @@ @@ -91,7 +108,15 @@
91 setProps 108 setProps
92 ); 109 );
93 nextTick(() => { 110 nextTick(() => {
94 - setProps(selectionOptions); 111 + setProps({
  112 + ...selectionOptions,
  113 + rowSelection: {
  114 + ...selectionOptions.rowSelection,
  115 + getCheckboxProps: (data) => {
  116 + return { disabled: data?.isRoot && unref(getIsChildTenant) };
  117 + },
  118 + },
  119 + });
95 }); 120 });
96 /** 121 /**
97 * 获得删除提示框的文字 122 * 获得删除提示框的文字
@@ -124,6 +149,7 @@ @@ -124,6 +149,7 @@
124 } 149 }
125 150
126 return { 151 return {
  152 + getIsChildTenant,
127 getI18n, 153 getI18n,
128 registerTable, 154 registerTable,
129 registerModal, 155 registerModal,
  1 +import jwtDecode from 'jwt-decode';
1 import { BasicColumn } from '/@/components/Table'; 2 import { BasicColumn } from '/@/components/Table';
2 import { FormSchema } from '/@/components/Table'; 3 import { FormSchema } from '/@/components/Table';
3 import { useI18n } from '/@/hooks/web/useI18n'; 4 import { useI18n } from '/@/hooks/web/useI18n';
  5 +import { getJwtToken } from '/@/utils/auth';
4 import { createOrganizationSearch } from '/@/utils/organizationSearch'; 6 import { createOrganizationSearch } from '/@/utils/organizationSearch';
5 const { t } = useI18n(); 7 const { t } = useI18n();
6 8
@@ -32,6 +34,12 @@ export const formSchema: FormSchema[] = [ @@ -32,6 +34,12 @@ export const formSchema: FormSchema[] = [
32 field: 'parentId', 34 field: 'parentId',
33 label: t('routes.common.organization.parentOrganization'), 35 label: t('routes.common.organization.parentOrganization'),
34 component: 'TreeSelect', 36 component: 'TreeSelect',
  37 + dynamicRules: () => {
  38 + const token = getJwtToken();
  39 + const res = jwtDecode(token as string) as Record<'isCommonTenant', boolean>;
  40 + return [{ required: res?.isCommonTenant, message: '请选择上级组织' }];
  41 + },
  42 + rulesMessageJoinLabel: true,
35 componentProps: { 43 componentProps: {
36 replaceFields: { 44 replaceFields: {
37 title: 'name', 45 title: 'name',
  1 +<template>
  2 + <BasicDrawer
  3 + v-bind="$attrs"
  4 + @register="registerDrawer"
  5 + showFooter
  6 + :title="getTitle"
  7 + width="500px"
  8 + @ok="handleSubmit"
  9 + >
  10 + <BasicForm @register="registerForm">
  11 + <template #menu>
  12 + <Spin :spinning="spinning">
  13 + <BasicTree
  14 + v-if="treeData.length"
  15 + checkable
  16 + toolbar
  17 + ref="treeRef"
  18 + :treeData="treeData"
  19 + @check="handleCheckClick"
  20 + :replace-fields="{ title: 'name', key: 'id' }"
  21 + :checkedKeys="roleMenus"
  22 + title="菜单分配"
  23 + />
  24 + </Spin>
  25 + </template>
  26 + </BasicForm>
  27 + </BasicDrawer>
  28 +</template>
  29 +<script lang="ts">
  30 + import { defineComponent, ref, computed, unref, nextTick } from 'vue';
  31 + import { BasicForm, useForm } from '/@/components/Form/index';
  32 + import { formSchema, KeysTypeEnum, RoleMenuDictEnum } from './role.data';
  33 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  34 + import { BasicTree, TreeActionType, TreeItem } from '/@/components/Tree';
  35 + import { useMessage } from '/@/hooks/web/useMessage';
  36 + const { t } = useI18n(); // 加载国际化
  37 + // 加载菜单数据
  38 + import { getMeMenuList, getMenusIdsByRoleId } from '/@/api/sys/menu';
  39 + import { useI18n } from '/@/hooks/web/useI18n';
  40 + import { MenuRecord } from '/@/api/sys/model/menuModel';
  41 + import { saveOrUpdateRoleInfoWithMenu } from '/@/api/system/system';
  42 + import { findDictItemByCode } from '/@/api/system/dict';
  43 + import { RoleEnum } from '/@/enums/roleEnum';
  44 + import { Spin } from 'ant-design-vue';
  45 + import { useRole } from '/@/hooks/business/useRole';
  46 + import { RoleListItem } from '/@/api/system/model/systemModel';
  47 +
  48 + type TreeData = MenuRecord & TreeItem;
  49 +
  50 + export default defineComponent({
  51 + name: 'RoleDrawer',
  52 + components: { BasicDrawer, BasicForm, BasicTree, Spin },
  53 + emits: ['success', 'register'],
  54 + setup(_, { emit }) {
  55 + const isUpdate = ref<boolean>(true);
  56 + const treeData = ref<TreeData[]>([]);
  57 + const roleMenus = ref<string[]>([]);
  58 + const roleId = ref<string>('');
  59 + const treeRef = ref<Nullable<TreeActionType>>();
  60 + const checked = ref<string[]>([]); //需要选中的节点
  61 + const spinning = ref(false);
  62 + const checkedKeysWithHalfChecked = ref<string[]>([]);
  63 +
  64 + const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
  65 + labelWidth: 100,
  66 + schemas: formSchema,
  67 + showActionButtonGroup: false,
  68 + });
  69 +
  70 + const transformName = (data: TreeData[]) => {
  71 + return data.map((item) => {
  72 + item.name = t(item.name);
  73 + if (item.children && item.children.length) {
  74 + item.children = transformName(item.children as unknown as TreeData[]);
  75 + }
  76 + return item;
  77 + });
  78 + };
  79 +
  80 + const { isTenantAdmin, isSysadmin, getRole } = useRole();
  81 + const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(
  82 + async (data: { isUpdate: boolean; record: RoleListItem }) => {
  83 + resetFields();
  84 + roleId.value = '';
  85 + // 在打开弹窗时清除所有选择的菜单
  86 + treeRef.value && treeRef.value?.setCheckedKeys([]);
  87 + isUpdate.value = data.isUpdate;
  88 + let roleType = unref(getRole) || RoleEnum.SYS_ADMIN;
  89 +
  90 + // 租户管理员创建角色时 菜单分配为客户菜单
  91 + if (unref(isTenantAdmin)) {
  92 + roleType = RoleEnum.CUSTOMER_USER;
  93 + }
  94 +
  95 + try {
  96 + spinning.value = true;
  97 + // 需要在setFieldsValue之前先填充treeData,否则Tree组件可能会报key not exist警告
  98 +
  99 + // if (!unref(treeData).length) {
  100 + // 获取全部的菜单
  101 + const menuListModel = await getMeMenuList();
  102 + treeData.value = transformName(menuListModel as unknown as TreeData[]);
  103 + // }
  104 +
  105 + const keys = await getPermissionByRole(roleType);
  106 + const { keyType } = RoleMenuDictEnum[roleType];
  107 + treeData.value = filterPermissionTreeData(
  108 + unref(treeData) as unknown as TreeData[],
  109 + keys,
  110 + keyType
  111 + );
  112 +
  113 + // 如果编辑的是超级管理员 则不再过滤平台管理员禁用的权限
  114 + // 如果是超级管理员创建角色 创建角色属于平台管理员 因此过滤平台管理员禁用的权限
  115 + if (data?.record?.roleType !== RoleEnum.SYS_ADMIN && unref(isSysadmin)) {
  116 + const keys = await getPermissionByRole(RoleEnum.PLATFORM_ADMIN);
  117 + const { keyType } = RoleMenuDictEnum[RoleEnum.PLATFORM_ADMIN];
  118 + treeData.value = filterPermissionTreeData(
  119 + unref(treeData) as unknown as TreeData[],
  120 + keys,
  121 + keyType
  122 + );
  123 + }
  124 +
  125 + // 更新
  126 + if (unref(isUpdate)) {
  127 + checked.value = [];
  128 + roleId.value = data.record.id;
  129 +
  130 + //通过角色id去获取角色对应的菜单的ids
  131 + checkedKeysWithHalfChecked.value = roleMenus.value = await getMenusIdsByRoleId(
  132 + data.record.id
  133 + );
  134 + excludeHalfCheckedKeys(unref(treeData));
  135 + await nextTick();
  136 + unref(treeRef)?.setCheckedKeys(roleMenus.value);
  137 + setFieldsValue(data.record);
  138 + } else {
  139 + }
  140 + } catch (error) {
  141 + throw error;
  142 + } finally {
  143 + spinning.value = false;
  144 + }
  145 + }
  146 + );
  147 +
  148 + const getTitle = computed(() => (!unref(isUpdate) ? '新增角色' : '编辑角色'));
  149 +
  150 + async function handleSubmit() {
  151 + setDrawerProps({ confirmLoading: true });
  152 + const { createMessage } = useMessage();
  153 + try {
  154 + const values = await validate();
  155 + const treeCheckedKeys: string[] = (unref(treeRef)?.getCheckedKeys() as string[]) || [];
  156 + const menu = [...new Set([...unref(checkedKeysWithHalfChecked), ...treeCheckedKeys])];
  157 + const req = {
  158 + id: roleId.value,
  159 + name: values.name,
  160 + remark: values.remark,
  161 + status: values.status,
  162 + menu,
  163 + };
  164 + if (req.menu == undefined) return createMessage.error('请勾选权限菜单');
  165 + saveOrUpdateRoleInfoWithMenu(req).then(() => {
  166 + closeDrawer();
  167 + emit('success');
  168 + createMessage.success(`${unref(isUpdate) ? '编辑' : '新增'}成功`);
  169 + });
  170 + } finally {
  171 + setTimeout(() => {
  172 + setDrawerProps({ confirmLoading: false });
  173 + }, 300);
  174 + }
  175 + }
  176 +
  177 + const getPermissionByRole = async (roleType: RoleEnum) => {
  178 + try {
  179 + const { key } = RoleMenuDictEnum[roleType];
  180 + const res = await findDictItemByCode({ dictCode: key });
  181 + return res.map((item) => item.itemValue);
  182 + } catch (error) {}
  183 + return [];
  184 + };
  185 +
  186 + const filterPermissionTreeData = (
  187 + data: MenuRecord[],
  188 + permissionKeys: string[],
  189 + keysType: KeysTypeEnum
  190 + ): TreeData[] => {
  191 + const permissionCompare = (
  192 + data: MenuRecord[],
  193 + permissionKeys: string[],
  194 + keysType: KeysTypeEnum
  195 + ): TreeData[] => {
  196 + return data.filter((item) => {
  197 + item.name = t(item.name);
  198 + const findFlag = permissionKeys.includes(item.permission);
  199 +
  200 + item.show = findFlag ? keysType === KeysTypeEnum.ENABLED : undefined;
  201 +
  202 + if (item.children && item.children.length) {
  203 + if (item.show) return true;
  204 + if (item.show === undefined) {
  205 + item.children = permissionCompare(item.children, permissionKeys, keysType);
  206 + item.show = item.children.some((item) =>
  207 + keysType === KeysTypeEnum.ENABLED
  208 + ? item.show
  209 + : item.show === undefined
  210 + ? true
  211 + : item.show
  212 + );
  213 + return item.show;
  214 + }
  215 + }
  216 +
  217 + return keysType === KeysTypeEnum.ENABLED
  218 + ? item.show
  219 + : item.show === undefined
  220 + ? true
  221 + : item.show;
  222 + }) as unknown as TreeData[];
  223 + };
  224 +
  225 + return permissionCompare(data, permissionKeys, keysType);
  226 + };
  227 +
  228 + const excludeHalfCheckedKeys = (treeData: MenuRecord[]) => {
  229 + const needExcludeKeys: string[] = [];
  230 + const query = (data: MenuRecord[]) => {
  231 + data.forEach((item) => {
  232 + item.checked = roleMenus.value.includes(item.id);
  233 + if (item.children && item.children.length) {
  234 + query(item.children);
  235 + item.checked = item.children.every((item) => item.checked);
  236 + }
  237 + if (!item.checked) {
  238 + needExcludeKeys.push(item.id);
  239 + }
  240 + });
  241 + };
  242 + query(treeData);
  243 + roleMenus.value = unref(roleMenus).filter((key) => !needExcludeKeys.includes(key));
  244 + return needExcludeKeys;
  245 + };
  246 +
  247 + const handleCheckClick = (selectedKeys: string[], event: CheckEvent) => {
  248 + checkedKeysWithHalfChecked.value = [
  249 + ...selectedKeys,
  250 + ...(event.halfCheckedKeys as string[]),
  251 + ];
  252 + };
  253 +
  254 + return {
  255 + spinning,
  256 + registerDrawer,
  257 + registerForm,
  258 + getTitle,
  259 + handleSubmit,
  260 + treeData,
  261 + roleMenus,
  262 + treeRef,
  263 + handleCheckClick,
  264 + };
  265 + },
  266 + });
  267 +</script>
  268 +
  269 +<style scoped lang="less">
  270 + :deep(.vben-basic-tree) {
  271 + width: 100% !important;
  272 + }
  273 +
  274 + :deep(.is-unflod) {
  275 + display: none !important;
  276 + }
  277 +
  278 + :deep(.is-flod) {
  279 + display: none !important;
  280 + }
  281 +</style>
@@ -89,7 +89,7 @@ @@ -89,7 +89,7 @@
89 89
90 // 租户管理员创建角色时 菜单分配为客户菜单 90 // 租户管理员创建角色时 菜单分配为客户菜单
91 if (unref(isTenantAdmin)) { 91 if (unref(isTenantAdmin)) {
92 - roleType = RoleEnum.CUSTOMER_USER; 92 + roleType = RoleEnum.TENANT_ADMIN;
93 } 93 }
94 94
95 try { 95 try {
@@ -160,6 +160,7 @@ @@ -160,6 +160,7 @@
160 remark: values.remark, 160 remark: values.remark,
161 status: values.status, 161 status: values.status,
162 menu, 162 menu,
  163 + roleType: 'PLATFORM_ADMIN',
163 }; 164 };
164 if (req.menu == undefined) return createMessage.error('请勾选权限菜单'); 165 if (req.menu == undefined) return createMessage.error('请勾选权限菜单');
165 saveOrUpdateRoleInfoWithMenu(req).then(() => { 166 saveOrUpdateRoleInfoWithMenu(req).then(() => {
  1 +<template>
  2 + <BasicDrawer
  3 + v-bind="$attrs"
  4 + @register="registerDrawer"
  5 + showFooter
  6 + :title="getTitle"
  7 + width="500px"
  8 + @ok="handleSubmit"
  9 + >
  10 + <BasicForm @register="registerForm">
  11 + <template #menu>
  12 + <Spin :spinning="spinning">
  13 + <BasicTree
  14 + v-if="treeData.length"
  15 + checkable
  16 + toolbar
  17 + ref="treeRef"
  18 + :treeData="treeData"
  19 + @check="handleCheckClick"
  20 + :replace-fields="{ title: 'name', key: 'id' }"
  21 + :checkedKeys="roleMenus"
  22 + title="菜单分配"
  23 + />
  24 + </Spin>
  25 + </template>
  26 + </BasicForm>
  27 + </BasicDrawer>
  28 +</template>
  29 +<script lang="ts">
  30 + import { defineComponent, ref, computed, unref, nextTick } from 'vue';
  31 + import { BasicForm, useForm } from '/@/components/Form/index';
  32 + import { formSchema, KeysTypeEnum, RoleMenuDictEnum } from './role.data';
  33 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  34 + import { BasicTree, TreeActionType, TreeItem } from '/@/components/Tree';
  35 + import { useMessage } from '/@/hooks/web/useMessage';
  36 + const { t } = useI18n(); // 加载国际化
  37 + // 加载菜单数据
  38 + import { getMeMenuList, getMenusIdsByRoleId } from '/@/api/sys/menu';
  39 + import { useI18n } from '/@/hooks/web/useI18n';
  40 + import { MenuRecord } from '/@/api/sys/model/menuModel';
  41 + import { saveOrUpdateRoleInfoWithMenu } from '/@/api/system/system';
  42 + import { findDictItemByCode } from '/@/api/system/dict';
  43 + import { RoleEnum } from '/@/enums/roleEnum';
  44 + import { Spin } from 'ant-design-vue';
  45 + import { useRole } from '/@/hooks/business/useRole';
  46 + import { RoleListItem } from '/@/api/system/model/systemModel';
  47 +
  48 + type TreeData = MenuRecord & TreeItem;
  49 +
  50 + export default defineComponent({
  51 + name: 'RoleDrawer',
  52 + components: { BasicDrawer, BasicForm, BasicTree, Spin },
  53 + emits: ['success', 'register'],
  54 + setup(_, { emit }) {
  55 + const isUpdate = ref<boolean>(true);
  56 + const treeData = ref<TreeData[]>([]);
  57 + const roleMenus = ref<string[]>([]);
  58 + const roleId = ref<string>('');
  59 + const treeRef = ref<Nullable<TreeActionType>>();
  60 + const checked = ref<string[]>([]); //需要选中的节点
  61 + const spinning = ref(false);
  62 + const checkedKeysWithHalfChecked = ref<string[]>([]);
  63 +
  64 + const [registerForm, { resetFields, setFieldsValue, validate, updateSchema }] = useForm({
  65 + labelWidth: 100,
  66 + schemas: formSchema,
  67 + showActionButtonGroup: false,
  68 + });
  69 +
  70 + const transformName = (data: TreeData[]) => {
  71 + return data.map((item) => {
  72 + item.name = t(item.name);
  73 + if (item.children && item.children.length) {
  74 + item.children = transformName(item.children as unknown as TreeData[]);
  75 + }
  76 + return item;
  77 + });
  78 + };
  79 +
  80 + const { isTenantAdmin, isSysadmin, getRole } = useRole();
  81 + const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(
  82 + async (data: { isUpdate: boolean; record: RoleListItem }) => {
  83 + if (data?.record) {
  84 + updateSchema({
  85 + field: 'status',
  86 + componentProps: {
  87 + options: [
  88 + { label: '启用', value: 1 },
  89 + { label: '停用', value: 0 },
  90 + ],
  91 + disabled: data.record.status == 1 ? true : false,
  92 + },
  93 + });
  94 + }
  95 +
  96 + resetFields();
  97 + roleId.value = '';
  98 + // 在打开弹窗时清除所有选择的菜单
  99 + treeRef.value && treeRef.value?.setCheckedKeys([]);
  100 + isUpdate.value = data.isUpdate;
  101 + let roleType = unref(getRole) || RoleEnum.SYS_ADMIN;
  102 +
  103 + // 租户管理员创建角色时 菜单分配为客户菜单
  104 + if (unref(isTenantAdmin)) {
  105 + roleType = RoleEnum.CUSTOMER_USER;
  106 + }
  107 +
  108 + try {
  109 + spinning.value = true;
  110 + // 需要在setFieldsValue之前先填充treeData,否则Tree组件可能会报key not exist警告
  111 +
  112 + // if (!unref(treeData).length) {
  113 + // 获取全部的菜单
  114 + const menuListModel = await getMeMenuList();
  115 + treeData.value = transformName(menuListModel as unknown as TreeData[]);
  116 + // }
  117 +
  118 + const keys = await getPermissionByRole(roleType);
  119 + const { keyType } = RoleMenuDictEnum[roleType];
  120 + treeData.value = filterPermissionTreeData(
  121 + unref(treeData) as unknown as TreeData[],
  122 + keys,
  123 + keyType
  124 + );
  125 +
  126 + // 如果编辑的是超级管理员 则不再过滤平台管理员禁用的权限
  127 + // 如果是超级管理员创建角色 创建角色属于平台管理员 因此过滤平台管理员禁用的权限
  128 + if (data?.record?.roleType !== RoleEnum.SYS_ADMIN && unref(isSysadmin)) {
  129 + const keys = await getPermissionByRole(RoleEnum.PLATFORM_ADMIN);
  130 + const { keyType } = RoleMenuDictEnum[RoleEnum.PLATFORM_ADMIN];
  131 + treeData.value = filterPermissionTreeData(
  132 + unref(treeData) as unknown as TreeData[],
  133 + keys,
  134 + keyType
  135 + );
  136 + }
  137 +
  138 + // 更新
  139 + if (unref(isUpdate)) {
  140 + checked.value = [];
  141 + roleId.value = data.record.id;
  142 +
  143 + //通过角色id去获取角色对应的菜单的ids
  144 + checkedKeysWithHalfChecked.value = roleMenus.value = await getMenusIdsByRoleId(
  145 + data.record.id
  146 + );
  147 + excludeHalfCheckedKeys(unref(treeData));
  148 + await nextTick();
  149 + unref(treeRef)?.setCheckedKeys(roleMenus.value);
  150 + setFieldsValue(data.record);
  151 + } else {
  152 + }
  153 + } catch (error) {
  154 + throw error;
  155 + } finally {
  156 + spinning.value = false;
  157 + }
  158 + }
  159 + );
  160 +
  161 + const getTitle = computed(() => (!unref(isUpdate) ? '新增客户角色' : '编辑客户角色'));
  162 +
  163 + async function handleSubmit() {
  164 + setDrawerProps({ confirmLoading: true });
  165 + const { createMessage } = useMessage();
  166 + try {
  167 + const values = await validate();
  168 + const treeCheckedKeys: string[] = (unref(treeRef)?.getCheckedKeys() as string[]) || [];
  169 + const menu = [...new Set([...unref(checkedKeysWithHalfChecked), ...treeCheckedKeys])];
  170 + const req = {
  171 + id: roleId.value,
  172 + name: values.name,
  173 + remark: values.remark,
  174 + status: values.status,
  175 + menu,
  176 + };
  177 + if (req.menu == undefined || !req.menu.length)
  178 + return createMessage.error('请勾选权限菜单');
  179 + saveOrUpdateRoleInfoWithMenu(req).then(() => {
  180 + closeDrawer();
  181 + emit('success');
  182 + createMessage.success(`${unref(isUpdate) ? '编辑' : '新增'}成功`);
  183 + });
  184 + } finally {
  185 + setTimeout(() => {
  186 + setDrawerProps({ confirmLoading: false });
  187 + }, 300);
  188 + }
  189 + }
  190 +
  191 + const getPermissionByRole = async (roleType: RoleEnum) => {
  192 + try {
  193 + const { key } = RoleMenuDictEnum[roleType];
  194 + const res = await findDictItemByCode({ dictCode: key });
  195 + return res.map((item) => item.itemValue);
  196 + } catch (error) {}
  197 + return [];
  198 + };
  199 +
  200 + const filterPermissionTreeData = (
  201 + data: MenuRecord[],
  202 + permissionKeys: string[],
  203 + keysType: KeysTypeEnum
  204 + ): TreeData[] => {
  205 + const permissionCompare = (
  206 + data: MenuRecord[],
  207 + permissionKeys: string[],
  208 + keysType: KeysTypeEnum
  209 + ): TreeData[] => {
  210 + return data.filter((item) => {
  211 + item.name = t(item.name);
  212 + const findFlag = permissionKeys.includes(item.permission);
  213 +
  214 + item.show = findFlag ? keysType === KeysTypeEnum.ENABLED : undefined;
  215 +
  216 + if (item.children && item.children.length) {
  217 + if (item.show) return true;
  218 + if (item.show === undefined) {
  219 + item.children = permissionCompare(item.children, permissionKeys, keysType);
  220 + item.show = item.children.some((item) =>
  221 + keysType === KeysTypeEnum.ENABLED
  222 + ? item.show
  223 + : item.show === undefined
  224 + ? true
  225 + : item.show
  226 + );
  227 + return item.show;
  228 + }
  229 + }
  230 +
  231 + return keysType === KeysTypeEnum.ENABLED
  232 + ? item.show
  233 + : item.show === undefined
  234 + ? true
  235 + : item.show;
  236 + }) as unknown as TreeData[];
  237 + };
  238 +
  239 + return permissionCompare(data, permissionKeys, keysType);
  240 + };
  241 +
  242 + const excludeHalfCheckedKeys = (treeData: MenuRecord[]) => {
  243 + const needExcludeKeys: string[] = [];
  244 + const query = (data: MenuRecord[]) => {
  245 + data.forEach((item) => {
  246 + item.checked = roleMenus.value.includes(item.id);
  247 + if (item.children && item.children.length) {
  248 + query(item.children);
  249 + item.checked = item.children.every((item) => item.checked);
  250 + }
  251 + if (!item.checked) {
  252 + needExcludeKeys.push(item.id);
  253 + }
  254 + });
  255 + };
  256 + query(treeData);
  257 + roleMenus.value = unref(roleMenus).filter((key) => !needExcludeKeys.includes(key));
  258 + return needExcludeKeys;
  259 + };
  260 +
  261 + const handleCheckClick = (selectedKeys: string[], event: CheckEvent) => {
  262 + checkedKeysWithHalfChecked.value = [
  263 + ...selectedKeys,
  264 + ...(event.halfCheckedKeys as string[]),
  265 + ];
  266 + };
  267 +
  268 + return {
  269 + spinning,
  270 + registerDrawer,
  271 + registerForm,
  272 + getTitle,
  273 + handleSubmit,
  274 + treeData,
  275 + roleMenus,
  276 + treeRef,
  277 + handleCheckClick,
  278 + };
  279 + },
  280 + });
  281 +</script>
  282 +
  283 +<style scoped lang="less">
  284 + :deep(.vben-basic-tree) {
  285 + width: 100% !important;
  286 + }
  287 +
  288 + :deep(.is-unflod) {
  289 + display: none !important;
  290 + }
  291 +
  292 + :deep(.is-flod) {
  293 + display: none !important;
  294 + }
  295 +</style>
  1 +<template>
  2 + <div>
  3 + <BasicTable
  4 + :rowSelection="{ type: 'checkbox' }"
  5 + @register="registerTable"
  6 + :clickToRowSelect="false"
  7 + >
  8 + <template #toolbar>
  9 + <Authority value="api:yt:custom:role:post">
  10 + <a-button type="primary" @click="handleCreate">新增客户角色</a-button>
  11 + </Authority>
  12 + <Authority value="api:yt:custom:role:delete">
  13 + <Popconfirm
  14 + title="您确定要批量删除数据"
  15 + ok-text="确定"
  16 + cancel-text="取消"
  17 + @confirm="handleDeleteOrBatchDelete(null)"
  18 + >
  19 + <a-button type="primary" color="error" :disabled="hasBatchDelete"> 批量删除 </a-button>
  20 + </Popconfirm>
  21 + </Authority>
  22 + </template>
  23 + <template #status="{ record }">
  24 + <Authority value="api:yt:custom:role:update">
  25 + <Switch
  26 + :checked="record.status === 1"
  27 + :loading="record.pendingStatus"
  28 + checkedChildren="启用"
  29 + unCheckedChildren="禁用"
  30 + @change="(checked:boolean)=>statusChange(checked,record)"
  31 + />
  32 + </Authority>
  33 + <Tag
  34 + v-if="!hasPermission('api:yt:custom:role:update')"
  35 + :color="record.status ? 'green' : 'red'"
  36 + >
  37 + {{ record.status ? '启用' : '禁用' }}
  38 + </Tag>
  39 + </template>
  40 + <template #action="{ record }">
  41 + <TableAction
  42 + :actions="[
  43 + {
  44 + label: '编辑',
  45 + auth: 'api:yt:custom:role:update',
  46 + icon: 'clarity:note-edit-line',
  47 + onClick: handleEdit.bind(null, record),
  48 + },
  49 + {
  50 + label: '删除',
  51 + auth: 'api:yt:custom:role:delete',
  52 + icon: 'ant-design:delete-outlined',
  53 + color: 'error',
  54 + ifShow: record.roleType != RoleEnum.SYS_ADMIN && !record.status,
  55 + popConfirm: {
  56 + title: '是否确认删除',
  57 + confirm: handleDeleteOrBatchDelete.bind(null, record),
  58 + },
  59 + },
  60 + ]"
  61 + />
  62 + </template>
  63 + </BasicTable>
  64 + <RoleDrawer @register="registerDrawer" @success="handleSuccess" />
  65 + </div>
  66 +</template>
  67 +<script lang="ts">
  68 + import { defineComponent, nextTick } from 'vue';
  69 + import { BasicTable, useTable, TableAction } from '/@/components/Table';
  70 + import { delRole, getRoleListByPage, setRoleStatus } from '/@/api/system/system';
  71 + import { useDrawer } from '/@/components/Drawer';
  72 + import RoleDrawer from './RoleDrawer.vue';
  73 + import { columns, searchFormSchema } from './role.data';
  74 + import { RoleEnum } from '/@/enums/roleEnum';
  75 + import { Authority } from '/@/components/Authority';
  76 + import { useBatchDelete } from '/@/hooks/web/useBatchDelete';
  77 + import { useMessage } from '/@/hooks/web/useMessage';
  78 + import { Switch, Popconfirm, Tag } from 'ant-design-vue';
  79 + import { usePermission } from '/@/hooks/web/usePermission';
  80 +
  81 + export default defineComponent({
  82 + name: 'RoleManagement',
  83 + components: { BasicTable, RoleDrawer, TableAction, Authority, Switch, Popconfirm, Tag },
  84 + setup() {
  85 + const { hasPermission } = usePermission();
  86 + const [registerDrawer, { openDrawer }] = useDrawer();
  87 + function handleSuccess() {
  88 + reload();
  89 + }
  90 + const [registerTable, { setProps, reload, setSelectedRowKeys }] = useTable({
  91 + title: '客户角色列表',
  92 + api: getRoleListByPage,
  93 + columns,
  94 + formConfig: {
  95 + labelWidth: 120,
  96 + schemas: searchFormSchema,
  97 + },
  98 + useSearchForm: true,
  99 + showTableSetting: true,
  100 + bordered: true,
  101 + showIndexColumn: false,
  102 + actionColumn: {
  103 + width: 200,
  104 + title: '操作',
  105 + dataIndex: 'action',
  106 + slots: { customRender: 'action' },
  107 + fixed: 'right',
  108 + },
  109 + });
  110 + const { hasBatchDelete, handleDeleteOrBatchDelete, selectionOptions, resetSelectedRowKeys } =
  111 + useBatchDelete(delRole, handleSuccess, setProps);
  112 + selectionOptions.rowSelection.getCheckboxProps = (record: Recordable) => {
  113 + // Demo:status为1的选择框禁用
  114 + if (record.status === 1) {
  115 + return { disabled: true };
  116 + } else {
  117 + return { disabled: false };
  118 + }
  119 + };
  120 + nextTick(() => {
  121 + setProps(selectionOptions);
  122 + });
  123 +
  124 + function handleCreate() {
  125 + openDrawer(true, {
  126 + isUpdate: false,
  127 + });
  128 + }
  129 +
  130 + function handleEdit(record: Recordable) {
  131 + openDrawer(true, {
  132 + record,
  133 + isUpdate: true,
  134 + });
  135 + }
  136 +
  137 + const statusChange = async (checked, record) => {
  138 + setProps({
  139 + loading: true,
  140 + });
  141 + setSelectedRowKeys([]);
  142 + resetSelectedRowKeys();
  143 + const newStatus = checked ? 1 : 0;
  144 + const { createMessage } = useMessage();
  145 + try {
  146 + await setRoleStatus(record.id, newStatus);
  147 + if (newStatus) {
  148 + createMessage.success(`启用成功`);
  149 + } else {
  150 + createMessage.success('禁用成功');
  151 + }
  152 + } finally {
  153 + setProps({
  154 + loading: false,
  155 + });
  156 + reload();
  157 + }
  158 + };
  159 +
  160 + return {
  161 + registerTable,
  162 + registerDrawer,
  163 + handleCreate,
  164 + handleEdit,
  165 + handleSuccess,
  166 + RoleEnum,
  167 + hasBatchDelete,
  168 + handleDeleteOrBatchDelete,
  169 + statusChange,
  170 + hasPermission,
  171 + };
  172 + },
  173 + });
  174 +</script>
  1 +import { BasicColumn } from '/@/components/Table';
  2 +import { FormSchema } from '/@/components/Table';
  3 +import { DictEnum } from '/@/enums/dictEnum';
  4 +import { RoleEnum } from '/@/enums/roleEnum';
  5 +
  6 +export enum KeysTypeEnum {
  7 + DISABLED = 'disabled',
  8 + ENABLED = 'enabled',
  9 +}
  10 +
  11 +export const RoleMenuDictEnum: Recordable<{ key: string; keyType: KeysTypeEnum }> = {
  12 + [RoleEnum.PLATFORM_ADMIN]: {
  13 + key: DictEnum.DISABLED_PLATFORM_ADMIN_AUTH,
  14 + keyType: KeysTypeEnum.DISABLED,
  15 + },
  16 + [RoleEnum.SYS_ADMIN]: { key: DictEnum.ENABLED_SYSADMIN_AUTH, keyType: KeysTypeEnum.ENABLED },
  17 + [RoleEnum.TENANT_ADMIN]: { key: DictEnum.DISABLED_TENANT_AUTH, keyType: KeysTypeEnum.DISABLED },
  18 + [RoleEnum.CUSTOMER_USER]: { key: DictEnum.DISABLE_CUSTOMER_AUTH, keyType: KeysTypeEnum.DISABLED },
  19 +};
  20 +
  21 +export const columns: BasicColumn[] = [
  22 + {
  23 + title: '角色名称',
  24 + dataIndex: 'name',
  25 + width: 200,
  26 + },
  27 + {
  28 + title: '角色Code',
  29 + dataIndex: 'code',
  30 + width: 200,
  31 + },
  32 + {
  33 + title: '状态',
  34 + dataIndex: 'status',
  35 + width: 120,
  36 + slots: { customRender: 'status' },
  37 + },
  38 +
  39 + {
  40 + title: '备注',
  41 + dataIndex: 'remark',
  42 + width: 240,
  43 + },
  44 + {
  45 + title: '创建时间',
  46 + dataIndex: 'createTime',
  47 + width: 180,
  48 + },
  49 +];
  50 +
  51 +export const searchFormSchema: FormSchema[] = [
  52 + {
  53 + field: 'roleName',
  54 + label: '角色名称',
  55 + component: 'Input',
  56 + colProps: { span: 6 },
  57 + componentProps: {
  58 + maxLength: 255,
  59 + },
  60 + },
  61 + {
  62 + field: 'status',
  63 + label: '状态',
  64 + component: 'Select',
  65 + componentProps: {
  66 + options: [
  67 + { label: '启用', value: 1 },
  68 + { label: '停用', value: 0 },
  69 + ],
  70 + },
  71 + colProps: { span: 6 },
  72 + },
  73 +];
  74 +
  75 +export const formSchema: FormSchema[] = [
  76 + {
  77 + field: 'name',
  78 + label: '角色名称',
  79 + required: true,
  80 + component: 'Input',
  81 + componentProps: {
  82 + maxLength: 255,
  83 + placeholder: '请输入角色名称',
  84 + },
  85 + },
  86 + {
  87 + field: 'status',
  88 + label: '状态',
  89 + component: 'RadioButtonGroup',
  90 + defaultValue: 1,
  91 + componentProps: {
  92 + options: [
  93 + { label: '启用', value: 1 },
  94 + { label: '停用', value: 0 },
  95 + ],
  96 + },
  97 + },
  98 + {
  99 + label: '备注',
  100 + field: 'remark',
  101 + component: 'InputTextArea',
  102 + componentProps: {
  103 + maxLength: 255,
  104 + placeholder: '请输入备注',
  105 + },
  106 + },
  107 + {
  108 + label: '',
  109 + field: 'menu',
  110 + slot: 'menu',
  111 + component: 'Input',
  112 + },
  113 +];
@@ -88,7 +88,7 @@ @@ -88,7 +88,7 @@
88 reload(); 88 reload();
89 } 89 }
90 const [registerTable, { setProps, reload, setSelectedRowKeys }] = useTable({ 90 const [registerTable, { setProps, reload, setSelectedRowKeys }] = useTable({
91 - title: '角色列表', 91 + title: '客户角色列表',
92 api: getRoleListByPage, 92 api: getRoleListByPage,
93 columns, 93 columns,
94 formConfig: { 94 formConfig: {
  1 +<template>
  2 + <BasicDrawer
  3 + v-bind="$attrs"
  4 + @register="registerDrawer"
  5 + showFooter
  6 + :title="getTitle"
  7 + width="500px"
  8 + @ok="handleSubmit"
  9 + >
  10 + <BasicForm @register="registerForm">
  11 + <template #menu>
  12 + <Spin :spinning="spinning">
  13 + <BasicTree
  14 + v-if="treeData.length"
  15 + :treeData="treeData"
  16 + :replaceFields="{ title: 'name', key: 'id' }"
  17 + :checkedKeys="roleMenus"
  18 + @check="handleCheckClick"
  19 + @unSelectAll="handleUnSelectAll"
  20 + checkable
  21 + toolbar
  22 + ref="treeRef"
  23 + title="权限分配"
  24 + />
  25 + </Spin>
  26 + </template>
  27 + </BasicForm>
  28 + </BasicDrawer>
  29 +</template>
  30 +<script lang="ts">
  31 + import { defineComponent, ref, computed, unref, nextTick } from 'vue';
  32 + import { BasicForm, useForm } from '/@/components/Form/index';
  33 + import { formSchema } from './role.data';
  34 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  35 + import { BasicTree, CheckEvent, TreeActionType, TreeItem, CheckKeys } from '/@/components/Tree';
  36 + const { t } = useI18n(); // 加载国际化
  37 + // 加载菜单数据
  38 + import { getAdminMenuList, getMenuList, getMenusIdsByRoleId } from '/@/api/sys/menu';
  39 + import { useI18n } from '/@/hooks/web/useI18n';
  40 + import { MenuRecord } from '/@/api/sys/model/menuModel';
  41 + import { saveOrUpdateRoleInfoWithMenu } from '/@/api/system/system';
  42 + import { RoleEnum } from '/@/enums/roleEnum';
  43 + import { useMessage } from '/@/hooks/web/useMessage';
  44 + import { findDictItemByCode } from '/@/api/system/dict';
  45 + import { Spin } from 'ant-design-vue';
  46 + import { useRole } from '/@/hooks/business/useRole';
  47 + import { RoleMenuDictEnum, KeysTypeEnum } from '../custom/role.data';
  48 +
  49 + type TreeData = MenuRecord & TreeItem;
  50 +
  51 + export default defineComponent({
  52 + name: 'RoleDrawer',
  53 + components: { BasicDrawer, BasicForm, BasicTree, Spin },
  54 + emits: ['success', 'register'],
  55 + setup(_, { emit }) {
  56 + const isUpdate = ref(true);
  57 + const treeData = ref<TreeData[]>([]);
  58 + const roleMenus = ref<string[]>([]);
  59 + const roleId = ref('');
  60 + const treeRef = ref<Nullable<TreeActionType>>(null);
  61 + const checked = ref<string[]>([]); //需要选中的节点
  62 + const spinning = ref(false);
  63 + const checkedKeysWithHalfChecked = ref<(string | number)[]>([]);
  64 +
  65 + const [registerForm, { resetFields, setFieldsValue, validate, updateSchema }] = useForm({
  66 + labelWidth: 90,
  67 + schemas: formSchema,
  68 + showActionButtonGroup: false,
  69 + });
  70 +
  71 + const transformName = (data: TreeData[]) => {
  72 + return data.map((item) => {
  73 + item.name = t(item.name);
  74 + if (item.children && item.children.length) {
  75 + item.children = transformName(item.children as unknown as TreeData[]);
  76 + }
  77 + return item;
  78 + });
  79 + };
  80 +
  81 + const { isPlatformAdmin } = useRole();
  82 + const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
  83 + if (data?.isUpdate) {
  84 + updateSchema({
  85 + field: 'status',
  86 + componentProps: {
  87 + options: [
  88 + { label: '启用', value: 1 },
  89 + { label: '停用', value: 0 },
  90 + ],
  91 + disabled: data.record.status == 1 ? true : false,
  92 + },
  93 + });
  94 + } else {
  95 + updateSchema({
  96 + field: 'status',
  97 + componentProps: {
  98 + options: [
  99 + { label: '启用', value: 1 },
  100 + { label: '停用', value: 0 },
  101 + ],
  102 + disabled: false,
  103 + },
  104 + });
  105 + }
  106 + resetFields();
  107 + roleId.value = '';
  108 + // 在打开弹窗时清除所有选择的菜单
  109 + treeRef.value && treeRef.value?.setCheckedKeys([]);
  110 + isUpdate.value = data.isUpdate;
  111 + const roleType = RoleEnum.TENANT_ADMIN;
  112 + try {
  113 + spinning.value = true;
  114 +
  115 + if (!unref(treeData).length) {
  116 + // 获取全部的菜单
  117 + const menuListModel = unref(isPlatformAdmin)
  118 + ? await getAdminMenuList()
  119 + : await getMenuList();
  120 + treeData.value = transformName(menuListModel as unknown as TreeData[]);
  121 + }
  122 + const keys = await getPermissionByRole(roleType);
  123 + const { keyType } = RoleMenuDictEnum[roleType];
  124 + treeData.value = filterPermissionTreeData(
  125 + unref(treeData) as unknown as TreeData[],
  126 + keys,
  127 + keyType
  128 + );
  129 +
  130 + // 更新
  131 + if (unref(isUpdate)) {
  132 + checked.value = [];
  133 + roleId.value = data.record.id;
  134 +
  135 + //通过角色id去获取角色对应的菜单的ids
  136 + checkedKeysWithHalfChecked.value = roleMenus.value = await getMenusIdsByRoleId(
  137 + data.record.id
  138 + );
  139 +
  140 + excludeHalfCheckedKeys(unref(treeData));
  141 +
  142 + await nextTick();
  143 + treeRef.value?.setCheckedKeys(roleMenus.value);
  144 + setFieldsValue(data.record);
  145 + } else {
  146 + }
  147 + } catch (error) {
  148 + throw error;
  149 + } finally {
  150 + spinning.value = false;
  151 + }
  152 + });
  153 + const getTitle = computed(() => (!unref(isUpdate) ? '新增管理员角色' : '编辑管理员角色'));
  154 +
  155 + // 取消全部的时候清除回显时获取的
  156 + const handleUnSelectAll = () => {
  157 + checkedKeysWithHalfChecked.value = [];
  158 + };
  159 +
  160 + async function handleSubmit() {
  161 + setDrawerProps({ loading: true, confirmLoading: true });
  162 + const { createMessage } = useMessage();
  163 + let treeCheckedKeys: string[] | CheckKeys =
  164 + (unref(treeRef)?.getCheckedKeys() as string[] | CheckKeys) || [];
  165 + //fix 取消层级独立后(unref(treeRef)?.getCheckedKeys() as string[])的数据不是数组,是{checked:[],halfChecked:[]}对象,迭代报错
  166 + if (!Array.isArray(treeCheckedKeys)) {
  167 + treeCheckedKeys = treeCheckedKeys?.checked;
  168 + }
  169 + const menu = [...new Set([...unref(checkedKeysWithHalfChecked), ...treeCheckedKeys])];
  170 + try {
  171 + const values = await validate();
  172 + const req = {
  173 + id: roleId.value,
  174 + name: values.name,
  175 + remark: values.remark,
  176 + status: values.status,
  177 + roleType: RoleEnum.TENANT_ADMIN,
  178 + menu,
  179 + };
  180 + if (req.menu == undefined || !req.menu.length)
  181 + return createMessage.error('请勾选权限菜单');
  182 + const res = await saveOrUpdateRoleInfoWithMenu(req);
  183 + if (res) {
  184 + closeDrawer();
  185 + emit('success');
  186 + createMessage.success(`${unref(isUpdate) ? '编辑' : '新增'}成功`);
  187 + setDrawerProps({ loading: false });
  188 + setDrawerProps({ confirmLoading: false });
  189 + }
  190 + } finally {
  191 + setTimeout(() => {
  192 + setDrawerProps({ loading: false });
  193 + setDrawerProps({ confirmLoading: false });
  194 + }, 300);
  195 + }
  196 + }
  197 +
  198 + const getPermissionByRole = async (roleType: RoleEnum) => {
  199 + try {
  200 + const { key } = RoleMenuDictEnum[roleType];
  201 + const res = await findDictItemByCode({ dictCode: key });
  202 + return res.map((item) => item.itemValue);
  203 + } catch (error) {}
  204 + return [];
  205 + };
  206 +
  207 + const filterPermissionTreeData = (
  208 + data: MenuRecord[],
  209 + permissionKeys: string[],
  210 + keysType: KeysTypeEnum
  211 + ): TreeData[] => {
  212 + const permissionCompare = (
  213 + data: MenuRecord[],
  214 + permissionKeys: string[],
  215 + keysType: KeysTypeEnum
  216 + ): TreeData[] => {
  217 + return data.filter((item) => {
  218 + item.name = t(item.name);
  219 + const findFlag = permissionKeys.includes(item.permission);
  220 +
  221 + item.show = findFlag ? keysType === KeysTypeEnum.ENABLED : undefined;
  222 +
  223 + if (item.children && item.children.length) {
  224 + if (item.show) return true;
  225 + if (item.show === undefined) {
  226 + item.children = permissionCompare(item.children, permissionKeys, keysType);
  227 + item.show = item.children.some((item) =>
  228 + keysType === KeysTypeEnum.ENABLED
  229 + ? item.show
  230 + : item.show === undefined
  231 + ? true
  232 + : item.show
  233 + );
  234 + return item.show;
  235 + }
  236 + }
  237 +
  238 + return keysType === KeysTypeEnum.ENABLED
  239 + ? item.show
  240 + : item.show === undefined
  241 + ? true
  242 + : item.show;
  243 + }) as unknown as TreeData[];
  244 + };
  245 +
  246 + return permissionCompare(data, permissionKeys, keysType);
  247 + };
  248 +
  249 + const excludeHalfCheckedKeys = (treeData: MenuRecord[]) => {
  250 + const needExcludeKeys: string[] = [];
  251 + const query = (data: MenuRecord[]) => {
  252 + data.forEach((item) => {
  253 + item.checked = roleMenus.value.includes(item.id);
  254 + if (item.children && item.children.length) {
  255 + query(item.children);
  256 + item.checked = item.children.every((item) => item.checked);
  257 + }
  258 + if (!item.checked) {
  259 + needExcludeKeys.push(item.id);
  260 + }
  261 + });
  262 + };
  263 + query(treeData);
  264 + roleMenus.value = unref(roleMenus).filter((key) => !needExcludeKeys.includes(key));
  265 + return needExcludeKeys;
  266 + };
  267 +
  268 + const handleCheckClick = (selectedKeys: CheckKeys, event: CheckEvent) => {
  269 + //fix 取消层级独立后selectedKeys不是数组,是{checked:[],halfChecked:[]}对象 迭代报错
  270 + if (!Array.isArray(selectedKeys)) {
  271 + selectedKeys = selectedKeys?.checked;
  272 + event.halfCheckedKeys = [];
  273 + }
  274 + checkedKeysWithHalfChecked.value = [
  275 + ...selectedKeys,
  276 + ...(event.halfCheckedKeys as string[]),
  277 + ];
  278 + };
  279 +
  280 + return {
  281 + spinning,
  282 + registerDrawer,
  283 + registerForm,
  284 + getTitle,
  285 + handleSubmit,
  286 + treeData,
  287 + roleMenus,
  288 + treeRef,
  289 + handleCheckClick,
  290 + handleUnSelectAll,
  291 + };
  292 + },
  293 + });
  294 +</script>
  295 +
  296 +<style scoped lang="less">
  297 + :deep(.vben-basic-tree) {
  298 + width: 100% !important;
  299 + }
  300 +
  301 + :deep(.is-unflod) {
  302 + display: none !important;
  303 + }
  304 +
  305 + :deep(.is-flod) {
  306 + display: none !important;
  307 + }
  308 +</style>
  1 +<template>
  2 + <div>
  3 + <BasicTable
  4 + @register="registerTable"
  5 + :rowSelection="{ type: 'checkbox' }"
  6 + :clickToRowSelect="false"
  7 + >
  8 + <template #toolbar>
  9 + <Authority value="api:yt:tenant:role:post">
  10 + <a-button type="primary" @click="handleCreate"> 新增管理员角色 </a-button>
  11 + </Authority>
  12 + <Authority value="api:yt:tenant:role:delete">
  13 + <Popconfirm
  14 + title="您确定要批量删除数据"
  15 + ok-text="确定"
  16 + cancel-text="取消"
  17 + @confirm="handleDeleteOrBatchDelete(null)"
  18 + >
  19 + <a-button type="primary" color="error" :disabled="hasBatchDelete"> 批量删除 </a-button>
  20 + </Popconfirm>
  21 + </Authority>
  22 + </template>
  23 + <template #status="{ record }">
  24 + <Switch
  25 + :checked="record.status === 1"
  26 + :loading="record.pendingStatus"
  27 + checkedChildren="启用"
  28 + unCheckedChildren="禁用"
  29 + @change="(checked:boolean)=>statusChange(checked,record)"
  30 + />
  31 + </template>
  32 + <template #action="{ record }">
  33 + <TableAction
  34 + :actions="[
  35 + {
  36 + label: '编辑',
  37 + auth: 'api:yt:tenant:role:update',
  38 + icon: 'clarity:note-edit-line',
  39 + onClick: handleEdit.bind(null, record),
  40 + },
  41 + {
  42 + label: '删除',
  43 + auth: 'api:yt:tenant:role:delete',
  44 + ifShow: !record.status,
  45 + icon: 'ant-design:delete-outlined',
  46 + color: 'error',
  47 + popConfirm: {
  48 + title: '是否确认删除',
  49 + confirm: handleDeleteOrBatchDelete.bind(null, record),
  50 + },
  51 + },
  52 + ]"
  53 + />
  54 + </template>
  55 + </BasicTable>
  56 + <RoleDrawer @register="registerDrawer" @success="handleSuccess" />
  57 + </div>
  58 +</template>
  59 +<script lang="ts">
  60 + import { defineComponent, nextTick } from 'vue';
  61 + import { BasicTable, useTable, TableAction } from '/@/components/Table';
  62 + import { delRole, getTenantRoleRoleListByPage, setRoleStatus } from '/@/api/system/system';
  63 + import { useMessage } from '/@/hooks/web/useMessage';
  64 + import { useDrawer } from '/@/components/Drawer';
  65 + import RoleDrawer from './RoleDrawer.vue';
  66 + import { columns, searchFormSchema } from './role.data';
  67 + import { RoleEnum } from '/@/enums/roleEnum';
  68 + import { Authority } from '/@/components/Authority';
  69 + import { useBatchDelete } from '/@/hooks/web/useBatchDelete';
  70 + import { Switch, Popconfirm } from 'ant-design-vue';
  71 +
  72 + export default defineComponent({
  73 + name: 'TenantRoleManagement',
  74 + components: { BasicTable, RoleDrawer, TableAction, Authority, Switch, Popconfirm },
  75 + setup() {
  76 + const [registerDrawer, { openDrawer }] = useDrawer();
  77 + function handleSuccess() {
  78 + reload();
  79 + }
  80 + const [registerTable, { reload, setProps, setSelectedRowKeys }] = useTable({
  81 + title: '管理员角色列表',
  82 + api: getTenantRoleRoleListByPage,
  83 + columns,
  84 + formConfig: {
  85 + labelWidth: 120,
  86 + schemas: searchFormSchema,
  87 + actionColOptions: { span: 12 },
  88 + },
  89 + useSearchForm: true,
  90 + showTableSetting: true,
  91 + bordered: true,
  92 + showIndexColumn: false,
  93 + actionColumn: {
  94 + width: 200,
  95 + title: '操作',
  96 + dataIndex: 'action',
  97 + slots: { customRender: 'action' },
  98 + fixed: 'right',
  99 + },
  100 + });
  101 + const { hasBatchDelete, handleDeleteOrBatchDelete, selectionOptions, resetSelectedRowKeys } =
  102 + useBatchDelete(delRole, handleSuccess, setProps);
  103 + selectionOptions.rowSelection.getCheckboxProps = (record: Recordable) => {
  104 + // Demo:status为1的选择框禁用
  105 + if (record.status === 1) {
  106 + return { disabled: true };
  107 + } else {
  108 + return { disabled: false };
  109 + }
  110 + };
  111 + nextTick(() => {
  112 + setProps(selectionOptions);
  113 + });
  114 +
  115 + function handleCreate() {
  116 + openDrawer(true, {
  117 + isUpdate: false,
  118 + });
  119 + }
  120 +
  121 + function handleEdit(record: Recordable) {
  122 + openDrawer(true, {
  123 + record,
  124 + isUpdate: true,
  125 + });
  126 + }
  127 + const statusChange = async (checked, record) => {
  128 + setProps({
  129 + loading: true,
  130 + });
  131 + setSelectedRowKeys([]);
  132 + resetSelectedRowKeys();
  133 + const newStatus = checked ? 1 : 0;
  134 + const { createMessage } = useMessage();
  135 + try {
  136 + await setRoleStatus(record.id, newStatus);
  137 + if (newStatus) {
  138 + createMessage.success(`启用成功`);
  139 + } else {
  140 + createMessage.success('禁用成功');
  141 + }
  142 + } finally {
  143 + setProps({
  144 + loading: false,
  145 + });
  146 + reload();
  147 + }
  148 + };
  149 +
  150 + return {
  151 + statusChange,
  152 + registerTable,
  153 + registerDrawer,
  154 + handleCreate,
  155 + handleEdit,
  156 + handleSuccess,
  157 + RoleEnum,
  158 + hasBatchDelete,
  159 + handleDeleteOrBatchDelete,
  160 + };
  161 + },
  162 + });
  163 +</script>
  1 +import { BasicColumn } from '/@/components/Table';
  2 +import { FormSchema } from '/@/components/Table';
  3 +import { RoleEnum } from '/@/enums/roleEnum';
  4 +export const columns: BasicColumn[] = [
  5 + {
  6 + title: '角色名称',
  7 + dataIndex: 'name',
  8 + width: 180,
  9 + },
  10 + {
  11 + title: '角色Code',
  12 + dataIndex: 'code',
  13 + width: 180,
  14 + },
  15 + {
  16 + title: '状态',
  17 + dataIndex: 'status',
  18 + width: 120,
  19 + slots: { customRender: 'status' },
  20 + },
  21 + {
  22 + title: '备注',
  23 + dataIndex: 'remark',
  24 + width: 240,
  25 + },
  26 + {
  27 + title: '创建时间',
  28 + dataIndex: 'createTime',
  29 + width: 180,
  30 + },
  31 +];
  32 +
  33 +export const searchFormSchema: FormSchema[] = [
  34 + {
  35 + field: 'roleName',
  36 + label: '角色名称',
  37 + component: 'Input',
  38 + colProps: { span: 6 },
  39 + componentProps: {
  40 + maxLength: 255,
  41 + placeholder: '请输入角色名称',
  42 + },
  43 + },
  44 +
  45 + {
  46 + field: 'roleType',
  47 + label: '',
  48 + component: 'Input',
  49 + colProps: { span: 8 },
  50 + defaultValue: RoleEnum.TENANT_ADMIN,
  51 + ifShow: false,
  52 + componentProps: {
  53 + maxLength: 20,
  54 + },
  55 + },
  56 +
  57 + {
  58 + field: 'status',
  59 + label: '状态',
  60 + component: 'Select',
  61 + componentProps: {
  62 + options: [
  63 + { label: '启用', value: 1 },
  64 + { label: '停用', value: 0 },
  65 + ],
  66 + },
  67 + colProps: { span: 6 },
  68 + },
  69 +];
  70 +
  71 +export const formSchema: FormSchema[] = [
  72 + {
  73 + field: 'name',
  74 + label: '角色名称',
  75 + required: true,
  76 + component: 'Input',
  77 + componentProps: {
  78 + maxLength: 255,
  79 + placeholder: '请输入角色名称',
  80 + },
  81 + },
  82 + {
  83 + field: 'status',
  84 + label: '状态',
  85 + component: 'RadioButtonGroup',
  86 + defaultValue: 1,
  87 + componentProps: {
  88 + options: [
  89 + { label: '启用', value: 1 },
  90 + { label: '停用', value: 0 },
  91 + ],
  92 + },
  93 + },
  94 + {
  95 + label: '备注',
  96 + field: 'remark',
  97 + component: 'InputTextArea',
  98 + componentProps: {
  99 + maxLength: 255,
  100 + placeholder: '请输入备注',
  101 + },
  102 + },
  103 + {
  104 + label: ' ',
  105 + field: 'menu',
  106 + slot: 'menu',
  107 + component: 'Input',
  108 + componentProps: {
  109 + maxLength: 255,
  110 + },
  111 + },
  112 +];
@@ -15,7 +15,9 @@ import { ProductPicker, validateProductPicker } from '../ProductPicker'; @@ -15,7 +15,9 @@ import { ProductPicker, validateProductPicker } from '../ProductPicker';
15 import { useGlobSetting } from '/@/hooks/setting'; 15 import { useGlobSetting } from '/@/hooks/setting';
16 import { TransportTypeEnum } from '/@/enums/deviceEnum'; 16 import { TransportTypeEnum } from '/@/enums/deviceEnum';
17 import { validateTCPCustomCommand } from '/@/components/Form/src/components/ThingsModelForm'; 17 import { validateTCPCustomCommand } from '/@/components/Form/src/components/ThingsModelForm';
  18 +import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect';
18 19
  20 +useComponentRegister('OrgTreeSelect', OrgTreeSelect);
19 useComponentRegister('DevicePicker', DevicePicker); 21 useComponentRegister('DevicePicker', DevicePicker);
20 useComponentRegister('ProductPicker', ProductPicker); 22 useComponentRegister('ProductPicker', ProductPicker);
21 useComponentRegister('PollCommandInput', PollCommandInput); 23 useComponentRegister('PollCommandInput', PollCommandInput);
@@ -25,6 +27,8 @@ const { disabledTaskCenterExecuteIntervalUnitSecond } = useGlobSetting(); @@ -25,6 +27,8 @@ const { disabledTaskCenterExecuteIntervalUnitSecond } = useGlobSetting();
25 export enum FormFieldsEnum { 27 export enum FormFieldsEnum {
26 // 任务名称 28 // 任务名称
27 NAME = 'name', 29 NAME = 'name',
  30 + // 所属组织
  31 + ORGANIZATION_ID = 'organizationId',
28 // 目标类型 32 // 目标类型
29 TARGET_TYPE = 'targetType', 33 TARGET_TYPE = 'targetType',
30 // 设备类型选择 34 // 设备类型选择
@@ -99,16 +103,56 @@ export const formSchemas: FormSchema[] = [ @@ -99,16 +103,56 @@ export const formSchemas: FormSchema[] = [
99 }, 103 },
100 }, 104 },
101 { 105 {
  106 + required: true,
  107 + field: FormFieldsEnum.ORGANIZATION_ID,
  108 + label: '所属组织',
  109 + colProps: { span: 24 },
  110 + component: 'OrgTreeSelect',
  111 + componentProps: ({ formActionType }) => {
  112 + const { setFieldsValue } = formActionType;
  113 + return {
  114 + onChange(key: string) {
  115 + setFieldsValue({
  116 + [FormFieldsEnum.EXECUTE_TARGET_DATA]: {
  117 + paramsOrg: key, //传递组织id到DevicePicker组件里进行组织过滤
  118 + change: true,
  119 + },
  120 + [FormFieldsEnum.DEVICE_PROFILE]: {
  121 + paramsOrg: key, //传递组织id到DevicePicker组件里进行组织过滤
  122 + change: true,
  123 + },
  124 + });
  125 + },
  126 + getPopupContainer: () => document.body,
  127 + };
  128 + },
  129 + },
  130 + {
102 field: FormFieldsEnum.TARGET_TYPE, 131 field: FormFieldsEnum.TARGET_TYPE,
103 component: 'RadioGroup', 132 component: 'RadioGroup',
104 label: '目标类型', 133 label: '目标类型',
105 defaultValue: TaskTargetEnum.DEVICES, 134 defaultValue: TaskTargetEnum.DEVICES,
106 helpMessage: ['执行任务的目标设备,可以是多个指定的设备,也可以是一个设备类型下的所有设备.'], 135 helpMessage: ['执行任务的目标设备,可以是多个指定的设备,也可以是一个设备类型下的所有设备.'],
107 - componentProps: {  
108 - options: [  
109 - { label: TaskTargetNameEnum.DEVICES, value: TaskTargetEnum.DEVICES },  
110 - { label: TaskTargetNameEnum.PRODUCTS, value: TaskTargetEnum.PRODUCTS },  
111 - ], 136 + componentProps: ({ formActionType, formModel }) => {
  137 + const { setFieldsValue } = formActionType;
  138 + return {
  139 + options: [
  140 + { label: TaskTargetNameEnum.DEVICES, value: TaskTargetEnum.DEVICES },
  141 + { label: TaskTargetNameEnum.PRODUCTS, value: TaskTargetEnum.PRODUCTS },
  142 + ],
  143 + onChange() {
  144 + setFieldsValue({
  145 + [FormFieldsEnum.EXECUTE_TARGET_DATA]: {
  146 + paramsOrg: formModel[FormFieldsEnum.ORGANIZATION_ID], //传递组织id到DevicePicker组件里进行组织过滤
  147 + change: true,
  148 + },
  149 + [FormFieldsEnum.DEVICE_PROFILE]: {
  150 + paramsOrg: formModel[FormFieldsEnum.ORGANIZATION_ID], //传递组织id到DevicePicker组件里进行组织过滤
  151 + change: true,
  152 + },
  153 + });
  154 + },
  155 + };
112 }, 156 },
113 }, 157 },
114 { 158 {
1 <script lang="ts" setup> 1 <script lang="ts" setup>
2 import { BasicForm, useForm } from '/@/components/Form'; 2 import { BasicForm, useForm } from '/@/components/Form';
3 import { BasicModal, useModalInner } from '/@/components/Modal'; 3 import { BasicModal, useModalInner } from '/@/components/Modal';
4 - import { formSchemas } from './config'; 4 + import { FormFieldsEnum, formSchemas } from './config';
5 import { nextTick, ref } from 'vue'; 5 import { nextTick, ref } from 'vue';
6 import { composeData, parseData } from './util'; 6 import { composeData, parseData } from './util';
7 import { createTask, updateTask } from '/@/api/task'; 7 import { createTask, updateTask } from '/@/api/task';
@@ -40,6 +40,18 @@ @@ -40,6 +40,18 @@
40 if (record && mode === DataActionModeEnum.UPDATE) { 40 if (record && mode === DataActionModeEnum.UPDATE) {
41 const res = parseData(record); 41 const res = parseData(record);
42 setFieldsValue({ ...res }); 42 setFieldsValue({ ...res });
  43 + setFieldsValue({
  44 + [FormFieldsEnum.EXECUTE_TARGET_DATA]: {
  45 + ...res.executeTargetData,
  46 + paramsOrg: res.organizationId, //传递组织id到DevicePicker组件里进行组织过滤
  47 + change: false,
  48 + },
  49 + [FormFieldsEnum.DEVICE_PROFILE]: {
  50 + ...res.executeTargetData,
  51 + paramsOrg: res.organizationId, //传递组织id到DevicePicker组件里进行组织过滤
  52 + change: false,
  53 + },
  54 + });
43 } 55 }
44 } 56 }
45 ); 57 );
@@ -92,6 +92,7 @@ export const composeData = (result: Required<FormValueType>): CreateTaskRecordTy @@ -92,6 +92,7 @@ export const composeData = (result: Required<FormValueType>): CreateTaskRecordTy
92 92
93 return { 93 return {
94 name, 94 name,
  95 + organizationId: result.organizationId,
95 targetType, 96 targetType,
96 executeContent: { 97 executeContent: {
97 pushContent: { 98 pushContent: {
@@ -127,6 +128,7 @@ export const parseData = (result: TaskRecordType): Required<FormValueType> => { @@ -127,6 +128,7 @@ export const parseData = (result: TaskRecordType): Required<FormValueType> => {
127 const { type: executeTimeType, period, periodType, time, pollUnit } = executeTime; 128 const { type: executeTimeType, period, periodType, time, pollUnit } = executeTime;
128 return { 129 return {
129 name, 130 name,
  131 + organizationId: result.organizationId,
130 targetType, 132 targetType,
131 rpcCommand: pushWay === PushWayEnum.MQTT ? JSON.stringify(rpcCommand, null, 2) : rpcCommand, 133 rpcCommand: pushWay === PushWayEnum.MQTT ? JSON.stringify(rpcCommand, null, 2) : rpcCommand,
132 transportType: pushWay, 134 transportType: pushWay,
@@ -19,6 +19,7 @@ @@ -19,6 +19,7 @@
19 value?: Recordable; 19 value?: Recordable;
20 multiple?: boolean; 20 multiple?: boolean;
21 max?: 3; 21 max?: 3;
  22 + paramsOrg?: string;
22 }>(), 23 }>(),
23 { 24 {
24 value: () => ({}), 25 value: () => ({}),
@@ -74,7 +75,7 @@ @@ -74,7 +75,7 @@
74 handleChange(key, value, option); 75 handleChange(key, value, option);
75 emit('update:value', { ..._value }); 76 emit('update:value', { ..._value });
76 }; 77 };
77 - const [register, { setFieldsValue, getFieldsValue, resetFields }] = useForm({ 78 + const [register, { setFieldsValue, getFieldsValue, resetFields, updateSchema }] = useForm({
78 schemas: [ 79 schemas: [
79 { 80 {
80 field: FormFieldsEnum.DEVICE_TYPE, 81 field: FormFieldsEnum.DEVICE_TYPE,
@@ -206,12 +207,45 @@ @@ -206,12 +207,45 @@
206 watch( 207 watch(
207 () => props.value, 208 () => props.value,
208 () => { 209 () => {
  210 + if (Reflect.has(props.value, 'paramsOrg')) {
  211 + const { paramsOrg, change } = props.value;
  212 + nextTick(() => {
  213 + updateOrgSchema(paramsOrg, change);
  214 + });
  215 + }
209 setValue(); 216 setValue();
210 }, 217 },
211 { 218 {
212 immediate: true, 219 immediate: true,
213 } 220 }
214 ); 221 );
  222 +
  223 + //更新组织,只能选择层级以下
  224 + const getOrganizationLevelList = async (organizationId) => {
  225 + const data = await getOrganizationList({ organizationId });
  226 + copyTransFun(data as any);
  227 + return data;
  228 + };
  229 +
  230 + const updateOrgSchema = async (organizationId: string, change?: boolean) => {
  231 + const data = await getOrganizationLevelList(organizationId);
  232 + if (change) {
  233 + setFieldsValue({
  234 + [FormFieldsEnum.ORGANIZATION]: null,
  235 + [FormFieldsEnum.DEVICE]: [],
  236 + });
  237 + }
  238 + updateSchema({
  239 + field: FormFieldsEnum.ORGANIZATION,
  240 + componentProps: {
  241 + treeData: data,
  242 + onChange(value: string, option: Recordable) {
  243 + handleEmit(FormFieldsEnum.ORGANIZATION, value, option);
  244 + },
  245 + },
  246 + });
  247 + };
  248 + //
215 </script> 249 </script>
216 250
217 <template> 251 <template>
@@ -14,6 +14,7 @@ @@ -14,6 +14,7 @@
14 const props = withDefaults( 14 const props = withDefaults(
15 defineProps<{ 15 defineProps<{
16 value?: Recordable; 16 value?: Recordable;
  17 + paramsOrg?: string;
17 }>(), 18 }>(),
18 { 19 {
19 value: () => ({}), 20 value: () => ({}),
@@ -42,7 +43,7 @@ @@ -42,7 +43,7 @@
42 emit('update:value', { ..._value }); 43 emit('update:value', { ..._value });
43 }; 44 };
44 45
45 - const [register, { setFieldsValue, getFieldsValue, resetFields }] = useForm({ 46 + const [register, { setFieldsValue, getFieldsValue, resetFields, updateSchema }] = useForm({
46 schemas: [ 47 schemas: [
47 { 48 {
48 field: FormFieldsEnum.DEVICE_TYPE, 49 field: FormFieldsEnum.DEVICE_TYPE,
@@ -135,12 +136,45 @@ @@ -135,12 +136,45 @@
135 watch( 136 watch(
136 () => props.value, 137 () => props.value,
137 () => { 138 () => {
  139 + if (Reflect.has(props.value, 'paramsOrg')) {
  140 + nextTick(() => {
  141 + const { paramsOrg, change } = props.value;
  142 + updateOrgSchema(paramsOrg, change);
  143 + });
  144 + }
138 setValue(); 145 setValue();
139 }, 146 },
140 { 147 {
141 immediate: true, 148 immediate: true,
142 } 149 }
143 ); 150 );
  151 +
  152 + //更新组织,只能选择层级以下
  153 + const getOrganizationLevelList = async (organizationId) => {
  154 + const data = await getOrganizationList({ organizationId });
  155 + copyTransFun(data as any);
  156 + return data;
  157 + };
  158 +
  159 + const updateOrgSchema = async (organizationId: string, change?: boolean) => {
  160 + const data = await getOrganizationLevelList(organizationId);
  161 + if (change) {
  162 + setFieldsValue({
  163 + [FormFieldsEnum.ORGANIZATION]: null,
  164 + [FormFieldsEnum.DEVICE_PROFILE]: [],
  165 + });
  166 + }
  167 + updateSchema({
  168 + field: FormFieldsEnum.ORGANIZATION,
  169 + componentProps: {
  170 + treeData: data,
  171 + onChange(value: string, option: Recordable) {
  172 + handleEmit(FormFieldsEnum.ORGANIZATION, value, option);
  173 + },
  174 + },
  175 + });
  176 + };
  177 + //
144 </script> 178 </script>
145 179
146 <template> 180 <template>
@@ -14,6 +14,7 @@ export enum FormFieldsEnum { @@ -14,6 +14,7 @@ export enum FormFieldsEnum {
14 TASK_TYPE = 'taskType', 14 TASK_TYPE = 'taskType',
15 TARGET_IDS = 'targetIds', 15 TARGET_IDS = 'targetIds',
16 TASK_RECORD = 'taskRecord', 16 TASK_RECORD = 'taskRecord',
  17 + TB_DEVICE_ID = 'tbDeviceId',
17 } 18 }
18 19
19 export enum TargetType { 20 export enum TargetType {
@@ -61,6 +62,12 @@ export const formSchemas: FormSchema[] = [ @@ -61,6 +62,12 @@ export const formSchemas: FormSchema[] = [
61 }, 62 },
62 }, 63 },
63 { 64 {
  65 + field: FormFieldsEnum.TB_DEVICE_ID,
  66 + component: 'Input',
  67 + label: '',
  68 + show: false,
  69 + },
  70 + {
64 field: FormFieldsEnum.TARGET_IDS, 71 field: FormFieldsEnum.TARGET_IDS,
65 component: 'ApiSelect', 72 component: 'ApiSelect',
66 label: '指定目标设备', 73 label: '指定目标设备',
@@ -68,6 +75,7 @@ export const formSchemas: FormSchema[] = [ @@ -68,6 +75,7 @@ export const formSchemas: FormSchema[] = [
68 rules: [{ required: true, message: '请选择指定目标设备', type: 'array' }], 75 rules: [{ required: true, message: '请选择指定目标设备', type: 'array' }],
69 componentProps: ({ formModel }) => { 76 componentProps: ({ formModel }) => {
70 const record = JSON.parse(formModel[FormFieldsEnum.TASK_RECORD]) as TaskRecordType; 77 const record = JSON.parse(formModel[FormFieldsEnum.TASK_RECORD]) as TaskRecordType;
  78 + const tbDeviceId = formModel[FormFieldsEnum.TB_DEVICE_ID];
71 const isDevices = record.targetType === TaskTargetEnum.DEVICES; 79 const isDevices = record.targetType === TaskTargetEnum.DEVICES;
72 const { executeTarget } = record; 80 const { executeTarget } = record;
73 const { organizationId, data } = executeTarget; 81 const { organizationId, data } = executeTarget;
@@ -79,6 +87,7 @@ export const formSchemas: FormSchema[] = [ @@ -79,6 +87,7 @@ export const formSchemas: FormSchema[] = [
79 return result.data.map((item) => ({ 87 return result.data.map((item) => ({
80 label: item.alias || item.name, 88 label: item.alias || item.name,
81 value: item.tbDeviceId, 89 value: item.tbDeviceId,
  90 + disabled: tbDeviceId ? item.tbDeviceId !== tbDeviceId : false,
82 })); 91 }));
83 } else { 92 } else {
84 const result = await getMeetTheConditionsDevice({ 93 const result = await getMeetTheConditionsDevice({
@@ -4,30 +4,54 @@ @@ -4,30 +4,54 @@
4 import { BasicForm, useForm } from '/@/components/Form'; 4 import { BasicForm, useForm } from '/@/components/Form';
5 import { BasicModal, useModalInner } from '/@/components/Modal'; 5 import { BasicModal, useModalInner } from '/@/components/Modal';
6 import { TaskTargetEnum, TaskTypeNameEnum } from '../../config'; 6 import { TaskTargetEnum, TaskTypeNameEnum } from '../../config';
7 - import { FormValue, TargetType, formSchemas } from './config'; 7 + import { FormFieldsEnum, FormValue, TargetNameType, TargetType, formSchemas } from './config';
8 import { unref } from 'vue'; 8 import { unref } from 'vue';
9 import { immediateExecute } from '/@/api/task'; 9 import { immediateExecute } from '/@/api/task';
10 import { useMessage } from '/@/hooks/web/useMessage'; 10 import { useMessage } from '/@/hooks/web/useMessage';
11 const props = defineProps<{ 11 const props = defineProps<{
12 reload: Fn; 12 reload: Fn;
  13 + fromOrigin?: boolean;
  14 + tbDeviceId?: string;
13 }>(); 15 }>();
14 16
15 defineEmits(['register']); 17 defineEmits(['register']);
16 18
17 const dataSource = ref<TaskRecordType>(); 19 const dataSource = ref<TaskRecordType>();
18 20
  21 + const updateExecuteTargetType = (options: Recordable[]) => {
  22 + updateSchema({
  23 + field: FormFieldsEnum.EXECUTE_TARGET_TYPE,
  24 + componentProps: {
  25 + options,
  26 + },
  27 + });
  28 + };
  29 +
19 const [registerModal, { closeModal }] = useModalInner((record: TaskRecordType) => { 30 const [registerModal, { closeModal }] = useModalInner((record: TaskRecordType) => {
20 resetFields(); 31 resetFields();
21 dataSource.value = record; 32 dataSource.value = record;
22 if (record) { 33 if (record) {
23 setFieldsValue({ taskRecord: JSON.stringify(record) } as FormValue); 34 setFieldsValue({ taskRecord: JSON.stringify(record) } as FormValue);
  35 + // 如果是从设备详情里面的任务进来的
  36 + if (props.fromOrigin) {
  37 + setFieldsValue({ tbDeviceId: props.tbDeviceId } as FormValue);
  38 + setFieldsValue({ [FormFieldsEnum.EXECUTE_TARGET_TYPE]: TargetType.ASSIGN } as FormValue);
  39 + setFieldsValue({ [FormFieldsEnum.TARGET_IDS]: [props.tbDeviceId] } as FormValue);
  40 + updateExecuteTargetType([{ label: TargetNameType.ASSIGN, value: TargetType.ASSIGN }]);
  41 + } else {
  42 + updateExecuteTargetType([
  43 + { label: TargetNameType.ALL, value: TargetType.ALL },
  44 + { label: TargetNameType.ASSIGN, value: TargetType.ASSIGN },
  45 + ]);
  46 + }
24 } 47 }
25 }); 48 });
26 49
27 - const [registerForm, { setFieldsValue, getFieldsValue, resetFields, validate }] = useForm({  
28 - schemas: formSchemas,  
29 - showActionButtonGroup: false,  
30 - }); 50 + const [registerForm, { setFieldsValue, getFieldsValue, resetFields, validate, updateSchema }] =
  51 + useForm({
  52 + schemas: formSchemas,
  53 + showActionButtonGroup: false,
  54 + });
31 55
32 const composeData = (record: FormValue): ImmediateExecuteTaskType => { 56 const composeData = (record: FormValue): ImmediateExecuteTaskType => {
33 const { executeTarget, targetIds } = record; 57 const { executeTarget, targetIds } = record;
@@ -154,10 +154,13 @@ @@ -154,10 +154,13 @@
154 } 154 }
155 155
156 function handleResetPassword(record: Recordable) { 156 function handleResetPassword(record: Recordable) {
157 - resetPassword(record.id).then(() => {  
158 - createMessage.success('清空密码成功'); 157 + try {
  158 + resetPassword(record.id).then(() => {
  159 + createMessage.success('清空密码成功');
  160 + });
  161 + } finally {
159 handleSuccess(); 162 handleSuccess();
160 - }); 163 + }
161 } 164 }
162 function handleSendMsg(record: Recordable) { 165 function handleSendMsg(record: Recordable) {
163 const req = new SendResetPasswordEmailMsg(record.id, MessageTypeEnum.PHONE_MESSAGE); 166 const req = new SendResetPasswordEmailMsg(record.id, MessageTypeEnum.PHONE_MESSAGE);
@@ -84,6 +84,7 @@ @@ -84,6 +84,7 @@
84 formConfig: { 84 formConfig: {
85 labelWidth: 120, 85 labelWidth: 120,
86 schemas: searchFormSchema, 86 schemas: searchFormSchema,
  87 + actionColOptions: { span: 12 },
87 }, 88 },
88 useSearchForm: true, 89 useSearchForm: true,
89 showTableSetting: true, 90 showTableSetting: true,