关于动态注册组件的问题

遗留问题

上节课菜单,你们放在PugMenu.vue的生命周期进行异步调用合理吗?

  • 答案是:不合理
  • 原因:每次刷新都会去服务器查询和同步一次菜单,着实没必要。
  • 解决方案:只查询,放入状态管理,让menuList存入sessionStorage中。

动态注册组件

  • 对路由的添加通常是通过 ​​routes​​来完成的,
  • 但是在某些情况下,你可能想在应用程序已经运行的时候添加或删除路由。

背后含义是什么?

  • 动态注册路由:就是在执行的过程中,我才确定根路由,父子路由的关系。从而提升性能。和网页加载速度。
  • 默认情况下:如果路由全部通过routes和父子路由children指定以后,都是静态加载,这种静态加载会在启动项目时候,全部把路由中所有的js,css, template全部编译一边,然后合并成一个js。这个js非常的大。影响加载的速度和性能。
  • 动态路由:在访问的时候才确定,把需要的路由相关js/css/tempalte进行合并js,在进行渲染。懒加载。

非动态绑定

import { createRouter, createWebHistory } from 'vue-router'
import store from '@/store'
import { showFullLoading, hideFullLoading, toastError } from '@/utils'


import Index from '@/views/PugAdmin.vue'
import Dashboard from '@/views/dashboard/Index.vue'

import ProductList from '@/views/product/List.vue'
import CategoryList from '@/views/category/List.vue'
import CouponList from '@/views/coupon/List.vue'
import UserList from '@/views/user/List.vue'
import OrderList from '@/views/order/List.vue'
import ImageList from '@/views/image/List.vue'
import NoticeList from '@/views/notice/List.vue'
import LevelList from '@/views/level/List.vue'

import ManagerList from '@/views/manager/List.vue'
import RoleList from '@/views/role/List.vue'
import PermissionList from '@/views/permission/List.vue'


// 动态路由,用于匹配菜单动态添加路由
const asyncRoutes = [{
path: '/',
name: "dashboard",
meta: { title: '后台首页' },
component: Dashboard
}, {
path: '/user/list',
name: "/user/list",
meta: { title: '用户管理' },
component: UserList
}, {
path: '/manager/list',
name: "/manager/list",
meta: { title: '后台管理员' },
component: ManagerList
}, {
path: '/role/list',
name: "/role/list",
meta: { title: '角色管理' },
component: RoleList
}, {
path: '/permission/list',
name: "/permission/list",
meta: { title: '权限管理' },
component: PermissionList
}, {
path: '/user/list',
name: "/user/list",
meta: { title: '用户管理' },
component: UserList
}, {
path: '/level/list',
name: "/level/list",
meta: { title: '会员等级' },
component: LevelList
}, {
path: "/category/list",
name: "/category/list",
component: CategoryList,
meta: { title: "分类列表" }
}, {
path: "/coupon/list",
name: "/coupon/list",
component: CouponList,
meta: { title: "优惠券管理" }
}, {
path: "/order/list",
name: "/order/list",
component: OrderList,
meta: {
title: "订单列表"
}
}, {
path: "/image/list",
name: "/image/list",
component: ImageList,
meta: {
title: "图库列表"
}
}, {
path: "/notice/list",
name: "/notice/list",
component: NoticeList,
meta: {
title: "公告列表"
}
}, {
path: '/product/list',
name: "/product/list",
meta: { title: '产品列表' },
component: ProductList
}];


//4 :定义路由配置规则
const routes = [{
path: "/",
meta: { title: "首页" },
name: "admin",
component: Index,
children: asyncRoutes
}, {
path: "/login",
name: "login",
meta: { title: "登录" },
component: () =>
import ('../views/Login.vue')
}, {
path: "/toLogin",
redirect: "/login"
}, { //----------------新增代码,建议把注释删掉
path: '/:pathMatch(.*)*',
name: '404',
meta: { title: "404" },
component: () =>
import ('../views/error/404.vue')
}]


//2 :创建路由对象
const router = createRouter({
// 引入访问模式
history: createWebHistory(),
routes
})


// 动态注册路由方法
export function registerRoutes(menuList) {
// 是否有新的路由
let hasNewRoutes = false
const findAndAddRoutesByMenus = (arr) => {
arr.forEach(e => {
// 查看每个
let item = asyncRoutes.find(o => o.path == e.path)
if (item && !router.hasRoute(item.path)) {
router.addRoute("admin", item)
hasNewRoutes = true
}
if (e.children && e.children.length > 0) {
findAndAddRoutesByMenus(e.children)
}
})
}

findAndAddRoutesByMenus(menuList)

return hasNewRoutes
}


