From 1e31d6d863002b4577cbd9ccd09514d888fd8b5c Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 2 Mar 2026 23:58:58 -0800 Subject: [PATCH] =?UTF-8?q?feat(mining-app):=20=E5=B8=82=E5=9C=BA=E6=95=B0?= =?UTF-8?q?=E6=8D=AE3=E4=B8=AA=E5=B0=8F=E6=95=B0=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E5=90=8E=E7=AB=AF=E8=BF=94=E5=9B=9E=E7=9A=84?= =?UTF-8?q?=E5=AE=8C=E6=95=B4=E7=B2=BE=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原先 formatCompact/formatWithCommas 内部用 double 运算,double 有效精度 仅约15-16位,对大数值(如98亿)除以1e8后小数部分会丢失精度。 后端 3 个字段均为 Decimal(30,8),通过 toFixed(8) 返回 8 位小数。 新增 formatCompactFull / formatWithCommasFull 函数: - _shiftDecimalLeft: 纯字符串小数点移位,不经 double 转换,零精度损失 - _addCommasFullPrecision: 整数部分加千位逗号 + 小数部分原样保留 - formatCompactFull: 万/亿缩写 + 完整后端精度(替代 formatCompact) - formatWithCommasFull: 逗号分隔 + 完整后端精度(替代 formatWithCommas) 兑换页 (trading_page.dart) 修改: - 剩余积分股: formatCompact(precision:4) → formatCompactFull (8位小数) - 已分配积分股: formatCompact(precision:2) → formatCompactFull (8位小数) - 积分股池: formatWithCommas → formatWithCommasFull (8位小数) - 已销毁量: 保持 formatIntWithCommas 整数显示不变 贡献值页 (contribution_page.dart) 修改: - 100亿销毁剩余量: formatAmount(4位) → formatCompactFull (完整精度) Co-Authored-By: Claude Opus 4.6 --- .../lib/core/utils/format_utils.dart | 88 +++++++++++++++++++ .../pages/contribution/contribution_page.dart | 2 +- .../pages/trading/trading_page.dart | 9 +- 3 files changed, 93 insertions(+), 6 deletions(-) diff --git a/frontend/mining-app/lib/core/utils/format_utils.dart b/frontend/mining-app/lib/core/utils/format_utils.dart index 9c4152da..8962369f 100644 --- a/frontend/mining-app/lib/core/utils/format_utils.dart +++ b/frontend/mining-app/lib/core/utils/format_utils.dart @@ -44,6 +44,94 @@ String formatWithCommas(String? value) { } } +/// 将小数点向左移动 [positions] 位(纯字符串操作,不丢精度) +/// e.g., _shiftDecimalLeft("9813194959.8105", 8) → "98.131949598105" +String _shiftDecimalLeft(String numStr, int positions) { + String sign = ''; + String v = numStr.trim(); + if (v.startsWith('-')) { + sign = '-'; + v = v.substring(1); + } + String intPart, decPart; + final dotIndex = v.indexOf('.'); + if (dotIndex >= 0) { + intPart = v.substring(0, dotIndex); + decPart = v.substring(dotIndex + 1); + } else { + intPart = v; + decPart = ''; + } + String digits = intPart + decPart; + int newDecPos = intPart.length - positions; + while (newDecPos <= 0) { + digits = '0$digits'; + newDecPos++; + } + final newInt = digits.substring(0, newDecPos); + var newDec = digits.substring(newDecPos); + newDec = newDec.replaceFirst(RegExp(r'0+$'), ''); + if (newDec.isEmpty) return '$sign$newInt'; + return '$sign$newInt.$newDec'; +} + +/// 整数部分加逗号千位分隔 + 保留原始小数位(纯字符串操作,不丢精度) +String _addCommasFullPrecision(String numStr) { + String sign = ''; + String v = numStr.trim(); + if (v.startsWith('-')) { + sign = '-'; + v = v.substring(1); + } + String intPart, decPart; + final dotIndex = v.indexOf('.'); + if (dotIndex >= 0) { + intPart = v.substring(0, dotIndex); + decPart = v.substring(dotIndex); // 包含小数点 + } else { + intPart = v; + decPart = ''; + } + final formatted = NumberFormat('#,##0', 'zh_CN').format(int.parse(intPart)); + if (decPart.isNotEmpty) { + decPart = decPart.replaceFirst(RegExp(r'0+$'), ''); + if (decPart == '.') decPart = ''; + } + return '$sign$formatted$decPart'; +} + +/// 与 formatCompact 相同的万/亿缩写,但使用字符串小数点移位保留后端返回的全部精度 +String formatCompactFull(String? value) { + if (value == null || value.isEmpty) return '0'; + try { + final d = Decimal.parse(value); + final abs = d.abs(); + if (abs >= Decimal.parse('1000000000000')) { + return '${_shiftDecimalLeft(value, 12)}万亿'; + } + if (abs >= Decimal.parse('100000000')) { + return '${_shiftDecimalLeft(value, 8)}亿'; + } + if (abs >= Decimal.parse('10000')) { + return '${_shiftDecimalLeft(value, 4)}万'; + } + return _addCommasFullPrecision(value); + } catch (e) { + return '0'; + } +} + +/// 原始数值 + 逗号千位分隔,保留后端返回的全部小数位(不受 double 精度限制) +String formatWithCommasFull(String? value) { + if (value == null || value.isEmpty) return '0'; + try { + Decimal.parse(value); // validate + return _addCommasFullPrecision(value); + } catch (e) { + return '0'; + } +} + /// 原始整数 + 逗号千位分隔(不保留小数) String formatIntWithCommas(String? value) { if (value == null || value.isEmpty) return '0'; 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 b3f73695..ace44918 100644 --- a/frontend/mining-app/lib/presentation/pages/contribution/contribution_page.dart +++ b/frontend/mining-app/lib/presentation/pages/contribution/contribution_page.dart @@ -231,7 +231,7 @@ class ContributionPage extends ConsumerWidget { hideAmounts ? '******' : market != null - ? formatAmount( + ? formatCompactFull( (Decimal.parse(market.totalShares) - Decimal.parse(market.totalMined) - Decimal.parse(market.blackHoleAmount)) diff --git a/frontend/mining-app/lib/presentation/pages/trading/trading_page.dart b/frontend/mining-app/lib/presentation/pages/trading/trading_page.dart index 1d93fd97..00f1cd44 100644 --- a/frontend/mining-app/lib/presentation/pages/trading/trading_page.dart +++ b/frontend/mining-app/lib/presentation/pages/trading/trading_page.dart @@ -360,12 +360,11 @@ class _TradingPageState extends ConsumerState { _buildMarketDataItem( '剩余积分股', market != null - ? formatCompact( + ? formatCompactFull( (Decimal.parse(market.totalShares) - Decimal.parse(market.totalMined) - Decimal.parse(market.blackHoleAmount)) - .toString(), - precision: 4) + .toString()) : null, _orange, isLoading, @@ -376,7 +375,7 @@ class _TradingPageState extends ConsumerState { // 而非交易流通池(circulationPool,目前无交易故为0) _buildMarketDataItem( '已分配积分股', - market != null ? formatCompact(market.totalMined) : null, + market != null ? formatCompactFull(market.totalMined) : null, _orange, isLoading, ), @@ -389,7 +388,7 @@ class _TradingPageState extends ConsumerState { children: [ _buildMarketDataItem( '积分股池', - market != null ? formatWithCommas(market.greenPoints) : null, + market != null ? formatWithCommasFull(market.greenPoints) : null, _orange, isLoading, ),