设为首页收藏本站
天天打卡

 找回密码
 立即注册
搜索
查看: 133|回复: 15

html原生table实现合并单元格以及合并表头的示例代码

[复制链接]

2

主题

5

回帖

56

积分

等待验证会员

积分
56

热心会员付费会员

发表于 2024-4-20 08:11:33 | 显示全部楼层 |阅读模式
目录


前言

因为公司业务越来越复杂,有些页面PC和app其实是一样的,我们肯定不想写两套代码,所以我们看看能不能写一个同时支持PC和app的table组件

思路

首先肯定想到的是原生table进行封装,因为我早就想这么干了,想通过原生的一些基础组件去封装成咱们可用的组件库。说搞就搞,先实现table的一些简单功能,因为公司用的js框架是vue,所以基于vue3去封装。

实现的功能

       
  • 表头分组   
  • 合并单元格   
  • 滚动条   
  • 单元格放组件

表头分组

表头分组这个概念也是ant-design-vue中看来的,反正我的理解就是合并单元格,但是它叫表头分组,可能专业些,好吧,已经复制了它的叫法。
通过配置去生成分组的表头,首先要对原生table的一些配置要比较熟练,介绍两个最重要的配置
       
  • rowspan 表格横跨的行数   
  • colspan 表格横跨的列数


配置

copy了一份ant的较为复杂的结构,然后稍微改一了一下标识字段,方便我们自己组件使用
  1. const columns: columnsType[] = [
  2.         { prop: 'index', label: '', width: 3 },
  3.         {  
  4.             label: 'Other',
  5.             children: [
  6.                 {
  7.                     prop: 'age', label: 'Age',
  8.                 },
  9.                 {
  10.                     label: 'Address',
  11.                     children: [
  12.                         {
  13.                             label: 'Street',
  14.                             prop: 'street'
  15.                         },
  16.                         {
  17.                             label: 'Block',
  18.                             children: [
  19.                               {
  20.                                 label: 'Building',
  21.                                 prop: 'building',
  22.                               },
  23.                               {
  24.                                 label: 'Door No.',
  25.                                 prop: 'number',
  26.                               },
  27.                             ],
  28.                           },
  29.                     ]
  30.                 }
  31.             ]
  32.         },
  33. ]
复制代码
主体代码
  1. // 头部
  2. <thead>
  3.         <tr v-for="(row, index) in renderHeaderList" :key="index">
  4.           <th
  5.             v-for="columnsItem in row"
  6.             :key="columnsItem.prop"
  7.             :rowspan="computedHeaderRowSpan(columnsItem)"
  8.             :colspan="computedHeaderColSpan(columnsItem)"
  9.             :class="`width-${columnsItem.width} height-${tableHeaderHeight} header-b`"
  10.           >
  11.             // 使用组件的原因是方便扩展其他业务需求
  12.             <headerCell :columnsItem="columnsItem"></headerCell>
  13.           </th>
  14.         </tr>
  15. </thead>
复制代码

横跨的行数

首先肉眼看到的肯定是表头横跨了4行。但是我们也不能写死成4行,我们需要通过计算得到这个表头最终横跨的几行。表头行数跨不跨行的判断依据是有无children。所以我这里是通过递归去扁平化这个数组,最终得到表格横跨了几行。
  1. /**
  2.      * @description 递归扁平化配置数组,得到渲染表头的数组以及横跨的的行数
  3.      * @param columns children
  4.      */
  5.     function handleRenderHeaderList(columns:columnsType[]) {
  6.         // 用于记录深度
  7.         headerIndex.value += 1
  8.        if (renderHeaderList.value.length <= headerIndex.value) {
  9.             renderHeaderList.value.push(columns)
  10.        } else {
  11.             renderHeaderList.value[headerIndex.value] = [...renderHeaderList.value[headerIndex.value],...columns]
  12.        }
  13.        // 用于记录是否重置深度
  14.        let isClearIndex = true
  15.         columns.forEach((item: columnsType) => {
  16.             // 判断是否还有子集
  17.             if (item.children && item.children.length > 0 ) {
  18.                 isClearIndex = false
  19.                 handleRenderHeaderList(item.children)
  20.             }
  21.         });
  22.         if(isClearIndex){
  23.             headerIndex.value = 0
  24.         }
  25.     }
  26.     /**
  27.      * @description 单独rowspan的计算
  28.      * @param columnsItem 单元格的配置
  29.      * @return 单元格的列数
  30.      */
  31.    function computedHeaderRowSpan(columnsItem:columnsType){
  32.     if(!columnsItem.children){
  33.         return renderHeaderList.value.length
  34.     }
  35.     return 1
  36.     }
复制代码

横跨的列数

