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


Posted in HTML / CSS onMay 07, 2023

前言

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

思路

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

实现的功能

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

表头分组

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

  • rowspan 表格横跨的行数
  • colspan 表格横跨的列数

配置

copy了一份ant的较为复杂的结构,然后稍微改一了一下标识字段,方便我们自己组件使用

const columns: columnsType[] = [
        { prop: 'index', label: '', width: 3 },
        {  
            label: 'Other',
            children: [
                {
                    prop: 'age', label: 'Age',
                },
                {
                    label: 'Address',
                    children: [
                        {
                            label: 'Street',
                            prop: 'street'
                        },
                        {
                            label: 'Block',
                            children: [
                              {
                                label: 'Building',
                                prop: 'building',
                              },
                              {
                                label: 'Door No.',
                                prop: 'number',
                              },
                            ],
                          },
                    ]
                }
            ]
        },
 ]

主体代码

// 头部
 <thead>
        <tr v-for="(row, index) in renderHeaderList" :key="index">
          <th
            v-for="columnsItem in row"
            :key="columnsItem.prop"
            :rowspan="computedHeaderRowSpan(columnsItem)"
            :colspan="computedHeaderColSpan(columnsItem)"
            :class="`width-${columnsItem.width} height-${tableHeaderHeight} header-b`"
          >
            // 使用组件的原因是方便扩展其他业务需求
            <headerCell :columnsItem="columnsItem"></headerCell>
          </th>
        </tr>
</thead>

横跨的行数

首先肉眼看到的肯定是表头横跨了4行。但是我们也不能写死成4行,我们需要通过计算得到这个表头最终横跨的几行。表头行数跨不跨行的判断依据是有无children。所以我这里是通过递归去扁平化这个数组,最终得到表格横跨了几行。

/**
     * @description 递归扁平化配置数组,得到渲染表头的数组以及横跨的的行数
     * @param columns children
     */
    function handleRenderHeaderList(columns:columnsType[]) {
        // 用于记录深度
        headerIndex.value += 1
       if (renderHeaderList.value.length <= headerIndex.value) {
            renderHeaderList.value.push(columns)
       } else {
            renderHeaderList.value[headerIndex.value] = [...renderHeaderList.value[headerIndex.value],...columns]
       }
       // 用于记录是否重置深度
       let isClearIndex = true
        columns.forEach((item: columnsType) => {
            // 判断是否还有子集
            if (item.children && item.children.length > 0 ) {
                isClearIndex = false
                handleRenderHeaderList(item.children)
            }
        });
        if(isClearIndex){
            headerIndex.value = 0
        }
    }
    /**
     * @description 单独rowspan的计算
     * @param columnsItem 单元格的配置
     * @return 单元格的列数 
     */
   function computedHeaderRowSpan(columnsItem:columnsType){
    if(!columnsItem.children){
        return renderHeaderList.value.length
    }
    return 1
    }

横跨的列数

这个列数也是不固定的,也需要去通过收集对应的children里面的项数来统计,因为我们是无法确认这个children的深度的,所以我这边用深度遍历来处理子集的收集问题。因为用递归此时vue会报警告,实际上我们也需要知道递归多了,内存就消耗的多,所以我们能不用递归就尽量不用递归。

/**
     * @description 单独colSpan的计算
     * @param columnsItem 单元格的配置
     * @return 单元格的列数
     */
    function computedHeaderColSpan(columnsItem:columnsType){
        if(!columnsItem.children){
            return 1
        }
        return flatColumnsItemChildren(columnsItem.children).length
    }
    /** 
     * @description 深度遍历扁平化数组获取单元格所占的列数
     * @param columnsItem 单元格的配置
     * @return 返回扁平化后的数组
     */
    function flatColumnsItemChildren(columnsItem:columnsType[]){
        // 深度遍历,扁平化数组
        let node, list = [...columnsItem], nodes = []
        while (node = list.shift()) {
            // 要过滤一下没有prop的,没有prop的列不参与最终的宽度计算
            if(node.prop){
                nodes.push(node)
            }
            node.children && list.unshift(...node.children)
        }
        return nodes
        // 递归会报警告,占内存
        // if(columnsItem.length === 0){
        //     return
        // }
        // columnsItem.forEach((item:columnsType)=>{
        //     if(item.children){
        //         flatColumnsItemChildren(item.children)
        //     }else{
        //         flatChildrenList.value.push(item)
        //     }
        // })
    }

实现效果图

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

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

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

配置

