当前位置:   article > 正文

新手前台 使用springboot2+vue-element-admin4.0 搭建后台管理系统 实现后台交互动态权限_springboot + elementui + vue2 admin

springboot + elementui + vue2 admin

前言:最近不是很忙,所以抽时间学了一下使用vue-element-admin4.0结合springboot2构建一个后台管理页面。

vue-element-admin4.0国内节点访问地址:vue-element-admin

本此使用的是GitHub - PanJiaChen/vue-element-admin at i18n 国际化分支的版本。说是除了国际化其他都一样。

本文主要介绍前台动态的使用资源权限。

  • 后台使用springboot2搭建项目就不说了,百度一下一大堆。
  • 前台开发前需要安装一下nodejs,这里注意一下nodejs和npm的版本要相互匹配,否则在编译的时候会报错。

(为什么要说笨版本问题呢?是因为我最开始安装的是nodejs11.06的版本,npm是安装的时候自带安装的,npm版本好像6.5。我手贱使用更新软件把nodejs更新了,更新到了12.13,问题来了,打开ide项目无法启动,报错。那是因为更新nodejs的时候没有同步更新到相匹配的npm版本。哎。。。。。。。最后把npm更新到最新版。版本是6.12.1)

他们使用的是vscode开发的,我觉得不太好用,我用的是webstorm

打开项目后需要安装一下依赖的模块。

npm install 

  • 1、全局请求配置的修改。

src/utils/request.js

  1. import axios from 'axios'
  2. // import { MessageBox, Message } from 'element-ui'
  3. import { MessageBox, Message } from 'element-ui'
  4. import store from '@/store'
  5. import { getToken, removeToken } from '@/utils/auth'
  6. const qs = require('querystring')
  7. // create an axios instance
  8. const service = axios.create({
  9. baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  10. // withCredentials: true, // send cookies when cross-domain requests
  11. timeout: 5000, // request timeout
  12. jsonData: false
  13. })
  14. // request interceptor
  15. service.interceptors.request.use(
  16. config => {
  17. if (store.getters.token) {
  18. // let each request carry token
  19. // ['X-Token'] is a custom headers key
  20. // please modify it according to the actual situation
  21. config.headers['X-Token'] = getToken()
  22. }
  23. if (config.method.toLowerCase() === 'get') {
  24. config.params = config.data
  25. } else if (config.method.toLowerCase() === 'post') {
  26. if (config.jsonData) {
  27. config.headers['Content-Type'] = 'application/json;charset=UTF-8'
  28. config.data = JSON.stringify(config.data)
  29. } else {
  30. config.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'
  31. config.data = qs.stringify(config.data)
  32. }
  33. }
  34. // console.log(config) // for debug
  35. return config
  36. },
  37. error => {
  38. // do something with request error
  39. console.log(error) // for debug
  40. return Promise.reject(error)
  41. }
  42. )
  43. // response interceptor
  44. service.interceptors.response.use(
  45. /**
  46. * If you want to get http information such as headers or status
  47. * Please return response => response
  48. */
  49. /**
  50. * Determine the request status by custom code
  51. * Here is just an example
  52. * You can also judge the status by HTTP Status Code
  53. */
  54. response => {
  55. const res = response.data
  56. // code==2000是业务逻辑成功,其他的返回code都是业务逻辑错误
  57. if (res.code === 5002) {
  58. // to re-login
  59. // token过期或者密码被修改了都需要重新获取token
  60. MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
  61. confirmButtonText: 'Re-Login',
  62. cancelButtonText: 'Cancel',
  63. type: 'warning'
  64. }).then(() => {
  65. store.dispatch('user/resetToken').then(() => {
  66. location.reload()
  67. })
  68. })
  69. } else {
  70. return res
  71. }
  72. },
  73. error => {
  74. // console.log('err' + error) // for debug
  75. Message({
  76. message: error.message,
  77. type: 'error',
  78. duration: 5 * 1000
  79. })
  80. removeToken()
  81. if (error.response && error.response.status === 403) {
  82. this.$router.push(`/login?redirect=${this.$route.fullPath}`)
  83. }
  84. return Promise.reject(error)
  85. }
  86. )
  87. export default service

这个文件修改的主要是get、post请求传递参数的设置。

get使用params传递参数。post就data

axios默认请求是json传递参数,但是我的项目中一些简单参数我还是希望是亿form的形式传递过去。后台接受参数时候比较简单。

所以我添加了一个jsonData的标识在请求方法中,根据这个标识来判断传递的是json还是普通的form数据。

