mta-mcp 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 (75) hide show
  1. package/README.md +818 -0
  2. package/agents/_TEMPLATE.md +153 -0
  3. package/agents/flutter.agent.md +222 -0
  4. package/agents/i18n.agent.md +78 -0
  5. package/agents/logicflow.agent.md +97 -0
  6. package/agents/vue3.agent.md +176 -0
  7. package/agents/wechat-miniprogram.agent.md +89 -0
  8. package/bin/mta.cjs +132 -0
  9. package/common/i18n.md +385 -0
  10. package/common/typescript-strict.md +186 -0
  11. package/dist/index.d.ts +64 -0
  12. package/dist/index.js +6493 -0
  13. package/dist/index.js.map +1 -0
  14. package/package.json +81 -0
  15. package/standards/README.md +194 -0
  16. package/standards/core/code-generation.md +421 -0
  17. package/standards/core/code-style.md +308 -0
  18. package/standards/core/dart-base.md +572 -0
  19. package/standards/core/mandatory-rules.md +103 -0
  20. package/standards/core/typescript-base.md +179 -0
  21. package/standards/frameworks/flutter-ui-system.md +497 -0
  22. package/standards/frameworks/flutter.md +1268 -0
  23. package/standards/frameworks/pinia.md +172 -0
  24. package/standards/frameworks/vue3-composition.md +779 -0
  25. package/standards/frameworks/wechat-miniprogram.md +2177 -0
  26. package/standards/libraries/element-plus.md +1128 -0
  27. package/standards/libraries/i18n.md +360 -0
  28. package/standards/libraries/logicflow.md +1007 -0
  29. package/standards/patterns/api-layer.md +187 -0
  30. package/standards/patterns/component-design.md +200 -0
  31. package/standards/patterns/design-system-restoration.md +570 -0
  32. package/standards/patterns/vue-api-mock-layer.md +958 -0
  33. package/standards/patterns/vue-css-nesting.md +604 -0
  34. package/standards/troubleshooting-cases/flutter/textfield-vertical-centering.md +107 -0
  35. package/standards/workflows/design-restoration-guide.md +164 -0
  36. package/standards/workflows/large-project-split.md +359 -0
  37. package/standards/workflows/problem-diagnosis.md +280 -0
  38. package/standards/workflows/textfield-centering-guide.md +157 -0
  39. package/templates/README.md +144 -0
  40. package/templates/common/types/_CONFIG.md +12 -0
  41. package/templates/common/types/api.ts +39 -0
  42. package/templates/common/types/common.ts +70 -0
  43. package/templates/config-templates/agents-section.md +9 -0
  44. package/templates/config-templates/custom-section.md +6 -0
  45. package/templates/config-templates/header.md +29 -0
  46. package/templates/config-templates/workflow-minimal.md +44 -0
  47. package/templates/copilot-instructions-mcp-optimized.md +158 -0
  48. package/templates/vue/api-layer/_CONFIG.md +145 -0
  49. package/templates/vue/api-layer/index.ts +58 -0
  50. package/templates/vue/api-layer/mock/index.ts +122 -0
  51. package/templates/vue/api-layer/modules/_template.ts +109 -0
  52. package/templates/vue/api-layer/modules/index.ts +16 -0
  53. package/templates/vue/api-layer/request.ts +279 -0
  54. package/templates/vue/api-layer/types.ts +80 -0
  55. package/troubleshooting/README.md +368 -0
  56. package/troubleshooting/USAGE_GUIDE.md +289 -0
  57. package/troubleshooting/flutter/clip-/351/230/264/345/275/261/350/243/201/345/211/252.md +244 -0
  58. package/troubleshooting/flutter/component-/351/200/232/347/224/250/345/214/226/346/217/220/345/217/226.md +269 -0
  59. package/troubleshooting/flutter/input-/345/255/227/346/256/265/347/274/272/345/244/261.md +240 -0
  60. package/troubleshooting/flutter/input-/350/276/271/346/241/206/351/227/256/351/242/230.md +236 -0
  61. package/troubleshooting/flutter/layout-/345/260/272/345/257/270/344/270/215/345/214/271/351/205/215.md +214 -0
  62. package/troubleshooting/flutter/shadow-/351/200/217/345/207/272/351/227/256/351/242/230.md +172 -0
  63. package/troubleshooting/flutter/sketch-/345/210/227/350/241/250item/345/214/272/345/237/237.md +212 -0
  64. package/troubleshooting/flutter/sketch-/345/233/276/346/240/207/345/260/272/345/257/270.md +135 -0
  65. package/troubleshooting/flutter/sketch-/345/256/214/346/225/264/346/217/220/345/217/226.md +201 -0
  66. package/troubleshooting/flutter/sketch-/345/261/236/346/200/247/346/234/252/344/275/277/347/224/250.md +139 -0
  67. package/troubleshooting/flutter/sketch-/350/203/214/346/231/257/345/261/202/351/253/230/345/272/246.md +264 -0
  68. package/troubleshooting/flutter/svg-/346/234/252/345/261/205/344/270/255.md +120 -0
  69. package/troubleshooting/flutter/svg-/351/242/234/350/211/262/345/274/202/345/270/270.md +117 -0
  70. package/troubleshooting/flutter/tabbar-/345/212/250/347/224/273/345/220/214/346/255/245.md +107 -0
  71. package/troubleshooting/flutter/withopacity-/345/274/203/347/224/250.md +81 -0
  72. package/troubleshooting/vue3/cascader-/350/257/257/346/233/277/346/215/242.md +130 -0
  73. package/troubleshooting/vue3/drawer-input-/346/240/267/345/274/217.md +181 -0
  74. package/troubleshooting/vue3/table-/347/274/226/350/276/221/345/217/226/346/266/210.md +148 -0
  75. package/troubleshooting/vue3/table-/350/276/271/346/241/206/351/227/256/351/242/230.md +178 -0
@@ -0,0 +1,1268 @@
1
+ # Flutter 开发规范
2
+
3
+ > 基于 Flutter 官方 [Style Guide](https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md) 和最佳实践
4
+
5
+ ## 🎯 核心原则
6
+
7
+ 1. **组合优于继承** - 通过组合构建复杂的 Widget 和逻辑
8
+ 2. **Widget 即 UI** - Flutter 中一切皆 Widget
9
+ 3. **不可变 Widget** - Widget(尤其是 StatelessWidget)应该是不可变的
10
+ 4. **状态分离** - 区分瞬时状态(ephemeral state)和应用状态(app state)
11
+ 5. **简洁声明式** - 编写简洁的现代声明式代码
12
+ 6. **性能优先** - 优化 Widget 重建和内存使用
13
+
14
+ ## Widget 设计
15
+
16
+ ### StatelessWidget vs StatefulWidget
17
+
18
+ ```dart
19
+ // ✅ 好 - 无状态 Widget,不可变
20
+ class UserAvatar extends StatelessWidget {
21
+ const UserAvatar({
22
+ super.key,
23
+ required this.imageUrl,
24
+ this.size = 40,
25
+ });
26
+
27
+ final String imageUrl;
28
+ final double size;
29
+
30
+ @override
31
+ Widget build(BuildContext context) {
32
+ return CircleAvatar(
33
+ radius: size / 2,
34
+ backgroundImage: NetworkImage(imageUrl),
35
+ );
36
+ }
37
+ }
38
+
39
+ // ✅ 好 - 有状态 Widget,状态清晰
40
+ class Counter extends StatefulWidget {
41
+ const Counter({super.key});
42
+
43
+ @override
44
+ State<Counter> createState() => _CounterState();
45
+ }
46
+
47
+ class _CounterState extends State<Counter> {
48
+ int _count = 0;
49
+
50
+ void _increment() {
51
+ setState(() {
52
+ _count++;
53
+ });
54
+ }
55
+
56
+ @override
57
+ Widget build(BuildContext context) {
58
+ return Column(
59
+ children: [
60
+ Text('Count: $_count'),
61
+ ElevatedButton(
62
+ onPressed: _increment,
63
+ child: const Text('Increment'),
64
+ ),
65
+ ],
66
+ );
67
+ }
68
+ }
69
+
70
+ // ❌ 坏 - 不必要的 StatefulWidget
71
+ class UserAvatar extends StatefulWidget {
72
+ const UserAvatar({super.key, required this.imageUrl});
73
+
74
+ final String imageUrl;
75
+
76
+ @override
77
+ State<UserAvatar> createState() => _UserAvatarState();
78
+ }
79
+
80
+ class _UserAvatarState extends State<UserAvatar> {
81
+ @override
82
+ Widget build(BuildContext context) {
83
+ return CircleAvatar(
84
+ backgroundImage: NetworkImage(widget.imageUrl),
85
+ );
86
+ }
87
+ }
88
+ ```
89
+
90
+ ### Widget 构造函数
91
+
92
+ ```dart
93
+ // ✅ 好 - 构造函数在最前,使用 const
94
+ class ProductCard extends StatelessWidget {
95
+ // 1. 默认构造函数首先
96
+ const ProductCard({
97
+ super.key,
98
+ required this.product,
99
+ this.onTap,
100
+ });
101
+
102
+ // 2. 命名构造函数
103
+ const ProductCard.compact({
104
+ super.key,
105
+ required this.product,
106
+ }) : onTap = null;
107
+
108
+ // 3. 字段
109
+ final Product product;
110
+ final VoidCallback? onTap;
111
+
112
+ // 4. build 方法
113
+ @override
114
+ Widget build(BuildContext context) {
115
+ return Card(
116
+ child: InkWell(
117
+ onTap: onTap,
118
+ child: Column(
119
+ children: [
120
+ Image.network(product.imageUrl),
121
+ Text(product.name),
122
+ Text('\$${product.price}'),
123
+ ],
124
+ ),
125
+ ),
126
+ );
127
+ }
128
+ }
129
+ ```
130
+
131
+ ### Widget 组合
132
+
133
+ ```dart
134
+ // ✅ 好 - 将大 Widget 拆分成小的可复用组件
135
+ class ProductListItem extends StatelessWidget {
136
+ const ProductListItem({super.key, required this.product});
137
+
138
+ final Product product;
139
+
140
+ @override
141
+ Widget build(BuildContext context) {
142
+ return Card(
143
+ child: Row(
144
+ children: [
145
+ _ProductImage(imageUrl: product.imageUrl),
146
+ Expanded(
147
+ child: _ProductInfo(product: product),
148
+ ),
149
+ _ProductActions(product: product),
150
+ ],
151
+ ),
152
+ );
153
+ }
154
+ }
155
+
156
+ class _ProductImage extends StatelessWidget {
157
+ const _ProductImage({required this.imageUrl});
158
+
159
+ final String imageUrl;
160
+
161
+ @override
162
+ Widget build(BuildContext context) {
163
+ return Image.network(
164
+ imageUrl,
165
+ width: 80,
166
+ height: 80,
167
+ fit: BoxFit.cover,
168
+ );
169
+ }
170
+ }
171
+
172
+ class _ProductInfo extends StatelessWidget {
173
+ const _ProductInfo({required this.product});
174
+
175
+ final Product product;
176
+
177
+ @override
178
+ Widget build(BuildContext context) {
179
+ return Padding(
180
+ padding: const EdgeInsets.all(8),
181
+ child: Column(
182
+ crossAxisAlignment: CrossAxisAlignment.start,
183
+ children: [
184
+ Text(
185
+ product.name,
186
+ style: Theme.of(context).textTheme.titleMedium,
187
+ ),
188
+ const SizedBox(height: 4),
189
+ Text(
190
+ product.description,
191
+ maxLines: 2,
192
+ overflow: TextOverflow.ellipsis,
193
+ ),
194
+ ],
195
+ ),
196
+ );
197
+ }
198
+ }
199
+
200
+ // ❌ 坏 - 单一巨型 Widget
201
+ class ProductListItem extends StatelessWidget {
202
+ const ProductListItem({super.key, required this.product});
203
+
204
+ final Product product;
205
+
206
+ @override
207
+ Widget build(BuildContext context) {
208
+ return Card(
209
+ child: Row(
210
+ children: [
211
+ Image.network(
212
+ product.imageUrl,
213
+ width: 80,
214
+ height: 80,
215
+ fit: BoxFit.cover,
216
+ ),
217
+ Expanded(
218
+ child: Padding(
219
+ padding: const EdgeInsets.all(8),
220
+ child: Column(
221
+ crossAxisAlignment: CrossAxisAlignment.start,
222
+ children: [
223
+ Text(
224
+ product.name,
225
+ style: Theme.of(context).textTheme.titleMedium,
226
+ ),
227
+ const SizedBox(height: 4),
228
+ Text(
229
+ product.description,
230
+ maxLines: 2,
231
+ overflow: TextOverflow.ellipsis,
232
+ ),
233
+ // ... 更多嵌套代码
234
+ ],
235
+ ),
236
+ ),
237
+ ),
238
+ // ... 更多代码
239
+ ],
240
+ ),
241
+ );
242
+ }
243
+ }
244
+ ```
245
+
246
+ ## 状态管理
247
+
248
+ ### 瞬时状态 (Ephemeral State)
249
+
250
+ ```dart
251
+ // ✅ 好 - 使用 setState 管理局部状态
252
+ class TabContainer extends StatefulWidget {
253
+ const TabContainer({super.key});
254
+
255
+ @override
256
+ State<TabContainer> createState() => _TabContainerState();
257
+ }
258
+
259
+ class _TabContainerState extends State<TabContainer> {
260
+ int _selectedIndex = 0;
261
+
262
+ @override
263
+ Widget build(BuildContext context) {
264
+ return Column(
265
+ children: [
266
+ TabBar(
267
+ currentIndex: _selectedIndex,
268
+ onTap: (index) {
269
+ setState(() {
270
+ _selectedIndex = index;
271
+ });
272
+ },
273
+ ),
274
+ IndexedStack(
275
+ index: _selectedIndex,
276
+ children: const [
277
+ HomeTab(),
278
+ ProfileTab(),
279
+ SettingsTab(),
280
+ ],
281
+ ),
282
+ ],
283
+ );
284
+ }
285
+ }
286
+ ```
287
+
288
+ ### 应用状态 (App State)
289
+
290
+ ```dart
291
+ // ✅ 好 - 使用状态管理方案(如 Provider, Riverpod, Bloc)
292
+
293
+ // 使用 Provider 示例
294
+ class CartProvider extends ChangeNotifier {
295
+ final List<Product> _items = [];
296
+
297
+ List<Product> get items => List.unmodifiable(_items);
298
+
299
+ int get itemCount => _items.length;
300
+
301
+ double get totalPrice =>
302
+ _items.fold(0, (sum, item) => sum + item.price);
303
+
304
+ void addItem(Product product) {
305
+ _items.add(product);
306
+ notifyListeners();
307
+ }
308
+
309
+ void removeItem(Product product) {
310
+ _items.remove(product);
311
+ notifyListeners();
312
+ }
313
+
314
+ void clear() {
315
+ _items.clear();
316
+ notifyListeners();
317
+ }
318
+ }
319
+
320
+ // 在 Widget 中使用
321
+ class CartButton extends StatelessWidget {
322
+ const CartButton({super.key});
323
+
324
+ @override
325
+ Widget build(BuildContext context) {
326
+ final itemCount = context.watch<CartProvider>().itemCount;
327
+
328
+ return Badge(
329
+ label: Text('$itemCount'),
330
+ child: IconButton(
331
+ icon: const Icon(Icons.shopping_cart),
332
+ onPressed: () => Navigator.pushNamed(context, '/cart'),
333
+ ),
334
+ );
335
+ }
336
+ }
337
+ ```
338
+
339
+ ## 布局最佳实践
340
+
341
+ ### 响应式布局
342
+
343
+ ```dart
344
+ // ✅ 好 - 使用 LayoutBuilder 创建响应式布局
345
+ class ResponsiveLayout extends StatelessWidget {
346
+ const ResponsiveLayout({super.key, required this.child});
347
+
348
+ final Widget child;
349
+
350
+ @override
351
+ Widget build(BuildContext context) {
352
+ return LayoutBuilder(
353
+ builder: (context, constraints) {
354
+ if (constraints.maxWidth > 840) {
355
+ return _DesktopLayout(child: child);
356
+ } else if (constraints.maxWidth > 600) {
357
+ return _TabletLayout(child: child);
358
+ } else {
359
+ return _MobileLayout(child: child);
360
+ }
361
+ },
362
+ );
363
+ }
364
+ }
365
+
366
+ // ✅ 好 - 使用 MediaQuery 获取屏幕信息
367
+ class AdaptiveCard extends StatelessWidget {
368
+ const AdaptiveCard({super.key});
369
+
370
+ @override
371
+ Widget build(BuildContext context) {
372
+ final size = MediaQuery.sizeOf(context);
373
+ final isSmallScreen = size.width < 600;
374
+
375
+ return Card(
376
+ child: Padding(
377
+ padding: EdgeInsets.all(isSmallScreen ? 8 : 16),
378
+ child: Column(
379
+ children: [
380
+ if (!isSmallScreen) const Header(),
381
+ const Content(),
382
+ ],
383
+ ),
384
+ ),
385
+ );
386
+ }
387
+ }
388
+ ```
389
+
390
+ ### 避免溢出
391
+
392
+ ```dart
393
+ // ✅ 好 - 使用 Flexible/Expanded 避免溢出
394
+ class UserInfo extends StatelessWidget {
395
+ const UserInfo({super.key, required this.user});
396
+
397
+ final User user;
398
+
399
+ @override
400
+ Widget build(BuildContext context) {
401
+ return Row(
402
+ children: [
403
+ const CircleAvatar(radius: 24),
404
+ const SizedBox(width: 8),
405
+ Expanded( // 防止文本溢出
406
+ child: Column(
407
+ crossAxisAlignment: CrossAxisAlignment.start,
408
+ children: [
409
+ Text(
410
+ user.name,
411
+ overflow: TextOverflow.ellipsis,
412
+ maxLines: 1,
413
+ ),
414
+ Text(
415
+ user.email,
416
+ overflow: TextOverflow.ellipsis,
417
+ maxLines: 1,
418
+ style: Theme.of(context).textTheme.bodySmall,
419
+ ),
420
+ ],
421
+ ),
422
+ ),
423
+ ],
424
+ );
425
+ }
426
+ }
427
+
428
+ // ❌ 坏 - 可能导致溢出
429
+ class UserInfo extends StatelessWidget {
430
+ const UserInfo({super.key, required this.user});
431
+
432
+ final User user;
433
+
434
+ @override
435
+ Widget build(BuildContext context) {
436
+ return Row(
437
+ children: [
438
+ const CircleAvatar(radius: 24),
439
+ const SizedBox(width: 8),
440
+ Column( // 没有限制宽度!
441
+ crossAxisAlignment: CrossAxisAlignment.start,
442
+ children: [
443
+ Text(user.name), // 可能溢出
444
+ Text(user.email),
445
+ ],
446
+ ),
447
+ ],
448
+ );
449
+ }
450
+ }
451
+ ```
452
+
453
+ ## 主题和样式
454
+
455
+ ### 使用 ThemeData
456
+
457
+ ```dart
458
+ // ✅ 好 - 定义完整的主题
459
+ class MyApp extends StatelessWidget {
460
+ const MyApp({super.key});
461
+
462
+ @override
463
+ Widget build(BuildContext context) {
464
+ return MaterialApp(
465
+ title: 'My App',
466
+ theme: ThemeData(
467
+ useMaterial3: true,
468
+ colorScheme: ColorScheme.fromSeed(
469
+ seedColor: Colors.blue,
470
+ brightness: Brightness.light,
471
+ ),
472
+ textTheme: const TextTheme(
473
+ displayLarge: TextStyle(
474
+ fontSize: 57,
475
+ fontWeight: FontWeight.bold,
476
+ ),
477
+ titleLarge: TextStyle(
478
+ fontSize: 22,
479
+ fontWeight: FontWeight.w600,
480
+ ),
481
+ bodyLarge: TextStyle(
482
+ fontSize: 16,
483
+ height: 1.5,
484
+ ),
485
+ ),
486
+ cardTheme: CardTheme(
487
+ elevation: 2,
488
+ shape: RoundedRectangleBorder(
489
+ borderRadius: BorderRadius.circular(12),
490
+ ),
491
+ ),
492
+ ),
493
+ home: const HomePage(),
494
+ );
495
+ }
496
+ }
497
+
498
+ // ✅ 好 - 使用主题值
499
+ class MyButton extends StatelessWidget {
500
+ const MyButton({super.key, required this.label});
501
+
502
+ final String label;
503
+
504
+ @override
505
+ Widget build(BuildContext context) {
506
+ final theme = Theme.of(context);
507
+
508
+ return ElevatedButton(
509
+ style: ElevatedButton.styleFrom(
510
+ backgroundColor: theme.colorScheme.primary,
511
+ foregroundColor: theme.colorScheme.onPrimary,
512
+ ),
513
+ onPressed: () {},
514
+ child: Text(label),
515
+ );
516
+ }
517
+ }
518
+
519
+ // ❌ 坏 - 硬编码颜色
520
+ class MyButton extends StatelessWidget {
521
+ const MyButton({super.key, required this.label});
522
+
523
+ final String label;
524
+
525
+ @override
526
+ Widget build(BuildContext context) {
527
+ return ElevatedButton(
528
+ style: ElevatedButton.styleFrom(
529
+ backgroundColor: Colors.blue, // 硬编码!
530
+ foregroundColor: Colors.white,
531
+ ),
532
+ onPressed: () {},
533
+ child: Text(label),
534
+ );
535
+ }
536
+ }
537
+ ```
538
+
539
+ ### ThemeExtension 扩展主题
540
+
541
+ ```dart
542
+ // ✅ 好 - 使用 ThemeExtension 添加自定义主题
543
+ @immutable
544
+ class CustomColors extends ThemeExtension<CustomColors> {
545
+ const CustomColors({
546
+ required this.success,
547
+ required this.warning,
548
+ required this.danger,
549
+ });
550
+
551
+ final Color success;
552
+ final Color warning;
553
+ final Color danger;
554
+
555
+ @override
556
+ CustomColors copyWith({
557
+ Color? success,
558
+ Color? warning,
559
+ Color? danger,
560
+ }) {
561
+ return CustomColors(
562
+ success: success ?? this.success,
563
+ warning: warning ?? this.warning,
564
+ danger: danger ?? this.danger,
565
+ );
566
+ }
567
+
568
+ @override
569
+ CustomColors lerp(CustomColors? other, double t) {
570
+ if (other is! CustomColors) return this;
571
+ return CustomColors(
572
+ success: Color.lerp(success, other.success, t)!,
573
+ warning: Color.lerp(warning, other.warning, t)!,
574
+ danger: Color.lerp(danger, other.danger, t)!,
575
+ );
576
+ }
577
+ }
578
+
579
+ // 在主题中使用
580
+ ThemeData(
581
+ extensions: [
582
+ CustomColors(
583
+ success: Colors.green,
584
+ warning: Colors.orange,
585
+ danger: Colors.red,
586
+ ),
587
+ ],
588
+ )
589
+
590
+ // 访问自定义主题
591
+ final customColors = Theme.of(context).extension<CustomColors>()!;
592
+ ```
593
+
594
+ ## 导航
595
+
596
+ ### 使用现代路由
597
+
598
+ ```dart
599
+ // ✅ 好 - 使用 go_router 或 auto_route
600
+ import 'package:go_router/go_router.dart';
601
+
602
+ final router = GoRouter(
603
+ routes: [
604
+ GoRoute(
605
+ path: '/',
606
+ builder: (context, state) => const HomePage(),
607
+ routes: [
608
+ GoRoute(
609
+ path: 'profile/:userId',
610
+ builder: (context, state) {
611
+ final userId = state.pathParameters['userId']!;
612
+ return ProfilePage(userId: userId);
613
+ },
614
+ ),
615
+ GoRoute(
616
+ path: 'settings',
617
+ builder: (context, state) => const SettingsPage(),
618
+ ),
619
+ ],
620
+ ),
621
+ ],
622
+ );
623
+
624
+ // 导航
625
+ context.go('/profile/123');
626
+ context.push('/settings');
627
+
628
+ // ❌ 坏 - 过时的命名路由
629
+ MaterialApp(
630
+ routes: {
631
+ '/': (context) => const HomePage(),
632
+ '/profile': (context) => const ProfilePage(),
633
+ },
634
+ )
635
+ ```
636
+
637
+ ## 性能优化
638
+
639
+ ### 避免不必要的重建
640
+
641
+ ```dart
642
+ // ✅ 好 - 使用 const 构造函数
643
+ class MyWidget extends StatelessWidget {
644
+ const MyWidget({super.key});
645
+
646
+ @override
647
+ Widget build(BuildContext context) {
648
+ return const Column(
649
+ children: [
650
+ Text('Static Text'), // const Widget 不会重建
651
+ Icon(Icons.home),
652
+ ],
653
+ );
654
+ }
655
+ }
656
+
657
+ // ✅ 好 - 提取子 Widget
658
+ class ParentWidget extends StatefulWidget {
659
+ const ParentWidget({super.key});
660
+
661
+ @override
662
+ State<ParentWidget> createState() => _ParentWidgetState();
663
+ }
664
+
665
+ class _ParentWidgetState extends State<ParentWidget> {
666
+ int _counter = 0;
667
+
668
+ @override
669
+ Widget build(BuildContext context) {
670
+ return Column(
671
+ children: [
672
+ Text('Counter: $_counter'),
673
+ ElevatedButton(
674
+ onPressed: () => setState(() => _counter++),
675
+ child: const Text('Increment'),
676
+ ),
677
+ const ExpensiveWidget(), // 不会随 counter 变化而重建
678
+ ],
679
+ );
680
+ }
681
+ }
682
+
683
+ class ExpensiveWidget extends StatelessWidget {
684
+ const ExpensiveWidget({super.key});
685
+
686
+ @override
687
+ Widget build(BuildContext context) {
688
+ // 昂贵的构建逻辑
689
+ return const Text('Expensive Widget');
690
+ }
691
+ }
692
+ ```
693
+
694
+ ### 列表性能
695
+
696
+ ```dart
697
+ // ✅ 好 - 使用 ListView.builder 处理长列表
698
+ class ProductList extends StatelessWidget {
699
+ const ProductList({super.key, required this.products});
700
+
701
+ final List<Product> products;
702
+
703
+ @override
704
+ Widget build(BuildContext context) {
705
+ return ListView.builder(
706
+ itemCount: products.length,
707
+ itemBuilder: (context, index) {
708
+ final product = products[index];
709
+ return ProductListItem(
710
+ key: ValueKey(product.id), // 使用唯一 key
711
+ product: product,
712
+ );
713
+ },
714
+ );
715
+ }
716
+ }
717
+
718
+ // ✅ 好 - 使用 ListView.separated 添加分隔符
719
+ ListView.separated(
720
+ itemCount: items.length,
721
+ itemBuilder: (context, index) => ListTile(title: Text(items[index])),
722
+ separatorBuilder: (context, index) => const Divider(),
723
+ )
724
+
725
+ // ❌ 坏 - 一次性构建所有项目
726
+ ListView(
727
+ children: products.map((p) => ProductListItem(product: p)).toList(),
728
+ )
729
+ ```
730
+
731
+ ### 图片优化
732
+
733
+ ```dart
734
+ // ✅ 好 - 使用 cached_network_image
735
+ import 'package:cached_network_image/cached_network_image.dart';
736
+
737
+ class ProductImage extends StatelessWidget {
738
+ const ProductImage({super.key, required this.imageUrl});
739
+
740
+ final String imageUrl;
741
+
742
+ @override
743
+ Widget build(BuildContext context) {
744
+ return CachedNetworkImage(
745
+ imageUrl: imageUrl,
746
+ placeholder: (context, url) =>
747
+ const Center(child: CircularProgressIndicator()),
748
+ errorWidget: (context, url, error) =>
749
+ const Icon(Icons.error),
750
+ fit: BoxFit.cover,
751
+ );
752
+ }
753
+ }
754
+
755
+ // ✅ 好 - 优化图片加载
756
+ Image.network(
757
+ imageUrl,
758
+ cacheWidth: 400, // 限制缓存图片宽度
759
+ cacheHeight: 400,
760
+ fit: BoxFit.cover,
761
+ )
762
+ ```
763
+
764
+ ## 测试
765
+
766
+ ### Widget 测试
767
+
768
+ ```dart
769
+ // ✅ 好 - 编写 Widget 测试
770
+ void main() {
771
+ testWidgets('Counter increments', (tester) async {
772
+ // Arrange
773
+ await tester.pumpWidget(const MaterialApp(home: Counter()));
774
+
775
+ // Assert initial state
776
+ expect(find.text('0'), findsOneWidget);
777
+ expect(find.text('1'), findsNothing);
778
+
779
+ // Act
780
+ await tester.tap(find.byIcon(Icons.add));
781
+ await tester.pump();
782
+
783
+ // Assert
784
+ expect(find.text('0'), findsNothing);
785
+ expect(find.text('1'), findsOneWidget);
786
+ });
787
+
788
+ testWidgets('Product card displays correctly', (tester) async {
789
+ // Arrange
790
+ const product = Product(
791
+ id: '1',
792
+ name: 'Test Product',
793
+ price: 99.99,
794
+ );
795
+
796
+ await tester.pumpWidget(
797
+ const MaterialApp(
798
+ home: Scaffold(
799
+ body: ProductCard(product: product),
800
+ ),
801
+ ),
802
+ );
803
+
804
+ // Assert
805
+ expect(find.text('Test Product'), findsOneWidget);
806
+ expect(find.text('\$99.99'), findsOneWidget);
807
+ });
808
+ }
809
+ ```
810
+
811
+ ### 集成测试
812
+
813
+ ```dart
814
+ // ✅ 好 - 编写集成测试
815
+ import 'package:integration_test/integration_test.dart';
816
+
817
+ void main() {
818
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
819
+
820
+ testWidgets('Complete purchase flow', (tester) async {
821
+ // 启动应用
822
+ await tester.pumpWidget(const MyApp());
823
+ await tester.pumpAndSettle();
824
+
825
+ // 浏览商品
826
+ expect(find.text('Products'), findsOneWidget);
827
+ await tester.tap(find.text('Add to Cart').first);
828
+ await tester.pumpAndSettle();
829
+
830
+ // 查看购物车
831
+ await tester.tap(find.byIcon(Icons.shopping_cart));
832
+ await tester.pumpAndSettle();
833
+
834
+ // 结账
835
+ await tester.tap(find.text('Checkout'));
836
+ await tester.pumpAndSettle();
837
+
838
+ // 验证
839
+ expect(find.text('Order Confirmed'), findsOneWidget);
840
+ });
841
+ }
842
+ ```
843
+
844
+ ## 国际化 (i18n)
845
+
846
+ ### 使用 intl 包
847
+
848
+ ```dart
849
+ // ✅ 好 - 正确的国际化实现
850
+ import 'package:flutter_localizations/flutter_localizations.dart';
851
+ import 'package:intl/intl.dart';
852
+
853
+ class AppLocalizations {
854
+ final Locale locale;
855
+
856
+ AppLocalizations(this.locale);
857
+
858
+ static AppLocalizations of(BuildContext context) {
859
+ return Localizations.of<AppLocalizations>(context, AppLocalizations)!;
860
+ }
861
+
862
+ static const LocalizationsDelegate<AppLocalizations> delegate =
863
+ _AppLocalizationsDelegate();
864
+
865
+ String get title => Intl.message(
866
+ 'My App',
867
+ name: 'title',
868
+ locale: locale.toString(),
869
+ );
870
+
871
+ String itemCount(int count) => Intl.plural(
872
+ count,
873
+ zero: 'No items',
874
+ one: '1 item',
875
+ other: '$count items',
876
+ name: 'itemCount',
877
+ args: [count],
878
+ locale: locale.toString(),
879
+ );
880
+ }
881
+
882
+ // 在 MaterialApp 中配置
883
+ MaterialApp(
884
+ localizationsDelegates: const [
885
+ AppLocalizations.delegate,
886
+ GlobalMaterialLocalizations.delegate,
887
+ GlobalWidgetsLocalizations.delegate,
888
+ ],
889
+ supportedLocales: const [
890
+ Locale('en', ''),
891
+ Locale('zh', ''),
892
+ ],
893
+ home: const HomePage(),
894
+ )
895
+
896
+ // ❌ 坏 - 硬编码文本
897
+ Text('Hello World') // 应该使用国际化
898
+ ```
899
+
900
+ ## 无障碍访问 (Accessibility)
901
+
902
+ ```dart
903
+ // ✅ 好 - 提供语义信息
904
+ Semantics(
905
+ label: '商品图片',
906
+ child: Image.network(product.imageUrl),
907
+ )
908
+
909
+ // ✅ 好 - 确保足够的对比度
910
+ Text(
911
+ 'Important Text',
912
+ style: TextStyle(
913
+ color: Colors.black, // 与白色背景对比度 21:1
914
+ fontSize: 16,
915
+ ),
916
+ )
917
+
918
+ // ✅ 好 - 合适的触摸目标大小(至少 48x48)
919
+ SizedBox(
920
+ width: 48,
921
+ height: 48,
922
+ child: IconButton(
923
+ icon: const Icon(Icons.add),
924
+ onPressed: () {},
925
+ ),
926
+ )
927
+ ```
928
+
929
+ ## 错误处理
930
+
931
+ ```dart
932
+ // ✅ 好 - 使用 ErrorWidget 自定义错误显示
933
+ void main() {
934
+ ErrorWidget.builder = (FlutterErrorDetails details) {
935
+ return Material(
936
+ child: Container(
937
+ color: Colors.red[100],
938
+ child: Center(
939
+ child: Text(
940
+ 'Error: ${details.exception}',
941
+ style: const TextStyle(color: Colors.red),
942
+ ),
943
+ ),
944
+ ),
945
+ );
946
+ };
947
+
948
+ runApp(const MyApp());
949
+ }
950
+
951
+ // ✅ 好 - 使用 FutureBuilder 处理异步
952
+ class UserProfile extends StatelessWidget {
953
+ const UserProfile({super.key, required this.userId});
954
+
955
+ final String userId;
956
+
957
+ @override
958
+ Widget build(BuildContext context) {
959
+ return FutureBuilder<User>(
960
+ future: fetchUser(userId),
961
+ builder: (context, snapshot) {
962
+ if (snapshot.connectionState == ConnectionState.waiting) {
963
+ return const Center(child: CircularProgressIndicator());
964
+ }
965
+
966
+ if (snapshot.hasError) {
967
+ return Center(
968
+ child: Text('Error: ${snapshot.error}'),
969
+ );
970
+ }
971
+
972
+ if (!snapshot.hasData) {
973
+ return const Center(child: Text('User not found'));
974
+ }
975
+
976
+ final user = snapshot.data!;
977
+ return UserDetails(user: user);
978
+ },
979
+ );
980
+ }
981
+ }
982
+ ```
983
+
984
+ ## 最佳实践总结
985
+
986
+ 1. **优先使用 const** - 提升性能,减少重建
987
+ 2. **组合小 Widget** - 保持代码可维护性
988
+ 3. **合理使用状态管理** - 区分局部和全局状态
989
+ 4. **响应式布局** - 适配不同屏幕尺寸
990
+ 5. **使用主题系统** - 避免硬编码样式
991
+ 6. **性能优化** - 使用 builder、const、key
992
+ 7. **编写测试** - Widget 测试和集成测试
993
+ 8. **国际化支持** - 使用 i18n 工具
994
+ 9. **无障碍访问** - 添加语义信息
995
+ 10. **错误处理** - 优雅处理异步和错误状态
996
+
997
+ ---
998
+
999
+ ## 📝 TextField 垂直居中规范(重要)
1000
+
1001
+ > ⚠️ **此问题曾导致 15+ 轮对话才修复,必须一步到位**
1002
+
1003
+ ### 问题场景
1004
+ TextField 中 placeholder、光标、输入内容三者需要在固定高度容器中垂直居中对齐。
1005
+
1006
+ ### 正确方案(一步到位)
1007
+
1008
+ ```dart
1009
+ // ✅ 正确 - Container.alignment + 统一 height + contentPadding.zero
1010
+ Widget _buildCenteredTextField(String placeholder, TextEditingController controller) {
1011
+ return Container(
1012
+ height: 36, // 固定容器高度
1013
+ alignment: Alignment.center, // 关键1:让 TextField 整体居中
1014
+ child: TextField(
1015
+ controller: controller,
1016
+ textAlign: TextAlign.center,
1017
+ style: const TextStyle(
1018
+ fontSize: 14,
1019
+ height: 1.43, // 关键2:行高 = 期望文本高度 ÷ 字号
1020
+ ),
1021
+ decoration: InputDecoration(
1022
+ hintText: placeholder,
1023
+ hintStyle: const TextStyle(
1024
+ fontSize: 14,
1025
+ height: 1.43, // 关键3:必须与 style.height 完全一致
1026
+ ),
1027
+ contentPadding: EdgeInsets.zero, // 关键4:清除默认 padding
1028
+ isDense: true, // 关键5:移除额外空间
1029
+ border: InputBorder.none,
1030
+ ),
1031
+ ),
1032
+ );
1033
+ }
1034
+
1035
+ // ❌ 错误 - 会导致 placeholder 和输入内容位置不一致
1036
+ TextField(
1037
+ textAlignVertical: TextAlignVertical.center, // 只影响输入内容
1038
+ style: TextStyle(fontSize: 14, height: 1.43),
1039
+ decoration: InputDecoration(
1040
+ hintStyle: TextStyle(fontSize: 14), // height 不一致!
1041
+ contentPadding: EdgeInsets.symmetric(vertical: 8), // 干扰居中
1042
+ ),
1043
+ )
1044
+ ```
1045
+
1046
+ ### 行高计算公式
1047
+
1048
+ 从设计稿获取:
1049
+ - 容器高度:36px
1050
+ - 文本 Y 坐标:8px(距顶部)
1051
+ - 文本高度:20px
1052
+ - 字号:14px
1053
+
1054
+ ```
1055
+ height = 文本高度 ÷ 字号 = 20 ÷ 14 = 1.43
1056
+ ```
1057
+
1058
+ ### 禁止事项
1059
+
1060
+ | 禁止 | 原因 |
1061
+ |------|------|
1062
+ | `style.height` ≠ `hintStyle.height` | placeholder 和输入内容位置不一致 |
1063
+ | 同时用 `textAlignVertical` 和 `height` | 产生冲突,效果不可预测 |
1064
+ | 用 `strutStyle + forceStrutHeight` | 可能压缩文字 |
1065
+ | 反复调整 `contentPadding` 试错 | 应先确定行高配置 |
1066
+
1067
+ ### 调试顺序(遇到问题时)
1068
+
1069
+ 1. 先从设计稿获取:容器高度、文本 Y 坐标、文本高度
1070
+ 2. 计算 `height = 文本高度 ÷ 字号`
1071
+ 3. 确保 `style.height` = `hintStyle.height`
1072
+ 4. 设置 `Container.alignment: Alignment.center`
1073
+ 5. 设置 `contentPadding: EdgeInsets.zero` + `isDense: true`
1074
+
1075
+ ---
1076
+
1077
+ ## 🎨 Sketch/Figma 设计稿还原规范
1078
+
1079
+ > ⚠️ **此章节为强制执行规范** - 所有 UI 还原任务必须严格遵循
1080
+
1081
+ ### 问题根源分析
1082
+
1083
+ 过去还原设计稿时存在以下问题导致效率低下:
1084
+
1085
+ | 问题 | 表现 | 根因 |
1086
+ |------|------|------|
1087
+ | 属性读取不完整 | 漏读渐变、圆角、阴影参数 | 只读取部分属性 |
1088
+ | 假设而非验证 | 假设圆形/颜色/图标 | 未从设计稿验证 |
1089
+ | 使用近似值 | 用 Material Icons 代替 | 未导出原始 SVG |
1090
+ | 分散查询 | 多轮对话才获取完整信息 | 每次只查一个属性 |
1091
+
1092
+ ### 强制执行:一次性完整提取
1093
+
1094
+ **在还原任何 UI 元素前,必须一次性提取所有属性(Sketch 示例):**
1095
+
1096
+ ```javascript
1097
+ // 完整样式提取脚本
1098
+ const sketch = require('sketch');
1099
+ const page = sketch.getSelectedDocument().selectedPage;
1100
+
1101
+ function extractFullStyle(layerName) {
1102
+ const layer = sketch.find(`[name="${layerName}"]`, page)[0];
1103
+ if (!layer) return console.log(`Layer "${layerName}" not found`);
1104
+
1105
+ console.log('=== 基本信息 ===');
1106
+ console.log(`Name: ${layer.name} (${layer.type})`);
1107
+ console.log(`Frame: ${layer.frame.width}x${layer.frame.height}`);
1108
+
1109
+ const style = layer.style;
1110
+
1111
+ // 1. 填充(颜色/渐变)
1112
+ console.log('=== 填充 ===');
1113
+ (style.fills || []).filter(f => f.enabled).forEach((fill, i) => {
1114
+ console.log(`Fill ${i}: Type=${fill.fillType}`);
1115
+ if (fill.fillType === 'Color') {
1116
+ console.log(` Color: ${fill.color}`);
1117
+ } else if (fill.fillType === 'Gradient') {
1118
+ console.log(` Gradient: ${fill.gradient.gradientType}`);
1119
+ fill.gradient.stops.forEach((stop, j) => {
1120
+ console.log(` Stop ${j}: ${stop.color} @ ${stop.position}`);
1121
+ });
1122
+ }
1123
+ });
1124
+
1125
+ // 2. 阴影
1126
+ console.log('=== 阴影 ===');
1127
+ (style.shadows || []).filter(s => s.enabled).forEach((s, i) => {
1128
+ console.log(`Shadow ${i}: Color=${s.color}, Offset=(${s.x}, ${s.y}), Blur=${s.blur}, Spread=${s.spread}`);
1129
+ });
1130
+
1131
+ // 3. 内阴影
1132
+ (style.innerShadows || []).filter(s => s.enabled).forEach((s, i) => {
1133
+ console.log(`InnerShadow ${i}: Color=${s.color}, Offset=(${s.x}, ${s.y}), Blur=${s.blur}, Spread=${s.spread}`);
1134
+ });
1135
+
1136
+ // 4. 边框
1137
+ console.log('=== 边框 ===');
1138
+ (style.borders || []).filter(b => b.enabled).forEach((b, i) => {
1139
+ console.log(`Border ${i}: Color=${b.color}, Width=${b.thickness}`);
1140
+ });
1141
+ }
1142
+
1143
+ extractFullStyle('Layer Name');
1144
+ ```
1145
+
1146
+ ### SVG 图标还原规范
1147
+
1148
+ > ⚠️ **禁止使用 Material Icons 或其他近似图标,必须从设计稿导出原始 SVG**
1149
+
1150
+ #### SVG 导出规范
1151
+
1152
+ **导出时保留完整 viewBox 和坐标**:
1153
+
1154
+ ```javascript
1155
+ // 从 Sketch 导出 SVG
1156
+ const sketch = require('sketch');
1157
+ const layer = sketch.find('[name="Icon Name"]', sketch.getSelectedDocument().selectedPage)[0];
1158
+ if (layer) {
1159
+ sketch.export(layer, {
1160
+ formats: 'svg',
1161
+ output: '/path/to/assets/icons/'
1162
+ });
1163
+ }
1164
+ ```
1165
+
1166
+ ```xml
1167
+ <!-- ❌ 错误 - 导出最小 viewBox -->
1168
+ <!-- viewBox="0 0 6 3" 放在 12x12 容器中需要额外居中处理 -->
1169
+
1170
+ <!-- ✅ 正确 - 导出完整容器 viewBox -->
1171
+ <svg viewBox="0 0 12 12">
1172
+ <!-- 保留元素在容器中的精确位置 -->
1173
+ <polygon fill="#1C2B45" fill-opacity="0.7" points="3.5 5 6 7.5 8.5 5"/>
1174
+ </svg>
1175
+ ```
1176
+
1177
+ #### SVG 使用规范
1178
+
1179
+ ```dart
1180
+ // ❌ 错误 - 强制覆盖颜色(会丢失透明度)
1181
+ SvgPicture.asset(
1182
+ 'assets/icons/dropdown_arrow.svg',
1183
+ colorFilter: ColorFilter.mode(
1184
+ someColor, // 覆盖了 SVG 原有颜色
1185
+ BlendMode.srcIn, // 覆盖了 SVG 原有透明度
1186
+ ),
1187
+ )
1188
+
1189
+ // ✅ 正确 - 保留 SVG 原有样式
1190
+ SvgPicture.asset(
1191
+ 'assets/icons/dropdown_arrow.svg',
1192
+ width: 12,
1193
+ height: 12,
1194
+ // 不使用 colorFilter,保留 SVG 原有颜色和透明度
1195
+ // 仅在外部明确指定颜色时才覆盖
1196
+ colorFilter: customColor != null
1197
+ ? ColorFilter.mode(customColor, BlendMode.srcIn)
1198
+ : null,
1199
+ )
1200
+ ```
1201
+
1202
+ #### 颜色透明度转换
1203
+
1204
+ 设计稿颜色格式:`#RRGGBBAA`(最后两位是透明度)
1205
+
1206
+ ```
1207
+ Sketch: #1c2b45b3 → R:28 G:43 B:69 A:70%
1208
+ Flutter: Color(0xB31C2B45) 或 SVG fill-opacity="0.7"
1209
+ ```
1210
+
1211
+ 常用透明度对照:
1212
+
1213
+ | 百分比 | Hex | 示例 |
1214
+ |--------|-----|------|
1215
+ | 100% | FF | #FFFFFFFF |
1216
+ | 70% | B3 | #1C2B45B3 |
1217
+ | 50% | 80 | #00000080 |
1218
+ | 15% | 26 | #1C2B4526 |
1219
+
1220
+ ### 问题速查表
1221
+
1222
+ > ⚠️ **修改代码前,先检查是否属于已知问题类型**
1223
+
1224
+ | 问题特征 | 问题 ID | 快速方案 |
1225
+ |----------|---------|----------|
1226
+ | 半透明容器颜色偏暗 | #1 阴影透出 | `HollowShadowPainter` 挖空阴影 |
1227
+ | 元素位置/间距不对 | #2 布局偏移 | 固定宽度 + 精确坐标 |
1228
+ | 选中项阴影模糊一片 | #3 裁剪问题 | `clipBehavior: Clip.none` |
1229
+ | focus 时出现蓝框 | #4 边框异常 | 全局 + 组件级移除边框 |
1230
+ | 形状错误(圆形vs圆角) | #5 shape 冲突 | 检查 `shape` vs `borderRadius` |
1231
+ | Row 内 Gap 间距无效 | #6 Gap 方向错误 | `SizedBox(width:)` 或 `Gap.h()` |
1232
+ | **SVG 颜色比设计稿浅** | #7 ColorFilter 覆盖 | **移除 ColorFilter,保留 SVG 原有样式** |
1233
+ | **SVG 图标未居中** | #8 viewBox 不匹配 | **SVG viewBox 与使用尺寸一致** |
1234
+
1235
+ ### 还原检查清单
1236
+
1237
+ 在还原任何 UI 元素前,必须确认以下所有属性:
1238
+
1239
+ | 属性 | 检查项 | Flutter 对应 |
1240
+ |------|--------|--------------|
1241
+ | **尺寸** | width, height | `width`, `height` |
1242
+ | **填充类型** | Color / Gradient / Image | `color` / `gradient` / `DecorationImage` |
1243
+ | **渐变细节** | stops, from, to, type | `LinearGradient`, `RadialGradient` |
1244
+ | **圆角** | cornerRadius (4个角) | `borderRadius` / `BoxShape.circle` |
1245
+ | **阴影** | color, x, y, blur, spread | `boxShadow: [BoxShadow(...)]` |
1246
+ | **内阴影** | 同上 | 需要特殊处理(Flutter 不原生支持) |
1247
+ | **边框** | color, thickness, position | `border: Border.all(...)` |
1248
+ | **不透明度** | opacity (颜色末尾两位) | 颜色 alpha 或 `Opacity` widget |
1249
+ | **图标** | SVG path, fill color, opacity | `SvgPicture.asset` |
1250
+
1251
+ ### 禁止事项
1252
+
1253
+ 1. ❌ **禁止假设形状** - 必须从设计稿读取 `cornerRadius`
1254
+ 2. ❌ **禁止假设颜色** - 必须读取完整的 `fills` 数组
1255
+ 3. ❌ **禁止使用近似图标** - 必须导出 SVG
1256
+ 4. ❌ **禁止分散查询** - 必须一次性获取所有属性
1257
+ 5. ❌ **禁止遗漏阴影参数** - 必须读取全部 5 个参数
1258
+ 6. ❌ **禁止忽略透明度** - 颜色 `#RRGGBBAA` 最后两位是透明度
1259
+ 7. ❌ **禁止 ColorFilter 覆盖 SVG** - 除非明确需要改变颜色
1260
+
1261
+ ---
1262
+
1263
+ **参考资源:**
1264
+ - [Flutter Documentation](https://flutter.dev/docs)
1265
+ - [Flutter Style Guide](https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md)
1266
+ - [Effective Dart](https://dart.dev/effective-dart)
1267
+ - [Material Design 3](https://m3.material.io/)
1268
+ - [Flutter Performance Best Practices](https://flutter.dev/docs/perf/best-practices)