fix(admin): correct distributed shares calculation to use 2M pool

The dashboard was incorrectly using 5 billion as the distribution pool
default when calculating already distributed shares. The actual mining
distribution pool is 2 million shares, not 100 billion.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-16 08:59:46 -08:00
parent 20a90fce4c
commit 64ccb8162a
9 changed files with 1065 additions and 4 deletions

View File

@ -536,13 +536,14 @@ export class DashboardService {
if (miningResponse.ok) {
const miningResult = await miningResponse.json();
const miningData = miningResult.data || miningResult;
// 使用 remainingDistribution 计算已分配
// 总量 50亿 - 剩余 = 已分配
// 使用 distributionPool - remainingDistribution 计算已分配
// 分配池是 200万不是100亿remainingDistribution 是剩余待分配量
// 已分配 = 分配池 - 剩余
const distributionPool = new Decimal(
miningData.distributionPool || '5000000000',
miningData.distributionPool || '2000000',
);
const remaining = new Decimal(
miningData.remainingDistribution || '5000000000',
miningData.remainingDistribution || '2000000',
);
totalDistributed = distributionPool.minus(remaining).toString();
}

View File

@ -7,6 +7,7 @@ import '../../data/datasources/remote/auth_remote_datasource.dart';
import '../../data/datasources/remote/mining_remote_datasource.dart';
import '../../data/datasources/remote/trading_remote_datasource.dart';
import '../../data/datasources/remote/contribution_remote_datasource.dart';
import '../../data/datasources/remote/referral_remote_datasource.dart';
import '../../data/repositories/mining_repository_impl.dart';
import '../../data/repositories/trading_repository_impl.dart';
import '../../data/repositories/contribution_repository_impl.dart';
@ -47,6 +48,11 @@ Future<void> configureDependencies() async {
() => AuthRemoteDataSourceImpl(client: getIt<ApiClient>()),
);
// Referral Data Source
getIt.registerLazySingleton<ReferralRemoteDataSource>(
() => ReferralRemoteDataSourceImpl(client: getIt<ApiClient>()),
);
// Repositories
getIt.registerLazySingleton<MiningRepository>(
() => MiningRepositoryImpl(

View File

@ -78,4 +78,10 @@ class ApiEndpoints {
// Mining Wallet Service 2.0 (Kong路由: /api/v2/mining-wallet)
static const String sharePoolBalance = '/api/v2/mining-wallet/pool-accounts/share-pool-balance';
// Referral Service 2.0 (Kong路由: /api/v2/referral)
static const String referralMe = '/api/v2/referral/me';
static const String referralDirects = '/api/v2/referral/me/direct-referrals';
static String userDirectReferrals(String accountSequence) =>
'/api/v2/referral/user/$accountSequence/direct-referrals';
}

View File

@ -19,6 +19,7 @@ import '../../presentation/pages/asset/receive_shares_page.dart';
import '../../presentation/pages/c2c/c2c_market_page.dart';
import '../../presentation/pages/c2c/c2c_publish_page.dart';
import '../../presentation/pages/c2c/c2c_order_detail_page.dart';
import '../../presentation/pages/profile/team_page.dart';
import '../../presentation/widgets/main_shell.dart';
import '../../presentation/providers/user_providers.dart';
import 'routes.dart';
@ -145,6 +146,10 @@ final appRouterProvider = Provider<GoRouter>((ref) {
return C2cOrderDetailPage(orderNo: orderNo);
},
),
GoRoute(
path: Routes.myTeam,
builder: (context, state) => const TeamPage(),
),
ShellRoute(
builder: (context, state, child) => MainShell(child: child),
routes: [

View File

@ -19,4 +19,6 @@ class Routes {
static const String c2cMarket = '/c2c-market';
static const String c2cPublish = '/c2c-publish';
static const String c2cOrderDetail = '/c2c-order-detail';
//
static const String myTeam = '/my-team';
}

View File

@ -0,0 +1,104 @@
import 'package:flutter/foundation.dart';
import '../../../core/network/api_client.dart';
import '../../../core/network/api_endpoints.dart';
import '../../models/referral_model.dart';
abstract class ReferralRemoteDataSource {
///
Future<ReferralInfoResponse> getMyReferralInfo();
///
Future<DirectReferralsResponse> getDirectReferrals({
int limit = 50,
int offset = 0,
});
///
Future<DirectReferralsResponse> getUserDirectReferrals({
required String accountSequence,
int limit = 100,
int offset = 0,
});
}
class ReferralRemoteDataSourceImpl implements ReferralRemoteDataSource {
final ApiClient client;
ReferralRemoteDataSourceImpl({required this.client});
@override
Future<ReferralInfoResponse> getMyReferralInfo() async {
try {
debugPrint('获取推荐信息...');
final response = await client.get(ApiEndpoints.referralMe);
if (response.statusCode == 200) {
final data = response.data as Map<String, dynamic>;
debugPrint('推荐信息获取成功: directReferralCount=${data['directReferralCount']}');
return ReferralInfoResponse.fromJson(data);
}
throw Exception('获取推荐信息失败');
} catch (e) {
debugPrint('获取推荐信息失败: $e');
rethrow;
}
}
@override
Future<DirectReferralsResponse> getDirectReferrals({
int limit = 50,
int offset = 0,
}) async {
try {
debugPrint('获取直推列表...');
final response = await client.get(
ApiEndpoints.referralDirects,
queryParameters: {
'limit': limit,
'offset': offset,
},
);
if (response.statusCode == 200) {
final data = response.data as Map<String, dynamic>;
debugPrint('直推列表获取成功: total=${data['total']}');
return DirectReferralsResponse.fromJson(data);
}
throw Exception('获取直推列表失败');
} catch (e) {
debugPrint('获取直推列表失败: $e');
rethrow;
}
}
@override
Future<DirectReferralsResponse> getUserDirectReferrals({
required String accountSequence,
int limit = 100,
int offset = 0,
}) async {
try {
debugPrint('获取用户直推列表: accountSequence=$accountSequence');
final response = await client.get(
ApiEndpoints.userDirectReferrals(accountSequence),
queryParameters: {
'limit': limit,
'offset': offset,
},
);
if (response.statusCode == 200) {
final data = response.data as Map<String, dynamic>;
debugPrint('用户直推列表获取成功: total=${data['total']}');
return DirectReferralsResponse.fromJson(data);
}
throw Exception('获取用户直推列表失败');
} catch (e) {
debugPrint('获取用户直推列表失败: $e');
rethrow;
}
}
}

View File

@ -0,0 +1,104 @@
/// ( referral-service)
class ReferralInfoResponse {
final String userId;
final String referralCode;
final String? referrerId;
final int referralChainDepth;
final int directReferralCount;
final int totalTeamCount;
final int personalPlantingCount;
final int teamPlantingCount;
final double leaderboardScore;
final int? leaderboardRank;
final DateTime createdAt;
ReferralInfoResponse({
required this.userId,
required this.referralCode,
this.referrerId,
required this.referralChainDepth,
required this.directReferralCount,
required this.totalTeamCount,
required this.personalPlantingCount,
required this.teamPlantingCount,
required this.leaderboardScore,
this.leaderboardRank,
required this.createdAt,
});
factory ReferralInfoResponse.fromJson(Map<String, dynamic> json) {
return ReferralInfoResponse(
userId: json['userId']?.toString() ?? '',
referralCode: json['referralCode'] ?? '',
referrerId: json['referrerId']?.toString(),
referralChainDepth: json['referralChainDepth'] ?? 0,
directReferralCount: json['directReferralCount'] ?? 0,
totalTeamCount: json['totalTeamCount'] ?? 0,
personalPlantingCount: json['personalPlantingCount'] ?? 0,
teamPlantingCount: json['teamPlantingCount'] ?? 0,
leaderboardScore: (json['leaderboardScore'] ?? 0).toDouble(),
leaderboardRank: json['leaderboardRank'],
createdAt: json['createdAt'] != null
? DateTime.parse(json['createdAt'])
: DateTime.now(),
);
}
}
///
class DirectReferralInfo {
final String userId;
final String accountSequence; // : D + YYMMDD + 5
final String referralCode;
final int personalPlantingCount; //
final int teamPlantingCount; //
final int directReferralCount; //
final DateTime joinedAt;
DirectReferralInfo({
required this.userId,
required this.accountSequence,
required this.referralCode,
required this.personalPlantingCount,
required this.teamPlantingCount,
required this.directReferralCount,
required this.joinedAt,
});
factory DirectReferralInfo.fromJson(Map<String, dynamic> json) {
return DirectReferralInfo(
userId: json['userId']?.toString() ?? '',
accountSequence: json['accountSequence']?.toString() ?? '',
referralCode: json['referralCode'] ?? '',
personalPlantingCount: json['personalPlantingCount'] ?? 0,
teamPlantingCount: json['teamPlantingCount'] ?? 0,
directReferralCount: json['directReferralCount'] ?? 0,
joinedAt: json['joinedAt'] != null
? DateTime.parse(json['joinedAt'])
: DateTime.now(),
);
}
}
///
class DirectReferralsResponse {
final List<DirectReferralInfo> referrals;
final int total;
final bool hasMore;
DirectReferralsResponse({
required this.referrals,
required this.total,
required this.hasMore,
});
factory DirectReferralsResponse.fromJson(Map<String, dynamic> json) {
return DirectReferralsResponse(
referrals: (json['referrals'] as List? ?? [])
.map((e) => DirectReferralInfo.fromJson(e))
.toList(),
total: json['total'] ?? 0,
hasMore: json['hasMore'] ?? false,
);
}
}

View File

@ -0,0 +1,283 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/di/injection.dart';
import '../../../data/datasources/remote/referral_remote_datasource.dart';
import '../../widgets/team_tree_widget.dart';
import '../../providers/user_providers.dart';
import '../../providers/profile_providers.dart';
class TeamPage extends ConsumerStatefulWidget {
const TeamPage({super.key});
@override
ConsumerState<TeamPage> createState() => _TeamPageState();
}
class _TeamPageState extends ConsumerState<TeamPage> {
//
static const Color _orange = Color(0xFFFF6B00);
static const Color _darkText = Color(0xFF1F2937);
static const Color _grayText = Color(0xFF6B7280);
static const Color _bgGray = Color(0xFFF3F4F6);
TeamTreeNode? _rootNode;
bool _isLoading = true;
String? _error;
@override
void initState() {
super.initState();
_loadRootNode();
}
Future<void> _loadRootNode() async {
final user = ref.read(userNotifierProvider);
final stats = ref.read(userStatsProvider).valueOrNull;
if (user.accountSequence == null) {
setState(() {
_isLoading = false;
_error = '用户信息未加载';
});
return;
}
try {
final dataSource = getIt<ReferralRemoteDataSource>();
final referralInfo = await dataSource.getMyReferralInfo();
setState(() {
_rootNode = TeamTreeNode.createRoot(
accountSequence: user.accountSequence!,
personalPlantingCount: referralInfo.personalPlantingCount,
teamPlantingCount: referralInfo.teamPlantingCount,
directReferralCount: referralInfo.directReferralCount,
);
_isLoading = false;
});
} catch (e) {
setState(() {
_isLoading = false;
_error = e.toString();
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: _bgGray,
appBar: AppBar(
title: const Text(
'我的团队',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: _darkText,
),
),
backgroundColor: Colors.white,
elevation: 0,
centerTitle: true,
iconTheme: const IconThemeData(color: _darkText),
),
body: _buildBody(),
);
}
Widget _buildBody() {
if (_isLoading) {
return const Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(_orange),
),
);
}
if (_error != null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.error_outline,
size: 64,
color: Colors.grey[400],
),
const SizedBox(height: 16),
Text(
'加载失败',
style: TextStyle(
fontSize: 16,
color: Colors.grey[600],
),
),
const SizedBox(height: 8),
Text(
_error!,
style: TextStyle(
fontSize: 14,
color: Colors.grey[500],
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
setState(() {
_isLoading = true;
_error = null;
});
_loadRootNode();
},
style: ElevatedButton.styleFrom(
backgroundColor: _orange,
foregroundColor: Colors.white,
),
child: const Text('重试'),
),
],
),
);
}
if (_rootNode == null) {
return const Center(
child: Text('暂无数据'),
);
}
return Column(
children: [
//
_buildStatsCard(),
//
_buildInstructions(),
//
Expanded(
child: Container(
margin: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: TeamTreeWidget(
rootNode: _rootNode!,
referralDataSource: getIt<ReferralRemoteDataSource>(),
),
),
),
),
],
);
}
Widget _buildStatsCard() {
return Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildStatItem(
'个人认种',
'${_rootNode?.personalPlantingCount ?? 0}',
Icons.eco,
),
_buildDivider(),
_buildStatItem(
'团队认种',
'${_rootNode?.teamPlantingCount ?? 0}',
Icons.groups,
),
_buildDivider(),
_buildStatItem(
'直推人数',
'${_rootNode?.directReferralCount ?? 0}',
Icons.person_add,
),
],
),
);
}
Widget _buildStatItem(String label, String value, IconData icon) {
return Column(
children: [
Icon(
icon,
color: _orange,
size: 24,
),
const SizedBox(height: 8),
Text(
value,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: _darkText,
),
),
const SizedBox(height: 4),
Text(
label,
style: const TextStyle(
fontSize: 12,
color: _grayText,
),
),
],
);
}
Widget _buildDivider() {
return Container(
width: 1,
height: 50,
color: _bgGray,
);
}
Widget _buildInstructions() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: _orange.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(
Icons.info_outline,
color: _orange,
size: 18,
),
const SizedBox(width: 8),
const Expanded(
child: Text(
'点击节点展开/收起下级成员,长按查看详情',
style: TextStyle(
fontSize: 12,
color: _grayText,
),
),
),
],
),
);
}
}

