功能目录

  • 一. 结构调整
  • 二. 父子层级展开合并
  • 三. 单元格合并
  • *四. 表格增删改操作

如题,公司业务需求,数据结构比较复杂,需要在一张表内实现多级树状数据展示及同属性的单元格合并,并在表格内实现增删改操作。

网上翻阅了很多实例,没有能解决所有需求的案例,于是自己实现了一套。

时间匆忙,逻辑有优化的地方还请无偿指出!

最终效果如下

图上,编码有父子层级,每个编码可包含多个交付阶段,每个交付阶段可包含多个文件,每个文件可添加不同文档项次

实现总结如下

一. 结构调整

首先跟后台确认了数据结构,根据右侧最详细内容为基准,以单层数组返回(以编码树级返回更好)。获取到数据后封装为树级数据。保证最详细处表格每一行都对应一条数据。如图示,忽略为展开子级数据,则图上一共对应七条数据。

其中,每个数据对象带有三个属性:code_cnt(每条编码下对应的第三部分行数)、stage_cnt(每个编码下的交付阶段对应的第三部分行数)、file_cnt(每个文件对应的第三部分行数)。后面用于表格合并。

  1. 封装完数据或直接获取到父子层级后,因存在多条数据同一编码,每条数据下都有相同children数据存在,所以需删除多余children,保留一条。又因展开时需展示在相同编码下方,所以需保存相同编码最后一条数据的children字段。如图上所示,X-R1.1.4编码有三条数据,应只保留项次编码为-D3.2.2的children数据,以保证点击展开子级时子层级展示在三条数据下方。
// 当同一编码多条数据且有children时,保留最后一级childrendeleteChildren(data) {for (let i = 0; i < data.length; i++) {if (data[i].children && data[i].children.length) {data[i].hasChild = true;  // 后续解释if ( data.some( (item, index) => index > i && item.code_id === data[i].code_id ) ) {delete data[i].children;} else {data[i].children = this.deleteChildren(data[i].children);}}}return data;}
  1. 因相同编码、相同阶段、相同文件需合并,所以需要递归标识出每个相同编码、阶段、文件的首条数据,以满足后续单元格合并需求。
// 单元格需合并时,标记首条数据dealDataBefore(data) {let id = "",  stage = "",  file = ""; for (let i = 0; i < data.length; i++) {if (!id || id !== data[i].interface_item_code) {// 第一条id = data[i].interface_item_code;data[i].isFirstLine = true;  // 标识编码首条数据stage = data[i].stage_keyid;data[i].isFirstStage = true;  // 标识阶段首条数据file = data[i].deliver_file_template_id;data[i].isFirstFile = true;  // 标识文件首条数据} else {if (!stage || stage !== data[i].stage_keyid) {stage = data[i].stage_keyid;data[i].isFirstStage = true;file = data[i].deliver_file_template_id;data[i].isFirstFile = true;} else {if (!file || file !== data[i].deliver_file_template_id) {file = data[i].deliver_file_template_id;data[i].isFirstFile = true;}}}if (data[i].children) {data[i].children = this.dealDataBefore(data[i].children);}}return data;},

二. 父子层级展开合并

第一步数据处理结束后,会发现交给element-ui渲染,无法展开关闭父子层级。

因为我们第一步对数据的处理,最左侧编码展示的数据已经没有children数据了,而有children数据的单元格将被上方合并无法点击。

如上图所示,4、5两条数据实则第3条数据的children,而显示的X-R1.1.4为第1条数据的单元格。
因此,我们需自己做子级的展开合并操作。

  1. 首先重写编码列的渲染模板
<el-table-columnlabel="编码"key="code"prop="code"show-overflow-tooltip
><template v-slot="{ row }"><span v-if="row.hasChild" class="arrow-icon" @click="toggleRowExpansion(row)"><i :class="row.isExpand ? 'el-icon-caret-bottom' : 'el-icon-caret-right'" /></span><span>{{ row.code }}</span></template>
</el-table-column>

第一步的hasChild标识意义就出来了,当有多条数据时,末条保留children,首条标记hasChild。
2. 递归获取到点击条目的同层级下所有相同编码的数据,后将最后一条数据子级做展开/关闭操作。即点击上图中X-R1.1.4的按钮时,需获取到相同编码的1、2、3数据,后将3设为展开/关闭状态。

toggleRowExpansion(row) {row.isExpand = !row.isExpand;let rowList = this.getRowList(row, this.tableList);const expansionRow = rowList[rowList.length - 1];this.$refs.detailTable &&this.$refs.detailTable.toggleRowExpansion(expansionRow, row.isExpand);
},
// 获取点击层级同编码所有数据数组
getRowList(row, list) {for (let i = 0; i < list.length; i++) {if (list[i].id === row.id)return list.filter((item) => item.code === row.code );if (list[i].children && list[i].children.length) {let res = this.getRowList(row, list[i].children);if (res) return res;}}return false;
},

三. 单元格合并

第一步已经封装好了数据,直接绑定table组件的span-method方法如下

