feat(mobile): profile页待领取/可结算/已过期列表统一显示预种数据

变更概要:
- wallet_service.dart: 新增 WalletPendingRewardItem 模型和 getWalletPendingRewards() 方法
  调用 GET /wallet/pending-rewards 获取 wallet-service 的待领取奖励列表
- profile_page.dart: 合并预种待领取奖励到列表中
  从 wallet-service 待领取列表中筛选 PPL 前缀的预种条目,转换为 PendingRewardItem
  与 reward-service 的正常认种待领取统一展示
- profile_page.dart: 已过期列表标记预种条目
  wallet-service GET /wallet/expired-rewards 已包含预种过期记录,
  渲染时通过 sourceOrderId.startsWith('PPL') 动态添加 [预种] 前缀
- profile_page.dart: 所有汇总金额统一从 wallet-service 取值
  _pendingUsdt / _expiredUsdt / _remainingSeconds 改为从 walletInfo.rewards 读取,
  wallet_accounts 包含正常认种 + 预种,是唯一的 source of truth

技术说明:
- 后端零改动,仅前端变更(零风险)
- 预种条目通过订单号 PPL 前缀与正常认种区分,避免重复显示
- 所有预种条目在卡片上显示 [预种] 前缀,方便用户区分来源

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-01 07:52:02 -08:00
parent 27cd72fe01
commit 4996c1d110
2 changed files with 151 additions and 18 deletions

View File

@ -161,6 +161,46 @@ class SettlePrePlantingResult {
} }
} }
/// [2026-03-01] wallet-service
/// GET /wallet/pending-rewards pending_rewards
class WalletPendingRewardItem {
final String id;
final double usdtAmount;
final double hashpowerAmount;
final String sourceOrderId;
final String allocationType;
final DateTime expireAt;
final String status;
final DateTime createdAt;
WalletPendingRewardItem({
required this.id,
required this.usdtAmount,
required this.hashpowerAmount,
required this.sourceOrderId,
required this.allocationType,
required this.expireAt,
required this.status,
required this.createdAt,
});
factory WalletPendingRewardItem.fromJson(Map<String, dynamic> json) {
return WalletPendingRewardItem(
id: json['id']?.toString() ?? '',
usdtAmount: (json['usdtAmount'] ?? 0).toDouble(),
hashpowerAmount: (json['hashpowerAmount'] ?? 0).toDouble(),
sourceOrderId: json['sourceOrderId'] ?? '',
allocationType: json['allocationType'] ?? '',
expireAt: DateTime.tryParse(json['expireAt'] ?? '') ?? DateTime.now(),
status: json['status'] ?? '',
createdAt: DateTime.tryParse(json['createdAt'] ?? '') ?? DateTime.now(),
);
}
/// PPL
bool get isPrePlanting => sourceOrderId.startsWith('PPL');
}
/// ///
enum FeeType { enum FeeType {
fixed, // fixed, //
@ -466,6 +506,52 @@ class WalletService {
} }
} }
/// [2026-03-01] wallet-service
///
/// GET /wallet/pending-rewards (wallet-service)
/// pending_rewards status=PENDING
/// profile sourceOrderId PPL
/// reward-service
Future<List<WalletPendingRewardItem>> getWalletPendingRewards() async {
try {
debugPrint('[WalletService] ========== 获取待领取奖励列表 ==========');
debugPrint('[WalletService] 请求: GET /wallet/pending-rewards');
final response = await _apiClient.get('/wallet/pending-rewards');
debugPrint('[WalletService] 响应状态码: ${response.statusCode}');
if (response.statusCode == 200) {
final responseData = response.data;
List<dynamic> dataList;
if (responseData is List) {
dataList = responseData;
} else if (responseData is Map<String, dynamic>) {
dataList = responseData['data'] as List<dynamic>? ?? [];
} else {
dataList = [];
}
debugPrint('[WalletService] 解析到 ${dataList.length} 条待领取奖励');
final items = dataList
.map((item) => WalletPendingRewardItem.fromJson(item as Map<String, dynamic>))
.toList();
debugPrint('[WalletService] ================================');
return items;
}
throw Exception('获取待领取奖励列表失败: ${response.statusCode}');
} catch (e, stackTrace) {
debugPrint('[WalletService] !!!!!!!!!! 获取待领取奖励列表异常 !!!!!!!!!!');
debugPrint('[WalletService] 错误: $e');
debugPrint('[WalletService] 堆栈: $stackTrace');
rethrow;
}
}
/// [2026-03-01] /// [2026-03-01]
/// ///
/// POST /wallet/settle-pre-planting (wallet-service) /// POST /wallet/settle-pre-planting (wallet-service)

