当前位置:   article > 正文

【Vue3+Ts项目】硅谷甄选 — 菜单权限+按钮权限_vue ts如何写权限控制指令,在菜单配置和按钮上使用权限标识

vue ts如何写权限控制指令,在菜单配置和按钮上使用权限标识

一、菜单权限

1.1 路由拆分 

 将项目路由拆分为:

  • 静态路由:login、404、home、screen
  • 异步路由:权限管理(包含三个子路由)、商品管理(包含四个子路由)
  • 任意路由:任意路由

 src/router/routes.ts

  1. // 对外暴露配置路由(常量路由):全部用户都可以访问到的路由
  2. export const constantRoute = [
  3. {
  4. // 登录
  5. path: '/login',
  6. component: () => import('@/views/login/index.vue'),
  7. name: 'login',
  8. meta: {
  9. title: '登录', // 菜单标题
  10. hidden: true, // 代表路由标题在菜单中是否隐藏 true:隐藏 false:显示
  11. icon: 'Promotion', // 菜单文字左侧的图标,支持element-plus全部图标
  12. },
  13. },
  14. {
  15. // 登录成功以后展示数据的路由
  16. path: '/',
  17. component: () => import('@/layout/index.vue'),
  18. name: 'layout',
  19. meta: {
  20. title: '',
  21. hidden: true,
  22. icon: '',
  23. },
  24. redirect: '/home',
  25. children: [
  26. {
  27. path: '/home',
  28. component: () => import('@/views/home/index.vue'),
  29. name: 'home',
  30. meta: {
  31. title: '首页',
  32. hidden: false,
  33. icon: 'HomeFilled',
  34. },
  35. },
  36. ],
  37. },
  38. {
  39. // 404
  40. path: '/404',
  41. component: () => import('@/views/404/index.vue'),
  42. name: '404',
  43. meta: {
  44. title: '404',
  45. hidden: true,
  46. icon: 'BrushFilled',
  47. },
  48. },
  49. {
  50. path: '/screen',
  51. component: () => import('@/views/screen/index.vue'),
  52. name: 'Screen',
  53. meta: {
  54. title: '数据大屏',
  55. hidden: false,
  56. icon: 'Platform',
  57. },
  58. }
  59. ]
  60. //异步路由
  61. export const asnycRoute = [
  62. {
  63. path: '/acl',
  64. component: () => import('@/layout/index.vue'),
  65. name: 'Acl',
  66. meta: {
  67. title: '权限管理',
  68. icon: 'Lock',
  69. },
  70. redirect: '/acl/user',
  71. children: [
  72. {
  73. path: '/acl/user',
  74. component: () => import('@/views/acl/user/index.vue'),
  75. name: 'User',
  76. meta: {
  77. title: '用户管理',
  78. icon: 'User',
  79. },
  80. },
  81. {
  82. path: '/acl/role',
  83. component: () => import('@/views/acl/role/index.vue'),
  84. name: 'Role',
  85. meta: {
  86. title: '角色管理',
  87. icon: 'UserFilled',
  88. },
  89. },
  90. {
  91. path: '/acl/permission',
  92. component: () => import('@/views/acl/permission/index.vue'),
  93. name: 'Permission',
  94. meta: {
  95. title: '菜单管理',
  96. icon: 'Monitor',
  97. },
  98. },
  99. ],
  100. },
  101. {
  102. path: '/product',
  103. component: () => import('@/layout/index.vue'),
  104. name: 'Product',
  105. meta: {
  106. title: '商品管理',
  107. icon: 'Goods',
  108. },
  109. redirect: '/product/trademark',
  110. children: [
  111. {
  112. path: '/product/trademark',
  113. component: () => import('@/views/product/trademark/index.vue'),
  114. name: 'Trademark',
  115. meta: {
  116. title: '品牌管理',
  117. icon: 'ShoppingCartFull',
  118. },
  119. },
  120. {
  121. path: '/product/attr',
  122. component: () => import('@/views/product/attr/index.vue'),
  123. name: 'Attr',
  124. meta: {
  125. title: '属性管理',
  126. icon: 'ChromeFilled',
  127. },
  128. },
  129. {
  130. path: '/product/spu',
  131. component: () => import('@/views/product/spu/index.vue'),
  132. name: 'Spu',
  133. meta: {
  134. title: 'SPU管理',
  135. icon: 'Calendar',
  136. },
  137. },
  138. {
  139. path: '/product/sku',
  140. component: () => import('@/views/product/sku/index.vue'),
  141. name: 'Sku',
  142. meta: {
  143. title: 'SKU管理',
  144. icon: 'Orange',
  145. },
  146. },
  147. ],
  148. },
  149. ]
  150. //任意路由
  151. export const anyRoute = {
  152. // 任意路由
  153. path: '/:pathMatch(.*)*',
  154. redirect: '/404',
  155. name: 'Any',
  156. meta: {
  157. title: '任意路由',
  158. hidden: true,
  159. icon: 'Wallet',
  160. },
  161. }

1.2  菜单权限业务实现 

 PS:退出登录记得删除添加的路由,防止切换角色后还能访问另一个角色的权限

