feat: added system management sample page

This commit is contained in:
Vben 2021-03-04 01:25:50 +08:00
parent cd8e924d46
commit 4628d94415
14 changed files with 150 additions and 68 deletions

View File

@ -8,12 +8,11 @@
- axios 支持 form-data 格式请求 - axios 支持 form-data 格式请求
- 新增图标选择器组件(支持本地和在线方式) - 新增图标选择器组件(支持本地和在线方式)
- 新增修改密码界面
- 新增部门管理示例界面
- 新增 WebSocket 示例和服务脚本 - 新增 WebSocket 示例和服务脚本
- Tree 组件新增 `renderIcon` 属性用于控制层级图标显示 - Tree 组件新增 `renderIcon` 属性用于控制层级图标显示
- Tree->actionItem 新增 show 属性,用于动态控制按钮显示 - Tree->actionItem 新增 show 属性,用于动态控制按钮显示
- Tree 新增工具栏/title/搜索功能 - Tree 新增工具栏/title/搜索功能
- 新增部门管理/修改密码/账号管理/角色管理/菜单管理示例界面
### ⚡ Performance Improvements ### ⚡ Performance Improvements

View File

@ -116,7 +116,7 @@ export default [
}, },
}, },
{ {
url: '/api/system/getRoleList', url: '/api/system/getRoleListByPage',
timeout: 100, timeout: 100,
method: 'get', method: 'get',
response: ({ query }) => { response: ({ query }) => {
@ -124,6 +124,14 @@ export default [
return resultPageSuccess(page, pageSize, roleList); return resultPageSuccess(page, pageSize, roleList);
}, },
}, },
{
url: '/api/system/getAllRoleList',
timeout: 100,
method: 'get',
response: () => {
return resultSuccess(roleList);
},
},
{ {
url: '/api/system/getDeptList', url: '/api/system/getDeptList',
timeout: 100, timeout: 100,

View File

@ -4,11 +4,14 @@ export type AccountParams = BasicPageParams & {
account?: string; account?: string;
nickname?: string; nickname?: string;
}; };
export type RoleParams = BasicPageParams & {
export type RoleParams = {
roleName?: string; roleName?: string;
status?: string; status?: string;
}; };
export type RolePageParams = BasicPageParams & RoleParams;
export type DeptParams = { export type DeptParams = {
deptName?: string; deptName?: string;
status?: string; status?: string;
@ -66,4 +69,6 @@ export type DeptListGetResultModel = BasicFetchResult<DeptListItem>;
export type MenuListGetResultModel = BasicFetchResult<MenuListItem>; export type MenuListGetResultModel = BasicFetchResult<MenuListItem>;
export type RoleListGetResultModel = BasicFetchResult<RoleListItem>; export type RolePageListGetResultModel = BasicFetchResult<RoleListItem>;
export type RoleListGetResultModel = RoleListItem[];

View File

@ -3,9 +3,11 @@ import {
DeptListItem, DeptListItem,
MenuParams, MenuParams,
RoleParams, RoleParams,
RolePageParams,
MenuListGetResultModel, MenuListGetResultModel,
DeptListGetResultModel, DeptListGetResultModel,
AccountListGetResultModel, AccountListGetResultModel,
RolePageListGetResultModel,
RoleListGetResultModel, RoleListGetResultModel,
} from './model/systemModel'; } from './model/systemModel';
import { defHttp } from '/@/utils/http/axios'; import { defHttp } from '/@/utils/http/axios';
@ -14,7 +16,8 @@ enum Api {
AccountList = '/system/getAccountList', AccountList = '/system/getAccountList',
DeptList = '/system/getDeptList', DeptList = '/system/getDeptList',
MenuList = '/system/getMenuList', MenuList = '/system/getMenuList',
RoleList = '/system/getRoleList', RolePageList = '/system/getRoleListByPage',
GetAllRoleList = '/system/getAllRoleList',
} }
export const getAccountList = (params: AccountParams) => export const getAccountList = (params: AccountParams) =>
@ -26,5 +29,8 @@ export const getDeptList = (params?: DeptListItem) =>
export const getMenuList = (params?: MenuParams) => export const getMenuList = (params?: MenuParams) =>
defHttp.get<MenuListGetResultModel>({ url: Api.MenuList, params }); defHttp.get<MenuListGetResultModel>({ url: Api.MenuList, params });
export const getRoleList = (params?: RoleParams) => export const getRoleListByPage = (params?: RolePageParams) =>
defHttp.get<RoleListGetResultModel>({ url: Api.RoleList, params }); defHttp.get<RolePageListGetResultModel>({ url: Api.RolePageList, params });
export const getAllRoleList = (params?: RoleParams) =>
defHttp.get<RoleListGetResultModel>({ url: Api.GetAllRoleList, params });

View File

@ -311,11 +311,12 @@ export default defineComponent({
const realColProps = { ...baseColProps, ...colProps }; const realColProps = { ...baseColProps, ...colProps };
const { isIfShow, isShow } = getShow(); const { isIfShow, isShow } = getShow();
const values = unref(getValues);
const getContent = () => { const getContent = () => {
return colSlot return colSlot
? getSlot(slots, colSlot, unref(getValues)) ? getSlot(slots, colSlot, values)
: renderColContent : renderColContent
? renderColContent(unref(getValues)) ? renderColContent(values)
: renderItem(); : renderItem();
}; };

View File

@ -10,9 +10,12 @@
<Icon icon="ion:ellipsis-vertical" /> <Icon icon="ion:ellipsis-vertical" />
<template #overlay> <template #overlay>
<Menu @click="handleMenuClick"> <Menu @click="handleMenuClick">
<MenuItem v-for="item in toolbarList" :key="item.value"> <template v-for="item in toolbarList" :key="item.value">
{{ item.label }} <MenuItem v-bind="{ key: item.value }">
</MenuItem> {{ item.label }}
</MenuItem>
<MenuDivider v-if="item.divider" />
</template>
</Menu> </Menu>
</template> </template>
</Dropdown> </Dropdown>
@ -46,6 +49,7 @@
Dropdown, Dropdown,
Menu, Menu,
MenuItem: Menu.Item, MenuItem: Menu.Item,
MenuDivider: Menu.Divider,
InputSearch: Input.Search, InputSearch: Input.Search,
}, },
props: { props: {
@ -64,9 +68,9 @@
const { t } = useI18n(); const { t } = useI18n();
const toolbarList = ref([ const toolbarList = ref([
{ label: t('component.tree.selectAll'), value: ToolbarEnum.SELECT_ALL }, { label: t('component.tree.selectAll'), value: ToolbarEnum.SELECT_ALL },
{ label: t('component.tree.unSelectAll'), value: ToolbarEnum.UN_SELECT_ALL }, { label: t('component.tree.unSelectAll'), value: ToolbarEnum.UN_SELECT_ALL, divider: true },
{ label: t('component.tree.expandAll'), value: ToolbarEnum.EXPAND_ALL }, { label: t('component.tree.expandAll'), value: ToolbarEnum.EXPAND_ALL },
{ label: t('component.tree.unExpandAll'), value: ToolbarEnum.UN_EXPAND_ALL }, { label: t('component.tree.unExpandAll'), value: ToolbarEnum.UN_EXPAND_ALL, divider: true },
{ label: t('component.tree.checkStrictly'), value: ToolbarEnum.CHECK_STRICTLY }, { label: t('component.tree.checkStrictly'), value: ToolbarEnum.CHECK_STRICTLY },
{ label: t('component.tree.checkUnStrictly'), value: ToolbarEnum.CHECK_UN_STRICTLY }, { label: t('component.tree.checkUnStrictly'), value: ToolbarEnum.CHECK_UN_STRICTLY },
]); ]);

View File

@ -1,7 +1,7 @@
<script lang="tsx"> <script lang="tsx">
import type { ReplaceFields, Keys, CheckKeys, TreeActionType, TreeItem } from './types'; import type { ReplaceFields, Keys, CheckKeys, TreeActionType, TreeItem } from './types';
import { defineComponent, reactive, computed, unref, ref, watchEffect, toRaw } from 'vue'; import { defineComponent, reactive, computed, unref, ref, watchEffect, toRaw, watch } from 'vue';
import { Tree } from 'ant-design-vue'; import { Tree } from 'ant-design-vue';
import { TreeIcon } from './TreeIcon'; import { TreeIcon } from './TreeIcon';
import TreeHeader from './TreeHeader.vue'; import TreeHeader from './TreeHeader.vue';
@ -27,6 +27,7 @@
} }
export default defineComponent({ export default defineComponent({
name: 'BasicTree', name: 'BasicTree',
inheritAttrs: false,
props: basicProps, props: basicProps,
emits: ['update:expandedKeys', 'update:selectedKeys', 'update:value', 'change'], emits: ['update:expandedKeys', 'update:selectedKeys', 'update:value', 'change'],
setup(props, { attrs, slots, emit }) { setup(props, { attrs, slots, emit }) {
@ -89,8 +90,9 @@
}, },
onCheck: (v: CheckKeys) => { onCheck: (v: CheckKeys) => {
state.checkedKeys = v; state.checkedKeys = v;
emit('change', v); const rawVal = toRaw(v);
emit('update:value', v); emit('change', rawVal);
emit('update:value', rawVal);
}, },
onRightClick: handleRightClick, onRightClick: handleRightClick,
}; };
@ -191,11 +193,21 @@
state.checkedKeys = props.checkedKeys; state.checkedKeys = props.checkedKeys;
}); });
watchEffect(() => { watch(
if (props.value) { () => props.value,
state.checkedKeys = props.value; () => {
state.checkedKeys = toRaw(props.value || []);
} }
}); );
// watchEffect(() => {
// console.log('======================');
// console.log(props.value);
// console.log('======================');
// if (props.value) {
// state.checkedKeys = props.value;
// }
// });
watchEffect(() => { watchEffect(() => {
state.checkStrictly = props.checkStrictly; state.checkStrictly = props.checkStrictly;

View File

@ -7,45 +7,29 @@ const menu: MenuModule = {
name: t('routes.demo.system.moduleName'), name: t('routes.demo.system.moduleName'),
path: '/system', path: '/system',
tag: { tag: {
dot: true, content: 'new',
}, },
children: [ children: [
{ {
path: 'account', path: 'account',
name: t('routes.demo.system.account'), name: t('routes.demo.system.account'),
tag: {
dot: true,
type: 'warn',
},
}, },
{ {
path: 'role', path: 'role',
name: t('routes.demo.system.role'), name: t('routes.demo.system.role'),
tag: {
dot: true,
},
}, },
{ {
path: 'menu', path: 'menu',
name: t('routes.demo.system.menu'), name: t('routes.demo.system.menu'),
tag: {
dot: true,
},
}, },
{ {
path: 'dept', path: 'dept',
name: t('routes.demo.system.dept'), name: t('routes.demo.system.dept'),
tag: {
dot: true,
},
}, },
{ {
path: 'changePassword', path: 'changePassword',
name: t('routes.demo.system.password'), name: t('routes.demo.system.password'),
tag: {
dot: true,
},
}, },
], ],
}, },

View File

@ -8,13 +8,16 @@
import { BasicModal, useModalInner } from '/@/components/Modal'; import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index'; import { BasicForm, useForm } from '/@/components/Form/index';
import { accountFormSchema } from './account.data'; import { accountFormSchema } from './account.data';
import { getDeptList } from '/@/api/demo/system';
export default defineComponent({ export default defineComponent({
name: 'AccountModal', name: 'AccountModal',
components: { BasicModal, BasicForm }, components: { BasicModal, BasicForm },
setup() { emits: ['success', 'register'],
setup(_, { emit }) {
const isUpdate = ref(true); const isUpdate = ref(true);
const [registerForm, { setFieldsValue, validate }] = useForm({ const [registerForm, { setFieldsValue, updateSchema, validate }] = useForm({
labelWidth: 100, labelWidth: 100,
schemas: accountFormSchema, schemas: accountFormSchema,
showActionButtonGroup: false, showActionButtonGroup: false,
@ -23,7 +26,7 @@
}, },
}); });
const [registerModal, { setModalProps }] = useModalInner((data) => { const [registerModal, { setModalProps }] = useModalInner(async (data) => {
setModalProps({ confirmLoading: false }); setModalProps({ confirmLoading: false });
isUpdate.value = !!data?.isUpdate; isUpdate.value = !!data?.isUpdate;
@ -32,6 +35,18 @@
...data.record, ...data.record,
}); });
} }
const treeData = await getDeptList();
updateSchema([
{
field: 'pwd',
show: !unref(isUpdate),
},
{
field: 'dept',
componentProps: { treeData },
},
]);
}); });
const getTitle = computed(() => (!unref(isUpdate) ? '新增账号' : '编辑账号')); const getTitle = computed(() => (!unref(isUpdate) ? '新增账号' : '编辑账号'));
@ -42,6 +57,7 @@
setModalProps({ confirmLoading: true }); setModalProps({ confirmLoading: true });
// TODO custom api // TODO custom api
console.log(values); console.log(values);
emit('success');
} finally { } finally {
setModalProps({ confirmLoading: false }); setModalProps({ confirmLoading: false });
} }

