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

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

一、项目组成

  VUE 2.6.11 + axios +VueX + ElementUI 2.13.2

二、整体思路

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

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

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

三、具体实现

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

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

   

  1. import Vue from 'vue'
  2. import VueRouter from 'vue-router'
  3.  
  4. Vue.use(VueRouter)
  5.  
  6. const routes = [{
  7. path: '/Login',
  8. name: 'Login',
  9. component: () => import('../views/Login.vue')
  10. }]
  11.  
  12. export function initRouter() {
  13. return new VueRouter({
  14. routes
  15. })
  16. }
  17.  
  18. export default initRouter()

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

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

  登录成功后的处理:

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

  前端所需数据结构:

  

  后台返回的数据结构:

  

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

  

  代码:

  

  1. const _import = require('@/router/_import_' + process.env.NODE_ENV) //获取组件的方法
  2. /**将router的json字符串中的component转换为组件对象 */
  3. export function filterAsyncRouter(asyncRouterMap) {
  4. if (!asyncRouterMap) return [];
  5.  
  6. function _iter(before) {
  7. const after = Object.assign({}, before)
  8. if (after.component) {
  9. after.component = _import(after.component);
  10. }
  11. if (after.children && after.children.length) {
  12. after.children = filterAsyncRouter(after.children);
  13. }
  14. return after
  15. }
  16.  
  17. return asyncRouterMap.map(_iter)
  18.  
  19. }

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

  

  各自的代码如下:

  _import_development.js:

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

  _import_production.js

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

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

  1. //动态生成路由
  2. axios
  3. .get("https://localhost:5001/AdminApi/Home/GetMenuJson?userid=" + 1)
  4. .then(res => {
  5. //取出路由列表 (isRoute=1)
  6. res.data.obj.router[0].children = res.data.obj.router[0].children.filter(
  7. a => a.meta.isRoute == 1 //isRoute=1的
  8. );
  9.  
  10. //存入缓存
  11. this.$store.commit("setRoutes", res.data.obj.router);
  12.  
  13. //转换组件对象
  14. var getRouter = filterAsyncRouter(res.data.obj.router);
  15.  
  16. //打印当前路由列表
  17. console.log("当前路由列表:", res.data.obj.router[0].children);
  18.  
  19. //清空之前的路由信息
  20. this.$router.matcher = initRouter().matcher;
  21.  
  22. //重新添加路由信息
  23. this.$router.addRoutes(getRouter);
  24.  
  25. //跳转到 Layout 组件
  26. this.$router.push("/");
  27. });

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

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

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

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

  

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

  1. <template>
  2. <div class="about">
  3. <button @click="login">登录</button>
  4. </div>
  5. </template>
  6.  
  7. <script>
  8. import { filterAsyncRouter } from "../common/promission";
  9. import axios from "axios";
  10. import { initRouter } from "@/router";
  11. export default {
  12. created() {
  13. this.$store.commit("logout");
  14. },
  15.  
  16. methods: {
  17. login() {
  18. //动态生成路由
  19. axios
  20. .get("https://localhost:5001/AdminApi/Home/GetMenuJson?userid=" + 1)
  21. .then(res => {
  22. //取出路由列表 (isRoute=1)
  23. res.data.obj.router[0].children = res.data.obj.router[0].children.filter(
  24. a => a.meta.isRoute == 1 //isRoute=1的
  25. );
  26.  
  27. //存入缓存
  28. this.$store.commit("setRoutes", res.data.obj.router);
  29.  
  30. //转换组件对象
  31. var getRouter = filterAsyncRouter(res.data.obj.router);
  32.  
  33. //打印当前路由列表
  34. console.log("当前路由列表:", res.data.obj.router[0].children);
  35.  
  36. //清空之前的路由信息
  37. this.$router.matcher = initRouter().matcher;
  38.  
  39. //重新添加路由信息
  40. this.$router.addRoutes(getRouter);
  41.  
  42. //跳转到 Layout 组件
  43. this.$router.push("/");
  44. });
  45.  
  46. axios
  47. .get(
  48. "https://localhost:5001/AdminApi/Access/ActionPermission?userid=" + 1
  49. )
  50. .then(res => {
  51. //存入权限
  52. console.log("权限列表:", res.data.obj);
  53. this.$store.commit("setAccess", res.data.obj);
  54. });
  55. }
  56. }
  57. };
  58. </script>

  promiss.js代码如下:

  1. const _import = require('@/router/_import_' + process.env.NODE_ENV) //获取组件的方法
  2. /**将router的json字符串中的component转换为组件对象 */
  3. export function filterAsyncRouter(asyncRouterMap) {
  4. if (!asyncRouterMap) return [];
  5.  
  6. function _iter(before) {
  7. const after = Object.assign({}, before)
  8. if (after.component) {
  9. after.component = _import(after.component);
  10. }
  11. if (after.children && after.children.length) {
  12. after.children = filterAsyncRouter(after.children);
  13. }
  14. return after
  15. }
  16.  
  17. return asyncRouterMap.map(_iter)
  18.  
  19. }

  store.js代码如下:

  1. import Vue from 'vue'
  2. import Vuex from 'vuex'
  3. import VuexPersistence from 'vuex-persist'
  4.  
  5. Vue.use(Vuex)
  6.  
  7. export default new Vuex.Store({
  8. state: {
  9. routes: [],
  10. },
  11. mutations: {
  12. setRoutes: (state, routes) => {
  13. state.routes = routes
  14. },
  15. setAccess: (state, access) => {
  16. state.access = access;
  17. },
  18. logout: (state) => {
  19. state.routes = [];
  20. state.access = []
  21. }
  22. },
  23. actions: {},
  24. modules: {},
  25. plugins: [new VuexPersistence().plugin]
  26. })

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

  

  1. <template>
  2. <el-container>
  3. <el-header>
  4. <el-dropdown>
  5. <i class="el-icon-setting"></i>
  6. <el-dropdown-menu slot="dropdown">
  7. <el-dropdown-item>修改密码</el-dropdown-item>
  8. <el-dropdown-item>退出</el-dropdown-item>
  9. </el-dropdown-menu>
  10. </el-dropdown>
  11. <span>王小虎</span>
  12. </el-header>
  13. <el-container>
  14. <el-aside width="250px">
  15. <el-menu @select="handleSelect">
  16. <el-submenu :index="group.name" v-for="group in groupList" :key="group.id">
  17. <template slot="title">
  18. <i class="el-icon-message"></i>
  19. {{group.title}}
  20. </template>
  21. <template v-for="router in routerList">
  22. <el-menu-item
  23. :index="router.path"
  24. :key="router.meta.id"
  25. v-if="router.meta.groupId == group.id"
  26. >{{router.meta.title}}</el-menu-item>
  27. </template>
  28. </el-submenu>
  29. </el-menu>
  30. </el-aside>
  31. <el-main>
  32. <router-view />
  33. </el-main>
  34. </el-container>
  35. </el-container>
  36. </template>
  37.  
  38. <script>
  39. import axios from "axios";
  40.  
  41. export default {
  42. data() {
  43. return {
  44. activeIndex: "/home/Index",
  45. groupList: [],
  46. routerList: []
  47. };
  48. },
  49. mounted() {
  50. this.getGroupList();
  51. this.getRouterList();
  52. },
  53. methods: {
  54. //菜单点击事件
  55. handleSelect(key) {
  56. this.$router.push(key);
  57. },
  58. //获取菜单分组数据
  59. getGroupList() {
  60. var that = this;
  61. axios
  62. .get("https://localhost:5001/AdminApi/Home/GetGroupJson")
  63. .then(res => {
  64. that.groupList = res.data.obj;
  65. });
  66. },
  67. //获取菜单数据
  68. getRouterList() {
  69. var that = this;
  70. axios
  71. .get("https://localhost:5001/AdminApi/Home/GetMenuJson")
  72. .then(res => {
  73. that.routerList = res.data.obj.router[0].children.filter(
  74. a => a.meta.display == 1 //取display=1的
  75. );
  76. console.log("当前菜单列表");
  77. console.log(that.routerList);
  78. });
  79. }
  80. }
  81. };
  82. </script>
  83.  
  84. <style>
  85. @import "../styles/layout.css"; /*引入公共样式*/
  86. </style>