src/store/modules/user.ts

  1. ......
  2. // 引入路由(常量路由)
  3. import { constantRoute, asnycRoute, anyRoute } from '@/router/routes'
  4. // 引入深拷贝方法
  5. //@ts-expect-error
  6. import cloneDeep from 'lodash/cloneDeep'
  7. import router from '@/router'
  8. // 用于过滤当前用户需要展示的异步路由
  9. function filterAsyncRoute(asnycRoute: any, routes: any) {
  10. return asnycRoute.filter((item: any) => {
  11. if (routes.includes(item.name)) {
  12. if (item.children && item.children.length > 0) {
  13. item.children = filterAsyncRoute(item.children, routes)
  14. }
  15. return true
  16. }
  17. })
  18. }
  19. ......
  20. const useUserStore = defineStore('User', {
  21. // 小仓库存储数据的地方
  22. state: (): UserState => {
  23. return {
  24. ......
  25. menuRoutes: constantRoute, // 仓库存储生成菜单需要数组(路由)
  26. removeRouteCallbackList: []
  27. }
  28. },
  29. // 异步|逻辑的地方
  30. actions: {
  31. ......
  32. // 获取用户信息
  33. async userInfo() {
  34. // 获取用户信息进行存储仓库当中(用户头像、名字)
  35. let result: userInfoResponeData = await reqUserInfo()
  36. // 如果获取信息成功,存储下用户信息
  37. if (result.code === 200) {
  38. this.username = result.data.name
  39. this.avatar = result.data.avatar
  40. // 计算当前用户需要展示的异步路由
  41. const userAsyncRoute = filterAsyncRoute(cloneDeep(asnycRoute),result.data.routes)
  42. // 菜单需要的数据整理完毕
  43. this.menuRoutes = [...constantRoute, ...userAsyncRoute, anyRoute]
  44. // 目前路由器管理的只有常量路由:用户计算完毕异步路由、任意路由动态追加
  45. ;[...userAsyncRoute, anyRoute].forEach((route: any) => {
  46. let removeRoute = router.addRoute(route)
  47. this.removeRouteCallbackList.push(removeRoute)
  48. })
  49. return 'ok'
  50. } else {
  51. return Promise.reject(new Error(result.message))
  52. }
  53. },
  54. // 退出登录
  55. async userLogout() {
  56. let result: any = await reqLogout()
  57. if (result.code === 200) {
  58. // 目前没有mock接口:退出登录接口(通知服务器本地用户唯一标识失败)
  59. this.token = ''
  60. this.username = ''
  61. this.avatar = ''
  62. REMOVE_TOKEN()
  63. // 退出登录时删除登录添加的路由
  64. this.removeRouteCallbackList.forEach((removeRouteCallback: any) => {
  65. removeRouteCallback()
  66. })
  67. return 'ok'
  68. } else {
  69. return Promise.reject(new Error(result.message))
  70. }
  71. },
  72. },
  73. getters: {},
  74. })

刷新的时候是异步路由,有可能获取到用户信息,异步路由还没有加载完毕,出现空白的效果!!

解决方法:在全局前置守卫中,获取用户信息后改成next({...to})

  • next() :直接放行(这种写法会导致刷新产生空白的效果)
  • next({...to}):等待路由加载完毕再放行

src/permission.ts

  1. ......
  2. try {
  3. // 获取用户信息
  4. await useStore.userInfo()
  5. // 放行
  6. //等待路由加载完毕再放行
  7. next({ ...to})
  8. } catch (error) {
  9. }
  10. .......

 二、按钮权限

1.1 按钮权限业务实现 

1.1.1 用户按钮权限信息存储

src/store/modules/user.ts 

  1. ......
  2. state: (): UserState => {
  3. return {
  4. ......
  5. //存储当前用户是否包含某一个按钮
  6. buttons: [],
  7. }
  8. ......
  9. async userInfo() {
  10. ......
  11. // 如果获取信息成功,存储下用户信息
  12. if (result.code === 200) {
  13. ......
  14. this.buttons = result.data.buttons
  15. ......
  16. }

 1.1.2 定义全局自定义指令

src/directive/has.ts 

  1. import pinia from "@/store"
  2. import useUserStore from "@/store/modules/user"
  3. const userStore = useUserStore(pinia)
  4. export const isHasButton = (app: any) => {
  5. // 获取对应的用户仓库
  6. // 全局自定义指令:实现按钮的权限
  7. app.directive('has', {
  8. // 代表使用这个全局指令的DOM|组件挂载完毕的时候会执行一次
  9. mounted(el: any, options: any) {
  10. // 自定义指令右侧的数值:如果在用户信息buttons数组中没有
  11. // 从DOM树上干掉
  12. if (!userStore.buttons.includes(options.value)) {
  13. el.parentNode.removeChild(el)
  14. }
  15. },
  16. })
  17. }

 在main.ts文件中引入自定义指令文件

  1. // 引入自定义指令文件
  2. import { isHasButton } from '@/directive/has'
  3. isHasButton(app)

 1.1.3 使用自定义指令配置按钮权限

PS:此处以其中一个按钮作为例子,项目中其他按钮的权限都需要配置

<el-button type="primary" size="default" icon="Plus" @click="addTrademark" v-has="'btn.Trademark.add'">
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Li_阴宅/article/detail/774098
推荐阅读
相关标签
  

闽ICP备14008679号