const columns: columnsType[] = [
        {
            prop: 'monitor',
            label: '班次',
            customCell: (_?: rowType, index?: number, columns?: columnsType) => {
                if (index === 2 && columns?.prop === 'monitor') {
                    return { colspan:3 };
                }
                if (index === 0 && columns?.prop === 'monitor') {
                    return { rowspan:2 };
                }
                if (index === 1 && columns?.prop === 'monitor') {
                    return { rowspan:0 };
                }
                return {colspan:1,rowspan:1};
            },
        },
        {
            prop: 'taHao',
            label: '塔号',
            customCell: (_?: rowType, index?: number, columns?: columnsType) => {
                if (index === 2 && columns?.prop === 'taHao') {
                    return {colspan : 0};
                }
                return  {colspan:1};
            },
        },
        {
            prop: 'materialNum',
            label: '投料量',
            customCell: (_?: rowType, index?: number, columns?: columnsType) => {
                if (index === 2 && columns?.prop === 'materialNum') {
                    return {colspan : 0};
                }
                return  {colspan:1};
            },
        },
        { prop: 'temperature', label: '沸腾罐温度', rowSpan: 2 },
        {
            prop: 'steamPressure',
            label: '蒸汽压力'
        },
        {
            prop: 'steamPressure1',
            label: '蒸汽压力2'
        },
        { prop: 'oxygen', label: '真空度' },
        { prop: 'productNum', label: '成品产量' },
        {
            prop: 'operatorName',
            label: '操作人'
        },
        {
            prop: 'operatorTime',
            label: '操作时间'
        },
    ];

主体代码以及单元格放组件

<tbody>
        <tr v-for="(item, index) in tableData" :key="index">
          <template
            v-for="(headerItem, headerIndex) in renderDataList"
            :key="headerIndex"
          >
            <td
              v-if="
                computedTdColspan(item, index, headerItem) !== 0 &&
                computedTdRowspan(item, index, headerItem) !== 0
              "
              align="center"
              :class="`height-${tableCellHeight} cell-b`"
              :colspan="computedTdColspan(item, index, headerItem)"
              :rowspan="computedTdRowspan(item, index, headerItem)"
            >
              // 动态组件提前写好组件去渲染对应的组件,此时的table单元格扩展性就变得非常强,不                  仅可以做展示用,也可以放输入框,下拉选择器之类的组件。
              <component
                :is="components[headerItem.type]"
                :ref="(el:unknown) => setComponentRef(el, headerItem.prop)"
                :form-item="headerItem"
                :value="item"
              ></component>
            </td>
          </template>
        </tr>
      </tbody>

横跨的行数

每个单元格渲染的时候,暴露一个函数出去,此函数的返回值有rowspan以及colspan,这样能准确的知道渲染每个单元格时此单元格占位多少。

/**
     * @description 计算单元格rowspan的占位
     * @param item 单元格一行的值
     * @param index 索引
     * @param columns 当前的单元格配置
     * @return colspan
     */
    function computedTdRowspan(item: rowType, index: number, columns: columnsType): number|undefined {
        if (columns.customCell) {
            let rowspan: number| undefined = 1
            if(columns.customCell(item, index, columns).rowspan ===0){
                rowspan = 0
            }
            if(columns.customCell(item, index, columns).rowspan){
                rowspan = columns.customCell(item, index, columns).rowspan
            }
            return rowspan
        }
        return 1;
    }

横跨的列数

每个单元格渲染的时候,暴露一个函数出去,此函数的返回值有rowspan以及colspan,这样能准确的知道渲染每个单元格时此单元格占位多少。

/**
     * @description 计算单元格colspan的占位
     * @param item 单元格一行的值
     * @param index 索引
     * @param columns 当前的单元格配置
     * @return colspan
     */
    function computedTdColspan(item: rowType, index: number, columns: columnsType): number|undefined {
        if (columns.customCell) {
            let colspan: number| undefined = 1
            if(columns.customCell(item, index, columns).colspan ===0){
                colspan = 0
            }
            if(columns.customCell(item, index, columns).colspan){
                colspan = columns.customCell(item, index, columns).colspan
            }
            return colspan
        }
        return 1;
    }

实现效果图

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

滚动条

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

总结

为了更好的在特定场景去控制table的高宽以及单元格的高宽,我们可以将他们的样式设定为动态的,我们可以通过配置去动态的改变他们的样式。然后就是处理一些无法确认层级的树形结构数据,我们也可以不通过递归去实现,节省内存。

