片段驱动生成
片段驱动生成是 flu-cli 的强大功能,允许你通过 VSCode 代码片段自定义生成的代码内容,实现团队代码风格统一。
什么是片段驱动?
传统方式
flu-cli 使用内置模板生成代码:
bash
flu-cli a page home
# 生成固定格式的代码片段驱动方式
flu-cli 优先使用你定义的 VSCode 片段:
bash
# 1. 在项目中定义片段
.vscode/dart.code-snippets
# 2. 生成代码时自动使用片段
flu-cli a page home
# 生成你自定义格式的代码工作原理
优先级
VSCode 片段 > 内置模板- flu-cli 检查项目中是否有对应的片段
- 如果有,使用片段生成代码
- 如果没有,使用内置模板
支持的片段键
| 类型 | 片段键 | 说明 |
|---|---|---|
| Page (Stateful) | flu.stPage | StatefulWidget 页面 |
| Page (Stateless) | flu.lessPage | StatelessWidget 页面 |
| Widget (Stateful) | flu.stWidget | StatefulWidget 组件 |
| Widget (Stateless) | flu.lessWidget | StatelessWidget 组件 |
| Component | flu.component | 复合组件 |
| ViewModel | flu.viewmodel | 视图模型 |
| Service (API) | flu.service.api | API 服务 |
| Service (Auth) | flu.service.auth | 认证服务 |
| Service (Storage) | flu.service.storage | 存储服务 |
| Model | flu.model | 数据模型 |
配置片段
1. 创建片段文件
在项目根目录创建:
.vscode/dart.code-snippets2. 定义片段
json
{
"Flutter Stateful Page": {
"prefix": "flu.stPage",
"body": [
"import 'package:flutter/material.dart';",
"import '${1:{{vm_import}}}';",
"",
"class {{Name}}Page extends StatefulWidget {",
" const {{Name}}Page({super.key});",
"",
" @override",
" State<{{Name}}Page> createState() => _{{Name}}PageState();",
"}",
"",
"class _{{Name}}PageState extends State<{{Name}}Page> {",
" late final {{Name}}ViewModel _viewModel;",
"",
" @override",
" void initState() {",
" super.initState();",
" _viewModel = {{Name}}ViewModel();",
" }",
"",
" @override",
" Widget build(BuildContext context) {",
" return Scaffold(",
" appBar: AppBar(title: const Text('{{title}}')),",
" body: const Center(child: Text('{{title}}')),",
" );",
" }",
"",
" @override",
" void dispose() {",
" _viewModel.dispose();",
" super.dispose();",
" }",
"}"
],
"description": "Create a stateful page with ViewModel"
}
}3. 使用片段
bash
flu-cli a page home --stateful
# 自动使用 flu.stPage 片段生成代码变量占位符
支持的变量
| 变量 | 格式 | 示例 | 说明 |
|---|---|---|---|
| PascalCase | HomePage | 类名 |
| camelCase | homePage | 变量名 |
| snake_case | home_page | 文件名 |
| Title Case | Home Page | 标题 |
| 相对路径 | ../viewmodels/home_viewmodel.dart | ViewModel 导入路径 |
变量转换规则
输入: user_list
| 变量 | 输出 |
|---|---|
| UserList |
| userList |
| user_list |
| User List |
使用示例
json
{
"body": [
"// 文件名: {{snake_name}}_page.dart",
"// 类名: {{Name}}Page",
"// 变量名: {{name}}",
"// 标题: {{title}}",
"",
"import 'package:flutter/material.dart';",
"import '{{vm_import}}';",
"",
"class {{Name}}Page extends StatelessWidget {",
" const {{Name}}Page({super.key});",
"",
" @override",
" Widget build(BuildContext context) {",
" return Scaffold(",
" appBar: AppBar(title: const Text('{{title}}')),",
" body: const Center(child: Text('{{title}}')),",
" );",
" }",
"}"
]
}生成结果(flu-cli a page user_list):
dart
// 文件名: user_list_page.dart
// 类名: UserListPage
// 变量名: userList
// 标题: User List
import 'package:flutter/material.dart';
import '../viewmodels/user_list_viewmodel.dart';
class UserListPage extends StatelessWidget {
const UserListPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('User List')),
body: const Center(child: Text('User List')),
);
}
}完整片段示例
Page 片段
Stateful Page
json
{
"Flutter Stateful Page": {
"prefix": "flu.stPage",
"body": [
"import 'package:flutter/material.dart';",
"import '{{vm_import}}';",
"",
"/// {{title}} Page",
"class {{Name}}Page extends StatefulWidget {",
" const {{Name}}Page({super.key});",
"",
" @override",
" State<{{Name}}Page> createState() => _{{Name}}PageState();",
"}",
"",
"class _{{Name}}PageState extends State<{{Name}}Page> {",
" late final {{Name}}ViewModel _viewModel;",
"",
" @override",
" void initState() {",
" super.initState();",
" _viewModel = {{Name}}ViewModel();",
" _viewModel.init();",
" }",
"",
" @override",
" Widget build(BuildContext context) {",
" return Scaffold(",
" appBar: AppBar(",
" title: const Text('{{title}}'),",
" ),",
" body: _buildBody(),",
" );",
" }",
"",
" Widget _buildBody() {",
" return const Center(",
" child: Text('{{title}}'),",
" );",
" }",
"",
" @override",
" void dispose() {",
" _viewModel.dispose();",
" super.dispose();",
" }",
"}"
]
}
}Stateless Page
json
{
"Flutter Stateless Page": {
"prefix": "flu.lessPage",
"body": [
"import 'package:flutter/material.dart';",
"import '{{vm_import}}';",
"",
"/// {{title}} Page",
"class {{Name}}Page extends StatelessWidget {",
" const {{Name}}Page({super.key});",
"",
" @override",
" Widget build(BuildContext context) {",
" final viewModel = {{Name}}ViewModel();",
" ",
" return Scaffold(",
" appBar: AppBar(",
" title: const Text('{{title}}'),",
" ),",
" body: const Center(",
" child: Text('{{title}}'),",
" ),",
" );",
" }",
"}"
]
}
}Widget 片段
json
{
"Flutter Stateless Widget": {
"prefix": "flu.lessWidget",
"body": [
"import 'package:flutter/material.dart';",
"",
"/// {{title}} Widget",
"class {{Name}}Widget extends StatelessWidget {",
" const {{Name}}Widget({super.key});",
"",
" @override",
" Widget build(BuildContext context) {",
" return Container(",
" child: const Text('{{title}}'),",
" );",
" }",
"}"
]
}
}ViewModel 片段
json
{
"Flutter ViewModel": {
"prefix": "flu.viewmodel",
"body": [
"import 'package:flutter/foundation.dart';",
"",
"/// {{title}} ViewModel",
"class {{Name}}ViewModel extends ChangeNotifier {",
" // State",
" bool _isLoading = false;",
" bool get isLoading => _isLoading;",
"",
" String? _error;",
" String? get error => _error;",
"",
" // Constructor",
" {{Name}}ViewModel();",
"",
" // Initialize",
" Future<void> init() async {",
" await loadData();",
" }",
"",
" // Load data",
" Future<void> loadData() async {",
" _setLoading(true);",
" _setError(null);",
"",
" try {",
" // TODO: Load data",
" await Future.delayed(const Duration(seconds: 1));",
" } catch (e) {",
" _setError(e.toString());",
" debugPrint('Error loading data: $e');",
" } finally {",
" _setLoading(false);",
" }",
" }",
"",
" // Private methods",
" void _setLoading(bool value) {",
" _isLoading = value;",
" notifyListeners();",
" }",
"",
" void _setError(String? value) {",
" _error = value;",
" notifyListeners();",
" }",
"",
" @override",
" void dispose() {",
" // Clean up",
" super.dispose();",
" }",
"}"
]
}
}Service 片段
json
{
"Flutter API Service": {
"prefix": "flu.service.api",
"body": [
"import 'dart:convert';",
"import 'package:http/http.dart' as http;",
"",
"/// {{title}} Service",
"class {{Name}}Service {",
" static const String _baseUrl = 'https://api.example.com';",
"",
" // GET",
" Future<dynamic> get{{Name}}s() async {",
" try {",
" final response = await http.get(",
" Uri.parse('$_baseUrl/{{snake_name}}s'),",
" headers: {'Content-Type': 'application/json'},",
" );",
"",
" if (response.statusCode == 200) {",
" return json.decode(response.body);",
" } else {",
" throw Exception('Failed to load {{snake_name}}s');",
" }",
" } catch (e) {",
" throw Exception('Error: $e');",
" }",
" }",
"",
" // POST",
" Future<dynamic> create{{Name}}(Map<String, dynamic> data) async {",
" try {",
" final response = await http.post(",
" Uri.parse('$_baseUrl/{{snake_name}}s'),",
" headers: {'Content-Type': 'application/json'},",
" body: json.encode(data),",
" );",
"",
" if (response.statusCode == 201) {",
" return json.decode(response.body);",
" } else {",
" throw Exception('Failed to create {{snake_name}}');",
" }",
" } catch (e) {",
" throw Exception('Error: $e');",
" }",
" }",
"",
" // PUT",
" Future<dynamic> update{{Name}}(String id, Map<String, dynamic> data) async {",
" try {",
" final response = await http.put(",
" Uri.parse('$_baseUrl/{{snake_name}}s/$id'),",
" headers: {'Content-Type': 'application/json'},",
" body: json.encode(data),",
" );",
"",
" if (response.statusCode == 200) {",
" return json.decode(response.body);",
" } else {",
" throw Exception('Failed to update {{snake_name}}');",
" }",
" } catch (e) {",
" throw Exception('Error: $e');",
" }",
" }",
"",
" // DELETE",
" Future<void> delete{{Name}}(String id) async {",
" try {",
" final response = await http.delete(",
" Uri.parse('$_baseUrl/{{snake_name}}s/$id'),",
" headers: {'Content-Type': 'application/json'},",
" );",
"",
" if (response.statusCode != 204) {",
" throw Exception('Failed to delete {{snake_name}}');",
" }",
" } catch (e) {",
" throw Exception('Error: $e');",
" }",
" }",
"}"
]
}
}Model 片段
json
{
"Flutter Model": {
"prefix": "flu.model",
"body": [
"/// {{title}} Model",
"class {{Name}} {",
" final String id;",
" final String name;",
"",
" {{Name}}({",
" required this.id,",
" required this.name,",
" });",
"",
" factory {{Name}}.fromJson(Map<String, dynamic> json) {",
" return {{Name}}(",
" id: json['id'] as String,",
" name: json['name'] as String,",
" );",
" }",
"",
" Map<String, dynamic> toJson() {",
" return {",
" 'id': id,",
" 'name': name,",
" };",
" }",
"",
" {{Name}} copyWith({",
" String? id,",
" String? name,",
" }) {",
" return {{Name}}(",
" id: id ?? this.id,",
" name: name ?? this.name,",
" );",
" }",
"",
" @override",
" String toString() {",
" return '{{Name}}(id: $id, name: $name)';",
" }",
"}"
]
}
}团队协作
1. 创建团队片段
在项目中创建 .vscode/dart.code-snippets,包含团队统一的代码风格。
2. 提交到 Git
bash
git add .vscode/dart.code-snippets
git commit -m "feat: add team code snippets"
git push3. 团队成员同步
团队成员拉取代码后,自动获得统一的代码片段。
bash
git pull
flu-cli a page home # 使用团队片段生成代码最佳实践
1. 保持一致性
所有片段使用相同的代码风格:
- 缩进
- 命名规范
- 注释风格
- 导入顺序
2. 添加注释
在片段中添加有用的注释:
json
{
"body": [
"/// {{title}} Page",
"/// ",
"/// Created by flu-cli",
"class {{Name}}Page extends StatelessWidget {"
]
}3. 使用 TODO
在需要补充的地方添加 TODO:
json
{
"body": [
"Future<void> loadData() async {",
" // TODO: Implement data loading",
"}"
]
}4. 包含错误处理
在片段中包含完整的错误处理:
json
{
"body": [
"try {",
" // TODO: Implement logic",
"} catch (e) {",
" debugPrint('Error: $e');",
" rethrow;",
"}"
]
}常见问题
片段不生效?
- 检查片段文件位置:
.vscode/dart.code-snippets - 检查片段键是否正确:
flu.stPage - 检查 JSON 格式是否正确
如何测试片段?
bash
# 1. 创建测试项目
flu-cli new test_project
# 2. 添加片段文件
# .vscode/dart.code-snippets
# 3. 生成代码测试
flu-cli a page test如何覆盖内置模板?
只需创建对应的片段键,flu-cli 会优先使用片段。
片段可以嵌套吗?
不支持。但可以在片段中使用变量占位符。