Ctrl+C 一时爽,改名改到火葬场。
右键 → 输名字 → 回车 → 3 秒搞定。
😤 Ctrl+C/V 工程师的日常
| 场景 | 操作 | 用时 | 内心 OS |
|---|---|---|---|
| 写新页面 | 找老页面→复制→改名⌨3处→删代码 | 3 分钟 | 我是来写业务的,不是当复读机! |
| 写 Model | 新建文件→写字段→写fromJson→嵌套对象再写一遍 | 10 分钟 | 2024年了还手写fromJson?? |
| 写 Widget | 找类似的→复制→改名→删多余代码 | 2 分钟 | 又是复制粘贴... |
🚀 Flu CLI:让机器干这些破事
核心思想:重复性工作,交给工具。
🎯 功能一:生成 Page(3 秒)
最简单的用法
操作:
- 在
lib/pages目录右键 - 选择
Flu: 生成文件 - 选
Page→ 输入profile - 选
Stateful
等 3 秒,文件自动生成并打开:
// lib/pages/profile_page.dart
import 'package:flutter/material.dart';
class ProfilePage extends StatefulWidget {
const ProfilePage({super.key});
@override
State<ProfilePage> createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: const Center(
child: Text('Profile Page'),
),
);
}
}
对比:
| 方式 | 用时 | 操作 |
|---|---|---|
| 复制粘贴 | 3 分钟 | 找文件 → 复制 → 改名 → 删代码 |
| Flu CLI | 3 秒 | 右键 → 选类型 → 输名字 |
省时:2 分 57 秒 🐟
进阶:自动带 ViewModel
场景:写页面时 99% 需要配套的 ViewModel。
传统方式:
- 生成 ProfilePage
- 再右键一次
- 生成 ProfileViewModel
- 在 Page 里 import ViewModel
- 写绑定代码
Flu CLI 方式:
生成 Page 时,勾选 ✅ 同时生成 ViewModel
自动生成两个文件:
文件 1:profile_page.dart
import 'package:flutter/material.dart';
import '../viewmodels/profile_viewmodel.dart'; // 自动 import
class ProfilePage extends StatefulWidget {
const ProfilePage({super.key});
@override
State<ProfilePage> createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
late ProfileViewModel _viewModel; // 自动声明
@override
void initState() {
super.initState();
_viewModel = ProfileViewModel(); // 自动初始化
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: const Center(
child: Text('Profile Page'),
),
);
}
@override
void dispose() {
_viewModel.dispose(); // 自动释放
super.dispose();
}
}
文件 2:profile_viewmodel.dart
import 'package:flutter/material.dart';
class ProfileViewModel extends ChangeNotifier {
// TODO: Add your business logic here
@override
void dispose() {
super.dispose();
}
}
省时:又省 2 分钟 🐟
高级:自动继承 BasePage
场景:团队有统一的 BasePage 基类,包含:
- 统一的 Loading 状态
- 统一的错误处理
- 统一的生命周期日志
问题:每次生成的 Page 都要手动改继承。
解决方案:配置 .flu-cli.json
{
"generators": {
"page": {
"withBasePage": true,
"basePageClass": "BasePage",
"basePageImport": "package:my_app/core/base/base_page.dart"
}
}
}
配置后,生成的代码自动继承:
import 'package:flutter/material.dart';
import 'package:my_app/core/base/base_page.dart'; // 自动 import
class ProfilePage extends BasePage { // 自动继承
const ProfilePage({super.key});
@override
State<ProfilePage> createState() => _ProfilePageState();
}
class _ProfilePageState extends BasePageState<ProfilePage> {
@override
Widget build(BuildContext context) {
return buildScaffold( // 使用基类的方法
title: 'Profile',
body: const Center(
child: Text('Profile Page'),
),
);
}
}
配置一次,永久生效 🐟🐟🐟
🎯 功能二:生成 Model(1 秒)
最强大的功能:从 JSON 生成
后端给的 JSON:
{
"id": 1001,
"nickname": "张三",
"avatar": "https://example.com/avatar.png",
"level": 5,
"profile": {
"gender": "male",
"birthday": "1995-01-01",
"city": "深圳"
},
"stats": {
"followers": 1234,
"following": 567
}
}
操作:
- 把 JSON 保存为
user.json - 右键这个文件 →
Flu: 生成文件→Model - 选择
从 JSON 生成
自动生成 3 个类:
user_model.dart
class UserModel {
final int? id;
final String? nickname;
final String? avatar;
final int? level;
final ProfileModel? profile;
final StatsModel? stats;
UserModel({
this.id,
this.nickname,
this.avatar,
this.level,
this.profile,
this.stats,
});
factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
id: json['id'] as int?,
nickname: json['nickname'] as String?,
avatar: json['avatar'] as String?,
level: json['level'] as int?,
profile: json['profile'] != null
? ProfileModel.fromJson(json['profile'] as Map<String, dynamic>)
: null,
stats: json['stats'] != null
? StatsModel.fromJson(json['stats'] as Map<String, dynamic>)
: null,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'nickname': nickname,
'avatar': avatar,
'level': level,
'profile': profile?.toJson(),
'stats': stats?.toJson(),
};
}
}
class ProfileModel {
final String? gender;
final String? birthday;
final String? city;
ProfileModel({this.gender, this.birthday, this.city});
factory ProfileModel.fromJson(Map<String, dynamic> json) {
return ProfileModel(
gender: json['gender'] as String?,
birthday: json['birthday'] as String?,
city: json['city'] as String?,
);
}
Map<String, dynamic> toJson() {
return {
'gender': gender,
'birthday': birthday,
'city': city,
};
}
}
class StatsModel {
final int? followers;
final int? following;
StatsModel({this.followers, this.following});
factory StatsModel.fromJson(Map<String, dynamic> json) {
return StatsModel(
followers: json['followers'] as int?,
following: json['following'] as int?,
);
}
Map<String, dynamic> toJson() {
return {
'followers': followers,
'following': following,
};
}
}
自动识别:
- ✅ 嵌套对象(profile、stats)
- ✅ 类型推断(int、String)
- ✅ 可选字段(全部 nullable)
- ✅ fromJson / toJson 方法
对比:
| 方式 | 用时 | 容易出错吗 |
|---|---|---|
| 手写 | 10 分钟 | ✅ 容易拼错字段 |
| Flu CLI | 1 秒 | ❌ 机器生成,不会错 |
省时:9 分 59 秒 🐟🐟🐟
🎯 功能三
:其他文件类型
Widget(可复用组件)
// custom_button_widget.dart
import 'package:flutter/material.dart';
class CustomButtonWidget extends StatelessWidget {
const CustomButtonWidget({super.key});
@override
Widget build(BuildContext context) {
return Container(
// TODO: Implement widget
);
}
}
Service(服务层)
// user_service.dart
class UserService {
// TODO: Add service methods
Future<void> fetchUserInfo() async {
// Implementation
}
}
Module(一整个功能模块)
输入 login,自动生成:
lib/features/login/
├── pages/
│ └── login_page.dart
├── viewmodels/
│ └── login_viewmodel.dart
├── widgets/
│ └── (empty)
└── models/
└── (empty)
一次生成整个模块结构 🐟🐟
🐟 摸鱼小结
| 操作 | 频率(3年) | 传统 | Flu CLI | 省时 |
|---|---|---|---|---|
| 生成 Page | 500 次 | 3 分钟 | 3 秒 | 24.5 小时 |
| 生成 Model | 300 次 | 10 分钟 | 1 秒 | 49.9 小时 |
| 生成 Widget | 200 次 | 2 分钟 | 2 秒 | 6.6 小时 |
| 总计 | 1000 次 | - | - | 81 小时 |
81 小时 = 10 个工作日 = 2 周。告别 Ctrl+C/V,去摸鱼吧!🐟🐟🐟
🛠️ 如何配置?
第一步:初始化配置
右键项目根目录 → Flu: 初始化项目 → 自动生成 .flu-cli.json
第二步:改配置(可选)
{
"generators": {
"page": {
"path": "lib/pages", // 生成路径
"fileSuffix": "_page", // 文件后缀
"withViewModel": true, // 自动生成 VM
"withBasePage": true, // 继承基类
"basePageClass": "BasePage",
"basePageImport": "package:my_app/core/base/base_page.dart"
},
"viewModel": {
"path": "lib/viewmodels",
"fileSuffix": "_viewmodel",
"withBaseViewModel": true,
"baseViewModelClass": "BaseViewModel",
"baseViewModelImport": "package:my_app/core/base/base_viewmodel.dart"
},
"model": {
"path": "lib/models",
"fileSuffix": "_model"
},
"widget": {
"path": "lib/widgets",
"fileSuffix": "_widget"
}
}
}
配置一次,全员共享(提交到仓库即可)
❓ FAQ
| 问题 | 答案 |
|---|---|
| 生成的代码能改吗? | 当然,就是普通 Dart 文件,随便改 |
| 支持 GetX/Riverpod 吗? | 支持,配置 ViewModel 基类即可(见下方示例) |
| 能自定义模板吗? | 暂不支持,但可配置基类、路径、后缀 |
| 生成失败怎么办? | 查看 VSCode 输出面板的 Flu CLI 频道 |
| JSON 转 Model 支持数组吗? | 支持,自动生成 List<XxxModel> |
GetX 配置示例:
{
"generators": {
"viewModel": {
"baseViewModelClass": "GetxController",
"baseViewModelImport": "package:get/get.dart"
}
}
}
📚 系列文章
💬 交流群:加微信
Huoye-TT备注 "flu-cli"⭐ 开源地址:Gitee | 求 Star!
📖 完整文档:huozhiye.cn/flu-cli/
如果这篇文章帮你省了时间,点个赞呗 👍