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.
- package/README.md +60 -0
- package/dist/chunk-FOMWV2YP.js +378 -0
- package/dist/chunk-SW6YDKXI.js +112 -0
- package/dist/factory-6DDXZYQP.js +6 -0
- package/dist/index.cjs +4668 -0
- package/dist/index.d.cts +644 -0
- package/dist/index.d.ts +644 -0
- package/dist/index.js +4037 -0
- package/dist/upgrade_snippets-XFR7Q444.js +8 -0
- package/locales/en-US.json +59 -0
- package/locales/zh-CN.json +59 -0
- package/package.json +52 -0
- package/templates/README.md +129 -0
- package/templates/core_files/base/base_list_page.dart.template +225 -0
- package/templates/core_files/base/base_list_viewmodel.dart.template +164 -0
- package/templates/core_files/base/base_page.dart.template +252 -0
- package/templates/core_files/base/base_viewmodel.dart.template +68 -0
- package/templates/core_files/base/index.dart.template +5 -0
- package/templates/core_files/config/app_config.dart.template +142 -0
- package/templates/core_files/config/app_initializer.dart.template +74 -0
- package/templates/core_files/config/index.dart.template +3 -0
- package/templates/core_files/index.dart.template +8 -0
- package/templates/core_files/network/README.md +378 -0
- package/templates/core_files/network/app_error_code.dart.template +49 -0
- package/templates/core_files/network/app_http.dart.template +306 -0
- package/templates/core_files/network/app_response.dart.template +81 -0
- package/templates/core_files/network/index.dart.template +12 -0
- package/templates/core_files/network/interceptors/app_response_interceptor.dart.template +44 -0
- package/templates/core_files/network/interceptors/auth_interceptor.dart.template +30 -0
- package/templates/core_files/network/interceptors/error_interceptor.dart.template +48 -0
- package/templates/core_files/network/interceptors/index.dart.template +6 -0
- package/templates/core_files/network/interceptors/log_interceptor.dart.template +97 -0
- package/templates/core_files/network/interceptors/network_error_interceptor.dart.template +58 -0
- package/templates/core_files/network/interceptors/retry_interceptor.dart.template +69 -0
- package/templates/core_files/network/response_adapter.dart.template +69 -0
- package/templates/core_files/router/app_routes.dart.template +32 -0
- package/templates/core_files/router/index.dart.template +3 -0
- package/templates/core_files/router/navigator_util_getx.dart.template +131 -0
- package/templates/core_files/router/navigator_util_material.dart.template +191 -0
- package/templates/core_files/storage/index.dart.template +3 -0
- package/templates/core_files/storage/storage_keys.dart.template +34 -0
- package/templates/core_files/storage/storage_util.dart.template +102 -0
- package/templates/core_files/theme/app_theme.dart.template +37 -0
- package/templates/core_files/theme/index.dart.template +3 -0
- package/templates/core_files/theme/status_views_theme.dart.template +40 -0
- package/templates/core_files/utils/index.dart.template +2 -0
- package/templates/core_files/utils/loading_util.dart.template +55 -0
- package/templates/core_files/utils/toast_util.dart.template +128 -0
- package/templates/examples/eg_list_page.dart.template +340 -0
- package/templates/examples/eg_list_viewmodel.dart.template +31 -0
- package/templates/examples/eg_service.dart.template +78 -0
- package/templates/examples/mock_data.dart.template +50388 -0
- package/templates/examples/tu_chong_model.dart.template +633 -0
- package/templates/request_helper.dart.template +59 -0
- package/templates/snippets/flu-cli.code-snippets +268 -0
- package/templates/snippets/flu-cli.code-snippets.backup +268 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cli.description": "🚀 Flutter MVVM Scaffolding Tool",
|
|
3
|
+
"cmd.new.desc": "Create Flutter project with real-time template preview",
|
|
4
|
+
"cmd.new.opt.template": "Template type: lite, modular, clean",
|
|
5
|
+
"cmd.new.opt.state": "State manager: default, provider, getx",
|
|
6
|
+
"cmd.new.opt.dir": "Project storage directory",
|
|
7
|
+
"cmd.new.opt.no_cache": "Do not use cache, force pull from Git",
|
|
8
|
+
"cmd.new.opt.remote": "Use remote Gitee templates (default uses local)",
|
|
9
|
+
"cmd.sync.desc": "Sync the latest standard code snippets to the current project",
|
|
10
|
+
"cmd.add.desc": "Add Flutter code component (page/p, widget/w, component/c, ...)",
|
|
11
|
+
"cmd.add.opt.feature": "Target feature module (modular/clean mode only)",
|
|
12
|
+
"cmd.add.opt.stateful": "Create StatefulWidget (StatelessWidget by default)",
|
|
13
|
+
"cmd.add.opt.stateless": "Force create StatelessWidget (even if with ViewModel, not recommended)",
|
|
14
|
+
"cmd.add.opt.list_page": "Create list page (BaseListPage)",
|
|
15
|
+
"cmd.add.opt.no_vm": "Do not generate ViewModel (page type only)",
|
|
16
|
+
"cmd.add.opt.json": "Generate from JSON file (model type only)",
|
|
17
|
+
"cmd.add.opt.list": "View supported types list",
|
|
18
|
+
"cmd.templates.desc": "View all templates or specific template details",
|
|
19
|
+
"cmd.update.desc": "Update template cache",
|
|
20
|
+
"cmd.update.opt.force": "Force refresh and clean untracked files",
|
|
21
|
+
"cmd.cache.desc": "Cache management (action: clean)",
|
|
22
|
+
"cmd.assets.desc": "Configure app icon and splash screen",
|
|
23
|
+
"cmd.template.desc": "Manage custom project templates",
|
|
24
|
+
"cmd.template.list.desc": "List all templates",
|
|
25
|
+
"cmd.template.add.desc": "Add custom template (id: unique ID, source: Git URL or local path)",
|
|
26
|
+
"cmd.template.add.opt.local": "Add local template (source is a path)",
|
|
27
|
+
"cmd.template.add.opt.name": "Display name",
|
|
28
|
+
"cmd.template.add.opt.branch": "Git branch (default is main)",
|
|
29
|
+
"cmd.template.add.opt.desc": "Description information",
|
|
30
|
+
"cmd.template.add.opt.force": "Overwrite existing template",
|
|
31
|
+
"cmd.template.remove.desc": "Delete custom template",
|
|
32
|
+
"cmd.completion.desc": "Generate Shell auto-completion script (bash/zsh)",
|
|
33
|
+
"cmd.config.desc": "Manage project generation config (.flu-cli.json)",
|
|
34
|
+
"cmd.config.init.desc": "Initialize config file (based on current project structure)",
|
|
35
|
+
"assets.intro": "🎨 Configure App Assets (Icon & Splash)",
|
|
36
|
+
"assets.setup_icon": "Do you want to set up an App Icon?",
|
|
37
|
+
"assets.icon_path": "Enter icon path (suggested 1024x1024 PNG)",
|
|
38
|
+
"assets.setup_splash": "Do you want to set up a Splash Screen?",
|
|
39
|
+
"assets.splash_logo": "Enter Splash Logo path",
|
|
40
|
+
"assets.bg_color": "Enter Splash background color (Hex)",
|
|
41
|
+
"assets.use_bg_image": "Use a background image for Splash?",
|
|
42
|
+
"assets.bg_image_path": "Enter background image path",
|
|
43
|
+
"assets.setup_dark": "Configure Splash for Dark Mode?",
|
|
44
|
+
"assets.dark_bg_color": "Enter Dark Mode background color (Hex)",
|
|
45
|
+
"assets.use_dark_logo": "Use a separate Logo for Dark Mode?",
|
|
46
|
+
"assets.dark_logo_path": "Enter Dark Mode Logo path",
|
|
47
|
+
"assets.no_assets": "No assets selected, operation ended",
|
|
48
|
+
"assets.configuring": "Configuring assets...",
|
|
49
|
+
"assets.success": "✓ Assets configured successfully",
|
|
50
|
+
"assets.done": "✨ App assets mapping completed!",
|
|
51
|
+
"assets.failed": "Failed to configure assets",
|
|
52
|
+
"assets.error_outro": "❌ Error occurred during configuration, please check logs",
|
|
53
|
+
"assets.not_flutter": "pubspec.yaml not found. Please run this command in a Flutter project root.",
|
|
54
|
+
"common.cancel": "Operation cancelled",
|
|
55
|
+
"common.bye": "Goodbye",
|
|
56
|
+
"snippets.sync_success": "✅ Code snippets synced successfully!",
|
|
57
|
+
"snippets.detect_config": "ℹ️ Project config detected, filtering snippets...",
|
|
58
|
+
"snippets.applied_filter": "Architecture-aware filtering applied (BasePage: {basePage}, ViewModel: {viewModel})"
|
|
59
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cli.description": "🚀 Flutter MVVM 脚手架工具",
|
|
3
|
+
"cmd.new.desc": "创建 Flutter 项目(实时模板预览)",
|
|
4
|
+
"cmd.new.opt.template": "模板类型: lite, modular, clean",
|
|
5
|
+
"cmd.new.opt.state": "状态管理器: default, provider, getx",
|
|
6
|
+
"cmd.new.opt.dir": "项目存放目录",
|
|
7
|
+
"cmd.new.opt.no_cache": "不使用缓存,强制从 Git 拉取",
|
|
8
|
+
"cmd.new.opt.remote": "使用远程 Gitee 模板(默认使用本地模板)",
|
|
9
|
+
"cmd.sync.desc": "同步最新版本的标准代码片段到当前项目",
|
|
10
|
+
"cmd.add.desc": "添加 Flutter 代码组件 (page/p, widget/w, component/c, ...)",
|
|
11
|
+
"cmd.add.opt.feature": "所属功能模块(仅 modular/clean 模式)",
|
|
12
|
+
"cmd.add.opt.stateful": "创建 StatefulWidget(默认 StatelessWidget)",
|
|
13
|
+
"cmd.add.opt.stateless": "强制创建 StatelessWidget(即使有 ViewModel,不推荐)",
|
|
14
|
+
"cmd.add.opt.list_page": "创建列表页 (BaseListPage)",
|
|
15
|
+
"cmd.add.opt.no_vm": "不生成 ViewModel(仅 page 类型)",
|
|
16
|
+
"cmd.add.opt.json": "从 JSON 文件生成(仅 model 类型)",
|
|
17
|
+
"cmd.add.opt.list": "查看支持的类型列表",
|
|
18
|
+
"cmd.templates.desc": "查看所有模板或指定模板详情",
|
|
19
|
+
"cmd.update.desc": "更新模板缓存",
|
|
20
|
+
"cmd.update.opt.force": "强制刷新并清理未跟踪文件",
|
|
21
|
+
"cmd.cache.desc": "缓存管理 (action: clean)",
|
|
22
|
+
"cmd.assets.desc": "配置应用图标和启动图",
|
|
23
|
+
"cmd.template.desc": "管理自定义项目模板",
|
|
24
|
+
"cmd.template.list.desc": "列出所有模板",
|
|
25
|
+
"cmd.template.add.desc": "添加自定义模板 (id: 唯一标识, source: Git URL 或本地路径)",
|
|
26
|
+
"cmd.template.add.opt.local": "添加本地模板 (source 为路径)",
|
|
27
|
+
"cmd.template.add.opt.name": "显示名称",
|
|
28
|
+
"cmd.template.add.opt.branch": "Git 分支 (默认 main)",
|
|
29
|
+
"cmd.template.add.opt.desc": "描述信息",
|
|
30
|
+
"cmd.template.add.opt.force": "覆盖已存在的模板",
|
|
31
|
+
"cmd.template.remove.desc": "删除自定义模板",
|
|
32
|
+
"cmd.completion.desc": "生成 Shell 自动补全脚本 (bash/zsh)",
|
|
33
|
+
"cmd.config.desc": "管理项目生成配置 (.flu-cli.json)",
|
|
34
|
+
"cmd.config.init.desc": "初始化配置文件 (基于当前项目结构)",
|
|
35
|
+
"assets.intro": "🎨 配置应用资源 (图标 & 启动图)",
|
|
36
|
+
"assets.setup_icon": "是否要设置应用图标 (App Icon)?",
|
|
37
|
+
"assets.icon_path": "请输入图标路径 (建议 1024x1024 PNG)",
|
|
38
|
+
"assets.setup_splash": "是否要设置启动图 (Splash Screen)?",
|
|
39
|
+
"assets.splash_logo": "请输入启动页 Logo 路径",
|
|
40
|
+
"assets.bg_color": "请输入启动页背景颜色 (Hex)",
|
|
41
|
+
"assets.use_bg_image": "是否使用启动页背景图?",
|
|
42
|
+
"assets.bg_image_path": "请输入背景图路径",
|
|
43
|
+
"assets.setup_dark": "是否配置暗黑模式下的启动页?",
|
|
44
|
+
"assets.dark_bg_color": "请输入暗黑模式背景颜色 (Hex)",
|
|
45
|
+
"assets.use_dark_logo": "是否使用独立的暗黑模式 Logo?",
|
|
46
|
+
"assets.dark_logo_path": "请输入暗黑模式 Logo 路径",
|
|
47
|
+
"assets.no_assets": "未选择任何资源,操作结束",
|
|
48
|
+
"assets.configuring": "正在配置资源...",
|
|
49
|
+
"assets.success": "✓ 资源配置成功",
|
|
50
|
+
"assets.done": "✨ 应用资源映射完成!",
|
|
51
|
+
"assets.failed": "资源配置失败",
|
|
52
|
+
"assets.error_outro": "❌ 配置过程中出现错误,请检查日志",
|
|
53
|
+
"assets.not_flutter": "未检测到 pubspec.yaml,请在 Flutter 项目根目录下运行此命令",
|
|
54
|
+
"common.cancel": "操作已取消",
|
|
55
|
+
"common.bye": "再见",
|
|
56
|
+
"snippets.sync_success": "✅ 代码片段同步成功!",
|
|
57
|
+
"snippets.detect_config": "ℹ️ 检测到项目配置,正在按需过滤代码片段...",
|
|
58
|
+
"snippets.applied_filter": "已应用架构感知过滤 (BasePage: {basePage}, ViewModel: {viewModel})"
|
|
59
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "flu-cli-core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Core logic for flu-cli",
|
|
5
|
+
"main": "dist/index.cjs",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"templates",
|
|
12
|
+
"locales"
|
|
13
|
+
],
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"import": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"require": {
|
|
21
|
+
"types": "./dist/index.d.cts",
|
|
22
|
+
"default": "./dist/index.cjs"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
28
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"flutter",
|
|
32
|
+
"cli",
|
|
33
|
+
"core"
|
|
34
|
+
],
|
|
35
|
+
"author": "火叶工作室",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"chalk": "^4.1.2",
|
|
39
|
+
"fs-extra": "^11.2.0",
|
|
40
|
+
"handlebars": "^4.7.8",
|
|
41
|
+
"json5": "^2.2.3",
|
|
42
|
+
"simple-git": "^3.20.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/chalk": "^0.4.31",
|
|
46
|
+
"@types/fs-extra": "^11.0.4",
|
|
47
|
+
"@types/handlebars": "^4.0.40",
|
|
48
|
+
"@types/node": "^20.19.25",
|
|
49
|
+
"tsup": "^8.0.1",
|
|
50
|
+
"typescript": "^5.3.3"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Flu-CLI 模板与基础设施资源
|
|
2
|
+
|
|
3
|
+
此目录包含 CLI 核心引擎使用的 **基础资源**。在 V6.0 架构中,这些资源分为 **“骨架配套”** 与 **“高级增强”** 两部分。
|
|
4
|
+
|
|
5
|
+
## 📂 目录结构
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
packages/core/templates/
|
|
9
|
+
├── core_files/ # 🔹 高级增强 (Enrichment Modules)
|
|
10
|
+
│ ├── base/ # 统一的 MVVM 基类 (BasePage, BaseViewModel)
|
|
11
|
+
│ ├── network/ # 统一的网络封装 (Dio, ApiResponse, HttpUtil)
|
|
12
|
+
│ ├── router/ # 统一的路由工具与常量模板
|
|
13
|
+
│ ├── theme/ # 预置的状态视图主题
|
|
14
|
+
│ └── utils/ # 🔹 核心工具类 (Loading, Toast, RequestHelper)
|
|
15
|
+
│
|
|
16
|
+
├── snippets/ # 🔹 生成代码片段 (Smart Snippets)
|
|
17
|
+
│ ├── pages/ # Page 与 ViewModel 模板逻辑
|
|
18
|
+
│ ├── widgets/ # 各种 Widget 变体
|
|
19
|
+
│ └── ...
|
|
20
|
+
│
|
|
21
|
+
└── README.md # 本文档
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 🏗️ 架构总览
|
|
27
|
+
|
|
28
|
+
Flu-CLI 采用 **"双层架构"** 设计,确保所有架构风格的项目都拥有统一、高质量的基础设施。
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
┌─────────────────────────────────────────────────────────┐
|
|
32
|
+
│ 应用层(架构层 - 可变) │
|
|
33
|
+
├─────────────────────────────────────────────────────────┤
|
|
34
|
+
│ Lite: 平房(简单) │
|
|
35
|
+
│ lib/pages/ # 页面 │
|
|
36
|
+
│ lib/viewmodels/ # ViewModel │
|
|
37
|
+
│ lib/services/ # 服务 │
|
|
38
|
+
│ lib/models/ # 模型 │
|
|
39
|
+
├─────────────────────────────────────────────────────────┤
|
|
40
|
+
│ Modular: 公寓(模块化) │
|
|
41
|
+
│ lib/features/home/ │
|
|
42
|
+
│ ├── pages/ │
|
|
43
|
+
│ ├── viewmodels/ │
|
|
44
|
+
│ ├── services/ │
|
|
45
|
+
│ └── models/ │
|
|
46
|
+
├─────────────────────────────────────────────────────────┤
|
|
47
|
+
│ Clean: 摩天大楼(复杂) │
|
|
48
|
+
│ lib/features/home/ │
|
|
49
|
+
│ ├── presentation/ # 表现层 │
|
|
50
|
+
│ ├── domain/ # 领域层 │
|
|
51
|
+
│ └── data/ # 数据层 │
|
|
52
|
+
└─────────────────────────────────────────────────────────┘
|
|
53
|
+
▲
|
|
54
|
+
│ 都依赖
|
|
55
|
+
│
|
|
56
|
+
┌──────────────────────────┴──────────────────────────────┐
|
|
57
|
+
│ 基础设施层(统一维护 - 不变) │
|
|
58
|
+
├─────────────────────────────────────────────────────────┤
|
|
59
|
+
│ lib/core/ │
|
|
60
|
+
│ ├── base/ # MVVM 基础组件 │
|
|
61
|
+
│ │ ├── base_viewmodel.dart │
|
|
62
|
+
│ │ ├── base_page.dart │
|
|
63
|
+
│ │ ├── base_list_viewmodel.dart │
|
|
64
|
+
│ │ ├── base_list_page.dart │
|
|
65
|
+
│ │ └── theme/status_views_theme.dart │
|
|
66
|
+
│ │ │
|
|
67
|
+
│ ├── network/ # 网络层(可选) │
|
|
68
|
+
│ │ └── http_util.dart │
|
|
69
|
+
│ │ │
|
|
70
|
+
│ ├── router/ # 路由系统(可选) │
|
|
71
|
+
│ │ ├── app_routes.dart │
|
|
72
|
+
│ │ └── navigator_util.dart │
|
|
73
|
+
│ │ │
|
|
74
|
+
│ ├── utils/ # 工具类(零依赖) │
|
|
75
|
+
│ │ ├── loading_util.dart │
|
|
76
|
+
│ │ ├── toast_util.dart │
|
|
77
|
+
│ │ └── request_helper.dart │
|
|
78
|
+
│ │ │
|
|
79
|
+
│ ├── config/ # 应用配置 │
|
|
80
|
+
│ │ └── app_config.dart │
|
|
81
|
+
│ │ │
|
|
82
|
+
│ └── storage/ # 本地存储(可选) │
|
|
83
|
+
│ └── storage_util.dart │
|
|
84
|
+
└─────────────────────────────────────────────────────────┘
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 核心理念
|
|
88
|
+
|
|
89
|
+
- **应用层可变**:根据项目复杂度选择 Lite / Modular / Clean 架构风格
|
|
90
|
+
- **基础设施不变**:所有架构风格共享同一套高质量的 `lib/core/` 基础设施
|
|
91
|
+
- **依赖向下**:应用层依赖基础设施层,确保架构清晰
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 🚀 核心设计:Skeleton + Enrichment
|
|
96
|
+
|
|
97
|
+
从 V6.0 开始,我们采用了 **“骨架 + 插件化增强”** 的模式。
|
|
98
|
+
|
|
99
|
+
### 1. Skeleton (骨架)
|
|
100
|
+
|
|
101
|
+
即外层提供的 `template_lite`, `template_modular` 等项目基础包。它们只包含最基础的、编译通过的 Flutter 代码。
|
|
102
|
+
|
|
103
|
+
### 2. Enrichment (增强) - 位于 `core_files/`
|
|
104
|
+
|
|
105
|
+
这些是 CLI 动态注入的“水电系统”。即便是一个纯净的 Flutter 项目,通过 CLI 的 `NetworkEnrichTask`,也可以一秒钟注入我们标准化的网络层。
|
|
106
|
+
|
|
107
|
+
- **Base 层**:所有模板(Lite/Modular/Clean)使用完全相同的 `BasePage` 和 `BaseViewModel`。
|
|
108
|
+
- **Network 层**:由 CLI 任务驱动,自动处理依赖注入、代码复制与示例代码生成。
|
|
109
|
+
- **Router 层**:包含动态路由扫描所需的基础骨架(如自动注入业务路由的 TODO 标记)。
|
|
110
|
+
|
|
111
|
+
## 📝 开发者指南
|
|
112
|
+
|
|
113
|
+
### 修改基础设施
|
|
114
|
+
|
|
115
|
+
如果你想升级所有生成项目的 `BasePage` 逻辑:
|
|
116
|
+
|
|
117
|
+
1. 修改 `core_files/base/base_page.dart.template`。
|
|
118
|
+
2. 运行 `npm run build` 以确保 CLI 索引更新。
|
|
119
|
+
3. 下次执行 `flu-cli new` 时,所有新项目都将获得更新。
|
|
120
|
+
|
|
121
|
+
### 修改代码片段
|
|
122
|
+
|
|
123
|
+
代码片段位于 `snippets/` 目录下,主要服务于 `generate page` 等文件级生成器。
|
|
124
|
+
|
|
125
|
+
- 修改这些文件将直接影响到 `flu-cli g` 命令的输出效果。
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
> **架构哲学**:骨架定义项目的“形状”,而基础设施定义项目的“灵魂”。Flu-CLI 确保了所有形状的项目都拥有同样健壮的灵魂。 🚀
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
import '../theme/index.dart';
|
|
4
|
+
import 'base_list_viewmodel.dart';
|
|
5
|
+
import 'base_page.dart';
|
|
6
|
+
|
|
7
|
+
/// 列表页面基类:在 BasePage 基础上扩展列表体与触底加载能力
|
|
8
|
+
abstract class BaseListPage<T, VM extends BaseListViewModel<T>>
|
|
9
|
+
extends BasePage<VM> {
|
|
10
|
+
/// 构造函数
|
|
11
|
+
const BaseListPage({super.key});
|
|
12
|
+
|
|
13
|
+
@override
|
|
14
|
+
State<BaseListPage<T, VM>> createState();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/// 列表页面State基类 - 用户继承此类
|
|
18
|
+
abstract class BaseListPageState<T, VM extends BaseListViewModel<T>,
|
|
19
|
+
W extends BaseListPage<T, VM>> extends BasePageState<VM, W> {
|
|
20
|
+
late final ScrollController _controller;
|
|
21
|
+
|
|
22
|
+
// ==================== 子类必须实现 ====================
|
|
23
|
+
|
|
24
|
+
/// 渲染单个条目
|
|
25
|
+
Widget buildItem(BuildContext context, T item, int index);
|
|
26
|
+
|
|
27
|
+
// ==================== 可选重写的列表配置 ====================
|
|
28
|
+
|
|
29
|
+
/// 构建列表容器(可选)
|
|
30
|
+
///
|
|
31
|
+
/// **返回 null**:使用默认 ListView.builder
|
|
32
|
+
///
|
|
33
|
+
/// **自定义列表容器**:
|
|
34
|
+
/// - 请使用 `scrollController` 以支持上拉加载
|
|
35
|
+
/// - 原生刷新功能仍然生效(除非 `enableRefresh = false`)
|
|
36
|
+
///
|
|
37
|
+
/// 示例:
|
|
38
|
+
/// ```dart
|
|
39
|
+
/// @override
|
|
40
|
+
/// Widget buildListWidget(BuildContext context) {
|
|
41
|
+
/// return GridView.builder(
|
|
42
|
+
/// controller: scrollController, // 必须绑定!
|
|
43
|
+
/// itemCount: viewModel.items.length,
|
|
44
|
+
/// itemBuilder: (ctx, i) => buildItem(ctx, viewModel.items[i], i),
|
|
45
|
+
/// );
|
|
46
|
+
/// }
|
|
47
|
+
/// ```
|
|
48
|
+
Widget? buildListWidget(BuildContext context) => null;
|
|
49
|
+
|
|
50
|
+
/// 构建列表 Header(可选)
|
|
51
|
+
Widget? buildHeader(BuildContext context) => null;
|
|
52
|
+
|
|
53
|
+
/// 自定义"加载更多"视图(可选)
|
|
54
|
+
///
|
|
55
|
+
/// @param hasMore 是否还有更多数据
|
|
56
|
+
/// @param isLoadingMore 是否正在加载
|
|
57
|
+
///
|
|
58
|
+
/// 返回 null 使用默认样式
|
|
59
|
+
Widget? loadMoreFooterBuilder(
|
|
60
|
+
BuildContext context,
|
|
61
|
+
bool hasMore,
|
|
62
|
+
bool isLoadingMore,
|
|
63
|
+
) =>
|
|
64
|
+
null;
|
|
65
|
+
|
|
66
|
+
/// 刷新容器包裹(用于接入第三方刷新库)
|
|
67
|
+
/// 默认使用 RefreshIndicator,可重写此方法接入第三方库(如 pull_to_refresh)
|
|
68
|
+
Widget refreshWrapperBuilder(BuildContext context, Widget child) {
|
|
69
|
+
if (!enableRefresh) return child;
|
|
70
|
+
return RefreshIndicator(
|
|
71
|
+
onRefresh: onRefreshCallback,
|
|
72
|
+
child: child,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// 列表体外层包裹
|
|
77
|
+
/// 用于添加 Scrollbar、NotificationListener、吸顶 Header 等额外结构
|
|
78
|
+
Widget listWrapperBuilder(BuildContext context, Widget list) => list;
|
|
79
|
+
|
|
80
|
+
/// 提供自定义 ScrollController
|
|
81
|
+
/// 返回 null 使用内部控制器;非空时由外部管理其生命周期
|
|
82
|
+
ScrollController? provideScrollController(BuildContext context) => null;
|
|
83
|
+
|
|
84
|
+
/// 下拉刷新回调(供第三方库使用)
|
|
85
|
+
Future<void> onRefreshCallback() async {
|
|
86
|
+
await viewModel.refreshData();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// 上拉加载回调(供第三方库使用)
|
|
90
|
+
Future<void> onLoadMoreCallback() async {
|
|
91
|
+
await viewModel.loadMore();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// 滚动事件回调
|
|
95
|
+
/// 可用于自定义触底策略或滚动埋点
|
|
96
|
+
void onScroll(BuildContext context, ScrollPosition position) {}
|
|
97
|
+
|
|
98
|
+
/// 是否启用自动触底加载
|
|
99
|
+
bool get enableAutoLoadMore => true;
|
|
100
|
+
|
|
101
|
+
/// 触底阈值
|
|
102
|
+
double get loadMoreThreshold => 100;
|
|
103
|
+
|
|
104
|
+
/// 是否启用下拉刷新
|
|
105
|
+
bool get enableRefresh => false;
|
|
106
|
+
|
|
107
|
+
/// 列表页 默认关闭 safeAreaBottom
|
|
108
|
+
@override
|
|
109
|
+
bool get safeAreaBottom => false;
|
|
110
|
+
|
|
111
|
+
// ==================== 内部实现 ====================
|
|
112
|
+
|
|
113
|
+
/// 页面是否为空:依据列表数据判断
|
|
114
|
+
@override
|
|
115
|
+
bool get isEmptyContent => viewModel.items.isEmpty;
|
|
116
|
+
|
|
117
|
+
/// 内部 ScrollController(供子类使用)
|
|
118
|
+
/// 当重写 buildListWidget 时,请绑定此 controller 以支持上拉加载
|
|
119
|
+
@protected
|
|
120
|
+
ScrollController get scrollController => _controller;
|
|
121
|
+
|
|
122
|
+
@override
|
|
123
|
+
void initState() {
|
|
124
|
+
super.initState();
|
|
125
|
+
_controller = provideScrollController(context) ?? ScrollController();
|
|
126
|
+
_controller.addListener(_onScroll);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@override
|
|
130
|
+
void dispose() {
|
|
131
|
+
_controller.removeListener(_onScroll);
|
|
132
|
+
if (provideScrollController(context) == null) {
|
|
133
|
+
_controller.dispose();
|
|
134
|
+
}
|
|
135
|
+
super.dispose();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// 触底检测:接近底部时尝试加载更多
|
|
139
|
+
void _onScroll() {
|
|
140
|
+
onScroll(context, _controller.position);
|
|
141
|
+
if (!enableAutoLoadMore) return;
|
|
142
|
+
if (!_controller.hasClients) return;
|
|
143
|
+
final max = _controller.position.maxScrollExtent;
|
|
144
|
+
final offset = _controller.offset;
|
|
145
|
+
if (offset >= max - loadMoreThreshold) {
|
|
146
|
+
onLoadMoreCallback();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/// 构建成功态内容:列表 + 刷新包裹
|
|
151
|
+
@override
|
|
152
|
+
Widget buildContent(BuildContext context) {
|
|
153
|
+
Widget body = _buildListWidget();
|
|
154
|
+
body = refreshWrapperBuilder(context, body);
|
|
155
|
+
return body;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/// 构建列表视图:支持自定义或默认实现
|
|
159
|
+
Widget _buildListWidget() {
|
|
160
|
+
final vm = viewModel;
|
|
161
|
+
final theme = Theme.of(context).extension<StatusViewsTheme>();
|
|
162
|
+
final list = buildListWidget(context) ??
|
|
163
|
+
ListView.builder(
|
|
164
|
+
controller: _controller,
|
|
165
|
+
itemCount: vm.items.length + 1,
|
|
166
|
+
itemBuilder: (ctx, i) {
|
|
167
|
+
// 支持自定义 Header
|
|
168
|
+
if (i == 0) {
|
|
169
|
+
final header = buildHeader(ctx);
|
|
170
|
+
if (header != null) return header;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
final last = i == vm.items.length;
|
|
174
|
+
if (last) {
|
|
175
|
+
// 加载失败:显示重试按钮
|
|
176
|
+
if (vm.loadMoreFailed) {
|
|
177
|
+
return Padding(
|
|
178
|
+
padding: const EdgeInsets.all(16),
|
|
179
|
+
child: Column(
|
|
180
|
+
mainAxisSize: MainAxisSize.min,
|
|
181
|
+
children: [
|
|
182
|
+
Text(
|
|
183
|
+
vm.loadMoreError?.toString() ?? '加载更多失败',
|
|
184
|
+
style: const TextStyle(color: Colors.red),
|
|
185
|
+
),
|
|
186
|
+
const SizedBox(height: 8),
|
|
187
|
+
ElevatedButton(
|
|
188
|
+
onPressed: vm.loadMore,
|
|
189
|
+
child: const Text('重试'),
|
|
190
|
+
),
|
|
191
|
+
],
|
|
192
|
+
),
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 用户自定义加载视图
|
|
197
|
+
final customFooter = loadMoreFooterBuilder(
|
|
198
|
+
ctx,
|
|
199
|
+
vm.hasMore,
|
|
200
|
+
vm.isLoadingMore,
|
|
201
|
+
);
|
|
202
|
+
if (customFooter != null) return customFooter;
|
|
203
|
+
|
|
204
|
+
// 主题自定义加载视图
|
|
205
|
+
final globalFooter =
|
|
206
|
+
theme?.loadMoreBuilder?.call(ctx, vm.hasMore);
|
|
207
|
+
if (globalFooter != null) return globalFooter;
|
|
208
|
+
|
|
209
|
+
// 默认加载视图
|
|
210
|
+
return Padding(
|
|
211
|
+
padding: const EdgeInsets.all(16),
|
|
212
|
+
child: Center(
|
|
213
|
+
child: vm.hasMore
|
|
214
|
+
? const CircularProgressIndicator()
|
|
215
|
+
: const Text('没有更多了'),
|
|
216
|
+
),
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
final item = vm.items[i];
|
|
220
|
+
return buildItem(ctx, item, i);
|
|
221
|
+
},
|
|
222
|
+
);
|
|
223
|
+
return listWrapperBuilder(context, list);
|
|
224
|
+
}
|
|
225
|
+
}
|