Commit 9d2453cd1c31de8203811d91aa273a5a0d9817fb

Authored by loveumiko
1 parent 6ceff922

feat/新增菜单客户租户

  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/RoleDrawer.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 + await SaveOrUpdateUserInfo(postData as any, unref(isUpdate));
  249 + closeModal();
  250 + emit('success');
  251 + createMessage.success(unref(isUpdate) ? '编辑成功' : '新增成功');
  252 + } finally {
  253 + setTimeout(() => {
  254 + setModalProps({ confirmLoading: false });
  255 + }, 300);
  256 + }
  257 + }
  258 + // 取消全部的时候清除回显时获取的
  259 + const handleUnSelectAll = () => {
  260 + checkedKeysWithHalfChecked.value = [];
  261 + };
  262 +
  263 + const strictlyStatus = ref(false); //层级关联或独立的状态 false为层级关联 true为层级独立
  264 +
  265 + const handleStrictlyStatus = (status) => (strictlyStatus.value = status);
  266 +
  267 + const handleCheckClick = (selectedKeys: CheckKeys, event: CheckEvent) => {
  268 + //fix 取消层级独立后selectedKeys不是数组,是{checked:[],halfChecked:[]}对象 迭代报错
  269 + // 层级独立
  270 + if (strictlyStatus.value) {
  271 + if (!Array.isArray(selectedKeys)) {
  272 + selectedKeys = selectedKeys?.checked;
  273 + event.halfCheckedKeys = [];
  274 + }
  275 + } else {
  276 + // 层级关联
  277 + event.halfCheckedKeys = [];
  278 + }
  279 + checkedKeysWithHalfChecked.value = [
  280 + ...selectedKeys,
  281 + ...(event.halfCheckedKeys as string[]),
  282 + ];
  283 + };
  284 +
  285 + const [registerDrawer, { openDrawer: addOpenDrawer }] = useDrawer();
  286 +
  287 + const handleOpenCreate = () => {
  288 + addOpenDrawer(true, { isUpdate: false });
  289 + };
  290 + const handleReload = async () => {
  291 + const groupListModel = await findCurrentUserGroups();
  292 + copyTransTreeFun(groupListModel);
  293 + organizationTreeData.value = groupListModel;
  294 + };
  295 +
  296 + return {
  297 + registerModal,
  298 + registerForm,
  299 + handleSubmit,
  300 + getTitle,
  301 + organizationTreeData,
  302 + checkGroup,
  303 + basicTreeRef,
  304 + treeExpandData,
  305 + roleOptions,
  306 + registerRoleDrawer,
  307 + handleOpenRole,
  308 + handleSuccess,
  309 + handleRoleSelect,
  310 + handleTreeSelect,
  311 + handleCheckClick,
  312 + handleUnSelectAll,
  313 + handleStrictlyStatus,
  314 + handleOpenCreate,
  315 + registerDrawer,
  316 + handleReload,
  317 + };
  318 + },
  319 + });
  320 +</script>
  321 +<style scoped lang="less">
  322 + :deep(.vben-basic-tree) {
  323 + width: 100% !important;
  324 + margin-top: -28px !important;
  325 + padding: 0;
  326 + }
  327 +
  328 + :deep(.is-unflod) {
  329 + display: none !important;
  330 + }
  331 +
  332 + :deep(.is-flod) {
  333 + display: none !important;
  334 + }
  335 +</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 + onChange(value) {
  185 + if (value == olderPhoneNumber) {
  186 + clearValidate('phoneNumber');
  187 + }
  188 + },
  189 + };
  190 + },
  191 + },
  192 + {
  193 + label: '邮箱',
  194 + field: 'email',
  195 + component: 'Input',
  196 + colProps: { span: 12 },
  197 + rules: emailRule,
  198 + },
  199 + {
  200 + field: 'accountExpireTime',
  201 + label: '有效期',
  202 + component: 'DatePicker',
  203 + colProps: { span: 12 },
  204 + componentProps: {
  205 + showTime: true,
  206 + format: 'YYYY-MM-DD HH:mm:ss',
  207 + },
  208 + },
  209 + {
  210 + field: 'enabled',
  211 + label: '状态',
  212 + component: 'RadioButtonGroup',
  213 + colProps: { span: 12 },
  214 + defaultValue: true,
  215 + componentProps: {
  216 + options: [
  217 + { label: '启用', value: true },
  218 + { label: '禁用', value: false },
  219 + ],
  220 + },
  221 + },
  222 + {
  223 + field: 'remark',
  224 + label: '备注',
  225 + component: 'InputTextArea',
  226 + colProps: { span: 24 },
  227 + componentProps: {
  228 + maxLength: 255,
  229 + placeholder: '请输入备注',
  230 + },
  231 + },
  232 + {
  233 + field: 'organizationIds',
  234 + label: '所属组织',
  235 + component: 'Input',
  236 + slot: 'organizationId',
  237 + required: true,
  238 + },
  239 +];
