element-ui 可复选树型表格

效果

在这里插入图片描述

思路

  • 自定义模板
  • 当点击某个行复选框时,其所有后代复选框都要同步状态,且其直系父辈状态需要根据所点击复选框的状态来修正
  • 点击全选复选框时,批量同步所有行内复选框状态

代码

非封装组件,按需自行改写
确保引入element-ui 的情况下直接新增如下代码的vue组件即可,部分数据检索和批处理存在优化空间。

<template><div><aside>树型表格复选框</aside><el-tableref="table":data="rows"row-key="id"borderdefault-expand-all><!--自定义复选框-start--><el-table-column label="全选" width="160" align="center"><template slot="header" slot-scope="scope"><el-checkbox :indeterminate="isIndeterminate" v-model="isFullChecked" @change="checkAllChange"/></template><template slot-scope="{row}"><el-checkbox :indeterminate="row.isIndeterminate" :value="row.checked" @change="checkRowChange(row)"/></template></el-table-column><!--自定义复选框-end--><el-table-column label="子节点部分选取" prop="isIndeterminate" width="160" align="center"><template slot-scope="{row}">{{row.isIndeterminate?'true':'false'}}</template></el-table-column><el-table-column label="是否选中" prop="checked" width="160" align="center"><template slot-scope="{row}">{{row.checked?'true':'false'}}</template></el-table-column><el-table-column type="index" label="序号" width="60" align="center"></el-table-column><el-table-column prop="id" label="ID" align="center" width="160"></el-table-column><el-table-column prop="parentId" label="PARENT_ID" align="center" width="160"></el-table-column></el-table><br><!--如果涉及分页,新的数据会直接覆盖掉原有的,只需要额外的操作将全选复选框重置即可--><el-button @click="getResource">刷新数据</el-button></div>
</template><script>
export default {name: "tree-table-checkbox",data() {return {rows: [],isFullChecked: false,isIndeterminate: false,}},created() {this.getResource()},watch: {// 直接监听rows数据是否变化,重置全选框rows() {this.isFullChecked = falsethis.isIndeterminate = false}},methods: {/**  同步更新子节点复选框*  isIndeterminate = false checked = false 表现为空    全部子节点取消勾选*  isIndeterminate = false checked = true  表现为选中  全部子节点勾选*  isIndeterminate = true   表现为半选(操作其子节点的结果),因此子节点要改为选中 不修改子节点状态  点击为全选中* */deepSetChildren(data) {const { isIndeterminate, checked } = dataconst recursionSetChecked = (arr, checked) => {if (arr && arr.length > 0) {arr.forEach(d => {d.checked = checkedd.isIndeterminate = falserecursionSetChecked(d.children, checked)})}}recursionSetChecked(data.children, isIndeterminate ? true : checked)},/***根据checked自定义加载*1)判断当前checked。* checked ==true  当前节点 变为false  下级所有级别节点变为false* checked == false 当前节点 变为true 下级所有级别节点变为true*2)双向递归刷新所有节点checked*/checkRowChange(data) {const vm = this;const { rows } = vm/** tbody中的复选框不使用v-model,否则会出现 checked=true 需要点两次才会变成false的情况* 可参考 https://blog.csdn.net/weixin_41597344/article/details/103196688* 因此这里再事件监听中手动取反*/data.checked = !data.checked//鼠标勾选/取消勾选 需要同步子节点状态和操作的行一致vm.deepSetChildren(data)/** 递归修正* 再整个数据树中,操作节点的下层子分支状态由操作节点确定,而其父辈节点需要根据操作节点的情况进行判断。* 这里没有单独找出操作节点的分支,直接处理了整棵树* @returns {*}*/const recursion = (node) => {if (node.children && node.children.length > 0) {// 向下递归node.children.forEach(d => recursion(d))// 获取子节点的情况(有多少勾选的,有多少半选的)const sumChecked = node.children.filter(d => d.checked).lengthnode.isIndeterminate = sumChecked > 0 && sumChecked < node.children.lengthif (node.children.some(d => d.isIndeterminate)) {node.isIndeterminate = true}node.checked = sumChecked !== 0} else {// 没有子节点的由自身确定状态node.isIndeterminate = false}return node}rows.forEach(d => {recursion(d)})//因为全选按钮不在整个数据树中,需要单独判断数据树一层的情况//如果全部勾选则 【全选框】 勾选if (rows.every(d => d.checked)) {vm.isFullChecked = true}//如果全部勾选则 【全选框】 取消勾选if (rows.every(d => !d.checked)) {vm.isFullChecked = false}//如果是存在半选或存在未选且有已选或半选 则 【全选框】半选vm.isIndeterminate = rows.some(d => d.isIndeterminate) ? true : rows.some(d => !d.checked) && rows.some(d => d.checked)},/*** 初始化时设定isFullChecked = false,每次点击取反* if  isFullChecked == true*设置所有节点checked = true* if  isFullChecked == false* 设置所有节点checked = false*/checkAllChange() {const vm = thisconst { rows } = vmconst recursionSetChecked = (item, checked) => {item.checked = checkeditem.isIndeterminate = falseif (item.children && item.children.length > 0) {item.children.forEach(d => recursionSetChecked(d, checked))}}this.isIndeterminate = falserows.forEach(d => recursionSetChecked(d, vm.isFullChecked))},//获取列表基础数据getResource() {const vm = this//测试用数据只有一个根节点 "0" 方便构建测试用数据树const data = [{ id: '1', parentId: '0' },{ id: '2', parentId: '0' },{ id: '2-0', parentId: '2' },{ id: '1-0', parentId: '1' },{ id: '1-1', parentId: '1' },{ id: '1-1-0', parentId: '1-1' },{ id: '1-1-0-0', parentId: '1-1-0' },{ id: '1-1-0-1', parentId: '1-1-0' },{ id: '1-2', parentId: '1' },{ id: '1-2-0', parentId: '1-2' },{ id: '1-2-1', parentId: '1-2' },]//1) 简单处理数据用于自定义渲染; checked:  indeterminate:data.forEach(d => {d.checked = false //是否选中d.isIndeterminate = false//是否是半选状态})vm.rows = vm.makeTree(data, 'id', 'parentId', '0')},/*** 构建树,与复选逻辑无关* @param data* @param idMark* @param pIdMark* @param rootId* @returns {*}*/makeTree(data, idMark, pIdMark, rootId) {//转化为字典,id为键值,并添加根节点对象let nodeDict = {};(nodeDict[rootId] = { children: [] })[idMark] = rootId;data.forEach(n => {(nodeDict[n[idMark]] = n).children = [];});data.forEach(function (d) {let parentNode = nodeDict[d[pIdMark]];if (parentNode) {parentNode.children.push(d);}});return nodeDict[rootId].children;}}
}
</script><style scoped></style>

补充

复选逻辑完全可以抽离,之前一直没时间,所以补充下

/*
* 层级树组递归
* */
Object.defineProperty(Array.prototype, 'recursive', {enumerable: false,value: function(handler) {const vm = thisconst recursive = (current, parent, index, siblings, path) => {if (handler({ current, parent, index, siblings, path })) return trueif (current.children && current.children.length > 0) {const nextPath = [...path, current.id]for (let i = 0, child; (child = current.children[i]) != null; i++) {if (recursive(child, current, i, current.children, nextPath)) {return true}}}}for (let i = 0, child; (child = vm[i]) != null; i++) {if (recursive(child, null, i, vm, [])) {return true}}}
})export class CheckManager {rootIdoptions = []nodeIterator = []nodeMap = {}parentMap = {}statusMap = {}constructor(rootId) {this.rootId = rootId != null ? rootId : Math.random()this.statusMap[0] = {checked: false,indeterminate: false}}setData(data) {const vm = this, { rootId } = vmvm.parentMap = {}//添加虚拟根节点vm.options = [{ id: rootId, parentId: null, children: data }]//记录节点选中状态let statusMapTemp = {}let nodeMapTemp = {}let nodeIteratorTemp = []vm.options.recursive(({ current, parent }) => {vm.parentMap[current.id] = parentnodeMapTemp[current.id] = currentstatusMapTemp[current.id] = { checked: false, indeterminate: false }if (current.id != rootId) {nodeIteratorTemp.push(current)}})vm.statusMap = statusMapTempvm.nodeMap = nodeMapTempvm.nodeIterator = nodeIteratorTempreturn statusMapTemp}toggle(id) {const vm = this, { options, statusMap, parentMap } = vmif (id == null) {return}let node = vm.nodeMap[id]let currStatus = statusMap[node.id]let checked = currStatus.checked = currStatus.indeterminate ? true : !currStatus.checkedcurrStatus.indeterminate = false//1)同步子节点状态和本节点一致if (node.children) node.children.recursive(({ current }) => {statusMap[current.id] = { checked, indeterminate: false }})//2) 向上计算父节点let parent = nodewhile ((parent = parentMap[parent.id])) {vm.updateNodeStatus(parent)}return statusMap}/*** 如果有子节点,则本节点状态由子节点确定*/setStatus(node) {const vm = this, { statusMap } = vmlet status = statusMap[node.id]if (!status) {status = statusMap[node.id] = {checked: false,indeterminate: false,}}if (node.children && node.children.length > 0) {node.children.forEach(d => vm.setStatus(d))vm.updateNodeStatus(node)} else {// 没有子节点的由自身确定状态node.indeterminate = false}}/*** 依据子节点状态更新本节点* @param node*/updateNodeStatus(node) {const vm = this, { options, statusMap, parentMap } = vmlet checkedNum = 0, unCheckedNum = 0, status = statusMap[node.id], hasHalfChecked = falsefor (let i = 0, item; (item = node.children[i]) != null; i++) {//记录子节点不同选中状态的数量statusMap[item.id].checked ? checkedNum++ : unCheckedNum++//有半选子节点或者  既有未选中子节点也有选中子节点  则本节点半选if ((statusMap[item.id].indeterminate) || (checkedNum > 0 && unCheckedNum > 0)) {status.indeterminate = truestatus.checked = truehasHalfChecked = truebreak}}if (!hasHalfChecked) {//子节点全部为选中状态则本节点选中status.indeterminate = falsestatus.checked = checkedNum > 0}}/*** 更新选中状态*/updateStatus() {const vm = thisfor (let i = 0, node; (node = vm.options[i]) != null; i++) {vm.setStatus(node)}return vm.statusMap}/** 节点变化后仅保留末级节点的选中状态且末级节点indeterminate = false*/refresh(data) {const vm = this, { rootId } = vmvm.parentMap = {}vm.options = [{ id: rootId, parentId: null, children: data }]//重新组织父节点映射vm.options.recursive(({ current, parent }) => {vm.parentMap[current.id] = parentcurrent.parentId = parent?.id})data.forEach(d => this.setStatus(d))return this.statusMap}setChecked(statusOps) {const vm = this, { statusMap } = vm;let prevStatusfor (let i = 0, nextStatus; (nextStatus = statusOps[i]) != null; i++) {prevStatus = statusMap[nextStatus.id]if (nextStatus.checked != null) {prevStatus.checked = nextStatus.checked}}return this.updateStatus()}getChecked() {const vm = this, { nodeIterator, statusMap } = vm;let result = [], statusfor (let i = 0, item; (item = nodeIterator[i]) != null; i++) {status = statusMap[item.id]if (status.checked) {result.push(item.id)}}return result}getCheckedNode() {const vm = this, { nodeIterator, statusMap, nodeMap } = vm;let result = [], statusfor (let i = 0, item; (item = nodeIterator[i]) != null; i++) {status = statusMap[item.id]if (status.checked) {result.push(nodeMap[item.id])}}return result}getHalfChecked() {const vm = this, { nodeIterator, statusMap } = vm;let result = [], statusfor (let i = 0, item; (item = nodeIterator[i]) != null; i++) {status = statusMap[item.id]if (status.checked && status.indeterminate == true) {result.push(item.id)}}return result}getFullChecked() {const vm = this, { nodeIterator, statusMap } = vm;let result = [], statusfor (let i = 0, item; (item = nodeIterator[i]) != null; i++) {status = statusMap[item.id]if (status.checked && status.indeterminate == false) {result.push(item.id)}}return result}
}

使用

创建管理类实例 并指明根节点id

let checkManger = new CheckManager(0);

赋值通过字段 id parentId children 表示父子关系的数据

let data = [id:1,parentId:0,children:[id:1,parentId:1,]
]
checkManger.setData(data)

获取计算后的 复选框状态集

checkStatusMap= checkManger.statusMap

渲染复选框并绑定切换事件

 <el-checkbox :value="checkStatusMap[row.id].checked" :indeterminate="checkStatusMap[row.id].indeterminate" @change="toggle(row.id)">
</el-checkbox>

更新复选框状态集

toggle(data) {this.checkStatusMap = checkManger.toggle(data);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/506994.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux 删除含有关键词的文件_linux下查找包含关键字的文件

在linux下如果要查找包含某个关键字的文件&#xff0c;如要在nagios目录下搜索带有关键字“store-rd-sys”的文件&#xff0c;在终端下运行命令&#xff1a;/usr/local/nagios/etc/* (nagios目录)法1&#xff1a;grep -r “关键字” 路径[rootnagios01 ~]#grep -r "store-…

树形可拖拽排序配置组件

效果 使用场景 vue2下自定义表格表头配置: 列排序&#xff0c;显示/隐藏等。确保表头以配置项的形式加载&#xff0c;这样表格才能对修改后的配置作响应 思路 1、表格使用render函数加载(如有疑问可私信)&#xff0c;通过类似如下的columns配置表头 columns: [{ label: 姓名…

maven 打包指定依赖包_Maven打包成Jar文件时依赖包的问题

我们项目中使用到第三方的库文件&#xff0c;这些jar库文件并没有放到Maven中央库上&#xff0c;导致我们需要在项目中自己配置使用。我们的两三个开发人员对Java都是很熟&#xff0c;因此在使用中遇到了一些问题&#xff0c;表现在&#xff1a;在本地中引入第三方jar包后&…

自适应浮动表单填充布局脚本

效果 1、适合搜素表单布局&#xff0c;查询重置等功能块始终位于最后一行的最后一列 2、适合普通多行两端对齐&#xff0c;未填充满的行左对齐 思路 此脚本目的为实现整齐风格的表单布局&#xff0c;为了达到整齐的效果&#xff0c;每个表单元素或者块都要设置一致的 宽度…

rstudio导入txt文件_R语言 | 读写txt、csv、excel文件

前段时间看到plotnine库(封装的R语言ggplot2)作的图太美了&#xff0c;有点想重新拾起R语言^_^R语言代码略带凌乱&#xff0c;讲真的还是更喜欢Python代码简洁。不过好几年不琢磨R语言&#xff0c;对R语言代码的凌乱美有些新奇&#xff0c;很好玩&#xff0c;这也许就是久别胜新…

erp采购总监个人总结_erp采购总监总结.docx

erp采购总监总结ERP沙盘模拟心得体会——采购总监  刚开始接触ERP&#xff0c;就从学长们那里了解到这门课比较有意思&#xff0c;然后又听老师说我们还有一个为期两周的ERP沙盘模拟&#xff0c;这让我对这门课更有了极大的兴趣。一直期待着沙盘模拟对抗早点到来&#xff0c;…

矩阵字符串配置任意合并表格布局

效果 核心 布局配置矩阵&#xff08;以下为多个模式),可以使用|或\n表示矩阵行 const gridArr [1,2,a,b 3,4,a,b 5,6,a,b ,1,2 3,4 5,6 ,1,2,3,4 3,4]任意横向或者纵向相同的字符表示一个合并块 使用 <CalcTable grid"1,2,a,b|3,4,a,b|5,6,a,b" ><span…

django 套vue 模板_Vue admin template + Django 快速进行Web开发

本文教大家如何使用Vue admin template和Django快速进行Web开发&#xff0c;旨在帮助我们使用现有的工具、框架及开源UI&#xff0c;让我们在基础较为薄弱的情况下&#xff0c;能进行Web开发。本文不会介绍过多的原理&#xff0c;实践为主。Vue admin template的简单使用Vue ad…

js浮点运算式

结果 calc(0.23*-0.03(4*0.2)) >0.7931 代码 //加|减|乘|除 浮点运算 const floatMulti (a, b) > {let m 0, s1 a.toString(), s2 b.toString(), s1l s1.split(.)[1], s2l s2.split(.)[1]m (s1l ? s1l.length : 0) (s2l ? s2l.length : 0)return Number(s1…

悬浮球多功能_一个悬浮球,怎么可以这么贴心~

原标题&#xff1a;一个悬浮球&#xff0c;怎么可以这么贴心~一个悬浮球 满足你N个愿望※ 专题&#xff5c;图文&#xff5c;悬浮球上手指南这个小蛋蛋是不少小朋友喜爱的零食&#xff0c;因为它能满足小朋友好几个愿望&#xff0c;能吃又能玩的零食哪个小朋友会不喜欢&#xf…

python 字典添加元素乱序了_Python有序字典的两个小“惊喜”

从 Python 3.6 开始&#xff0c;常规的字典会记住其插入的顺序&#xff1a;就是说&#xff0c;当遍历字典时&#xff0c;你获得字典中元素的顺序跟它们插入时的顺序相同。在 3.6 之前&#xff0c;字典是无序的&#xff1a;遍历顺序是随机的。关于有序字典&#xff0c;这里有两件…

el-table跨页选中

以id作为唯一标识 模板 <el-table :data"data" ref"table"select-all"selectAll"select"select"style"width:100%"><el-table-column type"selection"></el-table-column><el-table-colum…

bsc是指什么_为什么KPI令人厌恶?中小企业不要乱用KPI!

私信小编“绩效”两字&#xff0c;免费发送60分钟薪酬绩效管理内部培训视频。导读现在很多的企业都会对员工做一些绩效考核&#xff0c;大多数还是采用KPI的方式。但是员工对KPI的考核越来越反感&#xff0c;甚至出现抵触的情况。为什么会出现这种想象呢&#xff1f;其实很简单…

js原生popup_JavaScript的popup框

确认框用于使用户可以验证或者接受某些信息。当确认框出现后&#xff0c;用户需要点击确定或者取消按钮才能继续进行操作。如果用户点击确认&#xff0c;那么返回值为 true。如果用户点击取消&#xff0c;那么返回值为 false。语法&#xff1a;confirm("文本")functi…

解决微信小程序 [Component] slot ““ is not found.

解决方式 当使用自定义组件或者slot标签作为组件A的插槽内容时&#xff0c;在组件A中必须定义一个默认插槽&#xff0c;对普通view等标签无限制。且因为wx:if为false的插槽等同没有定义 场景复现&#xff08;仅以自己遇到情况为例&#xff09; 1、调试基础库2.19.4 2、使用w…

列表排序应用FLIP动画(vue)

效果 原理详解 链接 1.beforeUpdate 获取first 变化前位置 (以id建立map映射) 2.updated 获取变化后位置 last 3.禁用transition并transform元素回初始位置 4.异步恢复transition 并取消 transform 代码 <template><div ref"container"><div style&…

面试项目亮点_码农:面试被问到自己项目亮点时,感觉自己的回答虚伪的不行!...

据我个人观察&#xff0c;大多数程序员都对自己现有的项目有吐糟的习惯&#xff0c;比如吐糟代码逻辑混乱&#xff0c;代码规范问题&#xff0c;代码可读性差&#xff0c;代码没有注释&#xff0c;没有文档&#xff0c;代码极度冗余等等&#xff0c;总之满眼看到的都是一些缺陷…

无敌小恐龙

断网恐龙小游戏无敌版&#xff0c;控制台输入 boxCompare _> false

怎样设计访谈提纲_论访谈提纲的设计

访谈提纲的设计一、访谈提纲如何设计:首先是该怎么命题&#xff0c;也就是访谈的主题或大题目是什么&#xff1b;其次是访谈的题目设计在多少比较合理&#xff1b;再次是访谈的主要点是什么&#xff1b;之后是各题目之间是什么关系&#xff1b;再后是访谈提纲设计注意哪些基本点…