路由解析

94 阅读3分钟

路由代码解析

router.beforeEach((to, from, next) => {
  let href = location.href
  if (href.indexOf('#') > href.indexOf('?') && href.indexOf('?') > -1) {
    location.href = href.replace(location.search, '') + location.search
    return
  }
  if (location.search.indexOf('atk=') > -1) {
    const atkStart = location.search.indexOf('atk=') + 4
    let atkEnd = location.search.indexOf('&', atkStart)
    if (atkEnd == -1) {
      atkEnd = location.search.length
    }
    const atk = location.search.substring(atkStart, atkEnd)
    sessionStorage.setItem('atk', atk)
    location.search = ''
    next({path: to.path})
    return
  }

  let errCallback = () => {
    next(false)
    location.href = location.href.replace(location.hash, '#/loginInterface')
    Message({
      message: '请重新登录!',
      type: 'error'
    })
  }

  // meta中有guest为无需拦截的页面, sessionStorage中含有atk则是已登录状态
  if (to.meta?.outlink || to.meta.loginPage) {
    next()
  } else if (!sessionStorage.getItem('atk') && ((typeof store.state.account === 'object' && Object.keys(store.state.account).length > 0) || (to.meta && to.meta.guest === true))) {
    storeInitRules.init(to, store).then(() => {
      next()
    })
  } else if (sessionStorage.getItem('atk')) {
    if (Object.keys(store.getters.getAccount || {}).length) {
      if (to.path === '/') {
        let indexPath = '/reportUnit/main'
        // 报审方的单位管理账号
        if (store.getters.isReportManageOrg) {
          indexPath = '/projectOverview'
        }
        next({path: indexPath})
      } else {
        next()
      }
    } else {
      store.dispatch('public/account').then(() => {
        return storeInitRules.init(to, store)
      }).then(() => {
        if (to.path === '/') {
          let indexPath = '/reportUnit/main'
          // 报审方的单位管理账号
          if (store.getters.isReportManageOrg) {
            indexPath = '/projectOverview'
          }
          next({path: indexPath})
        } else {
          next()
        }
      }).catch(errCallback)
    }
  } else {
    // store.dispatch('public/account').then(res => {
    //   storeInitRules.init(to, store)
    // }).then(next).catch(errCallback)
    next(false)
    location.href = location.href.replace(location.hash, '#/loginInterface')
  }
})

这段代码是 Vue Router 的全局前置守卫(router.beforeEach,主要用于在路由跳转前进行一系列的逻辑判断,包括 URL 处理、登录状态校验、权限控制、页面跳转逻辑 等。下面逐段解析其作用:


1. URL 处理(修复 #? 顺序问题)

let href = location.href
if (href.indexOf('#') > href.indexOf('?') && href.indexOf('?') > -1) {
  location.href = href.replace(location.search, '') + location.search
  return
}
  • 作用
    如果 URL 中 #(hash)出现在 ?(query)之后(如 http://example.com/path?query=1#hash),则重新拼接 URL,确保 ?# 之前(如 http://example.com/path#hash?query=1)。
  • 原因
    某些浏览器或框架可能对 #? 的顺序敏感,调整顺序可避免路由解析异常。

2. 从 URL 提取 atk(认证 Token)并存储

if (location.search.indexOf('atk=') > -1) {
  const atkStart = location.search.indexOf('atk=') + 4
  let atkEnd = location.search.indexOf('&', atkStart)
  if (atkEnd == -1) {
    atkEnd = location.search.length
  }
  const atk = location.search.substring(atkStart, atkEnd)
  sessionStorage.setItem('atk', atk) // 存储 token
  location.search = '' // 清除 URL 中的 query 参数
  next({ path: to.path }) // 重新跳转当前路径(无 atk)
  return
}
  • 作用
    如果 URL 中包含 atk=xxx(如 http://example.com?atk=token123),提取 atk 并存入 sessionStorage,然后清除 URL 中的查询参数,避免 token 暴露。
  • 用途
    适用于第三方登录回调后携带 token 的场景,安全存储 token 并清理 URL。

3. 错误回调(跳转登录页)

let errCallback = () => {
  next(false) // 中断当前导航
  location.href = location.href.replace(location.hash, '#/loginInterface') // 强制跳登录页
  Message({ message: '请重新登录!', type: 'error' }) // 显示错误提示
}
  • 作用
    当权限校验失败时,中断导航并跳转到登录页,同时显示错误消息。

4. 路由拦截逻辑

情况 1:放行无需拦截的路由

if (to.meta?.outlink || to.meta.loginPage) {
  next() // 直接放行
}
  • 条件
    如果路由的 meta 中包含 outlink(外链)或 loginPage(登录页),则直接放行。

情况 2:未登录但允许游客访问(guest

else if (!sessionStorage.getItem('atk') && 
         ((typeof store.state.account === 'object' && Object.keys(store.state.account).length > 0) || 
          (to.meta && to.meta.guest === true))) {
  storeInitRules.init(to, store).then(() => {
    next() // 初始化权限后放行
  })
}
  • 条件
    • 用户未登录(sessionStorageatk)。
    • 但满足以下任一条件:
      1. store.state.account 已存在(可能是缓存用户信息)。
      2. 路由标记为 meta.guest: true(允许游客访问)。
  • 行为
    调用 storeInitRules.init 初始化权限,完成后放行。

情况 3:已登录(atk 存在)

else if (sessionStorage.getItem('atk')) {
  // 3.1 如果 store 中已有用户信息
  if (Object.keys(store.getters.getAccount || {}).length) {
    if (to.path === '/') {
      // 根据角色跳转到不同首页
      let indexPath = '/reportUnit/main'
      if (store.getters.isReportManageOrg) {
        indexPath = '/projectOverview'
      }
      next({ path: indexPath })
    } else {
      next() // 非根路径直接放行
    }
  } 
  // 3.2 如果 store 中无用户信息,先获取用户信息
  else {
    store.dispatch('public/account').then(() => {
      return storeInitRules.init(to, store)
    }).then(() => {
      if (to.path === '/') {
        // 同样根据角色跳转首页
        let indexPath = '/reportUnit/main'
        if (store.getters.isReportManageOrg) {
          indexPath = '/projectOverview'
        }
        next({ path: indexPath })
      } else {
        next()
      }
    }).catch(errCallback) // 失败则跳登录页
  }
}
  • 逻辑
    1. 如果用户信息已存在(store.getters.getAccount),则根据角色跳转到对应首页(如 /reportUnit/main/projectOverview)。
    2. 如果用户信息不存在,先调用 store.dispatch('public/account') 获取用户信息,再初始化权限,最后跳转。

情况 4:未登录且无权限

else {
  next(false) // 中断导航
  location.href = location.href.replace(location.hash, '#/loginInterface') // 强制跳登录页
}
  • 行为
    默认情况(未登录且无 guest 权限),直接跳转到登录页。

总结

这段代码的核心功能:

  1. URL 规范化:修复 #? 的顺序问题。
  2. Token 处理:从 URL 提取 atk 并存储到 sessionStorage
  3. 登录状态校验
    • 已登录 → 根据角色跳转首页或放行。
    • 未登录但允许游客访问 → 放行。
    • 未登录且需权限 → 跳登录页。
  4. 权限初始化:通过 storeInitRules.init 动态加载权限规则。
  5. 错误处理:统一跳登录页并提示错误。

适用场景

  • 需要登录认证的单页应用(SPA)。
  • 动态权限控制(如不同角色跳不同首页)。
  • 第三方登录回调后处理 token。