let loadNewRoute = false;
// 定义后置守卫--拦截器思想
router.beforeEach(async(to, from, next) => {
// 全屏动画开启
showFullLoading()

// 判断是否已经登录
var isLogin = store.getters["user/isLogin"];

// 没有登录,强制跳转回登录页
if (!isLogin && to.path != "/login") {
toastError("请先登录", "error")
next("/toLogin")
}

// 防止重复登录
if (isLogin && to.path == "/login") {
toastError("请勿重复登录", "error")
next({ path: from.path ? from.path : "/" })
}


next();
})



/* 后置守卫 */
router.afterEach((to, from) => {
// 结束全屏动画
hideFullLoading();
// 标题切换
document.title = to.meta.title + "-PugAdmin-后台管理系统" || "PugAdmin-后台管理系统";
})





// 3: 导出即可生效
export default router

这种绑定,对于后面的打包的时候,js体积会非常的大。不利于网页加载速度。

## 动态路由化

把asyncRoutes子路由的集合,动态的绑定 在 routes数据模型的 name: “admin”, 形成父子路由。

动态添加路由

router.addRoute({ path: '/about', component: About })

添加嵌套路由

router.addRoute('admin', { path: 'settings', component: AdminSettings })

原理

原理其实就是:把数据库查询出来的路由和router.js定义路由,进行匹配和注册。把存在和合法的动态注册到admin父路由上。

代码如下:

// 动态注册路由方法- 考虑到
export function registerRoutes(menuList) {
// 是否有新的路由
let hasNewRoutes = false
const findAndAddRoutesByMenus = (arr) => {
menuList.forEach(e => {
// 一定要查询我的router.js中asyncRoutes存在的路由,才去绑定,否则不绑定
let item = asyncRoutes.find(o => o.path == e.path)

// item存在,并且没有绑定过router.hasRoute,
if (item && !router.hasRoute(item.path)) {
// 如果没有绑定,就开始绑定。
router.addRoute("admin", item)
hasNewRoutes = true
}

// 递归,如果当前子菜单还有子元素,
if (e.children && e.children.length > 0) {
findAndAddRoutesByMenus(e.children)
}
})
}

// 开始递归调用动态绑定路由关系
findAndAddRoutesByMenus(menuList)

// 第一次绑定:就是 true,
// 如果已经全部绑定过就是: false
// 返回这个作用:就是为了让后续的路由访问只绑定一次,没必要每次访问都绑定
return hasNewRoutes
}

注册位置

beforeEach 前置守卫

  • 但是我们指定beforeEach前置守卫,每个路由请求都进入,所以必须找到开关,让注册只注册一次。所以就定义了 let loadNewRoute = false; ,然后注册以后,立马修改let loadNewRoute = true。就可以防止重复注册。

代码如下:

import { createRouter, createWebHistory } from 'vue-router'
import store from '@/store'
import { showFullLoading, hideFullLoading, toastError } from '@/utils'


import PugAdmin from '@/views/PugAdmin.vue'
import Dashboard from '@/views/dashboard/Index.vue'

import ProductList from '@/views/product/List.vue'
import CategoryList from '@/views/category/List.vue'
import CouponList from '@/views/coupon/List.vue'
import UserList from '@/views/user/List.vue'
import OrderList from '@/views/order/List.vue'
import ImageList from '@/views/image/List.vue'
import NoticeList from '@/views/notice/List.vue'
import LevelList from '@/views/level/List.vue'

import ManagerList from '@/views/manager/List.vue'
import RoleList from '@/views/role/List.vue'
import PermissionList from '@/views/permission/List.vue'


