Skip to content

快速开始示例

通过实际示例快速上手 flu-cli,创建一个简单的 Todo 应用。

目标

创建一个包含以下功能的 Todo 应用:

  • ✅ 查看 Todo 列表
  • ✅ 添加新 Todo
  • ✅ 标记完成
  • ✅ 删除 Todo

步骤 1: 创建项目

交互式创建

bash
flu-cli new

按照提示输入:

  1. 项目名称: todo_app
  2. 模板: 选择 Modular
  3. 项目路径: 回车(使用默认)
  4. 包名: 回车(使用默认 com.example.todo_app
  5. 作者: 输入你的名字
  6. 确认创建: 回车

或使用命令行

bash
flu-cli new todo_app -t modular
cd todo_app

步骤 2: 创建 Todo 模块

bash
cd todo_app
flu-cli a module todo

生成结构:

lib/features/todo/
├── pages/
├── viewmodels/
├── widgets/
├── services/
├── models/
└── index.dart

步骤 3: 创建 Todo 模型

创建 JSON 文件

bash
cat > todo.json << 'EOF'
{
  "id": "1",
  "title": "Learn Flutter",
  "description": "Complete Flutter tutorial",
  "is_completed": false,
  "created_at": "2024-01-01T00:00:00Z"
}
EOF

生成模型

bash
flu-cli a model todo -f todo --json todo.json

生成文件: lib/features/todo/models/todo_model.dart

查看生成的代码

dart
class Todo {
  final String id;
  final String title;
  final String description;
  final bool isCompleted;
  final String createdAt;

  Todo({
    required this.id,
    required this.title,
    required this.description,
    required this.isCompleted,
    required this.createdAt,
  });

  factory Todo.fromJson(Map<String, dynamic> json) {
    return Todo(
      id: json['id'] as String,
      title: json['title'] as String,
      description: json['description'] as String,
      isCompleted: json['is_completed'] as bool,
      createdAt: json['created_at'] as String,
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'title': title,
      'description': description,
      'is_completed': isCompleted,
      'created_at': createdAt,
    };
  }
}

步骤 4: 创建 Todo 服务

bash
flu-cli a service todo -f todo --type storage

生成文件: lib/features/todo/services/todo_service.dart

编辑服务

dart
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
import '../models/todo_model.dart';

class TodoService {
  static const String _key = 'todos';

  Future<List<Todo>> getTodos() async {
    final prefs = await SharedPreferences.getInstance();
    final String? todosJson = prefs.getString(_key);

    if (todosJson == null) return [];

    final List<dynamic> decoded = json.decode(todosJson);
    return decoded.map((json) => Todo.fromJson(json)).toList();
  }

  Future<void> saveTodos(List<Todo> todos) async {
    final prefs = await SharedPreferences.getInstance();
    final String encoded = json.encode(
      todos.map((todo) => todo.toJson()).toList(),
    );
    await prefs.setString(_key, encoded);
  }

  Future<void> addTodo(Todo todo) async {
    final todos = await getTodos();
    todos.add(todo);
    await saveTodos(todos);
  }

  Future<void> updateTodo(Todo todo) async {
    final todos = await getTodos();
    final index = todos.indexWhere((t) => t.id == todo.id);
    if (index != -1) {
      todos[index] = todo;
      await saveTodos(todos);
    }
  }

  Future<void> deleteTodo(String id) async {
    final todos = await getTodos();
    todos.removeWhere((t) => t.id == id);
    await saveTodos(todos);
  }
}

步骤 5: 创建 Todo 列表页面

bash
flu-cli a page todo_list -f todo

生成文件:

  • lib/features/todo/pages/todo_list_page.dart
  • lib/features/todo/viewmodels/todo_list_viewmodel.dart

编辑 ViewModel

dart
import 'package:flutter/foundation.dart';
import '../models/todo_model.dart';
import '../services/todo_service.dart';

class TodoListViewModel extends ChangeNotifier {
  final TodoService _service = TodoService();

  List<Todo> _todos = [];
  List<Todo> get todos => _todos;

  bool _isLoading = false;
  bool get isLoading => _isLoading;

  TodoListViewModel() {
    loadTodos();
  }

  Future<void> loadTodos() async {
    _isLoading = true;
    notifyListeners();

    try {
      _todos = await _service.getTodos();
    } catch (e) {
      debugPrint('Error loading todos: $e');
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }

  Future<void> addTodo(String title, String description) async {
    final todo = Todo(
      id: DateTime.now().millisecondsSinceEpoch.toString(),
      title: title,
      description: description,
      isCompleted: false,
      createdAt: DateTime.now().toIso8601String(),
    );

    await _service.addTodo(todo);
    await loadTodos();
  }

  Future<void> toggleTodo(Todo todo) async {
    final updated = Todo(
      id: todo.id,
      title: todo.title,
      description: todo.description,
      isCompleted: !todo.isCompleted,
      createdAt: todo.createdAt,
    );

    await _service.updateTodo(updated);
    await loadTodos();
  }

  Future<void> deleteTodo(String id) async {
    await _service.deleteTodo(id);
    await loadTodos();
  }
}

编辑页面

dart
import 'package:flutter/material.dart';
import '../viewmodels/todo_list_viewmodel.dart';

class TodoListPage extends StatefulWidget {
  const TodoListPage({super.key});

  @override
  State<TodoListPage> createState() => _TodoListPageState();
}

class _TodoListPageState extends State<TodoListPage> {
  late final TodoListViewModel _viewModel;
  final _titleController = TextEditingController();
  final _descController = TextEditingController();

  @override
  void initState() {
    super.initState();
    _viewModel = TodoListViewModel();
    _viewModel.addListener(() => setState(() {}));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Todo List'),
      ),
      body: _viewModel.isLoading
          ? const Center(child: CircularProgressIndicator())
          : _buildTodoList(),
      floatingActionButton: FloatingActionButton(
        onPressed: _showAddDialog,
        child: const Icon(Icons.add),
      ),
    );
  }

  Widget _buildTodoList() {
    if (_viewModel.todos.isEmpty) {
      return const Center(
        child: Text('No todos yet. Add one!'),
      );
    }

    return ListView.builder(
      itemCount: _viewModel.todos.length,
      itemBuilder: (context, index) {
        final todo = _viewModel.todos[index];
        return ListTile(
          leading: Checkbox(
            value: todo.isCompleted,
            onChanged: (_) => _viewModel.toggleTodo(todo),
          ),
          title: Text(
            todo.title,
            style: TextStyle(
              decoration: todo.isCompleted
                  ? TextDecoration.lineThrough
                  : null,
            ),
          ),
          subtitle: Text(todo.description),
          trailing: IconButton(
            icon: const Icon(Icons.delete),
            onPressed: () => _viewModel.deleteTodo(todo.id),
          ),
        );
      },
    );
  }

  void _showAddDialog() {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Add Todo'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextField(
              controller: _titleController,
              decoration: const InputDecoration(labelText: 'Title'),
            ),
            TextField(
              controller: _descController,
              decoration: const InputDecoration(labelText: 'Description'),
            ),
          ],
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('Cancel'),
          ),
          TextButton(
            onPressed: () {
              _viewModel.addTodo(
                _titleController.text,
                _descController.text,
              );
              _titleController.clear();
              _descController.clear();
              Navigator.pop(context);
            },
            child: const Text('Add'),
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    _titleController.dispose();
    _descController.dispose();
    _viewModel.dispose();
    super.dispose();
  }
}

