使用VUE开发用户后台时的动态路由问题、按钮权限问题以及其他页面处理问题
一叶、知秋 人气:3如今前后端分离是大势所趋,笔者虽然是做后台的,但也不得不学学前端的流行框架VUE -_-||| 。
为了学习VUE,笔者搭建了一个简单的用户后台,以此来了解VUE的开发思路(注:本项目不用于实际开发,只是为了学习,本文重点在于vue的动态路由添加,动态权限以及页面处理的一些小问题)。
一、项目组成
VUE 2.6.11 + axios +VueX + ElementUI 2.13.2
二、整体思路
1. 用户登录后,获取菜单数据,用以显示菜单。
2. 用户登录后,后台获取Vue路由数据,用以动态添加到VueRouter中。
3. 用户登录后,从后台获取用户的权限,用以控制用户是否对某一功能具有可操作权限。
三、具体实现
· 1. 登录。由于本人学习重点是使用VUE动态添加路由、菜单显示和权限控制,所以登录页面没有做具体功能,点击登录按钮就表示登录成功了。
由于登录页是用户的第一界面,不存在任何权限问题,所以笔者就直接将登录页的路由直接写在了VueRouter实例中。如下:
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) const routes = [{ path: '/Login', name: 'Login', component: () => import('../views/Login.vue') }] export function initRouter() { return new VueRouter({ routes }) } export default initRouter()
用户通过 http://localhost:8080/#/login 可访问到登陆页面:
点击登录按钮表示登录成功!
登录成功后的处理:
(1)向后台发请求拿到路由数据并存入VueX的state中,并通过addRoutes(routerObjArr)动态添加到路由实例中。注:后台返回的数据结构需跟route相一致,如图:
前端所需数据结构:
后台返回的数据结构:
细节处理:由于后台返回的component字段是个组件地址字符串,这就需要将后台返回的route数据 一 一 做处理,通过import() 方法动态的加载组件,然后将返回的compoent对象重新赋值到component字段上。如图:
代码:
const _import = require('@/router/_import_' + process.env.NODE_ENV) //获取组件的方法 /**将router的json字符串中的component转换为组件对象 */ export function filterAsyncRouter(asyncRouterMap) { if (!asyncRouterMap) return []; function _iter(before) { const after = Object.assign({}, before) if (after.component) { after.component = _import(after.component); } if (after.children && after.children.length) { after.children = filterAsyncRouter(after.children); } return after } return asyncRouterMap.map(_iter) }
图中所用的import方法,根据生产环境不同,引用不同的文件,如图:
各自的代码如下:
_import_development.js:
module.exports = file => require('@/views/' + file + '.vue').default // vue-loader at least v13.0.0+
_import_production.js
module.exports = file => () => import('@/views/' + file + '.vue')
将后台的返回的路由数据处理完成后,最后就可以使用addRoutes方法动态加入VueRouter中了。此时,用户便可以按照路由访问页面了。代码如下:
//动态生成路由 axios .get("https://localhost:5001/AdminApi/Home/GetMenuJson?userid=" + 1) .then(res => { //取出路由列表 (isRoute=1) res.data.obj.router[0].children = res.data.obj.router[0].children.filter( a => a.meta.isRoute == 1 //isRoute=1的 ); //存入缓存 this.$store.commit("setRoutes", res.data.obj.router); //转换组件对象 var getRouter = filterAsyncRouter(res.data.obj.router); //打印当前路由列表 console.log("当前路由列表:", res.data.obj.router[0].children); //清空之前的路由信息 this.$router.matcher = initRouter().matcher; //重新添加路由信息 this.$router.addRoutes(getRouter); //跳转到 Layout 组件 this.$router.push("/"); });
(2)向后台发请求拿到权限数据,并存入VueX的state中。代码如下:
axios .get( "https://localhost:5001/AdminApi/Access/ActionPermission?userid=" + 1 ) .then(res => { //存入权限 console.log("权限列表:", res.data.obj); this.$store.commit("setAccess", res.data.obj); });
(3)向后台请求数据并存入VueX中的state之前,需要清空上一次存入的数据(包括路由数据和权限数据),否则会造成数据混乱,如图:
(4)addRoutes之前,不仅要做component字段的字符串转对象的处理,还要清掉上一个用户登录后存入router中的路由数据,否则会造成数据混乱或者vue警告重复的路由名称。
Login.vue组件中的全部代码如下:
<template> <div class="about"> <button @click="login">登录</button> </div> </template> <script> import { filterAsyncRouter } from "../common/promission"; import axios from "axios"; import { initRouter } from "@/router"; export default { created() { this.$store.commit("logout"); }, methods: { login() { //动态生成路由 axios .get("https://localhost:5001/AdminApi/Home/GetMenuJson?userid=" + 1) .then(res => { //取出路由列表 (isRoute=1) res.data.obj.router[0].children = res.data.obj.router[0].children.filter( a => a.meta.isRoute == 1 //isRoute=1的 ); //存入缓存 this.$store.commit("setRoutes", res.data.obj.router); //转换组件对象 var getRouter = filterAsyncRouter(res.data.obj.router); //打印当前路由列表 console.log("当前路由列表:", res.data.obj.router[0].children); //清空之前的路由信息 this.$router.matcher = initRouter().matcher; //重新添加路由信息 this.$router.addRoutes(getRouter); //跳转到 Layout 组件 this.$router.push("/"); }); axios .get( "https://localhost:5001/AdminApi/Access/ActionPermission?userid=" + 1 ) .then(res => { //存入权限 console.log("权限列表:", res.data.obj); this.$store.commit("setAccess", res.data.obj); }); } } }; </script>
promiss.js代码如下:
const _import = require('@/router/_import_' + process.env.NODE_ENV) //获取组件的方法 /**将router的json字符串中的component转换为组件对象 */ export function filterAsyncRouter(asyncRouterMap) { if (!asyncRouterMap) return []; function _iter(before) { const after = Object.assign({}, before) if (after.component) { after.component = _import(after.component); } if (after.children && after.children.length) { after.children = filterAsyncRouter(after.children); } return after } return asyncRouterMap.map(_iter) }
store.js代码如下:
import Vue from 'vue' import Vuex from 'vuex' import VuexPersistence from 'vuex-persist' Vue.use(Vuex) export default new Vuex.Store({ state: { routes: [], }, mutations: { setRoutes: (state, routes) => { state.routes = routes }, setAccess: (state, access) => { state.access = access; }, logout: (state) => { state.routes = []; state.access = [] } }, actions: {}, modules: {}, plugins: [new VuexPersistence().plugin] })
2. 菜单。将Layout组件用作菜显示组件,将ele中的菜单组件复制到该组件中,并通过向后台请求数据,拿到菜单和菜单对应的分组数据 。拿到菜单和菜单分组数据后,循环遍历,将菜单按照对应的分组全部显示(后台判断当前用户可显示的菜单,没有权限的菜单直接不返给前台)。vue代码以及后台数据如下:
<template> <el-container> <el-header> <el-dropdown> <i class="el-icon-setting"></i> <el-dropdown-menu slot="dropdown"> <el-dropdown-item>修改密码</el-dropdown-item> <el-dropdown-item>退出</el-dropdown-item> </el-dropdown-menu> </el-dropdown> <span>王小虎</span> </el-header> <el-container> <el-aside width="250px"> <el-menu @select="handleSelect"> <el-submenu :index="group.name" v-for="group in groupList" :key="group.id"> <template slot="title"> <i class="el-icon-message"></i> {{group.title}} </template> <template v-for="router in routerList"> <el-menu-item :index="router.path" :key="router.meta.id" v-if="router.meta.groupId == group.id" >{{router.meta.title}}</el-menu-item> </template> </el-submenu> </el-menu> </el-aside> <el-main> <router-view /> </el-main> </el-container> </el-container> </template> <script> import axios from "axios"; export default { data() { return { activeIndex: "/home/Index", groupList: [], routerList: [] }; }, mounted() { this.getGroupList(); this.getRouterList(); }, methods: { //菜单点击事件 handleSelect(key) { this.$router.push(key); }, //获取菜单分组数据 getGroupList() { var that = this; axios .get("https://localhost:5001/AdminApi/Home/GetGroupJson") .then(res => { that.groupList = res.data.obj; }); }, //获取菜单数据 getRouterList() { var that = this; axios .get("https://localhost:5001/AdminApi/Home/GetMenuJson") .then(res => { that.routerList = res.data.obj.router[0].children.filter( a => a.meta.display == 1 //取display=1的 ); console.log("当前菜单列表"); console.log(that.routerList); }); } } }; </script> <style> @import "../styles/layout.css"; /*引入公共样式*/ </style>
后台分组数据:
{ "id": 14, "name": "Customer", "title": "客户中心", "target": "mainRigh", "url": "#", "icoCss": "layui-icon-username", "delFlag": 0, "sortNo": 0, "createMan": 1, "createTime": "2019-05-05T11:30:06" }, { "id": 9, "name": "System", "title": "系统设置", "target": "123", "url": "#", "icoCss": "fa-gears", "delFlag": 0, "sortNo": 1, "createMan": 1, "createTime": "2019-05-05T11:29:56" }
后台菜单数据:
效果图:
3. 功能页面的处理。
(1)组件的动态加载规则。 由于该vue项目中的组件是动态加载,那么后台返回的路由数据中的component字段中的路径自然也要按照某一种规则来返给前端。否则会造成import()组件的时候,由于地址不对解析加载不到组件而报错。
例如笔者是按照这种规则:
后台数据
斜杠”/“前边表示文件夹名称,后边表示组件名称,这样就可以按照这种规则动态加载到组件了。
(2).页面刷新变成空白页?(路由丢失)
遇到这个问题的话,在main.js中加入一段代码,每次刷新页面都把存入VueX state中的数据拿出来,判断一下路由里边还存不存在当前刷新页面的路由,如果没有,则对VueRouters重新赋值
main.js 代码如下:
import Vue from 'vue' import App from './App.vue' import router from './router' import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; import store from './common/store' import { filterAsyncRouter } from "./common/promission"; Vue.config.productionTip = false Vue.use(ElementUI); new Vue({ router, store, render: h => h(App), mounted() { // 缓存的路由信息 const routes = this.$store.state.routes // 判断当前路由是否被清除 const hasRoute = this.$router.options.routes.some(r => r.name == 'index') // 如果 缓存中有路由信息 并且 当前路由被清除 if (routes.length && !hasRoute) { //获取路由Json字符串 var getRouter = filterAsyncRouter(routes); // 再次添加路由信息 this.$router.addRoutes(getRouter); // 然后强制更新当前组件 this.$forceUpdate() } }, }).$mount('#app')
(3) 页面按钮的控制
将前面存入vuex state中的权限数据,在每个组件中都拿出来一下存入一个变量中,在按钮上使用v-if、array.indexOf('权限名称')来控制显示/隐藏。
原理是如果用户存在该权限,则v-if=”true“,按钮则显示,否则按钮隐藏。
代码如下:
<el-button @click="edit(scope.row)" type="text" size="small" v-if="accessList.indexOf('SysRole/AddEdit')>-1" >编辑</el-button>
效果图:
好了,笔者就介绍到这里。当然,如果要做一个完整的后台,肯定还有很多要做的东西,比如用户角色啊、角色授权啊等等;但笔者这次学习的重点是VUE的动态路由、动态权限,以及页面处理中一些比较常见的坑,所以别的就不多介绍了。
如有需要,朋友们可以联系我,大家多多交流。
加载全部内容