Project1_07_过渡1_Vue中的动态导航和动态路由绑定&前端按钮权限粒度管理

一、项目环境

  1. 前端技术栈:Vue-Cli
  2. 前端软体:WebStorm 2020.3
  3. 前端样式: BootstrapElement-UI

二、文章主题

  1. 内容概述:为将Project07与SpringSecurity/Shiro+jwt整合,主要参考了MarkerHub_前后端分离后台管理系统进行改写。本文是关于该视频P16内容的学习整理。VueX相关内容参考:不良人_VueX
  2. 项目源码:shoppingProject01_pub : version7.1
    注:目前前端界面已初步搭建完成,运行前端项目后,可供参考的地址分别为登录展示内容展示
    已实现的前端页面如图1所示:
    在这里插入图片描述
图1 前端页面展示

(1) 左侧为导航栏,期望用户有三种角色(admin,vip,normal),不同角色进入商城时左侧应具有不同的导航栏显示,如admin可以看到“商品管理、用户管理、订单查询”,而其他角色不行。

(2) 为此,需要动态获取左侧导航栏的条目并对各条目进行动态路由绑定。如此,不具有访问权限的用户左侧将不会显示对应条目,在地址栏输入受限路由地址也不会跳转到相应的界面。
4. WebStorm项目相关目录结构如图2所示
在这里插入图片描述

图2 相关目录结构展示

三、实现过程

Step1:mock.js模拟后端发送过来的数据

Mock.mock('/menu/nav','get',()=> {

  let nav = [
    {
      title: '商品',
      name: 'item',  // 用于高亮
      icon: 'el-icon-s-shop',
      component: '',
      path: '',
      children: [
        {
          title: '商品列表',
          name: 'itemList',
          icon: 'el-icon-s-goods',
          component: 'sys/item/itemList',
          path: '/itemList',
        },
        {
          title: '会员福利',
          name: 'itemVip',
          icon: 'el-icon-s-management',
          component: 'sys/item/itemVip',
          path: '/itemVip',
        },
        {
          title: '商品管理',
          name: 'itemContro',
          icon: 'el-icon-s-finance',
          component: 'sys/item/itemContro',
          path: '/itemContro',
        }
      ]
    },
    {
      title: '用户',
      name: 'user',
      icon: 'el-icon-s-custom',
      component: '',
      path: '',
      children: [
        {
          title: '用户排行榜',
          name: 'userRank',
          icon: 'el-icon-s-check',
          component: 'sys/user/userRank',
          path: '/userRank',
        },
        {
          title: '用户管理',
          name: 'userContro',
          icon: 'el-icon-s-finance',
          component: 'sys/user/userContro',
          path: '/userContro',
        }
      ]
    },
    {
      title: '订单',
      name: 'order',
      icon: 'el-icon-s-finance',
      component: '',
      path: '',
      children: [
        {
          title: '订单查询',
          name: 'orderList',
          icon: 'el-icon-s-finance',
          component: 'sys/orderList',
          path: 'orderList',
        }
      ]
    }
  ]

  let authoritys = []

  Result.data = {
    nav: nav,
    authoritys: authoritys
  }
  return Result
})
// 即发送的是导航栏信息和用户权限信息

Step2:动态路由绑定

router–>index.js 中硬绑定的路由内容如下(注释内容):
注: 该文件的主要目的是当用户点击左侧导航栏条目时,小页面可以跳转到相应的自定义页面,由下面讨论的SideMenu.vue文件可知,用户点击左侧导航栏点击时点的是path路径,而当前文件的目的就是将path路径与文件夹下相应的自定义页面进行绑定(即path与component的绑定)

import Vue from 'vue'
import Router from 'vue-router'

// 这里先是引用全局axios而非自定义axios
import axios from "axios";
import store from "../store"

Vue.use(Router)

