perf(projects): perf manage menu

This commit is contained in:
Soybean 2024-03-22 01:23:24 +08:00
parent 49558ca048
commit 71f2c5535b
9 changed files with 172 additions and 37 deletions

View File

@ -212,14 +212,15 @@ export function useTableOperate<T extends TableData = TableData>(data: Ref<T[]>,
return { return {
drawerVisible, drawerVisible,
openDrawer,
closeDrawer,
operateType, operateType,
handleAdd, handleAdd,
editingData, editingData,
handleEdit, handleEdit,
checkedRowKeys, checkedRowKeys,
onBatchDeleted, onBatchDeleted,
onDeleted, onDeleted
closeDrawer
}; };
} }

View File

@ -319,8 +319,9 @@ const local: App.I18n.Schema = {
menuName: 'Menu Name', menuName: 'Menu Name',
routeName: 'Route Name', routeName: 'Route Name',
routePath: 'Route Path', routePath: 'Route Path',
page: 'Page Component', routeParams: 'Route Params',
layout: 'Layout Component', layout: 'Layout Component',
page: 'Page Component',
i18nKey: 'I18n Key', i18nKey: 'I18n Key',
icon: 'Icon', icon: 'Icon',
localIcon: 'Local Icon', localIcon: 'Local Icon',

View File

@ -319,8 +319,9 @@ const local: App.I18n.Schema = {
menuName: '菜单名称', menuName: '菜单名称',
routeName: '路由名称', routeName: '路由名称',
routePath: '路由路径', routePath: '路由路径',
page: '页面组件', routeParams: '路由参数',
layout: '布局', layout: '布局',
page: '页面组件',
i18nKey: '国际化key', i18nKey: '国际化key',
icon: '图标', icon: '图标',
localIcon: '本地图标', localIcon: '本地图标',

View File

@ -49,3 +49,11 @@ export function fetchGetMenuList() {
method: 'get' method: 'get'
}); });
} }
/** get all pages */
export function fetchGetAllPages() {
return request<string[]>({
url: '/systemManage/getAllPages',
method: 'get'
});
}

View File