// 动态路由,用于匹配菜单动态添加路由
const asyncRoutes = [{
path: '/',
name: "dashboard",
meta: { title: '后台首页' },
component: Dashboard,
}, {
path: '/user/list',
name: "/user/list",
meta: { title: '用户管理' },
component: UserList
}, {
path: '/manager/list',
name: "/manager/list",
meta: { title: '后台管理员' },
component: ManagerList
}, {
path: '/role/list',
name: "/role/list",
meta: { title: '角色管理' },
component: RoleList
}, {
path: '/permission/list',
name: "/permission/list",
meta: { title: '权限管理' },
component: PermissionList
}, {
path: '/user/list',
name: "/user/list",
meta: { title: '用户管理' },
component: UserList
}, {
path: '/level/list',
name: "/level/list",
meta: { title: '会员等级' },
component: LevelList
}, {
path: "/category/list",
name: "/category/list",
component: CategoryList,
meta: { title: "分类列表" }
}, {
path: "/coupon/list",
name: "/coupon/list",
component: CouponList,
meta: { title: "优惠券管理" }
}, {
path: "/order/list",
name: "/order/list",
component: OrderList,
meta: {
title: "订单列表"
}
}, {
path: "/image/list",
name: "/image/list",
component: ImageList,
meta: {
title: "图库列表"
}
}, {
path: "/notice/list",
name: "/notice/list",
component: NoticeList,
meta: {
title: "公告列表"
}
}, {
path: '/product/list',
name: "/product/list",
meta: { title: '产品列表' },
component: ProductList
}];


//4 :定义路由配置规则
const routes = [{
path: "/",
meta: { title: "首页" },
name: "PugAdmin",
component: PugAdmin
}, {
path: "/login",
name: "login",
meta: { title: "登录" },
component: () =>
import ('../views/Login.vue')
}, {
path: "/toLogin",
redirect: "/login"
}, { //----------------新增代码,建议把注释删掉
path: '/:pathMatch(.*)*',
name: '404',
meta: { title: "404" },
component: () =>
import ('../views/error/404.vue')
}]


//2 :创建路由对象
const router = createRouter({
// 引入访问模式
history: createWebHistory(),
routes
})



// 动态注册路由方法- 考虑到
export function registerRoutes(menuList) {
// 是否有新的路由
let hasNewRoutes = false
const findAndAddRoutesByMenus = (arr) => {
arr.forEach(e => {
// 一定要查询我router.js中asyncRoutes存在的路由,才去绑定,否则不绑定
let item = asyncRoutes.find(o => o.path == e.path)
// item存在,并且没有绑定过router.hasRoute,
if (item && !router.hasRoute(item.path)) {
// 如果没有绑定,就开始绑定。这里就是admin,代表不论多个子元素,最中绑定全部挂载admin这集
// 也就意味着:后续所有的子元素,孙子元素等的路由访问,都跳转到PuaAdmin.vue的router-view的位置
router.addRoute("PugAdmin", item)
hasNewRoutes = true
}

// 递归,如果当前子菜单还有子元素,
if (e.children && e.children.length > 0) {
findAndAddRoutesByMenus(e.children)
}
})
}

// 开始递归调用动态绑定路由关系
findAndAddRoutesByMenus(menuList)

// 第一次绑定:就是 true,
// 如果已经全部绑定过就是: false
// 返回这个作用:就是为了让后续的路由访问只绑定一次,没必要每次访问都绑定
return hasNewRoutes
}


/*
这个开关,是用来控制动态路由注册只绑定一次
只要不刷新、F5,第一次就是false, 后面永远都是 true
*/
let loadNewRoute = false;
// 定义后置守卫--拦截器思想
router.beforeEach(async(to, from, next) => {
// 全屏动画开启
showFullLoading()

// 判断是否已经登录
var isLogin = store.getters["user/isLogin"];

// 没有登录,强制跳转回登录页
if (!isLogin && to.path != "/login") {
toastError("请先登录", "error")
next("/toLogin")
}

// 防止重复登录
if (isLogin && to.path == "/login") {
toastError("请勿重复登录", "error")
next({ path: from.path ? from.path : "/" })
}



let hasNewRoute = false; //F5的问题

if (isLogin && !loadNewRoute) {
// 锁住
loadNewRoute = true;

// 从数据查询菜单信息,开始进行动态注册
let menusList = await store.dispatch("menu/asyncGetMenuList")

// 动态注册路由
hasNewRoute = registerRoutes(menusList);
}

// 这里为啥要判断,是因为当前路由刷新的时候,要给一个具体的执行。直接执行next肯定不行。因为不知道下个页面是多少
next();
})



/* 后置守卫 */
router.afterEach((to, from) => {
// 结束全屏动画
hideFullLoading();
// 标题切换
document.title = to.meta.title + "-PugAdmin-后台管理系统" || "PugAdmin-后台管理系统";
})





// 3: 导出即可生效
export default router

动态注册路由的刷新问题

如果直接刷新路由,造成404页面。原因是?因为刷新的路由没有from只有to. 所以出现了404。next就不知道访问到哪里去。

··

关于动态注册组件的问题_vue.js