使用VUE開發使用者後臺時的動態路由問題、按鈕許可權問題以及其他頁面處理問題
如今前後端分離是大勢所趨,筆者雖然是做後臺的,但也不得不學學前端的流行框架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欄位是個元件地址字串
程式碼:
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的動態路由、動態許可權,以及頁面處理中一些比較常見的坑,所以別的就不多介紹了。
如有需要,朋友們可以聯絡我,大家多多交