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);
|
||||
}
|
||||
|
||||
@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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,26 @@
|
|||
import 'package:flutter/foundation.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 获取)
|
||||
class PendingRewardItem {
|
||||
final String id;
|
||||
|
|
@ -40,24 +60,79 @@ class PendingRewardItem {
|
|||
int get remainingSeconds => (remainingTimeMs / 1000).round();
|
||||
|
||||
/// 获取权益类型的中文名称
|
||||
String get rightTypeName {
|
||||
switch (rightType) {
|
||||
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 rightType;
|
||||
}
|
||||
String get rightTypeName => getAllocationTypeName(rightType);
|
||||
}
|
||||
|
||||
/// 可结算奖励条目 (从 GET /wallet/settleable-rewards 获取)
|
||||
class SettleableRewardItem {
|
||||
final String id;
|
||||
final String allocationType;
|
||||
final double usdtAmount;
|
||||
final double hashpowerAmount;
|
||||
final DateTime createdAt;
|
||||
final DateTime settledAt;
|
||||
final String sourceOrderId;
|
||||
|
||||
SettleableRewardItem({
|
||||
required this.id,
|
||||
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 获取)
|
||||
|
|
@ -219,4 +294,110 @@ class RewardService {
|
|||
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 获取)
|
||||
List<Map<String, dynamic>> _referrals = [];
|
||||
|
||||
// 团队树根节点(缓存以保持展开状态)
|
||||
TeamTreeNode? _teamTreeRootNode;
|
||||
|
||||
// 社区考核数据(从 authorization-service 获取)
|
||||
bool _hasCommunityAuth = false; // 是否有社区授权
|
||||
bool _communityBenefitActive = false; // 社区权益是否激活
|
||||
|
|
@ -112,6 +115,12 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
|||
// 待领取奖励列表(每一笔单独显示)
|
||||
List<PendingRewardItem> _pendingRewards = [];
|
||||
|
||||
// 可结算奖励列表(逐笔显示)
|
||||
List<SettleableRewardItem> _settleableRewards = [];
|
||||
|
||||
// 已过期奖励列表(逐笔显示)
|
||||
List<ExpiredRewardItem> _expiredRewards = [];
|
||||
|
||||
// 倒计时
|
||||
Timer? _timer;
|
||||
int _remainingSeconds = 0;
|
||||
|
|
@ -569,15 +578,19 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
|||
debugPrint('[ProfilePage] 获取 rewardServiceProvider...');
|
||||
final rewardService = ref.read(rewardServiceProvider);
|
||||
|
||||
// 并行加载汇总数据和待领取列表
|
||||
debugPrint('[ProfilePage] 调用 getMyRewardSummary() 和 getPendingRewards()...');
|
||||
// 并行加载汇总数据、待领取列表、可结算列表、已过期列表
|
||||
debugPrint('[ProfilePage] 调用 getMyRewardSummary()、getPendingRewards()、getSettleableRewards()、getExpiredRewards()...');
|
||||
final results = await Future.wait([
|
||||
rewardService.getMyRewardSummary(),
|
||||
rewardService.getPendingRewards(),
|
||||
rewardService.getSettleableRewards(),
|
||||
rewardService.getExpiredRewards(),
|
||||
]);
|
||||
|
||||
final summary = results[0] as RewardSummary;
|
||||
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] 待领取 USDT: ${summary.pendingUsdt}');
|
||||
|
|
@ -589,6 +602,8 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
|||
debugPrint('[ProfilePage] 过期时间: ${summary.pendingExpireAt}');
|
||||
debugPrint('[ProfilePage] 剩余秒数: ${summary.pendingRemainingSeconds}');
|
||||
debugPrint('[ProfilePage] 待领取奖励条目数: ${pendingRewards.length}');
|
||||
debugPrint('[ProfilePage] 可结算奖励条目数: ${settleableRewards.length}');
|
||||
debugPrint('[ProfilePage] 已过期奖励条目数: ${expiredRewards.length}');
|
||||
|
||||
if (mounted) {
|
||||
debugPrint('[ProfilePage] 更新 UI 状态...');
|
||||
|
|
@ -601,6 +616,8 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
|||
_expiredPower = summary.expiredTotalHashpower;
|
||||
_remainingSeconds = summary.pendingRemainingSeconds;
|
||||
_pendingRewards = pendingRewards;
|
||||
_settleableRewards = settleableRewards;
|
||||
_expiredRewards = expiredRewards;
|
||||
_isLoadingWallet = false;
|
||||
_walletError = null;
|
||||
_walletRetryCount = 0;
|
||||
|
|
@ -610,6 +627,8 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
|||
debugPrint('[ProfilePage] _pendingUsdt: $_pendingUsdt');
|
||||
debugPrint('[ProfilePage] _remainingSeconds: $_remainingSeconds');
|
||||
debugPrint('[ProfilePage] _pendingRewards: ${_pendingRewards.length} 条');
|
||||
debugPrint('[ProfilePage] _settleableRewards: ${_settleableRewards.length} 条');
|
||||
debugPrint('[ProfilePage] _expiredRewards: ${_expiredRewards.length} 条');
|
||||
|
||||
// 启动倒计时(如果有待领取收益)
|
||||
if (_remainingSeconds > 0) {
|
||||
|
|
@ -1415,55 +1434,38 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
|||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 第一行:推荐人序列号 | 下级社区
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildInfoItem('推荐人的序列号', _referrerSerial),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildInfoItem('下级社区', _childCommunity),
|
||||
),
|
||||
],
|
||||
// 左列:推荐人序列号、所属社区、上线社区、下级社区
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildInfoItem('推荐人的序列号', _referrerSerial),
|
||||
const SizedBox(height: 12),
|
||||
_buildInfoItem('所属社区', _community),
|
||||
const SizedBox(height: 12),
|
||||
_buildInfoItem('上线社区', _parentCommunity),
|
||||
const SizedBox(height: 12),
|
||||
_buildInfoItem('下级社区', _childCommunity),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// 第二行:所属社区 | 市团队
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildInfoItem('所属社区', _community),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildInfoItem('市团队', _authCityCompany),
|
||||
),
|
||||
],
|
||||
),
|
||||
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),
|
||||
),
|
||||
],
|
||||
// 右列:市团队、省团队、市区域、省区域
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildInfoItem('市团队', _authCityCompany),
|
||||
const SizedBox(height: 12),
|
||||
_buildInfoItem('省团队', _authProvinceCompany),
|
||||
const SizedBox(height: 12),
|
||||
_buildInfoItem('市区域', _cityCompany),
|
||||
const SizedBox(height: 12),
|
||||
_buildInfoItem('省区域', _provinceCompany),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -1574,7 +1576,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
|||
// 直推列表
|
||||
_buildReferralList(),
|
||||
const SizedBox(height: 16),
|
||||
// 我的伞下
|
||||
// 我的团队
|
||||
_buildMyTeamTree(),
|
||||
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),
|
||||
// 已结算 USDT
|
||||
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() {
|
||||
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() {
|
||||
return Column(
|
||||
|
|
@ -2358,15 +2554,22 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
|||
);
|
||||
}
|
||||
|
||||
/// 构建我的伞下树
|
||||
/// 构建我的团队树
|
||||
Widget _buildMyTeamTree() {
|
||||
// 创建根节点(自己)
|
||||
final rootNode = TeamTreeNode.createRoot(
|
||||
accountSequence: _serialNumber,
|
||||
personalPlantingCount: _personalPlantingCount,
|
||||
teamPlantingCount: _teamPlantingCount,
|
||||
directReferralCount: _directReferralCount,
|
||||
);
|
||||
// 创建或更新根节点(缓存以保持展开状态)
|
||||
// 只有在数据变化时才创建新的根节点
|
||||
if (_teamTreeRootNode == null ||
|
||||
_teamTreeRootNode!.accountSequence != _serialNumber ||
|
||||
_teamTreeRootNode!.personalPlantingCount != _personalPlantingCount ||
|
||||
_teamTreeRootNode!.teamPlantingCount != _teamPlantingCount ||
|
||||
_teamTreeRootNode!.directReferralCount != _directReferralCount) {
|
||||
_teamTreeRootNode = TeamTreeNode.createRoot(
|
||||
accountSequence: _serialNumber,
|
||||
personalPlantingCount: _personalPlantingCount,
|
||||
teamPlantingCount: _teamPlantingCount,
|
||||
directReferralCount: _directReferralCount,
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
|
@ -2374,7 +2577,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
|||
const Padding(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
'我的伞下',
|
||||
'我的团队',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontFamily: 'Inter',
|
||||
|
|
@ -2411,7 +2614,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
|||
),
|
||||
)
|
||||
: TeamTreeWidget(
|
||||
rootNode: rootNode,
|
||||
rootNode: _teamTreeRootNode!,
|
||||
referralService: ref.read(referralServiceProvider),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
Loading…
Reference in New Issue