Run any Skill in Manus
with one click
with one click
Run any Skill in Manus with one click
Get Started$pwd:
$ git log --oneline --stat
stars:0
forks:0
updated:May 6, 2026 at 14:20
SKILL.md
[HINT] Download the complete skill directory including SKILL.md and all related files
| 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