... ...
  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);
  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>
... ...
  1 +import { BasicColumn, FormSchema } from '/@/components/Table';
  2 +
  3 +export const searchFormSchema: FormSchema[] = [
  4 + {
  5 + field: 'roleName',
  6 + label: '角色名称',
  7 + component: 'Input',
  8 + colProps: { span: 6 },
  9 + componentProps: {
  10 + maxLength: 255,
  11 + },
  12 + },
  13 + {
  14 + field: 'status',
  15 + label: '状态',
  16 + component: 'Select',
  17 + componentProps: {
  18 + options: [
  19 + { label: '启用', value: 1 },
  20 + { label: '停用', value: 0 },
  21 + ],
  22 + },
  23 + colProps: { span: 6 },
  24 + },
  25 +];
  26 +
  27 +export const columns: BasicColumn[] = [
  28 + {
  29 + title: '角色名称',
  30 + dataIndex: 'name',
  31 + width: 200,
  32 + },
  33 + {
  34 + title: '角色Code',
  35 + dataIndex: 'code',
  36 + width: 200,
  37 + },
  38 + {
  39 + title: '状态',
  40 + dataIndex: 'status',
  41 + width: 120,
  42 + slots: { customRender: 'status' },
  43 + },
  44 +
  45 + {
  46 + title: '备注',
  47 + dataIndex: 'remark',
  48 + width: 240,
  49 + },
  50 + {
  51 + title: '创建时间',
  52 + dataIndex: 'createTime',
  53 + width: 180,
  54 + },
  55 +];
... ...
  1 +<template>
  2 + <div>123 </div>
  3 +</template>
  4 +
  5 +<script lang="ts" setup></script>
... ...
  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>
