关于动态注册组件的问题

关于动态注册组件的问题

遗留问题

上节课菜单,你们放在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就不知道访问到哪里去。

··
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙崎流河

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值