解决复杂的复杂表头表格的种种翻车

分享 已结 精帖
51 3298
岁月小偷
悬赏:80飞吻
更新于2018年11月14日21:29:49
下方有较大的更新,如果是以前使用过改插件的小伙伴注意查看额[微笑]
首先来看个事故现场吧:

出现的问题:
比较明显的有两个:
1、固定列的tr高度不正常。
2、tbody里面比表头多出了很多列
其实还有隐藏的不容易发现的问题,不过一步一步来,一个一个问题解决。
表头来自以前社区的一个小伙伴提的问题的表头,稍微有调整,为了测试一些问题:


问题一:首先是固定列高度的,也许原始的代码没有考虑到和并列作为固定列的情况,所以设置tr的代码只是很简单的给固定列的tr设置成原始table的thead的高度,但是肯定也没有什么规定说复杂表头的字段不能fixed的。
修改方案,思路有两个:
一个就是去修改源码中对应的逻辑,

红色的框框就是主要需要修改的地方,怎么修改见下面,实际跟思路2的改法是一样的,就是一个在源码上动,一个在自己扩展的js里面改,建议使用方法二
第二就是后续自己处理,so,还是延续tablePlug的思路,在setColumnWith里面动动手脚把高度给设置好:

然后效果是:


问题2:这个出现的原因很可能也是因为table本身没有考虑到的一个场景,就是单列合并的问题,所以原始代码中判断是否和并列的逻辑是colspan>1,这就错杀了很多无辜的和并列了,比如下面的红色框框中的th不是和并列那是啥?
解决方案,首先这个是一个特殊情况就是说单列合并,那么如何区分一个列是单利合并还是一个普通的字段列?这个就需要有一个逻辑或者说约定限定,比如只有字段列(包括复选列、数值列和工具列,泛指需要在tbody中显示的列)才能设置field,和并列不允许设置field那么就能够区分出来了,另外一个就是你去显示的指定某一些列是和并列,即使它的colspan是1或者没有设置,那么需要新属性,但是新版本实际有一个隐含的属性可以直接使用了,叫做colGroup,怎么来的?哪里有写?api里面实际没有任何的提及,这个是看了代码才发现的彩蛋。

那么也就是说如果你想要设置它是固定列的可以一开始就显示的指定比如:

效果是:

有没有发现少了很多了,但是眼尖的很快就发现不对呀,字段都没对应上,错开了,这个嘛,实际上是目前一个设定导致的,表格的列的宽度是有内部的div的决定的,但是如果是合并列呢?他们的样式是:

显然没有人来限定它的宽度,实际这个应该也是table内部的考虑,因为一般来说和并列的宽度怎么会比所有子列的宽度加起来还大呢?但是这个是无法保证的,保不证就有老长老长的内容呢?特别是单列的合并就更有可能出现了:

这个怎么处理后面有时间再考虑考虑了,现在当前的帖子估计还没1/3没时间和篇幅处理这个了,先给特殊处理先,最小宽度设置多一点,然后太过长的title去掉几个字结果是:

嗯,看着完事了,没多没少,也没错列,显示很完整,看着很舒服,(but真的没问题了吗?实际这里面存在一个更大的事故在里面而且是历史悠久了,后面再说)
那么有个问题,要写这么多colGroup,有没有办法简化,tablePlug会怎么处理,有办法,恢复现场,车开起来。

页面的设置:

效果是:

可见效果跟在字段里面设置colGroup的效果是一样的,但是其实一方面是更加简单了,另外一方面实则是起到一个最终把控的作用,避免了因为一些失误把该是和并列的没有设置成和并列,把一些本来就是字段列的给强制指定成和并列导致更多的问题。

该揭晓本帖子要说的最大的事故是什么了,上面的表格到这里实际上问题还不算大,都可以通过一些方式处理掉,但是有一个比较隐蔽的问题是:表头的字段跟tbody的字段错位了,你可能会问,动图看着不会呀,完全匹配上了,但是这里说的错位不是指的宽度啥的,而是真的“错位”,就是比如字段表头叫姓名,然而下面的内容却显示着年龄这种错位。这才是又隐蔽又严重的大事故。
如何验证,实际开发中遇到才更容易发现,测试中写着12345这些数字不明显,那么给添加一个新的测试数据如何,然后内容就跟着title走如下:

那么结果如何:



这样子的结果对于表格来说算不算是一个重大事故~
原因:还是那个问题,table的一些逻辑没考虑到,这个一般出现在复杂的复杂表头,通常在和并列的同时它的rowspan也有合并的情况下会出现!具体到源码中的一句代码就是:

