permission.ts 2.41 KB
import router from '@/router'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { ElMessage } from 'element-plus'
import { usePermissionStore, useUserStore } from '@/store'
import { getAccessToken } from '@/utils/auth'
import { APP_TITLE, HOME_PATH, LOGIN_PATH } from '@/config'

/**
 * 路由守卫
 *
 * 流程:
 *  1. 无 token → 白名单放行,其余跳登录页并带 redirect
 *  2. 有 token 但还没拉过用户信息 → 调 getPermissionInfo,动态注册路由后原地重定向
 *  3. 拉取失败(token 失效/后端不可用)→ 清状态回登录页
 */
NProgress.configure({ showSpinner: false, trickleSpeed: 120 })

// 无需登录即可访问
const whiteList = [LOGIN_PATH, '/404']

router.beforeEach(async (to, _from, next) => {
  NProgress.start()
  document.title = to.meta?.title ? `${to.meta.title} - ${APP_TITLE}` : APP_TITLE

  const token = getAccessToken()

  if (!token) {
    if (whiteList.includes(to.path)) {
      next()
    } else {
      next({ path: LOGIN_PATH, query: { redirect: to.fullPath } })
    }
    NProgress.done()
    return
  }

  // 已登录还去登录页 → 回首页
  if (to.path === LOGIN_PATH) {
    next({ path: '/' })
    NProgress.done()
    return
  }

  const userStore = useUserStore()
  if (!userStore.isUserInfoLoaded) {
    try {
      await userStore.getInfo()
      const permissionStore = usePermissionStore()
      // 落点修正:`/` 与 HOME_PATH('/dashboard') 都是「有首页菜单时」的默认落点,
      // 而 /dashboard 是动态路由,账号没被授权「首页」菜单时它并不存在,
      // 直接放行会命中 404 兜底。此时改送该账号实际可用的第一个页面。
      const isLanding = to.path === '/' || to.path === HOME_PATH
      const fallback = permissionStore.getDefaultPath()
      if (isLanding && !permissionStore.canReach(to.fullPath) && fallback !== to.path) {
        next({ path: fallback, replace: true })
        return
      }
      // 路由是刚注册的,必须用 replace 重新解析一次目标地址
      next({ ...to, replace: true })
    } catch (e: any) {
      userStore.resetState()
      ElMessage.error(e?.message || '获取用户信息失败,请重新登录')
      next({ path: LOGIN_PATH, query: { redirect: to.fullPath } })
      NProgress.done()
    }
    return
  }

  next()
})

router.afterEach(() => {
  NProgress.done()
})

router.onError(() => {
  NProgress.done()
})