Vue + Element UI 实现权限管理系统(动态加载菜单)

动态加载菜单

之前我们的导航树都是写死在页面里的,而实际应用中是需要从后台服务器获取菜单数据之后动态生成的。

我们在这里就用上一篇准备好的数据格式Mock出模拟数据,然后动态生成我们的导航菜单。

接口模块化

我们向来讲究模块化,之前接口都集中在,interface.js,我们现在把它改名为 api.js,并把里边原来登录、用户、菜单的相关接口都转移到我们新建的接口模块文件中。

模块化之后的文件结构如下图所示

模块化之后,模块接口写在相应的模块接口文件中,如下面是登录模块

login.js

  1. import axios from '../axios'
  2. /*
  3. * 系统登录模块
  4. */
  5. // 登录
  6. export const login = data => {
  7. return axios({
  8. url: '/login',
  9. method: 'post',
  10. data
  11. })
  12. }
  13. // 登出
  14. export const logout = () => {
  15. return axios({
  16. url: '/logout',
  17. method: 'get'
  18. })
  19. }

模块化之后,父模块可以像这样引入

api.js

  1. /*
  2. * 接口统一集成模块
  3. */
  4. import * as login from './moudules/login'
  5. import * as user from './moudules/user'
  6. import * as menu from './moudules/menu'
  7. // 默认全部导出
  8. export default {
  9. login,
  10. user,
  11. menu
  12. }

因为我们这里是导出的是父模块,所以在具体接口调用的时候,也需要在原来的基础上加上模块了,像这样。

如上面 api.js 中,我们导出了 login 的整个文件,而 login 文件下有 login,logout 等多个方法。

导航菜单树接口

我们在 menu.js 下创建一个查询导航菜单树的接口。

  1. import axios from '../axios'
  2. /*
  3. * 菜单管理模块
  4. */
  5. export const findMenuTree = () => {
  6. return axios({
  7. url: '/menu/findTree',
  8. method: 'get'
  9. })
  10. }

api.js 中如果没引入要记得引入。

页面接口调用

接口已经有了,我们在导航菜单组件 MenuBar.vue 中,加载菜单并存入 store 。

页面菜单渲染

还是在  MenuBar.vue 中,页面通过封装的菜单树组件读取store数据,递归生成菜单。

新建菜单树组件,递归生成菜单,并在点击响应函数里面根据菜单URL跳转到指定路由。

components/MenuTree/index.js

  1. <template>
  2. <el-submenu v-if="menu.children && menu.children.length >= 1" :index="menu.menuId + ''">
  3. <template slot="title">
  4. <span slot="title">{{menu.name}}</span>
  5. </template>
  6. <MenuTree v-for="item in menu.children" :key="item.menuId" :menu="item"></MenuTree>
  7. </el-submenu>
  8. <el-menu-item v-else :index="menu.menuId + ''" @click="handleRoute(menu)">
  9. <span slot="title">{{menu.name}}</span>
  10. </el-menu-item>
  11. </template>
  12. <script>
  13. export default {
  14. name: 'MenuTree',
  15. props: {
  16. menu: {
  17. type: Object,
  18. required: true
  19. }
  20. },
  21. methods: {
  22. handleRoute (menu) {
  23. // 通过菜单URL跳转至指定路由
  24. this.$router.push(menu.url)
  25. }
  26. }
  27. }
  28. </script>

提供Mock数据

接口有了,页面调用和渲染也写好了,该提供Mock数据了。

mock/modules/menu.js 中 mock findTree接口,data 对应数据太多,这里不贴了。

  1. export function findTree() {
  2. return {
  3. url: 'http://localhost:8080/menu/findTree',
  4. type: 'get',
  5. data: menuTreeData // json 对象数据
  6. }
  7. }

测试效果

启动完成,进入主页,我们看到导航菜单已经成功加载进来了,oh yeah!

然而,我们愉悦的点了点菜单,发现是这样的情况,oh no !

毛都没有,不过显然,聪明的你已经看穿了一切,我们之前只提供了一个叫 /user 的路由,并没有提供 /sys/user 的路由。

好吧,我们稍微修改一下,打开路由配置,把 /user 改成 /sys/user 试试。

果不其然,修改完之后便可以正常跳转到用户界面了。

但不对呀,这里路由配置是写死的,导航菜单是菜单数据动态生成的,这个路由配置也应该是根据菜单数据动态添加的啊,嗯,所以接下来我们就来讨论动态路由配置的问题。

动态路由实现

在 vue 的 route 中提供了 addRoutes 来实现动态路由,打开 MenuBar.vue ,我们在加载导航菜单的同时添加动态路由配置。

MenuBar.vue

其中 addDynamicMenuRoutes 是根据菜单返回动态路由配置的关键代码。

