26edb9e6ed
新增 src/layout/menu-mapper.ts:toSidebarEntries 处理分组/顶级路由叶/畸形节点, groupKeysForActive 按当前路由展开激活分组;AdminLayout 菜单区改用映射结果, 删除旧 visibleChildren/canSee 死逻辑;8 用例覆盖空树/单叶/缺 route 过滤等。
47 lines
1.8 KiB
TypeScript
47 lines
1.8 KiB
TypeScript
import type { AdminMenuNode } from '../types/admin'
|
|
|
|
export interface SidebarItem {
|
|
key: string
|
|
name: string
|
|
route: string
|
|
}
|
|
|
|
/** 侧边栏渲染项:分组(含子页面)或单页面项(含顶级路由叶节点)。 */
|
|
export type SidebarEntry =
|
|
| { kind: 'group'; key: string; name: string; children: SidebarItem[] }
|
|
| { kind: 'item'; key: string; name: string; route: string }
|
|
|
|
/**
|
|
* 后端菜单树 -> 侧边栏节点映射规则(任务 6):
|
|
* - 有可路由子节点的节点渲染为分组,只保留带 route 的子项;
|
|
* - 无子节点但有 route 的节点渲染为单页面项(不丢顶级叶菜单);
|
|
* - 既无子节点也无 route 的节点跳过(点不进去的纯占位不展示)。
|
|
*/
|
|
export function toSidebarEntries(nodes: AdminMenuNode[] | null | undefined): SidebarEntry[] {
|
|
const entries: SidebarEntry[] = []
|
|
for (const node of nodes || []) {
|
|
const name = node.name || ''
|
|
const key = node.key || ''
|
|
const children: SidebarItem[] = (node.children || [])
|
|
.filter((child): child is AdminMenuNode & { route: string } => Boolean(child.route))
|
|
.map((child) => ({ key: child.key || '', name: child.name || '', route: child.route }))
|
|
if (children.length > 0) {
|
|
entries.push({ kind: 'group', key, name, children })
|
|
} else if (node.route) {
|
|
entries.push({ kind: 'item', key, name, route: node.route })
|
|
}
|
|
}
|
|
return entries
|
|
}
|
|
|
|
/** 计算需默认展开的分组 key:分组下含当前激活路由子项时返回该分组 key。 */
|
|
export function groupKeysForActive(entries: SidebarEntry[], activeRoute: string): string[] {
|
|
const keys: string[] = []
|
|
for (const entry of entries) {
|
|
if (entry.kind === 'group' && entry.children.some((child) => child.route === activeRoute)) {
|
|
keys.push(entry.key)
|
|
}
|
|
}
|
|
return keys
|
|
}
|