按 08 总纲路径 C:Flutter 是卫星,P1 按需。本模块不是主线课程,不要在没有 APP 项目时提前学它——卫星语言最容易"学完就忘",只有项目逼着你用,两周才能出活。
安装细节(下载链接、PATH 配置截图)属于 09 环境模块;这里只给"为跑通工作台 APP 所必需的最小步骤"。版本锁死 stable 3.47.6(随包 Dart 3.13.5),不选 master/beta。
D:\dev\flutter(不要放 C:\Program Files,权限会坑你)。完整归档清单见 https://docs.flutter.dev/install/archive。D:\dev\flutter\bin 加入用户 PATH(系统属性 → 环境变量 → 用户变量 Path → 新建)。# PowerShell(用户变量,永久生效;重开终端后生效)
[Environment]::SetEnvironmentVariable("FLUTTER_STORAGE_BASE_URL","https://storage.flutter-io.cn","User")
[Environment]::SetEnvironmentVariable("PUB_HOSTED_URL","https://pub.flutter-io.cn","User")
# 重开 PowerShell 后验证
echo $env:FLUTTER_STORAGE_BASE_URL
flutter --version # 应输出 Flutter 3.47.6 / Dart 3.13.5
打开新的 PowerShell,运行 flutter doctor -v。按下面三块逐个修:
| doctor 检查项 | 要做什么 | 不想做 Android 时 |
|---|---|---|
| Flutter(自身) | 装完 SDK + 镜像后应自动绿;红了就 flutter doctor 按提示修。 | 必须绿。 |
| Windows 桌面开发 | 需要 Visual Studio 2022,安装时勾选"使用 C++ 的桌面开发"工作负载(含 MSVC v143、C++ CMake tools、Windows 10/11 SDK)。装完重开终端再 doctor。 | 这是本模块第一个平台,必须绿。 |
| Android 工具链 | 装 Android Studio → Settings → SDK Tools 勾"Android SDK Command-line Tools (latest)"→ 运行 flutter doctor --android-licenses 一路按 y。 | 暂时不做 Android?跳过本项即可:APP 先跑在 Windows 桌面上接工作台,doctor 里这一项是 [!] 不影响 flutter run -d windows。等要打 APK 再回来补。 |
| Chrome / VS Code | Chrome 可选(本模块不做 Web);VS Code 装"Dart-Code"扩展即可,非必须。 | 忽略。 |
目标状态:至少 [✓] Flutter 与 [✓] Windows 桌面开发 两项绿;Android 项可黄。跑 flutter doctor 看到这两项绿就进 §3。
Flutter 的 Windows 桌面端不是"套个 WebView",而是用 C++ 写的桌面壳(win32 runner),构建时需要 MSVC 编译器和 CMake。缺了它,flutter build windows 会在链接阶段报错(见 §8 报错表第 6 条)。装 VS2022 Community 版即可,免费;只勾"使用 C++ 的桌面开发"一个工作负载,别全装(省磁盘)。
你不是零基础。这一节不教"什么是变量",只列 Dart 和你已知语言的差异点,半小时扫完,够读懂后面的示例代码。
| 概念 | Dart 写法 | 对照 |
|---|---|---|
| 内置类型 | int double String bool | 同 Java/JS;注意 Dart 的数字分 int 和 double,不像 PHP 全是弱转换。 |
| 推断变量 | var x = 3;(x 被锁定为 int,不能再赋字符串) | 同 TypeScript 的 var;比 JS 严格。 |
| 不可变绑定 | final name = 'abc';(运行期确定一次)const PI = 3.14;(编译期常量) | 对照 Java 的 final / Go 的 const。能用 final 就用 final。 |
| 动态类型 | dynamic v = anything; | 等于 TS 的 any;不要滥用,空安全会被它绕开。 |
| 字符串 | '$name' 、 '${obj.field}' | JS 模板字符串;Dart 单双引号等价。 |
// 箭头函数:单行函数体,对照 JS/Go 的闭包 int add(int a, int b) => a + b; // 命名参数(花括号):对照 Python 关键字参数;required 必传 String greet({required String name, String title = ''}) => '$title $name'; // 类:默认无 setter/setter 魔法,就是普通字段 class Task { final int id; String title; bool done; Task({required this.id, required this.title, this.done = false}); } // mixin:Dart 的代码复用手段(对照 Java interface default / Go 无) mixin Loggable { void log(String msg) => print('[LOG] $msg'); } class Api with Loggable { void call() => log('called'); // 直接拿到 log 方法 }
Dart 单线程 + 事件循环,异步模型和 JS 几乎一模一样:Future<T> 就是 JS 的 Promise<T>,async/await 语义完全相同。和 Go 的 goroutine 不是一回事——Dart 没有抢占式线程,靠 await 挂起点让事件循环跑别的任务。
// 对照 JS:const r = await fetch(url).then(r => r.json()) Future<List<Task>> fetchTasks() async { final resp = await http.get(Uri.parse('/api/v1/tasks')); if (resp.statusCode == 200) { return parseTasks(resp.body); // 同步函数直接返回,包在 Future 里 } throw Exception('HTTP ${resp.statusCode}'); } // 调用处照样 await;错误用 try/catch(对照 Java/JS) try { final tasks = await fetchTasks(); } catch (e) { print('失败: $e'); }
| 写法 | 含义 | 对照 |
|---|---|---|
String s; | 非空,必须有值,否则编译报错 | Java 没有这层;这是 Dart 2.12+ 的强制空安全 |
String? s; | 可空,可能是 null;用它之前要判空 | 对照 Kotlin 的 String? / TS 的 string | null |
s! | "我确定它不是 null",强解包;真的是 null 会运行时炸 | 对照 Kotlin 的 !! |
s?.length | s 为 null 则整个表达式为 null,否则取 length | 对照 JS/TS 的可选链 ?. |
late String s; | "我保证稍后会给它赋值",先用着,编译期不报错 | State 对象里常用(initState 里才初始化) |
s ?? 'default' | null 则取默认值 | 对照 JS 的 ??(注意不是 ||) |
// List / Map / Set,都是字面量,对照 JS 数组与对象 final List<String> names = ['a', 'b']; final Map<String, dynamic> json = {'code': 0, 'data': []}; // 展开与 if-in-list:Flutter UI 里天天用 final items = [ 'header', if (showExtra) 'extra', // 集合 if ...names, // 展开 ]; names.map((n) => n.toUpperCase()).toList(); // 链式,同 JS
扫完这节就够了:Dart 对你而言 = "带空安全的 TypeScript,跑在 Flutter 上"。剩下的语法细节在写 §5/§6 代码时遇到再查,不要现在去读 Dart 官方语言之旅。
Flutter 的世界观只有一句话:一切皆 Widget,UI 是 Widget 树,build 方法就是"用当前状态算出这一帧长什么样"。没有 XML 模板、没有虚拟 DOM diff 之外的魔法——你在 build 里 new 一堆 Widget,框架负责高效地画到屏幕上。
| 类型 | 特征 | 什么时候用 |
|---|---|---|
| StatelessWidget | 一旦 build 完就不变;输入变了由父级重建它。 | 纯展示:一个按钮、一行文字、一张卡片。默认先用它。 |
| StatefulWidget | 有一个配套的 State 对象,setState() 后框架重新调用 build。 | 有"会变的东西":输入框内容、计数器、加载中/成功/失败切换。 |
// 最小 StatefulWidget:计数器(你将来在 §5 用 Riverpod 替代 setState) class Counter extends StatefulWidget { @override State<Counter> createState() => _CounterState(); } class _CounterState extends State<Counter> { int _n = 0; @override Widget build(BuildContext context) { return Column(children: [ Text('点击了 $_n 次'), ElevatedButton( onPressed: () => setState(() => _n++), // setState 触发重建 child: const Text('+1'), ), ]); } }
| 组件 | 干什么 | 易错点 |
|---|---|---|
| Row | 子元素水平排列(flex-direction: row)。 | 横向超宽会黄黑条纹报错;用 Expanded 包子元素让它自适应压缩。 |
| Column | 子元素垂直排列(flex-direction: column)。 | 放进 ListView 时不要设无限高;Column 要配 MainAxisAlignment 控制对齐。 |
| Stack | 子元素层叠(对照 CSS absolute 定位容器)。 | 里面定位用 Positioned(left:.. ,bottom:..)。 |
| ListView | 可滚动长列表,懒加载。 | 不要把 Column 直接塞进无限高滚动区;列表就用 ListView.builder。 |
// 文字 / 按钮 / 输入框 / 卡片:一次认全 Text('你好', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), ElevatedButton(onPressed: () {}, child: Text('登录')), TextButton(onPressed: () {}, child: Text('取消')), TextField( decoration: InputDecoration(labelText: '用户名', hintText: '请输入'), onChanged: (v) => username = v, ), Card( elevation: 2, child: ListTile( title: Text('任务标题'), subtitle: Text('备注'), trailing: Checkbox(value: false, onChanged: (v) {}), ), ), // 列表的标准写法:ListView.builder(懒加载,长列表专用) ListView.builder( itemCount: tasks.length, itemBuilder: (ctx, i) => Card(child: ListTile(title: Text(tasks[i].title))), )
Navigator.push(context, MaterialPageRoute(builder: (_) => TaskPage()));。登录页 ↔ 任务页这种两三个页面的 APP,用它就够了,不引第三方包。MaterialApp(
title: '工作台 APP',
theme: ThemeData(
colorSchemeSeed: const Color(0xFF0e7a6b), // 主色,一套色板自动派生
useMaterial3: true,
),
home: const LoginPage(), // 初始页;登录成功后换成 TaskPage
)
核心理解:Flutter 没有"找控件 id 改文字"这套(对照 Android View / jQuery)。你只写"在状态 S 下应该长什么样",状态变了就重新调 build。这和 Vue3 的响应式思路一致,你迁移成本很低。
2026 年现状一句话:Provider 是入门最小包,Riverpod 是作者重写的现代化版本(编译安全、不依赖 BuildContext),Bloc 是事件驱动的重武器。本模块 APP 规模小,但要接 dio 异步流,直接上 Riverpod——一次写对,不用将来从 Provider 迁移。
| 方案 | 版本(2026-10 核验) | 什么时候选它 | 本模块结论 |
|---|---|---|---|
| Provider | 6.1.5+1 | 状态极少、团队只想要最小概念、已熟悉 InheritedWidget。 | 概念最少,但要拿 BuildContext 找 Provider,异步样板偏多。 |
| Riverpod | flutter_riverpod 3.3.1 | 要类型安全、要优雅接异步 Future、不想到处传 context。 | 本模块推荐:登录态 + 任务列表这种异步场景正好用它。 |
| Bloc | bloc 系列 | 大型团队、严格事件-状态流、需要完整可测试的状态机。 | 样板代码重,本模块规模不上。 |
# flutter create workbench_app 之后,编辑 pubspec.yaml 的 dependencies:
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^3.3.1 # 状态管理(2026-10 核验 latest 3.3.1)
dio: ^5.11.1 # HTTP 客户端(2026-10 核验 latest 5.11.1)
# 然后运行(会生成 pubspec.lock = 锁文件,唯一事实来源)
# flutter pub get
版本纪律:上面写的是"兼容区间"(^),实际装进项目的版本由 pubspec.lock 锁定。换机器/换同事,flutter pub get 会按 lock 还原,不会飘。AI 若建议你升到 3.x 更高 minor,按 08 §7 规则 5 默认拒绝。
下面 3 个文件拼起来就是一个"全局计数器 + 任务列表"的 Riverpod 最小骨架(HTTP 接工作台在 §6 补全)。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'tasks_page.dart';
void main() => runApp(const ProviderScope(child: MyApp()));
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '工作台 APP',
theme: ThemeData(colorSchemeSeed: const Color(0xFF0e7a6b), useMaterial3: true),
home: const TasksPage(),
);
}
}
import 'package:flutter_riverpod/flutter_riverpod.dart'; // ① 一个最简单的状态:计数器(用 StateNotifierProvider,对照 Vue ref) final counterProvider = StateProvider<int>((ref) => 0); // ② 任务列表状态:状态类 + Notifier( Riverpod 3.x 推荐写法) class Task { final int id; final String title; bool done; Task({required this.id, required this.title, this.done = false}); } class TasksNotifier extends Notifier<List<Task>> { @override List<Task> build() => []; // 初始状态 void add(String title) { state = [...state, Task(id: state.length + 1, title: title)]; } void toggle(int id) { state = [ for (final t in state) if (t.id == id) Task(id: t.id, title: t.title, done: !t.done) else t, ]; } void remove(int id) { state = state.where((t) => t.id != id).toList(); } }
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'tasks_provider.dart';
class TasksPage extends ConsumerWidget {
const TasksPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final tasks = ref.watch(tasksProvider); // 监听,状态变就重建
final counter = ref.watch(counterProvider);
return Scaffold(
appBar: AppBar(title: Text('任务列表(已 $counter 次交互)')),
floatingActionButton: FloatingActionButton(
onPressed: () {
ref.read(tasksProvider.notifier).add('新任务');
ref.read(counterProvider.notifier).state++;
},
child: const Icon(Icons.add),
),
body: ListView.builder(
itemCount: tasks.length,
itemBuilder: (ctx, i) {
final t = tasks[i];
return Card(
child: ListTile(
title: Text(t.title,
style: TextStyle(
decoration: t.done ? TextDecoration.lineThrough : null)),
leading: Checkbox(
value: t.done,
onChanged: (_) => ref.read(tasksProvider.notifier).toggle(t.id),
),
trailing: IconButton(
icon: const Icon(Icons.delete, color: Colors.red),
onPressed: () => ref.read(tasksProvider.notifier).remove(t.id),
),
),
);
},
),
);
}
}
跑一下:flutter run -d windows,点 FAB 加任务、勾完成、点删除。这就是"计数器 → 任务列表"的完整闭环——§6 把 Notifier 里的本地数组换成真正的工作台 HTTP 请求即可。
这是本模块的真正验收点。严格按 workbench-contract 的端点写:不新增任何端点,请求/响应结构与 11 工作台后端完全一致。
| 包 | 特点 | 结论 |
|---|---|---|
| http | Dart 官方基础包,API 极简,但拦截器/超时/统一错误处理要自己包一层。 | 单脚本够用;APP 要带 token、要处理 401,不够省事。 |
| dio 5.11.1 | 拦截器、超时、FormData、统一异常 DioException 开箱即用;国内 Flutter 生态事实标准。 | 本模块选 dio:JWT 注入与 401 处理用拦截器一次写好。 |
| 方法 | 路径 | 请求体 | 说明 |
|---|---|---|---|
| POST | /api/v1/auth/login | {username, password} | 返回 {token, user}(包在统一信封里) |
| GET | /api/v1/tasks | 无 | 任务列表,需 Bearer |
| POST | /api/v1/tasks | {title, note, done} | 新建任务 |
| PUT | /api/v1/tasks/:id | 同上 | 更新(含标记完成) |
| DELETE | /api/v1/tasks/:id | 无 | 删除 |
统一响应信封:{"code":0,"data":{...},"msg":""},错误时 code 非 0。所有受保护请求带 Authorization: Bearer <jwt>。
import 'package:dio/dio.dart'; // ★ 本机地址:Windows 桌面跑用 localhost;Android 模拟器跑必须用 10.0.2.2 // (Android 模拟器里 localhost 指它自己,不是你的电脑) const String baseUrl = String.fromEnvironment( 'BASE_URL', defaultValue: 'http://localhost:8080', ); class Api { Api._() { dio = Dio(BaseOptions( baseUrl: baseUrl, connectTimeout: const Duration(seconds: 5), receiveTimeout: const Duration(seconds: 5), )); // 拦截器:每个请求自动塞 token;401 自动清 token 并跳登录 dio.interceptors.add(InterceptorsWrapper( onRequest: (opt, handler) { if (_token != null) { opt.headers['Authorization'] = 'Bearer $_token'; } handler.next(opt); }, onError: (e, handler) { if (e.response?.statusCode == 401) { _token = null; // 这里发一个全局事件/改状态,让 UI 切回登录页(见 6.5) } handler.next(e); }, )); } static final Api instance = Api._(); late final Dio dio; String? _token; // ① 登录拿 JWT Future<void> login(String username, String password) async { final resp = await dio.post('/api/v1/auth/login', data: { 'username': username, 'password': password, }); final body = resp.data as Map; if (body['code'] != 0) throw Exception(body['msg']); _token = body['data']['token'] as String; } // ② 任务模型(对齐工作台契约:id/title/note/done/created_at/updated_at) Task taskFromJson(Map<dynamic, dynamic> j) => Task( id: (j['id'] as num).toInt(), title: j['title'] as String, note: (j['note'] ?? '') as String, done: (j['done'] ?? false) as bool, ); // ③ 列表 Future<List<Task>> listTasks() async { final resp = await dio.get('/api/v1/tasks'); final body = resp.data as Map; if (body['code'] != 0) throw Exception(body['msg']); return (body['data'] as List).map((e) => taskFromJson(e)).toList(); } // ④ 新建 Future<void> createTask(String title, String note) async { final resp = await dio.post('/api/v1/tasks', data: {'title': title, 'note': note, 'done': false}); if ((resp.data as Map)['code'] != 0) throw Exception(resp.data['msg']); } // ⑤ 更新(标记完成同理,传完整 done) Future<void> updateTask(Task t) async { await dio.put('/api/v1/tasks/${t.id}', data: {'title': t.title, 'note': t.note, 'done': t.done}); } // ⑥ 删除 Future<void> deleteTask(int id) async { await dio.delete('/api/v1/tasks/$id'); } } // 与 §5 同一份 Task 模型(实际项目里放 models/task.dart,这里合并展示) class Task { final int id; String title; String note; bool done; Task({required this.id, required this.title, this.note = '', this.done = false}); }
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'api.dart'; // 异步任务列表:用 FutureProvider 直接包 dio 调用 final tasksProvider = FutureProvider<List<Task>>((ref) async { return Api.instance.listTasks(); // GET /api/v1/tasks }); // UI 里消费 AsyncValue:loading / error / data 三态一个都不漏 // ref.watch(tasksProvider).when( // loading: () => const CircularProgressIndicator(), // error: (e, _) => Text('加载失败: $e'), // data: (list) => ListView(...), // );
Api.instance.login(u, p);成功后 Navigator.pushReplacement 到任务页。authStateProvider(enum: unknown / loggedIn),它变 loggedOut 时 MaterialApp 的 home 自动切回登录页——这就是"token 过期被踢回登录"。when(error:…) 或 try/catch 弹 SnackBar 显示后端返回的 msg;不要吞异常(对照 08 §6.3 调试纪律)。# Windows 桌面直接跑(默认 BASE_URL=http://localhost:8080,无需改动)
flutter run -d windows
# Android 模拟器跑:必须用 10.0.2.2 代替 localhost
flutter run -d emulator-5554 --dart-define=BASE_URL=http://10.0.2.2:8080
对照 12 Vue3:你在 Vue3 里用 axios + Pinia 写过的那套(拦截器塞 token、401 跳登录、统一信封解包),这里 dio + Riverpod 是一一对应的,概念平移即可,没有新范式。
开发用 flutter run 热重载;交付物用下面两条命令。iOS 一句话:需要 Mac + Xcode,本模块不展开。
flutter build windows --release # 产物在:build\windows\x64\release\ # 里面 workbench_app.exe 连同 data\ 目录一起拷贝即可运行(绿色版)
前提:§2.2 的 VS2022"使用 C++ 的桌面开发"工作负载已装。无需签名就能自己用/内网分发;要上微软商店再另做 MSIX。
keytool -genkey -v -keystore workbench.jks -keyalg RSA -keysize 2048 -validity 10000 -alias workbench
android/ 下建 key.properties(不要提交进 git),再在 android/app/build.gradle.kts 的 signingConfigs 里读它——这是标准做法,具体 gradle 语法在你第一个真实打包任务时让 AI 按 Flutter 官方"Android 发布"文档生成,本模块不抄长配置。flutter build apk --release
# 产物:build\app\outputs\flutter-apk\app-release.apk,拷到手机安装即可
iOS 构建必须有一台 Mac(Xcode 是苹果独占),且需要 Apple Developer 账号签名才能装到真机。你当前是 Windows 机器,本模块不做 iOS;将来真要发 App Store 再借/买 Mac,按 Xcode Archive 流程走。
卡住了先查这张表。报错原文、原因、解决一一对应;还不行就按 08 §6.3 把报错原文喂给 AI。
| 报错原文/现象 | 原因 | 解决 |
|---|---|---|
[!] Windows Desktop developement component not found. Visual Studio Studio is not installed… |
缺 VS2022 C++ 工具链。 | 装 Visual Studio 2022 Community,勾选"使用 C++ 的桌面开发"工作负载(含 MSVC v143 + CMake + Windows SDK),重开终端再 flutter doctor。 |
[!] Android license status unknown. Run flutter doctor --android-licenses |
Android SDK 命令行工具装了但没接受协议。 | 运行 flutter doctor --android-licenses,全部按 y;还不行先在 Android Studio SDK Tools 里勾"Android SDK Command-line Tools (latest)"。 |
Gradle 下载卡住 / Could not GET … dl.google.com … timeout |
国内网络访问 Google Maven 慢。 | 编辑 android/build.gradle(或 settings.gradle),把 repositories 里的 google()/mavenCentral() 换成阿里云镜像前缀 maven { url 'https://maven.aliyun.com/repository/google' } 等,再 flutter clean 后重跑。 |
pub get 长时间不动 / TlsException: Handshake error |
没配国内 pub 镜像。 | 设用户环境变量 PUB_HOSTED_URL=https://pub.flutter-io.cn 与 FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn,重开终端。 |
Error: The non-nullable local/var … must be assigned before it can be used. |
Dart 空安全:非空变量没初始化就用。 | 声明处给初值;State 里延迟初始化的字段加 late;可空的类型加 ?。 |
type 'String' is not a subtype of type 'int' of 'index' 或 …'double' is not a subtype of 'int' |
JSON 转换想当然:id 后端返回数字直接 as int,或拿字符串当下标。 |
数字一律先 (j['id'] as num).toInt();字符串/枚举按真实类型解析。参考 §6.3 taskFromJson。 |
Connection refused / dio DioException [connectionTimeout] |
工作台没起;或 Android 上用了 localhost。 | 先确认 curl http://localhost:8080/healthz 通;Android 模拟器改 --dart-define=BASE_URL=http://10.0.2.2:8080;真机用电脑局域网 IP(如 192.168.x.x)并关防火墙。 |
Android 上 dio 请求直接失败,日志里 cleartext HTTP traffic not permitted |
Android 9+ 默认禁止明文 HTTP。 | 开发期在 android/app/src/main/AndroidManifest.xml 的 application 标签加 android:usesCleartextTraffic="true";上线前必须改 HTTPS。 |
flutter build windows 报缺 cmake / MSVC / 链接器错误 |
VS2022 装了但没勾 C++ CMake 工具或 Windows SDK。 | Visual Studio Installer → 修改 → "使用 C++ 的桌面开发"→ 右侧勾选"适用于 Windows 的 C++ CMake 工具"与最新 Windows 10/11 SDK → 修复安装。 |
[!] Android toolchain - Unable to locate Android SDK |
装了 Android Studio 但 Flutter 不知道 SDK 在哪。 | flutter config --android-sdk "C:\Users\<你>\AppData\Local\Android\Sdk",再 flutter doctor --android-licenses。暂时不做 Android 可忽略此项。 |
本模块只认一个验收:APP 真的登录了你的工作台,并能操作任务列表。复制下面清单逐条打勾。
| # | 验收项 | 通过标准 |
|---|---|---|
| 1 | 工作台后端在本机跑通:curl http://localhost:8080/healthz 返回 ok。 | 启动本模块的前置条件成立 |
| 2 | flutter --version 输出 3.47.6 / Dart 3.13.5;flutter doctor 里 Flutter 与 Windows 桌面两项绿。 | 环境达标 |
| 3 | 新建 workbench_app,pubspec 锁定 flutter_riverpod ^3.3.1 + dio ^5.11.1,flutter pub get 通过,pubspec.lock 已生成。 | 依赖冻结 |
| 4 | 登录页输入工作台账号密码,调用 POST /api/v1/auth/login 成功,拿到 token 并被拦截器自动塞到后续请求头。 | JWT 链路通 |
| 5 | 任务列表页 GET /api/v1/tasks 渲染出后端真实数据(不是 §5 的假本地数组)。 | 读通 |
| 6 | 能新建任务(POST)、勾选完成(PUT)、删除任务(DELETE),操作后列表刷新与工作台 Web 端一致。 | 增删改查通 |
| 7 | 人为让 token 失效(或后端重启改 JWT_SECRET),APP 下次请求收到 401,自动清 token 并切回登录页。 | 401 处理通 |
| 8 | 同一份代码在 Windows 桌面窗口跑通;若已补 Android 环境,在 Android 模拟器也跑通(用 10.0.2.2)。 | 跨端成立 |
| 9 | flutter build windows --release 出绿色版 exe;(可选)flutter build apk --release 出 APK。 | 打包通 |
全部打勾 = 本模块结业:你交付了一个真正接工作台的跨端 APP,而不是一个 demo。把这套 dio 拦截器 + Riverpod 任务列表骨架 fork 进你的 assets/scaffolds/(见 08 §5),下一个 APP 项目直接复用——这就是四件套里"架子"的卫星版。
本模块所有版本号与端点均来自 versions.md 与 workbench-contract;Dart 包版本由本模块在 pub.dev 现场核验。
| 组件 | 锁定版本(2026-10-14 核验) | 来源 |
|---|---|---|
| Flutter SDK | stable 3.47.6(2026-10-02) | versions.md A 节;https://docs.flutter.dev/install/archive |
| Dart | 3.13.5(随 Flutter,不单独装) | 同上 |
| dio | 5.11.1 | https://pub.dev/packages/dio |
| flutter_riverpod | 3.3.1 | https://pub.dev/packages/flutter_riverpod |
| provider(备选) | 6.1.5+1 | https://pub.dev/packages/provider |
| go_router(页面变多再上) | 18.0.2 | https://pub.dev/packages/go_router |
| 工作台端点 | login / tasks CRUD,Bearer JWT,统一信封 | workbench-contract.md §4(全系列唯一契约) |
| Flutter stable 3.47.6 归档与发布说明 | https://docs.flutter.dev/install/archive · https://docs.flutter.dev/release/release-notes |
| dio 5.11.1(2026-10 pub.dev latest) | https://pub.dev/packages/dio |
| flutter_riverpod 3.3.1(2026-10 pub.dev latest) | https://pub.dev/packages/flutter_riverpod |
| provider 6.1.5+1(备选方案版本核验) | https://pub.dev/packages/provider |
| go_router 18.0.2(官方维护,feature-complete) | https://pub.dev/packages/go_router |
| Flutter Windows 桌面构建需 VS C++ 工作负载;Android 模拟器访问本机用 10.0.2.2;国内镜像 storage.flutter-io.cn / pub.flutter-io.cn | https://docs.flutter.dev/platform-integration/windows/building · https://docs.flutter.dev/development/data-and-backend/networking · https://docs.flutter.dev/community/china |
| 工作台 API 契约(端点/信封/JWT/Task 模型) | workbench-contract.md(本系列内部规范 v1.0) |
(编者整理:本模块的"两周路线、选型规则、报错表、验收清单"为基于 07 报告与 08 总纲路径 C 的执行化设计,无外部独立来源。)