在开发后台系统时,通过菜单进行导航是非常重要的一件事情,在前端开发过程中使用vue2+elementui可以快速搭建菜单导航,本文主要记录两个菜单的生成方式,通过在前端router/index.js中直接进行配置,后端返回菜单数据进行对应,可以通过后端返回的菜单数据控制权限;另一种是部门静态导航,然后再拼接动态导航,生成完成页面导航。
npm install elementui --S
npm install vue-router@3 --S
npm install vuex --S
router/index.js
import Vue from 'vue';
import Router from 'vue-router';
Vue.use(Router);
const router = new Router({
routes: [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue'),
children: [
{
path: '/index',
name: 'Index',
component: () => import('@/views/Index.vue'),
},
{
path: '/documents/note',
name: 'NoteManagement',
component: () => import('@/views/NoteManagement.vue'),
},
{
path: '/documents/file',
name: 'FileManagement',
component: () => import('@/views/FileManagement.vue'),
},
{
path: '/documents/newMarkdown',
name: 'NewDocument',
component: () => import('@/components/RichTextEditor.vue'), // 新增路由指向RichTextEditor.vue
},
{
path: '/documents/newWord',
name: 'NewWord',
component: () => import('@/components/WordEditor.vue'),
},
{
path: '/documents/newExcel',
name: 'NewExcel',
component: () => import('@/components/ExcelEditor.vue'),
},
{
path: '/system/user',
name: 'UserManagement',
component: () => import('@/views/UserManagement.vue'),
},
{
path: '/system/menu',
name: 'MenuManagement',
component: () => import('@/views/MenuManagement.vue'),
},
{
path: '/system/role',
name: 'RoleManagement',
component: () => import('@/views/RoleManagement.vue'),
},
{
path: 'system/company',
name: 'CompanyManagement',
component: () => import('@/views/CompanyManagement.vue'),
},
{
path: '/system/dept',
name: 'DeptManagement',
component: () => import('@/views/DeptManagement.vue'),
},
{
path: '/target',
name: 'TargetManagement',
component: () => import('@/views/TargetManage.vue'),
},
{
path: '/targetTask',
name: 'TargetTask',
component: () => import('@/views/TargetTask.vue'), // 新增路由指向MonthTask.vue
},
{
path: '/dayTask',
name: 'DayTask',
component: () => import('@/views/DayTask.vue'), // 新增路由指向DayTask.vue
}
]
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login.vue'),
},
{
path: '/register',
name: 'Register',
component: () => import('@/views/Register.vue'), // 更新注册路由
},
],
});
// 导航守卫
// 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
router.beforeEach((to, from, next) => {
if (to.path === '/login') {
next();
} else {
let token = localStorage.getItem('Authorization');
if (token === null || token === '') {
next('/login');
} else {
next();
}
}
});
export default router;
main.js
import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import store from './store'
import router from './router/index'
// import mavonEditor from 'mavon-editor'
// import 'mavon-editor/dist/css/index.css';
// import mermaidItMarkdown from 'mermaid-it-markdown'
// mavonEditor.mavonEditor.getMarkdownIt().use(mermaidItMarkdown)
// Vue.use(mavonEditor)
Vue.config.productionTip = false
Vue.use(ElementUI)
new Vue({
store,
router,
render: h => h(App),
}).$mount('#app')
{{ item.menuName }}
{{ item.menuName }}
{{ child.menuName }}
//路由出口
菜单数据时根据用户id请求后端菜单权限后返回的菜单数据
安装vue-router、elementui步骤与静态导航相同
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
const constantRoutes = [
{
path: "/login",
name: "Login",
component: () => import("@/views/Login.vue")
},
{
path: "/home",
name: "Home",
component: () => import("@/views/Home.vue"),
children: []
},
// {
// path: "*",
// name: "NotFound",
// component: () => import("@/views/NotFound.vue")
// },
]
const createRouter = () => new VueRouter({
mode: "hash",
routes: constantRoutes
})
const router = createRouter();
//路由重置方法
export function resetRouter() {
const newRouter = createRouter();
router.matcher = newRouter.matcher; // 重置路由
}
//动态加载路由方法
export const addDynamicRoutes = (menus) => {
debugger;
const routes = [];
//1.转换菜单为路由配置
const asyncRoutes = coverMenusToRoutes1(routes,menus);
//2. 添加嵌套路由到Home
asyncRoutes.forEach(route => {
// debugger;
// if (route.name !== '') {
// router.addRoute("Home", route);
// }
router.addRoute("Home", route);
});
}
//菜单转换路由方法
const coverMenusToRoutes = (menus) => {
if (!menus) return [];
const routes = [];
menus.forEach(menu => {
const route = {
path: menu.path,
name: menu.path.slice(1),
meta: {title: menu.name,icon: menu.icon},
component: resolveComponent(menu.component),
};
if (menu.children && menu.children.length > 0) {
route.children = coverMenusToRoutes(menu.children);
}
routes.push(route);
})
return routes;
}
const coverMenusToRoutes1 = (routes,menus) => {
if (!menus) return [];
// const routes = [];
menus.forEach(menu => {
if (menu.component.length > 0){
const route = {
path: menu.path,
name: menu.path.slice(1),
meta: {title: menu.name,icon: menu.icon},
component: resolveComponent(menu.component),
};
routes.push(route);
}
if (menu.children && menu.children.length > 0) {
coverMenusToRoutes1(routes,menu.children);
}
})
return routes;
}
//动态解析组件路由
function resolveComponent(component) {
if (!component) return undefined;
return () => import(`@/views/${component}`);
}
// 导航守卫
// 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
router.beforeEach((to, from, next) => {
if (to.path === '/login') {
next();
} else {
let token = localStorage.getItem('token');
if (token === null || token === '') {
next('/login');
} else {
next();
}
}
});
export default router;
动态导航需要特别注意路径问题,如果路径不正确会导致菜单无法正常显示,因为在项目中返回的菜单数据时树形结构,在处理菜单数据时如果按树形结构嵌套再添加到Home路由的children列表中,菜单无法正常的显示,后面修改了处理逻辑,把有组件的菜单添加到Home路由的children列表后,菜单可以正常显示,需要特别注意
{{ item.name }}
{{child.name}}
{{ch.name}}
{{ child.name }}
{{ item.name }}
菜单生成子组件
{{item.name}}
{{item.name}}
子组件名称是必须要有的,递归调用时按照名称进行递归调用。在这个项目中子组件名称为:AsideMenu,递归调用时使用 AsideMenu来引用自身
调用菜单生成的父组件
使用vuex保存用户id,token,菜单列表,权限信息,角色信息
import Vue from "vue";
import vuex from "vuex";
Vue.use(vuex);
const store = new vuex.Store({
state: {
//用户id
userId: {},
//用户token
token: "",
//用户角色
role: "",
//用户权限
permission: "",
//用户菜单
menus: [],
//用户路由
},
getters: {
//获取用户id
getUserId(state) {
return state.userId;
},
//获取用户token
getToken(state) {
return state.token;
},
//获取用户角色
getRole(state) {
return state.role;
},
//获取用户权限
getPermission(state) {
return state.permission;
},
//获取用户菜单
getMenus(state) {
return state.menus;
},
},
mutations: {
//设置用户id
setUserId(state, userId) {
state.userId = userId;
localStorage.setItem("userId", userId);
},
//设置用户token
setToken(state, token) {
state.token = token;
localStorage.setItem("token", token);
},
//设置用户角色
setRole(state, role) {
state.role = role;
},
//设置用户权限
setPermission(state, permission) {
state.permission = permission;
},
//设置用户菜单
setMenus(state, menus) {
state.menus = menus;
localStorage.setItem("menus", menus);
},
}
})
export default store;
import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import router from './router'
import store from './store'
Vue.use(ElementUI)
Vue.config.productionTip = false
new Vue({
store,
router,
render: h => h(App),
}).$mount('#app')
登录页面存储用户信息、菜单信息、并动态加载路由
欢迎登录
登录
重置
{
"code": 200,
"message": "请求成功",
"data": {
"menus": [
{
"id": "1913479834787434497",
"parentId": null,
"name": "系统管理",
"path": "",
"component": "",
"perms": null,
"type": 1,
"icon": "el-icon-s-tools",
"orderNum": 0,
"visible": false,
"createTime": null,
"updateTime": null,
"rf1": null,
"rf2": null,
"rf3": null,
"rf4": null,
"rf5": null,
"children": [
{
"id": "1913484019050274818",
"parentId": "1913479834787434497",
"name": "菜单管理",
"path": "/menu",
"component": "MenuManage.vue",
"perms": null,
"type": 1,
"icon": "el-icon-menu",
"orderNum": 0,
"visible": false,
"createTime": null,
"updateTime": null,
"rf1": "系统管理",
"rf2": null,
"rf3": null,
"rf4": null,
"rf5": null,
"children": null
},
{
"id": "1913488214084083714",
"parentId": "1913479834787434497",
"name": "二级菜单",
"path": "",
"component": "",
"perms": null,
"type": 1,
"icon": "el-icon-location",
"orderNum": 1,
"visible": false,
"createTime": null,
"updateTime": null,
"rf1": "系统管理",
"rf2": null,
"rf3": null,
"rf4": null,
"rf5": null,
"children": [
{
"id": "1913488799369846786",
"parentId": "1913488214084083714",
"name": "三级菜单",
"path": "/san",
"component": "SanManage.vue",
"perms": null,
"type": 1,
"icon": "el-icon-star-on",
"orderNum": 0,
"visible": false,
"createTime": null,
"updateTime": null,
"rf1": "二级菜单",
"rf2": null,
"rf3": null,
"rf4": null,
"rf5": null,
"children": null
}
]
}
]
}
],
"user": {
"id": "123456",
"username": "admin",
"password": "$2a$10$0uPlhwgy.OlkV20pRJ/9Wu8OJ61OfbcMqMXf60qI4qsahlxJD4iUq",
"nickname": "wangcheng",
"avatar": null,
"email": null,
"mobile": null,
"status": 1,
"deptId": null,
"createTime": null,
"updateTime": null,
"token": "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIzMWMwOTgzNzQ2MTQ0ZGNiYmJhZTgwZmJhYzNkNWFjMSIsImlhdCI6MTc0NTE5NjAxNCwic3ViIjoiMTIzNDU2IiwiZXhwIjoxNzQ1ODAwODE0fQ.OyZaKaHRfu0PfMSubrnU1qLDOQHfisdQkTvByCMeIes",
"roles": null
}
}
}
项目开发过程中动态菜单生成、动态路由的正确配置是困难点。
递归调用最主要的是对自身的调用,要保持名称和调用自身组件的一致性。
动态路由要注意路径问题,不要因为菜单返回树形结构,后期处理的路由也是树形结构,造成子路由里面多层嵌套,无法正常渲染菜单。
MenuTreeOne.vue
AsideMenuOne.vue