Skip to content

大厂分层模板 ​

本节目标:理解“大厂分层模板”的核心思路,并能把它用于实际开发或面试表达。 给你一份大厂分层模板(可直接新建 src/router/guards/index.ts 使用)。我把复杂逻辑都放在上一行注释里;beforeEach / beforeResolve / afterEach 三段分工清晰、即插即用。

ts
// src/router/guards/index.ts
import type { NavigationGuard, Router } from 'vue-router'
import { useUserStore } from '@/store' // 按你项目实际路径
// import NProgress from 'nprogress'   // 可选:如需页面顶部进度条
/** ---------------------------
 * 工具:取安全回跳地址(仅站内)
 * -------------------------- */
// 复杂逻辑:将 to.FullPath/URL 统一为“同源相对路径”;否则回首页,避免 open redirect
const getSafeRedirect = (raw?: string) => {
  if (!raw) return '/'
  try {
    const decoded = decodeURIComponent(raw)
    if (decoded.startsWith('/') && !/^(https?:)?\/\//i.test(decoded)) return decoded
  } catch {}
  return '/'
}
/** ---------------------------
 * 全局前置:快速同步拦截(登录/白名单/基础角色)
 * -------------------------- */
const beforeEachGuards: NavigationGuard[] = [
  // 复杂逻辑:公共页快速放行(meta.public === true)
  (to, _from, next) => {
    if (to.matched.some((r) => r.meta?.public === true)) return next()
    next()
  },
  // 复杂逻辑:登录态校验(默认需要登录:meta.requiresAuth !== false)
  (to, from, next) => {
    const needAuth = to.matched.some((r) => r.meta?.requiresAuth !== false) // 默认收紧
    if (!needAuth) return next()
    const user = useUserStore()
    const isLoggedIn = Boolean(user.isLoggedIn)
    if (isLoggedIn) return next()
    // 复杂逻辑:未登录 → 去登录;带上 redirect
    const redirect = encodeURIComponent(to.fullPath || '/')
    // 复杂逻辑:若已在登录页且带 redirect,尽量复用(避免跳转风暴)
    if (to.name === 'user' || to.name === 'user.login') {
      return next()
    }
    return next({ name: 'user', query: { redirect } })
  },
  // 复杂逻辑:基础角色校验(当任一路由记录声明了 meta.roles)
  (to, _from, next) => {
    const needRoles: string[] = to.matched.flatMap((r) => (r.meta?.roles as string[]) || [])
    if (needRoles.length === 0) return next()
    const user = useUserStore()
    // 复杂逻辑:你的项目里角色存在 user.userInfo.role(单角色),或本地存储 roles(多角色)任选其一
    const myRoles = ((): string[] => {
      const one = user.userInfo?.role
      if (one) return [one]
      try {
        return JSON.parse(localStorage.getItem('roles') || '[]')
      } catch {
        return []
      }
    })()
    // 复杂逻辑:需要的角色只要命中其一即可通过
    if (needRoles.some((r) => myRoles.includes(r))) return next()
    // 复杂逻辑:无权限 → 去 404(或你单独的 403)
    return next({ name: 'not-found' })
  },
  // 可选:页面进度条开始
  // (_to, _from, next) => { NProgress.start(); next() },
]
/** ---------------------------
 * 全局解析:可取消的异步校验/预取(确认前最后一道)
 * -------------------------- */
const beforeResolveGuards: NavigationGuard[] = [
  // 复杂逻辑:示例一——手机号验证拦截(登录后首要合规检查)
  async (to, from, next) => {
    const user = useUserStore()
    const needAuth = to.matched.some((r) => r.meta?.requiresAuth !== false)
    if (!needAuth) return next()
    // 复杂逻辑:示例规则:若用户未完成手机号验证,且当前目标不是“手机号验证弹窗所在页/模块”,则引导
    // 你的项目里是“弹窗而非页面”,所以这里仅设置一个“标记”,由 layout 触发弹窗显示
    if (user.isLoggedIn && user.userInfo?.phone_verify === false) {
      // 复杂逻辑:在 query 打上 phone_verify=0,让 layout 感知后弹窗;并避免无限循环
      if (to.query.phone_verify !== '0') {
        return next({
          name: String(to.name || ''),
          params: to.params,
          query: { ...to.query, phone_verify: '0' },
          replace: true,
        })
      }
    }
    next()
  },
  // 复杂逻辑:示例二——页面数据预取(进入前确保必要数据就绪,避免闪烁)
  async (to, _from, next) => {
    try {
      // 复杂逻辑:当某些页面声明了 meta.prefetch=true 时,统一在这里预拉取
      const toPrefetch = to.matched.find((r) => r.meta?.prefetch === true)
      if (!toPrefetch) return next()
      // 举例:根据路由元信息决定预取哪些数据
      // await Promise.all([ api.fetchSomething(), api.fetchAnotherThing() ])
      return next()
    } catch (e) {
      console.error('[beforeResolve] prefetch failed:', e)
      // 复杂逻辑:预取失败可阻断本次导航(或者回到安全页)
      return next(false)
    }
  },
  // 复杂逻辑:示例三——登录回跳归口 /redirect(如你采用 redirect 中转模式)
  (to, from, next) => {
    const user = useUserStore()
    if ((from.name === 'user' || from.name === 'user.login') && user.isLoggedIn) {
      // 复杂逻辑:登录成功后,在 resolve 阶段把 redirect 参数交给 /redirect 统一处理
      const safe = getSafeRedirect(to.query.redirect as string | undefined)
      if (safe && to.name === 'user.login') {
        return next({ name: 'redirect', query: { to: safe }, replace: true })
      }
    }
    next()
  },
]
/** ---------------------------
 * 全局后置:不可取消(埋点/标题/进度条结束)
 * -------------------------- */
const afterEachGuards: Array<(to: any, from: any) => void> = [
  // 复杂逻辑:设置页面标题(优先 meta.title,否则用 i18n key)
  (to) => {
    const raw = to.matched.find((r) => r.meta?.title)?.meta?.title as string | undefined
    if (raw) document.title = typeof raw === 'string' ? raw : 'App'
  },
  // 可选:进度条结束
  // () => { NProgress.done() },
  // 复杂逻辑:PV 埋点(可替换为你的统计 SDK)
  // (to, from) => reportPV({ path: to.fullPath, from: from.fullPath }),
]
/** 暴露为和你现有用法一致的结构 */
export default {
  before: beforeEachGuards,
  after: afterEachGuards,
}
/** 供 router.beforeResolve 使用(你在 router/index.ts 里有 resolveGuards.forEach) */
export const resolveGuards = beforeResolveGuards

怎么接入 ​

你的 router/index.ts 里改两处(如果你沿用原结构):

ts
// 复杂逻辑:从本文件引入 default.before / default.after
import guards, { resolveGuards } from '@/router/guards' // ← 现在 resolveGuards 也从这里出
ts
// 复杂逻辑:注册顺序:beforeEach(快拦截) → beforeResolve(异步预取/细校验) → afterEach(收尾)
guards.before.forEach(router.beforeEach)
resolveGuards.forEach((guard) => router.beforeResolve(guard))
guards.after.forEach(router.afterEach)

> 说明: > > - beforeEach:快速决策(登录/白名单/基础角色),尽量同步、轻量; > - beforeResolve:异步与细节(手机号验证标记、数据预取、统一回跳); > - afterEach:不可取消(标题、进度条、埋点)。 如果你想把手机号未验证时在 (public).layout.vue 弹窗拉起的逻辑也一起给我,我可以给一个watch 路由 query 的最小实现片段。

本站总访问 — 次