1.数据库设计
菜单管理就一个表,通过层级及父级id进行判断,一级菜单level为0且父级id为null,子级level为1且父级id不为null
2 Node.js(express)
接口准备
addMenu = async function (req, res, next) {
try {
const { name, icon, url, view, parent_id } = req.query
if (!name || !icon || !url || !view) {
res.send({
status: 402,
msg: '缺少参数'
})
return
}
var flakeIdGen = new FlakeId();
let sql
console.log('parent_id', parent_id)
if (parent_id) {
sql = `insert into menu(id,name,icon,url,page,level,parent_id) values ('${intformat(flakeIdGen.next(), 'dec')}','${name}','${icon}','${url}','${view}',1,'${parent_id}')`
} else {
sql = `insert into menu(id,name,icon,url,page,level) values ('${intformat(flakeIdGen.next(), 'dec')}','${name}','${icon}','${url}','${view}',0)`
}
let arr = []
sqlQuery(sql, arr) //封装好的数据操作
res.send({
status: 0,
msg: parent_id ? '修改成功' : '添加成功'
})
} catch (error) {
next(error)
}
}
delMenu = async function (req, res, next) {
try {
const { id } = req.query
const sql = `delete from menu where id='${id}' or parent_id='${id}'`
let arr = []
sqlQuery(sql, arr)
res.send({
status: 0,
msg: '删除成功'
})
} catch (error) {
next(error)
}
}
editMenu = function (req, res, next) {
try {
const { id, name, icon, url, view, parent_id } = req.query
if (!id || !name || !icon || !url || !view) {
res.send({
status: 402,
msg: '缺少参数'
})
return
}
let pID
if (parent_id) {
pID = parent_id
} else {
pID = ''
}
const sql = `update menu set name='${name}',icon='${icon}',url='${url}',page='${view}',parent_id='${pID}' where id='${id}'`
sqlQuery(sql, arr = []).then(e => {
res.send({
status: 0,
msg: '修改成功'
})
})
} catch (error) {
error
}
}
editMenuStatus = function (req, res, next) {
try {
const { id, status } = req.query
const arr = []
const sql = `update menu set status='${status}' where id='${id}'`
sqlQuery(sql, arr).then(e => {
res.send({
status: 0,
msg: '修改成功'
})
})
} catch (error) {
next(error)
}
}
getAppointMenu = async (req, res, next) => {
try {
const { role_id } = req.query
if (!role_id) {
res.send({
status: 402,
msg: '缺少参数'
})
return
}
const sql = `select * from t_role_menu where role_id='${role_id}'`
let e = await sqlQuery(sql, arr = [])
let val = ''
e.forEach(el => {
val += el.menu_id + ','
})
const sql1 = `select * from menu where id in (${val.substring(0, val.length - 1)})`
let menuData = await sqlQuery(sql1, arr = [])
const dataVal = menuData.filter(e => { return e.level == 0 })
const data = JSON.parse(JSON.stringify(dataVal))
//const levelThreeData=await getLevelOne(2)
data.forEach((a, index) => {
dataVal[index].children = []
menuData.forEach(b => {
if (b.parent_id == a.id) {
dataVal[index].children.push(b)
}
})
})
res.send({
status: 0,
data: dataVal
})
} catch (error) {
next(error)
}
}
module.exports = {
addMenu,
delMenu,
editMenuStatus,
editMenu,
getAppointMenu
}
3 vue
前端在前置路由守卫中进行判断,若Vuex中无菜单数据,则通过接口请求菜单数据,处理格式后通过 router.addRoute动态进行渲染
/**
* 全站权限配置
*
*/
import NProgress from 'nprogress'; // progress bar
import router from "../router";
import store from "@/store/index";
import 'nprogress/nprogress.css';
import http from "@/utils/index.js";
import _this from '@/main.js'
import { MessageBox } from "element-ui";
NProgress.configure(
{
showSpinner: false,
easing: 'ease',
speed: 500,
trickleSpeed: 200,
minimum: 0.3
}); // NProgress是封装的进度条 是否有转圈效果
router.beforeEach(async (to, from, next) => {
console.log('跳转数据',to,from)
// 登录拦截
NProgress.start()
if (to.path === '/login') { //去往登录页直接放行
next()
} else {
const token = localStorage.getItem('token')//去往非登录页判断是否有token
if (!token) {
next({
path: '/login'
})
} else {
//判断是否有菜单数据,若无则重新请求,防止数据丢失
console.log('菜单数据长度',store.state.menuList.length)
if (store.state.menuList.length == 0) {
let res = await http.getRequest('/getMenuList')
if (res.status === 0) {
store.dispatch('dynamicRoutes', res.data).then(() => {
if (res.data && res.data.length > 0) {
res.data.forEach(e => {
const childrenRouter = e.children.map(el => {
return {
name: el.path,
path: el.url,
meta: { parent_name: e.name, name: el.name,icon:el.icon },
component: () => import(`@/views${el.url}.vue`),
}
})
const routerObj = {
path: `/${e.page}`,
name: e.page,
meta: { parent_name: e.name, name: e.name ,icon:e.icon},
component: () => import(`@/views${e.url}.vue`),
children: childrenRouter
}
// console.log('路由', routerObj)
router.addRoute(routerObj)
})
}
})
}else{
localStorage.removeItem("token");
store.dispatch("dynamicRoutes", []);
store.dispatch("dynamicUserInfo", {});
store.commit("setEditableTabs", []);
next({path:'/login'})
}
}
if (Object.keys(store.state.userInfo).length == 0) {
let res = await http.getRequest('/users/getUserInfo')
if (res.status == 0) {
store.dispatch('dynamicUserInfo', res.data).then(res => {
})
if(from.fullPath=='/login'){
next(
{path:store.state.menuList[0].children[0].url}
)
}else{
next({path:to.path})
}
}
}
next()
}
}
});
router.afterEach(() => {
NProgress.done(); // 一定要关闭进度条
// const title = store.getters.tag.label;
// 根据当前的标签也获取label的值动态设置浏览器标题
// router.$avueRouter.setTitle(title);
});
然后将处理好的菜单数据存于Vuex中供页面菜单栏进行展示
在菜单处进行取出,通过循环完成渲染即可
computed: {
menuList() {
return this.$store.state.menuList;
},
},
<el-menu
:default-active="defaultIndex"
class="el-menu-vertical-demo"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
:collapse="isCollapse_copy"
>
<el-submenu
:index="index + ''"
v-for="(item, index) in menuList"
:key="index"
>
<template slot="title">
<i :class="'iconfont ' + item.icon"></i>
<span>{{ item.name }}</span>
</template>
<div v-if="item.children.length != 0">
<el-menu-item
:index="`${index}-${index1}`"
v-for="(item1, index1) in item.children"
:key="index1"
@click="menuTo(item1)"
>
<template slot="title">
<i :class="'iconfont ' + item1.icon"></i>
<span>{{ item1.name }}</span>
</template>
</el-menu-item>
</div>
</el-submenu>
</el-menu>
完成上面操作后,即可动态控制路由菜单,效果如下