关于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");
}

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理