diff --git a/apps/backend-mock/api/system/menu/list.ts b/apps/backend-mock/api/system/menu/list.ts new file mode 100644 index 00000000..5328b2fd --- /dev/null +++ b/apps/backend-mock/api/system/menu/list.ts @@ -0,0 +1,12 @@ +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { MOCK_MENU_LIST } from '~/utils/mock-data'; +import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + + return useResponseSuccess(MOCK_MENU_LIST); +}); diff --git a/apps/backend-mock/api/system/menu/name-exists.ts b/apps/backend-mock/api/system/menu/name-exists.ts new file mode 100644 index 00000000..5599c22b --- /dev/null +++ b/apps/backend-mock/api/system/menu/name-exists.ts @@ -0,0 +1,28 @@ +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { MOCK_MENU_LIST } from '~/utils/mock-data'; +import { unAuthorizedResponse } from '~/utils/response'; + +const namesMap: Record = {}; + +function getNames(menus: any[]) { + menus.forEach((menu) => { + namesMap[menu.name] = String(menu.id); + if (menu.children) { + getNames(menu.children); + } + }); +} +getNames(MOCK_MENU_LIST); + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + const { id, name } = getQuery(event); + + return (name as string) in namesMap && + (!id || namesMap[name as string] !== String(id)) + ? useResponseSuccess(true) + : useResponseSuccess(false); +}); diff --git a/apps/backend-mock/api/system/menu/path-exists.ts b/apps/backend-mock/api/system/menu/path-exists.ts new file mode 100644 index 00000000..64774f79 --- /dev/null +++ b/apps/backend-mock/api/system/menu/path-exists.ts @@ -0,0 +1,28 @@ +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { MOCK_MENU_LIST } from '~/utils/mock-data'; +import { unAuthorizedResponse } from '~/utils/response'; + +const pathMap: Record = { '/': 0 }; + +function getPaths(menus: any[]) { + menus.forEach((menu) => { + pathMap[menu.path] = String(menu.id); + if (menu.children) { + getPaths(menu.children); + } + }); +} +getPaths(MOCK_MENU_LIST); + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + const { id, path } = getQuery(event); + + return (path as string) in pathMap && + (!id || pathMap[path as string] !== String(id)) + ? useResponseSuccess(true) + : useResponseSuccess(false); +}); diff --git a/apps/backend-mock/utils/mock-data.ts b/apps/backend-mock/utils/mock-data.ts index 8fa12fe5..fd7b969b 100644 --- a/apps/backend-mock/utils/mock-data.ts +++ b/apps/backend-mock/utils/mock-data.ts @@ -185,3 +185,195 @@ export const MOCK_MENUS = [ username: 'jack', }, ]; + +export const MOCK_MENU_LIST = [ + { + id: 1, + name: 'Workspace', + status: 1, + type: 'menu', + icon: 'mdi:dashboard', + path: '/workspace', + component: '/dashboard/workspace/index', + meta: { + icon: 'carbon:workspace', + title: 'page.dashboard.workspace', + affixTab: true, + order: 0, + }, + }, + { + id: 2, + meta: { + icon: 'carbon:settings', + order: 9997, + title: 'system.title', + badge: 'new', + badgeType: 'normal', + badgeVariants: 'primary', + }, + status: 1, + type: 'catalog', + name: 'System', + path: '/system', + children: [ + { + id: 201, + pid: 2, + path: '/system/menu', + name: 'SystemMenu', + authCode: 'System:Menu:List', + status: 1, + type: 'menu', + meta: { + icon: 'carbon:menu', + title: 'system.menu.title', + }, + component: '/system/menu/list', + children: [ + { + id: 20_101, + pid: 201, + name: 'SystemMenuCreate', + status: 1, + type: 'button', + authCode: 'System:Menu:Create', + meta: { title: 'common.create' }, + }, + { + id: 20_102, + pid: 201, + name: 'SystemMenuEdit', + status: 1, + type: 'button', + authCode: 'System:Menu:Edit', + meta: { title: 'common.edit' }, + }, + { + id: 20_103, + pid: 201, + name: 'SystemMenuDelete', + status: 1, + type: 'button', + authCode: 'System:Menu:Delete', + meta: { title: 'common.delete' }, + }, + ], + }, + { + id: 202, + pid: 2, + path: '/system/dept', + name: 'SystemDept', + status: 1, + type: 'menu', + authCode: 'System:Dept:List', + meta: { + icon: 'carbon:container-services', + title: 'system.dept.title', + }, + component: '/system/dept/list', + children: [ + { + id: 20_401, + pid: 201, + name: 'SystemDeptCreate', + status: 1, + type: 'button', + authCode: 'System:Dept:Create', + meta: { title: 'common.create' }, + }, + { + id: 20_402, + pid: 201, + name: 'SystemDeptEdit', + status: 1, + type: 'button', + authCode: 'System:Dept:Edit', + meta: { title: 'common.edit' }, + }, + { + id: 20_403, + pid: 201, + name: 'SystemDeptDelete', + status: 1, + type: 'button', + authCode: 'System:Dept:Delete', + meta: { title: 'common.delete' }, + }, + ], + }, + ], + }, + { + id: 9, + meta: { + badgeType: 'dot', + order: 9998, + title: 'demos.vben.title', + icon: 'carbon:data-center', + }, + name: 'Project', + path: '/vben-admin', + type: 'catalog', + status: 1, + children: [ + { + id: 901, + pid: 9, + name: 'VbenDocument', + path: '/vben-admin/document', + component: 'IFrameView', + type: 'embedded', + status: 1, + meta: { + icon: 'carbon:book', + iframeSrc: 'https://doc.vben.pro', + title: 'demos.vben.document', + }, + }, + { + id: 902, + pid: 9, + name: 'VbenGithub', + path: '/vben-admin/github', + component: 'IFrameView', + type: 'link', + status: 1, + meta: { + icon: 'carbon:logo-github', + link: 'https://github.com/vbenjs/vue-vben-admin', + title: 'Github', + }, + }, + { + id: 903, + pid: 9, + name: 'VbenAntdv', + path: '/vben-admin/antdv', + component: 'IFrameView', + type: 'link', + status: 0, + meta: { + icon: 'carbon:hexagon-vertical-solid', + badgeType: 'dot', + link: 'https://ant.vben.pro', + title: 'demos.vben.antdv', + }, + }, + ], + }, + { + id: 10, + component: '_core/about/index', + type: 'menu', + status: 1, + meta: { + icon: 'lucide:copyright', + order: 9999, + title: 'demos.vben.about', + }, + name: 'About', + path: '/about', + }, +]; diff --git a/docs/src/components/common-ui/vben-form.md b/docs/src/components/common-ui/vben-form.md index 7ad64136..9babb67a 100644 --- a/docs/src/components/common-ui/vben-form.md +++ b/docs/src/components/common-ui/vben-form.md @@ -518,20 +518,25 @@ import { z } from '#/adapter/form'; // 可选(可以是undefined),并且携带默认值。注意zod的optional不包括空字符串'' { - rules: z.string().default('默认值').optional(), + rules: z.string().default('默认值').optional(); } -// 可以是空字符串、undefined或者一个邮箱地址 +// 可以是空字符串、undefined或者一个邮箱地址(两种不同的用法) { - rules: z.union(z.string().email().optional(), z.literal("")) + rules: z.union([z.string().email().optional(), z.literal('')]); +} + +{ + rules: z.string().email().or(z.literal('')).optional(); } // 复杂校验 { - z.string().min(1, { message: "请输入" }) - .refine((value) => value === "123", { - message: "值必须为123", - }); + z.string() + .min(1, { message: '请输入' }) + .refine((value) => value === '123', { + message: '值必须为123', + }); } ``` diff --git a/package.json b/package.json index 3058e88a..ac65a86b 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "node": ">=20.10.0", "pnpm": ">=9.12.0" }, - "packageManager": "pnpm@9.15.5", + "packageManager": "pnpm@9.15.6", "pnpm": { "peerDependencyRules": { "allowedVersions": { diff --git a/packages/@core/ui-kit/form-ui/src/form-api.ts b/packages/@core/ui-kit/form-ui/src/form-api.ts index c3d7b2bb..db09d7c7 100644 --- a/packages/@core/ui-kit/form-ui/src/form-api.ts +++ b/packages/@core/ui-kit/form-ui/src/form-api.ts @@ -93,9 +93,9 @@ export class FormApi { return this.state; } - async getValues() { + async getValues>() { const form = await this.getForm(); - return form.values ? this.handleRangeTimeValue(form.values) : {}; + return (form.values ? this.handleRangeTimeValue(form.values) : {}) as T; } async isFieldValid(fieldName: string) { diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts b/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts index bbba08ff..99c6fa98 100644 --- a/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts +++ b/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts @@ -38,6 +38,7 @@ export class DrawerApi { const defaultState: DrawerState = { class: '', closable: true, + closeIconPlacement: 'right', closeOnClickModal: true, closeOnPressEscape: true, confirmLoading: false, diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue b/packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue index 1b1a4a50..673a1d45 100644 --- a/packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue +++ b/packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue @@ -55,6 +55,7 @@ const { cancelText, class: drawerClass, closable, + closeIconPlacement, closeOnClickModal, closeOnPressEscape, confirmLoading, diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/breadcrumb-view.vue b/packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/breadcrumb-view.vue index 88ee9fd0..88ba0e62 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/breadcrumb-view.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/breadcrumb-view.vue @@ -3,8 +3,8 @@ import type { BreadcrumbProps } from './types'; import { useForwardPropsEmits } from 'radix-vue'; -import Breadcrumb from './breadcrumb.vue'; import BreadcrumbBackground from './breadcrumb-background.vue'; +import Breadcrumb from './breadcrumb.vue'; interface Props extends BreadcrumbProps { class?: any; @@ -17,6 +17,23 @@ const emit = defineEmits<{ select: [string] }>(); const forward = useForwardPropsEmits(props, emit); + diff --git a/packages/effects/layouts/src/authentication/authentication.vue b/packages/effects/layouts/src/authentication/authentication.vue index ceae8027..0c79591e 100644 --- a/packages/effects/layouts/src/authentication/authentication.vue +++ b/packages/effects/layouts/src/authentication/authentication.vue @@ -66,7 +66,7 @@ const { authPanelCenter, authPanelLeft, authPanelRight, isDark } = class="text-foreground lg:text-foreground ml-4 mt-4 flex flex-1 items-center sm:left-6 sm:top-6" > -