到此这篇关于html原生table实现合并单元格以及合并表头的示例代码的文章就介绍到这了,更多相关html table合并单元格及表头内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章,希望大家以后多多支持三水点靠木!

 
HTML / CSS 相关文章推荐
浅析rem和em和px vh vw和% 移动端长度单位
Apr 28 HTML / CSS
利用CSS3的线性渐变linear-gradient制作边框的示例
Jun 02 HTML / CSS
CSS3 分类菜单效果
May 27 HTML / CSS
详解Canvas事件绑定
Jun 27 HTML / CSS
html5桌面通知(Web Notifications)实例解析
Jul 07 HTML / CSS
HTML5 video标签(播放器)学习笔记(一):使用入门
Apr 24 HTML / CSS
使用HTML5 Canvas为图片填充颜色和纹理的教程
Mar 21 HTML / CSS
canvas 实现 github404动态效果的示例代码
Nov 15 HTML / CSS
一文彻底解决HTML5页面中长按保存图片功能
Jun 10 HTML / CSS
html5中嵌入视频自动播放的问题解决
May 25 HTML / CSS
HTML5来实现本地文件读取和写入的实现方法
May 25 HTML / CSS
CSS font-variation 可变字体的魅力(实例详解)
Mar 03 HTML / CSS
html解决浏览器记住密码输入框的问题
May 07 #HTML / CSS
使用CSS实现百叶窗效果示例代码
使用CSS实现按钮边缘跑马灯动画
使用CSS实现音波加载效果
table不让td文字溢出操作方法
Dec 24 #HTML / CSS
table设置超出部分隐藏,鼠标移上去显示全部内容的方法
Dec 24 #HTML / CSS
td 内容自动换行 table表格td设置宽度后文字太多自动换行
Dec 24 #HTML / CSS
You might like
JAVA/JSP学习系列之四
2006/10/09 PHP
PHP 导出数据到淘宝助手CSV的方法分享
2010/02/27 PHP
php支付宝接口用法分析
2015/01/04 PHP
PHP简单实现循环链表功能示例
2017/11/10 PHP
Array的push与unshift方法性能比较分析
2011/03/05 Javascript
这段js代码得节约你多少时间
2011/12/20 Javascript
Jquery颜色选择器ColorPicker实现代码
2012/11/14 Javascript
jQuery简单实现禁用右键菜单
2015/03/10 Javascript
JavaScript实现随机替换图片的方法
2015/04/16 Javascript
Nodejs获取网络数据并生成Excel表格
2020/03/31 NodeJs
js获取客户端操作系统类型的方法【测试可用】
2016/05/27 Javascript
require简单实现单页应用程序(SPA)
2016/07/12 Javascript
Bootstrap模态框禁用空白处点击关闭
2016/10/20 Javascript
js调用父框架函数与弹窗调用父页面函数的简单方法
2016/11/01 Javascript
AngularJS的ng-click传参的方法
2017/06/19 Javascript
jQuery实现获取动态添加的标签对象示例
2018/06/28 jQuery
jQuery实现简单的Ajax调用功能示例
2019/02/15 jQuery
javascript自定义日期比较函数用法示例
2019/07/22 Javascript
vue中使用vee-validator完成表单校验方案
2019/11/01 Javascript
使用Taro实现小程序商城的购物车功能模块的实例代码
2020/06/05 Javascript
vue全局使用axios的操作
2020/09/08 Javascript
Vue中引入svg图标的两种方式
2021/01/14 Vue.js
[40:16]TFT vs Mski Supermajor小组赛C组 BO3 第二场 6.3
2018/06/04 DOTA
Python中的字符串操作和编码Unicode详解
2017/01/18 Python
python的一些加密方法及python 加密模块
2019/07/11 Python
使用pymysql查询数据库,把结果保存为列表并获取指定元素下标实例
2020/05/15 Python
python操作toml文件的示例代码
2020/11/27 Python
python利用proxybroker构建爬虫免费IP代理池的实现
2021/02/21 Python
澳大利亚汽车零部件、音响及配件超市:Automotive Superstore
2018/06/19 全球购物
介绍一下Prototype的$()函数,$F()函数,$A()函数都是什么作用?
2014/03/05 面试题
司马光教学反思
2014/02/01 职场文书
优秀大学生职业生涯规划书
2014/02/27 职场文书
同学聚会主持词
2014/03/18 职场文书
2014年党员个人工作总结
2014/12/02 职场文书
首席执行官观后感
2015/06/03 职场文书
小学远程教育工作总结
2015/08/13 职场文书