Commit 9a1ba74920045a7d65f4dd802d244510aff5b3a9

Authored by Vben
1 parent f79cae63

wip: system management sample page

CHANGELOG.zh_CN.md
... ... @@ -28,6 +28,7 @@
28 28 - 修复表格不能正确的获取选中行
29 29 - 修复全屏状态下 modal 高度计算错误
30 30 - 修复部分表格样式问题
  31 +- 修复树形表格 `indentSize`设置失效
31 32  
32 33 ## 2.0.1 (2021-02-21)
33 34  
... ...
mock/demo/system.ts
... ... @@ -18,6 +18,22 @@ const accountList = (() => {
18 18 return result;
19 19 })();
20 20  
  21 +const roleList = (() => {
  22 + const result: any[] = [];
  23 + for (let index = 0; index < 4; index++) {
  24 + result.push({
  25 + id: `${index}`,
  26 + orderNo: `${index + 1}`,
  27 + roleName: ['超级管理员', '管理员', '文章管理员', '普通用户'][index],
  28 + roleValue: '@first',
  29 + createTime: '@datetime',
  30 + remark: '@cword(10,20)',
  31 + 'status|1': ['0', '1'],
  32 + });
  33 + }
  34 + return result;
  35 +})();
  36 +
21 37 const deptList = (() => {
22 38 const result: any[] = [];
23 39 for (let index = 0; index < 3; index++) {
... ... @@ -49,6 +65,46 @@ const deptList = (() =&gt; {
49 65 return result;
50 66 })();
51 67  
  68 +const menuList = (() => {
  69 + const result: any[] = [];
  70 + for (let index = 0; index < 3; index++) {
  71 + result.push({
  72 + id: `${index}`,
  73 + icon: ['ion:layers-outline', 'ion:git-compare-outline', 'ion:tv-outline'][index],
  74 + component: 'LAYOUT',
  75 + menuName: ['Dashboard', '权限管理', '功能'][index],
  76 + permission: '',
  77 + orderNo: index + 1,
  78 + createTime: '@datetime',
  79 + 'status|1': ['0', '0', '1'],
  80 + children: (() => {
  81 + const children: any[] = [];
  82 + for (let j = 0; j < 4; j++) {
  83 + children.push({
  84 + id: `${index}-${j}`,
  85 + menuName: ['菜单1', '菜单2', '菜单3', '菜单4'][j],
  86 + icon: 'ion:document',
  87 + permission: ['menu1:view', 'menu2:add', 'menu3:update', 'menu4:del'][index],
  88 + component: [
  89 + '/dashboard/welcome/index',
  90 + '/dashboard/analysis/index',
  91 + '/dashboard/workbench/index',
  92 + '/dashboard/test/index',
  93 + ][j],
  94 + orderNo: j + 1,
  95 + createTime: '@datetime',
  96 + 'status|1': ['0', '1'],
  97 + parentMenu: `${index}`,
  98 + children: undefined,
  99 + });
  100 + }
  101 + return children;
  102 + })(),
  103 + });
  104 + }
  105 + return result;
  106 +})();
  107 +
52 108 export default [
53 109 {
54 110 url: '/api/system/getAccountList',
... ... @@ -60,6 +116,15 @@ export default [
60 116 },
61 117 },
62 118 {
  119 + url: '/api/system/getRoleList',
  120 + timeout: 100,
  121 + method: 'get',
  122 + response: ({ query }) => {
  123 + const { page = 1, pageSize = 20 } = query;
  124 + return resultPageSuccess(page, pageSize, roleList);
  125 + },
  126 + },
  127 + {
63 128 url: '/api/system/getDeptList',
64 129 timeout: 100,
65 130 method: 'get',
... ... @@ -67,4 +132,12 @@ export default [
67 132 return resultSuccess(deptList);
68 133 },
69 134 },
  135 + {
  136 + url: '/api/system/getMenuList',
  137 + timeout: 100,
  138 + method: 'get',
  139 + response: () => {
  140 + return resultSuccess(menuList);
  141 + },
  142 + },
70 143 ] as MockMethod[];
... ...
package.json
... ... @@ -53,7 +53,7 @@
53 53 "devDependencies": {
54 54 "@commitlint/cli": "^12.0.1",
55 55 "@commitlint/config-conventional": "^12.0.1",
56   - "@iconify/json": "^1.1.310",
  56 + "@iconify/json": "^1.1.311",
57 57 "@ls-lint/ls-lint": "^1.9.2",
58 58 "@purge-icons/generated": "^0.7.0",
59 59 "@types/crypto-js": "^4.0.1",
... ... @@ -110,7 +110,7 @@
110 110 "vite-plugin-pwa": "^0.5.6",
111 111 "vite-plugin-style-import": "^0.7.6",
112 112 "vite-plugin-theme": "^0.4.8",
113   - "vite-plugin-windicss": "0.6.2",
  113 + "vite-plugin-windicss": "0.6.4",
114 114 "vue-eslint-parser": "^7.6.0",
115 115 "yargs": "^16.2.0"
116 116 },
... ...
src/api/demo/model/systemModel.ts
... ... @@ -4,12 +4,21 @@ export type AccountParams = BasicPageParams &amp; {
4 4 account?: string;
5 5 nickname?: string;
6 6 };
  7 +export type RoleParams = BasicPageParams & {
  8 + roleName?: string;
  9 + status?: string;
  10 +};
7 11  
8 12 export type DeptParams = {
9 13 deptName?: string;
10 14 status?: string;
11 15 };
12 16  
  17 +export type MenuParams = {
  18 + menuName?: string;
  19 + status?: string;
  20 +};
  21 +
13 22 export interface AccountListItem {
14 23 id: string;
15 24 account: string;
... ... @@ -29,9 +38,32 @@ export interface DeptListItem {
29 38 status: number;
30 39 }
31 40  
  41 +export interface MenuListItem {
  42 + id: string;
  43 + orderNo: string;
  44 + createTime: string;
  45 + status: number;
  46 + icon: string;
  47 + component: string;
  48 + permission: string;
  49 +}
  50 +
  51 +export interface RoleListItem {
  52 + id: string;
  53 + roleName: string;
  54 + roleValue: string;
  55 + status: number;
  56 + orderNo: string;
  57 + createTime: string;
  58 +}
  59 +
32 60 /**
33 61 * @description: Request list return value
34 62 */
35 63 export type AccountListGetResultModel = BasicFetchResult<AccountListItem>;
36 64  
37 65 export type DeptListGetResultModel = BasicFetchResult<DeptListItem>;
  66 +
  67 +export type MenuListGetResultModel = BasicFetchResult<MenuListItem>;
  68 +
  69 +export type RoleListGetResultModel = BasicFetchResult<RoleListItem>;
... ...
src/api/demo/system.ts
1 1 import {
2 2 AccountParams,
3 3 DeptListItem,
  4 + MenuParams,
  5 + RoleParams,
  6 + MenuListGetResultModel,
4 7 DeptListGetResultModel,
5 8 AccountListGetResultModel,
  9 + RoleListGetResultModel,
6 10 } from './model/systemModel';
7 11 import { defHttp } from '/@/utils/http/axios';
8 12  
9 13 enum Api {
10   - // The address does not exist
11 14 AccountList = '/system/getAccountList',
12 15 DeptList = '/system/getDeptList',
  16 + MenuList = '/system/getMenuList',
  17 + RoleList = '/system/getRoleList',
13 18 }
14 19  
15 20 export const getAccountList = (params: AccountParams) =>
... ... @@ -17,3 +22,9 @@ export const getAccountList = (params: AccountParams) =&gt;
17 22  
18 23 export const getDeptList = (params?: DeptListItem) =>
19 24 defHttp.get<DeptListGetResultModel>({ url: Api.DeptList, params });
  25 +
  26 +export const getMenuList = (params?: MenuParams) =>
  27 + defHttp.get<MenuListGetResultModel>({ url: Api.MenuList, params });
  28 +
  29 +export const getRoleList = (params?: RoleParams) =>
  30 + defHttp.get<RoleListGetResultModel>({ url: Api.RoleList, params });
... ...
src/components/Form/src/componentMap.ts
... ... @@ -22,6 +22,7 @@ import RadioButtonGroup from &#39;./components/RadioButtonGroup.vue&#39;;
22 22 import ApiSelect from './components/ApiSelect.vue';
23 23 import { BasicUpload } from '/@/components/Upload';
24 24 import { StrengthMeter } from '/@/components/StrengthMeter';
  25 +import { IconPicker } from '/@/components/Icon';
25 26  
26 27 const componentMap = new Map<ComponentType, Component>();
27 28  
... ... @@ -53,6 +54,7 @@ componentMap.set(&#39;RangePicker&#39;, DatePicker.RangePicker);
53 54 componentMap.set('WeekPicker', DatePicker.WeekPicker);
54 55 componentMap.set('TimePicker', TimePicker);
55 56 componentMap.set('StrengthMeter', StrengthMeter);
  57 +componentMap.set('IconPicker', IconPicker);
56 58  
57 59 componentMap.set('Upload', BasicUpload);
58 60  
... ...
src/components/Form/src/types/index.ts
... ... @@ -107,4 +107,5 @@ export type ComponentType =
107 107 | 'Switch'
108 108 | 'StrengthMeter'
109 109 | 'Upload'
  110 + | 'IconPicker'
110 111 | 'Render';
... ...
src/components/Icon/src/IconPicker.vue
... ... @@ -3,8 +3,8 @@
3 3 disabled
4 4 :style="{ width }"
5 5 :placeholder="t('component.icon.placeholder')"
6   - v-model:value="currentSelect"
7 6 :class="prefixCls"
  7 + v-model:value="currentSelect"
8 8 >
9 9 <template #addonAfter>
10 10 <Popover
... ... @@ -91,6 +91,7 @@
91 91 const icons = getIcons();
92 92 export default defineComponent({
93 93 name: 'IconPicker',
  94 + inheritAttrs: false,
94 95 components: { [Input.name]: Input, Icon, Popover, ScrollContainer, Pagination, Empty },
95 96 props: {
96 97 value: propTypes.string,
... ...
src/components/Page/index.ts
1   -import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
2   -export const PageFooter = createAsyncComponent(() => import('./src/PageFooter.vue'));
3   -
  1 +export { default as PageFooter } from './src/PageFooter.vue';
4 2 export { default as PageWrapper } from './src/PageWrapper.vue';
... ...
src/components/Qrcode/index.ts
1   -import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
2   -export const QrCode = createAsyncComponent(() => import('./src/index.vue'));
  1 +export { default as QrCode } from './src/index.vue';
3 2  
4 3 export * from './src/types';
... ...
src/components/StrengthMeter/index.ts
1   -import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
2   -
3   -export const StrengthMeter = createAsyncComponent(() => import('./src/index.vue'));
  1 +export { default as StrengthMeter } from './src/index.vue';
... ...
src/components/Table/src/components/ExpandIcon.tsx
... ... @@ -3,7 +3,7 @@ import { BasicArrow } from &#39;/@/components/Basic&#39;;
3 3 export default () => {
4 4 return (props: Recordable) => {
5 5 if (!props.expandable) {
6   - return null;
  6 + return <span />;
7 7 }
8 8 return (
9 9 <BasicArrow
... ...
src/components/Table/src/components/settings/ColumnSetting.vue
... ... @@ -3,8 +3,8 @@
3 3 <template #title>
4 4 <span>{{ t('component.table.settingColumn') }}</span>
5 5 </template>
  6 + <!-- :getPopupContainer="getPopupContainer" -->
6 7 <Popover
7   - :getPopupContainer="getPopupContainer"
8 8 placement="bottomLeft"
9 9 trigger="click"
10 10 @visibleChange="handleVisibleChange"
... ...
src/components/Table/src/props.ts
... ... @@ -49,6 +49,8 @@ export const basicProps = {
49 49 default: null,
50 50 },
51 51  
  52 + indentSize: propTypes.number.def(24),
  53 +
52 54 canColDrag: propTypes.bool.def(true),
53 55 api: {
54 56 type: Function as PropType<(...arg: any[]) => Promise<any>>,
... ...
src/design/ant/input.less
... ... @@ -14,3 +14,11 @@
14 14 .ant-input-clear-icon {
15 15 margin-right: 5px;
16 16 }
  17 +
  18 +.ant-input-affix-wrapper-textarea-with-clear-btn {
  19 + padding: 0 !important;
  20 +
  21 + textarea.ant-input {
  22 + padding: 4px;
  23 + }
  24 +}
... ...
src/locales/lang/en/routes/demo/system.ts
... ... @@ -6,4 +6,7 @@ export default {
6 6 password: 'Change password',
7 7  
8 8 dept: 'Department management',
  9 +
  10 + menu: 'Menu management',
  11 + role: 'Role management',
9 12 };
... ...
src/locales/lang/zh_CN/routes/demo/system.ts
... ... @@ -6,4 +6,7 @@ export default {
6 6 password: '修改密码',
7 7  
8 8 dept: '部门管理',
  9 +
  10 + menu: '菜单管理',
  11 + role: '角色管理',
9 12 };
... ...
src/locales/useLocale.ts
... ... @@ -3,13 +3,11 @@
3 3 */
4 4 import type { LocaleType } from '/#/config';
5 5  
6   -import { ref } from 'vue';
7 6 import moment from 'moment';
8   -import { computed } from 'vue';
9 7  
10 8 import { i18n } from './setupI18n';
11 9 import { localeStore } from '/@/store/modules/locale';
12   -import { unref } from 'vue';
  10 +import { unref, computed } from 'vue';
13 11  
14 12 interface LangModule {
15 13 message: Recordable;
... ... @@ -17,8 +15,6 @@ interface LangModule {
17 15 momentLocaleName: string;
18 16 }
19 17  
20   -const antConfigLocale = ref<Nullable<Recordable>>(null);
21   -
22 18 const loadLocalePool: LocaleType[] = [];
23 19  
24 20 function setI18nLanguage(locale: LocaleType) {
... ... @@ -67,7 +63,6 @@ export function useLocale() {
67 63 getLocale,
68 64 getShowLocalePicker,
69 65 changeLocale,
70   - antConfigLocale,
71 66 getAntdLocale,
72 67 };
73 68 }
... ...
src/router/menus/modules/demo/system.ts
... ... @@ -18,7 +18,20 @@ const menu: MenuModule = {
18 18 type: 'warn',
19 19 },
20 20 },
21   -
  21 + {
  22 + path: 'role',
  23 + name: t('routes.demo.system.role'),
  24 + tag: {
  25 + content: 'new',
  26 + },
  27 + },
  28 + {
  29 + path: 'menu',
  30 + name: t('routes.demo.system.menu'),
  31 + tag: {
  32 + content: 'new',
  33 + },
  34 + },
22 35 {
23 36 path: 'dept',
24 37 name: t('routes.demo.system.dept'),
... ...
src/router/routes/modules/demo/system.ts
... ... @@ -23,6 +23,25 @@ const system: AppRouteModule = {
23 23 component: () => import('/@/views/demo/system/account/index.vue'),
24 24 },
25 25 {
  26 + path: 'role',
  27 + name: 'RoleManagement',
  28 + meta: {
  29 + title: t('routes.demo.system.role'),
  30 + ignoreKeepAlive: true,
  31 + },
  32 + component: () => import('/@/views/demo/system/role/index.vue'),
  33 + },
  34 +
  35 + {
  36 + path: 'menu',
  37 + name: 'MenuManagement',
  38 + meta: {
  39 + title: t('routes.demo.system.menu'),
  40 + ignoreKeepAlive: true,
  41 + },
  42 + component: () => import('/@/views/demo/system/menu/index.vue'),
  43 + },
  44 + {
26 45 path: 'dept',
27 46 name: 'DeptManagement',
28 47 meta: {
... ...
src/views/demo/system/dept/DeptModal.vue
... ... @@ -21,9 +21,6 @@
21 21 labelWidth: 100,
22 22 schemas: formSchema,
23 23 showActionButtonGroup: false,
24   - actionColOptions: {
25   - span: 23,
26   - },
27 24 });
28 25  
29 26 const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
... ...
src/views/demo/system/dept/dept.data.ts
... ... @@ -7,22 +7,23 @@ export const columns: BasicColumn[] = [
7 7 {
8 8 title: '部门名称',
9 9 dataIndex: 'deptName',
10   - width: 300,
  10 + width: 160,
  11 + align: 'left',
11 12 },
12 13 {
13 14 title: '排序',
14 15 dataIndex: 'orderNo',
15   - width: 80,
  16 + width: 50,
16 17 },
17 18 {
18 19 title: '状态',
19 20 dataIndex: 'status',
20   - width: 120,
  21 + width: 80,
21 22 customRender: ({ record }) => {
22 23 const status = record.status;
23 24 const enable = ~~status === 0;
24 25 const color = enable ? 'green' : 'red';
25   - const text = enable ? '正常' : '停用';
  26 + const text = enable ? '启用' : '停用';
26 27 return h(Tag, { color: color }, () => text);
27 28 },
28 29 },
... ... @@ -69,6 +70,7 @@ export const formSchema: FormSchema[] = [
69 70 field: 'parentDept',
70 71 label: '上级部门',
71 72 component: 'TreeSelect',
  73 +
72 74 componentProps: {
73 75 replaceFields: {
74 76 title: 'deptName',
... ... @@ -89,10 +91,11 @@ export const formSchema: FormSchema[] = [
89 91 field: 'status',
90 92 label: '状态',
91 93 component: 'RadioButtonGroup',
  94 + defaultValue: '0',
92 95 componentProps: {
93 96 options: [
94   - { label: '正常', value: '0' },
95   - { label: '禁用', value: '1' },
  97 + { label: '启用', value: '0' },
  98 + { label: '停用', value: '1' },
96 99 ],
97 100 },
98 101 required: true,
... ...
src/views/demo/system/dept/index.vue
... ... @@ -56,7 +56,7 @@
56 56 showTableSetting: true,
57 57 bordered: true,
58 58 showIndexColumn: false,
59   - indentSize: 20,
  59 + canResize: false,
60 60 actionColumn: {
61 61 width: 80,
62 62 title: '操作',
... ...
src/views/demo/system/menu/MenuDrawer.vue 0 → 100644
  1 +<template>
  2 + <BasicDrawer
  3 + v-bind="$attrs"
  4 + @register="registerDrawer"
  5 + showFooter
  6 + :title="getTitle"
  7 + width="50%"
  8 + @ok="handleSubmit"
  9 + >
  10 + <BasicForm @register="registerForm" />
  11 + </BasicDrawer>
  12 +</template>
  13 +<script lang="ts">
  14 + import { defineComponent, ref, computed, unref } from 'vue';
  15 + import { BasicForm, useForm } from '/@/components/Form/index';
  16 + import { formSchema } from './menu.data';
  17 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  18 +
  19 + import { getMenuList } from '/@/api/demo/system';
  20 +
  21 + export default defineComponent({
  22 + name: 'MenuDrawer',
  23 + components: { BasicDrawer, BasicForm },
  24 + emits: ['success', 'register'],
  25 + setup(_, { emit }) {
  26 + const isUpdate = ref(true);
  27 +
  28 + const [registerForm, { resetFields, setFieldsValue, updateSchema, validate }] = useForm({
  29 + labelWidth: 100,
  30 + schemas: formSchema,
  31 + showActionButtonGroup: false,
  32 + baseColProps: { lg: 12, md: 24 },
  33 + });
  34 +
  35 + const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
  36 + resetFields();
  37 + setDrawerProps({ confirmLoading: false });
  38 + isUpdate.value = !!data?.isUpdate;
  39 +
  40 + if (unref(isUpdate)) {
  41 + setFieldsValue({
  42 + ...data.record,
  43 + });
  44 + }
  45 + const treeData = await getMenuList();
  46 + updateSchema({
  47 + field: 'parentMenu',
  48 + componentProps: { treeData },
  49 + });
  50 + });
  51 +
  52 + const getTitle = computed(() => (!unref(isUpdate) ? '新增菜单' : '编辑菜单'));
  53 +
  54 + async function handleSubmit() {
  55 + try {
  56 + const values = await validate();
  57 + setDrawerProps({ confirmLoading: true });
  58 + // TODO custom api
  59 + console.log(values);
  60 + closeDrawer();
  61 + emit('success');
  62 + } finally {
  63 + setDrawerProps({ confirmLoading: false });
  64 + }
  65 + }
  66 +
  67 + return { registerDrawer, registerForm, getTitle, handleSubmit };
  68 + },
  69 + });
  70 +</script>
... ...
src/views/demo/system/menu/index.vue 0 → 100644
  1 +<template>
  2 + <div>
  3 + <BasicTable @register="registerTable">
  4 + <template #toolbar>
  5 + <a-button type="primary" @click="handleCreate"> 新增菜单 </a-button>
  6 + </template>
  7 + <template #action="{ record }">
  8 + <TableAction
  9 + :actions="[
  10 + {
  11 + icon: 'clarity:note-edit-line',
  12 + onClick: handleEdit.bind(null, record),
  13 + },
  14 + {
  15 + icon: 'ant-design:delete-outlined',
  16 + color: 'error',
  17 + popConfirm: {
  18 + title: '是否确认删除',
  19 + confirm: handleDelete.bind(null, record),
  20 + },
  21 + },
  22 + ]"
  23 + />
  24 + </template>
  25 + </BasicTable>
  26 + <MenuDrawer @register="registerDrawer" @success="handleSuccess" />
  27 + </div>
  28 +</template>
  29 +<script lang="ts">
  30 + import { defineComponent } from 'vue';
  31 +
  32 + import { BasicTable, useTable, TableAction } from '/@/components/Table';
  33 + import { getMenuList } from '/@/api/demo/system';
  34 +
  35 + import { useDrawer } from '/@/components/Drawer';
  36 + import MenuDrawer from './MenuDrawer.vue';
  37 +
  38 + import { columns, searchFormSchema } from './menu.data';
  39 +
  40 + export default defineComponent({
  41 + name: 'MenuManagement',
  42 + components: { BasicTable, MenuDrawer, TableAction },
  43 + setup() {
  44 + const [registerDrawer, { openDrawer }] = useDrawer();
  45 + const [registerTable, { reload }] = useTable({
  46 + title: '菜单列表',
  47 + api: getMenuList,
  48 + columns,
  49 + formConfig: {
  50 + labelWidth: 120,
  51 + schemas: searchFormSchema,
  52 + },
  53 + pagination: false,
  54 + striped: false,
  55 + useSearchForm: true,
  56 + showTableSetting: true,
  57 + bordered: true,
  58 + showIndexColumn: false,
  59 + canResize: false,
  60 + actionColumn: {
  61 + width: 80,
  62 + title: '操作',
  63 + dataIndex: 'action',
  64 + slots: { customRender: 'action' },
  65 + fixed: undefined,
  66 + },
  67 + });
  68 +
  69 + function handleCreate() {
  70 + openDrawer(true, {
  71 + isUpdate: false,
  72 + });
  73 + }
  74 +
  75 + function handleEdit(record: Recordable) {
  76 + openDrawer(true, {
  77 + record,
  78 + isUpdate: true,
  79 + });
  80 + }
  81 +
  82 + function handleDelete(record: Recordable) {
  83 + console.log(record);
  84 + }
  85 +
  86 + function handleSuccess() {
  87 + reload();
  88 + }
  89 +
  90 + return {
  91 + registerTable,
  92 + registerDrawer,
  93 + handleCreate,
  94 + handleEdit,
  95 + handleDelete,
  96 + handleSuccess,
  97 + };
  98 + },
  99 + });
  100 +</script>
... ...
src/views/demo/system/menu/menu.data.ts 0 → 100644
  1 +import { BasicColumn } from '/@/components/Table';
  2 +import { FormSchema } from '/@/components/Table';
  3 +import { h } from 'vue';
  4 +import { Tag } from 'ant-design-vue';
  5 +import { Icon } from '/@/components/Icon';
  6 +
  7 +export const columns: BasicColumn[] = [
  8 + {
  9 + title: '菜单名称',
  10 + dataIndex: 'menuName',
  11 + width: 200,
  12 + align: 'left',
  13 + },
  14 + {
  15 + title: '图标',
  16 + dataIndex: 'icon',
  17 + width: 50,
  18 + customRender: ({ record }) => {
  19 + return h(Icon, { icon: record.icon });
  20 + },
  21 + },
  22 + {
  23 + title: '权限标识',
  24 + dataIndex: 'permission',
  25 + width: 180,
  26 + },
  27 + {
  28 + title: '组件',
  29 + dataIndex: 'component',
  30 + },
  31 + {
  32 + title: '排序',
  33 + dataIndex: 'orderNo',
  34 + width: 50,
  35 + },
  36 + {
  37 + title: '状态',
  38 + dataIndex: 'status',
  39 + width: 80,
  40 + customRender: ({ record }) => {
  41 + const status = record.status;
  42 + const enable = ~~status === 0;
  43 + const color = enable ? 'green' : 'red';
  44 + const text = enable ? '启用' : '停用';
  45 + return h(Tag, { color: color }, () => text);
  46 + },
  47 + },
  48 + {
  49 + title: '创建时间',
  50 + dataIndex: 'createTime',
  51 + width: 180,
  52 + },
  53 +];
  54 +
  55 +const isDir = (type: string) => type === '0';
  56 +const isMenu = (type: string) => type === '1';
  57 +const isButton = (type: string) => type === '2';
  58 +
  59 +export const searchFormSchema: FormSchema[] = [
  60 + {
  61 + field: 'menuName',
  62 + label: '菜单名称',
  63 + component: 'Input',
  64 + colProps: { span: 8 },
  65 + },
  66 + {
  67 + field: 'status',
  68 + label: '状态',
  69 + component: 'Select',
  70 + componentProps: {
  71 + options: [
  72 + { label: '启用', value: '0' },
  73 + { label: '停用', value: '1' },
  74 + ],
  75 + },
  76 + colProps: { span: 8 },
  77 + },
  78 +];
  79 +
  80 +export const formSchema: FormSchema[] = [
  81 + {
  82 + field: 'type',
  83 + label: '菜单类型',
  84 + component: 'RadioButtonGroup',
  85 + defaultValue: '0',
  86 + componentProps: {
  87 + options: [
  88 + { label: '目录', value: '0' },
  89 + { label: '菜单', value: '1' },
  90 + { label: '按钮', value: '2' },
  91 + ],
  92 + },
  93 + colProps: { lg: 24, md: 24 },
  94 + },
  95 + {
  96 + field: 'menuName',
  97 + label: '菜单名称',
  98 + component: 'Input',
  99 + required: true,
  100 + },
  101 +
  102 + {
  103 + field: 'parentMenu',
  104 + label: '上级菜单',
  105 + component: 'TreeSelect',
  106 + componentProps: {
  107 + replaceFields: {
  108 + title: 'menuName',
  109 + key: 'id',
  110 + value: 'id',
  111 + },
  112 + getPopupContainer: () => document.body,
  113 + },
  114 + },
  115 +
  116 + {
  117 + field: 'orderNo',
  118 + label: '排序',
  119 + component: 'InputNumber',
  120 + required: true,
  121 + },
  122 + {
  123 + field: 'icon',
  124 + label: '图标',
  125 + component: 'IconPicker',
  126 + required: true,
  127 + show: ({ values }) => !isButton(values.type),
  128 + },
  129 +
  130 + {
  131 + field: 'routePath',
  132 + label: '路由地址',
  133 + component: 'Input',
  134 + required: true,
  135 + show: ({ values }) => !isButton(values.type),
  136 + },
  137 + {
  138 + field: 'component',
  139 + label: '组件路径',
  140 + component: 'Input',
  141 + show: ({ values }) => isMenu(values.type),
  142 + },
  143 + {
  144 + field: 'permission',
  145 + label: '权限标识',
  146 + component: 'Input',
  147 + show: ({ values }) => !isDir(values.type),
  148 + },
  149 + {
  150 + field: 'status',
  151 + label: '状态',
  152 + component: 'RadioButtonGroup',
  153 + defaultValue: '0',
  154 + componentProps: {
  155 + options: [
  156 + { label: '启用', value: '0' },
  157 + { label: '禁用', value: '1' },
  158 + ],
  159 + },
  160 + },
  161 + {
  162 + field: 'isExt',
  163 + label: '是否外链',
  164 + component: 'RadioButtonGroup',
  165 + defaultValue: '0',
  166 + componentProps: {
  167 + options: [
  168 + { label: '否', value: '0' },
  169 + { label: '是', value: '1' },
  170 + ],
  171 + },
  172 + show: ({ values }) => !isButton(values.type),
  173 + },
  174 +
  175 + {
  176 + field: 'keepalive',
  177 + label: '是否缓存',
  178 + component: 'RadioButtonGroup',
  179 + defaultValue: '0',
  180 + componentProps: {
  181 + options: [
  182 + { label: '否', value: '0' },
  183 + { label: '是', value: '1' },
  184 + ],
  185 + },
  186 + show: ({ values }) => isMenu(values.type),
  187 + },
  188 +
  189 + {
  190 + field: 'show',
  191 + label: '是否显示',
  192 + component: 'RadioButtonGroup',
  193 + defaultValue: '0',
  194 + componentProps: {
  195 + options: [
  196 + { label: '是', value: '0' },
  197 + { label: '否', value: '1' },
  198 + ],
  199 + },
  200 + show: ({ values }) => !isButton(values.type),
  201 + },
  202 +];
... ...
src/views/demo/system/role/RoleDrawer.vue 0 → 100644
  1 +<template>
  2 + <BasicDrawer
  3 + v-bind="$attrs"
  4 + @register="registerDrawer"
  5 + showFooter
  6 + :title="getTitle"
  7 + width="500px"
  8 + @ok="handleSubmit"
  9 + >
  10 + <BasicForm @register="registerForm" />
  11 + </BasicDrawer>
  12 +</template>
  13 +<script lang="ts">
  14 + import { defineComponent, ref, computed, unref } from 'vue';
  15 + import { BasicForm, useForm } from '/@/components/Form/index';
  16 + import { formSchema } from './role.data';
  17 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  18 +
  19 + import { getMenuList } from '/@/api/demo/system';
  20 +
  21 + export default defineComponent({
  22 + name: 'RoleDrawer',
  23 + components: { BasicDrawer, BasicForm },
  24 + emits: ['success', 'register'],
  25 + setup(_, { emit }) {
  26 + const isUpdate = ref(true);
  27 +
  28 + const [registerForm, { resetFields, setFieldsValue, updateSchema, validate }] = useForm({
  29 + labelWidth: 90,
  30 + schemas: formSchema,
  31 + showActionButtonGroup: false,
  32 + });
  33 +
  34 + const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
  35 + resetFields();
  36 + setDrawerProps({ confirmLoading: false });
  37 + isUpdate.value = !!data?.isUpdate;
  38 +
  39 + if (unref(isUpdate)) {
  40 + setFieldsValue({
  41 + ...data.record,
  42 + });
  43 + }
  44 + const treeData = await getMenuList();
  45 + updateSchema({
  46 + field: 'parentMenu',
  47 + componentProps: { treeData },
  48 + });
  49 + });
  50 +
  51 + const getTitle = computed(() => (!unref(isUpdate) ? '新增角色' : '编辑角色'));
  52 +
  53 + async function handleSubmit() {
  54 + try {
  55 + const values = await validate();
  56 + setDrawerProps({ confirmLoading: true });
  57 + // TODO custom api
  58 + console.log(values);
  59 + closeDrawer();
  60 + emit('success');
  61 + } finally {
  62 + setDrawerProps({ confirmLoading: false });
  63 + }
  64 + }
  65 +
  66 + return { registerDrawer, registerForm, getTitle, handleSubmit };
  67 + },
  68 + });
  69 +</script>
... ...
src/views/demo/system/role/index.vue 0 → 100644
  1 +<template>
  2 + <div>
  3 + <BasicTable @register="registerTable">
  4 + <template #toolbar>
  5 + <a-button type="primary" @click="handleCreate"> 新增角色 </a-button>
  6 + </template>
  7 + <template #action="{ record }">
  8 + <TableAction
  9 + :actions="[
  10 + {
  11 + icon: 'clarity:note-edit-line',
  12 + onClick: handleEdit.bind(null, record),
  13 + },
  14 + {
  15 + icon: 'ant-design:delete-outlined',
  16 + color: 'error',
  17 + popConfirm: {
  18 + title: '是否确认删除',
  19 + confirm: handleDelete.bind(null, record),
  20 + },
  21 + },
  22 + ]"
  23 + />
  24 + </template>
  25 + </BasicTable>
  26 + <RoleDrawer @register="registerDrawer" @success="handleSuccess" />
  27 + </div>
  28 +</template>
  29 +<script lang="ts">
  30 + import { defineComponent } from 'vue';
  31 +
  32 + import { BasicTable, useTable, TableAction } from '/@/components/Table';
  33 + import { getRoleList } from '/@/api/demo/system';
  34 +
  35 + import { useDrawer } from '/@/components/Drawer';
  36 + import RoleDrawer from './RoleDrawer.vue';
  37 +
  38 + import { columns, searchFormSchema } from './role.data';
  39 +
  40 + export default defineComponent({
  41 + name: 'RoleManagement',
  42 + components: { BasicTable, RoleDrawer, TableAction },
  43 + setup() {
  44 + const [registerDrawer, { openDrawer }] = useDrawer();
  45 + const [registerTable, { reload }] = useTable({
  46 + title: '角色列表',
  47 + api: getRoleList,
  48 + columns,
  49 + formConfig: {
  50 + labelWidth: 120,
  51 + schemas: searchFormSchema,
  52 + },
  53 + useSearchForm: true,
  54 + showTableSetting: true,
  55 + bordered: true,
  56 + showIndexColumn: false,
  57 + actionColumn: {
  58 + width: 80,
  59 + title: '操作',
  60 + dataIndex: 'action',
  61 + slots: { customRender: 'action' },
  62 + fixed: undefined,
  63 + },
  64 + });
  65 +
  66 + function handleCreate() {
  67 + openDrawer(true, {
  68 + isUpdate: false,
  69 + });
  70 + }
  71 +
  72 + function handleEdit(record: Recordable) {
  73 + openDrawer(true, {
  74 + record,
  75 + isUpdate: true,
  76 + });
  77 + }
  78 +
  79 + function handleDelete(record: Recordable) {
  80 + console.log(record);
  81 + }
  82 +
  83 + function handleSuccess() {
  84 + reload();
  85 + }
  86 +
  87 + return {
  88 + registerTable,
  89 + registerDrawer,
  90 + handleCreate,
  91 + handleEdit,
  92 + handleDelete,
  93 + handleSuccess,
  94 + };
  95 + },
  96 + });
  97 +</script>
... ...
src/views/demo/system/role/role.data.ts 0 → 100644
  1 +import { BasicColumn } from '/@/components/Table';
  2 +import { FormSchema } from '/@/components/Table';
  3 +import { h } from 'vue';
  4 +import { Tag } from 'ant-design-vue';
  5 +
  6 +export const columns: BasicColumn[] = [
  7 + {
  8 + title: '角色名称',
  9 + dataIndex: 'roleName',
  10 + width: 200,
  11 + },
  12 + {
  13 + title: '角色值',
  14 + dataIndex: 'roleValue',
  15 + width: 180,
  16 + },
  17 + {
  18 + title: '排序',
  19 + dataIndex: 'orderNo',
  20 + width: 50,
  21 + },
  22 + {
  23 + title: '状态',
  24 + dataIndex: 'status',
  25 + width: 80,
  26 + customRender: ({ record }) => {
  27 + const status = record.status;
  28 + const enable = ~~status === 0;
  29 + const color = enable ? 'green' : 'red';
  30 + const text = enable ? '启用' : '停用';
  31 + return h(Tag, { color: color }, () => text);
  32 + },
  33 + },
  34 + {
  35 + title: '创建时间',
  36 + dataIndex: 'createTime',
  37 + width: 180,
  38 + },
  39 + {
  40 + title: '备注',
  41 + dataIndex: 'remark',
  42 + },
  43 +];
  44 +
  45 +export const searchFormSchema: FormSchema[] = [
  46 + {
  47 + field: 'roleNme',
  48 + label: '角色名称',
  49 + component: 'Input',
  50 + colProps: { span: 8 },
  51 + },
  52 + {
  53 + field: 'status',
  54 + label: '状态',
  55 + component: 'Select',
  56 + componentProps: {
  57 + options: [
  58 + { label: '启用', value: '0' },
  59 + { label: '停用', value: '1' },
  60 + ],
  61 + },
  62 + colProps: { span: 8 },
  63 + },
  64 +];
  65 +
  66 +export const formSchema: FormSchema[] = [
  67 + {
  68 + field: 'roleName',
  69 + label: '角色名称',
  70 + required: true,
  71 + component: 'Input',
  72 + },
  73 + {
  74 + field: 'roleValue',
  75 + label: '角色值',
  76 + required: true,
  77 + component: 'Input',
  78 + },
  79 + {
  80 + field: 'status',
  81 + label: '状态',
  82 + component: 'RadioButtonGroup',
  83 + defaultValue: '0',
  84 + componentProps: {
  85 + options: [
  86 + { label: '启用', value: '0' },
  87 + { label: '停用', value: '1' },
  88 + ],
  89 + },
  90 + },
  91 + {
  92 + label: '备注',
  93 + field: 'remark',
  94 + component: 'InputTextArea',
  95 + },
  96 + {
  97 + label: '菜单分配',
  98 + field: 'menu',
  99 + slot: 'menu',
  100 + component: 'Render',
  101 + },
  102 +];
... ...
vite.config.ts
... ... @@ -36,7 +36,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig =&gt; {
36 36 replacement: pathResolve('src') + '/',
37 37 },
38 38 {
39   - // /@/xxxx => src/xxx
  39 + // /#/xxxx => types/xxx
40 40 find: /^\/#\//,
41 41 replacement: pathResolve('types') + '/',
42 42 },
... ... @@ -52,6 +52,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig =&gt; {
52 52 },
53 53  
54 54 build: {
  55 + cssCodeSplit: false,
55 56 // minify: 'esbuild',
56 57 outDir: OUTPUT_DIR,
57 58 polyfillDynamicImport: VITE_LEGACY,
... ...
yarn.lock
... ... @@ -1117,10 +1117,10 @@
1117 1117 dependencies:
1118 1118 cross-fetch "^3.0.6"
1119 1119  
1120   -"@iconify/json@^1.1.310":
1121   - version "1.1.310"
1122   - resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.310.tgz#4180cfdafbf4e218b8f78abae3434089bcb6ccf6"
1123   - integrity sha512-gLCuqulJPC2ly3upqPILOA1nV2fYfb99Dx64hLvneUb0r22opL+TfMfl352AjSZTFMGKV6ve8pNSAOS5gvY3vg==
  1120 +"@iconify/json@^1.1.311":
  1121 + version "1.1.311"
  1122 + resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.311.tgz#cafa09c427fdacce8198e787f87c194eaa7e3e38"
  1123 + integrity sha512-ghm5JynA3aft0goTlEt6w2aYZCTaOO4IE59Cf99ip81NDs0qw4OJ/JSoR09jIrcVDSVZJdp3jf0Tni1iRV1j+w==
1124 1124  
1125 1125 "@intlify/core-base@9.0.0":
1126 1126 version "9.0.0"
... ... @@ -1780,16 +1780,16 @@
1780 1780 dependencies:
1781 1781 vue-demi latest
1782 1782  
1783   -"@windicss/plugin-utils@0.6.2":
1784   - version "0.6.2"
1785   - resolved "https://registry.npmjs.org/@windicss/plugin-utils/-/plugin-utils-0.6.2.tgz#8fc76d9f2a1e3de123ffd54fdd9d1583801bb087"
1786   - integrity sha512-qR2h/vDn3LZtL0cC3id9nxPwhYqCtkcwASs63sHTUOzLhxz+zkG4xR+odndbR6PTjrlTgBC7n5hLjpq0lxRksg==
  1783 +"@windicss/plugin-utils@0.6.4":
  1784 + version "0.6.4"
  1785 + resolved "https://registry.npmjs.org/@windicss/plugin-utils/-/plugin-utils-0.6.4.tgz#854f411b156fea61b2e6724799e91f0f9407a637"
  1786 + integrity sha512-5RZQ71snmoKmZEXHyCByOohUM/SsWIX856+geMwOfbMOGL0guv/1TVg2PalFrZh3k/HfqQnO2ft5YSWKtrW32A==
1787 1787 dependencies:
1788   - esbuild "^0.8.52"
  1788 + esbuild "^0.8.54"
1789 1789 esbuild-register "^2.0.0"
1790 1790 fast-glob "^3.2.5"
1791 1791 micromatch "^4.0.2"
1792   - windicss "^2.2.0"
  1792 + windicss "^2.2.3"
1793 1793  
1794 1794 "@zxcvbn-ts/core@^0.2.0":
1795 1795 version "0.2.0"
... ... @@ -8957,13 +8957,13 @@ vite-plugin-theme@^0.4.8:
8957 8957 es-module-lexer "^0.3.26"
8958 8958 tinycolor2 "^1.4.2"
8959 8959  
8960   -vite-plugin-windicss@0.6.2:
8961   - version "0.6.2"
8962   - resolved "https://registry.npmjs.org/vite-plugin-windicss/-/vite-plugin-windicss-0.6.2.tgz#2b406c65768ce7df22451dc7b47c0026abd4bb24"
8963   - integrity sha512-V4WnjkxvriJSVQjswY+SrDKogOLNq1eG6dQw1wWcJRV+0QUz9pAGrMolSwed4d4MwSSbJrCA7If8xa+EFLUigw==
  8960 +vite-plugin-windicss@0.6.4:
  8961 + version "0.6.4"
  8962 + resolved "https://registry.npmjs.org/vite-plugin-windicss/-/vite-plugin-windicss-0.6.4.tgz#2d2bc7831dddff129d597959efaff616c6408a01"
  8963 + integrity sha512-4BuUglBXeNIrIDfSKyg7rvqKEmxvyja2aKJxgHdOCICV8jIz2MvrZaRS9a74gUihz+OohBYqLnkOlfD+sCIQBg==
8964 8964 dependencies:
8965   - "@windicss/plugin-utils" "0.6.2"
8966   - windicss "^2.2.0"
  8965 + "@windicss/plugin-utils" "0.6.4"
  8966 + windicss "^2.2.3"
8967 8967  
8968 8968 vite@^2.0.5:
8969 8969 version "2.0.5"
... ... @@ -9090,10 +9090,10 @@ which@^2.0.1:
9090 9090 dependencies:
9091 9091 isexe "^2.0.0"
9092 9092  
9093   -windicss@^2.2.0:
9094   - version "2.2.0"
9095   - resolved "https://registry.npmjs.org/windicss/-/windicss-2.2.0.tgz#42fe83b9bff6d4fc9caf85272fb6c6ddb990fbc1"
9096   - integrity sha512-UuPhwe/NMgGOmUMnaTR+64eMeoiRCYb/zKJfo4pm9XUb4BRFlaFMm1x0KF1RF8EtK/8jKvDlXMc/J6UPBj8/OA==
  9093 +windicss@^2.2.3:
  9094 + version "2.2.3"
  9095 + resolved "https://registry.npmjs.org/windicss/-/windicss-2.2.3.tgz#5d25cab4b397d64f74660cac9b947b29cda240e9"
  9096 + integrity sha512-rDaHpCgF3nq6tt21KC3691vhNZcbDHo0DJN+fgY5OmNCqd8muvZ9N4Zp9eqjWaJetg+ulz++MBM/pG9D70sj2w==
9097 9097  
9098 9098 wmf@~1.0.1:
9099 9099 version "1.0.2"
... ...