箭头处是原来的代码。它的逻辑是什么,大概的说明一下,它就是说如果遇到和并列,它需要去找到它的子列里面对应的内容,但是关键问题在于一个和并列它的子列真的就在它的next?实际上是想easy了,如果rowspan刚好是1那没问题,但是如果不是它的子列不可能这么简单的在它的下个cols中,而是跟这个rowspan走的,rowspan有多少它就得跨多少个下标去找,这里可能会有点绕,大家可以仔细思考思考看看或者画画图看看我说的逻辑对不对,所以改成下面被注释的,同时现在的table提供了一个eachCols的方法,也要对应的给换一下,所以这个源码有两处地方需要修改(源码搜索i1 + 1):



效果:



最后的这个问题一方面不仔细看不容易发现,发现了跟起来也不容易发现问题根源,但是就是这么一个很细小的逻辑处理问题就能够导致这么大的差异。
然后是这个问题能不能不修改源码的基础上在tablePlug处理掉,这个不是没有办法,比如可以在生成错乱之后done回调里面去乾坤大挪移,给tbody的列的顺序给调整对了,但是这个是最low的方式了,不同于前面的两个问题,这个这么做一方面效果不好,而且麻烦所以如果发现问题根源是源码的逻辑不合适,还是把它改对了这样子性价比更高一些,个人认为。
更新于2018年11月14日21:39:47
较大的更新是:将table内部的构造器也给透出来了,方便直接多构造器的一些方法在tablePlug里面重写,就不需要改动更多的源码比如上面的两处源码修改,完全可以在tablePlug里面修改了,而且这么修改的好处就是它直接在根源上就改过来了,而不用等到render一下然后再接收这个实例然后再单独的修改,下个render的时候还是需要对当前这个render的实例再次修改,性能上也会有一定的提升。
源码修改把Class透出来

然后把上面两处的源码修改换成在tablePlug里面做