后台分组数据:

  1. {
  2. "id": 14,
  3. "name": "Customer",
  4. "title": "客户中心",
  5. "target": "mainRigh",
  6. "url": "#",
  7. "icoCss": "layui-icon-username",
  8. "delFlag": 0,
  9. "sortNo": 0,
  10. "createMan": 1,
  11. "createTime": "2019-05-05T11:30:06"
  12. },
  13. {
  14. "id": 9,
  15. "name": "System",
  16. "title": "系统设置",
  17. "target": "123",
  18. "url": "#",
  19. "icoCss": "fa-gears",
  20. "delFlag": 0,
  21. "sortNo": 1,
  22. "createMan": 1,
  23. "createTime": "2019-05-05T11:29:56"
  24. }

后台菜单数据:

效果图:

  3.  功能页面的处理。

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

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

       后台数据

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

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

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

  main.js 代码如下:

  1. import Vue from 'vue'
  2. import App from './App.vue'
  3. import router from './router'
  4. import ElementUI from 'element-ui';
  5. import 'element-ui/lib/theme-chalk/index.css';
  6. import store from './common/store'
  7. import {
  8. filterAsyncRouter
  9. } from "./common/promission";
  10.  
  11. Vue.config.productionTip = false
  12. Vue.use(ElementUI);
  13.  
  14. new Vue({
  15. router,
  16. store,
  17. render: h => h(App),
  18. mounted() {
  19. // 缓存的路由信息
  20. const routes = this.$store.state.routes
  21. // 判断当前路由是否被清除
  22. const hasRoute = this.$router.options.routes.some(r => r.name == 'index')
  23. // 如果 缓存中有路由信息 并且 当前路由被清除
  24. if (routes.length && !hasRoute) {
  25. //获取路由Json字符串
  26. var getRouter = filterAsyncRouter(routes);
  27. // 再次添加路由信息
  28. this.$router.addRoutes(getRouter);
  29. // 然后强制更新当前组件
  30. this.$forceUpdate()
  31. }
  32. },
  33. }).$mount('#app')

  (3)  页面按钮的控制

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

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

  代码如下:

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

  

