@beppla/tapas-ui 1.2.35 → 1.2.36
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/components/StatisticsTable/StatisticsTable.tsx +451 -45
- package/components/StatisticsTable/index.tsx +4 -1
- package/components/index.tsx +10 -2
- package/package/commonjs/StatisticsTable/StatisticsTable.js +390 -75
- package/package/commonjs/StatisticsTable/StatisticsTable.js.map +1 -1
- package/package/commonjs/index.js.map +1 -1
- package/package/module/StatisticsTable/StatisticsTable.js +393 -78
- package/package/module/StatisticsTable/StatisticsTable.js.map +1 -1
- package/package/module/index.js.map +1 -1
- package/package/package.json +1 -1
- package/package/typescript/StatisticsTable/StatisticsTable.d.ts +29 -1
- package/package/typescript/StatisticsTable/StatisticsTable.d.ts.map +1 -1
- package/package/typescript/StatisticsTable/index.d.ts +1 -1
- package/package/typescript/StatisticsTable/index.d.ts.map +1 -1
- package/package/typescript/index.d.ts +3 -2
- package/package/typescript/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
import React, { useMemo } from 'react';
|
|
2
|
-
import { View, ScrollView, StyleSheet, Platform } from 'react-native';
|
|
1
|
+
import React, { useMemo, useCallback, useState, useRef } from 'react';
|
|
2
|
+
import { View, ScrollView, StyleSheet, Platform, TouchableOpacity, NativeScrollEvent, NativeSyntheticEvent } from 'react-native';
|
|
3
3
|
import Text from '../Text/Text';
|
|
4
4
|
import Pagination from '../Pagination/Pagination';
|
|
5
5
|
|
|
6
|
+
export type CellAlignment = 'left' | 'center' | 'right';
|
|
7
|
+
export type CellDataType = 'text' | 'number' | 'currency' | 'custom';
|
|
8
|
+
|
|
6
9
|
export interface StatisticsTableColumn {
|
|
7
10
|
key: string;
|
|
8
11
|
title: string;
|
|
9
|
-
width?: number;
|
|
12
|
+
width?: number; // 动态列宽
|
|
13
|
+
minWidth?: number; // 最小列宽
|
|
14
|
+
maxWidth?: number; // 最大列宽
|
|
15
|
+
align?: CellAlignment;
|
|
16
|
+
dataType?: CellDataType;
|
|
17
|
+
isAction?: boolean; // 是否是 action 列
|
|
18
|
+
render?: (cell: StatisticsTableCell, rowData: any) => React.ReactNode;
|
|
10
19
|
}
|
|
11
20
|
|
|
12
21
|
export interface StatisticsTableCell {
|
|
@@ -14,11 +23,18 @@ export interface StatisticsTableCell {
|
|
|
14
23
|
columnKey: string;
|
|
15
24
|
quantity: number;
|
|
16
25
|
amount: number;
|
|
26
|
+
data?: any; // 额外数据,用于 render 函数
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface StatisticsTableAction {
|
|
30
|
+
label: string;
|
|
31
|
+
onPress: (rowKey: string, data?: any) => void;
|
|
32
|
+
icon?: string;
|
|
17
33
|
}
|
|
18
34
|
|
|
19
35
|
export interface StatisticsTableProps {
|
|
20
36
|
// 行标题(如日期)
|
|
21
|
-
rows: Array<{ key: string; label: string }>;
|
|
37
|
+
rows: Array<{ key: string; label: string; data?: any; height?: number }>;
|
|
22
38
|
// 列标题(如门店)
|
|
23
39
|
columns: StatisticsTableColumn[];
|
|
24
40
|
// 单元格数据
|
|
@@ -39,6 +55,25 @@ export interface StatisticsTableProps {
|
|
|
39
55
|
maxHeight?: number;
|
|
40
56
|
rowLabelWidth?: number;
|
|
41
57
|
style?: any;
|
|
58
|
+
// 格式化函数
|
|
59
|
+
formatQuantity?: (quantity: number) => string;
|
|
60
|
+
formatAmount?: (amount: number) => string;
|
|
61
|
+
// 虚拟滚动(性能优化)
|
|
62
|
+
enableVirtualization?: boolean;
|
|
63
|
+
virtualRowHeight?: number;
|
|
64
|
+
// 行点击事件
|
|
65
|
+
onRowPress?: (rowKey: string, data?: any) => void;
|
|
66
|
+
// Action 列配置
|
|
67
|
+
rowActions?: StatisticsTableAction[];
|
|
68
|
+
// 动态行高
|
|
69
|
+
rowHeight?: number; // 默认行高
|
|
70
|
+
getRowHeight?: (rowKey: string, rowData?: any) => number; // 自定义每行高度
|
|
71
|
+
// 动态列宽
|
|
72
|
+
getColumnWidth?: (columnKey: string, column: StatisticsTableColumn) => number; // 自定义列宽
|
|
73
|
+
// 滚动阴影效果
|
|
74
|
+
enableScrollShadow?: boolean; // 启用滚动阴影
|
|
75
|
+
scrollShadowColor?: string; // 阴影颜色
|
|
76
|
+
scrollShadowSize?: number; // 阴影大小
|
|
42
77
|
}
|
|
43
78
|
|
|
44
79
|
export function StatisticsTable({
|
|
@@ -53,11 +88,36 @@ export function StatisticsTable({
|
|
|
53
88
|
maxHeight = 500,
|
|
54
89
|
rowLabelWidth = 150,
|
|
55
90
|
style,
|
|
91
|
+
formatQuantity = (q) => q.toLocaleString(),
|
|
92
|
+
formatAmount = (a) => `€${a.toFixed(2)}`,
|
|
93
|
+
enableVirtualization = false,
|
|
94
|
+
virtualRowHeight = 60,
|
|
95
|
+
onRowPress,
|
|
96
|
+
rowActions,
|
|
97
|
+
rowHeight = 60,
|
|
98
|
+
getRowHeight,
|
|
99
|
+
getColumnWidth,
|
|
100
|
+
enableScrollShadow = true,
|
|
101
|
+
scrollShadowColor = 'rgba(0, 0, 0, 0.1)',
|
|
102
|
+
scrollShadowSize = 8,
|
|
56
103
|
}: StatisticsTableProps) {
|
|
57
104
|
|
|
105
|
+
// 滚动状态
|
|
106
|
+
const [horizontalScrollState, setHorizontalScrollState] = useState({
|
|
107
|
+
isAtStart: true,
|
|
108
|
+
isAtEnd: false,
|
|
109
|
+
});
|
|
110
|
+
const [verticalScrollState, setVerticalScrollState] = useState({
|
|
111
|
+
isAtStart: true,
|
|
112
|
+
isAtEnd: false,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const horizontalScrollRef = useRef<ScrollView>(null);
|
|
116
|
+
const verticalScrollRef = useRef<ScrollView>(null);
|
|
117
|
+
|
|
58
118
|
// 构建矩阵数据
|
|
59
119
|
const matrix = useMemo(() => {
|
|
60
|
-
const data: Record<string, Record<string, { quantity: number; amount: number }>> = {};
|
|
120
|
+
const data: Record<string, Record<string, { quantity: number; amount: number; data?: any }>> = {};
|
|
61
121
|
rows.forEach(row => {
|
|
62
122
|
if (!data[row.key]) {
|
|
63
123
|
data[row.key] = {};
|
|
@@ -74,7 +134,9 @@ export function StatisticsTable({
|
|
|
74
134
|
const rowStats = useMemo(() => {
|
|
75
135
|
if (!showRowStats) return null;
|
|
76
136
|
return rows.map(row => {
|
|
77
|
-
const rowCells = columns
|
|
137
|
+
const rowCells = columns
|
|
138
|
+
.filter(col => !col.isAction) // 排除 action 列
|
|
139
|
+
.map(col => matrix[row.key]?.[col.key] || { quantity: 0, amount: 0 });
|
|
78
140
|
const sumQuantity = rowCells.reduce((sum, cell) => sum + cell.quantity, 0);
|
|
79
141
|
const sumAmount = rowCells.reduce((sum, cell) => sum + cell.amount, 0);
|
|
80
142
|
const count = rowCells.length || 1;
|
|
@@ -87,17 +149,104 @@ export function StatisticsTable({
|
|
|
87
149
|
// 计算列统计(每列的总和和平均值)
|
|
88
150
|
const columnStats = useMemo(() => {
|
|
89
151
|
if (!showColumnStats) return null;
|
|
90
|
-
return columns
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
152
|
+
return columns
|
|
153
|
+
.filter(col => !col.isAction) // 排除 action 列
|
|
154
|
+
.map(col => {
|
|
155
|
+
const colCells = rows.map(row => matrix[row.key]?.[col.key] || { quantity: 0, amount: 0 });
|
|
156
|
+
const sumQuantity = colCells.reduce((sum, cell) => sum + cell.quantity, 0);
|
|
157
|
+
const sumAmount = colCells.reduce((sum, cell) => sum + cell.amount, 0);
|
|
158
|
+
const count = colCells.length || 1;
|
|
159
|
+
const meanQuantity = sumQuantity / count;
|
|
160
|
+
const meanAmount = sumAmount / count;
|
|
161
|
+
return { sumQuantity, sumAmount, meanQuantity, meanAmount };
|
|
162
|
+
});
|
|
99
163
|
}, [rows, columns, matrix, showColumnStats]);
|
|
100
164
|
|
|
165
|
+
// 获取列宽
|
|
166
|
+
const getColWidth = useCallback((column: StatisticsTableColumn) => {
|
|
167
|
+
if (getColumnWidth) {
|
|
168
|
+
return getColumnWidth(column.key, column);
|
|
169
|
+
}
|
|
170
|
+
return column.width || 150;
|
|
171
|
+
}, [getColumnWidth]);
|
|
172
|
+
|
|
173
|
+
// 获取行高
|
|
174
|
+
const getRowHeightValue = useCallback((row: { key: string; label: string; data?: any; height?: number }) => {
|
|
175
|
+
if (row.height) return row.height;
|
|
176
|
+
if (getRowHeight) {
|
|
177
|
+
return getRowHeight(row.key, row.data);
|
|
178
|
+
}
|
|
179
|
+
return rowHeight;
|
|
180
|
+
}, [rowHeight, getRowHeight]);
|
|
181
|
+
|
|
182
|
+
// 处理水平滚动
|
|
183
|
+
const handleHorizontalScroll = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
184
|
+
const { contentOffset, contentSize, layoutMeasurement } = event.nativeEvent;
|
|
185
|
+
const isAtStart = contentOffset.x <= 0;
|
|
186
|
+
const isAtEnd = contentOffset.x + layoutMeasurement.width >= contentSize.width - 1;
|
|
187
|
+
|
|
188
|
+
setHorizontalScrollState({ isAtStart, isAtEnd });
|
|
189
|
+
}, []);
|
|
190
|
+
|
|
191
|
+
// 处理垂直滚动
|
|
192
|
+
const handleVerticalScroll = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
193
|
+
const { contentOffset, contentSize, layoutMeasurement } = event.nativeEvent;
|
|
194
|
+
const isAtStart = contentOffset.y <= 0;
|
|
195
|
+
const isAtEnd = contentOffset.y + layoutMeasurement.height >= contentSize.height - 1;
|
|
196
|
+
|
|
197
|
+
setVerticalScrollState({ isAtStart, isAtEnd });
|
|
198
|
+
}, []);
|
|
199
|
+
|
|
200
|
+
// 获取单元格对齐方式
|
|
201
|
+
const getCellAlignment = useCallback((column: StatisticsTableColumn, isHeader: boolean = false) => {
|
|
202
|
+
if (column.align) return column.align;
|
|
203
|
+
if (isHeader) return 'center';
|
|
204
|
+
// 默认:数字右对齐,文本左对齐
|
|
205
|
+
if (column.dataType === 'number' || column.dataType === 'currency') {
|
|
206
|
+
return 'right';
|
|
207
|
+
}
|
|
208
|
+
return 'left';
|
|
209
|
+
}, []);
|
|
210
|
+
|
|
211
|
+
// 渲染单元格内容
|
|
212
|
+
const renderCellContent = useCallback((column: StatisticsTableColumn, cell: any, rowData?: any) => {
|
|
213
|
+
// 自定义渲染
|
|
214
|
+
if (column.render) {
|
|
215
|
+
return column.render(cell, rowData);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Action 列
|
|
219
|
+
if (column.isAction && rowActions) {
|
|
220
|
+
return (
|
|
221
|
+
<View style={styles.actionContainer}>
|
|
222
|
+
{rowActions.map((action, index) => (
|
|
223
|
+
<TouchableOpacity
|
|
224
|
+
key={index}
|
|
225
|
+
style={styles.actionButton}
|
|
226
|
+
onPress={() => action.onPress(cell.rowKey, rowData)}
|
|
227
|
+
activeOpacity={0.7}
|
|
228
|
+
>
|
|
229
|
+
<Text style={styles.actionButtonText}>{action.label}</Text>
|
|
230
|
+
</TouchableOpacity>
|
|
231
|
+
))}
|
|
232
|
+
</View>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 默认渲染
|
|
237
|
+
const align = getCellAlignment(column);
|
|
238
|
+
return (
|
|
239
|
+
<View style={[styles.cellContentContainer, { alignItems: align === 'left' ? 'flex-start' : align === 'right' ? 'flex-end' : 'center' }]}>
|
|
240
|
+
<Text style={[styles.cellQuantity, align === 'right' && styles.cellQuantityRight]}>
|
|
241
|
+
{formatQuantity(cell.quantity)}
|
|
242
|
+
</Text>
|
|
243
|
+
<Text style={[styles.cellAmount, align === 'right' && styles.cellAmountRight]}>
|
|
244
|
+
{formatAmount(cell.amount)}
|
|
245
|
+
</Text>
|
|
246
|
+
</View>
|
|
247
|
+
);
|
|
248
|
+
}, [rowActions, getCellAlignment, formatQuantity, formatAmount]);
|
|
249
|
+
|
|
101
250
|
if (loading) {
|
|
102
251
|
return (
|
|
103
252
|
<View style={[styles.container, style]}>
|
|
@@ -118,23 +267,89 @@ export function StatisticsTable({
|
|
|
118
267
|
);
|
|
119
268
|
}
|
|
120
269
|
|
|
270
|
+
// 虚拟滚动实现(简化版)
|
|
271
|
+
const visibleRows = enableVirtualization
|
|
272
|
+
? rows.slice(0, Math.ceil(maxHeight / virtualRowHeight))
|
|
273
|
+
: rows;
|
|
274
|
+
|
|
121
275
|
return (
|
|
122
276
|
<View style={[styles.container, style]}>
|
|
277
|
+
{/* 左边阴影 */}
|
|
278
|
+
{enableScrollShadow && !horizontalScrollState.isAtStart && (
|
|
279
|
+
<View style={[
|
|
280
|
+
styles.shadowLeft,
|
|
281
|
+
{
|
|
282
|
+
width: scrollShadowSize,
|
|
283
|
+
backgroundColor: 'transparent',
|
|
284
|
+
shadowColor: scrollShadowColor,
|
|
285
|
+
}
|
|
286
|
+
]} />
|
|
287
|
+
)}
|
|
288
|
+
|
|
289
|
+
{/* 右边阴影 */}
|
|
290
|
+
{enableScrollShadow && !horizontalScrollState.isAtEnd && (
|
|
291
|
+
<View style={[
|
|
292
|
+
styles.shadowRight,
|
|
293
|
+
{
|
|
294
|
+
width: scrollShadowSize,
|
|
295
|
+
backgroundColor: 'transparent',
|
|
296
|
+
shadowColor: scrollShadowColor,
|
|
297
|
+
}
|
|
298
|
+
]} />
|
|
299
|
+
)}
|
|
300
|
+
|
|
123
301
|
<ScrollView
|
|
302
|
+
ref={horizontalScrollRef}
|
|
124
303
|
horizontal
|
|
125
304
|
showsHorizontalScrollIndicator={Platform.OS === 'web'}
|
|
305
|
+
onScroll={handleHorizontalScroll}
|
|
306
|
+
scrollEventThrottle={16}
|
|
126
307
|
>
|
|
127
308
|
<View>
|
|
309
|
+
{/* 顶部阴影 */}
|
|
310
|
+
{enableScrollShadow && !verticalScrollState.isAtStart && (
|
|
311
|
+
<View style={[
|
|
312
|
+
styles.shadowTop,
|
|
313
|
+
{
|
|
314
|
+
height: scrollShadowSize,
|
|
315
|
+
backgroundColor: 'transparent',
|
|
316
|
+
shadowColor: scrollShadowColor,
|
|
317
|
+
}
|
|
318
|
+
]} />
|
|
319
|
+
)}
|
|
320
|
+
|
|
128
321
|
{/* Table Header */}
|
|
129
322
|
<View style={styles.header}>
|
|
130
323
|
<View style={[styles.headerCell, { width: rowLabelWidth }]}>
|
|
131
324
|
<Text style={styles.headerText}></Text>
|
|
132
325
|
</View>
|
|
133
|
-
{columns.map((column) =>
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
326
|
+
{columns.map((column) => {
|
|
327
|
+
const align = getCellAlignment(column, true);
|
|
328
|
+
const colWidth = getColWidth(column);
|
|
329
|
+
return (
|
|
330
|
+
<View
|
|
331
|
+
key={column.key}
|
|
332
|
+
style={[
|
|
333
|
+
styles.headerCell,
|
|
334
|
+
{
|
|
335
|
+
width: colWidth,
|
|
336
|
+
minWidth: column.minWidth,
|
|
337
|
+
maxWidth: column.maxWidth,
|
|
338
|
+
},
|
|
339
|
+
align === 'right' && styles.headerCellRight,
|
|
340
|
+
align === 'left' && styles.headerCellLeft,
|
|
341
|
+
]}
|
|
342
|
+
>
|
|
343
|
+
<Text style={[
|
|
344
|
+
styles.headerText,
|
|
345
|
+
align === 'right' && styles.headerTextRight,
|
|
346
|
+
align === 'left' && styles.headerTextLeft,
|
|
347
|
+
]}>
|
|
348
|
+
{column.title}
|
|
349
|
+
</Text>
|
|
350
|
+
</View>
|
|
351
|
+
);
|
|
352
|
+
})}
|
|
138
353
|
{showRowStats && (
|
|
139
354
|
<>
|
|
140
355
|
<View style={[styles.headerCell, { width: 120 }]}>
|
|
@@ -149,39 +364,78 @@ export function StatisticsTable({
|
|
|
149
364
|
|
|
150
365
|
{/* Table Body */}
|
|
151
366
|
<ScrollView
|
|
367
|
+
ref={verticalScrollRef}
|
|
152
368
|
style={{ maxHeight }}
|
|
153
369
|
showsVerticalScrollIndicator={Platform.OS === 'web'}
|
|
370
|
+
removeClippedSubviews={enableVirtualization}
|
|
371
|
+
onScroll={handleVerticalScroll}
|
|
372
|
+
scrollEventThrottle={16}
|
|
154
373
|
>
|
|
155
|
-
{
|
|
374
|
+
{visibleRows.map((row, rowIndex) => {
|
|
156
375
|
const rowStat = rowStats?.[rowIndex];
|
|
376
|
+
const isClickable = !!onRowPress;
|
|
377
|
+
const currentRowHeight = getRowHeightValue(row);
|
|
157
378
|
|
|
158
379
|
return (
|
|
159
|
-
<
|
|
160
|
-
|
|
380
|
+
<TouchableOpacity
|
|
381
|
+
key={row.key}
|
|
382
|
+
style={[styles.row, { minHeight: currentRowHeight }]}
|
|
383
|
+
onPress={isClickable ? () => onRowPress(row.key, row.data) : undefined}
|
|
384
|
+
activeOpacity={isClickable ? 0.7 : 1}
|
|
385
|
+
disabled={!isClickable}
|
|
386
|
+
>
|
|
387
|
+
<View style={[styles.cell, { width: rowLabelWidth, minHeight: currentRowHeight }]}>
|
|
161
388
|
<Text style={styles.rowLabel}>{row.label}</Text>
|
|
162
389
|
</View>
|
|
163
390
|
{columns.map((column) => {
|
|
164
|
-
const cell = matrix[row.key]?.[column.key] || { quantity: 0, amount: 0 };
|
|
391
|
+
const cell = matrix[row.key]?.[column.key] || { quantity: 0, amount: 0, rowKey: row.key, columnKey: column.key };
|
|
392
|
+
const align = getCellAlignment(column);
|
|
393
|
+
const colWidth = getColWidth(column);
|
|
394
|
+
|
|
165
395
|
return (
|
|
166
|
-
<View
|
|
167
|
-
|
|
168
|
-
|
|
396
|
+
<View
|
|
397
|
+
key={column.key}
|
|
398
|
+
style={[
|
|
399
|
+
styles.cell,
|
|
400
|
+
{
|
|
401
|
+
width: colWidth,
|
|
402
|
+
minWidth: column.minWidth,
|
|
403
|
+
maxWidth: column.maxWidth,
|
|
404
|
+
minHeight: currentRowHeight,
|
|
405
|
+
},
|
|
406
|
+
align === 'right' && styles.cellRight,
|
|
407
|
+
align === 'left' && styles.cellLeft,
|
|
408
|
+
]}
|
|
409
|
+
>
|
|
410
|
+
{renderCellContent(column, cell, row.data)}
|
|
169
411
|
</View>
|
|
170
412
|
);
|
|
171
413
|
})}
|
|
172
414
|
{showRowStats && rowStat && (
|
|
173
415
|
<>
|
|
174
|
-
<View style={[styles.cell, { width: 120 }]}>
|
|
175
|
-
<
|
|
176
|
-
|
|
416
|
+
<View style={[styles.cell, styles.cellRight, { width: 120 }]}>
|
|
417
|
+
<View style={styles.cellContentContainer}>
|
|
418
|
+
<Text style={[styles.cellQuantity, styles.cellQuantityRight]}>
|
|
419
|
+
{formatQuantity(rowStat.sumQuantity)}
|
|
420
|
+
</Text>
|
|
421
|
+
<Text style={[styles.cellAmount, styles.cellAmountRight]}>
|
|
422
|
+
{formatAmount(rowStat.sumAmount)}
|
|
423
|
+
</Text>
|
|
424
|
+
</View>
|
|
177
425
|
</View>
|
|
178
|
-
<View style={[styles.cell, { width: 120 }]}>
|
|
179
|
-
<
|
|
180
|
-
|
|
426
|
+
<View style={[styles.cell, styles.cellRight, { width: 120 }]}>
|
|
427
|
+
<View style={styles.cellContentContainer}>
|
|
428
|
+
<Text style={[styles.cellQuantity, styles.cellQuantityRight]}>
|
|
429
|
+
{formatQuantity(Math.round(rowStat.meanQuantity * 10) / 10)}
|
|
430
|
+
</Text>
|
|
431
|
+
<Text style={[styles.cellAmount, styles.cellAmountRight]}>
|
|
432
|
+
{formatAmount(rowStat.meanAmount)}
|
|
433
|
+
</Text>
|
|
434
|
+
</View>
|
|
181
435
|
</View>
|
|
182
436
|
</>
|
|
183
437
|
)}
|
|
184
|
-
</
|
|
438
|
+
</TouchableOpacity>
|
|
185
439
|
);
|
|
186
440
|
})}
|
|
187
441
|
|
|
@@ -192,12 +446,36 @@ export function StatisticsTable({
|
|
|
192
446
|
<View style={[styles.cell, { width: rowLabelWidth }]}>
|
|
193
447
|
<Text style={styles.statsLabel}>Sum</Text>
|
|
194
448
|
</View>
|
|
195
|
-
{columns.map((column
|
|
196
|
-
const
|
|
449
|
+
{columns.map((column) => {
|
|
450
|
+
const colWidth = getColWidth(column);
|
|
451
|
+
if (column.isAction) {
|
|
452
|
+
return <View key={column.key} style={[styles.cell, { width: colWidth, minWidth: column.minWidth, maxWidth: column.maxWidth }]} />;
|
|
453
|
+
}
|
|
454
|
+
const stat = columnStats[columns.filter(c => !c.isAction).indexOf(column)];
|
|
455
|
+
const align = getCellAlignment(column);
|
|
456
|
+
|
|
197
457
|
return (
|
|
198
|
-
<View
|
|
199
|
-
|
|
200
|
-
|
|
458
|
+
<View
|
|
459
|
+
key={column.key}
|
|
460
|
+
style={[
|
|
461
|
+
styles.cell,
|
|
462
|
+
{
|
|
463
|
+
width: colWidth,
|
|
464
|
+
minWidth: column.minWidth,
|
|
465
|
+
maxWidth: column.maxWidth,
|
|
466
|
+
},
|
|
467
|
+
align === 'right' && styles.cellRight,
|
|
468
|
+
align === 'left' && styles.cellLeft,
|
|
469
|
+
]}
|
|
470
|
+
>
|
|
471
|
+
<View style={styles.cellContentContainer}>
|
|
472
|
+
<Text style={[styles.cellQuantity, align === 'right' && styles.cellQuantityRight]}>
|
|
473
|
+
{formatQuantity(stat?.sumQuantity || 0)}
|
|
474
|
+
</Text>
|
|
475
|
+
<Text style={[styles.cellAmount, align === 'right' && styles.cellAmountRight]}>
|
|
476
|
+
{formatAmount(stat?.sumAmount || 0)}
|
|
477
|
+
</Text>
|
|
478
|
+
</View>
|
|
201
479
|
</View>
|
|
202
480
|
);
|
|
203
481
|
})}
|
|
@@ -212,12 +490,36 @@ export function StatisticsTable({
|
|
|
212
490
|
<View style={[styles.cell, { width: rowLabelWidth }]}>
|
|
213
491
|
<Text style={styles.statsLabel}>Mean</Text>
|
|
214
492
|
</View>
|
|
215
|
-
{columns.map((column
|
|
216
|
-
const
|
|
493
|
+
{columns.map((column) => {
|
|
494
|
+
const colWidth = getColWidth(column);
|
|
495
|
+
if (column.isAction) {
|
|
496
|
+
return <View key={column.key} style={[styles.cell, { width: colWidth, minWidth: column.minWidth, maxWidth: column.maxWidth }]} />;
|
|
497
|
+
}
|
|
498
|
+
const stat = columnStats[columns.filter(c => !c.isAction).indexOf(column)];
|
|
499
|
+
const align = getCellAlignment(column);
|
|
500
|
+
|
|
217
501
|
return (
|
|
218
|
-
<View
|
|
219
|
-
|
|
220
|
-
|
|
502
|
+
<View
|
|
503
|
+
key={column.key}
|
|
504
|
+
style={[
|
|
505
|
+
styles.cell,
|
|
506
|
+
{
|
|
507
|
+
width: colWidth,
|
|
508
|
+
minWidth: column.minWidth,
|
|
509
|
+
maxWidth: column.maxWidth,
|
|
510
|
+
},
|
|
511
|
+
align === 'right' && styles.cellRight,
|
|
512
|
+
align === 'left' && styles.cellLeft,
|
|
513
|
+
]}
|
|
514
|
+
>
|
|
515
|
+
<View style={styles.cellContentContainer}>
|
|
516
|
+
<Text style={[styles.cellQuantity, align === 'right' && styles.cellQuantityRight]}>
|
|
517
|
+
{formatQuantity(Math.round((stat?.meanQuantity || 0) * 10) / 10)}
|
|
518
|
+
</Text>
|
|
519
|
+
<Text style={[styles.cellAmount, align === 'right' && styles.cellAmountRight]}>
|
|
520
|
+
{formatAmount(stat?.meanAmount || 0)}
|
|
521
|
+
</Text>
|
|
522
|
+
</View>
|
|
221
523
|
</View>
|
|
222
524
|
);
|
|
223
525
|
})}
|
|
@@ -231,6 +533,18 @@ export function StatisticsTable({
|
|
|
231
533
|
</>
|
|
232
534
|
)}
|
|
233
535
|
</ScrollView>
|
|
536
|
+
|
|
537
|
+
{/* 底部阴影 */}
|
|
538
|
+
{enableScrollShadow && !verticalScrollState.isAtEnd && (
|
|
539
|
+
<View style={[
|
|
540
|
+
styles.shadowBottom,
|
|
541
|
+
{
|
|
542
|
+
height: scrollShadowSize,
|
|
543
|
+
backgroundColor: 'transparent',
|
|
544
|
+
shadowColor: scrollShadowColor,
|
|
545
|
+
}
|
|
546
|
+
]} />
|
|
547
|
+
)}
|
|
234
548
|
</View>
|
|
235
549
|
</ScrollView>
|
|
236
550
|
|
|
@@ -272,11 +586,25 @@ const styles = StyleSheet.create({
|
|
|
272
586
|
borderRightWidth: 1,
|
|
273
587
|
borderRightColor: '#E0E0E0',
|
|
274
588
|
justifyContent: 'center',
|
|
589
|
+
alignItems: 'center',
|
|
590
|
+
},
|
|
591
|
+
headerCellLeft: {
|
|
592
|
+
alignItems: 'flex-start',
|
|
593
|
+
},
|
|
594
|
+
headerCellRight: {
|
|
595
|
+
alignItems: 'flex-end',
|
|
275
596
|
},
|
|
276
597
|
headerText: {
|
|
277
598
|
fontSize: 14,
|
|
278
599
|
fontWeight: '600',
|
|
279
600
|
color: '#2C3E50',
|
|
601
|
+
textAlign: 'center',
|
|
602
|
+
},
|
|
603
|
+
headerTextLeft: {
|
|
604
|
+
textAlign: 'left',
|
|
605
|
+
},
|
|
606
|
+
headerTextRight: {
|
|
607
|
+
textAlign: 'right',
|
|
280
608
|
},
|
|
281
609
|
row: {
|
|
282
610
|
flexDirection: 'row',
|
|
@@ -290,9 +618,19 @@ const styles = StyleSheet.create({
|
|
|
290
618
|
cell: {
|
|
291
619
|
padding: 12,
|
|
292
620
|
justifyContent: 'center',
|
|
621
|
+
alignItems: 'center',
|
|
293
622
|
borderRightWidth: 1,
|
|
294
623
|
borderRightColor: '#F0F0F0',
|
|
295
624
|
},
|
|
625
|
+
cellLeft: {
|
|
626
|
+
alignItems: 'flex-start',
|
|
627
|
+
},
|
|
628
|
+
cellRight: {
|
|
629
|
+
alignItems: 'flex-end',
|
|
630
|
+
},
|
|
631
|
+
cellContentContainer: {
|
|
632
|
+
width: '100%',
|
|
633
|
+
},
|
|
296
634
|
rowLabel: {
|
|
297
635
|
fontSize: 14,
|
|
298
636
|
fontWeight: '500',
|
|
@@ -303,16 +641,40 @@ const styles = StyleSheet.create({
|
|
|
303
641
|
fontWeight: '500',
|
|
304
642
|
color: '#2C3E50',
|
|
305
643
|
marginBottom: 4,
|
|
644
|
+
textAlign: 'left',
|
|
645
|
+
},
|
|
646
|
+
cellQuantityRight: {
|
|
647
|
+
textAlign: 'right',
|
|
306
648
|
},
|
|
307
649
|
cellAmount: {
|
|
308
650
|
fontSize: 12,
|
|
309
651
|
color: '#7F8C8D',
|
|
652
|
+
textAlign: 'left',
|
|
653
|
+
},
|
|
654
|
+
cellAmountRight: {
|
|
655
|
+
textAlign: 'right',
|
|
310
656
|
},
|
|
311
657
|
statsLabel: {
|
|
312
658
|
fontSize: 14,
|
|
313
659
|
fontWeight: '600',
|
|
314
660
|
color: '#2C3E50',
|
|
315
661
|
},
|
|
662
|
+
actionContainer: {
|
|
663
|
+
flexDirection: 'row',
|
|
664
|
+
gap: 8,
|
|
665
|
+
justifyContent: 'center',
|
|
666
|
+
},
|
|
667
|
+
actionButton: {
|
|
668
|
+
paddingHorizontal: 12,
|
|
669
|
+
paddingVertical: 6,
|
|
670
|
+
backgroundColor: '#FF6B00',
|
|
671
|
+
borderRadius: 4,
|
|
672
|
+
},
|
|
673
|
+
actionButtonText: {
|
|
674
|
+
fontSize: 12,
|
|
675
|
+
fontWeight: '600',
|
|
676
|
+
color: '#FFFFFF',
|
|
677
|
+
},
|
|
316
678
|
loadingRow: {
|
|
317
679
|
padding: 40,
|
|
318
680
|
justifyContent: 'center',
|
|
@@ -333,5 +695,49 @@ const styles = StyleSheet.create({
|
|
|
333
695
|
borderTopColor: '#E0E0E0',
|
|
334
696
|
backgroundColor: '#FAFAFA',
|
|
335
697
|
},
|
|
698
|
+
// 滚动阴影
|
|
699
|
+
shadowLeft: {
|
|
700
|
+
position: 'absolute',
|
|
701
|
+
left: 0,
|
|
702
|
+
top: 0,
|
|
703
|
+
bottom: 0,
|
|
704
|
+
zIndex: 10,
|
|
705
|
+
shadowOffset: { width: 2, height: 0 },
|
|
706
|
+
shadowOpacity: 0.15,
|
|
707
|
+
shadowRadius: 4,
|
|
708
|
+
elevation: 4,
|
|
709
|
+
},
|
|
710
|
+
shadowRight: {
|
|
711
|
+
position: 'absolute',
|
|
712
|
+
right: 0,
|
|
713
|
+
top: 0,
|
|
714
|
+
bottom: 0,
|
|
715
|
+
zIndex: 10,
|
|
716
|
+
shadowOffset: { width: -2, height: 0 },
|
|
717
|
+
shadowOpacity: 0.15,
|
|
718
|
+
shadowRadius: 4,
|
|
719
|
+
elevation: 4,
|
|
720
|
+
},
|
|
721
|
+
shadowTop: {
|
|
722
|
+
position: 'absolute',
|
|
723
|
+
left: 0,
|
|
724
|
+
right: 0,
|
|
725
|
+
top: 0,
|
|
726
|
+
zIndex: 10,
|
|
727
|
+
shadowOffset: { width: 0, height: 2 },
|
|
728
|
+
shadowOpacity: 0.15,
|
|
729
|
+
shadowRadius: 4,
|
|
730
|
+
elevation: 4,
|
|
731
|
+
},
|
|
732
|
+
shadowBottom: {
|
|
733
|
+
position: 'absolute',
|
|
734
|
+
left: 0,
|
|
735
|
+
right: 0,
|
|
736
|
+
bottom: 0,
|
|
737
|
+
zIndex: 10,
|
|
738
|
+
shadowOffset: { width: 0, height: -2 },
|
|
739
|
+
shadowOpacity: 0.15,
|
|
740
|
+
shadowRadius: 4,
|
|
741
|
+
elevation: 4,
|
|
742
|
+
},
|
|
336
743
|
});
|
|
337
|
-
|