skills/aidenreed937/comet/feature-workflow

feature-workflow

SKILL.md

Feature 开发工作流

完整的 Feature 开发流程,确保代码分层清晰、UI 无硬编码。


🔄 工作流程图

┌─────────────────────────────────────────────────────────────────────────────┐
│                           Phase 0: 需求分析                                  │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐                          │
│  │   文字描述   │  │  UI 截图    │  │  设计稿     │                          │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘                          │
│         └────────────────┼────────────────┘                                 │
│                          ▼                                                  │
│              ┌───────────────────────┐                                      │
│              │  提取: 实体 / API / UI │                                      │
│              └───────────┬───────────┘                                      │
└──────────────────────────┼──────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│                         Phase 1-4: 分层开发                                  │
│                                                                             │
│  ┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐   │
│  │ Domain  │───▶│  Data   │───▶│Provider │───▶│   UI    │───▶│  Route  │   │
│  │  实体    │    │ 数据源   │    │ 状态管理 │    │  页面   │    │  路由   │   │
│  └────┬────┘    └────┬────┘    └────┬────┘    └────┬────┘    └────┬────┘   │
│       │              │              │              │              │         │
│       ▼              ▼              ▼              ▼              ▼         │
│    [检查点]       [检查点]       [检查点]       [检查点]       [检查点]      │
└──────────────────────────┬──────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│                         Phase 5: 质量检查                                    │
│                                                                             │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐        │
│  │   analyze   │  │   format    │  │    test     │  │   l10n      │        │
│  └─────────────┘  └─────────────┘  └─────────────┘  └─────────────┘        │
│                                                                             │
│                    参考: .claude/skills/code-quality                        │
└─────────────────────────────────────────────────────────────────────────────┘

📋 Phase 0: 需求分析

输入类型

输入 分析要点
文字描述 提取功能点、业务规则、数据流向
UI 截图 识别组件结构、交互方式、状态变化
设计稿 提取颜色/字体(映射到 Theme)、间距、组件层级

分析输出

## 需求分析结果

### 1. 实体定义
- 实体名称: User
- 字段: id, name, email, avatar
- 关联: UserRole (可选)

### 2. API 接口
- GET /users - 获取用户列表
- GET /users/:id - 获取用户详情
- POST /users - 创建用户

### 3. UI 组件
- UserListPage: 列表页面
- UserListItem: 列表项组件
- UserDetailPage: 详情页面

### 4. 状态流转
- Initial → Loading → Loaded/Error
- 支持下拉刷新、分页加载

### 5. 国际化文本
- userListTitle: 用户列表
- userDetailTitle: 用户详情
- emptyList: 暂无用户

Phase 0 检查清单

检查项 状态
☐ 实体字段已明确
☐ API 接口已确认(或 mock 方案)
☐ UI 组件层级已拆分
☐ 状态流转已定义
☐ 国际化 key 已规划

🚫 核心原则:UI 层禁止硬编码

禁止项

// ❌ 禁止:硬编码文本
Text('用户列表')

// ❌ 禁止:硬编码颜色/尺寸
Container(color: Color(0xFF2196F3), padding: EdgeInsets.all(16))

// ❌ 禁止:模拟数据
final users = [User(name: 'Test'), User(name: 'Demo')];

// ❌ 禁止:魔法数字
SizedBox(height: 24)

正确做法

// ✅ 国际化文本
Text(context.l10n.userListTitle)

// ✅ 主题颜色/间距
Container(
  color: Theme.of(context).colorScheme.primary,
  padding: const EdgeInsets.all(AppSpacing.md),
)

// ✅ 从 Provider 获取数据
final users = ref.watch(userListProvider);

// ✅ 命名常量
SizedBox(height: AppSpacing.lg)

📁 开发顺序(自底向上)

Step 1: Domain 层(纯 Dart)

目的:定义业务实体和仓库接口

lib/features/<name>/domain/
├── entities/
│   └── <name>.dart          # 业务实体
└── repositories/
    └── <name>_repository.dart  # 仓库接口

实体模板

// domain/entities/user.dart
class User {
  const User({
    required this.id,
    required this.name,
    required this.email,
  });

  final String id;
  final String name;
  final String email;

  User copyWith({String? id, String? name, String? email}) {
    return User(
      id: id ?? this.id,
      name: name ?? this.name,
      email: email ?? this.email,
    );
  }

  
  bool operator ==(Object other) =>
      identical(this, other) || other is User && id == other.id;

  
  int get hashCode => id.hashCode;
}

仓库接口模板

// domain/repositories/user_repository.dart
import '../entities/user.dart';
import '../../../../core/utils/result.dart';

