feat(wallet/profile): 添加可结算和已过期奖励逐笔显示功能
后端: - wallet-service 新增 getSettleableRewards() 和 getExpiredRewards() 方法 - 新增 GET /wallet/settleable-rewards 和 GET /wallet/expired-rewards API 前端: - reward_service.dart 新增 SettleableRewardItem、ExpiredRewardItem 数据模型 - profile_page.dart 可结算区域和已过期区域支持逐笔明细显示 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
bad14bcb32
commit
257236480f
|
|
@ -117,4 +117,38 @@ export class WalletController {
|
||||||
}>> {
|
}>> {
|
||||||
return this.walletService.getPendingRewards(user.accountSequence);
|
return this.walletService.getPendingRewards(user.accountSequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('settleable-rewards')
|
||||||
|
@ApiOperation({ summary: '查询可结算奖励列表', description: '获取用户的逐笔可结算奖励(已领取待结算)' })
|
||||||
|
@ApiResponse({ status: 200, description: '可结算奖励列表' })
|
||||||
|
async getSettleableRewards(
|
||||||
|
@CurrentUser() user: CurrentUserPayload,
|
||||||
|
): Promise<Array<{
|
||||||
|
id: string;
|
||||||
|
usdtAmount: number;
|
||||||
|
hashpowerAmount: number;
|
||||||
|
sourceOrderId: string;
|
||||||
|
allocationType: string;
|
||||||
|
settledAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}>> {
|
||||||
|
return this.walletService.getSettleableRewards(user.accountSequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('expired-rewards')
|
||||||
|
@ApiOperation({ summary: '查询已过期奖励列表', description: '获取用户的逐笔已过期奖励(24h未领取)' })
|
||||||
|
@ApiResponse({ status: 200, description: '已过期奖励列表' })
|
||||||
|
async getExpiredRewards(
|
||||||
|
@CurrentUser() user: CurrentUserPayload,
|
||||||
|
): Promise<Array<{
|
||||||
|
id: string;
|
||||||
|
usdtAmount: number;
|
||||||
|
hashpowerAmount: number;
|
||||||
|
sourceOrderId: string;
|
||||||
|
allocationType: string;
|
||||||
|
expiredAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}>> {
|
||||||
|
return this.walletService.getExpiredRewards(user.accountSequence);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1622,6 +1622,62 @@ export class WalletApplicationService {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户的可结算奖励列表
|
||||||
|
* 从 pending_rewards 表中读取 status=SETTLED 的记录
|
||||||
|
*/
|
||||||
|
async getSettleableRewards(accountSequence: string): Promise<Array<{
|
||||||
|
id: string;
|
||||||
|
usdtAmount: number;
|
||||||
|
hashpowerAmount: number;
|
||||||
|
sourceOrderId: string;
|
||||||
|
allocationType: string;
|
||||||
|
settledAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}>> {
|
||||||
|
const rewards = await this.pendingRewardRepo.findByAccountSequence(
|
||||||
|
accountSequence,
|
||||||
|
PendingRewardStatus.SETTLED,
|
||||||
|
);
|
||||||
|
return rewards.map(r => ({
|
||||||
|
id: r.id.toString(),
|
||||||
|
usdtAmount: r.usdtAmount.value,
|
||||||
|
hashpowerAmount: r.hashpowerAmount.value,
|
||||||
|
sourceOrderId: r.sourceOrderId,
|
||||||
|
allocationType: r.allocationType,
|
||||||
|
settledAt: r.settledAt?.toISOString() ?? '',
|
||||||
|
createdAt: r.createdAt.toISOString(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户的已过期奖励列表
|
||||||
|
* 从 pending_rewards 表中读取 status=EXPIRED 的记录
|
||||||
|
*/
|
||||||
|
async getExpiredRewards(accountSequence: string): Promise<Array<{
|
||||||
|
id: string;
|
||||||
|
usdtAmount: number;
|
||||||
|
hashpowerAmount: number;
|
||||||
|
sourceOrderId: string;
|
||||||
|
allocationType: string;
|
||||||
|
expiredAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}>> {
|
||||||
|
const rewards = await this.pendingRewardRepo.findByAccountSequence(
|
||||||
|
accountSequence,
|
||||||
|
PendingRewardStatus.EXPIRED,
|
||||||
|
);
|
||||||
|
return rewards.map(r => ({
|
||||||
|
id: r.id.toString(),
|
||||||
|
usdtAmount: r.usdtAmount.value,
|
||||||
|
hashpowerAmount: r.hashpowerAmount.value,
|
||||||
|
sourceOrderId: r.sourceOrderId,
|
||||||
|
allocationType: r.allocationType,
|
||||||
|
expiredAt: r.expiredAt?.toISOString() ?? '',
|
||||||
|
createdAt: r.createdAt.toISOString(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 结算用户所有待领取奖励
|
* 结算用户所有待领取奖励
|
||||||
* 当用户认种后调用,将 PENDING 状态的奖励转为 SETTLED
|
* 当用户认种后调用,将 PENDING 状态的奖励转为 SETTLED
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,26 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import '../network/api_client.dart';
|
import '../network/api_client.dart';
|
||||||
|
|
||||||
|
/// 获取权益类型的中文名称(公共方法)
|
||||||
|
String getAllocationTypeName(String allocationType) {
|
||||||
|
switch (allocationType) {
|
||||||
|
case 'SHARE_RIGHT':
|
||||||
|
return '分享权益';
|
||||||
|
case 'PROVINCE_AREA_RIGHT':
|
||||||
|
return '省区域权益';
|
||||||
|
case 'PROVINCE_TEAM_RIGHT':
|
||||||
|
return '省团队权益';
|
||||||
|
case 'CITY_AREA_RIGHT':
|
||||||
|
return '市区域权益';
|
||||||
|
case 'CITY_TEAM_RIGHT':
|
||||||
|
return '市团队权益';
|
||||||
|
case 'COMMUNITY_RIGHT':
|
||||||
|
return '社区权益';
|
||||||
|
default:
|
||||||
|
return allocationType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 待领取奖励条目 (从 GET /rewards/pending 获取)
|
/// 待领取奖励条目 (从 GET /rewards/pending 获取)
|
||||||
class PendingRewardItem {
|
class PendingRewardItem {
|
||||||
final String id;
|
final String id;
|
||||||
|
|
@ -40,24 +60,79 @@ class PendingRewardItem {
|
||||||
int get remainingSeconds => (remainingTimeMs / 1000).round();
|
int get remainingSeconds => (remainingTimeMs / 1000).round();
|
||||||
|
|
||||||
/// 获取权益类型的中文名称
|
/// 获取权益类型的中文名称
|
||||||
String get rightTypeName {
|
String get rightTypeName => getAllocationTypeName(rightType);
|
||||||
switch (rightType) {
|
}
|
||||||
case 'SHARE_RIGHT':
|
|
||||||
return '分享权益';
|
/// 可结算奖励条目 (从 GET /wallet/settleable-rewards 获取)
|
||||||
case 'PROVINCE_AREA_RIGHT':
|
class SettleableRewardItem {
|
||||||
return '省区域权益';
|
final String id;
|
||||||
case 'PROVINCE_TEAM_RIGHT':
|
final String allocationType;
|
||||||
return '省团队权益';
|
final double usdtAmount;
|
||||||
case 'CITY_AREA_RIGHT':
|
final double hashpowerAmount;
|
||||||
return '市区域权益';
|
final DateTime createdAt;
|
||||||
case 'CITY_TEAM_RIGHT':
|
final DateTime settledAt;
|
||||||
return '市团队权益';
|
final String sourceOrderId;
|
||||||
case 'COMMUNITY_RIGHT':
|
|
||||||
return '社区权益';
|
SettleableRewardItem({
|
||||||
default:
|
required this.id,
|
||||||
return rightType;
|
required this.allocationType,
|
||||||
}
|
required this.usdtAmount,
|
||||||
|
required this.hashpowerAmount,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.settledAt,
|
||||||
|
required this.sourceOrderId,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SettleableRewardItem.fromJson(Map<String, dynamic> json) {
|
||||||
|
return SettleableRewardItem(
|
||||||
|
id: json['id']?.toString() ?? '',
|
||||||
|
allocationType: json['allocationType'] ?? '',
|
||||||
|
usdtAmount: (json['usdtAmount'] ?? 0).toDouble(),
|
||||||
|
hashpowerAmount: (json['hashpowerAmount'] ?? 0).toDouble(),
|
||||||
|
createdAt: DateTime.tryParse(json['createdAt'] ?? '') ?? DateTime.now(),
|
||||||
|
settledAt: DateTime.tryParse(json['settledAt'] ?? '') ?? DateTime.now(),
|
||||||
|
sourceOrderId: json['sourceOrderId'] ?? '',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 获取权益类型的中文名称
|
||||||
|
String get allocationTypeName => getAllocationTypeName(allocationType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 已过期奖励条目 (从 GET /wallet/expired-rewards 获取)
|
||||||
|
class ExpiredRewardItem {
|
||||||
|
final String id;
|
||||||
|
final String allocationType;
|
||||||
|
final double usdtAmount;
|
||||||
|
final double hashpowerAmount;
|
||||||
|
final DateTime createdAt;
|
||||||
|
final DateTime expiredAt;
|
||||||
|
final String sourceOrderId;
|
||||||
|
|
||||||
|
ExpiredRewardItem({
|
||||||
|
required this.id,
|
||||||
|
required this.allocationType,
|
||||||
|
required this.usdtAmount,
|
||||||
|
required this.hashpowerAmount,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.expiredAt,
|
||||||
|
required this.sourceOrderId,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ExpiredRewardItem.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ExpiredRewardItem(
|
||||||
|
id: json['id']?.toString() ?? '',
|
||||||
|
allocationType: json['allocationType'] ?? '',
|
||||||
|
usdtAmount: (json['usdtAmount'] ?? 0).toDouble(),
|
||||||
|
hashpowerAmount: (json['hashpowerAmount'] ?? 0).toDouble(),
|
||||||
|
createdAt: DateTime.tryParse(json['createdAt'] ?? '') ?? DateTime.now(),
|
||||||
|
expiredAt: DateTime.tryParse(json['expiredAt'] ?? '') ?? DateTime.now(),
|
||||||
|
sourceOrderId: json['sourceOrderId'] ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取权益类型的中文名称
|
||||||
|
String get allocationTypeName => getAllocationTypeName(allocationType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 奖励汇总信息 (从 reward-service 获取)
|
/// 奖励汇总信息 (从 reward-service 获取)
|
||||||
|
|
@ -219,4 +294,110 @@ class RewardService {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 获取可结算奖励列表
|
||||||
|
///
|
||||||
|
/// 调用 GET /wallet/settleable-rewards (wallet-service)
|
||||||
|
/// 返回所有已领取待结算的奖励条目
|
||||||
|
Future<List<SettleableRewardItem>> getSettleableRewards() async {
|
||||||
|
try {
|
||||||
|
debugPrint('[RewardService] ========== 获取可结算奖励列表 ==========');
|
||||||
|
debugPrint('[RewardService] 请求: GET /wallet/settleable-rewards');
|
||||||
|
|
||||||
|
final response = await _apiClient.get('/wallet/settleable-rewards');
|
||||||
|
|
||||||
|
debugPrint('[RewardService] 响应状态码: ${response.statusCode}');
|
||||||
|
debugPrint('[RewardService] 响应数据类型: ${response.data.runtimeType}');
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final responseData = response.data;
|
||||||
|
debugPrint('[RewardService] 原始响应数据: $responseData');
|
||||||
|
|
||||||
|
// API 返回格式可能是直接数组或 { 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('[RewardService] 解析到 ${dataList.length} 条可结算奖励');
|
||||||
|
|
||||||
|
final items = dataList
|
||||||
|
.map((item) => SettleableRewardItem.fromJson(item as Map<String, dynamic>))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
for (var item in items) {
|
||||||
|
debugPrint('[RewardService] - ${item.allocationTypeName}: ${item.usdtAmount} USDT, ${item.hashpowerAmount} 算力');
|
||||||
|
}
|
||||||
|
debugPrint('[RewardService] ================================');
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
debugPrint('[RewardService] 请求失败,状态码: ${response.statusCode}');
|
||||||
|
debugPrint('[RewardService] 响应内容: ${response.data}');
|
||||||
|
throw Exception('获取可结算奖励列表失败: ${response.statusCode}');
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
debugPrint('[RewardService] !!!!!!!!!! 获取可结算奖励列表异常 !!!!!!!!!!');
|
||||||
|
debugPrint('[RewardService] 错误: $e');
|
||||||
|
debugPrint('[RewardService] 堆栈: $stackTrace');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取已过期奖励列表
|
||||||
|
///
|
||||||
|
/// 调用 GET /wallet/expired-rewards (wallet-service)
|
||||||
|
/// 返回所有已过期的奖励条目(24h未领取)
|
||||||
|
Future<List<ExpiredRewardItem>> getExpiredRewards() async {
|
||||||
|
try {
|
||||||
|
debugPrint('[RewardService] ========== 获取已过期奖励列表 ==========');
|
||||||
|
debugPrint('[RewardService] 请求: GET /wallet/expired-rewards');
|
||||||
|
|
||||||
|
final response = await _apiClient.get('/wallet/expired-rewards');
|
||||||
|
|
||||||
|
debugPrint('[RewardService] 响应状态码: ${response.statusCode}');
|
||||||
|
debugPrint('[RewardService] 响应数据类型: ${response.data.runtimeType}');
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final responseData = response.data;
|
||||||
|
debugPrint('[RewardService] 原始响应数据: $responseData');
|
||||||
|
|
||||||
|
// API 返回格式可能是直接数组或 { 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('[RewardService] 解析到 ${dataList.length} 条已过期奖励');
|
||||||
|
|
||||||
|
final items = dataList
|
||||||
|
.map((item) => ExpiredRewardItem.fromJson(item as Map<String, dynamic>))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
for (var item in items) {
|
||||||
|
debugPrint('[RewardService] - ${item.allocationTypeName}: ${item.usdtAmount} USDT, ${item.hashpowerAmount} 算力');
|
||||||
|
}
|
||||||
|
debugPrint('[RewardService] ================================');
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
debugPrint('[RewardService] 请求失败,状态码: ${response.statusCode}');
|
||||||
|
debugPrint('[RewardService] 响应内容: ${response.data}');
|
||||||
|
throw Exception('获取已过期奖励列表失败: ${response.statusCode}');
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
debugPrint('[RewardService] !!!!!!!!!! 获取已过期奖励列表异常 !!!!!!!!!!');
|
||||||
|
debugPrint('[RewardService] 错误: $e');
|
||||||
|
debugPrint('[RewardService] 堆栈: $stackTrace');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,9 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
// 直推数据(从 referral-service 获取)
|
// 直推数据(从 referral-service 获取)
|
||||||
List<Map<String, dynamic>> _referrals = [];
|
List<Map<String, dynamic>> _referrals = [];
|
||||||
|
|
||||||
|
// 团队树根节点(缓存以保持展开状态)
|
||||||
|
TeamTreeNode? _teamTreeRootNode;
|
||||||
|
|
||||||
// 社区考核数据(从 authorization-service 获取)
|
// 社区考核数据(从 authorization-service 获取)
|
||||||
bool _hasCommunityAuth = false; // 是否有社区授权
|
bool _hasCommunityAuth = false; // 是否有社区授权
|
||||||
bool _communityBenefitActive = false; // 社区权益是否激活
|
bool _communityBenefitActive = false; // 社区权益是否激活
|
||||||
|
|
@ -112,6 +115,12 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
// 待领取奖励列表(每一笔单独显示)
|
// 待领取奖励列表(每一笔单独显示)
|
||||||
List<PendingRewardItem> _pendingRewards = [];
|
List<PendingRewardItem> _pendingRewards = [];
|
||||||
|
|
||||||
|
// 可结算奖励列表(逐笔显示)
|
||||||
|
List<SettleableRewardItem> _settleableRewards = [];
|
||||||
|
|
||||||
|
// 已过期奖励列表(逐笔显示)
|
||||||
|
List<ExpiredRewardItem> _expiredRewards = [];
|
||||||
|
|
||||||
// 倒计时
|
// 倒计时
|
||||||
Timer? _timer;
|
Timer? _timer;
|
||||||
int _remainingSeconds = 0;
|
int _remainingSeconds = 0;
|
||||||
|
|
@ -569,15 +578,19 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
debugPrint('[ProfilePage] 获取 rewardServiceProvider...');
|
debugPrint('[ProfilePage] 获取 rewardServiceProvider...');
|
||||||
final rewardService = ref.read(rewardServiceProvider);
|
final rewardService = ref.read(rewardServiceProvider);
|
||||||
|
|
||||||
// 并行加载汇总数据和待领取列表
|
// 并行加载汇总数据、待领取列表、可结算列表、已过期列表
|
||||||
debugPrint('[ProfilePage] 调用 getMyRewardSummary() 和 getPendingRewards()...');
|
debugPrint('[ProfilePage] 调用 getMyRewardSummary()、getPendingRewards()、getSettleableRewards()、getExpiredRewards()...');
|
||||||
final results = await Future.wait([
|
final results = await Future.wait([
|
||||||
rewardService.getMyRewardSummary(),
|
rewardService.getMyRewardSummary(),
|
||||||
rewardService.getPendingRewards(),
|
rewardService.getPendingRewards(),
|
||||||
|
rewardService.getSettleableRewards(),
|
||||||
|
rewardService.getExpiredRewards(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
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>;
|
||||||
|
final settleableRewards = results[2] as List<SettleableRewardItem>;
|
||||||
|
final expiredRewards = results[3] as List<ExpiredRewardItem>;
|
||||||
|
|
||||||
debugPrint('[ProfilePage] -------- 收益数据加载成功 --------');
|
debugPrint('[ProfilePage] -------- 收益数据加载成功 --------');
|
||||||
debugPrint('[ProfilePage] 待领取 USDT: ${summary.pendingUsdt}');
|
debugPrint('[ProfilePage] 待领取 USDT: ${summary.pendingUsdt}');
|
||||||
|
|
@ -589,6 +602,8 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
debugPrint('[ProfilePage] 过期时间: ${summary.pendingExpireAt}');
|
debugPrint('[ProfilePage] 过期时间: ${summary.pendingExpireAt}');
|
||||||
debugPrint('[ProfilePage] 剩余秒数: ${summary.pendingRemainingSeconds}');
|
debugPrint('[ProfilePage] 剩余秒数: ${summary.pendingRemainingSeconds}');
|
||||||
debugPrint('[ProfilePage] 待领取奖励条目数: ${pendingRewards.length}');
|
debugPrint('[ProfilePage] 待领取奖励条目数: ${pendingRewards.length}');
|
||||||
|
debugPrint('[ProfilePage] 可结算奖励条目数: ${settleableRewards.length}');
|
||||||
|
debugPrint('[ProfilePage] 已过期奖励条目数: ${expiredRewards.length}');
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
debugPrint('[ProfilePage] 更新 UI 状态...');
|
debugPrint('[ProfilePage] 更新 UI 状态...');
|
||||||
|
|
@ -601,6 +616,8 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
_expiredPower = summary.expiredTotalHashpower;
|
_expiredPower = summary.expiredTotalHashpower;
|
||||||
_remainingSeconds = summary.pendingRemainingSeconds;
|
_remainingSeconds = summary.pendingRemainingSeconds;
|
||||||
_pendingRewards = pendingRewards;
|
_pendingRewards = pendingRewards;
|
||||||
|
_settleableRewards = settleableRewards;
|
||||||
|
_expiredRewards = expiredRewards;
|
||||||
_isLoadingWallet = false;
|
_isLoadingWallet = false;
|
||||||
_walletError = null;
|
_walletError = null;
|
||||||
_walletRetryCount = 0;
|
_walletRetryCount = 0;
|
||||||
|
|
@ -610,6 +627,8 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
debugPrint('[ProfilePage] _pendingUsdt: $_pendingUsdt');
|
debugPrint('[ProfilePage] _pendingUsdt: $_pendingUsdt');
|
||||||
debugPrint('[ProfilePage] _remainingSeconds: $_remainingSeconds');
|
debugPrint('[ProfilePage] _remainingSeconds: $_remainingSeconds');
|
||||||
debugPrint('[ProfilePage] _pendingRewards: ${_pendingRewards.length} 条');
|
debugPrint('[ProfilePage] _pendingRewards: ${_pendingRewards.length} 条');
|
||||||
|
debugPrint('[ProfilePage] _settleableRewards: ${_settleableRewards.length} 条');
|
||||||
|
debugPrint('[ProfilePage] _expiredRewards: ${_expiredRewards.length} 条');
|
||||||
|
|
||||||
// 启动倒计时(如果有待领取收益)
|
// 启动倒计时(如果有待领取收益)
|
||||||
if (_remainingSeconds > 0) {
|
if (_remainingSeconds > 0) {
|
||||||
|
|
@ -1415,55 +1434,38 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
width: 1,
|
width: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// 第一行:推荐人序列号 | 下级社区
|
// 左列:推荐人序列号、所属社区、上线社区、下级社区
|
||||||
Row(
|
Expanded(
|
||||||
children: [
|
child: Column(
|
||||||
Expanded(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: _buildInfoItem('推荐人的序列号', _referrerSerial),
|
children: [
|
||||||
),
|
_buildInfoItem('推荐人的序列号', _referrerSerial),
|
||||||
Expanded(
|
const SizedBox(height: 12),
|
||||||
child: _buildInfoItem('下级社区', _childCommunity),
|
_buildInfoItem('所属社区', _community),
|
||||||
),
|
const SizedBox(height: 12),
|
||||||
],
|
_buildInfoItem('上线社区', _parentCommunity),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_buildInfoItem('下级社区', _childCommunity),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
// 右列:市团队、省团队、市区域、省区域
|
||||||
// 第二行:所属社区 | 市团队
|
Expanded(
|
||||||
Row(
|
child: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Expanded(
|
children: [
|
||||||
child: _buildInfoItem('所属社区', _community),
|
_buildInfoItem('市团队', _authCityCompany),
|
||||||
),
|
const SizedBox(height: 12),
|
||||||
Expanded(
|
_buildInfoItem('省团队', _authProvinceCompany),
|
||||||
child: _buildInfoItem('市团队', _authCityCompany),
|
const SizedBox(height: 12),
|
||||||
),
|
_buildInfoItem('市区域', _cityCompany),
|
||||||
],
|
const SizedBox(height: 12),
|
||||||
),
|
_buildInfoItem('省区域', _provinceCompany),
|
||||||
const SizedBox(height: 12),
|
],
|
||||||
// 第三行:上级社区 | 市区域
|
),
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _buildInfoItem('上级社区', _parentCommunity),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: _buildInfoItem('市区域', _cityCompany),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
// 第四行:省团队 | 省区域
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _buildInfoItem('省团队', _authProvinceCompany),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: _buildInfoItem('省区域', _provinceCompany),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -1574,7 +1576,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
// 直推列表
|
// 直推列表
|
||||||
_buildReferralList(),
|
_buildReferralList(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// 我的伞下
|
// 我的团队
|
||||||
_buildMyTeamTree(),
|
_buildMyTeamTree(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// 分享邀请按钮
|
// 分享邀请按钮
|
||||||
|
|
@ -1972,6 +1974,24 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
// 可结算奖励明细列表
|
||||||
|
if (_settleableRewards.isNotEmpty) ...[
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Divider(color: Color(0x33D4AF37), height: 1),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
const Text(
|
||||||
|
'可结算明细',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF5D4037),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
// 奖励条目列表
|
||||||
|
...(_settleableRewards.map((item) => _buildSettleableRewardItem(item))),
|
||||||
|
],
|
||||||
const SizedBox(height: 11),
|
const SizedBox(height: 11),
|
||||||
// 已结算 USDT
|
// 已结算 USDT
|
||||||
Row(
|
Row(
|
||||||
|
|
@ -2026,6 +2046,85 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 构建单条可结算奖励项
|
||||||
|
Widget _buildSettleableRewardItem(SettleableRewardItem item) {
|
||||||
|
// 格式化结算时间
|
||||||
|
final settledDate = '${item.settledAt.month}/${item.settledAt.day} ${item.settledAt.hour.toString().padLeft(2, '0')}:${item.settledAt.minute.toString().padLeft(2, '0')}';
|
||||||
|
|
||||||
|
// 构建金额显示文本
|
||||||
|
final List<String> amountParts = [];
|
||||||
|
if (item.usdtAmount > 0) {
|
||||||
|
amountParts.add('${_formatNumber(item.usdtAmount)} 积分');
|
||||||
|
}
|
||||||
|
if (item.hashpowerAmount > 0) {
|
||||||
|
amountParts.add('${_formatNumber(item.hashpowerAmount)} 算力');
|
||||||
|
}
|
||||||
|
final amountText = amountParts.isNotEmpty ? amountParts.join(' ') : '0 积分';
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(
|
||||||
|
color: const Color(0x22D4AF37),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// 第一行:权益类型 + 结算时间
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
item.allocationTypeName,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF5D4037),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.check_circle_outline,
|
||||||
|
size: 14,
|
||||||
|
color: Color(0xFF4CAF50),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
settledDate,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Color(0xFF4CAF50),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
// 第二行:金额信息
|
||||||
|
Text(
|
||||||
|
amountText,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Color(0xFF5D4037),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// 构建已过期区域
|
/// 构建已过期区域
|
||||||
Widget _buildExpiredSection() {
|
Widget _buildExpiredSection() {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
|
|
@ -2107,12 +2206,109 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
// 已过期奖励明细列表
|
||||||
|
if (_expiredRewards.isNotEmpty) ...[
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Divider(color: Color(0x33D4AF37), height: 1),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
const Text(
|
||||||
|
'已过期明细',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF5D4037),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
// 奖励条目列表
|
||||||
|
...(_expiredRewards.map((item) => _buildExpiredRewardItem(item))),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 构建单条已过期奖励项
|
||||||
|
Widget _buildExpiredRewardItem(ExpiredRewardItem item) {
|
||||||
|
// 格式化过期时间
|
||||||
|
final expiredDate = '${item.expiredAt.month}/${item.expiredAt.day} ${item.expiredAt.hour.toString().padLeft(2, '0')}:${item.expiredAt.minute.toString().padLeft(2, '0')}';
|
||||||
|
|
||||||
|
// 构建金额显示文本
|
||||||
|
final List<String> amountParts = [];
|
||||||
|
if (item.usdtAmount > 0) {
|
||||||
|
amountParts.add('${_formatNumber(item.usdtAmount)} 积分');
|
||||||
|
}
|
||||||
|
if (item.hashpowerAmount > 0) {
|
||||||
|
amountParts.add('${_formatNumber(item.hashpowerAmount)} 算力');
|
||||||
|
}
|
||||||
|
final amountText = amountParts.isNotEmpty ? amountParts.join(' ') : '0 积分';
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFF5F5F5), // 灰色背景表示已过期
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(
|
||||||
|
color: const Color(0x22999999),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// 第一行:权益类型 + 过期时间
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
item.allocationTypeName,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF999999), // 灰色文字
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.cancel_outlined,
|
||||||
|
size: 14,
|
||||||
|
color: Color(0xFFE57373), // 红色图标
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
expiredDate,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Color(0xFFE57373), // 红色文字
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
// 第二行:金额信息
|
||||||
|
Text(
|
||||||
|
amountText,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Color(0xFF999999), // 灰色文字
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// 构建操作按钮
|
/// 构建操作按钮
|
||||||
Widget _buildActionButtons() {
|
Widget _buildActionButtons() {
|
||||||
return Column(
|
return Column(
|
||||||
|
|
@ -2358,15 +2554,22 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 构建我的伞下树
|
/// 构建我的团队树
|
||||||
Widget _buildMyTeamTree() {
|
Widget _buildMyTeamTree() {
|
||||||
// 创建根节点(自己)
|
// 创建或更新根节点(缓存以保持展开状态)
|
||||||
final rootNode = TeamTreeNode.createRoot(
|
// 只有在数据变化时才创建新的根节点
|
||||||
accountSequence: _serialNumber,
|
if (_teamTreeRootNode == null ||
|
||||||
personalPlantingCount: _personalPlantingCount,
|
_teamTreeRootNode!.accountSequence != _serialNumber ||
|
||||||
teamPlantingCount: _teamPlantingCount,
|
_teamTreeRootNode!.personalPlantingCount != _personalPlantingCount ||
|
||||||
directReferralCount: _directReferralCount,
|
_teamTreeRootNode!.teamPlantingCount != _teamPlantingCount ||
|
||||||
);
|
_teamTreeRootNode!.directReferralCount != _directReferralCount) {
|
||||||
|
_teamTreeRootNode = TeamTreeNode.createRoot(
|
||||||
|
accountSequence: _serialNumber,
|
||||||
|
personalPlantingCount: _personalPlantingCount,
|
||||||
|
teamPlantingCount: _teamPlantingCount,
|
||||||
|
directReferralCount: _directReferralCount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -2374,7 +2577,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.only(top: 8),
|
padding: EdgeInsets.only(top: 8),
|
||||||
child: Text(
|
child: Text(
|
||||||
'我的伞下',
|
'我的团队',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontFamily: 'Inter',
|
fontFamily: 'Inter',
|
||||||
|
|
@ -2411,7 +2614,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: TeamTreeWidget(
|
: TeamTreeWidget(
|
||||||
rootNode: rootNode,
|
rootNode: _teamTreeRootNode!,
|
||||||
referralService: ref.read(referralServiceProvider),
|
referralService: ref.read(referralServiceProvider),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue