html原生table實(shí)現(xiàn)合并單元格以及合并表頭的示例代碼

前言
因?yàn)楣緲I(yè)務(wù)越來越復(fù)雜,有些頁面PC和app其實(shí)是一樣的,我們肯定不想寫兩套代碼,所以我們看看能不能寫一個(gè)同時(shí)支持PC和app的table組件
思路
首先肯定想到的是原生table進(jìn)行封裝,因?yàn)槲以缇拖脒@么干了,想通過原生的一些基礎(chǔ)組件去封裝成咱們可用的組件庫。說搞就搞,先實(shí)現(xiàn)table的一些簡(jiǎn)單功能,因?yàn)楣居玫膉s框架是vue,所以基于vue3去封裝。
實(shí)現(xiàn)的功能
- 表頭分組
- 合并單元格
- 滾動(dòng)條
- 單元格放組件
表頭分組
表頭分組這個(gè)概念也是ant-design-vue中看來的,反正我的理解就是合并單元格,但是它叫表頭分組,可能專業(yè)些,好吧,已經(jīng)復(fù)制了它的叫法。
通過配置去生成分組的表頭,首先要對(duì)原生table的一些配置要比較熟練,介紹兩個(gè)最重要的配置
- rowspan 表格橫跨的行數(shù)
- colspan 表格橫跨的列數(shù)
配置
copy了一份ant的較為復(fù)雜的結(jié)構(gòu),然后稍微改一了一下標(biāo)識(shí)字段,方便我們自己組件使用
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`" > // 使用組件的原因是方便擴(kuò)展其他業(yè)務(wù)需求 <headerCell :columnsItem="columnsItem"></headerCell> </th> </tr> </thead>
橫跨的行數(shù)
首先肉眼看到的肯定是表頭橫跨了4行。但是我們也不能寫死成4行,我們需要通過計(jì)算得到這個(gè)表頭最終橫跨的幾行。表頭行數(shù)跨不跨行的判斷依據(jù)是有無children。所以我這里是通過遞歸去扁平化這個(gè)數(shù)組,最終得到表格橫跨了幾行。
/** * @description 遞歸扁平化配置數(shù)組,得到渲染表頭的數(shù)組以及橫跨的的行數(shù) * @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 單獨(dú)rowspan的計(jì)算 * @param columnsItem 單元格的配置 * @return 單元格的列數(shù) */ function computedHeaderRowSpan(columnsItem:columnsType){ if(!columnsItem.children){ return renderHeaderList.value.length } return 1 }
橫跨的列數(shù)
這個(gè)列數(shù)也是不固定的,也需要去通過收集對(duì)應(yīng)的children里面的項(xiàng)數(shù)來統(tǒng)計(jì),因?yàn)槲覀兪菬o法確認(rèn)這個(gè)children的深度的,所以我這邊用深度遍歷來處理子集的收集問題。因?yàn)橛眠f歸此時(shí)vue會(huì)報(bào)警告,實(shí)際上我們也需要知道遞歸多了,內(nèi)存就消耗的多,所以我們能不用遞歸就盡量不用遞歸。
/** * @description 單獨(dú)colSpan的計(jì)算 * @param columnsItem 單元格的配置 * @return 單元格的列數(shù) */ function computedHeaderColSpan(columnsItem:columnsType){ if(!columnsItem.children){ return 1 } return flatColumnsItemChildren(columnsItem.children).length } /** * @description 深度遍歷扁平化數(shù)組獲取單元格所占的列數(shù) * @param columnsItem 單元格的配置 * @return 返回扁平化后的數(shù)組 */ function flatColumnsItemChildren(columnsItem:columnsType[]){ // 深度遍歷,扁平化數(shù)組 let node, list = [...columnsItem], nodes = [] while (node = list.shift()) { // 要過濾一下沒有prop的,沒有prop的列不參與最終的寬度計(jì)算 if(node.prop){ nodes.push(node) } node.children && list.unshift(...node.children) } return nodes // 遞歸會(huì)報(bào)警告,占內(nèi)存 // if(columnsItem.length === 0){ // return // } // columnsItem.forEach((item:columnsType)=>{ // if(item.children){ // flatColumnsItemChildren(item.children) // }else{ // flatChildrenList.value.push(item) // } // }) }
實(shí)現(xiàn)效果圖
合并單元格以及單元格放組件
合并單元格稍微簡(jiǎn)單些,只需要把每個(gè)單元格的colspan和rowspan寫成一個(gè)函數(shù)并且暴露出來就能處理
配置
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: '塔號(hào)', 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: '成品產(chǎn)量' }, { prop: 'operatorName', label: '操作人' }, { prop: 'operatorTime', label: '操作時(shí)間' }, ];
主體代碼以及單元格放組件
<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)" > // 動(dòng)態(tài)組件提前寫好組件去渲染對(duì)應(yīng)的組件,此時(shí)的table單元格擴(kuò)展性就變得非常強(qiáng),不 僅可以做展示用,也可以放輸入框,下拉選擇器之類的組件。 <component :is="components[headerItem.type]" :ref="(el:unknown) => setComponentRef(el, headerItem.prop)" :form-item="headerItem" :value="item" ></component> </td> </template> </tr> </tbody>
橫跨的行數(shù)
每個(gè)單元格渲染的時(shí)候,暴露一個(gè)函數(shù)出去,此函數(shù)的返回值有rowspan以及colspan,這樣能準(zhǔn)確的知道渲染每個(gè)單元格時(shí)此單元格占位多少。
/** * @description 計(jì)算單元格rowspan的占位 * @param item 單元格一行的值 * @param index 索引 * @param columns 當(dāng)前的單元格配置 * @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; }
橫跨的列數(shù)
每個(gè)單元格渲染的時(shí)候,暴露一個(gè)函數(shù)出去,此函數(shù)的返回值有rowspan以及colspan,這樣能準(zhǔn)確的知道渲染每個(gè)單元格時(shí)此單元格占位多少。
/** * @description 計(jì)算單元格colspan的占位 * @param item 單元格一行的值 * @param index 索引 * @param columns 當(dāng)前的單元格配置 * @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; }
實(shí)現(xiàn)效果圖
滾動(dòng)條
table自身是響應(yīng)式的,按照一定規(guī)則自動(dòng)去分配寬度和高度的,如果不在table外面包裹一層元素的話,table會(huì)一直自適應(yīng),沒法帶有滾動(dòng)條,我們需要給外層元素設(shè)置一個(gè)寬度或者高度,然后table也設(shè)置一個(gè)固定的寬度或者是高度,這樣內(nèi)部的table就會(huì)在限定的寬度或者高度下具有滾動(dòng)條。
總結(jié)
為了更好的在特定場(chǎng)景去控制table的高寬以及單元格的高寬,我們可以將他們的樣式設(shè)定為動(dòng)態(tài)的,我們可以通過配置去動(dòng)態(tài)的改變他們的樣式。然后就是處理一些無法確認(rèn)層級(jí)的樹形結(jié)構(gòu)數(shù)據(jù),我們也可以不通過遞歸去實(shí)現(xiàn),節(jié)省內(nèi)存。
到此這篇關(guān)于html原生table實(shí)現(xiàn)合并單元格以及合并表頭的示例代碼的文章就介紹到這了,更多相關(guān)html table合并單元格及表頭內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持腳本之家!
相關(guān)文章
使用html5實(shí)現(xiàn)表格實(shí)現(xiàn)標(biāo)題合并的實(shí)例代碼
在前端開發(fā)中經(jīng)常會(huì)遇到標(biāo)題合并的需求,今天小編給大家?guī)砹耸褂胔tml5實(shí)現(xiàn)表格實(shí)現(xiàn)標(biāo)題合并的實(shí)例代碼,感興趣的朋友跟隨小編一起看看吧2019-05-13- 表格是日常常用的工具,很多時(shí)候需要用到單元合并,本文主要介紹了HTML表格合并的具體實(shí)現(xiàn)方式, 具有一定的參考價(jià)值,感興趣的可以了解一下2023-01-05
HTML中table表格拆分合并(colspan、rowspan)
這篇文章主要介紹了HTML中table表格拆分合并(colspan、rowspan),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編2021-04-07- 這篇文章主要介紹了html+css合并表格邊框的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)2021-03-31
- 這篇文章主要介紹了詳解html中表格table的行列合并問題解決,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來2020-07-28