2025-01-20 11:43:19 +08:00
|
|
|
|
import type { FormSchemaGetter } from '#/adapter/form';
|
2024-10-17 15:16:22 +08:00
|
|
|
|
import type { VxeGridProps } from '#/adapter/vxe-table';
|
|
|
|
|
|
2024-11-06 20:56:19 +08:00
|
|
|
|
import { h } from 'vue';
|
|
|
|
|
|
2024-09-22 21:37:32 +08:00
|
|
|
|
import { DictEnum } from '@vben/constants';
|
2024-11-06 20:56:19 +08:00
|
|
|
|
import { FolderIcon, MenuIcon, OkButtonIcon, VbenIcon } from '@vben/icons';
|
2024-10-11 09:29:06 +08:00
|
|
|
|
import { $t } from '@vben/locales';
|
2024-09-24 11:23:02 +08:00
|
|
|
|
import { getPopupContainer } from '@vben/utils';
|
2024-09-22 21:37:32 +08:00
|
|
|
|
|
2025-01-20 11:43:19 +08:00
|
|
|
|
import { z } from '#/adapter/form';
|
2024-09-22 21:37:32 +08:00
|
|
|
|
import { getDictOptions } from '#/utils/dict';
|
2024-11-06 20:56:19 +08:00
|
|
|
|
import { renderDict } from '#/utils/render';
|
2024-09-22 21:37:32 +08:00
|
|
|
|
|
2024-09-24 11:23:02 +08:00
|
|
|
|
export const querySchema: FormSchemaGetter = () => [
|
|
|
|
|
{
|
|
|
|
|
component: 'Input',
|
|
|
|
|
fieldName: 'menuName',
|
|
|
|
|
label: '菜单名称 ',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
component: 'Select',
|
|
|
|
|
componentProps: {
|
|
|
|
|
getPopupContainer,
|
|
|
|
|
options: getDictOptions(DictEnum.SYS_NORMAL_DISABLE),
|
|
|
|
|
},
|
|
|
|
|
fieldName: 'status',
|
|
|
|
|
label: '菜单状态 ',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
component: 'Select',
|
|
|
|
|
componentProps: {
|
|
|
|
|
getPopupContainer,
|
|
|
|
|
options: getDictOptions(DictEnum.SYS_SHOW_HIDE),
|
|
|
|
|
},
|
|
|
|
|
fieldName: 'visible',
|
|
|
|
|
label: '显示状态',
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
2024-09-22 21:37:32 +08:00
|
|
|
|
// 菜单类型(M目录 C菜单 F按钮)
|
|
|
|
|
export const menuTypeOptions = [
|
|
|
|
|
{ label: '目录', value: 'M' },
|
|
|
|
|
{ label: '菜单', value: 'C' },
|
|
|
|
|
{ label: '按钮', value: 'F' },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
export const yesNoOptions = [
|
|
|
|
|
{ label: '是', value: '0' },
|
|
|
|
|
{ label: '否', value: '1' },
|
|
|
|
|
];
|
|
|
|
|
|
2024-10-05 15:35:58 +08:00
|
|
|
|
// (M目录 C菜单 F按钮)
|
|
|
|
|
const menuTypes = {
|
2024-11-06 20:56:19 +08:00
|
|
|
|
C: { icon: MenuIcon, value: '菜单' },
|
|
|
|
|
F: { icon: OkButtonIcon, value: '按钮' },
|
|
|
|
|
M: { icon: FolderIcon, value: '目录' },
|
2024-10-05 15:35:58 +08:00
|
|
|
|
};
|
|
|
|
|
export const columns: VxeGridProps['columns'] = [
|
|
|
|
|
{
|
|
|
|
|
title: '菜单名称',
|
|
|
|
|
field: 'menuName',
|
|
|
|
|
treeNode: true,
|
|
|
|
|
width: 200,
|
2024-10-11 09:29:06 +08:00
|
|
|
|
slots: {
|
|
|
|
|
// 需要i18n支持 否则返回原始值
|
|
|
|
|
default: ({ row }) => $t(row.menuName),
|
|
|
|
|
},
|
2024-10-05 15:35:58 +08:00
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '图标',
|
|
|
|
|
field: 'icon',
|
|
|
|
|
width: 80,
|
|
|
|
|
slots: {
|
|
|
|
|
default: ({ row }) => {
|
|
|
|
|
if (row?.icon === '#') {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
2024-10-20 17:51:50 +08:00
|
|
|
|
return (
|
2024-11-06 20:56:19 +08:00
|
|
|
|
<span class={'flex justify-center'}>
|
|
|
|
|
<VbenIcon icon={row.icon} />
|
|
|
|
|
</span>
|
2024-10-20 17:51:50 +08:00
|
|
|
|
);
|
2024-10-05 15:35:58 +08:00
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '排序',
|
|
|
|
|
field: 'orderNum',
|
|
|
|
|
width: 120,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '组件类型',
|
|
|
|
|
field: 'menuType',
|
|
|
|
|
width: 150,
|
|
|
|
|
slots: {
|
|
|
|
|
default: ({ row }) => {
|
|
|
|
|
const current = menuTypes[row.menuType as 'C' | 'F' | 'M'];
|
2024-10-05 21:04:44 +08:00
|
|
|
|
if (!current) {
|
|
|
|
|
return '未知';
|
|
|
|
|
}
|
|
|
|
|
return (
|
2024-11-06 20:56:19 +08:00
|
|
|
|
<span class="flex items-center justify-center gap-1">
|
|
|
|
|
{h(current.icon, { class: 'size-[18px]' })}
|
|
|
|
|
<span>{current.value}</span>
|
2024-10-05 21:04:44 +08:00
|
|
|
|
</span>
|
|
|
|
|
);
|
2024-10-05 15:35:58 +08:00
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '权限标识',
|
|
|
|
|
field: 'perms',
|
2025-05-07 22:52:13 +08:00
|
|
|
|
minWidth: 200,
|
2024-10-05 15:35:58 +08:00
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '组件路径',
|
|
|
|
|
field: 'component',
|
2025-05-07 22:52:13 +08:00
|
|
|
|
minWidth: 200,
|
2024-10-05 15:35:58 +08:00
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '状态',
|
|
|
|
|
field: 'status',
|
|
|
|
|
width: 100,
|
|
|
|
|
slots: {
|
|
|
|
|
default: ({ row }) => {
|
|
|
|
|
return renderDict(row.status, DictEnum.SYS_NORMAL_DISABLE);
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '显示',
|
|
|
|
|
field: 'visible',
|
|
|
|
|
width: 100,
|
|
|
|
|
slots: {
|
|
|
|
|
default: ({ row }) => {
|
|
|
|
|
return renderDict(row.visible, DictEnum.SYS_SHOW_HIDE);
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '创建时间',
|
|
|
|
|
field: 'createTime',
|
2025-05-07 22:52:13 +08:00
|
|
|
|
minWidth: 160,
|
2024-10-05 15:35:58 +08:00
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
field: 'action',
|
|
|
|
|
fixed: 'right',
|
|
|
|
|
slots: { default: 'action' },
|
|
|
|
|
title: '操作',
|
2025-04-12 15:01:28 +08:00
|
|
|
|
resizable: false,
|
|
|
|
|
width: 'auto',
|
2024-10-05 15:35:58 +08:00
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
2024-09-22 21:37:32 +08:00
|
|
|
|
export const drawerSchema: FormSchemaGetter = () => [
|
|
|
|
|
{
|
|
|
|
|
component: 'Input',
|
|
|
|
|
dependencies: {
|
|
|
|
|
show: () => false,
|
|
|
|
|
triggerFields: [''],
|
|
|
|
|
},
|
|
|
|
|
fieldName: 'menuId',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
component: 'TreeSelect',
|
|
|
|
|
defaultValue: 0,
|
|
|
|
|
fieldName: 'parentId',
|
|
|
|
|
label: '上级菜单',
|
|
|
|
|
rules: 'selectRequired',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
component: 'RadioGroup',
|
|
|
|
|
componentProps: {
|
|
|
|
|
buttonStyle: 'solid',
|
|
|
|
|
options: menuTypeOptions,
|
|
|
|
|
optionType: 'button',
|
|
|
|
|
},
|
|
|
|
|
defaultValue: 'M',
|
|
|
|
|
dependencies: {
|
|
|
|
|
componentProps: (_, api) => {
|
|
|
|
|
// 切换时清空校验
|
|
|
|
|
// 直接抄的源码 没有清空校验的方法
|
|
|
|
|
Object.keys(api.errors.value).forEach((key) => {
|
|
|
|
|
api.setFieldError(key, undefined);
|
|
|
|
|
});
|
|
|
|
|
return {};
|
|
|
|
|
},
|
|
|
|
|
triggerFields: ['menuType'],
|
|
|
|
|
},
|
|
|
|
|
fieldName: 'menuType',
|
|
|
|
|
label: '菜单类型',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
component: 'Input',
|
|
|
|
|
dependencies: {
|
|
|
|
|
// 类型不为按钮时显示
|
|
|
|
|
show: (values) => values.menuType !== 'F',
|
|
|
|
|
triggerFields: ['menuType'],
|
|
|
|
|
},
|
2024-11-07 19:20:45 +08:00
|
|
|
|
renderComponentContent: (model) => ({
|
|
|
|
|
addonBefore: () => <VbenIcon icon={model.icon} />,
|
2024-10-10 13:31:36 +08:00
|
|
|
|
addonAfter: () => (
|
|
|
|
|
<a href="https://icon-sets.iconify.design/" target="_blank">
|
|
|
|
|
搜索图标
|
|
|
|
|
</a>
|
|
|
|
|
),
|
|
|
|
|
}),
|
2024-09-22 21:37:32 +08:00
|
|
|
|
fieldName: 'icon',
|
2024-10-10 13:31:36 +08:00
|
|
|
|
help: '点击搜索图标跳转到iconify & 粘贴',
|
2024-09-22 21:37:32 +08:00
|
|
|
|
label: '菜单图标',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
component: 'Input',
|
2024-10-06 10:05:58 +08:00
|
|
|
|
fieldName: 'menuName',
|
2024-09-22 21:37:32 +08:00
|
|
|
|
label: '菜单名称',
|
2024-10-11 10:15:44 +08:00
|
|
|
|
help: '支持i18n写法, 如: menu.system.user',
|
2024-09-22 21:37:32 +08:00
|
|
|
|
rules: 'required',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
component: 'InputNumber',
|
|
|
|
|
fieldName: 'orderNum',
|
|
|
|
|
help: '排序, 数字越小越靠前',
|
|
|
|
|
label: '显示排序',
|
|
|
|
|
rules: 'required',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
component: 'Input',
|
|
|
|
|
componentProps: (model) => {
|
|
|
|
|
const placeholder =
|
|
|
|
|
model.isFrame === '0'
|
|
|
|
|
? '填写链接地址http(s):// 使用新页面打开'
|
|
|
|
|
: '填写`路由地址`或者`链接地址` 链接默认使用内部iframe内嵌打开';
|
|
|
|
|
return {
|
|
|
|
|
placeholder,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
dependencies: {
|
|
|
|
|
rules: (model) => {
|
|
|
|
|
if (model.isFrame !== '0') {
|
|
|
|
|
return z
|
|
|
|
|
.string({ message: '请输入路由地址' })
|
|
|
|
|
.refine((val) => !val.startsWith('/'), {
|
|
|
|
|
message: '路由地址不需要带/',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
// 为链接
|
|
|
|
|
return z
|
|
|
|
|
.string({ message: '请输入链接地址' })
|
|
|
|
|
.regex(/^https?:\/\//, { message: '请输入正确的链接地址' });
|
|
|
|
|
},
|
|
|
|
|
// 类型不为按钮时显示
|
2024-10-08 10:35:37 +08:00
|
|
|
|
show: (values) => values?.menuType !== 'F',
|
|
|
|
|
triggerFields: ['isFrame', 'menuType'],
|
2024-09-22 21:37:32 +08:00
|
|
|
|
},
|
|
|
|
|
fieldName: 'path',
|
2024-10-20 10:34:25 +08:00
|
|
|
|
help: `路由地址不带/, 如: menu, user\n 链接为http(s)://开头\n 链接默认使用内部iframe打开, 可通过{是否外链}控制打开方式`,
|
2024-09-22 21:37:32 +08:00
|
|
|
|
label: '路由地址',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
component: 'Input',
|
|
|
|
|
componentProps: (model) => {
|
|
|
|
|
return {
|
|
|
|
|
// 为链接时组件disabled
|
|
|
|
|
disabled: model.isFrame === '0',
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
defaultValue: '',
|
|
|
|
|
dependencies: {
|
|
|
|
|
rules: (model) => {
|
|
|
|
|
// 非链接时为必填项
|
|
|
|
|
if (model.path && !/^https?:\/\//.test(model.path)) {
|
2024-09-23 08:09:48 +08:00
|
|
|
|
return z
|
|
|
|
|
.string()
|
|
|
|
|
.min(1, { message: '非链接时必填组件路径' })
|
|
|
|
|
.refine((val) => !val.startsWith('/') && !val.endsWith('/'), {
|
|
|
|
|
message: '组件路径开头/末尾不需要带/',
|
|
|
|
|
});
|
2024-09-22 21:37:32 +08:00
|
|
|
|
}
|
2024-09-23 08:09:48 +08:00
|
|
|
|
// 为链接时非必填
|
|
|
|
|
return z.string().optional();
|
2024-09-22 21:37:32 +08:00
|
|
|
|
},
|
|
|
|
|
// 类型为菜单时显示
|
|
|
|
|
show: (values) => values.menuType === 'C',
|
|
|
|
|
triggerFields: ['menuType', 'path'],
|
|
|
|
|
},
|
|
|
|
|
fieldName: 'component',
|
|
|
|
|
help: '填写./src/views下的组件路径, 如system/menu/index',
|
|
|
|
|
label: '组件路径',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
component: 'RadioGroup',
|
|
|
|
|
componentProps: {
|
|
|
|
|
buttonStyle: 'solid',
|
|
|
|
|
options: yesNoOptions,
|
|
|
|
|
optionType: 'button',
|
|
|
|
|
},
|
|
|
|
|
defaultValue: '1',
|
|
|
|
|
dependencies: {
|
|
|
|
|
// 类型不为按钮时显示
|
|
|
|
|
show: (values) => values.menuType !== 'F',
|
|
|
|
|
triggerFields: ['menuType'],
|
|
|
|
|
},
|
|
|
|
|
fieldName: 'isFrame',
|
2024-10-20 10:34:25 +08:00
|
|
|
|
help: '外链为http(s)://开头\n 选择否时, 使用iframe从内部打开页面, 否则新窗口打开',
|
2024-09-22 21:37:32 +08:00
|
|
|
|
label: '是否外链',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
component: 'RadioGroup',
|
|
|
|
|
componentProps: {
|
|
|
|
|
buttonStyle: 'solid',
|
|
|
|
|
options: getDictOptions(DictEnum.SYS_SHOW_HIDE),
|
|
|
|
|
optionType: 'button',
|
|
|
|
|
},
|
|
|
|
|
defaultValue: '0',
|
|
|
|
|
dependencies: {
|
|
|
|
|
// 类型不为按钮时显示
|
|
|
|
|
show: (values) => values.menuType !== 'F',
|
|
|
|
|
triggerFields: ['menuType'],
|
|
|
|
|
},
|
|
|
|
|
fieldName: 'visible',
|
|
|
|
|
help: '隐藏后不会出现在菜单栏, 但仍然可以访问',
|
|
|
|
|
label: '是否显示',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
component: 'RadioGroup',
|
|
|
|
|
componentProps: {
|
|
|
|
|
buttonStyle: 'solid',
|
|
|
|
|
options: getDictOptions(DictEnum.SYS_NORMAL_DISABLE),
|
|
|
|
|
optionType: 'button',
|
|
|
|
|
},
|
|
|
|
|
defaultValue: '0',
|
|
|
|
|
dependencies: {
|
|
|
|
|
// 类型不为按钮时显示
|
|
|
|
|
show: (values) => values.menuType !== 'F',
|
|
|
|
|
triggerFields: ['menuType'],
|
|
|
|
|
},
|
|
|
|
|
fieldName: 'status',
|
|
|
|
|
help: '停用后不会出现在菜单栏, 也无法访问',
|
|
|
|
|
label: '菜单状态',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
component: 'Input',
|
|
|
|
|
dependencies: {
|
|
|
|
|
// 类型为菜单/按钮时显示
|
|
|
|
|
show: (values) => values.menuType !== 'M',
|
|
|
|
|
triggerFields: ['menuType'],
|
|
|
|
|
},
|
|
|
|
|
fieldName: 'perms',
|
2024-10-20 10:34:25 +08:00
|
|
|
|
help: `控制器中定义的权限字符\n 如: @SaCheckPermission("system:user:import")`,
|
2024-09-22 21:37:32 +08:00
|
|
|
|
label: '权限标识',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
component: 'Input',
|
2024-10-10 11:48:26 +08:00
|
|
|
|
componentProps: (model) => ({
|
2024-09-22 21:37:32 +08:00
|
|
|
|
// 为链接时组件disabled
|
2024-10-10 11:48:26 +08:00
|
|
|
|
disabled: model.isFrame === '0',
|
|
|
|
|
placeholder: '必须为json字符串格式',
|
2024-09-22 21:37:32 +08:00
|
|
|
|
}),
|
|
|
|
|
dependencies: {
|
|
|
|
|
// 类型为菜单时显示
|
|
|
|
|
show: (values) => values.menuType === 'C',
|
|
|
|
|
triggerFields: ['menuType'],
|
|
|
|
|
},
|
|
|
|
|
fieldName: 'queryParam',
|
2024-10-20 10:34:25 +08:00
|
|
|
|
help: 'vue-router中的query属性\n 如{"name": "xxx", "age": 16}',
|
2024-09-22 21:37:32 +08:00
|
|
|
|
label: '路由参数',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
component: 'RadioGroup',
|
|
|
|
|
componentProps: {
|
|
|
|
|
buttonStyle: 'solid',
|
|
|
|
|
options: yesNoOptions,
|
|
|
|
|
optionType: 'button',
|
|
|
|
|
},
|
|
|
|
|
defaultValue: '0',
|
|
|
|
|
dependencies: {
|
|
|
|
|
// 类型为菜单时显示
|
|
|
|
|
show: (values) => values.menuType === 'C',
|
|
|
|
|
triggerFields: ['menuType'],
|
|
|
|
|
},
|
|
|
|
|
fieldName: 'isCache',
|
|
|
|
|
help: '路由的keepAlive属性',
|
|
|
|
|
label: '是否缓存',
|
|
|
|
|
},
|
|
|
|
|
];
|