之前记录过一个动态路由实现过程,但是最近感觉那个还是臃肿,所以做了精简。以下是船新版本。
一、准备完整的路由和按钮的树
在这里需要先梳理一下父子结构关系,每个节点都给他一个唯一的id。这个完整的路由用来在设置界面展示完整的树,以及界面请求到权限id后进行递归遍历使用的。
- 以下是我项目的结构。
- 下面是我提前准备的路由结构,因为/home后期会做按钮权限,所以把他提出来,后边直接将按钮树加入homeHandle.
- 以下是首页的按钮结构树
- 下面是界面
二、组装路由和按钮的结构树,并保存初始的整体结构
上一步为了把home界面按钮树组装到路由的树中,我们预留了homeHandle。这里只需要引入并拷贝即可。
import {rootPath, homeHandle} from "@/router/store";
import {paneItems} from "@/views/home/js/store";
import {getUserMenuPermission} from "@/apis/user";
// 组装json
homeHandle.children = paneItems.children;
// 保存一下初始的树结构,下边要根据权限进行删除了---用来为用户添加权限时的展示
const rootPathCopy = JSON.parse(JSON.stringify(rootPath));
三、什么时候去判断路由和按钮该不该展示
之前的版本是在登录的时候请求了有权限的id,这样有个问题就是只要用户不退出登录,即使管理员修改了权限,也不会更新。而且将有权限的id存在浏览器本地,这种不太安全。 所以这次我放到,每次用户刷新界面,先去请求接口,再做判断进行跳转。
另外之前的版本会保存有权限的叶子结点整个枝干, 这次尝试了只保存有权限的叶子节点。(这里导致了判断的valid函数需要修改一下)
- 以下为router.js的代码:
import {createRouter, createWebHistory} from "vue-router";
import {useAuth} from "@/store/auth";
import {setRoutes} from "@/store/role";
const Login = () => import("@/views/login");
const routes = [
{
path: "/login", name: "login", component: Login
}
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL), routes
});
let isRefresh = true; // 做一个标志,防止每次跳转都会执行验证代码
router.beforeEach(async (to, from, next) => {
const auth = useAuth();
if (to.path === "/login") {
localStorage.clear();
isRefresh = true;
return next();
} else {
if (!auth.token) return next("/login");
if (isRefresh && to.path !== "/login") {//这里用到了isRedresh【也是主要的验证代码】
next(false); // 先将路由截断,因为下面会有异步请求,不这样做,会直接报找不到要跳转的路由
try {
await setRoutes(router);
isRefresh = false;
return router.push(to.path);
} catch (e) {
return router.push("/login");
}
}
return next();
}
});
export default router;
- 下面是验证方法的代码:
function setRoutes(Router) {
return new Promise(async (resolve, reject) => {
try {
let {data} = await getUserMenuPermission({userName: localStorage.getItem("ocean_username")});
const roles = {};
for (const {menu_key} of data) {
roles[menu_key] = true;
} // 获取到权限id后存成个json,这样查找效率会快一些
const checkNode = (node) => node.roleIndex !== undefined && roles[node.roleIndex]; // 他就是节点有无权限的判断
/*
下面是个后序遍历的方法,因为我这个项目不需要控制所有节点的权限,
所以有可能后序遍历到不了叶子节点就遇到了有权限的roleIndex,
这时候结束当下递归。如果一直到了子节点都没遇到有权限的roleIndex,
那就砍掉这个枝干。
在设置界面我只保存了有权限的”叶子节点”(这个是相对于设置界面的展示树来说的,
在展示树上面的节点都是需要控制的)。
*/
const validRoute = (function iterate(o, parent, oIndex) {
let newItem = null;
const isValid = checkNode(o);
if (o.children && o.children.length && !isValid) {
// 有孩子切没被验证成功,进行递归。
const allValid = [];
const routeValid = [];
for (let i = o.children.length - 1; i >= 0; i--) {
// 因为孩子可能会用splice砍掉自己,所以倒着遍历
const cur = o.children[i];
const validChild = iterate(cur, o.children, i);
if (validChild) {
// 如果返回了不是null,说明可能是路由,可能是按钮。
// 只有路由才要被加到newItem中,按钮直接操作O;
allValid.unshift(validChild);
if (validChild.component) routeValid.unshift(validChild);
}
}
if (!allValid.length && oIndex !== -1 && parent) parent.splice(oIndex, 1);
// 每一个子节点有权限 把自己砍掉
else if (o.component) {
// 自己是路由, 且有路由子节点把孩子加进来
newItem = {
...o
};
delete newItem.children;
delete newItem.redirect;
if (routeValid.length) {
newItem.children = routeValid;
newItem.redirect = routeValid[0].path;
}
} else newItem = { // 自己不是路由,返回O
...o
};
} else {
if (isValid) newItem = {
...o
}; // 被验证了 返回新节点
else if (oIndex !== -1 && parent) {
parent.splice(oIndex, 1);
} // 没验证成功 砍掉自己
}
return newItem;
})(rootPath, null, -1);
// 上边的跑完以后 validRoute是完整的路由树,rootPath中的按钮树也被裁剪完成了,
// 生成UI的时候去从rootPath中找按钮树即可。
validRoute && await Router.addRoute("", validRoute);
resolve("ok");
} catch (e) {
reject(null);
}
});
}
四、设置界面
这里我就用的el-tree。引入了rootPathCopy来生成的tree。
至此大概过程就结束了,刚刚做完,可能有bug。往后慢慢修改吧。