还有就是form数据需要转化,使用qs.stringify来转换。转化成A=1&B=2&c=3这样的形式。

  • 2、后台接口路径的配置。

 我是本地开发环境,所以直接配置的是:VUE_APP_BASE_API = 'http://localhost:8088'

  • 登录设置角色信息、过滤路由(根据角色动态的生成菜单)
  • store/user.js
  1. import { loginByPwd, logout, getLoginUserInfo } from '@/api/user'
  2. import { getToken, setToken, removeToken } from '@/utils/auth'
  3. import router, { resetRouter } from '@/router'
  4. const state = {
  5. token: getToken(),
  6. name: '',
  7. avatar: '',
  8. introduction: '',
  9. roles: []
  10. }
  11. const mutations = {
  12. SET_TOKEN: (state, token) => {
  13. state.token = token
  14. },
  15. SET_INTRODUCTION: (state, introduction) => {
  16. state.introduction = introduction
  17. },
  18. SET_NAME: (state, name) => {
  19. state.name = name
  20. },
  21. SET_AVATAR: (state, avatar) => {
  22. state.avatar = avatar
  23. },
  24. SET_ROLES: (state, roles) => {
  25. state.roles = roles
  26. }
  27. }
  28. const actions = {
  29. // user login
  30. loginByPwd({ commit }, userInfo) {
  31. const { userName, passWord } = userInfo
  32. return new Promise((resolve, reject) => {
  33. loginByPwd({ userName: userName.trim(), passWord: passWord }).then(response => {
  34. if (response.code === 2000) {
  35. commit('SET_TOKEN', response.data)
  36. setToken(response.data)
  37. }
  38. resolve(response)
  39. }).catch(error => {
  40. reject(error)
  41. })
  42. })
  43. },
  44. // get user info
  45. getLoginUserInfo({ commit, state }) {
  46. return new Promise((resolve, reject) => {
  47. getLoginUserInfo(state.token).then(response => {
  48. // const { data } = response
  49. // console.log('getLoginUserInfo', response)
  50. if (!response) {
  51. reject('Verification failed, please Login again.')
  52. }
  53. if (response.resultFlag) {
  54. commit('SET_ROLES', response.data.roleList)
  55. commit('SET_NAME', response.data.likeName)
  56. commit('SET_AVATAR', response.data.imgUrl)
  57. commit('SET_INTRODUCTION', '我是一个超级管理员哦')
  58. // 一个用户可能有多个角色,这里返回的是角色的集合信息
  59. // let allRole = response.data.roleList
  60. resolve(response)
  61. } else {
  62. console.error('获取当前登录用户信息出错了')
  63. }
  64. }).catch(error => {
  65. reject(error)
  66. })
  67. })
  68. },
  69. // user logout
  70. logout({ commit, state }) {
  71. return new Promise((resolve, reject) => {
  72. logout(state.token).then(() => {
  73. commit('SET_TOKEN', '')
  74. commit('SET_ROLES', [])
  75. removeToken()
  76. resetRouter()
  77. resolve()
  78. location.reload()
  79. }).catch(error => {
  80. reject(error)
  81. })
  82. })
  83. },
  84. // remove token
  85. resetToken({ commit }) {
  86. return new Promise(resolve => {
  87. commit('SET_TOKEN', '')
  88. commit('SET_ROLES', [])
  89. removeToken()
  90. resolve()
  91. })
  92. },
  93. // dynamically modify permissions
  94. changeRoles({ commit, dispatch }, role) {
  95. return new Promise(async resolve => {
  96. const token = role + '-token'
  97. commit('SET_TOKEN', token)
  98. setToken(token)
  99. const { roles } = await dispatch('getInfo')
  100. resetRouter()
  101. // generate accessible routes map based on roles
  102. const accessRoutes = await dispatch('permission/generateRoutes', roles, { root: true })
  103. // dynamically add accessible routes
  104. router.addRoutes(accessRoutes)
  105. // reset visited views and cached views
  106. dispatch('tagsView/delAllViews', null, { root: true })
  107. resolve()
  108. })
  109. }
  110. }
  111. export default {
  112. // namespaced: true,
  113. state,
  114. mutations,
  115. actions
  116. }

这个文件就是登录,成功后获取当前用户拥有的资源信息。

系统中或者说网络上现在都是直接使用roles来做的判断,就是在router.js文件中直接把菜单需要的角色设置在里边。

缺点是添加了角色就要修改代码打包发布。这简直是灾难。

response.data.roleList 就是用户拥有的角色信息,我这里还是多角色。把角色信息放在全局缓存里边。
  • src/permission.js 

这个文件在打开每个菜单的时候都会去执行以下,检查你的权限信息

检查的时候,把java后台返回的角色资源合并了一下,因为我使用的是多角色,拥有的菜单可能有重复的,这个是最烦人的,因为拥有的资源菜单是多维数组,在判断的时候需要和本地前台的系统路由进行相互判断。

  • src/store/modules/permission.js

