【树形权限】树形列表权限互斥选择、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);
    },
    
相关推荐
hackeroink16 分钟前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者2 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-2 小时前
验证码机制
前端·后端
燃先生._.3 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖4 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235244 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240255 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar5 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人6 小时前
前端知识补充—CSS
前端·css