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:
@@ -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' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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 # 类型检查
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -27,7 +27,7 @@
|
|||||||
- **时间兼容**:微信账单兼容 Excel 日期序列号与文本时间;支付宝在付款时间缺失时回退交易创建时间;淘宝订单同样兼容文本时间与 Excel 日期序列号。
|
- **时间兼容**:微信账单兼容 Excel 日期序列号与文本时间;支付宝在付款时间缺失时回退交易创建时间;淘宝订单同样兼容文本时间与 Excel 日期序列号。
|
||||||
- **订单聚合**:淘宝订单数据中多商品订单的订单级列(订单号、提交时间、状态、店铺、实付金额、运费)以合并单元格呈现,仅首个商品行有值;解析器按订单号分组聚合,每个订单输出一条记录,金额优先取订单级实付金额,为空时回退「Σ(商品金额×数量)+运费」。
|
- **订单聚合**:淘宝订单数据中多商品订单的订单级列(订单号、提交时间、状态、店铺、实付金额、运费)以合并单元格呈现,仅首个商品行有值;解析器按订单号分组聚合,每个订单输出一条记录,金额优先取订单级实付金额,为空时回退「Σ(商品金额×数量)+运费」。
|
||||||
- **不计收支处理**:京东流水的「不计收支」行(白条还款、小金库转入、全额退款等)标记为可导入但**不计入统计且默认不勾选**,类型按「交易说明含『退款』→ INCOME,否则 EXPENSE」推断。
|
- **不计收支处理**:京东流水的「不计收支」行(白条还款、小金库转入、全额退款等)标记为可导入但**不计入统计且默认不勾选**,类型按「交易说明含『退款』→ INCOME,否则 EXPENSE」推断。
|
||||||
- **计入统计默认值**:解析完成后按**账单来源**应用「计入统计」默认值(配置于系统设置,见 [动态数据源](/dev/database) 同级的应用配置)。采用「解析器判定 && 来源默认」语义——来源默认不计入时整批置为不计入;来源默认计入时保留解析器对「不计收支/中性」行的排除。确认导入时若请求未显式提供该字段,同样以来源默认兜底。
|
- **计入统计默认值**:解析完成后按**账单来源**应用「计入统计」默认值(配置于设置页,见 [动态数据源](/dev/database) 同级的应用配置)。采用「解析器判定 && 来源默认」语义——来源默认不计入时整批置为不计入;来源默认计入时保留解析器对「不计收支/中性」行的排除。确认导入时若请求未显式提供该字段,同样以来源默认兜底。
|
||||||
|
|
||||||
## 压缩包解密
|
## 压缩包解密
|
||||||
|
|
||||||
|
|||||||
+96
-40
@@ -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
@@ -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
@@ -45,7 +45,7 @@ MySQL 的 JDBC URL 会自动附带 UTF-8、关闭 SSL、`Asia/Shanghai` 时区
|
|||||||
|
|
||||||
## 切换行为
|
## 切换行为
|
||||||
|
|
||||||
登录后可在「系统设置 → 数据库配置」随时切换数据库。切换后原数据库的数据保留,**不做自动迁移**。
|
登录后可在「设置 → 数据库配置」随时切换数据库;测试与保存时通过 SSE 实时推送执行进度。切换后原数据库的数据保留,**不做自动迁移**。
|
||||||
|
|
||||||
切换成功后会清空进程内的用户偏好缓存(见[整体架构](/dev/architecture)),避免读到旧库的数据。
|
切换成功后会清空进程内的用户偏好缓存(见[整体架构](/dev/architecture)),避免读到旧库的数据。
|
||||||
|
|
||||||
|
|||||||
+6
-3
@@ -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
@@ -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
@@ -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`。
|
||||||
|
|||||||
@@ -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/ # 页面组件
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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`,用于图片记账)。
|
||||||
- 点击「测试连接」验证配置。
|
- 点击「测试连接」验证配置。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 功能一览
|
## 功能一览
|
||||||
|
|
||||||
@@ -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 调用汇总与明细。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
+26
-15
@@ -23,15 +23,17 @@
|
|||||||
|
|
||||||
## AI 搜索
|
## AI 搜索
|
||||||
|
|
||||||
启用 AI 后,页面顶部会出现「**AI 搜索**」按钮,点击打开搜索弹窗。用一句自然语言描述统计意图,系统会调用大模型解析为统计参数并自动回填、刷新图表,例如:
|
启用 AI 后,页面顶部会出现「**AI 搜索**」按钮,点击打开一个**可拖拽、可缩放的浮窗**(非模态)。用一句自然语言描述统计意图,系统会调用大模型解析为统计参数并自动回填、刷新图表,例如:
|
||||||
|
|
||||||
- 「上个月餐饮花了多少」→ 时间范围 + 分类
|
- 「上个月餐饮花了多少」→ 时间范围 + 分类
|
||||||
- 「近半年交通支出趋势,按周看」→ 时间范围 + 分类 + 按周粒度
|
- 「近半年交通支出趋势,按周看」→ 时间范围 + 分类 + 按周粒度
|
||||||
- 「今年各类支出占比」→ 时间范围 + 类型
|
- 「今年各类支出占比」→ 时间范围 + 类型
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
可解析并回填的参数包括:**时间范围、收支类型、分类、标签、交易渠道、金额区间、备注、统计口径**与**趋势粒度**。弹窗内提供示例问题,输入框会记住历史记录(可一键清空);回车或点击「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)并自动带入对应筛选条件:
|
||||||
|
|
||||||
- **类型**:点中柱子时取该柱子的收入 / 支出;点击空白处时不限定类型(沿用面板当前类型筛选);
|
- **趋势柱状图**:整列可点击——点中柱子,或点击柱子顶端上方的空白处均可。
|
||||||
- **日期范围**:该列代表的时间桶——按日→当天、按周→周一至周日、按月→当月、按季→当季、按年→当年;
|
- **类型**:点中柱子时取该柱子的收入 / 支出;点击空白处时不限定类型(沿用面板当前类型筛选);
|
||||||
- **其他筛选**:统计面板当前的统计口径、分类、交易渠道、标签、金额区间与备注关键字。
|
- **日期范围**:该列代表的时间桶——按日→当天、按周→周一至周日、按月→当月、按季→当季、按年→当年。
|
||||||
|
- **分类饼图**:点击某个扇区,按该分类(含「未分类」)与对应收支类型筛选。
|
||||||
|
- **渠道饼图**:点击某个扇区,按该交易渠道与对应收支类型筛选。
|
||||||
|
|
||||||
|
无论从哪张图下钻,都会同时保留面板当前的统计口径、标签、金额区间与备注关键字等筛选。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
跳转后可直接查看该时间段内的对应明细;筛选条件会照常记忆,收支记录页原有的每页条数与排序偏好不受影响。
|
页面底部的「**查看收支记录**」按钮则会携带**当前全部筛选条件**跳转,便于核对整体明细。
|
||||||
|
|
||||||
|
> 从[收支记录](/guide/transactions)点「查看统计」也会跳回本页,并还原收支记录当前的筛选条件(时间范围会切换为「自定义」)。
|
||||||
|
|
||||||
|
跳转后可直接查看对应明细;筛选条件会照常记忆,收支记录页原有的每页条数与排序偏好不受影响。
|
||||||
|
|
||||||
## 小技巧
|
## 小技巧
|
||||||
|
|
||||||
- 想知道「上个月餐饮花了多少」,可把时间范围设为「本月」或自定义,并在分类中只勾选「餐饮」。
|
- 想知道「上个月餐饮花了多少」,可把时间范围设为「本月」或自定义,并在分类中只勾选「餐饮」。
|
||||||
- 不计入统计的记录(如借款、账户间转账)默认不会计入汇总;如需查看全部,把统计口径切换为「全部记录」。
|
- 不计入统计的记录(如借款、账户间转账)默认不会计入汇总;如需查看全部,把统计口径切换为「全部记录」。
|
||||||
- 想快速核对某月、某周或某天的明细,直接点击「收支趋势」上对应的列(柱子或该列空白处),即可带着时间范围跳到收支记录。
|
- 想快速核对某月、某周或某天的明细,直接点击「收支趋势」上对应的列(柱子或该列空白处),即可带着时间范围跳到收支记录。
|
||||||
|
- 点击分类或渠道饼图的某个扇区,可一键下钻到该分类 / 渠道的明细;点「查看收支记录」则带上当前全部筛选。
|
||||||
|
|||||||
@@ -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
@@ -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 @@
|
|||||||
|
|
||||||
### 忘记密码怎么办?
|
### 忘记密码怎么办?
|
||||||
|
|
||||||
当前版本未提供自助找回。可在数据库中重置对应用户的密码哈希,或新建账号后迁移数据。
|
登录后可在「设置 → 账号安全」中,凭**原密码**修改密码(也支持一键随机生成新密码)。若已完全忘记密码,当前版本暂未提供自助找回,可在数据库中重置该用户的密码哈希,或新建账号后迁移数据。
|
||||||
|
|
||||||
### 数据存在哪里?
|
### 数据存在哪里?
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
首次进入时按引导选择数据库类型并填写连接信息,点击「保存并应用」即可。登录后可通过右上角用户菜单 →「数据库设置」随时切换数据库;切换后原数据库的数据保留,不做自动迁移。
|
首次进入时按引导选择数据库类型并填写连接信息,点击「保存并应用」即可。登录后可通过右上角用户菜单 →「设置」→「数据库配置」随时切换数据库;切换后原数据库的数据保留,不做自动迁移。
|
||||||
|
|
||||||
## 注册与登录
|
## 注册与登录
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
注册功能可在「系统设置 → 注册设置」中开启或关闭。新注册用户会自动获得一套默认分类、标签与邮箱发件人规则。
|
注册功能可在「设置 → 注册设置」中开启或关闭。新注册用户会自动获得一套默认分类、标签与邮箱发件人规则。
|
||||||
|
|
||||||
## 界面语言
|
## 界面语言
|
||||||
|
|
||||||
@@ -56,7 +56,8 @@
|
|||||||
2. 进入「分类管理」「标签管理」按自己的习惯调整分类与标签。
|
2. 进入「分类管理」「标签管理」按自己的习惯调整分类与标签。
|
||||||
3. 在「规则管理」配置常用自动归类规则(如「星巴克 → 餐饮」)。
|
3. 在「规则管理」配置常用自动归类规则(如「星巴克 → 餐饮」)。
|
||||||
4. 导出并导入历史账单(见[账单类型与获取](/guide/bills/overview))。
|
4. 导出并导入历史账单(见[账单类型与获取](/guide/bills/overview))。
|
||||||
5. 按需配置「AI 模型」「定时通知」「邮箱账单」。
|
5. 在「设置」中按需配置「AI 模型」「定时通知」「邮箱账单」,并可调整「外观设置」与「导航设置」。
|
||||||
|
6. 启用 AI 后,可用左侧「智能查数」以自然语言查询账本。
|
||||||
|
|
||||||
## 下一步
|
## 下一步
|
||||||
|
|
||||||
|
|||||||
+7
-4
@@ -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):访问日志、登录日志、运行日志
|
||||||
|
|
||||||
## 账单获取
|
## 账单获取
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -1,6 +1,6 @@
|
|||||||
# 系统日志
|
# 日志
|
||||||
|
|
||||||
系统日志通过右上角用户菜单 →「系统日志」进入,包含三个标签页:**访问日志**、**登录日志**、**运行日志**。页头右上角可选择日志保留时长(30 天 / 90 天 / 180 天 / 永久保留)。
|
日志通过右上角用户菜单 →「日志」进入,包含三个标签页:**访问日志**、**登录日志**、**运行日志**。页头右上角可选择日志保留时长(30 天 / 90 天 / 180 天 / 永久保留)。
|
||||||
|
|
||||||
## 访问日志
|
## 访问日志
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# 定时提醒与通知
|
# 定时提醒与通知
|
||||||
|
|
||||||
系统可按周期提醒你导入新账单,并通过邮箱或 Server 酱推送通知。配置入口为「系统设置 → 定时通知」。
|
系统可按周期提醒你导入新账单,并通过邮箱或 Server 酱推送通知。配置入口为「设置 → 定时通知」。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 提醒周期
|
## 提醒周期
|
||||||
|
|
||||||
|
|||||||
+20
-12
@@ -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、通知八类配置;模型用量与日志分别从设置页与顶部菜单进入。
|
||||||
- **交易渠道管理**是收支记录页的附属页,用于维护自定义渠道,维护完成后返回收支记录继续记账或筛选。
|
- **交易渠道管理**是收支记录页的附属页;**账单关系**是收支记录内的弹窗,两者都在整理完成后回到收支记录继续操作。
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# 账单关系
|
||||||
|
|
||||||
|
「账单关系」用于把**互相关联的多笔收支**组合在一起,例如「交通卡充值」与后续的乘车扣款、分期还款的每一期、支付链路上的多笔记录。建立关系只做关联,不会改动金额,也不影响统计。
|
||||||
|
|
||||||
|
## 两种关系类型
|
||||||
|
|
||||||
|
| 类型 | 含义 | 典型场景 |
|
||||||
|
|---|---|---|
|
||||||
|
| **父子关系** | 一个「父账单」(资金池)对应多个「子账单」(从资金池流出) | 交通卡充值 → 多次乘车;账户充值 → 逐笔消费 |
|
||||||
|
| **同级关系** | 多笔地位平等的账单聚合在一起 | 一笔消费在多个平台留下的关联记录、分次归还 |
|
||||||
|
|
||||||
|
- 父子关系中,**必须且只能有一条父账单**,其余为子账单。
|
||||||
|
- 同级关系中,所有成员地位相同,按加入顺序依次连接。
|
||||||
|
|
||||||
|
## 入口
|
||||||
|
|
||||||
|
| 入口 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| 收支记录列表「**关系**」列 | 该行所属关系组数量,点击「N 个关系」打开关系管理弹窗 |
|
||||||
|
| 收支记录「**批量操作**」 | 进入多选模式,勾选**至少两条**后点「建立关系」,直接新建关系组 |
|
||||||
|
| 账单**详情**弹窗 | 「账单关系」分组内查看该笔账单的全部关系 |
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 新建与编辑关系
|
||||||
|
|
||||||
|
1. 选择**关系类型**(父子关系 / 同级关系)。
|
||||||
|
2. 填写**关系名称**(可选,如「交通卡充值」)与**备注**。
|
||||||
|
3. 点「添加账单」,在分页选择弹窗中勾选要关联的账单(支持备注搜索)。
|
||||||
|
4. 父子关系下,用单选指定其中一条为**父账单**(从批量操作进入时,默认以被操作的那条为父账单,且不可移除);同级关系无需指定,成员按列表顺序排列。
|
||||||
|
5. 点击「保存」。
|
||||||
|
|
||||||
|
约束与说明:
|
||||||
|
|
||||||
|
- 一个关系组**至少包含两条账单**,成员上限 50 条。
|
||||||
|
- **一笔账单不能同时加入两个同类型的关系组**;如需更换,请先在原关系组中解除。
|
||||||
|
- **关系名称会记住历史**:再次新建同名类型的关系时,名称输入框会自动给出历史名称建议(按最近使用、去重,最多 50 条)。
|
||||||
|
- 关系类型会被**记忆**:下次新建时默认沿用上次选择的类型。
|
||||||
|
|
||||||
|
## 关系图
|
||||||
|
|
||||||
|
关系管理弹窗中,每个关系组都会以自绘的关系图展示:
|
||||||
|
|
||||||
|
- 节点为圆形,**支出为红色、收入为绿色**;当前正在查看的账单以蓝色描边且节点更大。
|
||||||
|
- **父子关系**:父账单居中在上,子账单分行排列,父 → 子连线带箭头。
|
||||||
|
- **同级关系**:成员按顺序链式连接,边上标注「关联」。
|
||||||
|
- 节点内显示金额,节点下方显示备注或分类;鼠标悬停可查看完整信息。成员较多时自动换行,图形自适应宽度。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## AI 生成名称与备注
|
||||||
|
|
||||||
|
启用 AI 后,新建/编辑关系弹窗中会出现「**AI 生成**」按钮。系统会把成员账单的**收支类型、交易渠道、分类名称与备注**发送给模型(**不包含金额**),由其归纳出一个关系名称与简短备注,并自动填入;你可以在保存前修改。
|
||||||
|
|
||||||
|
> 成员少于两条时无法生成,会提示先添加账单。生成的名称会覆盖名称框(备注仅在返回非空时覆盖)。
|
||||||
|
|
||||||
|
## 删除关系
|
||||||
|
|
||||||
|
在关系组卡片上点「删除」,确认后**仅解除关联**,不会删除任何账单。删除单笔账单时,系统会自动把它从所属关系组中移除;若某个关系组剩余成员不足两条、或父子关系不再有父账单,该关系组会自动解散。
|
||||||
|
|
||||||
|
## 与统计的关系
|
||||||
|
|
||||||
|
账单关系只用于**查看与整理**,不参与统计口径。每笔账单是否计入统计,仍由各自的「计入统计」开关决定,详见[收支记录](/guide/transactions)。
|
||||||
|
|
||||||
|
## 相关
|
||||||
|
|
||||||
|
- [收支记录](/guide/transactions):记录管理与批量操作。
|
||||||
|
- [AI 能力](/guide/ai):AI 生成关系名称、分类标签建议等。
|
||||||
@@ -52,6 +52,15 @@
|
|||||||
|
|
||||||
点击「搜索」按条件查询,「清空」重置全部条件。新增规则后回到第一页,便于查看新规则。
|
点击「搜索」按条件查询,「清空」重置全部条件。新增规则后回到第一页,便于查看新规则。
|
||||||
|
|
||||||
|
## 多选与批量删除
|
||||||
|
|
||||||
|
规则列表每行前都有勾选框,表头可**全选当前页**:
|
||||||
|
|
||||||
|
- 勾选若干规则后,点右上角「**批量删除**」并确认,即可一次性删除选中的规则。
|
||||||
|
- 未勾选任何规则时「批量删除」不可点击。
|
||||||
|
|
||||||
|
> 删除规则只影响后续导入时的自动归类,已应用的历史记录分类与标签不会被撤销。
|
||||||
|
|
||||||
## AI 生成规则
|
## AI 生成规则
|
||||||
|
|
||||||
启用 AI 后,规则管理页右上角会出现「**AI 生成规则**」按钮,可让 AI 依据**已打标签的记录**自动归纳出「关键词 → 分类/标签」规则,无需手工逐条编写。
|
启用 AI 后,规则管理页右上角会出现「**AI 生成规则**」按钮,可让 AI 依据**已打标签的记录**自动归纳出「关键词 → 分类/标签」规则,无需手工逐条编写。
|
||||||
|
|||||||
+45
-6
@@ -1,8 +1,20 @@
|
|||||||
# 系统设置
|
# 设置
|
||||||
|
|
||||||
系统设置通过右上角用户菜单 →「系统设置」进入,包含六个配置面板。
|
「设置」通过右上角用户菜单 →「设置」进入,包含八个配置面板。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## 账号安全
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
用于**修改当前登录账号的密码**:
|
||||||
|
|
||||||
|
- 输入**原密码**、**新密码**与**确认新密码**,点「修改密码」提交。
|
||||||
|
- 新密码长度需为 **6–50 个字符**,且不能与原密码相同。
|
||||||
|
- 可点「**随机生成**」自动生成一个 12 位、含大小写字母与数字的密码(已排除易混淆字符),生成后会明文显示并可一键**复制**;修改成功后请使用新密码登录。
|
||||||
|
|
||||||
|
> 修改密码仅校验原密码,**不提供**通过邮箱等方式的密码找回。
|
||||||
|
|
||||||
## 数据库配置
|
## 数据库配置
|
||||||
|
|
||||||
@@ -13,6 +25,8 @@
|
|||||||
- **MySQL**:主机、端口、数据库名、用户名、密码。
|
- **MySQL**:主机、端口、数据库名、用户名、密码。
|
||||||
- 提供「测试连接」与「保存并应用」。保存后立即生效并自动建表;已配置时密码留空表示不修改。
|
- 提供「测试连接」与「保存并应用」。保存后立即生效并自动建表;已配置时密码留空表示不修改。
|
||||||
|
|
||||||
|
测试与保存时会实时展示**执行进度**:连接测试、建表、结构迁移、数据源切换等步骤会以进度日志逐条推送,处理较久时请保持窗口打开,完成后点「关闭」即可。
|
||||||
|
|
||||||
系统支持启动后再配置数据库:未配置时前端会引导进入初始化页面,无需启动前准备。登录后也可随时切换数据库,切换后原数据库数据保留、不做自动迁移。
|
系统支持启动后再配置数据库:未配置时前端会引导进入初始化页面,无需启动前准备。登录后也可随时切换数据库,切换后原数据库数据保留、不做自动迁移。
|
||||||
|
|
||||||
## 注册设置
|
## 注册设置
|
||||||
@@ -41,12 +55,37 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
选择「不计入统计」的记录在[收支记录](/guide/transactions)列表中的展示方式:
|
外观设置包含三组配置,**修改后自动保存**,无需再点保存按钮:
|
||||||
|
|
||||||
|
**1. 不计入统计的记录样式**:选择「不计入统计」的记录在[收支记录](/guide/transactions)列表中的展示方式:
|
||||||
|
|
||||||
- **删除线(默认)**:整行加删除线,类型与金额列保持原样。
|
- **删除线(默认)**:整行加删除线,类型与金额列保持原样。
|
||||||
- **灰色标签**:类型列显示灰色「不计入」标签,金额列置灰且不显示正负号。
|
- **灰色标签**:类型列显示灰色「不计入」标签,金额列置灰且不显示正负号。
|
||||||
|
|
||||||
面板内提供**实时预览**,切换选项即可看到效果;该偏好按**当前账号**保存,登录后生效。
|
面板内提供**实时预览**,切换选项即可看到效果。
|
||||||
|
|
||||||
|
**2. 显示已隐藏备注的记录**(默认关闭):
|
||||||
|
|
||||||
|
- 关闭时,备注被隐藏的记录不会出现在收支记录列表与 Excel 导出中。
|
||||||
|
- 开启后,这些记录会正常显示(备注仍为 `******`,可临时「查看」)。
|
||||||
|
|
||||||
|
**3. 表格每页条数**:
|
||||||
|
|
||||||
|
- **表格每页条数选项**:所有表格分页共用的候选列表,默认 `10 / 20 / 50 / 100`,可自行增删(最多 10 项)。
|
||||||
|
- **默认每页条数**:从候选中选择,作为各表格未单独记忆时的默认值(默认 20)。
|
||||||
|
- 每张表格会分别记住上次选择的每页条数;本机记忆优先于全局默认值。
|
||||||
|
|
||||||
|
## 导航设置
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
调整左侧导航的**显示顺序与显隐**(按当前账号保存,跨设备生效):
|
||||||
|
|
||||||
|
- **拖动**每项左侧的手柄即可调整顺序,修改后自动保存。
|
||||||
|
- 用开关控制各项的显示或隐藏。
|
||||||
|
- 至少需保留一个可见项;「**恢复默认**」可还原为默认顺序与全部显示。
|
||||||
|
|
||||||
|
> 隐藏只移除菜单入口,页面仍可通过地址直接访问。当 AI 未启用时,依赖 AI 的菜单项(如「智能查数」)不会出现在此处,也不会显示在左侧导航。
|
||||||
|
|
||||||
## AI 模型配置
|
## AI 模型配置
|
||||||
|
|
||||||
@@ -68,5 +107,5 @@
|
|||||||
|
|
||||||
## 相关
|
## 相关
|
||||||
|
|
||||||
- [系统日志](/guide/logs):查看访问、登录与运行日志。
|
- [日志](/guide/logs):查看访问、登录与运行日志。
|
||||||
- [快速开始](/guide/getting-started):数据库配置与注册登录。
|
- [快速开始](/guide/getting-started):数据库配置与注册登录。
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# 智能查数
|
||||||
|
|
||||||
|
「智能查数」让你用**自然语言**直接查询账本,由 AI 生成一条**只读 SQL** 并执行,结果以统计卡片、表格或图表展示,无需自己拼筛选条件。
|
||||||
|
|
||||||
|
> 这是与[统计面板的「AI 搜索」](/guide/dashboard#ai-搜索)不同的能力:AI 搜索是「自然语言 → 统计面板筛选条件」,智能查数则是「自然语言 → SQL 查询」,适合更自由、更灵活的问答。
|
||||||
|
|
||||||
|
## 入口与前提
|
||||||
|
|
||||||
|
- 先在[设置](/guide/settings#ai-模型配置)中**启用 AI** 并配置 API Key。
|
||||||
|
- 启用后,左侧菜单会出现「**智能查数**」(未启用时不显示)。
|
||||||
|
- 直接访问路由 `/smart-query` 也可进入。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 提问
|
||||||
|
|
||||||
|
在页面顶部的输入框用一句自然语言描述想查的内容,例如:
|
||||||
|
|
||||||
|
- 上个月一共花了多少钱
|
||||||
|
- 按分类统计今年的支出
|
||||||
|
- 今年收入最高的一个月
|
||||||
|
- 各渠道的支出占比
|
||||||
|
- 本月支出最高的 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
@@ -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`)提交;解析结果会直接应用为筛选条件并立即查询。浮窗的位置与大小会被记住,输入时还能快速选择历史提问。筛选条件与每页条数、排序方式会自动记忆,下次进入自动还原。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## 表格与行操作
|
## 表格与行操作
|
||||||
|
|
||||||
表格展示:交易时间、类型、分类、金额、交易渠道、备注、标签、来源图片、计入统计开关与操作。
|
表格展示:交易时间、类型、分类、金额、交易渠道、备注、标签、计入统计开关、关系与操作。
|
||||||
|
|
||||||
- **备注隐藏**:可将敏感备注持久隐藏为 `******`,需要时临时「查看」或「取消隐藏」。
|
- **备注隐藏**:可将敏感备注持久隐藏为 `******`,需要时临时「查看」或「取消隐藏」。默认这些记录会从列表与 Excel 导出中排除,可在[设置 → 外观设置](/guide/settings#外观设置)中开启「显示已隐藏备注的记录」。
|
||||||
- **计入统计**:行内开关可直接切换;关闭后该记录不纳入月度统计(适合借款、转账等),行内会以删除线标示。
|
- **计入统计**:行内开关可直接切换;关闭后该记录不纳入月度统计(适合借款、转账等),行内会以删除线标示(样式可在外观设置中调整)。
|
||||||
- **来源图片**:图片记账生成的记录会显示「查看(N)」入口,弹窗内先加载缩略图,可按需「加载原图」查看对应截图。
|
- **关系**:展示该记录所属[账单关系](/guide/relations)组的数量,点击可查看与管理。
|
||||||
- **行操作**:编辑、查看「原始数据」(账单导入或图片记账时的原始字段,以 JSON 展示)、删除。
|
- **行操作**:编辑、详情、删除。
|
||||||
|
|
||||||

|
### 账单详情
|
||||||
|
|
||||||
分页支持每页 10/20/50/100/200/500 条。
|
点击行内「**详情**」打开「账单详情」弹窗,集中展示单条账单的以下信息:
|
||||||
|
|
||||||
## 查看原始数据
|
- **基本信息**:类型、金额、分类、交易渠道、交易时间、是否计入统计、备注与创建时间。
|
||||||
|
- **标签**:该记录的全部标签。
|
||||||
|
- **账单关系**:所属的[账单关系](/guide/relations)组,可在弹窗内直接新建。
|
||||||
|
- **来源图片**:图片记账生成的记录会展示其来源截图,默认加载缩略图,可按需点「加载原图」查看原图。
|
||||||
|
- **原始数据**:账单导入或图片记账时保留的原始字段(以 JSON 展示),用于核对金额、交易状态、对方账号、交易类型等明细。
|
||||||
|
|
||||||
账单导入或图片记账时,系统会完整保留每笔交易在原始账单中的字段(以 JSON 存储)。在记录行点击「**原始数据**」即可查看,用于核对金额、交易状态、对方账号、交易类型等明细。
|

|
||||||
|
|
||||||
- **账单导入**记录保留原始账单行字段;**图片记账**记录保留模型从截图提取的原始字段。
|
> - **账单导入**记录保留原始账单行字段;**图片记账**记录保留模型从截图提取的原始字段;**手动新增**的记录没有来源图片与原始数据。
|
||||||
- **手动新增**的记录没有原始数据,该按钮不可用。
|
> - 原始数据是排查导入异常、核对账单的重要依据。
|
||||||
- 原始数据是排查导入异常、核对账单的重要依据。
|
|
||||||
|
分页的每页条数**候选与默认值在[设置 → 外观设置](/guide/settings#外观设置)中统一配置**(默认候选 10 / 20 / 50 / 100,默认 20);各表格会分别记住上次选择的每页条数。
|
||||||
|
|
||||||
## 一键设置(批量设置)
|
## 一键设置(批量设置)
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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",
|
||||||
|
|||||||
+135
-17
@@ -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()
|
||||||
await modal.waitFor({ state: 'visible', timeout: 20000 })
|
if ((await detailBtn.count()) === 0) continue
|
||||||
await page.waitForLoadState('networkidle', { timeout: 20000 }).catch(() => undefined)
|
await detailBtn.evaluate((el) => (el as HTMLElement).click())
|
||||||
await page.waitForTimeout(1000)
|
const modal = page.locator('.n-modal', { hasText: '账单详情' }).first()
|
||||||
await modal.screenshot({ path: resolve(ELEM_DIR, 'source-images.png') })
|
await modal.waitFor({ state: 'visible', timeout: 20000 })
|
||||||
console.log('[OK] modal/source-images')
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => undefined)
|
||||||
|
await page.waitForTimeout(800)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|||||||
@@ -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'],
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
Reference in New Issue
Block a user