flu-cli-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +60 -0
  2. package/dist/chunk-FOMWV2YP.js +378 -0
  3. package/dist/chunk-SW6YDKXI.js +112 -0
  4. package/dist/factory-6DDXZYQP.js +6 -0
  5. package/dist/index.cjs +4668 -0
  6. package/dist/index.d.cts +644 -0
  7. package/dist/index.d.ts +644 -0
  8. package/dist/index.js +4037 -0
  9. package/dist/upgrade_snippets-XFR7Q444.js +8 -0
  10. package/locales/en-US.json +59 -0
  11. package/locales/zh-CN.json +59 -0
  12. package/package.json +52 -0
  13. package/templates/README.md +129 -0
  14. package/templates/core_files/base/base_list_page.dart.template +225 -0
  15. package/templates/core_files/base/base_list_viewmodel.dart.template +164 -0
  16. package/templates/core_files/base/base_page.dart.template +252 -0
  17. package/templates/core_files/base/base_viewmodel.dart.template +68 -0
  18. package/templates/core_files/base/index.dart.template +5 -0
  19. package/templates/core_files/config/app_config.dart.template +142 -0
  20. package/templates/core_files/config/app_initializer.dart.template +74 -0
  21. package/templates/core_files/config/index.dart.template +3 -0
  22. package/templates/core_files/index.dart.template +8 -0
  23. package/templates/core_files/network/README.md +378 -0
  24. package/templates/core_files/network/app_error_code.dart.template +49 -0
  25. package/templates/core_files/network/app_http.dart.template +306 -0
  26. package/templates/core_files/network/app_response.dart.template +81 -0
  27. package/templates/core_files/network/index.dart.template +12 -0
  28. package/templates/core_files/network/interceptors/app_response_interceptor.dart.template +44 -0
  29. package/templates/core_files/network/interceptors/auth_interceptor.dart.template +30 -0
  30. package/templates/core_files/network/interceptors/error_interceptor.dart.template +48 -0
  31. package/templates/core_files/network/interceptors/index.dart.template +6 -0
  32. package/templates/core_files/network/interceptors/log_interceptor.dart.template +97 -0
  33. package/templates/core_files/network/interceptors/network_error_interceptor.dart.template +58 -0
  34. package/templates/core_files/network/interceptors/retry_interceptor.dart.template +69 -0
  35. package/templates/core_files/network/response_adapter.dart.template +69 -0
  36. package/templates/core_files/router/app_routes.dart.template +32 -0
  37. package/templates/core_files/router/index.dart.template +3 -0
  38. package/templates/core_files/router/navigator_util_getx.dart.template +131 -0
  39. package/templates/core_files/router/navigator_util_material.dart.template +191 -0
  40. package/templates/core_files/storage/index.dart.template +3 -0
  41. package/templates/core_files/storage/storage_keys.dart.template +34 -0
  42. package/templates/core_files/storage/storage_util.dart.template +102 -0
  43. package/templates/core_files/theme/app_theme.dart.template +37 -0
  44. package/templates/core_files/theme/index.dart.template +3 -0
  45. package/templates/core_files/theme/status_views_theme.dart.template +40 -0
  46. package/templates/core_files/utils/index.dart.template +2 -0
  47. package/templates/core_files/utils/loading_util.dart.template +55 -0
  48. package/templates/core_files/utils/toast_util.dart.template +128 -0
  49. package/templates/examples/eg_list_page.dart.template +340 -0
  50. package/templates/examples/eg_list_viewmodel.dart.template +31 -0
  51. package/templates/examples/eg_service.dart.template +78 -0
  52. package/templates/examples/mock_data.dart.template +50388 -0
  53. package/templates/examples/tu_chong_model.dart.template +633 -0
  54. package/templates/request_helper.dart.template +59 -0
  55. package/templates/snippets/flu-cli.code-snippets +268 -0
  56. package/templates/snippets/flu-cli.code-snippets.backup +268 -0
