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,40 @@
1
+ import 'package:flutter/material.dart';
2
+ class StatusViewsTheme extends ThemeExtension<StatusViewsTheme> {
3
+ final Widget Function(BuildContext ctx)? loadingBuilder;
4
+ final Widget Function(BuildContext ctx, String? msg)? errorBuilder;
5
+ final Widget Function(BuildContext ctx)? emptyBuilder;
6
+ final Color? refreshIndicatorColor;
7
+ final Widget Function(BuildContext ctx, bool hasMore)? loadMoreBuilder;
8
+ const StatusViewsTheme({
9
+ this.loadingBuilder,
10
+ this.errorBuilder,
11
+ this.emptyBuilder,
12
+ this.refreshIndicatorColor,
13
+ this.loadMoreBuilder,
14
+ });
15
+ @override
16
+ StatusViewsTheme copyWith({
17
+ Widget Function(BuildContext)? loadingBuilder,
18
+ Widget Function(BuildContext, String?)? errorBuilder,
19
+ Widget Function(BuildContext)? emptyBuilder,
20
+ Color? refreshIndicatorColor,
21
+ Widget Function(BuildContext, bool)? loadMoreBuilder,
22
+ }) {
23
+ return StatusViewsTheme(
24
+ loadingBuilder: loadingBuilder ?? this.loadingBuilder,
25
+ errorBuilder: errorBuilder ?? this.errorBuilder,
26
+ emptyBuilder: emptyBuilder ?? this.emptyBuilder,
27
+ refreshIndicatorColor:
28
+ refreshIndicatorColor ?? this.refreshIndicatorColor,
29
+ loadMoreBuilder: loadMoreBuilder ?? this.loadMoreBuilder,
30
+ );
31
+ }
32
+ @override
33
+ ThemeExtension<StatusViewsTheme> lerp(
34
+ ThemeExtension<StatusViewsTheme>? other,
35
+ double t,
36
+ ) {
37
+ return this;
38
+ }
39
+ }
40
+
@@ -0,0 +1,2 @@
1
+ export 'loading_util.dart';
2
+ export 'toast_util.dart';
@@ -0,0 +1,55 @@
1
+ import 'package:flutter/material.dart';
2
+
3
+ /// Loading 工具类 - 基于 Overlay 的原生实现
4
+ ///
5
+ /// 优点:
6
+ /// 1. 零第三方依赖:保持项目纯洁,不增加 pubspec.yaml 负担。
7
+ /// 2. 灵活性高:完全基于 Flutter 原生机制,易于自定义 UI 样式。
8
+ /// 3. 轻量级:不占用额外打包体积。
9
+ class LoadingUtil {
10
+ static OverlayEntry? _overlayEntry;
11
+
12
+ /// 显示 Loading
13
+ ///
14
+ /// [context] 上下文
15
+ /// [message] 加载提示文字
16
+ static void show(BuildContext context, {String? message}) {
17
+ if (_overlayEntry != null) return;
18
+
19
+ _overlayEntry = OverlayEntry(
20
+ builder: (context) => Material(
21
+ color: Colors.black54,
22
+ child: Center(
23
+ child: Container(
24
+ padding: const EdgeInsets.all(20),
25
+ decoration: BoxDecoration(
26
+ color: Colors.white,
27
+ borderRadius: BorderRadius.circular(8),
28
+ ),
29
+ child: Column(
30
+ mainAxisSize: MainAxisSize.min,
31
+ children: [
32
+ const CircularProgressIndicator(),
33
+ if (message != null) ...[
34
+ const SizedBox(height: 16),
35
+ Text(
36
+ message,
37
+ style: const TextStyle(fontSize: 14, color: Colors.black),
38
+ ),
39
+ ],
40
+ ],
41
+ ),
42
+ ),
43
+ ),
44
+ ),
45
+ );
46
+
47
+ Overlay.of(context).insert(_overlayEntry!);
48
+ }
49
+
50
+ /// 隐藏 Loading
51
+ static void dismiss() {
52
+ _overlayEntry?.remove();
53
+ _overlayEntry = null;
54
+ }
55
+ }
@@ -0,0 +1,128 @@
1
+ import 'package:flutter/material.dart';
2
+ {{#if_getx}}
3
+ import 'package:get/get.dart';
4
+ {{else}}
5
+ import '../router/navigator_util.dart';
6
+ {{/if_getx}}
7
+
8
+ /// Toast 工具类 - 自动适配状态管理器
9
+ ///
10
+ /// Material 模式: 基于 SnackBar
11
+ /// GetX 模式: 基于 Get.snackbar
12
+ ///
13
+ /// 优点:
14
+ /// 1. 零第三方依赖(GetX 除外)
15
+ /// 2. 无需 BuildContext,全局可调用
16
+ /// 3. 自动清除旧消息,防止堆叠
17
+ class ToastUtil {
18
+ /// 显示 Toast 消息
19
+ ///
20
+ /// [message] 提示消息
21
+ /// [duration] 显示时长,默认 2 秒
22
+ /// [showCloseIcon] 是否显示关闭图标,默认 true
23
+ static void show(
24
+ String message, {
25
+ Duration duration = const Duration(seconds: 2),
26
+ bool showCloseIcon = true,
27
+ }) {
28
+ if (message.isEmpty) return;
29
+
30
+ {{#if_getx}}
31
+ // GetX 模式: 使用 Get.snackbar
32
+ Get.snackbar(
33
+ '',
34
+ message,
35
+ snackPosition: SnackPosition.BOTTOM,
36
+ duration: duration,
37
+ margin: const EdgeInsets.all(16),
38
+ borderRadius: 8,
39
+ isDismissible: true,
40
+ dismissDirection: DismissDirection.horizontal,
41
+ showProgressIndicator: false,
42
+ titleText: const SizedBox.shrink(), // 不显示标题
43
+ messageText: Text(
44
+ message,
45
+ style: const TextStyle(color: Colors.white),
46
+ ),
47
+ );
48
+ {{else}}
49
+ // Material 模式: 使用 ScaffoldMessenger
50
+ final state = NavigatorUtil.scaffoldMessengerKey.currentState;
51
+ if (state == null) {
52
+ debugPrint('⚠️ 无法显示Toast, ScaffoldMessengerState为空: $message');
53
+ return;
54
+ }
55
+
56
+ // 清除之前的 SnackBar,防止堆叠
57
+ state.clearSnackBars();
58
+
59
+ // 显示新的 SnackBar
60
+ state.showSnackBar(
61
+ SnackBar(
62
+ content: Text(message),
63
+ behavior: SnackBarBehavior.floating, // 浮动样式
64
+ duration: duration,
65
+ showCloseIcon: showCloseIcon,
66
+ ),
67
+ );
68
+ {{/if_getx}}
69
+ }
70
+
71
+ /// 显示成功消息
72
+ static void success(String message) {
73
+ {{#if_getx}}
74
+ Get.snackbar(
75
+ '成功',
76
+ message,
77
+ snackPosition: SnackPosition.BOTTOM,
78
+ backgroundColor: Colors.green,
79
+ colorText: Colors.white,
80
+ icon: const Icon(Icons.check_circle, color: Colors.white),
81
+ duration: const Duration(seconds: 2),
82
+ margin: const EdgeInsets.all(16),
83
+ borderRadius: 8,
84
+ );
85
+ {{else}}
86
+ show(message);
87
+ {{/if_getx}}
88
+ }
89
+
90
+ /// 显示错误消息
91
+ static void error(String message) {
92
+ {{#if_getx}}
93
+ Get.snackbar(
94
+ '错误',
95
+ message,
96
+ snackPosition: SnackPosition.BOTTOM,
97
+ backgroundColor: Colors.red,
98
+ colorText: Colors.white,
99
+ icon: const Icon(Icons.error, color: Colors.white),
100
+ duration: const Duration(seconds: 3),
101
+ margin: const EdgeInsets.all(16),
102
+ borderRadius: 8,
103
+ );
104
+ {{else}}
105
+ show(message);
106
+ {{/if_getx}}
107
+ }
108
+
109
+ /// 显示警告消息
110
+ static void warning(String message) {
111
+ {{#if_getx}}
112
+ Get.snackbar(
113
+ '警告',
114
+ message,
115
+ snackPosition: SnackPosition.BOTTOM,
116
+ backgroundColor: Colors.orange,
117
+ colorText: Colors.white,
118
+ icon: const Icon(Icons.warning, color: Colors.white),
119
+ duration: const Duration(seconds: 2),
120
+ margin: const EdgeInsets.all(16),
121
+ borderRadius: 8,
122
+ );
123
+ {{else}}
124
+ show(message);
125
+ {{/if_getx}}
126
+ }
127
+ }
128
+
@@ -0,0 +1,340 @@
1
+ import 'package:flutter/material.dart';
2
+ import '{{CORE_IMPORT}}';
3
+
4
+ {{#if_clean}}
5
+ import '{{DATA_IMPORT}}';
6
+ {{else}}
7
+ import '{{MODELS_IMPORT}}';
8
+ {{/if_clean}}
9
+ import '{{VIEWMODELS_IMPORT}}';
10
+
11
+ /// 示例:图片列表页
12
+ class EgListPage extends BaseListPage<TuChongItem, EgListViewModel> {
13
+ const EgListPage({super.key});
14
+
15
+ @override
16
+ State<EgListPage> createState() => _EgListPageState();
17
+ }
18
+
19
+ class _EgListPageState
20
+ extends BaseListPageState<TuChongItem, EgListViewModel, EgListPage> {
21
+ @override
22
+ String get title => '图片列表';
23
+ @override
24
+ bool get enableRefresh => true;
25
+ @override
26
+ EgListViewModel createViewModel() => EgListViewModel();
27
+
28
+ @override
29
+ Widget buildItem(BuildContext context, TuChongItem item, int index) {
30
+ Widget body = Container();
31
+
32
+ body = Column(
33
+ mainAxisAlignment: MainAxisAlignment.start,
34
+ crossAxisAlignment: CrossAxisAlignment.start,
35
+ children: [
36
+ _buildUserInfo(item),
37
+ const SizedBox(
38
+ height: 10,
39
+ ),
40
+ _buildTagsWidget(item),
41
+ ExampleTuItmeImagelessV(tuChongItem: item),
42
+ ],
43
+ );
44
+ body = Container(
45
+ margin: const EdgeInsets.only(
46
+ left: 15,
47
+ right: 15,
48
+ top: 15,
49
+ ),
50
+ padding: const EdgeInsets.all(15),
51
+ decoration: BoxDecoration(
52
+ color: Colors.white,
53
+ borderRadius: BorderRadius.circular(10),
54
+ boxShadow: const [
55
+ BoxShadow(
56
+ color: Colors.black12,
57
+ offset: Offset(0, 0),
58
+ blurRadius: 3,
59
+ ),
60
+ ],
61
+ ),
62
+ child: body,
63
+ );
64
+ return body;
65
+ }
66
+
67
+ // 构建用户信息
68
+ _buildUserInfo(TuChongItem item) {
69
+ final Widget body = Row(
70
+ children: [
71
+ _buildImageWidget(
72
+ url: item.imageUrl,
73
+ ),
74
+ const SizedBox(
75
+ width: 10,
76
+ ),
77
+ Expanded(
78
+ child: Column(
79
+ crossAxisAlignment: CrossAxisAlignment.start,
80
+ mainAxisAlignment: MainAxisAlignment.center,
81
+ children: [
82
+ Text(
83
+ item.site?.name ?? AppConstants.appName,
84
+ style: const TextStyle(
85
+ fontSize: 16,
86
+ fontWeight: FontWeight.bold,
87
+ ),
88
+ ),
89
+ if (item.site?.description?.isNotEmpty ?? false)
90
+ Text(
91
+ item.site?.description ?? '',
92
+ style: const TextStyle(
93
+ fontSize: 14,
94
+ color: Colors.grey,
95
+ ),
96
+ maxLines: 1,
97
+ overflow: TextOverflow.ellipsis,
98
+ ),
99
+ ],
100
+ ),
101
+ ),
102
+ ],
103
+ );
104
+ return body;
105
+ }
106
+
107
+ // 构建标签
108
+ _buildTagsWidget(TuChongItem tuChongItem) {
109
+ Widget body = Wrap(
110
+ spacing: 6,
111
+ runSpacing: 6,
112
+ children: [
113
+ ...List.generate(
114
+ tuChongItem.tagColors?.length ?? 0,
115
+ (index) {
116
+ final Widget body = Container(
117
+ decoration: BoxDecoration(
118
+ color: tuChongItem.tagColors?[index],
119
+ borderRadius: BorderRadius.circular(
120
+ 4,
121
+ ),
122
+ ),
123
+ padding: const EdgeInsets.symmetric(
124
+ horizontal: 6,
125
+ ),
126
+ child: Text(
127
+ tuChongItem.tags?[index] ?? '',
128
+ style: const TextStyle(
129
+ fontSize: 12,
130
+ color: Colors.white,
131
+ ),
132
+ ),
133
+ );
134
+ return body;
135
+ },
136
+ ),
137
+ ],
138
+ );
139
+ body = Padding(
140
+ padding: const EdgeInsets.only(bottom: 10),
141
+ child: body,
142
+ );
143
+ return body;
144
+ }
145
+ }
146
+
147
+ class ExampleTuItmeImagelessV extends StatelessWidget {
148
+ const ExampleTuItmeImagelessV({
149
+ super.key,
150
+ required this.tuChongItem,
151
+ this.heroTag = 'tuChongItem',
152
+ this.type = 0,
153
+ });
154
+ final TuChongItem tuChongItem;
155
+
156
+ final String heroTag;
157
+ final int type;
158
+ @override
159
+ Widget build(BuildContext context) {
160
+ Widget body = configImageListWidget();
161
+
162
+ body = ClipRRect(
163
+ borderRadius: BorderRadius.circular(10),
164
+ child: body,
165
+ );
166
+ body = Container(
167
+ padding: const EdgeInsets.all(5),
168
+ decoration: BoxDecoration(
169
+ color: Colors.lightBlue.withOpacity(0.1),
170
+ borderRadius: BorderRadius.circular(
171
+ 10,
172
+ ),
173
+ ),
174
+ child: body,
175
+ );
176
+ return body;
177
+ }
178
+
179
+ /// 配置图片数组视图
180
+ configImageListWidget() {
181
+ if (tuChongItem.realImages?.length == 1) {
182
+ return configOnlyOneImageWidget();
183
+ } else {
184
+ return GridView.count(
185
+ crossAxisCount: tuChongItem.crossAxisCount ?? 1,
186
+ childAspectRatio: tuChongItem.crossAxisCount == 2 ? 1 : 1,
187
+ crossAxisSpacing: 5.0,
188
+ mainAxisSpacing: 5.0,
189
+ shrinkWrap: true,
190
+ physics: const NeverScrollableScrollPhysics(),
191
+ children: [
192
+ ...List.generate(
193
+ tuChongItem.realImages!.length > 9
194
+ ? 9
195
+ : tuChongItem.realImages!.length, (index) {
196
+ final Widget body = configImageItem(
197
+ imageUrl: tuChongItem.realImages![index],
198
+ index: index,
199
+ allNum: tuChongItem.images?.length ?? 0,
200
+ heroTag: heroTag,
201
+ );
202
+ return body;
203
+ }),
204
+ ],
205
+ );
206
+ }
207
+ }
208
+
209
+ /// 配置单图视图
210
+ configOnlyOneImageWidget() {
211
+ final Widget body = LayoutBuilder(
212
+ builder: (p0, p1) {
213
+ final ImageItem imageItem = tuChongItem.images![0];
214
+ final double ratio = (imageItem.width ?? 0) / (imageItem.height ?? 1);
215
+ final double imageH = p1.maxWidth / ratio;
216
+ final Widget body = configTuchongImageWidget(
217
+ url: imageItem.imageUrl,
218
+ width: p1.maxWidth,
219
+ height: imageH,
220
+ index: 0,
221
+ heroTag: heroTag,
222
+ );
223
+ return body;
224
+ },
225
+ );
226
+ return body;
227
+ }
228
+
229
+ /// 配置图片item
230
+ configImageItem({
231
+ required String imageUrl,
232
+ required int index,
233
+ required int allNum,
234
+ String? heroTag,
235
+ }) {
236
+ Widget body = LayoutBuilder(
237
+ builder: (p0, p1) {
238
+ /// 加载图片
239
+ final Widget body = configTuchongImageWidget(
240
+ url: imageUrl,
241
+ heroTag: heroTag,
242
+ width: p1.maxWidth,
243
+ height: p1.maxHeight,
244
+ index: index,
245
+ isTap: false,
246
+ );
247
+ return body;
248
+ },
249
+ );
250
+ if (index == 8 && allNum > (index + 1)) {
251
+ body = Stack(
252
+ children: [
253
+ body,
254
+ Positioned.fill(
255
+ child: configResidueNumWidget(
256
+ num: allNum - 9,
257
+ ),
258
+ ),
259
+ ],
260
+ );
261
+ }
262
+
263
+ return body;
264
+ }
265
+
266
+ /// 配置图片显示视图
267
+ configTuchongImageWidget({
268
+ String? url,
269
+ double? width,
270
+ double? height,
271
+ String? heroTag,
272
+ bool isTap = true,
273
+ required int index,
274
+ }) {
275
+ final Widget body = url != null
276
+ ? _buildImageWidget(
277
+ url: url,
278
+ width: width,
279
+ height: height,
280
+ borderRadius: 6,
281
+ )
282
+ : Container();
283
+
284
+ return body;
285
+ }
286
+
287
+ /// 创建图片剩余个数
288
+ configResidueNumWidget({required int num}) {
289
+ Widget body = Text(
290
+ '+$num',
291
+ style: const TextStyle(
292
+ fontSize: 16,
293
+ color: Colors.white,
294
+ ),
295
+ );
296
+ body = Container(
297
+ decoration: const BoxDecoration(
298
+ color: Colors.black12,
299
+ ),
300
+ alignment: Alignment.center,
301
+ child: body,
302
+ );
303
+ return body;
304
+ }
305
+ }
306
+
307
+ /// 配置用户头像
308
+ _buildImageWidget({
309
+ required String url,
310
+ double? width = 50,
311
+ double? height = 50,
312
+ double borderRadius = 50,
313
+ }) {
314
+ if (url.isEmpty) {
315
+ return SizedBox(
316
+ width: width,
317
+ height: height,
318
+ );
319
+ }
320
+ Widget body = Image.network(
321
+ url,
322
+ width: width,
323
+ height: height,
324
+ fit: BoxFit.cover,
325
+ errorBuilder: (context, error, stackTrace) {
326
+ return Container(
327
+ width: width,
328
+ height: height,
329
+ color: Colors.grey[200],
330
+ child: const Icon(Icons.broken_image, color: Colors.grey),
331
+ );
332
+ },
333
+ );
334
+ body = ClipRRect(
335
+ borderRadius: BorderRadius.circular(borderRadius),
336
+ child: body,
337
+ );
338
+
339
+ return body;
340
+ }
@@ -0,0 +1,31 @@
1
+ import '{{CORE_IMPORT}}';
2
+ {{#if_clean}}
3
+ import '{{DATA_IMPORT}}';
4
+ {{else}}
5
+ import '{{MODELS_IMPORT}}';
6
+ import '{{SERVICES_IMPORT}}';
7
+ {{/if_clean}}
8
+
9
+
10
+
11
+ /// 图片列表 ViewModel
12
+ class EgListViewModel extends BaseListViewModel<TuChongItem> {
13
+ final EgService _photoService = EgService();
14
+ @override
15
+ int get pageSize => 10;
16
+ @override
17
+ Future<List<TuChongItem>> fetchPage({
18
+ required int page,
19
+ required int pageSize,
20
+ }) async {
21
+ int? lastPostId;
22
+ if (items.length > 1) {
23
+ lastPostId = items.last.postId;
24
+ }
25
+ return await _photoService.getTuChongList(
26
+ page: page,
27
+ pageSize: pageSize,
28
+ lastPostId: lastPostId,
29
+ );
30
+ }
31
+ }
@@ -0,0 +1,78 @@
1
+ import 'package:flutter/foundation.dart';
2
+ import '{{CORE_IMPORT}}';
3
+ {{#if_clean}}
4
+ import '{{DATA_IMPORT}}';
5
+ {{else}}
6
+ import '{{MODELS_IMPORT}}';
7
+ {{/if_clean}}
8
+
9
+ class EgService {
10
+ final AppHttp _http;
11
+ final bool useMock;
12
+
13
+ EgService({
14
+ AppHttp? http,
15
+ bool? useMock,
16
+ }) : _http = http ?? AppHttp(),
17
+ useMock = useMock ?? AppConfig.I.useMockData;
18
+
19
+ Future<List<TuChongItem>> getTuChongList({
20
+ required int page,
21
+ int pageSize = 10,
22
+ int? lastPostId,
23
+ }) async {
24
+ // 如果开启了 Mock 模式,从 mockData 加载数据
25
+ if (useMock) {
26
+ return _loadMockData(page, pageSize);
27
+ }
28
+
29
+ Map<String, dynamic> queryParameters = {};
30
+ if (lastPostId != null) {
31
+ queryParameters = {
32
+ 'post_id': lastPostId,
33
+ 'page': page,
34
+ 'type': 'loadmore',
35
+ };
36
+ }
37
+
38
+ final response = await _http.get(
39
+ '/feed-app',
40
+ queryParameters: queryParameters,
41
+ );
42
+
43
+ if (response.isSuccess && response.data is List) {
44
+ return (response.data as List)
45
+ .map((e) => TuChongItem.fromJson(e as Map<String, dynamic>))
46
+ .toList();
47
+ }
48
+
49
+ // 降级处理:请求失败时加载模拟数据
50
+ return _loadMockData(1, pageSize);
51
+ }
52
+
53
+ /// 加载本地 Mock 数据
54
+ List<TuChongItem> _loadMockData(int page, int pageSize) {
55
+ try {
56
+ // 从 mock_data.dart 获取数据
57
+ // 结构为 { 'feedList': [...] }
58
+ final List? feedList = mock['feedList'];
59
+ if (feedList != null) {
60
+ // 模拟分页
61
+ final int start = (page - 1) * pageSize;
62
+ if (start >= feedList.length) return [];
63
+
64
+ final int end = start + pageSize > feedList.length
65
+ ? feedList.length
66
+ : start + pageSize;
67
+ final subList = feedList.sublist(start, end);
68
+
69
+ return subList
70
+ .map((e) => TuChongItem.fromJson(e as Map<String, dynamic>))
71
+ .toList();
72
+ }
73
+ } catch (e) {
74
+ debugPrint('Mock 数据解析错误: $e');
75
+ }
76
+ return [];
77
+ }
78
+ }