import type { RouteRecordNormalized, Router } from 'vue-router' import { useUserStore } from '@/store/user' import { reportPermissions, routePermissions } from './permissions' /** * 路由准入与落地页解析(菜单、守卫、登录回跳共用一份规则)。 * * 之前守卫两处兜底硬编码 `/dashboard`,工作台收紧权限后普通用户会被 * 反复重定向到同一受限页(无限循环)。集中在这里后,任何角色的落地页 * 都必须先通过同一准入判定。 */ /** 可作为菜单项/落地页:有标题、未被 menu:false 排除、非免登录页 */ export function isMenuRecord(record: RouteRecordNormalized): boolean { return Boolean(record.meta.title) && record.meta.menu !== false && !record.meta.public } /** 当前角色是否可访问该路由(与守卫、菜单过滤同源) */ export function canAccessRoute(record: RouteRecordNormalized, roles: string[]): boolean { void roles if (record.meta.public) return false if (record.path === '/no-access') return true const permission = routePermissions[record.path] return !!permission && useUserStore().hasPermission(permission) } /** * 落地页候选(按优先级):工作台 → 业务数据 → 回款。 * 普通用户没有工作台权限时落到业务数据页,而不是被菜单 order 带到 * 基础数据管理页;候选都不可访问时再按菜单顺序兜底。 */ const HOME_CANDIDATES = ['/dashboard', '/business/businesses', '/business/receipts'] function matchedRecord(router: Router, path: string): RouteRecordNormalized | undefined { const matched = router.resolve(path).matched return matched[matched.length - 1] } /** 当前角色可访问的默认首页;无任何可访问页时回登录页(防循环) */ export function firstAccessiblePath(router: Router, roles: string[]): string { for (const path of HOME_CANDIDATES) { const record = matchedRecord(router, path) if (record && isMenuRecord(record) && canAccessRoute(record, roles)) return path } const fallback = router .getRoutes() .filter(isMenuRecord) .filter((record) => canAccessRoute(record, roles)) .sort((a, b) => (a.meta.order ?? 99) - (b.meta.order ?? 99)) return fallback[0]?.path ?? '/no-access' } /** 登录回跳目标是否可信且可访问(防开放重定向与回跳受限页) */ export function canAccessPath(router: Router, path: string, roles: string[]): boolean { if (!path.startsWith('/') || path.startsWith('//')) return false const resolved = router.resolve(path) if (resolved.name === 'report-print') { const resource = reportPermissions[String(resolved.params.code)] return !!resource && useUserStore().hasPermission(resource + ':export') } const record = matchedRecord(router, path) return Boolean(record && isMenuRecord(record) && canAccessRoute(record, roles)) }