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(() => 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(() => { const groups = new Map() 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 } }