这个列数也是不固定的,也需要去通过收集对应的children里面的项数来统计,因为我们是无法确认这个children的深度的,所以我这边用深度遍历来处理子集的收集问题。因为用递归此时vue会报警告,实际上我们也需要知道递归多了,内存就消耗的多,所以我们能不用递归就尽量不用递归。
  1.    /**
  2.      * @description 单独colSpan的计算
  3.      * @param columnsItem 单元格的配置
  4.      * @return 单元格的列数
  5.      */
  6.     function computedHeaderColSpan(columnsItem:columnsType){
  7.         if(!columnsItem.children){
  8.             return 1
  9.         }
  10.         return flatColumnsItemChildren(columnsItem.children).length
  11.     }
  12.     /**
  13.      * @description 深度遍历扁平化数组获取单元格所占的列数
  14.      * @param columnsItem 单元格的配置
  15.      * @return 返回扁平化后的数组
  16.      */
  17.     function flatColumnsItemChildren(columnsItem:columnsType[]){
  18.         // 深度遍历,扁平化数组
  19.         let node, list = [...columnsItem], nodes = []
  20.         while (node = list.shift()) {
  21.             // 要过滤一下没有prop的,没有prop的列不参与最终的宽度计算
  22.             if(node.prop){
  23.                 nodes.push(node)
  24.             }
  25.             node.children && list.unshift(...node.children)
  26.         }
  27.         return nodes
  28.         // 递归会报警告,占内存
  29.         // if(columnsItem.length === 0){
  30.         //     return
  31.         // }
  32.         // columnsItem.forEach((item:columnsType)=>{
  33.         //     if(item.children){
  34.         //         flatColumnsItemChildren(item.children)
  35.         //     }else{
  36.         //         flatChildrenList.value.push(item)
  37.         //     }
  38.         // })
  39.     }
复制代码

实现效果图



合并单元格以及单元格放组件

合并单元格稍微简单些,只需要把每个单元格的colspan和rowspan写成一个函数并且暴露出来就能处理


配置
  1. const columns: columnsType[] = [
  2.         {
  3.             prop: 'monitor',
  4.             label: '班次',
  5.             customCell: (_?: rowType, index?: number, columns?: columnsType) => {
  6.                 if (index === 2 && columns?.prop === 'monitor') {
  7.                     return { colspan:3 };
  8.                 }
  9.                 if (index === 0 && columns?.prop === 'monitor') {
  10.                     return { rowspan:2 };
  11.                 }
  12.                 if (index === 1 && columns?.prop === 'monitor') {
  13.                     return { rowspan:0 };
  14.                 }
  15.                 return {colspan:1,rowspan:1};
  16.             },
  17.         },
  18.         {
  19.             prop: 'taHao',
  20.             label: '塔号',
  21.             customCell: (_?: rowType, index?: number, columns?: columnsType) => {
  22.                 if (index === 2 && columns?.prop === 'taHao') {
  23.                     return {colspan : 0};
  24.                 }
  25.                 return  {colspan:1};
  26.             },
  27.         },
  28.         {
  29.             prop: 'materialNum',
  30.             label: '投料量',
  31.             customCell: (_?: rowType, index?: number, columns?: columnsType) => {
  32.                 if (index === 2 && columns?.prop === 'materialNum') {
  33.                     return {colspan : 0};
  34.                 }
  35.                 return  {colspan:1};
  36.             },
  37.         },
  38.         { prop: 'temperature', label: '沸腾罐温度', rowSpan: 2 },
  39.         {
  40.             prop: 'steamPressure',
  41.             label: '蒸汽压力'
  42.         },
  43.         {
  44.             prop: 'steamPressure1',
  45.             label: '蒸汽压力2'
  46.         },
  47.         { prop: 'oxygen', label: '真空度' },
  48.         { prop: 'productNum', label: '成品产量' },
  49.         {
  50.             prop: 'operatorName',
  51.             label: '操作人'
  52.         },
  53.         {
  54.             prop: 'operatorTime',
  55.             label: '操作时间'
  56.         },
  57.     ];
复制代码
主体代码以及单元格放组件
  1. <tbody>
  2.         <tr v-for="(item, index) in tableData" :key="index">
  3.           <template
  4.             v-for="(headerItem, headerIndex) in renderDataList"
  5.             :key="headerIndex"
  6.           >
  7.             <td
  8.               v-if="
  9.                 computedTdColspan(item, index, headerItem) !== 0 &&
  10.                 computedTdRowspan(item, index, headerItem) !== 0
  11.               "
  12.               align="center"
  13.               :class="`height-${tableCellHeight} cell-b`"
  14.               :colspan="computedTdColspan(item, index, headerItem)"
  15.               :rowspan="computedTdRowspan(item, index, headerItem)"
  16.             >
  17.               // 动态组件提前写好组件去渲染对应的组件,此时的table单元格扩展性就变得非常强,不                  仅可以做展示用,也可以放输入框,下拉选择器之类的组件。
  18.               <component
  19.                 :is="components[headerItem.type]"
  20.                 :ref="(el:unknown) => setComponentRef(el, headerItem.prop)"
  21.                 :form-item="headerItem"
  22.                 :value="item"
  23.               ></component>
  24.             </td>
  25.           </template>
  26.         </tr>
  27.       </tbody>
