feat:增加 menu 菜单的列表(新增、修改、删除 100%)

pull/62/head
YunaiV 2025-04-02 23:03:28 +08:00
parent 18ac4cb14c
commit 94ea0fc086
3 changed files with 70 additions and 61 deletions

View File

@ -34,4 +34,14 @@ const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
/** 有权限校验的路由列表,包含动态路由和静态路由 */ /** 有权限校验的路由列表,包含动态路由和静态路由 */
const accessRoutes = [...dynamicRoutes, ...staticRoutes]; const accessRoutes = [...dynamicRoutes, ...staticRoutes];
export { accessRoutes, coreRouteNames, routes };
// add by 芋艿from https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/router/routes/index.ts#L38-L45
const componentKeys: string[] = Object.keys(
import.meta.glob('../../views/**/*.vue'),
)
.filter((item) => !item.includes('/modules/'))
.map((v) => {
const path = v.replace('../../views/', '/');
return path.endsWith('.vue') ? path.slice(0, -4) : path;
});
export { accessRoutes, coreRouteNames, routes, componentKeys };

View File

@ -1,8 +1,7 @@
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemMenuApi } from '#/api/system/menu'; import type { SystemMenuApi } from '#/api/system/menu';
import { $t } from '#/locales'; import { DICT_TYPE } from '#/utils/dict';
import {DICT_TYPE} from '#/utils/dict';
export function useGridColumns( export function useGridColumns(
onActionClick: OnActionClickFn<SystemMenuApi.SystemMenu>, onActionClick: OnActionClickFn<SystemMenuApi.SystemMenu>,

View File

@ -5,6 +5,7 @@ import type { VbenFormSchema } from '#/adapter/form';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { computed, h, ref } from 'vue'; import { computed, h, ref } from 'vue';
import { componentKeys } from '#/router/routes';
import { useVbenForm, z } from '#/adapter/form'; import { useVbenForm, z } from '#/adapter/form';
import { createMenu, getMenu, updateMenu } from '#/api/system/menu'; import { createMenu, getMenu, updateMenu } from '#/api/system/menu';
import { getMenuList } from '#/api/system/menu'; import { getMenuList } from '#/api/system/menu';
@ -17,8 +18,6 @@ import { useVbenModal } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
// import { componentKeys } from '#/router/routes'; // TODO @
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
const formData = ref<SystemMenuApi.SystemMenu>(); const formData = ref<SystemMenuApi.SystemMenu>();
const getTitle = computed(() => const getTitle = computed(() =>
@ -28,6 +27,16 @@ const getTitle = computed(() =>
); );
const schema: VbenFormSchema[] = [ const schema: VbenFormSchema[] = [
{ {
component: 'Input',
fieldName: 'id',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'parentId',
label: '上级菜单',
component: 'ApiTreeSelect', component: 'ApiTreeSelect',
componentProps: { componentProps: {
allowClear: true, allowClear: true,
@ -55,8 +64,6 @@ const schema: VbenFormSchema[] = [
showSearch: true, showSearch: true,
treeDefaultExpandedKeys: [0], treeDefaultExpandedKeys: [0],
}, },
fieldName: 'parentId',
label: '上级菜单',
rules: 'selectRequired', rules: 'selectRequired',
renderComponentContent() { renderComponentContent() {
return { return {
@ -73,87 +80,84 @@ const schema: VbenFormSchema[] = [
}, },
}, },
{ {
fieldName: 'name',
label: '菜单名称',
component: 'Input', component: 'Input',
componentProps: { componentProps: {
placeholder: '请输入菜单名称', placeholder: '请输入菜单名称',
}, },
fieldName: 'name',
label: '菜单名称',
rules: 'required', rules: 'required',
}, },
{ {
fieldName: 'type',
label: '菜单类型',
component: 'RadioGroup', component: 'RadioGroup',
componentProps: { componentProps: {
options: getDictOptions(DICT_TYPE.SYSTEM_MENU_TYPE, 'number'), options: getDictOptions(DICT_TYPE.SYSTEM_MENU_TYPE, 'number'),
buttonStyle: 'solid', buttonStyle: 'solid',
optionType: 'button', optionType: 'button',
}, },
fieldName: 'type',
label: '菜单类型',
rules: z.number().default(SystemMenuTypeEnum.DIR), rules: z.number().default(SystemMenuTypeEnum.DIR),
}, },
{ {
fieldName: 'icon',
label: '菜单图标',
component: 'IconPicker', component: 'IconPicker',
componentProps: { componentProps: {
placeholder: '请选择菜单图标', placeholder: '请选择菜单图标',
prefix: 'carbon', prefix: 'carbon',
}, },
rules: 'required',
dependencies: { dependencies: {
show: (values) => {
return [SystemMenuTypeEnum.DIR, SystemMenuTypeEnum.MENU].includes(
values.type,
);
},
triggerFields: ['type'], triggerFields: ['type'],
show: (values) => {
return [SystemMenuTypeEnum.DIR, SystemMenuTypeEnum.MENU].includes(values.type);
},
}, },
fieldName: 'icon',
label: '菜单图标',
}, },
{ {
fieldName: 'path',
label: '路由地址',
component: 'Input', component: 'Input',
componentProps: { componentProps: {
placeholder: '请输入路由地址', placeholder: '请输入路由地址',
}, },
rules: z.string(),
help: '访问的路由地址,如:`user`。如需外网地址时,则以 `http(s)://` 开头',
dependencies: { dependencies: {
show: (values) => {
return [SystemMenuTypeEnum.DIR, SystemMenuTypeEnum.MENU].includes(
values.type,
);
},
triggerFields: ['type', 'parentId'], triggerFields: ['type', 'parentId'],
show: (values) => {
return [SystemMenuTypeEnum.DIR, SystemMenuTypeEnum.MENU].includes(values.type);
},
rules: (values) => { rules: (values) => {
let schema = z.string(); const schema = z.string().min(1, '路由地址不能为空');
if (isHttpUrl(values.path)) { if (isHttpUrl(values.path)) {
return 'required'; return schema;
} }
if (values.parentId === 0) { if (values.parentId === 0) {
return schema.refine((path) => path.charAt(0) === '/', { return schema.refine((path) => path.charAt(0) === '/', '路径必须以 / 开头');
message: '路径必须以 / 开头',
});
} }
return schema.refine((path) => path.charAt(0) !== '/', { return schema.refine((path) => path.charAt(0) !== '/', '路径不能以 / 开头');
message: '路径不能以 / 开头',
});
}, },
}, }
fieldName: 'path',
label: '路由地址',
}, },
{ {
fieldName: 'component',
label: '组件地址',
component: 'Input', component: 'Input',
componentProps: { componentProps: {
placeholder: '请输入组件地址', placeholder: '请输入组件地址',
}, },
dependencies: { dependencies: {
triggerFields: ['type'],
show: (values) => { show: (values) => {
return [SystemMenuTypeEnum.MENU].includes(values.type); return [SystemMenuTypeEnum.MENU].includes(values.type);
}, }
triggerFields: ['type'],
}, },
fieldName: 'component',
label: '组件地址',
}, },
{ {
fieldName: 'componentName',
label: '组件名称',
component: 'AutoComplete', component: 'AutoComplete',
componentProps: { componentProps: {
allowClear: true, allowClear: true,
@ -162,20 +166,14 @@ const schema: VbenFormSchema[] = [
return option.value.toLowerCase().includes(input.toLowerCase()); return option.value.toLowerCase().includes(input.toLowerCase());
}, },
placeholder: '请选择组件名称', placeholder: '请选择组件名称',
// options: componentKeys.map((v) => ({ value: v })), // TODO @ options: componentKeys.map((v) => ({ value: v })),
}, },
dependencies: { dependencies: {
// TODO @ triggerFields: ['type'],
rules: (values) => {
return values.type === 'menu' ? 'required' : null;
},
show: (values) => { show: (values) => {
return [SystemMenuTypeEnum.MENU].includes(values.type); return [SystemMenuTypeEnum.MENU].includes(values.type);
}, }
triggerFields: ['type'],
}, },
fieldName: 'componentName',
label: '组件名称',
}, },
{ {
component: 'Input', component: 'Input',
@ -217,13 +215,9 @@ const schema: VbenFormSchema[] = [
rules: z.number().default(CommonStatusEnum.ENABLE), rules: z.number().default(CommonStatusEnum.ENABLE),
}, },
{ {
fieldName: 'alwaysShow',
label: '总是显示',
component: 'RadioGroup', component: 'RadioGroup',
dependencies: {
show: (values) => {
return [SystemMenuTypeEnum.MENU].includes(values.type);
},
triggerFields: ['type'],
},
componentProps: { componentProps: {
options: [ options: [
{ label: '总是', value: true }, { label: '总是', value: true },
@ -232,19 +226,20 @@ const schema: VbenFormSchema[] = [
buttonStyle: 'solid', buttonStyle: 'solid',
optionType: 'button', optionType: 'button',
}, },
fieldName: 'alwaysShow',
label: '总是显示',
rules: 'required', rules: 'required',
defaultValue: true, defaultValue: true,
}, help: '选择不是时,当该菜单只有一个子菜单时,不展示自己,直接展示子菜单',
{
component: 'RadioGroup',
dependencies: { dependencies: {
triggerFields: ['type'],
show: (values) => { show: (values) => {
return [SystemMenuTypeEnum.MENU].includes(values.type); return [SystemMenuTypeEnum.MENU].includes(values.type);
}, },
triggerFields: ['type'],
}, },
},
{
fieldName: 'keepAlive',
label: '缓存状态',
component: 'RadioGroup',
componentProps: { componentProps: {
options: [ options: [
{ label: '缓存', value: true }, { label: '缓存', value: true },
@ -253,10 +248,15 @@ const schema: VbenFormSchema[] = [
buttonStyle: 'solid', buttonStyle: 'solid',
optionType: 'button', optionType: 'button',
}, },
fieldName: 'keepAlive',
label: '缓存状态',
rules: 'required', rules: 'required',
defaultValue: true, defaultValue: true,
help: '选择缓存时,则会被 `keep-alive` 缓存,必须填写「组件名称」字段',
dependencies: {
triggerFields: ['type'],
show: (values) => {
return [SystemMenuTypeEnum.MENU].includes(values.type);
},
},
}, },
]; ];