//合并单元格objectSpanMethod({ row, column, rowIndex, columnIndex }) {if (row.code_cnt > 1 && columnIndex < 3) {// 同编码,前三行合并return {rowspan: row.code_cnt,colspan: row.isFirstLine ? 1 : 0,};}if (row.stage_cnt > 1 && columnIndex === 3) {// 同交付阶段多文件,阶段合并return {rowspan: row.stage_cnt,colspan: row.isFirstStage ? 1 : 0,};}if (row.file_cnt > 1 && columnIndex === 4) {// 同文件多项次,文件合并return {rowspan: row.file_cnt,colspan: row.isFirstFile ? 1 : 0,};}},

*四. 表格增删改操作

截止前三步,表格的展示及交互已全部完成。
本业务流程中,文件为弹框选择,所以不做介绍。因产品要求,需在表格内直接完成文件后文档项次等增删改及操作,所以实现了后续功能(无需求可止步)。
isEdit标识当前行的编辑状态,据其修改表格列渲染模板。

  1. 新增
    因表格中文件、项次并非一定存在,所以会如第一张图第二条数据所示,直接出现文件后面为空的情况。此种情况可直接将该行置为编辑状态。
    若是后面几行,则需处理数据。
    矛盾点在于,因交付文件也是合并过的单元格,所以点击的时候也是同类数据首条,而我们添加的习惯是添加到其最后面。即当我们点击X-R1.1.4中 测试2 交付文件的+时,我们需要在其两条后加一条数据,并把前面单元格合并。
