flu-cli-core 1.0.5 → 1.1.1

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 (118) hide show
  1. package/README.md +9 -0
  2. package/dist/chunk-BGYZU6TU.js +466 -0
  3. package/dist/chunk-QGM4M3NI.js +37 -0
  4. package/dist/factory-LM2CTHPW.js +7 -0
  5. package/dist/factory-P6ABQFH3.js +7 -0
  6. package/dist/index.cjs +17766 -3244
  7. package/dist/index.d.cts +783 -99
  8. package/dist/index.d.ts +783 -99
  9. package/dist/index.js +17323 -2942
  10. package/dist/upgrade_snippets-BJ6CQY5Q.js +9 -0
  11. package/package.json +3 -3
  12. package/templates/README.md +12 -0
  13. package/templates/core_files/auth/auth_middleware.dart.template +33 -0
  14. package/templates/core_files/auth/auth_service.dart.template +22 -0
  15. package/templates/core_files/auth/auth_viewmodel_mixin.dart.template +9 -0
  16. package/templates/core_files/auth/index.dart.template +4 -0
  17. package/templates/core_files/base/base_service.dart.template +12 -0
  18. package/templates/core_files/base/index.dart.template +3 -0
  19. package/templates/core_files/config/agreement_document_page.dart.template +220 -0
  20. package/templates/core_files/config/app_agreement.dart.template +297 -0
  21. package/templates/core_files/config/app_config.dart.template +81 -22
  22. package/templates/core_files/config/app_env.dart.template +107 -0
  23. package/templates/core_files/config/app_initializer.dart.template +16 -23
  24. package/templates/core_files/config/index.dart.template +4 -1
  25. package/templates/core_files/config/privacy_dialog.dart.template +158 -0
  26. package/templates/core_files/index.dart.template +3 -0
  27. package/templates/core_files/mixins/page/keep_alive_mixin.dart.template +6 -0
  28. package/templates/core_files/mixins/page/scroll_controller_mixin.dart.template +7 -0
  29. package/templates/core_files/mixins/service/request_guard_mixin.dart.template +18 -0
  30. package/templates/core_files/mixins/viewmodel/debounce_mixin.dart.template +18 -0
  31. package/templates/core_files/network/app_http.dart.template +19 -4
  32. package/templates/core_files/network/index.dart.template +4 -0
  33. package/templates/core_files/network/interceptors/global_params_interceptor.dart.template +77 -0
  34. package/templates/core_files/network/interceptors/index.dart.template +3 -0
  35. package/templates/core_files/network/network_monitor.dart.template +18 -0
  36. package/templates/core_files/network/response_adapter.dart.template +8 -19
  37. package/templates/core_files/router/app_routes.dart.template +3 -6
  38. package/templates/core_files/storage/storage_keys.dart.template +6 -0
  39. package/templates/core_files/theme/app_color_config.dart.template +32 -0
  40. package/templates/core_files/theme/app_text_size_config.dart.template +22 -0
  41. package/templates/core_files/theme/app_text_style_config.dart.template +139 -0
  42. package/templates/core_files/theme/app_theme.dart.template +72 -12
  43. package/templates/core_files/theme/index.dart.template +3 -0
  44. package/templates/core_files/utils/loading_util.dart.template +1 -1
  45. package/templates/examples/eg_list_page.dart.template +1 -2
  46. package/templates/examples/eg_service.dart.template +27 -4
  47. package/templates/examples/home_feed_service.dart.template +37 -0
  48. package/templates/helper_examples/image_picker_example_page.dart.template +289 -0
  49. package/templates/helper_examples/index.dart.template +4 -0
  50. package/templates/helper_examples/payment_shell_example_page.dart.template +67 -0
  51. package/templates/helper_examples/permission_example_page.dart.template +365 -0
  52. package/templates/helper_examples/webview_example_page.dart.template +44 -0
  53. package/templates/helpers/image_picker/README.md.template +30 -0
  54. package/templates/helpers/image_picker/index.dart.template +73 -0
  55. package/templates/helpers/payment/README.md.template +29 -0
  56. package/templates/helpers/payment/index.dart.template +66 -0
  57. package/templates/helpers/permission/README.md.template +30 -0
  58. package/templates/helpers/permission/index.dart.template +67 -0
  59. package/templates/helpers/webview/README.md.template +29 -0
  60. package/templates/helpers/webview/index.dart.template +88 -0
  61. package/templates/starter_project/.env.dev.template +14 -0
  62. package/templates/starter_project/.env.prod.example.template +14 -0
  63. package/templates/starter_project/.env.staging.template +14 -0
  64. package/templates/starter_project/.vscode/launch.json.template +54 -0
  65. package/templates/starter_project/.vscode/settings.json.template +14 -0
  66. package/templates/starter_project/DEVELOPER_GUIDE.md.template +169 -0
  67. package/templates/starter_project/README.md.template +117 -0
  68. package/templates/starter_project/analysis_options.yaml.template +28 -0
  69. package/templates/starter_project/lib/app.dart.template +22 -0
  70. package/templates/starter_project/lib/main.dart.template +34 -0
  71. package/templates/starter_project/lib/pages/splash_page.dart.template +154 -0
  72. package/templates/template_clean/lib/features/home/data/datasources/index.dart +1 -0
  73. package/templates/template_clean/lib/features/home/data/models/index.dart +1 -0
  74. package/templates/template_clean/lib/features/home/domain/index.dart +1 -0
  75. package/templates/template_clean/lib/features/home/presentation/pages/home_page.dart +290 -0
  76. package/templates/template_clean/lib/features/home/presentation/pages/index.dart +2 -0
  77. package/templates/template_clean/lib/features/home/presentation/pages/splash_page.dart +154 -0
  78. package/templates/template_clean/lib/features/home/presentation/viewmodels/home_viewmodel.dart +17 -0
  79. package/templates/template_clean/lib/features/index.dart +2 -0
  80. package/templates/template_clean/lib/features/user/data/datasources/home_feed_service.dart +37 -0
  81. package/templates/template_clean/lib/features/user/data/datasources/index.dart +4 -0
  82. package/templates/template_clean/lib/features/user/data/models/index.dart +3 -0
  83. package/templates/template_clean/lib/features/user/data/models/user.dart +15 -0
  84. package/templates/template_clean/lib/features/user/domain/index.dart +1 -0
  85. package/templates/template_clean/lib/features/user/presentation/pages/index.dart +1 -0
  86. package/templates/template_clean/lib/features/user/presentation/pages/user_list_page.dart +27 -0
  87. package/templates/template_clean/lib/features/user/presentation/viewmodels/user_list_viewmodel.dart +88 -0
  88. package/templates/template_clean/lib/features/user/presentation/widgets/user_item_card.dart +24 -0
  89. package/templates/template_clean/lib/shared/extensions/index.dart +1 -0
  90. package/templates/template_clean/lib/shared/widgets/index.dart +1 -0
  91. package/templates/template_lite/lib/models/index.dart +1 -0
  92. package/templates/template_lite/lib/pages/home_page.dart +290 -0
  93. package/templates/template_lite/lib/pages/index.dart +3 -0
  94. package/templates/template_lite/lib/pages/splash_page.dart +154 -0
  95. package/templates/template_lite/lib/pages/user_list_page.dart +29 -0
  96. package/templates/template_lite/lib/services/home_feed_service.dart +37 -0
  97. package/templates/template_lite/lib/services/index.dart +5 -0
  98. package/templates/template_lite/lib/utils/index.dart +1 -0
  99. package/templates/template_lite/lib/viewmodels/home_viewmodel.dart +34 -0
  100. package/templates/template_lite/lib/viewmodels/index.dart +2 -0
  101. package/templates/template_lite/lib/viewmodels/user_list_viewmodel.dart +103 -0
  102. package/templates/template_lite/lib/widgets/index.dart +1 -0
  103. package/templates/template_lite/lib/widgets/user_item_widget.dart +57 -0
  104. package/templates/template_modular/lib/features/home/index.dart +2 -0
  105. package/templates/template_modular/lib/features/home/models/index.dart +1 -0
  106. package/templates/template_modular/lib/features/home/pages/home_page.dart +290 -0
  107. package/templates/template_modular/lib/features/home/pages/index.dart +2 -0
  108. package/templates/template_modular/lib/features/home/pages/splash_page.dart +154 -0
  109. package/templates/template_modular/lib/features/home/services/index.dart +1 -0
  110. package/templates/template_modular/lib/features/home/viewmodels/home_viewmodel.dart +17 -0
  111. package/templates/template_modular/lib/features/index.dart +2 -0
  112. package/templates/template_modular/lib/features/user/index.dart +6 -0
  113. package/templates/template_modular/lib/features/user/pages/user_list_page.dart +26 -0
  114. package/templates/template_modular/lib/features/user/services/home_feed_service.dart +37 -0
  115. package/templates/template_modular/lib/features/user/viewmodels/user_list_viewmodel.dart +103 -0
  116. package/templates/template_modular/lib/features/user/widgets/user_item_widget.dart +24 -0
  117. package/templates/template_modular/lib/shared/utils/index.dart +1 -0
  118. package/templates/template_modular/lib/shared/widgets/index.dart +1 -0