效果图:

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

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

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

  1. vue+elementui搭建后台管理界面(6登录和菜单权限控制)

    不同的权限对应不同的路由(菜单),同时侧边栏也根据权限异步生成,实现登录和鉴权思路如下: 登录:点击登录,服务器验证通过后返回一个 token ,然后存到 cookie,再根据 token 拉取用户权 ...

  2. 基于hi-nginx的web开发(python篇)——动态路由和请求方法

    hi.py的提供的路由装饰器接受两个参数,第一个参数指定动态路由的正则模式,第二个参数指定同意的http请求方法列表. 比如: @app.route(r"^/client/?$", ...

  3. vue 开发系列(八) 动态表单开发

    概要 动态表单指的是我们的表单不是通过vue 组件一个个编写的,我们的表单是根据后端生成的vue模板,在前端通过vue构建出来的.主要的思路是,在后端生成vue的模板,前端通过ajax的方式加载后端的 ...

  4. node vue 开发环境部署时,外部访问页面出现: Invalid Host header 服务器域名访问出现的问题

    这是新版本 webpack-dev-server  出于安全考虑, 默认检查 hostname,如果hostname不是配置内的,将中断访问.顾仅存在于开发环境: npm run dev,打包之后不会 ...

  5. vue开发 - 根据vue-router的meta动态设置html里标签的内容

    路由文件 :router/index.js import Vue from 'vue'import Router from 'vue-router'import index '@/view/index ...

  6. vue开发 - 根据vue-router的meta动态设置html里title标签内容

    1.路由文件 :router/index.js 添加 meta属性配置: import Vue from 'vue' import Router from 'vue-router' import in ...

  7. vue+elementui搭建后台管理界面(6登录和菜单权限控制[二])

    根据权限计算路由的代码 /** * 通过meta.role判断是否与当前用户权限匹配 * @param roles * @param route */ function hasRoles (roles ...

  8. vue开发关于微信授权登录以及路由mode模式(Hash|History)和手机平台(andriod|IOS)不得不说的故事

    引用链接: https://segmentfault.com/a/1190000010753247?utm_source=tuicool&utm_medium=referral

  9. .net简单页面后台绑定下拉框,按钮,分页 前台aspx页面

    一.aspx页面 <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Updat ...

随机推荐

  1. Python 文件的读取与写入

    1. 读取文件,文件中没有中文 备注 : 文件名 : EnglishFile.txt 文件位置 : 保存在所写的.py文件的同级目录,附上截图,便于参考 备注 : 文件位置可以改变,只需要把文件路径传 ...

  2. 用PHP获取网页上的信息相对于xpath效率低点

    用php实现对网页的抓取,及信息的收集,其实就是爬数据,具体实现步骤如下,首先应引入两个文件curl_html_get.php和save_file.php文件,两个文件具体代码是这样的curl_htm ...

  3. Linux Kernel Makefiles Kbuild en

    来自Linux kernel docs,顺便整理了一下排版 Linux Kernel Makefiles This document describes the Linux kernel Makefi ...

  4. React Native 架构一览

    一.架构设计 整体上分为三大块,Native.JavaScript 与 Bridge: Native 管理 UI 更新及交互,JavaScript 调用 Native 能力实现业务功能,Bridge ...

  5. JS理论-跨域解决方案

    一: 用过JS跨域 1.JSONP跨域(利用script标签不受网站同源策略影响) 2.documen.domian跨域(通过指定基础域名,达到在一个域) 二: 通过服务器跨域 1.通过代理服务器,比 ...

  6. Python --函数学习2

    一.函数参数和返回值 --参数:负责给函数传递一些必要的数据或者信息 -形参(形式参数):在函数定义的时候用到的参数,没有具体值,只是一个占位符号 -实参(实际参数):在调用函数的时候输入的值 exa ...

  7. 「雕爷学编程」Arduino动手做(12)——霍尔磁力模块

    37款传感器和模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的.鉴于本人手头积累了一些传感器与模块,依照实践出真知(动手试试)的理念,以学习和交流为目的,这里准备 ...

  8. vs2015 cppunit配置及使用

    目录 第一步 第二步 第三步 编译生成lib库 使用 calculator类测试 代码部分 第一步 下载源代码 http://sourceforge.net/projects/cppunit/file ...

  9. mysql运维入门1:基础及备份还原

    存储引擎 myisam 表强调的是性能 执行速度比innodb类型更快 不提供事务支持 如果执行大量的select操作,是首选 支持表锁,不支持行锁 innodb 提供事务支持.外键等高级数据库功能 ...

  10. JavaScript(对象的创建模式)

    JavaScript和其他语言略有不同,在JavaScript中,引用数据类型都是对象(包括函数).不过,在JavaScript中并没有“类”的概念,这决定了在JavaScript中不能直接来定义“类 ...