feat: 补充图片记账、交易渠道管理与前端偏好文档
- 新增「交易渠道管理」指南与「前端用户偏好」开发文档,并加入侧边栏导航 - 图片记账:补充多图上传、实时识别进度、结果勾选导入与「来源图片」回看,AI 配置新增视觉模型说明 - 数据模型补充 t_channel、t_ai_config/t_ai_image 等表与「类型+金额+时间」去重键说明,表数更新为 21 张 - API 文档补充 /api/channel、/api/transaction/batch、/api/ai/record/image(/stream)、/api/ai/image、/api/ai/channel 等接口 - 截图脚本新增渠道页、图片记账、来源图片与渠道 AI 辅助截图,并放宽导航超时
This commit is contained in:
@@ -39,6 +39,7 @@ export default defineConfig({
|
||||
items: [
|
||||
{ text: '收支记录', link: '/guide/transactions' },
|
||||
{ text: '分类与标签', link: '/guide/categories-tags' },
|
||||
{ text: '交易渠道管理', link: '/guide/channels' },
|
||||
{ text: '规则管理', link: '/guide/rules' },
|
||||
{ text: '邮箱账单', link: '/guide/email-bills' },
|
||||
],
|
||||
@@ -87,6 +88,7 @@ export default defineConfig({
|
||||
{ text: 'AI 集成', link: '/dev/ai' },
|
||||
{ text: '定时通知', link: '/dev/notifications' },
|
||||
{ text: '动态数据源', link: '/dev/database' },
|
||||
{ text: '前端用户偏好', link: '/dev/preferences' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -6,12 +6,14 @@
|
||||
|
||||
`DeepSeekClient` 基于 **OpenAI 官方 Java SDK**(`com.openai:openai-java`)调用,通过自定义 `baseUrl` 指向 DeepSeek 的 OpenAI 兼容接口:
|
||||
|
||||
- 使用 `OpenAIOkHttpClient`,按 `(baseUrl, apiKey)` 缓存客户端(上限 32,超出后清空重建)。
|
||||
- `temperature = 0.2`,`response_format = {"type":"json_object"}`,非流式,`maxRetries(0)`,请求超时 60 秒。
|
||||
- 使用 `OpenAIOkHttpClient`,按 `(baseUrl, apiKey, 超时)` 缓存客户端(上限 32,超出后清空重建)。
|
||||
- `temperature = 0.2`,`response_format = {"type":"json_object"}`,非流式,`maxRetries(0)`;文本请求超时 **120 秒**,视觉请求超时 **240 秒**(`chatWithImages` 单独放宽)。
|
||||
- 取 `choices[0].message.content` 与 `usage`(输入/输出/总 token;缓存命中 token 取 `promptTokensDetails.cachedTokens`,DeepSeek 私有字段读不到时为 0)。
|
||||
- 对外保持 `chat(baseUrl, apiKey, model, systemPrompt, userPrompt)` 与 `DeepSeekResponse` 不变,各 `Ai*ServiceImpl` 无需改动。
|
||||
- 另有 `chatWithImages(baseUrl, apiKey, model, systemPrompt, userText, imageDataUrls)`:将一张或多张图片以 base64 data URL 作为 `user` 消息的内容块发送(DeepSeek 视觉接口要求图片只能出现在 user 消息中),供图片记账使用。
|
||||
- SDK 异常统一转 `BusinessException`;异常链含超时(`SocketTimeoutException` 等)时返回面向用户的友好提示。
|
||||
|
||||
配置存储于 `t_ai_config`(每用户一条):`enabled`、`api_key`、`base_url`(默认 `https://api.deepseek.com`)、`model`(默认 `deepseek-chat`)。API Key 对外脱敏,支持连通性测试。
|
||||
配置存储于 `t_ai_config`(每用户一条):`enabled`、`api_key`、`base_url`(默认 `https://api.deepseek.com`)、`model`(默认 `deepseek-chat`)、`vision_model`(图片记账视觉模型,默认 `deepseek-flash`)。API Key 对外脱敏,支持连通性测试。
|
||||
|
||||
## 数据外发范围(隐私)
|
||||
|
||||
@@ -19,10 +21,11 @@
|
||||
|
||||
- **禁止**:`rawData`(账单原始 JSON)、金额、交易号、账户/卡号、姓名、地址、电话等。
|
||||
- **允许**:收支类型、交易渠道、备注(必要时截断/脱敏)、分类与标签的名称/ID、用户主动输入的自然语言文本。
|
||||
- **图片记账例外**:用户主动上传的账单/支付截图会**整张**发送给视觉模型(可能含金额、交易对方、订单号等),视为用户主动提供的输入;图片同时落盘于 `data/ai-images/`,仅本人可经 `GET /api/ai/image/{id}` 访问,禁止用于其他外发场景。
|
||||
|
||||
## 调用类型
|
||||
|
||||
`AiCallType` 定义了 9 类调用,全部记录用量:
|
||||
`AiCallType` 定义了 11 类调用,全部记录用量:
|
||||
|
||||
| 枚举值 | 含义 |
|
||||
|---|---|
|
||||
@@ -30,9 +33,11 @@
|
||||
| `TEST` | 连接测试 |
|
||||
| `QUERY` | 自然语言查询解析为筛选条件 |
|
||||
| `RECORD` | 自然语言记账解析为交易记录 |
|
||||
| `RECORD_IMAGE` | 图片记账解析(视觉模型) |
|
||||
| `DECORATE` | 分类 Emoji / 标签颜色建议 |
|
||||
| `CATEGORY` | 自然语言生成分类 |
|
||||
| `TAG` | 自然语言生成标签 |
|
||||
| `CHANNEL` | 自然语言生成交易渠道 |
|
||||
| `RULE` | 由已打标签记录生成分类与标签规则 |
|
||||
| `STATISTICS` | 自然语言统计查询解析 |
|
||||
|
||||
@@ -42,6 +47,7 @@
|
||||
|
||||
- 先按「类型 + 交易渠道 + 备注」聚合去重,仅将代表记录送模型;单批 ≤ 100 条、总量 ≤ 500 条,超出则分批调用。
|
||||
- 支持严格模式(仅从已有分类/标签中选择)与增强模式(可返回新分类/新标签)。
|
||||
- **单条建议**额外下发当前用户的渠道候选(内置取中文名、自定义取用户名称),模型可返回 `channel`,服务端仅接受候选中的编码;批量建议不下发渠道候选。
|
||||
- 应用时分类覆盖写入、标签追加;新建分类按「类型 + 名称」去重,新建标签按名称去重并校验颜色格式。
|
||||
|
||||
## 自然语言查询
|
||||
@@ -52,6 +58,25 @@
|
||||
|
||||
`AiRecordServiceImpl` 解析类型、金额、分类、标签、交易渠道、交易时间、备注、计入统计等字段;金额无效直接报错,时间缺失回退当前时间。
|
||||
|
||||
## 生成交易渠道
|
||||
|
||||
`AiChannelServiceImpl` 根据用户对渠道用途的自然语言描述,使用 `ai-channel-system` 模板生成 2–6 字的渠道名称(返回 `name` 与 `reason`,名称长度上限 20),对应 `POST /api/ai/channel`。前端在新增/编辑记录的「交易渠道 · AI 新建」中使用,确认后经 `POST /api/channel` 创建并选中。
|
||||
|
||||
## 图片记账
|
||||
|
||||
`AiImageRecordServiceImpl` 处理截图上传到生成记录的完整链路,支持**一次上传多张图片**(最多 5 张,单张 ≤ 10MB、总 ≤ 30MB),合并为一次视觉模型调用:
|
||||
|
||||
1. 校验图片数量、单张与总大小、格式(JPEG/PNG/GIF/WebP,按魔数识别),并确认 AI 已启用且配置了 API Key。
|
||||
2. 逐张经 `AiImageStorage` 落盘至 `data/ai-images/{userId}/{yyyyMM}/`(经 `AppHome` 解析运行目录),每张写入一条 `t_ai_image`(记录 MIME、尺寸、所用视觉模型等)。
|
||||
3. 以 `ai-record-image-system` 模板构造提示词(注入可用分类、标签、渠道与当前时间,要求模型对每笔输出 `imageIndex` 与 `rawData`),调用 `chatWithImages` 使用**视觉模型**一次性识别。
|
||||
4. 复用 `AiRecordJsonParser` 解析 `records` 数组(与文字记账共用:去除代码围栏、匹配分类/标签、校验渠道编码、回退时间),并回填摘要与各图记录数。
|
||||
5. **重复检测**:`TransactionDedupUtils` 以「类型 + 金额 + 交易时间」为去重键,与已有记录及本批其它记录比对,命中则标记 `duplicate=true`(前端默认不勾选)。
|
||||
6. 按模型标注的 `imageIndex` 将每笔记录关联到来源图片,前端确认后经 `POST /api/transaction/batch` 批量创建(同时写入 `rawData`、建立 `t_ai_image_transaction` 关联,并在导入阶段按同一去重键兜底跳过重复)。
|
||||
|
||||
**实时进度**:`AiImageRecordStreamService` 基于 SSE 将上述步骤的进度文本推送前端——`POST /api/ai/record/image/stream` 返回 `SseEmitter`,识别在后台线程执行,过程中逐条推送 `progress` 事件,结束推送 `done`(完整结果 JSON)或 `failed`(失败原因)。
|
||||
|
||||
**图片访问**:`GET /api/ai/image/{id}` 返回原图,`?thumbnail=true` 返回最长边 1280 的 JPEG 缩略图;仅图片所属用户可访问。交易列表的「来源图片」默认加载缩略图,用户可按需切换原图。
|
||||
|
||||
## 规则生成
|
||||
|
||||
`AiRuleServiceImpl` 由用户勾选的**已打标签记录**反推分类/标签规则:
|
||||
|
||||
+10
-3
@@ -29,13 +29,14 @@
|
||||
| 控制器 | 路径前缀 | 说明 |
|
||||
|---|---|---|
|
||||
| `UserController` | `/api/user` | 登录、注册、用户信息、注册开关 |
|
||||
| `TransactionController` | `/api/transaction` | 收支记录增删改查、批量操作、导出、交易渠道列表 |
|
||||
| `TransactionController` | `/api/transaction` | 收支记录增删改查、批量创建与操作、导出、交易渠道选项 |
|
||||
| `BillImportController` | `/api/transaction/import` | 账单解析与确认导入 |
|
||||
| `ChannelController` | `/api/channel` | 用户自定义交易渠道的增删改查 |
|
||||
| `CategoryController` | `/api/category` | 分类管理 |
|
||||
| `TagController` | `/api/tag` | 标签管理 |
|
||||
| `RuleController` | `/api/rule` | 自定义规则(列表、分页查询、增删改、启停) |
|
||||
| `StatisticsController` | `/api/statistics` | 统计汇总、分类、趋势、分组 |
|
||||
| `AiController` | `/api/ai` | AI 建议、查询、记账、规则生成、统计搜索、用量 |
|
||||
| `AiController` | `/api/ai` | AI 建议、查询、文字/图片记账、渠道生成、规则生成、统计搜索、用量 |
|
||||
| `NotificationController` | `/api/notification` | 通知配置、渠道、发送日志 |
|
||||
| `EmailBillController` | `/api/email-bill` | 邮箱配置、发件人规则、待处理账单 |
|
||||
| `LogController` | `/api/log` | 访问日志、登录日志、运行日志与 SSE |
|
||||
@@ -47,7 +48,13 @@
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| GET | `/api/transaction/channels` | 获取系统支持的交易渠道编码列表 |
|
||||
| GET | `/api/transaction/channels` | 获取交易渠道选项(内置 + 当前用户自定义,含 `code`/`name`/`builtin`) |
|
||||
| POST | `/api/transaction/batch` | 批量创建交易记录(逐条可带 `imageId`/`rawData`),导入时按「类型+金额+时间」跳过重复 |
|
||||
| GET / POST / PUT / DELETE | `/api/channel`、`/api/channel/{id}` | 自定义渠道列表(含使用账单数)、新建、改名、删除 |
|
||||
| POST | `/api/ai/record/image` | 上传一至多张账单/支付截图(字段 `files`),调用视觉模型识别为多条交易记录 |
|
||||
| POST | `/api/ai/record/image/stream` | 图片记账(SSE):实时推送识别进度,结束返回完整结果 |
|
||||
| GET | `/api/ai/image/{id}` | 查看本人上传的图片记账图片,`?thumbnail=true` 返回缩略图 |
|
||||
| POST | `/api/ai/channel` | 由自然语言描述生成交易渠道名称 |
|
||||
| GET | `/api/rule/page` | 分页查询规则(`keyword` / `enabled` / `type`) |
|
||||
| POST | `/api/ai/rule/suggest` | 依据所选已打标签记录生成规则建议(含 `duplicate` 标记) |
|
||||
| POST | `/api/ai/rule/apply` | 批量创建规则(自动去重、按需新建分类/标签) |
|
||||
|
||||
+2
-2
@@ -91,8 +91,8 @@ src/
|
||||
1. **运行时动态数据源**:启动无需数据库,配置后热切换,见[动态数据源](/dev/database)。
|
||||
2. **账单两阶段导入**:解析预览 → 确认导入,按交易号去重,见[账单解析](/dev/bill-parsing)。
|
||||
3. **规则引擎**:分类取最高优先级、标签取并集,见[规则引擎](/dev/rule-engine)。
|
||||
4. **体系化 AI 调用**:9 类调用类型并记录 token 用量,见 [AI 集成](/dev/ai)。
|
||||
5. **SSE 实时能力**:运行日志、邮箱检查进度实时推送。
|
||||
4. **体系化 AI 调用**:11 类调用类型并记录 token 用量,见 [AI 集成](/dev/ai)。
|
||||
5. **SSE 实时能力**:运行日志、邮箱检查进度与图片记账识别进度实时推送。
|
||||
6. **用户偏好缓存**:`t_user_preference` 的读取走 Spring Cache 进程内缓存(`ConcurrentMapCacheManager`),写入时按用户失效,切换数据源时整体清空。
|
||||
|
||||
## 相关文档
|
||||
|
||||
+32
-4
@@ -1,6 +1,6 @@
|
||||
# 数据模型
|
||||
|
||||
系统共 **18 张表**,覆盖用户、记账、标签、规则、AI、通知、邮箱账单与日志审计。所有表主键为自增 `id`,表间关系均为**逻辑外键**(通过 `user_id`、`category_id` 等字段在应用层维护,未声明物理外键)。
|
||||
系统共 **21 张表**,覆盖用户、记账、渠道、标签、规则、AI、通知、邮箱账单与日志审计。所有表主键为自增 `id`,表间关系均为**逻辑外键**(通过 `user_id`、`category_id` 等字段在应用层维护,未声明物理外键)。
|
||||
|
||||
## 表清单
|
||||
|
||||
@@ -10,12 +10,15 @@
|
||||
| 用户 | `t_user_preference` | 每用户多条的用户偏好(键值) |
|
||||
| 记账 | `t_category` | 收支分类(收入/支出,含图标与排序) |
|
||||
| 记账 | `t_transaction` | 收支记录(核心表) |
|
||||
| 记账 | `t_channel` | 用户自定义交易渠道(内置渠道由枚举维护) |
|
||||
| 标签 | `t_tag` | 标签 |
|
||||
| 标签 | `t_transaction_tag` | 交易-标签关联(多对多) |
|
||||
| 规则 | `t_rule` | 自定义归类规则 |
|
||||
| 规则 | `t_rule_tag` | 规则-标签关联(多对多) |
|
||||
| AI | `t_ai_config` | 每用户一条的 AI 配置 |
|
||||
| AI | `t_ai_config` | 每用户一条的 AI 配置(含视觉模型) |
|
||||
| AI | `t_ai_usage` | AI 调用用量记录 |
|
||||
| AI | `t_ai_image` | 图片记账上传的截图及其解析摘要 |
|
||||
| AI | `t_ai_image_transaction` | 图片与解析生成交易的关联 |
|
||||
| 通知 | `t_notification_config` | 每用户一条的提醒配置 |
|
||||
| 通知 | `t_notification_channel` | 通知渠道 |
|
||||
| 通知 | `t_notification_log` | 通知发送日志 |
|
||||
@@ -34,7 +37,9 @@
|
||||
| t_user | 1 : N | t_category | user_id |
|
||||
| t_user | 1 : N | t_transaction | user_id |
|
||||
| t_user | 1 : N | t_tag | user_id |
|
||||
| t_user | 1 : N | t_channel | user_id |
|
||||
| t_user | 1 : N | t_rule | user_id |
|
||||
| t_user | 1 : N | t_ai_image | user_id |
|
||||
| t_user | 1 : N | t_notification_channel | user_id |
|
||||
| t_user | 1 : N | t_email_bill_config | user_id |
|
||||
| t_user | 1 : N | t_email_bill_sender_rule | user_id |
|
||||
@@ -57,6 +62,10 @@
|
||||
| t_transaction | N : M | t_tag | 中间表 t_transaction_tag(联合唯一) |
|
||||
| t_category | 1 : N | t_rule | rule.category_id(命中目标分类) |
|
||||
| t_rule | N : M | t_tag | 中间表 t_rule_tag(联合唯一) |
|
||||
| t_ai_image | 1 : N | t_ai_image_transaction | image_id |
|
||||
| t_transaction | 1 : N | t_ai_image_transaction | transaction_id |
|
||||
|
||||
> 交易记录通过 `channel` 编码关联渠道:内置渠道编码取自 `ChannelType` 枚举,自定义渠道编码取自 `t_channel.code`(用户内唯一),因此渠道改名不影响历史记录。
|
||||
|
||||
### 通知与邮箱账单
|
||||
|
||||
@@ -84,7 +93,7 @@
|
||||
| external_no | 外部账单交易号,导入去重 |
|
||||
| transaction_time | 交易发生时间 |
|
||||
| include_in_stats | 是否计入统计(默认 1) |
|
||||
| raw_data | 账单导入原始 JSON(手动记录为空) |
|
||||
| raw_data | 原始字段 JSON(账单导入的原始账单行,或图片记账由模型提取的原始字段;手动记录为空) |
|
||||
|
||||
关键索引:唯一索引 `uk_user_external (user_id, external_no)` 实现导入去重;`idx_user_time (user_id, transaction_time)` 支撑按时间查询与统计。
|
||||
|
||||
@@ -97,6 +106,25 @@
|
||||
|
||||
`name`、`enabled`、`sort_order`(优先级)、`type`、`keyword`、`min_amount`、`max_amount`、`category_id`。
|
||||
|
||||
### t_channel(自定义交易渠道)
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| user_id | 所属用户 |
|
||||
| code | 渠道编码,后端生成(`CUSTOM_` + 随机串),稳定不变,交易记录按此关联 |
|
||||
| name | 渠道名称,用户可修改 |
|
||||
| sort_order | 排序序号,数值越小越靠前 |
|
||||
|
||||
唯一索引 `uk_channel_user_code (user_id, code)`、`uk_channel_user_name (user_id, name)` 分别保证用户内编码与名称唯一。仅存放用户自建渠道,内置渠道由 `ChannelType` 枚举维护。
|
||||
|
||||
### t_ai_config / t_ai_image
|
||||
|
||||
- `t_ai_config`:每用户一条,含 `enabled`、`api_key`、`base_url`、`model` 与 **`vision_model`**(图片记账使用的视觉模型,默认 `deepseek-flash`)。
|
||||
- `t_ai_image`:图片记账上传的截图,一次上传可产生多条(最多 5 张),含 `user_id`、`file_path`(相对应用运行目录,落盘于 `data/ai-images/`)、`mime_type`、`file_size`、`width`、`height`、`model`(解析所用视觉模型)、`summary`(识别摘要)、`record_count`(该图识别出的记录数)。
|
||||
- `t_ai_image_transaction`:图片与交易记录的关联,含 `image_id`、`transaction_id`,唯一索引 `uk_ai_image_transaction (image_id, transaction_id)`;每笔记录按模型标注的来源图分别建立关联。
|
||||
|
||||
> **去重键**:图片记账与批量导入使用「收支类型 + 金额 + 交易时间(到秒)」作为业务去重键(`TransactionDedupUtils`),用于标记疑似重复并在导入时跳过,弥补这类记录没有外部交易号的不足。
|
||||
|
||||
### t_user_preference
|
||||
|
||||
按用户存储的键值偏好:`pref_key` 为偏好键(如 `billSourceIncludeInStats`、`excludedDisplayStyle`),`pref_value` 为标量字符串或 JSON。唯一索引 `uk_user_pref (user_id, pref_key)` 保证每个用户每个偏好仅一条。未配置时由应用回退系统默认值。
|
||||
@@ -129,5 +157,5 @@ IMAP 连接信息(`host`/`port`/`use_ssl`/`username`/`password`/`folder`)、
|
||||
## 初始化与迁移
|
||||
|
||||
- 应用启动时由 `SchemaInitializer` 执行对应建表脚本(幂等)。
|
||||
- 建表脚本只做 `CREATE TABLE IF NOT EXISTS`,无法修改已有库的列;需要就地变更列名时由 `SchemaMigrator` 在启动/切换数据源时**幂等迁移**(如 `t_transaction.payment_method` → `channel`,MySQL 用 `information_schema`、SQLite 用 `PRAGMA table_info` 检测)。
|
||||
- 建表脚本只做 `CREATE TABLE IF NOT EXISTS`,无法修改已有库的列;需要就地变更列名或新增列时由 `SchemaMigrator` 在启动/切换数据源时**幂等迁移**(如 `t_transaction.payment_method` → `channel` 改名、`t_ai_config.vision_model` 加列;MySQL 用 `information_schema`、SQLite 用 `PRAGMA table_info` 检测列是否存在)。
|
||||
- 新用户注册时由 `DefaultDataInitializer` 初始化默认分类、标签与邮箱发件人规则。
|
||||
|
||||
+3
-2
@@ -5,12 +5,13 @@
|
||||
## 设计与实现
|
||||
|
||||
- [整体架构](/dev/architecture):技术栈、后端分层、请求流程、认证与前端结构
|
||||
- [数据模型](/dev/data-model):17 张表的字段与关系、双数据库脚本差异
|
||||
- [数据模型](/dev/data-model):21 张表的字段与关系、双数据库脚本差异
|
||||
- [账单解析](/dev/bill-parsing):解析器策略模式、两阶段导入、去重与 zip 解密
|
||||
- [规则引擎](/dev/rule-engine):规则模型与匹配逻辑
|
||||
- [AI 集成](/dev/ai):OpenAI SDK 客户端、9 类调用类型与用量记录
|
||||
- [AI 集成](/dev/ai):OpenAI SDK 客户端、11 类调用类型与用量记录
|
||||
- [定时通知](/dev/notifications):调度、渠道与发送日志
|
||||
- [动态数据源](/dev/database):SQLite / MySQL 运行时配置与切换
|
||||
- [前端用户偏好](/dev/preferences):偏好的存储介质、读写时机与完整清单
|
||||
|
||||
## 工程与规范
|
||||
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
# 前端用户偏好与持久化
|
||||
|
||||
本文梳理前端「用户偏好」的保存逻辑:**为什么保存、保存了哪些偏好、存在哪里、何时读写、目的是什么**。
|
||||
|
||||
## 为什么保存
|
||||
|
||||
收支管理系统的不少界面是「多 Tab」或「带筛选条件」的,如果每次进入都重置为默认值,用户每次都要重新选一遍语言、筛选项、分页、排序、上次使用的记账方式等,体验割裂。
|
||||
|
||||
为此项目在 `finance-app/AGENTS.md` 中约定:
|
||||
|
||||
> 多 Tab 或可切换的界面偏好需**持久化记忆**(`localStorage`),下次打开时恢复到上次选择;存储键统一以 `finance.` 前缀命名,读取时对非法值回退默认项,写入时忽略存储异常。
|
||||
|
||||
据此形成三条实践原则:
|
||||
|
||||
1. **进入页面即还原**:`onMounted` 或模块初始化时读取上次选择。
|
||||
2. **变化即保存**:用 `watch` 监听偏好变量,任一变化自动写入,无需用户点「保存」。
|
||||
3. **容错**:读取时校验取值白名单,非法/损坏数据回退默认;写入包在 `try/catch` 中,忽略隐私模式等存储异常。
|
||||
|
||||
偏好分三层存储:
|
||||
|
||||
| 存储介质 | 生命周期 | 适用场景 |
|
||||
|---|---|---|
|
||||
| `localStorage` | 跨会话、跨标签页 | 语言、筛选条件、Tab、历史记录等界面偏好 |
|
||||
| `sessionStorage` | 仅当前浏览器会话 | 内容区滚动位置(刷新/切换恢复) |
|
||||
| 后端(按当前用户) | 跟随账号 | 需要跨设备/多端一致的外观与功能配置 |
|
||||
|
||||
> 登录凭证 `token` 也存放在 `localStorage`,但它属于**认证状态**而非界面偏好,见文末说明。
|
||||
|
||||
## 总览表
|
||||
|
||||
### localStorage 偏好
|
||||
|
||||
| 键名 | 偏好内容 | 文件 | 保存时机 | 读取时机 |
|
||||
|---|---|---|---|---|
|
||||
| `locale` | 界面语言 `zh-CN`/`en-US`/`ja-JP` | `locales/index.ts:16` | 切换语言 | 模块初始化 |
|
||||
| `finance.dashboard.prefs` | 统计面板时间范围、粒度与 11 项筛选 | `views/Dashboard.vue:470` | 变量 `watch` 自动 | `onMounted` |
|
||||
| `finance.transaction.filterPrefs` | 收支列表筛选、分页、排序 | `views/TransactionList.vue:369` | 变量 `watch` 自动 | `onMounted` |
|
||||
| `finance.transaction.aiScope` | 批量 AI 建议范围 | `views/TransactionList.vue:146` | 变量 `watch` 自动 | ref 初始化 |
|
||||
| `finance.transaction.aiRecordTab` | AI 记账方式(文字/图片) | `views/TransactionList.vue:206` | 变量 `watch` 自动 | ref 初始化 |
|
||||
| `finance.transaction.aiQueryHistory` | AI 自然语言查询历史 | `views/TransactionList.vue:195` | 提交查询时 | `onMounted` |
|
||||
| `finance.transaction.remarkHistory` | 备注关键词历史 | `views/TransactionList.vue:300` | 备注失焦/回车 | `onMounted` |
|
||||
| `finance.dashboard.aiSearchHistory` | 统计面板 AI 查询历史 | `views/Dashboard.vue:91` | 提交 AI 搜索时 | `onMounted` |
|
||||
| `finance.billImport.billType` | 上次选择的账单来源类型 | `views/BillImportPanel.vue:25` | 切换类型时 | ref 初始化 |
|
||||
| `finance.transaction.relationType` | 账单关系类型偏好 | `components/TransactionRelationEditorModal.vue:14` | 创建模式切换类型时 | 弹窗初始化 |
|
||||
| `emailBill.tab` | 邮箱账单页标签页 | `views/EmailBillManage.vue:43` | Tab 切换时 | 路由重定向 + 组件 |
|
||||
| `logs.tab` | 系统日志页标签页 | `views/Logs.vue:25` | Tab 切换时 | 路由重定向 + 组件 |
|
||||
|
||||
> `locale`、`emailBill.tab`、`logs.tab` 是较早的实现,未使用 `finance.` 前缀;前两者分别为国际化与路由重定向的既有约定,改动时需保持键名稳定。
|
||||
|
||||
### sessionStorage 偏好
|
||||
|
||||
| 键名 | 偏好内容 | 文件 |
|
||||
|---|---|---|
|
||||
| `finance.scroll.<路由路径>` | 各页面内容区滚动位置 | `views/Layout.vue:83` |
|
||||
|
||||
### 后端按用户同步的偏好
|
||||
|
||||
| 接口 | 偏好内容 | 前端入口 |
|
||||
|---|---|---|
|
||||
| `GET/PUT /api/setting/excluded-display-style` | 「不计入统计」记录展示样式 | `components/DisplayConfigPanel.vue` |
|
||||
| `GET/PUT /api/setting/bill-source-include-in-stats` | 各账单来源「计入统计」默认值 | `components/BillSourceStatsPanel.vue` |
|
||||
| `GET/PUT /api/ai/config` | AI 启用开关、密钥、模型 | `components/AiConfigPanel.vue` |
|
||||
|
||||
## localStorage 偏好详解
|
||||
|
||||
### 界面语言 `locale`
|
||||
|
||||
- **文件**:`frontend/src/locales/index.ts`
|
||||
- **取值**:`zh-CN`、`en-US`、`ja-JP`
|
||||
- **读取**:`resolveInitialLocale()`(第 42 行)按「`localStorage` → `navigator.language` → `zh-CN`」优先级解析,作为 `createI18n` 初始语言。
|
||||
- **保存**:`setLocale()`(第 75 行)在切换语言时写入,并同步 `document.documentElement.lang`。
|
||||
- **目的**:记住用户语言选择,应用启动与切换即时生效。
|
||||
- **入口**:`components/LocaleSwitch.vue`。
|
||||
|
||||
### 统计面板偏好 `finance.dashboard.prefs`
|
||||
|
||||
- **文件**:`frontend/src/views/Dashboard.vue`(键名第 470 行)
|
||||
- **数据结构**(JSON 对象,保存见 `savePrefs()` 第 499 行):
|
||||
|
||||
| 字段 | 含义 | 默认值 |
|
||||
|---|---|---|
|
||||
| `rangeType` | 时间范围 `month`/`3month`/`12month`/`custom` | `month` |
|
||||
| `customRange` | 自定义起止时间戳 `[number, number]` | `null` |
|
||||
| `granularity` | 趋势粒度 `day`/`week`/`month`/`quarter`/`year` | `day` |
|
||||
| `filterStatsScope` | 统计口径 `INCLUDED`/`ALL`/`EXCLUDED` | `INCLUDED` |
|
||||
| `filterType` | 收支类型(多选) | `[]` |
|
||||
| `filterCategory` | 分类(多选,`0` 表示未分类) | `[]` |
|
||||
| `filterChannel` | 交易渠道(多选) | `[]` |
|
||||
| `filterTag` | 标签(多选) | `[]` |
|
||||
| `filterMinAmount` / `filterMaxAmount` | 金额区间 | `null` |
|
||||
| `filterRemark` | 备注关键字 | `''` |
|
||||
|
||||
- **读取**:`loadPrefs()`(第 474 行)在 `onMounted`(第 699 行)调用,逐项校验白名单;兼容旧字段 `filterPaymentMethod`(第 486 行)。
|
||||
- **保存**:`watch` 监听上述变量,任一变化调用 `savePrefs()`。
|
||||
- **跨页写入**:点击趋势图跳转收支列表时,`applyTrendNavigation()`(第 381 行)把统计口径与筛选**合并写入**收支列表的偏好键,并保留分页、排序等既有设置。
|
||||
- **目的**:下次进入统计面板自动还原时间范围、粒度与全部过滤条件。
|
||||
|
||||
### 收支列表筛选偏好 `finance.transaction.filterPrefs`
|
||||
|
||||
- **文件**:`frontend/src/views/TransactionList.vue`(键名第 369 行)
|
||||
- **数据结构**(保存见 `saveFilterPrefs()` 第 396 行):
|
||||
|
||||
| 字段 | 含义 | 默认值 |
|
||||
|---|---|---|
|
||||
| `filterType` | 收支类型 | `[]` |
|
||||
| `filterCategory` | 分类(`0` 表示未分类) | `[]` |
|
||||
| `filterChannel` | 交易渠道 | `[]` |
|
||||
| `filterDateRange` | 日期范围 `[number, number]` | `null` |
|
||||
| `filterIncludeInStats` | 计入统计三态 `null`/`'true'`/`'false'` | `null` |
|
||||
| `filterRemark` | 备注关键字 | `''` |
|
||||
| `filterMinAmount` / `filterMaxAmount` | 金额区间 | `null` |
|
||||
| `pageSize` | 每页条数 `10`/`20`/`50`/`100`/`200`/`500` | `20` |
|
||||
| `sortValue` | 排序 `transactionTime-desc` 等 4 种 | `transactionTime-desc` |
|
||||
| `filterTag` | 标签(`0` 表示无标签) | `[]` |
|
||||
|
||||
- **读取**:`loadFilterPrefs()`(第 372 行)在 `onMounted`(第 2030 行)调用;对 `pageSize` 校验白名单、对 `sortValue` 校验选项,兼容旧字段 `filterPaymentMethod`。
|
||||
- **保存**:`watch` 深度监听 11 个变量,任一变化自动保存(第 417 行)。
|
||||
- **目的**:记住筛选条件、每页条数与排序方式,下次进入自动还原。
|
||||
|
||||
### AI 相关偏好(收支列表)
|
||||
|
||||
| 键名 | 取值 | 说明 |
|
||||
|---|---|---|
|
||||
| `finance.transaction.aiScope` | `BOTH`/`CATEGORY`/`TAG` | 批量 AI 建议范围,非法值回退 `BOTH`(第 146 行) |
|
||||
| `finance.transaction.aiRecordTab` | `text`/`image` | AI 记账方式,非法值回退 `text`(第 206 行) |
|
||||
| `finance.transaction.aiQueryHistory` | 字符串数组(≤10) | AI 自然语言查询历史,最新在前 |
|
||||
| `finance.transaction.remarkHistory` | 字符串数组(≤10) | 备注关键词历史,失焦/回车记录 |
|
||||
|
||||
- **`aiRecordTab`** 是 `AGENTS.md` 点名的参考实现:记住用户上次用的记账方式(文字或图片),下次打开 AI 记账弹窗直接落到该 Tab。
|
||||
- **关键词历史**统一由 `composables/useKeywordHistory.ts` 管理:去重(忽略大小写)、最新在前、上限 10 条,损坏数据自动忽略,下拉末尾提供「清空历史」项。
|
||||
- 读取:`load()`;记录:`record()`;清空:`clear()`。
|
||||
- 三处使用:AI 查询历史、备注历史(均在 `TransactionList.vue`)、统计面板 AI 搜索历史(`Dashboard.vue`,键 `finance.dashboard.aiSearchHistory`)。
|
||||
|
||||
### 账单导入类型 `finance.billImport.billType`
|
||||
|
||||
- **文件**:`frontend/src/views/BillImportPanel.vue`(第 25 行)
|
||||
- **取值**:账单来源编码(`ALIPAY`、`WECHAT`、`CMB` 等)
|
||||
- **读取**:`readBillTypePref()`(第 62 行)作为初始值;`loadBillTypes()` 拿到后端列表后校正,非法值回退首个来源。
|
||||
- **保存**:`watch(billType)`(第 226 行)在切换类型时写入,同时清空已选文件,避免格式与类型不匹配。
|
||||
- **目的**:记住上次选择的账单来源。
|
||||
|
||||
### 账单关系类型 `finance.transaction.relationType`
|
||||
|
||||
- **文件**:`frontend/src/components/TransactionRelationEditorModal.vue`(第 14 行)
|
||||
- **取值**:`PARENT_CHILD`(默认)、`SIBLING`
|
||||
- **读取**:`loadPreferredRelationType()`(第 81 行)在新建弹窗初始化时调用。
|
||||
- **保存**:`watch(editorType)`(第 69 行)**仅在创建模式**(`editingGroupId === null`)写入,编辑既有关系组不覆盖偏好。
|
||||
- **目的**:新建关系组时默认沿用上次的关系类型。
|
||||
|
||||
### 页面标签页 `emailBill.tab` / `logs.tab`
|
||||
|
||||
- **文件**:`views/EmailBillManage.vue:43`、`views/Logs.vue:25`
|
||||
- **取值**:邮箱页 `configs`/`senderRules`/`messages`;日志页 `http`/`login`/`app`
|
||||
- **读取**:路由 `/email-bills`、`/logs` 的 `redirect` 读取偏好并拼接子路径(`router/index.ts:69`、第 93 行),带白名单校验,无记录回退默认。
|
||||
- **保存**:`watch(activeTab, ..., { immediate: true })` 在 Tab 切换时写入。
|
||||
- **目的**:下次点击「邮箱账单」「系统日志」回到上次标签页。
|
||||
- **说明**:这两个是 `AGENTS.md` 点名的参考实现,但键名未加 `finance.` 前缀,属早期实现。
|
||||
|
||||
## sessionStorage 偏好:滚动位置
|
||||
|
||||
- **文件**:`frontend/src/views/Layout.vue`(前缀第 83 行,键为 `finance.scroll.<路由路径>`)
|
||||
- **保存**:内容区 `scroll` 事件经 `requestAnimationFrame` 节流写入;路由切换前保存离开页;`beforeunload` 兜底(第 172 行)。
|
||||
- **读取**:`restoreScroll()`(第 139 行)在 `onMounted` 与路由切换后恢复;因页面内容异步加载、切换后高度可能不足,用 `rAF` 轮询等待内容高度足够再定位,2 秒超时后按当前可达最大值定位。
|
||||
- **目的**:刷新或页面切换后恢复阅读位置;仅当前会话有效,关闭浏览器即失效。
|
||||
|
||||
## 后端按用户同步的偏好
|
||||
|
||||
以下偏好需要跟随账号(换设备/多端一致),因此存后端,按当前用户隔离,前端通过设置页读写。
|
||||
|
||||
### 「不计入统计」展示样式
|
||||
|
||||
- **接口**:`GET /api/setting/excluded-display-style`、`PUT /api/setting/excluded-display-style`(`api/setting.ts:14`)
|
||||
- **取值**:`strikethrough`(默认,整行删除线)、`muted`(灰色标签)
|
||||
- **前端**:设置页 `components/DisplayConfigPanel.vue` 编辑保存;收支列表 `TransactionList.vue:647` 在 `onMounted` 读取并应用到「不计入统计」记录(失败保持默认删除线)。
|
||||
- **目的**:统一用户对「不计入统计」记录的外观选择。
|
||||
|
||||
### 账单来源「计入统计」默认值
|
||||
|
||||
- **接口**:`GET/PUT /api/setting/bill-source-include-in-stats`(`api/setting.ts:4`)
|
||||
- **数据结构**:`{ [账单来源编码]: boolean }`
|
||||
- **前端**:设置页 `components/BillSourceStatsPanel.vue` 编辑保存(可点整行或开关切换)。
|
||||
- **目的**:按账单来源设置导入时默认是否计入统计,仅影响统计口径,不改变导入结果。
|
||||
|
||||
### AI 配置
|
||||
|
||||
- **接口**:`GET /api/ai/config`、`PUT /api/ai/config`(`api/ai.ts:37`)
|
||||
- **数据结构**:`{ enabled, apiKey, hasApiKey, baseUrl, model, visionModel }`,`apiKey` 留空表示不修改原值。
|
||||
- **前端**:设置页 `components/AiConfigPanel.vue` 编辑保存。
|
||||
- **目的**:用户级 AI 能力配置;其他页面的 AI 元素统一经 `getAiConfig().enabled` 控制显隐。
|
||||
|
||||
> 严格说 AI 配置属「功能配置」而非纯界面偏好,但因按用户存储,一并列出。设置页中的数据库配置、注册开关、定时通知为**系统级全局配置**,不在此列。
|
||||
|
||||
## 未实现的偏好
|
||||
|
||||
以下常见界面偏好当前**没有**持久化实现,改动时无需假设其存在:
|
||||
|
||||
- **主题 / 暗色模式**:无主题存储键,界面主题固定。
|
||||
- **侧边栏折叠 / 展开**:`Layout.vue` 使用固定 `NLayoutSider`,无折叠状态。
|
||||
- **表格列显示 / 隐藏 / 顺序**:各页列配置为固定 `computed`。
|
||||
- **货币 / 金额格式**:无货币偏好。
|
||||
- **独立日期格式**:仅由语言 `locale` 间接驱动 Naive UI 日期本地化,无单独偏好。
|
||||
- **全局分页大小**:仅收支列表的 `pageSize` 持久化;日志、规则、邮箱账单等页面分页大小不持久化。
|
||||
- **IndexedDB / cookie**:未使用。
|
||||
- **Pinia 持久化插件 / vueuse `useStorage`**:未安装、未使用。
|
||||
|
||||
## 附:登录凭证 `token`
|
||||
|
||||
- **文件**:`frontend/src/store/user.ts`(第 8 行)
|
||||
- **保存**:`setToken()` 写入 `localStorage`;`logout()` 移除。
|
||||
- **使用**:`api/request.ts` 请求拦截器读取并写入 `Authorization` 头。
|
||||
- **说明**:这是认证状态,**不属于界面偏好**,列出以免混淆。`nickname`、`userId` 不持久化,每次由 `fetchUserInfo()` 从后端获取。
|
||||
|
||||
## 新增偏好的约定
|
||||
|
||||
新增一项用户界面偏好时:
|
||||
|
||||
1. 存储键以 `finance.` 前缀命名,按页面/模块分段(如 `finance.transaction.xxx`)。
|
||||
2. 优先用 `watch` 自动保存,避免要求用户手动点保存。
|
||||
3. 读取时对取值做白名单校验,非法或损坏数据回退默认项。
|
||||
4. 写入包 `try/catch`,忽略存储异常。
|
||||
5. 需要跨设备一致时,改存后端并按当前用户隔离,而非 `localStorage`。
|
||||
@@ -39,7 +39,7 @@ finance-app/
|
||||
| `notification` | 通知渠道发送器 |
|
||||
| `scheduler` | 定时任务(通知、日志清理、邮箱账单) |
|
||||
| `service` / `service.impl` | 业务层 |
|
||||
| `util` | 工具类(`JwtUtil`、`UserContext`、`AppLogUtils` 等) |
|
||||
| `util` | 工具类(`JwtUtil`、`UserContext`、`AppLogUtils`、`ImageUtils`、`AppHome`、`TransactionDedupUtils` 等) |
|
||||
| `vo` | 响应对象 |
|
||||
| `launcher` | 桌面 GUI 启动器(仅打入带 GUI 的 jar) |
|
||||
|
||||
@@ -83,7 +83,7 @@ views/ # 页面组件
|
||||
|---|---|
|
||||
| `config/database.json` | 数据库配置(SQLite / MySQL) |
|
||||
| `config/app.json` | 应用配置(注册开关、通知链接、日志保留天数、按账单来源的计入统计默认值) |
|
||||
| `data/` | SQLite 数据文件与邮箱账单附件 |
|
||||
| `data/` | SQLite 数据文件、邮箱账单附件与图片记账截图(`data/ai-images/`) |
|
||||
| `logs/` | 运行日志文件 |
|
||||
|
||||
## 相关文档
|
||||
|
||||
+36
-5
@@ -18,7 +18,10 @@
|
||||
- 智能查询 / 统计:你输入的自然语言,以及分类与标签名称;
|
||||
- 智能记账:你输入的一句话;
|
||||
- 生成分类/标签、推荐 Emoji/颜色:你输入的名称或描述;
|
||||
- 生成规则:所选记录的类型、备注、交易渠道、已有分类与标签(不含金额与原始账单数据)。
|
||||
- 生成规则:所选记录的类型、备注、交易渠道、已有分类与标签(不含金额与原始账单数据);
|
||||
- 图片记账:你**主动上传**的账单/支付截图会**整张**发送给视觉模型(可能含金额、交易对方、订单号等),视为你主动提供的输入。
|
||||
|
||||
图片同时会保存在本地(`data/ai-images/`),仅本人可查看;请勿上传包含他人敏感信息的图片。
|
||||
|
||||
API Key 仅保存在本地数据库中,仅用于向你配置的接口地址发起请求,不会随上述数据一并外发。
|
||||
|
||||
@@ -28,7 +31,7 @@ API Key 仅保存在本地数据库中,仅用于向你配置的接口地址发
|
||||
|
||||
- 开启「启用 AI 建议」。
|
||||
- 填写 **API Key**。
|
||||
- 可选修改 **接口地址**(默认 `https://api.deepseek.com`)与**模型**(默认 `deepseek-chat`)。
|
||||
- 可选修改 **接口地址**(默认 `https://api.deepseek.com`)、**模型**(默认 `deepseek-chat`)与**视觉模型**(默认 `deepseek-flash`,用于图片记账)。
|
||||
- 点击「测试连接」验证配置。
|
||||
|
||||

|
||||
@@ -38,10 +41,12 @@ API Key 仅保存在本地数据库中,仅用于向你配置的接口地址发
|
||||
| 功能 | 入口 | 说明 |
|
||||
|---|---|---|
|
||||
| 分类/标签建议 | 收支记录 → 一键设置 → AI 智能建议 | 对当前页记录批量建议分类与标签,可一键采纳 |
|
||||
| 单条建议 | 新增/编辑记录 → AI 推荐 | 推荐已有分类或标签 |
|
||||
| 单条建议 | 新增/编辑记录 → AI 推荐 | 推荐已有分类、标签或交易渠道 |
|
||||
| 智能查询 | 收支记录 → AI 智能查询 | 自然语言解析为筛选条件 |
|
||||
| 智能记账 | 收支记录 → 智能记账 | 一句话解析为一笔记录 |
|
||||
| 智能记账(文字) | 收支记录 → 智能记账 → 文字记账 | 一句话解析为一笔记录 |
|
||||
| 智能记账(图片) | 收支记录 → 智能记账 → 图片记账 | 上传一至多张账单/支付截图,实时识别为多条记录,勾选后批量导入 |
|
||||
| 生成分类 | 分类管理 → AI 生成 | 由用途描述生成分类 |
|
||||
| 生成渠道 | 新增/编辑记录 → 交易渠道 → AI 新建 | 由账单描述生成渠道名称并创建 |
|
||||
| 推荐 Emoji | 分类管理 → AI 推荐 | 为分类推荐 Emoji 图标 |
|
||||
| 推荐颜色 | 标签管理 → AI 推荐 | 为标签推荐颜色 |
|
||||
| 生成规则 | 规则管理 → AI 生成规则 | 由已打标签的记录归纳分类与标签规则 |
|
||||
@@ -66,10 +71,36 @@ API Key 仅保存在本地数据库中,仅用于向你配置的接口地址发
|
||||
|
||||
## 智能记账
|
||||
|
||||
在「智能记账」弹窗中描述一笔收支,AI 解析出类型、金额、分类、交易渠道、时间、备注、标签等字段,并预填到新增记录表单,确认后保存。
|
||||
「智能记账」弹窗提供「**文字记账**」与「[图片记账](#图片记账)」两个标签页,再次打开时会记住上次使用的方式。
|
||||
|
||||
在「文字记账」中描述一笔收支,AI 解析出类型、金额、分类、交易渠道、时间、备注、标签等字段,并预填到新增记录表单,确认后保存。
|
||||
|
||||

|
||||
|
||||
## 图片记账
|
||||
|
||||
在「智能记账」弹窗切换到「**图片记账**」标签页,上传账单或支付截图,AI 会调用**视觉模型**一次性识别其中的每一笔交易。
|
||||
|
||||

|
||||
|
||||
- **多图上传**:一次最多 **5 张**,单张不超过 **10MB**、总大小不超过 **30MB**;支持 JPEG / PNG / GIF / WebP。
|
||||
- **粘贴上传**:也可按 `Ctrl+V`(Mac 为 `⌘V`)直接粘贴剪贴板中的图片。
|
||||
- **补充说明**(可选):在下方输入框补充提示(如「这是 9 月的账单」),帮助模型理解。
|
||||
- 点击「识别图片」后,弹窗内会**实时显示识别进度**(校验图片、保存、调用模型、解析、查重等步骤),耗时较长的识别过程可见可控。
|
||||
- 识别完成后进入**结果弹窗**:
|
||||
|
||||

|
||||
|
||||
- 结果以表格逐条展示,**默认全选**(疑似重复项默认不勾选),可直接编辑**类型、金额、分类、时间、备注**。
|
||||
- **来源图**列标注该笔交易来自第几张上传图片;**重复**列标记与已有记录或本批其它记录「类型+金额+时间」一致的疑似重复项。
|
||||
- **原始数据**列可查看模型从图片中提取的原始字段(时间、对方、商品、订单号等),用于核对。
|
||||
- 顶部摘要说明识别到的账单内容;勾选后点击「导入勾选」批量写入收支记录(导入时会再次按去重键跳过重复)。
|
||||
- 未识别到有效交易时会给出提示,不会写入任何记录。
|
||||
|
||||
> 图片记账使用的是视觉模型,可在「系统设置 → AI 模型配置」中单独配置(默认 `deepseek-flash`)。详见[系统设置](/guide/settings)。
|
||||
|
||||
导入后的记录在[收支记录](/guide/transactions)页的「**来源图片**」列可回看原图(默认加载缩略图,可切换为原图)。
|
||||
|
||||
## AI 统计搜索
|
||||
|
||||
在统计面板顶部的 AI 搜索框中用一句自然语言描述统计意图,例如「上个月餐饮花了多少」「近半年交通支出趋势,按周看」。系统会解析为**时间范围、收支类型、分类、标签、交易渠道、金额区间、备注、统计口径与趋势粒度**并自动回填、刷新图表。详见[统计面板](/guide/dashboard)。
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# 交易渠道管理
|
||||
|
||||
「交易渠道」用于标记一笔收支的付款或收款途径,既包括账单导入时的来源,也包括人工记账时选择的渠道。系统内置一组常用渠道,并支持按账号自定义。
|
||||
|
||||
## 入口
|
||||
|
||||
在[收支记录](/guide/transactions)页顶部操作区点击「**渠道管理**」进入(独立页面,不占用左侧菜单)。
|
||||
|
||||

|
||||
|
||||
页面顶部提供返回按钮,可一键回到收支记录页。
|
||||
|
||||
## 内置渠道
|
||||
|
||||
以下渠道由系统内置,无需创建,名称随界面语言自动显示:
|
||||
|
||||
| 编码 | 名称 |
|
||||
|---|---|
|
||||
| `ALIPAY` | 支付宝 |
|
||||
| `WECHAT` | 微信 |
|
||||
| `MEITUAN` | 美团 |
|
||||
| `TAOBAO` | 淘宝 |
|
||||
| `JD` | 京东 |
|
||||
| `CMB` | 招商银行 |
|
||||
| `BOCOM` | 交通银行 |
|
||||
| `CASH` | 现金 |
|
||||
|
||||
> 账单导入时,系统会按账单来源自动写入对应的内置渠道;人工记账默认可选「现金」或任意自定义渠道。
|
||||
|
||||
## 自定义渠道
|
||||
|
||||
除内置渠道外,你可以新建自己的渠道(例如「某张储蓄卡」「公司报销」等)。
|
||||
|
||||
### 新建渠道
|
||||
|
||||
点击右上角「**新建渠道**」,输入名称后确认。
|
||||
|
||||

|
||||
|
||||
- 渠道名称在**当前账号内不可重复**。
|
||||
- 渠道编码由系统生成且**稳定不变**,交易记录按编码关联。
|
||||
|
||||
### 重命名渠道
|
||||
|
||||
在列表行点击「编辑」即可改名。**改名不影响历史账单**——所有已使用该渠道的记录会同步显示新名称。
|
||||
|
||||
### 删除渠道
|
||||
|
||||
在列表行点击「删除」即可删除。
|
||||
|
||||
- 若该渠道**已被账单使用**,系统会拒绝删除并提示使用数量,避免历史记录出现无效渠道。
|
||||
- 列表中的「使用账单数」列可直观看到每个渠道被多少笔记录使用。
|
||||
|
||||
## 在哪些地方使用
|
||||
|
||||
自定义渠道创建后,可在以下场景直接选用:
|
||||
|
||||
- **新增 / 编辑记录**:交易渠道下拉中出现在内置渠道之后。
|
||||
- **筛选与统计**:收支记录页筛选、统计面板的渠道维度与筛选均可选择自定义渠道,图表与分组会展示其名称。
|
||||
- **AI 能力**:智能查询、统计搜索与智能记账会把当前账号的自定义渠道一并作为可选渠道;新增/编辑记录时还可用「渠道 · AI 推荐」选择已有渠道,或「渠道 · AI 新建」由描述生成并创建渠道。
|
||||
- **导出**:Excel 导出时渠道列展示自定义渠道名称。
|
||||
|
||||
> 删除前请先确认没有账单仍在使用该渠道;如需保留历史,建议改名而非删除。
|
||||
|
||||
## 相关
|
||||
|
||||
- [收支记录](/guide/transactions):记账、筛选与账单导入。
|
||||
- [统计面板](/guide/dashboard):按渠道维度统计。
|
||||
- [AI 能力](/guide/ai):自然语言查询与图片记账。
|
||||
+2
-1
@@ -12,12 +12,13 @@
|
||||
|
||||
- [收支记录](/guide/transactions):记录管理、筛选、批量操作、账单导入、AI 建议与记账
|
||||
- [分类与标签](/guide/categories-tags):分类与标签的维护与使用建议
|
||||
- [交易渠道管理](/guide/channels):内置渠道与自定义渠道的新建、改名与删除
|
||||
- [规则管理](/guide/rules):按关键词/金额/类型自动归类打标,支持 AI 生成规则、搜索与分页
|
||||
- [邮箱账单](/guide/email-bills):IMAP 自动拉取账单邮件
|
||||
|
||||
## 智能与自动化
|
||||
|
||||
- [AI 能力](/guide/ai):分类标签建议、自然语言查询与记账、AI 搜索与规则生成、用量统计
|
||||
- [AI 能力](/guide/ai):分类标签与渠道建议、自然语言查询与记账、多图图片记账(实时进度)、AI 搜索与规则生成、用量统计
|
||||
- [定时提醒与通知](/guide/notifications):多渠道账单导入提醒
|
||||
|
||||
## 系统
|
||||
|
||||
@@ -17,10 +17,11 @@
|
||||
|---|---|
|
||||
| 收支记录 | 增删改查、分页与多条件筛选、批量设置分类/标签、批量删除、计入统计开关、备注隐藏、导出 Excel |
|
||||
| 分类与标签 | 收入/支出分类与标签的增删改查,新用户自动获得默认数据,AI 推荐 Emoji 与颜色 |
|
||||
| 交易渠道 | 内置渠道 + 自定义渠道的新建/改名/删除,改名不影响历史账单 |
|
||||
| 统计图表 | 月度收支、分类占比、收支趋势、交易渠道占比,支持按日/周/月/季/年粒度,点击趋势图可下钻对应明细 |
|
||||
| 账单导入 | 七类账单解析、zip 解密、解析预览、按交易号去重、规则建议 |
|
||||
| 自定义规则 | 关键词/收支类型/金额区间组合,优先级排序、启用停用,导入时自动应用;支持 AI 生成规则、搜索与分页 |
|
||||
| AI 能力 | 分类标签建议、自然语言查询、自然语言记账、AI 统计搜索、AI 生成规则、分类/标签生成、Emoji/颜色推荐、用量统计 |
|
||||
| AI 能力 | 分类标签与渠道建议、自然语言查询、文字/多图图片记账、AI 统计搜索、AI 生成规则、分类/标签/渠道生成、Emoji/颜色推荐、用量统计 |
|
||||
| 邮箱账单 | 多邮箱 IMAP IDLE 监听、发件人规则识别账单类型、待处理列表先预览再导入 |
|
||||
| 定时通知 | 每天/每周/每月提醒,邮箱 SMTP 与 Server 酱渠道,发送日志 |
|
||||
| 系统日志 | 访问日志、登录日志、运行日志在线查看,运行日志实时推送,自动清理 |
|
||||
|
||||
+6
-1
@@ -13,6 +13,7 @@
|
||||
| 收支记录 | `/transactions` | 记录管理与**账单导入** | 左侧菜单 |
|
||||
| 分类管理 | `/categories` | 收入/支出分类维护 | 左侧菜单 |
|
||||
| 标签管理 | `/tags` | 标签维护 | 左侧菜单 |
|
||||
| 交易渠道管理 | `/channels` | 自定义交易渠道维护 | 收支记录页「渠道管理」按钮 |
|
||||
| 规则管理 | `/rules` | 自动归类规则 | 左侧菜单 |
|
||||
| 邮箱账单 | `/email-bills/*` | 邮箱配置 / 发件人规则 / 待处理账单 | 左侧菜单 |
|
||||
| 系统设置 | `/settings` | 数据库 / 注册 / 账单来源统计 / 外观 / AI / 通知配置 | 顶部用户菜单 |
|
||||
@@ -33,6 +34,8 @@
|
||||
- **退出**:清空登录态并返回 `/login`
|
||||
|
||||
> 「模型使用情况」没有独立菜单入口,需从「系统设置 → AI 模型配置 → 使用情况」进入。
|
||||
>
|
||||
> 「交易渠道管理」同样没有独立菜单入口,需从「收支记录 → 渠道管理」进入。
|
||||
|
||||
## 典型跳转路径
|
||||
|
||||
@@ -41,9 +44,10 @@
|
||||
- **查看趋势明细**:统计面板 → 点击「收支趋势」某一列(柱子或该列空白处)→ 收支记录(自动带入时间范围、类型与当前筛选条件)
|
||||
- **账单导入**:收支记录 → 点击「导入账单」→ 解析预览 → 确认导入 → 回到收支记录
|
||||
- **手机导入**:通知邮件链接 → `/bill-import`(独立页,非弹窗)→ 上传解析 → 确认导入
|
||||
- **渠道维护**:收支记录 → 点击「渠道管理」→ 新建 / 改名 / 删除自定义渠道 → 返回收支记录
|
||||
- **自动归类**:规则管理配置规则 → 账单导入预览时命中并给出建议
|
||||
- **邮箱账单**:邮箱配置 → 发件人规则 → 待处理账单 → 解析 → 导入到收支记录
|
||||
- **AI 辅助**:系统设置启用 AI → 统计面板「AI 搜索」、收支记录「一键设置 / 智能记账 / AI 查询」、分类与标签「AI 推荐」、规则管理「AI 生成规则」
|
||||
- **AI 辅助**:系统设置启用 AI → 统计面板「AI 搜索」、收支记录「一键设置 / 文字·图片记账 / AI 查询」、编辑记录「分类·标签·渠道 AI 推荐与新建」、分类与标签「AI 推荐」、规则管理「AI 生成规则」
|
||||
- **系统维护**:顶部菜单 → 系统设置 / 系统日志
|
||||
|
||||
## 页面关系说明
|
||||
@@ -53,3 +57,4 @@
|
||||
- 收支记录页的「导入账单」**弹窗**(桌面端常用);
|
||||
- 通知邮件直达的 **`/bill-import` 独立页**(面向手机,非弹窗,元素精简,系统内无其他入口)。
|
||||
- **系统设置**聚合了数据库、注册、账单来源统计、外观、AI、通知六类配置;模型用量与日志分别从设置页与顶部菜单进入。
|
||||
- **交易渠道管理**是收支记录页的附属页,用于维护自定义渠道,维护完成后返回收支记录继续记账或筛选。
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
- **API Key**:DeepSeek 的 API Key(已配置时留空表示不修改)。
|
||||
- **接口地址**:默认 `https://api.deepseek.com`。
|
||||
- **模型**:默认 `deepseek-chat`。
|
||||
- **视觉模型**:默认 `deepseek-flash`,用于[图片记账](/guide/ai#图片记账)。
|
||||
- 提供「保存」与「测试连接」。
|
||||
- 右上角「使用情况」可查看 AI 调用统计与明细,详见 [AI 能力](/guide/ai)。
|
||||
|
||||
|
||||
+22
-8
@@ -12,6 +12,7 @@
|
||||
| 一键设置 | 对当前表格展示的记录批量设置分类、计入统计与标签 |
|
||||
| 批量删除 | 删除表格中勾选的记录 |
|
||||
| 导入账单 | 打开账单导入弹窗 |
|
||||
| 渠道管理 | 进入[交易渠道管理](/guide/channels)页,维护自定义渠道 |
|
||||
| 导出 Excel | 按当前筛选条件导出 |
|
||||
| 刷新 | 按当前筛选条件与页码重新加载列表 |
|
||||
| 智能记账 | 用一句话描述收支,AI 解析后预填表单(需启用 AI) |
|
||||
@@ -90,7 +91,7 @@
|
||||
|
||||
## 筛选与 AI 智能查询
|
||||
|
||||
**常用筛选**:类型、分类(含「未分类」)、日期范围、备注关键字、交易渠道。
|
||||
**常用筛选**:类型、分类(含「未分类」)、日期范围、备注关键字、交易渠道。交易渠道下拉包含内置渠道与账号下的[自定义渠道](/guide/channels)。
|
||||
|
||||
**更多筛选**:标签(含「无标签」)、计入统计、金额区间。
|
||||
|
||||
@@ -105,19 +106,23 @@
|
||||
|
||||
## 表格与行操作
|
||||
|
||||
表格展示:交易时间、类型、分类、金额、交易渠道、备注、标签、计入统计开关与操作。
|
||||
表格展示:交易时间、类型、分类、金额、交易渠道、备注、标签、来源图片、计入统计开关与操作。
|
||||
|
||||
- **备注隐藏**:可将敏感备注持久隐藏为 `******`,需要时临时「查看」或「取消隐藏」。
|
||||
- **计入统计**:行内开关可直接切换;关闭后该记录不纳入月度统计(适合借款、转账等),行内会以删除线标示。
|
||||
- **行操作**:编辑、查看「原始数据」(账单导入时的原始字段,以 JSON 展示)、删除。
|
||||
- **来源图片**:图片记账生成的记录会显示「查看(N)」入口,弹窗内先加载缩略图,可按需「加载原图」查看对应截图。
|
||||
- **行操作**:编辑、查看「原始数据」(账单导入或图片记账时的原始字段,以 JSON 展示)、删除。
|
||||
|
||||

|
||||
|
||||
分页支持每页 10/20/50/100/200/500 条。
|
||||
|
||||
## 查看原始数据
|
||||
|
||||
账单导入时,系统会完整保留每笔交易在原始账单中的字段(以 JSON 存储)。在记录行点击「**原始数据**」即可查看,用于核对金额、交易状态、对方账号、交易类型等明细。
|
||||
账单导入或图片记账时,系统会完整保留每笔交易在原始账单中的字段(以 JSON 存储)。在记录行点击「**原始数据**」即可查看,用于核对金额、交易状态、对方账号、交易类型等明细。
|
||||
|
||||
- 仅**账单导入**的记录有原始数据;**手动新增**的记录没有,该按钮不可用。
|
||||
- **账单导入**记录保留原始账单行字段;**图片记账**记录保留模型从截图提取的原始字段。
|
||||
- **手动新增**的记录没有原始数据,该按钮不可用。
|
||||
- 原始数据是排查导入异常、核对账单的重要依据。
|
||||
|
||||
## 一键设置(批量设置)
|
||||
@@ -137,10 +142,15 @@
|
||||
|
||||
## AI 智能记账
|
||||
|
||||
在「智能记账」弹窗中用自然语言描述一笔收支,例如「昨天中午吃饭 35 支付宝」,AI 会解析为类型、金额、分类、交易渠道、时间、备注、标签等字段,并预填到新增记录表单,确认后保存。
|
||||
「智能记账」弹窗提供**文字记账**与**图片记账**两种方式。
|
||||
|
||||

|
||||
|
||||
- **文字记账**:用一句话描述一笔收支,例如「昨天中午吃饭 35 支付宝」,AI 会解析为类型、金额、分类、交易渠道、时间、备注、标签等字段,并预填到新增记录表单,确认后保存。
|
||||
- **图片记账**:上传一至多张账单或支付截图(也可 `Ctrl+V` 粘贴),AI 实时识别其中的每一笔交易,生成可编辑的记录列表,勾选后批量导入。
|
||||
|
||||
再次打开弹窗会记住上次使用的记账方式。图片记账的用法与限制详见 [AI 能力 → 图片记账](/guide/ai#图片记账)。
|
||||
|
||||
## 手动添加记录
|
||||
|
||||
1. 在收支记录页右上角点击「**新增记录**」。
|
||||
@@ -151,7 +161,7 @@
|
||||
| 类型 | 支出 / 收入 |
|
||||
| 分类 | 新增时必填 |
|
||||
| 金额 | 大于 0 |
|
||||
| 交易渠道 | 可选 |
|
||||
| 交易渠道 | 可选,含内置渠道与[自定义渠道](/guide/channels);启用 AI 时可一键推荐或新建 |
|
||||
| 交易时间 | 默认当前时间 |
|
||||
| 备注 | 可选 |
|
||||
| 标签 | 可选,可多选 |
|
||||
@@ -169,7 +179,7 @@
|
||||
|
||||
### 编辑时的 AI 辅助
|
||||
|
||||
启用 AI 后,编辑弹窗中的**分类**与**标签**旁会出现 AI 辅助按钮(与新增记录一致):
|
||||
启用 AI 后,编辑弹窗中的**分类**、**标签**与**交易渠道**旁会出现 AI 辅助按钮(与新增记录一致):
|
||||
|
||||
| 按钮 | 作用 |
|
||||
|---|---|
|
||||
@@ -177,6 +187,10 @@
|
||||
| 分类 · **AI 新建** | 根据当前账单信息生成新分类;确认「保存并选用」后创建并选中,若已存在同名同类型分类则直接选中 |
|
||||
| 标签 · **AI 推荐** | 推荐已有标签并追加(自动去重) |
|
||||
| 标签 · **AI 新建** | 生成新标签(含建议颜色),确认后创建并选中;同名标签直接选中 |
|
||||
| 渠道 · **AI 推荐** | 根据当前类型、金额、渠道、备注,从已有渠道中推荐并填入 |
|
||||
| 渠道 · **AI 新建** | 生成新渠道名称;确认「保存并选用」后创建并选中,同名渠道直接选中 |
|
||||
|
||||

|
||||
|
||||
> 「AI 新建」需先填写备注或金额,便于模型理解;生成结果会显示确认条,点击「保存并选用」才真正创建。
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ layout: home
|
||||
hero:
|
||||
name: 收支管理系统
|
||||
text: 个人收支管理 · 多来源账单导入 · AI 智能记账
|
||||
tagline: 支持支付宝、微信、美团、淘宝、京东、招商银行、交通银行账单导入,提供分类标签管理、自定义规则、AI 建议与定时提醒
|
||||
tagline: 支持支付宝、微信、美团、淘宝、京东、招商银行、交通银行账单导入,提供分类标签与交易渠道管理、自定义规则、AI 建议(含多图图片记账)与定时提醒
|
||||
image:
|
||||
src: https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/dashboard.png
|
||||
alt: 统计面板
|
||||
@@ -24,10 +24,12 @@ features:
|
||||
details: 支持支付宝、微信、美团、淘宝、京东、招商银行、交通银行账单,解析预览后确认导入,按交易号自动去重。
|
||||
- title: 分类与标签
|
||||
details: 自定义收入/支出分类与标签,支持批量设置,AI 可推荐 Emoji 图标与标签颜色。
|
||||
- title: 交易渠道管理
|
||||
details: 内置常用渠道,支持自定义渠道的新建、改名与删除,记账时可由 AI 推荐或生成;改名不影响历史账单,筛选与统计均可选用。
|
||||
- title: 自定义规则
|
||||
details: 按关键词、收支类型、金额区间组合条件,命中后自动应用分类与标签,导入预览即展示建议。
|
||||
- title: AI 智能能力
|
||||
details: 基于 DeepSeek 的分类/标签建议、自然语言查询与记账、分类与标签生成,并统计 token 用量。
|
||||
details: 基于 DeepSeek 的分类/标签/渠道建议、自然语言查询、文字与多图图片记账(实时进度)、分类标签渠道生成,并统计 token 用量。
|
||||
- title: 统计与图表
|
||||
details: 月度收支汇总、分类占比、收支趋势、支付方式占比,支持多维度筛选与自定义时间范围。
|
||||
- title: 定时提醒与通知
|
||||
|
||||
@@ -30,7 +30,10 @@ export async function newContext(
|
||||
browser: Browser,
|
||||
viewport: { width: number; height: number } = DEFAULT_VIEWPORT,
|
||||
): Promise<BrowserContext> {
|
||||
return browser.newContext({ viewport })
|
||||
const context = await browser.newContext({ viewport })
|
||||
// 首次访问需等待 Vite 按需编译,放宽默认导航超时,避免冷启动误判失败
|
||||
context.setDefaultNavigationTimeout(120000)
|
||||
return context
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@ import { resolve } from 'node:path'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import type { Page } from 'playwright-core'
|
||||
import { clickText, injectSession, launchBrowser, login, newContext, waitPage } from './browser.js'
|
||||
import { BASE_URL, ELEM_DIR, SAMPLE_BILL } from './config.js'
|
||||
import { BASE_URL, ELEM_DIR, SAMPLE_BILL, SAMPLE_IMAGE, SAMPLE_IMAGE2 } from './config.js'
|
||||
|
||||
/** 页面就绪标志:列表页表格、设置面板或卡片 */
|
||||
const PAGE_READY = '.n-data-table, .config-card, .n-card'
|
||||
@@ -26,6 +26,7 @@ const MODAL_SCENES: Array<[string, string, string]> = [
|
||||
['add-rule', '/rules', '新增规则'],
|
||||
['add-email-config', '/email-bills/configs', '新增'],
|
||||
['add-channel', '/settings', '添加渠道'],
|
||||
['channel-create', '/channels', '新建渠道'],
|
||||
]
|
||||
|
||||
/**
|
||||
@@ -110,6 +111,101 @@ export async function captureBillParse(page: Page): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取「图片记账」:多图上传 Tab 与识别结果弹窗。
|
||||
*
|
||||
* @param page Page 实例
|
||||
* @param importRecords 识别后是否导入勾选记录(默认 true,便于后续截取来源图片)
|
||||
*/
|
||||
export async function captureAiRecordImage(page: Page, importRecords = true): Promise<void> {
|
||||
try {
|
||||
await page.setViewportSize({ width: 1440, height: 1000 })
|
||||
await page.goto(BASE_URL + '/transactions')
|
||||
await waitPage(page, { selector: PAGE_READY })
|
||||
if (!(await clickText(page, '智能记账'))) throw new Error('未找到「智能记账」按钮')
|
||||
const modal = page.locator('.n-modal').first()
|
||||
await modal.waitFor({ state: 'visible', timeout: 20000 })
|
||||
|
||||
// 切到「图片记账」标签页,上传两张虚构账单截图后截取上传界面
|
||||
await modal.locator('.n-tabs-tab', { hasText: '图片记账' }).first().click()
|
||||
await page.waitForTimeout(600)
|
||||
await modal.locator("input[type='file']").setInputFiles([SAMPLE_IMAGE, SAMPLE_IMAGE2])
|
||||
await page.waitForTimeout(600)
|
||||
await modal.screenshot({ path: resolve(ELEM_DIR, 'ai-record-image.png') })
|
||||
console.log('[OK] modal/ai-record-image')
|
||||
|
||||
const recognize = modal.locator('button', { hasText: '识别图片' }).first()
|
||||
await recognize.evaluate((el) => (el as HTMLElement).click())
|
||||
|
||||
// 结果弹窗出现(标题「图片记账结果」)且表格有数据
|
||||
const resultModal = page.locator('.n-modal', { hasText: '图片记账结果' }).first()
|
||||
await resultModal.waitFor({ state: 'visible', timeout: 180000 })
|
||||
await resultModal.locator('.n-data-table-tbody tr').first().waitFor({ state: 'visible', timeout: 180000 })
|
||||
await page.waitForLoadState('networkidle', { timeout: 20000 }).catch(() => undefined)
|
||||
await page.waitForTimeout(800)
|
||||
await resultModal.screenshot({ path: resolve(ELEM_DIR, 'ai-record-result.png') })
|
||||
console.log('[OK] modal/ai-record-result')
|
||||
|
||||
// 导入勾选记录,便于后续「来源图片」截图(去重键保证重复执行不会产生新记录)
|
||||
if (importRecords) {
|
||||
const importBtn = resultModal.locator('button', { hasText: '导入勾选' }).first()
|
||||
await importBtn.evaluate((el) => (el as HTMLElement).click())
|
||||
await resultModal.waitFor({ state: 'hidden', timeout: 60000 }).catch(() => undefined)
|
||||
await page.waitForTimeout(1000)
|
||||
console.log('[OK] modal/ai-record-import')
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(`[FAIL] modal/ai-record-image: ${err}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取「来源图片」查看弹窗(点击表格中图片记账记录的「查看(N)」)。
|
||||
*
|
||||
* @param page Page 实例
|
||||
*/
|
||||
export async function captureSourceImages(page: Page): Promise<void> {
|
||||
try {
|
||||
await page.setViewportSize({ width: 1440, height: 1000 })
|
||||
await page.goto(BASE_URL + '/transactions')
|
||||
await waitPage(page, { selector: '.n-data-table-tbody tr' })
|
||||
const viewBtn = page.locator('button', { hasText: /查看(\d+)/ }).first()
|
||||
await viewBtn.waitFor({ state: 'visible', timeout: 20000 })
|
||||
await viewBtn.evaluate((el) => (el as HTMLElement).click())
|
||||
const modal = page.locator('.n-modal', { hasText: '来源图片' }).first()
|
||||
await modal.waitFor({ state: 'visible', timeout: 20000 })
|
||||
await page.waitForLoadState('networkidle', { timeout: 20000 }).catch(() => undefined)
|
||||
await page.waitForTimeout(1000)
|
||||
await modal.screenshot({ path: resolve(ELEM_DIR, 'source-images.png') })
|
||||
console.log('[OK] modal/source-images')
|
||||
} catch (err) {
|
||||
console.log(`[FAIL] modal/source-images: ${err}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取编辑记录弹窗中「交易渠道」的 AI 推荐与新建按钮。
|
||||
*
|
||||
* @param page Page 实例
|
||||
*/
|
||||
export async function captureChannelAi(page: Page): Promise<void> {
|
||||
try {
|
||||
await page.setViewportSize({ width: 1440, height: 1000 })
|
||||
await page.goto(BASE_URL + '/transactions')
|
||||
await waitPage(page, { selector: PAGE_READY })
|
||||
const editBtn = page.locator('tbody tr').first().locator('button', { hasText: '编辑' }).first()
|
||||
await editBtn.waitFor({ state: 'visible', timeout: 20000 })
|
||||
await editBtn.click()
|
||||
const modal = page.locator('.n-modal').first()
|
||||
await modal.waitFor({ state: 'visible', timeout: 20000 })
|
||||
await page.waitForTimeout(800)
|
||||
await modal.screenshot({ path: resolve(ELEM_DIR, 'ai-channel-suggest.png') })
|
||||
console.log('[OK] modal/ai-channel-suggest')
|
||||
} catch (err) {
|
||||
console.log(`[FAIL] modal/ai-channel-suggest: ${err}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取「点击趋势柱状图跳转收支记录并自动带入筛选条件」的筛选卡片。
|
||||
*
|
||||
@@ -228,15 +324,17 @@ export async function captureAiRule(page: Page): Promise<void> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取系统设置的六个配置面板。
|
||||
* 截取系统设置的配置面板。
|
||||
*
|
||||
* @param page Page 实例
|
||||
* @param only 仅截取指定标题的面板(可选,缺省截取全部)
|
||||
*/
|
||||
export async function captureSettingsPanels(page: Page): Promise<void> {
|
||||
export async function captureSettingsPanels(page: Page, only?: string[]): Promise<void> {
|
||||
await page.setViewportSize({ width: 1440, height: 1200 })
|
||||
await page.goto(BASE_URL + '/settings')
|
||||
await waitPage(page, { selector: PAGE_READY })
|
||||
for (const [name, title] of SETTINGS_PANELS) {
|
||||
const panels = only ? SETTINGS_PANELS.filter(([, title]) => only.includes(title)) : SETTINGS_PANELS
|
||||
for (const [name, title] of panels) {
|
||||
try {
|
||||
const card = page.locator('.config-card', { hasText: title }).first()
|
||||
await card.waitFor({ state: 'visible', timeout: 20000 })
|
||||
@@ -265,6 +363,9 @@ async function main(): Promise<void> {
|
||||
await captureModals(page)
|
||||
await captureEditRecord(page)
|
||||
await captureAiRule(page)
|
||||
await captureAiRecordImage(page)
|
||||
await captureSourceImages(page)
|
||||
await captureChannelAi(page)
|
||||
await captureSettingsPanels(page)
|
||||
await captureBillParse(page)
|
||||
await captureTrendJump(page)
|
||||
|
||||
@@ -14,6 +14,7 @@ const PAGES: Array<[string, string, number, number, string]> = [
|
||||
['categories', '/categories', 1440, 1100, '.n-data-table-tbody tr'],
|
||||
['tags', '/tags', 1440, 1100, '.n-data-table-tbody tr'],
|
||||
['rules', '/rules', 1440, 1100, '.n-data-table-tbody tr'],
|
||||
['channels', '/channels', 1440, 1000, '.n-data-table-tbody tr'],
|
||||
['email-bills-configs', '/email-bills/configs', 1440, 1000, '.n-data-table-tbody tr'],
|
||||
['email-bills-sender-rules', '/email-bills/senderRules', 1440, 1000, '.n-data-table-tbody tr'],
|
||||
['email-bills-messages', '/email-bills/messages', 1440, 1000, '.n-data-table-tbody tr'],
|
||||
|
||||
@@ -24,3 +24,7 @@ export const OUT_DIR = resolve(ROOT, 'public', 'screenshots')
|
||||
export const ELEM_DIR = resolve(OUT_DIR, 'elements')
|
||||
/** 样例账单文件(虚构数据、未导入,用于「账单解析预览」截图) */
|
||||
export const SAMPLE_BILL = resolve(ROOT, 'scripts', 'samples', 'alipay_fake_preview.csv')
|
||||
/** 样例账单截图(虚构数据,用于「图片记账」识别截图) */
|
||||
export const SAMPLE_IMAGE = resolve(ROOT, 'scripts', 'samples', 'ai-bill_fake.png')
|
||||
/** 第二张样例账单截图(虚构数据,用于演示多图识别) */
|
||||
export const SAMPLE_IMAGE2 = resolve(ROOT, 'scripts', 'samples', 'ai-bill_fake2.png')
|
||||
|
||||
Reference in New Issue
Block a user