View File

@ -0,0 +1,550 @@
import 'package:flutter/material.dart';
import '../../data/models/referral_model.dart';
import '../../data/datasources/remote/referral_remote_datasource.dart';
///
class TeamTreeNode {
final String userId;
final String accountSequence;
final int personalPlantingCount;
final int teamPlantingCount;
final int directReferralCount;
List<TeamTreeNode>? children;
bool isExpanded;
bool isLoading;
TeamTreeNode({
required this.userId,
required this.accountSequence,
required this.personalPlantingCount,
required this.teamPlantingCount,
required this.directReferralCount,
this.children,
this.isExpanded = false,
this.isLoading = false,
});
///
bool get hasChildren => directReferralCount > 0;
/// DirectReferralInfo
factory TeamTreeNode.fromDirectReferralInfo(DirectReferralInfo info) {
return TeamTreeNode(
userId: info.userId,
accountSequence: info.accountSequence,
personalPlantingCount: info.personalPlantingCount,
teamPlantingCount: info.teamPlantingCount,
directReferralCount: info.directReferralCount,
);
}
///
factory TeamTreeNode.createRoot({
required String accountSequence,
required int personalPlantingCount,
required int teamPlantingCount,
required int directReferralCount,
}) {
return TeamTreeNode(
userId: '',
accountSequence: accountSequence,
personalPlantingCount: personalPlantingCount,
teamPlantingCount: teamPlantingCount,
directReferralCount: directReferralCount,
);
}
}
///
class TeamTreeWidget extends StatefulWidget {
final TeamTreeNode rootNode;
final ReferralRemoteDataSource referralDataSource;
const TeamTreeWidget({
super.key,
required this.rootNode,
required this.referralDataSource,
});
@override
State<TeamTreeWidget> createState() => _TeamTreeWidgetState();
}
class _TeamTreeWidgetState extends State<TeamTreeWidget> {
//
final Map<String, List<TeamTreeNode>> _childrenCache = {};
//
static const double nodeWidth = 80.0;
static const double nodeHeight = 60.0;
static const double nodeHorizontalSpacing = 12.0;
static const double nodeVerticalSpacing = 40.0;
// LayoutBuilder
double _containerWidth = 0;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
// 使
_containerWidth = constraints.maxWidth;
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: _containerWidth,
),
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: _buildTreeLevel([widget.rootNode], 0),
),
),
),
),
);
},
);
}
///
Widget _buildTreeLevel(List<TeamTreeNode> nodes, int level) {
if (nodes.isEmpty) return const SizedBox.shrink();
//
return Column(
children: [
//
Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (int i = 0; i < nodes.length; i++) ...[
_buildNodeWithChildren(nodes[i]),
if (i < nodes.length - 1) SizedBox(width: nodeHorizontalSpacing),
],
],
),
],
);
}
///
Widget _buildNodeWithChildren(TeamTreeNode node) {
return Column(
children: [
//
_buildNodeBox(node),
// 线
if (node.isExpanded && node.children != null && node.children!.isNotEmpty) ...[
// 线
CustomPaint(
size: Size(_calculateSubtreeWidth(node.children!), nodeVerticalSpacing),
painter: _TreeLinePainter(
parentWidth: nodeWidth,
childCount: node.children!.length,
childWidth: nodeWidth,
spacing: nodeHorizontalSpacing,
),
),
//
_buildTreeLevel(node.children!, 0),
],
],
);
}
///
double _calculateSubtreeWidth(List<TeamTreeNode> children) {
if (children.isEmpty) return nodeWidth;
return children.length * nodeWidth + (children.length - 1) * nodeHorizontalSpacing;
}
///
Widget _buildNodeBox(TeamTreeNode node) {
return GestureDetector(
onTap: () => _handleNodeTap(node),
onLongPress: () => _showNodeDetails(node),
child: Container(
width: nodeWidth,
height: nodeHeight,
decoration: BoxDecoration(
color: const Color(0xFFFFF5E6),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: const Color(0xFFFF6B00),
width: 1,
),
boxShadow: const [
BoxShadow(
color: Color(0x1A000000), // 10% opacity black
blurRadius: 4,
offset: Offset(0, 2),
),
],
),
child: node.isLoading
? const Center(
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFFFF6B00)),
),
),
)
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
//
Text(
_formatAccountSequence(node.accountSequence),
style: const TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
color: Color(0xFF5D4037),
),
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
// /
FittedBox(
fit: BoxFit.scaleDown,
child: Text(
'${node.personalPlantingCount}/${node.teamPlantingCount}',
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w500,
color: Color(0xCC5D4037),
),
),
),
const SizedBox(height: 4),
// /
if (node.hasChildren)
Container(
width: 20,
height: 16,
decoration: BoxDecoration(
color: const Color(0xFFFF6B00),
borderRadius: BorderRadius.circular(4),
),
child: Center(
child: Text(
node.isExpanded ? '' : '+',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.white,
height: 1,
),
),
),
),
],
),
),
);
}
///
String _formatAccountSequence(String seq) {
if (seq.length > 8) {
// D25121300001 -> ...00001
return '...${seq.substring(seq.length - 5)}';
}
return seq;
}
///
Future<void> _handleNodeTap(TeamTreeNode node) async {
if (!node.hasChildren) return;
if (node.isExpanded) {
//
setState(() {
node.isExpanded = false;
});
} else {
//
if (node.children != null) {
//
setState(() {
node.isExpanded = true;
});
} else {
//
await _loadChildren(node);
}
}
}
///
Future<void> _loadChildren(TeamTreeNode node) async {
//
if (_childrenCache.containsKey(node.accountSequence)) {
setState(() {
node.children = _childrenCache[node.accountSequence];
node.isExpanded = true;
});
return;
}
//
setState(() {
node.isLoading = true;
});
try {
final response = await widget.referralDataSource.getUserDirectReferrals(
accountSequence: node.accountSequence,
);
final children = response.referrals
.map((info) => TeamTreeNode.fromDirectReferralInfo(info))
.toList();
//
_childrenCache[node.accountSequence] = children;
if (mounted) {
setState(() {
node.children = children;
node.isExpanded = true;
node.isLoading = false;
});
}
} catch (e) {
debugPrint('加载子节点失败: $e');
if (mounted) {
setState(() {
node.isLoading = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('加载失败: $e'),
backgroundColor: Colors.red,
),
);
}
}
}
///
void _showNodeDetails(TeamTreeNode node) {
showModalBottomSheet(
context: context,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (context) => _NodeDetailsSheet(
node: node,
onExpandTap: node.hasChildren
? () {
Navigator.pop(context);
_handleNodeTap(node);
}
: null,
),
);
}
}
/// 线
class _TreeLinePainter extends CustomPainter {
final double parentWidth;
final int childCount;
final double childWidth;
final double spacing;
_TreeLinePainter({
required this.parentWidth,
required this.childCount,
required this.childWidth,
required this.spacing,
});
@override
void paint(Canvas canvas, Size size) {
if (childCount == 0) return;
final paint = Paint()
..color = const Color(0xFFFF6B00)
..strokeWidth = 1.5
..style = PaintingStyle.stroke;
//
final parentCenterX = size.width / 2;
const parentBottomY = 0.0;
//
final midY = size.height / 2;
//
final totalChildrenWidth = childCount * childWidth + (childCount - 1) * spacing;
final startX = (size.width - totalChildrenWidth) / 2;
// 线
canvas.drawLine(
Offset(parentCenterX, parentBottomY),
Offset(parentCenterX, midY),
paint,
);
if (childCount == 1) {
// 线
canvas.drawLine(
Offset(parentCenterX, midY),
Offset(parentCenterX, size.height),
paint,
);
} else {
// 线
final firstChildCenterX = startX + childWidth / 2;
final lastChildCenterX = startX + (childCount - 1) * (childWidth + spacing) + childWidth / 2;
canvas.drawLine(
Offset(firstChildCenterX, midY),
Offset(lastChildCenterX, midY),
paint,
);
// 线线
for (int i = 0; i < childCount; i++) {
final childCenterX = startX + i * (childWidth + spacing) + childWidth / 2;
canvas.drawLine(
Offset(childCenterX, midY),
Offset(childCenterX, size.height),
paint,
);
}
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
///
class _NodeDetailsSheet extends StatelessWidget {
final TeamTreeNode node;
final VoidCallback? onExpandTap;
const _NodeDetailsSheet({
required this.node,
this.onExpandTap,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'成员详情',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: Color(0xFF5D4037),
),
),
GestureDetector(
onTap: () => Navigator.pop(context),
child: const Icon(
Icons.close,
color: Color(0xFF999999),
size: 24,
),
),
],
),
const SizedBox(height: 20),
//
_buildDetailRow('序列号', node.accountSequence),
const SizedBox(height: 12),
//
_buildDetailRow('个人认种', '${node.personalPlantingCount}'),
const SizedBox(height: 12),
//
_buildDetailRow('团队认种', '${node.teamPlantingCount}'),
const SizedBox(height: 12),
//
_buildDetailRow('直推人数', '${node.directReferralCount}'),
const SizedBox(height: 24),
//
if (onExpandTap != null)
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: onExpandTap,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFFF6B00),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text(
node.isExpanded ? '收起下级' : '展开下级 (${node.directReferralCount}人)',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
),
//
SizedBox(height: MediaQuery.of(context).padding.bottom + 8),
],
),
);
}
Widget _buildDetailRow(String label, String value) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: const TextStyle(
fontSize: 14,
color: Color(0xFF8B5A2B),
),
),
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerRight,
child: Text(
value,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF5D4037),
),
),
),
),
],
);
}
}