View File

@ -1,3 +1,4 @@
import { getAllRoleList } from '/@/api/demo/system';
import { BasicColumn } from '/@/components/Table'; import { BasicColumn } from '/@/components/Table';
import { FormSchema } from '/@/components/Table'; import { FormSchema } from '/@/components/Table';
@ -55,25 +56,52 @@ export const accountFormSchema: FormSchema[] = [
component: 'Input', component: 'Input',
required: true, required: true,
}, },
{
field: 'pwd',
label: '密码',
component: 'InputPassword',
required: true,
show: true,
},
{
label: '角色',
field: 'role',
component: 'ApiSelect',
componentProps: {
api: getAllRoleList,
labelField: 'roleName',
valueField: 'roleValue',
},
required: true,
},
{
field: 'dept',
label: '所属部门',
component: 'TreeSelect',
componentProps: {
replaceFields: {
title: 'deptName',
key: 'id',
value: 'id',
},
getPopupContainer: () => document.body,
},
required: true,
},
{ {
field: 'nickname', field: 'nickname',
label: '昵称', label: '昵称',
component: 'Input', component: 'Input',
required: true, required: true,
}, },
{ {
label: '邮箱', label: '邮箱',
field: 'email', field: 'email',
component: 'Input', component: 'Input',
required: true, required: true,
}, },
// TODO
{
label: '角色',
field: 'role',
component: 'Input',
required: true,
},
{ {
label: '备注', label: '备注',
field: 'remark', field: 'remark',

View File

@ -2,7 +2,7 @@
<div :class="[prefixCls]"> <div :class="[prefixCls]">
<BasicTable @register="registerTable"> <BasicTable @register="registerTable">
<template #toolbar> <template #toolbar>
<a-button type="primary" @click="handleCreateAccount"> 新增账号 </a-button> <a-button type="primary" @click="handleCreate"> 新增账号 </a-button>
</template> </template>
<template #action="{ record }"> <template #action="{ record }">
<TableAction <TableAction
@ -23,7 +23,7 @@
/> />
</template> </template>
</BasicTable> </BasicTable>
<AccountModal @register="registerModal" /> <AccountModal @register="registerModal" @success="handleSuccess" />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -45,7 +45,7 @@
const { prefixCls } = useDesign('account-management'); const { prefixCls } = useDesign('account-management');
const [registerModal, { openModal }] = useModal(); const [registerModal, { openModal }] = useModal();
const [registerTable] = useTable({ const [registerTable, { reload }] = useTable({
title: '账号列表', title: '账号列表',
api: getAccountList, api: getAccountList,
columns, columns,
@ -64,7 +64,7 @@
}, },
}); });
function handleCreateAccount() { function handleCreate() {
openModal(true, { openModal(true, {
isUpdate: false, isUpdate: false,
}); });
@ -81,13 +81,18 @@
console.log(record); console.log(record);
} }
function handleSuccess() {
reload();
}
return { return {
prefixCls, prefixCls,
registerTable, registerTable,
registerModal, registerModal,
handleCreateAccount, handleCreate,
handleEdit, handleEdit,
handleDelete, handleDelete,
handleSuccess,
}; };
}, },
}); });