步骤 6: 添加依赖

编辑 pubspec.yaml:

yaml
dependencies:
    flutter:
        sdk: flutter
    shared_preferences: ^2.2.0

安装依赖:

bash
flutter pub get

步骤 7: 更新路由

编辑 lib/core/router/app_router.dart:

dart
import 'package:flutter/material.dart';
import '../../features/todo/pages/todo_list_page.dart';

class AppRouter {
  static Route<dynamic> generateRoute(RouteSettings settings) {
    switch (settings.name) {
      case '/':
        return MaterialPageRoute(builder: (_) => const TodoListPage());
      default:
        return MaterialPageRoute(
          builder: (_) => Scaffold(
            body: Center(
              child: Text('No route defined for ${settings.name}'),
            ),
          ),
        );
    }
  }
}

步骤 8: 运行项目

bash
flutter run

完整项目结构

todo_app/
├── lib/
│   ├── main.dart
│   ├── app.dart
│   ├── core/
│   │   ├── router/
│   │   │   └── app_router.dart
│   │   └── theme/
│   │       └── app_theme.dart
│   └── features/
│       └── todo/
│           ├── models/
│           │   ├── todo_model.dart
│           │   └── index.dart
│           ├── services/
│           │   ├── todo_service.dart
│           │   └── index.dart
│           ├── viewmodels/
│           │   ├── todo_list_viewmodel.dart
│           │   └── index.dart
│           ├── pages/
│           │   ├── todo_list_page.dart
│           │   └── index.dart
│           └── index.dart
├── pubspec.yaml
└── README.md

功能演示

1. 查看 Todo 列表

启动应用后,显示所有 Todo 项。

2. 添加 Todo

点击右下角的 + 按钮,输入标题和描述,点击 Add。

3. 标记完成

点击 Todo 项左侧的复选框,标记为完成/未完成。

4. 删除 Todo

点击 Todo 项右侧的删除图标,删除该项。

扩展功能

添加 Todo 详情页

bash
flu-cli a page todo_detail -f todo

添加 Todo 编辑功能

bash
flu-cli a page todo_edit -f todo

添加分类功能

bash
flu-cli a model category -f todo
flu-cli a service category -f todo --type storage

添加搜索功能

在 ViewModel 中添加搜索方法:

dart
List<Todo> searchTodos(String query) {
  return _todos.where((todo) =>
    todo.title.toLowerCase().contains(query.toLowerCase()) ||
    todo.description.toLowerCase().contains(query.toLowerCase())
  ).toList();
}

总结

通过这个示例,你学会了:

  1. ✅ 使用 flu-cli 创建项目
  2. ✅ 创建功能模块
  3. ✅ 从 JSON 生成模型
  4. ✅ 创建服务层
  5. ✅ 创建页面和 ViewModel
  6. ✅ 实现完整的 CRUD 功能

下一步

Released under the MIT License.