All files / src/router index.js

0% Statements 0/32
0% Branches 0/12
0% Functions 0/16
0% Lines 0/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140                                                                                                                                                                                                                                                                                       
import { createRouter, createWebHistory } from 'vue-router'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import DefaultLayout from '@/layouts/DefaultLayout.vue'
import { useAuthStore } from '@/stores/auth'
import { usePermissionStore } from '@/stores/permissions'
 
NProgress.configure({ showSpinner: false })
 
const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/login',
      name: 'login',
      component: () => import('@/views/login/index.vue'),
      meta: { title: '登录' },
    },
    {
      path: '/',
      component: DefaultLayout,
      redirect: '/dashboard',
      meta: { requiresAuth: true },
      children: [
        {
          path: '/dashboard',
          name: 'dashboard',
          component: () => import('@/views/dashboard/index.vue'),
          meta: { title: '工作台', componentName: 'Dashboard' },
        },
        // 测试页:用于演练 Tabs 导航(无需权限,纯布局验证)
        {
          path: '/test/list',
          name: 'test-list',
          component: () => import('@/views/test/TestPage.vue'),
          meta: { title: '测试-列表', componentName: 'TestPage' },
        },
        {
          path: '/test/form',
          name: 'test-form',
          component: () => import('@/views/test/TestPage.vue'),
          meta: { title: '测试-表单', componentName: 'TestPage' },
        },
        {
          path: '/test/detail',
          name: 'test-detail',
          component: () => import('@/views/test/TestPage.vue'),
          meta: { title: '测试-详情', componentName: 'TestPage' },
        },
        {
          path: '/test/chart',
          name: 'test-chart',
          component: () => import('@/views/test/TestPage.vue'),
          meta: { title: '测试-图表', componentName: 'TestPage' },
        },
        {
          path: '/test/table',
          name: 'test-table',
          component: () => import('@/views/test/TestPage.vue'),
          meta: { title: '测试-表格', componentName: 'TestPage' },
        },
        {
          path: '/test/settings',
          name: 'test-settings',
          component: () => import('@/views/test/TestPage.vue'),
          meta: { title: '测试-设置', componentName: 'TestPage' },
        },
        // 组件库演练场:纳入业务布局(带侧栏 / 顶栏 / Tabs)
        {
          path: '/demo',
          name: 'demo',
          component: () => import('@/views/demo/index.vue'),
          meta: { title: '组件库', componentName: 'Demo' },
        },
        // 附件组件演示
        {
          path: '/file-demo',
          name: 'file-demo',
          component: () => import('@/views/file-demo/index.vue'),
          meta: { title: '附件组件演示', componentName: 'FileDemo' },
        },
        // 详情页:同一组件按参数复用,但每个 fullPath 独立缓存、关闭即销毁
        // (per-param 的命名组件由 DefaultLayout 的 getPageWrapper 按路由动态生成)
        {
          path: '/detail/:id',
          name: 'detail',
          component: () => import('@/views/detail/DetailPage.vue'),
          meta: { title: '详情', cacheByKey: true },
        },
        // 模块管理:元数据驱动的模块配置中心(先静态路由,权限/动态菜单后续接入)
        {
          path: '/system/module-management',
          name: 'system-module-management',
          component: () => import('@/views/system/module-management/index.vue'),
          meta: { title: '模块管理', componentName: 'ModuleManagement' },
        },
        // 业务页面后续在此追加,配置 meta.moduleId 后自动走权限校验
      ],
    },
    {
      path: '/:pathMatch(.*)*',
      redirect: '/dashboard',
    },
  ],
})
 
// 登录守卫:未登录访问受保护页跳 /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()
    await permissionStore.load().catch(() => {})
 
    const moduleId = to.meta.moduleId
    if (moduleId && !permissionStore.canAccess(moduleId)) {
      return '/dashboard'
    }
  }
})
 
router.afterEach(() => {
  NProgress.done()
})
 
export default router