@beppla/tapas-ui 1.2.33 → 1.2.34
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 +337 -0
- package/components/StatisticsTable/index.tsx +7 -0
- package/components/index.tsx +2 -0
- package/package/commonjs/StatisticsTable/StatisticsTable.js +384 -0
- package/package/commonjs/StatisticsTable/StatisticsTable.js.map +1 -0
- package/package/commonjs/StatisticsTable/index.js +13 -0
- package/package/commonjs/StatisticsTable/index.js.map +1 -0
- package/package/commonjs/index.js +8 -0
- package/package/commonjs/index.js.map +1 -1
- package/package/module/StatisticsTable/StatisticsTable.js +378 -0
- package/package/module/StatisticsTable/StatisticsTable.js.map +1 -0
- package/package/module/StatisticsTable/index.js +4 -0
- package/package/module/StatisticsTable/index.js.map +1 -0
- package/package/module/index.js +1 -0
- package/package/module/index.js.map +1 -1
- package/package/package.json +1 -1
- package/package/typescript/StatisticsTable/StatisticsTable.d.ts +35 -0
- package/package/typescript/StatisticsTable/StatisticsTable.d.ts.map +1 -0
- package/package/typescript/StatisticsTable/index.d.ts +3 -0
- package/package/typescript/StatisticsTable/index.d.ts.map +1 -0
- package/package/typescript/index.d.ts +2 -0
- package/package/typescript/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { View, ScrollView, StyleSheet, Platform } from 'react-native';
|
|
3
|
+
import Text from '../Text/Text';
|
|
4
|
+
import Pagination from '../Pagination/Pagination';
|
|
5
|
+
|
|
6
|
+
export interface StatisticsTableColumn {
|
|
7
|
+
key: string;
|
|
8
|
+
title: string;
|
|
9
|
+
width?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface StatisticsTableCell {
|
|
13
|
+
rowKey: string;
|
|
14
|
+
columnKey: string;
|
|
15
|
+
quantity: number;
|
|
16
|
+
amount: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface StatisticsTableProps {
|
|
20
|
+
// 行标题(如日期)
|
|
21
|
+
rows: Array<{ key: string; label: string }>;
|
|
22
|
+
// 列标题(如门店)
|
|
23
|
+
columns: StatisticsTableColumn[];
|
|
24
|
+
// 单元格数据
|
|
25
|
+
cells: StatisticsTableCell[];
|
|
26
|
+
// 是否显示行统计(Sum/Mean 列)
|
|
27
|
+
showRowStats?: boolean;
|
|
28
|
+
// 是否显示列统计(Sum/Mean 行)
|
|
29
|
+
showColumnStats?: boolean;
|
|
30
|
+
// 分页配置
|
|
31
|
+
pagination?: {
|
|
32
|
+
current: number;
|
|
33
|
+
pageSize: number;
|
|
34
|
+
total: number;
|
|
35
|
+
onChange?: (page: number, pageSize: number) => void;
|
|
36
|
+
} | false;
|
|
37
|
+
loading?: boolean;
|
|
38
|
+
emptyText?: string;
|
|
39
|
+
maxHeight?: number;
|
|
40
|
+
rowLabelWidth?: number;
|
|
41
|
+
style?: any;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function StatisticsTable({
|
|
45
|
+
rows,
|
|
46
|
+
columns,
|
|
47
|
+
cells,
|
|
48
|
+
showRowStats = false,
|
|
49
|
+
showColumnStats = false,
|
|
50
|
+
pagination,
|
|
51
|
+
loading = false,
|
|
52
|
+
emptyText = 'No data',
|
|
53
|
+
maxHeight = 500,
|
|
54
|
+
rowLabelWidth = 150,
|
|
55
|
+
style,
|
|
56
|
+
}: StatisticsTableProps) {
|
|
57
|
+
|
|
58
|
+
// 构建矩阵数据
|
|
59
|
+
const matrix = useMemo(() => {
|
|
60
|
+
const data: Record<string, Record<string, { quantity: number; amount: number }>> = {};
|
|
61
|
+
rows.forEach(row => {
|
|
62
|
+
if (!data[row.key]) {
|
|
63
|
+
data[row.key] = {};
|
|
64
|
+
}
|
|
65
|
+
columns.forEach(col => {
|
|
66
|
+
const cell = cells.find(c => c.rowKey === row.key && c.columnKey === col.key);
|
|
67
|
+
data[row.key]![col.key] = cell || { quantity: 0, amount: 0 };
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
return data;
|
|
71
|
+
}, [rows, columns, cells]);
|
|
72
|
+
|
|
73
|
+
// 计算行统计(每行的总和和平均值)
|
|
74
|
+
const rowStats = useMemo(() => {
|
|
75
|
+
if (!showRowStats) return null;
|
|
76
|
+
return rows.map(row => {
|
|
77
|
+
const rowCells = columns.map(col => matrix[row.key]?.[col.key] || { quantity: 0, amount: 0 });
|
|
78
|
+
const sumQuantity = rowCells.reduce((sum, cell) => sum + cell.quantity, 0);
|
|
79
|
+
const sumAmount = rowCells.reduce((sum, cell) => sum + cell.amount, 0);
|
|
80
|
+
const count = rowCells.length || 1;
|
|
81
|
+
const meanQuantity = sumQuantity / count;
|
|
82
|
+
const meanAmount = sumAmount / count;
|
|
83
|
+
return { sumQuantity, sumAmount, meanQuantity, meanAmount };
|
|
84
|
+
});
|
|
85
|
+
}, [rows, columns, matrix, showRowStats]);
|
|
86
|
+
|
|
87
|
+
// 计算列统计(每列的总和和平均值)
|
|
88
|
+
const columnStats = useMemo(() => {
|
|
89
|
+
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
|
+
});
|
|
99
|
+
}, [rows, columns, matrix, showColumnStats]);
|
|
100
|
+
|
|
101
|
+
if (loading) {
|
|
102
|
+
return (
|
|
103
|
+
<View style={[styles.container, style]}>
|
|
104
|
+
<View style={styles.loadingRow}>
|
|
105
|
+
<Text>Loading...</Text>
|
|
106
|
+
</View>
|
|
107
|
+
</View>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (rows.length === 0 || columns.length === 0) {
|
|
112
|
+
return (
|
|
113
|
+
<View style={[styles.container, style]}>
|
|
114
|
+
<View style={styles.emptyRow}>
|
|
115
|
+
<Text style={styles.emptyText}>{emptyText}</Text>
|
|
116
|
+
</View>
|
|
117
|
+
</View>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<View style={[styles.container, style]}>
|
|
123
|
+
<ScrollView
|
|
124
|
+
horizontal
|
|
125
|
+
showsHorizontalScrollIndicator={Platform.OS === 'web'}
|
|
126
|
+
>
|
|
127
|
+
<View>
|
|
128
|
+
{/* Table Header */}
|
|
129
|
+
<View style={styles.header}>
|
|
130
|
+
<View style={[styles.headerCell, { width: rowLabelWidth }]}>
|
|
131
|
+
<Text style={styles.headerText}></Text>
|
|
132
|
+
</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
|
+
))}
|
|
138
|
+
{showRowStats && (
|
|
139
|
+
<>
|
|
140
|
+
<View style={[styles.headerCell, { width: 120 }]}>
|
|
141
|
+
<Text style={styles.headerText}>Sum</Text>
|
|
142
|
+
</View>
|
|
143
|
+
<View style={[styles.headerCell, { width: 120 }]}>
|
|
144
|
+
<Text style={styles.headerText}>Mean</Text>
|
|
145
|
+
</View>
|
|
146
|
+
</>
|
|
147
|
+
)}
|
|
148
|
+
</View>
|
|
149
|
+
|
|
150
|
+
{/* Table Body */}
|
|
151
|
+
<ScrollView
|
|
152
|
+
style={{ maxHeight }}
|
|
153
|
+
showsVerticalScrollIndicator={Platform.OS === 'web'}
|
|
154
|
+
>
|
|
155
|
+
{rows.map((row, rowIndex) => {
|
|
156
|
+
const rowStat = rowStats?.[rowIndex];
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<View key={row.key} style={styles.row}>
|
|
160
|
+
<View style={[styles.cell, { width: rowLabelWidth }]}>
|
|
161
|
+
<Text style={styles.rowLabel}>{row.label}</Text>
|
|
162
|
+
</View>
|
|
163
|
+
{columns.map((column) => {
|
|
164
|
+
const cell = matrix[row.key]?.[column.key] || { quantity: 0, amount: 0 };
|
|
165
|
+
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>
|
|
169
|
+
</View>
|
|
170
|
+
);
|
|
171
|
+
})}
|
|
172
|
+
{showRowStats && rowStat && (
|
|
173
|
+
<>
|
|
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>
|
|
177
|
+
</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>
|
|
181
|
+
</View>
|
|
182
|
+
</>
|
|
183
|
+
)}
|
|
184
|
+
</View>
|
|
185
|
+
);
|
|
186
|
+
})}
|
|
187
|
+
|
|
188
|
+
{/* Column Statistics (Sum/Mean rows) */}
|
|
189
|
+
{showColumnStats && columnStats && (
|
|
190
|
+
<>
|
|
191
|
+
<View style={[styles.row, styles.statsRow]}>
|
|
192
|
+
<View style={[styles.cell, { width: rowLabelWidth }]}>
|
|
193
|
+
<Text style={styles.statsLabel}>Sum</Text>
|
|
194
|
+
</View>
|
|
195
|
+
{columns.map((column, colIndex) => {
|
|
196
|
+
const stat = columnStats[colIndex];
|
|
197
|
+
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>
|
|
201
|
+
</View>
|
|
202
|
+
);
|
|
203
|
+
})}
|
|
204
|
+
{showRowStats && (
|
|
205
|
+
<>
|
|
206
|
+
<View style={[styles.cell, { width: 120 }]} />
|
|
207
|
+
<View style={[styles.cell, { width: 120 }]} />
|
|
208
|
+
</>
|
|
209
|
+
)}
|
|
210
|
+
</View>
|
|
211
|
+
<View style={[styles.row, styles.statsRow]}>
|
|
212
|
+
<View style={[styles.cell, { width: rowLabelWidth }]}>
|
|
213
|
+
<Text style={styles.statsLabel}>Mean</Text>
|
|
214
|
+
</View>
|
|
215
|
+
{columns.map((column, colIndex) => {
|
|
216
|
+
const stat = columnStats[colIndex];
|
|
217
|
+
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>
|
|
221
|
+
</View>
|
|
222
|
+
);
|
|
223
|
+
})}
|
|
224
|
+
{showRowStats && (
|
|
225
|
+
<>
|
|
226
|
+
<View style={[styles.cell, { width: 120 }]} />
|
|
227
|
+
<View style={[styles.cell, { width: 120 }]} />
|
|
228
|
+
</>
|
|
229
|
+
)}
|
|
230
|
+
</View>
|
|
231
|
+
</>
|
|
232
|
+
)}
|
|
233
|
+
</ScrollView>
|
|
234
|
+
</View>
|
|
235
|
+
</ScrollView>
|
|
236
|
+
|
|
237
|
+
{/* Pagination */}
|
|
238
|
+
{pagination !== false && pagination && (
|
|
239
|
+
<View style={styles.paginationContainer}>
|
|
240
|
+
<Pagination
|
|
241
|
+
totalCount={`${pagination.total} items`}
|
|
242
|
+
pageParams={{
|
|
243
|
+
page: pagination.current,
|
|
244
|
+
pageSize: pagination.pageSize,
|
|
245
|
+
totalItems: pagination.total,
|
|
246
|
+
totalPage: Math.ceil(pagination.total / pagination.pageSize),
|
|
247
|
+
}}
|
|
248
|
+
onPageChange={(params) => {
|
|
249
|
+
pagination.onChange?.(params.page, params.pageSize);
|
|
250
|
+
}}
|
|
251
|
+
/>
|
|
252
|
+
</View>
|
|
253
|
+
)}
|
|
254
|
+
</View>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const styles = StyleSheet.create({
|
|
259
|
+
container: {
|
|
260
|
+
backgroundColor: '#FFFFFF',
|
|
261
|
+
borderRadius: 8,
|
|
262
|
+
overflow: 'hidden',
|
|
263
|
+
},
|
|
264
|
+
header: {
|
|
265
|
+
flexDirection: 'row',
|
|
266
|
+
backgroundColor: '#F5F5F5',
|
|
267
|
+
borderBottomWidth: 2,
|
|
268
|
+
borderBottomColor: '#E0E0E0',
|
|
269
|
+
},
|
|
270
|
+
headerCell: {
|
|
271
|
+
padding: 12,
|
|
272
|
+
borderRightWidth: 1,
|
|
273
|
+
borderRightColor: '#E0E0E0',
|
|
274
|
+
justifyContent: 'center',
|
|
275
|
+
},
|
|
276
|
+
headerText: {
|
|
277
|
+
fontSize: 14,
|
|
278
|
+
fontWeight: '600',
|
|
279
|
+
color: '#2C3E50',
|
|
280
|
+
},
|
|
281
|
+
row: {
|
|
282
|
+
flexDirection: 'row',
|
|
283
|
+
borderBottomWidth: 1,
|
|
284
|
+
borderBottomColor: '#F0F0F0',
|
|
285
|
+
backgroundColor: '#FFFFFF',
|
|
286
|
+
},
|
|
287
|
+
statsRow: {
|
|
288
|
+
backgroundColor: '#F9F9F9',
|
|
289
|
+
},
|
|
290
|
+
cell: {
|
|
291
|
+
padding: 12,
|
|
292
|
+
justifyContent: 'center',
|
|
293
|
+
borderRightWidth: 1,
|
|
294
|
+
borderRightColor: '#F0F0F0',
|
|
295
|
+
},
|
|
296
|
+
rowLabel: {
|
|
297
|
+
fontSize: 14,
|
|
298
|
+
fontWeight: '500',
|
|
299
|
+
color: '#2C3E50',
|
|
300
|
+
},
|
|
301
|
+
cellQuantity: {
|
|
302
|
+
fontSize: 14,
|
|
303
|
+
fontWeight: '500',
|
|
304
|
+
color: '#2C3E50',
|
|
305
|
+
marginBottom: 4,
|
|
306
|
+
},
|
|
307
|
+
cellAmount: {
|
|
308
|
+
fontSize: 12,
|
|
309
|
+
color: '#7F8C8D',
|
|
310
|
+
},
|
|
311
|
+
statsLabel: {
|
|
312
|
+
fontSize: 14,
|
|
313
|
+
fontWeight: '600',
|
|
314
|
+
color: '#2C3E50',
|
|
315
|
+
},
|
|
316
|
+
loadingRow: {
|
|
317
|
+
padding: 40,
|
|
318
|
+
justifyContent: 'center',
|
|
319
|
+
alignItems: 'center',
|
|
320
|
+
},
|
|
321
|
+
emptyRow: {
|
|
322
|
+
padding: 40,
|
|
323
|
+
justifyContent: 'center',
|
|
324
|
+
alignItems: 'center',
|
|
325
|
+
},
|
|
326
|
+
emptyText: {
|
|
327
|
+
fontSize: 14,
|
|
328
|
+
color: '#999',
|
|
329
|
+
},
|
|
330
|
+
paginationContainer: {
|
|
331
|
+
padding: 16,
|
|
332
|
+
borderTopWidth: 1,
|
|
333
|
+
borderTopColor: '#E0E0E0',
|
|
334
|
+
backgroundColor: '#FAFAFA',
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
|
package/components/index.tsx
CHANGED
|
@@ -175,6 +175,8 @@ export { DataTable } from "./DataTable";
|
|
|
175
175
|
export type { DataTableProps, DataTableColumn } from "./DataTable";
|
|
176
176
|
export { StatisticCard } from "./StatisticCard";
|
|
177
177
|
export type { StatisticCardProps } from "./StatisticCard";
|
|
178
|
+
export { StatisticsTable } from "./StatisticsTable";
|
|
179
|
+
export type { StatisticsTableProps, StatisticsTableColumn, StatisticsTableCell } from "./StatisticsTable";
|
|
178
180
|
|
|
179
181
|
// 工具函数
|
|
180
182
|
export { isMobile, capitalizeFirstLetter } from "./common";
|