fix: 修复后台菜单排序生效

This commit is contained in:
sunlei 2026-06-29 18:06:42 +08:00
parent 4ba24f2b60
commit 083d5a4720
4 changed files with 87 additions and 4 deletions

View File

@ -46,7 +46,7 @@ pnpm run build:antdv-next
## 业务页面
- 系统管理 / 菜单管理维护后端 `admin_menu.sort` 排序字段,菜单树展示仍以后端返回顺序为准;默认首页入口收敛到环境总览 `/analytics`,不再保留假工作台 `/workspace` 页面。
- 系统管理 / 菜单管理维护后端 `admin_menu.sort` 排序字段`/menu/all` caller 会把后端 `sort` 映射到 Vben 菜单生成器读取的 `meta.order`,保证侧边栏菜单展示以后端返回顺序为准。默认首页入口收敛到环境总览 `/analytics`,不再保留假工作台 `/workspace` 页面。
- 系统管理 / 站内信是日志级通知列表,只展示 API 错误、QQBot 下线、NapCat 离线等后端自动捕获事件;页面提供筛选、处理/重新打开、置顶和删除,不提供人工新增或编辑。
- QQBot / 账号连接页拆分 OneBot 连接、QQ 登录、NapCat 运行和运行说明列;更新登录通过 SSE 展示 quick / password / captcha / new-device / qrcode 每步中文进度,密码登录触发 QQ 安全验证时在弹窗内完成腾讯验证码并回交 API新设备验证二维码和腾讯验证码分开展示行操作“运行态”打开只读抽屉展示 NapCat runtime/protocol/session behavior profile、风险模式和登录事件证据。
- QQBot / 插件平台页保留在线命令能力表,并提供 manifest 校验、本地插件安装、安装记录、运行事件和账号绑定抽屉,接口走 `/qqbot/plugin-platform/*`

View File

@ -109,4 +109,52 @@ describe('core menu api', () => {
},
]);
});
it('uses backend sort as the authoritative menu order before menu generation', async () => {
requestClientGet.mockResolvedValue([
{
name: 'Dashboard',
path: '/dashboard',
sort: 200,
meta: {
order: -1,
title: 'Dashboard',
},
},
{
name: 'QqBot',
path: '/qqbot',
sort: 1,
meta: {
title: 'QQBot',
},
children: [
{
name: 'QqBotAccount',
path: '/qqbot/account',
component: '/qqbot/account/list',
sort: 1,
meta: {
title: '账号连接',
},
},
],
},
{
name: 'Blog',
path: '/blog',
sort: 0,
meta: {
order: 100,
title: '博客管理',
},
},
]);
const { getAllMenusApi } = await import('./menu');
const menus = await getAllMenusApi();
expect(menus.map((menu) => menu.meta?.order)).toEqual([200, 1, 0]);
expect(menus[1]?.children?.[0]?.meta?.order).toBe(1);
});
});

View File

@ -97,15 +97,49 @@ export function isSupportedAdminMenuName(name?: null | string | symbol) {
return typeof name === 'string' && SUPPORTED_ADMIN_MENU_NAMES.has(name);
}
/**
* Vben `meta.order`
* @param menu - `/menu/all` `meta.order` DB `sort`
* @returns `sort` DB
*/
function normalizeBackendMenuOrder(
menu: RouteRecordStringComponent,
): RouteRecordStringComponent {
const sortOrder =
typeof menu.sort === 'number' && Number.isFinite(menu.sort)
? menu.sort
: undefined;
if (sortOrder === undefined) {
return menu;
}
const meta = menu.meta ?? { title: String(menu.name ?? '') };
return {
...menu,
meta: {
...meta,
order: sortOrder,
},
};
}
/**
* Admin 使
* @param menus - `/menu/all`
* @returns DB `sort` `meta.order`
*/
function filterSupportedAdminMenus(
menus: RouteRecordStringComponent[],
): RouteRecordStringComponent[] {
return menus
.map((menu) => {
const children = menu.children
? filterSupportedAdminMenus(menu.children)
const normalizedMenu = normalizeBackendMenuOrder(menu);
const children = normalizedMenu.children
? filterSupportedAdminMenus(normalizedMenu.children)
: undefined;
const menuWithoutChildren = { ...menu };
const menuWithoutChildren = { ...normalizedMenu };
delete menuWithoutChildren.children;
return {

View File

@ -130,6 +130,7 @@ type RouteRecordStringComponent<T = string> = Omit<
> & {
children?: RouteRecordStringComponent<T>[];
component: T;
sort?: number;
};
type ComponentRecordType = Record<string, () => Promise<Component>>;