前言
最近就业中没人要,终日呆在家里总想写点什么,顺便增强一下代码程度,在 router4.0 中增加路由的办法从 addRoutes() 变成 addRoute(),以前实现的形式就产生了变动,不过也只是小改变。
为什么不举荐间接写路由表?
如果把路由表固定写在前端页面中,用户就能够拜访所有页面,后端就须要一份跟前端一样的路由表来配置权限,对页面进行比对,依据不同角色返回相应的页面权限。如果前端路由表批改了,那么后端同时也须要批改,这样就须要同时保护两端,不仅麻烦,前端只能改源码,还有可能因为遗记批改而导致 bug。
动静路由
如果路由表是由后端获取的,那么你拜访了没有权限的页面会返回 404 谬误,并且只需后端保护,权限管制更加残缺。
具体实现
在本例中没有应用 Vuex 来存储后端传来的路由列表数据,我感觉没必要,间接应用 sessionStorage 来存储就能够了,因为一旦页面刷新了,Vuex 中的数据就会隐没,那就得从新从新申请数据,会影响页面的加载速度。
问题剖析
- 在什么时候加载路由表?
- 路由表加载完应该做什么?
- 刷新如何从新加载?
- 用户切换账号会有什么问题?
踩坑 (答复以上问题)
-
在用户登录后跳转的 router.beforeEach 钩子外面异步加载
router.beforeEach((to, from, next) => { // 注册动静路由 registerRoutes().then(() => {// 跳转事件}).catch(() => {// 解决异样事件}) });
-
进行路由重定向,因为之前跳转的时候地址还不存在路由表中,如果间接 next() 会找不到页面,所以须要重定向,这里还须要做一个判断,不然会进入死循环。
if (routeFlag) {next(); } else { // 注册动静路由 registerRoutes().then(() => { routeFlag = true; next({...to, replace: true}); }).catch(() => {// 解决异样事件}) }
- 首先判断用户 token 是否登录,如果已登录,获取 sessionStorage 存储的路由表,进入 beforeEach 会主动从新注册路由。
- 应该把 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");
}