可以看到我用系统提供的深度克隆把系统的动态路由克隆了一份,为什么要这样呢,因为我们不能改变系统本地的动态路由,如果你有权限新建角色,如果你改变了系统的路由,那你建的角色岂不是很多菜单都没办法选择。所以我深度克隆了一个全局的动态路由来操作,也不会改变原来的路由。

import { asyncRoutes, constantRoutes } from '@/router'

asyncRoutes是系统中定义的动态路由,需要做权限验证在左边菜单栏显示的都在这个对象里边设置

看到系统默认使用roles来做权限验证的,但是没办法和后台互动起来啊。

重点来了:

我花了很久才搞定这里,这是我目前想到的办法。

  1. /**
  2. *
  3. * @param {Array} userRouters 后台返回的用户权限json
  4. * @param {Array} allRouter 前端配置好的所有动态路由的集合
  5. * @return {Array} realRoutes 过滤后的路由
  6. */
  7. function recursionRouter(userRouters = [], allRouter = []) {
  8. const realRoutes = []
  9. allRouter.forEach(sysRoute => {
  10. if (sysRoute.meta) {
  11. if (sysRoute.flag === undefined) {
  12. sysRoute.flag = true
  13. }
  14. userRouters.forEach(item => {
  15. if (item.meta && item.meta.title === sysRoute.meta.title) {
  16. if (item.children && item.children.length > 0) {
  17. sysRoute.children.concat(recursionRouter(item.children, sysRoute.children))
  18. }
  19. if (sysRoute.flag) {
  20. realRoutes.push(sysRoute)
  21. sysRoute.flag = false
  22. }
  23. }
  24. })
  25. if (sysRoute.children) {
  26. sysRoute.children = sysRoute.children.filter(a => a.flag === false)
  27. }
  28. } else {
  29. realRoutes.push(sysRoute)
  30. }
  31. })
  32. return realRoutes
  33. }

就是把克隆的路由和后台返回的路由信息进行比对,最终得到一个本登录用户拥有的资源信息数组,多维数组哦。。。

这是我目前写的代码,js好坑的 return true都没办法退出函数拿来判断。

以上代码就是本文的精华。

如果有哪位有更简洁的更有效的方法,请回复,非常感谢。

还漏了一个系统添加角色:

还是系统中的添加角色方法。

这里把选择的资源的component属性删除了,太大了,而且没有必要保存在数据库中。

超级管理员添加到数据库中的资源信息:

