Commit b289da6c45ccaa284fe8c2645e1d8d6099a1ee7b
Merge branch 'feat/account-role-manage' into 'main_dev'
# Conflicts: # src/views/report/config/components/SelectDevice.vue
Showing
52 changed files
with
4131 additions
and
89 deletions
Too many changes to show.
To preserve performance only 52 of 61 files are displayed.
... | ... | @@ -8,10 +8,10 @@ VITE_GLOB_PUBLIC_PATH = / |
8 | 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 | 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 | 16 | # Delete console |
17 | 17 | VITE_GLOB_DROP_CONSOLE = true | ... | ... |
... | ... | @@ -57,6 +57,21 @@ enum DeviceManagerApi { |
57 | 57 | * @description 批量变更产品 |
58 | 58 | */ |
59 | 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 | 77 | export const devicePage = (params: DeviceQueryParam) => { |
... | ... | @@ -398,3 +413,43 @@ export const doBatchUpdateProduct = (params: { deviceIds: string[]; deviceProfil |
398 | 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 | 53 | customerId?: string; |
54 | 54 | alias?: string; |
55 | 55 | tbDeviceId?: string; |
56 | + customerName?: string; | |
56 | 57 | } |
57 | 58 | |
58 | 59 | export interface DeviceProfileModel { |
... | ... | @@ -207,6 +208,9 @@ export interface DeviceRecord { |
207 | 208 | manufacturer: string; |
208 | 209 | streamMode: string; |
209 | 210 | }; |
211 | + customerName?: string; | |
212 | + customerId?: string; | |
213 | + gatewayId?: string; | |
210 | 214 | } |
211 | 215 | |
212 | 216 | export interface DeviceModelOfMatterAttrs { | ... | ... |
src/api/system/account.ts
0 → 100644
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 | 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 | 102 | const userInfo: any = getAuthCache(USER_INFO_KEY); |
100 | 103 | const role = userInfo.roles[0]; |
101 | 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 | 108 | return item.roleType === 'PLATFORM_ADMIN'; |
104 | 109 | } else { |
105 | 110 | return item.roleType === 'CUSTOMER_USER'; |
... | ... | @@ -178,7 +183,7 @@ export const resetPassword = (params: ChangeAccountParams) => |
178 | 183 | * 清除密码 |
179 | 184 | * @param params |
180 | 185 | */ |
181 | -export const clearUserPassword = (userId: string) => | |
186 | +export const clearUserPassword = (userId: string, level: number) => | |
182 | 187 | defHttp.post({ |
183 | - url: Api.RESET_USER_PASSWORD + userId, | |
188 | + url: Api.RESET_USER_PASSWORD + userId + '/' + level, | |
184 | 189 | }); | ... | ... |
... | ... | @@ -102,7 +102,11 @@ |
102 | 102 | class="flex-auto" |
103 | 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 | 110 | <Button type="primary" :disabled="disabled"> |
107 | 111 | <Icon |
108 | 112 | :icon="controlDisableStatus ? 'ant-design:lock-outlined' : 'ant-design:unlock-outlined'" | ... | ... |
... | ... | @@ -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 | 31 | </div> |
28 | 32 | </BasicDrawer> |
29 | 33 | </template> |
... | ... | @@ -36,11 +40,13 @@ |
36 | 40 | import { alarmLevel, statusType } from '/@/views/device/list/config/detail.config'; |
37 | 41 | import { BasicDrawer, useDrawerInner } from '/@/components/Drawer'; |
38 | 42 | import { AlarmStatus } from '/@/enums/alarmEnum'; |
43 | + import { Authority } from '/@/components/Authority'; | |
39 | 44 | export default defineComponent({ |
40 | 45 | name: 'AlarmDetailDrawer', |
41 | 46 | components: { |
42 | 47 | BasicForm, |
43 | 48 | BasicDrawer, |
49 | + Authority, | |
44 | 50 | }, |
45 | 51 | emits: ['success', 'register'], |
46 | 52 | setup(_, { emit }) { | ... | ... |
... | ... | @@ -19,12 +19,16 @@ |
19 | 19 | </Button> |
20 | 20 | </template> |
21 | 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 | 32 | </template> |
29 | 33 | </BasicTable> |
30 | 34 | <AlarmDetailDrawer @register="registerDetailDrawer" @success="handleSuccess" /> |
... | ... | @@ -49,6 +53,7 @@ |
49 | 53 | import { AlarmLogItem } from '/@/api/device/model/deviceConfigModel'; |
50 | 54 | import { AlarmStatus } from '/@/enums/alarmEnum'; |
51 | 55 | import { useMessage } from '/@/hooks/web/useMessage'; |
56 | + import { Authority } from '/@/components/Authority'; | |
52 | 57 | |
53 | 58 | const props = defineProps<{ |
54 | 59 | deviceId?: string; | ... | ... |
... | ... | @@ -10,6 +10,7 @@ |
10 | 10 | <BasicForm @register="registerForm"> |
11 | 11 | <!-- 模板选择 --> |
12 | 12 | <template #templateId="{ model }"> |
13 | + <span style="display: none">{{ getFieldOrg(model['organizationId']) }}</span> | |
13 | 14 | <Select |
14 | 15 | v-model:value="model['templateId']" |
15 | 16 | placeholder="请选择模板" |
... | ... | @@ -35,7 +36,7 @@ |
35 | 36 | </BasicDrawer> |
36 | 37 | </template> |
37 | 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 | 40 | import { BasicForm, useForm } from '/@/components/Form'; |
40 | 41 | import { Select } from 'ant-design-vue'; |
41 | 42 | import { formSchema, PC_DEFAULT_CONTENT, PHONE_DEFAULT_CONTENT, Platform } from './center.data'; |
... | ... | @@ -61,11 +62,12 @@ |
61 | 62 | |
62 | 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 | 72 | const updateEnableTemplate = async (enable: number, disabled: boolean) => { |
71 | 73 | await setFieldsValue({ |
... | ... | @@ -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 | 105 | const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => { |
... | ... | @@ -107,6 +117,7 @@ |
107 | 117 | ]; |
108 | 118 | } |
109 | 119 | if (data.record.organizationDTO) { |
120 | + organizationIdStr.value = data.record['organizationId']; | |
110 | 121 | await setFieldsValue(data.record); |
111 | 122 | } else { |
112 | 123 | Reflect.deleteProperty(data.record, 'organizationId'); |
... | ... | @@ -135,16 +146,41 @@ |
135 | 146 | //新增修改 |
136 | 147 | const templateDisabled = ref(false); |
137 | 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 | 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 | 184 | selectTemplateOptions.value = items.map((item) => ({ |
149 | 185 | ...item, |
150 | 186 | label: item.name, |
... | ... | @@ -154,6 +190,7 @@ |
154 | 190 | const selectOptions: Ref<any[]> = ref([]); |
155 | 191 | |
156 | 192 | const handleTemplateChange = async (_, option) => { |
193 | + clearValidate('templateId'); | |
157 | 194 | const { productAndDevice } = option; |
158 | 195 | setFieldsValue({ platform: option?.platform }); |
159 | 196 | await nextTick(); |
... | ... | @@ -249,6 +286,7 @@ |
249 | 286 | createPickerSearch, |
250 | 287 | selectDeviceProfileRef, |
251 | 288 | templateDisabled, |
289 | + getFieldOrg, | |
252 | 290 | }; |
253 | 291 | }, |
254 | 292 | }); | ... | ... |
... | ... | @@ -36,7 +36,7 @@ const updateProductHelpMessage = [ |
36 | 36 | '修改设备产品时,如果"大屏"、"组态"、"看板"、"场景联动"、"数据流转"等有关联设备,请自行调整并保存。', |
37 | 37 | ]; |
38 | 38 | |
39 | -const updateOrgHelpMessage = [ | |
39 | +export const updateOrgHelpMessage = [ | |
40 | 40 | '注意:', |
41 | 41 | '1、修改设备组织时,如果"大屏"、"组态"、"看板"、"场景联动"、"数据流转"等有关联设备,请自行调整并保存。', |
42 | 42 | '2、网关设备在修改组织时,其关联网关子设备组织归属于网关组织及以下不用修改,否则将同步更新网关子设备组织为网关组织。', |
... | ... | @@ -314,6 +314,12 @@ export const step1Schemas: FormSchema[] = [ |
314 | 314 | ifShow: false, |
315 | 315 | }, |
316 | 316 | { |
317 | + field: 'customerId', | |
318 | + label: '用来判断编辑时禁用组织修改', | |
319 | + component: 'Input', | |
320 | + ifShow: false, | |
321 | + }, | |
322 | + { | |
317 | 323 | field: 'organizationId', |
318 | 324 | label: '所属组织', |
319 | 325 | component: 'LockControlGroup', |
... | ... | @@ -327,6 +333,7 @@ export const step1Schemas: FormSchema[] = [ |
327 | 333 | return { |
328 | 334 | component: 'OrgTreeSelect', |
329 | 335 | defaultLockStatus: !!formModel?.isUpdate, |
336 | + disabled: !!formModel?.customerId, | |
330 | 337 | componentProps: { |
331 | 338 | apiTreeSelectProps: { |
332 | 339 | params: { | ... | ... |
1 | 1 | import { formatToDateTime } from '/@/utils/dateUtil'; |
2 | -import { FormSchema } from '/@/components/Form'; | |
2 | +import { FormSchema, useComponentRegister } from '/@/components/Form'; | |
3 | 3 | import { BasicColumn } from '/@/components/Table'; |
4 | 4 | import { DeviceTypeEnum } from '/@/api/device/model/deviceModel'; |
5 | 5 | import { getCustomerList } from '/@/api/device/deviceManager'; |
... | ... | @@ -7,12 +7,15 @@ import { DescItem } from '/@/components/Description/index'; |
7 | 7 | import moment from 'moment'; |
8 | 8 | import { CSSProperties, h } from 'vue'; |
9 | 9 | import { Button, Tooltip } from 'ant-design-vue'; |
10 | -import { TypeEnum } from './data'; | |
10 | +import { TypeEnum, updateOrgHelpMessage } from './data'; | |
11 | 11 | import { PageEnum } from '/@/enums/pageEnum'; |
12 | 12 | import { useGo } from '/@/hooks/web/usePage'; |
13 | 13 | import { useAuthDeviceDetail } from '../hook/useAuthDeviceDetail'; |
14 | 14 | import { findDictItemByCode } from '/@/api/system/dict'; |
15 | 15 | import { isNullOrUnDef } from '/@/utils/is'; |
16 | +import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect'; | |
17 | + | |
18 | +useComponentRegister('OrgTreeSelect', OrgTreeSelect); | |
16 | 19 | |
17 | 20 | // 设备详情的描述 |
18 | 21 | export const descSchema = (emit: EmitType): DescItem[] => { |
... | ... | @@ -407,3 +410,28 @@ export const customerForm: FormSchema[] = [ |
407 | 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> | ... | ... |
1 | 1 | <script lang="ts" setup> |
2 | + import { ref } from 'vue'; | |
2 | 3 | import { TaskCard } from '/@/views/task/center/components/TaskCard'; |
3 | 4 | import { formSchemas } from '/@/views/task/center/config'; |
4 | 5 | import { getTaskCenterList } from '/@/api/task'; |
... | ... | @@ -29,6 +30,8 @@ |
29 | 30 | }, |
30 | 31 | }); |
31 | 32 | |
33 | + const fromDeviceDetail = ref(true); | |
34 | + | |
32 | 35 | const handleRunTask = (record: TaskRecordType) => { |
33 | 36 | openModal(true, record); |
34 | 37 | }; |
... | ... | @@ -50,7 +53,12 @@ |
50 | 53 | /> |
51 | 54 | </template> |
52 | 55 | </BasicCardList> |
53 | - <RunTaskModal :reload="reload" @register="registerRunTaskModal" /> | |
56 | + <RunTaskModal | |
57 | + :tbDeviceId="tbDeviceId" | |
58 | + :fromOrigin="fromDeviceDetail" | |
59 | + :reload="reload" | |
60 | + @register="registerRunTaskModal" | |
61 | + /> | |
54 | 62 | </section> |
55 | 63 | <!-- </PageWrapper> --> |
56 | 64 | </template> | ... | ... |
... | ... | @@ -23,13 +23,14 @@ |
23 | 23 | > |
24 | 24 | <AuthDropDown |
25 | 25 | v-if="authBtn(role)" |
26 | - :disabled="!isExistOption" | |
26 | + :disabled="isPublicAndPrivateFlag || !isExistOption" | |
27 | 27 | :dropMenuList="[ |
28 | 28 | { |
29 | 29 | text: '删除设备', |
30 | 30 | auth: DeviceListAuthEnum.DELETE, |
31 | 31 | icon: 'ant-design:delete-outlined', |
32 | 32 | event: '', |
33 | + disabled: !batchPrivateFlag, | |
33 | 34 | popconfirm: { |
34 | 35 | title: '您确定要批量删除数据', |
35 | 36 | onConfirm: () => handleDelete(), |
... | ... | @@ -40,6 +41,7 @@ |
40 | 41 | auth: DeviceListAuthEnum.ASSIGN, |
41 | 42 | icon: 'mdi:account-arrow-left', |
42 | 43 | event: '', |
44 | + disabled: !batchPrivateFlag, | |
43 | 45 | onClick: handleBatchAssign.bind(null), |
44 | 46 | }, |
45 | 47 | { |
... | ... | @@ -47,12 +49,35 @@ |
47 | 49 | auth: DeviceListAuthEnum.UPDATE_PRODUCT, |
48 | 50 | icon: 'clarity:note-edit-line', |
49 | 51 | event: '', |
50 | - disabled: batchUpdateProductFlag, | |
52 | + disabled: !batchPrivateFlag || batchUpdateProductFlag, | |
51 | 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 | 81 | </AuthDropDown> |
57 | 82 | </Authority> |
58 | 83 | </template> |
... | ... | @@ -225,6 +250,7 @@ |
225 | 250 | |
226 | 251 | <DeviceModal @register="registerModal" @success="handleSuccess" @reload="handleSuccess" /> |
227 | 252 | <CustomerModal @register="registerCustomerModal" @reload="handleReload" /> |
253 | + <OrgModal @register="registerOrgModal" @reload="handleReload" /> | |
228 | 254 | |
229 | 255 | <BatchImportModal @register="registerImportModal" @import-finally="handleImportFinally" /> |
230 | 256 | |
... | ... | @@ -254,6 +280,8 @@ |
254 | 280 | getGATEWAY, |
255 | 281 | privateDevice, |
256 | 282 | publicDevice, |
283 | + doBatchPublicDevice, | |
284 | + doBatchPrivateDevice, | |
257 | 285 | } from '/@/api/device/deviceManager'; |
258 | 286 | import { PageEnum } from '/@/enums/pageEnum'; |
259 | 287 | import { useGo } from '/@/hooks/web/usePage'; |
... | ... | @@ -264,6 +292,7 @@ |
264 | 292 | import { useDrawer } from '/@/components/Drawer'; |
265 | 293 | import DeviceDetailDrawer from './cpns/modal/DeviceDetailDrawer.vue'; |
266 | 294 | import CustomerModal from './cpns/modal/CustomerModal.vue'; |
295 | + import OrgModal from './cpns/modal/OrgModal.vue'; | |
267 | 296 | import BatchImportModal from './cpns/modal/BatchImportModal/index.vue'; |
268 | 297 | import { useMessage } from '/@/hooks/web/useMessage'; |
269 | 298 | import { USER_INFO_KEY } from '/@/enums/cacheEnum'; |
... | ... | @@ -292,6 +321,7 @@ |
292 | 321 | const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo); |
293 | 322 | const [registerModal, { openModal }] = useModal(); |
294 | 323 | const [registerCustomerModal, { openModal: openCustomerModal }] = useModal(); |
324 | + const [registerOrgModal, { openModal: openOrgodal }] = useModal(); | |
295 | 325 | const [registerDetailDrawer, { openDrawer }] = useDrawer(); |
296 | 326 | const [registerTbDetailDrawer, { openDrawer: openTbDeviceDrawer }] = useDrawer(); |
297 | 327 | const [registerGatewayDetailDrawer, { openDrawer: openGatewayDetailDrawer }] = useDrawer(); |
... | ... | @@ -326,6 +356,14 @@ |
326 | 356 | |
327 | 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 | 367 | const [ |
330 | 368 | registerTable, |
331 | 369 | { |
... | ... | @@ -376,7 +414,7 @@ |
376 | 414 | rowSelection: { |
377 | 415 | type: 'checkbox', |
378 | 416 | getCheckboxProps: (record: DeviceModel) => { |
379 | - return { disabled: !!record.customerId }; | |
417 | + return { disabled: !!record.customerId && record.customerName !== 'Public' }; | |
380 | 418 | }, |
381 | 419 | onSelect(_record, _selected, selectedRows) { |
382 | 420 | const [firstItem] = selectedRows as DeviceRecord[]; |
... | ... | @@ -384,6 +422,19 @@ |
384 | 422 | batchUpdateProductFlag.value = |
385 | 423 | !selectedRows.length || |
386 | 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 | 439 | onSelectAll(_selected, selectedRows) { |
389 | 440 | const [firstItem] = selectedRows as DeviceRecord[]; |
... | ... | @@ -391,6 +442,19 @@ |
391 | 442 | batchUpdateProductFlag.value = |
392 | 443 | !selectedRows.length || |
393 | 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 | 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 | 592 | const handleDelete = async (record?: DeviceRecord) => { |
496 | 593 | let ids: string[] = []; |
497 | 594 | if (record) { | ... | ... |
... | ... | @@ -17,6 +17,7 @@ |
17 | 17 | import { BasicDrawer, useDrawerInner } from '/@/components/Drawer'; |
18 | 18 | import { saveOrEditMessageConfig } from '/@/api/message/config'; |
19 | 19 | import { useMessage } from '/@/hooks/web/useMessage'; |
20 | + import { MessageEnum } from '/@/enums/messageEnum'; | |
20 | 21 | |
21 | 22 | export default defineComponent({ |
22 | 23 | name: 'ConfigDrawer', |
... | ... | @@ -85,6 +86,12 @@ |
85 | 86 | accessKeyId: values.accessKeyId, |
86 | 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 | 96 | Reflect.set(values, 'config', config); |
90 | 97 | let saveMessage = '添加成功'; | ... | ... |
... | ... | @@ -85,6 +85,9 @@ export const isDingtalk = (type: string) => { |
85 | 85 | export const isVoice = (type: string) => { |
86 | 86 | return type === MessageEnum.IS_VOICE; |
87 | 87 | }; |
88 | + | |
89 | +export const isEnterpriseWechat = (type: string) => type === MessageEnum.IS_ENTERPRISE_WECHAT; | |
90 | + | |
88 | 91 | export const messageTypeIsTencentCloud = (type: string) => { |
89 | 92 | return type === 'TENCENT_CLOUD'; |
90 | 93 | }; |
... | ... | @@ -105,13 +108,20 @@ export const formSchema: FormSchema[] = [ |
105 | 108 | label: '消息类型', |
106 | 109 | required: true, |
107 | 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 | 134 | api: async (params: Recordable) => { |
125 | 135 | try { |
126 | 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 | 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 | 151 | return result.filter((item) => item.itemValue === 'DING_TALK'); |
133 | - if (isVoice(Reflect.get(formModel, 'messageType'))) | |
152 | + if (isVoice(messageType)) | |
134 | 153 | return result.filter((item) => item.itemValue === 'ALI_VOICE'); |
135 | 154 | } catch (e) { |
136 | 155 | // eslint-disable-next-line no-console |
... | ... | @@ -145,10 +164,15 @@ export const formSchema: FormSchema[] = [ |
145 | 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 | 178 | field: 'appId', |
... | ... | @@ -283,7 +307,10 @@ export const formSchema: FormSchema[] = [ |
283 | 307 | label: 'agentId', |
284 | 308 | component: 'InputPassword', |
285 | 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 | 314 | componentProps: { |
288 | 315 | maxLength: 36, |
289 | 316 | placeholder: '请输入agentId', |
... | ... | @@ -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 | 362 | field: 'status', |
315 | 363 | label: '状态', |
316 | 364 | component: 'RadioButtonGroup', | ... | ... |
... | ... | @@ -9,6 +9,7 @@ |
9 | 9 | :disabled="disabled" |
10 | 10 | mode="multiple" |
11 | 11 | labelInValue |
12 | + :max-tag-count="selectDeviceMaxCount" | |
12 | 13 | /> |
13 | 14 | <template v-for="(item, index) in deviceList" :key="item.value"> |
14 | 15 | <SelectAttributes |
... | ... | @@ -55,7 +56,10 @@ |
55 | 56 | }; |
56 | 57 | |
57 | 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 | 63 | deviceList.value = options; |
60 | 64 | }; |
61 | 65 | ... | ... |
... | ... | @@ -34,13 +34,14 @@ |
34 | 34 | |
35 | 35 | const getValue = async () => { |
36 | 36 | const record = (await validateFields()) || {}; |
37 | - const { type, remark } = record; | |
37 | + const { type, remark, organizationId } = record; | |
38 | 38 | const datasourceType = Reflect.get(record, BasicInfoFormField.DATA_SOURCE_TYPE); |
39 | 39 | const convertDevices = Reflect.get(record, BasicInfoFormField.DATA_SOURCE_DEVICE); |
40 | 40 | const convertProducts = Reflect.get(record, BasicInfoFormField.DATA_SOURCE_PRODUCT); |
41 | 41 | return { |
42 | 42 | type, |
43 | 43 | remark, |
44 | + organizationId, | |
44 | 45 | datasourceType, |
45 | 46 | datasourceContent: { |
46 | 47 | convertProducts, | ... | ... |
1 | -import { FormSchema } from '/@/components/Form'; | |
1 | +import { FormSchema, useComponentRegister } from '/@/components/Form'; | |
2 | 2 | import { findDictItemByCode } from '/@/api/system/dict'; |
3 | 3 | import { getDeviceProfile } from '/@/api/alarm/position'; |
4 | 4 | import { BasicInfoFormField, DataSourceType } from '../enum'; |
5 | 5 | import { DeviceRecord } from '/@/api/device/model/deviceModel'; |
6 | 6 | import { useMessage } from '/@/hooks/web/useMessage'; |
7 | +import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect'; | |
8 | + | |
9 | +useComponentRegister('OrgTreeSelect', OrgTreeSelect); | |
7 | 10 | |
8 | 11 | export const stepConfig = ['选择流转方式', '完善配置参数']; |
9 | 12 | |
... | ... | @@ -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 | 70 | field: BasicInfoFormField.DATA_SOURCE_PRODUCT, |
61 | 71 | label: '数据源产品', |
62 | 72 | component: 'TransferModal', |
... | ... | @@ -108,9 +118,10 @@ export const modeForm = (disabled: boolean): FormSchema[] => { |
108 | 118 | componentProps: ({ formModel }) => { |
109 | 119 | const convertConfigId = formModel[BasicInfoFormField.CONVERT_CONFIG_ID]; |
110 | 120 | const deviceProfileIds = formModel[BasicInfoFormField.DATA_SOURCE_PRODUCT]; |
121 | + const organizationId = formModel[BasicInfoFormField.ORGANIZATION_ID]; | |
111 | 122 | return { |
112 | 123 | disabled, |
113 | - params: { convertConfigId, deviceProfileIds }, | |
124 | + params: { convertConfigId, deviceProfileIds, organizationId }, | |
114 | 125 | transformValue: handleGroupDevice, |
115 | 126 | openModalValidate: () => { |
116 | 127 | if (!deviceProfileIds || !deviceProfileIds?.length) { | ... | ... |
... | ... | @@ -30,7 +30,7 @@ |
30 | 30 | auth: 'api:yt:sceneLinkage:update', |
31 | 31 | icon: 'clarity:note-edit-line', |
32 | 32 | onClick: handleEdit.bind(null, record), |
33 | - ifShow: record.creator === userId && record.status !== 1, | |
33 | + ifShow: record.status !== 1, | |
34 | 34 | }, |
35 | 35 | ]" |
36 | 36 | :drop-down-actions="[ |
... | ... | @@ -39,7 +39,7 @@ |
39 | 39 | auth: 'api:yt:sceneLinkage:delete', |
40 | 40 | icon: 'ant-design:delete-outlined', |
41 | 41 | color: 'error', |
42 | - ifShow: record.creator === userId && record.status !== 1, | |
42 | + ifShow: record.status !== 1, | |
43 | 43 | popConfirm: { |
44 | 44 | title: '是否确认删除', |
45 | 45 | confirm: handleDeleteOrBatchDelete.bind(null, record), |
... | ... | @@ -82,8 +82,6 @@ |
82 | 82 | import { useBatchDelete } from '/@/hooks/web/useBatchDelete'; |
83 | 83 | import { Switch, Popconfirm, Tag } from 'ant-design-vue'; |
84 | 84 | import { columns, searchFormSchema } from './config/config.data'; |
85 | - import { USER_INFO_KEY } from '/@/enums/cacheEnum'; | |
86 | - import { getAuthCache } from '/@/utils/auth'; | |
87 | 85 | import { SceneLinkageDrawer } from './components/SceneLinkageDrawer'; |
88 | 86 | import { useMessage } from '/@/hooks/web/useMessage'; |
89 | 87 | import { Authority } from '/@/components/Authority'; |
... | ... | @@ -91,8 +89,6 @@ |
91 | 89 | import { SceneLinkageDrawerDataType } from './components/SceneLinkageDrawer/type'; |
92 | 90 | import { DataActionModeEnum } from '/@/enums/toolEnum'; |
93 | 91 | |
94 | - const userInfo: any = getAuthCache(USER_INFO_KEY); | |
95 | - const userId = userInfo.userId; | |
96 | 92 | const { hasPermission } = usePermission(); |
97 | 93 | |
98 | 94 | const [registerDrawer, { openDrawer }] = useDrawer(); | ... | ... |
... | ... | @@ -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 | 138 | field: 'remark', |
132 | 139 | label: '说明', |
133 | 140 | component: 'InputTextArea', | ... | ... |
... | ... | @@ -8,9 +8,8 @@ |
8 | 8 | |
9 | 9 | <script> |
10 | 10 | import { defineComponent } from 'vue'; |
11 | - import { useRoute } from 'vue-router'; | |
11 | + import { useRoute, useRouter } from 'vue-router'; | |
12 | 12 | import { PageWrapper } from '/@/components/Page'; |
13 | - import { useGo } from '/@/hooks/web/usePage'; | |
14 | 13 | import { Description } from '../../../components/Description'; |
15 | 14 | import { getAccountInfo } from '../../../api/system/system'; |
16 | 15 | import { accountSchema } from './account.detail.data'; |
... | ... | @@ -22,7 +21,8 @@ |
22 | 21 | components: { PageWrapper, Description }, |
23 | 22 | setup() { |
24 | 23 | const route = useRoute(); |
25 | - const go = useGo(); | |
24 | + | |
25 | + const ROUTER = useRouter(); | |
26 | 26 | const { setTitle, close } = useTabs(); |
27 | 27 | const [register, { setDescProps }] = useDescription({ |
28 | 28 | title: '账号基础信息', |
... | ... | @@ -52,7 +52,8 @@ |
52 | 52 | function goBack() { |
53 | 53 | // 本例的效果时点击返回始终跳转到账号列表页,实际应用时可返回上一页 |
54 | 54 | close(); |
55 | - go('/system/account'); | |
55 | + ROUTER.go(-1); | |
56 | + // go('/system/account'); | |
56 | 57 | } |
57 | 58 | return { goBack, accountSchema, accountData, register }; |
58 | 59 | }, | ... | ... |
... | ... | @@ -245,7 +245,10 @@ |
245 | 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 | 252 | closeModal(); |
250 | 253 | emit('success'); |
251 | 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 | +]; | ... | ... |
src/views/system/account/custom/index.vue
0 → 100644
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 | 209 | const handleClearPassword = async (record: Recordable) => { |
210 | 210 | const { id } = record; |
211 | 211 | if (!id) return; |
212 | - const { message } = await clearUserPassword(id); | |
212 | + const { message } = await clearUserPassword(id, 1); | |
213 | 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> | ... | ... |
src/views/system/account/tenant/config.ts
0 → 100644
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 | +]; | ... | ... |
src/views/system/account/tenant/index.vue
0 → 100644
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> | ... | ... |
src/views/system/account/tenant/role.data.ts
0 → 100644
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 | 25 | label: '编辑', |
26 | 26 | auth: 'api:yt:organization:update', |
27 | 27 | icon: 'clarity:note-edit-line', |
28 | + disabled: record.isRoot && getIsChildTenant, | |
28 | 29 | onClick: handleEdit.bind(null, record), |
29 | 30 | }, |
30 | 31 | { |
... | ... | @@ -32,6 +33,7 @@ |
32 | 33 | auth: 'api:yt:organization:delete', |
33 | 34 | icon: 'ant-design:delete-outlined', |
34 | 35 | color: 'error', |
36 | + disabled: record.isRoot && getIsChildTenant, | |
35 | 37 | popConfirm: { |
36 | 38 | title: getDeleteTitle(), //是否确认删除//getDeleteTitle() |
37 | 39 | confirm: handleDeleteOrBatchDelete.bind(null, record), |
... | ... | @@ -45,7 +47,7 @@ |
45 | 47 | </div> |
46 | 48 | </template> |
47 | 49 | <script lang="ts"> |
48 | - import { computed, defineComponent, nextTick } from 'vue'; | |
50 | + import { computed, defineComponent, nextTick, unref } from 'vue'; | |
49 | 51 | import { BasicTable, useTable, TableAction } from '/@/components/Table'; |
50 | 52 | // 加载自定义侧边弹出框 组件 |
51 | 53 | import { useDrawer } from '/@/components/Drawer'; |
... | ... | @@ -56,6 +58,8 @@ |
56 | 58 | import { useBatchDelete } from '/@/hooks/web/useBatchDelete'; |
57 | 59 | import { Authority } from '/@/components/Authority'; |
58 | 60 | import { Popconfirm } from 'ant-design-vue'; |
61 | + import { getJwtToken } from '/@/utils/auth'; | |
62 | + import jwtDecode from 'jwt-decode'; | |
59 | 63 | |
60 | 64 | export default defineComponent({ |
61 | 65 | name: 'DeptManagement', |
... | ... | @@ -65,6 +69,11 @@ |
65 | 69 | const { t } = useI18n(); // 加载国际化 |
66 | 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 | 77 | const [registerTable, { reload, expandAll, setProps }] = useTable({ |
69 | 78 | title: t('routes.common.organization.toolOrganizationList'), |
70 | 79 | api: getOrganizationList, |
... | ... | @@ -76,6 +85,14 @@ |
76 | 85 | showTableSetting: true, |
77 | 86 | bordered: true, |
78 | 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 | 96 | actionColumn: { |
80 | 97 | width: 200, |
81 | 98 | title: t('routes.common.common.operation'), //操作 |
... | ... | @@ -91,7 +108,15 @@ |
91 | 108 | setProps |
92 | 109 | ); |
93 | 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 | 149 | } |
125 | 150 | |
126 | 151 | return { |
152 | + getIsChildTenant, | |
127 | 153 | getI18n, |
128 | 154 | registerTable, |
129 | 155 | registerModal, | ... | ... |
1 | +import jwtDecode from 'jwt-decode'; | |
1 | 2 | import { BasicColumn } from '/@/components/Table'; |
2 | 3 | import { FormSchema } from '/@/components/Table'; |
3 | 4 | import { useI18n } from '/@/hooks/web/useI18n'; |
5 | +import { getJwtToken } from '/@/utils/auth'; | |
4 | 6 | import { createOrganizationSearch } from '/@/utils/organizationSearch'; |
5 | 7 | const { t } = useI18n(); |
6 | 8 | |
... | ... | @@ -32,6 +34,12 @@ export const formSchema: FormSchema[] = [ |
32 | 34 | field: 'parentId', |
33 | 35 | label: t('routes.common.organization.parentOrganization'), |
34 | 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 | 43 | componentProps: { |
36 | 44 | replaceFields: { |
37 | 45 | title: 'name', | ... | ... |
src/views/system/role/CustomRoleDrawer.vue
0 → 100644
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 | 89 | |
90 | 90 | // 租户管理员创建角色时 菜单分配为客户菜单 |
91 | 91 | if (unref(isTenantAdmin)) { |
92 | - roleType = RoleEnum.CUSTOMER_USER; | |
92 | + roleType = RoleEnum.TENANT_ADMIN; | |
93 | 93 | } |
94 | 94 | |
95 | 95 | try { |
... | ... | @@ -160,6 +160,7 @@ |
160 | 160 | remark: values.remark, |
161 | 161 | status: values.status, |
162 | 162 | menu, |
163 | + roleType: 'PLATFORM_ADMIN', | |
163 | 164 | }; |
164 | 165 | if (req.menu == undefined) return createMessage.error('请勾选权限菜单'); |
165 | 166 | saveOrUpdateRoleInfoWithMenu(req).then(() => { | ... | ... |
src/views/system/role/custom/RoleDrawer.vue
0 → 100644
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> | ... | ... |
src/views/system/role/custom/index.vue
0 → 100644
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> | ... | ... |
src/views/system/role/custom/role.data.ts
0 → 100644
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 | +]; | ... | ... |
src/views/system/role/tenant/RoleDrawer.vue
0 → 100644
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> | ... | ... |
src/views/system/role/tenant/index.vue
0 → 100644
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> | ... | ... |
src/views/system/role/tenant/role.data.ts
0 → 100644
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 | +]; | ... | ... |