首页 > 其他分享 >Vue.js_Vue Router 4.x 动态路由解决刷新空白

Vue.js_Vue Router 4.x 动态路由解决刷新空白

时间:2022-12-02 18:02:40浏览次数:44  
标签:Vue const import ts js router Router children 路由

问题描述:

基于对 Vue Router 3.x 没有改变前,我们常规的实现一定,在 store 中根据获取的用户权限,对路由进行过滤并返回,然后到路由守卫的地方,使用 addRoutes 动态添加路由。但是在 Vue Router 4.x 以后对这部分进行了修改。
修改点:

  1. 删除API addRoutes
  2. 改用API addRoute,新增API removeRoute,下附官方该 API 的说明:

也就是说这两个API是我们实现动态路由的关键,但是按照官方的说明及以往的开发经验,最终我出错了,动态路由页面刷新空白。分析下问题原因:

动态路由实现常规思路

路由

  1. 创建 ./src/router 目录
  2. 新建 index.ts 文件用于书写通用的路由 routescreateRouter 对象
// ./src/router/index.ts

import { createWebHistory, createRouter } from 'vue-router'
import GuardEach from './guard'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    { path: '/*', redirect: '/'},
    { path: '/404', name: '404', component: () => import('@/views/common/404.vue') },
    { path: '/login', name: 'Login', component: () => import('@/views/common/login.vue') },
    { path: '/', name: 'Home', redirect: '/home', component: () => import('@/views/layout/index.vue'),
      children: [
        { path: '/home', name: 'Home-Index', component: () => import('@/views/home/index.vue') }
      ]
    }
  ]
})
GuardEach(router)

export default router
  1. 新建 async 目录,用于按业务模块存放你的异步路由 routes,当然了如果的的动态路由实在少,你可以可以写在 index.ts 中导出,或者一个文件搞定,这里演示我们就比如他是一个 ts 文件
// ./src/router/async.ts

import type { RouteRecordRaw } from 'vue-router'

export const Member: Readonly<RouteRecordRaw[]> = [
  { path: '/member', name: 'Member', redirect: '/member/list', meta: { code: 1 },
    component: () => import('@/views/layout/index.vue'),
    children: [
      { path: 'list', name: 'Member-List', meta: { code: 2 } component: () => import('@/views/member/list.vue') },
      { path: 'item', name: 'Member-Item', meta: { code: 3 } component: () => import('@/views/member/item.vue') }
    ]
  }
]
  1. 新建 guard.ts 文件,用于书写路由守卫逻辑(大部分程序员习惯将其放至在 ./src/permission.ts中,这个看个人喜好,无关痛痒)
// ./src/router/guard.ts

import { userStore } from '@/stores'
import type { Router } from 'vue-router'

let hasInitAuth = true
export default function (router: Router) {
  router.beforeEach((to, from, next) => {
    const user = userStore()

    const authRoutes = user.getAuthRoutes(router.options.routes) || []
    if (hasInitAuth) {
      authRoutes.forEach((item: any) => router.addRoute(item))
      hasInitAuth = false
      router.push({ ...to, replace: true })
    }

    next()
  })
}
  1. 这里为了处理路由和菜单,我新增了一个文件 extend.ts,用于存放一些扩展的函数(仅供参考)
// ./src/router/extend.ts

import type { RouteRecordRaw } from 'vue-router'

// 生成有权限的路由表
export function createAuthRoutes(asyncRoutes: Readonly<RouteRecordRaw[]>, authCode: Number) {
  return asyncRoutes.filter((s) => {
    const code = s.meta?.code
    const child = s.children
    if (child && child.length > 0) createAuthRoutes(child, authCode)
    return !code || (code && authCode)
  })
}

// 生成有权限的导航菜单
export function createNavMenus(allRoutes: Readonly<RouteRecordRaw[]>) {
  function mapItem(data: Readonly<RouteRecordRaw[]>): any[] {
    return data.map((s) => {
      let children = []
      if (s.children && s.children.length > 0) {
        children = mapItem(s.children) || []
      }
      return {
        title: s.meta?.name as string,
        children: children.length === 0 ? undefined : children
      }
    })
  }

  function filterItem(data: Readonly<RouteRecordRaw[]>): any[] {
    return data.filter((s) => {
      if (s.children && s.children.length > 0) {
        s.children = filterItem(s.children)
      }
      return true
    })
  }
  return filterItem(mapItem(allRoutes))
}

