Skip to content

Commit 6026664

Browse files
authoredMar 16, 2022
feat(Table): Table 新增树形数据结构展示功能 #539 (#679)
1 parent 1f4d321 commit 6026664

File tree

4 files changed

+269
-85
lines changed

4 files changed

+269
-85
lines changed
 

‎packages/react-table/README.md

+83
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,87 @@ const Demo = () => (
724724
ReactDOM.render(<Demo />, _mount_);
725725
```
726726

727+
### 树形数据展示
728+
729+
表格支持树形数据的展示,当数据中有 children 字段时会自动展示为树形表格,如果不需要或配置为其他字段可以用 childrenColumnName 进行配置。
730+
731+
可以通过设置 indentSize 以控制每一层的缩进宽度
732+
733+
> ⚠️ 注意: 树形数据展示和`expandable.expandedRowRender`请不要同时出现,后续或将支持
734+
<!--rehype:style=border-left: 8px solid #ffe564;background-color: #ffe56440;padding: 12px 16px;-->
735+
736+
<!--rehype:bgWhite=true&codeSandbox=true&codePen=true-->
737+
```jsx
738+
import ReactDOM from 'react-dom';
739+
import React from 'react';
740+
import { Table, Button, Icon } from 'uiw';
741+
742+
const columns = [
743+
{
744+
title: '姓名',
745+
ellipsis: true,
746+
key: 'name',
747+
},
748+
{
749+
title: '年龄',
750+
style: { color: 'red' },
751+
key: 'age',
752+
},
753+
{
754+
title: '操作',
755+
key: 'edit',
756+
width: 98,
757+
render: (text, key, rowData, rowNumber, columnNumber) => (
758+
<div>
759+
<Button size="small" type="danger">删除</Button>
760+
<Button size="small" type="success">修改</Button>
761+
</div>
762+
),
763+
},
764+
];
765+
const dataSource = [
766+
{
767+
name: '邓紫棋',
768+
age: '10',
769+
id: '1',
770+
children: [
771+
{
772+
name: '邓紫棋-0-1',
773+
age: '10',
774+
id: '1-1',
775+
children: [
776+
{ name: '邓紫棋-0-1-1', age: '10', id: '1-1-1',},
777+
{ name: '邓紫棋-0-1-2', age: '10', id: '1-1-2',}
778+
]
779+
},
780+
{name: '邓紫棋-0-2', age: '10', id: '1-1'},
781+
{name: '邓紫棋-0-3', age: '10', id: '1-1'},
782+
]
783+
},
784+
{ name: '李易峰', age: '32', id: '2',},
785+
{ name: '范冰冰', age: '23', id: '3',
786+
children: [
787+
{name: '范冰冰0-1', age: '23', id: '3-1'},
788+
{name: '范冰冰0-2', age: '23', id: '3-2'},
789+
{name: '范冰冰0-3', age: '23', id: '3-3'},
790+
]
791+
},
792+
];
793+
const Demo = () => {
794+
const [expandedRowKeys, setExpandedRowKeys] = React.useState([])
795+
return (
796+
<div>
797+
<Table
798+
rowKey="id"
799+
columns={columns}
800+
data={dataSource}
801+
/>
802+
</div>
803+
)
804+
};
805+
ReactDOM.render(<Demo />, _mount_);
806+
```
807+
727808
## Props
728809

729810
### Table
@@ -768,3 +849,5 @@ ReactDOM.render(<Demo />, _mount_);
768849
| expandedRowKeys | 控制展开的行 rowKey数组 | Array | - |
769850
| onExpandedRowsChange | 展开的行变化触发 | (expandedRows)=>void | - |
770851
| onExpand | 点击展开图标触发 | (expanded,record,index)=>void | - |
852+
| indentSize | 控制树形结构每一层的缩进宽度 | number | 16 |
853+
| childrenColumnName | 指定树形结构的列名 | string | children |

‎packages/react-table/src/TableTr.tsx

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import React, { useMemo, useState } from 'react';
2+
import Icon from '@uiw/react-icon';
3+
import { TableProps } from './';
4+
import './style/index.less';
5+
import { noop } from '@uiw/utils';
6+
7+
interface TableTrProps<T> {
8+
rowKey?: keyof T;
9+
data: T[];
10+
keys: string[];
11+
render: { [key: string]: any };
12+
ellipsis?: Record<string, boolean>;
13+
prefixCls: string;
14+
onCell: TableProps['onCell'];
15+
isExpandedDom: (record: T, index: number) => false | JSX.Element;
16+
// 控制树形结构每一层的缩进宽度
17+
indentSize: number;
18+
// 层级
19+
hierarchy: number;
20+
childrenColumnName: string;
21+
}
22+
23+
export default function TableTr<T extends { [key: string]: any }>(props: TableTrProps<T>) {
24+
const {
25+
rowKey,
26+
data,
27+
keys,
28+
render,
29+
ellipsis,
30+
prefixCls,
31+
onCell = noop,
32+
isExpandedDom,
33+
hierarchy,
34+
indentSize,
35+
childrenColumnName,
36+
} = props;
37+
38+
const [expandIndex, setExpandIndex] = useState<Array<T[keyof T] | number>>([]);
39+
40+
const IconDom = useMemo(() => {
41+
return (key: T[keyof T] | number, isOpacity: boolean) => {
42+
const flag = expandIndex.includes(key);
43+
return (
44+
<Icon
45+
type={flag ? 'minus-square-o' : 'plus-square-o'}
46+
style={{ marginRight: 10, opacity: isOpacity ? 1 : 0, marginLeft: hierarchy * indentSize }}
47+
onClick={() => {
48+
setExpandIndex(flag ? expandIndex.filter((it) => it !== key) : [...expandIndex, key]);
49+
}}
50+
/>
51+
);
52+
};
53+
}, [expandIndex]);
54+
if (!Array.isArray(data) || !data.length) {
55+
return null;
56+
}
57+
return (
58+
<React.Fragment>
59+
{data.map((trData, rowNum) => {
60+
const key = rowKey ? trData[rowKey] : rowNum;
61+
return (
62+
<React.Fragment key={rowNum}>
63+
<tr key={key}>
64+
{keys.map((keyName, colNum) => {
65+
let objs: React.TdHTMLAttributes<HTMLTableDataCellElement> = {
66+
children: trData[keyName],
67+
};
68+
if (render[keyName]) {
69+
const child = render[keyName](trData[keyName], keyName, trData, rowNum, colNum);
70+
if (React.isValidElement(child)) {
71+
objs.children = child;
72+
} else {
73+
if (child.props) {
74+
objs = { ...child.props, children: objs.children };
75+
if (child.props.rowSpan === 0 || child.props.colSpan === 0) return null;
76+
}
77+
if (child.children) {
78+
objs.children = child.children;
79+
}
80+
}
81+
}
82+
if (ellipsis && ellipsis[keyName]) {
83+
objs.className = `${prefixCls}-ellipsis`;
84+
}
85+
const isHasChildren = Array.isArray(trData[childrenColumnName]);
86+
if (colNum === 0 && (hierarchy || isHasChildren)) {
87+
objs.className = `${objs.className} ${prefixCls}-has-children`;
88+
objs.children = (
89+
<>
90+
{IconDom(key, isHasChildren)}
91+
{objs.children}
92+
</>
93+
);
94+
}
95+
return (
96+
<td {...objs} key={colNum} onClick={(evn) => onCell(trData, { rowNum, colNum, keyName }, evn)} />
97+
);
98+
})}
99+
</tr>
100+
{expandIndex.includes(key) && (
101+
<TableTr {...props} data={trData[childrenColumnName]} hierarchy={hierarchy + 1} />
102+
)}
103+
{isExpandedDom(trData, rowNum)}
104+
</React.Fragment>
105+
);
106+
})}
107+
</React.Fragment>
108+
);
109+
}

‎packages/react-table/src/index.tsx

+73-85
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Icon from '@uiw/react-icon';
44
import Thead from './Thead';
55
import { getLevelItems, getAllColumnsKeys } from './util';
66
import ExpandableComponent from './Expandable';
7+
import TableTr from './TableTr';
78
import './style/index.less';
89

910
// 展开配置
@@ -16,14 +17,18 @@ export interface ExpandableType<T> {
1617
rowExpandable?: (record: T) => boolean;
1718
// 初始时,是否展开所有行
1819
defaultExpandAllRows?: boolean;
19-
// 默认展开的行 rowKey数组
20+
// 默认展开的行 rowKey数组
2021
defaultExpandedRowKeys?: Array<T[keyof T] | number>;
21-
// 控制展开的行 rowKey数组
22+
// 控制展开的行 rowKey数组
2223
expandedRowKeys?: Array<T[keyof T] | number>;
2324
// 展开的行变化触发
2425
onExpandedRowsChange?: (expandedRows: Array<T[keyof T] | number>) => void;
2526
// 点击展开图标触发
2627
onExpand?: (expanded: boolean, record: T, index: number) => void;
28+
// 控制树形结构每一层的缩进宽度
29+
indentSize?: number;
30+
// 指定树形结构的列名
31+
childrenColumnName?: string;
2732
}
2833

2934
export type TableColumns<T = any> = {
@@ -127,54 +132,62 @@ export default function Table<T extends { [key: string]: V }, V>(props: TablePro
127132
);
128133
};
129134
}, [expandable, expandIndex]);
130-
let keys = getAllColumnsKeys<T>(columns);
131-
let selfColumns: TableColumns<T>[] = [];
132-
if (expandable?.expandedRowRender) {
133-
keys = ['uiw-expanded', ...keys];
134-
selfColumns = [
135-
{
136-
title: '',
137-
key: 'uiw-expanded',
138-
width: 50,
139-
align: 'center',
140-
render: (text, key, record, index) => {
141-
return (
142-
<ExpandableComponent
143-
defaultExpand={
144-
expandable.defaultExpandAllRows === undefined
145-
? !!expandable.defaultExpandedRowKeys?.includes(rowKey ? record[rowKey] : index)
146-
: !!expandable.defaultExpandAllRows
147-
}
148-
onClick={(expand) => {
149-
expandable.onExpand?.(expand, record, index);
150-
if (expand) {
151-
const result = expandIndex.filter((it) => (rowKey ? it !== record[rowKey] : it !== index));
152-
expandable.onExpandedRowsChange ? expandable.onExpandedRowsChange(result) : setExpandIndex(result);
153-
} else {
154-
const result = [...expandIndex, rowKey ? record[rowKey] : index];
155-
expandable.onExpandedRowsChange ? expandable.onExpandedRowsChange(result) : setExpandIndex(result);
156-
}
157-
}}
158-
expandIcon={(expand) => {
159-
if (expandable.rowExpandable && !expandable.rowExpandable?.(record)) {
160-
return null;
161-
}
162-
if (expandable.expandIcon) {
163-
return expandable.expandIcon(expand, record, index);
135+
136+
const self = useMemo(() => {
137+
let keys = getAllColumnsKeys<T>(columns);
138+
let selfColumns: TableColumns<T>[] = [];
139+
if (expandable?.expandedRowRender) {
140+
keys = ['uiw-expanded', ...keys];
141+
selfColumns = [
142+
{
143+
title: '',
144+
key: 'uiw-expanded',
145+
width: 50,
146+
align: 'center',
147+
render: (text, key, record, index) => {
148+
return (
149+
<ExpandableComponent
150+
defaultExpand={
151+
expandable.defaultExpandAllRows === undefined
152+
? !!expandable.defaultExpandedRowKeys?.includes(rowKey ? record[rowKey] : index)
153+
: !!expandable.defaultExpandAllRows
164154
}
165-
return expand ? <Icon type="minus-square-o" /> : <Icon type="plus-square-o" />;
166-
}}
167-
/>
168-
);
155+
onClick={(expand) => {
156+
expandable.onExpand?.(expand, record, index);
157+
if (expand) {
158+
const result = expandIndex.filter((it) => (rowKey ? it !== record[rowKey] : it !== index));
159+
expandable.onExpandedRowsChange ? expandable.onExpandedRowsChange(result) : setExpandIndex(result);
160+
} else {
161+
const result = [...expandIndex, rowKey ? record[rowKey] : index];
162+
expandable.onExpandedRowsChange ? expandable.onExpandedRowsChange(result) : setExpandIndex(result);
163+
}
164+
}}
165+
expandIcon={(expand) => {
166+
if (expandable.rowExpandable && !expandable.rowExpandable?.(record)) {
167+
return null;
168+
}
169+
if (expandable.expandIcon) {
170+
return expandable.expandIcon(expand, record, index);
171+
}
172+
return expand ? <Icon type="minus-square-o" /> : <Icon type="plus-square-o" />;
173+
}}
174+
/>
175+
);
176+
},
169177
},
170-
},
171-
...columns,
172-
];
173-
} else {
174-
selfColumns = [...columns];
175-
}
178+
...columns,
179+
];
180+
} else {
181+
selfColumns = [...columns];
182+
}
183+
return {
184+
keys,
185+
selfColumns,
186+
};
187+
}, [columns, expandIndex]);
188+
176189
const cls = [prefixCls, className, bordered ? `${prefixCls}-bordered` : null].filter(Boolean).join(' ').trim();
177-
const { header, render, ellipsis } = getLevelItems(selfColumns);
190+
const { header, render, ellipsis } = getLevelItems(self.selfColumns);
178191

179192
return (
180193
<div>
@@ -184,44 +197,19 @@ export default function Table<T extends { [key: string]: V }, V>(props: TablePro
184197
{columns && columns.length > 0 && <Thead onCellHead={onCellHead} data={header} />}
185198
{data && data.length > 0 && (
186199
<tbody>
187-
{data.map((trData, rowNum) => {
188-
return (
189-
<React.Fragment key={rowNum}>
190-
<tr key={rowKey ? trData[rowKey] + '' : rowNum}>
191-
{keys.map((keyName, colNum) => {
192-
let objs: React.TdHTMLAttributes<HTMLTableDataCellElement> = {
193-
children: trData[keyName],
194-
};
195-
if (render[keyName]) {
196-
const child = render[keyName](trData[keyName], keyName, trData, rowNum, colNum);
197-
if (React.isValidElement(child)) {
198-
objs.children = child;
199-
} else {
200-
if (child.props) {
201-
objs = { ...child.props, children: objs.children };
202-
if (child.props.rowSpan === 0 || child.props.colSpan === 0) return null;
203-
}
204-
if (child.children) {
205-
objs.children = child.children;
206-
}
207-
}
208-
}
209-
if (ellipsis && ellipsis[keyName]) {
210-
objs.className = `${prefixCls}-ellipsis`;
211-
}
212-
return (
213-
<td
214-
{...objs}
215-
key={colNum}
216-
onClick={(evn) => onCell(trData, { rowNum, colNum, keyName }, evn)}
217-
/>
218-
);
219-
})}
220-
</tr>
221-
{isExpandedDom(trData, rowNum)}
222-
</React.Fragment>
223-
);
224-
})}
200+
<TableTr
201+
rowKey={rowKey}
202+
data={data}
203+
keys={self.keys}
204+
render={render}
205+
ellipsis={ellipsis}
206+
prefixCls={prefixCls}
207+
onCell={onCell}
208+
hierarchy={0}
209+
isExpandedDom={isExpandedDom}
210+
indentSize={expandable?.indentSize || 16}
211+
childrenColumnName={expandable?.childrenColumnName || 'children'}
212+
/>
225213
</tbody>
226214
)}
227215
{data && data.length === 0 && empty && (

0 commit comments

Comments
 (0)
Please sign in to comment.