abstract class UserRepository {
  Future<Result<List<User>>> getUsers();
  Future<Result<User>> getUserById(String id);
  Future<Result<void>> saveUser(User user);
}

Step 2: Data 层

目的:实现数据源和仓库

lib/features/<name>/data/
├── datasources/
│   ├── <name>_remote_data_source.dart  # 网络数据源
│   └── <name>_local_data_source.dart   # 本地数据源
├── models/
│   └── <name>_dto.dart                 # 数据传输对象
└── repositories/
    └── <name>_repository_impl.dart     # 仓库实现

远程数据源模板

// data/datasources/user_remote_data_source.dart
import '../../../../core/network/dio_client.dart';
import '../models/user_dto.dart';

abstract class UserRemoteDataSource {
  Future<List<UserDto>> getUsers();
  Future<UserDto> getUserById(String id);
}

class UserRemoteDataSourceImpl implements UserRemoteDataSource {
  UserRemoteDataSourceImpl({required this.dioClient});

  final DioClient dioClient;

  
  Future<List<UserDto>> getUsers() async {
    final response = await dioClient.get('/users');
    return (response.data as List)
        .map((json) => UserDto.fromJson(json))
        .toList();
  }

  
  Future<UserDto> getUserById(String id) async {
    final response = await dioClient.get('/users/$id');
    return UserDto.fromJson(response.data);
  }
}

DTO 模板

// data/models/user_dto.dart
import '../../domain/entities/user.dart';

class UserDto {
  UserDto({required this.id, required this.name, required this.email});

  factory UserDto.fromJson(Map<String, dynamic> json) {
    return UserDto(
      id: json['id'] as String,
      name: json['name'] as String,
      email: json['email'] as String,
    );
  }

  final String id;
  final String name;
  final String email;

  Map<String, dynamic> toJson() => {'id': id, 'name': name, 'email': email};

  User toEntity() => User(id: id, name: name, email: email);
}

仓库实现模板

// data/repositories/user_repository_impl.dart
import '../../../../core/error/error_mapper.dart';
import '../../../../core/utils/result.dart';
import '../../domain/entities/user.dart';
import '../../domain/repositories/user_repository.dart';
import '../datasources/user_remote_data_source.dart';

class UserRepositoryImpl implements UserRepository {
  UserRepositoryImpl({required this.remoteDataSource});

  final UserRemoteDataSource remoteDataSource;

  
  Future<Result<List<User>>> getUsers() async {
    try {
      final dtos = await remoteDataSource.getUsers();
      return Success(dtos.map((dto) => dto.toEntity()).toList());
    } catch (e) {
      return Err(ErrorMapper.mapException(e));
    }
  }

  
  Future<Result<User>> getUserById(String id) async {
    try {
      final dto = await remoteDataSource.getUserById(id);
      return Success(dto.toEntity());
    } catch (e) {
      return Err(ErrorMapper.mapException(e));
    }
  }

  
  Future<Result<void>> saveUser(User user) async {
    // 实现保存逻辑
    return const Success(null);
  }
}

Step 3: Presentation 层 - Provider

目的:状态管理和业务逻辑

lib/features/<name>/presentation/
└── providers/
    └── <name>_provider.dart

Provider 模板(异步数据)

// presentation/providers/user_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../../../../app/di.dart';
import '../../data/datasources/user_remote_data_source.dart';
import '../../data/repositories/user_repository_impl.dart';
import '../../domain/entities/user.dart';
import '../../domain/repositories/user_repository.dart';

// 数据源 Provider
final userRemoteDataSourceProvider = Provider<UserRemoteDataSource>((ref) {
  return UserRemoteDataSourceImpl(dioClient: ref.watch(dioClientProvider));
});

// 仓库 Provider
final userRepositoryProvider = Provider<UserRepository>((ref) {
  return UserRepositoryImpl(
    remoteDataSource: ref.watch(userRemoteDataSourceProvider),
  );
});

// 状态定义
sealed class UserListState {
  const UserListState();
}

class UserListInitial extends UserListState {
  const UserListInitial();
}

class UserListLoading extends UserListState {
  const UserListLoading();
}

class UserListLoaded extends UserListState {
  const UserListLoaded(this.users);
  final List<User> users;
}

class UserListError extends UserListState {
  const UserListError(this.message);
  final String message;
}

// Controller
final userListControllerProvider =
    NotifierProvider<UserListController, UserListState>(
  UserListController.new,
);

class UserListController extends Notifier<UserListState> {
  
