1
- import React from 'react' ;
1
+ import React , { useMemo , useState , useEffect } from 'react' ;
2
2
import { IProps , HTMLDivProps , noop } from '@uiw/utils' ;
3
+ import Icon from '@uiw/react-icon' ;
3
4
import Thead from './Thead' ;
4
5
import { getLevelItems , getAllColumnsKeys } from './util' ;
6
+ import ExpandableComponent from './Expandable' ;
5
7
import './style/index.less' ;
6
8
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 ;
9
31
key ?: string ;
10
32
width ?: number ;
11
33
colSpan ?: number ;
12
- children ?: TableColumns < T , V > [ ] ;
34
+ children ?: TableColumns < T > [ ] ;
13
35
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 ;
15
37
style ?: React . CSSProperties ;
16
38
[ key : string ] : any ;
17
39
} ;
18
40
19
41
export interface TableProps < T extends { [ key : string ] : V } = any , V = any > extends IProps , Omit < HTMLDivProps , 'title' > {
20
42
prefixCls ?: string ;
21
- columns ?: TableColumns < T , V > [ ] ;
43
+ columns ?: TableColumns < T > [ ] ;
22
44
data ?: Array < T > ;
23
45
title ?: React . ReactNode ;
24
46
footer ?: React . ReactNode ;
@@ -30,19 +52,20 @@ export interface TableProps<T extends { [key: string]: V } = any, V = any> exten
30
52
evn : React . MouseEvent < HTMLTableCellElement > ,
31
53
) => void | React . ReactNode ;
32
54
onCellHead ?: (
33
- data : TableColumns < T , V > ,
55
+ data : TableColumns < T > ,
34
56
rowNum : number ,
35
57
colNum : number ,
36
58
evn : React . MouseEvent < HTMLTableCellElement > ,
37
59
) => void ;
60
+ expandable ?: ExpandableType < T > ;
61
+ rowKey ?: keyof T ;
38
62
}
39
63
40
64
export interface ICellOptions {
41
65
rowNum : number ;
42
66
colNum : number ;
43
67
keyName : string ;
44
68
}
45
-
46
69
export default function Table < T extends { [ key : string ] : V } , V > ( props : TableProps < T , V > = { } ) {
47
70
const {
48
71
prefixCls = 'w-table' ,
@@ -56,12 +79,103 @@ export default function Table<T extends { [key: string]: V }, V>(props: TablePro
56
79
onCellHead = noop ,
57
80
empty,
58
81
children,
82
+ expandable,
83
+ rowKey,
59
84
...other
60
85
} = 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 ] ) ;
61
106
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
+ }
62
176
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
+
65
179
return (
66
180
< div >
67
181
< div style = { { overflowY : 'scroll' } } className = { cls } { ...other } >
@@ -70,35 +184,44 @@ export default function Table<T extends { [key: string]: V }, V>(props: TablePro
70
184
{ columns && columns . length > 0 && < Thead onCellHead = { onCellHead } data = { header } /> }
71
185
{ data && data . length > 0 && (
72
186
< 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
+ }
87
208
}
88
- if ( child . children ) {
89
- objs . children = child . children ;
209
+ if ( ellipsis && ellipsis [ keyName ] ) {
210
+ objs . className = ` ${ prefixCls } -ellipsis` ;
90
211
}
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
+ } ) }
102
225
</ tbody >
103
226
) }
104
227
{ data && data . length === 0 && empty && (
0 commit comments