用最新的element是可以实现树形数据的展示,但是没有复选框效果,用2.72以前的版本的话,是根本没有展开树形数据的效果,也没有复选框效果,
需求:在2.72以下的老版本上做一个树形展示的效果,并且还要有复选框
分享下从查找资料到解决问题的心酸之路,如果着急要效果代码的,请直接看最后一步!!!
需求效果图:
调研之路:
element 2.72以上的版本效果与入口:点我
如下所示: 只有树形的效果,如果想要复选框效果,点我进入
element 2.5.4的版本效果与入口:点我
注意:只能支持一个展开行类似的效果,没有复选框
解决具体步骤:
1、思路:前提 只有展开行的效果
1、我们要给他加一个复选框的效果
2、我们要在复选框加一个icon 图标来控制他的展开与关闭
3、我们既要实现效果,有不能展示它默认的这个展示开关(因为他每一行都有,不满足树形的要求,我们只要求有子级的才可以有开关)
2、添加复选框的效果
因为是自定义的,所以我们使用element的header插槽,保证只有一个,这个复选框是总复选框,然后再是每一行的复选框, 用 checks 字段来控制他的复选框是否勾选,这个字段是我们自己加在数据里的。
<el-table-column width="80">
<template slot="header" slot-scope="scope">
<el-checkbox
v-model="allCheckout"
style="padding-left: 10px"
@change="checkAllFun()"
/>
</template>
<template slot-scope="scope">
<div style="position: relative">
<el-checkbox
v-model="scope.row.checks"
style="padding-left: 10px"
@change="checkChange(scope.row)"
/>
</div>
</template>
</el-table-column>
总全选的事件操作:点击总全选,所有的数据都跟着改变
data:
allCheckout:false
methods:
// 总全选点击事件
checkAllFun() {
this.tableData.forEach(items => {
this.$set(items, "checks", this.allCheckout);
if (items.children) {
items.children.forEach((item) => {
this.$set(item, "checks", this.allCheckout);
});
}
})
},
单个复选框的点击事件
单个复选框的点击逻辑相对多分为三种:
1、一级复选框无子级的点击
2、一级复选框有子级的点击
3、二级复选框无子级的点击
以及他们三个点击后,对总复选框的影响
//插槽复选框逻辑
checkChange(row) {
// expend 父级复选框
if (row.children) {
if (row.checks) {
row.children.map((item) => {
this.$set(item, "checks", true);
});
this.$set(row, "checks", true);
} else {
row.children.map((item) => {
this.$set(item, "checks", false);
});
this.$set(row, "checks", false);
}
}else{
// expend 子级复选框
if(row.checks == undefined) row.checks = false
this.$set(row, "checks", row.checks)
const findTypeArr = this.tableData.filter(v=>v.id == row.id)
let jb_type = ''
if(findTypeArr.length>0){
jb_type = '1'
}else{
jb_type = '2'
}
// 一级的操作
if(jb_type == '1' && !row.checks){
this.allCheckout = false
}
if(jb_type == '1' && row.checks){
this.allCheckSetFun()
}
// 二级操作
if(jb_type == '2' && !row.checks){
this.allCheckout = false
this.tableData.forEach(one => {
if (one.children && one.children.length > 0) {
one.children.forEach(two => {
if (two.id == row.id) {
this.$set(one, "checks", false)
}
})
}
})
}
if(jb_type == '2' && row.checks){
this.tableData.forEach(one => {
if (one.children && one.children.length > 0) {
one.children.forEach(two => {
if (two.id == row.id) {
let twoSelArr = one.children.filter(v=>v.checks)
if(twoSelArr.length == one.children.length){
this.$set(one, "checks", true)
}else{
this.$set(one, "checks", false)
}
}
})
}
})
}
}
// 刷新外部总全选状态
this.allCheckSetFun()
// 刷新
this.uploadTable = false
this.$nextTick(()=>{
this.uploadTable = true
})
},
// 修改外部总勾选
allCheckSetFun(){
let oneSelArr = this.tableData.filter(v=>v.checks)
if(oneSelArr.length == this.tableData.length){
this.allCheckout = true
}else{
this.allCheckout = false
}
this.getNowSelDataTwoType()
},
3、我们要添加,点击自定义的icon来控制本身的展开与关闭事件
1)添加icon图标
这里只涉及子级的复选框,然后定义 expend 字段来表示是否打开
<template slot-scope="scope">
<div style="position: relative">
<el-checkbox
v-model="scope.row.checks"
style="padding-left: 10px"
@change="checkChange(scope.row)"
/>
<span class="expendIcon" v-if="scope.row.children && scope.row.children.length>0" @click="expendClick(scope.row,scope.row.expend)">
<i class="el-icon-arrow-right" v-if="!scope.row.expend"></i>
<i class="el-icon-arrow-down" v-if="scope.row.expend"></i>
</span>
</div>
</template>
2)点击图标,展开或者关闭当前行的展开行
data里面需要添加两个属性,第一个是唯一字段,第二个是数组,数组内部是展开的id信息
row-key="id"
:expand-row-keys="expandRowArr"然后点击事件里面,动态修改他的 expandRowArr
data
expandRowArr:[],//展开内容
methods:
// 是否展开
expendClick(row,title){
if(!title){
// 打开
this.expandRowArr.push(row.id)
this.tableData.forEach(items => {
if(items.id == row.id){
if(items.expend == undefined) items.expend = false
this.$set(items, "expend", true);
}
})
}else{
// 关闭
let oldArr = this.expandRowArr
let newArr = []
oldArr.forEach(item=>{
if(item != row.id){
newArr.push(item)
}
})
this.expandRowArr = newArr
this.tableData.forEach(items => {
// this.$refs.multipleTable.data.forEach(items => {
if(items.id == row.id){
this.$set(items, "expend", false);
}
})
}
},
3) 功能实现以后,我们要隐藏掉他的原本的展开与关闭开关
<el-table-column type="expand" width="1">
<template slot-scope="props">
//展开后的内容...
</template>
</el-table-column>
4、到目前位置,功能都有了,在我们封装的内容里面增加支持插槽与render函数
<template v-for="(column, index) in columns">
<!-- slot 添加自定义配置项 -->
<slot v-if="column.slot" :name="column.slot" :tit='index'></slot>
<!-- 默认渲染列-渲染每一列的汉字 -->
<el-table-column
v-else
:prop="column.prop"
:label="column.title"
:align="column.align"
:width="column.width"
:show-overflow-tooltip="true">
<template slot-scope="scope">
<!--正常渲染-->
<template v-if="!column.render">
<template v-if="column.formatter">
<span v-html="column.formatter(scope.row, column)"></span>
</template>
<template v-else>
<span>{{scope.row[column.prop]}}</span>
</template>
</template>
<!--render函数-->
<template v-else>
<expandDom :column="column" :row="scope.row" :render="column.render" :index="index"></expandDom>
</template>
</template>
</el-table-column>
</template>
components: {
/**
* render函数渲染组件
* */
expandDom: {
functional: true,
props: {
row: Object,
render: Function,
index: Number,
column: {
type: Object,
default: null
}
},
render: (h, ctx) => {
const params = {
row: ctx.props.row,
index: ctx.props.index
}
if (ctx.props.column) params.column = ctx.props.column
return ctx.props.render(h, params)
}
}
},
源码分享:
注意:展开内容请根据自己的需要进行改动,我这里是私有功能
引入代码:
<checkTable ref="checkTable" :columns="productColumns" :tableData="productList" >
<el-table-column slot="col_slot_1" label="商品列表" width="180">
<template slot-scope="scope">
<img :src="scope.row.goods_image" class="goods-image" />
</template>
</el-table-column>
</checkTable>
data:
productColumns:{
slot:'col_slot_1'
}
productList:[{
id:1,
name:'张三',
children:[{ //有该字段的有子级
id:1,
name:'张三儿子',
}]
}]
封装代码:checkTable.vue
<template>
<el-table
v-if="uploadTable"
class="multipleTable"
ref="multipleTable"
border
row-key="id"
:expand-row-keys="expandRowArr"
:data="tableData"
>
<el-table-column width="80">
<template slot="header" slot-scope="scope">
<el-checkbox
v-model="allCheckout"
style="padding-left: 10px"
@change="checkAllFun()"
/>
</template>
<template slot-scope="scope">
<div style="position: relative">
<el-checkbox
v-model="scope.row.checks"
style="padding-left: 10px"
@change="checkChange(scope.row)"
/>
<span class="expendIcon" v-if="scope.row.children && scope.row.children.length>0" @click="expendClick(scope.row,scope.row.expend)">
<i class="el-icon-arrow-right" v-if="!scope.row.expend"></i>
<i class="el-icon-arrow-down" v-if="scope.row.expend"></i>
</span>
</div>
</template>
</el-table-column>
<el-table-column type="expand" width="1">
<template slot-scope="props">
//展开的内容展示
...
</template>
</el-table-column>
<template v-for="(column, index) in columns">
<!-- slot 添加自定义配置项 -->
<slot v-if="column.slot" :name="column.slot" :tit='index'></slot>
<!-- 默认渲染列-渲染每一列的汉字 -->
<el-table-column
v-else
:prop="column.prop"
:label="column.title"
:align="column.align"
:width="column.width"
:show-overflow-tooltip="true">
<template slot-scope="scope">
<!--正常渲染-->
<template v-if="!column.render">
<template v-if="column.formatter">
<span v-html="column.formatter(scope.row, column)"></span>
</template>
<template v-else>
<span>{{scope.row[column.prop]}}</span>
</template>
</template>
<!--render函数-->
<template v-else>
<expandDom :column="column" :row="scope.row" :render="column.render" :index="index"></expandDom>
</template>
</template>
</el-table-column>
</template>
</el-table>
</template>
<script>
/**
* @封装的复选框加列表expend展开功能列表
* @createTime 2023.6
* @auth ry
* */
export default {
name: "checkTable.vue",
props:{
// 表格数据
tableData:{
type: Array,
deep:true,
immediate:true,
default:()=>[]
},
//表头
columns: {
type:Array,
default:()=>[]
},
tableClass: {
type: String,
default: 'checkTable'
},
ifChangeTrue:{
type:String | Number,
}
},
watch:{
tableData(arr){
this.tableData.map(item=>{
if(item.children && item.children.length>0) item.expend = false
return item
})
}
},
components: {
/**
* render函数渲染组件
* */
expandDom: {
functional: true,
props: {
row: Object,
render: Function,
index: Number,
column: {
type: Object,
default: null
}
},
render: (h, ctx) => {
const params = {
row: ctx.props.row,
index: ctx.props.index
}
if (ctx.props.column) params.column = ctx.props.column
return ctx.props.render(h, params)
}
}
},
data(){
return{
allCheckout: false, //全选
expandRowArr:[],//展开内容
uploadTable:true,
}
},
mounted(){
this.clearCheckFun()
},
methods:{
// 获取当前选中的数据,有子级的,子级+父级
getNowSelData(){
let selData = []
this.tableData.forEach(item=>{
// 一级无子级
if(item.checks && !item.children){
selData.push(item)
}
// 一级有子级
if(item.checks && item.children && item.children.length){
selData.push(item)
selData = selData.concat(item.children)
}
// 二级无子级
if(!item.checks && item.children && item.children.length){
item.children.forEach(two=>{
if(two.checks){
selData.push(two)
}
})
}
})
return selData
},
// 获取当前选中的数据-有子级的,父级(内部有children)
getNowSelDataOneType(){
let selData = []
this.tableData.forEach(item=> {
// 一级无子级
if(item.checks && !item.children){
selData.push(item)
}
// 一级有子级
if(item.checks && item.children && item.children.length){
selData.push(item)
}
// 二级无子级
if(!item.checks && item.children && item.children.length){
item.children.forEach(two=>{
if(two.checks){
selData.push(two)
}
})
}
})
return selData
},
// 获取当前选中的数据-有子级是子级,没有则是他自己
getNowSelDataTwoType(){
let selData = []
this.tableData.forEach(item=> {
// 一级无子级
if(item.checks && !item.children){
selData.push(item)
}
// 一级有子级
if(item.checks && item.children && item.children.length){
selData = selData.concat(item.children)
}
// 二级无子级
if(!item.checks && item.children && item.children.length){
item.children.forEach(two=>{
if(two.checks){
selData.push(two)
}
})
}
})
this.$emit('changeShopNum',selData)
return selData
},
// 清理选中效果
clearCheckFun(){
setTimeout(()=>{
if(this.ifChangeTrue=='1'){
this.allCheckout = true
}else{
this.allCheckout = false
}
this.checkAllFun()
},500)
},
// 总全选点击事件
checkAllFun() {
this.tableData.forEach(items => {
this.$set(items, "checks", this.allCheckout);
if (items.children) {
items.children.forEach((item) => {
this.$set(item, "checks", this.allCheckout);
});
}
})
this.getNowSelDataTwoType()
},
//插槽复选框逻辑
checkChange(row) {
// expend 父级复选框
if (row.children) {
if (row.checks) {
row.children.map((item) => {
this.$set(item, "checks", true);
});
this.$set(row, "checks", true);
} else {
row.children.map((item) => {
this.$set(item, "checks", false);
});
this.$set(row, "checks", false);
}
}else{
// expend 子级复选框
if(row.checks == undefined) row.checks = false
this.$set(row, "checks", row.checks)
const findTypeArr = this.tableData.filter(v=>v.id == row.id)
let jb_type = ''
if(findTypeArr.length>0){
jb_type = '1'
}else{
jb_type = '2'
}
// 一级的操作
if(jb_type == '1' && !row.checks){
this.allCheckout = false
}
if(jb_type == '1' && row.checks){
this.allCheckSetFun()
}
// 二级操作
if(jb_type == '2' && !row.checks){
this.allCheckout = false
this.tableData.forEach(one => {
if (one.children && one.children.length > 0) {
one.children.forEach(two => {
if (two.id == row.id) {
this.$set(one, "checks", false)
}
})
}
})
}
if(jb_type == '2' && row.checks){
this.tableData.forEach(one => {
if (one.children && one.children.length > 0) {
one.children.forEach(two => {
if (two.id == row.id) {
let twoSelArr = one.children.filter(v=>v.checks)
if(twoSelArr.length == one.children.length){
this.$set(one, "checks", true)
}else{
this.$set(one, "checks", false)
}
}
})
}
})
}
}
// 刷新外部总全选状态
this.allCheckSetFun()
// 刷新
this.uploadTable = false
this.$nextTick(()=>{
this.uploadTable = true
})
},
// 修改外部总勾选
allCheckSetFun(){
let oneSelArr = this.tableData.filter(v=>v.checks)
if(oneSelArr.length == this.tableData.length){
this.allCheckout = true
}else{
this.allCheckout = false
}
this.getNowSelDataTwoType()
},
// 是否展开
expendClick(row,title){
if(!title){
// 打开
this.expandRowArr.push(row.id)
this.tableData.forEach(items => {
if(items.id == row.id){
if(items.expend == undefined) items.expend = false
this.$set(items, "expend", true);
}
})
}else{
// 关闭
let oldArr = this.expandRowArr
let newArr = []
oldArr.forEach(item=>{
if(item != row.id){
newArr.push(item)
}
})
this.expandRowArr = newArr
this.tableData.forEach(items => {
// this.$refs.multipleTable.data.forEach(items => {
if(items.id == row.id){
this.$set(items, "expend", false);
}
})
}
},
}
}
</script>
<style scoped>
.expendIcon{
position: absolute;
right:10px;
cursor: pointer;
margin-left:10px;
}
.expandUl{
display: flex;
padding:10px 0 10px 30px;
}
.expandUlSon{
width: 95%;
display: flex;
justify-content: space-around;
}
.expandUlSonDiv{
min-width: 100px;
margin-left:20px;
}
</style>