import { createRouter, createWebHistory } from 'vue-router' import NProgress from 'nprogress' import 'nprogress/nprogress.css' import { routes } from './routes' import { useAuthStore } from '@/stores/auth' import { usePermissionStore } from '@/stores/permissions' import { useMenuStore } from '@/stores/menus' import { useI18nStore } from '@/stores/i18n' NProgress.configure({ showSpinner: false }) /** * 路由实例。 * 路由表本身在 ./routes.js(菜单管理的「路由地址」下拉也要读它), * 本文件只保留实例创建与全局守卫,避免页面组件反向依赖守卫逻辑。 */ const router = createRouter({ history: createWebHistory(), routes, }) // 登录守卫:未登录访问受保护页跳 /login?redirect=xxx;已登录访问 /login 跳 /dashboard // 权限守卫:路由配置 meta.moduleId 时校验访问权限,无权限跳 /dashboard router.beforeEach(async (to) => { NProgress.start() const authStore = useAuthStore() if (to.path === '/login') { if (authStore.isAuthenticated) return '/dashboard' return } if (to.matched.some((r) => r.meta.requiresAuth)) { if (!authStore.isAuthenticated) { return { path: '/login', query: { redirect: to.fullPath } } } // 统一在路由守卫加载权限与动态菜单:首屏(/dashboard)与业务页都在挂载前 await 完成, // 菜单一次性到位、不会“跳出”;load 内部按主体去重,重复进入同一主体不重复请求 const permissionStore = usePermissionStore() const menuStore = useMenuStore() await Promise.all([ permissionStore.load().catch(() => {}), menuStore.load().catch(() => {}), ]) // 业务数据多语言配置(s_i18n_type 语言清单):随权限一并加载, // 供菜单/模块等多语言编辑读取;内部去重、失败不阻断页面 const languagesStore = useI18nStore() await languagesStore.loadLanguages().catch(() => {}) const moduleId = to.meta.moduleId if (moduleId && !permissionStore.canAccess(moduleId)) { return '/dashboard' } // 模块中心(/module/*)是系统管理员的专用页面:非 g3soft 一律拦回工作台。 // 这是路由层的特殊判断,与菜单树无关(菜单隐藏与路由放行由同一管理员判定决定), // 避免「菜单上看不见、手输地址却能进」的越权。 if (to.path.startsWith('/module/') && !permissionStore.isG3soft) { return '/dashboard' } } }) router.afterEach(() => { NProgress.done() }) export default router