Commit 9a1ba74920045a7d65f4dd802d244510aff5b3a9
1 parent
f79cae63
wip: system management sample page
Showing
31 changed files
with
852 additions
and
52 deletions
CHANGELOG.zh_CN.md
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 = (() => { |
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 & { |
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) => |
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 './components/RadioButtonGroup.vue'; |
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('RangePicker', 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
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
src/components/StrengthMeter/index.ts
src/components/Table/src/components/ExpandIcon.tsx
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
src/design/ant/input.less
src/locales/lang/en/routes/demo/system.ts
src/locales/lang/zh_CN/routes/demo/system.ts
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
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
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 => { |
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 => { |
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" | ... | ... |