View File

@ -785,17 +785,19 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
rewardService.getSettleableRewards(), rewardService.getSettleableRewards(),
rewardService.getExpiredRewards(), rewardService.getExpiredRewards(),
walletService.getLedgerStatistics(), walletService.getLedgerStatistics(),
prePlantingService.getMyRewards(), // prePlantingService.getMyRewards(), //
walletService.getMyWallet(), // [2026-03-01] settleable_usdt walletService.getMyWallet(), // [2026-03-01]
walletService.getWalletPendingRewards(), // [2026-03-01] wallet
]); ]);
final summary = results[0] as RewardSummary; final summary = results[0] as RewardSummary;
final pendingRewards = results[1] as List<PendingRewardItem>; final pendingRewards = results[1] as List<PendingRewardItem>;
var settleableRewards = results[2] as List<SettleableRewardItem>; var settleableRewards = results[2] as List<SettleableRewardItem>;
final expiredRewards = results[3] as List<ExpiredRewardItem>; var expiredRewards = results[3] as List<ExpiredRewardItem>;
final ledgerStats = results[4] as LedgerStatistics; final ledgerStats = results[4] as LedgerStatistics;
final prePlantingRewards = results[5] as PrePlantingMyRewards; final prePlantingRewards = results[5] as PrePlantingMyRewards;
final walletInfo = results[6] as WalletResponse; final walletInfo = results[6] as WalletResponse;
final walletPendingRewards = results[7] as List<WalletPendingRewardItem>;
// //
// SettleableRewardItem // SettleableRewardItem
@ -815,6 +817,31 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
settleableRewards = [...settleableRewards, ...prePlantingSettleable]; settleableRewards = [...settleableRewards, ...prePlantingSettleable];
debugPrint('[ProfilePage] 预种可结算奖励: ${prePlantingSettleable.length} 条, 金额: ${prePlantingRewards.settleableUsdt}'); debugPrint('[ProfilePage] 预种可结算奖励: ${prePlantingSettleable.length} 条, 金额: ${prePlantingRewards.settleableUsdt}');
// [2026-03-01]
// wallet-service pending_rewards +
// reward-service /rewards/pending
// sourceOrderId PPL
final prePlantingPending = walletPendingRewards
.where((r) => r.isPrePlanting)
.map((r) => PendingRewardItem(
id: 'wp-${r.id}',
rightType: r.allocationType,
usdtAmount: r.usdtAmount,
hashpowerAmount: r.hashpowerAmount,
createdAt: r.createdAt,
expireAt: r.expireAt,
remainingTimeMs: r.expireAt.difference(DateTime.now()).inMilliseconds.clamp(0, 86400000),
sourceOrderNo: r.sourceOrderId,
memo: '[预种] ${getAllocationTypeName(r.allocationType)}',
))
.toList();
final mergedPendingRewards = [...pendingRewards, ...prePlantingPending];
debugPrint('[ProfilePage] 预种待领取奖励: ${prePlantingPending.length}');
// [2026-03-01] wallet-service GET /wallet/expired-rewards
// []
// sourceOrderId.startsWith('PPL')
// REWARD_SETTLED "已结算" // REWARD_SETTLED "已结算"
// summary.settledTotalUsdt // summary.settledTotalUsdt
double settledFromLedger = 0.0; double settledFromLedger = 0.0;
@ -842,18 +869,18 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
if (mounted) { if (mounted) {
debugPrint('[ProfilePage] 更新 UI 状态...'); debugPrint('[ProfilePage] 更新 UI 状态...');
setState(() { setState(() {
_pendingUsdt = summary.pendingUsdt; // [2026-03-01] wallet-service
_pendingPower = summary.pendingHashpower; // wallet_accounts + source of truth
// [2026-03-01] wallet-service // reward-service summary
// wallet_accounts.settleable_usdt + source of truth _pendingUsdt = walletInfo.rewards.pendingUsdt;
// reward-service summary.settleableUsdt _pendingPower = walletInfo.rewards.pendingHashpower;
_settleableUsdt = walletInfo.rewards.settleableUsdt; _settleableUsdt = walletInfo.rewards.settleableUsdt;
// 使 // 使
_settledUsdt = settledFromLedger; _settledUsdt = settledFromLedger;
_expiredUsdt = summary.expiredTotalUsdt; _expiredUsdt = walletInfo.rewards.expiredTotalUsdt;
_expiredPower = summary.expiredTotalHashpower; _expiredPower = walletInfo.rewards.expiredTotalHashpower;
_remainingSeconds = summary.pendingRemainingSeconds; _remainingSeconds = walletInfo.rewards.pendingRemainingSeconds;
_pendingRewards = pendingRewards; _pendingRewards = mergedPendingRewards;
_settleableRewards = settleableRewards; _settleableRewards = settleableRewards;
_expiredRewards = expiredRewards; _expiredRewards = expiredRewards;
_isLoadingWallet = false; _isLoadingWallet = false;
@ -2475,6 +2502,11 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
final seconds = (remainingSeconds % 60).toString().padLeft(2, '0'); final seconds = (remainingSeconds % 60).toString().padLeft(2, '0');
final countdown = '$hours:$minutes:$seconds'; final countdown = '$hours:$minutes:$seconds';
// [2026-03-01] sourceOrderNo PPL []
final displayTypeName = item.sourceOrderNo.startsWith('PPL')
? '[预种] ${item.rightTypeName}'
: item.rightTypeName;
// //
final List<String> amountParts = []; final List<String> amountParts = [];
if (item.usdtAmount > 0) { if (item.usdtAmount > 0) {
@ -2507,7 +2539,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
item.rightTypeName, displayTypeName,
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
fontFamily: 'Inter', fontFamily: 'Inter',
@ -2586,7 +2618,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
item.rightTypeName, displayTypeName,
style: const TextStyle( style: const TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: 'Inter', fontFamily: 'Inter',
@ -2618,6 +2650,11 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
final seconds = (remainingSeconds % 60).toString().padLeft(2, '0'); final seconds = (remainingSeconds % 60).toString().padLeft(2, '0');
final countdown = '$hours:$minutes:$seconds'; final countdown = '$hours:$minutes:$seconds';
// [2026-03-01] []
final displayTypeName = item.sourceOrderNo.startsWith('PPL')
? '[预种] ${item.rightTypeName}'
: item.rightTypeName;
// //
final List<String> amountParts = []; final List<String> amountParts = [];
if (item.usdtAmount > 0) { if (item.usdtAmount > 0) {
@ -2649,7 +2686,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
item.rightTypeName, displayTypeName,
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
fontFamily: 'Inter', fontFamily: 'Inter',
@ -3227,6 +3264,11 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
// //
final expiredDate = '${item.expiredAt.month}/${item.expiredAt.day} ${item.expiredAt.hour.toString().padLeft(2, '0')}:${item.expiredAt.minute.toString().padLeft(2, '0')}'; final expiredDate = '${item.expiredAt.month}/${item.expiredAt.day} ${item.expiredAt.hour.toString().padLeft(2, '0')}:${item.expiredAt.minute.toString().padLeft(2, '0')}';
// [2026-03-01] []
final displayTypeName = item.sourceOrderId.startsWith('PPL')
? '[预种] ${item.rightTypeName}'
: item.rightTypeName;
// //
final List<String> amountParts = []; final List<String> amountParts = [];
if (item.usdtAmount > 0) { if (item.usdtAmount > 0) {
@ -3257,7 +3299,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
item.rightTypeName, displayTypeName,
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
fontFamily: 'Inter', fontFamily: 'Inter',
@ -3311,6 +3353,11 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
// //
final expiredDate = '${item.expiredAt.month}/${item.expiredAt.day} ${item.expiredAt.hour.toString().padLeft(2, '0')}:${item.expiredAt.minute.toString().padLeft(2, '0')}'; final expiredDate = '${item.expiredAt.month}/${item.expiredAt.day} ${item.expiredAt.hour.toString().padLeft(2, '0')}:${item.expiredAt.minute.toString().padLeft(2, '0')}';
// [2026-03-01] sourceOrderId PPL []
final displayTypeName = item.sourceOrderId.startsWith('PPL')
? '[预种] ${item.rightTypeName}'
: item.rightTypeName;
// //
final List<String> amountParts = []; final List<String> amountParts = [];
if (item.usdtAmount > 0) { if (item.usdtAmount > 0) {
@ -3343,7 +3390,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
item.rightTypeName, displayTypeName,
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
fontFamily: 'Inter', fontFamily: 'Inter',
@ -3397,7 +3444,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
item.rightTypeName, displayTypeName,
style: const TextStyle( style: const TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: 'Inter', fontFamily: 'Inter',