• 使用VUE开发用户后台时的动态路由问题、按钮权限问题以及其他页面处理问题


    如今前后端分离是大势所趋,笔者虽然是做后台的,但也不得不学学前端的流行框架VUE -_-||| 。

    为了学习VUE,笔者搭建了一个简单的用户后台,以此来了解VUE的开发思路(注:本项目不用于实际开发,只是为了学习,本文重点在于vue的动态路由添加,动态权限以及页面处理的一些小问题)。

    一、项目组成

      VUE 2.6.11 + axios +VueX + ElementUI 2.13.2 

    二、整体思路

      1.  用户登录后,获取菜单数据,用以显示菜单。

      2.  用户登录后,后台获取Vue路由数据,用以动态添加到VueRouter中。

      3.  用户登录后,从后台获取用户的权限,用以控制用户是否对某一功能具有可操作权限。

    三、具体实现

    ·   1.  登录。由于本人学习重点是使用VUE动态添加路由、菜单显示和权限控制,所以登录页面没有做具体功能,点击登录按钮就表示登录成功了。

          由于登录页是用户的第一界面,不存在任何权限问题,所以笔者就直接将登录页的路由直接写在了VueRouter实例中。如下:

       

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    
    Vue.use(VueRouter)
    
    const routes = [{
      path: '/Login',
      name: 'Login',
      component: () => import('../views/Login.vue')
    }]
    
    export function initRouter() {
      return new VueRouter({
        routes
      })
    }
    
    export default initRouter()

      用户通过 http://localhost:8080/#/login 可访问到登陆页面:

           点击登录按钮表示登录成功!

      登录成功后的处理:

      (1)向后台发请求拿到路由数据并存入VueX的state中,并通过addRoutes(routerObjArr)动态添加到路由实例中注:后台返回的数据结构需跟route相一致,如图:

      前端所需数据结构:

      

      后台返回的数据结构:

      

     

       细节处理:由于后台返回的component字段是个组件地址字符串,这就需要将后台返回的route数据 一 一 做处理,通过import() 方法动态的加载组件,然后将返回的compoent对象重新赋值到component字段上。如图:

      

      代码:

      

    const _import = require('@/router/_import_' + process.env.NODE_ENV) //获取组件的方法
    /**将router的json字符串中的component转换为组件对象 */
    export function filterAsyncRouter(asyncRouterMap) {
      if (!asyncRouterMap) return [];
    
      function _iter(before) {
        const after = Object.assign({}, before)
        if (after.component) {
          after.component = _import(after.component);
        }
        if (after.children && after.children.length) {
          after.children = filterAsyncRouter(after.children);
        }
        return after
      }
    
      return asyncRouterMap.map(_iter)
    
    }

       图中所用的import方法,根据生产环境不同,引用不同的文件,如图:

      

       各自的代码如下:

      _import_development.js:

    module.exports = file => require('@/views/' + file + '.vue').default // vue-loader at least v13.0.0+

      _import_production.js

    module.exports = file => () => import('@/views/' + file + '.vue')

      将后台的返回的路由数据处理完成后,最后就可以使用addRoutes方法动态加入VueRouter中了。此时,用户便可以按照路由访问页面了。代码如下:

    //动态生成路由
          axios
            .get("https://localhost:5001/AdminApi/Home/GetMenuJson?userid=" + 1)
            .then(res => {
              //取出路由列表 (isRoute=1)
              res.data.obj.router[0].children = res.data.obj.router[0].children.filter(
                a => a.meta.isRoute == 1 //isRoute=1的
              );
    
              //存入缓存
              this.$store.commit("setRoutes", res.data.obj.router);
    
              //转换组件对象
              var getRouter = filterAsyncRouter(res.data.obj.router);
    
              //打印当前路由列表
              console.log("当前路由列表:", res.data.obj.router[0].children);
    
              //清空之前的路由信息
              this.$router.matcher = initRouter().matcher;
    
              //重新添加路由信息
              this.$router.addRoutes(getRouter);
    
              //跳转到 Layout 组件
              this.$router.push("/");
            });

      (2)向后台发请求拿到权限数据,并存入VueX的state中。代码如下:

         axios
            .get(
              "https://localhost:5001/AdminApi/Access/ActionPermission?userid=" + 1
            )
            .then(res => {
              //存入权限
              console.log("权限列表:", res.data.obj);
              this.$store.commit("setAccess", res.data.obj);
            });

      (3)向后台请求数据并存入VueX中的state之前,需要清空上一次存入的数据(包括路由数据和权限数据),否则会造成数据混乱,如图:   

            

      (4)addRoutes之前,不仅要做component字段的字符串转对象的处理,还要清掉上一个用户登录后存入router中的路由数据,否则会造成数据混乱或者vue警告重复的路由名称。

      

       Login.vue组件中的全部代码如下:

    <template>
      <div class="about">
        <button @click="login">登录</button>
      </div>
    </template>
    
    <script>
    import { filterAsyncRouter } from "../common/promission";
    import axios from "axios";
    import { initRouter } from "@/router";
    export default {
      created() {
        this.$store.commit("logout");
      },
    
      methods: {
        login() {
          //动态生成路由
          axios
            .get("https://localhost:5001/AdminApi/Home/GetMenuJson?userid=" + 1)
            .then(res => {
              //取出路由列表 (isRoute=1)
              res.data.obj.router[0].children = res.data.obj.router[0].children.filter(
                a => a.meta.isRoute == 1 //isRoute=1的
              );
    
              //存入缓存
              this.$store.commit("setRoutes", res.data.obj.router);
    
              //转换组件对象
              var getRouter = filterAsyncRouter(res.data.obj.router);
    
              //打印当前路由列表
              console.log("当前路由列表:", res.data.obj.router[0].children);
    
              //清空之前的路由信息
              this.$router.matcher = initRouter().matcher;
    
              //重新添加路由信息
              this.$router.addRoutes(getRouter);
    
              //跳转到 Layout 组件
              this.$router.push("/");
            });
    
          axios
            .get(
              "https://localhost:5001/AdminApi/Access/ActionPermission?userid=" + 1
            )
            .then(res => {
              //存入权限
              console.log("权限列表:", res.data.obj);
              this.$store.commit("setAccess", res.data.obj);
            });
        }
      }
    };
    </script>

      promiss.js代码如下:

    const _import = require('@/router/_import_' + process.env.NODE_ENV) //获取组件的方法
    /**将router的json字符串中的component转换为组件对象 */
    export function filterAsyncRouter(asyncRouterMap) {
      if (!asyncRouterMap) return [];
    
      function _iter(before) {
        const after = Object.assign({}, before)
        if (after.component) {
          after.component = _import(after.component);
        }
        if (after.children && after.children.length) {
          after.children = filterAsyncRouter(after.children);
        }
        return after
      }
    
      return asyncRouterMap.map(_iter)
    
    }

      store.js代码如下:

    import Vue from 'vue'
    import Vuex from 'vuex'
    import VuexPersistence from 'vuex-persist'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state: {
        routes: [],
      },
      mutations: {
        setRoutes: (state, routes) => {
          state.routes = routes
        },
        setAccess: (state, access) => {
          state.access = access;
        },
        logout: (state) => {
          state.routes = [];
          state.access = []
        }
      },
      actions: {},
      modules: {},
      plugins: [new VuexPersistence().plugin]
    })

      2. 菜单。将Layout组件用作菜显示组件,将ele中的菜单组件复制到该组件中,并通过向后台请求数据,拿到菜单和菜单对应的分组数据 。拿到菜单和菜单分组数据后,循环遍历,将菜单按照对应的分组全部显示(后台判断当前用户可显示的菜单,没有权限的菜单直接不返给前台)。vue代码以及后台数据如下:

      

    <template>
      <el-container>
        <el-header>
          <el-dropdown>
            <i class="el-icon-setting"></i>
            <el-dropdown-menu slot="dropdown">
              <el-dropdown-item>修改密码</el-dropdown-item>
              <el-dropdown-item>退出</el-dropdown-item>
            </el-dropdown-menu>
          </el-dropdown>
          <span>王小虎</span>
        </el-header>
        <el-container>
          <el-aside width="250px">
            <el-menu @select="handleSelect">
              <el-submenu :index="group.name" v-for="group in groupList" :key="group.id">
                <template slot="title">
                  <i class="el-icon-message"></i>
                  {{group.title}}
                </template>
                <template v-for="router in routerList">
                  <el-menu-item
                    :index="router.path"
                    :key="router.meta.id"
                    v-if="router.meta.groupId == group.id"
                  >{{router.meta.title}}</el-menu-item>
                </template>
              </el-submenu>
            </el-menu>
          </el-aside>
          <el-main>
            <router-view />
          </el-main>
        </el-container>
      </el-container>
    </template>
    
    <script>
    import axios from "axios";
    
    export default {
      data() {
        return {
          activeIndex: "/home/Index",
          groupList: [],
          routerList: []
        };
      },
      mounted() {
        this.getGroupList();
        this.getRouterList();
      },
      methods: {
        //菜单点击事件
        handleSelect(key) {
          this.$router.push(key);
        },
        //获取菜单分组数据
        getGroupList() {
          var that = this;
          axios
            .get("https://localhost:5001/AdminApi/Home/GetGroupJson")
            .then(res => {
              that.groupList = res.data.obj;
            });
        },
        //获取菜单数据
        getRouterList() {
          var that = this;
          axios
            .get("https://localhost:5001/AdminApi/Home/GetMenuJson")
            .then(res => {
              that.routerList = res.data.obj.router[0].children.filter(
                a => a.meta.display == 1 //取display=1的
              );
              console.log("当前菜单列表");
              console.log(that.routerList);
            });
        }
      }
    };
    </script>
    
    <style>
    @import "../styles/layout.css"; /*引入公共样式*/
    </style>

    后台分组数据:

    {
    "id": 14,
    "name": "Customer",
    "title": "客户中心",
    "target": "mainRigh",
    "url": "#",
    "icoCss": "layui-icon-username",
    "delFlag": 0,
    "sortNo": 0,
    "createMan": 1,
    "createTime": "2019-05-05T11:30:06"
    },
    {
    "id": 9,
    "name": "System",
    "title": "系统设置",
    "target": "123",
    "url": "#",
    "icoCss": "fa-gears",
    "delFlag": 0,
    "sortNo": 1,
    "createMan": 1,
    "createTime": "2019-05-05T11:29:56"
    }

    后台菜单数据:

    效果图:

      3.  功能页面的处理。

        (1)组件的动态加载规则由于该vue项目中的组件是动态加载,那么后台返回的路由数据中的component字段中的路径自然也要按照某一种规则来返给前端。否则会造成import()组件的时候,由于地址不对解析加载不到组件而报错。

       例如笔者是按照这种规则:

           后台数据

     

     斜杠”/“前边表示文件夹名称,后边表示组件名称,这样就可以按照这种规则动态加载到组件了。

     

     (2).页面刷新变成空白页?(路由丢失)

      遇到这个问题的话,在main.js中加入一段代码,每次刷新页面都把存入VueX state中的数据拿出来,判断一下路由里边还存不存在当前刷新页面的路由,如果没有,则对VueRouters重新赋值

      main.js 代码如下:

    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    import ElementUI from 'element-ui';
    import 'element-ui/lib/theme-chalk/index.css';
    import store from './common/store'
    import {
      filterAsyncRouter
    } from "./common/promission";
    
    Vue.config.productionTip = false
    Vue.use(ElementUI);
    
    new Vue({
      router,
      store,
      render: h => h(App),
      mounted() {
        // 缓存的路由信息
        const routes = this.$store.state.routes
        // 判断当前路由是否被清除
        const hasRoute = this.$router.options.routes.some(r => r.name == 'index')
        // 如果 缓存中有路由信息 并且 当前路由被清除
        if (routes.length && !hasRoute) {
          //获取路由Json字符串
          var getRouter = filterAsyncRouter(routes);
          // 再次添加路由信息
          this.$router.addRoutes(getRouter);
          // 然后强制更新当前组件
          this.$forceUpdate()
        }
      },
    }).$mount('#app')

      (3)  页面按钮的控制

      将前面存入vuex state中的权限数据,在每个组件中都拿出来一下存入一个变量中,在按钮上使用v-if、array.indexOf('权限名称')来控制显示/隐藏。

      原理是如果用户存在该权限,则v-if=”true“,按钮则显示,否则按钮隐藏。

      代码如下:

    <el-button
              @click="edit(scope.row)"
              type="text"
              size="small"
              v-if="accessList.indexOf('SysRole/AddEdit')>-1"
            >编辑</el-button>

      

     效果图:

    好了,笔者就介绍到这里。当然,如果要做一个完整的后台,肯定还有很多要做的东西,比如用户角色啊、角色授权啊等等;但笔者这次学习的重点是VUE的动态路由、动态权限,以及页面处理中一些比较常见的坑,所以别的就不多介绍了。

    如有需要,朋友们可以联系我,大家多多交流。

  • 相关阅读:
    SpringBoot中添加事务
    隐藏样式
    Mybatis配置解析
    题目1064:反序数------玩转小聪明
    题目1063:整数和
    题目1062:分段函数23333333333333
    题目1060:完数VS盈数------这题做得我想骂人
    题目1059:abc----------就喜欢这样的题
    题目1050:完数-----------runtime error的问题
    题目1049:字符串去特定字符
  • 原文地址:https://www.cnblogs.com/w821759016/p/13032468.html
Copyright © 2020-2023  润新知