@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
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
| `minRows` | `number` | `0` | 最小行数(如果实际数据行数少于此值,会添加占位行) |
|
|
13
13
|
| `minColumns` | `number` | `0` | 最小列数(如果实际数据列数少于此值,会添加占位列) |
|
|
14
14
|
| `placeholderRowHeight` | `number` | `rowHeight` (56) | 占位行的高度 |
|
|
15
|
-
| `placeholderColumnWidth` | `number` | `150` |
|
|
15
|
+
| `placeholderColumnWidth` | `number \| number[]` | `150` | 占位列的宽度(可以是数字或数组为每列单独设置) |
|
|
16
16
|
|
|
17
17
|
## 工作原理
|
|
18
18
|
|
|
@@ -84,6 +84,8 @@
|
|
|
84
84
|
|
|
85
85
|
### 自定义占位尺寸
|
|
86
86
|
|
|
87
|
+
**统一设置所有占位列宽度:**
|
|
88
|
+
|
|
87
89
|
```tsx
|
|
88
90
|
<StatisticsTable
|
|
89
91
|
rows={rows}
|
|
@@ -93,11 +95,34 @@
|
|
|
93
95
|
minRows={10}
|
|
94
96
|
minColumns={5}
|
|
95
97
|
placeholderRowHeight={80} // 占位行高度 80px
|
|
96
|
-
placeholderColumnWidth={200} //
|
|
98
|
+
placeholderColumnWidth={200} // 所有占位列宽度 200px
|
|
97
99
|
maxHeight={600}
|
|
98
100
|
/>
|
|
99
101
|
```
|
|
100
102
|
|
|
103
|
+
**为每个占位列单独设置宽度(更好地适配屏幕宽度):**
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
<StatisticsTable
|
|
107
|
+
rows={rows} // 假设有 2 列数据
|
|
108
|
+
columns={columns}
|
|
109
|
+
cells={cells}
|
|
110
|
+
enablePlaceholder={true}
|
|
111
|
+
minColumns={5} // 需要 3 个占位列
|
|
112
|
+
placeholderColumnWidth={[180, 250, 120]} // 为每个占位列单独设置宽度
|
|
113
|
+
// 占位列 1: 180px
|
|
114
|
+
// 占位列 2: 250px
|
|
115
|
+
// 占位列 3: 120px
|
|
116
|
+
/>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
如果数组长度少于所需的占位列数量,最后一个值会被重复使用:
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
placeholderColumnWidth={[200, 150]} // minColumns=5, 需要 3 个占位列
|
|
123
|
+
// 结果: [200, 150, 150] - 最后的值 (150) 被重复使用
|
|
124
|
+
```
|
|
125
|
+
|
|
101
126
|
### 配合统计功能
|
|
102
127
|
|
|
103
128
|
```tsx
|
|
@@ -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
|
|
@@ -7,7 +7,6 @@ exports.StatisticsTable = StatisticsTable;
|
|
|
7
7
|
var _react = _interopRequireWildcard(require("react"));
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
9
|
var _themed = require("@rneui/themed");
|
|
10
|
-
var _Pagination = _interopRequireDefault(require("../Pagination/Pagination"));
|
|
11
10
|
var _Hoverable = _interopRequireDefault(require("../Hoverable/Hoverable"));
|
|
12
11
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
13
12
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -92,8 +91,8 @@ function StatisticsTable({
|
|
|
92
91
|
cells,
|
|
93
92
|
showRowStats = false,
|
|
94
93
|
showColumnStats = false,
|
|
95
|
-
pagination,
|
|
96
94
|
loading = false,
|
|
95
|
+
loadingText = 'Loading...',
|
|
97
96
|
emptyText = 'No data',
|
|
98
97
|
maxHeight = 500,
|
|
99
98
|
rowLabelWidth = 150,
|
|
@@ -152,6 +151,9 @@ function StatisticsTable({
|
|
|
152
151
|
isAtBottom: false
|
|
153
152
|
});
|
|
154
153
|
|
|
154
|
+
// 虚拟滚动状态
|
|
155
|
+
const [virtualScrollOffset, setVirtualScrollOffset] = (0, _react.useState)(0);
|
|
156
|
+
|
|
155
157
|
// Hover 状态
|
|
156
158
|
const [hoveredCell, setHoveredCell] = (0, _react.useState)(null);
|
|
157
159
|
const scrollViewRef = (0, _react.useRef)(null);
|
|
@@ -194,12 +196,13 @@ function StatisticsTable({
|
|
|
194
196
|
backgroundColor: colors.colorSurface // 米色背景
|
|
195
197
|
},
|
|
196
198
|
header: {
|
|
197
|
-
backgroundColor: headerStyle?.backgroundColor || colors.
|
|
198
|
-
//
|
|
199
|
+
backgroundColor: headerStyle?.backgroundColor || colors.colorSurface7,
|
|
200
|
+
// rgba(0, 0, 0, 0.08)
|
|
199
201
|
borderBottomColor: colors.colorTableBorder
|
|
200
202
|
},
|
|
201
203
|
headerCell: {
|
|
202
|
-
borderRightColor: colors.colorTableBorder
|
|
204
|
+
borderRightColor: colors.colorTableBorder,
|
|
205
|
+
backgroundColor: headerStyle?.backgroundColor || colors.colorSurface7
|
|
203
206
|
},
|
|
204
207
|
headerText: {
|
|
205
208
|
color: headerStyle?.textColor || colors.colorTextPrimary,
|
|
@@ -208,7 +211,7 @@ function StatisticsTable({
|
|
|
208
211
|
},
|
|
209
212
|
// Row Stats 表头样式
|
|
210
213
|
statsHeaderCell: {
|
|
211
|
-
backgroundColor: rowStatsHeaderStyle?.backgroundColor ||
|
|
214
|
+
backgroundColor: rowStatsHeaderStyle?.backgroundColor || colors.colorSurface7,
|
|
212
215
|
borderRightColor: colors.colorTableBorder,
|
|
213
216
|
borderBottomColor: colors.colorTableBorder
|
|
214
217
|
},
|
|
@@ -261,10 +264,6 @@ function StatisticsTable({
|
|
|
261
264
|
fontSize: columnStatsLabelStyle?.fontSize || 14,
|
|
262
265
|
fontWeight: columnStatsLabelStyle?.fontWeight || '600'
|
|
263
266
|
},
|
|
264
|
-
paginationContainer: {
|
|
265
|
-
borderTopColor: colors.colorTableBorder,
|
|
266
|
-
backgroundColor: colors.colorSurface
|
|
267
|
-
},
|
|
268
267
|
// Column Stats 分隔线
|
|
269
268
|
columnStatsDivider: {
|
|
270
269
|
borderTopWidth: 2,
|
|
@@ -350,11 +349,15 @@ function StatisticsTable({
|
|
|
350
349
|
// 添加占位列来填充
|
|
351
350
|
const placeholderColumns = Array.from({
|
|
352
351
|
length: missingColumns
|
|
353
|
-
}, (_, i) =>
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
352
|
+
}, (_, i) => {
|
|
353
|
+
// 支持数组形式的宽度设置
|
|
354
|
+
const width = Array.isArray(placeholderColumnWidth) ? placeholderColumnWidth[i] || placeholderColumnWidth[placeholderColumnWidth.length - 1] || 150 : placeholderColumnWidth;
|
|
355
|
+
return {
|
|
356
|
+
key: `_placeholder_col_${i}`,
|
|
357
|
+
title: '',
|
|
358
|
+
width: width
|
|
359
|
+
};
|
|
360
|
+
});
|
|
358
361
|
finalColumns = [...columns, ...placeholderColumns];
|
|
359
362
|
}
|
|
360
363
|
|
|
@@ -413,8 +416,13 @@ function StatisticsTable({
|
|
|
413
416
|
y: contentOffset.y,
|
|
414
417
|
animated: false
|
|
415
418
|
});
|
|
419
|
+
|
|
420
|
+
// 更新虚拟滚动偏移量
|
|
421
|
+
if (enableVirtualization) {
|
|
422
|
+
setVirtualScrollOffset(contentOffset.y);
|
|
423
|
+
}
|
|
416
424
|
handleScroll(event);
|
|
417
|
-
}, [handleScroll]);
|
|
425
|
+
}, [handleScroll, enableVirtualization]);
|
|
418
426
|
|
|
419
427
|
// 处理单元格 hover
|
|
420
428
|
const handleCellHover = (0, _react.useCallback)((rowKey, columnKey, isHovered) => {
|
|
@@ -523,7 +531,10 @@ function StatisticsTable({
|
|
|
523
531
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
524
532
|
style: styles.loadingRow,
|
|
525
533
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
526
|
-
|
|
534
|
+
style: {
|
|
535
|
+
color: colors.colorTextPlaceholder
|
|
536
|
+
},
|
|
537
|
+
children: loadingText
|
|
527
538
|
})
|
|
528
539
|
})
|
|
529
540
|
});
|
|
@@ -542,7 +553,75 @@ function StatisticsTable({
|
|
|
542
553
|
})
|
|
543
554
|
});
|
|
544
555
|
}
|
|
545
|
-
|
|
556
|
+
|
|
557
|
+
// 虚拟滚动计算
|
|
558
|
+
const virtualScrollData = (0, _react.useMemo)(() => {
|
|
559
|
+
if (!enableVirtualization) {
|
|
560
|
+
return {
|
|
561
|
+
visibleRows: displayRows,
|
|
562
|
+
startIndex: 0,
|
|
563
|
+
endIndex: displayRows.length,
|
|
564
|
+
offsetTop: 0,
|
|
565
|
+
offsetBottom: 0,
|
|
566
|
+
totalHeight: 0
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// 计算每行的累积高度
|
|
571
|
+
const rowHeights = [];
|
|
572
|
+
let totalHeight = 0;
|
|
573
|
+
displayRows.forEach(row => {
|
|
574
|
+
const height = row.height || (getRowHeight ? getRowHeight(row.key, row.data) : rowHeight || virtualRowHeight);
|
|
575
|
+
rowHeights.push(height);
|
|
576
|
+
totalHeight += height;
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// 计算可视区域
|
|
580
|
+
const viewportHeight = maxHeight - (actualShowColumnStats ? 120 : 0);
|
|
581
|
+
const scrollTop = virtualScrollOffset;
|
|
582
|
+
|
|
583
|
+
// 查找起始索引
|
|
584
|
+
let startIndex = 0;
|
|
585
|
+
let accumulatedHeight = 0;
|
|
586
|
+
for (let i = 0; i < rowHeights.length; i++) {
|
|
587
|
+
const currentHeight = rowHeights[i] || virtualRowHeight;
|
|
588
|
+
if (accumulatedHeight + currentHeight > scrollTop) {
|
|
589
|
+
startIndex = i;
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
accumulatedHeight += currentHeight;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// 添加缓冲区(上下各多渲染几行)
|
|
596
|
+
const overscan = 3;
|
|
597
|
+
startIndex = Math.max(0, startIndex - overscan);
|
|
598
|
+
|
|
599
|
+
// 查找结束索引
|
|
600
|
+
let endIndex = startIndex;
|
|
601
|
+
let visibleHeight = 0;
|
|
602
|
+
for (let i = startIndex; i < rowHeights.length; i++) {
|
|
603
|
+
const currentHeight = rowHeights[i] || virtualRowHeight;
|
|
604
|
+
visibleHeight += currentHeight;
|
|
605
|
+
endIndex = i + 1;
|
|
606
|
+
if (visibleHeight >= viewportHeight) {
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
endIndex = Math.min(displayRows.length, endIndex + overscan);
|
|
611
|
+
|
|
612
|
+
// 计算上下占位空间
|
|
613
|
+
const offsetTop = rowHeights.slice(0, startIndex).reduce((sum, h) => sum + h, 0);
|
|
614
|
+
const offsetBottom = rowHeights.slice(endIndex).reduce((sum, h) => sum + h, 0);
|
|
615
|
+
return {
|
|
616
|
+
visibleRows: displayRows.slice(startIndex, endIndex),
|
|
617
|
+
startIndex,
|
|
618
|
+
endIndex,
|
|
619
|
+
offsetTop,
|
|
620
|
+
offsetBottom,
|
|
621
|
+
totalHeight
|
|
622
|
+
};
|
|
623
|
+
}, [enableVirtualization, displayRows, virtualScrollOffset, maxHeight, actualShowColumnStats, rowHeight, getRowHeight]);
|
|
624
|
+
const visibleRows = virtualScrollData.visibleRows;
|
|
546
625
|
|
|
547
626
|
// Web 阴影样式
|
|
548
627
|
const webShadowStyle = _reactNative.Platform.OS === 'web' ? {
|
|
@@ -616,7 +695,7 @@ function StatisticsTable({
|
|
|
616
695
|
})
|
|
617
696
|
}, column.key);
|
|
618
697
|
})]
|
|
619
|
-
}), /*#__PURE__*/(0, _jsxRuntime.
|
|
698
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
|
|
620
699
|
ref: bodyScrollViewRef,
|
|
621
700
|
style: {
|
|
622
701
|
maxHeight: maxHeight - columnStatsHeight
|
|
@@ -624,15 +703,24 @@ function StatisticsTable({
|
|
|
624
703
|
showsVerticalScrollIndicator: showScrollIndicator,
|
|
625
704
|
onScroll: handleBodyScroll,
|
|
626
705
|
scrollEventThrottle: 16,
|
|
627
|
-
children:
|
|
706
|
+
children: [enableVirtualization && virtualScrollData.offsetTop > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
707
|
+
style: {
|
|
708
|
+
height: virtualScrollData.offsetTop
|
|
709
|
+
}
|
|
710
|
+
}), visibleRows.map((row, rowIndex) => {
|
|
628
711
|
const currentRowHeight = getRowHeightValue(row);
|
|
629
712
|
const isClickable = !!onRowPress;
|
|
630
713
|
const isRowHovered = hoveredCell?.rowKey === row.key;
|
|
631
714
|
const isPlaceholder = row.key.startsWith('_placeholder_row_');
|
|
715
|
+
const isLastRow = rowIndex === visibleRows.length - 1;
|
|
632
716
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
633
717
|
style: [styles.row, themedStyles.row, {
|
|
634
718
|
minHeight: currentRowHeight
|
|
635
|
-
}, isRowHovered && !isPlaceholder && themedStyles.rowHovered
|
|
719
|
+
}, isRowHovered && !isPlaceholder && themedStyles.rowHovered,
|
|
720
|
+
// 最后一行且没有 Column Stats 时移除底部边框,避免与表格底边重合
|
|
721
|
+
isLastRow && !actualShowColumnStats && {
|
|
722
|
+
borderBottomWidth: 0
|
|
723
|
+
}],
|
|
636
724
|
onPress: isClickable && !isPlaceholder ? () => onRowPress(row.key, row.data) : undefined,
|
|
637
725
|
activeOpacity: isClickable && !isPlaceholder ? 0.7 : 1,
|
|
638
726
|
disabled: !isClickable || isPlaceholder,
|
|
@@ -676,7 +764,11 @@ function StatisticsTable({
|
|
|
676
764
|
}, column.key);
|
|
677
765
|
})]
|
|
678
766
|
}, row.key);
|
|
679
|
-
})
|
|
767
|
+
}), enableVirtualization && virtualScrollData.offsetBottom > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
768
|
+
style: {
|
|
769
|
+
height: virtualScrollData.offsetBottom
|
|
770
|
+
}
|
|
771
|
+
})]
|
|
680
772
|
}), actualShowColumnStats && columnStats && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Animated.View, {
|
|
681
773
|
style: [styles.columnStatsContainer, themedStyles.columnStatsDivider, enableStatsAnimation && {
|
|
682
774
|
opacity: columnStatsAnimation,
|
|
@@ -826,15 +918,21 @@ function StatisticsTable({
|
|
|
826
918
|
children: rowStatsHeaders.mean
|
|
827
919
|
})
|
|
828
920
|
})]
|
|
829
|
-
}), /*#__PURE__*/(0, _jsxRuntime.
|
|
921
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
|
|
830
922
|
ref: rowStatsScrollRef,
|
|
831
923
|
style: {
|
|
832
924
|
maxHeight: maxHeight - columnStatsHeight
|
|
833
925
|
},
|
|
834
926
|
showsVerticalScrollIndicator: false,
|
|
835
927
|
scrollEnabled: false,
|
|
836
|
-
children:
|
|
837
|
-
|
|
928
|
+
children: [enableVirtualization && virtualScrollData.offsetTop > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
929
|
+
style: {
|
|
930
|
+
height: virtualScrollData.offsetTop
|
|
931
|
+
}
|
|
932
|
+
}), visibleRows.map((row, rowIndex) => {
|
|
933
|
+
// 计算在原始数组中的索引
|
|
934
|
+
const actualIndex = enableVirtualization ? virtualScrollData.startIndex + rowIndex : rowIndex;
|
|
935
|
+
const rowStat = rowStats?.[actualIndex];
|
|
838
936
|
const currentRowHeight = getRowHeightValue(row);
|
|
839
937
|
const isRowHovered = hoveredCell?.rowKey === row.key;
|
|
840
938
|
const isSumHovered = hoveredCell?.rowKey === row.key && hoveredCell?.columnKey === '__row_sum__';
|
|
@@ -885,23 +983,13 @@ function StatisticsTable({
|
|
|
885
983
|
})
|
|
886
984
|
})]
|
|
887
985
|
}, row.key);
|
|
888
|
-
})
|
|
986
|
+
}), enableVirtualization && virtualScrollData.offsetBottom > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
987
|
+
style: {
|
|
988
|
+
height: virtualScrollData.offsetBottom
|
|
989
|
+
}
|
|
990
|
+
})]
|
|
889
991
|
})]
|
|
890
992
|
})]
|
|
891
|
-
}), pagination !== false && pagination && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
892
|
-
style: [styles.paginationContainer, themedStyles.paginationContainer],
|
|
893
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Pagination.default, {
|
|
894
|
-
totalCount: `${pagination.total} items`,
|
|
895
|
-
pageParams: {
|
|
896
|
-
page: pagination.current,
|
|
897
|
-
pageSize: pagination.pageSize,
|
|
898
|
-
totalItems: pagination.total,
|
|
899
|
-
totalPage: Math.ceil(pagination.total / pagination.pageSize)
|
|
900
|
-
},
|
|
901
|
-
onPageChange: params => {
|
|
902
|
-
pagination.onChange?.(params.page, params.pageSize);
|
|
903
|
-
}
|
|
904
|
-
})
|
|
905
993
|
})]
|
|
906
994
|
});
|
|
907
995
|
}
|
|
@@ -990,9 +1078,6 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
990
1078
|
emptyText: {
|
|
991
1079
|
fontSize: 14
|
|
992
1080
|
},
|
|
993
|
-
paginationContainer: {
|
|
994
|
-
borderTopWidth: 1
|
|
995
|
-
},
|
|
996
1081
|
// Row Stats 容器
|
|
997
1082
|
rowStatsContainer: {
|
|
998
1083
|
position: 'relative'
|
|
@@ -1010,11 +1095,13 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
1010
1095
|
},
|
|
1011
1096
|
shadowLeft: {
|
|
1012
1097
|
left: 0,
|
|
1013
|
-
top:
|
|
1098
|
+
top: 40,
|
|
1099
|
+
// 从表头底部开始
|
|
1014
1100
|
bottom: 0
|
|
1015
1101
|
},
|
|
1016
1102
|
shadowRight: {
|
|
1017
|
-
top:
|
|
1103
|
+
top: 40,
|
|
1104
|
+
// 从表头底部开始
|
|
1018
1105
|
bottom: 0
|
|
1019
1106
|
},
|
|
1020
1107
|
// 水平阴影条
|
|
@@ -1027,7 +1114,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
1027
1114
|
backgroundColor: 'transparent'
|
|
1028
1115
|
},
|
|
1029
1116
|
shadowTop: {
|
|
1030
|
-
top:
|
|
1117
|
+
top: 40 // header height (paddingVertical: 12*2 + content ~16)
|
|
1031
1118
|
},
|
|
1032
1119
|
shadowBottom: {
|
|
1033
1120
|
// bottom 由动态样式设置
|