# 记账小助手 — 项目文档

## 项目概述

| 项目 | 说明 |
|------|------|
| **应用名称** | 记账小助手（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` | 模拟数据配置 |
