diff --git a/flutter_monisuo/lib/core/event/app_event_bus.dart b/flutter_monisuo/lib/core/event/app_event_bus.dart new file mode 100644 index 0000000..b0bc173 --- /dev/null +++ b/flutter_monisuo/lib/core/event/app_event_bus.dart @@ -0,0 +1,50 @@ +import 'dart:async'; + +/// 应用事件类型 +enum AppEventType { + /// 资产变动(余额、持仓等) + assetChanged, + + /// 订单变动(充提订单状态变化) + orderChanged, +} + +/// 应用事件 +class AppEvent { + final AppEventType type; + final Map? data; + + const AppEvent(this.type, {this.data}); +} + +/// 轻量级应用内事件总线 +/// 基于 StreamController.broadcast,零外部依赖 +class AppEventBus { + final StreamController _controller = + StreamController.broadcast(); + + /// 广播事件 + void fire(AppEventType type, {Map? data}) { + if (!_controller.isClosed) { + _controller.add(AppEvent(type, data: data)); + } + } + + /// 监听指定类型事件 + StreamSubscription on( + AppEventType type, + void Function(AppEvent) callback, + ) { + return _controller.stream + .where((event) => event.type == type) + .listen(callback); + } + + /// 监听任意事件 + Stream get stream => _controller.stream; + + /// 销毁 + void dispose() { + _controller.close(); + } +} diff --git a/flutter_monisuo/lib/ui/pages/mine/welfare_center_page.dart b/flutter_monisuo/lib/ui/pages/mine/welfare_center_page.dart new file mode 100644 index 0000000..a3358cb --- /dev/null +++ b/flutter_monisuo/lib/ui/pages/mine/welfare_center_page.dart @@ -0,0 +1,609 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; +import 'package:provider/provider.dart'; +import 'package:google_fonts/google_fonts.dart'; +import '../../../core/theme/app_color_scheme.dart'; +import '../../../core/theme/app_spacing.dart'; +import '../../../core/utils/toast_utils.dart'; +import '../../../data/services/bonus_service.dart'; +import '../../../providers/asset_provider.dart'; +import '../../../providers/auth_provider.dart'; +import '../../components/glass_panel.dart'; +import '../../components/neon_glow.dart'; + +/// 福利中心页面 +class WelfareCenterPage extends StatefulWidget { + const WelfareCenterPage({super.key}); + + @override + State createState() => _WelfareCenterPageState(); +} + +class _WelfareCenterPageState extends State { + Map? _welfareData; + bool _isLoading = true; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) => _loadData()); + } + + Future _loadData() async { + try { + final bonusService = context.read(); + final response = await bonusService.getWelfareStatus(); + if (mounted) { + setState(() { + _welfareData = response.data; + _isLoading = false; + }); + } + } catch (_) { + if (mounted) setState(() => _isLoading = false); + } + } + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + + return Scaffold( + backgroundColor: colorScheme.background, + appBar: AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + title: Text( + '福利中心', + style: GoogleFonts.spaceGrotesk( + fontWeight: FontWeight.bold, + color: colorScheme.onSurface, + ), + ), + leading: IconButton( + icon: Icon(LucideIcons.chevronLeft, color: colorScheme.onSurface), + onPressed: () => Navigator.of(context).pop(), + ), + ), + body: _isLoading + ? Center( + child: CircularProgressIndicator(color: colorScheme.primary), + ) + : RefreshIndicator( + onRefresh: _loadData, + color: colorScheme.primary, + child: SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + padding: AppSpacing.pagePadding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 推广码卡片 + _buildReferralCodeCard(colorScheme), + SizedBox(height: AppSpacing.lg), + + // 首充福利卡片 + _buildNewUserBonusCard(colorScheme), + SizedBox(height: AppSpacing.lg), + + // 推广奖励列表 + _buildReferralRewardsSection(colorScheme), + SizedBox(height: AppSpacing.lg), + + // 规则说明 + _buildRulesCard(colorScheme), + ], + ), + ), + ), + ); + } + + /// 推广码卡片 + Widget _buildReferralCodeCard(ColorScheme colorScheme) { + final referralCode = _welfareData?['referralCode'] as String? ?? ''; + + return GlassPanel( + padding: EdgeInsets.all(AppSpacing.lg), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: EdgeInsets.all(AppSpacing.sm), + decoration: BoxDecoration( + color: colorScheme.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(AppRadius.md), + ), + child: Icon( + LucideIcons.users, + color: colorScheme.primary, + size: 20, + ), + ), + SizedBox(width: AppSpacing.md), + Text( + '我的推广码', + style: GoogleFonts.spaceGrotesk( + fontSize: 16, + fontWeight: FontWeight.bold, + color: colorScheme.onSurface, + ), + ), + ], + ), + SizedBox(height: AppSpacing.lg), + Container( + width: double.infinity, + padding: EdgeInsets.symmetric( + horizontal: AppSpacing.lg, + vertical: AppSpacing.md, + ), + decoration: BoxDecoration( + color: colorScheme.surfaceContainerHigh, + borderRadius: BorderRadius.circular(AppRadius.lg), + border: Border.all( + color: colorScheme.outlineVariant.withOpacity(0.2), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + referralCode.isEmpty ? '暂无推广码' : referralCode, + style: GoogleFonts.spaceGrotesk( + fontSize: 24, + fontWeight: FontWeight.bold, + letterSpacing: 4, + color: referralCode.isEmpty + ? colorScheme.onSurfaceVariant + : colorScheme.primary, + ), + ), + if (referralCode.isNotEmpty) ...[ + SizedBox(width: AppSpacing.md), + GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: referralCode)); + ToastUtils.show('推广码已复制'); + }, + child: Container( + padding: EdgeInsets.all(AppSpacing.xs + 2), + decoration: BoxDecoration( + color: colorScheme.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(AppRadius.sm), + ), + child: Icon( + LucideIcons.copy, + size: 18, + color: colorScheme.primary, + ), + ), + ), + ], + ], + ), + ), + SizedBox(height: AppSpacing.sm), + Text( + '分享推广码给好友,好友注册并充值满1000u后您可领取100u奖励', + style: TextStyle( + fontSize: 11, + color: colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ); + } + + /// 首充福利卡片 + Widget _buildNewUserBonusCard(ColorScheme colorScheme) { + final newUserBonus = _welfareData?['newUserBonus'] as Map?; + final eligible = newUserBonus?['eligible'] as bool? ?? false; + final claimed = newUserBonus?['claimed'] as bool? ?? false; + final deposited = newUserBonus?['deposited'] as bool? ?? false; + + String statusText; + Color statusColor; + String buttonText; + bool canClaim; + + if (claimed) { + statusText = '100 USDT 已领取'; + statusColor = AppColorScheme.up; + buttonText = '已领取'; + canClaim = false; + } else if (eligible) { + statusText = '符合条件,立即领取!'; + statusColor = colorScheme.primary; + buttonText = '领取 100u'; + canClaim = true; + } else { + statusText = deposited ? '已完成充值' : '完成首次充值后可领取'; + statusColor = colorScheme.onSurfaceVariant; + buttonText = '未解锁'; + canClaim = false; + } + + return GlassPanel( + padding: EdgeInsets.all(AppSpacing.lg), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: EdgeInsets.all(AppSpacing.sm), + decoration: BoxDecoration( + color: (claimed ? AppColorScheme.up : colorScheme.primary) + .withOpacity(0.1), + borderRadius: BorderRadius.circular(AppRadius.md), + ), + child: Icon( + claimed ? LucideIcons.check : LucideIcons.gift, + color: claimed ? AppColorScheme.up : colorScheme.primary, + size: 20, + ), + ), + SizedBox(width: AppSpacing.md), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '首充福利', + style: GoogleFonts.spaceGrotesk( + fontSize: 16, + fontWeight: FontWeight.bold, + color: colorScheme.onSurface, + ), + ), + SizedBox(height: 2), + Text( + statusText, + style: TextStyle( + fontSize: 12, + color: statusColor, + ), + ), + ], + ), + ), + SizedBox( + child: NeonButton( + text: buttonText, + type: canClaim ? NeonButtonType.primary : NeonButtonType.outline, + onPressed: canClaim ? () => _claimNewUserBonus() : null, + height: 36, + showGlow: canClaim, + ), + ), + ], + ), + SizedBox(height: AppSpacing.md), + Text( + '新用户首次充值完成后可领取 100 USDT', + style: TextStyle( + fontSize: 11, + color: colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ); + } + + /// 推广奖励列表 + Widget _buildReferralRewardsSection(ColorScheme colorScheme) { + final referralRewards = + _welfareData?['referralRewards'] as List? ?? []; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '推广奖励', + style: GoogleFonts.spaceGrotesk( + fontSize: 16, + fontWeight: FontWeight.bold, + color: colorScheme.onSurface, + ), + ), + SizedBox(height: AppSpacing.sm), + Text( + '被推广人每累计充值满 1000u,您可领取 100u 奖励(每人最多8次)', + style: TextStyle( + fontSize: 12, + color: colorScheme.onSurfaceVariant, + ), + ), + SizedBox(height: AppSpacing.md), + if (referralRewards.isEmpty) + GlassPanel( + padding: EdgeInsets.all(AppSpacing.xl), + child: Center( + child: Column( + children: [ + Icon( + LucideIcons.users, + size: 36, + color: colorScheme.onSurfaceVariant.withOpacity(0.4), + ), + SizedBox(height: AppSpacing.sm), + Text( + '暂无推广用户', + style: TextStyle( + color: colorScheme.onSurfaceVariant, + fontSize: 13, + ), + ), + ], + ), + ), + ) + else + ...referralRewards.map((item) { + final data = item as Map; + return _buildReferralRewardCard(data, colorScheme); + }), + ], + ); + } + + Widget _buildReferralRewardCard( + Map data, ColorScheme colorScheme) { + final username = data['username'] as String? ?? ''; + final totalDeposit = data['totalDeposit']?.toString() ?? '0'; + final claimableCount = data['claimableCount'] as int? ?? 0; + final milestones = data['milestones'] as List? ?? []; + + return Padding( + padding: EdgeInsets.only(bottom: AppSpacing.md), + child: GlassPanel( + padding: EdgeInsets.all(AppSpacing.md), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + CircleAvatar( + radius: 16, + backgroundColor: colorScheme.primary.withOpacity(0.1), + child: Text( + username.isNotEmpty ? username[0].toUpperCase() : '?', + style: TextStyle( + color: colorScheme.primary, + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ), + SizedBox(width: AppSpacing.sm), + Text( + username, + style: GoogleFonts.spaceGrotesk( + fontSize: 14, + fontWeight: FontWeight.w600, + color: colorScheme.onSurface, + ), + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + '累计充值: $totalDeposit U', + style: TextStyle( + fontSize: 12, + color: colorScheme.onSurfaceVariant, + ), + ), + if (claimableCount > 0) + Text( + '$claimableCount 个可领取', + style: TextStyle( + fontSize: 11, + color: colorScheme.primary, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ], + ), + SizedBox(height: AppSpacing.sm), + // 里程碑进度 + Wrap( + spacing: 6, + runSpacing: 6, + children: milestones.map((m) { + final milestone = m as Map; + final earned = milestone['earned'] as bool? ?? false; + final claimed = milestone['claimed'] as bool? ?? false; + final claimable = milestone['claimable'] as bool? ?? false; + final milestoneNum = milestone['milestone'] as int? ?? 0; + + Color bgColor; + Color textColor; + VoidCallback? onTap; + + if (claimed) { + bgColor = AppColorScheme.up.withOpacity(0.15); + textColor = AppColorScheme.up; + onTap = null; + } else if (claimable) { + bgColor = colorScheme.primary.withOpacity(0.15); + textColor = colorScheme.primary; + onTap = () => _claimReferralBonus( + data['userId'] as int, + milestoneNum, + ); + } else if (earned) { + bgColor = colorScheme.surfaceContainerHigh; + textColor = colorScheme.onSurfaceVariant; + onTap = null; + } else { + bgColor = colorScheme.surfaceContainerHighest + .withOpacity(0.5); + textColor = colorScheme.onSurfaceVariant.withOpacity(0.5); + onTap = null; + } + + return GestureDetector( + onTap: onTap, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 6, + ), + decoration: BoxDecoration( + color: bgColor, + borderRadius: BorderRadius.circular(AppRadius.md), + border: Border.all( + color: claimable + ? colorScheme.primary.withOpacity(0.3) + : Colors.transparent, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (claimed) + Icon(LucideIcons.check, size: 12, color: textColor) + else if (claimable) + Icon(LucideIcons.gift, size: 12, color: textColor), + if (claimed || claimable) SizedBox(width: 4), + Text( + '${milestoneNum}k', + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + color: textColor, + ), + ), + ], + ), + ), + ); + }).toList(), + ), + ], + ), + ), + ); + } + + /// 规则说明 + Widget _buildRulesCard(ColorScheme colorScheme) { + return Container( + padding: EdgeInsets.all(AppSpacing.md), + decoration: BoxDecoration( + color: colorScheme.surfaceContainerHigh.withOpacity(0.3), + borderRadius: BorderRadius.circular(AppRadius.lg), + border: Border.all( + color: colorScheme.outlineVariant.withOpacity(0.1), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(LucideIcons.info, size: 16, color: colorScheme.onSurfaceVariant), + SizedBox(width: AppSpacing.sm), + Text( + '规则说明', + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: colorScheme.onSurface, + ), + ), + ], + ), + SizedBox(height: AppSpacing.sm), + _buildRuleItem('新用户首次充值完成后可领取 100 USDT 首充福利', colorScheme), + _buildRuleItem('被推广人累计充值每满 1000u,推广人可领 100u', colorScheme), + _buildRuleItem('每位被推广人最多可触发 8 次推广奖励', colorScheme), + _buildRuleItem('所有奖励将直接存入资金账户', colorScheme), + ], + ), + ); + } + + Widget _buildRuleItem(String text, ColorScheme colorScheme) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 3), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only(top: 6), + width: 4, + height: 4, + decoration: BoxDecoration( + color: colorScheme.onSurfaceVariant, + shape: BoxShape.circle, + ), + ), + SizedBox(width: AppSpacing.sm), + Expanded( + child: Text( + text, + style: TextStyle( + fontSize: 12, + color: colorScheme.onSurfaceVariant, + ), + ), + ), + ], + ), + ); + } + + Future _claimNewUserBonus() async { + try { + final bonusService = context.read(); + final response = await bonusService.claimNewUserBonus(); + if (!mounted) return; + + if (response.success) { + context.read().refreshAll(force: true); + ToastUtils.show('领取成功!100 USDT 已到账'); + _loadData(); + } else { + ToastUtils.show(response.message ?? '领取失败'); + } + } catch (e) { + ToastUtils.show('领取失败: $e'); + } + } + + Future _claimReferralBonus(int referredUserId, int milestone) async { + try { + final bonusService = context.read(); + final response = await bonusService.claimReferralBonus( + referredUserId, + milestone, + ); + if (!mounted) return; + + if (response.success) { + context.read().refreshAll(force: true); + ToastUtils.show('领取成功!100 USDT 已到账'); + _loadData(); + } else { + ToastUtils.show(response.message ?? '领取失败'); + } + } catch (e) { + ToastUtils.show('领取失败: $e'); + } + } +} diff --git a/uploads/kyc/reg_1775286400431_back.jpg b/uploads/kyc/reg_1775286400431_back.jpg new file mode 100644 index 0000000..8074cfb Binary files /dev/null and b/uploads/kyc/reg_1775286400431_back.jpg differ diff --git a/uploads/kyc/reg_1775286400431_front.jpg b/uploads/kyc/reg_1775286400431_front.jpg new file mode 100644 index 0000000..8074cfb Binary files /dev/null and b/uploads/kyc/reg_1775286400431_front.jpg differ diff --git a/uploads/kyc/reg_1775295539042_back.jpg b/uploads/kyc/reg_1775295539042_back.jpg new file mode 100644 index 0000000..8074cfb Binary files /dev/null and b/uploads/kyc/reg_1775295539042_back.jpg differ diff --git a/uploads/kyc/reg_1775295539042_front.jpg b/uploads/kyc/reg_1775295539042_front.jpg new file mode 100644 index 0000000..8074cfb Binary files /dev/null and b/uploads/kyc/reg_1775295539042_front.jpg differ diff --git a/uploads/kyc/reg_1775303319060_back.jpg b/uploads/kyc/reg_1775303319060_back.jpg new file mode 100644 index 0000000..8074cfb Binary files /dev/null and b/uploads/kyc/reg_1775303319060_back.jpg differ diff --git a/uploads/kyc/reg_1775303319060_front.jpg b/uploads/kyc/reg_1775303319060_front.jpg new file mode 100644 index 0000000..8074cfb Binary files /dev/null and b/uploads/kyc/reg_1775303319060_front.jpg differ diff --git a/uploads/kyc/reg_1775304499997_back.jpg b/uploads/kyc/reg_1775304499997_back.jpg new file mode 100644 index 0000000..8074cfb Binary files /dev/null and b/uploads/kyc/reg_1775304499997_back.jpg differ diff --git a/uploads/kyc/reg_1775304499997_front.jpg b/uploads/kyc/reg_1775304499997_front.jpg new file mode 100644 index 0000000..8074cfb Binary files /dev/null and b/uploads/kyc/reg_1775304499997_front.jpg differ diff --git a/uploads/kyc/reg_1775306976977_back.jpg b/uploads/kyc/reg_1775306976977_back.jpg new file mode 100644 index 0000000..8074cfb Binary files /dev/null and b/uploads/kyc/reg_1775306976977_back.jpg differ diff --git a/uploads/kyc/reg_1775306976977_front.jpg b/uploads/kyc/reg_1775306976977_front.jpg new file mode 100644 index 0000000..8074cfb Binary files /dev/null and b/uploads/kyc/reg_1775306976977_front.jpg differ diff --git a/uploads/kyc/reg_1775307185809_back.jpg b/uploads/kyc/reg_1775307185809_back.jpg new file mode 100644 index 0000000..e2d3e08 Binary files /dev/null and b/uploads/kyc/reg_1775307185809_back.jpg differ diff --git a/uploads/kyc/reg_1775307185809_front.jpg b/uploads/kyc/reg_1775307185809_front.jpg new file mode 100644 index 0000000..4c21993 Binary files /dev/null and b/uploads/kyc/reg_1775307185809_front.jpg differ