@@ -0,0 +1,306 @@
1
+ import 'package:dio/dio.dart';
2
+ import 'package:flutter/foundation.dart';
3
+
4
+ import '../config/app_config.dart';
5
+ import 'app_error_code.dart';
6
+ import 'app_response.dart';
7
+ import 'interceptors/index.dart';
8
+ import 'response_adapter.dart';
9
+
10
+ /// App 网络请求核心类
11
+ ///
12
+ /// 1. 统一返回 [AppResponse],不抛出异常
13
+ /// 2. 支持泛型自动解析 [fromJson]
14
+ /// 3. 支持配置适配器 [AppResponseAdapter]
15
+ /// 4. 支持请求级错误控制 [showError]
16
+ class AppHttp {
17
+ static final AppHttp _instance = AppHttp._internal();
18
+ factory AppHttp() => _instance;
19
+
20
+ late Dio _dio;
21
+ bool _initialized = false;
22
+ bool _autoShowError = true;
23
+
24
+ AppHttp._internal();
25
+
26
+ /// 获取 Dio 实例 (懒加载)
27
+ Dio get dio {
28
+ if (!_initialized) {
29
+ // 默认初始化,防止未调用 init 时报错
30
+ init();
31
+ }
32
+ return _dio;
33
+ }
34
+
35
+ /// 全局初始化
36
+ ///
37
+ /// [adapter] 响应适配器,默认使用 [DefaultResponseAdapter]
38
+ /// [interceptors] 额外的拦截器
39
+ /// [autoShowError] 是否全局开启错误弹窗,默认 true
40
+ static void init({
41
+ AppResponseAdapter? adapter,
42
+ List<Interceptor>? interceptors,
43
+ bool autoShowError = true,
44
+ }) {
45
+ final instance = AppHttp._instance;
46
+ instance._autoShowError = autoShowError;
47
+ final config = AppConfig.I;
48
+
49
+ instance._dio = Dio(
50
+ BaseOptions(
51
+ baseUrl: config.apiBaseUrl,
52
+ connectTimeout: config.connectTimeout,
53
+ receiveTimeout: config.receiveTimeout,
54
+ headers: {
55
+ 'Content-Type': 'application/json',
56
+ 'Accept': 'application/json',
57
+ },
58
+ ),
59
+ );
60
+
61
+ // 1. 添加日志拦截器(完整输出,不截断)
62
+ if (config.enableLog) {
63
+ instance._dio.interceptors.add(
64
+ FullLogInterceptor(
65
+ requestBody: true,
66
+ responseBody: true,
67
+ ),
68
+ );
69
+ }
70
+
71
+ // 2. 添加响应处理拦截器 (核心)
72
+ instance._dio.interceptors.add(
73
+ AppResponseInterceptor(
74
+ adapter: adapter ?? DefaultResponseAdapter(),
75
+ autoShowError: autoShowError,
76
+ ),
77
+ );
78
+
79
+ // 2.5 添加网络错误拦截器
80
+ instance._dio.interceptors.add(
81
+ NetworkErrorInterceptor(autoShowError: autoShowError),
82
+ );
83
+
84
+ // 3. 添加自定义拦截器
85
+ if (interceptors != null) {
86
+ instance._dio.interceptors.addAll(interceptors);
87
+ }
88
+
89
+ instance._initialized = true;
90
+ }
91
+
92
+ // ========================================================================
93
+ // 核心请求方法 - 统一返回 AppResponse,不抛异常
94
+ // ========================================================================
95
+
96
+ /// GET 请求
97
+ ///
98
+ /// [path] 请求路径
99
+ /// [fromJson] 解析函数,将 JSON 转换为对象 T
100
+ /// [showError] 本次请求是否显示错误弹窗,null 则使用全局配置
101
+ Future<AppResponse<T>> get<T>(
102
+ String path, {
103
+ Map<String, dynamic>? queryParameters,
104
+ T Function(dynamic)? fromJson,
105
+ bool? showError,
106
+ Options? options,
107
+ CancelToken? cancelToken,
108
+ }) async {
109
+ return _request<T>(
110
+ method: 'GET',
111
+ path: path,
112
+ queryParameters: queryParameters,
113
+ fromJson: fromJson,
114
+ showError: showError,
115
+ options: options,
116
+ cancelToken: cancelToken,
117
+ );
118
+ }
119
+
120
+ /// POST 请求
121
+ Future<AppResponse<T>> post<T>(
122
+ String path, {
123
+ dynamic data,
124
+ Map<String, dynamic>? queryParameters,
125
+ T Function(dynamic)? fromJson,
126
+ bool? showError,
127
+ Options? options,
128
+ CancelToken? cancelToken,
129
+ }) async {
130
+ return _request<T>(
131
+ method: 'POST',
132
+ path: path,
133
+ data: data,
134
+ queryParameters: queryParameters,
135
+ fromJson: fromJson,
136
+ showError: showError,
137
+ options: options,
138
+ cancelToken: cancelToken,
139
+ );
140
+ }
141
+
142
+ /// PUT 请求
143
+ Future<AppResponse<T>> put<T>(
144
+ String path, {
145
+ dynamic data,
146
+ Map<String, dynamic>? queryParameters,
147
+ T Function(dynamic)? fromJson,
148
+ bool? showError,
149
+ Options? options,
150
+ CancelToken? cancelToken,
151
+ }) async {
152
+ return _request<T>(
153
+ method: 'PUT',
154
+ path: path,
155
+ data: data,
156
+ queryParameters: queryParameters,
157
+ fromJson: fromJson,
158
+ showError: showError,
159
+ options: options,
160
+ cancelToken: cancelToken,
161
+ );
162
+ }
163
+
164
+ /// DELETE 请求
165
+ Future<AppResponse<T>> delete<T>(
166
+ String path, {
167
+ dynamic data,
168
+ Map<String, dynamic>? queryParameters,
169
+ T Function(dynamic)? fromJson,
170
+ bool? showError,
171
+ Options? options,
172
+ CancelToken? cancelToken,
173
+ }) async {
174
+ return _request<T>(
175
+ method: 'DELETE',
176
+ path: path,
177
+ data: data,
178
+ queryParameters: queryParameters,
179
+ fromJson: fromJson,
180
+ showError: showError,
181
+ options: options,
182
+ cancelToken: cancelToken,
183
+ );
184
+ }
185
+
186
+ // ========================================================================
187
+ // 私有统一请求处理逻辑
188
+ // ========================================================================
189
+
190
+ Future<AppResponse<T>> _request<T>({
191
+ required String method,
192
+ required String path,
193
+ dynamic data,
194
+ Map<String, dynamic>? queryParameters,
195
+ T Function(dynamic)? fromJson,
196
+ bool? showError,
197
+ Options? options,
198
+ CancelToken? cancelToken,
199
+ }) async {
200
+ try {
201
+ // 组装 Options
202
+ options ??= Options();
203
+ options.method = method;
204
+ // 将请求级错误控制参数传入 extra
205
+ options.extra ??= {};
206
+ if (showError != null) {
207
+ options.extra!['showError'] = showError;
208
+ }
209
+
210
+ final response = await dio.request(
211
+ path,
212
+ data: data,
213
+ queryParameters: queryParameters,
214
+ options: options,
215
+ cancelToken: cancelToken,
216
+ );
217
+
218
+ // 解析响应 (此时 response.data 已经被拦截器转换为标准 Map)
219
+ return _parseResponse<T>(response.data, fromJson);
220
+ } on DioException catch (e) {
221
+ return _handleDioError<T>(e, showError: showError ?? _autoShowError);
222
+ } catch (e) {
223
+ return AppResponse<T>(
224
+ code: -1,
225
+ msg: '未知错误: ${e.toString()}',
226
+ data: null,
227
+ );
228
+ }
229
+ }
230
+
231
+ /// 解析标准响应 Map 为 AppResponse
232
+ AppResponse<T> _parseResponse<T>(
233
+ dynamic data,
234
+ T Function(dynamic)? fromJson,
235
+ ) {
236
+ if (data is Map<String, dynamic>) {
237
+ final code = data['code'] as int;
238
+ final msg = data['msg'] as String;
239
+ final rawData = data['data'];
240
+
241
+ // 泛型数据解析
242
+ T? parsedData;
243
+ if (rawData != null && fromJson != null) {
244
+ try {
245
+ parsedData = fromJson(rawData);
246
+ } catch (e) {
247
+ debugPrint('❌ JSON解析失败: $e');
248
+ // 解析失败视为错误,或者你可以决定返回 data=null 但 code=200
249
+ // 这里建议视为错误,因为数据契约不符
250
+ return AppResponse<T>(
251
+ code: AppErrorCode.parseError,
252
+ msg: '数据解析失败: $e',
253
+ originalData: rawData,
254
+ );
255
+ }
256
+ } else if (rawData != null && rawData is T) {
257
+ // 如果未提供解析函数但类型匹配 (例如 T 是 String 或 Map)
258
+ parsedData = rawData;
259
+ }
260
+
261
+ return AppResponse<T>(
262
+ code: code,
263
+ msg: msg,
264
+ data: parsedData,
265
+ originalData: rawData,
266
+ );
267
+ }
268
+ return AppResponse<T>(
269
+ code: AppErrorCode.parseError,
270
+ msg: '响应格式错误',
271
+ data: null,
272
+ );
273
+ }
274
+
275
+ /// 处理 Dio 错误
276
+ AppResponse<T> _handleDioError<T>(DioException e, {bool showError = true}) {
277
+ String msg = '';
278
+ switch (e.type) {
279
+ case DioExceptionType.connectionTimeout:
280
+ msg = '连接超时';
281
+ break;
282
+ case DioExceptionType.sendTimeout:
283
+ msg = '请求超时';
284
+ break;
285
+ case DioExceptionType.receiveTimeout:
286
+ msg = '响应超时';
287
+ break;
288
+ case DioExceptionType.badResponse:
289
+ msg = '服务器错误 (${e.response?.statusCode})';
290
+ break;
291
+ case DioExceptionType.cancel:
292
+ msg = '请求取消';
293
+ break;
294
+ default:
295
+ msg = e.message ?? '网络连接异常';
296
+ }
297
+
298
+ // 注意:网络错误的弹窗已由 NetworkErrorInterceptor 统一处理
299
+ // 这里只需返回标准的错误响应即可
300
+ return AppResponse<T>(
301
+ code: AppErrorCode.networkError,
302
+ msg: msg,
303
+ data: null,
304
+ );
305
+ }
306
+ }
@@ -0,0 +1,81 @@
1
+ import 'app_error_code.dart';
2
+
3
+ /// 统一响应模型
4
+ ///
5
+ /// 无论后端返回什么格式,经过 [AppResponseAdapter] 处理后,
6
+ /// 最终在 Service 层拿到的永远是这个对象。
7
+ class AppResponse<T> {
8
+ /// 统一状态码
9
+ /// 0 或 200 表示成功,其他表示失败
10
+ final int code;
11
+
12
+ /// 统一消息
13
+ /// 成功消息或错误提示
14
+ final String msg;
15
+
16
+ /// 业务数据
17
+ final T? data;
18
+
19
+ /// 原始响应数据 (保留备查)
20
+ final dynamic originalData;
21
+
22
+ AppResponse({
23
+ required this.code,
24
+ required this.msg,
25
+ this.data,
26
+ this.originalData,
27
+ });
28
+
29
+ /// 创建成功响应
30
+ factory AppResponse.success({
31
+ T? data,
32
+ String msg = '成功',
33
+ int code = AppErrorCode.success,
34
+ }) {
35
+ return AppResponse(
36
+ code: code,
37
+ msg: msg,
38
+ data: data,
39
+ );
40
+ }
41
+
42
+ /// 创建失败响应
43
+ factory AppResponse.fail({
44
+ required String msg,
45
+ int code = AppErrorCode.networkError,
46
+ T? data,
47
+ }) {
48
+ return AppResponse(
49
+ code: code,
50
+ msg: msg,
51
+ data: data,
52
+ );
53
+ }
54
+
55
+ // ========================================================================
56
+ // 状态判断方法
57
+ // ========================================================================
58
+
59
+ /// 请求是否成功
60
+ bool get isSuccess => code == AppErrorCode.success || code == AppErrorCode.successAlt;
61
+
62
+ /// 请求是否失败
63
+ bool get isFailed => !isSuccess;
64
+
65
+ /// 是否未授权 (需要登录)
66
+ bool get isUnauthorized => code == AppErrorCode.unauthorized;
67
+
68
+ /// 是否禁止访问 (权限不足)
69
+ bool get isForbidden => code == AppErrorCode.forbidden;
70
+
71
+ /// 是否网络错误
72
+ bool get isNetworkError => code == AppErrorCode.networkError;
73
+
74
+ /// 是否解析错误
75
+ bool get isParseError => code == AppErrorCode.parseError;
76
+
77
+ @override
78
+ String toString() {
79
+ return 'AppResponse(code: $code, msg: $msg, data: $data)';
80
+ }
81
+ }
@@ -0,0 +1,12 @@
1
+ // 导出网络层类 (按字母顺序排列)
2
+
3
+ // 错误码常量
4
+ export 'app_error_code.dart';
5
+ // 网络工具
6
+ export 'app_http.dart';
7
+ // 网络响应
8
+ export 'app_response.dart';
9
+ // 网络拦截器
10
+ export 'interceptors/index.dart';
11
+ // 网络响应适配器
12
+ export 'response_adapter.dart';
@@ -0,0 +1,44 @@
1
+ import 'package:dio/dio.dart';
2
+ import 'package:flutter/material.dart';
3
+
4
+ import '../../utils/toast_util.dart';
5
+ import '../response_adapter.dart';
6
+
7
+ /// 响应处理拦截器
8
+ ///
9
+ /// 1. 使用适配器转换响应数据
10
+ /// 2. 自动处理错误提示 (可配置)
11
+ class AppResponseInterceptor extends Interceptor {
12
+ final AppResponseAdapter adapter;
13
+ final bool autoShowError;
14
+
15
+ AppResponseInterceptor({
16
+ required this.adapter,
17
+ this.autoShowError = true,
18
+ });
19
+
20
+ @override
21
+ void onResponse(Response response, ResponseInterceptorHandler handler) {
22
+ // 1. 转换数据格式
23
+ final adaptedMap = adapter.adapt(response.data);
24
+
25
+ // 2. 替换原始数据为标准格式
26
+ response.data = adaptedMap;
27
+
28
+ // 3. 自动错误提示处理
29
+ // 优先使用请求配置的 showError,如果未配置则使用全局配置
30
+ final showError =
31
+ response.requestOptions.extra['showError'] as bool? ?? autoShowError;
32
+
33
+ final code = adaptedMap['code'];
34
+ final msg = adaptedMap['msg'];
35
+
36
+ // 如果失败且需要显示错误
37
+ if (code != 200 && code != 0 && showError == true) {
38
+ ToastUtil.error(msg);
39
+ debugPrint('❌ API错误: $msg');
40
+ }
41
+
42
+ handler.next(response);
43
+ }
44
+ }
@@ -0,0 +1,30 @@
1
+ import 'package:dio/dio.dart';
2
+
3
+ /// Token 自动注入拦截器
4
+ ///
5
+ /// 在每个请求的 Header 中自动添加 Authorization token
6
+ ///
7
+ /// 使用示例:
8
+ /// ```dart
9
+ /// HttpUtil.init(
10
+ /// interceptors: [
11
+ /// AuthInterceptor(
12
+ /// getToken: () => StorageUtil.getString('token') ?? '',
13
+ /// ),
14
+ /// ],
15
+ /// );
16
+ /// ```
17
+ class AuthInterceptor extends Interceptor {
18
+ final String Function() getToken;
19
+
20
+ AuthInterceptor({required this.getToken});
21
+
22
+ @override
23
+ void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
24
+ final token = getToken();
25
+ if (token.isNotEmpty) {
26
+ options.headers['Authorization'] = 'Bearer $token';
27
+ }
28
+ handler.next(options);
29
+ }
30
+ }
@@ -0,0 +1,48 @@
1
+ import 'package:dio/dio.dart';
2
+ import 'package:flutter/foundation.dart';
3
+
4
+ /// 统一错误处理拦截器
5
+ ///
6
+ /// 处理 401/403 等常见错误状态码
7
+ ///
8
+ /// 使用示例:
9
+ /// ```dart
10
+ /// HttpUtil.init(
11
+ /// interceptors: [
12
+ /// ErrorInterceptor(
13
+ /// onUnauthorized: (_) {
14
+ /// // 跳转登录页
15
+ /// Get.offAllNamed('/login');
16
+ /// },
17
+ /// onForbidden: (_) {
18
+ /// // 显示无权限提示
19
+ /// showToast('无访问权限');
20
+ /// },
21
+ /// ),
22
+ /// ],
23
+ /// );
24
+ /// ```
25
+ class ErrorInterceptor extends Interceptor {
26
+ final void Function(int statusCode)? onUnauthorized;
27
+ final void Function(int statusCode)? onForbidden;
28
+
29
+ ErrorInterceptor({
30
+ this.onUnauthorized,
31
+ this.onForbidden,
32
+ });
33
+
34
+ @override
35
+ void onError(DioException err, ErrorInterceptorHandler handler) {
36
+ final statusCode = err.response?.statusCode;
37
+
38
+ if (statusCode == 401) {
39
+ debugPrint('❌ 未授权访问,请重新登录');
40
+ onUnauthorized?.call(401);
41
+ } else if (statusCode == 403) {
42
+ debugPrint('❌ 无访问权限');
43
+ onForbidden?.call(403);
44
+ }
45
+
46
+ handler.next(err);
47
+ }
48
+ }
@@ -0,0 +1,6 @@
1
+ export 'app_response_interceptor.dart';
2
+ export 'auth_interceptor.dart';
3
+ export 'error_interceptor.dart';
4
+ export 'log_interceptor.dart';
5
+ export 'network_error_interceptor.dart';
6
+ export 'retry_interceptor.dart';
@@ -0,0 +1,97 @@
1
+ import 'dart:convert';
2
+ import 'package:dio/dio.dart';
3
+ import 'package:flutter/foundation.dart';
4
+
5
+ /// 完整日志拦截器
6
+ ///
7
+ /// 解决 debugPrint 长度限制导致响应内容被截断的问题
8
+ class FullLogInterceptor extends Interceptor {
9
+ final bool request;
10
+ final bool requestHeader;
11
+ final bool requestBody;
12
+ final bool responseHeader;
13
+ final bool responseBody;
14
+ final bool error;
15
+
16
+ FullLogInterceptor({
17
+ this.request = true,
18
+ this.requestHeader = false,
19
+ this.requestBody = true,
20
+ this.responseHeader = false,
21
+ this.responseBody = true,
22
+ this.error = true,
23
+ });
24
+
25
+ @override
26
+ void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
27
+ if (request) {
28
+ _log('┌─────────────────────────────────────────────────');
29
+ _log('│ Request: ${options.method} ${options.uri}');
30
+ }
31
+ if (requestHeader) {
32
+ _log('│ Headers: ${options.headers}');
33
+ }
34
+ if (requestBody && options.data != null) {
35
+ _log('│ Body: ${_formatJson(options.data)}');
36
+ }
37
+ if (request) {
38
+ _log('└─────────────────────────────────────────────────');
39
+ }
40
+ handler.next(options);
41
+ }
42
+
43
+ @override
44
+ void onResponse(Response response, ResponseInterceptorHandler handler) {
45
+ if (responseBody) {
46
+ _log('┌─────────────────────────────────────────────────');
47
+ _log('│ Response: ${response.statusCode} ${response.requestOptions.uri}');
48
+ if (responseHeader) {
49
+ _log('│ Headers: ${response.headers.map}');
50
+ }
51
+ _log('│ Body:');
52
+ _logLongText(_formatJson(response.data));
53
+ _log('└─────────────────────────────────────────────────');
54
+ }
55
+ handler.next(response);
56
+ }
57
+
58
+ @override
59
+ void onError(DioException err, ErrorInterceptorHandler handler) {
60
+ if (error) {
61
+ _log('┌─────────────────────────────────────────────────');
62
+ _log('│ ❌ Error: ${err.type}');
63
+ _log('│ Message: ${err.message}');
64
+ if (err.response != null) {
65
+ _log('│ Status: ${err.response?.statusCode}');
66
+ _logLongText(_formatJson(err.response?.data));
67
+ }
68
+ _log('└─────────────────────────────────────────────────');
69
+ }
70
+ handler.next(err);
71
+ }
72
+
73
+ /// 格式化 JSON
74
+ String _formatJson(dynamic data) {
75
+ try {
76
+ if (data is Map || data is List) {
77
+ return const JsonEncoder.withIndent(' ').convert(data);
78
+ }
79
+ return data.toString();
80
+ } catch (e) {
81
+ return data.toString();
82
+ }
83
+ }
84
+
85
+ /// 输出长文本(分段打印,避免截断)
86
+ void _logLongText(String text) {
87
+ const chunkSize = 800;
88
+ for (var i = 0; i < text.length; i += chunkSize) {
89
+ final end = (i + chunkSize < text.length) ? i + chunkSize : text.length;
90
+ _log('│ ${text.substring(i, end)}');
91
+ }
92
+ }
93
+
94
+ void _log(String msg) {
95
+ debugPrint(msg);
96
+ }
97
+ }
@@ -0,0 +1,58 @@
1
+ import 'package:dio/dio.dart';
2
+ import 'package:flutter/foundation.dart';
3
+
4
+ import '../../utils/toast_util.dart';
5
+
6
+ /// 网络错误拦截器
7
+ ///
8
+ /// 统一处理超时、连接失败等网络异常,并自动弹出错误提示
9
+ ///
10
+ /// 使用示例:
11
+ /// ```dart
12
+ /// AppHttp.init(
13
+ /// autoShowError: true, // 全局开启错误弹窗
14
+ /// interceptors: [
15
+ /// // 其他拦截器...
16
+ /// ],
17
+ /// );
18
+ /// ```
19
+ class NetworkErrorInterceptor extends Interceptor {
20
+ final bool autoShowError;
21
+
22
+ NetworkErrorInterceptor({this.autoShowError = true});
23
+
24
+ @override
25
+ void onError(DioException err, ErrorInterceptorHandler handler) {
26
+ // 优先使用请求级配置,如果未设置则使用全局配置
27
+ final showError =
28
+ err.requestOptions.extra['showError'] as bool? ?? autoShowError;
29
+
30
+ if (showError) {
31
+ final msg = _getErrorMessage(err);
32
+ ToastUtil.error(msg);
33
+ debugPrint('❌ 网络错误: $msg');
34
+ }
35
+
36
+ handler.next(err);
37
+ }
38
+
39
+ /// 根据异常类型返回友好的错误提示
40
+ String _getErrorMessage(DioException e) {
41
+ switch (e.type) {
42
+ case DioExceptionType.connectionTimeout:
43
+ return '连接超时';
44
+ case DioExceptionType.sendTimeout:
45
+ return '请求超时';
46
+ case DioExceptionType.receiveTimeout:
47
+ return '响应超时';
48
+ case DioExceptionType.badResponse:
49
+ return '服务器错误 (${e.response?.statusCode})';
50
+ case DioExceptionType.cancel:
51
+ return '请求取消';
52
+ case DioExceptionType.connectionError:
53
+ return '网络连接异常';
54
+ default:
55
+ return e.message ?? '未知网络错误';
56
+ }
57
+ }
58
+ }