当前位置:   article > 正文

vue3 Element-plus 列表状,权限配置_vue3 tree权限

vue3 tree权限

1、前言

常见的权限配置是树状的也就是这样的

现在要做的是列表状的也就是这样的

 这两种展现形式比较

1、树状的展现形式,相对比较简单、只需要使用element-plus或其他组件库,将后端返回的数据处理成树状数据即可,代码简单,但是如果权限模块较多,就很直观

2、相对的列表状的权限配置,权限就相对直观,一目了然,但是相对的,代码数据处理复杂度会翻倍(当然也有可能是因为我用的方法问题)

2、代码

2.1树状权限的主要代码--(如果只想看列表权限配置,可直接跳过)

  1. <template>
  2. <Dialog
  3. v-model="state.visible"
  4. title="权限配置"
  5. width="400px"
  6. :max-height="500"
  7. @close="handleClose(false)"
  8. @open="handleOpen"
  9. >
  10. <!-- 表单内容 -->
  11. <!-- default-checked-keys 带有的节点 -->
  12. <el-tree
  13. ref="treeRef"
  14. :data="state.data"
  15. show-checkbox
  16. node-key="id"
  17. default-expand-all
  18. :default-checked-keys="state.checkKeys"
  19. :props="defaultProps"
  20. />
  21. <template #footer>
  22. <el-button @click="state.visible = false"> 取消 </el-button>
  23. <el-button type="primary" @click="handleSubmit"> 确定 </el-button>
  24. </template>
  25. </Dialog>
  26. </template>
  27. <script lang="ts" setup>
  28. import { ref, reactive, computed } from 'vue'
  29. import {
  30. getAllPermission,
  31. getPermissionUnderRole,
  32. addPermissionToRole,
  33. } from '../../../api'
  34. import { Permission } from '../../../types'
  35. const defaultProps = {
  36. children: 'nodeList',
  37. label: 'name',
  38. }
  39. const props = defineProps<{
  40. visible: boolean
  41. roleId: number
  42. }>()
  43. const emits = defineEmits<{
  44. (e: 'update:visible', data: boolean)
  45. (e: 'getList')
  46. }>()
  47. const treeRef = ref(null)
  48. const state = reactive({
  49. visible: computed({
  50. get: () => props.visible,
  51. set(value: boolean) {
  52. emits('update:visible', value)
  53. },
  54. }),
  55. data: [] as Permission[],
  56. checkKeys: [],
  57. })
  58. const handleSubmit = async () => {
  59. const res = await addPermissionToRole({
  60. roleId: props.roleId,
  61. resourceIds: [
  62. ...treeRef.value.getCheckedKeys(),
  63. ...treeRef.value.getHalfCheckedKeys(),
  64. ],
  65. })
  66. if (res.code === '00000000') {
  67. // 触发父组件重新请求列表数据,没有触发, 所说义会触发bug
  68. console.log('出发父类请求列表方法')
  69. emits('getList')
  70. }
  71. state.visible = false
  72. }
  73. // 感觉请求数据的时机还是有点问题
  74. const handleOpen = async () => {
  75. // 获取当前角色拥有的权限
  76. const res2 = await getPermissionUnderRole({
  77. roleId: props.roleId,
  78. })
  79. if (res2.code === '00000000') {
  80. // 转成树状数据
  81. let tmp = transToTree(res2.data as Permission[], 0)
  82. let tmpArr = []
  83. console.log('tmp', tmp)
  84. tmp.forEach((item) => {
  85. item.nodeList.forEach((item2) => {
  86. if (item2.nodeList.length === 0) {
  87. tmpArr.push(item2.id)
  88. }
  89. tmpArr.push(...item2.nodeList.map((item) => item.id))
  90. })
  91. })
  92. console.log('tmpArr', tmpArr)
  93. // 以上这都拿主要是 保存当前角色拥有的权限id、拿最后一级的
  94. // 就是没有nodeList数组或者数组长度为0的
  95. state.checkKeys = tmpArr
  96. }
  97. // 这个api是获取所有可选权限
  98. const res = await getAllPermission({ roleId: props.roleId })
  99. if (res.code === '00000000') {
  100. state.data = res.data as Permission[]
  101. }
  102. }
  103. // 递归处理数据---这就是主要难点之一
  104. function transToTree(data: Permission[], id: number) {
  105. const arr = []
  106. data.forEach((item) => {
  107. if (item.parentId === id) {
  108. const nodeList = transToTree(data, item.id)
  109. if (nodeList) {
  110. item.nodeList = nodeList
  111. }
  112. arr.push(item)
  113. }
  114. })
  115. return arr
  116. }
  117. const handleClose = (val: boolean) => {
  118. // state.checkKeys = []
  119. state.visible = val
  120. }
  121. </script>
  122. <style scoped></style>

