docs: 同步汇账最新功能,新增智能查数、账单关系等文档

对照 ledgerly 自文档上次更新以来的全部改动,补充新功能文档、修正品牌与版本残留,并新增下载与安装页;配图已重新截取并上传 OSS,本地截图目录不纳入版本管理。

变更内容:
- 新增「智能查数」「账单关系」两个使用文档页,并登记侧边栏与总览
- 设置页扩充为八个面板(账号安全、数据库实时进度、外观、导航等)并改为即时保存
- 收支记录补充关系列、批量操作、AI 搜索浮窗、可配置分页与「账单详情」弹窗
- 统计面板补充饼图/渠道图下钻与筛选双向联动;规则管理补充批量删除
- AI 能力补充智能查数与关系命名;新增下载与安装页
- 开发文档同步:AI 13 类调用、数据模型 24 张表、API、前端偏好、构建部署
- 统一「汇账 Ledgerly」品牌与「设置/日志」菜单文案,版本号对齐 0.1
- 截图脚本新增账单详情等场景并更新登记

说明:
- download.md 的 OSS 下载链接保持待发布产物回填,暂不调整
- 已通过 npm run docs:build 与 npm run typecheck:scripts
This commit is contained in:
2026-09-26 21:57:38 +08:00
parent b979c6e498
commit 923d6db5c0
35 changed files with 841 additions and 216 deletions
+5 -2
View File
@@ -30,6 +30,7 @@ export default defineConfig({
items: [ items: [
{ text: '项目简介', link: '/guide/introduction' }, { text: '项目简介', link: '/guide/introduction' },
{ text: '快速开始', link: '/guide/getting-started' }, { text: '快速开始', link: '/guide/getting-started' },
{ text: '下载与安装', link: '/guide/download' },
{ text: '统计面板', link: '/guide/dashboard' }, { text: '统计面板', link: '/guide/dashboard' },
{ text: '页面总览', link: '/guide/pages' }, { text: '页面总览', link: '/guide/pages' },
], ],
@@ -38,6 +39,7 @@ export default defineConfig({
text: '记账与整理', text: '记账与整理',
items: [ items: [
{ text: '收支记录', link: '/guide/transactions' }, { text: '收支记录', link: '/guide/transactions' },
{ text: '账单关系', link: '/guide/relations' },
{ text: '分类与标签', link: '/guide/categories-tags' }, { text: '分类与标签', link: '/guide/categories-tags' },
{ text: '交易渠道管理', link: '/guide/channels' }, { text: '交易渠道管理', link: '/guide/channels' },
{ text: '规则管理', link: '/guide/rules' }, { text: '规则管理', link: '/guide/rules' },
@@ -48,14 +50,15 @@ export default defineConfig({
text: '智能与自动化', text: '智能与自动化',
items: [ items: [
{ text: 'AI 能力', link: '/guide/ai' }, { text: 'AI 能力', link: '/guide/ai' },
{ text: '智能查数', link: '/guide/sql-query' },
{ text: '定时提醒与通知', link: '/guide/notifications' }, { text: '定时提醒与通知', link: '/guide/notifications' },
], ],
}, },
{ {
text: '系统', text: '系统',
items: [ items: [
{ text: '系统设置', link: '/guide/settings' }, { text: '设置', link: '/guide/settings' },
{ text: '系统日志', link: '/guide/logs' }, { text: '日志', link: '/guide/logs' },
], ],
}, },
{ {
+6 -3
View File
@@ -54,10 +54,11 @@ doc-site/
│ ├── config.mts # 站点配置(导航、侧边栏、搜索、端口 5175) │ ├── config.mts # 站点配置(导航、侧边栏、搜索、端口 5175)
│ └── theme/ # 自定义主题(图片全屏查看、图片样式) │ └── theme/ # 自定义主题(图片全屏查看、图片样式)
├── scripts/ ├── scripts/
│ ├── ts/ # 维护脚本(TypeScript) │ ├── ts/ # 维护脚本(TypeScript,含截图、演示数据、OSS 上传)
│ └── samples/ # 样例账单 │ └── samples/ # 虚构样例账单
├── refs/ # 联网检索到的资料原文(不参与构建) ├── refs/ # 联网检索到的资料原文(不参与构建)
├── jar-home/ # jar 一体化实例运行目录(测试库副本) ├── jar-home/ # jar 一体化实例运行目录(测试库副本)
├── .env.local # 截图 OSS 上传等本地配置(不入库)
├── package.json ├── package.json
└── tsconfig.json └── tsconfig.json
``` ```
@@ -67,13 +68,15 @@ doc-site/
脚本位于 `scripts/ts/`,用 `tsx` 直接运行 TypeScript,浏览器自动化基于 `playwright-core`(Chromium),HTTP 基于 `axios`。 脚本位于 `scripts/ts/`,用 `tsx` 直接运行 TypeScript,浏览器自动化基于 `playwright-core`(Chromium),HTTP 基于 `axios`。
```sh ```sh
npm run capture # 一键重新截图(整页 + 元素 + 卡片 + 编辑/设置面板 + 解析预览) npm run capture # 一键重新截图(整页 + 元素 + 卡片 + 编辑/设置面板 + 解析预览,慎用)
npm run capture:pages # 仅整页截图 npm run capture:pages # 仅整页截图
npm run capture:elements # 仅元素截图 npm run capture:elements # 仅元素截图
npm run make:bills # 生成虚构样例账单
npm run seed # 导入样例账单(演示数据) npm run seed # 导入样例账单(演示数据)
npm run seed:rules # 补充标签与规则 npm run seed:rules # 补充标签与规则
npm run refs:search # 检索资料到 refs/ npm run refs:search # 检索资料到 refs/
npm run refs:fetch # 抓取指定 URL 到 refs/ npm run refs:fetch # 抓取指定 URL 到 refs/
npm run oss:upload # 上传截图到 OSS(读取 .env.local)
npm run test:browser # 验证浏览器链路 npm run test:browser # 验证浏览器链路
npm run typecheck:scripts # 类型检查 npm run typecheck:scripts # 类型检查
``` ```
+28 -7
View File
@@ -4,14 +4,14 @@
## 客户端 ## 客户端
`DeepSeekClient` 基于 **OpenAI 官方 Java SDK**(`com.openai:openai-java`)调用,通过自定义 `baseUrl` 指向 DeepSeek 的 OpenAI 兼容接口: `DeepSeekClient` 直接调用 DeepSeek 的 **OpenAI 兼容 Chat Completions 接口**(`{baseUrl}/chat/completions`),为减小可执行 jar 体积,使用 **Spring `RestClient` + JDK `HttpClient`** 而非第三方 SDK:
- 使用 `OpenAIOkHttpClient`,按 `(baseUrl, apiKey, 超时)` 缓存客户端(上限 32,超出后清空重建)。 - 请求体包含 `model`、`messages`、`temperature = 0.2`、`response_format = {"type":"json_object"}`、`stream = false`。
- `temperature = 0.2`,`response_format = {"type":"json_object"}`,非流式,`maxRetries(0)`;文本请求超时 **120 秒**,视觉请求超时 **240 秒**(`chatWithImages` 单独放宽)。 - 文本请求超时 **120 秒**,视觉请求超时 **240 秒**(`chatWithImages` 单独放宽)。
- 取 `choices[0].message.content` 与 `usage`(输入/输出/总 token;缓存命中 token 取 `promptTokensDetails.cachedTokens`,DeepSeek 私有字段读不到时为 0)。 - 解析 `choices[0].message.content` 与 `usage`(输入/输出/总 token;缓存命中 token 取 DeepSeek 的缓存字段,读不到时为 0)。
- 对外保持 `chat(baseUrl, apiKey, model, systemPrompt, userPrompt)` 与 `DeepSeekResponse` 不变,各 `Ai*ServiceImpl` 无需改动。 - 对外保持 `chat(baseUrl, apiKey, model, systemPrompt, userPrompt)` 与 `DeepSeekResponse` 不变,各 `Ai*ServiceImpl` 无需改动。
- 另有 `chatWithImages(baseUrl, apiKey, model, systemPrompt, userText, imageDataUrls)`:将一张或多张图片以 base64 data URL 作为 `user` 消息的内容块发送(DeepSeek 视觉接口要求图片只能出现在 user 消息中),供图片记账使用。 - 另有 `chatWithImages(baseUrl, apiKey, model, systemPrompt, userText, imageDataUrls)`:将一张或多张图片以 base64 data URL 作为 `user` 消息的内容块发送(DeepSeek 视觉接口要求图片只能出现在 user 消息中),供图片记账使用。
- SDK 异常统一转 `BusinessException`;异常链含超时(`SocketTimeoutException` 等)时返回面向用户的友好提示。 - 调用异常统一转 `BusinessException`;异常链含超时(`SocketTimeoutException` 等)时返回面向用户的友好提示。
配置存储于 `t_ai_config`(每用户一条):`enabled`、`api_key`、`base_url`(默认 `https://api.deepseek.com`)、`model`(默认 `deepseek-chat`)、`vision_model`(图片记账视觉模型,默认 `deepseek-flash`)。API Key 对外脱敏,支持连通性测试。 配置存储于 `t_ai_config`(每用户一条):`enabled`、`api_key`、`base_url`(默认 `https://api.deepseek.com`)、`model`(默认 `deepseek-chat`)、`vision_model`(图片记账视觉模型,默认 `deepseek-flash`)。API Key 对外脱敏,支持连通性测试。
@@ -21,11 +21,12 @@
- **禁止**:`rawData`(账单原始 JSON)、金额、交易号、账户/卡号、姓名、地址、电话等。 - **禁止**:`rawData`(账单原始 JSON)、金额、交易号、账户/卡号、姓名、地址、电话等。
- **允许**:收支类型、交易渠道、备注(必要时截断/脱敏)、分类与标签的名称/ID、用户主动输入的自然语言文本。 - **允许**:收支类型、交易渠道、备注(必要时截断/脱敏)、分类与标签的名称/ID、用户主动输入的自然语言文本。
- **智能查数**:仅发送用户提问与可查询的**表结构说明**,不发送任何账单数据行;数据查询与隔离在本地由安全网关完成。
- **图片记账例外**:用户主动上传的账单/支付截图会**整张**发送给视觉模型(可能含金额、交易对方、订单号等),视为用户主动提供的输入;图片同时落盘于 `data/ai-images/`,仅本人可经 `GET /api/ai/image/{id}` 访问,禁止用于其他外发场景。 - **图片记账例外**:用户主动上传的账单/支付截图会**整张**发送给视觉模型(可能含金额、交易对方、订单号等),视为用户主动提供的输入;图片同时落盘于 `data/ai-images/`,仅本人可经 `GET /api/ai/image/{id}` 访问,禁止用于其他外发场景。
## 调用类型 ## 调用类型
`AiCallType` 定义了 11 类调用,全部记录用量: `AiCallType` 定义了 13 类调用,全部记录用量:
| 枚举值 | 含义 | | 枚举值 | 含义 |
|---|---| |---|---|
@@ -40,6 +41,8 @@
| `CHANNEL` | 自然语言生成交易渠道 | | `CHANNEL` | 自然语言生成交易渠道 |
| `RULE` | 由已打标签记录生成分类与标签规则 | | `RULE` | 由已打标签记录生成分类与标签规则 |
| `STATISTICS` | 自然语言统计查询解析 | | `STATISTICS` | 自然语言统计查询解析 |
| `RELATION` | 由关联账单生成关系名称与备注 |
| `SQL` | 自然语言转换为只读 SQL 查询(智能查数) |
## 分类/标签建议 ## 分类/标签建议
@@ -75,7 +78,7 @@
**实时进度**:`AiImageRecordStreamService` 基于 SSE 将上述步骤的进度文本推送前端——`POST /api/ai/record/image/stream` 返回 `SseEmitter`,识别在后台线程执行,过程中逐条推送 `progress` 事件,结束推送 `done`(完整结果 JSON)或 `failed`(失败原因)。 **实时进度**:`AiImageRecordStreamService` 基于 SSE 将上述步骤的进度文本推送前端——`POST /api/ai/record/image/stream` 返回 `SseEmitter`,识别在后台线程执行,过程中逐条推送 `progress` 事件,结束推送 `done`(完整结果 JSON)或 `failed`(失败原因)。
**图片访问**:`GET /api/ai/image/{id}` 返回原图,`?thumbnail=true` 返回最长边 1280 的 JPEG 缩略图;仅图片所属用户可访问。交易列表的「来源图片」默认加载缩略图,用户可按需切换原图。 **图片访问**:`GET /api/ai/image/{id}` 返回原图,`?thumbnail=true` 返回最长边 1280 的 JPEG 缩略图;仅图片所属用户可访问。收支记录「账单详情」弹窗中的来源图片默认加载缩略图,用户可按需切换原图。
## 规则生成 ## 规则生成
@@ -89,6 +92,24 @@
`AiStatisticsServiceImpl` 在提示词中注入分类、标签与当前日期,将自然语言解析为统计面板参数:时间范围(预设→绝对时间)、收支类型、分类、标签、交易渠道、金额区间、备注、统计口径(`statsScope`)与趋势粒度(`dimension`)。 `AiStatisticsServiceImpl` 在提示词中注入分类、标签与当前日期,将自然语言解析为统计面板参数:时间范围(预设→绝对时间)、收支类型、分类、标签、交易渠道、金额区间、备注、统计口径(`statsScope`)与趋势粒度(`dimension`)。
## 生成账单关系名称
`AiRelationServiceImpl` 依据用户新建关系组时勾选的**关联账单**生成名称与备注,对应 `POST /api/ai/relation`。
- 请求体为 `AiRelationRequest`(`type` + `members`);每个成员仅携带白名单字段——**收支类型、交易渠道、分类名称与备注**(`AiRelationMemberRequest` 明确不含金额、交易号与 `rawData`)。
- 用户提示词由服务端集中拼接(关系类型中文名 + 成员字段,备注截断 30 字,最多 20 条);系统提示词模板 `ai-relation-system` 要求输出 4–12 字名称、≤40 字备注,并说明借贷场景(借出/归还、分次归还)的判定,只输出 JSON。
- 解析后名称截断 ≤50 且必须非空、备注 ≤200;经 `AiUsageService` 记录 `AiCallType.RELATION` 用量。前端在[账单关系](/guide/relations)编辑弹窗中用它覆盖名称(备注非空时覆盖备注)。
## 智能查数(Text-to-SQL)
`AiSqlQueryService` 将自然语言交给大模型生成**只读 SQL** 并执行,配套独立的提问历史,对应 `POST /api/ai/sql`。
- 系统提示词模板 `ai-sql-system`:按当前数据源方言(SQLite / MySQL)注入日期函数说明与**动态表结构**——`TextToSqlSchemaProvider` 从数据库元数据读取列(字段增删改自动同步),并剔除 `raw_data`、`external_no` 等敏感列;要求模型只输出 `{sql, explanation, chartType}`,且**不要自带 `user_id`**(由网关统一注入)。
- **安全网关** `cn.rosercode.ledgerly.util.sql.TextToSqlGuard` 是唯一执行通道:仅允许单条 `SELECT`(拒绝写操作/DDL/多语句/`SELECT INTO`/`FOR UPDATE`/危险函数);表白名单由 `TextToSqlTables` 静态登记(`t_transaction`、`t_category`、`t_tag`、`t_channel`、`t_transaction_group`、`t_rule` 及无 `user_id` 的关联表,新增可查表必须显式加入);对每张表强制注入 `user_id` 隔离条件(主表加 `WHERE`、JOIN 表加 `ON`、关联表用父表归属子查询);强制行数上限 `MAX_ROWS = 1000`。
- 结果 VO `AiSqlQueryResultVO` 含 `sql`、`explanation`、`chartType`(`SCALAR`/`TABLE`/`BAR`/`PIE`)、`columns`、`rows`、`rowCount`、`truncated`;列类型映射为 `NUMBER`/`STRING`/`DATE`/`DATETIME`/`BOOLEAN`。
- **提问历史**:`AiSqlHistoryService` 在每次成功后写入 `t_ai_sql_history`(按用户),提供 `GET /api/ai/sql/history`(limit 上限 200)与 `DELETE /api/ai/sql/history`。
- 用量记录 `AiCallType.SQL`。
## 用量记录 ## 用量记录
- 每次调用写入 `t_ai_usage`:调用类型、模型、分析条数、输入/输出/总/缓存 token、是否成功、错误信息、耗时。 - 每次调用写入 `t_ai_usage`:调用类型、模型、分析条数、输入/输出/总/缓存 token、是否成功、错误信息、耗时。
+22 -7
View File
@@ -28,21 +28,22 @@
| 控制器 | 路径前缀 | 说明 | | 控制器 | 路径前缀 | 说明 |
|---|---|---| |---|---|---|
| `UserController` | `/api/user` | 登录、注册、用户信息、注册开关 | | `UserController` | `/api/user` | 登录、注册、用户信息、注册开关、修改密码 |
| `TransactionController` | `/api/transaction` | 收支记录增删改查、批量创建与操作、导出、交易渠道选项 | | `TransactionController` | `/api/transaction` | 收支记录增删改查、批量创建与操作、备注隐藏、导出、交易渠道选项 |
| `TransactionGroupController` | `/api/transaction/group` | 账单关系组的查询、创建、更新、删除与名称历史 |
| `BillImportController` | `/api/transaction/import` | 账单解析与确认导入 | | `BillImportController` | `/api/transaction/import` | 账单解析与确认导入 |
| `ChannelController` | `/api/channel` | 用户自定义交易渠道的增删改查 | | `ChannelController` | `/api/channel` | 用户自定义交易渠道的增删改查 |
| `CategoryController` | `/api/category` | 分类管理 | | `CategoryController` | `/api/category` | 分类管理 |
| `TagController` | `/api/tag` | 标签管理 | | `TagController` | `/api/tag` | 标签管理 |
| `RuleController` | `/api/rule` | 自定义规则(列表、分页查询、增删改、启停) | | `RuleController` | `/api/rule` | 自定义规则(列表、分页查询、增删改、启停、批量删除) |
| `StatisticsController` | `/api/statistics` | 统计汇总、分类、趋势、分组 | | `StatisticsController` | `/api/statistics` | 统计汇总、分类、趋势、分组 |
| `AiController` | `/api/ai` | AI 建议、查询、文字/图片记账、渠道生成、规则生成、统计搜索、用量 | | `AiController` | `/api/ai` | AI 建议、查询、智能查数、文字/图片记账、渠道/关系/规则生成、统计搜索、用量 |
| `NotificationController` | `/api/notification` | 通知配置、渠道、发送日志 | | `NotificationController` | `/api/notification` | 通知配置、渠道、发送日志 |
| `EmailBillController` | `/api/email-bill` | 邮箱配置、发件人规则、待处理账单 | | `EmailBillController` | `/api/email-bill` | 邮箱配置、发件人规则、待处理账单 |
| `LogController` | `/api/log` | 访问日志、登录日志、运行日志与 SSE | | `LogController` | `/api/log` | 访问日志、登录日志、运行日志与 SSE |
| `DatabaseController` | `/api/database` | 数据库状态、测试与配置 | | `DatabaseController` | `/api/database` | 数据库状态、测试与配置(含 SSE 进度) |
| `AppConfigController` | `/api/app` | 应用配置(通知链接、日志保留) | | `AppConfigController` | `/api/app` | 应用配置(通知链接、日志保留) |
| `UserSettingController` | `/api/setting` | 用户偏好(账单来源统计默认值、不计入统计展示样式) | | `UserSettingController` | `/api/setting` | 用户偏好(账单来源统计默认值、展示样式、隐藏备注显示、分页、导航) |
## 关键接口 ## 关键接口
@@ -50,16 +51,30 @@
|---|---|---| |---|---|---|
| GET | `/api/transaction/channels` | 获取交易渠道选项(内置 + 当前用户自定义,含 `code`/`name`/`builtin`) | | GET | `/api/transaction/channels` | 获取交易渠道选项(内置 + 当前用户自定义,含 `code`/`name`/`builtin`) |
| POST | `/api/transaction/batch` | 批量创建交易记录(逐条可带 `imageId`/`rawData`),导入时按「类型+金额+时间」跳过重复 | | POST | `/api/transaction/batch` | 批量创建交易记录(逐条可带 `imageId`/`rawData`),导入时按「类型+金额+时间」跳过重复 |
| PUT | `/api/transaction/{id}/remark-hidden` | 隐藏 / 取消隐藏某条记录的备注 |
| GET / POST / PUT / DELETE | `/api/channel`、`/api/channel/{id}` | 自定义渠道列表(含使用账单数)、新建、改名、删除 | | GET / POST / PUT / DELETE | `/api/channel`、`/api/channel/{id}` | 自定义渠道列表(含使用账单数)、新建、改名、删除 |
| GET | `/api/transaction/group/by-transaction/{id}` | 查询某笔账单所属的全部关系组(含成员快照) |
| GET | `/api/transaction/group/names` | 关系名称历史(可按 `type` 过滤,去重、倒序) |
| POST / PUT / DELETE | `/api/transaction/group`、`/api/transaction/group/{id}` | 创建 / 更新 / 删除账单关系组(删除仅解除关联,不删除账单) |
| PUT | `/api/user/password` | 修改当前登录用户的密码(校验原密码,新旧需不同) |
| POST | `/api/ai/record/image` | 上传一至多张账单/支付截图(字段 `files`),调用视觉模型识别为多条交易记录 | | POST | `/api/ai/record/image` | 上传一至多张账单/支付截图(字段 `files`),调用视觉模型识别为多条交易记录 |
| POST | `/api/ai/record/image/stream` | 图片记账(SSE):实时推送识别进度,结束返回完整结果 | | POST | `/api/ai/record/image/stream` | 图片记账(SSE):实时推送识别进度,结束返回完整结果 |
| GET | `/api/ai/image/{id}` | 查看本人上传的图片记账图片,`?thumbnail=true` 返回缩略图 | | GET | `/api/ai/image/{id}` | 查看本人上传的图片记账图片,`?thumbnail=true` 返回缩略图 |
| POST | `/api/ai/channel` | 由自然语言描述生成交易渠道名称 | | POST | `/api/ai/channel` | 由自然语言描述生成交易渠道名称 |
| POST | `/api/ai/relation` | 由关联账单生成关系名称与备注 |
| POST | `/api/ai/sql` | 智能查数:自然语言生成只读 SQL 并执行 |
| GET / DELETE | `/api/ai/sql/history` | 查询 / 清空智能查数提问历史(`limit` 上限 200) |
| GET | `/api/rule/page` | 分页查询规则(`keyword` / `enabled` / `type`) | | GET | `/api/rule/page` | 分页查询规则(`keyword` / `enabled` / `type`) |
| DELETE | `/api/rule/batch` | 批量删除规则(body `{ ids }`,返回实际删除数) |
| POST | `/api/ai/rule/suggest` | 依据所选已打标签记录生成规则建议(含 `duplicate` 标记) | | POST | `/api/ai/rule/suggest` | 依据所选已打标签记录生成规则建议(含 `duplicate` 标记) |
| POST | `/api/ai/rule/apply` | 批量创建规则(自动去重、按需新建分类/标签) | | POST | `/api/ai/rule/apply` | 批量创建规则(自动去重、按需新建分类/标签) |
| POST | `/api/ai/statistics` | 自然语言解析为统计面板参数 | | POST | `/api/ai/statistics` | 自然语言解析为统计面板参数 |
| GET / PUT | `/api/app/bill-source-include-in-stats` | 查询 / 更新按账单来源的「计入统计」默认值 | | GET / PUT | `/api/setting/excluded-display-style` | 查询 / 更新「不计入统计」记录展示样式 |
| GET / PUT | `/api/setting/show-hidden-data` | 查询 / 更新是否显示已隐藏备注的记录 |
| GET / PUT | `/api/setting/pagination-prefs` | 查询 / 更新表格分页候选列表与默认每页条数 |
| GET / PUT | `/api/setting/nav-menu` | 查询 / 更新左侧导航顺序与显隐 |
| GET / PUT | `/api/setting/bill-source-include-in-stats` | 查询 / 更新按账单来源的「计入统计」默认值 |
| POST | `/api/database/config/stream` | 应用数据库配置并通过 SSE 推送进度(另有 `/api/database/test/stream`) |
## 统一响应结构 ## 统一响应结构
+3 -3
View File
@@ -12,7 +12,7 @@
| 认证 | JWT(HMAC) | | 认证 | JWT(HMAC) |
| 对象映射 | MapStruct 1.6.3 | | 对象映射 | MapStruct 1.6.3 |
| 账单解析 | EasyExcel(xlsx)/ Apache POI(xls)+ commons-csv + PDFBox + Zip4j | | 账单解析 | EasyExcel(xlsx)/ Apache POI(xls)+ commons-csv + PDFBox + Zip4j |
| AI | DeepSeek(OpenAI Java SDK) | | AI | DeepSeek(OpenAI 兼容接口,Spring RestClient) |
| 通知 | Jakarta Mail(SMTP / IMAP)+ Server 酱 | | 通知 | Jakarta Mail(SMTP / IMAP)+ Server 酱 |
| API 文档 | Knife4j(OpenAPI 3) | | API 文档 | Knife4j(OpenAPI 3) |
@@ -91,8 +91,8 @@ src/
1. **运行时动态数据源**:启动无需数据库,配置后热切换,见[动态数据源](/dev/database)。 1. **运行时动态数据源**:启动无需数据库,配置后热切换,见[动态数据源](/dev/database)。
2. **账单两阶段导入**:解析预览 → 确认导入,按交易号去重,见[账单解析](/dev/bill-parsing)。 2. **账单两阶段导入**:解析预览 → 确认导入,按交易号去重,见[账单解析](/dev/bill-parsing)。
3. **规则引擎**:分类取最高优先级、标签取并集,见[规则引擎](/dev/rule-engine)。 3. **规则引擎**:分类取最高优先级、标签取并集,见[规则引擎](/dev/rule-engine)。
4. **体系化 AI 调用**:11 类调用类型并记录 token 用量,见 [AI 集成](/dev/ai)。 4. **体系化 AI 调用**:13 类调用类型并记录 token 用量,见 [AI 集成](/dev/ai)。
5. **SSE 实时能力**:运行日志、邮箱检查进度与图片记账识别进度实时推送。 5. **SSE 实时能力**:运行日志、邮箱检查进度、图片记账识别进度与数据库配置执行进度实时推送。
6. **用户偏好缓存**:`t_user_preference` 的读取走 Spring Cache 进程内缓存(`ConcurrentMapCacheManager`),写入时按用户失效,切换数据源时整体清空。 6. **用户偏好缓存**:`t_user_preference` 的读取走 Spring Cache 进程内缓存(`ConcurrentMapCacheManager`),写入时按用户失效,切换数据源时整体清空。
## 相关文档 ## 相关文档
+2 -2
View File
@@ -5,8 +5,8 @@
## 样例与实现位置 ## 样例与实现位置
- 编码:GBK(解析器兼容 UTF-8 / UTF-8 BOM) - 编码:GBK(解析器兼容 UTF-8 / UTF-8 BOM)
- 解析器:`src/main/java/cn/rosercode/finance/importer/AlipayBillParser.java` - 解析器:`src/main/java/cn/rosercode/ledgerly/importer/AlipayBillParser.java`
- 落库服务:`src/main/java/cn/rosercode/finance/service/impl/BillImportServiceImpl.java` - 落库服务:`src/main/java/cn/rosercode/ledgerly/service/impl/BillImportServiceImpl.java`
## 文件结构 ## 文件结构
+1 -1
View File
@@ -27,7 +27,7 @@
- **时间兼容**:微信账单兼容 Excel 日期序列号与文本时间;支付宝在付款时间缺失时回退交易创建时间;淘宝订单同样兼容文本时间与 Excel 日期序列号。 - **时间兼容**:微信账单兼容 Excel 日期序列号与文本时间;支付宝在付款时间缺失时回退交易创建时间;淘宝订单同样兼容文本时间与 Excel 日期序列号。
- **订单聚合**:淘宝订单数据中多商品订单的订单级列(订单号、提交时间、状态、店铺、实付金额、运费)以合并单元格呈现,仅首个商品行有值;解析器按订单号分组聚合,每个订单输出一条记录,金额优先取订单级实付金额,为空时回退「Σ(商品金额×数量)+运费」。 - **订单聚合**:淘宝订单数据中多商品订单的订单级列(订单号、提交时间、状态、店铺、实付金额、运费)以合并单元格呈现,仅首个商品行有值;解析器按订单号分组聚合,每个订单输出一条记录,金额优先取订单级实付金额,为空时回退「Σ(商品金额×数量)+运费」。
- **不计收支处理**:京东流水的「不计收支」行(白条还款、小金库转入、全额退款等)标记为可导入但**不计入统计且默认不勾选**,类型按「交易说明含『退款』→ INCOME,否则 EXPENSE」推断。 - **不计收支处理**:京东流水的「不计收支」行(白条还款、小金库转入、全额退款等)标记为可导入但**不计入统计且默认不勾选**,类型按「交易说明含『退款』→ INCOME,否则 EXPENSE」推断。
- **计入统计默认值**:解析完成后按**账单来源**应用「计入统计」默认值(配置于系统设置,见 [动态数据源](/dev/database) 同级的应用配置)。采用「解析器判定 && 来源默认」语义——来源默认不计入时整批置为不计入;来源默认计入时保留解析器对「不计收支/中性」行的排除。确认导入时若请求未显式提供该字段,同样以来源默认兜底。 - **计入统计默认值**:解析完成后按**账单来源**应用「计入统计」默认值(配置于设置页,见 [动态数据源](/dev/database) 同级的应用配置)。采用「解析器判定 && 来源默认」语义——来源默认不计入时整批置为不计入;来源默认计入时保留解析器对「不计收支/中性」行的排除。确认导入时若请求未显式提供该字段,同样以来源默认兜底。
## 压缩包解密 ## 压缩包解密
+96 -40
View File
@@ -1,9 +1,12 @@
# 构建与部署 # 构建与部署
本章介绍「汇账」的构建方式、分发形态与生产部署流程。
## 前置条件 ## 前置条件
- **JDK 21**(Temurin 21 LTS) - **JDK 21**(Temurin 21 LTS,含 `jpackage`)
- **Node.js**(构建前端时需要) - **Node.js**(构建前端时需要)
- 依赖仓库:本地 Nexus(Maven)与 verdaccio(npm);构建时经 `-I nexus-init.gradle` 注入 Maven 私服地址
## 开发模式 ## 开发模式
@@ -12,85 +15,138 @@
```bash ```bash
# 后端(默认 8080) # 后端(默认 8080)
cd ledgerly cd ledgerly
set JAVA_HOME=D:\venv\jdk-21 ./gradlew bootRun # Windows: gradlew.bat bootRun
gradlew.bat bootRun
# 前端(默认 5173,接口代理到 8080) # 前端(默认 5173,接口代理到 8080)
cd ledgerly/frontend cd ledgerly/frontend
npm run dev npm run dev
``` ```
- 后端地址:http://localhost:8080 - 后端地址:`http://localhost:8080`,接口文档 `http://localhost:8080/doc.html`
- 后端 API 文档:http://localhost:8080/doc.html - 前端地址:`http://localhost:5173`,`/api` 经 Vite 代理到后端 8080
- 前端地址:http://localhost:5173,接口经 Vite 代理到后端 8080
## 打包可执行 jar(前后端一体化) ## 构建可执行 jar(前后端一体化)
`bootJar` 会先执行前端 `npm run build`,再把 `frontend/dist` 复制到 jar 内的 `BOOT-INF/classes/static/`,单文件同时提供页面与接口。 `bootJar` 会先执行前端 `npm run build`,再把 `frontend/dist` 复制到 jar 内的 `BOOT-INF/classes/static/`,单文件同时提供页面与接口。
```bash ```bash
gradle bootJar # 普通 jar(无 GUI,不含 JavaFX)
# 或使用宿主机脚本 ./gradlew -I nexus-init.gradle bootJar
# 带 GUI 启动器的 jar
./gradlew -I nexus-init.gradle bootGuiJar
# 或使用宿主机脚本(一次产出两者)
./package.sh ./package.sh
``` ```
输出:`build/libs/ledgerly-0.0.1-SNAPSHOT.jar` 产物文件名携带目标 JDK 版本:
| 任务 | 产物 | 说明 |
|---|---|---|
| `bootJar` | `build/libs/ledgerly-0.1-jdk21.jar` | 普通服务 jar |
| `bootGuiJar` | `build/libs/ledgerly-0.1-jdk21-gui.jar` | 入口为桌面启动器 |
启动(默认 8080,可另指定端口): 启动(默认 8080,可另指定端口):
```bash ```bash
java -jar build/libs/ledgerly-0.0.1-SNAPSHOT.jar --server.port=18081 java -jar build/libs/ledgerly-0.1-jdk21.jar --server.port=18081
``` ```
访问对应端口即可;刷新 `/dashboard` 等子路由由后端 `SpaErrorController` 回退到 `index.html`。 构建任务链(`build.gradle`):`npmInstall` → `frontendBuild` → `bootJar`。
::: tip 提示 ::: tip 提示
jar 内置的前端为生产构建,访问时与后端同源,无需额外的反向代理。 jar 内置的前端为生产构建,访问时与后端同源,无需额外的反向代理。
::: :::
构建任务链(`build.gradle`): 为减小可执行 jar 体积,普通 jar 与 GUI jar 均做了裁剪:AI 调用改用 Spring `RestClient` 直连 OpenAI 兼容接口(不再打包 OpenAI Java SDK);接口文档仅保留 `/v3/api-docs`(JSON),Knife4j 的 `doc.html` UI 依赖为 `developmentOnly`,不进生产 jar;普通 jar 通过 `exclude` 排除桌面启动器包,保持无 JavaFX 依赖。
- `npmInstall`:安装前端依赖(`node_modules` 存在且 `package.json` 未变化时跳过) 运行时首次配置或切换数据库时,「测试连接」与「保存并应用」会通过 SSE **实时推送执行进度**(连接测试、建表、结构迁移、数据源切换等)。
- `frontendBuild`:执行 `npm run build`
- `bootJar`:依赖 `frontendBuild`,并将前端产物打入静态资源目录
## Docker 部署 ## 构建免安装应用(jpackage app-image)
`run/docker-compose.yml` 定义前后端两个服务,纯复用本地镜像、不构建新镜像: `jpackageAppImage` 聚合任务一次生成两个 app-image,自带精简运行时,双击即用;**仅能为当前操作系统生成**(Windows 上生成 Windows 产物,Linux 上生成 Linux 产物)。
| 服务 | 镜像 | 端口 | 说明 | ```bash
./gradlew -I nexus-init.gradle jpackageAppImage
# 也可单独执行 jpackageServerAppImage / jpackageGuiAppImage
```
| 任务 | 应用名 | 源 jar | 入口 |
|---|---|---|---| |---|---|---|---|
| backend | `eclipse-temurin:21-jre` | 18080→8080 | 挂载 `run/backend/*.jar`,持久化 `config/`、`data/`、`logs/` | | `jpackageServerAppImage` | `LedgerlyServer` | `bootJar` | `bin/LedgerlyServer`(Windows 为 `LedgerlyServer.exe`,附带控制台) |
| front | `nginx:alpine` | 15180→80 | 挂载 `run/front/dist` 与 Nginx 配置 | | `jpackageGuiAppImage` | `Ledgerly` | `bootGuiJar` | `bin/Ledgerly`(Windows 为 `Ledgerly.exe`) |
环境变量 `DB_URL` / `DB_USERNAME` / `DB_PASSWORD` 仅作为首次启动的默认配置来源,也可在界面中完成数据库配置。 产物位于 `build/jpackage/LedgerlyServer/` 与 `build/jpackage/Ledgerly/`。
图标不随源码分发:打包时 `downloadAppIcon` 从 `src/main/resources/launcher/icon.url` 下载源图,再由 `tools/IconGen.java` 生成平台图标(`ico` / `icns` / `png`);可用 `-PsourceIcon=<png>` 指定本地源图。
## 桌面 GUI 启动器 ## 桌面 GUI 启动器
`launcher` 包提供系统托盘 + 控制窗口的桌面启动器(启动/停止服务、打开浏览器、查看日志),通过 `bootGuiJar` 打包: `cn.rosercode.ledgerly.launcher` 提供系统托盘 + 控制窗口的桌面启动器(进程内启停服务、设置端口、打开浏览器、实时查看日志、单实例运行),仅打入带 GUI 的 jar,普通 jar 不含 JavaFX 依赖。
```bash
gradle bootGuiJar
```
- 入口类:`cn.rosercode.finance.launcher.LauncherMain`
- 依赖 JavaFX 与 dorkbox SystemTray,仅加入带 GUI 的 jar(`guiRuntime` 配置),普通 jar 不含这些依赖
- 平台默认 Windows(可通过 `-PjavafxPlatform=linux` 本地验证)
- JavaFX 与 JNA 原生库需 `requiresUnpack` 从嵌套 jar 解压
> 注意:该启动器为规划中的能力,具体实现以当前源码为准。
## 分发形态 ## 分发形态
| 形态 | 说明 | | 形态 | 构建方式 | 说明 |
|---|---| |---|---|---|
| jar | `bootJar`,需 JDK 21 运行 | | 普通 jar | `bootJar` | 需 JDK 21 运行,跨平台 |
| jar(带 GUI) | `bootGuiJar`,内含桌面启动器 | | 带 GUI 启动器 jar | `bootGuiJar` | 内含桌面启动器,跨平台(打包含 JavaFX 全平台原生库) |
| 可执行文件(控制台 / GUI) | 规划中,计划采用 `jpackage`(JDK 自带,各平台分别构建) | | Windows 免安装包 | `jpackageAppImage` | 在 Windows 上构建,zip 分发 |
| Docker 镜像 | 通过 `run/docker-compose.yml` 编排 | | Linux 免安装包 | `jpackageAppImage` | 在 Linux 上构建,tar.gz 分发 |
## 发布产物与下载地址
发布产物上传至阿里云 OSS(桶 `adccrac`,对象键前缀 `ledgerly/release/<版本>/`),通过 10 年有效的长期签名 URL 对外提供下载;每个文件同时提供 `.sha256` 校验文件与汇总清单 `SHA256SUMS`。
当前工程版本为 `0.1`;最新下载地址见使用文档的[下载与安装](/guide/download)(该页链接指向已上传的发布产物)。
上传方式:
- Linux 产物(jar 与 Linux app-image 包):工作区根 `oss-release-upload.py`,需在带 `oss2` 的虚拟环境执行(如 `20260807_131528/oss-venv`),上传后输出长期签名 URL。
- Windows 产物(Windows app-image 包):在 Windows 主机上用 Node 脚本(`ali-oss` + STS)直接上传,不回传本地。
## 生产部署(云服务器)
服务部署在云服务器 `101.35.20.223` 的 `/opt/ledgerly` 目录,由 Docker Compose 编排:
| 容器 | 镜像 | 端口 | 说明 |
|---|---|---|---|
| `ledgerly` | `eclipse-temurin:21-jre` | `127.0.0.1:15180 → 8080` | 挂载 `app/ledgerly-0.1.jar`、`config/`、`data/`,JVM `-Xmx512m` |
| `ledgerly-docs` | `nginx:1.27` | `127.0.0.1:15181 → 80` | 挂载 `docs-dist/` 与 `docs/nginx.conf` |
云上 `nginx` 容器反代这两个回环端口并终止 HTTPS,证书由 `certbot` 容器负责续期:
- 服务:[https://finance.rosercode.cn](https://finance.rosercode.cn)
- 文档:[https://docs.finance.rosercode.cn](https://docs.finance.rosercode.cn)
### 发布流程
服务(构建 → 复制 → 重启):
```bash
# 1) 本地构建 jar(见上)
# 2) 复制到云端部署目录,目标文件名须与 compose 挂载一致
scp -i ~/.ssh/id_ed25519_www.rosercode.cn \
build/libs/ledgerly-0.1-jdk21.jar \
root@101.35.20.223:/opt/ledgerly/app/ledgerly-0.1.jar
# 3) 重启容器
ssh -i ~/.ssh/id_ed25519_www.rosercode.cn root@101.35.20.223 \
'cd /opt/ledgerly && docker compose restart'
```
文档站(构建 → 覆盖 `docs-dist`,目录挂载即时生效):
```bash
cd doc-site && npm run docs:build
scp -i ~/.ssh/id_ed25519_www.rosercode.cn -r .vitepress/dist/* \
root@101.35.20.223:/opt/ledgerly/docs-dist/
```
> 云服务器不能直连 DockerHub,新镜像需本地 `docker save | gzip` → `scp` → 云端 `docker load` 导入。
## 相关文档 ## 相关文档
- [项目结构](/dev/project-structure) - [项目结构](/dev/project-structure)
- [开发环境与规范](/dev/development) - [开发环境与规范](/dev/development)
- [下载与安装(使用文档)](/guide/download)
- [快速开始(使用文档)](/guide/getting-started) - [快速开始(使用文档)](/guide/getting-started)
+15 -2
View File
@@ -1,6 +1,6 @@
# 数据模型 # 数据模型
系统共 **21 张表**,覆盖用户、记账、渠道、标签、规则、AI、通知、邮箱账单与日志审计。所有表主键为自增 `id`,表间关系均为**逻辑外键**(通过 `user_id`、`category_id` 等字段在应用层维护,未声明物理外键)。 系统共 **24 张表**,覆盖用户、记账、账单关系、渠道、标签、规则、AI、通知、邮箱账单与日志审计。所有表主键为自增 `id`,表间关系均为**逻辑外键**(通过 `user_id`、`category_id` 等字段在应用层维护,未声明物理外键)。
## 表清单 ## 表清单
@@ -13,12 +13,15 @@
| 记账 | `t_channel` | 用户自定义交易渠道(内置渠道由枚举维护) | | 记账 | `t_channel` | 用户自定义交易渠道(内置渠道由枚举维护) |
| 标签 | `t_tag` | 标签 | | 标签 | `t_tag` | 标签 |
| 标签 | `t_transaction_tag` | 交易-标签关联(多对多) | | 标签 | `t_transaction_tag` | 交易-标签关联(多对多) |
| 账单关系 | `t_transaction_group` | 账单关系组(父子/同级) |
| 账单关系 | `t_transaction_group_item` | 关系组成员(角色与顺序) |
| 规则 | `t_rule` | 自定义归类规则 | | 规则 | `t_rule` | 自定义归类规则 |
| 规则 | `t_rule_tag` | 规则-标签关联(多对多) | | 规则 | `t_rule_tag` | 规则-标签关联(多对多) |
| AI | `t_ai_config` | 每用户一条的 AI 配置(含视觉模型) | | AI | `t_ai_config` | 每用户一条的 AI 配置(含视觉模型) |
| AI | `t_ai_usage` | AI 调用用量记录 | | AI | `t_ai_usage` | AI 调用用量记录 |
| AI | `t_ai_image` | 图片记账上传的截图及其解析摘要 | | AI | `t_ai_image` | 图片记账上传的截图及其解析摘要 |
| AI | `t_ai_image_transaction` | 图片与解析生成交易的关联 | | AI | `t_ai_image_transaction` | 图片与解析生成交易的关联 |
| AI | `t_ai_sql_history` | 智能查数提问历史(每用户多条) |
| 通知 | `t_notification_config` | 每用户一条的提醒配置 | | 通知 | `t_notification_config` | 每用户一条的提醒配置 |
| 通知 | `t_notification_channel` | 通知渠道 | | 通知 | `t_notification_channel` | 通知渠道 |
| 通知 | `t_notification_log` | 通知发送日志 | | 通知 | `t_notification_log` | 通知发送日志 |
@@ -38,6 +41,7 @@
| t_user | 1 : N | t_transaction | user_id | | t_user | 1 : N | t_transaction | user_id |
| t_user | 1 : N | t_tag | user_id | | t_user | 1 : N | t_tag | user_id |
| t_user | 1 : N | t_channel | user_id | | t_user | 1 : N | t_channel | user_id |
| t_user | 1 : N | t_transaction_group | user_id |
| t_user | 1 : N | t_rule | user_id | | t_user | 1 : N | t_rule | user_id |
| t_user | 1 : N | t_ai_image | 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_notification_channel | user_id |
@@ -64,6 +68,8 @@
| t_rule | N : M | t_tag | 中间表 t_rule_tag(联合唯一) | | t_rule | N : M | t_tag | 中间表 t_rule_tag(联合唯一) |
| t_ai_image | 1 : N | t_ai_image_transaction | image_id | | t_ai_image | 1 : N | t_ai_image_transaction | image_id |
| t_transaction | 1 : N | t_ai_image_transaction | transaction_id | | t_transaction | 1 : N | t_ai_image_transaction | transaction_id |
| t_transaction_group | 1 : N | t_transaction_group_item | group_id(联合唯一 group_id + transaction_id) |
| t_transaction | 1 : N | t_transaction_group_item | transaction_id |
> 交易记录通过 `channel` 编码关联渠道:内置渠道编码取自 `ChannelType` 枚举,自定义渠道编码取自 `t_channel.code`(用户内唯一),因此渠道改名不影响历史记录。 > 交易记录通过 `channel` 编码关联渠道:内置渠道编码取自 `ChannelType` 枚举,自定义渠道编码取自 `t_channel.code`(用户内唯一),因此渠道改名不影响历史记录。
@@ -106,6 +112,12 @@
`name`、`enabled`、`sort_order`(优先级)、`type`、`keyword`、`min_amount`、`max_amount`、`category_id`。 `name`、`enabled`、`sort_order`(优先级)、`type`、`keyword`、`min_amount`、`max_amount`、`category_id`。
### t_transaction_group / t_transaction_group_item(账单关系)
- `t_transaction_group`:`user_id`、`type`(`PARENT_CHILD` 父子 / `SIBLING` 同级)、`name`、`remark`;索引 `user_id`。
- `t_transaction_group_item`:`group_id`、`transaction_id`、`role`(`PARENT` / `CHILD` / `MEMBER`)、`sort_order`(同级链路的顺序);唯一索引 `(group_id, transaction_id)` 防止同一账单在同一组内重复,另有 `transaction_id` 索引。
- 约束:一个关系组至少两条成员、上限 50 条;父子关系必须且只能有一条 `PARENT`;一笔账单不能同时加入两个**同类型**的关系组。删除账单时会自动清理其成员记录,成员不足两条或父子组缺父时整组删除(仅解除关联,不删除账单)。详见[账单关系(使用说明)](/guide/relations)。
### t_channel(自定义交易渠道) ### t_channel(自定义交易渠道)
| 字段 | 说明 | | 字段 | 说明 |
@@ -122,12 +134,13 @@
- `t_ai_config`:每用户一条,含 `enabled`、`api_key`、`base_url`、`model` 与 **`vision_model`**(图片记账使用的视觉模型,默认 `deepseek-flash`)。 - `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`:图片记账上传的截图,一次上传可产生多条(最多 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)`;每笔记录按模型标注的来源图分别建立关联。 - `t_ai_image_transaction`:图片与交易记录的关联,含 `image_id`、`transaction_id`,唯一索引 `uk_ai_image_transaction (image_id, transaction_id)`;每笔记录按模型标注的来源图分别建立关联。
- `t_ai_sql_history`:智能查数(自然语言转 SQL)的提问历史,含 `user_id`、`query_text`(截断 500 字符)、`created_at`;索引 `(user_id, created_at)`。
> **去重键**:图片记账与批量导入使用「收支类型 + 金额 + 交易时间(到秒)」作为业务去重键(`TransactionDedupUtils`),用于标记疑似重复并在导入时跳过,弥补这类记录没有外部交易号的不足。 > **去重键**:图片记账与批量导入使用「收支类型 + 金额 + 交易时间(到秒)」作为业务去重键(`TransactionDedupUtils`),用于标记疑似重复并在导入时跳过,弥补这类记录没有外部交易号的不足。
### t_user_preference ### t_user_preference
按用户存储的键值偏好:`pref_key` 为偏好键(如 `billSourceIncludeInStats`、`excludedDisplayStyle`),`pref_value` 为标量字符串或 JSON。唯一索引 `uk_user_pref (user_id, pref_key)` 保证每个用户每个偏好仅一条。未配置时由应用回退系统默认值。 按用户存储的键值偏好:`pref_key` 为偏好键,`pref_value` 为标量字符串或 JSON。已使用的偏好键包括:`billSourceIncludeInStats`(各来源计入统计默认值)、`excludedDisplayStyle`(不计入记录展示样式)、`showHiddenData`(是否显示已隐藏备注的记录)、`paginationPrefs`(表格分页候选与默认值)、`navMenu`(左侧导航顺序与显隐)。唯一索引 `uk_user_pref (user_id, pref_key)` 保证每个用户每个偏好仅一条。未配置时由应用回退系统默认值。
### t_email_bill_config ### t_email_bill_config
+1 -1
View File
@@ -45,7 +45,7 @@ MySQL 的 JDBC URL 会自动附带 UTF-8、关闭 SSL、`Asia/Shanghai` 时区
## 切换行为 ## 切换行为
登录后可在「系统设置 → 数据库配置」随时切换数据库。切换后原数据库的数据保留,**不做自动迁移**。 登录后可在「设置 → 数据库配置」随时切换数据库;测试与保存时通过 SSE 实时推送执行进度。切换后原数据库的数据保留,**不做自动迁移**。
切换成功后会清空进程内的用户偏好缓存(见[整体架构](/dev/architecture)),避免读到旧库的数据。 切换成功后会清空进程内的用户偏好缓存(见[整体架构](/dev/architecture)),避免读到旧库的数据。
+6 -3
View File
@@ -20,6 +20,8 @@
- 前端改动**不使用 `npm run build` 验证**,直接依赖 Vite 开发服务器的 HMR。 - 前端改动**不使用 `npm run build` 验证**,直接依赖 Vite 开发服务器的 HMR。
- `npm run build`(`vue-tsc -b && vite build`)的产物仅用于生产部署,与开发模式无关。 - `npm run build`(`vue-tsc -b && vite build`)的产物仅用于生产部署,与开发模式无关。
- 界面文案统一在 `frontend/src/locales/messages.ts` 维护,新增 key 需同时提供中文(zh)、英文(en)、日文(ja)三语。 - 界面文案统一在 `frontend/src/locales/messages.ts` 维护,新增 key 需同时提供中文(zh)、英文(en)、日文(ja)三语。
- 界面偏好需持久化记忆,存储键统一以 `ledgerly.` 前缀命名并集中维护于 `frontend/src/utils/storageKeys.ts`,读写经 `frontend/src/utils/storage.ts`(禁止业务代码直接调用 `localStorage`/`sessionStorage`);旧 `finance.` 前缀由 `migrateLegacyPrefixedKeys()` 自动迁移。详见[前端用户偏好](/dev/preferences)。
- **与 AI 相关的界面元素必须受 AI 启用状态控制**:未启用时一律不渲染(`v-if="aiEnabled"`),启用状态统一走共享状态 `frontend/src/composables/useAiEnabled.ts`(侧边栏「智能查数」菜单项亦据此显隐)。
## 后端开发 ## 后端开发
@@ -41,7 +43,7 @@
- 格式化:`docker exec ledgerly-backend-dev gradle -I /project/nexus-init.gradle spotlessApply` - 格式化:`docker exec ledgerly-backend-dev gradle -I /project/nexus-init.gradle spotlessApply`
- `spotlessCheck` 已挂到 `check`,`build` 时会校验。 - `spotlessCheck` 已挂到 `check`,`build` 时会校验。
- **日志**:统一使用 Lombok `@Slf4j`,按级别使用(`error` 未预期异常、`warn` 可预期业务失败、`info` 关键写操作与外部调用、`debug` 流程细节)。 - **日志**:统一使用 Lombok `@Slf4j`,按级别使用(`error` 未预期异常、`warn` 可预期业务失败、`info` 关键写操作与外部调用、`debug` 流程细节)。
- **控制器**:方法签名禁止声明 `HttpServletRequest` / `HttpServletResponse`,统一使用 `cn.rosercode.finance.util.UserContext`。 - **控制器**:方法签名禁止声明 `HttpServletRequest` / `HttpServletResponse`,统一使用 `cn.rosercode.ledgerly.util.UserContext`。
- **数据库表结构变更**:需同时维护 `sql/init.sql`(MySQL)与 `sql/schema-sqlite.sql`(SQLite),SQLite 以 `;;` 分隔语句。 - **数据库表结构变更**:需同时维护 `sql/init.sql`(MySQL)与 `sql/schema-sqlite.sql`(SQLite),SQLite 以 `;;` 分隔语句。
- **对象转换**:实体与 VO 之间统一使用 MapStruct(`converter.VoConverter`),不在 Service 中手写 setter。 - **对象转换**:实体与 VO 之间统一使用 MapStruct(`converter.VoConverter`),不在 Service 中手写 setter。
- **定时任务**:基于 Spring `@Scheduled`,新增调度器直接标注即可。 - **定时任务**:基于 Spring `@Scheduled`,新增调度器直接标注即可。
@@ -49,8 +51,9 @@
## Git 提交规范 ## Git 提交规范
- 提交范围仅包含源码与配置;个人账单原始数据、`docs/`、`reload.sh`、`.gitignore`、`nexus-init.gradle` 及 AI 测试环境基础设施不纳入提交。 - 提交范围仅包含源码与配置;个人账单原始数据、`docs/`、`reload.sh`、`.gitignore`、`nexus-init.gradle` 及 AI 测试环境基础设施不纳入提交。
- commit message 使用 `feat: 中文描述` 风格;纯重构/规范调整用 `refactor:`,构建与依赖配置用 `chore:` 或 `build:`。 - commit message 采用「标题 + 概述段 + 分组列表」结构;标题为 `<type>: <中文简述>`,纯重构/规范调整用 `refactor:`,构建与依赖配置用 `chore:` 或 `build:`。
- 远端为本地 Gitea,主分支 `master`;推送使用一次性内嵌令牌的 URL,避免令牌写入 `origin` 配置。 - 远端为本地 Gitea,**提交与推送仅使用 `dev` 分支**(`master` 仅用于稳定/发布,不纳入日常开发);推送使用一次性内嵌令牌的 URL,避免令牌写入 `origin` 配置。
- 每次创建 commit 后需执行 `./package.sh` 打包验证,并在 `frontend/` 执行一次 `npx vue-tsc -b --force` 类型检查。
## 相关文档 ## 相关文档
+3 -3
View File
@@ -5,10 +5,10 @@
## 设计与实现 ## 设计与实现
- [整体架构](/dev/architecture):技术栈、后端分层、请求流程、认证与前端结构 - [整体架构](/dev/architecture):技术栈、后端分层、请求流程、认证与前端结构
- [数据模型](/dev/data-model):21 张表的字段与关系、双数据库脚本差异 - [数据模型](/dev/data-model):24 张表的字段与关系、双数据库脚本差异
- [账单解析](/dev/bill-parsing):解析器策略模式、两阶段导入、去重与 zip 解密 - [账单解析](/dev/bill-parsing):解析器策略模式、两阶段导入、去重与 zip 解密
- [规则引擎](/dev/rule-engine):规则模型与匹配逻辑 - [规则引擎](/dev/rule-engine):规则模型与匹配逻辑
- [AI 集成](/dev/ai):OpenAI SDK 客户端、11 类调用类型与用量记录 - [AI 集成](/dev/ai):RestClient 客户端、13 类调用类型与用量记录
- [定时通知](/dev/notifications):调度、渠道与发送日志 - [定时通知](/dev/notifications):调度、渠道与发送日志
- [动态数据源](/dev/database):SQLite / MySQL 运行时配置与切换 - [动态数据源](/dev/database):SQLite / MySQL 运行时配置与切换
- [前端用户偏好](/dev/preferences):偏好的存储介质、读写时机与完整清单 - [前端用户偏好](/dev/preferences):偏好的存储介质、读写时机与完整清单
@@ -16,7 +16,7 @@
## 工程与规范 ## 工程与规范
- [项目结构](/dev/project-structure):目录组织与各包职责 - [项目结构](/dev/project-structure):目录组织与各包职责
- [构建与部署](/dev/build-deploy):`bootJar`、Docker 编排、桌面 GUI 启动器与 jpackage - [构建与部署](/dev/build-deploy):`bootJar` / `bootGuiJar`、jpackage 免安装包、发布产物下载与云服务器部署
- [开发环境与规范](/dev/development):AI 测试环境、前后端开发流程、代码规范、Git 提交规范 - [开发环境与规范](/dev/development):AI 测试环境、前后端开发流程、代码规范、Git 提交规范
- [账单字段说明](/dev/bill-fields):支付宝账单导入字段的读取与丢弃情况 - [账单字段说明](/dev/bill-fields):支付宝账单导入字段的读取与丢弃情况
- [API 文档](/dev/api):Knife4j(OpenAPI 3)接口文档 - [API 文档](/dev/api):Knife4j(OpenAPI 3)接口文档
+84 -39
View File
@@ -8,7 +8,7 @@
为此项目在 `ledgerly/AGENTS.md` 中约定: 为此项目在 `ledgerly/AGENTS.md` 中约定:
> 多 Tab 或可切换的界面偏好需**持久化记忆**(`localStorage`),下次打开时恢复到上次选择;存储键统一以 `finance.` 前缀命名,读取时对非法值回退默认项,写入时忽略存储异常。 > 多 Tab 或可切换的界面偏好需**持久化记忆**(`localStorage`),下次打开时恢复到上次选择;存储键统一以 `ledgerly.` 前缀命名,读取时对非法值回退默认项,写入时忽略存储异常。
据此形成三条实践原则: 据此形成三条实践原则:
@@ -32,38 +32,45 @@
| 键名 | 偏好内容 | 文件 | 保存时机 | 读取时机 | | 键名 | 偏好内容 | 文件 | 保存时机 | 读取时机 |
|---|---|---|---|---| |---|---|---|---|---|
| `locale` | 界面语言 `zh-CN`/`en-US`/`ja-JP` | `locales/index.ts:16` | 切换语言 | 模块初始化 | | `ledgerly.locale` | 界面语言 `zh-CN`/`en-US`/`ja-JP` | `locales/index.ts` | 切换语言 | 模块初始化 |
| `finance.dashboard.prefs` | 统计面板时间范围、粒度与 11 项筛选 | `views/Dashboard.vue:470` | 变量 `watch` 自动 | `onMounted` | | `ledgerly.dashboard.prefs` | 统计面板时间范围、粒度与多项筛选 | `views/Dashboard.vue` | 变量 `watch` 自动 | `onMounted` |
| `finance.transaction.filterPrefs` | 收支列表筛选、分页、排序 | `views/TransactionList.vue:369` | 变量 `watch` 自动 | `onMounted` | | `ledgerly.transaction.filterPrefs` | 收支列表筛选、分页、排序 | `views/TransactionList.vue` | 变量 `watch` 自动 | `onMounted` |
| `finance.transaction.aiScope` | 批量 AI 建议范围 | `views/TransactionList.vue:146` | 变量 `watch` 自动 | ref 初始化 | | `ledgerly.transaction.aiScope` | 批量 AI 建议范围 | `views/TransactionList.vue` | 变量 `watch` 自动 | ref 初始化 |
| `finance.transaction.aiRecordTab` | AI 记账方式(文字/图片) | `views/TransactionList.vue:206` | 变量 `watch` 自动 | ref 初始化 | | `ledgerly.transaction.aiRecordTab` | AI 记账方式(文字/图片) | `views/TransactionList.vue` | 变量 `watch` 自动 | ref 初始化 |
| `finance.transaction.aiQueryHistory` | AI 自然语言查询历史 | `views/TransactionList.vue:195` | 提交查询时 | `onMounted` | | `ledgerly.transaction.aiQueryHistory` | AI 自然语言查询历史 | `views/TransactionList.vue` / `components/AiQueryWindow.vue` | 提交查询时 | 浮窗聚焦时 |
| `finance.transaction.remarkHistory` | 备注关键词历史 | `views/TransactionList.vue:300` | 备注失焦/回车 | `onMounted` | | `ledgerly.dashboard.aiSearchHistory` | 统计面板 AI 查询历史 | `views/Dashboard.vue` | 提交 AI 搜索时 | 浮窗聚焦时 |
| `finance.dashboard.aiSearchHistory` | 统计面板 AI 查询历史 | `views/Dashboard.vue:91` | 提交 AI 搜索时 | `onMounted` | | `ledgerly.transaction.aiQueryWindow` | 收支记录 AI 查询浮窗位置与尺寸 | `components/FloatingPanel.vue` | 拖拽/缩放结束时 | 浮窗初始化 |
| `finance.billImport.billType` | 上次选择的账单来源类型 | `views/BillImportPanel.vue:25` | 切换类型时 | ref 初始化 | | `ledgerly.dashboard.aiQueryWindow` | 统计面板 AI 搜索浮窗位置与尺寸 | `components/FloatingPanel.vue` | 拖拽/缩放结束时 | 浮窗初始化 |
| `finance.transaction.relationType` | 账单关系类型偏好 | `components/TransactionRelationEditorModal.vue:14` | 创建模式切换类型时 | 弹窗初始化 | | `ledgerly.transaction.remarkHistory` | 备注关键词历史 | `views/TransactionList.vue` | 备注失焦/回车 | `onMounted` |
| `emailBill.tab` | 邮箱账单页标签页 | `views/EmailBillManage.vue:43` | Tab 切换时 | 路由重定向 + 组件 | | `ledgerly.billImport.billType` | 上次选择的账单来源类型 | `views/BillImportPanel.vue` | 切换类型时 | ref 初始化 |
| `logs.tab` | 系统日志页标签页 | `views/Logs.vue:25` | Tab 切换时 | 路由重定向 + 组件 | | `ledgerly.transaction.relationType` | 账单关系类型偏好 | `components/TransactionRelationEditorModal.vue` | 创建模式切换类型时 | 弹窗初始化 |
| `ledgerly.pagination.tablePageSizes` | 各表格分别记住的每页条数 | `utils/tablePageSizePrefs.ts` | 切换每页条数时 | 表格初始化 |
| `ledgerly.emailBill.tab` | 邮箱账单页标签页 | `views/EmailBillManage.vue` | Tab 切换时 | 路由重定向 + 组件 |
| `ledgerly.logs.tab` | 日志页标签页 | `views/Logs.vue` | Tab 切换时 | 路由重定向 + 组件 |
> `locale`、`emailBill.tab`、`logs.tab` 是较早的实现,未使用 `finance.` 前缀;前两者分别为国际化与路由重定向的既有约定,改动时需保持键名稳定。 > `locale`、`emailBill.tab`、`logs.tab` 是较早的实现,最初未加前缀,现已统一为 `ledgerly.` 前缀:在 `LEGACY_STORAGE_KEYS` 登记旧键、读取前用 `migrateKey` 一次性迁移;品牌改名(`finance.` → `ledgerly.`)的旧键由 `migrateLegacyPrefixedKeys()` 在模块加载时批量迁移,新键已存在时不覆盖。
### sessionStorage 偏好 ### sessionStorage 偏好
| 键名 | 偏好内容 | 文件 | | 键名 | 偏好内容 | 文件 |
|---|---|---| |---|---|---|
| `finance.scroll.<路由路径>` | 各页面内容区滚动位置 | `views/Layout.vue:83` | | `ledgerly.scroll.<路由路径>` | 各页面内容区滚动位置 | `views/Layout.vue` |
| `ledgerly.sqlQuery.session` | 智能查数当前会话的提问、结果与视图(结果超 500 行时只存提问与视图) | `views/SqlQuery.vue` |
### 后端按用户同步的偏好 ### 后端按用户同步的偏好
| 接口 | 偏好内容 | 前端入口 | | 接口 | 偏好内容 | 前端入口 |
|---|---|---| |---|---|---|
| `GET/PUT /api/setting/excluded-display-style` | 「不计入统计」记录展示样式 | `components/DisplayConfigPanel.vue` | | `GET/PUT /api/setting/excluded-display-style` | 「不计入统计」记录展示样式 | `components/DisplayConfigPanel.vue` |
| `GET/PUT /api/setting/show-hidden-data` | 是否显示已隐藏备注的记录 | `components/DisplayConfigPanel.vue` |
| `GET/PUT /api/setting/pagination-prefs` | 表格分页候选与默认每页条数 | `components/DisplayConfigPanel.vue` |
| `GET/PUT /api/setting/nav-menu` | 左侧导航顺序与显隐 | `components/NavMenuConfigPanel.vue` |
| `GET/PUT /api/setting/bill-source-include-in-stats` | 各账单来源「计入统计」默认值 | `components/BillSourceStatsPanel.vue` | | `GET/PUT /api/setting/bill-source-include-in-stats` | 各账单来源「计入统计」默认值 | `components/BillSourceStatsPanel.vue` |
| `GET/PUT /api/ai/config` | AI 启用开关、密钥、模型 | `components/AiConfigPanel.vue` | | `GET/PUT /api/ai/config` | AI 启用开关、密钥、模型 | `components/AiConfigPanel.vue` |
## localStorage 偏好详解 ## localStorage 偏好详解
### 界面语言 `locale` ### 界面语言 `ledgerly.locale`
- **文件**:`frontend/src/locales/index.ts` - **文件**:`frontend/src/locales/index.ts`
- **取值**:`zh-CN`、`en-US`、`ja-JP` - **取值**:`zh-CN`、`en-US`、`ja-JP`
@@ -72,7 +79,7 @@
- **目的**:记住用户语言选择,应用启动与切换即时生效。 - **目的**:记住用户语言选择,应用启动与切换即时生效。
- **入口**:`components/LocaleSwitch.vue`。 - **入口**:`components/LocaleSwitch.vue`。
### 统计面板偏好 `finance.dashboard.prefs` ### 统计面板偏好 `ledgerly.dashboard.prefs`
- **文件**:`frontend/src/views/Dashboard.vue`(键名第 470 行) - **文件**:`frontend/src/views/Dashboard.vue`(键名第 470 行)
- **数据结构**(JSON 对象,保存见 `savePrefs()` 第 499 行): - **数据结构**(JSON 对象,保存见 `savePrefs()` 第 499 行):
@@ -95,7 +102,7 @@
- **跨页写入**:点击趋势图跳转收支列表时,`applyTrendNavigation()`(第 381 行)把统计口径与筛选**合并写入**收支列表的偏好键,并保留分页、排序等既有设置。 - **跨页写入**:点击趋势图跳转收支列表时,`applyTrendNavigation()`(第 381 行)把统计口径与筛选**合并写入**收支列表的偏好键,并保留分页、排序等既有设置。
- **目的**:下次进入统计面板自动还原时间范围、粒度与全部过滤条件。 - **目的**:下次进入统计面板自动还原时间范围、粒度与全部过滤条件。
### 收支列表筛选偏好 `finance.transaction.filterPrefs` ### 收支列表筛选偏好 `ledgerly.transaction.filterPrefs`
- **文件**:`frontend/src/views/TransactionList.vue`(键名第 369 行) - **文件**:`frontend/src/views/TransactionList.vue`(键名第 369 行)
- **数据结构**(保存见 `saveFilterPrefs()` 第 396 行): - **数据结构**(保存见 `saveFilterPrefs()` 第 396 行):
@@ -121,17 +128,17 @@
| 键名 | 取值 | 说明 | | 键名 | 取值 | 说明 |
|---|---|---| |---|---|---|
| `finance.transaction.aiScope` | `BOTH`/`CATEGORY`/`TAG` | 批量 AI 建议范围,非法值回退 `BOTH`(第 146 行) | | `ledgerly.transaction.aiScope` | `BOTH`/`CATEGORY`/`TAG` | 批量 AI 建议范围,非法值回退 `BOTH`(第 146 行) |
| `finance.transaction.aiRecordTab` | `text`/`image` | AI 记账方式,非法值回退 `text`(第 206 行) | | `ledgerly.transaction.aiRecordTab` | `text`/`image` | AI 记账方式,非法值回退 `text`(第 206 行) |
| `finance.transaction.aiQueryHistory` | 字符串数组(≤10) | AI 自然语言查询历史,最新在前 | | `ledgerly.transaction.aiQueryHistory` | 字符串数组(≤10) | AI 自然语言查询历史,最新在前 |
| `finance.transaction.remarkHistory` | 字符串数组(≤10) | 备注关键词历史,失焦/回车记录 | | `ledgerly.transaction.remarkHistory` | 字符串数组(≤10) | 备注关键词历史,失焦/回车记录 |
- **`aiRecordTab`** 是 `AGENTS.md` 点名的参考实现:记住用户上次用的记账方式(文字或图片),下次打开 AI 记账弹窗直接落到该 Tab。 - **`aiRecordTab`** 是 `AGENTS.md` 点名的参考实现:记住用户上次用的记账方式(文字或图片),下次打开 AI 记账弹窗直接落到该 Tab。
- **关键词历史**统一由 `composables/useKeywordHistory.ts` 管理:去重(忽略大小写)、最新在前、上限 10 条,损坏数据自动忽略,下拉末尾提供「清空历史」项。 - **关键词历史**统一由 `composables/useKeywordHistory.ts` 管理:去重(忽略大小写)、最新在前、上限 10 条,损坏数据自动忽略,下拉末尾提供「清空历史」项。
- 读取:`load()`;记录:`record()`;清空:`clear()`。 - 读取:`load()`;记录:`record()`;清空:`clear()`。
- 三处使用:AI 查询历史、备注历史(均在 `TransactionList.vue`)、统计面板 AI 搜索历史(`Dashboard.vue`,键 `finance.dashboard.aiSearchHistory`)。 - 三处使用:AI 查询历史、备注历史(均在 `TransactionList.vue`)、统计面板 AI 搜索历史(`Dashboard.vue`,键 `ledgerly.dashboard.aiSearchHistory`)。
### 账单导入类型 `finance.billImport.billType` ### 账单导入类型 `ledgerly.billImport.billType`
- **文件**:`frontend/src/views/BillImportPanel.vue`(第 25 行) - **文件**:`frontend/src/views/BillImportPanel.vue`(第 25 行)
- **取值**:账单来源编码(`ALIPAY`、`WECHAT`、`CMB` 等) - **取值**:账单来源编码(`ALIPAY`、`WECHAT`、`CMB` 等)
@@ -139,7 +146,7 @@
- **保存**:`watch(billType)`(第 226 行)在切换类型时写入,同时清空已选文件,避免格式与类型不匹配。 - **保存**:`watch(billType)`(第 226 行)在切换类型时写入,同时清空已选文件,避免格式与类型不匹配。
- **目的**:记住上次选择的账单来源。 - **目的**:记住上次选择的账单来源。
### 账单关系类型 `finance.transaction.relationType` ### 账单关系类型 `ledgerly.transaction.relationType`
- **文件**:`frontend/src/components/TransactionRelationEditorModal.vue`(第 14 行) - **文件**:`frontend/src/components/TransactionRelationEditorModal.vue`(第 14 行)
- **取值**:`PARENT_CHILD`(默认)、`SIBLING` - **取值**:`PARENT_CHILD`(默认)、`SIBLING`
@@ -147,18 +154,32 @@
- **保存**:`watch(editorType)`(第 69 行)**仅在创建模式**(`editingGroupId === null`)写入,编辑既有关系组不覆盖偏好。 - **保存**:`watch(editorType)`(第 69 行)**仅在创建模式**(`editingGroupId === null`)写入,编辑既有关系组不覆盖偏好。
- **目的**:新建关系组时默认沿用上次的关系类型。 - **目的**:新建关系组时默认沿用上次的关系类型。
### 页面标签页 `emailBill.tab` / `logs.tab` ### 页面标签页 `ledgerly.emailBill.tab` / `ledgerly.logs.tab`
- **文件**:`views/EmailBillManage.vue:43`、`views/Logs.vue:25` - **文件**:`views/EmailBillManage.vue`、`views/Logs.vue`
- **取值**:邮箱页 `configs`/`senderRules`/`messages`;日志页 `http`/`login`/`app` - **取值**:邮箱页 `configs`/`senderRules`/`messages`;日志页 `http`/`login`/`app`
- **读取**:路由 `/email-bills`、`/logs` 的 `redirect` 读取偏好并拼接子路径(`router/index.ts:69`、第 93 行),带白名单校验,无记录回退默认。 - **读取**:路由 `/email-bills`、`/logs` 的 `redirect` 读取偏好并拼接子路径(`router/index.ts`),带白名单校验,无记录回退默认。
- **保存**:`watch(activeTab, ..., { immediate: true })` 在 Tab 切换时写入。 - **保存**:`watch(activeTab, ..., { immediate: true })` 在 Tab 切换时写入。
- **目的**:下次点击「邮箱账单」「系统日志」回到上次标签页。 - **目的**:下次点击「邮箱账单」「日志」回到上次标签页。
- **说明**:这两个是 `AGENTS.md` 点名的参考实现,但键名未加 `finance.` 前缀,属早期实现。 - **说明**:这两个是 `AGENTS.md` 点名的参考实现,早期未加前缀,现已统一为 `ledgerly.` 前缀并做旧键迁移。
### AI 查询浮窗位置 `ledgerly.transaction.aiQueryWindow` / `ledgerly.dashboard.aiQueryWindow`
- **文件**:`components/FloatingPanel.vue`(键经 `storageKey` 传入,由 `AiQueryWindow.vue` 复用)
- **取值**:`{ x, y, width, height }`,非法值回退居中与默认尺寸(默认 560 × 360)。
- **保存**:拖拽或缩放结束时写入;读取在浮窗初始化时。
- **目的**:记住 AI 查询浮窗上次的位置与大小,下次打开保持原样。
### 各表格每页条数 `ledgerly.pagination.tablePageSizes`
- **文件**:`frontend/src/utils/tablePageSizePrefs.ts`、`composables/useTablePageSize.ts`
- **取值**:`Record<tableKey, number>`,按表格键(`transaction`、`categoryExpense`、`rule`、`logsHttp`、`smart-query` 等)分别记录。
- **优先级**:本机记录(且须在当前分页候选中)> 全局默认值(后端 `paginationPrefs`)> 20。
- **保存**:切换某表格每页条数时写入;候选列表与全局默认值在[设置 → 外观设置](/guide/settings#外观设置)中配置(存后端)。
## sessionStorage 偏好:滚动位置 ## sessionStorage 偏好:滚动位置
- **文件**:`frontend/src/views/Layout.vue`(前缀第 83 行,键为 `finance.scroll.<路由路径>`) - **文件**:`frontend/src/views/Layout.vue`(前缀第 83 行,键为 `ledgerly.scroll.<路由路径>`)
- **保存**:内容区 `scroll` 事件经 `requestAnimationFrame` 节流写入;路由切换前保存离开页;`beforeunload` 兜底(第 172 行)。 - **保存**:内容区 `scroll` 事件经 `requestAnimationFrame` 节流写入;路由切换前保存离开页;`beforeunload` 兜底(第 172 行)。
- **读取**:`restoreScroll()`(第 139 行)在 `onMounted` 与路由切换后恢复;因页面内容异步加载、切换后高度可能不足,用 `rAF` 轮询等待内容高度足够再定位,2 秒超时后按当前可达最大值定位。 - **读取**:`restoreScroll()`(第 139 行)在 `onMounted` 与路由切换后恢复;因页面内容异步加载、切换后高度可能不足,用 `rAF` 轮询等待内容高度足够再定位,2 秒超时后按当前可达最大值定位。
- **目的**:刷新或页面切换后恢复阅读位置;仅当前会话有效,关闭浏览器即失效。 - **目的**:刷新或页面切换后恢复阅读位置;仅当前会话有效,关闭浏览器即失效。
@@ -181,9 +202,33 @@
- **前端**:设置页 `components/BillSourceStatsPanel.vue` 编辑保存(可点整行或开关切换)。 - **前端**:设置页 `components/BillSourceStatsPanel.vue` 编辑保存(可点整行或开关切换)。
- **目的**:按账单来源设置导入时默认是否计入统计,仅影响统计口径,不改变导入结果。 - **目的**:按账单来源设置导入时默认是否计入统计,仅影响统计口径,不改变导入结果。
### 显示已隐藏备注的记录
- **接口**:`GET/PUT /api/setting/show-hidden-data`(`api/setting.ts`)
- **数据结构**:`{ show: boolean }`,默认 `false`。
- **存储**:`t_user_preference` 的 `pref_key = showHiddenData`,值以 `"true"`/`"false"` 保存。
- **前端**:设置页 `components/DisplayConfigPanel.vue` 切换即存;收支列表与 Excel 导出据此过滤(关闭时排除 `remark_hidden = true` 的记录),统计备注搜索不受影响。
- **目的**:统一控制「已隐藏备注的记录」是否出现在列表与导出中。
### 表格分页偏好
- **接口**:`GET/PUT /api/setting/pagination-prefs`(`api/setting.ts`)
- **数据结构**:`{ options: number[], defaultSize: number }`,默认候选 `[10, 20, 50, 100]`、默认 20;后端会规范化(去重升序、1–1000、最多 10 项)。
- **存储**:`t_user_preference` 的 `pref_key = paginationPrefs`(JSON)。
- **前端**:设置页 `components/DisplayConfigPanel.vue` 编辑、防抖 500ms 自动保存;`composables/usePageSizeOptions.ts` 提供全局单例,`useTablePageSize(tableKey)` 读取各表记忆值。
- **目的**:统一所有表格的分页候选与默认每页条数。
### 导航顺序与显隐
- **接口**:`GET/PUT /api/setting/nav-menu`(`api/setting.ts`)
- **数据结构**:`{ items: [{ key, visible }] }`,未配置时以前端 `utils/navItems.ts` 的默认顺序与可见性为准。
- **存储**:`t_user_preference` 的 `pref_key = navMenu`(JSON)。
- **前端**:设置页 `components/NavMenuConfigPanel.vue`(`vuedraggable` 拖拽 + 开关,600ms 防抖保存、可恢复默认);`composables/useNavMenu.ts` 与后端合并,`Layout.vue` 据此渲染。隐藏仅移除菜单入口,路由仍可访问。
- **目的**:按账号自定义左侧导航的顺序与显隐,跨设备生效。
### AI 配置 ### AI 配置
- **接口**:`GET /api/ai/config`、`PUT /api/ai/config`(`api/ai.ts:37`) - **接口**:`GET /api/ai/config`、`PUT /api/ai/config`(`api/ai.ts`)
- **数据结构**:`{ enabled, apiKey, hasApiKey, baseUrl, model, visionModel }`,`apiKey` 留空表示不修改原值。 - **数据结构**:`{ enabled, apiKey, hasApiKey, baseUrl, model, visionModel }`,`apiKey` 留空表示不修改原值。
- **前端**:设置页 `components/AiConfigPanel.vue` 编辑保存。 - **前端**:设置页 `components/AiConfigPanel.vue` 编辑保存。
- **目的**:用户级 AI 能力配置;其他页面的 AI 元素统一经 `getAiConfig().enabled` 控制显隐。 - **目的**:用户级 AI 能力配置;其他页面的 AI 元素统一经 `getAiConfig().enabled` 控制显隐。
@@ -195,11 +240,10 @@
以下常见界面偏好当前**没有**持久化实现,改动时无需假设其存在: 以下常见界面偏好当前**没有**持久化实现,改动时无需假设其存在:
- **主题 / 暗色模式**:无主题存储键,界面主题固定。 - **主题 / 暗色模式**:无主题存储键,界面主题固定。
- **侧边栏折叠 / 展开**:`Layout.vue` 使用固定 `NLayoutSider`,无折叠状态。 - **侧边栏整体折叠 / 展开**:`Layout.vue` 使用固定 `NLayoutSider`,无折叠状态(但菜单项的**顺序与显隐**可配置,见上「导航顺序与显隐」)。
- **表格列显示 / 隐藏 / 顺序**:各页列配置为固定 `computed`。 - **表格列显示 / 隐藏 / 顺序**:各页列配置为固定 `computed`。
- **货币 / 金额格式**:无货币偏好。 - **货币 / 金额格式**:无货币偏好。
- **独立日期格式**:仅由语言 `locale` 间接驱动 Naive UI 日期本地化,无单独偏好。 - **独立日期格式**:仅由语言 `locale` 间接驱动 Naive UI 日期本地化,无单独偏好。
- **全局分页大小**:仅收支列表的 `pageSize` 持久化;日志、规则、邮箱账单等页面分页大小不持久化。
- **IndexedDB / cookie**:未使用。 - **IndexedDB / cookie**:未使用。
- **Pinia 持久化插件 / vueuse `useStorage`**:未安装、未使用。 - **Pinia 持久化插件 / vueuse `useStorage`**:未安装、未使用。
@@ -214,8 +258,9 @@
新增一项用户界面偏好时: 新增一项用户界面偏好时:
1. 存储键以 `finance.` 前缀命名,按页面/模块分段(如 `finance.transaction.xxx`)。 1. 存储键常量集中登记在 `frontend/src/utils/storageKeys.ts` 的 `STORAGE_KEYS`,以 `ledgerly.` 前缀命名、按页面/模块分段(如 `ledgerly.transaction.xxx`);涉及改名时在 `LEGACY_STORAGE_KEYS` 登记旧键并用 `migrateKey` 迁移。
2. 优先用 `watch` 自动保存,避免要求用户手动点保存。 2. 读写统一经 `frontend/src/utils/storage.ts`,**禁止**在业务代码中直接调用 `localStorage`/`sessionStorage`。
3. 读取时对取值做白名单校验,非法或损坏数据回退默认项。 3. 优先用 `watch` 自动保存,避免要求用户手动点保存。
4. 写入包 `try/catch`,忽略存储异常。 4. 读取时对取值做白名单校验,非法或损坏数据回退默认项;写入包 `try/catch`,忽略存储异常。
5. 需要跨设备一致时,改存后端并按当前用户隔离,而非 `localStorage`。 5. 组件内的字符串型偏好用 `useStringPreference`,关键词历史用 `useKeywordHistory`,浮窗位置尺寸用 `FloatingPanel`(键经 `storageKey` 传入)。
6. 需要跨设备一致时,改存后端并按当前用户隔离,而非 `localStorage`。
+6 -4
View File
@@ -19,7 +19,7 @@ ledgerly/
## 后端包结构 ## 后端包结构
`src/main/java/cn/rosercode/finance/` `src/main/java/cn/rosercode/ledgerly/`
| 包 | 职责 | | 包 | 职责 |
|---|---| |---|---|
@@ -39,7 +39,8 @@ ledgerly/
| `notification` | 通知渠道发送器 | | `notification` | 通知渠道发送器 |
| `scheduler` | 定时任务(通知、日志清理、邮箱账单) | | `scheduler` | 定时任务(通知、日志清理、邮箱账单) |
| `service` / `service.impl` | 业务层 | | `service` / `service.impl` | 业务层 |
| `util` | 工具类(`JwtUtil`、`UserContext`、`AppLogUtils`、`ImageUtils`、`AppHome`、`TransactionDedupUtils` 等) | | `util` | 工具类(`JwtUtil`、`UserContext`、`AppLogUtils`、`ImageUtils`、`AppHome`、`TransactionDedupUtils` 等);`util.sql` 为智能查数的安全网关(`TextToSqlGuard` / `TextToSqlTables` / `TextToSqlSchemaProvider`) |
| `prompt` | AI 提示词模板加载(`PromptTemplateService`,模板位于 `resources/prompts/`) |
| `vo` | 响应对象 | | `vo` | 响应对象 |
| `launcher` | 桌面 GUI 启动器(仅打入带 GUI 的 jar) | | `launcher` | 桌面 GUI 启动器(仅打入带 GUI 的 jar) |
@@ -67,11 +68,12 @@ App.vue # 应用根组件
main.ts # 入口(挂载应用与插件) main.ts # 入口(挂载应用与插件)
style.css # 全局样式 style.css # 全局样式
api/ # 各模块 REST 接口封装(axios) api/ # 各模块 REST 接口封装(axios)
components/ # 复用组件(账单预览、导入结果、AI/通知/数据库配置面板等) components/ # 复用组件(账单预览、导入结果、AI/通知/数据库配置面板、账单关系、AI 查询浮窗等)
composables/ # 组合式函数(AI 启用状态、导航配置、分页选项、关键词历史等)
locales/ # 中/英/日三语文案(messages.ts) locales/ # 中/英/日三语文案(messages.ts)
router/ # 路由与导航守卫 router/ # 路由与导航守卫
store/ # Pinia 状态(user / database / registration) store/ # Pinia 状态(user / database / registration)
utils/ # 工具(交易渠道标签、SSE 封装、渲染辅助) utils/ # 工具(交易渠道标签、SSE 封装、本地偏好存储、渲染辅助)
views/ # 页面组件 views/ # 页面组件
``` ```
+1
View File
@@ -34,6 +34,7 @@
- `GET /api/rule/list`:返回当前用户全部规则(按优先级排序),供内部复用。 - `GET /api/rule/list`:返回当前用户全部规则(按优先级排序),供内部复用。
- `GET /api/rule/page`:分页查询,支持 `keyword`(匹配规则名称或关键词)、`enabled`、`type` 筛选,返回 `PageResult<RuleVO>`。 - `GET /api/rule/page`:分页查询,支持 `keyword`(匹配规则名称或关键词)、`enabled`、`type` 筛选,返回 `PageResult<RuleVO>`。
- `DELETE /api/rule/batch`:批量删除(body `{ ids }`);先按 `userId` 过滤归属,清理规则-标签关联后删除,返回实际删除数。
## AI 生成规则 ## AI 生成规则
+15 -9
View File
@@ -1,6 +1,6 @@
# AI 能力 # AI 能力
系统集成 **DeepSeek** 大模型,在分类打标、查询、记账等环节提供智能辅助。所有 AI 功能需先在「系统设置 → AI 模型配置」中启用并配置 API Key。 系统集成 **DeepSeek** 大模型,在分类打标、查询、记账等环节提供智能辅助。所有 AI 功能需先在「设置 → AI 模型配置」中启用并配置 API Key。
## 数据与隐私 ## 数据与隐私
@@ -19,6 +19,8 @@
- 智能记账:你输入的一句话; - 智能记账:你输入的一句话;
- 生成分类/标签、推荐 Emoji/颜色:你输入的名称或描述; - 生成分类/标签、推荐 Emoji/颜色:你输入的名称或描述;
- 生成规则:所选记录的类型、备注、交易渠道、已有分类与标签(不含金额与原始账单数据); - 生成规则:所选记录的类型、备注、交易渠道、已有分类与标签(不含金额与原始账单数据);
- 生成账单关系名称:所选账单的收支类型、交易渠道、分类名称与备注(不含金额与原始账单数据);
- 智能查数:你输入的自然语言,以及可供查询的表结构说明(不含账单原始字段);
- 图片记账:你**主动上传**的账单/支付截图会**整张**发送给视觉模型(可能含金额、交易对方、订单号等),视为你主动提供的输入。 - 图片记账:你**主动上传**的账单/支付截图会**整张**发送给视觉模型(可能含金额、交易对方、订单号等),视为你主动提供的输入。
图片同时会保存在本地(`data/ai-images/`),仅本人可查看;请勿上传包含他人敏感信息的图片。 图片同时会保存在本地(`data/ai-images/`),仅本人可查看;请勿上传包含他人敏感信息的图片。
@@ -27,14 +29,14 @@ API Key 仅保存在本地数据库中,仅用于向你配置的接口地址发
## 配置 ## 配置
在「系统设置 → AI 模型配置」中: 在「设置 → AI 模型配置」中:
- 开启「启用 AI 建议」。 - 开启「启用 AI 建议」。
- 填写 **API Key**。 - 填写 **API Key**。
- 可选修改 **接口地址**(默认 `https://api.deepseek.com`)、**模型**(默认 `deepseek-chat`)与**视觉模型**(默认 `deepseek-flash`,用于图片记账)。 - 可选修改 **接口地址**(默认 `https://api.deepseek.com`)、**模型**(默认 `deepseek-chat`)与**视觉模型**(默认 `deepseek-flash`,用于图片记账)。
- 点击「测试连接」验证配置。 - 点击「测试连接」验证配置。
![系统设置](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/settings.png) ![设置](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/settings.png)
## 功能一览 ## 功能一览
@@ -51,6 +53,8 @@ API Key 仅保存在本地数据库中,仅用于向你配置的接口地址发
| 推荐颜色 | 标签管理 → AI 推荐 | 为标签推荐颜色 | | 推荐颜色 | 标签管理 → AI 推荐 | 为标签推荐颜色 |
| 生成规则 | 规则管理 → AI 生成规则 | 由已打标签的记录归纳分类与标签规则 | | 生成规则 | 规则管理 → AI 生成规则 | 由已打标签的记录归纳分类与标签规则 |
| 统计搜索 | 统计面板 → AI 搜索 | 自然语言解析为统计筛选条件与维度 | | 统计搜索 | 统计面板 → AI 搜索 | 自然语言解析为统计筛选条件与维度 |
| 生成关系名称 | 收支记录 → 账单关系 → AI 生成 | 依据关联账单的类型、渠道、分类与备注生成关系名称与备注 |
| 智能查数 | 左侧菜单 → 智能查数 | 自然语言转只读 SQL 查询并展示结果 |
## 分类与标签建议 ## 分类与标签建议
@@ -60,14 +64,14 @@ API Key 仅保存在本地数据库中,仅用于向你配置的接口地址发
## 智能查询 ## 智能查询
在收支记录页的 AI 查询框中输入自然语言,例如: 在收支记录页点「AI 搜索」打开**可拖拽、可缩放的浮窗**,在输入框中用自然语言描述,例如:
- 「上个月餐饮」→ 时间范围 + 分类 - 「上个月餐饮」→ 时间范围 + 分类
- 「超过500的支出」→ 类型 + 金额区间 - 「超过500的支出」→ 类型 + 金额区间
- 「待报销的支出」→ 标签 + 类型 - 「待报销的支出」→ 标签 + 类型
- 「微信支付」→ 交易渠道 - 「微信支付」→ 交易渠道
解析结果会直接应用为筛选条件并立即查询。 按 `Ctrl + Enter`(Mac 为 `⌘ + Enter`)提交,解析结果会直接应用为筛选条件并立即查询;浮窗的位置与大小会被记住。详见[收支记录](/guide/transactions)。
## 智能记账 ## 智能记账
@@ -97,13 +101,15 @@ API Key 仅保存在本地数据库中,仅用于向你配置的接口地址发
- 顶部摘要说明识别到的账单内容;勾选后点击「导入勾选」批量写入收支记录(导入时会再次按去重键跳过重复)。 - 顶部摘要说明识别到的账单内容;勾选后点击「导入勾选」批量写入收支记录(导入时会再次按去重键跳过重复)。
- 未识别到有效交易时会给出提示,不会写入任何记录。 - 未识别到有效交易时会给出提示,不会写入任何记录。
> 图片记账使用的是视觉模型,可在「系统设置 → AI 模型配置」中单独配置(默认 `deepseek-flash`)。详见[系统设置](/guide/settings)。 > 图片记账使用的是视觉模型,可在「设置 → AI 模型配置」中单独配置(默认 `deepseek-flash`)。详见[设置](/guide/settings)。
导入后的记录在[收支记录](/guide/transactions)页的「**来源图片**」列可回看原图(默认加载缩略图,可切换为原图)。 导入后的记录可在[收支记录](/guide/transactions)的「**详情**」弹窗中回看来源图片(默认加载缩略图,可点「加载原图」切换为原图)。
## AI 统计搜索 ## AI 统计搜索
在统计面板顶部的 AI 搜索框中用一句自然语言描述统计意图,例如「上个月餐饮花了多少」「近半年交通支出趋势,按周看」。系统会解析为**时间范围、收支类型、分类、标签、交易渠道、金额区间、备注、统计口径与趋势粒度**并自动回填、刷新图表。详见[统计面板](/guide/dashboard)。 在统计面板顶部点「AI 搜索」打开浮窗,用一句自然语言描述统计意图,例如「上个月餐饮花了多少」「近半年交通支出趋势,按周看」。系统会解析为**时间范围、收支类型、分类、标签、交易渠道、金额区间、备注、统计口径与趋势粒度**并自动回填、刷新图表。详见[统计面板](/guide/dashboard)。
> 想要更自由的问答式查询(直接生成查询并返回明细),可使用[智能查数](/guide/sql-query)。
## AI 生成规则 ## AI 生成规则
@@ -111,7 +117,7 @@ API Key 仅保存在本地数据库中,仅用于向你配置的接口地址发
## 用量统计 ## 用量统计
「模型使用情况」页面(系统设置 → AI 模型配置 →「使用情况」)展示 AI 调用汇总与明细。 「模型使用情况」页面(设置 → AI 模型配置 →「使用情况」)展示 AI 调用汇总与明细。
![模型使用情况](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/ai-usage.png) ![模型使用情况](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/ai-usage.png)
+25 -14
View File
@@ -23,15 +23,17 @@
## AI 搜索 ## AI 搜索
启用 AI 后,页面顶部会出现「**AI 搜索**」按钮,点击打开搜索弹窗。用一句自然语言描述统计意图,系统会调用大模型解析为统计参数并自动回填、刷新图表,例如: 启用 AI 后,页面顶部会出现「**AI 搜索**」按钮,点击打开一个**可拖拽、可缩放的浮窗**(非模态)。用一句自然语言描述统计意图,系统会调用大模型解析为统计参数并自动回填、刷新图表,例如:
- 「上个月餐饮花了多少」→ 时间范围 + 分类 - 「上个月餐饮花了多少」→ 时间范围 + 分类
- 「近半年交通支出趋势,按周看」→ 时间范围 + 分类 + 按周粒度 - 「近半年交通支出趋势,按周看」→ 时间范围 + 分类 + 按周粒度
- 「今年各类支出占比」→ 时间范围 + 类型 - 「今年各类支出占比」→ 时间范围 + 类型
![AI 统计搜索弹窗](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/ai-search.png) ![AI 统计搜索浮窗](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/ai-query-window.png)
可解析并回填的参数包括:**时间范围、收支类型、分类、标签、交易渠道、金额区间、备注、统计口径**与**趋势粒度**。弹窗内提供示例问题,输入框会记住历史记录(可一键清空);回车或点击「AI 查询」即可执行,无可用条件时会提示且不改动当前筛选。详见 [AI 能力](/guide/ai)。 可解析并回填的参数包括:**时间范围、收支类型、分类、标签、交易渠道、金额区间、备注、统计口径**与**趋势粒度**。浮窗内提供示例问题,输入框会记住历史记录(可一键清空);按 `Ctrl + Enter`(Mac 为 `⌘ + Enter`)或点击提交按钮执行,无可用条件时会提示且不改动当前筛选。详见 [AI 能力](/guide/ai)。
> 想要返回明细的自由问答式查询,可使用[智能查数](/guide/sql-query)。
## 筛选条件 ## 筛选条件
@@ -39,7 +41,7 @@
- **类型**:支出 / 收入(多选)。 - **类型**:支出 / 收入(多选)。
- **分类**:多选,含「未分类」。 - **分类**:多选,含「未分类」。
- **交易渠道**:多选。 - **交易渠道**:多选。
- **标签**:多选。 - **标签**:多选,含「无标签」。
- **金额区间**:最小 ~ 最大。 - **金额区间**:最小 ~ 最大。
- **备注搜索**。 - **备注搜索**。
@@ -55,28 +57,37 @@
| 图表 | 说明 | | 图表 | 说明 |
|---|---| |---|---|
| 收支趋势(柱状图) | 按所选粒度展示收入(绿)与支出(红),点击柱子或该列空白处可跳转查看对应明细 | | 收支趋势(柱状图) | 按所选粒度展示收入(绿)与支出(红),**点击柱子或该列空白处**跳转查看对应明细 |
| 支出分类占比(环形图) | 各分类支出金额占比 | | 支出分类占比(环形图) | 各分类支出金额占比,**点击扇区**跳转该分类明细 |
| 收入分类占比(环形图) | 各分类收入金额占比 | | 收入分类占比(环形图) | 各分类收入金额占比,**点击扇区**跳转该分类明细 |
| 支出交易渠道占比(环形图) | 仅统计支出,按交易渠道 | | 支出交易渠道占比(环形图) | 仅统计支出,按交易渠道,**点击扇区**跳转该渠道明细 |
| 收入交易渠道占比(环形图) | 仅统计收入,按交易渠道 | | 收入交易渠道占比(环形图) | 仅统计收入,按交易渠道,**点击扇区**跳转该渠道明细 |
切换语言时,图表标题、图例与交易渠道名称会实时重绘。 切换语言时,图表标题、图例与交易渠道名称会实时重绘。
### 点击趋势图查看明细 ### 点击图表查看明细
「收支趋势」柱状图的**整列都可点击**:点中柱子,或点击柱子顶端上方的空白处均可。点击后跳转到[收支记录](/guide/transactions),并自动带入该列对应的筛选条件: 所有图表都可点击下钻,跳转到[收支记录](/guide/transactions)并自动带入对应筛选条件:
- **趋势柱状图**:整列可点击——点中柱子,或点击柱子顶端上方的空白处均可。
- **类型**:点中柱子时取该柱子的收入 / 支出;点击空白处时不限定类型(沿用面板当前类型筛选); - **类型**:点中柱子时取该柱子的收入 / 支出;点击空白处时不限定类型(沿用面板当前类型筛选);
- **日期范围**:该列代表的时间桶——按日→当天、按周→周一至周日、按月→当月、按季→当季、按年→当年; - **日期范围**:该列代表的时间桶——按日→当天、按周→周一至周日、按月→当月、按季→当季、按年→当年。
- **其他筛选**:统计面板当前的统计口径、分类、交易渠道、标签、金额区间与备注关键字。 - **分类饼图**:点击某个扇区,按该分类(含「未分类」)与对应收支类型筛选。
- **渠道饼图**:点击某个扇区,按该交易渠道与对应收支类型筛选。
无论从哪张图下钻,都会同时保留面板当前的统计口径、标签、金额区间与备注关键字等筛选。
![点击趋势柱状图跳转收支记录](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/dashboard-trend-jump.png) ![点击趋势柱状图跳转收支记录](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/dashboard-trend-jump.png)
跳转后可直接查看该时间段内的对应明细;筛选条件会照常记忆,收支记录页原有的每页条数与排序偏好不受影响。 页面底部的「**查看收支记录**」按钮则会携带**当前全部筛选条件**跳转,便于核对整体明细。
> 从[收支记录](/guide/transactions)点「查看统计」也会跳回本页,并还原收支记录当前的筛选条件(时间范围会切换为「自定义」)。
跳转后可直接查看对应明细;筛选条件会照常记忆,收支记录页原有的每页条数与排序偏好不受影响。
## 小技巧 ## 小技巧
- 想知道「上个月餐饮花了多少」,可把时间范围设为「本月」或自定义,并在分类中只勾选「餐饮」。 - 想知道「上个月餐饮花了多少」,可把时间范围设为「本月」或自定义,并在分类中只勾选「餐饮」。
- 不计入统计的记录(如借款、账户间转账)默认不会计入汇总;如需查看全部,把统计口径切换为「全部记录」。 - 不计入统计的记录(如借款、账户间转账)默认不会计入汇总;如需查看全部,把统计口径切换为「全部记录」。
- 想快速核对某月、某周或某天的明细,直接点击「收支趋势」上对应的列(柱子或该列空白处),即可带着时间范围跳到收支记录。 - 想快速核对某月、某周或某天的明细,直接点击「收支趋势」上对应的列(柱子或该列空白处),即可带着时间范围跳到收支记录。
- 点击分类或渠道饼图的某个扇区,可一键下钻到该分类 / 渠道的明细;点「查看收支记录」则带上当前全部筛选。
+109
View File
@@ -0,0 +1,109 @@
# 下载与安装
本章介绍如何获取「汇账」的安装包并在本机运行。
系统提供两种分发形态:
| 形态 | 适用场景 | 说明 |
|---|---|---|
| 免安装应用(app-image) | 推荐 | 自带精简 Java 运行时,解压即用,**无需另装 JDK** |
| 可执行 jar | 已有 JDK 21 的环境 | 单个 jar 文件,`java -jar` 运行 |
每个平台各提供两个版本:
- **个人记账(GUI 版)**:带图形界面启动器,可在窗口或系统托盘中启停服务、打开浏览器、实时查看日志。
- **后端服务(服务版)**:命令行启动,适合服务器或后台常驻运行。
## 下载地址
当前版本:`0.0.1-SNAPSHOT`。以下均为阿里云 OSS 长期签名地址,有效期至 **2036 年**。
### Windows(x64)
| 版本 | 下载 | 校验 |
|---|---|---|
| 个人记账(GUI) | [Ledgerly-windows-x64.zip](https://adccrac.oss-cn-beijing.aliyuncs.com/ledgerly/release/0.0.1-SNAPSHOT/windows/Ledgerly-windows-x64.zip?OSSAccessKeyId=LTAI5tA21KsXJTQoSmt4m7YJ&Expires=2105596492&Signature=2bzQh3TpGGA2qop%2FXh%2FbyN9av%2Fg%3D) | [sha256](https://adccrac.oss-cn-beijing.aliyuncs.com/ledgerly/release/0.0.1-SNAPSHOT/windows/Ledgerly-windows-x64.zip.sha256?OSSAccessKeyId=LTAI5tA21KsXJTQoSmt4m7YJ&Expires=2105596492&Signature=uKasei8G%2Bft2kZNscrJIRKfCg8c%3D) |
| 后端服务 | [LedgerlyServer-windows-x64.zip](https://adccrac.oss-cn-beijing.aliyuncs.com/ledgerly/release/0.0.1-SNAPSHOT/windows/LedgerlyServer-windows-x64.zip?OSSAccessKeyId=LTAI5tA21KsXJTQoSmt4m7YJ&Expires=2105596492&Signature=02PxWpHN%2FVjGnm%2BOAtzJakeG3GM%3D) | [sha256](https://adccrac.oss-cn-beijing.aliyuncs.com/ledgerly/release/0.0.1-SNAPSHOT/windows/LedgerlyServer-windows-x64.zip.sha256?OSSAccessKeyId=LTAI5tA21KsXJTQoSmt4m7YJ&Expires=2105596492&Signature=ToeO4rwW6I1%2FA12bMZOV4pKsMPs%3D) |
| 校验清单 | [SHA256SUMS](https://adccrac.oss-cn-beijing.aliyuncs.com/ledgerly/release/0.0.1-SNAPSHOT/windows/SHA256SUMS?OSSAccessKeyId=LTAI5tA21KsXJTQoSmt4m7YJ&Expires=2105596492&Signature=gyaLuqTqV9ra9CeJYEZZTOg1qFQ%3D) | |
### Linux(x64)
| 版本 | 下载 | 校验 |
|---|---|---|
| 个人记账(GUI) | [Ledgerly-linux-x64.tar.gz](https://adccrac.oss-cn-beijing.aliyuncs.com/ledgerly/release/0.0.1-SNAPSHOT/linux/Ledgerly-linux-x64.tar.gz?OSSAccessKeyId=LTAI5tA21KsXJTQoSmt4m7YJ&Expires=2105596492&Signature=cMLZ4xpv0lhz0GWC7FPXXThu4MM%3D) | [sha256](https://adccrac.oss-cn-beijing.aliyuncs.com/ledgerly/release/0.0.1-SNAPSHOT/linux/Ledgerly-linux-x64.tar.gz.sha256?OSSAccessKeyId=LTAI5tA21KsXJTQoSmt4m7YJ&Expires=2105596492&Signature=5hEMux5%2BmLcbrg%2BNuAbwvk15qSk%3D) |
| 后端服务 | [LedgerlyServer-linux-x64.tar.gz](https://adccrac.oss-cn-beijing.aliyuncs.com/ledgerly/release/0.0.1-SNAPSHOT/linux/LedgerlyServer-linux-x64.tar.gz?OSSAccessKeyId=LTAI5tA21KsXJTQoSmt4m7YJ&Expires=2105596492&Signature=VsJhCRQeWNqQuBfkyXI1llxbrko%3D) | [sha256](https://adccrac.oss-cn-beijing.aliyuncs.com/ledgerly/release/0.0.1-SNAPSHOT/linux/LedgerlyServer-linux-x64.tar.gz.sha256?OSSAccessKeyId=LTAI5tA21KsXJTQoSmt4m7YJ&Expires=2105596492&Signature=h2evKRHMhJhQTKgv2XFKSOMR14E%3D) |
| 校验清单 | [SHA256SUMS](https://adccrac.oss-cn-beijing.aliyuncs.com/ledgerly/release/0.0.1-SNAPSHOT/SHA256SUMS?OSSAccessKeyId=LTAI5tA21KsXJTQoSmt4m7YJ&Expires=2105596492&Signature=9AEbPWUyrZNxTbE3TCzVeIc3hy8%3D) | |
### 可执行 jar(跨平台)
需要本机已安装 **JDK 21**。
| 版本 | 下载 | 校验 |
|---|---|---|
| 普通 jar | [ledgerly-0.0.1-SNAPSHOT-jdk21.jar](https://adccrac.oss-cn-beijing.aliyuncs.com/ledgerly/release/0.0.1-SNAPSHOT/ledgerly-0.0.1-SNAPSHOT-jdk21.jar?OSSAccessKeyId=LTAI5tA21KsXJTQoSmt4m7YJ&Expires=2105596492&Signature=198KOxfQgVxy9Bp5EGyqTjbcoX4%3D) | [sha256](https://adccrac.oss-cn-beijing.aliyuncs.com/ledgerly/release/0.0.1-SNAPSHOT/ledgerly-0.0.1-SNAPSHOT-jdk21.jar.sha256?OSSAccessKeyId=LTAI5tA21KsXJTQoSmt4m7YJ&Expires=2105596492&Signature=YsP4w312R4%2BkqCFgqsaVUGY5q0k%3D) |
| 带 GUI 启动器 jar | [ledgerly-0.0.1-SNAPSHOT-jdk21-gui.jar](https://adccrac.oss-cn-beijing.aliyuncs.com/ledgerly/release/0.0.1-SNAPSHOT/ledgerly-0.0.1-SNAPSHOT-jdk21-gui.jar?OSSAccessKeyId=LTAI5tA21KsXJTQoSmt4m7YJ&Expires=2105596492&Signature=e6x%2FIxXBxINyaeX2BDkhnvgKvyY%3D) | [sha256](https://adccrac.oss-cn-beijing.aliyuncs.com/ledgerly/release/0.0.1-SNAPSHOT/ledgerly-0.0.1-SNAPSHOT-jdk21-gui.jar.sha256?OSSAccessKeyId=LTAI5tA21KsXJTQoSmt4m7YJ&Expires=2105596492&Signature=TfA5AwPl5OwdUv2FKLf%2F1XSXetQ%3D) |
## 运行
### Windows
解压 zip 后进入解压出的目录:
- **个人记账(GUI)**:双击 `Ledgerly.exe`,在启动器界面上点击「启动服务」,再点「打开浏览器」。
- **后端服务**:运行 `LedgerlyServer.exe`,会附带控制台窗口显示日志;服务在本机 `http://localhost:8080` 提供页面与接口。
### Linux
个人记账(GUI):
```bash
tar xzf Ledgerly-linux-x64.tar.gz
cd Ledgerly
./bin/Ledgerly
```
后端服务:
```bash
tar xzf LedgerlyServer-linux-x64.tar.gz
cd LedgerlyServer
./bin/LedgerlyServer
```
### 可执行 jar
```bash
java -jar ledgerly-0.0.1-SNAPSHOT-jdk21.jar
```
带 GUI 启动器的 jar(需要桌面环境):
```bash
java -jar ledgerly-0.0.1-SNAPSHOT-jdk21-gui.jar
```
默认访问地址为 `http://localhost:8080`,可在「设置」中修改端口,或运行时用 `--server.port=18081` 指定。
## 校验下载文件
建议下载后校验文件完整性(`sha256` 文件内容即对应压缩包/ jar 的 SHA-256 值)。
Linux:
```bash
sha256sum -c Ledgerly-linux-x64.tar.gz.sha256
```
Windows PowerShell:
```powershell
Get-FileHash -Algorithm SHA256 .\Ledgerly-windows-x64.zip
# 与 .sha256 文件中的值比对
```
## 运行目录与数据
- 配置、日志、数据默认落在程序所在目录;可用 `--home=<目录>` 参数或环境变量 `LEDGERLY_HOME` 指定基准目录。
- 首次访问时,页面会引导选择 **SQLite**(本地单文件,零配置)或 **MySQL**,保存后自动建表。
> 更多安装与使用说明见[快速开始](/guide/getting-started);自行构建与部署见[构建与部署](/dev/build-deploy)。
+4 -4
View File
@@ -14,7 +14,7 @@
### 支持哪些账单? ### 支持哪些账单?
支付宝、微信、美团、招商银行、交通银行,详见[账单类型与获取](/guide/bills/overview)。 支付宝、微信、美团、淘宝、京东、招商银行、交通银行,详见[账单类型与获取](/guide/bills/overview)。
### 导入后重复了怎么办? ### 导入后重复了怎么办?
@@ -50,7 +50,7 @@
### 想让备注不显示在界面上? ### 想让备注不显示在界面上?
可在记录行使用「隐藏」功能,备注会显示为 `******`,需要时再「查看」或「取消隐藏」。 可在记录行使用「隐藏」功能,备注会显示为 `******`,需要时再「查看」或「取消隐藏」。默认这些记录会从收支记录列表与 Excel 导出中排除;如需让其显示,可在「设置 → 外观设置」开启「显示已隐藏备注的记录」。
### 分类和标签有什么区别? ### 分类和标签有什么区别?
@@ -60,7 +60,7 @@
### AI 功能不可用? ### AI 功能不可用?
请先在「系统设置 → AI 模型配置」中开启并填写 API Key,然后点「测试连接」验证。 请先在「设置 → AI 模型配置」中开启并填写 API Key,然后点「测试连接」验证。
### 邮箱账单会自动入库吗? ### 邮箱账单会自动入库吗?
@@ -78,7 +78,7 @@
### 忘记密码怎么办? ### 忘记密码怎么办?
当前版本未提供自助找回。可在数据库中重置对应用户的密码哈希,或新建账号后迁移数据。 登录后可在「设置 → 账号安全」中,凭**原密码**修改密码(也支持一键随机生成新密码)。若已完全忘记密码,当前版本暂未提供自助找回,可在数据库中重置该用户的密码哈希,或新建账号后迁移数据。
### 数据存在哪里? ### 数据存在哪里?
+4 -3
View File
@@ -33,7 +33,7 @@
![数据库初始化](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/settings.png) ![数据库初始化](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/settings.png)
首次进入时按引导选择数据库类型并填写连接信息,点击「保存并应用」即可。登录后可通过右上角用户菜单 →「数据库设置」随时切换数据库;切换后原数据库的数据保留,不做自动迁移。 首次进入时按引导选择数据库类型并填写连接信息,点击「保存并应用」即可。登录后可通过右上角用户菜单 →「设置」→「数据库配置」随时切换数据库;切换后原数据库的数据保留,不做自动迁移。
## 注册与登录 ## 注册与登录
@@ -44,7 +44,7 @@
![注册卡片](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/register-card.png) ![注册卡片](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/register-card.png)
注册功能可在「系统设置 → 注册设置」中开启或关闭。新注册用户会自动获得一套默认分类、标签与邮箱发件人规则。 注册功能可在「设置 → 注册设置」中开启或关闭。新注册用户会自动获得一套默认分类、标签与邮箱发件人规则。
## 界面语言 ## 界面语言
@@ -56,7 +56,8 @@
2. 进入「分类管理」「标签管理」按自己的习惯调整分类与标签。 2. 进入「分类管理」「标签管理」按自己的习惯调整分类与标签。
3. 在「规则管理」配置常用自动归类规则(如「星巴克 → 餐饮」)。 3. 在「规则管理」配置常用自动归类规则(如「星巴克 → 餐饮」)。
4. 导出并导入历史账单(见[账单类型与获取](/guide/bills/overview))。 4. 导出并导入历史账单(见[账单类型与获取](/guide/bills/overview))。
5. 按需配置「AI 模型」「定时通知」「邮箱账单」。 5. 在「设置」中按需配置「AI 模型」「定时通知」「邮箱账单」,并可调整「外观设置」与「导航设置」。
6. 启用 AI 后,可用左侧「智能查数」以自然语言查询账本。
## 下一步 ## 下一步
+7 -4
View File
@@ -6,25 +6,28 @@
- [项目简介](/guide/introduction):系统定位、核心功能与技术栈 - [项目简介](/guide/introduction):系统定位、核心功能与技术栈
- [快速开始](/guide/getting-started):数据库配置、注册登录、界面语言与首次使用建议 - [快速开始](/guide/getting-started):数据库配置、注册登录、界面语言与首次使用建议
- [下载与安装](/guide/download):Windows / Linux 安装包与可执行 jar 的下载、运行与校验
- [统计面板](/guide/dashboard):时间范围、筛选条件与图表说明 - [统计面板](/guide/dashboard):时间范围、筛选条件与图表说明
## 记账与整理 ## 记账与整理
- [收支记录](/guide/transactions):记录管理、筛选、批量操作、账单导入、AI 建议与记账 - [收支记录](/guide/transactions):记录管理、筛选、批量操作、账单导入、AI 建议与记账
- [账单关系](/guide/relations):父子/同级关系、关系图与 AI 生成关系名称
- [分类与标签](/guide/categories-tags):分类与标签的维护与使用建议 - [分类与标签](/guide/categories-tags):分类与标签的维护与使用建议
- [交易渠道管理](/guide/channels):内置渠道与自定义渠道的新建、改名与删除 - [交易渠道管理](/guide/channels):内置渠道与自定义渠道的新建、改名与删除
- [规则管理](/guide/rules):按关键词/金额/类型自动归类打标,支持 AI 生成规则、搜索与分页 - [规则管理](/guide/rules):按关键词/金额/类型自动归类打标,支持 AI 生成规则、搜索、分页与批量删除
- [邮箱账单](/guide/email-bills):IMAP 自动拉取账单邮件 - [邮箱账单](/guide/email-bills):IMAP 自动拉取账单邮件
## 智能与自动化 ## 智能与自动化
- [AI 能力](/guide/ai):分类标签与渠道建议、自然语言查询与记账、多图图片记账(实时进度)、AI 搜索与规则生成、用量统计 - [AI 能力](/guide/ai):分类标签与渠道建议、自然语言查询与记账、多图图片记账(实时进度)、AI 搜索与规则生成、关系命名、用量统计
- [智能查数](/guide/sql-query):用自然语言生成只读 SQL 查询账本,结果支持卡片/表格/图表
- [定时提醒与通知](/guide/notifications):多渠道账单导入提醒 - [定时提醒与通知](/guide/notifications):多渠道账单导入提醒
## 系统 ## 系统
- [系统设置](/guide/settings):数据库、注册、账单来源统计、外观、AI、通知配置 - [设置](/guide/settings):账号安全、数据库、注册、账单来源统计、外观、导航、AI、通知配置
- [系统日志](/guide/logs):访问日志、登录日志、运行日志 - [日志](/guide/logs):访问日志、登录日志、运行日志
## 账单获取 ## 账单获取
+5 -4
View File
@@ -18,13 +18,14 @@
| 收支记录 | 增删改查、分页与多条件筛选、批量设置分类/标签、批量删除、计入统计开关、备注隐藏、导出 Excel | | 收支记录 | 增删改查、分页与多条件筛选、批量设置分类/标签、批量删除、计入统计开关、备注隐藏、导出 Excel |
| 分类与标签 | 收入/支出分类与标签的增删改查,新用户自动获得默认数据,AI 推荐 Emoji 与颜色 | | 分类与标签 | 收入/支出分类与标签的增删改查,新用户自动获得默认数据,AI 推荐 Emoji 与颜色 |
| 交易渠道 | 内置渠道 + 自定义渠道的新建/改名/删除,改名不影响历史账单 | | 交易渠道 | 内置渠道 + 自定义渠道的新建/改名/删除,改名不影响历史账单 |
| 统计图表 | 月度收支、分类占比、收支趋势、交易渠道占比,支持按日/周/月/季/年粒度,点击趋势图可下钻对应明细 | | 统计图表 | 月度收支、分类占比、收支趋势、交易渠道占比,支持按日/周/月/季/年粒度,图表可点击下钻对应明细 |
| 账单关系 | 父子/同级多笔账单关联、自动绘制关系图,AI 生成关系名称与备注 |
| 账单导入 | 七类账单解析、zip 解密、解析预览、按交易号去重、规则建议 | | 账单导入 | 七类账单解析、zip 解密、解析预览、按交易号去重、规则建议 |
| 自定义规则 | 关键词/收支类型/金额区间组合,优先级排序、启用停用,导入时自动应用;支持 AI 生成规则、搜索与分页 | | 自定义规则 | 关键词/收支类型/金额区间组合,优先级排序、启用停用,导入时自动应用;支持 AI 生成规则、搜索、分页与批量删除 |
| AI 能力 | 分类标签与渠道建议、自然语言查询、文字/多图图片记账、AI 统计搜索、AI 生成规则、分类/标签/渠道生成、Emoji/颜色推荐、用量统计 | | AI 能力 | 分类标签与渠道建议、自然语言查询、智能查数(自然语言转只读 SQL)、文字/多图图片记账、AI 统计搜索、AI 生成规则、分类/标签/渠道生成、关系命名、Emoji/颜色推荐、用量统计 |
| 邮箱账单 | 多邮箱 IMAP IDLE 监听、发件人规则识别账单类型、待处理列表先预览再导入 | | 邮箱账单 | 多邮箱 IMAP IDLE 监听、发件人规则识别账单类型、待处理列表先预览再导入 |
| 定时通知 | 每天/每周/每月提醒,邮箱 SMTP 与 Server 酱渠道,发送日志 | | 定时通知 | 每天/每周/每月提醒,邮箱 SMTP 与 Server 酱渠道,发送日志 |
| 系统日志 | 访问日志、登录日志、运行日志在线查看,运行日志实时推送,自动清理 | | 日志 | 访问日志、登录日志、运行日志在线查看,运行日志实时推送,自动清理 |
| 账号与多语言 | JWT 登录、注册开关、运行时数据库配置(SQLite/MySQL)、中/英/日三语 | | 账号与多语言 | JWT 登录、注册开关、运行时数据库配置(SQLite/MySQL)、中/英/日三语 |
## 技术栈 ## 技术栈
+2 -2
View File
@@ -1,6 +1,6 @@
# 系统日志 # 日志
系统日志通过右上角用户菜单 →「系统日志」进入,包含三个标签页:**访问日志**、**登录日志**、**运行日志**。页头右上角可选择日志保留时长(30 天 / 90 天 / 180 天 / 永久保留)。 日志通过右上角用户菜单 →「日志」进入,包含三个标签页:**访问日志**、**登录日志**、**运行日志**。页头右上角可选择日志保留时长(30 天 / 90 天 / 180 天 / 永久保留)。
## 访问日志 ## 访问日志
+2 -2
View File
@@ -1,8 +1,8 @@
# 定时提醒与通知 # 定时提醒与通知
系统可按周期提醒你导入新账单,并通过邮箱或 Server 酱推送通知。配置入口为「系统设置 → 定时通知」。 系统可按周期提醒你导入新账单,并通过邮箱或 Server 酱推送通知。配置入口为「设置 → 定时通知」。
![系统设置](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/settings.png) ![设置](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/settings.png)
## 提醒周期 ## 提醒周期
+20 -12
View File
@@ -10,30 +10,35 @@
| 注册 | `/register` | 创建账号 | 登录页「没有账号?去注册」(注册开启时) | | 注册 | `/register` | 创建账号 | 登录页「没有账号?去注册」(注册开启时) |
| 数据库初始化 | `/database-setup` | 首次配置数据库 | 数据库未配置时自动引导 | | 数据库初始化 | `/database-setup` | 首次配置数据库 | 数据库未配置时自动引导 |
| 统计面板 | `/dashboard` | 收支汇总与图表 | 登录后默认页;左侧菜单 | | 统计面板 | `/dashboard` | 收支汇总与图表 | 登录后默认页;左侧菜单 |
| 智能查数 | `/smart-query` | 自然语言转只读 SQL 查询 | 左侧菜单(**AI 启用时**) |
| 收支记录 | `/transactions` | 记录管理与**账单导入** | 左侧菜单 | | 收支记录 | `/transactions` | 记录管理与**账单导入** | 左侧菜单 |
| 分类管理 | `/categories` | 收入/支出分类维护 | 左侧菜单 | | 分类管理 | `/categories` | 收入/支出分类维护 | 左侧菜单 |
| 标签管理 | `/tags` | 标签维护 | 左侧菜单 | | 标签管理 | `/tags` | 标签维护 | 左侧菜单 |
| 交易渠道管理 | `/channels` | 自定义交易渠道维护 | 收支记录页「渠道管理」按钮 | | 交易渠道管理 | `/channels` | 自定义交易渠道维护 | 收支记录页「渠道管理」按钮 |
| 规则管理 | `/rules` | 自动归类规则 | 左侧菜单 | | 规则管理 | `/rules` | 自动归类规则 | 左侧菜单 |
| 邮箱账单 | `/email-bills/*` | 邮箱配置 / 发件人规则 / 待处理账单 | 左侧菜单 | | 邮箱账单 | `/email-bills/*` | 邮箱配置 / 发件人规则 / 待处理账单 | 左侧菜单 |
| 系统设置 | `/settings` | 数据库 / 注册 / 账单来源统计 / 外观 / AI / 通知配置 | 顶部用户菜单 | | 设置 | `/settings` | 账号安全 / 数据库 / 注册 / 账单来源统计 / 外观 / 导航 / AI / 通知配置 | 顶部用户菜单 |
| 模型使用情况 | `/ai-usage` | AI 调用统计与明细 | 设置页「AI 模型配置」→ 使用情况 | | 模型使用情况 | `/ai-usage` | AI 调用统计与明细 | 设置页「AI 模型配置」→ 使用情况 |
| 系统日志 | `/logs/*` | 访问 / 登录 / 运行日志 | 顶部用户菜单 | | 日志 | `/logs/*` | 访问 / 登录 / 运行日志 | 顶部用户菜单 |
| 账单导入(独立页) | `/bill-import` | 手机端上传账单 | **仅由通知邮件链接直达** | | 账单导入(独立页) | `/bill-import` | 手机端上传账单 | **仅由通知邮件链接直达** |
> 「账单关系」不是独立页面,它是收支记录内的弹窗:由列表「关系」列或「批量操作 → 建立关系」进入,详见[账单关系](/guide/relations)。
## 全局导航 ## 全局导航
### 左侧菜单 ### 左侧菜单
统计面板 → 收支记录 → 分类管理 → 标签管理 → 规则管理 → 邮箱账单 统计面板 → 智能查数(AI 启用时)→ 收支记录 → 分类管理 → 标签管理 → 规则管理 → 邮箱账单
左侧菜单的**顺序与显隐可自定义**(设置 → 导航设置),并在下次进入时还原。
### 顶部用户菜单 ### 顶部用户菜单
- **系统日志**:`/logs` - **日志**:`/logs`
- **系统设置**:`/settings` - **设置**:`/settings`
- **退出**:清空登录态并返回 `/login` - **退出**:清空登录态并返回 `/login`
> 「模型使用情况」没有独立菜单入口,需从「系统设置 → AI 模型配置 → 使用情况」进入。 > 「模型使用情况」没有独立菜单入口,需从「设置 → AI 模型配置 → 使用情况」进入。
> >
> 「交易渠道管理」同样没有独立菜单入口,需从「收支记录 → 渠道管理」进入。 > 「交易渠道管理」同样没有独立菜单入口,需从「收支记录 → 渠道管理」进入。
@@ -41,20 +46,23 @@
- **首次使用**:`/database-setup` → `/register` → `/login` → `/dashboard` - **首次使用**:`/database-setup` → `/register` → `/login` → `/dashboard`
- **手动记账**:收支记录 → 点击「新增记录」→ 表单弹窗 → 保存 - **手动记账**:收支记录 → 点击「新增记录」→ 表单弹窗 → 保存
- **查看趋势明细**:统计面板 → 点击「收支趋势」某一列(柱子或该列空白处)→ 收支记录(自动带入时间范围、类型与当前筛选条件) - **查看趋势明细**:统计面板 → 点击趋势柱、分类/渠道饼图或「查看收支记录」→ 收支记录(自动带入对应筛选条件)
- **反向跳转**:收支记录 → 点击「查看统计」→ 统计面板(还原当前筛选条件)
- **账单导入**:收支记录 → 点击「导入账单」→ 解析预览 → 确认导入 → 回到收支记录 - **账单导入**:收支记录 → 点击「导入账单」→ 解析预览 → 确认导入 → 回到收支记录
- **手机导入**:通知邮件链接 → `/bill-import`(独立页,非弹窗)→ 上传解析 → 确认导入 - **手机导入**:通知邮件链接 → `/bill-import`(独立页,非弹窗)→ 上传解析 → 确认导入
- **渠道维护**:收支记录 → 点击「渠道管理」→ 新建 / 改名 / 删除自定义渠道 → 返回收支记录 - **渠道维护**:收支记录 → 点击「渠道管理」→ 新建 / 改名 / 删除自定义渠道 → 返回收支记录
- **账单关系**:收支记录「关系」列 → 关系弹窗;或「批量操作」勾选多条 → 建立关系
- **智能查数**:左侧菜单「智能查数」→ 输入自然语言 → 查看结果、生成的 SQL 与历史提问
- **自动归类**:规则管理配置规则 → 账单导入预览时命中并给出建议 - **自动归类**:规则管理配置规则 → 账单导入预览时命中并给出建议
- **邮箱账单**:邮箱配置 → 发件人规则 → 待处理账单 → 解析 → 导入到收支记录 - **邮箱账单**:邮箱配置 → 发件人规则 → 待处理账单 → 解析 → 导入到收支记录
- **AI 辅助**:系统设置启用 AI → 统计面板「AI 搜索」、收支记录「一键设置 / 文字·图片记账 / AI 查询」、编辑记录「分类·标签·渠道 AI 推荐与新建」、分类与标签「AI 推荐」、规则管理「AI 生成规则」 - **AI 辅助**:设置启用 AI → 统计面板「AI 搜索」、收支记录「一键设置 / 文字·图片记账 / AI 查询」、编辑记录「分类·标签·渠道 AI 推荐与新建」、分类与标签「AI 推荐」、规则管理「AI 生成规则」、账单关系「AI 生成」、左侧「智能查数」
- **系统维护**:顶部菜单 → 系统设置 / 系统日志 - **系统维护**:顶部菜单 → 设置 / 日志
## 页面关系说明 ## 页面关系说明
- **收支记录**是核心页面:手动记账、账单导入、批量设置、AI 能力都从这里进入,导入完成后也回到该页查看。 - **收支记录**是核心页面:手动记账、账单导入、批量操作、账单关系与 AI 能力都从这里进入,导入完成后也回到该页查看。
- **账单导入**有两条独立路径: - **账单导入**有两条独立路径:
- 收支记录页的「导入账单」**弹窗**(桌面端常用); - 收支记录页的「导入账单」**弹窗**(桌面端常用);
- 通知邮件直达的 **`/bill-import` 独立页**(面向手机,非弹窗,元素精简,系统内无其他入口)。 - 通知邮件直达的 **`/bill-import` 独立页**(面向手机,非弹窗,元素精简,系统内无其他入口)。
- **系统设置**聚合了数据库、注册、账单来源统计、外观、AI、通知六类配置;模型用量与日志分别从设置页与顶部菜单进入。 - **设置**聚合了账号安全、数据库、注册、账单来源统计、外观、导航、AI、通知八类配置;模型用量与日志分别从设置页与顶部菜单进入。
- **交易渠道管理**是收支记录页的附属页,用于维护自定义渠道,维护完成后返回收支记录继续记账或筛选。 - **交易渠道管理**是收支记录页的附属页;**账单关系**是收支记录内的弹窗,两者都在整理完成后回到收支记录继续操作。
+68
View File
@@ -0,0 +1,68 @@
# 账单关系
「账单关系」用于把**互相关联的多笔收支**组合在一起,例如「交通卡充值」与后续的乘车扣款、分期还款的每一期、支付链路上的多笔记录。建立关系只做关联,不会改动金额,也不影响统计。
## 两种关系类型
| 类型 | 含义 | 典型场景 |
|---|---|---|
| **父子关系** | 一个「父账单」(资金池)对应多个「子账单」(从资金池流出) | 交通卡充值 → 多次乘车;账户充值 → 逐笔消费 |
| **同级关系** | 多笔地位平等的账单聚合在一起 | 一笔消费在多个平台留下的关联记录、分次归还 |
- 父子关系中,**必须且只能有一条父账单**,其余为子账单。
- 同级关系中,所有成员地位相同,按加入顺序依次连接。
## 入口
| 入口 | 说明 |
|---|---|
| 收支记录列表「**关系**」列 | 该行所属关系组数量,点击「N 个关系」打开关系管理弹窗 |
| 收支记录「**批量操作**」 | 进入多选模式,勾选**至少两条**后点「建立关系」,直接新建关系组 |
| 账单**详情**弹窗 | 「账单关系」分组内查看该笔账单的全部关系 |
![新建账单关系弹窗](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/relation-editor.png)
## 新建与编辑关系
1. 选择**关系类型**(父子关系 / 同级关系)。
2. 填写**关系名称**(可选,如「交通卡充值」)与**备注**。
3. 点「添加账单」,在分页选择弹窗中勾选要关联的账单(支持备注搜索)。
4. 父子关系下,用单选指定其中一条为**父账单**(从批量操作进入时,默认以被操作的那条为父账单,且不可移除);同级关系无需指定,成员按列表顺序排列。
5. 点击「保存」。
约束与说明:
- 一个关系组**至少包含两条账单**,成员上限 50 条。
- **一笔账单不能同时加入两个同类型的关系组**;如需更换,请先在原关系组中解除。
- **关系名称会记住历史**:再次新建同名类型的关系时,名称输入框会自动给出历史名称建议(按最近使用、去重,最多 50 条)。
- 关系类型会被**记忆**:下次新建时默认沿用上次选择的类型。
## 关系图
关系管理弹窗中,每个关系组都会以自绘的关系图展示:
- 节点为圆形,**支出为红色、收入为绿色**;当前正在查看的账单以蓝色描边且节点更大。
- **父子关系**:父账单居中在上,子账单分行排列,父 → 子连线带箭头。
- **同级关系**:成员按顺序链式连接,边上标注「关联」。
- 节点内显示金额,节点下方显示备注或分类;鼠标悬停可查看完整信息。成员较多时自动换行,图形自适应宽度。
![账单关系图](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/relation-panel.png)
## AI 生成名称与备注
启用 AI 后,新建/编辑关系弹窗中会出现「**AI 生成**」按钮。系统会把成员账单的**收支类型、交易渠道、分类名称与备注**发送给模型(**不包含金额**),由其归纳出一个关系名称与简短备注,并自动填入;你可以在保存前修改。
> 成员少于两条时无法生成,会提示先添加账单。生成的名称会覆盖名称框(备注仅在返回非空时覆盖)。
## 删除关系
在关系组卡片上点「删除」,确认后**仅解除关联**,不会删除任何账单。删除单笔账单时,系统会自动把它从所属关系组中移除;若某个关系组剩余成员不足两条、或父子关系不再有父账单,该关系组会自动解散。
## 与统计的关系
账单关系只用于**查看与整理**,不参与统计口径。每笔账单是否计入统计,仍由各自的「计入统计」开关决定,详见[收支记录](/guide/transactions)。
## 相关
- [收支记录](/guide/transactions):记录管理与批量操作。
- [AI 能力](/guide/ai):AI 生成关系名称、分类标签建议等。
+9
View File
@@ -52,6 +52,15 @@
点击「搜索」按条件查询,「清空」重置全部条件。新增规则后回到第一页,便于查看新规则。 点击「搜索」按条件查询,「清空」重置全部条件。新增规则后回到第一页,便于查看新规则。
## 多选与批量删除
规则列表每行前都有勾选框,表头可**全选当前页**:
- 勾选若干规则后,点右上角「**批量删除**」并确认,即可一次性删除选中的规则。
- 未勾选任何规则时「批量删除」不可点击。
> 删除规则只影响后续导入时的自动归类,已应用的历史记录分类与标签不会被撤销。
## AI 生成规则 ## AI 生成规则
启用 AI 后,规则管理页右上角会出现「**AI 生成规则**」按钮,可让 AI 依据**已打标签的记录**自动归纳出「关键词 → 分类/标签」规则,无需手工逐条编写。 启用 AI 后,规则管理页右上角会出现「**AI 生成规则**」按钮,可让 AI 依据**已打标签的记录**自动归纳出「关键词 → 分类/标签」规则,无需手工逐条编写。
+45 -6
View File
@@ -1,8 +1,20 @@
# 系统设置 # 设置
系统设置通过右上角用户菜单 →「系统设置」进入,包含六个配置面板。 「设置」通过右上角用户菜单 →「设置」进入,包含八个配置面板。
![系统设置](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/settings.png) ![设置](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/settings.png)
## 账号安全
![账号安全面板](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/panel-account-security.png)
用于**修改当前登录账号的密码**:
- 输入**原密码**、**新密码**与**确认新密码**,点「修改密码」提交。
- 新密码长度需为 **6–50 个字符**,且不能与原密码相同。
- 可点「**随机生成**」自动生成一个 12 位、含大小写字母与数字的密码(已排除易混淆字符),生成后会明文显示并可一键**复制**;修改成功后请使用新密码登录。
> 修改密码仅校验原密码,**不提供**通过邮箱等方式的密码找回。
## 数据库配置 ## 数据库配置
@@ -13,6 +25,8 @@
- **MySQL**:主机、端口、数据库名、用户名、密码。 - **MySQL**:主机、端口、数据库名、用户名、密码。
- 提供「测试连接」与「保存并应用」。保存后立即生效并自动建表;已配置时密码留空表示不修改。 - 提供「测试连接」与「保存并应用」。保存后立即生效并自动建表;已配置时密码留空表示不修改。
测试与保存时会实时展示**执行进度**:连接测试、建表、结构迁移、数据源切换等步骤会以进度日志逐条推送,处理较久时请保持窗口打开,完成后点「关闭」即可。
系统支持启动后再配置数据库:未配置时前端会引导进入初始化页面,无需启动前准备。登录后也可随时切换数据库,切换后原数据库数据保留、不做自动迁移。 系统支持启动后再配置数据库:未配置时前端会引导进入初始化页面,无需启动前准备。登录后也可随时切换数据库,切换后原数据库数据保留、不做自动迁移。
## 注册设置 ## 注册设置
@@ -41,12 +55,37 @@
![外观设置面板](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/panel-display.png) ![外观设置面板](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/panel-display.png)
选择「不计入统计」的记录在[收支记录](/guide/transactions)列表中的展示方式: 外观设置包含三组配置,**修改后自动保存**,无需再点保存按钮:
**1. 不计入统计的记录样式**:选择「不计入统计」的记录在[收支记录](/guide/transactions)列表中的展示方式:
- **删除线(默认)**:整行加删除线,类型与金额列保持原样。 - **删除线(默认)**:整行加删除线,类型与金额列保持原样。
- **灰色标签**:类型列显示灰色「不计入」标签,金额列置灰且不显示正负号。 - **灰色标签**:类型列显示灰色「不计入」标签,金额列置灰且不显示正负号。
面板内提供**实时预览**,切换选项即可看到效果;该偏好按**当前账号**保存,登录后生效。 面板内提供**实时预览**,切换选项即可看到效果。
**2. 显示已隐藏备注的记录**(默认关闭):
- 关闭时,备注被隐藏的记录不会出现在收支记录列表与 Excel 导出中。
- 开启后,这些记录会正常显示(备注仍为 `******`,可临时「查看」)。
**3. 表格每页条数**:
- **表格每页条数选项**:所有表格分页共用的候选列表,默认 `10 / 20 / 50 / 100`,可自行增删(最多 10 项)。
- **默认每页条数**:从候选中选择,作为各表格未单独记忆时的默认值(默认 20)。
- 每张表格会分别记住上次选择的每页条数;本机记忆优先于全局默认值。
## 导航设置
![导航设置面板](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/panel-nav-menu.png)
调整左侧导航的**显示顺序与显隐**(按当前账号保存,跨设备生效):
- **拖动**每项左侧的手柄即可调整顺序,修改后自动保存。
- 用开关控制各项的显示或隐藏。
- 至少需保留一个可见项;「**恢复默认**」可还原为默认顺序与全部显示。
> 隐藏只移除菜单入口,页面仍可通过地址直接访问。当 AI 未启用时,依赖 AI 的菜单项(如「智能查数」)不会出现在此处,也不会显示在左侧导航。
## AI 模型配置 ## AI 模型配置
@@ -68,5 +107,5 @@
## 相关 ## 相关
- [系统日志](/guide/logs):查看访问、登录与运行日志。 - [日志](/guide/logs):查看访问、登录与运行日志。
- [快速开始](/guide/getting-started):数据库配置与注册登录。 - [快速开始](/guide/getting-started):数据库配置与注册登录。
+74
View File
@@ -0,0 +1,74 @@
# 智能查数
「智能查数」让你用**自然语言**直接查询账本,由 AI 生成一条**只读 SQL** 并执行,结果以统计卡片、表格或图表展示,无需自己拼筛选条件。
> 这是与[统计面板的「AI 搜索」](/guide/dashboard#ai-搜索)不同的能力:AI 搜索是「自然语言 → 统计面板筛选条件」,智能查数则是「自然语言 → SQL 查询」,适合更自由、更灵活的问答。
## 入口与前提
- 先在[设置](/guide/settings#ai-模型配置)中**启用 AI** 并配置 API Key。
- 启用后,左侧菜单会出现「**智能查数**」(未启用时不显示)。
- 直接访问路由 `/smart-query` 也可进入。
![智能查数](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/sql-query.png)
## 提问
在页面顶部的输入框用一句自然语言描述想查的内容,例如:
- 上个月一共花了多少钱
- 按分类统计今年的支出
- 今年收入最高的一个月
- 各渠道的支出占比
- 本月支出最高的 5 笔
输入框支持多行(自动增高),点击「查询」或按 `Ctrl + Enter`(Mac 为 `⌘ + Enter`)提交;页面也会给出示例问题,点击即可直接查询。
## 结果展示
系统会根据查询结果自动选择展示方式:
| 结果形态 | 展示 |
|---|---|
| 只有一行 | **统计卡片**,逐项展示各列的数值 |
| 多行且含数值列 | **动态表格**,可切换为**柱状图**或**饼图** |
| 多行但无数值列 | 表格 |
- 结果中的**渠道**列会自动显示渠道名称(含自定义渠道),而不是内部编码。
- 结果最多返回 **1000 行**;超过时会提示「仅展示前 1000 行」。
- 无结果的查询会显示空态提示。
## 生成的 SQL
查询结果上方会给出 AI 生成的 SQL,默认折叠:
- 使用 `sql-formatter` 缩进、`highlight.js` 语法高亮,便于阅读。
- 可点「复制 SQL」把格式化后的语句复制到剪贴板。
- 同时展示对查询意图的简要说明。
## 历史提问
页面下方是**历史提问**折叠面板,按账号在服务端持久化:
- 展示提问内容与时间(精确到分钟),默认展示最近若干条。
- 支持按关键字**搜索**历史。
- 点击某条历史可**回填并立即重新查询**;也可复制该次提问。
- 可一键**清空历史**。
> 当前会话内的提问与结果会缓存在浏览器会话中,切换账号时自动清除;结果超过 500 行时只缓存提问与视图,避免占用过多空间。
## 安全与隐私
智能查数始终以**只读**方式访问你自己的数据:
- 仅允许**单条 `SELECT`** 查询,拒绝任何写操作、DDL、多语句、`SELECT INTO`、`FOR UPDATE` 与危险函数。
- 只能查询**系统白名单内的数据表**,且每张表都会被强制注入当前用户的隔离条件,**只能看到本人数据**。
- 查询**不返回原始数据(`rawData`)与交易号**等敏感字段。
- 生成的 SQL 会经过安全网关校验后才执行,并限制执行时间与返回行数。
AI 本身收到的信息仅为你的自然语言提问与可查询的表结构说明,不包含账单原始字段。
## 相关
- [AI 能力](/guide/ai):系统全部 AI 功能一览。
- [统计面板](/guide/dashboard):固定的统计汇总与图表。
+25 -15
View File
@@ -9,8 +9,9 @@
| 按钮 | 功能 | | 按钮 | 功能 |
|---|---| |---|---|
| 排序 | 交易时间(新→旧 / 旧→新)、金额(高→低 / 低→高) | | 排序 | 交易时间(新→旧 / 旧→新)、金额(高→低 / 低→高) |
| 查看统计 | 携带当前筛选条件跳转到[统计面板](/guide/dashboard) |
| 一键设置 | 对当前表格展示的记录批量设置分类、计入统计与标签 | | 一键设置 | 对当前表格展示的记录批量设置分类、计入统计与标签 |
| 批量删除 | 删除表格中勾选的记录 | | 批量操作 / 退出批量 | 进入多选模式,可批量删除或为选中的多条记录[建立账单关系](/guide/relations) |
| 导入账单 | 打开账单导入弹窗 | | 导入账单 | 打开账单导入弹窗 |
| 渠道管理 | 进入[交易渠道管理](/guide/channels)页,维护自定义渠道 | | 渠道管理 | 进入[交易渠道管理](/guide/channels)页,维护自定义渠道 |
| 导出 Excel | 按当前筛选条件导出 | | 导出 Excel | 按当前筛选条件导出 |
@@ -18,6 +19,8 @@
| 智能记账 | 用一句话描述收支,AI 解析后预填表单(需启用 AI) | | 智能记账 | 用一句话描述收支,AI 解析后预填表单(需启用 AI) |
| 新增记录 | 手动新增一笔记录 | | 新增记录 | 手动新增一笔记录 |
> 默认为普通模式,表格不显示勾选列;点「批量操作」后才进入多选模式,勾选记录后可「批量删除」,或勾选至少两条后「建立关系」。
## 账单导入 ## 账单导入
「收支记录」页支持把支付宝、微信、美团、淘宝、京东、招商银行、交通银行的账单文件解析并导入为收支记录。 「收支记录」页支持把支付宝、微信、美团、淘宝、京东、招商银行、交通银行的账单文件解析并导入为收支记录。
@@ -95,35 +98,42 @@
**更多筛选**:标签(含「无标签」)、计入统计、金额区间。 **更多筛选**:标签(含「无标签」)、计入统计、金额区间。
启用 AI 后,顶部会出现 **AI 智能查询**输入框,可用自然语言查询,例如: 启用 AI 后,顶部会出现「**AI 搜索**」入口,点击打开一个**可拖拽、可缩放的浮窗**(非模态,打开后仍可继续操作主界面)。在浮窗中用自然语言查询,例如:
- 「上个月餐饮」 - 「上个月餐饮」
- 「超过500的支出」 - 「超过500的支出」
- 「待报销的支出」 - 「待报销的支出」
- 「微信支付」 - 「微信支付」
解析结果会直接应用为筛选条件并立即查询。筛选条件与每页条数、排序方式会自动记忆,下次进入自动还原。 浮窗输入框支持多行,按 `Ctrl + Enter`(Mac 为 `⌘ + Enter`)提交;解析结果会直接应用为筛选条件并立即查询。浮窗的位置与大小会被记住,输入时还能快速选择历史提问。筛选条件与每页条数、排序方式会自动记忆,下次进入自动还原。
![AI 查询浮窗](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/ai-query-window.png)
## 表格与行操作 ## 表格与行操作
表格展示:交易时间、类型、分类、金额、交易渠道、备注、标签、来源图片、计入统计开关与操作。 表格展示:交易时间、类型、分类、金额、交易渠道、备注、标签、计入统计开关、关系与操作。
- **备注隐藏**:可将敏感备注持久隐藏为 `******`,需要时临时「查看」或「取消隐藏」。 - **备注隐藏**:可将敏感备注持久隐藏为 `******`,需要时临时「查看」或「取消隐藏」。默认这些记录会从列表与 Excel 导出中排除,可在[设置 → 外观设置](/guide/settings#外观设置)中开启「显示已隐藏备注的记录」。
- **计入统计**:行内开关可直接切换;关闭后该记录不纳入月度统计(适合借款、转账等),行内会以删除线标示。 - **计入统计**:行内开关可直接切换;关闭后该记录不纳入月度统计(适合借款、转账等),行内会以删除线标示(样式可在外观设置中调整)。
- **来源图片**:图片记账生成的记录会显示「查看(N)」入口,弹窗内先加载缩略图,可按需「加载原图」查看对应截图。 - **关系**:展示该记录所属[账单关系](/guide/relations)组的数量,点击可查看与管理。
- **行操作**:编辑、查看「原始数据」(账单导入或图片记账时的原始字段,以 JSON 展示)、删除。 - **行操作**:编辑、详情、删除。
![来源图片弹窗](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/source-images.png) ### 账单详情
分页支持每页 10/20/50/100/200/500 条。 点击行内「**详情**」打开「账单详情」弹窗,集中展示单条账单的以下信息:
## 查看原始数据 - **基本信息**:类型、金额、分类、交易渠道、交易时间、是否计入统计、备注与创建时间。
- **标签**:该记录的全部标签。
- **账单关系**:所属的[账单关系](/guide/relations)组,可在弹窗内直接新建。
- **来源图片**:图片记账生成的记录会展示其来源截图,默认加载缩略图,可按需点「加载原图」查看原图。
- **原始数据**:账单导入或图片记账时保留的原始字段(以 JSON 展示),用于核对金额、交易状态、对方账号、交易类型等明细。
账单导入或图片记账时,系统会完整保留每笔交易在原始账单中的字段(以 JSON 存储)。在记录行点击「**原始数据**」即可查看,用于核对金额、交易状态、对方账号、交易类型等明细。 ![账单详情弹窗](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/bill-detail.png)
- **账单导入**记录保留原始账单行字段;**图片记账**记录保留模型从截图提取的原始字段。 > - **账单导入**记录保留原始账单行字段;**图片记账**记录保留模型从截图提取的原始字段;**手动新增**的记录没有来源图片与原始数据。
- **手动新增**的记录没有原始数据,该按钮不可用。 > - 原始数据是排查导入异常、核对账单的重要依据。
- 原始数据是排查导入异常、核对账单的重要依据。
分页的每页条数**候选与默认值在[设置 → 外观设置](/guide/settings#外观设置)中统一配置**(默认候选 10 / 20 / 50 / 100,默认 20);各表格会分别记住上次选择的每页条数。
## 一键设置(批量设置) ## 一键设置(批量设置)
+6 -4
View File
@@ -4,7 +4,7 @@ layout: home
hero: hero:
name: 汇账 name: 汇账
text: 个人收支管理 · 多来源账单导入 · AI 智能记账 text: 个人收支管理 · 多来源账单导入 · AI 智能记账
tagline: 支持支付宝、微信、美团、淘宝、京东、招商银行、交通银行账单导入,提供分类标签与交易渠道管理、自定义规则、AI 建议(含多图图片记账)与定时提醒 tagline: 支持支付宝、微信、美团、淘宝、京东、招商银行、交通银行账单导入,提供分类标签与交易渠道管理、自定义规则、账单关系、AI 建议(含多图图片记账与智能查数)与定时提醒
image: image:
src: https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/dashboard.png src: https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/dashboard.png
alt: 统计面板 alt: 统计面板
@@ -22,6 +22,8 @@ hero:
features: features:
- title: 多来源账单导入 - title: 多来源账单导入
details: 支持支付宝、微信、美团、淘宝、京东、招商银行、交通银行账单,解析预览后确认导入,按交易号自动去重。 details: 支持支付宝、微信、美团、淘宝、京东、招商银行、交通银行账单,解析预览后确认导入,按交易号自动去重。
- title: 账单关系
details: 把父子/同级的多笔账单组合关联,自动绘制关系图,并可由 AI 生成关系名称与备注。
- title: 分类与标签 - title: 分类与标签
details: 自定义收入/支出分类与标签,支持批量设置,AI 可推荐 Emoji 图标与标签颜色。 details: 自定义收入/支出分类与标签,支持批量设置,AI 可推荐 Emoji 图标与标签颜色。
- title: 交易渠道管理 - title: 交易渠道管理
@@ -29,14 +31,14 @@ features:
- title: 自定义规则 - title: 自定义规则
details: 按关键词、收支类型、金额区间组合条件,命中后自动应用分类与标签,导入预览即展示建议。 details: 按关键词、收支类型、金额区间组合条件,命中后自动应用分类与标签,导入预览即展示建议。
- title: AI 智能能力 - title: AI 智能能力
details: 基于 DeepSeek 的分类/标签/渠道建议、自然语言查询、文字与多图图片记账(实时进度)、分类标签渠道生成,并统计 token 用量。 details: 基于 DeepSeek 的分类/标签/渠道建议、自然语言查询与查数(智能查数)、文字与多图图片记账(实时进度)、分类标签渠道生成、关系命名,并统计 token 用量。
- title: 统计与图表 - title: 统计与图表
details: 月度收支汇总、分类占比、收支趋势、支付方式占比,支持多维度筛选与自定义时间范围。 details: 月度收支汇总、分类占比、收支趋势、交易渠道占比,支持多维度筛选与自定义时间范围,图表可点击下钻明细。
- title: 定时提醒与通知 - title: 定时提醒与通知
details: 按每天/每周/每月周期提醒导入新账单,支持邮箱 SMTP 与 Server 酱多渠道推送。 details: 按每天/每周/每月周期提醒导入新账单,支持邮箱 SMTP 与 Server 酱多渠道推送。
- title: 邮箱账单自动拉取 - title: 邮箱账单自动拉取
details: 通过 IMAP IDLE 监听邮箱,新账单邮件到达后自动拉取附件,先预览再确认导入。 details: 通过 IMAP IDLE 监听邮箱,新账单邮件到达后自动拉取附件,先预览再确认导入。
- title: 系统日志 - title: 日志
details: 接口访问日志、登录日志与后端运行日志在线查看,运行日志支持 SSE 实时推送。 details: 接口访问日志、登录日志与后端运行日志在线查看,运行日志支持 SSE 实时推送。
--- ---
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "ledgerly-docs", "name": "ledgerly-docs",
"private": true, "private": true,
"version": "0.0.1", "version": "0.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"docs:dev": "vitepress dev", "docs:dev": "vitepress dev",
+134 -16
View File
@@ -3,7 +3,7 @@ import { resolve } from 'node:path'
import { pathToFileURL } from 'node:url' import { pathToFileURL } from 'node:url'
import type { Page } from 'playwright-core' import type { Page } from 'playwright-core'
import { clickText, injectSession, launchBrowser, login, newContext, waitPage } from './browser.js' import { clickText, injectSession, launchBrowser, login, newContext, waitPage } from './browser.js'
import { BASE_URL, ELEM_DIR, SAMPLE_BILL, SAMPLE_IMAGE, SAMPLE_IMAGE2 } from './config.js' import { BASE_URL, ELEM_DIR, OUT_DIR, SAMPLE_BILL, SAMPLE_IMAGE, SAMPLE_IMAGE2 } from './config.js'
/** 页面就绪标志:列表页表格、设置面板或卡片 */ /** 页面就绪标志:列表页表格、设置面板或卡片 */
const PAGE_READY = '.n-data-table, .config-card, .n-card' const PAGE_READY = '.n-data-table, .config-card, .n-card'
@@ -160,26 +160,59 @@ export async function captureAiRecordImage(page: Page, importRecords = true): Pr
} }
/** /**
* 截取「来源图片」查看弹窗(点击表格中图片记账记录的「查看(N)」)。 * 截取「账单详情」弹窗(含基本信息、标签、账单关系、来源图片与原始数据)。
*
* 优先选择「有来源图片、无账单关系」的记录,使弹窗聚焦来源图片与原始数据。
* *
* @param page Page 实例 * @param page Page 实例
*/ */
export async function captureSourceImages(page: Page): Promise<void> { export async function captureBillDetail(page: Page): Promise<void> {
try { try {
await page.setViewportSize({ width: 1440, height: 1000 }) await page.setViewportSize({ width: 1440, height: 1200 })
await page.goto(BASE_URL + '/transactions') await page.goto(BASE_URL + '/transactions')
await waitPage(page, { selector: '.n-data-table-tbody tr' }) await waitPage(page, { selector: '.n-data-table-tbody tr' })
const viewBtn = page.locator('button', { hasText: /查看(\d+)/ }).first() const rows = page.locator('.n-data-table-tbody tr')
await viewBtn.waitFor({ state: 'visible', timeout: 20000 }) const count = await rows.count()
await viewBtn.evaluate((el) => (el as HTMLElement).click()) for (let i = 0; i < count; i++) {
const modal = page.locator('.n-modal', { hasText: '来源图片' }).first() const detailBtn = rows.nth(i).locator('button', { hasText: '详情' }).first()
if ((await detailBtn.count()) === 0) continue
await detailBtn.evaluate((el) => (el as HTMLElement).click())
const modal = page.locator('.n-modal', { hasText: '账单详情' }).first()
await modal.waitFor({ state: 'visible', timeout: 20000 }) await modal.waitFor({ state: 'visible', timeout: 20000 })
await page.waitForLoadState('networkidle', { timeout: 20000 }).catch(() => undefined) await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => undefined)
await page.waitForTimeout(1000) await page.waitForTimeout(800)
await modal.screenshot({ path: resolve(ELEM_DIR, 'source-images.png') })
console.log('[OK] modal/source-images') const imageSection = modal.locator('.detail-section', { hasText: '来源图片' }).first()
const imgCount = await imageSection.locator('img').count()
const relationSection = modal.locator('.detail-section', { hasText: '账单关系' }).first()
const relationText = (await relationSection.innerText().catch(() => '')) || ''
const noRelation = relationText.includes('暂无关联账单')
if (imgCount > 0 && noRelation) {
// 加大视口并取消弹窗内部滚动,使来源图片与原始数据一并入镜
await page.setViewportSize({ width: 1440, height: 2400 })
await page.evaluate(() => {
document
.querySelectorAll<HTMLElement>('.n-modal, .n-modal .n-card, .n-modal .n-card__content')
.forEach((el) => {
el.style.maxHeight = 'none'
el.style.overflow = 'visible'
})
})
await page.waitForTimeout(500)
await modal.screenshot({ path: resolve(ELEM_DIR, 'bill-detail.png') })
console.log('[OK] modal/bill-detail')
return
}
// 不满足条件则关闭弹窗,继续查看下一行
await page.locator('.n-modal .n-base-close').first().click().catch(() => undefined)
await page.keyboard.press('Escape').catch(() => undefined)
await page.waitForTimeout(400)
}
throw new Error('未找到「有来源图片且无账单关系」的记录')
} catch (err) { } catch (err) {
console.log(`[FAIL] modal/source-images: ${err}`) console.log(`[FAIL] modal/bill-detail: ${err}`)
} }
} }
@@ -218,12 +251,12 @@ export async function captureTrendJump(page: Page): Promise<void> {
await waitPage(page, { selector: 'canvas' }) await waitPage(page, { selector: 'canvas' })
// 切到「近12月 + 按月」,柱子更宽便于稳定点中;同时清空收支记录筛选偏好,避免旧条件干扰 // 切到「近12月 + 按月」,柱子更宽便于稳定点中;同时清空收支记录筛选偏好,避免旧条件干扰
await page.evaluate(() => { await page.evaluate(() => {
localStorage.setItem('finance.dashboard.prefs', JSON.stringify({ localStorage.setItem('ledgerly.dashboard.prefs', JSON.stringify({
rangeType: '12month', granularity: 'month', filterStatsScope: 'INCLUDED', rangeType: '12month', granularity: 'month', filterStatsScope: 'INCLUDED',
filterType: [], filterCategory: [], filterChannel: [], filterTag: [], filterType: [], filterCategory: [], filterChannel: [], filterTag: [],
filterMinAmount: null, filterMaxAmount: null, filterRemark: '', filterMinAmount: null, filterMaxAmount: null, filterRemark: '',
})) }))
localStorage.removeItem('finance.transaction.filterPrefs') localStorage.removeItem('ledgerly.transaction.filterPrefs')
}) })
await page.reload() await page.reload()
await waitPage(page, { selector: 'canvas' }) await waitPage(page, { selector: 'canvas' })
@@ -255,10 +288,12 @@ export async function captureTrendJump(page: Page): Promise<void> {
/** 系统设置面板:输出文件名与面板标题 */ /** 系统设置面板:输出文件名与面板标题 */
const SETTINGS_PANELS: Array<[string, string]> = [ const SETTINGS_PANELS: Array<[string, string]> = [
['panel-account-security', '账号安全'],
['panel-database', '数据库配置'], ['panel-database', '数据库配置'],
['panel-registration', '注册设置'], ['panel-registration', '注册设置'],
['panel-bill-source-stats', '账单来源统计默认值'], ['panel-bill-source-stats', '账单来源统计默认值'],
['panel-display', '外观设置'], ['panel-display', '外观设置'],
['panel-nav-menu', '导航设置'],
['panel-ai', 'AI 模型配置'], ['panel-ai', 'AI 模型配置'],
['panel-notification', '定时通知'], ['panel-notification', '定时通知'],
] ]
@@ -348,6 +383,89 @@ export async function captureSettingsPanels(page: Page, only?: string[]): Promis
} }
} }
/**
* 截取 AI 查询浮窗(统计面板顶部的「AI 搜索」入口)。
*
* @param page Page 实例
*/
export async function captureAiQueryWindow(page: Page): Promise<void> {
try {
await page.setViewportSize({ width: 1440, height: 1000 })
await page.goto(BASE_URL + '/dashboard')
await waitPage(page, { selector: 'canvas' })
if (!(await clickText(page, 'AI 搜索'))) throw new Error('未找到「AI 搜索」按钮')
const panel = page.locator('.floating-panel').first()
await panel.waitFor({ state: 'visible', timeout: 20000 })
await page.waitForTimeout(600)
await panel.screenshot({ path: resolve(ELEM_DIR, 'ai-query-window.png') })
console.log('[OK] modal/ai-query-window')
} catch (err) {
console.log(`[FAIL] modal/ai-query-window: ${err}`)
}
}
/**
* 截取账单关系:关系查看面板(含关系图)与新建关系弹窗。
*
* @param page Page 实例
*/
export async function captureRelations(page: Page): Promise<void> {
try {
await page.setViewportSize({ width: 1440, height: 1100 })
await page.goto(BASE_URL + '/transactions')
await waitPage(page, { selector: '.n-data-table-tbody tr' })
const relBtn = page.locator('button', { hasText: /个关系/ }).first()
await relBtn.waitFor({ state: 'visible', timeout: 20000 })
await relBtn.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, 'relation-panel.png') })
console.log('[OK] modal/relation-panel')
const createBtn = modal.locator('button', { hasText: '新建关系' }).first()
await createBtn.waitFor({ state: 'visible', timeout: 15000 })
await createBtn.evaluate((el) => (el as HTMLElement).click())
const editor = page.locator('.n-modal', { hasText: '新建账单关系' }).first()
await editor.waitFor({ state: 'visible', timeout: 20000 })
await page.waitForTimeout(800)
await editor.screenshot({ path: resolve(ELEM_DIR, 'relation-editor.png') })
console.log('[OK] modal/relation-editor')
} catch (err) {
console.log(`[FAIL] modal/relations: ${err}`)
}
}
/**
* 截取智能查数整页(先执行一次查询再截图)。
*
* @param page Page 实例
*/
export async function captureSqlQuery(page: Page): Promise<void> {
try {
await page.setViewportSize({ width: 1440, height: 1180 })
await page.goto(BASE_URL + '/smart-query')
await waitPage(page, { selector: '.n-card' })
const input = page.locator('textarea').first()
await input.waitFor({ state: 'visible', timeout: 20000 })
await input.fill('按分类统计今年的支出')
await Promise.all([
page.waitForResponse(
(r) => r.url().includes('/ai/sql') && r.request().method() === 'POST',
{ timeout: 130000 },
),
page.locator('button', { hasText: '查询' }).first().click(),
])
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => undefined)
await page.waitForTimeout(1500)
await page.screenshot({ path: resolve(OUT_DIR, 'sql-query.png') })
console.log('[OK] page/sql-query')
} catch (err) {
console.log(`[FAIL] page/sql-query: ${err}`)
}
}
async function main(): Promise<void> { async function main(): Promise<void> {
const browser = await launchBrowser() const browser = await launchBrowser()
try { try {
@@ -364,7 +482,7 @@ async function main(): Promise<void> {
await captureEditRecord(page) await captureEditRecord(page)
await captureAiRule(page) await captureAiRule(page)
await captureAiRecordImage(page) await captureAiRecordImage(page)
await captureSourceImages(page) await captureBillDetail(page)
await captureChannelAi(page) await captureChannelAi(page)
await captureSettingsPanels(page) await captureSettingsPanels(page)
await captureBillParse(page) await captureBillParse(page)
+2 -1
View File
@@ -10,6 +10,7 @@ const PAGES: Array<[string, string, number, number, string]> = [
['login', '/login', 1440, 900, '.n-card'], ['login', '/login', 1440, 900, '.n-card'],
['register', '/register', 1440, 900, '.n-card'], ['register', '/register', 1440, 900, '.n-card'],
['dashboard', '/dashboard', 1440, 1280, 'canvas'], ['dashboard', '/dashboard', 1440, 1280, 'canvas'],
['sql-query', '/smart-query', 1440, 1180, '.n-card'],
['transactions', '/transactions', 1720, 1200, '.n-data-table-tbody tr'], ['transactions', '/transactions', 1720, 1200, '.n-data-table-tbody tr'],
['categories', '/categories', 1440, 1100, '.n-data-table-tbody tr'], ['categories', '/categories', 1440, 1100, '.n-data-table-tbody tr'],
['tags', '/tags', 1440, 1100, '.n-data-table-tbody tr'], ['tags', '/tags', 1440, 1100, '.n-data-table-tbody tr'],
@@ -19,7 +20,7 @@ const PAGES: Array<[string, string, number, number, string]> = [
['email-bills-sender-rules', '/email-bills/senderRules', 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'], ['email-bills-messages', '/email-bills/messages', 1440, 1000, '.n-data-table-tbody tr'],
['bill-import', '/bill-import', 1440, 1000, '.n-card'], ['bill-import', '/bill-import', 1440, 1000, '.n-card'],
['settings', '/settings', 1440, 1320, '.config-card'], ['settings', '/settings', 1440, 1900, '.config-card'],
['ai-usage', '/ai-usage', 1440, 1000, '.n-card'], ['ai-usage', '/ai-usage', 1440, 1000, '.n-card'],
['logs-http', '/logs/http', 1440, 1000, '.n-data-table-tbody tr'], ['logs-http', '/logs/http', 1440, 1000, '.n-data-table-tbody tr'],
['logs-login', '/logs/login', 1440, 1000, '.n-data-table-tbody tr'], ['logs-login', '/logs/login', 1440, 1000, '.n-data-table-tbody tr'],
+3
View File
@@ -164,6 +164,9 @@ async function assumeRole(config: OssConfig): Promise<StsCredentials> {
accessKeyId: config.accessKeyId, accessKeyId: config.accessKeyId,
accessKeySecret: config.accessKeySecret, accessKeySecret: config.accessKeySecret,
endpoint: STS_ENDPOINT, endpoint: STS_ENDPOINT,
// 默认连接超时较短,网络抖动时易 ConnectTimeout,这里放宽
connectTimeout: 20000,
readTimeout: 20000,
}), }),
) )
const request = new AssumeRoleRequest({ const request = new AssumeRoleRequest({