Skip to content

Commit 5d1d047

Browse files
authoredMar 15, 2022
feat(Table): Table组件添加可展开属性 (#664)
1 parent d894734 commit 5d1d047

File tree

7 files changed

+260
-40
lines changed

7 files changed

+260
-40
lines changed
 

‎packages/react-table/README.md

+73
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,62 @@ const Demo = () => (
668668
ReactDOM.render(<Demo />, _mount_);
669669
```
670670

671+
### 可展开
672+
673+
<!--rehype:bgWhite=true&codeSandbox=true&codePen=true-->
674+
```jsx
675+
import ReactDOM from 'react-dom';
676+
import { Table, Button, Icon } from 'uiw';
677+
678+
const columns = [
679+
{
680+
title: '姓名',
681+
ellipsis: true,
682+
key: 'name',
683+
},
684+
{
685+
title: '年龄',
686+
style: { color: 'red' },
687+
key: 'age',
688+
},
689+
{
690+
title: '操作',
691+
key: 'edit',
692+
width: 98,
693+
render: (text, key, rowData, rowNumber, columnNumber) => (
694+
<div>
695+
<Button size="small" type="danger">删除</Button>
696+
<Button size="small" type="success">修改</Button>
697+
</div>
698+
),
699+
},
700+
];
701+
const dataSource = [
702+
{ name: '邓紫棋', age: '10', id: '1'},
703+
{ name: '李易峰', age: '32', id: '2'},
704+
{ name: '范冰冰', age: '23', id: '3'},
705+
];
706+
const Demo = () => (
707+
<div>
708+
<Table
709+
rowKey="id"
710+
expandable={{
711+
expandedRowRender: (record, index, expanded)=>{
712+
return <div>{record.name}</div>
713+
},
714+
// defaultExpandAllRows: true,
715+
rowExpandable: (r)=>r.name!=='李易峰',
716+
expandIcon: (expanded) => expanded ? <Icon type="minus-circle-o"/> : <Icon type="plus-circle-o"/>,
717+
defaultExpandedRowKeys: ["1"]
718+
}}
719+
columns={columns}
720+
data={dataSource}
721+
/>
722+
</div>
723+
);
724+
ReactDOM.render(<Demo />, _mount_);
725+
```
726+
671727
## Props
672728

673729
### Table
@@ -682,6 +738,8 @@ ReactDOM.render(<Demo />, _mount_);
682738
| empty | 无数据状态 | ReactNode | - |
683739
| onCellHead | 表头单元格点击回调 | ~~`Function(text, key, rowData, rowNumber, columnNumber)`~~ /<br/> Function(data: IColumns, colNum: number, rowNum: number, evn: React.MouseEvent<HTMLTableCellElement\>) `@3.0.0+` | - |
684740
| onCell | 单元格点击回调 | ~~`Function(text, key, rowData, rowNumber, columnNumber)`~~ /<br/> Function(data: IColumns, options:{ colNum: number, rowNum: number, keyName: string }, evn: React.MouseEvent<HTMLTableCellElement\>) `@3.1.0+` | - |
741+
| expandable | 可展开配置 | ExpandableType | - |
742+
| rowKey | 表格行 key 的取值 | string | - |
685743

686744
### ColumnProps
687745

@@ -695,3 +753,18 @@ ReactDOM.render(<Demo />, _mount_);
695753
| colSpan | 合并表头行。| Number | - |
696754
| ellipsis | 超过宽度将自动省略。`v4.8.7+`| Boolean | `false` |
697755
| render | 生成复杂数据的渲染函数,参数分别为当前行的值,当前值的 `key`,行索引数据,当前行号,当前列号。| `Function(text, key, rowData, rowNumber, columnNumber)` | - |
756+
757+
### expandable
758+
759+
注意 expandedRowKeys 与 onExpandedRowsChange 必须成对出现
760+
761+
| 参数 | 说明 | 类型 | 默认值 |
762+
|--------- |-------- |--------- |-------- |
763+
| expandedRowRender | 自定义展开行| (record, index, expanded) => React.ReactNode | - |
764+
| expandIcon | 自定义图标 | (expanded, record, index) => React.ReactNode; | - |
765+
| rowExpandable | 是否允许展开| (record)=>boolean | - |
766+
| defaultExpandAllRows | 初始时,是否展开所有行| boolean | false |
767+
| defaultExpandedRowKeys | 初始时,默认展开的行 rowKey数组 | Array | - |
768+
| expandedRowKeys | 控制展开的行 rowKey数组 | Array | - |
769+
| onExpandedRowsChange | 展开的行变化触发 | (expandedRows)=>void | - |
770+
| onExpand | 点击展开图标触发 | (expanded,record,index)=>void | - |

‎packages/react-table/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"react-dom": ">=16.9.0"
4545
},
4646
"dependencies": {
47+
"@uiw/react-icon": "^4.13.11",
4748
"@uiw/utils": "^4.13.12"
4849
}
4950
}
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React, {useState} from 'react';
2+
3+
interface ExpandableProps<T> {
4+
onClick: (expand: boolean)=>void;
5+
expandIcon: (expanded: boolean) => React.ReactNode,
6+
defaultExpand: boolean,
7+
}
8+
/**
9+
* 可展开配置
10+
*/
11+
export default function ExpandableComponent<T>({defaultExpand,onClick, expandIcon}: ExpandableProps<T>) {
12+
const [ expand, setExpand ] = useState<boolean>(defaultExpand)
13+
return <div
14+
style={{display: 'flex',justifyContent:'center',alignItems:'center'}}
15+
onClick={()=>{
16+
setExpand(!expand)
17+
onClick(expand)
18+
// record: T, index: number, expanded: boolean
19+
}}
20+
children={expandIcon(expand)}
21+
/>
22+
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { TableProps, TableColumns } from './';
44
import './style/index.less';
55

66
export interface TheadProps<T extends { [key: string]: V }, V = any> extends IProps {
7-
data?: TableColumns<T, V>[][];
7+
data?: TableColumns<T>[][];
88
onCellHead?: TableProps<T, V>['onCellHead'];
99
}
1010

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

+160-37
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,46 @@
1-
import React from 'react';
1+
import React, { useMemo, useState, useEffect } from 'react';
22
import { IProps, HTMLDivProps, noop } from '@uiw/utils';
3+
import Icon from '@uiw/react-icon';
34
import Thead from './Thead';
45
import { getLevelItems, getAllColumnsKeys } from './util';
6+
import ExpandableComponent from './Expandable';
57
import './style/index.less';
68

7-
export type TableColumns<T = any, V = any> = {
8-
title?: string | ((data: TableColumns<T, V>, rowNum: number, colNum: number) => JSX.Element) | JSX.Element;
9+
// 展开配置
10+
export interface ExpandableType<T> {
11+
// 展开行
12+
expandedRowRender?: (record: T, index: number, expanded: boolean) => React.ReactNode;
13+
// 自定义图标
14+
expandIcon?: (expanded: boolean, record: T, index: number) => React.ReactNode;
15+
// 是否允许展开
16+
rowExpandable?: (record: T) => boolean;
17+
// 初始时,是否展开所有行
18+
defaultExpandAllRows?: boolean;
19+
// 默认展开的行 rowKey数组
20+
defaultExpandedRowKeys?: Array<T[keyof T] | number>;
21+
// 控制展开的行 rowKey数组
22+
expandedRowKeys?: Array<T[keyof T] | number>;
23+
// 展开的行变化触发
24+
onExpandedRowsChange?: (expandedRows: Array<T[keyof T] | number>) => void;
25+
// 点击展开图标触发
26+
onExpand?: (expanded: boolean, record: T, index: number) => void;
27+
}
28+
29+
export type TableColumns<T = any> = {
30+
title?: string | ((data: TableColumns<T>, rowNum: number, colNum: number) => JSX.Element) | JSX.Element;
931
key?: string;
1032
width?: number;
1133
colSpan?: number;
12-
children?: TableColumns<T, V>[];
34+
children?: TableColumns<T>[];
1335
ellipsis?: boolean;
14-
render?: (text: V, keyName: V, rowData: T, rowNumber: number, columnNumber: number) => React.ReactNode;
36+
render?: (text: string, keyName: string, rowData: T, rowNumber: number, columnNumber: number) => React.ReactNode;
1537
style?: React.CSSProperties;
1638
[key: string]: any;
1739
};
1840

1941
export interface TableProps<T extends { [key: string]: V } = any, V = any> extends IProps, Omit<HTMLDivProps, 'title'> {
2042
prefixCls?: string;
21-
columns?: TableColumns<T, V>[];
43+
columns?: TableColumns<T>[];
2244
data?: Array<T>;
2345
title?: React.ReactNode;
2446
footer?: React.ReactNode;
@@ -30,19 +52,20 @@ export interface TableProps<T extends { [key: string]: V } = any, V = any> exten
3052
evn: React.MouseEvent<HTMLTableCellElement>,
3153
) => void | React.ReactNode;
3254
onCellHead?: (
33-
data: TableColumns<T, V>,
55+
data: TableColumns<T>,
3456
rowNum: number,
3557
colNum: number,
3658
evn: React.MouseEvent<HTMLTableCellElement>,
3759
) => void;
60+
expandable?: ExpandableType<T>;
61+
rowKey?: keyof T;
3862
}
3963

4064
export interface ICellOptions {
4165
rowNum: number;
4266
colNum: number;
4367
keyName: string;
4468
}
45-
4669
export default function Table<T extends { [key: string]: V }, V>(props: TableProps<T, V> = {}) {
4770
const {
4871
prefixCls = 'w-table',
@@ -56,12 +79,103 @@ export default function Table<T extends { [key: string]: V }, V>(props: TablePro
5679
onCellHead = noop,
5780
empty,
5881
children,
82+
expandable,
83+
rowKey,
5984
...other
6085
} = props;
86+
const [expandIndex, setExpandIndex] = useState<Array<T[keyof T] | number>>([]);
87+
useEffect(() => {
88+
if (expandable) {
89+
if (expandable.defaultExpandAllRows) {
90+
setExpandIndex(data.map((it, index) => (rowKey ? it[rowKey] : index)));
91+
return;
92+
}
93+
if (expandable.defaultExpandedRowKeys) {
94+
setExpandIndex(expandable.defaultExpandedRowKeys);
95+
return;
96+
}
97+
}
98+
}, []);
99+
useEffect(() => {
100+
if (expandable) {
101+
if (expandable.expandedRowKeys && JSON.stringify(expandable.expandedRowKeys) !== JSON.stringify(expandIndex)) {
102+
setExpandIndex(expandable.expandedRowKeys);
103+
}
104+
}
105+
}, [expandable?.expandedRowKeys]);
61106

107+
const isExpandedDom = useMemo(() => {
108+
return (record: T, index: number) => {
109+
if (!expandable) {
110+
return false;
111+
}
112+
if (!expandable.expandedRowRender) {
113+
return false;
114+
}
115+
let flag = true;
116+
if (expandable.rowExpandable) {
117+
flag = expandable.rowExpandable(record);
118+
}
119+
return (
120+
flag && (
121+
<tr style={expandIndex.includes(rowKey ? record[rowKey] : index) ? {} : { display: 'none' }}>
122+
<td style={{ paddingLeft: 16 }} colSpan={columns.length + 1}>
123+
{expandable.expandedRowRender(record, index, true)}
124+
</td>
125+
</tr>
126+
)
127+
);
128+
};
129+
}, [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);
164+
}
165+
return expand ? <Icon type="minus-square-o" /> : <Icon type="plus-square-o" />;
166+
}}
167+
/>
168+
);
169+
},
170+
},
171+
...columns,
172+
];
173+
} else {
174+
selfColumns = [...columns];
175+
}
62176
const cls = [prefixCls, className, bordered ? `${prefixCls}-bordered` : null].filter(Boolean).join(' ').trim();
63-
const { header, render, ellipsis } = getLevelItems(columns);
64-
const keys = getAllColumnsKeys<T>(columns);
177+
const { header, render, ellipsis } = getLevelItems(selfColumns);
178+
65179
return (
66180
<div>
67181
<div style={{ overflowY: 'scroll' }} className={cls} {...other}>
@@ -70,35 +184,44 @@ export default function Table<T extends { [key: string]: V }, V>(props: TablePro
70184
{columns && columns.length > 0 && <Thead onCellHead={onCellHead} data={header} />}
71185
{data && data.length > 0 && (
72186
<tbody>
73-
{data.map((trData, rowNum) => (
74-
<tr key={rowNum}>
75-
{keys.map((keyName, colNum) => {
76-
let objs: React.TdHTMLAttributes<HTMLTableDataCellElement> = {
77-
children: trData[keyName],
78-
};
79-
if (render[keyName]) {
80-
const child = render[keyName](trData[keyName], keyName, trData, rowNum, colNum);
81-
if (React.isValidElement(child)) {
82-
objs.children = child;
83-
} else {
84-
if (child.props) {
85-
objs = { ...child.props, children: objs.children };
86-
if (child.props.rowSpan === 0 || child.props.colSpan === 0) return null;
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+
}
87208
}
88-
if (child.children) {
89-
objs.children = child.children;
209+
if (ellipsis && ellipsis[keyName]) {
210+
objs.className = `${prefixCls}-ellipsis`;
90211
}
91-
}
92-
}
93-
if (ellipsis && ellipsis[keyName]) {
94-
objs.className = `${prefixCls}-ellipsis`;
95-
}
96-
return (
97-
<td {...objs} key={colNum} onClick={(evn) => onCell(trData, { rowNum, colNum, keyName }, evn)} />
98-
);
99-
})}
100-
</tr>
101-
))}
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+
})}
102225
</tbody>
103226
)}
104227
{data && data.length === 0 && empty && (

‎packages/react-table/src/util.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,4 @@ export function getAllColumnsKeys<T>(data: TableColumns<T>[], keys: any[] = []):
126126
}
127127
}
128128
return keys;
129-
}
129+
}

‎website/src/routes/components/table/index.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { Table, Empty, Notify, Button, Checkbox, Pagination, Loader, Tooltip } from 'uiw';
2+
import { Table, Icon, Empty, Notify, Button, Checkbox, Pagination, Loader, Tooltip } from 'uiw';
33
import Markdown from '../../../components/Markdown';
44

55
export default () => (
@@ -14,6 +14,7 @@ export default () => (
1414
Loader,
1515
Tooltip,
1616
Empty,
17+
Icon,
1718
}}
1819
renderPage={async () => {
1920
const md = await import('uiw/node_modules/@uiw/react-table/README.md');

0 commit comments

Comments
 (0)
Please sign in to comment.