  UserListState build() {
    // 初始化时加载数据
    Future.microtask(loadUsers);
    return const UserListLoading();
  }

  UserRepository get _repository => ref.read(userRepositoryProvider);

  Future<void> loadUsers() async {
    state = const UserListLoading();
    final result = await _repository.getUsers();
    result.when(
      success: (users) => state = UserListLoaded(users),
      failure: (failure) => state = UserListError(failure.message),
    );
  }

  Future<void> refresh() async {
    await loadUsers();
  }
}

Step 4: Presentation 层 - UI

目的:纯 UI 展示,无业务逻辑

lib/features/<name>/presentation/
├── pages/
│   └── <name>_page.dart      # 页面容器
└── widgets/
    └── <name>_view.dart      # 视图组件

Page 模板

// presentation/pages/user_list_page.dart
import 'package:flutter/material.dart';

import '../../../../core/l10n/l10n.dart';
import '../../../../core/widgets/app_scaffold.dart';
import '../widgets/user_list_view.dart';

class UserListPage extends StatelessWidget {
  const UserListPage({super.key});

  
  Widget build(BuildContext context) {
    return AppScaffold(
      appBar: AppBar(title: Text(context.l10n.userListTitle)),
      body: const UserListView(),
    );
  }
}

View 模板(处理状态)

// presentation/widgets/user_list_view.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../../../../core/l10n/l10n.dart';
import '../../../../core/widgets/error_view.dart';
import '../../../../core/widgets/loading_indicator.dart';
import '../providers/user_provider.dart';
import 'user_list_item.dart';

class UserListView extends ConsumerWidget {
  const UserListView({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(userListControllerProvider);

    return switch (state) {
      UserListInitial() => const SizedBox.shrink(),
      UserListLoading() => const LoadingIndicator(),
      UserListError(:final message) => ErrorView(
          message: message,
          onRetry: () => ref.read(userListControllerProvider.notifier).refresh(),
        ),
      UserListLoaded(:final users) => users.isEmpty
          ? Center(child: Text(context.l10n.emptyList))
          : RefreshIndicator(
              onRefresh: () =>
                  ref.read(userListControllerProvider.notifier).refresh(),
              child: ListView.builder(
                itemCount: users.length,
                itemBuilder: (context, index) => UserListItem(user: users[index]),
              ),
            ),
    };
  }
}

Item 模板

// presentation/widgets/user_list_item.dart
import 'package:flutter/material.dart';

import '../../domain/entities/user.dart';

class UserListItem extends StatelessWidget {
  const UserListItem({super.key, required this.user});

  final User user;

  
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    return ListTile(
      leading: CircleAvatar(
        backgroundColor: theme.colorScheme.primaryContainer,
        child: Text(
          user.name.isNotEmpty ? user.name[0].toUpperCase() : '?',
          style: TextStyle(color: theme.colorScheme.onPrimaryContainer),
        ),
      ),
      title: Text(user.name, style: theme.textTheme.titleMedium),
      subtitle: Text(user.email, style: theme.textTheme.bodySmall),
    );
  }
}

Step 5: 路由配置

// presentation/routes.dart
import 'package:go_router/go_router.dart';

import 'pages/user_list_page.dart';

class UserRoutes {
  UserRoutes._();

  static const String userList = '/users';
  static const String userDetail = '/users/:id';
}

List<GoRoute> buildUserRoutes() => [
  GoRoute(
    path: UserRoutes.userList,
    builder: (context, state) => const UserListPage(),
  ),
];

注册到 app/router.dart

import '../features/user/presentation/routes.dart';

final routerProvider = Provider<GoRouter>((ref) => GoRouter(
  routes: [
    ...buildUserRoutes(),
    // 其他路由...
  ],
));

Step 6: 国际化

添加到 l10n/app_en.arb

{
  "userListTitle": "Users",
  "emptyList": "No data available"
}

添加到 l10n/app_zh.arb

{
  "userListTitle": "用户列表",
  "emptyList": "暂无数据"
}

生成

flutter gen-l10n

✅ 各阶段检查清单

Phase 1: Domain 检查点

检查项 状态
☐ 实体类使用 const 构造函数
☐ 所有字段使用 final
☐ 实现 copyWith 方法
☐ 重写 ==hashCode
☐ 仓库接口返回 Result<T>
☐ 无 Flutter 依赖(纯 Dart)

Phase 2: Data 检查点

检查项 状态
☐ DTO 与 Entity 分离
fromJson / toJson 实现完整
toEntity() 转换方法
☐ 数据源接口 + 实现分离
☐ 异常捕获并转换为 Failure
☐ 使用 ErrorMapper.mapException()