[{"path":"/permission","redirect":"/permission/page","alwaysShow":true,"name":"Permission","meta":{"title":"permission","icon":"lock","roles":["admin","editor"]},"children":[{"path":"page","name":"PagePermission","meta":{"title":"pagePermission","roles":["admin"]}},{"path":"directive","name":"DirectivePermission","meta":{"title":"directivePermission"}},{"path":"role","name":"RolePermission","meta":{"title":"rolePermission","roles":["admin"]}}],"flag":true},{"path":"/icon","children":[{"path":"/icon/index","name":"Icons","meta":{"title":"icons","icon":"icon","noCache":true}}],"flag":true},{"path":"/components","redirect":"noRedirect","name":"ComponentDemo","meta":{"title":"components","icon":"component"},"children":[{"path":"tinymce","name":"TinymceDemo","meta":{"title":"tinymce"}},{"path":"markdown","name":"MarkdownDemo","meta":{"title":"markdown"}},{"path":"json-editor","name":"JsonEditorDemo","meta":{"title":"jsonEditor"}},{"path":"split-pane","name":"SplitpaneDemo","meta":{"title":"splitPane"}},{"path":"avatar-upload","name":"AvatarUploadDemo","meta":{"title":"avatarUpload"}},{"path":"dropzone","name":"DropzoneDemo","meta":{"title":"dropzone"}},{"path":"sticky","name":"StickyDemo","meta":{"title":"sticky"}},{"path":"count-to","name":"CountToDemo","meta":{"title":"countTo"}},{"path":"mixin","name":"ComponentMixinDemo","meta":{"title":"componentMixin"}},{"path":"back-to-top","name":"BackToTopDemo","meta":{"title":"backToTop"}},{"path":"drag-dialog","name":"DragDialogDemo","meta":{"title":"dragDialog"}},{"path":"drag-select","name":"DragSelectDemo","meta":{"title":"dragSelect"}},{"path":"dnd-list","name":"DndListDemo","meta":{"title":"dndList"}},{"path":"drag-kanban","name":"DragKanbanDemo","meta":{"title":"dragKanban"}}],"flag":true},{"path":"/charts","redirect":"noRedirect","name":"Charts","meta":{"title":"charts","icon":"chart"},"children":[{"path":"keyboard","name":"KeyboardChart","meta":{"title":"keyboardChart","noCache":true}},{"path":"line","name":"LineChart","meta":{"title":"lineChart","noCache":true}},{"path":"mix-chart","name":"MixChart","meta":{"title":"mixChart","noCache":true}}],"flag":true},{"path":"/nested","redirect":"/nested/menu1/menu1-1","name":"Nested","meta":{"title":"nested","icon":"nested"},"children":[{"path":"menu1","name":"Menu1","meta":{"title":"menu1"},"redirect":"/nested/menu1/menu1-1","children":[{"path":"menu1-1","name":"Menu1-1","meta":{"title":"menu1-1"}},{"path":"menu1-2","name":"Menu1-2","redirect":"/nested/menu1/menu1-2/menu1-2-1","meta":{"title":"menu1-2"},"children":[{"path":"menu1-2-1","name":"Menu1-2-1","meta":{"title":"menu1-2-1"}},{"path":"menu1-2-2","name":"Menu1-2-2","meta":{"title":"menu1-2-2"}}]},{"path":"menu1-3","name":"Menu1-3","meta":{"title":"menu1-3"}}]},{"path":"menu2","name":"Menu2","meta":{"title":"menu2"}}],"flag":true},{"path":"/table","redirect":"/table/complex-table","name":"Table","meta":{"title":"Table","icon":"table"},"children":[{"path":"dynamic-table","name":"DynamicTable","meta":{"title":"dynamicTable"}},{"path":"drag-table","name":"DragTable","meta":{"title":"dragTable"}},{"path":"inline-edit-table","name":"InlineEditTable","meta":{"title":"inlineEditTable"}},{"path":"complex-table","name":"ComplexTable","meta":{"title":"complexTable"}}],"flag":true},{"path":"/example","redirect":"/example/list","name":"Example","meta":{"title":"example","icon":"example"},"children":[{"path":"create","name":"CreateArticle","meta":{"title":"createArticle","icon":"edit"}},{"path":"list","name":"ArticleList","meta":{"title":"articleList","icon":"list"}}],"flag":true},{"path":"/tab","children":[{"path":"/tab/index","name":"Tab","meta":{"title":"tab","icon":"tab"}}],"flag":true},{"path":"/error","redirect":"noRedirect","name":"ErrorPages","meta":{"title":"errorPages","icon":"404"},"children":[{"path":"401","name":"Page401","meta":{"title":"page401","noCache":true}},{"path":"404","name":"Page404","meta":{"title":"page404","noCache":true}}],"flag":true},{"path":"/error-log","children":[{"path":"/error-log/log","name":"ErrorLog","meta":{"title":"errorLog","icon":"bug"}}],"flag":true},{"path":"/excel","redirect":"/excel/export-excel","name":"Excel","meta":{"title":"excel","icon":"excel"},"children":[{"path":"export-excel","name":"ExportExcel","meta":{"title":"exportExcel"}},{"path":"export-selected-excel","name":"SelectExcel","meta":{"title":"selectExcel"}},{"path":"export-merge-header","name":"MergeHeader","meta":{"title":"mergeHeader"}},{"path":"upload-excel","name":"UploadExcel","meta":{"title":"uploadExcel"}}],"flag":true},{"path":"/zip","redirect":"/zip/download","alwaysShow":true,"name":"Zip","meta":{"title":"zip","icon":"zip"},"children":[{"path":"/zip/download","name":"ExportZip","meta":{"title":"exportZip"}}],"flag":true},{"path":"/pdf","redirect":"/pdf/index","children":[{"path":"/pdf/index","name":"PDF","meta":{"title":"pdf","icon":"pdf"}}],"flag":true},{"path":"/theme","children":[{"path":"/theme/index","name":"Theme","meta":{"title":"theme","icon":"theme"}}],"flag":true},{"path":"/clipboard","children":[{"path":"/clipboard/index","name":"ClipboardDemo","meta":{"title":"clipboardDemo","icon":"clipboard"}}],"flag":true},{"path":"/i18n","children":[{"path":"/i18n/index","name":"I18n","meta":{"title":"i18n","icon":"international"}}],"flag":true},{"path":"external-link","children":[{"path":"/external-link/https:/github.com/PanJiaChen/vue-element-admin","meta":{"title":"externalLink","icon":"link"}}],"flag":true}]

可以格式化json看一下结构,除了component 其他都一样

  • 注意点 
  • 退出后换个用户登录,资源菜单有时候没有刷新,使用系统中的logout还是有问题。所以我在退出的时候加了强制刷新。

======================2020年1月9日18:07:00==============================

请求的jsonflag在传递的时候做了下修改,放在headers里边传递过去

相应的在判断里边也修改一下  

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号