# 项目长期记忆:强宝小助手 ## 项目概况 - 名称:`qiangbao_accounting`(强宝小助手),一个 Flutter 记账类 App。 - 定位:由 uniApp 项目 `pages/login/login.vue` 复刻迁移而来(见 `login_page.dart` 顶部注释)。 - 环境:Flutter **3.47.4** stable / Dart **3.13.3**(Windows 开发)。 - 目标平台:仅 `android/`、`ios/`、`web/`,未配置桌面端。 ## 技术栈与约定 - 状态管理:**flutter_riverpod ^3.0.0**(`Notifier` / `NotifierProvider`,非旧版 StateNotifier)。 - 网络:**dio ^5.8.0**,API 层按模块拆分在 `lib/api/`,每个模块导出 `xxxApiProvider = Provider(...)`。 - 本地存储:`shared_preferences`,统一封装在 `lib/core/storage/local_storage.dart`。 - 其他依赖:`image_picker`(OCR 选图)、`intl`、`cupertino_icons`。 - UI:Material 3(Flutter 3.16+ 默认启用),未显式设置 `useMaterial3`。 ## 架构与关键文件 - `lib/app.dart`:根组件 `QiangbaoApp extends ConsumerWidget`。配置 M3 主题,用 `auth.isLoggedIn` 在 `MainShell` / `LoginPage` 之间分发。全局 `navigatorKey` 已抽到 `lib/core/navigation/navigator_key.dart`(避免 app.dart ↔ 网络层循环依赖),供网络层 401 跳转与全局 toast 使用。 - `lib/core/network/auth_redirect.dart`:网络层与 Riverpod 的桥接(`configureAuthRedirect(container)` 在 `main.dart` 调用),提供 `forceLogout()` / `logoutAndGoLogin()`。 - `lib/pages/shell/main_shell.dart`:主框架,4 个 Tab 是**角色**不是功能 —— 首页(仪表盘)/记录(全局录入)/助手(AI)/我的,用 `IndexedStack` 保留各页状态,`NavigationBar`(M3)承载导航,默认 `_index = 0`(首页)。`initState` 里有登录态兜底跳转。 - `lib/pages/login/login_page.dart`:登录/注册页,含毛玻璃卡片(`BackdropFilter`)、动画背景圆球(`AnimationController` + `Stack` + `Positioned` + `Transform.translate`)、记住密码(`utils/crypto.dart` 加解密)。 - 首页、统计、我的**都已是真实实现**(不是占位页)。记账录入页在 `pages/add/`(`AddPage`,手动记账 / OCR 双 Tab)。 ### 模块框架(2026-09-23 做的「阶段 A」) - **`lib/core/module/app_module.dart` 是模块注册表**,`appModules` 列表是唯一的真相来源:首页仪表盘的卡片、`MinePage` 的「功能模块」列表都从它生成。**新增一个模块 = 往列表加一条 + 写页面**,导航/首页/设置页都不用改。 - 每个 `AppModule` 有:`id`(稳定标识,将来做排序/开关/AI 可读范围时持久化用)、`name`、`blurb`、`icon`(emoji,和项目卡片风格一致)、`open`(打开入口页的 `WidgetBuilder`)、**可选的 `summary`**(首页卡片上的摘要 widget,由模块自己提供,`null` 就不显示)。 - **具体模块不进底部 Tab**。代价是打开 App 多一次点击才能看到账单,换来的是「以后加模块不用改布局」。这是用户明确选的。 - 目录按模块划分:`pages/bill/`(记账:首页/统计/编辑 + `widgets/`)、`pages/note/`、`pages/add/`(记录 Tab 的录入)、`pages/home/`(全局仪表盘)、`pages/assistant/`(AI 占位)。 - 「记录」Tab 目前直接就是记账录入 —— 只有一种记录类型。**等第二种记录类型(运动/投资)出现时才需要在它上面加一层「选类型」**。 - 统计**不再是顶层 Tab**,从记账首页右上角图标进(`BillStatisticsPage`)。 - `pages/bill/bill_home_page.dart` 和 `bill_statistics_page.dart` 都是被 push 进来的,所以顶部自绘了返回箭头,底部留白也按 `Navigator.canPop()` 判断(有导航栏时留 90,没有时留 24)。 ### AI 助手(2026-09-23 阶段 B) - 链路:`lib/api/ai_api.dart`(SSE 客户端)→ `lib/providers/ai_provider.dart`(`assistantProvider` + `chatSessionsProvider` + `aiStatusProvider`)→ `lib/pages/assistant/assistant_page.dart`(真对话页)。 - **SSE 是手动解析的**:dio 没有内置 SSE 支持,用 `ResponseType.stream` 拿字节流,再 `cast>().transform(utf8.decoder).transform(LineSplitter())` 按 `event:` / `data:` / 空行切。`cast` 不能省 —— dio 给的是 `Stream`,Dart 泛型不允许它直接喂给 `StreamTransformer, String>`。 - **`sessionId` 传 null 开新会话**,后端在 `done` 事件里才把新 ID 返回,`AssistantNotifier.send` 必须把它记下来,否则下一条消息又会开一个新会话。 - 历史会话存后端(`chat_session` / `chat_message` 两张表),App 只发 `sessionId + content`。**工具调用的中间过程不入库**,所以 App 不用理解 tool 消息格式。 - 助手气泡用 `MarkdownBody` 渲染(回答里常有列表/代码块),用户气泡用 `SelectableText`。 - 后端未配 key 时不报错:`/api/ai/status` 返回 `enabled:false`,`/chat` 推一个 error 事件,页面顶部显示红色提示条。 ## 设计规范(配色) - 品牌主色:`#C8956E`(驼色/焦糖色),在 `app.dart` 中作为 `ColorScheme.fromSeed` 的 seedColor。 - 深棕文字色:`#5D4037`;辅助文字:`#8D6E63`;页面底色:`#F8F8F8`。 - 登录页另用暖橙渐变 `#FFE5CC → #FFD4A8 → #FFC08A`,点缀金色 `#FFD700`、粉色 `#FFB6C1`。 - 注意:项目中颜色多为**硬编码字面量**,尚未统一走 `Theme.of(context).colorScheme`(有优化空间)。 ## 网络环境配置(重要) - 环境切换在 `lib/core/network/api_env.dart`:`baseUrl` 三级优先级 = `--dart-define=API_BASE_URL` > `!kDebugMode` 取 `kProdBaseUrl` > `kLocalBaseUrl`。 - 消费链:`dio_client.dart` 的 `dioProvider` → `BaseOptions.baseUrl`;API 层使用相对路径(如 `/auth/login`)。 - 生产地址当前为 `http://103.36.220.231:12345/api`(明文 HTTP + 裸 IP)。**与注释及 `docs/Flutter打包与真机安装指南.md` 中记录的 `https://accounting.aqroid.cn/api` 不一致。** - `ios/Runner/Info.plist` 无任何 ATS 例外配置 → iOS 包请求 http 会被拦截。 - `android/app/src/main/AndroidManifest.xml` 已设 `android:usesCleartextTraffic="true"`。 - 已知风险:① `--dart-define` 在 release 下同样生效,可击穿"正式包走生产"的保证;② `kDebugMode` 在 profile 模式为 false → profile 包会连生产,宜改用 `kReleaseMode`。 ## 待办 / 可优化点 - **模块的排序与开关还没做**(注册表里 `id` 已为此预留);AI 的「可读范围」也需要按 `id` 配置。 - **`test/page_layout_test.dart` 在守布局**:用 320 / 393 / 480 三种宽度断言仪表盘和记账首页不溢出。改这两个页面的横向布局后跑一下 —— 它已经抓到过一次真 bug(记账首页顶部四个元素挤一行,在 393 宽的常规机上溢出 32px)。 - 硬编码颜色可迁移为 M3 角色色(`colorScheme.primary` 等),并补暗色模式。 - `api_env.dart` 优先用 `kReleaseMode` 显式判断,并统一生产地址(域名 + HTTPS);多环境改用 `--dart-define-from-file`;加运行时自检/启动日志显示当前 baseUrl。 - iOS 若需访问明文 HTTP,需在 `Info.plist` 增加 ATS 例外(`NSAllowsLocalNetworking` 或域例外)。 - `MainShell` 未做响应式:宽屏(≥600dp/840dp)应切换 `NavigationRail` / `NavigationDrawer`。 - `MainShell.initState` 的 `addPostFrameCallback` 回调里可加 `if (!mounted) return;`。 - Flutter 3.47 起 Material/Cupertino 已解耦为 `material_ui` / `cupertino_ui` 包,`package:flutter/material.dart` 仍可用,暂无需迁移。 ## 应用名称与图标(已落地) - 显示名统一为 **强宝小助手**:`AndroidManifest.xml` 的 `android:label`、`Info.plist` 的 `CFBundleDisplayName`、`web/index.html` 的 `` 与 `apple-mobile-web-app-title`;`web/manifest.json` 用 `short_name = 强宝助手`(4 字以内避免桌面截断)。 - 图标链路:`tools/make_app_icon.py`(去水印 + 抠图 + 缩放)→ `assets/icon/app_icon.png` + `app_icon_fg.png` → `dart run flutter_launcher_icons`。 - 图标配置在 `pubspec.yaml` 末尾的 `flutter_launcher_icons` 段;dev 依赖已加 `flutter_launcher_icons: ^0.14.0`。 - 素材归档在 `assets/icon/_source/`(`raw_bear.png` 采用、`raw_bear_dog.png` 备选)。`assets/icon/` 未声明为 Flutter assets,不会进包。 - **`assets/images/logo.png`(1120×928 贴纸海报)不可用作图标**:非正方形、带虚化底纹、含大字,48px 下不可读。 - 改图标 = 重跑上面两条命令;改名字后需卸载重装才刷新桌面。 ## 用户画像与偏好 - 用户正在**系统学习 Flutter 基础概念**,常就单个文件/代码片段提问(如 `GlobalKey`、`ConsumerWidget`、`ref`、`Stack`)。 - 沟通偏好:希望用**中文**解释,讲清「是什么 + 为什么这么写 + 在本项目中的上下文」,并配合结构化的表格/分层说明。 - 用户对 Material 3、多端适配、官方文档出处感兴趣,回答时应附**官方文档链接**。