+

{{ appName }}

diff --git a/packages/locales/src/langs/en-US/common.json b/packages/locales/src/langs/en-US/common.json index 304ee3e7..440af82b 100644 --- a/packages/locales/src/langs/en-US/common.json +++ b/packages/locales/src/langs/en-US/common.json @@ -16,5 +16,7 @@ "disabled": "Disabled", "edit": "Edit", "delete": "Delete", - "create": "Create" + "create": "Create", + "yes": "Yes", + "no": "No" } diff --git a/packages/locales/src/langs/en-US/ui.json b/packages/locales/src/langs/en-US/ui.json index ae03d549..4d2f08ce 100644 --- a/packages/locales/src/langs/en-US/ui.json +++ b/packages/locales/src/langs/en-US/ui.json @@ -4,7 +4,10 @@ "selectRequired": "Please select {0}", "minLength": "{0} must be at least {1} characters", "maxLength": "{0} can be at most {1} characters", - "length": "{0} must be {1} characters long" + "length": "{0} must be {1} characters long", + "alreadyExists": "{0} `{1}` already exists", + "startWith": "{0} must start with `{1}`", + "invalidURL": "Please input a valid URL" }, "actionTitle": { "edit": "Modify {0}", diff --git a/packages/locales/src/langs/zh-CN/common.json b/packages/locales/src/langs/zh-CN/common.json index 86591f73..95ec5f7d 100644 --- a/packages/locales/src/langs/zh-CN/common.json +++ b/packages/locales/src/langs/zh-CN/common.json @@ -16,5 +16,7 @@ "disabled": "已禁用", "edit": "修改", "delete": "删除", - "create": "新增" + "create": "新增", + "yes": "是", + "no": "否" } diff --git a/packages/locales/src/langs/zh-CN/ui.json b/packages/locales/src/langs/zh-CN/ui.json index ce297472..ef2e507c 100644 --- a/packages/locales/src/langs/zh-CN/ui.json +++ b/packages/locales/src/langs/zh-CN/ui.json @@ -4,7 +4,10 @@ "selectRequired": "请选择{0}", "minLength": "{0}至少{1}个字符", "maxLength": "{0}最多{1}个字符", - "length": "{0}长度必须为{1}个字符" + "length": "{0}长度必须为{1}个字符", + "alreadyExists": "{0} `{1}` 已存在", + "startWith": "{0}必须以 {1} 开头", + "invalidURL": "请输入有效的链接" }, "actionTitle": { "edit": "修改{0}", diff --git a/playground/src/adapter/vxe-table.ts b/playground/src/adapter/vxe-table.ts index 1f329881..b4a1b3c8 100644 --- a/playground/src/adapter/vxe-table.ts +++ b/playground/src/adapter/vxe-table.ts @@ -5,7 +5,7 @@ import { h } from 'vue'; import { IconifyIcon } from '@vben/icons'; import { $te } from '@vben/locales'; import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; -import { isFunction, isString } from '@vben/utils'; +import { get, isFunction, isString } from '@vben/utils'; import { objectOmit } from '@vueuse/core'; import { Button, Image, Popconfirm, Tag } from 'ant-design-vue'; @@ -77,8 +77,8 @@ setupVbenVxeTable({ // 单元格渲染: Tag vxeUI.renderer.add('CellTag', { renderTableDefault({ options, props }, { column, row }) { - const value = row[column.field]; - const tagOptions = options || [ + const value = get(row, column.field); + const tagOptions = options ?? [ { color: 'success', label: $t('common.enabled'), value: 1 }, { color: 'error', label: $t('common.disabled'), value: 0 }, ]; @@ -87,7 +87,7 @@ setupVbenVxeTable({ Tag, { ...props, - ...objectOmit(tagItem, ['label']), + ...objectOmit(tagItem ?? {}, ['label']), }, { default: () => tagItem?.label ?? value }, ); diff --git a/playground/src/api/system/menu.ts b/playground/src/api/system/menu.ts new file mode 100644 index 00000000..507a5aec --- /dev/null +++ b/playground/src/api/system/menu.ts @@ -0,0 +1,158 @@ +import type { Recordable } from '@vben/types'; + +import { requestClient } from '#/api/request'; + +export namespace SystemMenuApi { + /** 徽标颜色集合 */ + export const BadgeVariants = [ + 'default', + 'destructive', + 'primary', + 'success', + 'warning', + ] as const; + /** 徽标类型集合 */ + export const BadgeTypes = ['dot', 'normal'] as const; + /** 菜单类型集合 */ + export const MenuTypes = [ + 'catalog', + 'menu', + 'embedded', + 'link', + 'button', + ] as const; + /** 系统菜单 */ + export interface SystemMenu { + [key: string]: any; + /** 后端权限标识 */ + authCode: string; + /** 子级 */ + children?: SystemMenu[]; + /** 组件 */ + component?: string; + /** 菜单ID */ + id: string; + /** 菜单元数据 */ + meta?: { + /** 激活时显示的图标 */ + activeIcon?: string; + /** 作为路由时,需要激活的菜单的Path */ + activePath?: string; + /** 固定在标签栏 */ + affixTab?: boolean; + /** 在标签栏固定的顺序 */ + affixTabOrder?: number; + /** 徽标内容(当徽标类型为normal时有效) */ + badge?: string; + /** 徽标类型 */ + badgeType?: (typeof BadgeTypes)[number]; + /** 徽标颜色 */ + badgeVariants?: (typeof BadgeVariants)[number]; + /** 在菜单中隐藏下级 */ + hideChildrenInMenu?: boolean; + /** 在面包屑中隐藏 */ + hideInBreadcrumb?: boolean; + /** 在菜单中隐藏 */ + hideInMenu?: boolean; + /** 在标签栏中隐藏 */ + hideInTab?: boolean; + /** 菜单图标 */ + icon?: string; + /** 内嵌Iframe的URL */ + iframeSrc?: string; + /** 是否缓存页面 */ + keepAlive?: boolean; + /** 外链页面的URL */ + link?: string; + /** 同一个路由最大打开的标签数 */ + maxNumOfOpenTab?: number; + /** 无需基础布局 */ + noBasicLayout?: boolean; + /** 是否在新窗口打开 */ + openInNewWindow?: boolean; + /** 菜单排序 */ + order?: number; + /** 额外的路由参数 */ + query?: Recordable; + /** 菜单标题 */ + title?: string; + }; + /** 菜单名称 */ + name: string; + /** 路由路径 */ + path: string; + /** 父级ID */ + pid: string; + /** 重定向 */ + redirect?: string; + /** 菜单类型 */ + type: (typeof MenuTypes)[number]; + } +} + +/** + * 获取菜单数据列表 + */ +async function getMenuList() { + return requestClient.get>( + '/system/menu/list', + ); +} + +async function isMenuNameExists( + name: string, + id?: SystemMenuApi.SystemMenu['id'], +) { + return requestClient.get('/system/menu/name-exists', { + params: { id, name }, + }); +} + +async function isMenuPathExists( + path: string, + id?: SystemMenuApi.SystemMenu['id'], +) { + return requestClient.get('/system/menu/path-exists', { + params: { id, path }, + }); +} + +/** + * 创建菜单 + * @param data 菜单数据 + */ +async function createMenu( + data: Omit, +) { + return requestClient.post('/system/menu', data); +} + +/** + * 更新菜单 + * + * @param id 菜单 ID + * @param data 菜单数据 + */ +async function updateMenu( + id: string, + data: Omit, +) { + return requestClient.put(`/system/menu/${id}`, data); +} + +/** + * 删除菜单 + * @param id 菜单 ID + */ +async function deleteMenu(id: string) { + return requestClient.delete(`/system/menu/${id}`); +} + +export { + createMenu, + deleteMenu, + getMenuList, + isMenuNameExists, + isMenuPathExists, + updateMenu, +}; diff --git a/playground/src/locales/langs/en-US/system.json b/playground/src/locales/langs/en-US/system.json index 20f9fd11..7c1ae2f8 100644 --- a/playground/src/locales/langs/en-US/system.json +++ b/playground/src/locales/langs/en-US/system.json @@ -9,5 +9,44 @@ "remark": "Remark", "operation": "Operation", "parentDept": "Parent Department" + }, + "menu": { + "title": "Menu Management", + "parent": "Parent Menu", + "menuTitle": "Title", + "menuName": "Menu Name", + "name": "Menu", + "type": "Type", + "typeCatalog": "Catalog", + "typeMenu": "Menu", + "typeButton": "Button", + "typeLink": "Link", + "typeEmbedded": "Embedded", + "icon": "Icon", + "activeIcon": "Active Icon", + "activePath": "Active Path", + "path": "Route Path", + "component": "Component", + "status": "Status", + "authCode": "Auth Code", + "badge": "Badge", + "operation": "Operation", + "linkSrc": "Link Address", + "affixTab": "Affix In Tabs", + "keepAlive": "Keep Alive", + "hideInMenu": "Hide In Menu", + "hideInTab": "Hide In Tabbar", + "hideChildrenInMenu": "Hide Children In Menu", + "hideInBreadcrumb": "Hide In Breadcrumb", + "advancedSettings": "Other Settings", + "activePathMustExist": "The path could not find a valid menu", + "activePathHelp": "When jumping to the current route, \nthe menu path that needs to be activated must be specified when it does not display in the navigation menu.", + "badgeType": { + "title": "Badge Type", + "dot": "Dot", + "normal": "Text", + "none": "None" + }, + "badgeVariants": "Badge Style" } } diff --git a/playground/src/locales/langs/zh-CN/system.json b/playground/src/locales/langs/zh-CN/system.json index 7fa1f1b1..04b9f8a9 100644 --- a/playground/src/locales/langs/zh-CN/system.json +++ b/playground/src/locales/langs/zh-CN/system.json @@ -9,5 +9,44 @@ "status": "状态", "title": "部门管理" }, + "menu": { + "activeIcon": "激活图标", + "activePath": "激活路径", + "activePathHelp": "跳转到当前路由时,需要激活的菜单路径。\n当不在导航菜单中显示时,需要指定激活路径", + "activePathMustExist": "该路径未能找到有效的菜单", + "advancedSettings": "其它设置", + "affixTab": "固定在标签", + "authCode": "权限标识", + "badge": "徽章内容", + "badgeVariants": "徽标样式", + "badgeType": { + "dot": "点", + "none": "无", + "normal": "文字", + "title": "徽标类型" + }, + "component": "页面组件", + "hideChildrenInMenu": "隐藏子菜单", + "hideInBreadcrumb": "在面包屑中隐藏", + "hideInMenu": "隐藏菜单", + "hideInTab": "在标签栏中隐藏", + "icon": "图标", + "keepAlive": "缓存标签页", + "linkSrc": "链接地址", + "menuName": "菜单名称", + "menuTitle": "标题", + "name": "菜单", + "operation": "操作", + "parent": "上级菜单", + "path": "路由地址", + "status": "状态", + "title": "菜单管理", + "type": "类型", + "typeButton": "按钮", + "typeCatalog": "目录", + "typeEmbedded": "内嵌", + "typeLink": "外链", + "typeMenu": "菜单" + }, "title": "系统管理" } diff --git a/playground/src/router/routes/index.ts b/playground/src/router/routes/index.ts index e6fb1440..275eb837 100644 --- a/playground/src/router/routes/index.ts +++ b/playground/src/router/routes/index.ts @@ -34,4 +34,14 @@ const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name); /** 有权限校验的路由列表,包含动态路由和静态路由 */ const accessRoutes = [...dynamicRoutes, ...staticRoutes]; -export { accessRoutes, coreRouteNames, routes }; + +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, componentKeys, coreRouteNames, routes }; diff --git a/playground/src/router/routes/modules/demos.ts b/playground/src/router/routes/modules/demos.ts index 422e5bf1..b283967c 100644 --- a/playground/src/router/routes/modules/demos.ts +++ b/playground/src/router/routes/modules/demos.ts @@ -147,8 +147,6 @@ const routes: RouteRecordRaw[] = [ { name: 'HideChildrenInMenuParentDemo', path: '/demos/features/hide-menu-children', - component: () => - import('#/views/demos/features/hide-menu-children/parent.vue'), meta: { hideChildrenInMenu: true, icon: 'ic:round-menu', @@ -160,10 +158,10 @@ const routes: RouteRecordRaw[] = [ path: '', component: () => import( - '#/views/demos/features/hide-menu-children/children.vue' + '#/views/demos/features/hide-menu-children/parent.vue' ), meta: { - hideInMenu: true, + // hideInMenu: true, title: $t('demos.features.hideChildrenInMenu'), }, }, @@ -174,7 +172,10 @@ const routes: RouteRecordRaw[] = [ import( '#/views/demos/features/hide-menu-children/children.vue' ), - meta: { title: $t('demos.features.hideChildrenInMenu') }, + meta: { + activePath: '/demos/features/hide-menu-children', + title: $t('demos.features.hideChildrenInMenu'), + }, }, ], }, diff --git a/playground/src/router/routes/modules/system.ts b/playground/src/router/routes/modules/system.ts index 0de34651..11dd4843 100644 --- a/playground/src/router/routes/modules/system.ts +++ b/playground/src/router/routes/modules/system.ts @@ -12,6 +12,15 @@ const routes: RouteRecordRaw[] = [ name: 'System', path: '/system', children: [ + { + path: '/system/menu', + name: 'SystemMenu', + meta: { + icon: 'mdi:menu', + title: $t('system.menu.title'), + }, + component: () => import('#/views/system/menu/list.vue'), + }, { path: '/system/dept', name: 'SystemDept', diff --git a/playground/src/views/demos/features/hide-menu-children/children.vue b/playground/src/views/demos/features/hide-menu-children/children.vue index 9b009e36..1959f96d 100644 --- a/playground/src/views/demos/features/hide-menu-children/children.vue +++ b/playground/src/views/demos/features/hide-menu-children/children.vue @@ -1,3 +1,23 @@ + + diff --git a/playground/src/views/demos/features/hide-menu-children/parent.vue b/playground/src/views/demos/features/hide-menu-children/parent.vue index 92087751..b732c1be 100644 --- a/playground/src/views/demos/features/hide-menu-children/parent.vue +++ b/playground/src/views/demos/features/hide-menu-children/parent.vue @@ -4,8 +4,14 @@ import { Fallback } from '@vben/common-ui'; diff --git a/playground/src/views/system/menu/data.ts b/playground/src/views/system/menu/data.ts new file mode 100644 index 00000000..75190b4a --- /dev/null +++ b/playground/src/views/system/menu/data.ts @@ -0,0 +1,109 @@ +import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemMenuApi } from '#/api/system/menu'; + +import { $t } from '#/locales'; + +export function getMenuTypeOptions() { + return [ + { + color: 'processing', + label: $t('system.menu.typeCatalog'), + value: 'catalog', + }, + { color: 'default', label: $t('system.menu.typeMenu'), value: 'menu' }, + { color: 'error', label: $t('system.menu.typeButton'), value: 'button' }, + { + color: 'success', + label: $t('system.menu.typeEmbedded'), + value: 'embedded', + }, + { color: 'warning', label: $t('system.menu.typeLink'), value: 'link' }, + ]; +} + +export function useColumns( + onActionClick: OnActionClickFn, +): VxeTableGridOptions['columns'] { + return [ + { + align: 'left', + field: 'meta.title', + fixed: 'left', + slots: { default: 'title' }, + title: $t('system.menu.menuTitle'), + treeNode: true, + width: 250, + }, + { + align: 'center', + cellRender: { name: 'CellTag', options: getMenuTypeOptions() }, + field: 'type', + title: $t('system.menu.type'), + width: 100, + }, + { + field: 'authCode', + title: $t('system.menu.authCode'), + width: 200, + }, + { + align: 'left', + field: 'path', + title: $t('system.menu.path'), + width: 200, + }, + + { + align: 'left', + field: 'component', + formatter: ({ row }) => { + switch (row.type) { + case 'catalog': + case 'menu': { + return row.component ?? ''; + } + case 'embedded': { + return row.meta?.iframeSrc ?? ''; + } + case 'link': { + return row.meta?.link ?? ''; + } + } + return ''; + }, + minWidth: 200, + title: $t('system.menu.component'), + }, + { + cellRender: { name: 'CellTag' }, + field: 'status', + title: $t('system.menu.status'), + width: 100, + }, + + { + align: 'right', + cellRender: { + attrs: { + nameField: 'name', + onClick: onActionClick, + }, + name: 'CellOperation', + options: [ + { + code: 'append', + text: '新增下级', + }, + 'edit', // 默认的编辑按钮 + 'delete', // 默认的删除按钮 + ], + }, + field: 'operation', + fixed: 'right', + headerAlign: 'center', + showOverflow: false, + title: $t('system.menu.operation'), + width: 200, + }, + ]; +} diff --git a/playground/src/views/system/menu/list.vue b/playground/src/views/system/menu/list.vue new file mode 100644 index 00000000..9ab45644 --- /dev/null +++ b/playground/src/views/system/menu/list.vue @@ -0,0 +1,162 @@ + + + diff --git a/playground/src/views/system/menu/modules/form.vue b/playground/src/views/system/menu/modules/form.vue new file mode 100644 index 00000000..f04cb4f7 --- /dev/null +++ b/playground/src/views/system/menu/modules/form.vue @@ -0,0 +1,521 @@ + + diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index de103d40..646d58c8 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -14,44 +14,44 @@ packages: - playground catalog: '@ast-grep/napi': ^0.32.3 - '@changesets/changelog-github': ^0.5.0 - '@changesets/cli': ^2.27.12 + '@changesets/changelog-github': ^0.5.1 + '@changesets/cli': ^2.28.1 '@changesets/git': ^3.0.2 '@clack/prompts': ^0.9.1 '@commitlint/cli': ^19.7.1 '@commitlint/config-conventional': ^19.7.1 '@ctrl/tinycolor': ^4.1.0 - '@eslint/js': ^9.20.0 - '@faker-js/faker': ^9.5.0 - '@iconify/json': ^2.2.307 + '@eslint/js': ^9.21.0 + '@faker-js/faker': ^9.5.1 + '@iconify/json': ^2.2.311 '@iconify/tailwind': ^1.2.0 '@iconify/vue': ^4.3.0 '@intlify/core-base': ^11.1.1 '@intlify/unplugin-vue-i18n': ^6.0.3 - '@jspm/generator': ^2.5.0 + '@jspm/generator': ^2.5.1 '@manypkg/get-packages': ^2.2.2 - '@nolebase/vitepress-plugin-git-changelog': ^2.14.0 + '@nolebase/vitepress-plugin-git-changelog': ^2.15.0 '@playwright/test': ^1.50.1 - '@pnpm/workspace.read-manifest': ^1000.0.2 - '@stylistic/stylelint-plugin': ^3.1.1 + '@pnpm/workspace.read-manifest': ^1000.1.0 + '@stylistic/stylelint-plugin': ^3.1.2 '@tailwindcss/nesting': 0.0.0-insiders.565cd3e '@tailwindcss/typography': ^0.5.16 - '@tanstack/vue-query': ^5.66.3 + '@tanstack/vue-query': ^5.66.9 '@tanstack/vue-store': ^0.7.0 '@types/archiver': ^6.0.3 '@types/eslint': ^9.6.1 '@types/html-minifier-terser': ^7.0.2 - '@types/jsonwebtoken': ^9.0.8 + '@types/jsonwebtoken': ^9.0.9 '@types/lodash.clonedeep': ^4.5.9 '@types/lodash.get': ^4.4.9 '@types/lodash.isequal': ^4.5.8 - '@types/node': ^22.13.4 + '@types/node': ^22.13.5 '@types/nprogress': ^0.2.3 '@types/postcss-import': ^14.0.3 '@types/qrcode': ^1.5.5 '@types/sortablejs': ^1.15.8 - '@typescript-eslint/eslint-plugin': ^8.24.0 - '@typescript-eslint/parser': ^8.24.0 + '@typescript-eslint/eslint-plugin': ^8.25.0 + '@typescript-eslint/parser': ^8.25.0 '@vee-validate/zod': ^4.15.0 '@vite-pwa/vitepress': ^0.5.3 '@vitejs/plugin-vue': ^5.2.1 @@ -65,7 +65,7 @@ catalog: ant-design-vue: ^4.2.6 archiver: ^7.0.1 autoprefixer: ^10.4.20 - axios: ^1.7.9 + axios: ^1.8.1 axios-mock-adapter: ^2.1.0 cac: ^6.7.14 chalk: ^5.4.1 @@ -85,9 +85,9 @@ catalog: depcheck: ^1.4.7 dotenv: ^16.4.7 echarts: ^5.6.0 - element-plus: ^2.9.4 - eslint: ^9.20.1 - eslint-config-turbo: ^2.4.2 + element-plus: ^2.9.5 + eslint: ^9.21.0 + eslint-config-turbo: ^2.4.4 eslint-plugin-command: ^0.2.7 eslint-plugin-eslint-comments: ^3.2.0 eslint-plugin-import-x: ^4.6.1 @@ -106,7 +106,7 @@ catalog: find-up: ^7.0.0 get-port: ^7.1.0 globals: ^15.15.0 - h3: ^1.15.0 + h3: ^1.15.1 happy-dom: ^16.8.1 html-minifier-terser: ^7.2.0 husky: ^9.1.7 @@ -127,22 +127,22 @@ catalog: pinia-plugin-persistedstate: ^4.2.0 pkg-types: ^1.3.1 playwright: ^1.50.1 - postcss: ^8.5.2 + postcss: ^8.5.3 postcss-antd-fixes: ^0.2.0 postcss-html: ^1.8.0 postcss-import: ^16.1.0 - postcss-preset-env: ^10.1.4 + postcss-preset-env: ^10.1.5 postcss-scss: ^4.0.9 - prettier: ^3.5.1 + prettier: ^3.5.2 prettier-plugin-tailwindcss: ^0.6.11 publint: ^0.2.12 qrcode: ^1.5.4 - radix-vue: ^1.9.14 + radix-vue: ^1.9.17 resolve.exports: ^2.0.3 rimraf: ^6.0.1 - rollup: ^4.34.7 + rollup: ^4.34.8 rollup-plugin-visualizer: ^5.14.0 - sass: ^1.85.0 + sass: ^1.85.1 sortablejs: ^1.15.6 stylelint: ^16.14.1 stylelint-config-recess-order: ^5.1.1 @@ -152,26 +152,26 @@ catalog: stylelint-config-standard: ^36.0.1 stylelint-order: ^6.0.4 stylelint-prettier: ^5.0.3 - stylelint-scss: ^6.11.0 + stylelint-scss: ^6.11.1 tailwind-merge: ^2.6.0 tailwindcss: ^3.4.17 tailwindcss-animate: ^1.0.7 theme-colors: ^0.1.0 tippy.js: ^6.2.5 - turbo: ^2.4.2 + turbo: ^2.4.4 typescript: ^5.7.3 - unbuild: ^3.3.1 + unbuild: ^3.5.0 unplugin-element-plus: ^0.9.1 vee-validate: ^4.15.0 - vite: ^6.1.0 + vite: ^6.2.0 vite-plugin-compression: ^0.5.1 - vite-plugin-dts: ^4.5.0 + vite-plugin-dts: ^4.5.1 vite-plugin-html: ^3.2.2 vite-plugin-lazy-import: ^1.0.7 vite-plugin-pwa: ^0.21.1 vite-plugin-vue-devtools: ^7.7.2 vitepress: ^1.6.3 - vitepress-plugin-group-icons: ^1.3.5 + vitepress-plugin-group-icons: ^1.3.6 vitest: ^2.1.9 vue: ^3.5.13 vue-eslint-parser: ^9.4.3 @@ -180,7 +180,7 @@ catalog: vue-router: ^4.5.0 vue-tippy: ^6.6.0 vue-tsc: 2.1.10 - vxe-pc-ui: ^4.3.87 + vxe-pc-ui: ^4.3.99 vxe-table: 4.10.0 watermark-js-plus: ^1.5.8 zod: ^3.24.2