From e4a2a0e37ad3aaac3b99438fe77e8a2dbc7a516c Mon Sep 17 00:00:00 2001 From: hailin Date: Sun, 1 Mar 2026 10:41:11 -0800 Subject: [PATCH] =?UTF-8?q?feat(ledger):=20=E6=B5=81=E6=B0=B4=E6=98=8E?= =?UTF-8?q?=E7=BB=86=E6=98=BE=E7=A4=BA=E6=9D=A5=E6=BA=90=E7=94=A8=E6=88=B7?= =?UTF-8?q?ID=20+=20=E7=BB=9F=E8=AE=A1=E5=85=BC=E5=AE=B9=E5=8E=86=E5=8F=B2?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E8=BD=AC=E6=8D=A2=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - wallet_service.dart: LedgerEntry 新增 sourceAccountFromMemo 从 memo 提取来源用户 - ledger_detail_page.dart: 流水列表项显示"来自 Dxxx"金色文字 - ledger_detail_page.dart: 权益详情弹窗添加来源用户行和备注行 - wallet-application.service.ts: 统计/趋势保留 memo 兼容历史批量转换记录 Co-Authored-By: Claude Opus 4.6 --- .../services/wallet-application.service.ts | 12 +++++++++--- .../lib/core/services/wallet_service.dart | 7 +++++++ .../presentation/pages/ledger_detail_page.dart | 14 ++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/backend/services/wallet-service/src/application/services/wallet-application.service.ts b/backend/services/wallet-service/src/application/services/wallet-application.service.ts index 28fa2e9c..b7d6e03f 100644 --- a/backend/services/wallet-service/src/application/services/wallet-application.service.ts +++ b/backend/services/wallet-service/src/application/services/wallet-application.service.ts @@ -2697,7 +2697,11 @@ export class WalletApplicationService { const amount = Number(entry.amount); // 只计算非临时性流水的收支统计 - if (!excludeFromStats.has(entry.entryType)) { + // 兼容历史数据:老的批量转换记录 entry_type 仍为 REWARD_TO_SETTLEABLE, + // 通过 memo 中 "pending rewards settled" 识别并排除 + const isExcluded = excludeFromStats.has(entry.entryType) || + (entry.entryType === 'REWARD_TO_SETTLEABLE' && entry.memo?.includes('pending rewards settled')); + if (!isExcluded) { if (amount > 0) { totalIncome += amount; } else { @@ -2791,8 +2795,10 @@ export class WalletApplicationService { const amount = Number(entry.amount); const existing = dailyMap.get(dateStr) || { income: 0, expense: 0, count: 0 }; - // 只计算非临时性流水的收支统计 - if (!excludeFromStats.has(entry.entryType)) { + // 只计算非临时性流水的收支统计(兼容历史批量转换数据) + const isExcluded = excludeFromStats.has(entry.entryType) || + (entry.entryType === 'REWARD_TO_SETTLEABLE' && entry.memo?.includes('pending rewards settled')); + if (!isExcluded) { if (amount > 0) { existing.income += amount; periodIncome += amount; diff --git a/frontend/mobile-app/lib/core/services/wallet_service.dart b/frontend/mobile-app/lib/core/services/wallet_service.dart index 51091980..039e5e2d 100644 --- a/frontend/mobile-app/lib/core/services/wallet_service.dart +++ b/frontend/mobile-app/lib/core/services/wallet_service.dart @@ -1522,6 +1522,13 @@ class LedgerEntry { return baseName; } + /// 从 memo 中提取来源用户ID(格式如:"来源: D26022600016") + String? get sourceAccountFromMemo { + if (memo == null) return null; + final match = RegExp(r'来源:\s*(D\d+)').firstMatch(memo!); + return match?.group(1); + } + /// 是否为收入 bool get isIncome => amount > 0; } diff --git a/frontend/mobile-app/lib/features/trading/presentation/pages/ledger_detail_page.dart b/frontend/mobile-app/lib/features/trading/presentation/pages/ledger_detail_page.dart index c185b033..2cd881c1 100644 --- a/frontend/mobile-app/lib/features/trading/presentation/pages/ledger_detail_page.dart +++ b/frontend/mobile-app/lib/features/trading/presentation/pages/ledger_detail_page.dart @@ -1002,6 +1002,16 @@ class _LedgerDetailPageState extends ConsumerState color: Color(0xFF5D4037), ), ), + if (entry.sourceAccountFromMemo != null) ...[ + const SizedBox(height: 2), + Text( + '来自 ${entry.sourceAccountFromMemo}', + style: const TextStyle( + fontSize: 11, + color: Color(0xFFD4AF37), + ), + ), + ], const SizedBox(height: 4), Text( _formatDate(entry.createdAt), @@ -1715,11 +1725,15 @@ class _RewardDetailSheet extends StatelessWidget { ), _buildDetailRow('权益类型', entry.displayName), _buildDetailRow('流水类型', entry.entryTypeName), + if (entry.sourceAccountFromMemo != null) + _buildDetailRow('来源用户', entry.sourceAccountFromMemo!), if (entry.balanceAfter != null) _buildDetailRow('入账后余额', '${_formatAmount(entry.balanceAfter!)} 绿积分'), _buildDetailRow('入账时间', _formatDateTime(entry.createdAt)), if (entry.refOrderId != null && entry.refOrderId!.isNotEmpty) _buildDetailRow('关联订单', entry.refOrderId!), + if (entry.memo != null && entry.memo!.isNotEmpty) + _buildDetailRow('备注', entry.memo!), ], ), ),