数据库设计
每一个用户都有一个用户id,为了对他们进行后台菜单的动态设计,即每个用户看到的菜单是不一样的,那么我们就要为每一个用户规定一个角色,这就引入了role表,通过user_roler的表建立联系。
同样的,每个角色看到不同的菜单,菜单由后端根据登录的角色向数据库查询生成,这就引入了menu表,通过role_menu的表建立联系。
我们插入几个号数据:
path: 代表前端路由的访问路径
component: 代表前端路由所在的组件位置
前端设计
- 用户登录返回用户的角色id,存储在vuex中
login () {
var params = {
username: this.username,
password: this.password
}
this.$store.commit('setUserName', this.username)
post('user/login', params).then(res => {
// 将返回的token存储在vuex中
this.$store.commit('setUserToken', res.token)
this.$store.commit('setUserRole', res.rid)
// 路由跳转到登出界面
this.$router.replace('/admin')
})
}
-
格式化后端返回的路由
当用户选择进入后台的时候,我们先要初始化后台的菜单,在main.js中写入:
router.beforeEach((to, from, next) => {
if (store.state.username && (to.name === 'adminDashboard')) {
axios.get('/user/getAuth').then(resp => {
initAdminMenu(router, store)
})
}
if (to.meta.requireAuth) { // 如果目标路由需要认证
axios.get('/user/getAuth').then(result => { // 向后端请求当前用户的状态
debugger
if (result.data.code === 200) {
next()
} else {
alert('您还未登录')
next({
path: '/login'
})
}
})
} else {
next()
}
initAdmin.js方法如下:
import {get} from '@/utils/http'
import {formatRoutes} from '@/utils/routerFormat'
export default function initAdminMenu (router, store) {
if (store.state.adminMenus.length > 0) {
return
}
get('/menu/get/' + store.state.rid).then(resp => {
var fmtRoutes = formatRoutes(resp)
router.addRoutes(fmtRoutes)
store.commit('initAdminMenu', fmtRoutes)
})
}
routerFormat.js方法如下:
export function formatRoutes (routes) {
let fmtRoutes = []
routes.forEach(routes => {
if (routes.child) {
routes.child = formatRoutes(routes.child)
}
let fmtRoute = {
path: routes.path,
component: resolve => {
require(['@/components/admin/' + routes.component + '.vue'], resolve)
},
name: routes.name,
nameZh: routes.name_zh,
iconCls: routes.icon,
children: routes.child
}
fmtRoutes.push(fmtRoute)
})
return fmtRoutes
}
- Vuex设置存储
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// const key = 'user'
const store = new Vuex.Store({
state: {
username: window.localStorage.getItem('user' || '[]') == null ? '' : JSON.parse(window.localStorage.getItem('user' || '[]')).username,
token: ''
},
mutations: {
setUserName (state, username) {
state.username = username
},
setUserToken (state, token) {
state.token = token
},
setUserRole (state, role) {
state.rid = role
},
initAdminMenu (state, menu) {
state.adminMenus = menu
},
logout (state, token) {
state.username = null
state.token = ''
state.rid = -1
state.adminMenus = []
window.localStorage.removeItem('user')
}
}
})
export default store
- 封装路由组件
后台界面顶部,侧边栏和主界面构成,由于顶部和侧边栏不变,所以我们可以将顶部和侧边栏封装。
Head.vue
<!-- -->
<template>
<div>
<div class="home_title">后台系统</div>
<div class="home_userinfoContainer">
<el-dropdown @command="handleCommand">
<span class="el-dropdown-link home_userinfo">
{{currentUserName}}
<i class="el-icon-arrow-down el-icon--right home_userinfo"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="goIndex">返回主页</el-dropdown-item>
<el-dropdown-item command="logout" divided>退出登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</template>
<script>
import {post} from '@/utils/http'
export default {
data () {
return {
currentUserName: ''
}
},
mounted: function getUser () {
this.currentUserName = this.$store.state.username
},
methods: {
handleCommand (command) {
var _this = this
if (command === 'logout') {
this.$confirm('注销登录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(function () {
post('/user/logout')
_this.$store.commit('logout')
_this.$router.replace({name: 'login'})
_this.$message.success('退出成功')
}, function () {
// 取消
})
} else if (command === 'goIndex') {
this.$router.push({name: 'index'})
}
}
}
}
</script>
<style>
.el-header {
background-color: #20a0ff;
color: #333;
text-align: center;
display: flex;
align-items: center;
justify-content: space-between;
}
.home_title {
color: #fff;
font-size: 22px;
display: inline;
}
.home_userinfo {
color: #fff;
cursor: pointer;
}
.home_userinfoContainer {
position: absolute;
right: 0px;
display: inline;
margin-right: 20px;
}
</style>
Side.vue
<!-- -->
<template>
<div>
<el-menu router default-active="/admin/dashboard" mode="vertical" :collapse="isCollapse">
<template v-for="(item,i) in adminMenus">
<el-submenu :key="i" :index="i + ''" style="text-align: left">
<span slot="title">
<i :class="item.iconCls" style="font-size:10px"></i>
{{item.nameZh}}
</span>
<el-menu-item v-for="child in item.children" :key="child.path" :index="child.path">
<i :class="child.icon"></i>
{{ child.nameZh }}
</el-menu-item>
</el-submenu>
</template>
</el-menu>
</div>
</template>
<script>
export default {
data () {
return {
isCollapse: false
}
},
computed: {
adminMenus () {
debugger
return this.$store.state.adminMenus
}
}
}
</script>
主界面 AdminIndex.vue
<!-- -->
<template>
<div>
<el-container>
<el-header>
<Head></Head>
</el-header>
<el-container ref="homePage">
<el-aside :width="sideWidth">
<Side></Side>
</el-aside>
<el-main>
<router-view />
</el-main>
</el-container>
</el-container>
</div>
</template>
<script>
import Side from '@/components/home/Side'
import Head from '@/components/home/Head'
export default {
data () {
return {
clientHeight: '',
sideWidth: '200px'
}
},
mounted: function () {
// 获取浏览器可视区域高度
this.clientHeight = `${document.documentElement.clientHeight}`
this.sideWidth = document.body.clientWidth * 0.15 + 'px'
// document.body.clientWidth;
// console.log(self.clientHeight);
window.onresize = function temp () {
this.clientHeight = `${document.documentElement.clientHeight}`
}
},
watch: {
// 如果 `clientHeight` 发生改变,这个函数就会运行
clientHeight: function () {
this.changeFixed(this.clientHeight)
}
},
methods: {
changeFixed (clientHeight) {
// 动态修改样式
// console.log(clientHeight);
// console.log(this.$refs.homePage.$el.style.height);
this.$refs.homePage.$el.style.height = clientHeight - 80 + 'px'
}
},
components: {
Head,
Side
}
}
</script>
<style>
</style>
- 建立页面并写入Vue路由
项目结构
路由注册
import Vue from 'vue'
import Router from 'vue-router'
import Login from '@/components/Login'
import Logout from '@/components/Logout'
import Admin from '@/components/admin/AdminIndex'
Vue.use(Router)
export default new Router({
mode: 'history',
routes: [
{
path: '/login',
name: 'login',
component: Login
},
{
path: '/logout',
name: 'logout',
component: Logout,
meta: {requireAuth: true}
},
{
path: '/admin',
name: 'admin',
component: Admin,
redirect: '/admin/dashboard',
children: [
{
path: '/admin/dashboard',
name: 'adminDashboard',
component: () => import('@/components/admin/dashboard/Index')
},
{
path: '/admin/user/Profile',
name: 'adminUserProfile',
component: () => import('@/components/admin/user/Profile')
},
{
path: '/admin/user/Role',
name: 'adminUserRole',
component: () => import('@/components/admin/user/Role')
},
{
path: '/admin/test/Test1',
name: 'adminTest1',
component: () => import('@/components/admin/test/Test1')
},
{
path: '/admin/test/Test2',
name: 'adminTest2',
component: () => import('@/components/admin/test/Test2')
}
]
}
]
})
后端设计
- 数据库查询菜单
建立UserAuthMapper文件,在里面对数据库进行查询获取用户的菜单信息:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jkd.springbootproject.mapper.UserAuthMapper">
<resultMap id="BaseResultMap" type="com.jkd.springbootproject.pojo.Menu" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="path" property="path" jdbcType="VARCHAR" />
<result column="name" property="name" jdbcType="VARCHAR" />
<result column="name_zh" property="nameZh" jdbcType="VARCHAR" />
<result column="icon" property="icon" jdbcType="VARCHAR" />
<result column="component" property="component" jdbcType="VARCHAR" />
<result column="parent_id" property="parentId" jdbcType="INTEGER" />
</resultMap>
<select id="searchRoleByUid" resultType="com.jkd.springbootproject.pojo.Role">
select r.* from role r,user_role ur where ur.uid = #{uid} and ur.rid = r.id
</select>
<select id="getUserMenu" resultMap="BaseResultMap">
select m.* from menu m,role_menu rm where rm.rid = #{rid} and rm.mid = m.id
</select>
<select id="getByParentId" resultMap="BaseResultMap">
select m.* from menu m,role_menu rm where m.parent_id = #{parentId} and rm.rid = #{rid} and m.id = rm.mid
</select>
<select id="getByPid" resultType="com.jkd.springbootproject.pojo.Menu">
select * from menu where parent_id = #{pid}
</select>
<select id="getAllMenu" resultType="com.jkd.springbootproject.pojo.Menu">
select * from menu
</select>
<select id="getParentMenuId" resultType="java.lang.Integer">
select id from menu where parent_id = 0
</select>
<select id="getUserRoleId" resultType="java.lang.Integer">
select r.id from role r,user_role ur,user u where ur.rid = r.id and ur.uid = u.id and u.username = #{username}
</select>
</mapper>
- 建立UserAuthService文件,对返回的菜单进行处理
package com.jkd.springbootproject.service;
import com.jkd.springbootproject.mapper.UserAuthMapper;
import com.jkd.springbootproject.pojo.Menu;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* @projectName: springbootproject
* @package: com.jkd.springbootproject.service
* @className: UserAuthService
* @author: JKD
* @description: 对返回的菜单进行处理
* @version: 1.0
*/
@Service
public class UserAuthService {
@Autowired
private UserAuthMapper userAuthMapper;
public List<Menu> getByChildMenu(Integer rid, Integer parentId) {
return userAuthMapper.getByParentId(rid,parentId);
}
public List<Menu> getUserMenu(Integer rid) {
return userAuthMapper.getUserMenu(rid);
}
public Map<String,List> getAllMenu(){
Map<String,List> result = new HashMap<>();
List<Menu> noParentMenu = userAuthMapper.getAllMenu();
result.put("noParent",menuManage(noParentMenu));
result.put("parent",userAuthMapper.getParentMenuId());
return result;
}
private List menuManage(List<Menu> menus) {//对菜单进行格式化
for(Menu menu : menus){
menu.setChild(userAuthMapper.getByPid(menu.getId()));
}
Iterator<Menu> iterator = menus.iterator();
while (iterator.hasNext()) {
Menu menu = iterator.next();
if (menu.getParentId() != 0) {
iterator.remove();
}
}
return menus;
}
}
- 控制层
package com.jkd.springbootproject.controller;
import com.jkd.springbootproject.pojo.Menu;
import com.jkd.springbootproject.service.UserAuthService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/menu")
public class MenuController {
@Autowired
private UserAuthService userAuthService;
@GetMapping("/get/{rid}")
public List<Menu> getMenu(@PathVariable Integer rid){
List<Menu> menus = userAuthService.getUserMenu(rid);
for(Menu menu : menus){
if(menu.getParentId() == 0) {
menu.setChild(userAuthService.getByChildMenu(rid,menu.getId()));
}
}
Iterator<Menu> iterator = menus.iterator();
while (iterator.hasNext()) {
Menu menu = iterator.next();
if (menu.getParentId() != 0) {
iterator.remove();
}
}
return menus;
}
@GetMapping("/all")
public Map<String,List> getAllMenu(){
return userAuthService.getAllMenu();
}
}
- Mapper.java
package com.jkd.springbootproject.mapper;
import com.jkd.springbootproject.pojo.Menu;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @projectName: springbootproject
* @package: com.jkd.springbootproject.mapper
* @className: UserAuthMapper
* @author: JKD
* @description: 用户菜单权限
* @version: 1.0
*/
@Component
public interface UserAuthMapper {
List<Menu> getByParentId(Integer rid, Integer parentId);
List<Menu> getUserMenu(Integer rid);
List<Menu> getAllMenu();
List<Integer> getParentMenuId();
List<Menu> getByPid(Integer id);
}
- pojo
package com.jkd.springbootproject.pojo;
import java.io.Serializable;
import java.util.List;
public class Menu implements Serializable,Cloneable{
/** 菜单表id */
private Integer id ;
/** 前端路由的访问路径 */
private String path ;
/** 菜单名 */
private String name ;
/** 菜单中文名 */
private String nameZh ;
/** 菜单图标 */
private String icon ;
/** 前端路由所在的组件位置 */
private String component ;
/** 父菜单 */
private Integer parentId ;
private List< Menu> child ;
public List<Menu> getChild() {
return child;
}
public void setChild(List<Menu> child) {
this.child = child;
}
/** 菜单表id */
public Integer getId(){
return this.id;
}
/** 菜单表id */
public void setId(Integer id){
this.id = id;
}
/** 前端路由的访问路径 */
public String getPath(){
return this.path;
}
/** 前端路由的访问路径 */
public void setPath(String path){
this.path = path;
}
/** 菜单名 */
public String getName(){
return this.name;
}
/** 菜单名 */
public void setName(String name){
this.name = name;
}
/** 菜单中文名 */
public String getNameZh(){
return this.nameZh;
}
/** 菜单中文名 */
public void setNameZh(String nameZh){
this.nameZh = nameZh;
}
/** 菜单图标 */
public String getIcon(){
return this.icon;
}
/** 菜单图标 */
public void setIcon(String icon){
this.icon = icon;
}
/** 前端路由所在的组件位置 */
public String getComponent(){
return this.component;
}
/** 前端路由所在的组件位置 */
public void setComponent(String component){
this.component = component;
}
/** 父菜单 */
public Integer getParentId(){
return this.parentId;
}
/** 父菜单 */
public void setParentId(Integer parentId){
this.parentId = parentId;
}
}