动态加载用户菜单
- 2020 年 2 月 18 日
- 筆記
流程是用户登录后进入模块页面,点击不同的模块,进入菜单页面(模块不同,菜单内容也不同)

遇到的问题
1、菜单数据存储到store中页面刷新后页面空白
解决方法:在全局导航守卫中每次都初始化菜单
2、如何动态生成路由 (动态生成路由会叠加,如果已经存在再生成会警告)
采用方法:router . addRoutes ( data );
3、不同模块切换进入菜单页面,高亮显示有问题
解决方法:
: default-active = " routePath "
created() { this.routePath = this.$route.path; },
3、退出登录后如何清空store中数据(vuex中并没有清空的方法)
暂时采用: location . reload ();
下面全部采用json模拟数据

详细代码如下
userPermission.json
{ "data":{"name":"小李"}, "token":"111", "responseCode":"0000" }
Login.vue
<template> <div> <el-form :rules="rules" ref="loginForm" v-loading="loading" element-loading-text="正在登录..." element-loading-spinner="el-icon-loading" element-loading-background="rgba(0, 0, 0, 0.8)" :model="loginForm" class="loginContainer" > <h3 class="loginTitle">系统登录</h3> <el-form-item prop="username"> <el-input size="normal" type="text" v-model="loginForm.username" auto-complete="off" placeholder="请输入用户名" ></el-input> </el-form-item> <el-form-item prop="password"> <el-input size="normal" type="password" v-model="loginForm.password" auto-complete="off" placeholder="请输入密码" @keydown.enter.native="submitLogin" ></el-input> </el-form-item> <el-checkbox size="normal" class="loginRemember" v-model="checked"></el-checkbox> <el-button size="normal" type="primary" style="width: 100%;" @click="submitLogin">登录</el-button> </el-form> <div class="login-bottom"> bottom </div> </div> </template> <script> export default { name: "Login", data() { return { loading: false, loginForm: { username: "admin", password: "1" }, checked: true, rules: { username: [ { required: true, message: "请输入用户名", trigger: "blur" } ], password: [{ required: true, message: "请输入密码", trigger: "blur" }] } }; }, methods: { submitLogin() { this.$refs.loginForm.validate(valid => { if (valid) { this.loading = true; this.$axios .get("mock/userPermission.json") .then(res => { console.log(res.data); let data = res.data if(data.responseCode=="0000"){ this.$store.commit("updatePermissionInfo", res.data); sessionStorage.setItem("user", data.data.name); sessionStorage.setItem("token", data.token); this.$router.push("/dashboard"); } }) .catch(error => { console.log(error.response) this.$alert(error, "提示", { confirmButtonText: "确定", type: "warning" }); }); } else { this.$message.error("请输入所有字段"); return false; } }); } } }; </script> <style scoped> .loginContainer { width: 450px; height: 320px; border-radius: 15px; background: #fff; position: absolute; left: 0; right: 0; top: -60px; bottom: 0; /* background:linear-gradient(to bottom,#0675bd, #0363a1); */ box-shadow: 0 0 25px #cac6c6; padding: 15px 35px 15px 35px; margin: auto; } .loginTitle { margin: 15px auto 20px auto; text-align: center; color: #505458; } .loginRemember { text-align: left; margin: 0px 0px 15px 0px; } .loginTip { color: #d31245; font-style: italic; margin-bottom: 25px; } .login-bottom { position: fixed; bottom: 15px; width: 100%; text-align: center; } .login-bottom img { vertical-align: middle; width: 65px; margin-right: 10px; } </style>
模块页面
dishboardInfo.json
{ "data": [{ "id":1, "path": "/industry", "name": "模块1", "component": "Home" }, { "id":2, "path": "/commercial", "name": "模块2", "component": "Home" }] }
dashboard.vue
<template> <div class> <ul class="dashboard"> <!-- <router-link tag="li" v-for="(item,index) in info" :key="index" :to="item.path">{{item.menuName}}</router-link> --> <li v-for="(item,index) in info" :key="index" @click="handleClick(index)">{{item.name}}</li> </ul> </div> </template> <script> export default { data() { return { info: [] }; }, created() { let userName = sessionStorage.getItem("user"); console.log(userName); this.$axios.get("mock/dishboardInfo.json").then(res => { console.log(res.data); this.info = res.data.data; }); }, computed: {}, methods: { handleClick(index) { this.$router.push({ path: this.info[index].path, query: { menuId: this.info[index].id } }); } } }; </script>

路由
import Vue from 'vue' import VueRouter from 'vue-router' import Login from '../views/Login.vue' import Dashboard from '../views/dashboard.vue' import Home from '../views/Home.vue' Vue.use(VueRouter) // const originalPush = VueRouter.prototype.push // VueRouter.prototype.push = function push(location) { // return originalPush.call(this, location).catch(err => err) // } const routes = [{ path: '/', redirect: "/login", }, { path: "/login", name: 'login', component: Login }, { path: "/dashboard", name: 'dashboard', component: Dashboard }, { path: '/:id', name: "home", //component: () => import('../views/Home.vue') component: Home, } ] const router = new VueRouter({ routes }) export default router
Store
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { currentModule: sessionStorage.getItem("currentModule") || "", currentMenuId: sessionStorage.getItem("currentMenuId") || "", //routes:[], routes: { industry: [], commercial: [], person: [] }, permissionInfo: JSON.parse(sessionStorage.getItem("permissionInfo")) || {}, }, mutations: { updatePermissionInfo(state, permissionInfo) { state.permissionInfo = permissionInfo }, initRoutes(state, data) { console.log(data) if (data) { state.routes[data.key] = data.value } else { state.routes = { industry: [], commercial: [] } } // if (JSON.stringify(data) == "{}"){ // console.log("空对象") // state.routes = { // industry: [], // commercial: [] // } // return // } // console.log("有对象") // state.routes[data.key]= data.value }, setCurrentModule(state, currentModule) { state.currentModule = currentModule sessionStorage.setItem("currentModule", currentModule) }, setCurrentMenuId(state, currentMenuId) { state.currentMenuId = currentMenuId sessionStorage.setItem("currentMenuId", currentMenuId) } }, actions: {}, modules: {} })
menu.js
import axios from 'axios' export const initMenu = (router, store, to) => { if (to.path == "/dashboard" || to.path == "/login") { return } let currentMenuId = to.query.menuId || sessionStorage.getItem("currentMenuId") let currentModule = to.params.id || sessionStorage.getItem("currentModule") console.log(currentModule) if (currentModule && store.state.routes[currentModule].length > 0) { //sessionStorage.setItem("currentModule", currentModule) store.commit("setCurrentModule", currentModule) store.commit("setCurrentMenuId", currentMenuId) console.log("return") return; } else { //console.log("nei-1") axios.get("mock/menudata-"+currentModule+".json").then(res => { let data = res.data.data let fmRoutes = formatRoutes(data); //router.options.routes = fmRoutes router.addRoutes(fmRoutes); let dataObj = {} dataObj.key = currentModule dataObj.value = data console.log(dataObj) //dataObj.value = fmRoutes store.commit("initRoutes", dataObj) store.commit("setCurrentModule", currentModule) store.commit("setCurrentMenuId", currentMenuId) }); } } export const formatRoutes = (menuData) => { //console.log(data); let fmRoutes = []; menuData.forEach(item => { let { path, menuName, component, childMenu } = item; //console.log(children) if (childMenu && childMenu instanceof Array) { childMenu = formatRoutes(childMenu); } let fmRouter = { path: path, name: menuName, children: childMenu, component(resolve) { if (component.startsWith("Home")) { require(["../views/" + component + ".vue"], resolve); } else if (component.startsWith("Baobiao")) { require(["../views/baobiao/" + component + ".vue"], resolve); } else if (component.startsWith("DataAccount")) { require(["../views/dataAccount/" + component + ".vue"], resolve); } } //component: () => import("../views/" + component + ".vue") }; fmRoutes.push(fmRouter); }); //console.log(fmRoutes); return fmRoutes; }
main.js
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' // 引入element-ui 组件 import ElementUI from 'element-ui' // 引入element-ui 样式文件 import 'element-ui/lib/theme-chalk/index.css' import './assets/js/jQuery-2.1.4.min.js' import axios from "axios" import '@/assets/css/global.css' import 'font-awesome/css/font-awesome.min.css' import {hasPermission} from "./utils/hasPermission" import {initMenu} from "./utils/menu" Vue.use(ElementUI) //axios.defaults.baseURL = 'http://xxxxx' // var instance = axios.create({ // baseURL: 'http://localhost:8080/' // }); // Vue.prototype.$instance = instance //添加一个请求拦截器 // axios.interceptors.request.use(function (config) { // config.data=JSON.stringify(config.data); // return config; // }, function (error) { // // Do something with request error // console.info("error: "); // console.info(error); // return Promise.reject(error); // }); import qs from "qs"; Vue.prototype.$qs = qs; Vue.prototype.$axios = axios Vue.prototype.hasPerm = hasPermission Vue.config.productionTip = false //正常运行的代码 router.beforeEach((to, from, next) => { let token = window.sessionStorage.getItem('token'); if (to.path != '/login' && !token) { next({ path: '/login' }) } else { if (to.path == '/login' && token) { next('/dashboard') } else { initMenu(router,store,to) next() } } }) var VUE = new Vue({ router, store, render: h => h(App) }).$mount('#app')
Home.vue
<template> <div> <el-container :style="{height: containerHeight, border: '1px solid #eee'}" id="con"> <el-header style="background:#3c8dbc;"> <div class="left pull-left"> <img src="../assets/imgs/weeglogo.png" alt /> <span>当前模块:</span> <span>{{currentModuleToChinese}}</span> </div> <div class="right pull-right"> <router-link to="/dashboard"> <i class="fa fa-home margin_r20" style="font-size:20px;" aria-hidden="true"></i> </router-link> <span class="margin_r20">{{user}}</span> <span style="color:#f9c05e;" @click="logout"> <i class="fa fa-power-off" aria-hidden="true"></i> 注销 </span> </div> </el-header> <el-container> <el-aside width="230px"> <el-menu router :default-active="routePath" unique-opened background-color="#1f3146" text-color="#32acca" active-text-color="#ffd04b" > <NavMenu :navMenus="menuData"></NavMenu> </el-menu> </el-aside> <el-container> <el-main> <el-tabs :value="activeTabItem" @tab-remove="closeTab" class="content-body" @tab-click="tabClick" > <el-tab-pane label="首页" name="adminIndex"> <admin-index></admin-index> </el-tab-pane> <el-tab-pane v-for="item in tabs" :label="item.label" :key="item.index" :name="item.index+''" :closable="item.closable" > </el-tab-pane> </el-tabs> <bread-crumb></bread-crumb> <!-- <div>{{breab}}</div> --> <router-view></router-view> </el-main> <el-footer> footer </el-footer> </el-container> </el-container> </el-container> </div> </template> <script> import NavMenu from "@/components/NavMenu.vue"; import BreadCrumb from "@/components/Breadcrumb.vue"; import AdminIndex from "@/components/AdminIndex.vue"; export default { data() { return { containerHeight: "", //menuData: [], routePath: "", currentModuleChinese: "", user: window.sessionStorage.getItem("user") }; }, created() { this.routePath = this.$route.path; }, computed: { menuData() { let id = this.$store.state.currentModule; console.log(id); console.log(this.$store.state.routes[id]); return this.$store.state.routes[id]; }, currentModuleToChinese() { let currentModule = this.$store.state.currentModule; switch (currentModule) { case "industry": return "模块1"; break; case "commercial": return "模块2"; break; case "person": return "模块3"; break; } }, tabs(){ return this.$store.state.tabs }, activeTabItem(){ return this.$store.state.activeTabItem } }, components: { NavMenu, BreadCrumb }, methods: { logout() { this.$confirm("此操作将注销登录,是否继续?", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning" }) .then(() => { //this.$axios.get("/logout"); window.sessionStorage.removeItem("token"); window.sessionStorage.removeItem("currentMenuId"); //this.$store.commit("initRoutes",{}) //this.$store.commit("initRoutes",null) console.log(this.$store.state.routes); location.reload(); //this.$router.replace("/") }) .catch(() => { this.$message({ type: "info", message: "已取消操作" }); }); } }, // watch:{ // $route(){ // console.log(this.$route.path) // this.routePath = this.$route.path // } // }, mounted() { console.log("mounted"); this.containerHeight = window.innerHeight + "px"; console.log($); $(window).resize(function() { console.log("hi"); $("#con").height($(window).height() - 2); }); //this.$router.push("/industrySub2") } }; </script> <style> .el-header { background-color: #377fa9; color: #fff; height: 50px !important; line-height: 50px !important; } .el-header .left img { width: 120px; vertical-align: middle; } .el-header .left span { font-size: 20px; color: #edf8ff; margin-left: 15px; } .el-header .right { float: right; } .el-header .right a { color: #fff; } .el-aside { /* color: #32acca !important; */ background: #1f3146 !important; } .el-menu { border-right: none !important; /* background: #1f3146 !important; */ } .el-main{padding-top:0 !important;} .el-footer { background: gray; height: 40px !important; line-height: 40px !important; } .el-footer { border-top: 1px solid #ccc; background: #f8fafd; padding: 10px; margin-left: 0; } .el-footer img { vertical-align: middle; width: 65px; margin-right: 10px; } </style>
关于无限极菜单,上一篇博客中有详细的介绍