async handleAddFileItem(row) {// 该文件下无项次,则直接修改该项if (!row.file_item_code) {this.editMap[row.id] = { ...row };   // 该map用于存储当前在编辑项的原始状态,用于取消操作row.isEdit = true;} else {this.tableList = this.addCnt(row, this.tableList);}
},
addCnt(row, list) {// code_cnt 相同编码加一// stage_cnt 该编码下相同stage加一// file_cnt 该文件加一let hasAdd = false,addIndex = 0; // 标记加入数据下标let firstLineIndex = "";for (let i = 0; i < list.length; i++) {// 已循环至该添加项次,退出循环并返回修改后数据if (hasAdd && addIndex === i) return list;if (list[i].id === row.id) {firstLineIndex === "" && (firstLineIndex = i);// 同编码所有项次cnt加一list[i].code_cnt++;if (list[i].stage_keyid === row.stage_keyid) {// 同交付阶段cnt加一list[i].stage_cnt++;if (list[i].file_code === row.file_code) {list[i].file_cnt++;}}// 当前点击条目if (list[i].union_id === row.union_id) {let children =list[i + list[i].deliver_file_cnt - 2].children || [];let newLine = {code_id: list[i].code_id,code_cnt: list[i].code_cnt,file_cnt: list[i].file_cnt,file_code: list[i].file_code,deliver_file_template_id: list[i].deliver_file_template_id,isEdit: true,isAdd: true,  // 用于后续删除时标识删除条目为新增还是编辑条目id: new Date().getTime(),  // row-key必须字段parent_id: list[i].parent_id,stage: list[i].stage,stage_cnt: list[i].stage_cnt,stage_keyid: list[i].stage_keyid,children: children,isExpand: list[firstLineIndex].isExpand,};// children迁移!!!// 因当前条变为最后一条,需将前面条目children迁移至本条,并同步开闭状态list[i + list[i].file_cnt - 2].children = [];// 在所有相同文件数据最后一条后添加addIndex = i + list[i].file_cnt - 1;list.splice(addIndex, 0, newLine);hasAdd = true;if (children.length) {this.$nextTick(() => {this.$refs.detailTable.toggleRowExpansion(newLine,list[firstLineIndex].isExpand);});}}} else {// 未找到编码则继续寻找if (list[i].children && list[i].children.length) {list[i].children = this.addCnt(row, list[i].children);}}}return list;},
  1. 编辑
    编辑操作较为简单,将isEdit置为true,并在editMap中保存初始状态即可
    this.editMap[row.union_id] = { …row };
    row.isEdit = true;
  2. 新增/编辑条目删除/取消修改操作
async cancelFileItemDeal(row) {if (row.isAdd) {// 新增条目this.tableList= this.delCnt(row, this.tableList);} else {// 编辑项复原for (let key in this.editMap[row.id]) {row[key] = this.editMap[row.id][key];}delete this.editMap[row.id];}
},
delCnt(row, list) {// code_cnt 相同编码减一// stage_cnt 该编码下相同stage减一// file_cnt 该文件减一let hasDelete = false;let firstLineIndex = "";for (let i = 0; i < list.length; i++) {// 已删除并循环至其他项次,退出循环if (hasDelete && list[i].id !== row.id) return list;if (list[i].id === row.id) {firstLineIndex === "" && (firstLineIndex = i);// 同编码所有项次cnt加一list[i].code_cnt--;if (list[i].stage_keyid === row.stage_keyid) {// 同交付阶段cnt加一list[i].stage_cnt--;if (list[i].file_code === row.file_code) {list[i].file_cnt--;}}// 当前点击条目if (list[i].id === row.id) {let children = list[i].children;if (children && children.length) {list[i - 1].children = children;this.$nextTick(() => {this.$refs.detailTable.toggleRowExpansion(list[i - 1],list[firstLineIndex].isExpand);});}// 直接删除list.splice(i, 1);hasDelete = true;}} else {// 未找到编码则继续寻找if (list[i].children && list[i].children.length) {list[i].children = this.delCnt(row, list[i].children);}}}return list;
},
  1. 删除
    删除可直接调用后端接口,后合并数据,无需多余处理

至此,该表格的完整功能实现完成!!!

基于element-ui的table实现树级表格操作及单元格合并相关推荐

  1. 【技巧】实现饿了么Element UI的table单击(点击)编辑单元格内容

    <template><el-table@cell-click="tableCellClick":cell-class-name="({ row, col ...

  2. 表格(table属性、th/td属性、单元格合并)

    表格 现在HTML 表格应该用于表格数据 ​语法:表格的容器<table>行<tr><td>普通单元格</td><th>表头单元格</ ...

  3. element ui 的table的表头和表格没对齐

    在 app.vue的style里面加上 /* 解决element-ui的table表格控件表头与内容列不对齐问题 */.el-table th.gutter{display: table-cell!i ...

  4. Web前端笔记-element ui中table中某列添加a便签进行跳转

    效果是这样的: 这里的文章标题和查看都可以进行跳转. 其中对应的代码如下: <template style="height: 100%"><el-table:da ...

  5. Element UI 之table表格表头过长使用点点…显示,并添加鼠标移入悬浮显示

    Element UI 之table表格表头过长使用点点-显示,并添加鼠标移入悬浮显示 需求 鼠标移入表头 关键点: 1.样式中添加:(如果在scope中会不起作用) .el-table .cell { ...

  6. Element UI 的 table 单元格合并

    项目中遇到表格单元格合并的需求,在此记录整个解决过程. 项目使用的是 Element UI,表格使用的是 table 组件.Element UI 的 table 表格组件中对单元格进行合并,需要使用 ...

  7. 二次封装Element Ui的Table中使用render渲染函数

    render函数的使用 render在element UI中的使用 render函数是什么 具体参数用法 render在element UI中的使用 {prop: 'button',label: '操 ...

  8. Vue.js 根据数据,进行Table单元格合并(原生方式以及element组件方式)

    表格代码 <table cellspacing="0" cellpadding="0" border="0" style=" ...

  9. asp单元格合并后宽度没有合并_ElementUI Table组件如何使用合并行或列功能深入解析...

    需求,对指定的列表展示进行合并单元格,循环展示指定行和指定列. 1.官方文档 通过给table传入span-method方法可以实现合并行或列,方法的参数是一个对象,里面包含当前行row.当前列col ...

最新文章

  1. [R语言画图]气泡图symbols
  2. Win10环境下,word2016嵌入mathtype出现灰色的解决办法
  3. python 图像无缝拼接,OpenCV Python 系列教程3 - Core 组件
  4. Shell命令-磁盘与文件系统之dumpe2fs、dump
  5. 树莓派 python spi,树莓派测试SPI-基于设备操作ioctl
  6. java103 101 104 101_编写一个java程序将100,101,102,103,104,105这6个数以数组的形式写入到D:\\test.t...
  7. 华为手机截屏怎么截长图_华为手机5种常用截屏方式,教你轻松定格屏幕精彩瞬间...
  8. pythonscrapy爬虫安装_零基础写python爬虫之爬虫框架Scrapy安装配置
  9. java反射创建带参数对象_反射 Java反射对象创建 - 闪电教程JSRUN
  10. Gradle Eclipse插件教程
  11. The Speed 歌词
  12. 摘抄(SAP所有模块用户出口(User Exits) )
  13. 看完这篇iOS面试题,一天3offer!!!
  14. PopupWindow 监听返回键
  15. Java 面向对象 习题2(基础篇)
  16. 平板电脑如何刷linux,平板电脑刷windows的方法是什么_如何把平板刷windows图文步骤...
  17. JavaMail发送QQ邮件
  18. 2022新版QQ微信域名防红PHP源码+强制跳转打开
  19. 虚拟机中安装软路由RouterOS详解教程
  20. CUDA/cudnn/CUDA Toolkit/NVCC区别简介

热门文章

  1. 解决WES 7 中Composite Bus找不到驱动的bug
  2. java计算机毕业设计基于安卓Android的电子废弃物回收利用APP
  3. 设计模式学习(1)简单工厂模式:君子远庖厨
  4. Cglib和jdk动态代理
  5. 运营商大数据到底有什么意义?
  6. 把机顶盒刷成Linux操作系统
  7. c语言面试题sizeof,C语言面试题——sizeof的注意点
  8. 遥测终端机RTU的特点以及应用领域
  9. 【数据结构】CH6 数组和广义表
  10. protel99se在PCB中按照顶层或底层分类导出元件BOM的方法