Skip to content

片段驱动生成

片段驱动生成是 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 片段 > 内置模板
  1. flu-cli 检查项目中是否有对应的片段
  2. 如果有,使用片段生成代码
  3. 如果没有,使用内置模板

支持的片段键

类型片段键说明
Page (Stateful)flu.stPageStatefulWidget 页面
Page (Stateless)flu.lessPageStatelessWidget 页面
Widget (Stateful)flu.stWidgetStatefulWidget 组件
Widget (Stateless)flu.lessWidgetStatelessWidget 组件
Componentflu.component复合组件
ViewModelflu.viewmodel视图模型
Service (API)flu.service.apiAPI 服务
Service (Auth)flu.service.auth认证服务
Service (Storage)flu.service.storage存储服务
Modelflu.model数据模型

配置片段

1. 创建片段文件

在项目根目录创建:

.vscode/dart.code-snippets

2. 定义片段

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 片段生成代码

变量占位符

支持的变量

变量格式示例说明
PascalCaseHomePage类名
camelCasehomePage变量名
snake_casehome_page文件名
Title CaseHome Page标题
相对路径../viewmodels/home_viewmodel.dartViewModel 导入路径

变量转换规则

输入: 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 push

3. 团队成员同步

团队成员拉取代码后,自动获得统一的代码片段。

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;",
        "}"
    ]
}

常见问题

片段不生效?

  1. 检查片段文件位置: .vscode/dart.code-snippets
  2. 检查片段键是否正确: flu.stPage
  3. 检查 JSON 格式是否正确

如何测试片段?

bash
# 1. 创建测试项目
flu-cli new test_project

# 2. 添加片段文件
# .vscode/dart.code-snippets

# 3. 生成代码测试
flu-cli a page test

如何覆盖内置模板?

只需创建对应的片段键,flu-cli 会优先使用片段。

片段可以嵌套吗?

不支持。但可以在片段中使用变量占位符。

下一步

Released under the MIT License.