本文主要是介绍el-table树形表格实现 父子联动勾选,子部分勾选时,父处于半勾选的状态,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
el-table树形表格实现 父子联动勾选,子部分勾选时,父处于半勾选的状态
需求背景
el-table,支持树形表格,但是多选框并没有自动支持父子联动勾选;
- 勾选全选,只有最外层的行被勾选;
- 勾选父,子级不会自动勾选;
- 勾选部分子,父级不会自动处于半勾选状态;
- 依次勾选全部的子级,父级不会自动勾选;
具体要求:
- 勾选全选按钮,表格所有层级 均被勾选,取消勾选全选按钮,表格所有层级 均被取消勾选;
- 父级勾选,子级(以及更深层级的子级)全部勾选,父级取消勾选,子级(以及更深层级的子级)全部取消勾选;
- 子级部分勾选 或 处于 半勾选的状态,则父级处于半勾选的状态
- 依次勾选全部的子级,父级会自动勾选;
开发分析
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树形表格实现 父子联动勾选,子部分勾选时,父处于半勾选的状态的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!