team_detail_screen.dart 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_riverpod/flutter_riverpod.dart';
  3. import 'package:go_router/go_router.dart';
  4. import 'package:intl/intl.dart';
  5. import '../../../core/l10n/app_localizations.dart';
  6. import '../../../core/theme/app_colors.dart';
  7. import '../../../data/repositories/broker_repository.dart';
  8. // ── State ─────────────────────────────────────────────────
  9. class _TeamDetailState {
  10. final DateTime month;
  11. final String filter; // 'all' | 'month' | 'week' | 'day'
  12. _TeamDetailState({required this.month, required this.filter});
  13. _TeamDetailState copyWith({DateTime? month, String? filter}) =>
  14. _TeamDetailState(month: month ?? this.month, filter: filter ?? this.filter);
  15. }
  16. // ── Screen ───────────────────────────────────────────────
  17. class TeamDetailScreen extends ConsumerStatefulWidget {
  18. const TeamDetailScreen({super.key});
  19. @override
  20. ConsumerState<TeamDetailScreen> createState() => _TeamDetailScreenState();
  21. }
  22. class _TeamDetailScreenState extends ConsumerState<TeamDetailScreen> {
  23. _TeamDetailState _state = _TeamDetailState(month: DateTime.now(), filter: 'all');
  24. Map<String, dynamic> _data = {};
  25. bool _loading = false;
  26. String? _error;
  27. @override
  28. void initState() {
  29. super.initState();
  30. _load();
  31. }
  32. /// 对应安卓 requestAgentAwards(endDate) → GET agent/stat?endDate=
  33. Future<void> _load() async {
  34. setState(() { _loading = true; _error = null; });
  35. try {
  36. final endDate = _buildEndDate();
  37. final data = await ref.read(brokerRepositoryProvider).getTotalAward(endDate: endDate);
  38. if (mounted) setState(() { _data = data; _loading = false; });
  39. } catch (e) {
  40. if (mounted) setState(() { _error = e.toString(); _loading = false; });
  41. }
  42. }
  43. /// 对应安卓各个 load 方法传递的 endDate
  44. String? _buildEndDate() {
  45. final fmt = DateFormat('yyyy-MM-dd');
  46. final now = DateTime.now();
  47. switch (_state.filter) {
  48. case 'day': // loadToday
  49. return fmt.format(now);
  50. case 'week': // loadWeekBefore
  51. return fmt.format(now.subtract(const Duration(days: 7)));
  52. case 'month': // loadCurrentMonth → "${month}-01"
  53. return '${_state.month.year}-${_state.month.month.toString().padLeft(2, '0')}-01';
  54. default: // loadAllDate → null
  55. return null;
  56. }
  57. }
  58. Future<void> _pickMonth() async {
  59. final now = DateTime.now();
  60. final picked = await showDatePicker(
  61. context: context,
  62. initialDate: _state.month,
  63. firstDate: DateTime(now.year - 2),
  64. lastDate: now,
  65. initialDatePickerMode: DatePickerMode.year,
  66. );
  67. if (picked != null && mounted) {
  68. setState(() => _state = _state.copyWith(
  69. month: DateTime(picked.year, picked.month),
  70. filter: 'month',
  71. ));
  72. _load();
  73. }
  74. }
  75. void _setFilter(String f) {
  76. setState(() => _state = _state.copyWith(filter: f));
  77. _load();
  78. }
  79. @override
  80. Widget build(BuildContext context) {
  81. final cs = Theme.of(context).colorScheme;
  82. return Scaffold(
  83. backgroundColor: cs.surface,
  84. appBar: AppBar(
  85. backgroundColor: cs.surface,
  86. elevation: 0,
  87. leading: IconButton(
  88. icon: const Icon(Icons.arrow_back_ios, size: 18),
  89. onPressed: () => context.pop(),
  90. ),
  91. title: Text(AppLocalizations.of(context)!.teamDetail, style: TextStyle(color: cs.onSurface, fontSize: 17, fontWeight: FontWeight.w600)),
  92. centerTitle: true,
  93. ),
  94. body: Padding(
  95. padding: const EdgeInsets.all(16),
  96. child: Column(
  97. crossAxisAlignment: CrossAxisAlignment.start,
  98. children: [
  99. // ── 筛选栏:三个独立带边框按钮 ───────────────
  100. Row(children: [
  101. // 月份选择
  102. Expanded(
  103. flex: 5,
  104. child: GestureDetector(
  105. onTap: _pickMonth,
  106. child: Container(
  107. height: 38,
  108. decoration: BoxDecoration(
  109. color: _state.filter == 'month' ? AppColors.brand : Colors.transparent,
  110. border: Border.all(color: AppColors.lightBorder, width: 1),
  111. borderRadius: BorderRadius.circular(6),
  112. ),
  113. child: Row(
  114. mainAxisAlignment: MainAxisAlignment.center,
  115. children: [
  116. Text(
  117. '${_state.month.year}-${_state.month.month.toString().padLeft(2, '0')}',
  118. style: TextStyle(
  119. fontSize: 13,
  120. fontWeight: _state.filter == 'month' ? FontWeight.w600 : FontWeight.normal,
  121. color: _state.filter == 'month' ? Colors.black : cs.onSurface,
  122. ),
  123. ),
  124. const SizedBox(width: 2),
  125. Icon(Icons.arrow_drop_down, size: 18,
  126. color: _state.filter == 'month' ? Colors.black : cs.onSurface.withAlpha(160)),
  127. ],
  128. ),
  129. ),
  130. ),
  131. ),
  132. const SizedBox(width: 8),
  133. // 一周内
  134. Expanded(
  135. flex: 3,
  136. child: GestureDetector(
  137. onTap: () => _setFilter('week'),
  138. child: Container(
  139. height: 38,
  140. alignment: Alignment.center,
  141. decoration: BoxDecoration(
  142. color: _state.filter == 'week' ? AppColors.brand : Colors.transparent,
  143. border: Border.all(color: AppColors.lightBorder, width: 1),
  144. borderRadius: BorderRadius.circular(6),
  145. ),
  146. child: Text(
  147. AppLocalizations.of(context)!.withinOneWeek,
  148. style: TextStyle(
  149. fontSize: 13,
  150. fontWeight: _state.filter == 'week' ? FontWeight.w600 : FontWeight.normal,
  151. color: _state.filter == 'week' ? Colors.black : cs.onSurface,
  152. ),
  153. ),
  154. ),
  155. ),
  156. ),
  157. const SizedBox(width: 8),
  158. // 当日
  159. Expanded(
  160. flex: 3,
  161. child: GestureDetector(
  162. onTap: () => _setFilter('day'),
  163. child: Container(
  164. height: 38,
  165. alignment: Alignment.center,
  166. decoration: BoxDecoration(
  167. color: _state.filter == 'day' ? AppColors.brand : Colors.transparent,
  168. border: Border.all(color: AppColors.lightBorder, width: 1),
  169. borderRadius: BorderRadius.circular(6),
  170. ),
  171. child: Text(
  172. AppLocalizations.of(context)!.today,
  173. style: TextStyle(
  174. fontSize: 13,
  175. fontWeight: _state.filter == 'day' ? FontWeight.w600 : FontWeight.normal,
  176. color: _state.filter == 'day' ? Colors.black : cs.onSurface,
  177. ),
  178. ),
  179. ),
  180. ),
  181. ),
  182. ]),
  183. const SizedBox(height: 24),
  184. // ── 内容区 ────────────────────────────────
  185. if (_loading)
  186. const Center(child: CircularProgressIndicator())
  187. else if (_error != null)
  188. Center(child: Text('${AppLocalizations.of(context)!.loadFailed}: $_error', style: TextStyle(color: cs.error)))
  189. else ...[
  190. Text(AppLocalizations.of(context)!.teamTotalAssets, style: TextStyle(fontSize: 13, color: cs.onSurface.withAlpha(120))),
  191. const SizedBox(height: 8),
  192. Row(crossAxisAlignment: CrossAxisAlignment.baseline, textBaseline: TextBaseline.alphabetic, children: [
  193. Text(
  194. _formatAmount(_data['totalBalance']),
  195. style: TextStyle(fontSize: 28, fontWeight: FontWeight.w700, color: cs.onSurface),
  196. ),
  197. const SizedBox(width: 6),
  198. Text('USDT', style: TextStyle(fontSize: 14, color: cs.onSurface.withAlpha(160))),
  199. ]),
  200. ],
  201. ],
  202. ),
  203. ),
  204. );
  205. }
  206. String _formatAmount(dynamic v) {
  207. if (v == null) return '0.00';
  208. final d = double.tryParse(v.toString()) ?? 0.0;
  209. if (d >= 1000) {
  210. final intPart = d.truncate().toString();
  211. final decPart = d.toStringAsFixed(2).split('.')[1];
  212. final buf = StringBuffer();
  213. for (int i = 0; i < intPart.length; i++) {
  214. if (i > 0 && (intPart.length - i) % 3 == 0) buf.write(',');
  215. buf.write(intPart[i]);
  216. }
  217. return '${buf.toString()}.$decPart';
  218. }
  219. return d.toStringAsFixed(2);
  220. }
  221. }