2.2、列表权限主要代码

  1. <template>
  2. <div class="roles-setting">
  3. <el-breadcrumb>
  4. <el-breadcrumb-item>{{ state.name }}-权限配置</el-breadcrumb-item>
  5. </el-breadcrumb>
  6. <div class="setting-box">
  7. <div class="header">
  8. <el-checkbox
  9. v-model="state2.allCheck.selected"
  10. :indeterminate="state2.allCheck.indeterminate"
  11. label="全选"
  12. />
  13. </div>
  14. <div class="content">
  15. <!-- 每个模块的权限 -->
  16. <div v-for="item in state.data" :key="item.id" class="permission-table">
  17. <div class="title">{{ item.name }}</div>
  18. <div class="table-content">
  19. <!-- 每一行的权限 -->
  20. <div v-for="item2 in item.nodeList" :key="item2.id" class="row">
  21. <div class="three-menu">
  22. <el-checkbox
  23. v-model="item2.selected"
  24. :indeterminate="item2.indeterminate"
  25. :label="item2.name"
  26. @change="handleCheckAllChange(item2.id)"
  27. />
  28. </div>
  29. <div class="btn-permission">
  30. <el-checkbox
  31. v-for="item3 in item2.nodeList"
  32. :key="item3.id"
  33. v-model="item3.selected"
  34. :label="item3.id"
  35. @change="handleChange(item3.id)"
  36. >{{ item3.name }}
  37. </el-checkbox>
  38. </div>
  39. </div>
  40. </div>
  41. </div>
  42. </div>
  43. <div class="footer">
  44. <el-button @click="handleCancel">取消</el-button>
  45. <el-button type="primary" @click="handleSubmit">确认</el-button>
  46. </div>
  47. </div>
  48. </div>
  49. </template>
  50. <script setup lang="ts">
  51. import { reactive, onBeforeMount, computed, watch } from 'vue'
  52. import { ElMessage } from 'element-plus'
  53. import { useRoute } from 'vue-router'
  54. // 引入的接口
  55. import { getAllPermission, addPermissionToRole } from '../../../api'
  56. // 数据类型
  57. import { Permission } from '../../../types'
  58. const route = useRoute()
  59. const state = reactive({
  60. data: [] as Permission[],
  61. // 因为这是新页面,但是又不在左侧菜单(一般后台的左侧菜单栏)中的,所以f5刷新的时候会
  62. // roleId及name置空,所说义需要持久保存一下
  63. name: route.params.name || window.localStorage.getItem('permission-name'),
  64. roleId:
  65. route.params.roleId || window.localStorage.getItem('permission-roleId'),
  66. resourceIds: [], // 保存所有勾选id值,用于发请求
  67. checked: false,
  68. })
  69. // 控制最上面全选按钮状态变更
  70. const state2 = reactive({
  71. allCheck: {
  72. selected: computed({
  73. get() {
  74. return controlAllCkeckStatus(state.data, 'all')
  75. },
  76. set(val: boolean) {
  77. controlAllCkeckStatus(state.data, 'set', val)
  78. },
  79. }),
  80. indeterminate: computed(() => controlAllCkeckStatus(state.data, 'half')),
  81. },
  82. })
  83. // 控制三级菜单的选择状态
  84. const handleCheckAllChange = (id: number) => {
  85. // 传id进来,进而控制其nodeList 每项的选择
  86. const findData = findByIdInTreeData(state.data, id)
  87. // 去除中间态
  88. findData.indeterminate = false
  89. // findData.name = '测试名字'
  90. // 通过findData 可以直接更改state.data的数据 相当于是将
  91. // findData = state.data[i].nodeList[j] 浅拷贝会关联数据
  92. // 循环遍历其下的子级状态
  93. for (let i = 0; i < findData.nodeList.length; i++) {
  94. findData.nodeList[i].selected = findData.selected
  95. }
  96. }
  97. // 每个按钮权限的状态
  98. const handleChange = (id: number) => {
  99. const parentId = findByIdInTreeData(state.data, id).parentId
  100. const findData = findByIdInTreeData(state.data, parentId)
  101. // 全选
  102. if (findData.nodeList.every((item) => item.selected)) {
  103. findData.selected = true
  104. findData.indeterminate = false
  105. } else if (findData.nodeList.some((item) => item.selected)) {
  106. // 中间态
  107. findData.selected = false
  108. findData.indeterminate = true
  109. } else {
  110. // 全不选
  111. findData.selected = false
  112. findData.indeterminate = false
  113. }
  114. }
  115. const getList = async () => {
  116. // 获取所有可选的权限展示出来
  117. const res = await getAllPermission({ roleId: state.roleId })
  118. if (res.code === '00000000') {
  119. state.data = res.data as Permission[]
  120. // 循环模块模块(重置权限的状态)--因为后端返回的数据不完全符合我们的需求
  121. // 第一层--模块数据
  122. for (let i = 0; i < state.data.length; i++) {
  123. // 第二层--每行数据
  124. for (let j = 0; j < state.data[i].nodeList.length; j++) {
  125. state.data[i].nodeList[j].indeterminate =
  126. state.data[i].nodeList[j].nodeList.some((item) => item.selected) &&
  127. !state.data[i].nodeList[j].nodeList.every((item) => item.selected)
  128. if (state.data[i].nodeList[j].indeterminate) {
  129. state.data[i].nodeList[j].selected = false
  130. }
  131. }
  132. }
  133. }
  134. }
  135. onBeforeMount(async () => {
  136. // 有值就保存到本地, 避免刷新的时候数据丢失
  137. if (route.params.roleId && route.params.name) {
  138. window.localStorage.setItem(
  139. 'permission-roleId',
  140. route.params.roleId as string
  141. )
  142. window.localStorage.setItem('permission-name', route.params.name as string)
  143. }
  144. await getList()
  145. })
  146. // 取消就重新请求原来的数据
  147. const handleCancel = async () => {
  148. await getList()
  149. }
  150. const handleSubmit = async () => {
  151. // 将selected为true或indeterminate为true的id取出来放到一起
  152. getCheckIdFromTreeData(state.data, state.resourceIds)
  153. const res = await addPermissionToRole({
  154. roleId: state.roleId,
  155. resourceIds: state.resourceIds,
  156. })
  157. if (res.code === '00000000') {
  158. ElMessage.success('给角色配置权限成功')
  159. }
  160. }
  161. // 根据id查找树状数据里面的某一项
  162. function findByIdInTreeData(treeData: Permission[], id: number): Permission {
  163. for (let i = 0; i < treeData.length; i++) {
  164. if (treeData[i].id === id) {
  165. return treeData[i]
  166. } else {
  167. const findData = findByIdInTreeData(treeData[i].nodeList, id)
  168. // 找到才返回,没找到就继续找
  169. if (findData) {
  170. return findData
  171. }
  172. }
  173. }
  174. }
  175. // 将勾选的数据的id保存到指定数组
  176. function getCheckIdFromTreeData(
  177. treeData: Permission[],
  178. resultBuf: number[]
  179. ): void {
  180. treeData.forEach((item) => {
  181. if (item.selected || item.indeterminate) {
  182. resultBuf.push(item.id)
  183. }
  184. if (item.nodeList.length > 0) {
  185. getCheckIdFromTreeData(item.nodeList, resultBuf)
  186. }
  187. })
  188. }
  189. // 控制全选按钮的状态(全选、中间态、全不选、)
  190. // flag: all--判断是否全选、half判断是否是中间态、set设置权限
  191. function controlAllCkeckStatus(
  192. treeData: Permission[],
  193. flag: string,
  194. val?: boolean
  195. ): boolean {
  196. let buf = []
  197. if (flag === 'half') {
  198. for (let i = 0; i < treeData.length; i++) {
  199. buf[i] =
  200. (treeData[i].nodeList.some((item) => item.selected) &&
  201. !treeData[i].nodeList.every((item) => item.selected)) ||
  202. treeData[i].nodeList.some((item) => item.indeterminate)
  203. }
  204. console.log('buf', buf)
  205. return buf.some((item) => item)
  206. } else if (flag === 'all') {
  207. for (let i = 0; i < treeData.length; i++) {
  208. buf[i] = treeData[i].nodeList.every((item) => item.selected)
  209. }
  210. return buf.every((item) => item)
  211. } else {
  212. for (let i = 0; i < treeData.length; i++) {
  213. for (let j = 0; j < treeData[i].nodeList.length; j++) {
  214. treeData[i].nodeList[j].selected = val
  215. treeData[i].nodeList[j].indeterminate = false
  216. handleCheckAllChange(treeData[i].nodeList[j].id)
  217. }
  218. }
  219. }
  220. }
  221. </script>
  222. <style scoped lang="scss">
  223. .roles-setting {
  224. width: 100%;
  225. height: 100%;
  226. .setting-box {
  227. position: relative;
  228. height: 100%;
  229. margin-top: 10px;
  230. .header {
  231. display: flex;
  232. align-items: center;
  233. height: 40px;
  234. width: 100%;
  235. box-sizing: border-box;
  236. .el-checkbox {
  237. margin-left: 10px;
  238. }
  239. border-bottom: solid 1px #dcdfe6;
  240. }
  241. .content {
  242. width: 100%;
  243. height: calc(100% - 90px);
  244. box-sizing: border-box;
  245. padding: 10px;
  246. .permission-table {
  247. margin-bottom: 30px;
  248. .title {
  249. margin-bottom: 5px;
  250. }
  251. .table-content {
  252. background: #ededf0;
  253. border: solid 1px #dcdfe6;
  254. width: 100%;
  255. .row {
  256. display: flex;
  257. // min-height: 40px;
  258. align-items: center;
  259. border-bottom: solid 1px #dcdfe6;
  260. &:last-child {
  261. border-bottom: none;
  262. }
  263. .three-menu {
  264. min-width: 150px;
  265. box-sizing: border-box;
  266. padding-left: 10px;
  267. border-right: solid 1px #dcdfe6;
  268. }
  269. .btn-permission {
  270. box-sizing: border-box;
  271. padding-left: 10px;
  272. .el-checkbox {
  273. min-width: 120px;
  274. }
  275. }
  276. }
  277. }
  278. }
  279. }
  280. .footer {
  281. border-top: solid 1px #dcdfe6;
  282. height: 50px;
  283. box-sizing: border-box;
  284. width: 100%;
  285. position: absolute;
  286. bottom: 0;
  287. display: flex;
  288. justify-content: center;
  289. align-items: center;
  290. }
  291. }
  292. }
  293. </style>