View File

@ -7,7 +7,18 @@
width="500px" width="500px"
@ok="handleSubmit" @ok="handleSubmit"
> >
<BasicForm @register="registerForm" /> <BasicForm @register="registerForm">
<template #menu="{ model, field }">
<BasicTree
v-model:value="model[field]"
:treeData="treeData"
:replaceFields="{ title: 'menuName', key: 'id' }"
checkable
toolbar
title="菜单分配"
/>
</template>
</BasicForm>
</BasicDrawer> </BasicDrawer>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -15,17 +26,19 @@
import { BasicForm, useForm } from '/@/components/Form/index'; import { BasicForm, useForm } from '/@/components/Form/index';
import { formSchema } from './role.data'; import { formSchema } from './role.data';
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer'; import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
import { BasicTree, TreeItem } from '/@/components/Tree';
import { getMenuList } from '/@/api/demo/system'; import { getMenuList } from '/@/api/demo/system';
export default defineComponent({ export default defineComponent({
name: 'RoleDrawer', name: 'RoleDrawer',
components: { BasicDrawer, BasicForm }, components: { BasicDrawer, BasicForm, BasicTree },
emits: ['success', 'register'], emits: ['success', 'register'],
setup(_, { emit }) { setup(_, { emit }) {
const isUpdate = ref(true); const isUpdate = ref(true);
const treeData = ref<TreeItem[]>([]);
const [registerForm, { resetFields, setFieldsValue, updateSchema, validate }] = useForm({ const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
labelWidth: 90, labelWidth: 90,
schemas: formSchema, schemas: formSchema,
showActionButtonGroup: false, showActionButtonGroup: false,
@ -41,11 +54,7 @@
...data.record, ...data.record,
}); });
} }
const treeData = await getMenuList(); treeData.value = ((await getMenuList()) as any) as TreeItem[];
updateSchema({
field: 'parentMenu',
componentProps: { treeData },
});
}); });
const getTitle = computed(() => (!unref(isUpdate) ? '新增角色' : '编辑角色')); const getTitle = computed(() => (!unref(isUpdate) ? '新增角色' : '编辑角色'));
@ -63,7 +72,13 @@
} }
} }
return { registerDrawer, registerForm, getTitle, handleSubmit }; return {
registerDrawer,
registerForm,
getTitle,
handleSubmit,
treeData,
};
}, },
}); });
</script> </script>

