task-6(壳层/路由): 定义后端菜单树到侧边栏节点映射规则并落地

新增 src/layout/menu-mapper.ts:toSidebarEntries 处理分组/顶级路由叶/畸形节点,
groupKeysForActive 按当前路由展开激活分组;AdminLayout 菜单区改用映射结果,
删除旧 visibleChildren/canSee 死逻辑;8 用例覆盖空树/单叶/缺 route 过滤等。
This commit is contained in:
2026-09-05 12:17:17 +08:00
parent 4a0e464308
commit 26edb9e6ed
4 changed files with 158 additions and 31 deletions
+12 -30
View File
@@ -5,7 +5,7 @@ import { ElMessage } from 'element-plus'
import { useAdminSessionStore } from '@/stores/admin-session'
import { joinAdminPath } from '@/config/app'
import { pageTitleOf, topbarUserOf } from '@/layout/topbar-model'
import type { AdminMenuNode } from '@/types/admin'
import { groupKeysForActive, toSidebarEntries } from '@/layout/menu-mapper'
const route = useRoute()
const router = useRouter()
@@ -13,27 +13,12 @@ const session = useAdminSessionStore()
const collapsed = ref(false)
const activePath = computed(() => route.path)
const groups = computed(() => session.menuTree.filter((node) => node.children?.length))
const menuEntries = computed(() => toSidebarEntries(session.menuTree))
const openedKeys = computed(() => groupKeysForActive(menuEntries.value, route.path))
const pageTitle = computed(() => pageTitleOf(route.meta.title as string | undefined))
const userVm = computed(() => topbarUserOf(session.user))
const logoUrl = joinAdminPath('assets', 'logo.jpg')
function menuPath(node: AdminMenuNode): string {
return node.route || '/account/users'
}
function visibleChildren(node: AdminMenuNode) {
return (node.children || []).filter((child) => child.route)
}
function canSee(node: AdminMenuNode) {
return session.isSuperAdmin.valueOf() || hasMenu(node, String(route.meta.menuKey || '')) || node.route === activePath.value
}
function hasMenu(node: AdminMenuNode, key: string): boolean {
return node.key === key || (node.children || []).some((child) => hasMenu(child, key))
}
async function signOut() {
try {
await session.signOut()
@@ -55,23 +40,22 @@ async function signOut() {
</div>
<el-scrollbar class="admin-menu-scroll">
<el-menu :default-active="activePath" :collapse="collapsed" router class="admin-menu">
<template v-for="group in groups" :key="group.key">
<el-sub-menu :index="group.key">
<el-menu :default-active="activePath" :default-openeds="openedKeys" :collapse="collapsed" router class="admin-menu">
<template v-for="entry in menuEntries" :key="entry.key">
<el-sub-menu v-if="entry.kind === 'group'" :index="entry.key">
<template #title>
<span class="menu-group-title">{{ group.name }}</span>
<span class="menu-group-title">{{ entry.name }}</span>
</template>
<el-menu-item
v-for="item in visibleChildren(group)"
:key="item.key"
:index="menuPath(item)"
>
<el-menu-item v-for="item in entry.children" :key="item.key" :index="item.route">
{{ item.name }}
</el-menu-item>
</el-sub-menu>
<el-menu-item v-else :key="entry.key" :index="entry.route">
{{ entry.name }}
</el-menu-item>
</template>
</el-menu>
<el-empty v-if="!groups.length && !session.loading" description="暂无可用菜单" :image-size="72" />
<el-empty v-if="!menuEntries.length && !session.loading" description="暂无可用菜单" :image-size="72" />
</el-scrollbar>
<button class="sidebar-collapse" type="button" @click="collapsed = !collapsed">
@@ -100,5 +84,3 @@ async function signOut() {
</section>
</div>
</template>