el-table树形表格实现 父子联动勾选,子部分勾选时,父处于半勾选的状态

本文主要是介绍el-table树形表格实现 父子联动勾选,子部分勾选时,父处于半勾选的状态,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

el-table树形表格实现 父子联动勾选,子部分勾选时,父处于半勾选的状态

需求背景

el-table,支持树形表格,但是多选框并没有自动支持父子联动勾选;

  1. 勾选全选,只有最外层的行被勾选;
  2. 勾选父,子级不会自动勾选;
  3. 勾选部分子,父级不会自动处于半勾选状态;
  4. 依次勾选全部的子级,父级不会自动勾选;

具体要求:

  1. 勾选全选按钮,表格所有层级 均被勾选,取消勾选全选按钮,表格所有层级 均被取消勾选;
  2. 父级勾选,子级(以及更深层级的子级)全部勾选,父级取消勾选,子级(以及更深层级的子级)全部取消勾选;
  3. 子级部分勾选 或 处于 半勾选的状态,则父级处于半勾选的状态
  4. 依次勾选全部的子级,父级会自动勾选;

开发分析

1、半勾选状态的实现

当前只有标题头的勾选框有半勾选状态,行上没有,需要手动实现
首先是半勾选的样式,需要从标题头勾选框的半勾选状态的样式复制;当父级处于半勾选状态时,去使用这个类的样式;当行的某一数据标识为true时,勾选框的列 的类名添加indeterminate,否则勾选框的列 的类名为空;
可以使用cell-class-name属性,进行实现< el-table :row-key=“valueKey”>< /el-table>