列表状权限配置的数据结构

  1. "data": [
  2. {
  3. "id": 1,
  4. "parentId": 0,
  5. "name": "营销管理",
  6. "selected": true,
  7. "nodeList": [
  8. {
  9. "id": 3,
  10. "parentId": 1,
  11. "name": "优惠券管理",
  12. "selected": true,
  13. "nodeList": [
  14. {
  15. "id": 11,
  16. "parentId": 3,
  17. "name": "查看",
  18. "selected": true,
  19. "nodeList": []
  20. },
  21. {
  22. "id": 12,
  23. "parentId": 3,
  24. "name": "新增优惠券",
  25. "selected": true,
  26. "nodeList": []
  27. },
  28. {
  29. "id": 13,
  30. "parentId": 3,
  31. "name": "开启优惠券状态",
  32. "selected": true,
  33. "nodeList": []
  34. },
  35. {
  36. "id": 14,
  37. "parentId": 3,
  38. "name": "停用优惠券状态",
  39. "selected": true,
  40. "nodeList": []
  41. }
  42. ]
  43. },
  44. {
  45. "id": 4,
  46. "parentId": 1,
  47. "name": "发放管理",
  48. "selected": true,
  49. "nodeList": [
  50. {
  51. "id": 15,
  52. "parentId": 4,
  53. "name": "查看",
  54. "selected": true,
  55. "nodeList": []
  56. },
  57. {
  58. "id": 16,
  59. "parentId": 4,
  60. "name": "新增发放",
  61. "selected": true,
  62. "nodeList": []
  63. },
  64. {
  65. "id": 17,
  66. "parentId": 4,
  67. "name": "开启发放状态",
  68. "selected": true,
  69. "nodeList": []
  70. },
  71. {
  72. "id": 18,
  73. "parentId": 4,
  74. "name": "停用发放状态",
  75. "selected": true,
  76. "nodeList": []
  77. }
  78. ]
  79. },
  80. {
  81. "id": 5,
  82. "parentId": 1,
  83. "name": "领取记录",
  84. "selected": false,
  85. "nodeList": [
  86. {
  87. "id": 19,
  88. "parentId": 5,
  89. "name": "查看",
  90. "selected": false,
  91. "nodeList": []
  92. },
  93. {
  94. "id": 20,
  95. "parentId": 5,
  96. "name": "导出",
  97. "selected": false,
  98. "nodeList": []
  99. }
  100. ]
  101. },
  102. {
  103. "id": 6,
  104. "parentId": 1,
  105. "name": "ATM壁纸",
  106. "selected": false,
  107. "nodeList": [
  108. {
  109. "id": 21,
  110. "parentId": 6,
  111. "name": "查看",
  112. "selected": false,
  113. "nodeList": []
  114. },
  115. {
  116. "id": 22,
  117. "parentId": 6,
  118. "name": "新增壁纸",
  119. "selected": false,
  120. "nodeList": []
  121. },
  122. {
  123. "id": 23,
  124. "parentId": 6,
  125. "name": "开启/批量开启",
  126. "selected": false,
  127. "nodeList": []
  128. },
  129. {
  130. "id": 24,
  131. "parentId": 6,
  132. "name": "停用/批量停用",
  133. "selected": false,
  134. "nodeList": []
  135. },
  136. {
  137. "id": 25,
  138. "parentId": 6,
  139. "name": "编辑",
  140. "selected": false,
  141. "nodeList": []
  142. },
  143. {
  144. "id": 26,
  145. "parentId": 6,
  146. "name": "删除",
  147. "selected": false,
  148. "nodeList": []
  149. }
  150. ]
  151. }
  152. ]
  153. },
  154. {
  155. "id": 2,
  156. "parentId": 0,
  157. "name": "供应链",
  158. "selected": true,
  159. "nodeList": [
  160. {
  161. "id": 7,
  162. "parentId": 2,
  163. "name": "校区库存",
  164. "selected": true,
  165. "nodeList": [
  166. {
  167. "id": 27,
  168. "parentId": 7,
  169. "name": "查看",
  170. "selected": true,
  171. "nodeList": []
  172. },
  173. {
  174. "id": 28,
  175. "parentId": 7,
  176. "name": "详情",
  177. "selected": true,
  178. "nodeList": []
  179. }
  180. ]
  181. },
  182. {
  183. "id": 8,
  184. "parentId": 2,
  185. "name": "仓库库存",
  186. "selected": false,
  187. "nodeList": [
  188. {
  189. "id": 29,
  190. "parentId": 8,
  191. "name": "查看",
  192. "selected": false,
  193. "nodeList": []
  194. },
  195. {
  196. "id": 30,
  197. "parentId": 8,
  198. "name": "详情",
  199. "selected": false,
  200. "nodeList": []
  201. },
  202. {
  203. "id": 31,
  204. "parentId": 8,
  205. "name": "新增",
  206. "selected": false,
  207. "nodeList": []
  208. },
  209. {
  210. "id": 32,
  211. "parentId": 8,
  212. "name": "修改",
  213. "selected": false,
  214. "nodeList": []
  215. },
  216. {
  217. "id": 33,
  218. "parentId": 8,
  219. "name": "删除",
  220. "selected": false,
  221. "nodeList": []
  222. }
  223. ]
  224. },
  225. {
  226. "id": 9,
  227. "parentId": 2,
  228. "name": "打印机库存",
  229. "selected": false,
  230. "nodeList": []
  231. },
  232. {
  233. "id": 10,
  234. "parentId": 2,
  235. "name": "采购库存",
  236. "selected": false,
  237. "nodeList": []
  238. }
  239. ]
  240. }
  241. ]

个人感觉数据处理还有很多优化的地方,暂时就先这样!! 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/119070
推荐阅读
相关标签
  

闽ICP备14008679号