文章目录
-
- 一、实现如上树形列表
-
- [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 后,发现简单、直白、明了,无法满足下图业务的需求。所以参考了一些其他的网络资料,具体的思路:在动态处理从后端拿回来的数据的时候,是需要从数据中找到一个唯一的"标识"去判断是否是相同种类的数据。然后根据这个"标识"去做逻辑判断。
-
首先需要在 data 中定义需要数据,根据数组中存储的标识去合并数据
data() { return { // 合并单元格需要的数据 spanArr: [], //遍历数据时,根据相同的标识去存储记录 pos: 0, // 二维数组的索引 }; },
-
然后在 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; } }, },
-
最后在 created 或 mounted 中调用处理函数将异步获取到的数据传递过去进行处理:
mounted() { this.tableData = .... //异步获取数据 this.getSpanArr(this.tableData); },