library-picturebook-activity/frontend/src/router/index.ts

668 lines
21 KiB
TypeScript
Raw Normal View History

2026-01-09 18:14:35 +08:00
import { createRouter, createWebHistory } from "vue-router"
import type { RouteRecordRaw } from "vue-router"
import { nextTick } from "vue"
import { useAuthStore } from "@/stores/auth"
import { convertMenusToRoutes } from "@/utils/menu"
import "@/types/router"
2025-11-23 14:04:20 +08:00
// 基础路由(不需要动态加载的)
const baseRoutes: RouteRecordRaw[] = [
{
path: "/:tenantCode/login",
name: "Login",
component: () => import("@/views/auth/Login.vue"),
meta: { requiresAuth: false },
},
{
path: "/login",
name: "LoginFallback",
component: () => import("@/views/auth/Login.vue"),
meta: { requiresAuth: false },
},
2026-01-09 18:14:35 +08:00
{
path: "/model-viewer",
name: "ModelViewer",
component: () => import("@/views/model/ModelViewer.vue"),
meta: { requiresAuth: false },
},
2025-11-23 14:04:20 +08:00
{
path: "/:tenantCode",
name: "Main",
component: () => import("@/layouts/BasicLayout.vue"),
redirect: (to) => {
2026-01-09 18:14:35 +08:00
// 默认跳转到 workbench路由守卫会处理重定向到第一个菜单
return { path: `/${to.params.tenantCode}/workbench` }
2025-11-23 14:04:20 +08:00
},
meta: {},
children: [
2026-01-08 09:17:46 +08:00
// 创建比赛路由(不需要在菜单中显示)
{
path: "contests/create",
name: "ContestsCreate",
component: () => import("@/views/contests/Create.vue"),
meta: {
title: "创建比赛",
requiresAuth: true,
permissions: ["contest:create"],
},
},
// 赛事详情路由(不需要在菜单中显示)
{
path: "contests/:id",
name: "ContestsDetail",
component: () => import("@/views/contests/Detail.vue"),
meta: {
title: "赛事详情",
requiresAuth: true,
2026-01-12 20:04:11 +08:00
permissions: ["contest:read", "activity:read"],
2026-01-08 09:17:46 +08:00
},
},
// 编辑比赛路由(不需要在菜单中显示)
{
path: "contests/:id/edit",
name: "ContestsEdit",
component: () => import("@/views/contests/Create.vue"),
meta: {
title: "编辑比赛",
requiresAuth: true,
permissions: ["contest:update"],
},
},
// 赛事评委管理路由(不需要在菜单中显示)
{
path: "contests/:id/judges",
name: "ContestsJudges",
component: () => import("@/views/contests/judges/Index.vue"),
meta: {
title: "评委管理",
requiresAuth: true,
permissions: ["contest:read"],
},
},
2026-01-09 18:14:35 +08:00
// 个人赛报名路由
{
path: "contests/:id/register/individual",
name: "ContestsRegisterIndividual",
component: () => import("@/views/contests/RegisterIndividual.vue"),
meta: {
title: "赛事报名(个人赛)",
requiresAuth: true,
},
},
// 团队赛报名路由
{
path: "contests/:id/register/team",
name: "ContestsRegisterTeam",
component: () => import("@/views/contests/RegisterTeam.vue"),
meta: {
title: "赛事报名(团队赛)",
requiresAuth: true,
},
},
// 报名记录路由
{
path: "contests/registrations/:id/records",
name: "RegistrationRecords",
component: () => import("@/views/contests/registrations/Records.vue"),
meta: {
title: "报名记录",
requiresAuth: true,
permissions: ["contest:registration:read"],
},
},
2026-01-12 16:06:34 +08:00
// 评审进度详情路由
{
path: "contests/reviews/:id/progress",
name: "ReviewProgressDetail",
component: () => import("@/views/contests/reviews/ProgressDetail.vue"),
meta: {
title: "评审进度详情",
requiresAuth: true,
permissions: ["review:read"],
},
},
// 参赛作品详情列表路由
{
path: "contests/works/:id/list",
name: "WorksDetail",
component: () => import("@/views/contests/works/WorksDetail.vue"),
meta: {
title: "参赛作品详情",
requiresAuth: true,
permissions: ["work:read"],
},
},
2026-01-09 18:14:35 +08:00
// 作业提交记录路由
{
path: "homework/submissions",
name: "HomeworkSubmissions",
component: () => import("@/views/homework/Submissions.vue"),
meta: {
title: "作业提交记录",
requiresAuth: true,
permissions: ["homework:read"],
},
},
// 学生作业详情路由
{
path: "homework/detail/:id",
name: "HomeworkDetail",
component: () => import("@/views/homework/StudentDetail.vue"),
meta: {
title: "作业详情",
requiresAuth: true,
permissions: ["homework:read", "homework:student:read"],
},
},
2026-01-12 20:04:11 +08:00
// 教师我的指导路由
{
path: "student-activities/guidance",
name: "TeacherGuidance",
component: () => import("@/views/contests/Guidance.vue"),
meta: {
title: "我的指导",
requiresAuth: true,
permissions: ["activity:read"],
},
},
2025-11-23 14:04:20 +08:00
// 动态路由将在这里添加
],
},
{
path: "/403",
name: "Forbidden",
component: () => import("@/views/error/403.vue"),
meta: { requiresAuth: false },
},
{
path: "/:pathMatch(.*)*",
name: "NotFound",
component: () => import("@/views/error/404.vue"),
},
2026-01-09 18:14:35 +08:00
]
2025-11-23 14:04:20 +08:00
const router = createRouter({
history: createWebHistory(),
routes: baseRoutes,
2026-01-09 18:14:35 +08:00
})
2025-11-23 14:04:20 +08:00
// 标记是否已经添加了动态路由
2026-01-09 18:14:35 +08:00
let dynamicRoutesAdded = false
// 保存已添加的路由名称,用于清理
let addedRouteNames: string[] = []
// 保存上次的菜单数据,用于检测变化
let lastMenusHash: string = ""
/**
*
*/
export function resetDynamicRoutes(): void {
dynamicRoutesAdded = false
addedRouteNames = []
lastMenusHash = ""
}
2025-11-23 14:04:20 +08:00
/**
*
* @returns Promise resolve
*/
2026-01-09 18:14:35 +08:00
export async function addDynamicRoutes(): Promise<void> {
const authStore = useAuthStore()
if (!authStore.menus || authStore.menus.length === 0) {
// 如果没有菜单,重置标记
if (dynamicRoutesAdded) {
resetDynamicRoutes()
}
return
}
// 计算菜单数据的哈希值,用于检测变化
const menusHash = JSON.stringify(
authStore.menus.map((m) => ({ id: m.id, path: m.path }))
)
2025-11-23 14:04:20 +08:00
2026-01-09 18:14:35 +08:00
// 如果菜单数据没有变化且已经添加过路由,直接返回
if (dynamicRoutesAdded && menusHash === lastMenusHash) {
return
}
// 如果已经添加过路由,先移除旧路由
if (dynamicRoutesAdded && addedRouteNames.length > 0) {
addedRouteNames.forEach((routeName) => {
if (router.hasRoute(routeName)) {
router.removeRoute(routeName)
}
})
addedRouteNames = []
}
2025-11-23 14:04:20 +08:00
// 将菜单转换为路由
2026-01-09 18:14:35 +08:00
const dynamicRoutes = convertMenusToRoutes(authStore.menus)
if (dynamicRoutes.length === 0) {
return
}
2025-11-23 14:04:20 +08:00
// 添加动态路由到根路由下
dynamicRoutes.forEach((route) => {
2026-01-09 18:14:35 +08:00
router.addRoute("Main", route)
if (route.name) {
addedRouteNames.push(route.name as string)
}
})
dynamicRoutesAdded = true
lastMenusHash = menusHash
2025-11-23 14:04:20 +08:00
2026-01-09 18:14:35 +08:00
// 等待多个 tick确保路由已完全注册
await nextTick()
await nextTick()
await nextTick()
2025-11-23 14:04:20 +08:00
2026-01-09 18:14:35 +08:00
// 额外等待一小段时间,确保路由系统完全更新
await new Promise((resolve) => setTimeout(resolve, 50))
2025-11-23 14:04:20 +08:00
}
/**
*
*/
function extractTenantCodeFromPath(path: string): string | null {
2026-01-09 18:14:35 +08:00
const match = path.match(/^\/([^/]+)/)
return match ? match[1] : null
2025-11-23 14:04:20 +08:00
}
/**
*
*/
function buildPathWithTenantCode(tenantCode: string, path: string): string {
// 如果路径已经包含租户编码,直接返回
if (path.startsWith(`/${tenantCode}/`)) {
2026-01-09 18:14:35 +08:00
return path
2025-11-23 14:04:20 +08:00
}
// 移除开头的斜杠(如果有)
2026-01-09 18:14:35 +08:00
const cleanPath = path.startsWith("/") ? path.slice(1) : path
2025-11-23 14:04:20 +08:00
// 如果路径是根路径,返回租户编码路径
if (cleanPath === "" || cleanPath === tenantCode) {
2026-01-09 18:14:35 +08:00
return `/${tenantCode}/workbench`
2025-11-23 14:04:20 +08:00
}
2026-01-09 18:14:35 +08:00
return `/${tenantCode}/${cleanPath}`
2025-11-23 14:04:20 +08:00
}
router.beforeEach(async (to, _from, next) => {
2026-01-09 18:14:35 +08:00
const authStore = useAuthStore()
2025-11-23 14:04:20 +08:00
// 从URL中提取租户编码
2026-01-09 18:14:35 +08:00
const tenantCodeFromUrl = extractTenantCodeFromPath(to.path)
2025-11-23 14:04:20 +08:00
// 如果 token 存在但用户信息不存在,先获取用户信息
if (authStore.token && !authStore.user) {
try {
2026-01-09 18:14:35 +08:00
const userInfo = await authStore.fetchUserInfo()
2025-11-23 14:04:20 +08:00
// 如果获取用户信息失败或用户信息为空,跳转到登录页
if (!userInfo) {
2026-01-09 18:14:35 +08:00
authStore.logout()
2025-11-23 14:04:20 +08:00
const tenantCode =
2026-01-09 18:14:35 +08:00
tenantCodeFromUrl || extractTenantCodeFromPath(to.path)
2025-11-23 14:04:20 +08:00
if (tenantCode) {
next({
path: `/${tenantCode}/login`,
query: { redirect: to.fullPath },
2026-01-09 18:14:35 +08:00
})
2025-11-23 14:04:20 +08:00
} else {
2026-01-09 18:14:35 +08:00
next({ name: "LoginFallback", query: { redirect: to.fullPath } })
2025-11-23 14:04:20 +08:00
}
2026-01-09 18:14:35 +08:00
return
2025-11-23 14:04:20 +08:00
}
// 获取用户信息后,检查租户编码一致性
2026-01-09 18:14:35 +08:00
const userTenantCode = userInfo?.tenantCode
2025-11-23 14:04:20 +08:00
if (userTenantCode) {
// 如果URL中的租户编码与用户信息不一致更正URL
if (tenantCodeFromUrl && tenantCodeFromUrl !== userTenantCode) {
const correctedPath = buildPathWithTenantCode(
userTenantCode,
to.path.replace(`/${tenantCodeFromUrl}`, "")
2026-01-09 18:14:35 +08:00
)
next({ path: correctedPath, replace: true })
return
2025-11-23 14:04:20 +08:00
}
// 如果URL中没有租户编码添加租户编码
if (!tenantCodeFromUrl) {
2026-01-09 18:14:35 +08:00
const correctedPath = buildPathWithTenantCode(userTenantCode, to.path)
next({ path: correctedPath, replace: true })
return
2025-11-23 14:04:20 +08:00
}
}
// 获取用户信息后,添加动态路由并等待生效
2026-01-09 18:14:35 +08:00
await addDynamicRoutes()
2025-11-23 14:04:20 +08:00
// 保存原始目标路径
2026-01-09 18:14:35 +08:00
const targetPath = to.fullPath
2025-11-23 14:04:20 +08:00
// 路由已生效,重新解析目标路由
2026-01-09 18:14:35 +08:00
const resolved = router.resolve(targetPath)
2025-11-23 14:04:20 +08:00
// 如果解析后的路由不是404说明路由存在重新导航
if (resolved.name !== "NotFound") {
2026-01-09 18:14:35 +08:00
next({ path: targetPath, replace: true })
2025-11-23 14:04:20 +08:00
} else {
2026-01-09 18:14:35 +08:00
// 如果路由不存在,尝试重定向到用户第一个菜单
if (authStore.menus && authStore.menus.length > 0) {
const findFirstMenuPath = (menus: any[]): string | null => {
for (const menu of menus) {
if (menu.path && menu.component) {
// 移除开头的斜杠
return menu.path.startsWith("/")
? menu.path.slice(1)
: menu.path
}
if (menu.children && menu.children.length > 0) {
const childPath = findFirstMenuPath(menu.children)
if (childPath) return childPath
}
}
return null
}
const firstMenuPath = findFirstMenuPath(authStore.menus)
if (firstMenuPath) {
const user = authStore.user as { tenantCode?: string } | null
const userTenantCode = user?.tenantCode
const tenantCode =
tenantCodeFromUrl ||
extractTenantCodeFromPath(to.path) ||
userTenantCode
if (tenantCode) {
next({ path: `/${tenantCode}/${firstMenuPath}`, replace: true })
return
}
}
}
2025-11-23 14:04:20 +08:00
// 如果路由不存在但需要认证跳转到登录页而不是404
if (to.meta.requiresAuth === false) {
// 路由确实不存在允许继续会显示404页面
2026-01-09 18:14:35 +08:00
next()
2025-11-23 14:04:20 +08:00
} else {
const tenantCode =
2026-01-09 18:14:35 +08:00
tenantCodeFromUrl || extractTenantCodeFromPath(to.path)
2025-11-23 14:04:20 +08:00
if (tenantCode) {
next({
path: `/${tenantCode}/login`,
query: { redirect: to.fullPath },
2026-01-09 18:14:35 +08:00
})
2025-11-23 14:04:20 +08:00
} else {
2026-01-09 18:14:35 +08:00
next({ name: "LoginFallback", query: { redirect: to.fullPath } })
2025-11-23 14:04:20 +08:00
}
}
}
2026-01-09 18:14:35 +08:00
return
2025-11-23 14:04:20 +08:00
} catch (error) {
// 获取失败,清除 token 并跳转到登录页
2026-01-09 18:14:35 +08:00
console.error("获取用户信息失败:", error)
authStore.logout()
const tenantCode = tenantCodeFromUrl || extractTenantCodeFromPath(to.path)
2025-11-23 14:04:20 +08:00
if (tenantCode) {
next({
path: `/${tenantCode}/login`,
query: { redirect: to.fullPath },
2026-01-09 18:14:35 +08:00
})
2025-11-23 14:04:20 +08:00
} else {
2026-01-09 18:14:35 +08:00
next({ name: "LoginFallback", query: { redirect: to.fullPath } })
2025-11-23 14:04:20 +08:00
}
2026-01-09 18:14:35 +08:00
return
2025-11-23 14:04:20 +08:00
}
}
// 如果 token 不存在,但需要认证,跳转到登录页
if (!authStore.token && to.meta.requiresAuth !== false) {
2026-01-09 18:14:35 +08:00
const tenantCode = tenantCodeFromUrl || extractTenantCodeFromPath(to.path)
2025-11-23 14:04:20 +08:00
if (tenantCode) {
next({
path: `/${tenantCode}/login`,
query: { redirect: to.fullPath },
2026-01-09 18:14:35 +08:00
})
2025-11-23 14:04:20 +08:00
} else {
2026-01-09 18:14:35 +08:00
next({ name: "LoginFallback", query: { redirect: to.fullPath } })
2025-11-23 14:04:20 +08:00
}
2026-01-09 18:14:35 +08:00
return
2025-11-23 14:04:20 +08:00
}
// 如果已登录,检查租户编码一致性
if (authStore.isAuthenticated && authStore.user) {
2026-01-09 18:14:35 +08:00
const userTenantCode = authStore.user.tenantCode
2025-11-23 14:04:20 +08:00
if (userTenantCode) {
// 如果URL中的租户编码与用户信息不一致更正URL
if (tenantCodeFromUrl && tenantCodeFromUrl !== userTenantCode) {
const correctedPath = buildPathWithTenantCode(
userTenantCode,
to.path.replace(`/${tenantCodeFromUrl}`, "")
2026-01-09 18:14:35 +08:00
)
next({ path: correctedPath, replace: true })
return
}
// 如果URL中没有租户编码添加租户编码排除不需要认证的特殊路由
const skipTenantCodePaths = ["/login", "/model-viewer", "/403"]
const shouldSkipTenantCode = skipTenantCodePaths.some(p => to.path.startsWith(p))
if (!tenantCodeFromUrl && !shouldSkipTenantCode) {
const correctedPath = buildPathWithTenantCode(userTenantCode, to.path)
next({ path: correctedPath, replace: true })
return
}
}
}
// 如果已登录且有菜单数据,添加或更新动态路由
if (authStore.isAuthenticated && authStore.menus.length > 0) {
// 保存添加路由前的状态
const wasRoutesAdded = dynamicRoutesAdded
// 添加或更新动态路由
await addDynamicRoutes()
// 如果这是第一次添加路由,需要重新导航
if (!wasRoutesAdded && dynamicRoutesAdded) {
// 等待路由完全生效
await nextTick()
await nextTick()
// 保存原始目标路径
const targetPath = to.fullPath
// 路由已生效,重新解析目标路由
const resolved = router.resolve(targetPath)
// 如果访问的是主路由或 workbench且路由不存在重定向到第一个菜单
const isMainRoute = to.name === "Main" || to.path.endsWith("/workbench")
// 如果解析后的路由不是404说明路由存在重新导航
if (resolved.name !== "NotFound" && !isMainRoute) {
next({ path: targetPath, replace: true })
return
}
// 如果路由不存在或是主路由,尝试重定向到用户第一个菜单
if (authStore.menus && authStore.menus.length > 0) {
const findFirstMenuPath = (menus: any[]): string | null => {
for (const menu of menus) {
if (menu.path && menu.component) {
// 移除开头的斜杠
return menu.path.startsWith("/") ? menu.path.slice(1) : menu.path
}
if (menu.children && menu.children.length > 0) {
const childPath = findFirstMenuPath(menu.children)
if (childPath) return childPath
}
}
return null
}
const firstMenuPath = findFirstMenuPath(authStore.menus)
if (firstMenuPath) {
const userTenantCode = authStore.user
? (authStore.user.tenantCode as string | undefined)
: undefined
const tenantCode =
tenantCodeFromUrl ||
extractTenantCodeFromPath(to.path) ||
userTenantCode
if (tenantCode) {
// 再次等待,确保路由完全注册
await nextTick()
next({ path: `/${tenantCode}/${firstMenuPath}`, replace: true })
return
}
}
2025-11-23 14:04:20 +08:00
}
2026-01-09 18:14:35 +08:00
// 如果没有任何菜单跳转到404页面
const tenantCodeFor404 =
tenantCodeFromUrl ||
extractTenantCodeFromPath(to.path) ||
(authStore.user
? (authStore.user.tenantCode as string | undefined)
: undefined)
if (tenantCodeFor404) {
next({ path: `/${tenantCodeFor404}/404`, replace: true })
} else {
next({ name: "NotFound" })
2025-11-23 14:04:20 +08:00
}
2026-01-09 18:14:35 +08:00
return
2025-11-23 14:04:20 +08:00
}
}
2026-01-09 18:14:35 +08:00
// 如果已登录且有菜单,但路由已添加,检查当前路由是否存在
2025-11-23 14:04:20 +08:00
if (
authStore.isAuthenticated &&
authStore.menus.length > 0 &&
2026-01-09 18:14:35 +08:00
dynamicRoutesAdded
2025-11-23 14:04:20 +08:00
) {
2026-01-09 18:14:35 +08:00
const resolved = router.resolve(to.fullPath)
// 如果路由不存在,且不是登录页,尝试重定向到用户第一个菜单
if (
resolved.name === "NotFound" &&
to.name !== "Login" &&
to.name !== "LoginFallback"
) {
const findFirstMenuPath = (menus: any[]): string | null => {
for (const menu of menus) {
if (menu.path && menu.component) {
return menu.path.startsWith("/") ? menu.path.slice(1) : menu.path
}
if (menu.children && menu.children.length > 0) {
const childPath = findFirstMenuPath(menu.children)
if (childPath) return childPath
}
}
return null
}
const firstMenuPath = findFirstMenuPath(authStore.menus)
if (firstMenuPath) {
const userTenantCode = authStore.user
? (authStore.user.tenantCode as string | undefined)
: undefined
2025-11-23 14:04:20 +08:00
const tenantCode =
2026-01-09 18:14:35 +08:00
tenantCodeFromUrl ||
extractTenantCodeFromPath(to.path) ||
userTenantCode
2025-11-23 14:04:20 +08:00
if (tenantCode) {
2026-01-09 18:14:35 +08:00
next({ path: `/${tenantCode}/${firstMenuPath}`, replace: true })
return
2025-11-23 14:04:20 +08:00
}
2026-01-09 18:14:35 +08:00
}
// 如果没有任何菜单跳转到404页面
const tenantCodeFor404 =
tenantCodeFromUrl ||
extractTenantCodeFromPath(to.path) ||
(authStore.user
? (authStore.user.tenantCode as string | undefined)
: undefined)
if (tenantCodeFor404) {
next({ path: `/${tenantCodeFor404}/404`, replace: true })
return
2025-11-23 14:04:20 +08:00
}
}
}
// 检查是否需要认证
if (to.meta.requiresAuth !== false) {
// 如果没有 token跳转到登录页
if (!authStore.token) {
2026-01-09 18:14:35 +08:00
const tenantCode = tenantCodeFromUrl || extractTenantCodeFromPath(to.path)
2025-11-23 14:04:20 +08:00
if (tenantCode) {
next({
path: `/${tenantCode}/login`,
query: { redirect: to.fullPath },
2026-01-09 18:14:35 +08:00
})
2025-11-23 14:04:20 +08:00
} else {
2026-01-09 18:14:35 +08:00
next({ name: "LoginFallback", query: { redirect: to.fullPath } })
2025-11-23 14:04:20 +08:00
}
2026-01-09 18:14:35 +08:00
return
2025-11-23 14:04:20 +08:00
}
// 如果有 token 但没有用户信息,跳转到登录页
if (!authStore.user) {
2026-01-09 18:14:35 +08:00
const tenantCode = tenantCodeFromUrl || extractTenantCodeFromPath(to.path)
2025-11-23 14:04:20 +08:00
if (tenantCode) {
next({
path: `/${tenantCode}/login`,
query: { redirect: to.fullPath },
2026-01-09 18:14:35 +08:00
})
2025-11-23 14:04:20 +08:00
} else {
2026-01-09 18:14:35 +08:00
next({ name: "LoginFallback", query: { redirect: to.fullPath } })
2025-11-23 14:04:20 +08:00
}
2026-01-09 18:14:35 +08:00
return
2025-11-23 14:04:20 +08:00
}
}
// 如果已登录,访问登录页则重定向到首页
if (
(to.name === "Login" || to.name === "LoginFallback") &&
authStore.isAuthenticated
) {
// 确保动态路由已添加并等待生效
if (!dynamicRoutesAdded && authStore.menus.length > 0) {
2026-01-09 18:14:35 +08:00
await addDynamicRoutes()
2025-11-23 14:04:20 +08:00
}
// 重定向到带租户编码的工作台
2026-01-09 18:14:35 +08:00
const userTenantCode = authStore.user?.tenantCode || "default"
next({ path: `/${userTenantCode}/workbench` })
return
2025-11-23 14:04:20 +08:00
}
// 处理登录页面的租户编码
if (to.name === "LoginFallback" && !tenantCodeFromUrl) {
// 如果访问的是 /login但没有租户编码检查是否有用户信息中的租户编码
if (authStore.isAuthenticated && authStore.user?.tenantCode) {
2026-01-09 18:14:35 +08:00
const userTenantCode = authStore.user.tenantCode
next({ path: `/${userTenantCode}/login`, replace: true })
return
2025-11-23 14:04:20 +08:00
}
// 如果没有租户编码,允许访问(会显示租户输入框)
2026-01-09 18:14:35 +08:00
next()
return
2025-11-23 14:04:20 +08:00
}
// 检查角色权限
2026-01-09 18:14:35 +08:00
const requiredRoles = to.meta.roles
2025-11-23 14:04:20 +08:00
if (requiredRoles && requiredRoles.length > 0) {
if (!authStore.hasAnyRole(requiredRoles)) {
// 没有所需角色,跳转到 403 页面
2026-01-09 18:14:35 +08:00
next({ name: "Forbidden" })
return
2025-11-23 14:04:20 +08:00
}
}
// 检查权限
2026-01-09 18:14:35 +08:00
const requiredPermissions = to.meta.permissions
2025-11-23 14:04:20 +08:00
if (requiredPermissions && requiredPermissions.length > 0) {
if (!authStore.hasAnyPermission(requiredPermissions)) {
// 没有所需权限,跳转到 403 页面
2026-01-09 18:14:35 +08:00
next({ name: "Forbidden" })
return
2025-11-23 14:04:20 +08:00
}
}
2026-01-09 18:14:35 +08:00
next()
})
2025-11-23 14:04:20 +08:00
2026-01-09 18:14:35 +08:00
export default router