title | date | tags | ||
---|---|---|---|---|
动态表头生成算法 |
2017-09-18 |
|
最近接了个需求,需要根据数据结构动态的生成表格。会出现嵌套的表头。
在我的印象中,antd
的 Table
实现了这样的功能。然后我就看了它的数据结构,类似这样的
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
},
{
title: 'Other',
children: [
{
title: 'Age',
dataIndex: 'age',
key: 'age',
},
{
title: 'Address',
children: [
{
title: 'Street',
dataIndex: 'street',
key: 'street',
},
{
title: 'Block',
children: [
{
title: 'Building',
dataIndex: 'building',
key: 'building',
},
{
title: 'Door No.',
dataIndex: 'number',
key: 'number',
},
],
},
],
},
],
},
{
title: 'Company',
children: [
{
title: 'Company Address',
dataIndex: 'companyAddress',
key: 'companyAddress',
},
{
title: 'Company Name',
dataIndex: 'companyName',
key: 'companyName',
},
],
},
{
title: 'Gender',
dataIndex: 'gender',
key: 'gender',
},
];
const dataSourse = [{
key: i,
name: 'John Brown',
age: i + 1,
street: 'Lake Park',
building: 'C',
number: 2035,
companyAddress: 'Lake Street 42',
companyName: 'SoftLake Co',
gender: 'M',
}, {
key: i,
name: 'John Brown',
age: i + 1,
street: 'Lake Park',
building: 'C',
number: 2035,
companyAddress: 'Lake Street 42',
companyName: 'SoftLake Co',
gender: 'M',
}, {
key: i,
name: 'John Brown',
age: i + 1,
street: 'Lake Park',
building: 'C',
number: 2035,
companyAddress: 'Lake Street 42',
companyName: 'SoftLake Co',
gender: 'M',
}];
在antd的官网上,生成了这样的示例。
Name | Other | Company | Gender | ||||
---|---|---|---|---|---|---|---|
Age | Address | Company Address | Company Name | ||||
Street | Block | ||||||
Building | Door No. | ||||||
John Brown | 1 | Lake Park | C | 2035 | Lake Street 42 | SoftLake Co | M |
John Brown | 2 | Lake Park | C | 2035 | Lake Street 42 | SoftLake Co | M |
John Brown | 2 | Lake Park | C | 2035 | Lake Street 42 | SoftLake Co | M |
经过研究,它的html是这样的
<table class="T-table" border="1px solid #dcdcdc">
<thead>
<tr>
<th rowspan="4">Name</th><th colspan="4">Other</th>
<th colspan="2">Company</th><th rowspan="4">Gender</th>
</tr>
<tr>
<th rowspan="3">Age</th><th colspan="3">Address</th>
<th rowspan="3">Company Address</th>
<th rowspan="3">Company Name</th>
</tr>
<tr>
<th rowspan="2">Street</th><th colspan="2">Block</th>
</tr>
<tr>
<th rowspan="1">Building</th>
<th rowspan="1">Door No.</th>
</tr>
</thead>
<tbody>
<tr>
<td>John Brown</td><td>1</td>
<td>Lake Park</td><td>C</td>
<td>2035</td>
<td>Lake Street 42</td><td>SoftLake Co</td>
<td>M</td>
</tr>
<tr>
<td>John Brown</td>
<td>2</td>
<td>Lake Park</td>
<td>C</td>
<td>2035</td>
<td>Lake Street 42</td>
<td>SoftLake Co</td>
<td>M</td>
</tr>
<tr>
<td>John Brown</td>
<td>2</td>
<td>Lake Park</td>
<td>C</td>
<td>2035</td>
<td>Lake Street 42</td>
<td>SoftLake Co</td>
<td>M</td>
</tr>
</tbody>
</table>
接下来,我就要用自己的算法来实现这个表头。保证表头的嵌套和数据的对号入座。
因为是React项目,如果要写jsx是一定要引入React的,不然就会报错。
// (求大佬帮忙改进)
import React from 'react';
// 表的最大深度
let max = 0;
// 节点的最大children数
let maxColSpan = 0;
// 放置格式化后的表头节点
export const stack = {};
// 只与dataIndex相关的key
export const key = [];
// 放置每个节点的层级
const level = {};
// 将节点归类,比如,第一层或者只有一层的为一类,第二层的为一类
const tr = {};
/**
* 获取表的最大深度,顺便生成key和level
* @param item
* @param length
*/
function getMax(item, length) {
if (item.children) {
item.children.forEach((i) => {
getMax(i, length + 1);
});
} else {
max = max < length ? length : max;
key.push(item.title);
}
level[item.title] = length;
}
/**
* 获取节点下面最底部的节点个数
* @param item
* @param length
*/
function getChildrenMaxLength(item, length) {
if (item.children) {
item.children.forEach((i) => {
getChildrenMaxLength(i, (length + item.children.length) - 1);
});
} else {
maxColSpan = maxColSpan > length ? maxColSpan : length;
}
}
/**
* 生成节点的colSpan和rowSpan
* @param item
*/
function getSpan(item) {
if (item.children) {
maxColSpan = 0;
getChildrenMaxLength(item, 1);
stack[item.title] = {
...item,
colSpan: maxColSpan,
};
item.children.forEach((i) => {
getSpan(i);
});
} else {
stack[item.title] = {
...item,
rowSpan: (max + 1) - level[item.title],
};
}
}
export default (columns) => {
columns.forEach((col) => {
getMax(col, 1);
});
columns.forEach((col) => {
getSpan(col);
});
// 根据根据节点的层级,进行分类
Object.keys(level).forEach((item) => {
if (!tr[level[item]]) {
tr[level[item]] = [];
}
tr[level[item]].push(item);
});
// 生成表头
return Object.values(tr).map((item, index) => <tr key={`T-tHead-tr-${index}`}>
{
item.map((it, ind) => {
const th = stack[it];
return (<th
key={`T-tHead-th-${ind}`}
colSpan={th.colSpan}
rowSpan={th.rowSpan}
>
{th.title}
</th>);
})
}
</tr>);
};
在使用之时,就能生成对应的表头了。