stores

  1. 创建一个模块或一个文件,用来实现对 ./src/router/async/**/*.ts 的所有 routes 们进行权限的过滤,以及右侧菜单的生成,最终你需要在你的 state 中存储两个值 authAsyncRoutes(有权限的动态路由列表),authNavMenus(有权限的导航菜单列表)
  2. 根据你的业务场景需要,对这两个值做相关的持久化存储
// ./src/stores/index.ts

import { defineStore } from 'pinia'
import type { RouteRecordRaw } from 'vue-router'
import { createAuthRoutes, createNavMenus } from '@/router/extend'
import asyncRoutes from '@/router/async'

export const userStore = defineStore('USER_STORES', {
  state: () => ({
    authNavMenus: [] as any[]
  }),

  actions: {
    getAuthRoutes(syncRoutes: Readonly<RouteRecordRaw[]>) {
      /**
       * 1. 获取缓存用户权限路由
       * 2. 获取缓存用户权限菜单
       * 3. 如果存在缓存,则给导航菜单赋值并返回有权限的路由
       */
      const authAsyncRoutes = sessionStorage.get('UserRoutes')
      const authNavMenus = sessionStorage.get('UserMenus')
      if (authAsyncRoutes && authNavMenus) {
        this.authNavMenus = authNavMenus
        return authAsyncRoutes
      }

      /**
       * 4. 如果不存在缓存,则获取当前用户的权限配置(我的业务场景是二进制权限配置,因此是一个最大权限值)
       * 5. 根据权限配置,生成有权限的路由
       * 6. 根据有权限的路由,生成导航菜单,并赋值
       * 7. 将有权限的路由和导航菜单进行缓存
       * 9. 返回有权限的路由
       */
      const authCode = sessionStorage.get('UserAuthCode')
      if (authCode) {
        const authAsyncRoutes = createAuthRoutes(asyncRoutes, authCode)
        const allRoutes = syncRoutes.concat(authAsyncRoutes)
        this.authNavMenus = createNavMenus(allRoutes)

        sessionStorage.set('UserRoutes', authAsyncRoutes)
        sessionStorage.set('UserMenus', this.authNavMenus)
        return authAsyncRoutes
      }
    }
  }
})

main.ts

  1. 引入路由和 stores,并use,ok!感受报错和刷新白屏的洗礼!
// ./src/main.ts

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'

const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.use(router)

app.mount('#app')

是不是发现没有没有看出什么问题,其实这其中有两个问题

  1. 控制台报错问题,具体是因为 next() 这个发生了一些修改,看了官方文档其实还是一知半解,经过不断实验,不断探索,终于我发现了问题:
// ./src/router/guard.ts 中以下代码错误,这段代码等同于执行了一个 next()

router.push({ ...to, replace: true })

// ,,因此这应该修改为:
  if (hasInitAuth) {
    authRoutes.forEach((item: any) => router.addRoute(item))
    hasInitAuth = false
    router.push({ ...to, replace: true })
+  } else {
+    next()
+  }
- next()
  1. 解决报错问题,发现刷新空白,经查看报错提示及查看了缓存的有权限的路由发现,缓存中是不存在路由组件关联的,因此这里我们需要手动将视图组件导入并关联,需要在添加如下代码:
// ./src/router/extend.ts 新增如下代码:

/**
 * 动态添加路由当缓存时只会存储其路由清单树,不会存储其关联的视图组件
 * 故而当重新刷新或进入页面时,需要重新将视图组件与路由清单树关联
 * 否则会导致页面空白,无法正常显示
 */