复制代码

横跨的行数

每个单元格渲染的时候,暴露一个函数出去,此函数的返回值有rowspan以及colspan,这样能准确的知道渲染每个单元格时此单元格占位多少。
  1. /**
  2.      * @description 计算单元格rowspan的占位
  3.      * @param item 单元格一行的值
  4.      * @param index 索引
  5.      * @param columns 当前的单元格配置
  6.      * @return colspan
  7.      */
  8.     function computedTdRowspan(item: rowType, index: number, columns: columnsType): number|undefined {
  9.         if (columns.customCell) {
  10.             let rowspan: number| undefined = 1
  11.             if(columns.customCell(item, index, columns).rowspan ===0){
  12.                 rowspan = 0
  13.             }
  14.             if(columns.customCell(item, index, columns).rowspan){
  15.                 rowspan = columns.customCell(item, index, columns).rowspan
  16.             }
  17.             return rowspan
  18.         }
  19.         return 1;
  20.     }
复制代码

横跨的列数

每个单元格渲染的时候,暴露一个函数出去,此函数的返回值有rowspan以及colspan,这样能准确的知道渲染每个单元格时此单元格占位多少。
  1. /**
  2.      * @description 计算单元格colspan的占位
  3.      * @param item 单元格一行的值
  4.      * @param index 索引
  5.      * @param columns 当前的单元格配置
  6.      * @return colspan
  7.      */
  8.     function computedTdColspan(item: rowType, index: number, columns: columnsType): number|undefined {
  9.         if (columns.customCell) {
  10.             let colspan: number| undefined = 1
  11.             if(columns.customCell(item, index, columns).colspan ===0){
  12.                 colspan = 0
  13.             }
  14.             if(columns.customCell(item, index, columns).colspan){
  15.                 colspan = columns.customCell(item, index, columns).colspan
  16.             }
  17.             return colspan
  18.         }
  19.         return 1;
  20.     }
复制代码

实现效果图



滚动条

table自身是响应式的,按照一定规则自动去分配宽度和高度的,如果不在table外面包裹一层元素的话,table会一直自适应,没法带有滚动条,我们需要给外层元素设置一个宽度或者高度,然后table也设置一个固定的宽度或者是高度,这样内部的table就会在限定的宽度或者高度下具有滚动条。

总结

为了更好的在特定场景去控制table的高宽以及单元格的高宽,我们可以将他们的样式设定为动态的,我们可以通过配置去动态的改变他们的样式。然后就是处理一些无法确认层级的树形结构数据,我们也可以不通过递归去实现,节省内存。
到此这篇关于html原生table实现合并单元格以及合并表头的示例代码的文章就介绍到这了,更多相关html table合并单元格及表头内容请搜索脚本之家以前的文章或继续浏览下面的相关文章,希望大家以后多多支持脚本之家!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×

0

主题

46

回帖

95

积分

等待验证会员

积分
95
发表于 2024-4-30 23:09:22 | 显示全部楼层
我想了解更多

2

主题

33

回帖

111

积分

注册会员

积分
111
发表于 2024-6-2 09:01:22 | 显示全部楼层
确实牛逼

0

主题

51

回帖

103

积分

注册会员

积分
103
发表于 2024-6-14 19:48:08 | 显示全部楼层
谢谢你的提醒,我会注意的。

1

主题

53

回帖

106

积分

注册会员

积分
106
发表于 2024-8-16 06:14:51 | 显示全部楼层
非常感谢你的观点,让我受益良多!

0

主题

69

回帖

139

积分

注册会员

积分
139
发表于 2024-8-27 17:17:00 | 显示全部楼层
保持尊重和礼貌对待其他成员是必要的。
  • 打卡等级:无名新人
  • 打卡总天数:1
  • 打卡月天数:0
  • 打卡总奖励:7
  • 最近打卡:2024-05-05 22:03:52

2

主题

62

回帖

177

积分

注册会员

积分
177
发表于 2024-9-7 01:28:17 | 显示全部楼层
6666666666

1

主题

53

回帖

126

积分

注册会员

积分
126
发表于 2024-9-20 05:02:51 | 显示全部楼层
每日一回
发表于 2024-9-24 00:19:51 | 显示全部楼层
说得太好了,完全同意!

1

主题

63

回帖

149

积分

注册会员

积分
149
发表于 2024-9-27 09:46:05 | 显示全部楼层
看了LZ的帖子,我只想说一句很好很强大!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|爱云论坛 - d.taiji888.cn - 技术学习 免费资源分享 ( 蜀ICP备2022010826号 )|天天打卡

GMT+8, 2024-11-15 05:11 , Processed in 0.087098 second(s), 28 queries .

Powered by i云网络 Licensed

© 2023-2028 正版授权

快速回复 返回顶部 返回列表