View File

@ -30,7 +30,7 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { BasicTable, useTable, TableAction } from '/@/components/Table'; import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { getRoleList } from '/@/api/demo/system'; import { getRoleListByPage } from '/@/api/demo/system';
import { useDrawer } from '/@/components/Drawer'; import { useDrawer } from '/@/components/Drawer';
import RoleDrawer from './RoleDrawer.vue'; import RoleDrawer from './RoleDrawer.vue';
@ -44,7 +44,7 @@
const [registerDrawer, { openDrawer }] = useDrawer(); const [registerDrawer, { openDrawer }] = useDrawer();
const [registerTable, { reload }] = useTable({ const [registerTable, { reload }] = useTable({
title: '角色列表', title: '角色列表',
api: getRoleList, api: getRoleListByPage,
columns, columns,
formConfig: { formConfig: {
labelWidth: 120, labelWidth: 120,

View File

@ -2,7 +2,6 @@ import { BasicColumn } from '/@/components/Table';
import { FormSchema } from '/@/components/Table'; import { FormSchema } from '/@/components/Table';
import { h } from 'vue'; import { h } from 'vue';
import { Tag } from 'ant-design-vue'; import { Tag } from 'ant-design-vue';
export const columns: BasicColumn[] = [ export const columns: BasicColumn[] = [
{ {
title: '角色名称', title: '角色名称',
@ -94,9 +93,9 @@ export const formSchema: FormSchema[] = [
component: 'InputTextArea', component: 'InputTextArea',
}, },
{ {
label: '菜单分配', label: ' ',
field: 'menu', field: 'menu',
slot: 'menu', slot: 'menu',
component: 'Render', component: 'Input',
}, },
]; ];