broker_apply_screen.dart 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_riverpod/flutter_riverpod.dart';
  3. import '../../../core/l10n/app_localizations.dart';
  4. import '../../../core/navigation/broker_navigation.dart';
  5. import '../../../core/theme/app_colors.dart';
  6. import '../../../data/repositories/broker_repository.dart';
  7. import '../../widgets/common/app_refresh_indicator.dart';
  8. final _applyListProvider =
  9. FutureProvider.autoDispose<List<Map<String, dynamic>>>((ref) async {
  10. final repo = ref.read(brokerRepositoryProvider);
  11. return repo.getMyApplyList();
  12. });
  13. class BrokerApplyScreen extends ConsumerWidget {
  14. const BrokerApplyScreen({super.key});
  15. @override
  16. Widget build(BuildContext context, WidgetRef ref) {
  17. final l10n = AppLocalizations.of(context)!;
  18. final cs = Theme.of(context).colorScheme;
  19. final isDark = Theme.of(context).brightness == Brightness.dark;
  20. final listAsync = ref.watch(_applyListProvider);
  21. Future<void> refreshList() async {
  22. ref.invalidate(_applyListProvider);
  23. await ref.read(_applyListProvider.future);
  24. }
  25. return Scaffold(
  26. appBar: AppBar(
  27. title: Text(
  28. l10n.brokerApplyRecords,
  29. style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
  30. ),
  31. centerTitle: true,
  32. ),
  33. body: listAsync.when(
  34. loading: () => const Center(child: CircularProgressIndicator()),
  35. error: (_, __) => AppRefreshIndicator(
  36. onRefresh: refreshList,
  37. child: SingleChildScrollView(
  38. physics: const AlwaysScrollableScrollPhysics(),
  39. child: SizedBox(
  40. height: MediaQuery.sizeOf(context).height * 0.6,
  41. child: Center(
  42. child: Column(
  43. mainAxisSize: MainAxisSize.min,
  44. children: [
  45. Text(l10n.loadFailed,
  46. style: TextStyle(color: cs.onSurface.withAlpha(153))),
  47. const SizedBox(height: 12),
  48. ElevatedButton(
  49. onPressed: refreshList,
  50. child: Text(l10n.retry),
  51. ),
  52. ],
  53. ),
  54. ),
  55. ),
  56. ),
  57. ),
  58. data: (list) {
  59. if (list.isEmpty) {
  60. return AppRefreshIndicator(
  61. onRefresh: refreshList,
  62. child: SingleChildScrollView(
  63. physics: const AlwaysScrollableScrollPhysics(),
  64. child: SizedBox(
  65. height: MediaQuery.sizeOf(context).height * 0.6,
  66. child: _EmptyState(
  67. l10n: l10n,
  68. cs: cs,
  69. isDark: isDark,
  70. onApply: () => showBrokerApplyDialog(context, ref).then((_) {
  71. ref.invalidate(_applyListProvider);
  72. }),
  73. ),
  74. ),
  75. ),
  76. );
  77. }
  78. return AppRefreshIndicator(
  79. onRefresh: refreshList,
  80. child: ListView.separated(
  81. physics: const AlwaysScrollableScrollPhysics(),
  82. padding: const EdgeInsets.all(16),
  83. itemCount: list.length,
  84. separatorBuilder: (_, __) => const SizedBox(height: 12),
  85. itemBuilder: (_, i) => _ApplyCard(
  86. item: list[i],
  87. l10n: l10n,
  88. cs: cs,
  89. isDark: isDark,
  90. ),
  91. ),
  92. );
  93. },
  94. ),
  95. );
  96. }
  97. }
  98. class _EmptyState extends StatelessWidget {
  99. const _EmptyState({
  100. required this.l10n,
  101. required this.cs,
  102. required this.isDark,
  103. required this.onApply,
  104. });
  105. final AppLocalizations l10n;
  106. final ColorScheme cs;
  107. final bool isDark;
  108. final VoidCallback onApply;
  109. @override
  110. Widget build(BuildContext context) {
  111. return Center(
  112. child: Padding(
  113. padding: const EdgeInsets.all(32),
  114. child: Column(
  115. mainAxisSize: MainAxisSize.min,
  116. children: [
  117. Icon(Icons.inbox_outlined,
  118. size: 64, color: cs.onSurface.withAlpha(60)),
  119. const SizedBox(height: 16),
  120. Text(l10n.noRecord,
  121. style: TextStyle(
  122. color: cs.onSurface.withAlpha(153), fontSize: 15)),
  123. const SizedBox(height: 24),
  124. SizedBox(
  125. width: double.infinity,
  126. child: ElevatedButton(
  127. onPressed: onApply,
  128. style: ElevatedButton.styleFrom(
  129. backgroundColor: AppColors.brand,
  130. foregroundColor: Colors.black,
  131. shape: RoundedRectangleBorder(
  132. borderRadius: BorderRadius.circular(10),
  133. ),
  134. padding: const EdgeInsets.symmetric(vertical: 14),
  135. ),
  136. child: Text(
  137. l10n.applyNow,
  138. style: const TextStyle(
  139. fontSize: 15,
  140. fontWeight: FontWeight.w600,
  141. ),
  142. ),
  143. ),
  144. ),
  145. ],
  146. ),
  147. ),
  148. );
  149. }
  150. }
  151. class _ApplyCard extends StatelessWidget {
  152. const _ApplyCard(
  153. {required this.item,
  154. required this.l10n,
  155. required this.cs,
  156. required this.isDark});
  157. final Map<String, dynamic> item;
  158. final AppLocalizations l10n;
  159. final ColorScheme cs;
  160. final bool isDark;
  161. @override
  162. Widget build(BuildContext context) {
  163. final status = item['auditStatus'];
  164. // 0=待审核, 1=审核失败, 2=审核通过
  165. final isPending = isBrokerApplyPending(status);
  166. final isApproved = status == 2 || status == 'AUDIT_SUCCESS';
  167. final isRejected = status == 1 || status == 'AUDIT_DEFEATED';
  168. final statusText = isApproved
  169. ? l10n.auditStatusApproved
  170. : isPending
  171. ? l10n.auditStatusPending
  172. : l10n.auditStatusRejected;
  173. final statusColor = isApproved
  174. ? AppColors.rise
  175. : isPending
  176. ? AppColors.brand
  177. : cs.error;
  178. final statusBg = isApproved
  179. ? AppColors.rise.withAlpha(25)
  180. : isPending
  181. ? AppColors.brand.withAlpha(25)
  182. : cs.error.withAlpha(25);
  183. final createTime = item['createTime']?.toString() ?? '';
  184. final rejectReason = item['rejectReason']?.toString() ?? '';
  185. return Container(
  186. padding: const EdgeInsets.all(16),
  187. decoration: BoxDecoration(
  188. color: isDark ? AppColors.darkBgSecondary : AppColors.lightBgSecondary,
  189. borderRadius: BorderRadius.circular(12),
  190. ),
  191. child: Column(
  192. crossAxisAlignment: CrossAxisAlignment.start,
  193. children: [
  194. Row(
  195. children: [
  196. Expanded(
  197. child: Text(
  198. l10n.brokerApplyTitle,
  199. style: TextStyle(
  200. color: cs.onSurface,
  201. fontSize: 15,
  202. fontWeight: FontWeight.w600),
  203. ),
  204. ),
  205. Container(
  206. padding:
  207. const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
  208. decoration: BoxDecoration(
  209. color: statusBg,
  210. borderRadius: BorderRadius.circular(12),
  211. ),
  212. child: Text(
  213. statusText,
  214. style: TextStyle(
  215. color: statusColor,
  216. fontSize: 12,
  217. fontWeight: FontWeight.w600),
  218. ),
  219. ),
  220. ],
  221. ),
  222. if (createTime.isNotEmpty) ...[
  223. const SizedBox(height: 8),
  224. Text('${l10n.applyTime}: $createTime',
  225. style:
  226. TextStyle(color: cs.onSurface.withAlpha(120), fontSize: 13)),
  227. ],
  228. if (isRejected && rejectReason.isNotEmpty) ...[
  229. const SizedBox(height: 8),
  230. Text('${l10n.rejectReasonLabel}: $rejectReason',
  231. style: TextStyle(color: cs.error, fontSize: 13)),
  232. ],
  233. ],
  234. ),
  235. );
  236. }
  237. }