const router =  new Router({
  routes: [
    {
      path: '/',
      name: 'Home',
      component: () => import('../views/Home'),
      children: [
        {
          path: '/userCenter',
          name: 'UserCenter',
          component: () => import('../views/user/userCenter')
        },
        {
          path: '/index',
          name: 'Index',
          component: () => import('../views/Index')
        },
    //     {
    //       path: '/itemContro',
    //       name: 'ItemContro',
    //       component: () => import('../views/sys/item/itemContro')
    //     },
    //     {
    //       path: '/itemList',
    //       name: 'ItemList',
    //       component: () => import('../views/sys/item/itemList')
    //     },
    //     {
    //       path: '/itemVip',
    //       name: 'ItemVip',
    //       component: () => import('../views/sys/item/itemVip')
    //     },
    //     {
    //       path: '/userContro',
    //       name: 'UserContro',
    //       component: () => import('../views/sys/user/userContro')
    //     },
    //     {
    //       path: '/userRank',
    //       name: 'UserRank',
    //       component: () => import('../views/sys/user/userRank')
    //     },
    //     {
    //       path: '/orderList',
    //       name: 'OrderList',
    //       component: () => import('../views/sys/orderList')
    //     }
      ]},
    {
      path: '/login',
      name: 'Login',
      component: () => import('../views/Login')
    },
    {
      path: '/mailReg',
      name: 'MailReg',
      component: () => import('../views/user/mailReg')
    },
    {
      path: '/nickReg',
      name: 'NickReg',
      component: () => import('../views/user/nickReg')
    }
  ]
})

在router --> index.js中增加如下方法:

// 在路由加载之前的判定(因为我希望不同角色的用户看到不同的导航栏)
router.beforeEach((to,from,next)=>{
  
  // 这一步是为了判断是否之前已经加载了路由,避免重复加载,store中的menus中的hasRoute方法之后进行讨论
  let hasRoute = store.state.menus.hasRoute

  if ( !hasRoute ) {
    
    axios.get("/menu/nav",{
      headers: {
        Authorization: localStorage.getItem("token")  // 这里是因为我要集成jwt和SpirngSecurity,token信息在用户登录后必携带于请求头
      }
    }).then(res => {

      console.log(res.data.data)

      // 拿到menuList,存在store中。store是vuex的内容,用于管理全局的数据、更新数据,方便各组件进行调用和修改
      store.commit("setMenuList",res.data.data.nav)

      // 拿到用户权限(目前不做处理)
      store.commit("setPermList",res.data.data.authoritys)

      // 动态绑定路由
        // 方法是新建一个newRoutes,之后加入到始祖路由router中
      let newRoutes = router.options.routes
      res.data.data.nav.forEach(menu => {     // 见上方mock.js中发送过来的信息,menu是父条目,父条目没有对应的路由
        if ( menu.children ) {
          menu.children.forEach(e => {        // 对于父条目中的每一个子条目
            // 转成路由, 定义方法 menuToRoute,将子条目中的路由抽取出来形成真实的路由(主要是component转换如' @/views/sys/item/itemList.vue ')
            // 意思是 src目录下的views目录下的sys目录下的item目录下的itemList组件
            let route = menuToRoute(e) 

            // 把路由添加到路由管理中
            if (route) {
              newRoutes[0].children.push(route)  // 获取的每一个route都会在始祖routes的第一个route的children中填充
            }
          })
        }
      })
      console.log("newRoutes")
      console.log(newRoutes)
      router.addRoutes(newRoutes)  // 始祖route中加入获取到的所有路由

      hasRoute = true              // 这个flag为了下次页面刷新时,不用重复进行动态路由绑定
      store.commit("changeRouteStatus",hasRoute) // flag加到公共store的changeRouteStatus方法中
    })
  }

  next()                           // 放行,始祖路由可以开始工作了
})

// 导航转成路由
const menuToRoute = (menu) => {
  if ( !menu.component ) {     // 像主条目没有route,这里就直接返回空值
    return null
  }
  let route = {               // 获取到了一个完整的route
    name: menu.name,
    path: menu.path,
    meta: {                   // 这里是加载但在本文件下不需要的信息
      icon: menu.icon,
      title: menu.title
    }
  }

  route.component = () => import('@/views/'+ menu.component +'.vue')  // 形成真实的route中的component
  return route
}

Step3:动态获取导航栏

在views==>inclu==>SideMenu.vue中的代码如下:
注:该文件的主要目的是展示左侧导航栏信息

