@beppla/tapas-ui 1.4.6 → 1.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commonjs/StatisticsTable/PLACEHOLDER_FEATURE.md +27 -2
- package/commonjs/StatisticsTable/README.md +164 -26
- package/commonjs/StatisticsTable/StatisticsTable.js +133 -46
- package/commonjs/StatisticsTable/StatisticsTable.js.map +1 -1
- package/commonjs/StatisticsTable/VIRTUAL_SCROLLING.md +254 -0
- package/module/StatisticsTable/PLACEHOLDER_FEATURE.md +27 -2
- package/module/StatisticsTable/README.md +164 -26
- package/module/StatisticsTable/StatisticsTable.js +133 -46
- package/module/StatisticsTable/StatisticsTable.js.map +1 -1
- package/module/StatisticsTable/VIRTUAL_SCROLLING.md +254 -0
- package/package.json +1 -1
- package/typescript/StatisticsTable/StatisticsTable.d.ts +3 -8
- package/typescript/StatisticsTable/StatisticsTable.d.ts.map +1 -1
|
@@ -8,10 +8,12 @@ A versatile table component for displaying matrix data with optional row and col
|
|
|
8
8
|
- ✅ Row statistics (Sum/Mean columns)
|
|
9
9
|
- ✅ Column statistics (Sum/Mean rows)
|
|
10
10
|
- ✅ Horizontal and vertical scrolling
|
|
11
|
-
- ✅
|
|
11
|
+
- ✅ Virtual scrolling for large datasets (high performance)
|
|
12
|
+
- ✅ External pagination support
|
|
12
13
|
- ✅ Loading state
|
|
13
14
|
- ✅ Empty state
|
|
14
15
|
- ✅ Placeholder rows and columns (fill container when data is insufficient)
|
|
16
|
+
- ✅ Internationalization (i18n) support
|
|
15
17
|
- ✅ Customizable styling
|
|
16
18
|
- ✅ TypeScript support
|
|
17
19
|
- ✅ Cross-platform (Web & Mobile)
|
|
@@ -87,22 +89,43 @@ const cells = [
|
|
|
87
89
|
/>
|
|
88
90
|
```
|
|
89
91
|
|
|
90
|
-
## With Pagination
|
|
92
|
+
## With External Pagination
|
|
93
|
+
|
|
94
|
+
The StatisticsTable component doesn't include built-in pagination. Use the separate Pagination component for better layout control:
|
|
91
95
|
|
|
92
96
|
```tsx
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
97
|
+
import { StatisticsTable, Pagination } from '@beppla/tapas-ui';
|
|
98
|
+
|
|
99
|
+
function MyTable() {
|
|
100
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
101
|
+
const pageSize = 10;
|
|
102
|
+
|
|
103
|
+
// Calculate paginated rows
|
|
104
|
+
const paginatedRows = rows.slice(
|
|
105
|
+
(currentPage - 1) * pageSize,
|
|
106
|
+
currentPage * pageSize
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<>
|
|
111
|
+
<StatisticsTable
|
|
112
|
+
rows={paginatedRows}
|
|
113
|
+
columns={columns}
|
|
114
|
+
cells={cells}
|
|
115
|
+
/>
|
|
116
|
+
<Pagination
|
|
117
|
+
totalCount={`${rows.length} items`}
|
|
118
|
+
pageParams={{
|
|
119
|
+
page: currentPage,
|
|
120
|
+
pageSize: pageSize,
|
|
121
|
+
totalItems: rows.length,
|
|
122
|
+
totalPage: Math.ceil(rows.length / pageSize),
|
|
123
|
+
}}
|
|
124
|
+
onPageChange={(params) => setCurrentPage(params.page)}
|
|
125
|
+
/>
|
|
126
|
+
</>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
106
129
|
```
|
|
107
130
|
|
|
108
131
|
## With Placeholder Rows and Columns
|
|
@@ -134,11 +157,34 @@ You can control the size of placeholder rows and columns:
|
|
|
134
157
|
minRows={10}
|
|
135
158
|
minColumns={5}
|
|
136
159
|
placeholderRowHeight={80} // Each placeholder row will be 80px tall
|
|
137
|
-
placeholderColumnWidth={200} //
|
|
160
|
+
placeholderColumnWidth={200} // All placeholder columns will be 200px wide
|
|
138
161
|
maxHeight={600}
|
|
139
162
|
/>
|
|
140
163
|
```
|
|
141
164
|
|
|
165
|
+
**Individual placeholder column widths** for better screen adaptation:
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
<StatisticsTable
|
|
169
|
+
rows={rows} // 2 data columns
|
|
170
|
+
columns={columns}
|
|
171
|
+
cells={cells}
|
|
172
|
+
enablePlaceholder={true}
|
|
173
|
+
minColumns={5} // Need 3 placeholder columns
|
|
174
|
+
placeholderColumnWidth={[180, 220, 150]} // Individual widths for each placeholder
|
|
175
|
+
// Placeholder column 1: 180px
|
|
176
|
+
// Placeholder column 2: 220px
|
|
177
|
+
// Placeholder column 3: 150px
|
|
178
|
+
/>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
If array length is less than placeholder columns needed, the last value will be reused:
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
placeholderColumnWidth={[200, 150]} // minColumns=5, need 3 placeholders
|
|
185
|
+
// Result: [200, 150, 150] - last value (150) reused
|
|
186
|
+
```
|
|
187
|
+
|
|
142
188
|
**How it works:**
|
|
143
189
|
- If `rows.length < minRows`: Adds `(minRows - rows.length)` empty placeholder rows
|
|
144
190
|
- If columns count `< minColumns`: Adds `(minColumns - columns.count)` empty placeholder columns
|
|
@@ -176,6 +222,102 @@ Scroll shadow features:
|
|
|
176
222
|
- Shadows automatically show/hide based on scroll position
|
|
177
223
|
- Works seamlessly with row statistics and column statistics
|
|
178
224
|
|
|
225
|
+
## Virtual Scrolling for Large Datasets
|
|
226
|
+
|
|
227
|
+
For tables with hundreds or thousands of rows, enable virtual scrolling for optimal performance:
|
|
228
|
+
|
|
229
|
+
```tsx
|
|
230
|
+
<StatisticsTable
|
|
231
|
+
rows={thousandsOfRows}
|
|
232
|
+
columns={columns}
|
|
233
|
+
cells={cells}
|
|
234
|
+
enableVirtualization={true}
|
|
235
|
+
virtualRowHeight={56} // Default row height for calculation
|
|
236
|
+
maxHeight={600}
|
|
237
|
+
/>
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**How it works:**
|
|
241
|
+
- Only renders rows visible in the viewport (plus a small buffer)
|
|
242
|
+
- Automatically calculates which rows to render based on scroll position
|
|
243
|
+
- Maintains proper scroll behavior with placeholder spaces above/below
|
|
244
|
+
- Supports dynamic row heights via `getRowHeight` prop
|
|
245
|
+
- Typical performance: 10,000+ rows scroll smoothly
|
|
246
|
+
|
|
247
|
+
**Performance tips:**
|
|
248
|
+
- Set `virtualRowHeight` to match your average row height
|
|
249
|
+
- Use `getRowHeight` for dynamic heights, but be aware it's called frequently
|
|
250
|
+
- For uniform row heights, use the default `rowHeight` for best performance
|
|
251
|
+
|
|
252
|
+
**Example with dynamic heights:**
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
<StatisticsTable
|
|
256
|
+
rows={largeDataset}
|
|
257
|
+
columns={columns}
|
|
258
|
+
cells={cells}
|
|
259
|
+
enableVirtualization={true}
|
|
260
|
+
getRowHeight={(rowKey, rowData) => {
|
|
261
|
+
// Return height based on row data
|
|
262
|
+
return rowData?.isExpanded ? 120 : 56;
|
|
263
|
+
}}
|
|
264
|
+
maxHeight={600}
|
|
265
|
+
/>
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Internationalization (i18n)
|
|
269
|
+
|
|
270
|
+
Customize loading and empty state texts for different languages:
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
// English
|
|
274
|
+
<StatisticsTable
|
|
275
|
+
rows={rows}
|
|
276
|
+
columns={columns}
|
|
277
|
+
cells={cells}
|
|
278
|
+
loading={isLoading}
|
|
279
|
+
loadingText="Loading..."
|
|
280
|
+
emptyText="No data available"
|
|
281
|
+
/>
|
|
282
|
+
|
|
283
|
+
// Chinese
|
|
284
|
+
<StatisticsTable
|
|
285
|
+
rows={rows}
|
|
286
|
+
columns={columns}
|
|
287
|
+
cells={cells}
|
|
288
|
+
loading={isLoading}
|
|
289
|
+
loadingText="加载中..."
|
|
290
|
+
emptyText="暂无数据"
|
|
291
|
+
/>
|
|
292
|
+
|
|
293
|
+
// Spanish
|
|
294
|
+
<StatisticsTable
|
|
295
|
+
rows={rows}
|
|
296
|
+
columns={columns}
|
|
297
|
+
cells={cells}
|
|
298
|
+
loading={isLoading}
|
|
299
|
+
loadingText="Cargando..."
|
|
300
|
+
emptyText="No hay datos"
|
|
301
|
+
/>
|
|
302
|
+
|
|
303
|
+
// With i18n library
|
|
304
|
+
import { useTranslation } from 'react-i18next';
|
|
305
|
+
|
|
306
|
+
function MyTable() {
|
|
307
|
+
const { t } = useTranslation();
|
|
308
|
+
|
|
309
|
+
return (
|
|
310
|
+
<StatisticsTable
|
|
311
|
+
rows={rows}
|
|
312
|
+
columns={columns}
|
|
313
|
+
cells={cells}
|
|
314
|
+
loadingText={t('table.loading')}
|
|
315
|
+
emptyText={t('table.empty')}
|
|
316
|
+
/>
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
179
321
|
## Props
|
|
180
322
|
|
|
181
323
|
| Prop | Type | Default | Description |
|
|
@@ -185,16 +327,19 @@ Scroll shadow features:
|
|
|
185
327
|
| `cells` | `StatisticsTableCell[]` | Required | Cell data |
|
|
186
328
|
| `showRowStats` | `boolean` | `false` | Show row statistics (Sum/Mean columns) |
|
|
187
329
|
| `showColumnStats` | `boolean` | `false` | Show column statistics (Sum/Mean rows) |
|
|
188
|
-
| `pagination` | `PaginationConfig \| false` | `false` | Pagination configuration |
|
|
189
330
|
| `loading` | `boolean` | `false` | Show loading state |
|
|
190
|
-
| `
|
|
331
|
+
| `loadingText` | `string` | `'Loading...'` | Text to display during loading (for i18n) |
|
|
332
|
+
| `emptyText` | `string` | `'No data'` | Text to display when empty (for i18n) |
|
|
191
333
|
| `maxHeight` | `number` | `500` | Maximum table body height |
|
|
192
334
|
| `rowLabelWidth` | `number` | `150` | Width of first column (row labels) |
|
|
193
335
|
| `enablePlaceholder` | `boolean` | `false` | Enable placeholder rows/columns to fill container |
|
|
194
336
|
| `minRows` | `number` | `0` | Minimum number of rows (adds placeholders if needed) |
|
|
195
337
|
| `minColumns` | `number` | `0` | Minimum number of columns (adds placeholders if needed) |
|
|
196
338
|
| `placeholderRowHeight` | `number` | `rowHeight` (56) | Height of each placeholder row |
|
|
197
|
-
| `placeholderColumnWidth` | `number` | `150` | Width of each
|
|
339
|
+
| `placeholderColumnWidth` | `number \| number[]` | `150` | Width of placeholder columns (single number or array for each column) |
|
|
340
|
+
| `enableVirtualization` | `boolean` | `false` | Enable virtual scrolling for large datasets |
|
|
341
|
+
| `virtualRowHeight` | `number` | `56` | Average row height for virtual scroll calculations |
|
|
342
|
+
| `getRowHeight` | `(rowKey, rowData) => number` | - | Dynamic row height function (used in virtual scrolling) |
|
|
198
343
|
| `enableScrollShadow` | `boolean` | `true` | Enable scroll shadows on all four sides |
|
|
199
344
|
| `showScrollIndicator` | `boolean` | `false` | Show scroll indicators/scrollbars |
|
|
200
345
|
| `style` | `ViewStyle` | - | Custom container styles |
|
|
@@ -214,13 +359,6 @@ interface StatisticsTableCell {
|
|
|
214
359
|
quantity: number;
|
|
215
360
|
amount: number;
|
|
216
361
|
}
|
|
217
|
-
|
|
218
|
-
interface PaginationConfig {
|
|
219
|
-
current: number;
|
|
220
|
-
pageSize: number;
|
|
221
|
-
total: number;
|
|
222
|
-
onChange?: (page: number, pageSize: number) => void;
|
|
223
|
-
}
|
|
224
362
|
```
|
|
225
363
|
|
|
226
364
|
## Use Cases
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import React, { useMemo, useCallback, useState, useRef, useEffect } from 'react';
|
|
4
4
|
import { View, ScrollView, StyleSheet, Platform, TouchableOpacity, Text, Animated } from 'react-native';
|
|
5
5
|
import { useTheme } from '@rneui/themed';
|
|
6
|
-
import Pagination from '../Pagination/Pagination';
|
|
7
6
|
import Hoverable from '../Hoverable/Hoverable';
|
|
8
7
|
|
|
9
8
|
// Tooltip 内容类型
|
|
@@ -86,8 +85,8 @@ export function StatisticsTable({
|
|
|
86
85
|
cells,
|
|
87
86
|
showRowStats = false,
|
|
88
87
|
showColumnStats = false,
|
|
89
|
-
pagination,
|
|
90
88
|
loading = false,
|
|
89
|
+
loadingText = 'Loading...',
|
|
91
90
|
emptyText = 'No data',
|
|
92
91
|
maxHeight = 500,
|
|
93
92
|
rowLabelWidth = 150,
|
|
@@ -146,6 +145,9 @@ export function StatisticsTable({
|
|
|
146
145
|
isAtBottom: false
|
|
147
146
|
});
|
|
148
147
|
|
|
148
|
+
// 虚拟滚动状态
|
|
149
|
+
const [virtualScrollOffset, setVirtualScrollOffset] = useState(0);
|
|
150
|
+
|
|
149
151
|
// Hover 状态
|
|
150
152
|
const [hoveredCell, setHoveredCell] = useState(null);
|
|
151
153
|
const scrollViewRef = useRef(null);
|
|
@@ -188,12 +190,13 @@ export function StatisticsTable({
|
|
|
188
190
|
backgroundColor: colors.colorSurface // 米色背景
|
|
189
191
|
},
|
|
190
192
|
header: {
|
|
191
|
-
backgroundColor: headerStyle?.backgroundColor || colors.
|
|
192
|
-
//
|
|
193
|
+
backgroundColor: headerStyle?.backgroundColor || colors.colorSurface7,
|
|
194
|
+
// rgba(0, 0, 0, 0.08)
|
|
193
195
|
borderBottomColor: colors.colorTableBorder
|
|
194
196
|
},
|
|
195
197
|
headerCell: {
|
|
196
|
-
borderRightColor: colors.colorTableBorder
|
|
198
|
+
borderRightColor: colors.colorTableBorder,
|
|
199
|
+
backgroundColor: headerStyle?.backgroundColor || colors.colorSurface7
|
|
197
200
|
},
|
|
198
201
|
headerText: {
|
|
199
202
|
color: headerStyle?.textColor || colors.colorTextPrimary,
|
|
@@ -202,7 +205,7 @@ export function StatisticsTable({
|
|
|
202
205
|
},
|
|
203
206
|
// Row Stats 表头样式
|
|
204
207
|
statsHeaderCell: {
|
|
205
|
-
backgroundColor: rowStatsHeaderStyle?.backgroundColor ||
|
|
208
|
+
backgroundColor: rowStatsHeaderStyle?.backgroundColor || colors.colorSurface7,
|
|
206
209
|
borderRightColor: colors.colorTableBorder,
|
|
207
210
|
borderBottomColor: colors.colorTableBorder
|
|
208
211
|
},
|
|
@@ -255,10 +258,6 @@ export function StatisticsTable({
|
|
|
255
258
|
fontSize: columnStatsLabelStyle?.fontSize || 14,
|
|
256
259
|
fontWeight: columnStatsLabelStyle?.fontWeight || '600'
|
|
257
260
|
},
|
|
258
|
-
paginationContainer: {
|
|
259
|
-
borderTopColor: colors.colorTableBorder,
|
|
260
|
-
backgroundColor: colors.colorSurface
|
|
261
|
-
},
|
|
262
261
|
// Column Stats 分隔线
|
|
263
262
|
columnStatsDivider: {
|
|
264
263
|
borderTopWidth: 2,
|
|
@@ -344,11 +343,15 @@ export function StatisticsTable({
|
|
|
344
343
|
// 添加占位列来填充
|
|
345
344
|
const placeholderColumns = Array.from({
|
|
346
345
|
length: missingColumns
|
|
347
|
-
}, (_, i) =>
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
346
|
+
}, (_, i) => {
|
|
347
|
+
// 支持数组形式的宽度设置
|
|
348
|
+
const width = Array.isArray(placeholderColumnWidth) ? placeholderColumnWidth[i] || placeholderColumnWidth[placeholderColumnWidth.length - 1] || 150 : placeholderColumnWidth;
|
|
349
|
+
return {
|
|
350
|
+
key: `_placeholder_col_${i}`,
|
|
351
|
+
title: '',
|
|
352
|
+
width: width
|
|
353
|
+
};
|
|
354
|
+
});
|
|
352
355
|
finalColumns = [...columns, ...placeholderColumns];
|
|
353
356
|
}
|
|
354
357
|
|
|
@@ -407,8 +410,13 @@ export function StatisticsTable({
|
|
|
407
410
|
y: contentOffset.y,
|
|
408
411
|
animated: false
|
|
409
412
|
});
|
|
413
|
+
|
|
414
|
+
// 更新虚拟滚动偏移量
|
|
415
|
+
if (enableVirtualization) {
|
|
416
|
+
setVirtualScrollOffset(contentOffset.y);
|
|
417
|
+
}
|
|
410
418
|
handleScroll(event);
|
|
411
|
-
}, [handleScroll]);
|
|
419
|
+
}, [handleScroll, enableVirtualization]);
|
|
412
420
|
|
|
413
421
|
// 处理单元格 hover
|
|
414
422
|
const handleCellHover = useCallback((rowKey, columnKey, isHovered) => {
|
|
@@ -517,7 +525,10 @@ export function StatisticsTable({
|
|
|
517
525
|
children: /*#__PURE__*/_jsx(View, {
|
|
518
526
|
style: styles.loadingRow,
|
|
519
527
|
children: /*#__PURE__*/_jsx(Text, {
|
|
520
|
-
|
|
528
|
+
style: {
|
|
529
|
+
color: colors.colorTextPlaceholder
|
|
530
|
+
},
|
|
531
|
+
children: loadingText
|
|
521
532
|
})
|
|
522
533
|
})
|
|
523
534
|
});
|
|
@@ -536,7 +547,75 @@ export function StatisticsTable({
|
|
|
536
547
|
})
|
|
537
548
|
});
|
|
538
549
|
}
|
|
539
|
-
|
|
550
|
+
|
|
551
|
+
// 虚拟滚动计算
|
|
552
|
+
const virtualScrollData = useMemo(() => {
|
|
553
|
+
if (!enableVirtualization) {
|
|
554
|
+
return {
|
|
555
|
+
visibleRows: displayRows,
|
|
556
|
+
startIndex: 0,
|
|
557
|
+
endIndex: displayRows.length,
|
|
558
|
+
offsetTop: 0,
|
|
559
|
+
offsetBottom: 0,
|
|
560
|
+
totalHeight: 0
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// 计算每行的累积高度
|
|
565
|
+
const rowHeights = [];
|
|
566
|
+
let totalHeight = 0;
|
|
567
|
+
displayRows.forEach(row => {
|
|
568
|
+
const height = row.height || (getRowHeight ? getRowHeight(row.key, row.data) : rowHeight || virtualRowHeight);
|
|
569
|
+
rowHeights.push(height);
|
|
570
|
+
totalHeight += height;
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
// 计算可视区域
|
|
574
|
+
const viewportHeight = maxHeight - (actualShowColumnStats ? 120 : 0);
|
|
575
|
+
const scrollTop = virtualScrollOffset;
|
|
576
|
+
|
|
577
|
+
// 查找起始索引
|
|
578
|
+
let startIndex = 0;
|
|
579
|
+
let accumulatedHeight = 0;
|
|
580
|
+
for (let i = 0; i < rowHeights.length; i++) {
|
|
581
|
+
const currentHeight = rowHeights[i] || virtualRowHeight;
|
|
582
|
+
if (accumulatedHeight + currentHeight > scrollTop) {
|
|
583
|
+
startIndex = i;
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
accumulatedHeight += currentHeight;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// 添加缓冲区(上下各多渲染几行)
|
|
590
|
+
const overscan = 3;
|
|
591
|
+
startIndex = Math.max(0, startIndex - overscan);
|
|
592
|
+
|
|
593
|
+
// 查找结束索引
|
|
594
|
+
let endIndex = startIndex;
|
|
595
|
+
let visibleHeight = 0;
|
|
596
|
+
for (let i = startIndex; i < rowHeights.length; i++) {
|
|
597
|
+
const currentHeight = rowHeights[i] || virtualRowHeight;
|
|
598
|
+
visibleHeight += currentHeight;
|
|
599
|
+
endIndex = i + 1;
|
|
600
|
+
if (visibleHeight >= viewportHeight) {
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
endIndex = Math.min(displayRows.length, endIndex + overscan);
|
|
605
|
+
|
|
606
|
+
// 计算上下占位空间
|
|
607
|
+
const offsetTop = rowHeights.slice(0, startIndex).reduce((sum, h) => sum + h, 0);
|
|
608
|
+
const offsetBottom = rowHeights.slice(endIndex).reduce((sum, h) => sum + h, 0);
|
|
609
|
+
return {
|
|
610
|
+
visibleRows: displayRows.slice(startIndex, endIndex),
|
|
611
|
+
startIndex,
|
|
612
|
+
endIndex,
|
|
613
|
+
offsetTop,
|
|
614
|
+
offsetBottom,
|
|
615
|
+
totalHeight
|
|
616
|
+
};
|
|
617
|
+
}, [enableVirtualization, displayRows, virtualScrollOffset, maxHeight, actualShowColumnStats, rowHeight, getRowHeight]);
|
|
618
|
+
const visibleRows = virtualScrollData.visibleRows;
|
|
540
619
|
|
|
541
620
|
// Web 阴影样式
|
|
542
621
|
const webShadowStyle = Platform.OS === 'web' ? {
|
|
@@ -610,7 +689,7 @@ export function StatisticsTable({
|
|
|
610
689
|
})
|
|
611
690
|
}, column.key);
|
|
612
691
|
})]
|
|
613
|
-
}), /*#__PURE__*/
|
|
692
|
+
}), /*#__PURE__*/_jsxs(ScrollView, {
|
|
614
693
|
ref: bodyScrollViewRef,
|
|
615
694
|
style: {
|
|
616
695
|
maxHeight: maxHeight - columnStatsHeight
|
|
@@ -618,15 +697,24 @@ export function StatisticsTable({
|
|
|
618
697
|
showsVerticalScrollIndicator: showScrollIndicator,
|
|
619
698
|
onScroll: handleBodyScroll,
|
|
620
699
|
scrollEventThrottle: 16,
|
|
621
|
-
children:
|
|
700
|
+
children: [enableVirtualization && virtualScrollData.offsetTop > 0 && /*#__PURE__*/_jsx(View, {
|
|
701
|
+
style: {
|
|
702
|
+
height: virtualScrollData.offsetTop
|
|
703
|
+
}
|
|
704
|
+
}), visibleRows.map((row, rowIndex) => {
|
|
622
705
|
const currentRowHeight = getRowHeightValue(row);
|
|
623
706
|
const isClickable = !!onRowPress;
|
|
624
707
|
const isRowHovered = hoveredCell?.rowKey === row.key;
|
|
625
708
|
const isPlaceholder = row.key.startsWith('_placeholder_row_');
|
|
709
|
+
const isLastRow = rowIndex === visibleRows.length - 1;
|
|
626
710
|
return /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
627
711
|
style: [styles.row, themedStyles.row, {
|
|
628
712
|
minHeight: currentRowHeight
|
|
629
|
-
}, isRowHovered && !isPlaceholder && themedStyles.rowHovered
|
|
713
|
+
}, isRowHovered && !isPlaceholder && themedStyles.rowHovered,
|
|
714
|
+
// 最后一行且没有 Column Stats 时移除底部边框,避免与表格底边重合
|
|
715
|
+
isLastRow && !actualShowColumnStats && {
|
|
716
|
+
borderBottomWidth: 0
|
|
717
|
+
}],
|
|
630
718
|
onPress: isClickable && !isPlaceholder ? () => onRowPress(row.key, row.data) : undefined,
|
|
631
719
|
activeOpacity: isClickable && !isPlaceholder ? 0.7 : 1,
|
|
632
720
|
disabled: !isClickable || isPlaceholder,
|
|
@@ -670,7 +758,11 @@ export function StatisticsTable({
|
|
|
670
758
|
}, column.key);
|
|
671
759
|
})]
|
|
672
760
|
}, row.key);
|
|
673
|
-
})
|
|
761
|
+
}), enableVirtualization && virtualScrollData.offsetBottom > 0 && /*#__PURE__*/_jsx(View, {
|
|
762
|
+
style: {
|
|
763
|
+
height: virtualScrollData.offsetBottom
|
|
764
|
+
}
|
|
765
|
+
})]
|
|
674
766
|
}), actualShowColumnStats && columnStats && /*#__PURE__*/_jsxs(Animated.View, {
|
|
675
767
|
style: [styles.columnStatsContainer, themedStyles.columnStatsDivider, enableStatsAnimation && {
|
|
676
768
|
opacity: columnStatsAnimation,
|
|
@@ -820,15 +912,21 @@ export function StatisticsTable({
|
|
|
820
912
|
children: rowStatsHeaders.mean
|
|
821
913
|
})
|
|
822
914
|
})]
|
|
823
|
-
}), /*#__PURE__*/
|
|
915
|
+
}), /*#__PURE__*/_jsxs(ScrollView, {
|
|
824
916
|
ref: rowStatsScrollRef,
|
|
825
917
|
style: {
|
|
826
918
|
maxHeight: maxHeight - columnStatsHeight
|
|
827
919
|
},
|
|
828
920
|
showsVerticalScrollIndicator: false,
|
|
829
921
|
scrollEnabled: false,
|
|
830
|
-
children:
|
|
831
|
-
|
|
922
|
+
children: [enableVirtualization && virtualScrollData.offsetTop > 0 && /*#__PURE__*/_jsx(View, {
|
|
923
|
+
style: {
|
|
924
|
+
height: virtualScrollData.offsetTop
|
|
925
|
+
}
|
|
926
|
+
}), visibleRows.map((row, rowIndex) => {
|
|
927
|
+
// 计算在原始数组中的索引
|
|
928
|
+
const actualIndex = enableVirtualization ? virtualScrollData.startIndex + rowIndex : rowIndex;
|
|
929
|
+
const rowStat = rowStats?.[actualIndex];
|
|
832
930
|
const currentRowHeight = getRowHeightValue(row);
|
|
833
931
|
const isRowHovered = hoveredCell?.rowKey === row.key;
|
|
834
932
|
const isSumHovered = hoveredCell?.rowKey === row.key && hoveredCell?.columnKey === '__row_sum__';
|
|
@@ -879,23 +977,13 @@ export function StatisticsTable({
|
|
|
879
977
|
})
|
|
880
978
|
})]
|
|
881
979
|
}, row.key);
|
|
882
|
-
})
|
|
980
|
+
}), enableVirtualization && virtualScrollData.offsetBottom > 0 && /*#__PURE__*/_jsx(View, {
|
|
981
|
+
style: {
|
|
982
|
+
height: virtualScrollData.offsetBottom
|
|
983
|
+
}
|
|
984
|
+
})]
|
|
883
985
|
})]
|
|
884
986
|
})]
|
|
885
|
-
}), pagination !== false && pagination && /*#__PURE__*/_jsx(View, {
|
|
886
|
-
style: [styles.paginationContainer, themedStyles.paginationContainer],
|
|
887
|
-
children: /*#__PURE__*/_jsx(Pagination, {
|
|
888
|
-
totalCount: `${pagination.total} items`,
|
|
889
|
-
pageParams: {
|
|
890
|
-
page: pagination.current,
|
|
891
|
-
pageSize: pagination.pageSize,
|
|
892
|
-
totalItems: pagination.total,
|
|
893
|
-
totalPage: Math.ceil(pagination.total / pagination.pageSize)
|
|
894
|
-
},
|
|
895
|
-
onPageChange: params => {
|
|
896
|
-
pagination.onChange?.(params.page, params.pageSize);
|
|
897
|
-
}
|
|
898
|
-
})
|
|
899
987
|
})]
|
|
900
988
|
});
|
|
901
989
|
}
|
|
@@ -984,9 +1072,6 @@ const styles = StyleSheet.create({
|
|
|
984
1072
|
emptyText: {
|
|
985
1073
|
fontSize: 14
|
|
986
1074
|
},
|
|
987
|
-
paginationContainer: {
|
|
988
|
-
borderTopWidth: 1
|
|
989
|
-
},
|
|
990
1075
|
// Row Stats 容器
|
|
991
1076
|
rowStatsContainer: {
|
|
992
1077
|
position: 'relative'
|
|
@@ -1004,11 +1089,13 @@ const styles = StyleSheet.create({
|
|
|
1004
1089
|
},
|
|
1005
1090
|
shadowLeft: {
|
|
1006
1091
|
left: 0,
|
|
1007
|
-
top:
|
|
1092
|
+
top: 40,
|
|
1093
|
+
// 从表头底部开始
|
|
1008
1094
|
bottom: 0
|
|
1009
1095
|
},
|
|
1010
1096
|
shadowRight: {
|
|
1011
|
-
top:
|
|
1097
|
+
top: 40,
|
|
1098
|
+
// 从表头底部开始
|
|
1012
1099
|
bottom: 0
|
|
1013
1100
|
},
|
|
1014
1101
|
// 水平阴影条
|
|
@@ -1021,7 +1108,7 @@ const styles = StyleSheet.create({
|
|
|
1021
1108
|
backgroundColor: 'transparent'
|
|
1022
1109
|
},
|
|
1023
1110
|
shadowTop: {
|
|
1024
|
-
top:
|
|
1111
|
+
top: 40 // header height (paddingVertical: 12*2 + content ~16)
|
|
1025
1112
|
},
|
|
1026
1113
|
shadowBottom: {
|
|
1027
1114
|
// bottom 由动态样式设置
|