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 {
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]
///
/// POST /wallet/settle-pre-planting (wallet-service)

View File

@ -785,17 +785,19 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
rewardService.getSettleableRewards(),
rewardService.getExpiredRewards(),
walletService.getLedgerStatistics(),
prePlantingService.getMyRewards(), //
walletService.getMyWallet(), // [2026-03-01] settleable_usdt
prePlantingService.getMyRewards(), //
walletService.getMyWallet(), // [2026-03-01]
walletService.getWalletPendingRewards(), // [2026-03-01] wallet
]);
final summary = results[0] as RewardSummary;
final pendingRewards = results[1] as List<PendingRewardItem>;
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 prePlantingRewards = results[5] as PrePlantingMyRewards;
final walletInfo = results[6] as WalletResponse;
final walletPendingRewards = results[7] as List<WalletPendingRewardItem>;
//
// SettleableRewardItem
@ -815,6 +817,31 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
settleableRewards = [...settleableRewards, ...prePlantingSettleable];
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 "已结算"
// summary.settledTotalUsdt
double settledFromLedger = 0.0;
@ -842,18 +869,18 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
if (mounted) {
debugPrint('[ProfilePage] 更新 UI 状态...');
setState(() {
_pendingUsdt = summary.pendingUsdt;
_pendingPower = summary.pendingHashpower;
// [2026-03-01] wallet-service
// wallet_accounts.settleable_usdt + source of truth
// reward-service summary.settleableUsdt
// [2026-03-01] wallet-service
// wallet_accounts + source of truth
// reward-service summary
_pendingUsdt = walletInfo.rewards.pendingUsdt;
_pendingPower = walletInfo.rewards.pendingHashpower;
_settleableUsdt = walletInfo.rewards.settleableUsdt;
// 使
_settledUsdt = settledFromLedger;
_expiredUsdt = summary.expiredTotalUsdt;
_expiredPower = summary.expiredTotalHashpower;
_remainingSeconds = summary.pendingRemainingSeconds;
_pendingRewards = pendingRewards;
_expiredUsdt = walletInfo.rewards.expiredTotalUsdt;
_expiredPower = walletInfo.rewards.expiredTotalHashpower;
_remainingSeconds = walletInfo.rewards.pendingRemainingSeconds;
_pendingRewards = mergedPendingRewards;
_settleableRewards = settleableRewards;
_expiredRewards = expiredRewards;
_isLoadingWallet = false;
@ -2475,6 +2502,11 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
final seconds = (remainingSeconds % 60).toString().padLeft(2, '0');
final countdown = '$hours:$minutes:$seconds';
// [2026-03-01] sourceOrderNo PPL []
final displayTypeName = item.sourceOrderNo.startsWith('PPL')
? '[预种] ${item.rightTypeName}'
: item.rightTypeName;
//
final List<String> amountParts = [];
if (item.usdtAmount > 0) {
@ -2507,7 +2539,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
item.rightTypeName,
displayTypeName,
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
@ -2586,7 +2618,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
item.rightTypeName,
displayTypeName,
style: const TextStyle(
fontSize: 13,
fontFamily: 'Inter',
@ -2618,6 +2650,11 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
final seconds = (remainingSeconds % 60).toString().padLeft(2, '0');
final countdown = '$hours:$minutes:$seconds';
// [2026-03-01] []
final displayTypeName = item.sourceOrderNo.startsWith('PPL')
? '[预种] ${item.rightTypeName}'
: item.rightTypeName;
//
final List<String> amountParts = [];
if (item.usdtAmount > 0) {
@ -2649,7 +2686,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
item.rightTypeName,
displayTypeName,
style: const TextStyle(
fontSize: 14,
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')}';
// [2026-03-01] []
final displayTypeName = item.sourceOrderId.startsWith('PPL')
? '[预种] ${item.rightTypeName}'
: item.rightTypeName;
//
final List<String> amountParts = [];
if (item.usdtAmount > 0) {
@ -3257,7 +3299,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
item.rightTypeName,
displayTypeName,
style: const TextStyle(
fontSize: 14,
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')}';
// [2026-03-01] sourceOrderId PPL []
final displayTypeName = item.sourceOrderId.startsWith('PPL')
? '[预种] ${item.rightTypeName}'
: item.rightTypeName;
//
final List<String> amountParts = [];
if (item.usdtAmount > 0) {
@ -3343,7 +3390,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
item.rightTypeName,
displayTypeName,
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
@ -3397,7 +3444,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
item.rightTypeName,
displayTypeName,
style: const TextStyle(
fontSize: 13,
fontFamily: 'Inter',