@beppla/tapas-ui 1.2.34 → 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.
@@ -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.map(col => matrix[row.key]?.[col.key] || { quantity: 0, amount: 0 });
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.map(col => {
91
- const colCells = rows.map(row => matrix[row.key]?.[col.key] || { quantity: 0, amount: 0 });
92
- const sumQuantity = colCells.reduce((sum, cell) => sum + cell.quantity, 0);
93
- const sumAmount = colCells.reduce((sum, cell) => sum + cell.amount, 0);
94
- const count = colCells.length || 1;
95
- const meanQuantity = sumQuantity / count;
96
- const meanAmount = sumAmount / count;
97
- return { sumQuantity, sumAmount, meanQuantity, meanAmount };
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
- <View key={column.key} style={[styles.headerCell, { width: column.width || 150 }]}>
135
- <Text style={styles.headerText}>{column.title}</Text>
136
- </View>
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
- {rows.map((row, rowIndex) => {
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
- <View key={row.key} style={styles.row}>
160
- <View style={[styles.cell, { width: rowLabelWidth }]}>
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 key={column.key} style={[styles.cell, { width: column.width || 150 }]}>
167
- <Text style={styles.cellQuantity}>{cell.quantity}</Text>
168
- <Text style={styles.cellAmount}>€{cell.amount.toFixed(2)}</Text>
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
- <Text style={styles.cellQuantity}>{rowStat.sumQuantity}</Text>
176
- <Text style={styles.cellAmount}>€{rowStat.sumAmount.toFixed(2)}</Text>
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
- <Text style={styles.cellQuantity}>{rowStat.meanQuantity.toFixed(1)}</Text>
180
- <Text style={styles.cellAmount}>€{rowStat.meanAmount.toFixed(2)}</Text>
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
- </View>
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, colIndex) => {
196
- const stat = columnStats[colIndex];
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 key={column.key} style={[styles.cell, { width: column.width || 150 }]}>
199
- <Text style={styles.cellQuantity}>{stat?.sumQuantity || 0}</Text>
200
- <Text style={styles.cellAmount}>€{stat?.sumAmount.toFixed(2) || '0.00'}</Text>
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, colIndex) => {
216
- const stat = columnStats[colIndex];
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 key={column.key} style={[styles.cell, { width: column.width || 150 }]}>
219
- <Text style={styles.cellQuantity}>{stat?.meanQuantity.toFixed(1) || '0.0'}</Text>
220
- <Text style={styles.cellAmount}>€{stat?.meanAmount.toFixed(2) || '0.00'}</Text>
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
-
@@ -2,6 +2,9 @@ export { StatisticsTable } from './StatisticsTable';
2
2
  export type {
3
3
  StatisticsTableProps,
4
4
  StatisticsTableColumn,
5
- StatisticsTableCell
5
+ StatisticsTableCell,
6
+ StatisticsTableAction,
7
+ CellAlignment,
8
+ CellDataType,
6
9
  } from './StatisticsTable';
7
10