From 0576733579f75ff83a403308b03fdf3966ca3c22 Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 2 Mar 2026 22:28:55 -0800 Subject: [PATCH] =?UTF-8?q?fix(mining-app):=20=E7=BB=9F=E4=B8=80=E8=B4=A1?= =?UTF-8?q?=E7=8C=AE=E5=80=BC=E9=A1=B5=E4=B8=8E=E5=85=91=E6=8D=A2=E9=A1=B5?= =?UTF-8?q?=E7=9A=84=E5=89=A9=E4=BD=99=E7=A7=AF=E5=88=86=E8=82=A1=E8=AE=A1?= =?UTF-8?q?=E7=AE=97=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 贡献值页原先使用 sharePoolBalance API (Pool A + Pool B 余额) 显示剩余积分股, 兑换页使用公式 totalShares - totalMined - blackHoleAmount 计算。 两者显示结果不一致 (差异约 228,343 积分股)。 根本原因调查: - mining-wallet-service 的 Kafka 消费者在服务重启期间丢失了部分事件 - Pool B 仅处理了 15.7% 的挖矿分配事件 (28,261/180,497) - Pool A 遗漏了 66,591 笔销毁扣减事件 - 池账户余额是通过 Kafka 事件维护的记账台账,存在消费遗漏 - totalShares - totalMined - blackHoleAmount 公式基于实际挖矿和销毁数据计算,是数学上的 ground truth - 实际用户分配和销毁均 100% 准确,仅池账户记账有偏差 修复方案: - 贡献值页改用 marketOverviewProvider (同兑换页) - 使用公式 totalShares - totalMined - blackHoleAmount 计算剩余积分股 - 两个页面现在显示完全一致的数据 Co-Authored-By: Claude Opus 4.6 --- .../pages/contribution/contribution_page.dart | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/frontend/mining-app/lib/presentation/pages/contribution/contribution_page.dart b/frontend/mining-app/lib/presentation/pages/contribution/contribution_page.dart index 2ca4ff7a..b3f73695 100644 --- a/frontend/mining-app/lib/presentation/pages/contribution/contribution_page.dart +++ b/frontend/mining-app/lib/presentation/pages/contribution/contribution_page.dart @@ -1,3 +1,4 @@ +import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; @@ -5,10 +6,12 @@ import '../../../core/constants/app_colors.dart'; import '../../../core/router/routes.dart'; import '../../../core/utils/format_utils.dart'; import '../../../domain/entities/contribution.dart'; +import '../../../domain/entities/market_overview.dart'; import '../../../domain/entities/share_account.dart'; import '../../providers/user_providers.dart'; import '../../providers/contribution_providers.dart'; import '../../providers/mining_providers.dart'; +import '../../providers/trading_providers.dart'; import '../../widgets/shimmer_loading.dart'; class ContributionPage extends ConsumerWidget { @@ -25,8 +28,8 @@ class ContributionPage extends ConsumerWidget { final contributionAsync = ref.watch(contributionProvider(accountSequence)); // 获取预估收益(基于用户每秒收益计算) final estimatedEarnings = ref.watch(estimatedEarningsProvider(accountSequence)); - // 获取积分股池余量 - final sharePoolAsync = ref.watch(sharePoolBalanceProvider); + // 获取市场概览(用于计算剩余积分股 = totalShares - totalMined - blackHoleAmount) + final marketAsync = ref.watch(marketOverviewProvider); // 获取挖矿账户信息(含首次挖矿时间,用于730天倒计时) final shareAccountAsync = ref.watch(shareAccountProvider(accountSequence)); @@ -35,8 +38,8 @@ class ContributionPage extends ConsumerWidget { final contribution = contributionAsync.valueOrNull; final hasError = contributionAsync.hasError; final error = contributionAsync.error; - final isSharePoolLoading = sharePoolAsync.isLoading; - final sharePoolBalance = sharePoolAsync.valueOrNull; + final isMarketLoading = marketAsync.isLoading; + final market = marketAsync.valueOrNull; return Scaffold( backgroundColor: AppColors.backgroundOf(context), @@ -45,7 +48,7 @@ class ContributionPage extends ConsumerWidget { child: RefreshIndicator( onRefresh: () async { ref.invalidate(contributionProvider(accountSequence)); - ref.invalidate(sharePoolBalanceProvider); + ref.invalidate(marketOverviewProvider); ref.invalidate(shareAccountProvider(accountSequence)); }, child: hasError && contribution == null @@ -74,7 +77,7 @@ class ContributionPage extends ConsumerWidget { sliver: SliverList( delegate: SliverChildListDelegate([ // 总贡献值卡片 - _buildTotalContributionCard(context, ref, contribution, isLoading, sharePoolBalance, isSharePoolLoading), + _buildTotalContributionCard(context, ref, contribution, isLoading, market, isMarketLoading), const SizedBox(height: 16), // 三栏统计 _buildThreeColumnStats(context, ref, contribution, isLoading), @@ -165,8 +168,8 @@ class ContributionPage extends ConsumerWidget { WidgetRef ref, Contribution? contribution, bool isLoading, - SharePoolBalance? sharePoolBalance, - bool isSharePoolLoading, + MarketOverview? market, + bool isMarketLoading, ) { final isDark = AppColors.isDark(context); final total = contribution?.totalContribution ?? '0'; @@ -219,13 +222,21 @@ class ContributionPage extends ConsumerWidget { '100亿销毁剩余量: ', style: TextStyle(fontSize: 12, color: AppColors.textSecondaryOf(context)), ), - isSharePoolLoading + isMarketLoading ? const ShimmerText( placeholder: '----', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: _orange), ) : Text( - hideAmounts ? '******' : formatAmount(sharePoolBalance?.total ?? '0'), + hideAmounts + ? '******' + : market != null + ? formatAmount( + (Decimal.parse(market.totalShares) - + Decimal.parse(market.totalMined) - + Decimal.parse(market.blackHoleAmount)) + .toString()) + : '0', style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: _orange), ), ],