这么做是一个双刃剑,看怎么使用了,当然利大于弊。
回帖
  • 莫辞
    2018-11-12
    [good] [good] [good]
    0 回复
  • 感觉这个table组件目前是用的最多也是业务最复杂问题最多的一个,我们自己这样修改,最大的问题在于没办法更新,连续看了几次版本更新这个组件的源码,连核心方法都是经常变的。希望闲心把这个组件能独立出来,做出layer一样,这个组件对于管理系统其实相当重要。
    3 回复
  • @雷锋2班红领巾 不知道我的理解对不对,你说想要更方便的复用templet对吧,比如有不同的字段,但是其实他们的解析都一样,就差一个字段名称,但是现在templet里面只有一个参数d所以即使功能是一样的,也要分别写两个templet的方法,然后唯一的区别就是字段不一样了,是不是这个场景呢?如果是的话我这有个相关的处理你可以试试看额:

    实现的原理就是自己套多一层,封装一个templet的方法合集,返回某一类的模板,对应的function

    不知道你要的是不是类似这样子的
    1 回复
  • 0 回复
  • @kaixinsile 嗯嗯,是一个很重要的一个模块,但是这个做成独立的组件我是觉得没什么意义而且也不太现实,你说的里面源码会变核心方法改这个版本更新坑定是会的,毕竟是想要越来越好用,想要效率更好,有时候也需要fix一些bug,所以这个不管是独立出来也好还是作为layui的一部分也好这个是没差别的,不太现实我觉得table实际上是一个比较综合的组件,它会用到laypage,laytpl,layer,form等几个组件,如果要独立出来势必就得在table内部去实现这些内容,做到不依赖其他的组件,这样子就不太现实也没什么用了,实际你项目中使用layui的往往也不会只用它的table,其他的很多组件也是很好用的,个人看法额。
    0 回复
  • SmallBlack
    2018-11-13
    我现在的问题是表头正常,但是下面的字段对不上
    0 回复
  • Zlheb
    2018-11-13
    [赞] [赞] [赞]
    0 回复
  • 大佬永远是大佬 [哈哈]
    0 回复
  • @四年之约 谢谢认可[微笑]
    0 回复
  • 大佬。还有一种场景是,处理字段的显示,如:把所有为""、null、undefined、0的数据显示为 "/",目前表格的templet 传递的参数“d”是整行的数据,也就是如果这一行我要修改如上的n个字段,要么得修改json、要么后台处理、要么就是写n个templet。修改json和后台处理不利于表格数据的前端操作,所以最好的方式就是templet。要是表格的templet能再多传递一个参数为当前单元格数据就完美了,如上需求只需要定义一个templet即可
    0 回复
  • 你这个真的是研究的深入啊
    0 回复
  • QQ糖
    2018-11-22
    大佬强无敌呀[奥特曼]
    0 回复
  • 又一力作[good]
    0 回复
  • @爱死寂寞人 @静夜喧哗 谢谢认可[微笑] @一个幽默的代码 代码是最诚实的哈,遇到一些实在没办法一下子看出问题的最后只能看它的背后的执行的代码逻辑了
    0 回复
  • QQ糖
    2018-11-22
    @岁月小偷 大佬,layui可以设计这样的表头么 上面一个字段,下面对应着两个列。我有个表得这样设计[可怜]
    0 回复
  • @岁月小偷 感谢大佬,这样确实可行[太开心]
    0 回复
  • nhzex
    2018-11-22
    @岁月小偷 这个好想法哦,学习学习
    0 回复
  • 0 回复
  • @静夜喧哗 这个的需求有点奇特额,一般来说表头合并应该有对应的子表头或者说数据列一般来说都需要有对应的表头列,你要的估计是下面这个

    目前没遇到这个需求,不知道有没有什么更好的方式,但是能做出来利用一些“旁门左道”的方法:

    在done回调里面把需要合并起来的头部,把其中的一列作为保留的,然后把它的宽度加上后面需要合并起来的列的宽度,最后把后面被合并进去的隐藏,有点像贪吃蛇一样。最后看到的就是你要的效果了,有点障眼法的意思。
    //执行一个 table 实例
    table.render({
    elem: '#demo'
    ,height: 420
    ,url: '/demo/table/user/' //数据接口
    ,title: '用户表'
    ,page: true //开启分页
    ,toolbar: 'default' //开启工具栏,此处显示默认图标,可以自定义模板,详见文档
    ,totalRow: true //开启合计行
    ,cols: [[ //表头
    {type: 'checkbox', fixed: 'left'}
    ,{field: 'id', title: 'ID', width:80, sort: true, fixed: 'left', totalRowText: '合计:'}
    ,{field: 'username', title: '用户名', width:80}
    ,{field: 'sex', title: '性别', width:80, sort: true}
    ,{field: 'experience', title: '积分', width: 90, sort: true, totalRow: true},
    {field: 'score', title: '评分', width: 80, sort: true, totalRow: true}
    ,{field: 'city', title: '城市', width:150}
    ,{field: 'sign', title: '签名', width: 200}
    ,{field: 'classify', title: '职业', width: 100}
    ,{field: 'wealth', title: '财富', width: 135, sort: true, totalRow: true}
    ,{fixed: 'right', width: 165, align:'center', toolbar: '#barDemo'}
    ]],
    done: function(){
    var tableView = this.elem.next();
    tableView.find('th[data-field="experience"] div').width(tableView.find('th[data-field="experience"]').width()+tableView.find('th[data-field="score"] div').width() +1);

    tableView.find('th[data-field="score"]').hide()
    }
    0 回复
  • 0 回复
  • @码完才准走 @nhzex 谢谢认可[微笑]
    0 回复
  • QQ糖
    2018-11-22
    @岁月小偷 爱你大佬。[礼物]
    0 回复
  • @静夜喧哗 不客气,后面的话就是优化一下上面框框里面的代码的逻辑,比如应该要判断一下是不是已经合并过了,不然后面切换页面的时候也会走done回调,没有判断的话会不断的累加上去,另外一个就是一般这种列一般就不能让它resize,不然改变了列宽样式会变回出现列对不上的问题。
    0 回复
  • QQ糖
    2018-11-22
    @岁月小偷 加1是什么意思?



    ,还有大佬, 我把所有表头隐藏了,怎么才能隐藏指定的id=‘tableone’,而其他的不隐藏呢,小白求大佬帮助
    0 回复
  • @静夜喧哗 +1这个是表头两个th之间的border宽度,如果合并两个就得加2-1,如果是三个就的加3-1以此类推。你说的id是?什么表格的id还是?如果是表格的id那么应该是
    #tableone+div .layui-table-header {
    display: none;
    }
    0 回复
  • QQ糖
    2018-11-22
    @岁月小偷 对是表格的id,谢谢大佬
    0 回复
  • @静夜喧哗 不客气[微笑]
    0 回复
  • QQ糖
    2018-11-22
    @岁月小偷 对了大佬,我不添加if语句,把它初始化的话是不是也可以,tableView.find('th[data-field="experience"] div').width(value);我用if的时候到那里就不走了,没找出错误原因。。。我比较笨[闭嘴]
    0 回复
  • @静夜喧哗 嗯嗯,render或者reload是没有问题的,但是如果是点击页面切换一个页面的话就有可能会出问题,所以最好还是得写得更加健壮一些,你没有效果有没有提示什么错误呢?或者把你的代码截个图我看看是什么问题。
    0 回复
  • 大佬,真够复杂的。厉害[good]
    0 回复