diff --git a/frontend/mobile-app/lib/core/services/authorization_service.dart b/frontend/mobile-app/lib/core/services/authorization_service.dart index a932fe96..17b67ba0 100644 --- a/frontend/mobile-app/lib/core/services/authorization_service.dart +++ b/frontend/mobile-app/lib/core/services/authorization_service.dart @@ -468,6 +468,8 @@ class StickmanRankingResponse { final double monthlyEarnings; final bool isCurrentUser; final String? accountSequence; + final int finalTarget; // 最终目标数量 + final double progressPercentage; // 进度百分比 (0-100) StickmanRankingResponse({ required this.id, @@ -479,6 +481,8 @@ class StickmanRankingResponse { required this.monthlyEarnings, required this.isCurrentUser, this.accountSequence, + required this.finalTarget, + required this.progressPercentage, }); factory StickmanRankingResponse.fromJson(Map json) { @@ -492,6 +496,8 @@ class StickmanRankingResponse { monthlyEarnings: (json['monthlyEarnings'] ?? 0).toDouble(), isCurrentUser: json['isCurrentUser'] ?? false, accountSequence: json['accountSequence']?.toString(), + finalTarget: json['finalTarget'] ?? 50000, + progressPercentage: (json['progressPercentage'] ?? 0).toDouble(), ); } } diff --git a/frontend/mobile-app/lib/features/authorization/presentation/widgets/stickman_race_widget.dart b/frontend/mobile-app/lib/features/authorization/presentation/widgets/stickman_race_widget.dart index 3c027636..7a64e7ce 100644 --- a/frontend/mobile-app/lib/features/authorization/presentation/widgets/stickman_race_widget.dart +++ b/frontend/mobile-app/lib/features/authorization/presentation/widgets/stickman_race_widget.dart @@ -8,10 +8,11 @@ class StickmanRankingData { final String nickname; final String? avatarUrl; final int completedCount; // 完成数量 - final int targetCount; // 目标数量 (省: 50000, 市: 10000) + final int targetCount; // 目标数量 (从后端获取) final double monthlyEarnings; // 本月可结算收益 final bool isCurrentUser; // 是否是当前用户 final String? accountSequence; // 账户序列号(用于查看详情) + final double progressPercentage; // 进度百分比 (0-100, 从后端获取) StickmanRankingData({ required this.id, @@ -22,10 +23,11 @@ class StickmanRankingData { required this.monthlyEarnings, this.isCurrentUser = false, this.accountSequence, + required this.progressPercentage, }); - /// 完成进度 (0.0 - 1.0) - double get progress => (completedCount / targetCount).clamp(0.0, 1.0); + /// 完成进度 (0.0 - 1.0),使用后端计算的进度百分比 + double get progress => (progressPercentage / 100).clamp(0.0, 1.0); } /// 授权类型 diff --git a/frontend/mobile-app/lib/features/authorization/presentation/widgets/user_profile_dialog.dart b/frontend/mobile-app/lib/features/authorization/presentation/widgets/user_profile_dialog.dart index ec7644d9..054f4b9b 100644 --- a/frontend/mobile-app/lib/features/authorization/presentation/widgets/user_profile_dialog.dart +++ b/frontend/mobile-app/lib/features/authorization/presentation/widgets/user_profile_dialog.dart @@ -98,134 +98,70 @@ class _UserProfilePageState extends ConsumerState { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: const Color(0xFFF5F0E6), - body: CustomScrollView( - slivers: [ - // 自定义AppBar - SliverAppBar( - expandedHeight: 200, - pinned: true, - backgroundColor: const Color(0xFFD4AF37), - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios, color: Colors.white), - onPressed: () => Navigator.of(context).pop(), - ), - flexibleSpace: FlexibleSpaceBar( - background: Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - colors: [Color(0xFFD4AF37), Color(0xFFB8860B)], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - ), - child: _isLoading || _profile == null - ? const Center( - child: CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation(Colors.white), - ), - ) - : _buildHeaderContent(), + body: Container( + width: double.infinity, + height: double.infinity, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0xFFFFF5E6), + Color(0xFFFFE4B5), + ], + ), + ), + child: SafeArea( + child: Column( + children: [ + // 顶部导航栏 + _buildAppBar(), + // 内容区域 + Expanded( + child: _isLoading + ? _buildLoading() + : _error != null + ? _buildError() + : _buildContent(), ), - ), + ], ), - // 内容区域 - SliverToBoxAdapter( - child: _isLoading - ? _buildLoading() - : _error != null - ? _buildError() - : _buildContent(), - ), - ], + ), ), ); } - Widget _buildHeaderContent() { - final profile = _profile!; - return SafeArea( - child: Padding( - padding: const EdgeInsets.fromLTRB(20, 60, 20, 20), - child: Row( - children: [ - // 头像 - Container( - width: 80, - height: 80, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: Colors.white, - width: 3, - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.2), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], - ), - child: ClipOval( - child: _buildAvatar(profile.avatarUrl, size: 80), + Widget _buildAppBar() { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.arrow_back_ios, color: Color(0xFF5D4037)), + onPressed: () => Navigator.of(context).pop(), + ), + Expanded( + child: Text( + widget.nickname ?? '用户详情', + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Color(0xFF5D4037), ), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, ), - const SizedBox(width: 20), - // 昵称和标签 - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - profile.nickname, - style: const TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - const SizedBox(height: 8), - Wrap( - spacing: 6, - runSpacing: 4, - children: profile.authorizations.map((auth) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), - decoration: BoxDecoration( - color: auth.benefitActive - ? Colors.white.withValues(alpha: 0.3) - : Colors.white.withValues(alpha: 0.15), - borderRadius: BorderRadius.circular(12), - ), - child: Text( - auth.displayTitle, - style: const TextStyle( - fontSize: 12, - color: Colors.white, - fontWeight: FontWeight.w500, - ), - ), - ); - }).toList(), - ), - ], - ), - ), - ], - ), + ), + const SizedBox(width: 48), // 平衡左侧返回按钮 + ], ), ); } Widget _buildLoading() { return const Center( - child: Padding( - padding: EdgeInsets.all(60), - child: CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation(Color(0xFFD4AF37)), - ), + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(Color(0xFFD4AF37)), ), ); } @@ -238,39 +174,45 @@ class _UserProfilePageState extends ConsumerState { mainAxisSize: MainAxisSize.min, children: [ Container( - width: 80, - height: 80, + width: 60, + height: 60, decoration: BoxDecoration( - color: Colors.red.withValues(alpha: 0.1), + color: const Color(0xFFFFF3E0), shape: BoxShape.circle, + border: Border.all(color: const Color(0xFFFFCC80)), ), child: const Icon( Icons.error_outline, - color: Colors.red, - size: 40, + color: Color(0xFFE65100), + size: 30, ), ), - const SizedBox(height: 20), + const SizedBox(height: 16), Text( _error ?? '加载失败', style: const TextStyle( - fontSize: 16, + fontSize: 14, color: Color(0xFF5D4037), ), textAlign: TextAlign.center, ), - const SizedBox(height: 24), - ElevatedButton.icon( - onPressed: _loadProfile, - icon: const Icon(Icons.refresh), - label: const Text('重试'), - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFFD4AF37), - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), - shape: RoundedRectangleBorder( + const SizedBox(height: 20), + GestureDetector( + onTap: _loadProfile, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10), + decoration: BoxDecoration( + color: const Color(0xFFD4AF37), borderRadius: BorderRadius.circular(20), ), + child: const Text( + '重试', + style: TextStyle( + fontSize: 14, + color: Colors.white, + fontWeight: FontWeight.w600, + ), + ), ), ), ], @@ -281,19 +223,22 @@ class _UserProfilePageState extends ConsumerState { Widget _buildContent() { final profile = _profile!; - return Padding( - padding: const EdgeInsets.all(20), + return SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + // 用户头像和基本信息 + _buildUserHeader(profile), + const SizedBox(height: 16), + // 统计数据卡片 _buildStatsCard(profile), - const SizedBox(height: 20), + const SizedBox(height: 16), // 基本信息卡片 _buildInfoCard( title: '基本信息', - icon: Icons.info_outline, children: [ _buildInfoRow('用户ID', profile.accountSequence), _buildInfoRow('注册时间', _formatDate(profile.registeredAtDateTime)), @@ -309,7 +254,6 @@ class _UserProfilePageState extends ConsumerState { if (profile.authorizations.isNotEmpty) ...[ _buildInfoCard( title: '授权信息', - icon: Icons.verified_user_outlined, children: profile.authorizations.map((auth) { return _buildAuthorizationRow(auth); }).toList(), @@ -320,8 +264,7 @@ class _UserProfilePageState extends ConsumerState { // 私密信息(仅特权用户可见) if (_hasPrivilege && profile.hasPrivateInfo) _buildInfoCard( - title: '联系信息', - icon: Icons.lock_outline, + title: '联系信息(仅管理员可见)', children: [ if (profile.phoneNumber != null) _buildInfoRow('手机号', profile.phoneNumber!), @@ -340,45 +283,132 @@ class _UserProfilePageState extends ConsumerState { ); } - Widget _buildStatsCard(UserProfileResponse profile) { + Widget _buildUserHeader(UserProfileResponse profile) { return Container( - padding: const EdgeInsets.all(20), + padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: Colors.white, + color: Colors.white.withValues(alpha: 0.9), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( - color: Colors.black.withValues(alpha: 0.05), + color: const Color(0xFFD4AF37).withValues(alpha: 0.1), blurRadius: 10, - offset: const Offset(0, 4), + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + // 头像 + Container( + width: 72, + height: 72, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: const Color(0xFFD4AF37), + width: 2, + ), + boxShadow: [ + BoxShadow( + color: const Color(0xFFD4AF37).withValues(alpha: 0.2), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: ClipOval( + child: _buildAvatar(profile.avatarUrl, size: 72), + ), + ), + const SizedBox(width: 16), + // 昵称和标签 + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + profile.nickname, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + color: Color(0xFF5D4037), + ), + ), + const SizedBox(height: 8), + Wrap( + spacing: 6, + runSpacing: 4, + children: profile.authorizations.map((auth) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + decoration: BoxDecoration( + color: auth.benefitActive + ? const Color(0xFFD4AF37).withValues(alpha: 0.15) + : const Color(0xFF9E9E9E).withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: auth.benefitActive + ? const Color(0xFFD4AF37).withValues(alpha: 0.3) + : const Color(0xFF9E9E9E).withValues(alpha: 0.3), + ), + ), + child: Text( + auth.displayTitle, + style: TextStyle( + fontSize: 11, + color: auth.benefitActive + ? const Color(0xFFB8860B) + : const Color(0xFF757575), + fontWeight: FontWeight.w500, + ), + ), + ); + }).toList(), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildStatsCard(UserProfileResponse profile) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12), + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.9), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: const Color(0xFFD4AF37).withValues(alpha: 0.1), + blurRadius: 10, + offset: const Offset(0, 2), ), ], ), child: Row( children: [ _buildStatItem( - icon: Icons.people_outline, - label: '直推人数', + label: '直推', value: '${profile.directReferralCount}', color: const Color(0xFF4CAF50), ), _buildStatDivider(), _buildStatItem( - icon: Icons.account_tree_outlined, - label: '伞下用户', + label: '伞下', value: '${profile.umbrellaUserCount}', color: const Color(0xFF2196F3), ), _buildStatDivider(), _buildStatItem( - icon: Icons.eco_outlined, label: '个人认种', value: '${profile.personalPlantingCount}', color: const Color(0xFF8BC34A), ), _buildStatDivider(), _buildStatItem( - icon: Icons.forest_outlined, label: '团队认种', value: '${profile.teamPlantingCount}', color: const Color(0xFFD4AF37), @@ -389,7 +419,6 @@ class _UserProfilePageState extends ConsumerState { } Widget _buildStatItem({ - required IconData icon, required String label, required String value, required Color color, @@ -397,16 +426,6 @@ class _UserProfilePageState extends ConsumerState { return Expanded( child: Column( children: [ - Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: color.withValues(alpha: 0.1), - shape: BoxShape.circle, - ), - child: Icon(icon, color: color, size: 20), - ), - const SizedBox(height: 8), Text( value, style: TextStyle( @@ -432,8 +451,8 @@ class _UserProfilePageState extends ConsumerState { Widget _buildStatDivider() { return Container( width: 1, - height: 50, - color: const Color(0xFFE0E0E0), + height: 36, + color: const Color(0xFFE0D5C5), ); } @@ -467,7 +486,7 @@ class _UserProfilePageState extends ConsumerState { return Container( width: size, height: size, - color: const Color(0xFFD4AF37).withValues(alpha: 0.3), + color: const Color(0xFFFFF5E6), child: Icon( Icons.person, size: size * 0.5, @@ -479,52 +498,33 @@ class _UserProfilePageState extends ConsumerState { Widget _buildInfoCard({ required String title, required List children, - IconData? icon, }) { return Container( width: double.infinity, - padding: const EdgeInsets.all(20), + padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: Colors.white, + color: Colors.white.withValues(alpha: 0.9), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( - color: Colors.black.withValues(alpha: 0.05), + color: const Color(0xFFD4AF37).withValues(alpha: 0.1), blurRadius: 10, - offset: const Offset(0, 4), + offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - if (icon != null) ...[ - Container( - width: 32, - height: 32, - decoration: BoxDecoration( - color: const Color(0xFFD4AF37).withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(8), - ), - child: Icon(icon, size: 18, color: const Color(0xFFD4AF37)), - ), - const SizedBox(width: 12), - ], - Text( - title, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Color(0xFF5D4037), - ), - ), - ], + Text( + title, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: Color(0xFF5D4037), + ), ), - const SizedBox(height: 16), - const Divider(height: 1, color: Color(0xFFEEEEEE)), - const SizedBox(height: 16), + const SizedBox(height: 12), ...children, ], ), @@ -533,16 +533,16 @@ class _UserProfilePageState extends ConsumerState { Widget _buildInfoRow(String label, String value) { return Padding( - padding: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.only(bottom: 10), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( - width: 90, + width: 80, child: Text( label, style: const TextStyle( - fontSize: 14, + fontSize: 13, color: Color(0xFF8B7355), ), ), @@ -551,7 +551,7 @@ class _UserProfilePageState extends ConsumerState { child: Text( value, style: const TextStyle( - fontSize: 14, + fontSize: 13, color: Color(0xFF5D4037), fontWeight: FontWeight.w500, ), @@ -564,40 +564,40 @@ class _UserProfilePageState extends ConsumerState { Widget _buildAuthorizationRow(UserAuthorizationInfo auth) { return Padding( - padding: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.only(bottom: 10), child: Row( children: [ Container( - width: 10, - height: 10, + width: 8, + height: 8, decoration: BoxDecoration( shape: BoxShape.circle, - color: auth.benefitActive ? Colors.green : Colors.grey, + color: auth.benefitActive ? const Color(0xFF4CAF50) : const Color(0xFF9E9E9E), ), ), - const SizedBox(width: 12), + const SizedBox(width: 10), Expanded( child: Text( auth.displayTitle, style: const TextStyle( - fontSize: 14, + fontSize: 13, color: Color(0xFF5D4037), ), ), ), Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: auth.benefitActive - ? Colors.green.withValues(alpha: 0.1) - : Colors.grey.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(12), + ? const Color(0xFF4CAF50).withValues(alpha: 0.1) + : const Color(0xFF9E9E9E).withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(10), ), child: Text( auth.benefitActive ? '权益激活' : '权益未激活', style: TextStyle( - fontSize: 12, - color: auth.benefitActive ? Colors.green : Colors.grey, + fontSize: 11, + color: auth.benefitActive ? const Color(0xFF4CAF50) : const Color(0xFF9E9E9E), fontWeight: FontWeight.w500, ), ), diff --git a/frontend/mobile-app/lib/features/profile/presentation/pages/profile_page.dart b/frontend/mobile-app/lib/features/profile/presentation/pages/profile_page.dart index 08bb1eb1..db56832e 100644 --- a/frontend/mobile-app/lib/features/profile/presentation/pages/profile_page.dart +++ b/frontend/mobile-app/lib/features/profile/presentation/pages/profile_page.dart @@ -609,10 +609,11 @@ class _ProfilePageState extends ConsumerState { nickname: r.nickname, avatarUrl: r.avatarUrl, completedCount: r.completedCount, - targetCount: 50000, // 省公司目标5万 + targetCount: r.finalTarget, monthlyEarnings: r.monthlyEarnings, isCurrentUser: r.isCurrentUser, accountSequence: r.accountSequence, + progressPercentage: r.progressPercentage, )).toList(); }); } @@ -643,10 +644,11 @@ class _ProfilePageState extends ConsumerState { nickname: r.nickname, avatarUrl: r.avatarUrl, completedCount: r.completedCount, - targetCount: 10000, // 市公司目标1万 + targetCount: r.finalTarget, monthlyEarnings: r.monthlyEarnings, isCurrentUser: r.isCurrentUser, accountSequence: r.accountSequence, + progressPercentage: r.progressPercentage, )).toList(); }); }