Phase 3: Provider 检查点

检查项 状态
☐ 状态使用 sealed class 定义
☐ 包含 Initial/Loading/Loaded/Error 状态
☐ Controller 继承 NotifierAsyncNotifier
☐ 数据加载在 Controller 中完成
☐ Provider 依赖链正确(DataSource → Repository → Controller)

Phase 4: UI 检查点

检查项 状态
☐ 文本使用 context.l10n.xxx(无硬编码)
☐ 颜色使用 Theme.of(context)(无硬编码)
☐ 间距使用命名常量(无魔法数字)
☐ 数据来自 Provider(无模拟数据)
☐ Page 与 View/Item 组件分离
☐ 使用 switch 表达式处理状态
☐ Loading/Error/Empty 状态 UI 完整
☐ 使用 const 构造函数

Phase 4.5: Route & L10n 检查点

检查项 状态
☐ 路由常量定义在 routes.dart
buildXxxRoutes() 函数已导出
☐ 路由已注册到 app/router.dart
☐ 国际化 key 已添加到 app_en.arb
☐ 国际化 key 已添加到 app_zh.arb
☐ 已运行 flutter gen-l10n

🔍 Phase 5: 质量检查

参考: .claude/skills/code-quality/SKILL.md

执行命令

# 1. 代码分析(必须通过)
flutter analyze --fatal-infos

# 2. 格式检查(必须通过)
dart format --set-exit-if-changed .

# 3. 运行测试(必须通过)
flutter test test/features/<name>/

# 4. 生成国际化(如有变更)
flutter gen-l10n

# 5. 依赖检查(建议)
flutter pub outdated

Phase 5 检查清单

5.1 静态分析

检查项 命令 状态
☐ 无 analyze 错误 flutter analyze
☐ 无 analyze 警告 flutter analyze --fatal-infos
☐ 代码格式正确 dart format --set-exit-if-changed .

5.2 测试覆盖

检查项 状态
☐ Domain 层单元测试
☐ Provider/Controller 测试
☐ 测试全部通过

5.3 安全检查

检查项 状态
☐ 无硬编码 API 密钥/Token
☐ 无硬编码密码/Secret
☐ 敏感数据使用 SecureStorage
☐ 网络请求使用 HTTPS
☐ 无敏感信息在日志中输出

5.4 性能检查

检查项 标准 状态
☐ 单文件行数 < 500 行
☐ Widget 嵌套层级 < 10 层
☐ 列表使用 ListView.builder -
☐ 使用 const 构造函数 -
☐ 避免在 build 中创建大对象 -

5.5 代码规范

检查项 状态
☐ 文件命名 snake_case
☐ 类命名 PascalCase
☐ 私有成员 _ 前缀
☐ 导入语句已排序
☐ 无未使用的导入/变量

质量检查自动化(推荐)

使用子代理执行完整质量检查:

Task({
  subagent_type: 'general-purpose',
  description: '运行 Feature 质量检查',
  prompt: `
对 lib/features/<name>/ 执行完整质量检查:

1. flutter analyze lib/features/<name>/
2. dart format --set-exit-if-changed lib/features/<name>/
3. flutter test test/features/<name>/

如有错误,分析并修复,再次验证直到全部通过。
返回检查结果摘要。

遵循 .claude/skills/code-quality/SKILL.md 中的规范。
  `,
})

📋 完整检查清单汇总

阶段 核心检查项
Phase 0 需求分析完整(实体/API/UI/状态/L10n)
Phase 1 Domain 纯 Dart,immutable 实体
Phase 2 Data DTO 分离,异常转 Failure
Phase 3 Provider sealed class 状态
Phase 4 UI 无硬编码,数据来自 Provider
Phase 4.5 路由注册,国际化完成
Phase 5 analyze + format + test 全通过

🔧 常用命令速查

# 开发流程
flutter pub get                              # 获取依赖
flutter gen-l10n                             # 生成国际化

# 质量检查
flutter analyze                              # 代码分析
flutter analyze lib/features/<name>/         # 分析指定 feature
dart format .                                # 格式化
dart format lib/features/<name>/             # 格式化指定 feature

# 测试
flutter test                                 # 全部测试
flutter test test/features/<name>/           # Feature 测试
flutter test --coverage                      # 覆盖率报告

# 依赖
flutter pub outdated                         # 检查过期依赖
flutter pub upgrade                          # 升级依赖
Weekly Installs
4
First Seen
Jan 29, 2026
Installed on
cursor4
opencode4
qoder3
gemini-cli3
codebuddy3
claude-code3