【树形权限】树形列表权限互斥选择、el-tree设置禁用等等

文章目录

    • 一、实现如上树形列表
      • [1.1 首先要就是渲染树形列表](#1.1 首先要就是渲染树形列表)
      • [1.2 然后通过插槽处理头部标题](#1.2 然后通过插槽处理头部标题)
      • [1.3 再通过插槽处理表格body体内容](#1.3 再通过插槽处理表格body体内容)
      • [1.4 让body体中的选框和表头中的选框产生关联](#1.4 让body体中的选框和表头中的选框产生关联)
    • [二、将 el-tree 整棵树设为禁用状态](#二、将 el-tree 整棵树设为禁用状态)
    • 三、动态表格合并

需求:按照权限管理配置的数据权限树展开;点击查看按钮后进入其他指定机构选择弹窗为一树形结构

本文章对项目中出现得关键点进行总结。

数字化管理平台
Vue3+Vite+VueRouter+Pinia+Axios+ElementPlus
权限系统-商城
个人博客地址

一、实现如上树形列表

在 element 官方表格示例中,实现树形表格列表数据渲染,非常简单。只需要按部就班,替换相关的数据即可。

但是很明显,我们这个项目中的需求,除了树形列表外,还有一些非常规的需求(😓想拿板砖拍死产品🤯的心都有了,有没有想过开发啥感受😶,好不好实现🤢!)。没有办法,还是得硬着头皮上,谁让咱没有话语权,说干就干,着手研究。这个过程一定要跟后端商量好交互的数据格式,不然自己干自己的,你最后会很痛苦(PS:最好有一个靠谱的经验丰富的后端来配合你,能让你节省很多时间,少走很多弯路)。

1.1 首先要就是渲染树形列表

很简单,直接贴代码

c 复制代码
<el-table
      :data="tableData"
      style="width: 100%"
      row-key="id"
      lazy
      :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
    >
      <el-table-column type="index" label="序号"></el-table-column>
      <el-table-column prop="name" label="名称"></el-table-column>
      <el-table-column prop="one_level"></el-table-column>
      <el-table-column prop="two_level"></el-table-column>
      <el-table-column prop="three_level"></el-table-column>
      <el-table-column prop="four_level"> </el-table-column>
      <el-table-column prop="operation" label="其他指定机构"></el-table-column>
</el-table>

1.2 然后通过插槽处理头部标题

这里的数据只需要渲染名称一行,后面全部都是选框,所以就是头部那里也需要重新去搞一下。

el-table 本身是带有 type=checkbox 属性来实现复选功能的,当时就尝试了四个,但是你需要实现互斥关系,并且还需要加一些文字,自带的就多少有点不够用了。

所以这个使用采用插槽 #header 去重写头部,并使用 el-radio 填充,实现单选互斥效果,代码如下:

c 复制代码
<el-table
      :data="tableData"
      style="width: 100%"
      row-key="id"
      lazy
      :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
    >
      <el-table-column type="index" label="序号"></el-table-column>
      <el-table-column prop="name" label="名称"></el-table-column>
      <el-table-column prop="one_level">
        <template #header>
          <div>
            <el-radio v-model="tableHeader.level" label="1" @change="headerChange(1)">一级组织机构内所有数据</el-radio>
          </div>
        </template>
      </el-table-column>
      <el-table-column prop="two_level">
        <template #header>
          <div>
            <el-radio
              v-model="tableHeader.level"
              label="2"
              @change="headerChange(2)"
            >本级组织机构及下属组织机构数据</el-radio>
          </div>
        </template>
      </el-table-column>
      <el-table-column prop="three_level">
        <template #header>
          <div>
            <el-radio v-model="tableHeader.level" label="3" @change="headerChange(3)">本账号及下属组织机构数据</el-radio>
          </div>
        </template>
      </el-table-column>
      <el-table-column prop="four_level">
        <template #header>
          <div>
            <el-radio v-model="tableHeader.level" label="4" @change="headerChange(4)">本账号数据</el-radio>
          </div>
        </template>
      </el-table-column>
      <el-table-column prop="operation" label="其他指定机构"></el-table-column>
</el-table>

1.3 再通过插槽处理表格body体内容

同样,body体通过默认插槽配置对应的内容和选框,这个时候我采用的是el-checkbox,通过样式穿透改其样式让它在页面看起来更加符合产品设计要求。代码如下:

c 复制代码
    <el-table
      :data="tableData"
      style="width: 100%"
      row-key="id"
      lazy
      :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
    >
      <el-table-column type="index" label="序号"></el-table-column>
      <el-table-column prop="name" label="名称"></el-table-column>
      <el-table-column prop="one_level">
        <template #header>
          <div>
            <el-radio v-model="tableHeader.level" label="1" @change="headerChange(1)">一级组织机构内所有数据</el-radio>
          </div>
        </template>
        <template #default="{row, column, $index}">
          <div>
            <el-checkbox
              v-model="tableHeader['one_level'+$index]"
              :checked="checked['one_level'+$index]"
              :label="column.property+$index"
              :disabled="cutIdx !== 1"
              @change="itemChange(column.property+$index, $index)"
            >勾选 {{$index}}~{{row.id}}</el-checkbox>
          </div>
        </template>
      </el-table-column>
      <el-table-column prop="two_level">
        <template #header>
          <div>
            <el-radio
              v-model="tableHeader.level"
              label="2"
              @change="headerChange(2)"
            >本级组织机构及下属组织机构数据</el-radio>
          </div>
        </template>
        <template #default="{row, column, $index}">
          <div>
            <el-checkbox
              v-model="tableHeader['two_level'+$index]"
              :checked="checked['two_level'+$index]"
              :label="column.property+$index"
              :disabled="cutIdx !== 2"
              @change="itemChange(column.property+$index, $index)"
            >勾选 {{$index}}~{{row.id}}</el-checkbox>
          </div>
        </template>
      </el-table-column>
      <el-table-column prop="three_level">
        <template #header>
          <div>
            <el-radio v-model="tableHeader.level" label="3" @change="headerChange(3)">本账号及下属组织机构数据</el-radio>
          </div>
        </template>
        <template #default="{row, column, $index}">
          <div>
            <el-checkbox
              v-model="tableHeader['three_level'+$index]"
              :checked="checked['three_level'+$index]"
              :label="column.property+$index"
              @change="itemChange(column.property+$index, $index)"
              :disabled="cutIdx !== 3"
            >勾选 {{$index}}~{{row.id}}</el-checkbox>
          </div>
        </template>
      </el-table-column>
      <el-table-column prop="four_level">
        <template #header>
          <div>
            <el-radio v-model="tableHeader.level" label="4" @change="headerChange(4)">本账号数据</el-radio>
          </div>
        </template>
        <template #default="{row, column, $index}">
          <div>
            <el-checkbox
              v-model="tableHeader['four_level'+$index]"
              :checked="checked['four_level'+$index]"
              :label="column.property+$index"
              @change="itemChange(column.property+$index, $index)"
              :disabled="cutIdx !== 4"
            >勾选 {{$index}}~{{row.id}}</el-checkbox>
          </div>
        </template>
      </el-table-column>
      <el-table-column prop="operation" label="其他指定机构">
        <template #default>
          <el-button type="success" size="mini">查看</el-button>
        </template>
      </el-table-column>
</el-table>

1.4 让body体中的选框和表头中的选框产生关联

表头互斥,代表这一但选中其中一个表头,只能选择body体中当前列的选框内容,这个时候我根据表头的选择,禁用非当前列。

当然,如果有特殊需求,你可以适当的自己修改一下。

c 复制代码
headerChange(idx) {
      if (this.cutIdx !== idx) {
        this.ids.forEach((_, i) => {
          if (this.checked[this.cutIdx]) {
            this.$nextTick(() => {
              this.$set(
                this.tableHeader,
                i + this.levelMap.get(this.cutIdx),
                false
              )
              this.$set(this.checked, this.levelMap.get(this.cutIdx) + i, false)
            })
          }
        })
        this.cutIdx = idx
        console.log(idx, this.tableHeader, this.levelMap.get(idx), '不同列')
      }
      // 清空所有
      for (const key in this.tableHeader) {
        if (key !== 'level') {
          console.log(key, 123123)
          this.$nextTick(() => {
            this.$set(this.tableHeader, key, false)
            this.$set(this.checked, key, false)
          })
        }
      }
      // 当前下面所有都选中
      this.ids.forEach((_, i) => {
        // console.log(levelMap.get(idx) + i)
        this.$nextTick(() => {
          this.$set(this.tableHeader, this.levelMap.get(idx) + i, true)
          this.$set(this.checked, this.levelMap.get(idx) + i, true)
          console.log(
            this.tableHeader,
            this.checked[this.levelMap.get(idx) + i]
          )
        })
      })
    },
    itemChange(item, idx) {
      console.log(item, idx, this.checked[item])
      // this.tableHeader[idx] = true
      let _this = this
      this.$nextTick(() => {
        if (this.checked[item]) {
          _this.$set(this.checked, item, false)
          for (const key in this.tableHeader) {
            if (key === 'level') {
              this.tableHeader[key] = ''
            }
          }
        } else {
          _this.$set(this.checked, item, true)
        }
        const res = this.ids.every((_, i) => {
          return this.checked[this.levelMap.get(this.cutIdx) + i] === true
        })
        if (res) {
          for (const key in this.tableHeader) {
            if (key === 'level') {
              this.tableHeader[key] = this.cutIdx + ''
              // this.$set(this.tableHeader, key, this.cutIdx + '')
              console.log(key, this.levelMap.get(this.cutIdx), this.tableHeader)
            }
          }
        }
      })
    },
    // 递归获取ids
    getIds(data) {
      data.forEach(v => {
        this.ids.push(v.id)
        if (v.children && v.children.length > 0) {
          this.getIds(v.children)
        }
      })
    }

二、将 el-tree 整棵树设为禁用状态

element 官方给出的示例中,是给部分 tree 节点通过 disabled 属性设置禁用状态。它所写的仅是一段静态代码,如果是后端返回得数据很多,我们想要在查看状态下禁用所有树形节点,显然非常麻烦,需要处理大量的数据。

所以这里通过巧用 props 属性,来实现树形节点的禁用效果。

c 复制代码
<el-tree
    ref="permissionTree"
    :data="permissionTree"
    :show-checkbox="showCheckbox"
     node-key="keyId"
     :props="defaultProps">
</el-tree>
c 复制代码
data() {
	return {
		defaultProps: {
          label: 'labelName',
          children: 'childrenList',
          disabled: this.isDisabled,
		}
	}
},
methods: {
	isDisabled() {
		return this.$route.params.type === 'view' ? true : false;
	}
}

三、动态表格合并

一些项目中,需要使用到 elementUI table 组件的方法 :span-method="objectSpanMethod" 进行单元格的动态合并。查看了官方 API 后,发现简单、直白、明了,无法满足下图业务的需求。所以参考了一些其他的网络资料,具体的思路:在动态处理从后端拿回来的数据的时候,是需要从数据中找到一个唯一的"标识"去判断是否是相同种类的数据。然后根据这个"标识"去做逻辑判断。

  1. 首先需要在 data 中定义需要数据,根据数组中存储的标识去合并数据

    复制代码
    data() {
        return {
          // 合并单元格需要的数据
          spanArr: [], //遍历数据时,根据相同的标识去存储记录
          pos: 0, // 二维数组的索引
        };
      },
  2. 然后在 methods 中定义方法去处理标识(需要合并的数据单元格)

    复制代码
    methods: {
        // 列表数据处理函数
        getSpanArr(data) {
          //页面展示的数据,不一定是全部的数据,所以每次都清空之前存储的 保证遍历的数据是最新的数据。以免造成数据渲染混乱
          this.spanArr = [];
          this.pos = 0;
          //遍历数据
          data.forEach((item, index) => {
            //判断是否是第一项
            if (index === 0) {
              this.spanArr.push(1);
              this.pos = 0;
            } else {
              //不是第一项时,就根据标识去存储
              if (data[index].sdTarget === data[index - 1].sdTarget) {
                // 查找到符合条件的数据时每次要把之前存储的数据+1
                this.spanArr[this.pos] += 1;
                this.spanArr.push(0);
              } else {
                // 没有符合的数据时,要记住当前的index
                this.spanArr.push(1);
                this.pos = index;
              }
            }
          });
          console.log(this.spanArr, this.pos);
        },
    
        // 指定合并的单元格函数
        objectSpanMethod({ row, column, rowIndex, columnIndex }) {
          // 页面列表上 表格合并行 -> 第几列(从0开始)
          // 需要合并多个单元格时 依次增加判断条件即可
          if (columnIndex === 0) {
            // 二维数组存储的数据 取出
            const _row = this.spanArr[rowIndex];
            const _col = _row > 0 ? 1 : 0;
            return {
              rowspan: _row,
              colspan: _col,
            };
            //不可以return {rowspan:0, colspan: 0} 会造成数据不渲染, 也可以不写else,eslint过不了的话就返回false
          } else {
            return false;
          }
        },
     },
  3. 最后在 created 或 mounted 中调用处理函数将异步获取到的数据传递过去进行处理:

    复制代码
    mounted() {
    	this.tableData = .... //异步获取数据
    	this.getSpanArr(this.tableData);
    },
相关推荐
码界奇点2 分钟前
Java Web学习 第1篇前端基石HTML 入门与核心概念解析
java·前端·学习·xhtml
云枫晖8 分钟前
Webpack系列-开发环境
前端·webpack
Rverdoser13 分钟前
制作网站的价格一般由什么组成
前端·git·github
拉不动的猪13 分钟前
深入理解 JavaScript 中的静态属性、原型属性与实例属性
前端·javascript·面试
linda261821 分钟前
链接形式与跳转逻辑总览
前端·javascript
怪可爱的地球人25 分钟前
骨架屏
前端
用户6778471506229 分钟前
前端将html导出为word文件
前端
前端付豪31 分钟前
如何使用 Vuex 设计你的数据流
前端·javascript·vue.js
李雨泽33 分钟前
通过 Prisma 将结构推送到数据库
前端
前端小万38 分钟前
使用 AI 开发一款聊天工具
前端·全栈