broker_navigation.dart 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_riverpod/flutter_riverpod.dart';
  3. import 'package:go_router/go_router.dart';
  4. import '../l10n/app_localizations.dart';
  5. import '../theme/app_colors.dart';
  6. import '../utils/dialog_utils.dart';
  7. import '../utils/top_toast.dart';
  8. import '../../data/repositories/broker_repository.dart';
  9. import '../../providers/profile_provider.dart';
  10. /// 等待用户资料加载完成
  11. Future<void> waitForProfileLoad(WidgetRef ref) async {
  12. if (!ref.read(profileProvider).isLoadingProfile) {
  13. return;
  14. }
  15. for (int i = 0; i < 30; i++) {
  16. await Future.delayed(const Duration(milliseconds: 100));
  17. if (!ref.read(profileProvider).isLoadingProfile) {
  18. break;
  19. }
  20. }
  21. }
  22. /// 判断申请是否处于待审核
  23. bool isBrokerApplyPending(dynamic status) {
  24. return status == 0 ||
  25. status == '0' ||
  26. status == 'AUDIT_ING';
  27. }
  28. /// 经纪商入口:已是经纪商进主页,有待审申请进记录页,否则弹申请窗
  29. Future<void> openBrokerEntry(BuildContext context, WidgetRef ref) async {
  30. await waitForProfileLoad(ref);
  31. if (!context.mounted) {
  32. return;
  33. }
  34. if (ref.read(profileProvider).user.isBroker) {
  35. context.push('/broker');
  36. return;
  37. }
  38. try {
  39. final list = await ref.read(brokerRepositoryProvider).getMyApplyList();
  40. if (!context.mounted) {
  41. return;
  42. }
  43. final hasPending = list.any(
  44. (item) => isBrokerApplyPending(item['auditStatus']),
  45. );
  46. if (hasPending) {
  47. context.push('/broker/apply');
  48. return;
  49. }
  50. } catch (_) {
  51. // 列表拉取失败时仍允许用户发起申请
  52. }
  53. if (!context.mounted) {
  54. return;
  55. }
  56. await showBrokerApplyDialog(context, ref);
  57. }
  58. /// 展示经纪商申请弹窗(对齐 Web 非经纪商提示)
  59. Future<void> showBrokerApplyDialog(
  60. BuildContext context,
  61. WidgetRef ref,
  62. ) async {
  63. final l10n = AppLocalizations.of(context)!;
  64. final cs = Theme.of(context).colorScheme;
  65. // 保留页面 context,dialog 关闭后其内部 context 不在 GoRouter 子树中
  66. final hostContext = context;
  67. await showDialog<void>(
  68. context: context,
  69. barrierDismissible: false,
  70. builder: (dialogContext) {
  71. var submitting = false;
  72. return StatefulBuilder(
  73. builder: (dialogContext, setState) {
  74. return AlertDialog(
  75. backgroundColor: cs.surface,
  76. shape: RoundedRectangleBorder(
  77. borderRadius: BorderRadius.circular(16),
  78. ),
  79. content: Column(
  80. mainAxisSize: MainAxisSize.min,
  81. children: [
  82. const Text('🏦', style: TextStyle(fontSize: 36)),
  83. const SizedBox(height: 12),
  84. Text(
  85. l10n.brokerApplyTitle,
  86. textAlign: TextAlign.center,
  87. style: TextStyle(
  88. color: cs.onSurface,
  89. fontSize: 18,
  90. fontWeight: FontWeight.w600,
  91. ),
  92. ),
  93. const SizedBox(height: 12),
  94. Text(
  95. l10n.brokerApplyDesc,
  96. textAlign: TextAlign.center,
  97. style: TextStyle(
  98. color: cs.onSurface.withAlpha(180),
  99. fontSize: 14,
  100. height: 1.5,
  101. ),
  102. ),
  103. ],
  104. ),
  105. actions: [
  106. Row(
  107. children: [
  108. Expanded(
  109. child: OutlinedButton(
  110. onPressed: submitting
  111. ? null
  112. : () {
  113. Navigator.of(dialogContext).pop();
  114. },
  115. style: OutlinedButton.styleFrom(
  116. side: BorderSide(color: cs.outline.withAlpha(80)),
  117. shape: RoundedRectangleBorder(
  118. borderRadius: BorderRadius.circular(10),
  119. ),
  120. padding: const EdgeInsets.symmetric(vertical: 14),
  121. ),
  122. child: Text(
  123. l10n.cancel,
  124. style: TextStyle(
  125. color: cs.onSurface.withAlpha(180),
  126. fontSize: 15,
  127. ),
  128. ),
  129. ),
  130. ),
  131. const SizedBox(width: 12),
  132. Expanded(
  133. child: ElevatedButton(
  134. onPressed: submitting
  135. ? null
  136. : () async {
  137. setState(() {
  138. submitting = true;
  139. });
  140. Navigator.of(dialogContext).pop();
  141. await submitBrokerApply(hostContext, ref);
  142. },
  143. style: ElevatedButton.styleFrom(
  144. backgroundColor: AppColors.brand,
  145. foregroundColor: Colors.black,
  146. disabledBackgroundColor:
  147. AppColors.brand.withAlpha(100),
  148. disabledForegroundColor: Colors.black.withAlpha(100),
  149. shape: RoundedRectangleBorder(
  150. borderRadius: BorderRadius.circular(10),
  151. ),
  152. padding: const EdgeInsets.symmetric(vertical: 14),
  153. ),
  154. child: submitting
  155. ? SizedBox(
  156. width: 18,
  157. height: 18,
  158. child: CircularProgressIndicator(
  159. strokeWidth: 2,
  160. color: Colors.black.withAlpha(180),
  161. ),
  162. )
  163. : Text(
  164. l10n.applyNow,
  165. style: const TextStyle(
  166. fontSize: 15,
  167. fontWeight: FontWeight.w600,
  168. ),
  169. ),
  170. ),
  171. ),
  172. ],
  173. ),
  174. ],
  175. );
  176. },
  177. );
  178. },
  179. );
  180. }
  181. /// 提交经纪商申请并展示后端返回的 message
  182. Future<void> submitBrokerApply(BuildContext context, WidgetRef ref) async {
  183. final l10n = AppLocalizations.of(context)!;
  184. try {
  185. final err = await ref.read(brokerRepositoryProvider).submitApply();
  186. if (!context.mounted) {
  187. return;
  188. }
  189. if (err != null && err.isNotEmpty) {
  190. showTopToast(context, message: err);
  191. if (isAlreadyBrokerError(err)) {
  192. context.push('/broker');
  193. } else if (isApplyPendingError(err)) {
  194. context.push('/broker/apply');
  195. }
  196. return;
  197. }
  198. if (err != null && err.isEmpty) {
  199. showTopToast(context, message: l10n.operationFailed);
  200. return;
  201. }
  202. showTopToast(
  203. context,
  204. message: l10n.applySubmitted,
  205. backgroundColor: AppColors.rise,
  206. );
  207. context.push('/broker/apply');
  208. } catch (e) {
  209. if (!context.mounted) {
  210. return;
  211. }
  212. final message = extractErrorMessage(e);
  213. showTopToast(
  214. context,
  215. message: message.isNotEmpty ? message : l10n.operationFailed,
  216. );
  217. if (isApplyPendingError(message)) {
  218. context.push('/broker/apply');
  219. }
  220. }
  221. }