... ...
  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:role:saveOrUpdateRoleInfoWithMenu:post">
  10 + <a-button type="primary" @click="handleCreate">新增角色</a-button>
  11 + </Authority>
  12 + <Authority value="api:yt: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:role:saveOrUpdateRoleInfoWithMenu: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:role:saveOrUpdateRoleInfoWithMenu: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:role:saveOrUpdateRoleInfoWithMenu:update',
  46 + icon: 'clarity:note-edit-line',
  47 + onClick: handleEdit.bind(null, record),
  48 + },
  49 + {
  50 + label: '删除',
  51 + auth: 'api:yt:role:delete',
  52 + icon: 'ant-design:delete-outlined',
  53 + color: 'error',
  54 + ifShow: record.roleType != RoleEnum.SYS_ADMIN,
  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 }] = 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 + resetFields();
  84 + roleId.value = '';
  85 + // 在打开弹窗时清除所有选择的菜单
  86 + treeRef.value && treeRef.value?.setCheckedKeys([]);
  87 + isUpdate.value = data.isUpdate;
  88 + const roleType = RoleEnum.TENANT_ADMIN;
  89 + try {
  90 + spinning.value = true;
  91 +
  92 + if (!unref(treeData).length) {
  93 + // 获取全部的菜单
  94 + const menuListModel = unref(isPlatformAdmin)
  95 + ? await getAdminMenuList()
  96 + : await getMenuList();
  97 + treeData.value = transformName(menuListModel as unknown as TreeData[]);
  98 + }
  99 + const keys = await getPermissionByRole(roleType);
  100 + const { keyType } = RoleMenuDictEnum[roleType];
  101 + treeData.value = filterPermissionTreeData(
  102 + unref(treeData) as unknown as TreeData[],
  103 + keys,
  104 + keyType
  105 + );
  106 +
  107 + // 更新
  108 + if (unref(isUpdate)) {
  109 + checked.value = [];
  110 + roleId.value = data.record.id;
  111 +
  112 + //通过角色id去获取角色对应的菜单的ids
  113 + checkedKeysWithHalfChecked.value = roleMenus.value = await getMenusIdsByRoleId(
  114 + data.record.id
  115 + );
  116 +
  117 + excludeHalfCheckedKeys(unref(treeData));
  118 +
  119 + await nextTick();
  120 + treeRef.value?.setCheckedKeys(roleMenus.value);
  121 + setFieldsValue(data.record);
  122 + } else {
  123 + }
  124 + } catch (error) {
  125 + throw error;
  126 + } finally {
  127 + spinning.value = false;
  128 + }
  129 + });
  130 + const getTitle = computed(() => (!unref(isUpdate) ? '新增角色' : '编辑角色'));
  131 +
  132 + // 取消全部的时候清除回显时获取的
  133 + const handleUnSelectAll = () => {
  134 + checkedKeysWithHalfChecked.value = [];
  135 + };
  136 +
  137 + async function handleSubmit() {
  138 + setDrawerProps({ loading: true, confirmLoading: true });
  139 + const { createMessage } = useMessage();
  140 + let treeCheckedKeys: string[] | CheckKeys =
  141 + (unref(treeRef)?.getCheckedKeys() as string[] | CheckKeys) || [];
  142 + //fix 取消层级独立后(unref(treeRef)?.getCheckedKeys() as string[])的数据不是数组,是{checked:[],halfChecked:[]}对象,迭代报错
  143 + if (!Array.isArray(treeCheckedKeys)) {
  144 + treeCheckedKeys = treeCheckedKeys?.checked;
  145 + }
  146 + const menu = [...new Set([...unref(checkedKeysWithHalfChecked), ...treeCheckedKeys])];
  147 + try {
  148 + const values = await validate();
  149 + const req = {
  150 + id: roleId.value,
  151 + name: values.name,
  152 + remark: values.remark,
  153 + status: values.status,
  154 + roleType: RoleEnum.TENANT_ADMIN,
  155 + menu,
  156 + };
  157 + if (req.menu == undefined || !req.menu.length)
  158 + return createMessage.error('请勾选权限菜单');
  159 + const res = await saveOrUpdateRoleInfoWithMenu(req);
  160 + if (res) {
  161 + closeDrawer();
  162 + emit('success');
  163 + createMessage.success(`${unref(isUpdate) ? '编辑' : '新增'}成功`);
  164 + setDrawerProps({ loading: false });
  165 + setDrawerProps({ confirmLoading: false });
  166 + }
  167 + } finally {
  168 + setTimeout(() => {
  169 + setDrawerProps({ loading: false });
  170 + setDrawerProps({ confirmLoading: false });
  171 + }, 300);
  172 + }
  173 + }
  174 +
  175 + const getPermissionByRole = async (roleType: RoleEnum) => {
  176 + try {
  177 + const { key } = RoleMenuDictEnum[roleType];
  178 + const res = await findDictItemByCode({ dictCode: key });
  179 + return res.map((item) => item.itemValue);
  180 + } catch (error) {}
  181 + return [];
  182 + };
  183 +
  184 + const filterPermissionTreeData = (
  185 + data: MenuRecord[],
  186 + permissionKeys: string[],
  187 + keysType: KeysTypeEnum
  188 + ): TreeData[] => {
  189 + const permissionCompare = (
  190 + data: MenuRecord[],
  191 + permissionKeys: string[],
  192 + keysType: KeysTypeEnum
  193 + ): TreeData[] => {
  194 + return data.filter((item) => {
  195 + item.name = t(item.name);
  196 + const findFlag = permissionKeys.includes(item.permission);
  197 +
  198 + item.show = findFlag ? keysType === KeysTypeEnum.ENABLED : undefined;
  199 +
  200 + if (item.children && item.children.length) {
  201 + if (item.show) return true;
  202 + if (item.show === undefined) {
  203 + item.children = permissionCompare(item.children, permissionKeys, keysType);
  204 + item.show = item.children.some((item) =>
  205 + keysType === KeysTypeEnum.ENABLED
  206 + ? item.show
  207 + : item.show === undefined
  208 + ? true
  209 + : item.show
  210 + );
  211 + return item.show;
  212 + }
  213 + }
  214 +
  215 + return keysType === KeysTypeEnum.ENABLED
  216 + ? item.show
  217 + : item.show === undefined
  218 + ? true
  219 + : item.show;
  220 + }) as unknown as TreeData[];
  221 + };
  222 +
  223 + return permissionCompare(data, permissionKeys, keysType);
  224 + };
  225 +
  226 + const excludeHalfCheckedKeys = (treeData: MenuRecord[]) => {
  227 + const needExcludeKeys: string[] = [];
  228 + const query = (data: MenuRecord[]) => {
  229 + data.forEach((item) => {
  230 + item.checked = roleMenus.value.includes(item.id);
  231 + if (item.children && item.children.length) {
  232 + query(item.children);
  233 + item.checked = item.children.every((item) => item.checked);
  234 + }
  235 + if (!item.checked) {
  236 + needExcludeKeys.push(item.id);
  237 + }
  238 + });
  239 + };
  240 + query(treeData);
  241 + roleMenus.value = unref(roleMenus).filter((key) => !needExcludeKeys.includes(key));
  242 + return needExcludeKeys;
  243 + };
  244 +
  245 + const handleCheckClick = (selectedKeys: CheckKeys, event: CheckEvent) => {
  246 + //fix 取消层级独立后selectedKeys不是数组,是{checked:[],halfChecked:[]}对象 迭代报错
  247 + if (!Array.isArray(selectedKeys)) {
  248 + selectedKeys = selectedKeys?.checked;
  249 + event.halfCheckedKeys = [];
  250 + }
  251 + checkedKeysWithHalfChecked.value = [
  252 + ...selectedKeys,
  253 + ...(event.halfCheckedKeys as string[]),
  254 + ];
  255 + };
  256 +
  257 + return {
  258 + spinning,
  259 + registerDrawer,
  260 + registerForm,
  261 + getTitle,
  262 + handleSubmit,
  263 + treeData,
  264 + roleMenus,
  265 + treeRef,
  266 + handleCheckClick,
  267 + handleUnSelectAll,
  268 + };
  269 + },
  270 + });
  271 +</script>
  272 +
  273 +<style scoped lang="less">
  274 + :deep(.vben-basic-tree) {
  275 + width: 100% !important;
  276 + }
  277 +
  278 + :deep(.is-unflod) {
  279 + display: none !important;
  280 + }
  281 +
  282 + :deep(.is-flod) {
  283 + display: none !important;
  284 + }
  285 +</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:role:saveOrUpdateRoleInfoWithMenu:post">
  10 + <a-button type="primary" @click="handleCreate"> 新增角色 </a-button>
  11 + </Authority>
  12 + <Authority value="api:yt: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:role:saveOrUpdateRoleInfoWithMenu:update',
  38 + icon: 'clarity:note-edit-line',
  39 + onClick: handleEdit.bind(null, record),
  40 + },
  41 + {
  42 + label: '删除',
  43 + auth: 'api:yt: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 + },
  88 + useSearchForm: true,
  89 + showTableSetting: true,
  90 + bordered: true,
  91 + showIndexColumn: false,
  92 + actionColumn: {
  93 + width: 200,
  94 + title: '操作',
  95 + dataIndex: 'action',
  96 + slots: { customRender: 'action' },
  97 + fixed: 'right',
  98 + },
  99 + });
  100 + const { hasBatchDelete, handleDeleteOrBatchDelete, selectionOptions, resetSelectedRowKeys } =
  101 + useBatchDelete(delRole, handleSuccess, setProps);
  102 + selectionOptions.rowSelection.getCheckboxProps = (record: Recordable) => {
  103 + // Demo:status为1的选择框禁用
  104 + if (record.status === 1) {
  105 + return { disabled: true };
  106 + } else {
  107 + return { disabled: false };
  108 + }
  109 + };
  110 + nextTick(() => {
  111 + setProps(selectionOptions);
  112 + });
  113 +
  114 + function handleCreate() {
  115 + openDrawer(true, {
  116 + isUpdate: false,
  117 + });
  118 + }
  119 +
  120 + function handleEdit(record: Recordable) {
  121 + openDrawer(true, {
  122 + record,
  123 + isUpdate: true,
  124 + });
  125 + }
  126 + const statusChange = async (checked, record) => {
  127 + setProps({
  128 + loading: true,
  129 + });
  130 + setSelectedRowKeys([]);
  131 + resetSelectedRowKeys();
  132 + const newStatus = checked ? 1 : 0;
  133 + const { createMessage } = useMessage();
  134 + try {
  135 + await setRoleStatus(record.id, newStatus);
  136 + if (newStatus) {
  137 + createMessage.success(`启用成功`);
  138 + } else {
  139 + createMessage.success('禁用成功');
  140 + }
  141 + } finally {
  142 + setProps({
  143 + loading: false,
  144 + });
  145 + reload();
  146 + }
  147 + };
  148 +
  149 + return {
  150 + statusChange,
  151 + registerTable,
  152 + registerDrawer,
  153 + handleCreate,
  154 + handleEdit,
  155 + handleSuccess,
  156 + RoleEnum,
  157 + hasBatchDelete,
  158 + handleDeleteOrBatchDelete,
  159 + };
  160 + },
  161 + });
  162 +</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: 0,
  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 +];
... ...