<template>

  <el-menu
    default-active="2"
    class="el-menu-vertical-demo"
    background-color="#545c64"
    text-color="#fff"
    active-text-color="#ffd04b">

    <router-link to="/index">
      <el-menu-item index="1-1">
        <template slot="title">
          <i class="el-icon-s-home"></i>
          <span slot="title">首页</span>
        </template>
      </el-menu-item>
    </router-link>

    <el-submenu :index="menu.name" v-for="menu in menuList">
      <template slot="title">
        <i :class="menu.icon"></i>
        <span>{{ menu.title }}</span>      <!-- 当前父条目的标题 -->
      </template>

      <router-link :to="thing.path" v-for="thing in menu.children">    <!-- 与router合作,点击时切换小页面 -->
        <el-menu-item :index="thing.name">                   <!-- 当前子条目的name,name的作用是点它时点亮 -->
          <template slot="title">
            <i :class="thing.icon"></i>
            <span slot="title">{{ thing.title }}</span>       <!-- 当前子条目的标题 -->
          </template>
        </el-menu-item>
      </router-link>
    </el-submenu>

  </el-menu>

</template>

<script>
export default {
  name: "SideMenu",
  data() {
    return {
    }
  },
  computed: {               // 为什么在computed获取menuList?
      menuList: {           // 因为computed中可以持续的获取当前页面中的menuList信息进行渲染,防止页面渲染完成后才获取到数据导致页面无法显示数据
        get() {
          return this.$store.state.menus.menuList   // 接下来我们讨论store
        }
      }
  },
  methods: {

  }
}
</script>

<style scoped>

.el-menu-vertical-demo {
  height: 100%;
}

</style>

Step4:Vuex中的存储

在 store–>index.js中的代码如下:

import Vue from 'vue'
import Vuex from 'vuex'
import menus from "./modules/menus"

Vue.use(Vuex); // 配置内置vue注册vuex状态管理


const store =  new Vuex.Store({
  state: {
    token: ''                              // 存储jwt令牌信息(本次不讨论)
  },
  mutations: {
    SET_TOKEN: (state, token) => {         // 设置jwt令牌信息的方法(本次不讨论)
      state.token = token
      localStorage.setItem("token",token)
    },
    resetState: (state) => {               // 用户logout时清楚令牌信息(本次不讨论)
      state.token = ''
    }
  },
  actions:{

  },
  modules:{
    menus                                  // 主要看这里,加载了menus模块,该文件也import了modules文件夹下的menus
  }


});

export default store;

在 store–>modules–>menus.js中的代码如下:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex); // 配置内置vue注册vuex状态管理


export default{
  state: {
    menuList: [], // menuList,由mock.js发到前端,在router->index.js中获取并存储(router->index.js也用它绑定动态路由了),在SideMenu使用
    permList: [],
    hasRoute: false // 是否已动态绑定路由的判决flag,在router->index.js中使用
  },
  mutations: {
      setMenuList(state,menus) {  // 获取menus,存储到当前的menuList中
          state.menuList = menus
      },
      setPermList(state,perms) {
          state.permList = perms
      },
      changeRouteStatus(state,hasRoute) {  // 获取flag,存储到当前hasRoute中,
      state.hasRoute = hasRoute           // 此外 sessionStorage.setItem("hasRoute",hasRoute)防止页面刷新时动态绑定的路由丢失
      sessionStorage.setItem("hasRoute",hasRoute)
     }
  },
  actions:{

  }

}

四、前端按钮权限细粒度管理

Step1: src目录下,定义文件globalFun.js实现全局皆可调用,代码如下:

import Vue from "vue"

Vue.mixin({
  methods: {
      hasAuth(perm) {
        var authority = this.$store.state.menus.permList
        return authority.indexOf(perm) > -1
      }
  }
})

Step2:在main.js中注册globalFun.js,如图3所示
在这里插入图片描述

图3 main.js中注册globalFun.js

Step3:使用方法

在itemVip.vue组件中添加如下v-if判断条件:

在这里插入图片描述

图4 itemVip.vue组件中实现按钮权限细粒度管理

此时,前端界面如图5所示:

在这里插入图片描述

图5 用户无权限时,itemVip.vue组件的前端页面展示情况

若在mock.js中添加如下权限编码段:
Mock.mock('/menu/nav','get',()=> {

  let nav = [...]

  let authoritys = ['sys:user:vipbutton','sys:user:adminbutton']   // 这里

  Result.data = {
    nav: nav,
    authoritys: authoritys
  }

  return Result
})

此时,前端界面如图6所示:
在这里插入图片描述

图6 用户有权限时,itemVip.vue组件的前端页面展示情况

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值