乐趣区

关于vue.js:vue-router-v4x-路由的动态加载实现-完整代码

前言

最近就业中没人要,终日呆在家里总想写点什么,顺便增强一下代码程度,在 router4.0 中增加路由的办法从 addRoutes() 变成 addRoute(),以前实现的形式就产生了变动,不过也只是小改变。

为什么不举荐间接写路由表?

如果把路由表固定写在前端页面中,用户就能够拜访所有页面,后端就须要一份跟前端一样的路由表来配置权限,对页面进行比对,依据不同角色返回相应的页面权限。如果前端路由表批改了,那么后端同时也须要批改,这样就须要同时保护两端,不仅麻烦,前端只能改源码,还有可能因为遗记批改而导致 bug。

动静路由

如果路由表是由后端获取的,那么你拜访了没有权限的页面会返回 404 谬误,并且只需后端保护,权限管制更加残缺。

具体实现

在本例中没有应用 Vuex 来存储后端传来的路由列表数据,我感觉没必要,间接应用 sessionStorage 来存储就能够了,因为一旦页面刷新了,Vuex 中的数据就会隐没,那就得从新从新申请数据,会影响页面的加载速度。

问题剖析

  1. 在什么时候加载路由表?
  2. 路由表加载完应该做什么?
  3. 刷新如何从新加载?
  4. 用户切换账号会有什么问题?

踩坑 (答复以上问题)

  1. 在用户登录后跳转的 router.beforeEach 钩子外面异步加载

    router.beforeEach((to, from, next) => {
        // 注册动静路由
        registerRoutes().then(() => {// 跳转事件}).catch(() => {// 解决异样事件})
    });
  2. 进行路由重定向,因为之前跳转的时候地址还不存在路由表中,如果间接 next() 会找不到页面,所以须要重定向,这里还须要做一个判断,不然会进入死循环。

    if (routeFlag) {next();
    } else {
        // 注册动静路由
        registerRoutes().then(() => {
            routeFlag = true;
            next({...to, replace: true});
        }).catch(() => {// 解决异样事件})
    }
  3. 首先判断用户 token 是否登录,如果已登录,获取 sessionStorage 存储的路由表,进入 beforeEach 会主动从新注册路由。
  4. 应该把 sessionStorage 存储的路由表移除,不然切换账号后会获取到上一个登录账号的路由表。

残缺代码

Vue3 + Router4 + TypeScript

// @/router/index.ts
import {createRouter, createWebHashHistory, RouteRecordRaw} from "vue-router";
import {store} from "@/store";
import {registerRoutes} from "@/router/dynamic";

// 根底页面
const routes: Array<RouteRecordRaw> = [
    {
        path: "/login",
        name: "Login",
        component: () => import("@/views/login.vue"),
        meta: {title: "登陆",},
    },
    {
        path: "/register",
        name: "Register",
        component: () => import("@/views/register.vue"),
        meta: {title: "注册",},
    },
    {
        path: "/",
        redirect: "/home",
        name: "HomeIndex",
        component: () => import("@/views/index.vue"),
        meta: {title: "首页",},
    },
];

const router = createRouter({history: createWebHashHistory(),
    routes,
});

// 避免路由有限循环
let routeFlag = false;

router.beforeEach((to, from, next) => {
    const token = store.state.user.token;

    if (token) {if (routeFlag) {next();
        } else {
            // 注册动静路由
            registerRoutes().then(() => {
                routeFlag = true;
                next({...to, replace: true});
            }).catch(() => {// 解决异样事件})
        }
    } else {
        routeFlag = false;
        if (to.name === "Login" || to.name === "Register") {next();
        } else {
            next({
                name: "Login",
                query: {redirect: to.fullPath},
            });
        }
    }
});

export default router;

类型定义我就不贴出来了,有需要就本人写,不然就 typeof,sessionData 的封装办法也不贴了,你也能够间接用 localStorage.getItem()

// @/router/dynamic.ts
import router from '@/router'
import {sessionData} from "@/lib/storage";
import {IAdminRoute} from "@/api/admin";
import {ElLoading} from "element-plus";

/**
 * 注册路由
 * 用户切换账号需移除 sessionStorage 中的 routerMap 数据
 */
export const registerRoutes = (): Promise<boolean> => {const routerMap: IAdminRoute[] = sessionData.get("routerMap");

    return new Promise((resolve, reject) => {
        // 增加 404 页面
        router.addRoute({path: "/:catchAll(.*)",
            redirect: "/404",
            name: "NotFound",
        })

        if (routerMap.length) {addRoutes(routerMap);
            resolve(true);
        } else {const loading = ElLoading.service();
            // 模仿后端申请数据
            window.setTimeout(() => {loading.close();
                const result = [
                    {
                        path: "/product",
                        name: "Product",
                        component: "layouts/page/index.vue",
                        meta: {title: "商品治理",},
                        children: [
                            {
                                path: "index",
                                name: "ProductIndex",
                                component: "views/product/product-index.vue",
                                meta: {
                                    title: "商品列表",
                                    auth: ["delete"]
                                },
                            },
                            {
                                path: "detail",
                                name: "ProductDetail",
                                component: "views/product/product-detail.vue",
                                meta: {
                                    title: "商品详情",
                                    auth: ["upload"]
                                },
                            }
                        ],
                    },
                    {
                        path: "/admin",
                        name: "Admin",
                        component: "layouts/page/index.vue",
                        meta: {title: "系统管理",},
                        children: [
                            {
                                path: "index",
                                name: "AdminIndex",
                                component: "views/admin/admin-index.vue",
                                meta: {
                                    title: "管理员列表",
                                    auth: ["delete", "audit"]
                                },
                            },
                            {
                                path: "edit",
                                name: "AdminEdit",
                                component: "views/admin/admin-edit.vue",
                                meta: {
                                    hidden: true,
                                    title: "管理员编辑",
                                    auth: ["add", "edit"]
                                },
                            },
                            {
                                path: "role",
                                name: "AdminRole",
                                component: "views/admin/admin-role.vue",
                                meta: {title: "管理员角色",},
                            }
                        ],
                    },
                ];
                sessionData.set("routerMap", result as never);
                addRoutes(result);
                resolve(true);
            }, 1000)
        }
    })
}

/**
 * 动静增加路由
 */
const addRoutes = (routes: IAdminRoute[], parentName = ""): void => {routes.forEach((item) => {if (item.path && item.component) {const componentString = item.component.replace(/^\/+/, ""), // 过滤字符串后面所有'/' 字符
                componentPath = componentString.replace(/\.\w+$/, ""); // 过滤掉后缀名,为了让 import 退出 .vue,不然会有正告提醒...

            const route = {
                path: item.path,
                redirect: item.redirect,
                name: item.name,
                component: () => import("@/" + componentPath + ".vue"),
                meta: item.meta
            }

            if (parentName) {
                // 子级路由
                router.addRoute(parentName, route);
            } else {
                // 父级路由
                router.addRoute(route);
            }

            if (item.children && item.children.length) {addRoutes(item.children, item.name);
            }
        }
    })

};

/**
 * 生成治理菜单
 */
export const getAuthMenu = () => {
    // 这里就依据路由生成后盾左侧菜单
    const routerMap = sessionData.get("routerMap");
}
退出移动版