@ -502,8 +502,9 @@ declare namespace App {
menuName: string; menuName: string;
routeName: string; routeName: string;
routePath: string; routePath: string;
page: string; routeParams: string;
layout: string; layout: string;
page: string;
i18nKey: string; i18nKey: string;
icon: string; icon: string;
localIcon: string; localIcon: string;
@ -524,8 +525,8 @@ declare namespace App {
menuName: string; menuName: string;
routeName: string; routeName: string;
routePath: string; routePath: string;
page: string;
layout: string; layout: string;
page: string;
i18nKey: string; i18nKey: string;
icon: string; icon: string;
localIcon: string; localIcon: string;

View File

@ -14,7 +14,7 @@ declare namespace CommonType {
* @property value: The option value * @property value: The option value
* @property label: The option label * @property label: The option label
*/ */
type Option<K> = { value: K; label: string }; type Option<K = string> = { value: K; label: string };
type YesOrNo = 'Y' | 'N'; type YesOrNo = 'Y' | 'N';

View File

@ -1,17 +1,21 @@
<script setup lang="tsx"> <script setup lang="tsx">
import { ref } from 'vue'; import { ref } from 'vue';
import type { Ref } from 'vue';
import { NButton, NPopconfirm, NTag } from 'naive-ui'; import { NButton, NPopconfirm, NTag } from 'naive-ui';
import { fetchGetMenuList } from '@/service/api'; import { useBoolean } from '@sa/hooks';
import { fetchGetAllPages, fetchGetMenuList } from '@/service/api';
import { useAppStore } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
import { useTable, useTableOperate } from '@/hooks/common/table'; import { useTable, useTableOperate } from '@/hooks/common/table';
import { $t } from '@/locales'; import { $t } from '@/locales';
import { yesOrNoRecord } from '@/constants/common'; import { yesOrNoRecord } from '@/constants/common';
import { enableStatusRecord, menuTypeRecord } from '@/constants/business'; import { enableStatusRecord, menuTypeRecord } from '@/constants/business';
import SvgIcon from '@/components/custom/svg-icon.vue'; import SvgIcon from '@/components/custom/svg-icon.vue';
import MenuOperateDrawer from './modules/menu-operate-drawer.vue'; import MenuOperateDrawer, { type OperateType } from './modules/menu-operate-drawer.vue';
const appStore = useAppStore(); const appStore = useAppStore();
const { bool: drawerVisible, setTrue: openDrawer, setFalse: _closeDrawer } = useBoolean();
const wrapperRef = ref<HTMLElement | null>(null); const wrapperRef = ref<HTMLElement | null>(null);
const { columns, columnChecks, data, loading, pagination, getData } = useTable({ const { columns, columnChecks, data, loading, pagination, getData } = useTable({
@ -143,11 +147,11 @@ const { columns, columnChecks, data, loading, pagination, getData } = useTable({
render: row => ( render: row => (
<div class="flex-center justify-end gap-8px"> <div class="flex-center justify-end gap-8px">
{row.menuType === '1' && ( {row.menuType === '1' && (
<NButton type="primary" ghost size="small" onClick={() => handleAddChildMenu(row.id)}> <NButton type="primary" ghost size="small" onClick={() => handleAddChildMenu(row)}>
{$t('page.manage.menu.addChildMenu')} {$t('page.manage.menu.addChildMenu')}
</NButton> </NButton>
)} )}
<NButton type="primary" ghost size="small" onClick={() => edit(row.id)}> <NButton type="primary" ghost size="small" onClick={() => handleEdit(row)}>
{$t('common.edit')} {$t('common.edit')}
</NButton> </NButton>
<NPopconfirm onPositiveClick={() => handleDelete(row.id)}> <NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
@ -166,17 +170,14 @@ const { columns, columnChecks, data, loading, pagination, getData } = useTable({
] ]
}); });
const { const { checkedRowKeys, onBatchDeleted, onDeleted } = useTableOperate(data, getData);
drawerVisible,
operateType, const operateType = ref<OperateType>('add');
editingData,
handleAdd, function handleAdd() {
handleEdit, operateType.value = 'add';
checkedRowKeys, openDrawer();
onBatchDeleted, }
onDeleted
// closeDrawer
} = useTableOperate(data, getData);
async function handleBatchDelete() { async function handleBatchDelete() {
// request // request
@ -192,15 +193,37 @@ function handleDelete(id: number) {
onDeleted(); onDeleted();
} }
function handleAddChildMenu(id: number) { /** the edit menu data or the parent menu data when adding a child menu */
console.log(id); const editingData: Ref<Api.SystemManage.Menu | null> = ref(null);
handleAdd(); function handleEdit(item: Api.SystemManage.Menu) {
operateType.value = 'edit';
editingData.value = { ...item };
openDrawer();
} }
function edit(id: number) { function handleAddChildMenu(item: Api.SystemManage.Menu) {
handleEdit(id); operateType.value = 'addChild';
editingData.value = { ...item };
openDrawer();
} }
const allPages = ref<string[]>([]);
async function getAllPages() {
const { data: pages } = await fetchGetAllPages();
allPages.value = pages || [];
}
function init() {
getAllPages();
}
// init
init();
</script> </script>
<template> <template>
@ -233,6 +256,7 @@ function edit(id: number) {
v-model:visible="drawerVisible" v-model:visible="drawerVisible"
:operate-type="operateType" :operate-type="operateType"
:row-data="editingData" :row-data="editingData"
:all-pages="allPages"
@submitted="getData" @submitted="getData"
/> />
</NCard> </NCard>

View File

@ -6,16 +6,21 @@ import { $t } from '@/locales';
import { enableStatusOptions, menuIconTypeOptions, menuTypeOptions } from '@/constants/business'; import { enableStatusOptions, menuIconTypeOptions, menuTypeOptions } from '@/constants/business';
import SvgIcon from '@/components/custom/svg-icon.vue'; import SvgIcon from '@/components/custom/svg-icon.vue';
import { getLocalIcons } from '@/utils/icon'; import { getLocalIcons } from '@/utils/icon';
import { getLayoutAndPage } from './shared';
defineOptions({ defineOptions({
name: 'MenuOperateDrawer' name: 'MenuOperateDrawer'
}); });
export type OperateType = NaiveUI.TableOperateType | 'addChild';
interface Props { interface Props {
/** the type of operation */ /** the type of operation */
operateType: NaiveUI.TableOperateType; operateType: OperateType;
/** the edit row data */ /** the edit menu data or the parent menu data when adding a child menu */
rowData?: Api.SystemManage.Menu | null; rowData?: Api.SystemManage.Menu | null;
/** all pages */
allPages: string[];
} }
const props = defineProps<Props>(); const props = defineProps<Props>();
@ -34,8 +39,9 @@ const { formRef, validate, restoreValidation } = useNaiveForm();
const { defaultRequiredRule } = useFormRules(); const { defaultRequiredRule } = useFormRules();
const title = computed(() => { const title = computed(() => {
const titles: Record<NaiveUI.TableOperateType, string> = { const titles: Record<OperateType, string> = {
add: $t('page.manage.menu.addMenu'), add: $t('page.manage.menu.addMenu'),
addChild: $t('page.manage.menu.addChildMenu'),
edit: $t('page.manage.menu.editMenu') edit: $t('page.manage.menu.editMenu')
}; };
return titles[props.operateType]; return titles[props.operateType];
@ -43,8 +49,21 @@ const title = computed(() => {
type Model = Pick< type Model = Pick<
Api.SystemManage.Menu, Api.SystemManage.Menu,
'menuType' | 'menuName' | 'icon' | 'iconType' | 'routeName' | 'routePath' | 'status' | 'hideInMenu' | 'order' | 'menuType'
>; | 'menuName'
| 'icon'
| 'iconType'
| 'routeName'
| 'routePath'
| 'component'
| 'status'
| 'hideInMenu'
| 'order'
| 'parentId'
> & {
layout: string;
page: string;
};
const model: Model = reactive(createDefaultModel()); const model: Model = reactive(createDefaultModel());
@ -56,9 +75,12 @@ function createDefaultModel(): Model {
iconType: '1', iconType: '1',
routeName: '', routeName: '',
routePath: '', routePath: '',
layout: '',
page: '',
status: null, status: null,
hideInMenu: false, hideInMenu: false,
order: 0 order: 0,
parentId: 0
}; };
} }
@ -69,6 +91,8 @@ const rules: Record<RuleKey, App.Global.FormRule> = {
userStatus: defaultRequiredRule userStatus: defaultRequiredRule
}; };
const disabledMenuType = computed(() => props.operateType === 'edit');
const localIcons = getLocalIcons(); const localIcons = getLocalIcons();
const localIconOptions = localIcons.map<SelectOption>(item => ({ const localIconOptions = localIcons.map<SelectOption>(item => ({
label: () => ( label: () => (
@ -80,14 +104,55 @@ const localIconOptions = localIcons.map<SelectOption>(item => ({
value: item value: item
})); }));
function handleUpdateModelWhenEdit() { const showLayout = computed(() => model.parentId === 0);
const showPage = computed(() => model.menuType === '2');
const pageOptions = computed(() => {
const allPages = [...props.allPages];
if (model.routeName && !allPages.includes(model.routeName)) {
allPages.unshift(model.routeName);
}
const opts: CommonType.Option[] = allPages.map(page => ({
label: page,
value: page
}));
return opts;
});
const layoutOptions: CommonType.Option[] = [
{
label: 'base',
value: 'base'
},
{
label: 'blank',
value: 'blank'
}
];
function handleUpdateModel() {
if (props.operateType === 'add') { if (props.operateType === 'add') {
Object.assign(model, createDefaultModel()); Object.assign(model, createDefaultModel());
return; return;
} }
if (props.operateType === 'addChild' && props.rowData) {
const { id } = props.rowData;
Object.assign(model, createDefaultModel(), { parentId: id });
}
if (props.operateType === 'edit' && props.rowData) { if (props.operateType === 'edit' && props.rowData) {
Object.assign(model, props.rowData); const { component, ...rest } = props.rowData;
const { layout, page } = getLayoutAndPage(component);
Object.assign(model, rest, { layout, page });
} }
} }
@ -105,7 +170,7 @@ async function handleSubmit() {
watch(visible, () => { watch(visible, () => {
if (visible.value) { if (visible.value) {
handleUpdateModelWhenEdit(); handleUpdateModel();
restoreValidation(); restoreValidation();
} }
}); });
@ -116,7 +181,7 @@ watch(visible, () => {
<NDrawerContent :title="title" :native-scrollbar="false" closable> <NDrawerContent :title="title" :native-scrollbar="false" closable>
<NForm ref="formRef" :model="model" :rules="rules" label-placement="left" :label-width="80"> <NForm ref="formRef" :model="model" :rules="rules" label-placement="left" :label-width="80">
<NFormItem :label="$t('page.manage.menu.menuType')" path="menuType"> <NFormItem :label="$t('page.manage.menu.menuType')" path="menuType">
<NRadioGroup v-model:value="model.menuType"> <NRadioGroup v-model:value="model.menuType" :disabled="disabledMenuType">
<NRadio v-for="item in menuTypeOptions" :key="item.value" :value="item.value" :label="$t(item.label)" /> <NRadio v-for="item in menuTypeOptions" :key="item.value" :value="item.value" :label="$t(item.label)" />
</NRadioGroup> </NRadioGroup>
</NFormItem> </NFormItem>
@ -150,6 +215,16 @@ watch(visible, () => {
<NFormItem :label="$t('page.manage.menu.routePath')" path="routePath"> <NFormItem :label="$t('page.manage.menu.routePath')" path="routePath">
<NInput v-model:value="model.routePath" :placeholder="$t('page.manage.menu.form.routePath')" /> <NInput v-model:value="model.routePath" :placeholder="$t('page.manage.menu.form.routePath')" />
</NFormItem> </NFormItem>
<NFormItem v-if="showLayout" :label="$t('page.manage.menu.layout')" path="layout">
<NSelect
v-model:value="model.layout"
:options="layoutOptions"
:placeholder="$t('page.manage.menu.form.layout')"
/>
</NFormItem>
<NFormItem v-if="showPage" :label="$t('page.manage.menu.page')" path="page">
<NSelect v-model:value="model.page" :options="pageOptions" :placeholder="$t('page.manage.menu.form.page')" />
</NFormItem>
<NFormItem :label="$t('page.manage.menu.menuStatus')" path="status"> <NFormItem :label="$t('page.manage.menu.menuStatus')" path="status">
<NRadioGroup v-model:value="model.status"> <NRadioGroup v-model:value="model.status">
<NRadio v-for="item in enableStatusOptions" :key="item.value" :value="item.value" :label="$t(item.label)" /> <NRadio v-for="item in enableStatusOptions" :key="item.value" :value="item.value" :label="$t(item.label)" />

View File

@ -0,0 +1,24 @@
const LAYOUT_PREFIX = 'layout.';
const VIEW_PREFIX = 'view.';
export function getLayoutAndPage(component?: string | null) {
const FIRST_LEVEL_ROUTE_COMPONENT_SPLIT = '$';
let layout = '';
let page = '';
const [layoutOrPage, pageItem] = component?.split(FIRST_LEVEL_ROUTE_COMPONENT_SPLIT) || [];
layout = getLayout(layoutOrPage);
page = getPage(pageItem || layoutOrPage);
return { layout, page };
}
function getLayout(layout: string) {
return layout.startsWith(LAYOUT_PREFIX) ? layout.replace(LAYOUT_PREFIX, '') : '';
}
function getPage(page: string) {
return page.startsWith(VIEW_PREFIX) ? page.replace(VIEW_PREFIX, '') : '';
}