feat(projects): page: support manage_menu more options. close #366

This commit is contained in:
Soybean 2024-05-07 01:36:22 +08:00
parent ebe55af7d5
commit c4b5c65625
9 changed files with 286 additions and 205 deletions

View File

@ -57,7 +57,7 @@ function updateExpandedKeys() {
} }
function handleClickMenu(key: RouteKey) { function handleClickMenu(key: RouteKey) {
const { query } = routeStore.getSelectedMenuMetaByKey(key) || {}; const query = routeStore.getRouteQueryOfMetaByKey(key);
routerPushByKey(key, { query }); routerPushByKey(key, { query });
} }

View File

@ -362,7 +362,7 @@ const local: App.I18n.Schema = {
menuName: 'Menu Name', menuName: 'Menu Name',
routeName: 'Route Name', routeName: 'Route Name',
routePath: 'Route Path', routePath: 'Route Path',
routeParams: 'Route Params', pathParam: 'Path Param',
layout: 'Layout Component', layout: 'Layout Component',
page: 'Page Component', page: 'Page Component',
i18nKey: 'I18n Key', i18nKey: 'I18n Key',
@ -377,7 +377,6 @@ const local: App.I18n.Schema = {
activeMenu: 'Active Menu', activeMenu: 'Active Menu',
multiTab: 'Multi Tab', multiTab: 'Multi Tab',
fixedIndexInTab: 'Fixed Index In Tab', fixedIndexInTab: 'Fixed Index In Tab',
roles: 'Roles',
query: 'Query Params', query: 'Query Params',
button: 'Button', button: 'Button',
buttonCode: 'Button Code', buttonCode: 'Button Code',
@ -389,6 +388,7 @@ const local: App.I18n.Schema = {
menuName: 'Please enter menu name', menuName: 'Please enter menu name',
routeName: 'Please enter route name', routeName: 'Please enter route name',
routePath: 'Please enter route path', routePath: 'Please enter route path',
pathParam: 'Please enter path param',
page: 'Please select page component', page: 'Please select page component',
layout: 'Please select layout component', layout: 'Please select layout component',
i18nKey: 'Please enter i18n key', i18nKey: 'Please enter i18n key',
@ -402,7 +402,6 @@ const local: App.I18n.Schema = {
multiTab: 'Please select whether to support multiple tabs', multiTab: 'Please select whether to support multiple tabs',
fixedInTab: 'Please select whether to fix in the tab', fixedInTab: 'Please select whether to fix in the tab',
fixedIndexInTab: 'Please enter the index fixed in the tab', fixedIndexInTab: 'Please enter the index fixed in the tab',
roles: 'Please select roles',
queryKey: 'Please enter route parameter Key', queryKey: 'Please enter route parameter Key',
queryValue: 'Please enter route parameter Value', queryValue: 'Please enter route parameter Value',
button: 'Please select whether it is a button', button: 'Please select whether it is a button',

View File

@ -362,7 +362,7 @@ const local: App.I18n.Schema = {
menuName: '菜单名称', menuName: '菜单名称',
routeName: '路由名称', routeName: '路由名称',
routePath: '路由路径', routePath: '路由路径',
routeParams: '路由参数', pathParam: '路径参数',
layout: '布局', layout: '布局',
page: '页面组件', page: '页面组件',
i18nKey: '国际化key', i18nKey: '国际化key',
@ -377,7 +377,6 @@ const local: App.I18n.Schema = {
activeMenu: '高亮的菜单', activeMenu: '高亮的菜单',
multiTab: '支持多页签', multiTab: '支持多页签',
fixedIndexInTab: '固定在页签中的序号', fixedIndexInTab: '固定在页签中的序号',
roles: '角色',
query: '路由参数', query: '路由参数',
button: '按钮', button: '按钮',
buttonCode: '按钮编码', buttonCode: '按钮编码',
@ -389,6 +388,7 @@ const local: App.I18n.Schema = {
menuName: '请输入菜单名称', menuName: '请输入菜单名称',
routeName: '请输入路由名称', routeName: '请输入路由名称',
routePath: '请输入路由路径', routePath: '请输入路由路径',
pathParam: '请输入路径参数',
page: '请选择页面组件', page: '请选择页面组件',
layout: '请选择布局组件', layout: '请选择布局组件',
i18nKey: '请输入国际化key', i18nKey: '请输入国际化key',
@ -402,7 +402,6 @@ const local: App.I18n.Schema = {
multiTab: '请选择是否支持多标签', multiTab: '请选择是否支持多标签',
fixedInTab: '请选择是否固定在页签中', fixedInTab: '请选择是否固定在页签中',
fixedIndexInTab: '请输入固定在页签中的序号', fixedIndexInTab: '请输入固定在页签中的序号',
roles: '请选择角色',
queryKey: '请输入路由参数Key', queryKey: '请输入路由参数Key',
queryValue: '请输入路由参数Value', queryValue: '请输入路由参数Value',
button: '请选择是否按钮', button: '请选择是否按钮',

View File

@ -332,15 +332,31 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
} }
/** /**
* Get selected menu meta by key * Get route meta by key
* *
* @param selectedKey Selected menu key * @param key Route key
*/ */
function getSelectedMenuMetaByKey(selectedKey: string) { function getRouteMetaByKey(key: string) {
// The routes in router.options.routes are static, you need to use router.getRoutes() to get all the routes.
const allRoutes = router.getRoutes(); const allRoutes = router.getRoutes();
return allRoutes.find(route => route.name === selectedKey)?.meta || null; return allRoutes.find(route => route.name === key)?.meta || null;
}
/**
* Get route query of meta by key
*
* @param key
*/
function getRouteQueryOfMetaByKey(key: string) {
const meta = getRouteMetaByKey(key);
const query: Record<string, string> = {};
meta?.query?.forEach(item => {
query[item.key] = item.value;
});
return query;
} }
return { return {
@ -360,6 +376,6 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
setIsInitAuthRoute, setIsInitAuthRoute,
getIsAuthRouteExist, getIsAuthRouteExist,
getSelectedMenuKeyPath, getSelectedMenuKeyPath,
getSelectedMenuMetaByKey getRouteQueryOfMetaByKey
}; };
}); });

64
src/typings/api.d.ts vendored
View File

@ -171,6 +171,20 @@ declare namespace Api {
*/ */
type IconType = '1' | '2'; type IconType = '1' | '2';
type MenuPropsOfRoute = Pick<
import('vue-router').RouteMeta,
| 'i18nKey'
| 'keepAlive'
| 'constant'
| 'order'
| 'href'
| 'hideInMenu'
| 'activeMenu'
| 'multiTab'
| 'fixedIndexInTab'
| 'query'
>;
type Menu = Common.CommonRecord<{ type Menu = Common.CommonRecord<{
/** parent menu id */ /** parent menu id */
parentId: number; parentId: number;
@ -184,56 +198,16 @@ declare namespace Api {
routePath: string; routePath: string;
/** component */ /** component */
component?: string; component?: string;
/**
* i18n key
*
* it is for internationalization
*/
i18nKey?: App.I18n.I18nKey;
/** iconify icon name or local icon name */ /** iconify icon name or local icon name */
icon: string; icon: string;
/** icon type */ /** icon type */
iconType: IconType; iconType: IconType;
/** menu order */ /** buttons */
order: number; buttons?: MenuButton[] | null;
/** whether to cache the route */
keepAlive?: boolean;
/**
* Is constant route
*
* Does not need to login, and the route is defined in the front-end
*/
constant?: boolean;
/** outer link */
href?: string;
/** whether to hide the route in the menu */
hideInMenu?: boolean;
/**
* The menu key will be activated when entering the route
*
* The route is not in the menu
*
* @example
* the route is "user_detail", if it is set to "user_list", the menu "user_list" will be activated
*/
activeMenu?: import('@elegant-router/types').LastLevelRouteKey;
/** By default, the same route path will use one tab, if set to true, it will use multiple tabs */
multiTab?: boolean;
/** If set, the route will be fixed in tabs, and the value is the order of fixed tabs */
fixedIndexInTab?: number | null;
/** if set query parameters, it will be automatically carried when entering the route */
query: Record<string, string>;
/** menu buttons */
buttons?: MenuButton[];
/**
* Roles of the route
*
* Route can be accessed if the current user has at least one of the roles
*/
roles?: string[];
/** children menu */ /** children menu */
children?: Menu[]; children?: Menu[] | null;
}>; }> &
MenuPropsOfRoute;
/** menu list */ /** menu list */
type MenuList = Common.PaginatingQueryRecord<Menu>; type MenuList = Common.PaginatingQueryRecord<Menu>;

View File

@ -158,7 +158,7 @@ declare namespace App {
/** The menu label */ /** The menu label */
label: string; label: string;
/** The menu i18n key */ /** The menu i18n key */
i18nKey?: I18n.I18nKey; i18nKey?: I18n.I18nKey | null;
/** The route key */ /** The route key */
routeKey: RouteKey; routeKey: RouteKey;
/** The route path */ /** The route path */
@ -216,7 +216,7 @@ declare namespace App {
*/ */
localIcon?: string; localIcon?: string;
/** I18n key */ /** I18n key */
i18nKey?: I18n.I18nKey; i18nKey?: I18n.I18nKey | null;
}; };
/** Form rule */ /** Form rule */
@ -537,7 +537,7 @@ declare namespace App {
menuName: string; menuName: string;
routeName: string; routeName: string;
routePath: string; routePath: string;
routeParams: string; pathParam: string;
layout: string; layout: string;
page: string; page: string;
i18nKey: string; i18nKey: string;
@ -552,7 +552,6 @@ declare namespace App {
activeMenu: string; activeMenu: string;
multiTab: string; multiTab: string;
fixedIndexInTab: string; fixedIndexInTab: string;
roles: string;
query: string; query: string;
button: string; button: string;
buttonCode: string; buttonCode: string;
@ -564,6 +563,7 @@ declare namespace App {
menuName: string; menuName: string;
routeName: string; routeName: string;
routePath: string; routePath: string;
pathParam: string;
layout: string; layout: string;
page: string; page: string;
i18nKey: string; i18nKey: string;
@ -577,7 +577,6 @@ declare namespace App {
multiTab: string; multiTab: string;
fixedInTab: string; fixedInTab: string;
fixedIndexInTab: string; fixedIndexInTab: string;
roles: string;
queryKey: string; queryKey: string;
queryValue: string; queryValue: string;
button: string; button: string;

View File

@ -13,21 +13,23 @@ declare module 'vue-router' {
* *
* It's used in i18n, if it is set, the title will be ignored * It's used in i18n, if it is set, the title will be ignored
*/ */
i18nKey?: App.I18n.I18nKey; i18nKey?: App.I18n.I18nKey | null;
/** /**
* Roles of the route * Roles of the route
* *
* Route can be accessed if the current user has at least one of the roles * Route can be accessed if the current user has at least one of the roles
*
* It only works when the route mode is "static", if the route mode is "dynamic", it will be ignored
*/ */
roles?: string[]; roles?: string[];
/** Whether to cache the route */ /** Whether to cache the route */
keepAlive?: boolean; keepAlive?: boolean | null;
/** /**
* Is constant route * Is constant route
* *
* Does not need to login, and the route is defined in the front-end * Does not need to login, and the route is defined in the front-end
*/ */
constant?: boolean; constant?: boolean | null;
/** /**
* Iconify icon * Iconify icon
* *
@ -41,11 +43,11 @@ declare module 'vue-router' {
*/ */
localIcon?: string; localIcon?: string;
/** Router order */ /** Router order */
order?: number; order?: number | null;
/** The outer link of the route */ /** The outer link of the route */
href?: string; href?: string | null;
/** Whether to hide the route in the menu */ /** Whether to hide the route in the menu */
hideInMenu?: boolean; hideInMenu?: boolean | null;
/** /**
* The menu key will be activated when entering the route * The menu key will be activated when entering the route
* *
@ -54,12 +56,12 @@ declare module 'vue-router' {
* @example * @example
* the route is "user_detail", if it is set to "user_list", the menu "user_list" will be activated * the route is "user_detail", if it is set to "user_list", the menu "user_list" will be activated
*/ */
activeMenu?: import('@elegant-router/types').RouteKey; activeMenu?: import('@elegant-router/types').RouteKey | null;
/** By default, the same route path will use one tab, if set to true, it will use multiple tabs */ /** By default, the same route path will use one tab, if set to true, it will use multiple tabs */
multiTab?: boolean; multiTab?: boolean | null;
/** If set, the route will be fixed in tabs, and the value is the order of fixed tabs */ /** If set, the route will be fixed in tabs, and the value is the order of fixed tabs */
fixedIndexInTab?: number | null; fixedIndexInTab?: number | null;
/** if set query parameters, it will be automatically carried when entering the route */ /** if set query parameters, it will be automatically carried when entering the route */
query?: Record<string, string>; query?: { key: string; value: string }[] | null;
} }
} }

View File

@ -1,8 +1,6 @@
<script setup lang="tsx"> <script setup lang="tsx">
import type { Ref } from 'vue';
import { computed, reactive, ref, watch } from 'vue'; import { computed, reactive, ref, watch } from 'vue';
import type { SelectOption } from 'naive-ui'; import type { SelectOption } from 'naive-ui';
import type { LastLevelRouteKey } from '@elegant-router/types';
import { useFormRules, useNaiveForm } from '@/hooks/common/form'; import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { $t } from '@/locales'; import { $t } from '@/locales';
import { enableStatusOptions, menuIconTypeOptions, menuTypeOptions } from '@/constants/business'; import { enableStatusOptions, menuIconTypeOptions, menuTypeOptions } from '@/constants/business';
@ -11,9 +9,10 @@ import { getLocalIcons } from '@/utils/icon';
import { fetchGetAllRoles } from '@/service/api'; import { fetchGetAllRoles } from '@/service/api';
import { import {
getLayoutAndPage, getLayoutAndPage,
transformLayoutAndPageToComponent, getPathParamFromRoutePath,
transformToKeyValuePairs, getRoutePathByRouteName,
transformToQueryObject getRoutePathWithParam,
transformLayoutAndPageToComponent
} from './shared'; } from './shared';
defineOptions({ defineOptions({
@ -59,28 +58,28 @@ type Model = Pick<
Api.SystemManage.Menu, Api.SystemManage.Menu,
| 'menuType' | 'menuType'
| 'menuName' | 'menuName'
| 'i18nKey'
| 'icon'
| 'iconType'
| 'routeName' | 'routeName'
| 'routePath' | 'routePath'
| 'component' | 'component'
| 'order'
| 'i18nKey'
| 'icon'
| 'iconType'
| 'status' | 'status'
| 'parentId'
| 'keepAlive'
| 'constant'
| 'href'
| 'hideInMenu' | 'hideInMenu'
| 'activeMenu' | 'activeMenu'
| 'order'
| 'parentId'
| 'constant'
| 'keepAlive'
| 'href'
| 'multiTab' | 'multiTab'
| 'fixedIndexInTab' | 'fixedIndexInTab'
| 'roles'
| 'buttons'
| 'query'
> & { > & {
query: NonNullable<Api.SystemManage.Menu['query']>;
buttons: NonNullable<Api.SystemManage.Menu['buttons']>;
layout: string; layout: string;
page: string; page: string;
pathParam: string;
}; };
const model: Model = reactive(createDefaultModel()); const model: Model = reactive(createDefaultModel());
@ -89,26 +88,27 @@ function createDefaultModel(): Model {
return { return {
menuType: '1', menuType: '1',
menuName: '', menuName: '',
i18nKey: '' as App.I18n.I18nKey,
icon: '',
iconType: '1',
routeName: '', routeName: '',
routePath: '', routePath: '',
pathParam: '',
component: '',
layout: '', layout: '',
page: '', page: '',
status: '1', i18nKey: null,
hideInMenu: false, icon: '',
activeMenu: '' as LastLevelRouteKey, iconType: '1',
order: 0,
parentId: 0, parentId: 0,
constant: false, status: '1',
keepAlive: false, keepAlive: false,
href: '', constant: false,
order: 0,
href: null,
hideInMenu: false,
activeMenu: null,
multiTab: false, multiTab: false,
fixedIndexInTab: null, fixedIndexInTab: null,
roles: [], query: [],
buttons: [], buttons: []
query: {}
}; };
} }
@ -164,13 +164,6 @@ const layoutOptions: CommonType.Option[] = [
} }
]; ];
const dynamicQueryKeyValuePairs: Ref<Record<string, string>[]> = ref([
{
key: '',
value: ''
}
]);
/** the enabled role options */ /** the enabled role options */
const roleOptions = ref<CommonType.Option<string>[]>([]); const roleOptions = ref<CommonType.Option<string>[]>([]);
@ -199,12 +192,19 @@ function handleInitModel() {
} }
if (props.operateType === 'edit') { if (props.operateType === 'edit') {
const { component, query, ...rest } = props.rowData; const { component, ...rest } = props.rowData;
const { layout, page } = getLayoutAndPage(component); const { layout, page } = getLayoutAndPage(component);
const { path, param } = getPathParamFromRoutePath(rest.routePath);
Object.assign(model, rest, { layout, page }); Object.assign(model, rest, { layout, page, routePath: path, pathParam: param });
dynamicQueryKeyValuePairs.value = transformToKeyValuePairs(query); }
if (!model.query) {
model.query = [];
}
if (!model.buttons) {
model.buttons = [];
} }
} }
@ -212,16 +212,49 @@ function closeDrawer() {
visible.value = false; visible.value = false;
} }
function handleUpdateRoutePathByRouteName() {
if (model.routeName) {
model.routePath = getRoutePathByRouteName(model.routeName);
} else {
model.routePath = '';
}
}
function handleUpdateI18nKeyByRouteName() {
if (model.routeName) {
model.i18nKey = `route.${model.routeName}` as App.I18n.I18nKey;
} else {
model.i18nKey = null;
}
}
function handleCreateButton() {
const buttonItem: Api.SystemManage.MenuButton = {
code: '',
desc: ''
};
return buttonItem;
}
function getSubmitParams() {
const { layout, page, pathParam, ...params } = model;
const component = transformLayoutAndPageToComponent(layout, page);
const routePath = getRoutePathWithParam(model.routePath, pathParam);
params.component = component;
params.routePath = routePath;
return params;
}
async function handleSubmit() { async function handleSubmit() {
await validate(); await validate();
model.component = transformLayoutAndPageToComponent(model.layout, model.page); const params = getSubmitParams();
model.query = transformToQueryObject(dynamicQueryKeyValuePairs.value);
// model.buttons = []; console.log('params: ', params);
// Need: Get buttons based on roles
console.log('model:', model);
// request // request
window.$message?.success($t('common.updateSuccess')); window.$message?.success($t('common.updateSuccess'));
@ -236,68 +269,59 @@ watch(visible, () => {
getRoleOptions(); getRoleOptions();
} }
}); });
watch(
() => model.routeName,
() => {
handleUpdateRoutePathByRouteName();
handleUpdateI18nKeyByRouteName();
}
);
</script> </script>
<template> <template>
<NModal v-model:show="visible" :title="title" preset="card" class="w-720px"> <NModal v-model:show="visible" :title="title" preset="card" class="w-800px">
<NScrollbar class="h-400px"> <NScrollbar class="h-480px">
<NForm ref="formRef" :model="model" :rules="rules" label-placement="left" :label-width="100"> <NForm ref="formRef" :model="model" :rules="rules" label-placement="left" :label-width="100">
<NGrid> <NGrid responsive="screen" item-responsive>
<NFormItemGi span="12" :label="$t('page.manage.menu.menuType')" path="menuType"> <NFormItemGi span="24 m:12" :label="$t('page.manage.menu.menuType')" path="menuType">
<NRadioGroup v-model:value="model.menuType" :disabled="disabledMenuType"> <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>
</NFormItemGi> </NFormItemGi>
<NFormItemGi span="12" :label="$t('page.manage.menu.order')" path="order"> <NFormItemGi span="24 m:12" :label="$t('page.manage.menu.menuName')" path="menuName">
<NInputNumber v-model:value="model.order" :placeholder="$t('page.manage.menu.form.order')" />
</NFormItemGi>
<NFormItemGi span="24" :label="$t('page.manage.menu.menuName')" path="menuName">
<NInput v-model:value="model.menuName" :placeholder="$t('page.manage.menu.form.menuName')" /> <NInput v-model:value="model.menuName" :placeholder="$t('page.manage.menu.form.menuName')" />
</NFormItemGi> </NFormItemGi>
<NFormItemGi span="24" :label="$t('page.manage.menu.i18nKey')" path="i18nKey"> <NFormItemGi span="24 m:12" :label="$t('page.manage.menu.routeName')" path="routeName">
<NInput v-model:value="model.i18nKey" :placeholder="$t('page.manage.menu.form.i18nKey')" /> <NInput v-model:value="model.routeName" :placeholder="$t('page.manage.menu.form.routeName')" />
</NFormItemGi> </NFormItemGi>
<NFormItemGi span="12" :label="$t('page.manage.menu.menuStatus')" path="status"> <NFormItemGi span="24 m:12" :label="$t('page.manage.menu.routePath')" path="routePath">
<NRadioGroup v-model:value="model.status"> <NInput v-model:value="model.routePath" disabled :placeholder="$t('page.manage.menu.form.routePath')" />
<NRadio
v-for="item in enableStatusOptions"
:key="item.value"
:value="item.value"
:label="$t(item.label)"
/>
</NRadioGroup>
</NFormItemGi> </NFormItemGi>
<NFormItemGi span="12" :label="$t('page.manage.menu.hideInMenu')" path="hideInMenu"> <NFormItemGi span="24 m:12" :label="$t('page.manage.menu.pathParam')" path="pathParam">
<NRadioGroup v-model:value="model.hideInMenu"> <NInput v-model:value="model.pathParam" :placeholder="$t('page.manage.menu.form.pathParam')" />
<NRadio value :label="$t('common.yesOrNo.yes')" />
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
</NRadioGroup>
</NFormItemGi> </NFormItemGi>
<NFormItemGi span="12" :label="$t('page.manage.menu.keepAlive')" path="keepAlive"> <NFormItemGi v-if="showLayout" span="24 m:12" :label="$t('page.manage.menu.layout')" path="layout">
<NRadioGroup v-model:value="model.keepAlive"> <NSelect
<NRadio value :label="$t('common.yesOrNo.yes')" /> v-model:value="model.layout"
<NRadio :value="false" :label="$t('common.yesOrNo.no')" /> :options="layoutOptions"
</NRadioGroup> :placeholder="$t('page.manage.menu.form.layout')"
</NFormItemGi>
<NFormItemGi span="12" :label="$t('page.manage.menu.constant')" path="constant">
<NRadioGroup v-model:value="model.constant">
<NRadio value :label="$t('common.yesOrNo.yes')" />
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
</NRadioGroup>
</NFormItemGi>
<NFormItemGi span="12" :label="$t('page.manage.menu.multiTab')" path="multiTab">
<NRadioGroup v-model:value="model.multiTab">
<NRadio value :label="$t('common.yesOrNo.yes')" />
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
</NRadioGroup>
</NFormItemGi>
<NFormItemGi span="12" :label="$t('page.manage.menu.fixedIndexInTab')" path="fixedIndexInTab">
<NInputNumber
v-model:value="model.fixedIndexInTab"
:placeholder="$t('page.manage.menu.form.fixedIndexInTab')"
/> />
</NFormItemGi> </NFormItemGi>
<NFormItemGi span="24" :label="$t('page.manage.menu.iconTypeTitle')" path="iconType"> <NFormItemGi v-if="showPage" span="24 m:12" :label="$t('page.manage.menu.page')" path="page">
<NSelect
v-model:value="model.page"
:options="pageOptions"
:placeholder="$t('page.manage.menu.form.page')"
/>
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.i18nKey')" path="i18nKey">
<NInput v-model:value="model.i18nKey" :placeholder="$t('page.manage.menu.form.i18nKey')" />
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.order')" path="order">
<NInputNumber v-model:value="model.order" class="w-full" :placeholder="$t('page.manage.menu.form.order')" />
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.iconTypeTitle')" path="iconType">
<NRadioGroup v-model:value="model.iconType"> <NRadioGroup v-model:value="model.iconType">
<NRadio <NRadio
v-for="item in menuIconTypeOptions" v-for="item in menuIconTypeOptions"
@ -307,7 +331,7 @@ watch(visible, () => {
/> />
</NRadioGroup> </NRadioGroup>
</NFormItemGi> </NFormItemGi>
<NFormItemGi span="24" :label="$t('page.manage.menu.icon')" path="icon"> <NFormItemGi span="24 m:12" :label="$t('page.manage.menu.icon')" path="icon">
<template v-if="model.iconType === '1'"> <template v-if="model.iconType === '1'">
<NInput v-model:value="model.icon" :placeholder="$t('page.manage.menu.form.icon')" class="flex-1"> <NInput v-model:value="model.icon" :placeholder="$t('page.manage.menu.form.icon')" class="flex-1">
<template #suffix> <template #suffix>
@ -323,53 +347,102 @@ watch(visible, () => {
/> />
</template> </template>
</NFormItemGi> </NFormItemGi>
<NFormItemGi span="24" :label="$t('page.manage.menu.routeName')" path="routeName"> <NFormItemGi span="24 m:12" :label="$t('page.manage.menu.menuStatus')" path="status">
<NInput v-model:value="model.routeName" :placeholder="$t('page.manage.menu.form.routeName')" /> <NRadioGroup v-model:value="model.status">
<NRadio
v-for="item in enableStatusOptions"
:key="item.value"
:value="item.value"
:label="$t(item.label)"
/>
</NRadioGroup>
</NFormItemGi> </NFormItemGi>
<NFormItemGi span="24" :label="$t('page.manage.menu.routePath')" path="routePath"> <NFormItemGi span="24 m:12" :label="$t('page.manage.menu.keepAlive')" path="keepAlive">
<NInput v-model:value="model.routePath" :placeholder="$t('page.manage.menu.form.routePath')" /> <NRadioGroup v-model:value="model.keepAlive">
<NRadio value :label="$t('common.yesOrNo.yes')" />
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
</NRadioGroup>
</NFormItemGi> </NFormItemGi>
<NFormItemGi v-if="showLayout" span="24" :label="$t('page.manage.menu.layout')" path="layout"> <NFormItemGi span="24 m:12" :label="$t('page.manage.menu.constant')" path="constant">
<NSelect <NRadioGroup v-model:value="model.constant">
v-model:value="model.layout" <NRadio value :label="$t('common.yesOrNo.yes')" />
:options="layoutOptions" <NRadio :value="false" :label="$t('common.yesOrNo.no')" />
:placeholder="$t('page.manage.menu.form.layout')" </NRadioGroup>
/>
</NFormItemGi> </NFormItemGi>
<NFormItemGi v-if="showPage" span="24" :label="$t('page.manage.menu.page')" path="page"> <NFormItemGi span="24 m:12" :label="$t('page.manage.menu.href')" path="href">
<NSelect <NInput v-model:value="model.href" :placeholder="$t('page.manage.menu.form.href')" />
v-model:value="model.page"
:options="pageOptions"
:placeholder="$t('page.manage.menu.form.page')"
/>
</NFormItemGi> </NFormItemGi>
<NFormItemGi v-if="showPage" span="24" :label="$t('page.manage.menu.activeMenu')" path="activeMenu"> <NFormItemGi span="24 m:12" :label="$t('page.manage.menu.hideInMenu')" path="hideInMenu">
<NRadioGroup v-model:value="model.hideInMenu">
<!-- eslint-disable-next-line vue/prefer-true-attribute-shorthand -->
<NRadio :value="true" :label="$t('common.yesOrNo.yes')" />
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
</NRadioGroup>
</NFormItemGi>
<NFormItemGi
v-if="model.hideInMenu"
span="24 m:12"
:label="$t('page.manage.menu.activeMenu')"
path="activeMenu"
>
<NSelect <NSelect
v-model:value="model.activeMenu" v-model:value="model.activeMenu"
:options="pageOptions" :options="pageOptions"
clearable
:placeholder="$t('page.manage.menu.form.activeMenu')" :placeholder="$t('page.manage.menu.form.activeMenu')"
/> />
</NFormItemGi> </NFormItemGi>
<NFormItemGi span="24" :label="$t('page.manage.menu.href')" path="href"> <NFormItemGi span="24 m:12" :label="$t('page.manage.menu.multiTab')" path="multiTab">
<NInput v-model:value="model.href" :placeholder="$t('page.manage.menu.form.href')" /> <NRadioGroup v-model:value="model.multiTab">
<NRadio value :label="$t('common.yesOrNo.yes')" />
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
</NRadioGroup>
</NFormItemGi> </NFormItemGi>
<NFormItemGi span="24" :label="$t('page.manage.menu.roles')" path="roles"> <NFormItemGi span="24 m:12" :label="$t('page.manage.menu.fixedIndexInTab')" path="fixedIndexInTab">
<NSelect <NInputNumber
v-model:value="model.roles" v-model:value="model.fixedIndexInTab"
multiple class="w-full"
:options="roleOptions" clearable
:placeholder="$t('page.manage.menu.form.roles')" :placeholder="$t('page.manage.menu.form.fixedIndexInTab')"
/> />
</NFormItemGi> </NFormItemGi>
<NFormItemGi span="24" :label="$t('page.manage.menu.query')"> <NFormItemGi span="24" :label="$t('page.manage.menu.query')">
<NDynamicInput <NDynamicInput
v-model:value="dynamicQueryKeyValuePairs" v-model:value="model.query"
preset="pair" preset="pair"
:key-placeholder="$t('page.manage.menu.form.queryKey')" :key-placeholder="$t('page.manage.menu.form.queryKey')"
:value-placeholder="$t('page.manage.menu.form.queryValue')" :value-placeholder="$t('page.manage.menu.form.queryValue')"
> >
<template #action="{ index, create, remove }"> <template #action="{ index, create, remove }">
<NSpace class="ml-2"> <NSpace class="ml-12px">
<NButton size="medium" @click="() => create(index)">
<icon-ic:round-plus class="text-icon" />
</NButton>
<NButton size="medium" @click="() => remove(index)">
<icon-ic-round-remove class="text-icon" />
</NButton>
</NSpace>
</template>
</NDynamicInput>
</NFormItemGi>
<NFormItemGi span="24" :label="$t('page.manage.menu.button')">
<NDynamicInput v-model:value="model.buttons" :on-create="handleCreateButton">
<template #default="{ value }">
<div class="ml-8px flex-y-center flex-1 gap-12px">
<NInput
v-model:value="value.code"
:placeholder="$t('page.manage.menu.form.buttonCode')"
class="flex-1"
/>
<NInput
v-model:value="value.desc"
:placeholder="$t('page.manage.menu.form.buttonDesc')"
class="flex-1"
/>
</div>
</template>
<template #action="{ index, create, remove }">
<NSpace class="ml-12px">
<NButton size="medium" @click="() => create(index)"> <NButton size="medium" @click="() => create(index)">
<icon-ic:round-plus class="text-icon" /> <icon-ic:round-plus class="text-icon" />
</NButton> </NButton>

View File

@ -41,20 +41,39 @@ export function transformLayoutAndPageToComponent(layout: string, page: string)
return ''; return '';
} }
export function transformToQueryObject(data: Record<string, string>[]) { /**
const query: Record<string, string> = {}; * Get route name by route path
data.forEach(pair => { *
if (pair.key && pair.value) { * @param routeName
query[pair.key] = pair.value; */
} export function getRoutePathByRouteName(routeName: string) {
}); return `/${routeName.replace(/_/g, '/')}`;
return query;
} }
export function transformToKeyValuePairs(query?: Record<string, string>) { /**
const safeQuery = query || {}; * Get path param from route path
return Object.entries(safeQuery).map(([key, value]) => ({ *
key, * @param routePath route path
value */
})); export function getPathParamFromRoutePath(routePath: string) {
const [path, param = ''] = routePath.split('/:');
return {
path,
param
};
}
/**
* Get route path with param
*
* @param routePath route path
* @param param path param
*/
export function getRoutePathWithParam(routePath: string, param: string) {
if (param.trim()) {
return `${routePath}/:${param}`;
}
return routePath;
} }