@@ -0,0 +1,27 @@
1
+ import 'package:flutter/material.dart';
2
+
3
+ import '../../../../core/index.dart';
4
+ import '../../data/models/user.dart';
5
+ import '../viewmodels/user_list_viewmodel.dart';
6
+ import '../widgets/user_item_card.dart';
7
+
8
+ class UserListPage extends BaseListPage<User, UserListViewModel> {
9
+ const UserListPage({super.key});
10
+
11
+ @override
12
+ State<UserListPage> createState() => _UserListPageState();
13
+ }
14
+
15
+ class _UserListPageState
16
+ extends BaseListPageState<User, UserListViewModel, UserListPage> {
17
+ @override
18
+ String get title => '列表样板';
19
+
20
+ @override
21
+ UserListViewModel createViewModel() => UserListViewModel();
22
+
23
+ @override
24
+ Widget buildItem(BuildContext context, User item, int index) {
25
+ return UserItemCard(item: item);
26
+ }
27
+ }
@@ -0,0 +1,88 @@
1
+ import '../../../../core/index.dart';
2
+ {{#if_network_example}}
3
+ import '../../data/datasources/index.dart';
4
+ {{/if_network_example}}
5
+ import '../../data/models/user.dart';
6
+
7
+ enum DemoListMode {
8
+ standard,
9
+ finite,
10
+ empty,
11
+ firstPageError,
12
+ loadMoreError,
13
+ }
14
+
15
+ class UserListViewModel extends BaseListViewModel<User> {
16
+ {{#if_network_example}}
17
+ UserListViewModel({
18
+ HomeFeedService? feedService,
19
+ }) {
20
+ _feedService = feedService ?? HomeFeedService();
21
+ pageSize = 12;
22
+ }
23
+ {{else}}
24
+ UserListViewModel() {
25
+ pageSize = 12;
26
+ }
27
+ {{/if_network_example}}
28
+
29
+ final DemoListMode mode = _resolveMode();
30
+ {{#if_network_example}}
31
+ late final HomeFeedService _feedService;
32
+ {{/if_network_example}}
33
+
34
+ @override
35
+ Future<List<User>> fetchPage({
36
+ required int page,
37
+ required int pageSize,
38
+ }) async {
39
+ {{#if_network_example}}
40
+ if (!AppConfig.I.useMockData) {
41
+ final posts = await _feedService.fetchPosts(page: page, pageSize: pageSize);
42
+ return posts.map(User.fromNetworkJson).toList();
43
+ }
44
+
45
+ {{/if_network_example}}
46
+ await Future<void>.delayed(const Duration(milliseconds: 250));
47
+
48
+ if (mode == DemoListMode.empty && page == 1) {
49
+ return const <User>[];
50
+ }
51
+
52
+ if (mode == DemoListMode.firstPageError && page == 1) {
53
+ throw Exception('列表样板加载失败,请点击重试');
54
+ }
55
+
56
+ if (mode == DemoListMode.loadMoreError && page >= 2) {
57
+ throw Exception('分页加载失败,请重试');
58
+ }
59
+
60
+ if (mode == DemoListMode.finite && page >= 3) {
61
+ return List<User>.generate(
62
+ 5,
63
+ (index) => User('样板记录 ${(page - 1) * pageSize + index + 1}'),
64
+ );
65
+ }
66
+
67
+ return List<User>.generate(
68
+ pageSize,
69
+ (index) => User('样板记录 ${(page - 1) * pageSize + index + 1}'),
70
+ );
71
+ }
72
+
73
+ static DemoListMode _resolveMode() {
74
+ switch (EnvConfig.demoListMode.trim().toLowerCase()) {
75
+ case 'standard':
76
+ return DemoListMode.standard;
77
+ case 'empty':
78
+ return DemoListMode.empty;
79
+ case 'first_page_error':
80
+ return DemoListMode.firstPageError;
81
+ case 'load_more_error':
82
+ return DemoListMode.loadMoreError;
83
+ case 'finite':
84
+ default:
85
+ return DemoListMode.finite;
86
+ }
87
+ }
88
+ }
@@ -0,0 +1,24 @@
1
+ import 'package:flutter/material.dart';
2
+
3
+ import '../../data/models/user.dart';
4
+
5
+ class UserItemCard extends StatelessWidget {
6
+ final User item;
7
+
8
+ const UserItemCard({
9
+ super.key,
10
+ required this.item,
11
+ });
12
+
13
+ @override
14
+ Widget build(BuildContext context) {
15
+ return ListTile(
16
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
17
+ tileColor: Colors.white,
18
+ leading: CircleAvatar(child: Text(item.name.substring(0, 1))),
19
+ title: Text(item.name),
20
+ subtitle: Text('${item.name.toLowerCase()}@example.com'),
21
+ trailing: const Icon(Icons.chevron_right),
22
+ );
23
+ }
24
+ }
@@ -0,0 +1 @@
1
+ // 跨 feature 共享扩展导出入口。
@@ -0,0 +1 @@
1
+ // 跨 feature 共享组件导出入口。
@@ -0,0 +1 @@
1
+ export '../viewmodels/user_list_viewmodel.dart' show User;
@@ -0,0 +1,290 @@
1
+ import 'package:flutter/material.dart';
2
+
3
+ import '../core/index.dart';
4
+ {{#if_base_page}}
5
+ import '../viewmodels/index.dart';
6
+ {{/if_base_page}}
7
+
8
+ {{#if_base_page}}
9
+ class HomePage extends BasePage<HomeViewModel> {
10
+ const HomePage({super.key});
11
+
12
+ @override
13
+ State<HomePage> createState() => _HomePageState();
14
+ }
15
+
16
+ class _HomePageState extends BasePageState<HomeViewModel, HomePage> {
17
+ @override
18
+ bool get showAppBar => false;
19
+
20
+ @override
21
+ bool get safeAreaTop => false;
22
+
23
+ @override
24
+ bool get safeAreaBottom => false;
25
+
26
+ @override
27
+ Color? get backgroundColor => const Color(0xFFF4F7FB);
28
+
29
+ @override
30
+ HomeViewModel createViewModel() => HomeViewModel();
31
+
32
+ @override
33
+ Widget buildContent(BuildContext context) => const _HomePageBody();
34
+ }
35
+ {{else}}
36
+ class HomePage extends StatefulWidget {
37
+ const HomePage({super.key});
38
+
39
+ @override
40
+ State<HomePage> createState() => _HomePageState();
41
+ }
42
+
43
+ class _HomePageState extends State<HomePage> {
44
+ @override
45
+ Widget build(BuildContext context) {
46
+ return const Scaffold(
47
+ backgroundColor: Color(0xFFF4F7FB),
48
+ body: _HomePageBody(),
49
+ );
50
+ }
51
+ }
52
+ {{/if_base_page}}
53
+
54
+ class _HomePageBody extends StatelessWidget {
55
+ const _HomePageBody();
56
+
57
+ @override
58
+ Widget build(BuildContext context) {
59
+ final config = AppConfig.isInitialized ? AppConfig.I : null;
60
+ final appName = config?.appName ?? '{{projectDisplayName}}';
61
+ final envLabel = (config?.env.name ?? 'dev').toUpperCase();
62
+
63
+ return SafeArea(
64
+ child: CustomScrollView(
65
+ slivers: [
66
+ SliverToBoxAdapter(
67
+ child: Padding(
68
+ padding: const EdgeInsets.fromLTRB(20, 14, 20, 28),
69
+ child: Column(
70
+ crossAxisAlignment: CrossAxisAlignment.stretch,
71
+ children: [
72
+ _buildTopBar(appName, envLabel),
73
+ const SizedBox(height: 28),
74
+ _buildSectionTitle('开始使用'),
75
+ const SizedBox(height: 12),
76
+ _buildActionList([
77
+ _buildActionCard(
78
+ icon: Icons.add_box_outlined,
79
+ title: '创建第一个页面',
80
+ description: '使用 flu add page 或 VSCode 右键生成业务页面',
81
+ accentColor: const Color(0xFF2563EB),
82
+ onTap: () => _showCreatePageHint(context),
83
+ ),
84
+ _buildActionCard(
85
+ icon: Icons.privacy_tip_outlined,
86
+ title: '协议与隐私',
87
+ description: '查看用户协议、隐私政策和本地合规说明',
88
+ accentColor: const Color(0xFF059669),
89
+ onTap: () => _openAgreementMenu(context),
90
+ ),
91
+ ]),
92
+ // 示例模块 - 由 CLI 在选择示例时动态注入
93
+ ],
94
+ ),
95
+ ),
96
+ ),
97
+ ],
98
+ ),
99
+ );
100
+ }
101
+
102
+ Widget _buildTopBar(String appName, String envLabel) {
103
+ return Row(
104
+ children: [
105
+ Container(
106
+ width: 42,
107
+ height: 42,
108
+ decoration: BoxDecoration(
109
+ color: const Color(0xFF111827),
110
+ borderRadius: BorderRadius.circular(13),
111
+ ),
112
+ child: const Icon(
113
+ Icons.dashboard_customize_outlined,
114
+ color: Colors.white,
115
+ size: 21,
116
+ ),
117
+ ),
118
+ const SizedBox(width: 12),
119
+ Expanded(
120
+ child: Column(
121
+ crossAxisAlignment: CrossAxisAlignment.start,
122
+ children: [
123
+ Text(
124
+ appName,
125
+ maxLines: 1,
126
+ overflow: TextOverflow.ellipsis,
127
+ style: const TextStyle(
128
+ color: Color(0xFF111827),
129
+ fontSize: 19,
130
+ fontWeight: FontWeight.w800,
131
+ ),
132
+ ),
133
+ const SizedBox(height: 2),
134
+ Text(
135
+ 'Lite · $envLabel',
136
+ style: const TextStyle(
137
+ color: Color(0xFF64748B),
138
+ fontSize: 12,
139
+ fontWeight: FontWeight.w600,
140
+ ),
141
+ ),
142
+ ],
143
+ ),
144
+ ),
145
+ ],
146
+ );
147
+ }
148
+
149
+ Widget _buildSectionTitle(String title) {
150
+ return Text(
151
+ title,
152
+ style: const TextStyle(
153
+ color: Color(0xFF111827),
154
+ fontSize: 18,
155
+ fontWeight: FontWeight.w800,
156
+ ),
157
+ );
158
+ }
159
+
160
+ Widget _buildActionList(List<Widget> actions) {
161
+ return Column(
162
+ children: [
163
+ for (var i = 0; i < actions.length; i++) ...[
164
+ if (i > 0) const SizedBox(height: 10),
165
+ actions[i],
166
+ ],
167
+ ],
168
+ );
169
+ }
170
+
171
+ Widget _buildActionCard({
172
+ required IconData icon,
173
+ required String title,
174
+ required String description,
175
+ required Color accentColor,
176
+ required VoidCallback onTap,
177
+ }) {
178
+ return Material(
179
+ color: Colors.white,
180
+ borderRadius: BorderRadius.circular(16),
181
+ child: InkWell(
182
+ onTap: onTap,
183
+ borderRadius: BorderRadius.circular(16),
184
+ child: Container(
185
+ padding: const EdgeInsets.all(16),
186
+ decoration: BoxDecoration(
187
+ border: Border.all(color: const Color(0xFFE2E8F0)),
188
+ borderRadius: BorderRadius.circular(16),
189
+ ),
190
+ child: Row(
191
+ children: [
192
+ Container(
193
+ width: 42,
194
+ height: 42,
195
+ decoration: BoxDecoration(
196
+ color: accentColor.withAlpha(20),
197
+ borderRadius: BorderRadius.circular(12),
198
+ ),
199
+ child: Icon(icon, color: accentColor, size: 21),
200
+ ),
201
+ const SizedBox(width: 12),
202
+ Expanded(
203
+ child: Column(
204
+ crossAxisAlignment: CrossAxisAlignment.start,
205
+ children: [
206
+ Text(
207
+ title,
208
+ maxLines: 1,
209
+ overflow: TextOverflow.ellipsis,
210
+ style: const TextStyle(
211
+ color: Color(0xFF111827),
212
+ fontSize: 15,
213
+ fontWeight: FontWeight.w800,
214
+ ),
215
+ ),
216
+ const SizedBox(height: 5),
217
+ Text(
218
+ description,
219
+ maxLines: 2,
220
+ overflow: TextOverflow.ellipsis,
221
+ style: const TextStyle(
222
+ color: Color(0xFF64748B),
223
+ fontSize: 12,
224
+ height: 1.35,
225
+ ),
226
+ ),
227
+ ],
228
+ ),
229
+ ),
230
+ const SizedBox(width: 8),
231
+ const Icon(
232
+ Icons.chevron_right,
233
+ color: Color(0xFF94A3B8),
234
+ size: 20,
235
+ ),
236
+ ],
237
+ ),
238
+ ),
239
+ ),
240
+ );
241
+ }
242
+
243
+ void _showCreatePageHint(BuildContext context) {
244
+ ScaffoldMessenger.of(context).showSnackBar(
245
+ const SnackBar(
246
+ content: Text('在终端运行 flu add page,或在 VSCode 资源管理器右键生成页面。'),
247
+ ),
248
+ );
249
+ }
250
+
251
+ void _openAgreementMenu(BuildContext context) {
252
+ showModalBottomSheet<void>(
253
+ context: context,
254
+ showDragHandle: true,
255
+ backgroundColor: Colors.white,
256
+ builder: (sheetContext) {
257
+ return SafeArea(
258
+ child: Padding(
259
+ padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
260
+ child: Column(
261
+ mainAxisSize: MainAxisSize.min,
262
+ children: [
263
+ ListTile(
264
+ leading: const Icon(Icons.assignment_outlined),
265
+ title: const Text('用户协议'),
266
+ onTap: () {
267
+ Navigator.of(sheetContext).pop();
268
+ _openAgreement(AgreementDocumentType.userAgreement);
269
+ },
270
+ ),
271
+ ListTile(
272
+ leading: const Icon(Icons.privacy_tip_outlined),
273
+ title: const Text('隐私政策'),
274
+ onTap: () {
275
+ Navigator.of(sheetContext).pop();
276
+ _openAgreement(AgreementDocumentType.privacyPolicy);
277
+ },
278
+ ),
279
+ ],
280
+ ),
281
+ ),
282
+ );
283
+ },
284
+ );
285
+ }
286
+
287
+ void _openAgreement(AgreementDocumentType type) {
288
+ NavigatorUtil.push(AgreementDocumentPage(type: type));
289
+ }
290
+ }
@@ -0,0 +1,3 @@
1
+ export 'home_page.dart';
2
+ export 'splash_page.dart';
3
+ export 'user_list_page.dart';
@@ -0,0 +1,154 @@
1
+ import 'package:flutter/foundation.dart';
2
+ import 'package:flutter/material.dart';
3
+ import 'package:flutter/services.dart';
4
+
5
+ import '../core/index.dart';
6
+
7
+ class SplashPage extends StatefulWidget {
8
+ const SplashPage({super.key});
9
+
10
+ @override
11
+ State<SplashPage> createState() => _SplashPageState();
12
+ }
13
+
14
+ class _SplashPageState extends State<SplashPage>
15
+ with SingleTickerProviderStateMixin {
16
+ late final AnimationController _controller;
17
+ late final Animation<double> _opacity;
18
+ late final Animation<double> _scale;
19
+ bool _blocked = false;
20
+
21
+ @override
22
+ void initState() {
23
+ super.initState();
24
+ _controller = AnimationController(
25
+ vsync: this,
26
+ duration: const Duration(milliseconds: 820),
27
+ );
28
+ _opacity = CurvedAnimation(
29
+ parent: _controller,
30
+ curve: Curves.easeOutCubic,
31
+ );
32
+ _scale = Tween<double>(begin: 0.92, end: 1.0).animate(
33
+ CurvedAnimation(
34
+ parent: _controller,
35
+ curve: Curves.easeOutBack,
36
+ ),
37
+ );
38
+ _controller.forward();
39
+ WidgetsBinding.instance.addPostFrameCallback((_) {
40
+ _checkPrivacy();
41
+ });
42
+ }
43
+
44
+ @override
45
+ void dispose() {
46
+ _controller.dispose();
47
+ super.dispose();
48
+ }
49
+
50
+ Future<void> _checkPrivacy() async {
51
+ if (!EnvConfig.enableAgreementDialog || AppConfig.I.hasAcceptedAgreement) {
52
+ _goHome();
53
+ return;
54
+ }
55
+
56
+ if (!mounted) return;
57
+ final agreed = await PrivacyDialog.show(context);
58
+ if (!mounted) return;
59
+
60
+ if (agreed == true) {
61
+ await AppConfig.acceptCurrentAgreement();
62
+ _goHome();
63
+ return;
64
+ }
65
+
66
+ _handleAgreementRejected();
67
+ }
68
+
69
+ void _goHome() {
70
+ if (!mounted) return;
71
+ NavigatorUtil.pushNamed(AppRoutes.home, clearStack: true);
72
+ }
73
+
74
+ void _handleAgreementRejected() {
75
+ if (kIsWeb || defaultTargetPlatform == TargetPlatform.iOS) {
76
+ setState(() {
77
+ _blocked = true;
78
+ });
79
+ return;
80
+ }
81
+ SystemNavigator.pop();
82
+ }
83
+
84
+ @override
85
+ Widget build(BuildContext context) {
86
+ final colors = AppColorConfig.I;
87
+ return Scaffold(
88
+ backgroundColor: colors.background,
89
+ body: SafeArea(
90
+ child: Center(
91
+ child: FadeTransition(
92
+ opacity: _opacity,
93
+ child: ScaleTransition(
94
+ scale: _scale,
95
+ child: Column(
96
+ mainAxisSize: MainAxisSize.min,
97
+ children: [
98
+ Container(
99
+ width: 84,
100
+ height: 84,
101
+ decoration: BoxDecoration(
102
+ gradient: LinearGradient(
103
+ begin: Alignment.topLeft,
104
+ end: Alignment.bottomRight,
105
+ colors: [
106
+ colors.primary,
107
+ colors.primary.withBlue(212),
108
+ ],
109
+ ),
110
+ borderRadius: BorderRadius.circular(26),
111
+ boxShadow: [
112
+ BoxShadow(
113
+ color: colors.primary.withAlpha(45),
114
+ blurRadius: 24,
115
+ offset: const Offset(0, 12),
116
+ ),
117
+ ],
118
+ ),
119
+ child: const Icon(
120
+ Icons.blur_on_rounded,
121
+ color: Colors.white,
122
+ size: 38,
123
+ ),
124
+ ),
125
+ const SizedBox(height: 22),
126
+ Text(
127
+ EnvConfig.appName,
128
+ style: AppTextStyleConfig.I.titleLarge(
129
+ fontWeight: FontWeight.w800,
130
+ ),
131
+ ),
132
+ const SizedBox(height: 8),
133
+ Text(
134
+ _blocked ? '需要同意协议后继续使用' : '正在准备首页入口...',
135
+ style: AppTextStyleConfig.I.bodySmall(
136
+ color: colors.titleSecondary,
137
+ ),
138
+ ),
139
+ if (_blocked) ...[
140
+ const SizedBox(height: 18),
141
+ FilledButton(
142
+ onPressed: _checkPrivacy,
143
+ child: const Text('重新阅读并同意'),
144
+ ),
145
+ ],
146
+ ],
147
+ ),
148
+ ),
149
+ ),
150
+ ),
151
+ ),
152
+ );
153
+ }
154
+ }
@@ -0,0 +1,29 @@
1
+ import 'package:flutter/material.dart';
2
+
3
+ import '../core/index.dart';
4
+ import '../viewmodels/index.dart';
5
+ import '../widgets/index.dart';
6
+
7
+ class UserListPage extends BaseListPage<User, UserListViewModel> {
8
+ const UserListPage({super.key});
9
+
10
+ @override
11
+ State<UserListPage> createState() => _UserListPageState();
12
+ }
13
+
14
+ class _UserListPageState
15
+ extends BaseListPageState<User, UserListViewModel, UserListPage> {
16
+ @override
17
+ String get title => '列表样板';
18
+
19
+ @override
20
+ bool get enableRefresh => true;
21
+
22
+ @override
23
+ UserListViewModel createViewModel() => UserListViewModel();
24
+
25
+ @override
26
+ Widget buildItem(BuildContext context, User item, int index) {
27
+ return UserItemWidget(item: item);
28
+ }
29
+ }
@@ -0,0 +1,37 @@
1
+ {{#if_network_example}}
2
+ import '../core/index.dart';
3
+
4
+ class HomeFeedService {
5
+ HomeFeedService({
6
+ AppHttp? http,
7
+ }) : _http = http ?? AppHttp();
8
+
9
+ final AppHttp _http;
10
+
11
+ Future<List<Map<String, dynamic>>> fetchPosts({
12
+ required int page,
13
+ required int pageSize,
14
+ }) async {
15
+ final response = await _http.get<List<Map<String, dynamic>>>(
16
+ '/posts',
17
+ queryParameters: {
18
+ '_page': page,
19
+ '_limit': pageSize,
20
+ },
21
+ showError: false,
22
+ fromJson: (json) {
23
+ final list = json as List;
24
+ return list
25
+ .map((item) => Map<String, dynamic>.from(item as Map))
26
+ .toList();
27
+ },
28
+ );
29
+
30
+ if (response.isSuccess && response.data != null) {
31
+ return response.data!;
32
+ }
33
+
34
+ throw Exception(response.msg);
35
+ }
36
+ }
37
+ {{/if_network_example}}
@@ -0,0 +1,5 @@
1
+ // 业务服务统一导出入口。
2
+ // 当你选择网络示例后,会导出示例接口服务;真实业务服务可继续补充到这里。
3
+ {{#if_network_example}}
4
+ export 'home_feed_service.dart';
5
+ {{/if_network_example}}
@@ -0,0 +1 @@
1
+ // 非 core 的页面级/业务级工具可在这里集中导出。