+  export function authRouteTreePlug(
+    authRoutesTree: Readonly<RouteRecordRaw[]>,
+    parentPath: string = ''
+  ) {
+    // 查阅资料得出,在这里不支持 () => import() 的写法,需要使用 import.meta.glob导入组件,而后在使用,这里不要使用 @ 哦,否则会找不到,具体原因不明
+    const modules = import.meta.glob('../views/**/*.vue')
+
+    return authRoutesTree.map((item) => {
+      const itemPath = item.path.slice(0, 1) === '/' ? item.path : `/${item.path}`
+      const hasChild = item.children && item.children.length > 0
+
+      // 由于这里是管理后台,因此一级路由需要使用layout布局组件,故增加判断,各位看官可以根据需求修改
+      const compPath = item.redirect && hasChild ? '/layout/index' : parentPath + itemPath
+      item.component = modules[`../views${compPath}.vue`]
+
+      if (hasChild) {
+        item.children = authRouteTreePlug(item.children as Readonly<RouteRecordRaw[]>, itemPath)
+      }
+      return item
+    })
+  }
// ./src/stores/index.ts 修改如下这段代码:

/**
 * 1. 获取缓存用户权限路由
 * 2. 获取缓存用户权限菜单
 * 3. 如果存在缓存,则给导航菜单赋值并返回有权限的路由
 */
+ const authAsyncRoutesTree = sessionStorage.get('UserRoutes')
  const authNavMenus = sessionStorage.get('UserMenus')
+ if (authAsyncRoutesTree && authNavMenus) {
+   const authAsyncRoutes = authRouteTreePlug(authRoutesTree)
    this.authNavMenus = authNavMenus
    return authAsyncRoutes
  }

这下终于解决了,完事!

标签:Vue,const,import,ts,js,router,Router,children,路由
From: https://www.cnblogs.com/leona-d/p/16944855.html

相关文章

  • 【Springboot】Json转换工具
    HttpClient请求数据后是json字符串,需要我们自己把Json字符串反序列化为对象,我们会使用JacksonJson工具来实现。JacksonJson是SpringMVC内置的json处理工具,其中有一个Objec......
  • xml_解析_Jsoup_根据选择器查询与xml_解析_Jsoup_根据Xpath查询
    xml_解析_Jsoup_根据选择器查询 快捷查询方式:1.selector:选择器  使用的方法:Elements select​(StringcssQuery)......
  • vue文件分片上传,断点续传
    ​ 1 背景用户本地有一份txt或者csv文件,无论是从业务数据库导出、还是其他途径获取,当需要使用蚂蚁的大数据分析工具进行数据加工、挖掘和共创应用的时候,首先要将本地文......
  • VUE $refs 与 $el 详解
    $refs与$el是什么?作用是什么?ref,$refs,$el,三者之间的关系是什么?ref(给元素或者子组件注册引用信息)就像你要给元素设置样式,就需要先给元素设定一个class一样,同理......
  • js滚动指定的距离,实现表格内定位
    使用scrollIntoview会滚动整个页面。使用原生的scrollTo方法来代替。letitem=document.getElementById("item");//指定的元素letwrapper=document.getElementB......
  • vue多个方法的异步请求
    1、async和awaitasync/await是一种建立在Promise之上的编写异步或非阻塞代码的新方法。async是异步的意思,而await是asyncwait的简写,即异步等待。1//假设这是......
  • 关于Namespace的值是怎么影响jsp页面位置不同访问方法的
    小记:​​<constantname="struts.devMode"value="true"/>​​这个是设置开发模式的语句,保证项目配置在修改之后能直接刷新体现出来的。首先在完成项目的配置之后,web.xml文......
  • js插件fullcalendar配置项及样例
     部分配置项<linkhref="./plugins/fullcalendar-5.11.2/lib/main.css"rel="stylesheet"/><scripttype="text/javascript"language="javascript"src="./plugins/......
  • Newtonsoft.Json 对象序列化 -- 系列文章
    Newtonsoft.Json是一个非常棒的.net对象转Json,Json字符串转对象的类库,此分类中主要记录日常使用的方法以及功能总结。三:C#对象转换Json时的一些高级(特殊)设置;二:C#对......
  • js-day05-对象
    为什么要学习对象没有对象时,保存网站用户信息时不方便,很难区别 对象是什么1.对象是一种数据类型2.无序的数据集合对象有什么特点1.无序的数据的集......