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