以前写的是写什么玩意,全是bug,现在初步修正一下内容:登录bug,动态路由的添加与清空,动态路由刷新后白屏的情况。
效果图:
动态路由(前端控制):在Main.ts(我是在router里面写)里的路由守卫来做
实现了根据用户角色来获取相对应的路由,实现了权限管理。但是!一刷新问题就来了,页面直接一片空白。
网上冲浪一整天,网上相关问题解决方案我都尝试过来,感觉他们全是复制粘贴的,可行性都没有:
1.网上最多的解决方案:next({…to, replace: true }),会造成死循环,尽管解决了死循环问题,还是没用
2.将路由信息保存本地(或者利用vuex数据持久化插件vuex-persistedstate+ 数据加密插件secure-ls 一起使用的都有),可以拿到地址,但是匹配不了路由。
问题的关键:beforeEach是异步,在路由守卫之前,路由就已经匹配结束了,刷新后自然匹配不到路由,所以刷新后就是空白的(若不是,点击添加的动态路由路径也会跳转空白)。
我的解决办法:1.将所有动态路由数据中含有该角色的筛选出来,2.把动态添加路由的方法也转化成异步函数:
router => index.js 部分数据:
// 将所有动态路由中含有该角色的筛选出来
function AddRouter(res, current_role) {
let newarr = [];
res.forEach((item) => {
if (item.meta && item.meta.roles.includes(current_role)) {
newarr.push(item);
}
});
// console.log(newarr);
return newarr;
}
async function createNewRouter() {
//前端控制路由,后端只是提供角色, 将静态路由和动态路由(根据后端获取的角色来筛选)组合在一起,然后放在vuex中去
let DongtaiRouter = houduanluyou;
let userInfo = JSON.parse(window.localStorage.getItem("userInfo")); //从后端获取到的角色
console.log(userInfo);
switch (userInfo?.name) {
case "管理员":
let add = AddRouter(DongtaiRouter, "admin");
// console.log(add);
// 动静态路由拼接放在vuex中去
store.commit("QuietRouter", add);
//添加动态路由
for (let i = 0; i < add.length; i++) {
router.addRoute(add[i]); //addRoute 接收的是对象
}
break;
case "总经理":
let add2 = AddRouter(DongtaiRouter, "manager");
// console.log(add2);
// 动静态路由拼接放在vuex中去
store.commit("QuietRouter", add2);
//添加动态路由
for (let i = 0; i < add2.length; i++) {
router.addRoute(add2[i]); //addRoute 接收的是对象
}
break;
default:
store.commit("QuietRouter", []);
break;
}
}
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes, houduanluyou
})
async function init() {
return new Promise((resolve, reject) => {
createNewRouter() //添加动态路由
console.log('初始化完成');
resolve()
})
}
Vue.use(Router)
init()
//全局路由守卫(跳转任何一次路由都会执行一次)
router.beforeEach((to, from, next) => {
if (to.path == '/') {//如果是登录页面路径,就直接next()
next();
} else {
// to.matched有效防止直接更改url地址访问的情况(测试的时候最好不要用动态路由,没有角色匹配不了)
if (to.matched.some(record => record.meta.requiresAuth)) { //判断当前要跳转的路径是否需要登录权限
let falg = window.localStorage.getItem('falg')
//console.log(falg);
if (falg && falg != null) { //判断是否登录
console.log('有权限,已登录');
next()
} else {
alert('有权限,没登录');
next({
path:'/',
query: { redirect: to.fullPath } //在登录事件中获取新路径数据,登录成功后跳转到之前要跳转的页面
})
}
}else{
console.log('这个不需要权限,直接通过');
next()
}
}
})
export { router, init } //将方法 init 抛出,在登录按钮中也要调用一次,否则上一角色退出后,另一个角色由于异步问题无法获取登录信息和路由
store => index.js 部分数据:
import createPersistedState from "vuex-persistedstate"; //数据持久化插件
import SecureLS from "secure-ls"; //数据加密插件
var ls = new SecureLS({
encodingType: "aes", //加密类型
isCompression: false,
encryptionSecret: "encryption", //PBKDF2值
});
Vue.use(Vuex)
export default new Vuex.Store({
state: {
jingtai:[],//静态路由
routerarr:[], //静态路由+动态路由
},
getters: {
updata(state){
return state.routerarr
}
},
mutations: {
QuietRouter(state,data){
state.jingtai=router.options.routes
state.routerarr=state.jingtai.concat(data)
},
// 退出时清空路由
celRouters(state,data){
state.jingtai=[]
state.routerarr=[]
console.log('执行了',state.routerarr);
}
},
// 数据持久化(一般的数据可以实现,动态路由数据不行)
// plugins: [createPersistedState()],
// plugins: [
// createPersistedState({
// key: "encryptionStore",
// storage: {
// getItem: (key) => ls.get(key),
// setItem: (key, routerarr) => ls.set(key, routerarr),
// removeItem: (key) => ls.remove(key),
// },
// }),
// ], //vuex持久化+加密
})
动态路由添加后,角色登出时,及时清空添加的动态路由:网上最多的方法就是 resetRouter,我没搞出来,可能我方法没搞对,有懂的大佬可以指点我一下,谢谢! 我是采用 location.reload() 强制刷新解决(也可直接清空vuex中的路由数据)。
递归组件(直接使用Vuex中的路由数据):
LeftMenu.vue 文件:
<template>
<div >
<el-row :style="row" class="tac" >
<el-col :span="12">
<!-- Menu组件上提供了跳转方式 启用el-menu上的router属性;
第二步是设置导航的index属性,index的值就是要跳转的路径。
el-menu上的 :default-active="$route.path" 就是默认跳转到index的值-->
<el-menu
:style="menu"
:default-active="$route.path"
class="el-menu-vertical-demo"
router
:collapse="false">
<!-- 菜单具体项组件 将路由数据 routerarr 传递给子组件-->
<MenuItem :route='routerarr' ></MenuItem>
</el-menu>
</el-col>
</el-row>
</div>
</template>
MenuItem.vue 文件:
<template>
<div>
<el-submenu
:style="submenu"
v-for="(child,index2) in route"
:key="index2"
v-if="child.meta.hasSubMenu"
:index="getPath(child.path)" > // 这里index的值就是要跳转的路径
<template slot="title">
<i :class="child.meta.icon"></i>
<span slot="title">{{child.meta.title}} </span>
</template>
<!-- 递归组件本身 ,这里要注意使用的是 跟下面的属性name的值保持一致,相当于循环使用该组件 -->
<MenuItem :basepath="getPath(child.path)" :route='child.children'></MenuItem>
</el-submenu>
<el-menu-item :style="menuitem" :index="getPath(child.path)" v-else>
<i :class="child.meta.icon"></i>
<span slot="title">{{child.meta.title}} </span>
</el-menu-item>
</div>
</template>
<script>
export default {
name:'MenuItem', // 递归的组件,用inport引入注册来使用则报错
props:['route','basepath'],
// 这个方法是用来拼接路径
methods :{
// routepath 为当前菜单的path值
// getpath: 拼接 当前菜单的上一级菜单的path 和 当前菜单的path
getPath: function(routePath){
let path=''
if(this.basepath==undefined){
path = routePath
}else{
path = this.basepath + (this.basepath?'/':'') + routePath
}
// console.log(path);
return path
}
},
</script>
实现递归组件:
从 下面 的精简的代码可以看到:
一级菜单循环的是route,即最初我们HomeView.vue 文件传递给 LeftMenu.vue 文件再传递给MenuItem.vue文件的路由数据routerarr,循环出来的每一项定义为child
二级菜单循环的是child.children,循环出来的每一项定义为childRoute
三级菜单循环的是childRoute.children,循环出来的每一项定义为grandson
所以不难看出二级菜单、三级菜单循环的数据源都是前一个循环结果项的children,所以这就实现了 MenuItem.vue 文件
<!-- 一级菜单 -->
<el-submenu v-for="child in route" v-if="child.meta.hasSubMenu">
<!-- 二级菜单 -->
<el-submenu v-for="childRoute in child.children" v-if="childRoute.meta.hasSubMenu">
<!-- 三级菜单 -->
<el-submenu v-for="grandson in childRoute.children" v-if="grandson.meta.hasSubMenu">
</el-submenu>
</el-submenu>
</el-submenu>
菜单栏实现了,然后就是路由的跳转:这里我使用了NavMenu组件提供的跳转方式:使用步骤:1.启用el-menu上的router属性;2.第二步是设置菜单的路径index属性值。
1.启用el-menu上的router属性:
<el-menu
:style="menu"
:default-active="$route.path"
class="el-menu-vertical-demo"
router
:collapse="false">
<!-- 菜单具体项组件 -->
<MenuItem :route='routerarr' ></MenuItem>
</el-menu>
2.设置菜单的index属性,index的值就是要跳转的路径:
首先我们知道每个菜单栏的路径:
首页
员工管理
员工统计 index="/employee/employeeStatistics"
员工管理 index="/employee/employeeManage"
考勤管理
考勤统计 index="/attendManage/attendStatistics"
考勤列表 index="/attendManage/attendList"
异常管理 index="/attendManage/exceptManage"
员工统计
员工统计 index="/timeManage/timeStatistics"
员工统计 index="/timeManage/timeList"
选项一 index="/timeManage/timeList/options1"
选项二 index="/timeManage/timeList/options2"
我们代码中拿到菜单的路径是 child.path ,拿到这个值和我们每个菜单栏的路径并不符合,都缺少了上一级菜单的path值,所以我们需要将当前菜单path值传递给下一级菜单,然后将传递下来的值basepath和下一级菜单的path拼接在一起就是下一级菜单的正确路径,
完整代码如下
<template>
<div>
<el-submenu
:style="submenu"
v-for="(child,index2) in route"
:key="index2"
v-if="child.meta.hasSubMenu"
:index="getPath(child.path)" >
<template slot="title">
<i :class="child.meta.icon"></i>
<span slot="title">{{child.meta.title}} </span>
</template>
<!-- 递归组件 -->
<MenuItem :basepath="getPath(child.path)" :route='child.children'></MenuItem>
</el-submenu>
<el-menu-item :style="menuitem" :index="getPath(child.path)" v-else>
<i :class="child.meta.icon"></i>
<span slot="title">{{child.meta.title}} </span>
</el-menu-item>
</div>
</template>
<script>
export default {
name:'MenuItem',
props:['route','basepath'],
data(){
return{
menuitem:{
boxSize:'border-box',
// paddingLeft:'40px',
},
submenu:{
// paddingLeft:'20px',
// paddingRight:'20px',
boxSize:'border-box',
},
}
},
mounted(){
console.log(this.route);
},
methods :{
// routepath 为当前菜单的path值
// getpath: 拼接 当前菜单的上一级菜单的path 和 当前菜单的path
getPath: function(routePath){
let path=''
if(this.basepath==undefined){
path = routePath
}else{
path = this.basepath + (this.basepath?'/':'') + routePath
}
// console.log(path);
return path
}
},
}
</script>
注意这个递归组件这里<MenuItem :basepath="getPath(child.path)" :route='child.children'></MenuItem>也是调用了getPath 方法来拼接,如果不调用,我们可以看到二级菜单的index值没问题,但是仔细看,发现工时管理-工时列表下的两个三级菜单index值还是有问题,缺少了工时管理这个一级菜单的path。因为basepath传递的只是上一级菜单的path,在递归二级菜单时,index的值是一级菜单的path值+二级菜单的path值;那当我们递归三级菜单时,index的值就是二级菜单的path值+三级菜单的path值,这也就是为什么工时管理-工时列表下的两个三级菜单index值存在问题。
////////////////////////////// 后面的都是废话,可不用看!
第三.来说说登录我遇到的坑吧
登录也是用的element表单提交
<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
1.这里复制下来一定要 注意 el-from 里面的 label-width="100px" 属性,最好是删除这个属性,不然后面调整样式时有一个 margin-left='100px' 很难调整。
鸡肋:NProgress 进度条:
安装:cnpm install --save nprogress
main.js中引入:
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
根据需求在main.js中进行一些配置:
NProgress.configure({
easing: 'ease', // 动画方式
speed: 1000, // 递增进度条的速度
showSpinner: false, // 是否显示加载ico
trickleSpeed: 1000, // 自动递增间隔
minimum: 1.0 // 初始化时的最小百分比
})
然后再main.js中全局路由守卫:
router.beforeEach((to, from , next) => {
// 每次切换页面时,调用进度条
NProgress.start();
next();
});
router.afterEach(() => {
// 在即将进入新的页面组件前,关闭掉进度条
NProgress.done()
})
样式:它默认显示为蓝色进度条,自定义进度条颜色,可在全局css中或在app.vue下写入自己自定义的css样式; 比如:
/* 自定义进度条颜色 */
#nprogress .bar {
background: #F811B2 !important;
}
相关配置属性NProgress使用笔记