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

相关文章

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

Python的Darts库实现时间序列预测

《Python的Darts库实现时间序列预测》Darts一个集统计、机器学习与深度学习模型于一体的Python时间序列预测库,本文主要介绍了Python的Darts库实现时间序列预测,感兴趣的可以了解... 目录目录一、什么是 Darts?二、安装与基本配置安装 Darts导入基础模块三、时间序列数据结构与

Python使用FastAPI实现大文件分片上传与断点续传功能

《Python使用FastAPI实现大文件分片上传与断点续传功能》大文件直传常遇到超时、网络抖动失败、失败后只能重传的问题,分片上传+断点续传可以把大文件拆成若干小块逐个上传,并在中断后从已完成分片继... 目录一、接口设计二、服务端实现(FastAPI)2.1 运行环境2.2 目录结构建议2.3 serv

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

Nginx部署HTTP/3的实现步骤

《Nginx部署HTTP/3的实现步骤》本文介绍了在Nginx中部署HTTP/3的详细步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录前提条件第一步:安装必要的依赖库第二步:获取并构建 BoringSSL第三步:获取 Nginx

MyBatis Plus实现时间字段自动填充的完整方案

《MyBatisPlus实现时间字段自动填充的完整方案》在日常开发中,我们经常需要记录数据的创建时间和更新时间,传统的做法是在每次插入或更新操作时手动设置这些时间字段,这种方式不仅繁琐,还容易遗漏,... 目录前言解决目标技术栈实现步骤1. 实体类注解配置2. 创建元数据处理器3. 服务层代码优化填充机制详

Python实现Excel批量样式修改器(附完整代码)

《Python实现Excel批量样式修改器(附完整代码)》这篇文章主要为大家详细介绍了如何使用Python实现一个Excel批量样式修改器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录前言功能特性核心功能界面特性系统要求安装说明使用指南基本操作流程高级功能技术实现核心技术栈关键函

Java实现字节字符转bcd编码

《Java实现字节字符转bcd编码》BCD是一种将十进制数字编码为二进制的表示方式,常用于数字显示和存储,本文将介绍如何在Java中实现字节字符转BCD码的过程,需要的小伙伴可以了解下... 目录前言BCD码是什么Java实现字节转bcd编码方法补充总结前言BCD码(Binary-Coded Decima

SpringBoot全局域名替换的实现

《SpringBoot全局域名替换的实现》本文主要介绍了SpringBoot全局域名替换的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录 项目结构⚙️ 配置文件application.yml️ 配置类AppProperties.Ja