🐟 Flutter 摸鱼指南(四):团队统一规范,再也不吵架

33 阅读10分钟

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

核心思想:把规范写成配置文件,提交到仓库,机器强制执行。


🧬 配置文件详解

init-config.gif

如何生成?

右键项目根目录 → 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 - 生成路径

config-path-fileSuffix.gif

作用:控制文件生成的目录

示例

"page": {
  "path": "lib/features/{feature}/pages"
}

效果

  • lib/features/auth/ 下右键生成 Page
  • 文件会出现在 lib/features/auth/pages/
  • {feature} 自动替换为 auth

支持的占位符

  • {feature} - 自动识别功能模块名

2. fileSuffix - 文件后缀

作用:统一文件命名规范

示例

"page": {
  "fileSuffix": "_page"
}

效果

输入生成文件类名
loginlogin_page.dartLoginPage
profileprofile_page.dartProfilePage

常见配置

{
  "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 - 继承基类

config-base-class.gif

作用:自动继承项目的基类

示例

"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
}

效果

传统方式

  1. 右键生成 LoginPage
  2. 再右键生成 LoginViewModel
  3. 手动在 Page 里 import ViewModel

配置后

  1. 右键生成 LoginPage
  2. 自动生成 LoginViewModel
  3. 自动 import 并初始化

5. defaultType - 默认类型

作用:设置默认的 Widget 类型

示例

"page": {
  "defaultType": "stateful"  // 默认 Stateful
}

可选值

  • stateful - StatefulWidget
  • stateless - 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 同步和使用统一的配置规范 具体内容:

  1. 场景一:配置文件提交到 Git
    • 展示团队 Leader 的操作:
      • 配置好 .flu-cli.json 文件
      • Git add .flu-cli.json
      • Git commit -m "chore: add flu-cli config for team"
      • Git push
  2. 场景二:团队成员拉取配置
    • 展示团队成员 A 的操作:
      • Git pull(拉取最新代码)
      • 项目中出现 .flu-cli.json 文件
      • VSCode 自动识别配置(或提示重启)
  3. 场景三:统一规范生效
    • 成员 A 使用 Flu CLI 生成 Page
    • 生成的文件路径、命名、基类继承完全符合团队配置
    • 与 Leader 生成的文件保持一致的结构
  4. 场景四:配置更新同步
    • Leader 更新配置(如修改路径)
    • 提交并推送
    • 成员 B pull 后,新生成的文件自动应用新配置
  5. 展示团队协作的完整闭环

所有人生成的代码结构自动统一

  • ✅ 目录统一
  • ✅ 文件命名统一
  • ✅ 自动继承基类
  • ✅ 不需要培训
  • ✅ 不需要文档
  • ✅ 不需要 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 中添加模板

  1. 右键空文件夹 → Flu: 创建新项目
  2. 选择 自定义模板...本地文件夹
  3. 选择本地模板目录

适合场景

  • 个人开发者
  • 不方便使用 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 片段会自动隐藏,避免生成无法编译的代码。


可用片段

前缀生成内容需要配置
stPageStateful Page(继承 BasePage)withBasePage: true
lessPageStateless Page-
viewmodelViewModel 类-
baseViewModelViewModel(继承基类)withBaseViewModel: true
stWidgetStateful Widget-
lessWidgetStateless Widget-
modelModel 类(带 fromJson/toJson)-
serviceService 类-

📝 完整配置参考

{
  "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、或者...摸鱼 🐟


🔗 相关链接


🎉 系列完结撒花

感谢你看完整个系列!

如果 Flu CLI 帮你省了时间,欢迎:

  • ⭐ 给 Gitee 仓库 点个 Star
  • 📢 分享给其他 Flutter 开发者
  • 💬 加群提需求,说不定下个版本就实现

祝你每天多摸 2 小时鱼!🐟🐟🐟


如果这个系列让你告别了搬砖,点个赞呗 👍