!3 perf: menu add i18n

Merge pull request !3 from small monkey/menu-i18n
This commit is contained in:
马铃薯头 2025-05-20 01:51:49 +00:00 committed by Gitee
commit c53f320fef
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
5 changed files with 201 additions and 80 deletions

View File

@ -551,19 +551,40 @@ const local: App.I18n.Schema = {
}, },
menu: { menu: {
title: 'Menu List', title: 'Menu List',
parentId: 'Parent Menu',
iconType: 'Icon Type',
menuName: 'Menu Name', menuName: 'Menu Name',
icon: 'Menu Icon', icon: 'Menu Icon',
sort: 'Sort', orderNum: 'Sort',
permission: 'Permission', perms: 'Permission Code',
component: 'Component Path', component: 'Component Path',
path: 'Route Path', path: 'Route Path',
externalPath: 'External Path',
query: 'Route Parameters', query: 'Route Parameters',
iframeQuery: 'Iframe Address',
isFrame: 'External Link', isFrame: 'External Link',
isCache: 'Cache', isCache: 'Cache',
menuType: 'Menu Type', menuType: 'Menu Type',
visible: 'Visible', visible: 'Visible',
status: 'Status', status: 'Status',
createTime: 'Create Time', createTime: 'Create Time',
cache: 'cache',
noCache: 'No Cache',
rootName: 'Root',
buttonPermissionList: 'Button Permission List',
emptyMenu: 'Empty Menu',
menuDetail: 'Menu Detail',
iconifyTip: 'iconify address`https://icones.js.org`',
isFrameTip: 'If you choose External Link, the routing address needs to start with `http(s)://`',
isCacheTip:
'If you select yes, it will be cached by `keep-alive`, and the `name` and address of the matching component must be consistent',
visibleTip: 'If you choose Hide, the route will not appear in the sidebar, but it can still be accessed.',
statusTip: 'If you choose to disable, the route will not appear in the sidebar and cannot be accessed.',
permsTip: "Permission string defined in the controller, such as: @SaCheckPermission('system:user:list')",
componentTip:
'The component path to access, such as: `system/user/index`, which is in the `views` directory by default',
pathTip:
'Router pathExample`user`If the external network address needs to be accessed in the internal link,then `http(s)://` beginning',
form: { form: {
parentId: { parentId: {
required: 'Please select Parent Menu', required: 'Please select Parent Menu',
@ -581,9 +602,13 @@ const local: App.I18n.Schema = {
required: 'Please enter Menu Name', required: 'Please enter Menu Name',
invalid: 'Menu Name cannot be empty' invalid: 'Menu Name cannot be empty'
}, },
sort: { perms: {
required: 'Please enter Sort', required: 'Please enter permission code',
invalid: 'Sort cannot be empty' invalid: 'Permission code cannot be empty'
},
orderNum: {
required: 'Please enter order num',
invalid: 'Order num cannot be empty'
}, },
isFrame: { isFrame: {
required: 'Please select External Link', required: 'Please select External Link',
@ -618,10 +643,18 @@ const local: App.I18n.Schema = {
invalid: 'Permission cannot be empty' invalid: 'Permission cannot be empty'
} }
}, },
placeholder: {
iconifyIconPlaceholder: 'Please enter an icon',
localIconPlaceholder: 'Please select the local icon',
queryKey: 'Please enter a key',
queryValue: 'Please enter a value',
queryIframe: 'Please enter a iframe address'
},
directory: 'Directory', directory: 'Directory',
menu: 'Menu', menu: 'Menu',
button: 'Button', button: 'Button',
addMenu: 'Add Menu', addMenu: 'Add Menu',
addChildMenu: 'Add Child Menu',
editMenu: 'Edit Menu' editMenu: 'Edit Menu'
}, },
notice: { notice: {

View File

@ -551,19 +551,38 @@ const local: App.I18n.Schema = {
}, },
menu: { menu: {
title: '菜单列表', title: '菜单列表',
parentId: '上级菜单',
iconType: '图标类型',
menuName: '菜单名称', menuName: '菜单名称',
icon: '菜单图标', icon: '菜单图标',
sort: '排序', orderNum: '排序',
permission: '权限标识', perms: '权限字符',
component: '组件路径', component: '组件路径',
path: '路由地址', path: '路由地址',
externalPath: '外链地址',
query: '路由参数', query: '路由参数',
iframeQuery: 'iframe 地址',
isFrame: '是否外链', isFrame: '是否外链',
isCache: '是否缓存', isCache: '是否缓存',
menuType: '菜单类型', menuType: '菜单类型',
visible: '显示状态', visible: '显示状态',
status: '菜单状态', status: '菜单状态',
createTime: '创建时间', createTime: '创建时间',
cache: '缓存',
noCache: '不缓存',
rootName: '根目录',
buttonPermissionList: '按钮权限列表',
emptyMenu: '暂无菜单',
menuDetail: '菜单详情',
iconifyTip: 'iconify 地址https://icones.js.org',
isFrameTip: '选择是外链则路由地址需要以`http(s)://`开头',
isCacheTip: '选择是则会被`keep-alive`缓存,需要匹配组件的`name`和地址保持一致',
visibleTip: '选择隐藏则路由将不会出现在侧边栏,但仍然可以访问',
statusTip: '选择停用则路由将不会出现在侧边栏,也不能被访问',
permsTip: "控制器中定义的权限字符,如:`@SaCheckPermission('system:user:list')`",
componentTip: '访问的组件路径,如:`system/user/index`,默认在`views`目录下',
pathTip:
'Router pathExample`user`If the external network address needs to be accessed in the internal link,then `http(s)://` beginning',
form: { form: {
parentId: { parentId: {
required: '请选择上级菜单', required: '请选择上级菜单',
@ -581,10 +600,14 @@ const local: App.I18n.Schema = {
required: '请输入菜单名称', required: '请输入菜单名称',
invalid: '菜单名称不能为空' invalid: '菜单名称不能为空'
}, },
sort: { orderNum: {
required: '请输入排序', required: '请输入排序',
invalid: '排序不能为空' invalid: '排序不能为空'
}, },
perms: {
required: '请输入权限字符',
invalid: '权限字符不能为空'
},
isFrame: { isFrame: {
required: '请选择是否外链', required: '请选择是否外链',
invalid: '是否外链不能为空' invalid: '是否外链不能为空'
@ -618,10 +641,18 @@ const local: App.I18n.Schema = {
invalid: '权限标识不能为空' invalid: '权限标识不能为空'
} }
}, },
placeholder: {
iconifyIconPlaceholder: '请输入图标',
localIconPlaceholder: '请选择本地图标',
queryKey: '请输入 Key',
queryValue: '请输入 Value',
queryIframe: '请输入 iframe 地址'
},
directory: '目录', directory: '目录',
menu: '菜单', menu: '菜单',
button: '按钮', button: '按钮',
addMenu: '新增菜单', addMenu: '新增菜单',
addChildMenu: '新增子菜单',
editMenu: '编辑菜单' editMenu: '编辑菜单'
}, },
notice: { notice: {

33
src/typings/app.d.ts vendored
View File

@ -657,25 +657,44 @@ declare namespace App {
}; };
menu: { menu: {
title: string; title: string;
parentId: string;
iconType: string;
menuName: string; menuName: string;
icon: string; icon: string;
sort: string; orderNum: string;
permission: string; perms: string;
component: string; component: string;
path: string; path: string;
externalPath: string;
query: string; query: string;
iframeQuery: string;
isFrame: string; isFrame: string;
isCache: string; isCache: string;
menuType: string; menuType: string;
visible: string; visible: string;
status: string; status: string;
createTime: string; createTime: string;
cache: string;
noCache: string;
rootName: string;
buttonPermissionList: string;
emptyMenu: string;
menuDetail: string;
iconifyTip: string;
isFrameTip: string;
isCacheTip: string;
visibleTip: string;
statusTip: string;
permsTip: string;
componentTip: string;
pathTip: string;
form: { form: {
parentId: FormMsg; parentId: FormMsg;
menuType: FormMsg; menuType: FormMsg;
icon: FormMsg; icon: FormMsg;
menuName: FormMsg; menuName: FormMsg;
sort: FormMsg; orderNum: FormMsg;
perms: FormMsg;
isFrame: FormMsg; isFrame: FormMsg;
path: FormMsg; path: FormMsg;
component: FormMsg; component: FormMsg;
@ -685,10 +704,18 @@ declare namespace App {
status: FormMsg; status: FormMsg;
permission: FormMsg; permission: FormMsg;
}; };
placeholder: {
iconifyIconPlaceholder: string;
localIconPlaceholder: string;
queryKey: string;
queryValue: string;
queryIframe: string;
};
directory: string; directory: string;
menu: string; menu: string;
button: string; button: string;
addMenu: string; addMenu: string;
addChildMenu: string;
editMenu: string; editMenu: string;
}; };
notice: { notice: {

View File

@ -46,7 +46,7 @@ const getMeunTree = async () => {
treeData.value = [ treeData.value = [
{ {
menuId: 0, menuId: 0,
menuName: '根目录', menuName: $t('page.system.menu.rootName'),
icon: 'material-symbols:home-outline-rounded', icon: 'material-symbols:home-outline-rounded',
children: handleTree(data, { idField: 'menuId', filterFn: item => item.menuType !== 'F' }) children: handleTree(data, { idField: 'menuId', filterFn: item => item.menuType !== 'F' })
} }
@ -112,7 +112,7 @@ function renderSuffix({ option }: { option: TreeOption }) {
text text
class="h-18px" class="h-18px"
icon="ic-round-plus" icon="ic-round-plus"
tooltip-content="新增子菜单" tooltip-content={$t('page.system.menu.addChildMenu')}
onClick={(event: Event) => { onClick={(event: Event) => {
event.stopPropagation(); event.stopPropagation();
handleAddMenu(option.menuId as CommonType.IdType); handleAddMenu(option.menuId as CommonType.IdType);
@ -203,18 +203,18 @@ const btnColumns: DataTableColumns<Api.System.Menu> = [
} }
}, },
{ {
title: '权限名称', title: $t('page.system.menu.menuName'),
key: 'menuName', key: 'menuName',
minWidth: 120 minWidth: 120
}, },
{ {
title: '权限标识', title: $t('page.system.menu.perms'),
key: 'perms', key: 'perms',
align: 'center', align: 'center',
minWidth: 120 minWidth: 120
}, },
{ {
title: '状态', title: $t('page.system.menu.status'),
key: 'status', key: 'status',
minWidth: 80, minWidth: 80,
align: 'center', align: 'center',
@ -223,13 +223,13 @@ const btnColumns: DataTableColumns<Api.System.Menu> = [
} }
}, },
{ {
title: '创建时间', title: $t('page.system.menu.createTime'),
key: 'createTime', key: 'createTime',
align: 'center', align: 'center',
minWidth: 150 minWidth: 150
}, },
{ {
title: '操作', title: $t('common.action'),
key: 'actions', key: 'actions',
width: 80, width: 80,
align: 'center', align: 'center',
@ -286,27 +286,27 @@ const btnColumns: DataTableColumns<Api.System.Menu> = [
<template> <template>
<TableSiderLayout default-expanded> <TableSiderLayout default-expanded>
<template #header>菜单列表</template> <template #header>{{ $t('page.system.menu.title') }}</template>
<template #header-extra> <template #header-extra>
<ButtonIcon <ButtonIcon
v-if="hasAuth('system:menu:add')" v-if="hasAuth('system:menu:add')"
size="small" size="small"
icon="material-symbols:add-rounded" icon="material-symbols:add-rounded"
class="h-28px text-icon" class="h-28px text-icon"
tooltip-content="新增菜单" :tooltip-content="$t('page.system.menu.addMenu')"
@click.stop="handleAddMenu(0)" @click.stop="handleAddMenu(0)"
/> />
<ButtonIcon <ButtonIcon
size="small" size="small"
icon="material-symbols:refresh-rounded" icon="material-symbols:refresh-rounded"
class="h-28px text-icon" class="h-28px text-icon"
tooltip-content="刷新" :tooltip-content="$t('common.refresh')"
@click.stop="reset" @click.stop="reset"
/> />
</template> </template>
<template #sider> <template #sider>
<div class="flex gap-6px"> <div class="flex gap-6px">
<NInput v-model:value="name" size="small" placeholder="请输入菜单名称" /> <NInput v-model:value="name" size="small" :placeholder="$t('page.system.menu.form.menuName.required')" />
</div> </div>
<NSpin :show="loading" class="infinite-scroll"> <NSpin :show="loading" class="infinite-scroll">
<NTree <NTree
@ -330,7 +330,7 @@ const btnColumns: DataTableColumns<Api.System.Menu> = [
@update:selected-keys="(_: Array<string & number>, option: Array<TreeOption | null>) => handleClickTree(option)" @update:selected-keys="(_: Array<string & number>, option: Array<TreeOption | null>) => handleClickTree(option)"
> >
<template #empty> <template #empty>
<NEmpty description="暂无菜单" class="h-full min-h-200px justify-center" /> <NEmpty :description="$t('page.system.menu.emptyMenu')" class="h-full min-h-200px justify-center" />
</template> </template>
</NTree> </NTree>
</NSpin> </NSpin>
@ -338,7 +338,7 @@ const btnColumns: DataTableColumns<Api.System.Menu> = [
<div class="h-full flex-col-stretch gap-16px"> <div class="h-full flex-col-stretch gap-16px">
<template v-if="currentMenu"> <template v-if="currentMenu">
<NCard <NCard
title="菜单详情" :title="$t('page.system.menu.menuDetail')"
:bordered="false" :bordered="false"
size="small" size="small"
class="max-h-50% card-wrapper" class="max-h-50% card-wrapper"
@ -356,13 +356,13 @@ const btnColumns: DataTableColumns<Api.System.Menu> = [
<template #icon> <template #icon>
<icon-material-symbols-add-rounded /> <icon-material-symbols-add-rounded />
</template> </template>
新增子菜单 {{ $t('page.system.menu.addChildMenu') }}
</NButton> </NButton>
<NButton v-if="hasAuth('system:menu:edit')" size="small" ghost type="primary" @click="handleUpdateMenu"> <NButton v-if="hasAuth('system:menu:edit')" size="small" ghost type="primary" @click="handleUpdateMenu">
<template #icon> <template #icon>
<icon-material-symbols-drive-file-rename-outline-outline /> <icon-material-symbols-drive-file-rename-outline-outline />
</template> </template>
编辑 {{ $t('common.edit') }}
</NButton> </NButton>
<NPopconfirm @positive-click="() => handleDeleteMenu()"> <NPopconfirm @positive-click="() => handleDeleteMenu()">
<template #trigger> <template #trigger>
@ -391,46 +391,50 @@ const btnColumns: DataTableColumns<Api.System.Menu> = [
label-class="w-20% min-w-88px" label-class="w-20% min-w-88px"
content-class="w-100px" content-class="w-100px"
> >
<NDescriptionsItem label="菜单类型"> <NDescriptionsItem :label="$t('page.system.menu.menuName')">
<NTag class="m-1" size="small" type="primary">{{ menuTypeRecord[currentMenu.menuType!] }}</NTag> <NTag class="m-1" size="small" type="primary">{{ menuTypeRecord[currentMenu.menuType!] }}</NTag>
</NDescriptionsItem> </NDescriptionsItem>
<NDescriptionsItem label="菜单状态"> <NDescriptionsItem :label="$t('page.system.menu.status')">
<DictTag size="small" :value="currentMenu.status" dict-code="sys_normal_disable" /> <DictTag size="small" :value="currentMenu.status" dict-code="sys_normal_disable" />
</NDescriptionsItem> </NDescriptionsItem>
<NDescriptionsItem label="菜单名称">{{ currentMenu.menuName }}</NDescriptionsItem> <NDescriptionsItem :label="$t('page.system.menu.addChildMenu')">
<NDescriptionsItem v-if="currentMenu.menuType === 'C'" label="组件路径"> {{ currentMenu.menuName }}
</NDescriptionsItem>
<NDescriptionsItem v-if="currentMenu.menuType === 'C'" :label="$t('page.system.menu.component')">
{{ currentMenu.component }} {{ currentMenu.component }}
</NDescriptionsItem> </NDescriptionsItem>
<NDescriptionsItem :label="currentMenu.isFrame !== '0' ? '路由地址' : '外链地址'"> <NDescriptionsItem
:label="currentMenu.isFrame !== '0' ? $t('page.system.menu.path') : $t('page.system.menu.externalPath')"
>
{{ currentMenu.path }} {{ currentMenu.path }}
</NDescriptionsItem> </NDescriptionsItem>
<NDescriptionsItem <NDescriptionsItem
v-if="currentMenu.menuType === 'C'" v-if="currentMenu.menuType === 'C'"
:label="currentMenu.isFrame !== '2' ? '路由参数' : 'iframe 地址'" :label="currentMenu.isFrame !== '2' ? $t('page.system.menu.query') : $t('page.system.menu.iframeQuery')"
> >
{{ currentMenu.queryParam }} {{ currentMenu.queryParam }}
</NDescriptionsItem> </NDescriptionsItem>
<NDescriptionsItem v-if="currentMenu.menuType !== 'M'" label="权限标识"> <NDescriptionsItem v-if="currentMenu.menuType !== 'M'" :label="$t('page.system.menu.perms')">
{{ currentMenu.perms }} {{ currentMenu.perms }}
</NDescriptionsItem> </NDescriptionsItem>
<NDescriptionsItem label="是否外链"> <NDescriptionsItem :label="$t('page.system.menu.isFrame')">
<NTag v-if="currentMenu.isFrame" class="m-1" size="small" :type="tagMap[currentMenu.isFrame]"> <NTag v-if="currentMenu.isFrame" class="m-1" size="small" :type="tagMap[currentMenu.isFrame]">
{{ menuIsFrameRecord[currentMenu.isFrame] }} {{ menuIsFrameRecord[currentMenu.isFrame] }}
</NTag> </NTag>
</NDescriptionsItem> </NDescriptionsItem>
<NDescriptionsItem label="显示状态"> <NDescriptionsItem :label="$t('page.system.menu.visible')">
<DictTag size="small" :value="currentMenu.visible" dict-code="sys_show_hide" /> <DictTag size="small" :value="currentMenu.visible" dict-code="sys_show_hide" />
</NDescriptionsItem> </NDescriptionsItem>
<NDescriptionsItem v-if="currentMenu.menuType === 'C'" label="是否缓存"> <NDescriptionsItem v-if="currentMenu.menuType === 'C'" :label="$t('page.system.menu.isCache')">
<NTag v-if="currentMenu.isCache" class="m-1" size="small" :type="tagMap[currentMenu.isCache]"> <NTag v-if="currentMenu.isCache" class="m-1" size="small" :type="tagMap[currentMenu.isCache]">
{{ currentMenu.isCache === '0' ? '缓存' : '不缓存' }} {{ currentMenu.isCache === '0' ? $t('page.system.menu.cache') : $t('page.system.menu.noCache') }}
</NTag> </NTag>
</NDescriptionsItem> </NDescriptionsItem>
</NDescriptions> </NDescriptions>
</NCard> </NCard>
<NCard <NCard
title="按钮权限列表" :title="$t('page.system.menu.buttonPermissionList')"
:bordered="false" :bordered="false"
size="small" size="small"
class="h-full overflow-auto card-wrapper" class="h-full overflow-auto card-wrapper"
@ -441,7 +445,7 @@ const btnColumns: DataTableColumns<Api.System.Menu> = [
size="small" size="small"
icon="ic-round-refresh" icon="ic-round-refresh"
class="h-28px text-icon" class="h-28px text-icon"
tooltip-content="刷新" :tooltip-content="$t('common.refresh')"
@click.stop="getBtnMenuList" @click.stop="getBtnMenuList"
/> />
</template> </template>

View File

@ -45,8 +45,8 @@ const queryList = ref<{ key: string; value: string }[]>([]);
const drawerTitle = computed(() => { const drawerTitle = computed(() => {
const titles: Record<NaiveUI.TableOperateType, string> = { const titles: Record<NaiveUI.TableOperateType, string> = {
add: '新增菜单', add: $t('page.system.menu.addMenu'),
edit: '编辑菜单' edit: $t('page.system.menu.editMenu')
}; };
return titles[props.operateType]; return titles[props.operateType];
}); });
@ -69,7 +69,7 @@ function createDefaultModel(): Model {
visible: '0', visible: '0',
status: '0', status: '0',
perms: '', perms: '',
icon: '', icon: undefined,
remark: '' remark: ''
}; };
} }
@ -77,10 +77,10 @@ function createDefaultModel(): Model {
type RuleKey = Extract<keyof Model, 'menuName' | 'orderNum' | 'path' | 'component'>; type RuleKey = Extract<keyof Model, 'menuName' | 'orderNum' | 'path' | 'component'>;
const rules: Record<RuleKey, App.Global.FormRule> = { const rules: Record<RuleKey, App.Global.FormRule> = {
menuName: createRequiredRule('菜单名称不能为空'), menuName: createRequiredRule($t('page.system.menu.form.menuName.invalid')),
orderNum: createNumberRequiredRule('菜单排序不能为空'), orderNum: createNumberRequiredRule($t('page.system.menu.form.orderNum.invalid')),
path: createRequiredRule('路由地址不能为空'), path: createRequiredRule($t('page.system.menu.form.path.invalid')),
component: createRequiredRule('组件路径不能为空') component: createRequiredRule($t('page.system.menu.form.component.invalid'))
}; };
const isBtn = computed(() => model.menuType === 'F'); const isBtn = computed(() => model.menuType === 'F');
@ -228,17 +228,22 @@ function onCreate() {
<NDrawerContent :title="drawerTitle" :native-scrollbar="false" closable> <NDrawerContent :title="drawerTitle" :native-scrollbar="false" closable>
<NForm ref="formRef" :model="model" :rules="rules"> <NForm ref="formRef" :model="model" :rules="rules">
<NGrid responsive="screen" item-responsive> <NGrid responsive="screen" item-responsive>
<NFormItemGi :span="24" label="上级菜单" path="pid"> <NFormItemGi :span="24" :label="$t('page.system.menu.parentId')" path="pid">
<NTreeSelect <NTreeSelect
v-model:value="model.parentId" v-model:value="model.parentId"
:options="treeData as []" :options="treeData as []"
label-field="menuName" label-field="menuName"
key-field="menuId" key-field="menuId"
:default-expanded-keys="[0]" :default-expanded-keys="[0]"
placeholder="请选择上级菜单" :placeholder="$t('page.system.menu.form.parentId.required')"
/> />
</NFormItemGi> </NFormItemGi>
<NFormItemGi v-if="model.menuType !== 'F'" :span="24" label="菜单类型" path="menuType"> <NFormItemGi
v-if="model.menuType !== 'F'"
:span="24"
:label="$t('page.system.menu.menuType')"
path="menuType"
>
<NRadioGroup v-model:value="model.menuType"> <NRadioGroup v-model:value="model.menuType">
<NRadioButton <NRadioButton
v-for="item in menuTypeOptions.filter(item => item.value !== 'F')" v-for="item in menuTypeOptions.filter(item => item.value !== 'F')"
@ -248,10 +253,10 @@ function onCreate() {
/> />
</NRadioGroup> </NRadioGroup>
</NFormItemGi> </NFormItemGi>
<NFormItemGi :span="24" label="菜单名称" path="menuName"> <NFormItemGi :span="24" :label="$t('page.system.menu.menuName')" path="menuName">
<NInput v-model:value="model.menuName" placeholder="请输入菜单名称" /> <NInput v-model:value="model.menuName" :placeholder="$t('page.system.menu.form.menuName.required')" />
</NFormItemGi> </NFormItemGi>
<NFormItemGi v-if="!isBtn" span="24" label="图标类型"> <NFormItemGi v-if="!isBtn" span="24" :label="$t('page.system.menu.iconType')">
<NRadioGroup v-model:value="iconType"> <NRadioGroup v-model:value="iconType">
<NRadio v-for="item in menuIconTypeOptions" :key="item.value" :value="item.value" :label="item.label" /> <NRadio v-for="item in menuIconTypeOptions" :key="item.value" :value="item.value" :label="item.label" />
</NRadioGroup> </NRadioGroup>
@ -259,40 +264,51 @@ function onCreate() {
<NFormItemGi v-if="!isBtn" span="24" path="icon"> <NFormItemGi v-if="!isBtn" span="24" path="icon">
<template #label> <template #label>
<div class="flex-center"> <div class="flex-center">
<FormTip content="iconify 地址:`https://icones.js.org`" /> <FormTip :content="$t('page.system.menu.iconifyTip')" />
<span class="pl-3px">菜单图标</span> <span class="pl-3px">{{ $t('page.system.menu.icon') }}</span>
</div> </div>
</template> </template>
<template v-if="iconType === '1'"> <template v-if="iconType === '1'">
<NInput v-model:value="model.icon" placeholder="请输入图标" class="flex-1"> <NInput
v-model:value="model.icon"
:placeholder="$t('page.system.menu.placeholder.iconifyIconPlaceholder')"
class="flex-1"
>
<template #suffix> <template #suffix>
<SvgIcon v-if="model.icon" :icon="model.icon" class="text-icon" /> <SvgIcon v-if="model.icon" :icon="model.icon" class="text-icon" />
</template> </template>
</NInput> </NInput>
</template> </template>
<template v-if="iconType === '2'"> <template v-if="iconType === '2'">
<NSelect v-model:value="model.icon" placeholder="请选择本地图标" filterable :options="localIconOptions" /> <NSelect
v-model:value="model.icon"
:placeholder="$t('page.system.menu.placeholder.localIconPlaceholder')"
filterable
:options="localIconOptions"
/>
</template> </template>
</NFormItemGi> </NFormItemGi>
<NFormItemGi v-if="!isBtn" :span="24" path="path"> <NFormItemGi v-if="!isBtn" :span="24" path="path">
<template #label> <template #label>
<div class="flex-center"> <div class="flex-center">
<FormTip content="访问的路由地址,如:`user`,如外网地址需内链访问则以 `http(s)://` 开头" /> <FormTip :content="$t('page.system.menu.pathTip')" />
<span>{{ model.isFrame !== '0' ? '路由地址' : '外链地址' }}</span> <span>
{{ model.isFrame !== '0' ? $t('page.system.menu.path') : $t('page.system.menu.externalPath') }}
</span>
</div> </div>
</template> </template>
<NInput v-model:value="model.path" placeholder="请输入路由地址" /> <NInput v-model:value="model.path" :placeholder="$t('page.system.menu.form.path.required')" />
</NFormItemGi> </NFormItemGi>
<NFormItemGi v-if="isMenu && model.isFrame === '1'" :span="24" path="component"> <NFormItemGi v-if="isMenu && model.isFrame === '1'" :span="24" path="component">
<template #label> <template #label>
<div class="flex-center"> <div class="flex-center">
<FormTip content="访问的组件路径,如:`system/user/index`,默认在`views`目录下" /> <FormTip :content="$t('page.system.menu.componentTip')" />
<span>组件路径</span> <span>{{ $t('page.system.menu.component') }}</span>
</div> </div>
</template> </template>
<NInputGroup> <NInputGroup>
<NInputGroupLabel>views/</NInputGroupLabel> <NInputGroupLabel>views/</NInputGroupLabel>
<NInput v-model:value="model.component" placeholder="请输入组件地址" /> <NInput v-model:value="model.component" :placeholder="$t('page.system.menu.form.path.required')" />
<NInputGroupLabel>/index.vue</NInputGroupLabel> <NInputGroupLabel>/index.vue</NInputGroupLabel>
</NInputGroup> </NInputGroup>
</NFormItemGi> </NFormItemGi>
@ -300,7 +316,7 @@ function onCreate() {
v-if="isMenu && model.isFrame !== '0'" v-if="isMenu && model.isFrame !== '0'"
span="24" span="24"
:show-feedback="!queryList.length" :show-feedback="!queryList.length"
:label="model.isFrame !== '2' ? '路由参数' : 'iframe 地址'" :label="model.isFrame !== '2' ? $t('page.system.menu.query') : $t('page.system.menu.iframeQuery')"
> >
<NDynamicInput <NDynamicInput
v-if="model.isFrame !== '2'" v-if="model.isFrame !== '2'"
@ -315,7 +331,10 @@ function onCreate() {
ignore-path-change ignore-path-change
:show-label="false" :show-label="false"
:path="`query[${index}].key`" :path="`query[${index}].key`"
:rule="{ ...createRequiredRule('请输入 Key'), validator: value => isNotNull(value) }" :rule="{
...createRequiredRule($t('page.system.menu.placeholder.queryKey')),
validator: value => isNotNull(value)
}"
> >
<NInput v-model:value="queryList[index].key" placeholder="Key" @keydown.enter.prevent /> <NInput v-model:value="queryList[index].key" placeholder="Key" @keydown.enter.prevent />
</NFormItem> </NFormItem>
@ -325,29 +344,36 @@ function onCreate() {
ignore-path-change ignore-path-change
:show-label="false" :show-label="false"
:path="`query[${index}].value`" :path="`query[${index}].value`"
:rule="{ ...createRequiredRule('请输入 Value'), validator: value => isNotNull(value) }" :rule="{
...createRequiredRule($t('page.system.menu.placeholder.queryValue')),
validator: value => isNotNull(value)
}"
> >
<NInput v-model:value="queryList[index].value" placeholder="Value" @keydown.enter.prevent /> <NInput v-model:value="queryList[index].value" placeholder="Value" @keydown.enter.prevent />
</NFormItem> </NFormItem>
</div> </div>
</template> </template>
</NDynamicInput> </NDynamicInput>
<NInput v-else v-model:value="model.queryParam" placeholder="请输入 iframe 地址" /> <NInput
v-else
v-model:value="model.queryParam"
:placeholder="$t('page.system.menu.placeholder.queryIframe')"
/>
</NFormItemGi> </NFormItemGi>
<NFormItemGi :span="24" path="perms"> <NFormItemGi :span="24" path="perms">
<template #label> <template #label>
<div class="flex-center"> <div class="flex-center">
<FormTip content="控制器中定义的权限字符,如:@SaCheckPermission('system:user:list')" /> <FormTip :content="$t('page.system.menu.permsTip')" />
<span>权限字符</span> <span>{{ $t('page.system.menu.perms') }}</span>
</div> </div>
</template> </template>
<NInput v-model:value="model.perms" placeholder="请输入菜单名称" /> <NInput v-model:value="model.perms" :placeholder="$t('page.system.menu.form.perms.required')" />
</NFormItemGi> </NFormItemGi>
<NFormItemGi v-if="!isBtn" :span="12" path="isFrame"> <NFormItemGi v-if="!isBtn" :span="12" path="isFrame">
<template #label> <template #label>
<div class="flex-center"> <div class="flex-center">
<FormTip content="选择是外链则路由地址需要以`http(s)://`开头" /> <FormTip :content="$t('page.system.menu.isFrameTip')" />
<span>是否外链</span> <span>{{ $t('page.system.menu.isFrame') }}</span>
</div> </div>
</template> </template>
<NRadioGroup v-model:value="model.isFrame"> <NRadioGroup v-model:value="model.isFrame">
@ -364,8 +390,8 @@ function onCreate() {
<NFormItemGi v-if="isMenu" :span="12" path="isCache"> <NFormItemGi v-if="isMenu" :span="12" path="isCache">
<template #label> <template #label>
<div class="flex-center"> <div class="flex-center">
<FormTip content="选择是则会被`keep-alive`缓存,需要匹配组件的`name`和地址保持一致" /> <FormTip :content="$t('page.system.menu.isCacheTip')" />
<span>是否缓存</span> <span>{{ $t('page.system.menu.isCache') }}</span>
</div> </div>
</template> </template>
<NRadioGroup v-model:value="model.isCache"> <NRadioGroup v-model:value="model.isCache">
@ -375,11 +401,11 @@ function onCreate() {
</NSpace> </NSpace>
</NRadioGroup> </NRadioGroup>
</NFormItemGi> </NFormItemGi>
<NFormItemGi v-if="!isBtn" :span="12" label="显示状态" path="visible"> <NFormItemGi v-if="!isBtn" :span="12" :label="$t('page.system.menu.visible')" path="visible">
<template #label> <template #label>
<div class="flex-center"> <div class="flex-center">
<FormTip content="选择隐藏则路由将不会出现在侧边栏,但仍然可以访问" /> <FormTip :content="$t('page.system.menu.visibleTip')" />
<span>显示状态</span> <span>{{ $t('page.system.menu.visible') }}</span>
</div> </div>
</template> </template>
<DictRadio v-model:value="model.visible" dict-code="sys_show_hide" /> <DictRadio v-model:value="model.visible" dict-code="sys_show_hide" />
@ -387,14 +413,14 @@ function onCreate() {
<NFormItemGi :span="12" path="status"> <NFormItemGi :span="12" path="status">
<template #label> <template #label>
<div class="flex-center"> <div class="flex-center">
<FormTip content="选择停用则路由将不会出现在侧边栏,也不能被访问" /> <FormTip :content="$t('page.system.menu.statusTip')" />
<span>菜单状态</span> <span>{{ $t('page.system.menu.status') }}</span>
</div> </div>
</template> </template>
<DictRadio v-model:value="model.status" dict-code="sys_normal_disable" /> <DictRadio v-model:value="model.status" dict-code="sys_normal_disable" />
</NFormItemGi> </NFormItemGi>
<NFormItemGi :span="12" label="显示排序" path="orderNum"> <NFormItemGi :span="12" :label="$t('page.system.menu.orderNum')" path="orderNum">
<NInputNumber v-model:value="model.orderNum" placeholder="请输入排序" /> <NInputNumber v-model:value="model.orderNum" :placeholder="$t('page.system.menu.form.orderNum.required')" />
</NFormItemGi> </NFormItemGi>
</NGrid> </NGrid>
</NForm> </NForm>