# 记账小助手 — 项目文档 ## 项目概述 | 项目 | 说明 | |------|------| | **应用名称** | 记账小助手(keeping_accounts) | | **平台** | HarmonyOS (API 6.1 / Stage 模式) | | **Bundle Name** | `com.example.keeping_accounts` | | **版本** | 1.0.0 (versionCode: 1000000) | | **开发框架** | ArkTS + ArkUI 声明式 UI | | **构建工具** | Hvigor | | **目标设备** | 手机 (phone) + 手表(通过蓝牙同步) | | **跨平台** | 支持 Android / iOS(通过 ArkUI-X) | --- ## 目录结构 ``` keeping_accounts/ ├── AppScope/ # 应用全局配置 │ ├── app.json5 # Bundle 名称、图标、版本号 │ └── resources/ # 全局资源(图标、背景、字符串) ├── build-profile.json5 # 签名配置、SDK 版本、模块列表 ├── hvigorfile.ts # Hvigor 构建入口 ├── code-linter.json5 # 代码检查规则 ├── lodefusi/ # 主模块(entry) │ ├── build-profile.json5 # 模块构建配置 │ ├── oh-package.json5 # 模块依赖声明 │ ├── hvigorfile.ts # 模块构建脚本 │ ├── obfuscation-rules.txt # 代码混淆规则 │ ├── src/ │ │ ├── main/ │ │ │ ├── module.json5 # 模块清单(权限、Ability、页面路由) │ │ │ ├── ets/ │ │ │ │ ├── pages/ │ │ │ │ │ └── Index.ets # 主页面(启动页 + Tab 导航) │ │ │ │ ├── lodefusiability/ │ │ │ │ │ └── LodefusiAbility.ets # UIAbility 入口 │ │ │ │ ├── components/ │ │ │ │ │ ├── Overview.ets # 总览页面(月度统计) │ │ │ │ │ ├── AddExpense.ets # 记账页面(手动 + AI) │ │ │ │ │ ├── ExpenseRecords.ets # 记录列表(按日期分组) │ │ │ │ │ ├── Device.ets # 设备页面(蓝牙手表同步) │ │ │ │ │ └── BluetoothTransfer.ets # 蓝牙传输模态页 │ │ │ │ └── model/ │ │ │ │ ├── ExpenseModel.ets # 数据模型 + 本地持久化 │ │ │ │ ├── ApiService.ets # HTTP API 服务 │ │ │ │ ├── ApiConfig.ets # 服务器地址配置 │ │ │ │ └── BluetoothService.ets # 蓝牙 SPP 传输服务 │ │ │ └── resources/ # 模块资源(颜色、字符串、图标) │ │ ├── mock/ # Mock 数据配置 │ │ ├── ohosTest/ # 自动化测试 │ │ └── test/ # 本地单元测试 │ └── ... └── .arkui-x/ # ArkUI-X 跨平台配置(Android/iOS) ├── android/ # Android 工程 └── ios/ # iOS 工程 ``` --- ## 架构设计 ``` ┌─────────────────────────────────────────────────┐ │ Index.ets (主入口) │ │ 启动页 → Tab 导航 (总览/记账/设备) │ ├──────────┬──────────────────┬───────────────────┤ │ Overview │ AddExpense │ DevicePage │ │ 月度统计 │ 手动记账+AI分类 │ 蓝牙设备管理+同步 │ ├──────────┴──────────────────┴───────────────────┤ │ ExpenseRecords (记录列表) │ │ 按日期分组、分类筛选、长按删除 │ ├─────────────────────────────────────────────────┤ │ BluetoothTransfer (蓝牙传输) │ │ 发送选择 → 搜索设备 → 连接 → 传输 │ ├──────────┬──────────────────┬───────────────────┤ │ExpenseModel│ ApiService │ BluetoothService │ │ 本地存储 │ HTTP 同步 │ SPP 串口蓝牙 │ │Preferences│ /api/bills │ RFCOMM 通道 │ └──────────┴──────────────────┴───────────────────┘ ``` **数据流**: 1. 用户在记账页添加记录 → `ExpenseModel.addExpense()` 写入本地 Preferences 2. 同时异步调用 `ApiService.submitBill()` 同步到远端服务器(失败则静默降级,仅保存本地) 3. 总览页通过 `@Consume` 获取全量数据,按月筛选后计算分类汇总 4. 设备页通过 `BluetoothService.connectAndSend()` 将选中数据以 JSON 形式 SPP 传输给手表 --- ## 模块详解 ### 1. 主入口 — `Index.ets` - **启动页**:显示深蓝渐变背景 + "¥" 图标 + "这个月你省钱了吗?" slogan,带有淡入动画(1s)+ 2.2s 后淡出 - **主界面**:3 个 Tab 页切换(底部导航栏),每个 Tab 有自定义图标: - 总览(环形图标) - 记账("+" 图标) - 设备(手表图标) - 页面级别的 `@Provide expenses` 将消费数据共享给所有子组件 ### 2. 总览页 — `Overview.ets` - **环形进度卡片**:总收入(绿)、总支出(红)、结余(蓝),显示当月合计金额 - **消费分类网格**:按金额降序排列,每个分类显示颜色圆点、金额、线性进度条 - **收入来源网格**:同理展示收入分类 - **空状态**:当月无记录时显示 "💰 本月暂无记录" - **刷新按钮**:从本地存储重新加载数据 ### 3. 记账页 — `AddExpense.ets` - **类型切换**:消费(红色主题)/ 收入(绿色主题),切换时重置分类选择 - **金额输入**:仅允许数字和小数点,自动过滤非法字符,显示 "¥" 前缀 - **分类选择**: - **手动模式**:4 列 Grid 展示分类色块,选中项高亮 + 阴影 - **AI 智能分类模式**(仅消费类型可用): - 用户输入消费内容描述,开启 Toggle 开关 - 调用 `/api/transfer` 接口进行 AI 分类 - 显示加载动画 "AI 正在识别..." - 识别结果以蓝色标签展示,自动填充分类 - 分类完成后自动调用 `/api/bills` 同步到服务器 - **备注输入**:选填文本,AI 模式下替代为描述输入框 - **提交按钮**:带 loading 状态,成功后显示 "✓ 添加成功" 浮层 Toast(1.8s 自动消失) - **服务端同步**:静默失败策略,服务器不可用时 Toast 提示但数据已保存在本地 ### 4. 记录列表 — `ExpenseRecords.ets` - **按日期分组**:`yyyy-MM-dd` 为 key,每日显示日期标题 + 当日收支汇总 - **记录行**: - 左侧:分类颜色圆点 + 分类名 + AI 标签(蓝色胶囊)+ 备注 - 右侧:金额(消费红/收入绿)+ 格式化时间 `MM-dd HH:mm` - **长按删除**:触发确认对话框,确认后从本地存储删除 - **分类筛选**:支持按分类过滤,顶部显示筛选标签 + 记录数量 + 返回按钮 - **空状态**:无记录时显示 "📋 暂无消费记录" ### 5. 设备页 — `DevicePage.ets` - **蓝牙状态栏**:显示蓝牙开关状态(绿点/灰点),刷新按钮 - **已配对设备列表**: - 显示设备名称、连接状态(绿点/灰点)、上次同步时间、已同步条数 - 点击"连接"按钮模拟连接 - 点击"同步"按钮将选中数据通过蓝牙发送 - **同步设置**: - 发送消费记录(Toggle 开关) - 发送收入记录(Toggle 开关) - **手表应用设置**: - 手表端显示消费明细 / 收入明细 / 震动提醒(预留 UI) - **同步结果**:顶部显示同步进度/结果横幅 ### 6. 蓝牙传输 — `BluetoothTransfer.ets` 这是一个模态弹窗组件,支持发送和接收两种模式: **发送流程**: 1. 选择记录(Checkbox 多选 + 全选) 2. 搜索附近的蓝牙设备(12s 超时) 3. 显示设备列表(设备名 + 地址 + 连接按钮) 4. 连接设备并通过 SPP 传输选中的 JSON 数据 5. 显示传输进度百分比 **接收流程**: 1. 等待发送方连接(显示本机蓝牙名称) 2. 自动接收数据(`__BT_EOF__` 标记结束) 3. 接收完成后显示记录数,提供"导入数据"按钮 4. 导入将数据合并到本地存储 **状态机**:`IDLE → SELECTING → SCANNING → CONNECTING → TRANSFERRING → COMPLETE / ERROR` (接收时:`IDLE → WAITING → TRANSFERRING → COMPLETE / ERROR`) --- ## 数据模型 ### ExpenseItem ```typescript interface ExpenseItem { id: string; // 唯一标识(时间戳+计数器+随机数) type: '消费' | '收入'; // 交易类型 category: string; // 分类名称(餐饮、工资等) amount: number; // 金额 remark: string; // 备注 message: string; // AI 分类返回的建议消息 createTime: string; // ISO 时间字符串 } ``` ### 预设分类 | 类型 | 分类项 | |------|--------| | **消费** | 餐饮、医药、数码、还款、购物、交通、娱乐、住房、日用、服饰、教育、人情、其他 | | **收入** | 工资、奖金、理财、兼职、报销、退款、红包、其他收入 | 每个分类有对应的颜色值,用于 UI 中的圆点和进度条等元素。 ### 数据持久化 - **本地存储**:使用 HarmonyOS `preferences` API,键值对存储在 `expense_store`,key 为 `expenses` - **数据迁移**:`migrateItem()` 函数兼容旧版数据结构(如 `mash` → `amount`、`name` → `category`、`text/note` → `remark` 等字段映射) - **生成 ID**:`Date.now().toString(36) + 计数器.toString(36) + 随机数` --- ## API 服务 ### 服务器配置 - 配置文件存储在 `app_config` preferences 中,key 为 `server_url` - 默认地址:`http://192.168.1.1:9255` ### 接口列表 | 接口 | 方法 | 路径 | 说明 | |------|------|------|------| | 提交账单 | POST | `/api/bills` | 将消费/收入记录同步到服务器 | | AI 分类 | POST | `/api/transfer` | 根据消费描述自动推荐分类 | | 查询账单 | GET | `/api/bills?type=&category=` | 按类型和分类筛选查询 | ### 错误处理 - 网络不可用、超时(连接 5s / 读取 10s)、服务端异常均返回 `{ code: -1, msg: '...' }` - 调用方采用静默降级策略,不影响本地数据保存 --- ## 蓝牙服务 — `BluetoothService` ### 技术方案 - 使用 **SPP(Serial Port Profile)** 基于 RFCOMM 通道传输 - UUID:`00001101-0000-1000-8000-00805F9B34FB` - 数据格式:JSON 字符串 + `__BT_EOF__` 结束标记 - 分块传输:每块 512 字节 ### 主要功能 | 方法 | 说明 | |------|------| | `isBluetoothEnabled()` | 检查蓝牙是否开启 | | `enableBluetooth()` | 打开蓝牙 | | `waitForBluetoothOn(timeoutMs)` | 等待蓝牙开启(监听 `stateChange` 事件) | | `getLocalDeviceName()` | 获取本机蓝牙名称 | | `getPairedDevices()` | 获取已配对设备列表 | | `startDiscovery(cb)` | 搜索附近蓝牙设备 | | `stopDiscovery()` | 停止搜索 | | `startServer(onResult, onProgress)` | 启动 SPP 服务端监听(接收模式) | | `connectAndSend(deviceId, data, onResult, onProgress)` | 连接设备并发送数据 | | `stopServer()` / `cancelTransfer()` / `destroy()` | 终止/清理资源 | ### UTF-8 编解码 - 内建 `strToBytes()` 和 `bytesToStr()` 实现 UTF-8 编解码(支持中文),不依赖外部库 --- ## 权限列表 | 权限 | 用途 | |------|------| | `ohos.permission.INTERNET` | HTTP API 通信 | | `ohos.permission.USE_BLUETOOTH` | 蓝牙数据传输 | | `ohos.permission.DISCOVER_BLUETOOTH` | 搜索附近蓝牙设备 | --- ## 离线策略 整体采用 **本地优先(Local-First)** 架构: 1. 用户操作直接写入本地 Preferences,操作体验不受网络影响 2. 服务器同步为异步旁路流程,成功/失败均不影响本地数据完整性 3. 蓝牙传输同样先将接收数据合并到本地存储,再触发 UI 刷新 4. 总览页刷新按钮从本地存储重新读取,确保数据一致性 --- ## 跨平台支持(ArkUI-X) 项目包含 `.arkui-x/` 目录,配置了 Android 和 iOS 的原生工程: - **Android**:Gradle 构建,包含 `arkui_android_adapter.jar` 和相关 `.so` 库 - **iOS**:Xcode 工程(`app.xcodeproj`),包含 `AppDelegate` 和 `LodefusiLodefusiAbilityViewController` 这意味着同一套 ArkTS 代码可以编译为 HarmonyOS / Android / iOS 三端应用。 --- ## 测试 | 测试类型 | 路径 | 说明 | |----------|------|------| | 本地单元测试 | `src/test/List.test.ets`、`LocalUnit.test.ets` | 逻辑层测试 | | 自动化测试 | `src/ohosTest/ets/test/Ability.test.ets`、`List.test.ets` | 设备上运行的 UI 测试 | | Mock 配置 | `src/mock/mock-config.json5` | 模拟数据配置 |