| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- import 'package:flutter/material.dart';
- import 'package:flutter_riverpod/flutter_riverpod.dart';
- import 'package:go_router/go_router.dart';
- import '../../../core/l10n/app_localizations.dart';
- import '../../../core/theme/app_colors.dart';
- import '../../../data/repositories/broker_repository.dart';
- // ── Providers ─────────────────────────────────────────────
- final _brokerInfoProvider = FutureProvider.autoDispose<Map<String, dynamic>?>((ref) {
- return ref.read(brokerRepositoryProvider).getBrokerInfo();
- });
- /// 今日返佣记录(对应安卓 rebatesInfo,startTime=今日零时)
- final _brokerTodayRewardsProvider = FutureProvider.autoDispose<List<Map<String, dynamic>>>((ref) {
- final now = DateTime.now();
- final todayStart = DateTime(now.year, now.month, now.day, 0, 0, 0);
- return ref.read(brokerRepositoryProvider).getRewardList(
- pageSize: 50,
- startTime: todayStart,
- );
- });
- // ── Screen ───────────────────────────────────────────────
- class BrokerScreen extends ConsumerWidget {
- const BrokerScreen({super.key});
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final cs = Theme.of(context).colorScheme;
- final infoAsync = ref.watch(_brokerInfoProvider);
- final rewardsAsync = ref.watch(_brokerTodayRewardsProvider);
- return Scaffold(
- backgroundColor: cs.surface,
- appBar: AppBar(
- backgroundColor: cs.surface,
- elevation: 0,
- leading: IconButton(
- icon: const Icon(Icons.arrow_back_ios, size: 18),
- onPressed: () => context.pop(),
- ),
- title: Text(AppLocalizations.of(context)!.broker, style: TextStyle(color: cs.onSurface, fontSize: 17, fontWeight: FontWeight.w600)),
- centerTitle: true,
- ),
- body: infoAsync.when(
- loading: () => const Center(child: CircularProgressIndicator()),
- error: (e, _) => Center(child: Text('${AppLocalizations.of(context)!.loadFailed}: $e')),
- data: (info) => _BrokerBody(info: info, rewardsAsync: rewardsAsync),
- ),
- );
- }
- }
- class _BrokerBody extends StatelessWidget {
- const _BrokerBody({required this.info, required this.rewardsAsync});
- final Map<String, dynamic>? info;
- final AsyncValue<List<Map<String, dynamic>>> rewardsAsync;
- @override
- Widget build(BuildContext context) {
- final cs = Theme.of(context).colorScheme;
- final isDark = Theme.of(context).brightness == Brightness.dark;
- final uid = info?['id']?.toString() ?? '--';
- // 与 profile_provider 保持一致:优先取 email,fallback 到 username
- final email = (info?['email'] as String?)?.trim().isNotEmpty == true
- ? info!['email'] as String
- : (info?['username'] as String? ?? '');
- final maskedEmail = _maskEmail(email);
- // 代理商显示名:按优先级依次尝试各字段
- final nickName = ((info?['nickName']
- ?? info?['agentName']
- ?? info?['name']
- ?? info?['realName']) as String? ?? '').trim();
- // 统计数据(字段对应安卓 AgentInfo:uc/agent/info)
- final todayNew = info?['todayNewUserCount']?.toString() ?? '0';
- final todayTrading = info?['todayOrderUserCount']?.toString() ?? '0';
- final todayRebate = _truncate4(info?['todayAward']?.toString() ?? '0.00');
- return SingleChildScrollView(
- padding: const EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- // ── 用户信息卡片 ──────────────────────────────
- Container(
- width: double.infinity,
- padding: const EdgeInsets.all(16),
- decoration: BoxDecoration(
- color: isDark ? AppColors.darkBgSecondary : AppColors.lightBgSecondary,
- borderRadius: BorderRadius.circular(12),
- border: Border.all(color: cs.outlineVariant.withAlpha(60)),
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- nickName.isNotEmpty
- ? AppLocalizations.of(context)!.brokerWelcomeNamed(nickName)
- : AppLocalizations.of(context)!.brokerWelcome,
- style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700, color: cs.onSurface),
- ),
- const SizedBox(height: 6),
- Row(children: [
- Flexible(
- child: Text(maskedEmail, maxLines: 1, overflow: TextOverflow.ellipsis,
- style: TextStyle(fontSize: 13, color: cs.onSurface.withAlpha(120))),
- ),
- const SizedBox(width: 8),
- Container(
- padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
- decoration: BoxDecoration(
- color: cs.onSurface.withAlpha(18),
- borderRadius: BorderRadius.circular(4),
- ),
- child: Text('UID: $uid',
- style: TextStyle(fontSize: 12, color: cs.onSurface.withAlpha(140))),
- ),
- ]),
- ],
- ),
- ),
- const SizedBox(height: 12),
- // ── 提示横幅 ─────────────────────────────────
- Container(
- padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
- decoration: BoxDecoration(
- color: AppColors.brand.withAlpha(25),
- borderRadius: BorderRadius.circular(10),
- border: Border.all(color: AppColors.brand.withAlpha(80)),
- ),
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- const Icon(Icons.info_outline, size: 16, color: AppColors.brand),
- const SizedBox(width: 8),
- Expanded(
- child: Text(
- AppLocalizations.of(context)!.brokerInviteTip,
- style: TextStyle(fontSize: 12, color: cs.onSurface.withAlpha(160), height: 1.5),
- ),
- ),
- ],
- ),
- ),
- const SizedBox(height: 20),
- // ── 统计数据(来自 agentInfo,与安卓一致)────
- _StatsRow(todayNew: todayNew, todayTrading: todayTrading, todayRebate: todayRebate),
- const SizedBox(height: 24),
- // ── 两个按钮 ──────────────────────────────────
- Row(children: [
- Expanded(
- child: ElevatedButton(
- style: ElevatedButton.styleFrom(
- backgroundColor: Colors.black,
- foregroundColor: Colors.white,
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
- padding: const EdgeInsets.symmetric(vertical: 14),
- ),
- onPressed: () => context.push('/broker/my-invitations'),
- child: Text(AppLocalizations.of(context)!.myInvitations, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
- ),
- ),
- const SizedBox(width: 12),
- Expanded(
- child: ElevatedButton(
- style: ElevatedButton.styleFrom(
- backgroundColor: Colors.black,
- foregroundColor: Colors.white,
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
- padding: const EdgeInsets.symmetric(vertical: 14),
- ),
- onPressed: () => context.push('/broker/team-detail'),
- child: Text(AppLocalizations.of(context)!.teamDetail, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
- ),
- ),
- ]),
- const SizedBox(height: 24),
- // ── 今日返佣列表(对应安卓 awardsLiveData)────
- Text(AppLocalizations.of(context)!.inviteList, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: cs.onSurface)),
- const SizedBox(height: 12),
- _RewardsTable(rewardsAsync: rewardsAsync),
- ],
- ),
- );
- }
- String _maskEmail(String email) {
- if (email.isEmpty) return '--';
- final atIdx = email.indexOf('@');
- if (atIdx <= 1) return email;
- return '${email[0]}**${email.substring(atIdx)}';
- }
- }
- /// 截断到4位小数,不四舍五入
- String _truncate4(String value) {
- final d = double.tryParse(value);
- if (d == null) return value;
- final s = d.toStringAsFixed(10);
- final dot = s.indexOf('.');
- if (dot == -1) return '$s.0000';
- final end = dot + 5; // dot + 4 decimals
- return end >= s.length ? s.padRight(end, '0') : s.substring(0, end);
- }
- class _StatsRow extends StatelessWidget {
- const _StatsRow({required this.todayNew, required this.todayTrading, required this.todayRebate});
- final String todayNew;
- final String todayTrading;
- final String todayRebate;
- @override
- Widget build(BuildContext context) {
- final cs = Theme.of(context).colorScheme;
- return Row(
- children: [
- Expanded(child: _StatCell(value: todayNew, label: AppLocalizations.of(context)!.todayNewUsers)),
- Container(width: 1, height: 40, color: cs.outlineVariant.withAlpha(80)),
- Expanded(child: _StatCell(value: todayTrading, label: AppLocalizations.of(context)!.todayTradingUsers)),
- Container(width: 1, height: 40, color: cs.outlineVariant.withAlpha(80)),
- Expanded(child: _StatCell(value: todayRebate, label: AppLocalizations.of(context)!.todayRebateLabel)),
- ],
- );
- }
- }
- class _StatCell extends StatelessWidget {
- const _StatCell({required this.value, required this.label});
- final String value;
- final String label;
- @override
- Widget build(BuildContext context) {
- final cs = Theme.of(context).colorScheme;
- return Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Text(value, style: TextStyle(fontSize: 22, fontWeight: FontWeight.w700, color: cs.onSurface)),
- const SizedBox(height: 4),
- Text(label, textAlign: TextAlign.center,
- style: TextStyle(fontSize: 11, color: cs.onSurface.withAlpha(120), height: 1.4)),
- ],
- );
- }
- }
- class _RewardsTable extends StatelessWidget {
- const _RewardsTable({required this.rewardsAsync});
- final AsyncValue<List<Map<String, dynamic>>> rewardsAsync;
- @override
- Widget build(BuildContext context) {
- final cs = Theme.of(context).colorScheme;
- return rewardsAsync.when(
- loading: () => const Center(child: CircularProgressIndicator(strokeWidth: 2)),
- error: (e, _) => Text(AppLocalizations.of(context)!.loadFailed, style: TextStyle(color: cs.error)),
- data: (rewards) {
- if (rewards.isEmpty) {
- return Padding(
- padding: const EdgeInsets.symmetric(vertical: 32),
- child: Center(child: Text(AppLocalizations.of(context)!.noInviteRecord, style: TextStyle(color: cs.onSurface.withAlpha(120)))),
- );
- }
- return Column(children: [
- // 表头
- Container(
- padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
- decoration: BoxDecoration(
- color: cs.surfaceContainerHighest.withAlpha(60),
- borderRadius: BorderRadius.circular(6)),
- child: Row(children: [
- Expanded(child: Text(AppLocalizations.of(context)!.accountLabel,
- style: TextStyle(fontSize: 12, color: cs.onSurface.withAlpha(120)))),
- Expanded(child: Text('ID', textAlign: TextAlign.center,
- style: TextStyle(fontSize: 12, color: cs.onSurface.withAlpha(120)))),
- Expanded(child: Text(AppLocalizations.of(context)!.todayRebateLabel,
- textAlign: TextAlign.right, maxLines: 1, overflow: TextOverflow.ellipsis,
- style: TextStyle(fontSize: 12, color: cs.onSurface.withAlpha(120)))),
- ]),
- ),
- ...rewards.map((r) => _RewardRow(reward: r)),
- ]);
- },
- );
- }
- }
- class _RewardRow extends StatelessWidget {
- const _RewardRow({required this.reward});
- final Map<String, dynamic> reward;
- String _maskName(String? name) {
- if (name == null || name.isEmpty) return '--';
- if (name.contains('@')) {
- final atIdx = name.indexOf('@');
- if (atIdx > 1) return '${name[0]}**${name.substring(atIdx)}';
- return name;
- }
- if (name.length > 4) return '${name.substring(0, 3)}****${name.substring(name.length - 2)}';
- return name;
- }
- @override
- Widget build(BuildContext context) {
- final cs = Theme.of(context).colorScheme;
- final name = _maskName(reward['username'] as String?);
- final memberId = reward['memberId']?.toString() ?? '--';
- final amount = reward['num']?.toString() ?? '0.0000';
- final amountFormatted = double.tryParse(amount)?.toStringAsFixed(4) ?? amount;
- return Container(
- padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
- decoration: BoxDecoration(
- border: Border(bottom: BorderSide(color: cs.outlineVariant.withAlpha(40)))),
- child: Row(children: [
- Expanded(child: Text(name, maxLines: 1, overflow: TextOverflow.ellipsis,
- style: TextStyle(fontSize: 13, color: cs.onSurface))),
- Expanded(child: Text(memberId, textAlign: TextAlign.center,
- maxLines: 1, overflow: TextOverflow.ellipsis,
- style: TextStyle(fontSize: 13, color: cs.onSurface))),
- Expanded(child: Text(amountFormatted, textAlign: TextAlign.right,
- maxLines: 1, overflow: TextOverflow.ellipsis,
- style: TextStyle(fontSize: 13, color: cs.onSurface))),
- ]),
- );
- }
- }
|