ワンクリックで
product-studio
Flutter Studio 客户端开发流程。从需求到发布,覆盖项目初始化、数据模型、UI 开发、构建、提交的完整工作流。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Flutter Studio 客户端开发流程。从需求到发布,覆盖项目初始化、数据模型、UI 开发、构建、提交的完整工作流。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
代码质量审计,使用 lizard/ruff 对 Python 项目进行复杂度分析、代码规范检查和重构建议。
Code 阶段 DevOps 基础设施管理。项目脚手架初始化、编码规范工具链配置、pre-commit 设置、CI lint 门禁规则。
发布 Git 仓库 Release。使用 qtcloud-devops CLI,必须先写 CHANGELOG 再发布,禁止跳步。支持子模块和主仓库两种流程。
审查仓库状态、CHANGELOG、版本一致性等,支持发布前检查、代码审查、文档审查等多场景。
数据需求文档(DRD)编写技能。用户需要编写或评审 DRD 时使用,指导定义数据 schema、字段规范、枚举约束和数据关系,作为数据契约独立于实现。
三层测试体系:单元测试、服务测试、端到端测试
| name | product-studio |
| description | Flutter Studio 客户端开发流程。从需求到发布,覆盖项目初始化、数据模型、UI 开发、构建、提交的完整工作流。 |
Flutter Studio 客户端开发流程。
flutter create --project-name <name> --org com.quanttide --platforms android,ios,web,macos,linux <path>
必须的参数:
--project-name: 包名,也是 Linux 产物名(如 qtconsult_studio)--org: Android 包名前缀--platforms: 目标平台列表name: qtconsult_studio
description: <描述>
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^9.1.0
freezed_annotation: ^3.1.0
json_annotation: ^4.11.0
go_router: ^14.0.0
dev_dependencies:
freezed: ^3.2.5
build_runner: ^2.4.6
json_serializable: ^6.9.0
flutter:
assets:
- assets/<data>.json
必须添加的依赖:
flutter_bloc:状态管理(替代 provider)freezed_annotation + freezed + build_runner + json_serializable:数据模型代码生成go_router:URL 路由assets/:数据源 JSON 文件lib/
main.dart # 入口 + BlocProvider
router.dart # RouteConfig + GoRouter + buildScreen
theme.dart # 颜色工具
constants.dart # UI 映射常量
models/ # freezed 数据模型
sources/ # 数据源抽象(base/file_source/bundle_source)
blocs/ # BLoC 状态管理
screens/ # 页面级组件
views/ # 可复用 UI 组件
test/
models/ # 模型单元测试
sources/ # 数据源测试
blocs/ # BLoC 状态管理测试
views/ # Widget 测试
screens/ # 页面级组件测试
数据源按来源类型分(base/file/bundle),不按模型分。theme.dart 和 constants.dart 直接放在 lib/ 根目录。
替代原始方案以规避以下问题:
dart:io,直接文件 I/O 导致平台锁死改用三层抽象:
sealed class DataResult<T> { const DataResult(); }
class DataSuccess<T> extends DataResult<T> {
final T data;
const DataSuccess(this.data);
}
class DataError<T> extends DataResult<T> {
final String message; final Object? error;
const DataError(this.message, {this.error});
}
abstract class DataSource {
Future<String> load(String path);
}
class DataLoader<T> {
final DataSource source;
final String path;
final T Function(Map<String, dynamic>) fromJson;
const DataLoader(this.source, this.path, this.fromJson);
Future<DataResult<T>> load() async { /* ... */ }
}
默认使用 BundleSource(基于 rootBundle,跨所有平台包括 Web),避免 FileSource(依赖 dart:io,Web 不可用)。
替代原始方案以规避以下问题:
buildScreen 字符串 switch — routeId→screen 双重映射,新增页面需改两处,遗漏则不生效json['params'] 拆解逻辑,字段名不一致导致隐蔽 bug改用以下模式:
redirect 回调统一管理 AppLifecycle 和权限校验Map<String, RouteConfig> 路由表 — 自包含,消除双重映射ScreenContext 单 source 传参 — builder callback 统一解析 screen 参数const routeConfigs = <String, RouteConfig>{
'projects': RouteConfig(path: '/projects', title: '项目'),
};
使用 freezed 生成 fromJson / copyWith / == / hashCode。
@freezed
class Lesson with _$Lesson {
const factory Lesson({
required String id,
required String title,
@Default('') String description,
}) = _Lesson;
factory Lesson.fromJson(Map<String, dynamic> json) =>
_$LessonFromJson(json);
}
编写后运行 dart run build_runner build 生成 .freezed.dart 和 .g.dart。
关键实践:
@Default,枚举 fallback 用 @JsonKey(fromJson:)extension XxxX on Xxx,不在 freezed 类里直接写constants.dartFlutter 默认用项目目录名作为应用名称,需手动修改以下文件:
| 平台 | 文件 | 修改内容 |
|---|---|---|
| Linux | linux/runner/my_application.cc | gtk_header_bar_set_title 和 gtk_window_set_title |
| Android | android/app/src/main/AndroidManifest.xml | android:label |
| iOS | ios/Runner/Info.plist | CFBundleDisplayName 和 CFBundleName |
test/
models/ # 模型单元测试,与 lib/models/ 一一对应
sources/ # 数据源/数据加载器测试
blocs/
views/ # Widget 测试,与 lib/screens/ + lib/views/ 一一对应
screens/ # Widget 测试,与 lib/screens/ + lib/views/ 一一对应
每条模型的 fromJson、copyWith 和关联枚举都需要测试:
group('Lesson', () {
test('fromJson parses all fields', () {
final lesson = Lesson.fromJson({'id': 'l1', 'title': 'T'});
expect(lesson.id, 'l1');
});
test('copyWith overrides specified fields', () {
final copy = lesson.copyWith(title: 'new');
expect(copy.title, 'new');
expect(copy.id, lesson.id);
});
});
测试要点:
copyWith 覆盖指定字段、保持未指定字段test('fromString returns published for "published"', () {
expect(ContentStatus.fromString('published'), ContentStatus.published);
});
test('fromString returns draft for unknown value', () {
expect(ContentStatus.fromString(''), ContentStatus.draft);
});
test('label returns Chinese', () {
expect(ContentStatus.draft.label, '草稿');
});
Service 测试模式:用 inject() 或 BlocProvider.value 注入数据,不加载真实 JSON 文件。
// sources 测试:用 MockSource 注入
final source = _MockSource('{"id": "l1"}');
final loader = DataLoader<Lesson>(source, 'test.json', Lesson.fromJson);
final result = await loader.load();
expect((result as DataSuccess).data.id, 'l1');
// BLoC 测试:直接设置初始状态
final bloc = LessonBloc(LessonState(data: initialData));
bloc.add(UpdateTitle('new'));
await pumpEventQueue();
expect(bloc.state.data.title, 'new');
// Widget 测试:用 BlocProvider.value 注入
Widget createTest(LessonBloc bloc) {
return MaterialApp(
home: BlocProvider.value(
value: bloc,
child: const MyScreen(),
),
);
}
设置测试视口:
testWidgets('description', (tester) async {
tester.view.physicalSize = const Size(1280, 800);
tester.view.devicePixelRatio = 1.0;
addTearDown(() {
tester.view.resetPhysicalSize();
tester.view.resetDevicePixelRatio();
});
});
Widget 测试要点:
BlocProvider.value 注入 BLoC 实例load() 方法(依赖文件 I/O)addTearDown 重置 view 参数,避免影响后续测试pumpAndSettle() 等待动画完成await pumpEventQueue() 等待异步处理cd src/<project>
dart analyze lib/ test/
flutter test
dart run build_runner build # freezed 模型变更后
flutter build linux
必须满足:
dart analyze lib/ test/ 零报错flutter test 全部通过flutter build linux 构建成功注意:pre-commit hook 仅运行
dart analyze,不包含flutter test(非交互式 shell 中不稳定)。本地开发仍需以完整验证为目标。
遵循 devops-commit 规范,提交类型:
feat: 新功能fix: 修复chore: 构建/配置变更docs: 文档子模块提交流程:
# 子模块内
cd apps/<submodule>
git add -A && git commit -m "feat: <描述>" && git push
# 主仓库
cd ../..
git add apps/<submodule>
git commit -m "chore: update <submodule> submodule (<描述>)" && git push