addDynamicMenuRoutes 方法详情:

  1. /**
  2. * 添加动态(菜单)路由
  3. * @param {*} menuList 菜单列表
  4. * @param {*} routes 递归创建的动态(菜单)路由
  5. */
  6. addDynamicMenuRoutes (menuList = [], routes = []) {
  7. var temp = []
  8. for (var i = 0; i < menuList.length; i++) {
  9. if (menuList[i].children && menuList[i].children.length >= 1) {
  10. temp = temp.concat(menuList[i].children)
  11. } else if (menuList[i].url && /\S/.test(menuList[i].url)) {
  12. menuList[i].url = menuList[i].url.replace(/^\//, '')
  13. // 创建路由配置
  14. var route = {
  15. path: menuList[i].url,
  16. component: null,
  17. name: menuList[i].name,
  18. meta: {
  19. menuId: menuList[i].menuId,
  20. title: menuList[i].name,
  21. isDynamic: true,
  22. isTab: true,
  23. iframeUrl: ''
  24. }
  25. }
  26. // url以http[s]://开头, 通过iframe展示
  27. if (isURL(menuList[i].url)) {
  28. route['path'] = menuList[i].url
  29. route['name'] = menuList[i].name
  30. route['meta']['iframeUrl'] = menuList[i].url
  31. } else {
  32. try {
  33. // 根据菜单URL动态加载vue组件,这里要求vue组件须按照url路径存储
  34. // 如url="sys/user",则组件路径应是"@/views/sys/user.vue",否则组件加载不到
  35. let array = menuList[i].url.split('/')
  36. let url = array[0].substring(0,1).toUpperCase()+array[0].substring(1) + '/' + array[1].substring(0,1).toUpperCase()+array[1] .substring(1)
  37. route['component'] = resolve => require([@/views/${url}], resolve)
  38. } catch (e) {}
  39. }
  40. routes.push(route)
  41. }
  42. }
  43. if (temp.length >= 1) {
  44. this.addDynamicMenuRoutes(temp, routes)
  45. } else {
  46. console.log(routes)
  47. }
  48. return routes
  49. }

动态菜单页面的组件结构稍微调整下,需要跟菜单url匹配,才能根据菜单url确定组件路径来动态加载组件。

把路由文件清理一下,把动态菜单相关的路由配置处理掉,留下一些固定的全局路由就好。

动态路由测试

启动完成,进入主页,点击用户管理,路由到了用户管理页面。

点击机构管理,路由到了机构管理页面。

好了,到这里动态路由功能已经实现了,给自己鼓个掌吧。

页面刷新出大坑

先前我们是将导航菜单和路由的加载放在菜单栏页面MenuBar.vue中,一切显示和路由也都正常,看起来没什么问题。然而当我们在非根据路径刷新页面时,问题出现了。

如下图所示,我们在用户管理页面的时候,点击刷新浏览器,然后就白茫茫一片了,这是因为浏览器的刷新会导致整个vue重新加载,路由被重新初始化了,后面在Menu.bar添加的动态路由没有了,所以跳转的时候没有找到匹配路由,跳转的是一个不存在的页面,故而白茫茫一片。

专业填坑指南

这显然是动态菜单和路由的加载时机不对,怎么解决这个问题呢,既然问题出在加载时机,那就找一个在页面属性的时候也能触发重新加载的地方就好了。

这样的地方也不少,像vue加载过程中的钩子函数,路由导航守卫函数等都可以,我们这里就选择在路由导航守卫的 beforeEach 函数内加载,保证每次路由跳转的时候都能够拥有动态菜单和路由。

把原先在MenuBar.vue中加载动态菜单和路由的代码,转移到路由配置 router/index 中来。

beforeEach:

  1. router.beforeEach((to, from, next) => {
  2. // 登录界面登录成功之后,会把用户信息保存在会话
  3. // 存在时间为会话生命周期,页面关闭即失效。
  4. let isLogin = sessionStorage.getItem('user')
  5. if (to.path === '/login') {
  6. // 如果是访问登录界面,如果用户会话信息存在,代表已登录过,跳转到主页
  7. if(isLogin) {
  8. next({ path: '/' })
  9. } else {
  10. next()
  11. }
  12. } else {
  13. // 如果访问非登录界面,且户会话信息不存在,代表未登录,则跳转到登录界面
  14. if (!isLogin) {
  15. next({ path: '/login' })
  16. } else {
  17. // 加载动态菜单和路由
  18. addDynamicMenuAndRoutes()
  19. next()
  20. }
  21. }
  22. })

addDynamicMenuAndRoutes:

  1. /**
  2. * 加载动态菜单和路由
  3. */
  4. function addDynamicMenuAndRoutes() {
  5. api.menu.findMenuTree()
  6. .then( (res) => {
  7. store.commit('setMenuTree', res.data)
  8. // 添加动态路由
  9. let dynamicRoutes = addDynamicRoutes(res.data)
  10. router.options.routes[0].children = router.options.routes[0].children.concat(dynamicRoutes)
  11. router.addRoutes(router.options.routes);
  12. })
  13. .catch(function(res) {
  14. alert(res);
  15. });
  16. }

addDynamicRoutes:

  1. /**
  2. * 添加动态(菜单)路由
  3. * @param {*} menuList 菜单列表
  4. * @param {*} routes 递归创建的动态(菜单)路由
  5. */
  6. function addDynamicRoutes (menuList = [], routes = []) {
  7. var temp = []
  8. for (var i = 0; i < menuList.length; i++) {
  9. if (menuList[i].children && menuList[i].children.length >= 1) {
  10. temp = temp.concat(menuList[i].children)
  11. } else if (menuList[i].url && /\S/.test(menuList[i].url)) {
  12. menuList[i].url = menuList[i].url.replace(/^\//, '')
  13. // 创建路由配置
  14. var route = {
  15. path: menuList[i].url,
  16. component: null,
  17. name: menuList[i].name,
  18. meta: {
  19. menuId: menuList[i].menuId,
  20. title: menuList[i].name,
  21. isDynamic: true,
  22. isTab: true,
  23. iframeUrl: ''
  24. }
  25. }
  26. // url以http[s]://开头, 通过iframe展示
  27. if (isURL(menuList[i].url)) {
  28. route['path'] = menuList[i].url
  29. route['name'] = menuList[i].name
  30. route['meta']['iframeUrl'] = menuList[i].url
  31. } else {
  32. try {
  33. // 根据菜单URL动态加载vue组件,这里要求vue组件须按照url路径存储
  34. // 如url="sys/user",则组件路径应是"@/views/sys/user.vue",否则组件加载不到
  35. let array = menuList[i].url.split('/')
  36. let url = array[0].substring(0,1).toUpperCase()+array[0].substring(1) + '/' + array[1].substring(0,1).toUpperCase()+array[1] .substring(1)
  37. route['component'] = resolve => require([@/views/${url}], resolve)
  38. } catch (e) {}
  39. }
  40. routes.push(route)
  41. }
  42. }
  43. if (temp.length >= 1) {
  44. addDynamicRoutes(temp, routes)
  45. } else {
  46. console.log(routes)
  47. }
  48. return routes
  49. }

当然,别忘了把要用到的几个东西引入进来,把导航菜单栏的代码清理一下。

测试效果

启动完成,进入主页,点击用户管理,点击刷新按钮。

刷新后,菜单收起来了,然而页面还是正确的停留在用户管理页面。妈妈再也不用担心我会刷新了!

保存加载状态

现在每次路由跳转前都会重新获取菜单数据生成菜单和路由,及时页面没有刷新也会重复获取,这样很影响性能。我们改良一下,加载成功之后把状态保存到store,每次加载之前先检查store的加载状态,这样就可以避免在非页面刷新的情形下还频发重复的加载了。

在 store 中添加菜单路由加载状态,避免页面未刷新而重复加载。

修改路由配置,在加载之前判断加载状态,只有未加载的情况下才加载,并在加载之后保存加载状态。

求解一个问题

在路由跳转的时候,路由好像是在原路径基础上叠加路由路径跳转的。

如路径在 http://localhost:8090/#/sys/dept 的时候,点击用户管理。

代码对应 this.$router.push(‘’sys/user),路由就赚到了 http://localhost:8090/#/sys/sys/user。

比正确路由多了一个 sys,目前还不到为什么。

目前我是在实际跳转之前,先跳回主页面然后在做真正的跳转。

这样问题可以解决,但无端端多了一步跳转总归不好,求解中。。。

Vue + Element UI 实现权限管理系统(动态加载菜单)的更多相关文章

  1. Vue + Element UI 实现权限管理系统

    Vue + Element UI 实现权限管理系统 前端篇(一):搭建开发环境 https://www.cnblogs.com/xifengxiaoma/p/9533018.html

  2. Vue + Element UI 实现权限管理系统 前端篇(十):动态加载菜单

    动态加载菜单 之前我们的导航树都是写死在页面里的,而实际应用中是需要从后台服务器获取菜单数据之后动态生成的. 我们在这里就用上一篇准备好的数据格式Mock出模拟数据,然后动态生成我们的导航菜单. 接口 ...

  3. Vue + Element UI 实现权限管理系统 前端篇(十五):嵌套外部网页

    嵌套外部网页 在有些时候,我们需要在我们的内容栏主区域显示外部网页.如查看服务端提供的SQL监控页面,接口文档页面等. 这个时候就要求我们的导航菜单能够解析嵌套网页的URL,并根据URL路由到相应的嵌 ...

  4. Vue + Element UI 实现权限管理系统 前端篇(十三):页面权限控制

    权限控制方案 既然是后台权限管理系统,当然少不了权限控制啦,至于权限控制,前端方面当然就是对页面资源的访问和操作控制啦. 前端资源权限主要又分为两个部分,即导航菜单的查看权限和页面增删改操作按钮的操作 ...

  5. Vue + Element UI 实现权限管理系统 前端篇(十二):用户管理模块

    用户管理模块 添加接口 在 http/moduls/user.js 中添加用户管理相关接口. import axios from '../axios' / 用户管理模块 */ // 保存 exp ...

  6. Vue + Element UI 实现权限管理系统(更换皮肤主题)

    自定义主题 命令行主题工具 1.安装主题工具 首先安装「主题生成工具」,可以全局安装或者安装在当前项目下,推荐安装在项目里,方便别人 clone 项目时能直接安装依赖并启动. yarn add ele ...

  7. Vue + Element UI 实现权限管理系统 前端篇(六):更换皮肤主题

    自定义主题 命令行主题工具 1.安装主题工具 首先安装「主题生成工具」,可以全局安装或者安装在当前项目下,推荐安装在项目里,方便别人 clone 项目时能直接安装依赖并启动. yarn add ele ...

  8. Vue + Element UI 实现权限管理系统 前端篇(十四):菜单功能实现

    菜单功能实现 菜单接口封装 菜单管理是一个对菜单树结构的增删改查操作. 提供一个菜单查询接口,查询整颗菜单树形结构. http/modules/menu.js 添加 findMenuTree 接口. ...

  9. Vue + Element UI 实现权限管理系统 前端篇(十六):系统备份还原

    系统备份还原 在很多时候,我们需要系统数据进行备份还原.我们这里就使用MySql的备份还原命令实现系统备份还原的功能. 后台接口准备 系统备份还原是对数据库的备份还原,所以必须有后台接口的支持,我们准 ...

随机推荐

  1. python内置模块(3)

    博主所有python文章均为python3.5.1环境. 目录: 1.collections 2.subprocess 3.configparser 4.XML 5.shutil 一. collect ...

  2. AIX 下某些日志定时清空

    最近发现weblogic proxy日志一直增加,达到18G把tmp空间撑满,导致系统无法访问,故设定时任务先拷贝后5000行日志做备份后清空proxy日志. vi wl_proxyclear.sh ...

  3. shell脚本 -d 是目录文件,那么-e,-f分别是什么?还有"! -e"这又是什么意思呢?

    -e filename 如果 filename存在,则为真-d filename 如果 filename为目录,则为真 -f filename 如果 filename为常规文件,则为真-L filen ...

  4. Linux 如何设置只允许域名访问站点而禁止IP访问站点

    最近在论坛里看到有人问到 Linux 如何设置只允许域名访问站点而禁止IP访问站点的问题,之前自己也用过这个功能,可以防止别人用 IP 地址来访问到自己的网站,下面我就我自己的环境给出解决方法,我用的 ...

  5. Dynamics CRM 2013 停用和激活按钮的显示与隐藏

    CRM中命令栏上的有些按钮是可以通过权限控制显示和隐藏的,比如新建.保存.保存并关闭.删除等,但惟独激活和停用无法控制,但我们还是可以用权限去控制,只是稍微绕了那么一下. 这里就要涉及到按钮的自定义了 ...

  6. Vim-latex 插件 的安装

    ref:https://www.jianshu.com/p/ddd825064062 Vim-latex 插件 1. 安装 Vim-latex 插件是一个强大的Latex插件, 它的安装方法是: 将下 ...

  7. 细数Linux的文件权限

    普通权限 普通权限使用ls -l查看,最前面显示的即是,如: # ls -l .txt -rw-r--r-- 1 root root 8338 7月 19 20:27 1.txt 权限介绍: -/d/ ...

  8. [20171120]理解v$session的state字段(11G).txt

    [20171120]理解v$session的state字段(11G).txt --//https://blogs.oracle.com/database4cn/vsession-%e4%bd%a0%e ...

  9. HDU 1875:畅通工程再续(最小生成树)

    畅通工程再续 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Sub ...

  10. 剑指offer十五之反转链表

    一.题目 输入一个链表,反转链表后,输出链表的所有元素. 二.思路 详细分析见代码注释 三.代码 public class Solution {     public ListNode Reverse ...


Original url: Access
Created at: 2020-03-05 19:30:01
Category: default
Tags: none

请先后发表评论
  • 最新评论
  • 总共0条评论