tableCellClassName ({row, column}) {let cellClassName = ''if (row.indeterminate && column.type === 'selection') {cellClassName = 'indeterminate'}return cellClassName}
<style lang="scss" scoped>
::v-deep .lov_table {.indeterminate {.el-checkbox__input {.el-checkbox__inner {background-color: #5f4efd;border-color: #5f4efd;&::before {content: '';position: absolute;display: block;background-color: #fff;height: 2px;-webkit-transform: scale(.5);transform: scale(.5);left: 0;right: 0;top: 5px;}}}}
}
</style>

2、编辑初始化时,要给保存前的数据自动勾选上

获取到数据后,使用modalTableRef.toggleRowSelection(row, true) 进行勾选
同时给树形结构的数据初始化setTreeMetaData,使得每一个子节点都有parentId,方便后续的联动操作

async getList() {this.loading = truetry {const res = await this.lovRemote.request({...this.remoteParams,...this.queryForm,pageIndex: this.pageIndex,pageSize: this.pageSize})if (res?.data) {const remoteResult: any = this.$attrs.remoteResultconst result = remoteResult ? remoteResult(res) : res.data.list || res.datasetTreeMetaData(result, this.valueKey, 'children')this.list = resultthis.totalRows = res.data.totalthis.$nextTick(() => {this.initSelection()})}} catch (err) {this.$message.error(err.message)} finally {this.loading = false}}initSelection() {const modalTableRef: any = this.$refs.modalTableRefmodalTableRef.clearSelection(); //* 打扫干净屋子再请客if (this.cachedSelection.length) {const checkSelect = (data) => {data.forEach((item) => {this.cachedSelection.forEach((sel) => {if (item[this.valueKey] === sel[this.valueKey]) {if (this.multiple) {modalTableRef.toggleRowSelection(item, true)resetTableTreeSelection({record: item, userSelection: this.cachedSelection, list: this.list, tableRef: modalTableRef, toggleSub: false})} else {modalTableRef.setCurrentRow(item)}}})if (getType(item.children) === 'Array' && item.children.length) {checkSelect(item.children)}})}// 递归选中勾选缓存数据checkSelect(this.list)}}

3、父子联动勾选

使用selection-change事件(当选择项发生变化时会触发该事件)监听的话,只有一个selection参数,并不知道当前勾选的是哪一行,于是使用select(当用户手动勾选数据行的 Checkbox 时触发的事件)和select-all(当用户手动勾选全选 Checkbox 时触发的事件)

/** 勾选全选操作(多选模式) */
selectAllOnClick() {const modalTableRef: any = this.$refs.modalTableRefconst isAllSelected = modalTableRef?.store.states.isAllSelected; //? 是否全部勾选if (this.needRelativeSelect) {this.list.forEach(record => {toggleSubAll({record, toggleRowSelection: modalTableRef.toggleRowSelection, selected: isAllSelected})})const finalSelection = modalTableRef?.store.states.selection;this.cachedSelection = finalSelection;return}
}/** 勾选单条操作(多选模式) */
selectOnClick(selection, row) {const modalTableRef: any = this.$refs.modalTableRefif (this.needRelativeSelect) {resetTableTreeSelection({record: row, userSelection: selection, list: this.list, tableRef: modalTableRef})const finalSelection = modalTableRef?.store.states.selection;this.cachedSelection = finalSelection;return}
}

util.js

/*** todo 对树形结构的数据进行加工,直接子节设置parentValue* @param data 树形结构的数据* @param treeKey 树形结构的id字段* @param childrenKey 树形结构子级的字段* @param parentNode 上级节点*/
const setTreeMetaData = (data, treeKey: string = 'id', childrenKey: string = 'children' ,parentNode?: any) => {data.forEach(item => {if (parentNode) item.parentValue = parentNode[treeKey];if (Array.isArray(item[childrenKey]) && item[childrenKey].length) {item[childrenKey].forEach(child => {child.parentValue = item[treeKey];if (Array.isArray(child[childrenKey]) && child[childrenKey].length) {setTreeMetaData(child[childrenKey], treeKey, childrenKey, child)}});}})
}
/*** todo 获取展开后的树形表格数据 深层 变成 一层* @param treeData 树形结构的数据* @param childrenKey 树形结构子级的字段* @returns 展开后的树形表格数据*/
const getExpandedTreeData = (treeData, childrenKey: string = 'children') => {return treeData.reduce((accumulator, curItem) => {if (Array.isArray(curItem[childrenKey]) && curItem[childrenKey].length) {return accumulator.concat(curItem).concat(getExpandedTreeData(curItem[childrenKey]))}return accumulator.concat(curItem)}, [])
}
/*** todo 将当前行下的所有子级切换勾选状态* @param record 单前行* @param toggleRowSelection 切换行的勾选状态的内置方法* @param childrenKey 树形结构子级的字段* @param selected 统一设置的 勾选的状态*/
const toggleSubAll = ({record, toggleRowSelection, childrenKey = 'children', selected = true}) => {if (Array.isArray(record[childrenKey]) && record[childrenKey].length) { //* 有子级record[childrenKey].forEach(subRecord => {toggleRowSelection(subRecord, selected); //* 调用el-table内置方法,进行勾选if (Array.isArray(subRecord[childrenKey]) && subRecord[childrenKey].length) { //* 子级还有下级toggleSubAll({record: subRecord, toggleRowSelection, childrenKey, selected})}})}
}
/*** todo 设置树形表格父级的勾选状态* @param parentValue 父级的id* @param expandedList 树形表格展开后的数据* @param userSelection 用户勾选的所有数据* @param tableRef 表格的ref* @param treeKey 树形结构的id字段*/
const setTableTreeParentSelection = ({parentValue, expandedList, userSelection, tableRef, treeKey = 'id'}) => {const toggleRowSelection = tableRef.toggleRowSelection;const subList = expandedList.filter(item => item.parentValue === parentValue);const parentRecord = expandedList.find(item => item[treeKey] === parentValue)const selectedList = subList.filter(subRecord => userSelection.some(selectedRecord => selectedRecord[treeKey] === subRecord[treeKey]))const halfSelectedList = subList.filter(subRecord => subRecord.indeterminate === 'Y')parentRecord.indeterminate = undefined;if (subList.length === selectedList.length) { //* 所有子级全部勾选,父级勾选toggleRowSelection(parentRecord, true)} else if (!selectedList.length && !halfSelectedList.length) { //* 所有子级 全部没有勾选也没有半勾选toggleRowSelection(parentRecord, false)} else {//* 子级部分勾选,toggleRowSelection(parentRecord, false)parentRecord.indeterminate = 'Y'}const currentSelection = tableRef?.store.states.selection;if (parentRecord.parentValue) setTableTreeParentSelection({parentValue: parentRecord.parentValue, expandedList, userSelection: currentSelection, tableRef})
}
/*** todo 重置树形表格的勾选状态* @param record 用户勾选的单前行* @param userSelection 用户勾选的所有数据* @param list 当前页面的所有数据(树形结构)* @param childrenKey 树形结构子级的字段* @param tableRef 表格的ref*/
const resetTableTreeSelection = ({record, userSelection, list, tableRef, treeKey = 'id', childrenKey = 'children', toggleSub = true}) => {const toggleRowSelection = tableRef.toggleRowSelection; //? 切换行的勾选状态的内置方法const isSelected = userSelection.some(item => item[treeKey] === record[treeKey]); //* 当前项被勾选record.indeterminate = undefined;toggleSub && toggleSubAll({record, toggleRowSelection, childrenKey, selected: isSelected})const expandedTreeData = getExpandedTreeData(list, childrenKey);if (record.parentValue) setTableTreeParentSelection({parentValue: record.parentValue, expandedList: expandedTreeData, userSelection, tableRef, treeKey})
}

设计缺点

不支持表格勾选状态还没有缓存就再次查询的场景,比如前端手动过滤,点击【查询】按钮进行后端精确查询,翻页等,也就是说,勾选状态没有缓存
这个设计方案只是为了简单的场景,只有新增-编辑-查询,没有查询,分页等复杂的场景;
有查询分页的,请看二次设计的方案 el-table树形表格实现 父子联动勾选 并查询时进行勾选缓存

完整代码

<template><el-dialogclass="lov_modal":width="`${width || '60vw'}`":title="title":visible="visible"@close="closeModal"destroy-on-closeappend-to-body:close-on-click-modal="false"><base-query-form:defaultQuery="remoteParams":fieldProps="queryItems":loading="loading"@query="getQuery"/><el-tableborder:height="500"ref="modalTableRef":data="list"class="lov_table"v-loading="loading":row-key="valueKey"highlight-current-row:cell-class-name="tableCellClassName"@current-change="currentChange"@select="selectOnClick"@select-all="selectAllOnClick"@row-dblclick="rowOnDoubleClick"><el-table-columnv-if="multiple"type="selection"width="55"reserve-selectionalign="center"/><el-table-column:label="colLabel(column)":prop="column.descKey || column.key"show-overflow-tooltipv-for="column in tableColumns":key="column.key":formatter="column.format"v-bind="column"></el-table-column></el-table><el-paginationv-if="!$attrs.remoteResult"class="table_pagination":disabled="loading"@size-change="listSizeOnChange"@current-change="listCurrentOnChange":current-page="pageIndex":page-sizes="[10, 20, 50, 100]":page-size="pageSize"layout="total, sizes, prev, pager, next, jumper":total="totalRows"></el-pagination><span slot="footer" class="dialog-footer"><el-button @click="closeModal">取 消</el-button><el-button type="primary" @click="confirmOnClick">确 定</el-button></span></el-dialog>
</template><script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
import BaseQueryForm from '@/components/BaseQueryForm/index.vue'
import { ColumnProp } from '@/utils/interface'
import * as LOV_CONFIG from './lovModalConfig'
import { getType, isNil } from '@/utils/util'
import { resetTableTreeSelection, setTreeMetaData, toggleSubAll } from '../BaseSearchTable/utils'@Component({components: {BaseQueryForm},name: 'lovModal'
})
export default class extends Vue {@Prop({ required: true }) lovCode: string@Prop() remoteParams: any@Prop() valueKey: string@Prop() cached: any[]@Prop() width: string@Prop({ default: '弹窗' }) title: string@Prop({ default: false }) multiple: boolean; //? 是否开启多选/** 树形数据全部展开标识 */@Prop({ default: false }) expandAllFlag: boolean@Prop({ default: false }) needRelativeSelect: boolean; //? 树形表格,是否需要联动勾选list = []// selection = []cachedSelection = []currentRecord = nulltotalRows: number = 0loading: boolean = falsequeryForm: any = {}pageIndex = 1pageSize = 10visible = falseget lovRemote() {return LOV_CONFIG[this.lovCode].lovRemote}get lovModalColumns(): ColumnProp[] {return LOV_CONFIG[this.lovCode].columns}get colLabel() {return (col) => {return col.i18nKey ? this.$t(`table.${col.i18nKey}`) : col.name}}get tableColumns() {return this.lovModalColumns.filter((item) => item.showInTable)}get queryItems() {return this.lovModalColumns.filter((item) => item.showInQuery)}getQuery(params) {this.queryForm = paramsthis.initList()}initList() {this.pageIndex = 1this.totalRows = 0this.list = []this.getList()}initSelection() {const modalTableRef: any = this.$refs.modalTableRefmodalTableRef.clearSelection(); //* 打扫干净屋子再请客if (this.cachedSelection.length) {const checkSelect = (data) => {data.forEach((item) => {this.cachedSelection.forEach((sel) => {if (item[this.valueKey] === sel[this.valueKey]) {if (this.multiple) {modalTableRef.toggleRowSelection(item, true)resetTableTreeSelection({record: item, userSelection: this.cachedSelection, list: this.list, tableRef: modalTableRef, toggleSub: false})} else {modalTableRef.setCurrentRow(item)}}})if (getType(item.children) === 'Array' && item.children.length) {checkSelect(item.children)}})}// 递归选中勾选缓存数据checkSelect(this.list)}}async getList() {this.loading = truetry {const res = await this.lovRemote.request({...this.remoteParams,...this.queryForm,pageIndex: this.pageIndex,pageSize: this.pageSize})if (res?.data) {const remoteResult: any = this.$attrs.remoteResultconst result = remoteResult ? remoteResult(res) : res.data.list || res.datasetTreeMetaData(result, this.valueKey, 'children')this.list = resultthis.totalRows = res.data.totalthis.$nextTick(() => {this.initSelection()})}} catch (err) {this.$message.error(err.message)} finally {this.loading = false}}listSizeOnChange(val) {this.pageIndex = 1this.pageSize = valthis.$nextTick(() => {this.initList()})}listCurrentOnChange(val) {this.pageIndex = valthis.$nextTick(() => {this.getList()})}toggleRowExpanAll(isExpan) {this.toggleRowExpan(this.list, isExpan)}toggleRowExpan(data, isExpan) {const tree: any = this.$refs.modalTableRefdata.forEach((item) => {tree.toggleRowExpansion(item, isExpan)if (!isNil(item.children) && item.children.length) {this.toggleRowExpan(item.children, isExpan)}})}async showModal() {this.visible = truethis.cachedSelection = JSON.parse(JSON.stringify(this.cached))this.queryForm = { ...this.queryForm, ...this.remoteParams }await this.getList()this.expandAllFlag && this.toggleRowExpanAll(this.expandAllFlag)}closeModal() {this.pageIndex = 1this.pageSize = 10this.totalRows = 0this.visible = falsethis.cachedSelection = []this.queryForm = {}this.currentRecord = null}/** 点击单行操作 */currentChange(val) {this.currentRecord = val}tableCellClassName ({row, column}) {let cellClassName = ''if (row.indeterminate && column.type === 'selection') {cellClassName = 'indeterminate'}return cellClassName}/** 勾选全选操作(多选模式) */selectAllOnClick() {const modalTableRef: any = this.$refs.modalTableRefconst isAllSelected = modalTableRef?.store.states.isAllSelected; //? 是否全部勾选if (this.needRelativeSelect) {this.list.forEach(record => {toggleSubAll({record, toggleRowSelection: modalTableRef.toggleRowSelection, selected: isAllSelected})})const finalSelection = modalTableRef?.store.states.selection;this.cachedSelection = finalSelection;return}}/** 勾选单条操作(多选模式) */selectOnClick(selection, row) {const modalTableRef: any = this.$refs.modalTableRefif (this.needRelativeSelect) {resetTableTreeSelection({record: row, userSelection: selection, list: this.list, tableRef: modalTableRef})const finalSelection = modalTableRef?.store.states.selection;this.cachedSelection = finalSelection;return}}/** 双击行 */rowOnDoubleClick(row) {this.currentChange(row)if (!this.multiple) { //* 单选时,双击行时执行【确认】操作this.$nextTick(() => {this.confirmOnClick()})}}/** 点击确认按钮 */confirmOnClick() {if (this.multiple) {if(this.cachedSelection.length == 0){let message=(this.$t('documentation.pleaseSelect') as any)+this.titlethis.$message({type:'warning',message})}this.$emit('onOk', this.cachedSelection)} else {if(!this.currentRecord) {let message=(this.$t('documentation.pleaseSelect') as any)+this.titlethis.$message({type:'error',message})return}this.$emit('onOk', this.currentRecord)}}
}
</script><style lang="scss" scoped>
::v-deep .lov_table {.indeterminate {.el-checkbox__input {.el-checkbox__inner {background-color: #5f4efd;border-color: #5f4efd;&::before {content: '';position: absolute;display: block;background-color: #fff;height: 2px;-webkit-transform: scale(.5);transform: scale(.5);left: 0;right: 0;top: 5px;}}}}
}
</style>

这篇关于el-table树形表格实现 父子联动勾选,子部分勾选时,父处于半勾选的状态的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/753208

相关文章

pytorch自动求梯度autograd的实现

《pytorch自动求梯度autograd的实现》autograd是一个自动微分引擎,它可以自动计算张量的梯度,本文主要介绍了pytorch自动求梯度autograd的实现,具有一定的参考价值,感兴趣... autograd是pytorch构建神经网络的核心。在 PyTorch 中,结合以下代码例子,当你

SpringBoot集成Milvus实现数据增删改查功能

《SpringBoot集成Milvus实现数据增删改查功能》milvus支持的语言比较多,支持python,Java,Go,node等开发语言,本文主要介绍如何使用Java语言,采用springboo... 目录1、Milvus基本概念2、添加maven依赖3、配置yml文件4、创建MilvusClient

JS+HTML实现在线图片水印添加工具

《JS+HTML实现在线图片水印添加工具》在社交媒体和内容创作日益频繁的今天,如何保护原创内容、展示品牌身份成了一个不得不面对的问题,本文将实现一个完全基于HTML+CSS构建的现代化图片水印在线工具... 目录概述功能亮点使用方法技术解析延伸思考运行效果项目源码下载总结概述在社交媒体和内容创作日益频繁的

openCV中KNN算法的实现

《openCV中KNN算法的实现》KNN算法是一种简单且常用的分类算法,本文主要介绍了openCV中KNN算法的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录KNN算法流程使用OpenCV实现KNNOpenCV 是一个开源的跨平台计算机视觉库,它提供了各

OpenCV图像形态学的实现

《OpenCV图像形态学的实现》本文主要介绍了OpenCV图像形态学的实现,包括腐蚀、膨胀、开运算、闭运算、梯度运算、顶帽运算和黑帽运算,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起... 目录一、图像形态学简介二、腐蚀(Erosion)1. 原理2. OpenCV 实现三、膨胀China编程(

通过Spring层面进行事务回滚的实现

《通过Spring层面进行事务回滚的实现》本文主要介绍了通过Spring层面进行事务回滚的实现,包括声明式事务和编程式事务,具有一定的参考价值,感兴趣的可以了解一下... 目录声明式事务回滚:1. 基础注解配置2. 指定回滚异常类型3. ​不回滚特殊场景编程式事务回滚:1. ​使用 TransactionT

Android实现打开本地pdf文件的两种方式

《Android实现打开本地pdf文件的两种方式》在现代应用中,PDF格式因其跨平台、稳定性好、展示内容一致等特点,在Android平台上,如何高效地打开本地PDF文件,不仅关系到用户体验,也直接影响... 目录一、项目概述二、相关知识2.1 PDF文件基本概述2.2 android 文件访问与存储权限2.

使用Python实现全能手机虚拟键盘的示例代码

《使用Python实现全能手机虚拟键盘的示例代码》在数字化办公时代,你是否遇到过这样的场景:会议室投影电脑突然键盘失灵、躺在沙发上想远程控制书房电脑、或者需要给长辈远程协助操作?今天我要分享的Pyth... 目录一、项目概述:不止于键盘的远程控制方案1.1 创新价值1.2 技术栈全景二、需求实现步骤一、需求

Spring Shell 命令行实现交互式Shell应用开发

《SpringShell命令行实现交互式Shell应用开发》本文主要介绍了SpringShell命令行实现交互式Shell应用开发,能够帮助开发者快速构建功能丰富的命令行应用程序,具有一定的参考价... 目录引言一、Spring Shell概述二、创建命令类三、命令参数处理四、命令分组与帮助系统五、自定义S

SpringBatch数据写入实现

《SpringBatch数据写入实现》SpringBatch通过ItemWriter接口及其丰富的实现,提供了强大的数据写入能力,本文主要介绍了SpringBatch数据写入实现,具有一定的参考价值,... 目录python引言一、ItemWriter核心概念二、数据库写入实现三、文件写入实现四、多目标写入