Code Review 最常吵的不是 Bug,是“你为什么放这个目录”。
配置文件一提交,讨论 0 分钟,自动统一。
😤 团队协作的痛点
场景一:新人入职
新人:项目目录怎么组织?页面放哪?
老员工:
- 张三说:放
lib/pages - 李四说:放
lib/screens - 王五说:放
lib/ui/views
新人:???
结果:新人自己决定,又出现第四种...
场景二:Code Review
Reviewer:为什么不继承 BasePage?
开发者:啊?有 BasePage 吗?在哪?
Reviewer:在 lib/core/base/base_page.dart,看文档!
开发者:文档在哪?
Reviewer:...(内心崩溃)
场景三:重构
架构师:统一一下,以后页面都放 lib/features/{feature}/pages。
团队:好的。
一周后:
- 老代码还是
lib/pages - 新代码有人用
lib/features,有人忘了 - 又乱了
🚀 解决方案:.flu-cli.json
核心思想:把规范写成配置文件,提交到仓库,机器强制执行。
🧬 配置文件详解
如何生成?
右键项目根目录 → Flu: 初始化项目 → 自动生成 .flu-cli.json
长什么样?
{
"template": "modular",
"generators": {
"page": {
"path": "lib/features/{feature}/pages",
"fileSuffix": "_page",
"defaultType": "stateful",
"withViewModel": true,
"withBasePage": true,
"basePageClass": "BasePage",
"basePageImport": "package:my_app/core/base/base_page.dart"
},
"viewModel": {
"path": "lib/features/{feature}/viewmodels",
"fileSuffix": "_viewmodel",
"withBaseViewModel": true,
"baseViewModelClass": "BaseViewModel",
"baseViewModelImport": "package:my_app/core/base/base_viewmodel.dart"
},
"widget": {
"path": "lib/shared/widgets",
"fileSuffix": "_widget",
"defaultType": "stateless"
},
"model": {
"path": "lib/shared/models",
"fileSuffix": "_model"
},
"service": {
"path": "lib/shared/services",
"fileSuffix": "_service"
}
}
}
🎯 配置项详解
1. path - 生成路径
作用:控制文件生成的目录
示例:
"page": {
"path": "lib/features/{feature}/pages"
}
效果:
- 在
lib/features/auth/下右键生成 Page - 文件会出现在
lib/features/auth/pages/ {feature}自动替换为auth
支持的占位符:
{feature}- 自动识别功能模块名
2. fileSuffix - 文件后缀
作用:统一文件命名规范
示例:
"page": {
"fileSuffix": "_page"
}
效果:
| 输入 | 生成文件 | 类名 |
|---|---|---|
login | login_page.dart | LoginPage |
profile | profile_page.dart | ProfilePage |
常见配置:
{
"page": { "fileSuffix": "_page" }, // login_page.dart
"viewModel": { "fileSuffix": "_vm" }, // login_vm.dart
"widget": { "fileSuffix": "_widget" }, // custom_button_widget.dart
"model": { "fileSuffix": "_model" } // user_model.dart
}
3. withBasePage - 继承基类
作用:自动继承项目的基类
示例:
"page": {
"withBasePage": true,
"basePageClass": "BasePage",
"basePageImport": "package:my_app/core/base/base_page.dart"
}
效果:
传统方式(手动继承):
import 'package:flutter/material.dart';
class LoginPage extends StatefulWidget { // ❌ 没继承 BasePage
// ...
}
配置后(自动继承):
import 'package:flutter/material.dart';
import 'package:my_app/core/base/base_page.dart'; // ✅ 自动 import
class LoginPage extends BasePage { // ✅ 自动继承
// ...
}
ViewModel 同理:
"viewModel": {
"withBaseViewModel": true,
"baseViewModelClass": "BaseViewModel",
"baseViewModelImport": "package:my_app/core/base/base_viewmodel.dart"
}
4. withViewModel - 自动生成 ViewModel
作用:生成 Page 时自动生成配套的 ViewModel
示例:
"page": {
"withViewModel": true
}
效果:
传统方式:
- 右键生成 LoginPage
- 再右键生成 LoginViewModel
- 手动在 Page 里 import ViewModel
配置后:
- 右键生成 LoginPage
- 自动生成 LoginViewModel
- 自动 import 并初始化
5. defaultType - 默认类型
作用:设置默认的 Widget 类型
示例:
"page": {
"defaultType": "stateful" // 默认 Stateful
}
可选值:
stateful- StatefulWidgetstateless- StatelessWidget
🏢 团队统一规范方案
Step 1:团队讨论规范
开会确定:
- 页面放哪个目录?
- 文件后缀用什么?
- 是否继承基类?
Step 2:写成配置文件
把规范写成 .flu-cli.json:
{
"generators": {
"page": {
"path": "lib/pages", // ✅ 统一路径
"fileSuffix": "_page", // ✅ 统一后缀
"withBasePage": true // ✅ 自动继承
},
"viewModel": {
"path": "lib/viewmodels",
"fileSuffix": "_viewmodel"
}
}
}
Step 3:提交到仓库
git add .flu-cli.json
git commit -m "Add Flu CLI config"
git push
Step 4:团队成员拉取
git pull
完事儿。
效果
场景:演示团队成员如何通过 Git 同步和使用统一的配置规范 具体内容:
- 场景一:配置文件提交到 Git
- 展示团队 Leader 的操作:
- 配置好 .flu-cli.json 文件
- Git add .flu-cli.json
- Git commit -m "chore: add flu-cli config for team"
- Git push
- 展示团队 Leader 的操作:
- 场景二:团队成员拉取配置
- 展示团队成员 A 的操作:
- Git pull(拉取最新代码)
- 项目中出现 .flu-cli.json 文件
- VSCode 自动识别配置(或提示重启)
- 展示团队成员 A 的操作:
- 场景三:统一规范生效
- 成员 A 使用 Flu CLI 生成 Page
- 生成的文件路径、命名、基类继承完全符合团队配置
- 与 Leader 生成的文件保持一致的结构
- 场景四:配置更新同步
- Leader 更新配置(如修改路径)
- 提交并推送
- 成员 B pull 后,新生成的文件自动应用新配置
- 展示团队协作的完整闭环
所有人生成的代码结构自动统一:
- ✅ 目录统一
- ✅ 文件命名统一
- ✅ 自动继承基类
- ✅ 不需要培训
- ✅ 不需要文档
- ✅ 不需要 Code Review 吵架
机器强制执行规范,比人靠谱。 🐟🐟🐟
🎨 自定义团队模板(终极方案)
场景
公司有一套「黄金标准」项目结构:
- 封装好的网络库
- 统一的主题配置
- 标准的基类
- 常用工具类
- 配置文件
.flu-cli.json
需求:新项目直接用这套标准。
解决方案:自定义模板
Flu CLI 支持两种模板来源:
方式一:Git 模板(团队推荐)
Step 1:把标准项目推到 Git 仓库
https://gitee.com/your-company/flutter-template.git
仓库内容:
flutter-template/
├── lib/
│ ├── core/
│ │ ├── network/
│ │ │ └── http_client.dart # ✅ 公司网络库
│ │ ├── base/
│ │ │ ├── base_page.dart # ✅ 公司基类
│ │ │ └── base_viewmodel.dart
│ │ └── theme/
│ │ └── company_theme.dart # ✅ 公司主题
│ ├── features/
│ └── shared/
├── .flu-cli.json # ✅ 公司规范
└── pubspec.yaml
Step 2:在 Flu CLI 中添加模板
详见 第一篇:5 秒建项目
Step 3:全员使用
新项目创建时,选 公司标准模板 → 5 秒精装房到手
方式二:本地模板(个人使用)
Step 1:把标准项目放在本地目录
/Users/you/my-company-template/
Step 2:在 Flu CLI 中添加模板
- 右键空文件夹 →
Flu: 创建新项目 - 选择
自定义模板...→本地文件夹 - 选择本地模板目录
适合场景:
- 个人开发者
- 不方便使用 Git 的环境
- 需要频繁修改模板
好处
| 对比 | 没有模板 | Git 模板 | 本地模板 |
|---|---|---|---|
| 新项目创建 | 30 分钟搭架子 | 5 秒 | 5 秒 |
| 团队统一 | 靠文档,不靠谱 | 100% 统一 | 仅个人 |
| 模板更新 | 手动通知 | Git 更新,重新拉取 | 直接修改 |
| 新人培训 | 2 小时 | 0 小时 | 0 小时 |
| 网络要求 | - | 需要访问 Git | 不需要 |
🧩 智能代码片段
除了生成文件,Flu CLI 还提供智能代码片段。
智能之处:根据你的配置自动显示/隐藏片段。
示例
如果你配置了:
{
"generators": {
"page": {
"withBasePage": true
}
}
}
那么在 .dart 文件里输入 stPage:
会生成继承 BasePage 的代码:
import 'package:my_app/core/base/base_page.dart';
class LoginPage extends BasePage {
const LoginPage({super.key});
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends BasePageState<LoginPage> {
@override
Widget build(BuildContext context) {
return buildScaffold(
title: 'Login',
body: Container(),
);
}
}
如果你没配置 withBasePage:
stPage 片段会自动隐藏,避免生成无法编译的代码。
可用片段
| 前缀 | 生成内容 | 需要配置 |
|---|---|---|
stPage | Stateful Page(继承 BasePage) | withBasePage: true |
lessPage | Stateless Page | - |
viewmodel | ViewModel 类 | - |
baseViewModel | ViewModel(继承基类) | withBaseViewModel: true |
stWidget | Stateful Widget | - |
lessWidget | Stateless Widget | - |
model | Model 类(带 fromJson/toJson) | - |
service | Service 类 | - |
📝 完整配置参考
{
"template": "modular",
"generators": {
"page": {
"path": "lib/features/{feature}/pages",
"fileSuffix": "_page",
"defaultType": "stateful",
"withViewModel": true,
"withBasePage": true,
"basePageClass": "BasePage",
"basePageImport": "package:my_app/core/base/base_page.dart"
},
"viewModel": {
"path": "lib/features/{feature}/viewmodels",
"fileSuffix": "_viewmodel",
"withBaseViewModel": true,
"baseViewModelClass": "BaseViewModel",
"baseViewModelImport": "package:my_app/core/base/base_viewmodel.dart"
},
"widget": {
"path": "lib/shared/widgets",
"fileSuffix": "_widget",
"defaultType": "stateless"
},
"component": {
"path": "lib/features/{feature}/widgets",
"fileSuffix": "_component",
"defaultType": "stateless"
},
"model": {
"path": "lib/shared/models",
"fileSuffix": "_model"
},
"service": {
"path": "lib/shared/services",
"fileSuffix": "_service"
},
"module": {
"path": "lib/features/{feature}",
"structure": ["pages", "viewmodels", "widgets", "models", "services"]
}
}
}
❓ FAQ
| 问题 | 答案 |
|---|---|
| 配置文件放哪? | 项目根目录,和 pubspec.yaml 同级 |
| 要提交到仓库吗? | 必须提交,团队成员 pull 后自动应用配置 |
| 能为不同项目配置不同规范吗? | 能,每个项目有自己的 .flu-cli.json |
| 会影响已有代码吗? | 不会,只影响新生成的文件 |
| 能修改配置文件吗? | 随时可以改,改完后新生成的文件应用新配置 |
| 支持 GetX / Riverpod 吗? | 支持,配置基类即可(见下方示例) |
GetX 配置示例:
{
"generators": {
"viewModel": {
"baseViewModelClass": "GetxController",
"baseViewModelImport": "package:get/get.dart"
}
}
}
🐟 摸鱼小结
| 功能 | 省时 | 摸鱼指数 |
|---|---|---|
| 统一目录结构 | 不再争吵 | 🐟🐟🐟 |
| 统一文件命名 | 不再争吵 | 🐟🐟🐟 |
| 自动继承基类 | 每次少改 3 处 | 🐟🐟🐟🐟 |
| 自定义团队模板 | 新项目 30 分钟 → 5 秒 | 🐟🐟🐟🐟🐟 |
| 智能代码片段 | 少记忆,少出错 | 🐟🐟🐟 |
配置一次,摸鱼一年。
📚 系列总结
四篇摸鱼指南看完,你学会了:
| 篇章 | 内容 | 核心收益 |
|---|---|---|
| 第一篇 | 5 秒建项目 | 告别搭架子 |
| 第二篇 | 3 秒生成文件 | 告别 Ctrl+C/V |
| 第三篇 | 10 秒配图标 | 告别切图 |
| 第四篇 | 团队统一 | 告别争吵 |
省下的时间,去学点新东西、写写 Side Project、或者...摸鱼 🐟
🔗 相关链接
- 📖 完整文档:huozhiye.cn/flu-cli/
- 💻 源码仓库:Gitee
- 🛒 VSCode 市场:Flu CLI
- 💬 交流群:微信
Huoye-TT备注 "flu-cli"
🎉 系列完结撒花
感谢你看完整个系列!
如果 Flu CLI 帮你省了时间,欢迎:
- ⭐ 给 Gitee 仓库 点个 Star
- 📢 分享给其他 Flutter 开发者
- 💬 加群提需求,说不定下个版本就实现
祝你每天多摸 2 小时鱼!🐟🐟🐟
如果这个系列让你告别了搬砖,点个赞呗 👍