Commit b289da6c45ccaa284fe8c2645e1d8d6099a1ee7b

Authored by xp.Huang
2 parents d88d2390 5f11983c

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

# Conflicts:
#   src/views/report/config/components/SelectDevice.vue
Showing 52 changed files with 4131 additions and 89 deletions

Too many changes to show.

To preserve performance only 52 of 61 files are displayed.

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