一、动态菜单
(一). 前端代码
- 登录页
<template>
<div class="container">
<el-form ref="loginForm" :rules="loginRules" :model="form" label-width="80px">
<el-row>
<el-col :span="3">
<div class="grid-content bg-purple-dark"> </div>
</el-col>
<el-col :span="9">
<el-image style="width: 100%; height: 100%" :src="url"></el-image>
</el-col>
<el-col :span="1">
<div class="grid-content bg-purple-dark"> </div>
</el-col>
<el-col :span="8">
<h3>新星电商后台管理系统</h3>
<el-form-item label="用户" prop="username">
<el-input v-model="form.username" placeholder="输入用户"></el-input>
</el-form-item>
<el-form-item label="密码">
<el-input type="password" v-model="form.password" placeholder="输入密码"></el-input>
</el-form-item>
<el-button type="primary" @click="login('loginForm')">登录</el-button>
</el-col>
<el-col :span="5">
<div class="grid-content bg-purple-dark"> </div>
</el-col>
</el-row>
</el-form>
</div>
</template>
<script>
//
import { login } from "@/api/login.js";
import { getRouter } from "@/api/menu.js";
import { setToken } from "@/utils/store.js";
export default {
data() {
return {
url: "/images/logoLeft.png",
form: { username: "admin", password: "123456" },
// 表单验证
loginRules: {
username: [
{ required: true, message: "请输入用户名称", trigger: "blur" }
]
}
};
},
methods: {
login(loginForm) {
this.$refs[loginForm].validate(valid => {
if (valid) {
login(this.form).then(response => {
if (response.data.code == "0000") {
// 令牌存储
setToken(response.data.data);
this.$router.push("/layout");
} else {
alert(response.data.message);
}
});
} else {
console.log("error submit!!");
}
});
}
}
};
</script>
<style lang="scss" scoped>
.container {
margin-top: 160px;
}
</style>
- 侧边栏
<template>
<div>
<h3>首页</h3>
<el-menu :default-openeds="['2']" :collapse="isCollapse">
<!-- 目录 -->
<el-submenu :index="item.key" v-for="item in menuData" :key="item.id">
<template slot="title">
<i :class="item.icon"></i>
<span slot="title">{{item.title}}</span>
</template>
<el-menu-item-group>
<!-- 子菜单 -->
<el-menu-item
v-for="childItem in item.children"
:key="childItem.id"
:index="item.key"
@click="navRouter(childItem.path)"
>{{childItem.title}}</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
</div>
</template>
<script>
import { getMenu } from "@/api/menu.js";
export default {
data() {
return {
menuData: []
};
},
props: {
isCollapse: {
type: Boolean
}
},
created() {
this.initMenu();
},
methods: {
navRouter(url) {
this.$router.push("/layout/" + url);
},
initMenu() {
getMenu().then(res => {
this.menuData = res.data.data;
});
}
}
};
</script>
- 主页
<template>
<el-container>
<el-aside :width="asWidth">
<Aside :isCollapse="isCollapse" />
</el-aside>
<el-container>
<el-header>
<el-row>
<el-col :span="8" class="menu">
<el-button type="primary" class="el-icon-s-fold" @click="menu"></el-button>
</el-col>
<el-col :span="8" :offset="8" class="menu logout">
<el-button type="info" @click="logout">退出登录</el-button>
</el-col>
</el-row>
</el-header>
<el-main>
<router-view />
</el-main>
</el-container>
</el-container>
</template>
<script>
// 导入组件
import Aside from "@/views/layout/Aside.vue";
import { deleteKey } from "@/utils/store.js";
import { log } from 'util';
export default {
name: "layout",
components: {
Aside
},
data() {
return { isCollapse: true, asWidth: "100" };
},
methods: {
menu() {
this.isCollapse = !this.isCollapse;
},
logout() {
// 移除token
deleteKey("token");
this.$router.push("/");
}
}
};
</script>
<style lang="scss" scoped>
.menu {
margin-top: 10px;
text-align: left;
}
.menu.logout {
text-align: right;
}
</style>
- 令牌存储store.js
var store=window.localStorage;
export function setToken(val){
store.token=val;
}
export function deleteKey(key){
store.removeItem(key)
}
export function getToken(){
return store.token;
}
- 路由处理router.js
import dynamicRouter from "@/router/dynamicRouter.js";
import { getToken } from '@/utils/store.js';
import Vue from 'vue';
import Router from 'vue-router';
import { getRouter } from "@/api/menu.js";
Vue.use(Router)
/**
* 重写路由的push方法
*/
const routerPush = Router.prototype.push
Router.prototype.push = function push(location) {
return routerPush.call(this, location).catch(error=> error)
}
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'login',
component: () => import('@/views/Login.vue')
}
]
});
/**
* 避免循环执行
*/
let newRoutes = null;
router.beforeEach((to, from, next) => {
// 判断是否是登录界面
if (to.path != '/') {
// 从获取store中Token
if (getToken()) {
// 刷新动态路由丢失
if (!newRoutes) {
// 调用API获取动态路由
getRouter().then(res => {
res.data.data.forEach(element => {
// 重要:赋值给变量
let value = element.component;
element.component = function component(resolve) {
require(["@/views" + value], resolve);
};
});
dynamicRouter.routes[0].children = res.data.data;
newRoutes = dynamicRouter.routes;
// 添加路由数据
router.addRoutes(newRoutes);
next({ ...to, replace: true });
});
} else {
next()
}
}else{
next("/");
}
} else {
// 如果是登录界面放行
next();
}
});
export default router;
- 动态路由定义dynamicRouter.js
/**
* 动态路由定义
*/
let dynamicRouter = {
routes: [
{
path: "/layout",
name: "layout",
component: () => import("@/views/layout/Layout.vue"),
children: []
}
]
}
export default dynamicRouter;
- 登录API
import request from "@/utils/request.js";
/**
* 登录方法
* @param {} data
*/
export function login(data){
return request({
url:"/admin-service/auth/login",
method:"post",
data:data
});
}
- 菜单路由API
import request from "@/utils/request.js";
/**
* 获取菜单数据
*/
export function getMenu(){
return request({
url:"/admin-service/menu/admin",
method:"get"
});
}
/**
* 获取路由数据
*/
export function getRouter(){
return request({
url:"/admin-service/menu/router/admin",
method:"get"
});
}
(二). 后台代码
- 数据表结构与数据
在此只列表菜单表,其它表,用户、角色、权限未列。
CREATE TABLE `system_menu` (
`id` bigint(20) NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '路由名',
`path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '路径',
`menu_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '显示名称',
`parent_id` int(11) NULL DEFAULT NULL COMMENT '父级ID',
`icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图标',
`component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组件地址',
`deleted` int(11) NULL DEFAULT NULL COMMENT '逻辑删除(1 已删除 0未删除)',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of system_menu
-- ----------------------------
INSERT INTO `system_menu` VALUES (1, 'userManage', '', '会员管理', 0, 'el-icon-s-custom', NULL, 0, '2019-09-11 14:33:17', NULL);
INSERT INTO `system_menu` VALUES (2, 'user', 'user', '会员列表', 1, 'el-icon-s-custom', '/user/index.vue', 0, '2019-09-11 14:35:26', NULL);
INSERT INTO `system_menu` VALUES (3, 'role', 'role', '角色管理', 0, 'el-icon-s-custom', '/role/index.vue', 0, '2019-09-12 10:49:40', NULL);
INSERT INTO `system_menu` VALUES (4, 'goods', '', '商品管理', 0, 'el-icon-shopping-cart-full', NULL, 0, '2019-09-19 10:28:36', NULL);
INSERT INTO `system_menu` VALUES (5, 'category', 'category', '分类管理', 4, NULL, '/category/index.vue', 0, '2019-09-19 10:29:58', NULL);
INSERT INTO `system_menu` VALUES (6, 'goods', 'goods', '商品管理', 4, NULL, '/goods/index.vue', 0, '2019-09-19 10:30:52', NULL);
- 菜单VO类代码
@Data
public class MenuVO {
private Long id;
private String title;
private String icon;
private String key;
private String path;
private Set<MenuVO> children;
}
- 路由VO类代码
@Data
public class RouterVO {
/**
* 名称
*/
private String name;
/**
* 路径
*/
private String path;
/**
* 组件
*/
private String component;
}
- 菜单路由业务类代码
@Override
public List<MenuVO> getMenusByUsername(String username) {
// 1.根据用户名查找角色
List<RoleVO> roles = roleMapper.getRolesByUsername(username);
List<MenuVO> menuVOList=new ArrayList<>();
for (RoleVO roleVO : roles) {
List<Menu> lists = menuMapper.getMenusByRoleId(roleVO.getId());
for (Menu menu:lists) {
if(null!=menu&&null!=menu.getParentId()&&menu.getParentId()==0) {
MenuVO menuVO = new MenuVO();
menuVO.setId(menu.getId());
menuVO.setKey(menu.getId().toString());
menuVO.setTitle(menu.getMenuName());
menuVO.setIcon(menu.getIcon());
menuVO.setPath(menu.getPath());
menuVO.setChildren(this.getSubMenu(lists,menuVO));
// 添加一级菜单数据
menuVOList.add(menuVO);
}
}
}
return menuVOList;
}
/**
* 获取子菜单
* @param lists
* @param menuVO
* @return
*/
private Set<MenuVO> getSubMenu( List<Menu> lists, MenuVO menuVO ){
Set<MenuVO> menuVOList=new HashSet<>();
for (Menu menu0:lists) {
if(menuVO.getId()==menu0.getParentId()){
MenuVO menuVO0 = new MenuVO();
menuVO0.setId(menu0.getId());
menuVO0.setKey(menu0.getId().toString());
menuVO0.setPath(menu0.getPath());
menuVO0.setIcon(menu0.getIcon());
menuVO0.setTitle(menu0.getMenuName());
menuVOList.add(menuVO0);
// 添加到集合
Set<MenuVO> menuVOSet=this.getSubMenu(lists,menuVO0);
menuVO0.setChildren(menuVOSet);
}
}
return menuVOList;
}
}
/**
* 获取路由
* @param username
* @return
*/
@Override
public List<RouterVO> getRouter(String username) {
// 1. 根据用户名查找角色
List<RoleVO> roles = roleMapper.getRolesByUsername(username);
List<RouterVO> routerVOList=new ArrayList<>();
for (RoleVO roleVO : roles) {
// 2. 通过角获取菜单
List<Menu> lists = menuMapper.getMenusByRoleId(roleVO.getId());
for (Menu menu:lists) {
// 目录为空
if(StringUtils.isNotEmpty(menu.getPath())) {
RouterVO routerVO = new RouterVO();
routerVO.setName(menu.getName());
routerVO.setComponent(menu.getComponent());
routerVO.setPath(menu.getPath());
// 添加一级菜单数据
routerVOList.add(routerVO);
}
}
}
return routerVOList;
}
}
- 获取菜单与路由控制器代码
@GetMapping("{username}")
public Result<List<MenuVO>> getMenus(@PathVariable("username") String username) {
return Result.ok(adminService.getMenusByUsername(username));
}
@GetMapping("router/{username}")
public Result<List<RouterVO>> getRouter(@PathVariable("username") String username) {
return Result.ok(menuService.getRouter(username));
}
二、常见问题
- 动态路中添加之坑
vue lazy recursive ^./.*$
解决:
数据赋值给局部变量后添加。
let value = element.component;
element.component = function component(resolve) {
require(["@/views" + value], resolve);
};
- 刷新页面空白
解决:
在beforeEach中读取后台数据进行路由添加。
网友评论