最近开发了一个需求,element 树状表格,表格全选时所有项(包括所有子节点)都选中,选中树状表格父节点时,这个父节点下所有子节点也都要选中,如果某个父节点下的所有子节点没有全部,则这个父节点处于半选状态
效果图:
1.HTML
<template>
<el-table
v-loading="loading"
:data="orderList"
@selection-change="handleSelectionChange"
:row-key='rowKeyFunc'
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
:row-class-name="rowClassNameFun"
ref="table"
@select-all="selectAllFun"
@select="selectFun"
>
<el-table-column type="selection" width="55" align="center"/>
<el-table-column label="运营商" align="center" prop="providerType" width="120px"/>
<el-table-column
label="创建时间"
align="center"
prop="gmtCreate"
width="180"
>
</el-table-column>
<el-table-column
label="客户编号"
align="center"
width="200"
prop="customerNo"
/>
<el-table-column
label="系统单号"
align="center"
width="200"
prop="orderNo"
/>
</el-table>
</template>
2.JS
<script>
export default {
name: "index",
data() {
return {
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
// 总条数
total: 0,
// 运单管理表格数据
orderList: [],
// 查询参数
queryParams: {
},
page: {
pageNum: 1,
pageSize: 15,
},
oneProductIsSelect:[],
isFun:false
};
},
created() {
this.getList();
},
methods: {
// 查询列表
getList() {
this.loading = true;
listOrder(this.queryParams, this.page).then((response) => {
this.orderList=response.rows
//这一步是为了将父和子之间有联系,给子定义一个taskId,将子的taskId和父的id一致
this.orderList.forEach((item, index) => {
if (item.children) {
item.children.forEach((cItem, cIndex) => {
cItem.taskId = item.id;
});
}
});
// 由于后端返回的数组id不唯一(父里的id和其中一个子的id一样),然后:row-key='id'里面的id要是唯一值,所以处理了一下,将父的id改变,将数组里面的id都唯一,当然,你可以跟后端商量一下,返回给你一个唯一值,这个处理代码就可以省略了
this.orderList = this.orderList.map((item,index)=>{
return{
...item,
uuid:`${index}-${this.guid()}`
}
})
this.total = response.total; //页数
this.loading = false; // 遮罩层
this.initData(this.orderList)
});
},
//生成唯一ID
guid() {
return Number(
Math.random().toString().substr(3, 3) + Date.now()
).toString(36);
},
//row-key唯一值
rowKeyFunc(row){
if(row.uuid){
return row.uuid
}else {
return row.id
}
},
//初始化数据,将数据都用isSelect标记一下,isSelect为false不选中、true选中、half半选
initData(data) {
data.forEach((item) => {
item.isSelect = false; //默认为不选中
if (item.children && item.children.length) {
this.initData(item.children);
}
});
},
// 判断是不是全选
checkIsAllSelect() {
this.oneProductIsSelect = [];
this.orderList.forEach((item) => {
this.oneProductIsSelect.push(item.isSelect);
});
//判断一级产品是否是全选.如果一级产品全为true,则设置为取消全选,否则全选
let isAllSelect = this.oneProductIsSelect.every((selectStatusItem) => {
return true == selectStatusItem;
});
return isAllSelect;
},
// 全选或者全不选(这个是祖父的勾选)
selectAllFun(selection) {
let isAllSelect = this.checkIsAllSelect();
this.orderList.forEach((item) => {
item.isSelect = isAllSelect;
this.$refs.table.toggleRowSelection(item, !isAllSelect);
this.selectFun(selection, item);
});
},
selectFun(selection, row) {
this.setRowIsSelect(row);
},
setRowIsSelect(row) {
//当点击父级点复选框时,当前的状态可能为未知状态,所以当前行状态设为false并选中,即可实现子级点全选效果
if (row.isSelect == "half") {
row.isSelect = false;
this.$refs.table.toggleRowSelection(row, true);
}
row.isSelect = !row.isSelect;
//判断操作的是子级点复选框还是父级点复选框,如果是父级点,则控制子级点的全选和不全选
if (row.children && row.children.length > 0) {
row.children.forEach((item) => {
item.isSelect = row.isSelect;
this.$refs.table.toggleRowSelection(item, row.isSelect);
});
} else {
//操作的是子节点 1、获取父节点 2、判断子节点选中个数,如果全部选中则父节点设为选中状态,如果都不选中,则为不选中状态,如果部分选择,则设为不明确状态
let parentId = row.taskId;
this.orderList.forEach((item) => {
let isAllSelect = [];
if (item.id == parentId) {
if(item.children){
item.children.forEach((databaseSourceListItem) => {
isAllSelect.push(databaseSourceListItem.isSelect);
});
}
if (
isAllSelect.every((selectItem) => {
return true == selectItem;
})
) {
item.isSelect = true;
this.$refs.table.toggleRowSelection(item, true);
} else if (
isAllSelect.every((selectItem) => {
return false == selectItem;
})
) {
item.isSelect = false;
this.$refs.table.toggleRowSelection(item, false);
} else{
item.isSelect ="half";
}
}
});
}
},
rowClassNameFun({row}){
if(row.isSelect=='half'){
return "indeterminate";
}
},
handleSelectionChange(selection) {
let ids = []
let waybill=[]
selection.forEach(node => {
if(node.isSelect==true){//如果上来勾选了
if(node.children && node.children.length > 0){//判断是否有子元素,如果有子,遍历子
node.children.forEach(child=>{
if(child.isSelect){
let index = ids.findIndex(item =>
item.outOrderNo === child.outOrderNo
&& item.packageNo === child.packageNo
);
if(index === -1){
ids.push({
outOrderNo: child.outOrderNo,
packageNo: child.packageNo
})
}
if (!waybill.includes(child.trackNumber)) {
waybill.push(child.trackNumber);
}
}
})
}else {//如果没有子,
console.log('选中')
let index = ids.findIndex(item =>
item.outOrderNo === node.outOrderNo
&& item.packageNo === node.packageNo
);
if(index===-1){
ids.push({
outOrderNo: node.outOrderNo,
packageNo: node.packageNo
})
}
if (!waybill.includes(node.trackNumber)) {
waybill.push(node.trackNumber);
}
}
}else if(node.isSelect=="half"){//如果是半选,遍历子,将勾选的子进行push
console.log('半选')
if(node.children && node.children.length > 0){
node.children.forEach(child=>{
if(child.isSelect){
}
})
}else {
}
}else {
console.log('取消')
}
})
this.waybill=waybill
this.ids = ids
this.single = this.ids.length !== 1;
this.multiple = !this.ids.length
},
},
};
</script>
这个里面需要有个注意的地方,当父里面有两个子,勾选父,@selection-change="handleSelectionChange"的handleSelectionChange会跑三遍,一遍父、一边父与子、一边子
// 多选框选中数据
handleSelectionChange(selection) {
console.log(selection,'selection')
}
这个里面还是比较混乱的,打印出来三次,这个我是可以理解的,因为勾选了三个,但是打印的这个顺序,我不太懂,由于有个勾选之后,操作批量下载和批量取消,不勾选的时候,是禁止点击批量下载和批量取消的
效果图
原来代码逻辑
// 多选框选中数据
handleSelectionChange(selection) {
console.log(selection,'selection')
this.single = selection.length !== 1;
this.multiple = !selection.length
this.ids = selection.map((item) => item.trackNumber);
},
按理说,这个就能实现以上的功能,但是,没有实现,我进行取消勾选,还是打印了三次,最后一次有值,所以以上代码不能实现此功能
于是我就对上面的代码进行了改进
// 多选框选中数据
handleSelectionChange: _.debounce(function(selection) {
let ids = []
let waybill=[]
selection.forEach(node => {
console.log(node.isSelect,node)
if(node.isSelect==true){//如果上来勾选了
if(node.children && node.children.length > 0){//判断是否有子元素,如果有子,遍历子
node.children.forEach(child=>{
if(child.isSelect){
let index = ids.findIndex(item =>
item.outOrderNo === child.outOrderNo
&& item.packageNo === child.packageNo
);
if(index === -1){
ids.push({
outOrderNo: child.outOrderNo,
packageNo: child.packageNo
})
}
if (!waybill.includes(child.trackNumber)) {
waybill.push(child.trackNumber);
}
}
})
}else {//如果没有子,
let index = ids.findIndex(item =>
item.outOrderNo === node.outOrderNo
&& item.packageNo === node.packageNo
);
if(index===-1){
ids.push({
outOrderNo: node.outOrderNo,
packageNo: node.packageNo
})
}
if (!waybill.includes(node.trackNumber)) {
waybill.push(node.trackNumber);
}
}
}else if(node.isSelect=="half"){//如果是半选,遍历子,将勾选的子进行push
if(node.children && node.children.length > 0){
node.children.forEach(child=>{
if(child.isSelect){
}
})
}else {
}
}else {
}
})
this.waybill=waybill
this.ids = ids
this.single = this.ids.length !== 1;
this.multiple = !this.ids.length
}, 300),
根据上面取到的this.ids、this.waybill,来进行批量取消操作,可根据自己的代码逻辑进行修改
//取消(取消里面改动点:父packageNo为空,子packageNo有值,后端根据packageNo来进行父与子取消区分)
handleCancal(row) {
//操作里面的取消
let trackNumber
//批量取消
let trackNumbers
let outOrderNo
let outOrderNos
if (row.trackNumber) {
trackNumber = row.trackNumber.split(',')
outOrderNo = [{
outOrderNo:row.outOrderNo,
packageNo:row.packageNo,
}]
} else {
trackNumbers = this.waybill
outOrderNos=this.ids
}
//处理单独取消的弹框换行
let arry
let newData = []
for (let index in trackNumber) {
newData.push(trackNumber[index] + "<br/>")
}
arry = newData.join('')
//处理批量取消的弹框换行
let arrys
let newDatas = []
for (let index in trackNumbers) {
newDatas.push(trackNumbers[index] + "<br/>")
}
arrys = newDatas.join('')
let parmas = outOrderNo || outOrderNos
this.$confirm(
this.$t('order.surecan') + "<br/>" + (arry || arrys) + this.$t('order.waybill') + "<br/>" + this.$t('order.notee'),
{
confirmButtonText: this.$t('operation.confirm'),
cancelButtonText: this.$t('operation.cancel'),
type: "warning",
dangerouslyUseHTMLString: true,
})
.then(function () {
return cancalOrder(parmas);
})
.then(() => {
this.getList();
this.msgSuccess(this.$t('msg.application'));
})
.catch(function () {
});
},
3.CSS
半选的样式
<style lang="scss" scoped >
/deep/.indeterminate {
.el-table-column--selection .cell .el-checkbox {
display: block !important;
}
.el-checkbox__input .el-checkbox__inner {
background-color: #4a97eb !important;
border-color: #4a97eb !important;
color: #fff !important;
}
}
/deep/.indeterminate .el-checkbox__input.is-checked .el-checkbox__inner::after {
transform: scale(0.5);
}
/deep/.indeterminate .el-checkbox__input .el-checkbox__inner::after {
border-color: #c0c4cc !important;
background-color: #c0c4cc;
}
/deep/.indeterminate .el-checkbox__input .el-checkbox__inner::after {
content: "";
position: absolute;
display: block;
background-color: #fff;
height: 2px;
transform: scale(0.5);
left: 0;
right: 0;
top: 5px;
width: auto !important;
}
</style>
以上就可以实现树状表格选择父节点子节点全选,子节点不全选父节点半选,但是对于handleSelectionChange,里面处理数据还是很懵的,虽然解决了,但是感觉不是最优的方法,如果有好的解决方法,可以分享下,欢迎分享和指正。