[Springboot+Vue]做一个权限管理后台(七):动态加载后台菜单

数据库设计

每一个用户都有一个用户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;
    }
}

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值