功能目录
如题,公司业务需求,数据结构比较复杂,需要在一张表内实现多级树状数据展示及同属性的单元格合并,并在表格内实现增删改操作。
网上翻阅了很多实例,没有能解决所有需求的案例,于是自己实现了一套。
时间匆忙,逻辑有优化的地方还请无偿指出!
最终效果如下
图上,编码有父子层级,每个编码可包含多个交付阶段,每个交付阶段可包含多个文件,每个文件可添加不同文档项次
实现总结如下
一. 结构调整
首先跟后台确认了数据结构,根据右侧最详细内容为基准,以单层数组返回(以编码树级返回更好)。获取到数据后封装为树级数据。保证最详细处表格每一行都对应一条数据。如图示,忽略为展开子级数据,则图上一共对应七条数据。
其中,每个数据对象带有三个属性:code_cnt(每条编码下对应的第三部分行数)、stage_cnt(每个编码下的交付阶段对应的第三部分行数)、file_cnt(每个文件对应的第三部分行数)。后面用于表格合并。
- 封装完数据或直接获取到父子层级后,因存在多条数据同一编码,每条数据下都有相同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;}
- 因相同编码、相同阶段、相同文件需合并,所以需要递归标识出每个相同编码、阶段、文件的首条数据,以满足后续单元格合并需求。
// 单元格需合并时,标记首条数据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条数据的单元格。
因此,我们需自己做子级的展开合并操作。
- 首先重写编码列的渲染模板
<el-table-columnlabel="编码"key="code"prop="code"show-overflow-tooltip><templatev-slot="{ row }"><spanv-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;}}returnfalse;},
三. 单元格合并
第一步已经封装好了数据,直接绑定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标识当前行的编辑状态,据其修改表格列渲染模板。
- 新增 因表格中文件、项次并非一定存在,所以会如第一张图第二条数据所示,直接出现文件后面为空的情况。此种情况可直接将该行置为编辑状态。 若是后面几行,则需处理数据。 矛盾点在于,因交付文件也是合并过的单元格,所以点击的时候也是同类数据首条,而我们添加的习惯是添加到其最后面。即当我们点击X-R1.1.4中 测试2 交付文件的+时,我们需要在其两条后加一条数据,并把前面单元格合并。
asynchandleAddFileItem(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:newDate().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;},
- 编辑 编辑操作较为简单,将isEdit置为true,并在editMap中保存初始状态即可 this.editMap[row.union_id] = { …row }; row.isEdit = true;
- 新增/编辑条目删除/取消修改操作
asynccancelFileItemDeal(row){if(row.isAdd){// 新增条目this.tableList=this.delCnt(row,this.tableList);}else{// 编辑项复原for(let key inthis.editMap[row.id]){
row[key]=this.editMap[row.id][key];}deletethis.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;},
- 删除 删除可直接调用后端接口,后合并数据,无需多余处理
至此,该表格的完整功能实现完成!!!
版权归原作者 trottoir 所有, 如有侵权,请联系我们删除。