72 lines
2.3 KiB
TypeScript
72 lines
2.3 KiB
TypeScript
import { computed } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
|
|
import { useUserStore } from '@/store/user'
|
|
import { ROLE_ADMIN } from '@/utils/constants'
|
|
|
|
/** 菜单节点:从路由表派生,避免菜单标题与页面标题两处维护漂移。 */
|
|
export interface MenuNode {
|
|
path: string
|
|
title: string
|
|
order: number
|
|
group?: string
|
|
}
|
|
|
|
export interface MenuGroup {
|
|
name: string
|
|
items: MenuNode[]
|
|
}
|
|
|
|
/**
|
|
* 侧边菜单数据。
|
|
*
|
|
* 规则:
|
|
* - 取路由表中带 `meta.title` 且未被 `meta.menu === false` 排除的记录
|
|
* - `meta.requiresAdmin` 的项仅管理员可见(与路由守卫双保险,隐藏入口在前)
|
|
* - `meta.group` 相同的项聚合为一个子菜单;无 group 的为顶层菜单项
|
|
* - 按 `meta.order` 排序
|
|
*/
|
|
export function useAppMenu() {
|
|
const router = useRouter()
|
|
const userStore = useUserStore()
|
|
|
|
const isAdmin = computed(() => userStore.roles.includes(ROLE_ADMIN))
|
|
const userRoles = computed(() => userStore.roles)
|
|
|
|
const menuNodes = computed<MenuNode[]>(() =>
|
|
router
|
|
.getRoutes()
|
|
.filter((record) => Boolean(record.meta.title) && record.meta.menu !== false)
|
|
.filter((record) => !record.meta.requiresAdmin || isAdmin.value)
|
|
// 职能页面(审批等):任一列入角色命中即可见(admin 须显式列入集合)
|
|
.filter(
|
|
(record) =>
|
|
!record.meta.allowedRoles || record.meta.allowedRoles.some((role) => userRoles.value.includes(role)),
|
|
)
|
|
.map((record) => ({
|
|
path: record.path,
|
|
title: record.meta.title ?? '',
|
|
order: record.meta.order ?? 99,
|
|
group: record.meta.group,
|
|
}))
|
|
.sort((a, b) => a.order - b.order),
|
|
)
|
|
|
|
/** 无分组的顶层菜单项 */
|
|
const topLevelMenus = computed(() => menuNodes.value.filter((node) => !node.group))
|
|
|
|
/** 分组菜单(按 group 名聚合,组内保持 order 顺序) */
|
|
const groupedMenus = computed<MenuGroup[]>(() => {
|
|
const groups = new Map<string, MenuNode[]>()
|
|
for (const node of menuNodes.value) {
|
|
if (!node.group) continue
|
|
const items = groups.get(node.group) ?? []
|
|
items.push(node)
|
|
groups.set(node.group, items)
|
|
}
|
|
return [...groups.entries()].map(([name, items]) => ({ name, items }))
|
|
})
|
|
|
|
return { topLevelMenus, groupedMenus }
|
|
}
|