Skip to content

Commit eb51e6e

Browse files
authored
feat(Table): 完成Table组件合并单元格功能 (DevCloudFE#347)
* feat(Table): 使用Rem规范修改Table组件样式命名 * refactor(Table): TD和TH组件从body、header中抽离,解决一个文件定义多个组件的eslint报错 * feat(Table): 完成Table组件合并单元格功能 * feat(Table): 修改函数命名
1 parent 821f1df commit eb51e6e

File tree

4 files changed

+163
-9
lines changed

4 files changed

+163
-9
lines changed

packages/devui-vue/devui/table/src/components/body/body.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { TABLE_TOKEN } from '../../table-types';
33
import TD from '../body-td/body-td';
44
import { Checkbox } from '../../../../checkbox';
55
import { useNamespace } from '../../../../shared/hooks/use-namespace';
6+
import { useMergeCell } from './use-body';
67
import './body.scss';
78

89
export default defineComponent({
@@ -12,8 +13,9 @@ export default defineComponent({
1213
const { _data: data, _columns: columns, _checkList: checkList, isFixedLeft } = table.store.states;
1314
const ns = useNamespace('table');
1415
const hoverEnabled = computed(() => table.props.rowHoveredHighlight);
15-
16+
const { tableSpans, removeCells } = useMergeCell();
1617
const tdAttrs = computed(() => (isFixedLeft.value ? { class: `${ns.m('sticky-cell')} left`, style: 'left:0;' } : null));
18+
1719
const renderCheckbox = (index: number) =>
1820
table.props.checkable ? (
1921
<td {...tdAttrs.value}>
@@ -27,9 +29,15 @@ export default defineComponent({
2729
return (
2830
<tr key={rowIndex} class={{ 'hover-enabled': hoverEnabled.value }}>
2931
{renderCheckbox(rowIndex)}
30-
{columns.value.map((column, index) => (
31-
<TD column={column} index={index} row={row} />
32-
))}
32+
{columns.value.map((column, columnIndex) => {
33+
const cellId = `${rowIndex}-${columnIndex}`;
34+
const [rowspan, colspan] = tableSpans.value[cellId] ?? [1, 1];
35+
36+
if (removeCells.value.includes(cellId)) {
37+
return null;
38+
}
39+
return <TD column={column} index={columnIndex} row={row} rowspan={rowspan} colspan={colspan} />;
40+
})}
3341
</tr>
3442
);
3543
})}
Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,64 @@
1-
import { computed, ComputedRef } from 'vue';
2-
import { Column } from '../column/column-types';
3-
import { TableBodyPropsTypes } from './body-types';
1+
import { inject, computed } from 'vue';
2+
import { TABLE_TOKEN } from '../../table-types';
43

5-
interface Data {
6-
rowColumns: ComputedRef<(Record<string, any> & { columns: Column[] })[]>;
4+
export function useMergeCell() {
5+
const table = inject(TABLE_TOKEN);
6+
const { _data: data, _columns: columns } = table.store.states;
7+
8+
const getSpan = (row: any, column: any, rowIndex: number, columnIndex: number) => {
9+
const fn = table?.props.spanMethod;
10+
let rowspan = 1;
11+
let colspan = 1;
12+
13+
if (typeof fn === 'function') {
14+
const result = fn({ row, column, rowIndex, columnIndex });
15+
16+
if (Array.isArray(result)) {
17+
rowspan = result[0];
18+
colspan = result[1];
19+
} else if (typeof result === 'object') {
20+
rowspan = result.rowspan;
21+
colspan = result.colspan;
22+
}
23+
}
24+
25+
return { rowspan, colspan };
26+
};
27+
28+
const tableSpans = computed(() => {
29+
const result: Record<string, [number, number]> = {};
30+
31+
if (table?.props.spanMethod) {
32+
data.value.forEach((row, rowIndex) => {
33+
columns.value.forEach((column, columnIndex) => {
34+
const { rowspan, colspan } = getSpan(row, column, rowIndex, columnIndex);
35+
if (rowspan > 1 || colspan > 1) {
36+
result[`${rowIndex}-${columnIndex}`] = [rowspan, colspan];
37+
}
38+
});
39+
});
40+
}
41+
42+
return result;
43+
});
44+
45+
const removeCells = computed(() => {
46+
const result: string[] = [];
47+
for (const indexKey of Object.keys(tableSpans.value)) {
48+
const indexArray = indexKey.split('-').map((item) => Number(item));
49+
const spans = tableSpans.value[indexKey];
50+
for (let i = 1; i < spans[0]; i++) {
51+
result.push(`${indexArray[0] + i}-${indexArray[1]}`);
52+
for (let j = 1; j < spans[1]; j++) {
53+
result.push(`${indexArray[0] + i}-${indexArray[1] + j}`);
54+
}
55+
}
56+
for (let i = 1; i < spans[1]; i++) {
57+
result.push(`${indexArray[0]}-${indexArray[1] + i}`);
58+
}
59+
}
60+
return result;
61+
});
62+
63+
return { tableSpans, removeCells };
764
}

packages/devui-vue/devui/table/src/table-types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ import { TableStore } from './store';
33

44
export type TableSize = 'sm' | 'md' | 'lg';
55

6+
export type SpanMethod = (data: {
7+
row: any;
8+
column: any;
9+
rowIndex: number;
10+
columnIndex: number;
11+
}) => number[] | { rowspan: number; colspan: number };
12+
613
export const TableProps = {
714
data: {
815
type: Array as PropType<Record<string, any>[]>,
@@ -61,6 +68,9 @@ export const TableProps = {
6168
type: Boolean,
6269
default: false,
6370
},
71+
spanMethod: {
72+
type: Function as PropType<SpanMethod>,
73+
},
6474
};
6575

6676
export type TablePropsTypes = ExtractPropTypes<typeof TableProps>;

packages/devui-vue/docs/components/table/index.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,71 @@ export default defineComponent({
302302

303303
:::
304304

305+
### 合并单元格
306+
307+
:::demo 通过`span-method`方法可以自定义合并单元格,方法参数是一个对象,对象包含属性如下:当前行`row`、当前列`column`、当前行索引`rowIndex`、当前列索引`columnIndex`。该方法可以返回包含两个元素的数组,第一个元素是`rowspan`,第二个元素是`colspan`;也可以返回一个对象,属性为`rowspan``colspan`
308+
309+
```vue
310+
<template>
311+
<d-table :data="baseTableData" :span-method="spanMethod">
312+
<d-column field="firstName" header="First Name"></d-column>
313+
<d-column field="lastName" header="Last Name"></d-column>
314+
<d-column field="gender" header="Gender"></d-column>
315+
<d-column field="date" header="Date of birth"></d-column>
316+
</d-table>
317+
</template>
318+
319+
<script>
320+
import { defineComponent, ref } from 'vue';
321+
322+
export default defineComponent({
323+
setup() {
324+
const baseTableData = ref([
325+
{
326+
firstName: 'Mark',
327+
lastName: 'Otto',
328+
date: '1990/01/11',
329+
gender: 'Male',
330+
},
331+
{
332+
firstName: 'Jacob',
333+
lastName: 'Thornton',
334+
gender: 'Female',
335+
date: '1990/01/12',
336+
},
337+
{
338+
firstName: 'Danni',
339+
lastName: 'Chen',
340+
gender: 'Male',
341+
date: '1990/01/13',
342+
},
343+
{
344+
firstName: 'green',
345+
lastName: 'gerong',
346+
gender: 'Male',
347+
date: '1990/01/14',
348+
},
349+
]);
350+
const spanMethod = ({ row, column, rowIndex, columnIndex }) => {
351+
if (rowIndex === 0 && columnIndex === 0) {
352+
return { rowspan: 1, colspan: 2 };
353+
}
354+
if (rowIndex === 2 && columnIndex === 0) {
355+
return [2, 2];
356+
}
357+
if (rowIndex === 2 && columnIndex === 3) {
358+
return [2, 1];
359+
}
360+
};
361+
362+
return { baseTableData, spanMethod };
363+
},
364+
});
365+
</script>
366+
```
367+
368+
:::
369+
305370
### d-table 参数
306371

307372
| 参数 | 类型 | 默认值 | 说明 |
@@ -318,6 +383,7 @@ export default defineComponent({
318383
| show-loading | `Boolean` | false | 显示加载动画 |
319384
| header-bg | `Boolean` | false | 头部背景 |
320385
| table-layout | `'fixed' \| 'auto'` | 'fixed' | 表格布局,可选值为'auto' |
386+
| span-method | `SpanMethod` | -- | 合并单元格的计算方法 |
321387

322388
### d-column 参数
323389

@@ -336,3 +402,16 @@ export default defineComponent({
336402
| 名称 | 说明 |
337403
| :------ | :--------------------- |
338404
| default | 默认插槽,自定义列内容 |
405+
406+
### 类型定义
407+
408+
#### SpanMethod
409+
410+
```typescript
411+
type SpanMethod = (data: {
412+
row: any;
413+
column: any;
414+
rowIndex: number;
415+
columnIndex: number;
416+
}) => number[] | { rowspan: number; colspan: number };
417+
```

0 commit comments

Comments
 (0)