From 923d6db5c094cd239b11c2780434e71bbc4ebccb Mon Sep 17 00:00:00 2001 From: rosercode Date: Sat, 26 Sep 2026 21:57:38 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=90=8C=E6=AD=A5=E6=B1=87=E8=B4=A6?= =?UTF-8?q?=E6=9C=80=E6=96=B0=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=99=BA=E8=83=BD=E6=9F=A5=E6=95=B0=E3=80=81=E8=B4=A6=E5=8D=95?= =?UTF-8?q?=E5=85=B3=E7=B3=BB=E7=AD=89=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 对照 ledgerly 自文档上次更新以来的全部改动,补充新功能文档、修正品牌与版本残留,并新增下载与安装页;配图已重新截取并上传 OSS,本地截图目录不纳入版本管理。 变更内容: - 新增「智能查数」「账单关系」两个使用文档页,并登记侧边栏与总览 - 设置页扩充为八个面板(账号安全、数据库实时进度、外观、导航等)并改为即时保存 - 收支记录补充关系列、批量操作、AI 搜索浮窗、可配置分页与「账单详情」弹窗 - 统计面板补充饼图/渠道图下钻与筛选双向联动;规则管理补充批量删除 - AI 能力补充智能查数与关系命名;新增下载与安装页 - 开发文档同步:AI 13 类调用、数据模型 24 张表、API、前端偏好、构建部署 - 统一「汇账 Ledgerly」品牌与「设置/日志」菜单文案,版本号对齐 0.1 - 截图脚本新增账单详情等场景并更新登记 说明: - download.md 的 OSS 下载链接保持待发布产物回填,暂不调整 - 已通过 npm run docs:build 与 npm run typecheck:scripts --- .vitepress/config.mts | 7 +- README.md | 9 +- dev/ai.md | 35 ++++++-- dev/api.md | 29 +++++-- dev/architecture.md | 6 +- dev/bill-fields.md | 4 +- dev/bill-parsing.md | 2 +- dev/build-deploy.md | 136 ++++++++++++++++++++--------- dev/data-model.md | 17 +++- dev/database.md | 2 +- dev/development.md | 9 +- dev/index.md | 6 +- dev/preferences.md | 123 +++++++++++++++++--------- dev/project-structure.md | 10 ++- dev/rule-engine.md | 1 + guide/ai.md | 24 ++++-- guide/dashboard.md | 41 +++++---- guide/download.md | 109 +++++++++++++++++++++++ guide/faq.md | 8 +- guide/getting-started.md | 7 +- guide/index.md | 11 ++- guide/introduction.md | 9 +- guide/logs.md | 4 +- guide/notifications.md | 4 +- guide/pages.md | 32 ++++--- guide/relations.md | 68 +++++++++++++++ guide/rules.md | 9 ++ guide/settings.md | 51 +++++++++-- guide/sql-query.md | 74 ++++++++++++++++ guide/transactions.md | 40 +++++---- index.md | 10 ++- package.json | 2 +- scripts/ts/capture-elements.ts | 152 +++++++++++++++++++++++++++++---- scripts/ts/capture-pages.ts | 3 +- scripts/ts/upload-oss.ts | 3 + 35 files changed, 841 insertions(+), 216 deletions(-) create mode 100644 guide/download.md create mode 100644 guide/relations.md create mode 100644 guide/sql-query.md diff --git a/.vitepress/config.mts b/.vitepress/config.mts index 4bf82bd..f2bfd3d 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -30,6 +30,7 @@ export default defineConfig({ items: [ { text: '项目简介', link: '/guide/introduction' }, { text: '快速开始', link: '/guide/getting-started' }, + { text: '下载与安装', link: '/guide/download' }, { text: '统计面板', link: '/guide/dashboard' }, { text: '页面总览', link: '/guide/pages' }, ], @@ -38,6 +39,7 @@ export default defineConfig({ text: '记账与整理', items: [ { text: '收支记录', link: '/guide/transactions' }, + { text: '账单关系', link: '/guide/relations' }, { text: '分类与标签', link: '/guide/categories-tags' }, { text: '交易渠道管理', link: '/guide/channels' }, { text: '规则管理', link: '/guide/rules' }, @@ -48,14 +50,15 @@ export default defineConfig({ text: '智能与自动化', items: [ { text: 'AI 能力', link: '/guide/ai' }, + { text: '智能查数', link: '/guide/sql-query' }, { text: '定时提醒与通知', link: '/guide/notifications' }, ], }, { text: '系统', items: [ - { text: '系统设置', link: '/guide/settings' }, - { text: '系统日志', link: '/guide/logs' }, + { text: '设置', link: '/guide/settings' }, + { text: '日志', link: '/guide/logs' }, ], }, { diff --git a/README.md b/README.md index f24abf2..cebfcd7 100644 --- a/README.md +++ b/README.md @@ -54,10 +54,11 @@ doc-site/ │ ├── config.mts # 站点配置(导航、侧边栏、搜索、端口 5175) │ └── theme/ # 自定义主题(图片全屏查看、图片样式) ├── scripts/ -│ ├── ts/ # 维护脚本(TypeScript) -│ └── samples/ # 样例账单 +│ ├── ts/ # 维护脚本(TypeScript,含截图、演示数据、OSS 上传) +│ └── samples/ # 虚构样例账单 ├── refs/ # 联网检索到的资料原文(不参与构建) ├── jar-home/ # jar 一体化实例运行目录(测试库副本) +├── .env.local # 截图 OSS 上传等本地配置(不入库) ├── package.json └── tsconfig.json ``` @@ -67,13 +68,15 @@ doc-site/ 脚本位于 `scripts/ts/`,用 `tsx` 直接运行 TypeScript,浏览器自动化基于 `playwright-core`(Chromium),HTTP 基于 `axios`。 ```sh -npm run capture # 一键重新截图(整页 + 元素 + 卡片 + 编辑/设置面板 + 解析预览) +npm run capture # 一键重新截图(整页 + 元素 + 卡片 + 编辑/设置面板 + 解析预览,慎用) npm run capture:pages # 仅整页截图 npm run capture:elements # 仅元素截图 +npm run make:bills # 生成虚构样例账单 npm run seed # 导入样例账单(演示数据) npm run seed:rules # 补充标签与规则 npm run refs:search # 检索资料到 refs/ npm run refs:fetch # 抓取指定 URL 到 refs/ +npm run oss:upload # 上传截图到 OSS(读取 .env.local) npm run test:browser # 验证浏览器链路 npm run typecheck:scripts # 类型检查 ``` diff --git a/dev/ai.md b/dev/ai.md index 1e9fb1e..0e997ce 100644 --- a/dev/ai.md +++ b/dev/ai.md @@ -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,超出后清空重建)。 -- `temperature = 0.2`,`response_format = {"type":"json_object"}`,非流式,`maxRetries(0)`;文本请求超时 **120 秒**,视觉请求超时 **240 秒**(`chatWithImages` 单独放宽)。 -- 取 `choices[0].message.content` 与 `usage`(输入/输出/总 token;缓存命中 token 取 `promptTokensDetails.cachedTokens`,DeepSeek 私有字段读不到时为 0)。 +- 请求体包含 `model`、`messages`、`temperature = 0.2`、`response_format = {"type":"json_object"}`、`stream = false`。 +- 文本请求超时 **120 秒**,视觉请求超时 **240 秒**(`chatWithImages` 单独放宽)。 +- 解析 `choices[0].message.content` 与 `usage`(输入/输出/总 token;缓存命中 token 取 DeepSeek 的缓存字段,读不到时为 0)。 - 对外保持 `chat(baseUrl, apiKey, model, systemPrompt, userPrompt)` 与 `DeepSeekResponse` 不变,各 `Ai*ServiceImpl` 无需改动。 - 另有 `chatWithImages(baseUrl, apiKey, model, systemPrompt, userText, imageDataUrls)`:将一张或多张图片以 base64 data URL 作为 `user` 消息的内容块发送(DeepSeek 视觉接口要求图片只能出现在 user 消息中),供图片记账使用。 -- SDK 异常统一转 `BusinessException`;异常链含超时(`SocketTimeoutException` 等)时返回面向用户的友好提示。 +- 调用异常统一转 `BusinessException`;异常链含超时(`SocketTimeoutException` 等)时返回面向用户的友好提示。 配置存储于 `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)、金额、交易号、账户/卡号、姓名、地址、电话等。 - **允许**:收支类型、交易渠道、备注(必要时截断/脱敏)、分类与标签的名称/ID、用户主动输入的自然语言文本。 +- **智能查数**:仅发送用户提问与可查询的**表结构说明**,不发送任何账单数据行;数据查询与隔离在本地由安全网关完成。 - **图片记账例外**:用户主动上传的账单/支付截图会**整张**发送给视觉模型(可能含金额、交易对方、订单号等),视为用户主动提供的输入;图片同时落盘于 `data/ai-images/`,仅本人可经 `GET /api/ai/image/{id}` 访问,禁止用于其他外发场景。 ## 调用类型 -`AiCallType` 定义了 11 类调用,全部记录用量: +`AiCallType` 定义了 13 类调用,全部记录用量: | 枚举值 | 含义 | |---|---| @@ -40,6 +41,8 @@ | `CHANNEL` | 自然语言生成交易渠道 | | `RULE` | 由已打标签记录生成分类与标签规则 | | `STATISTICS` | 自然语言统计查询解析 | +| `RELATION` | 由关联账单生成关系名称与备注 | +| `SQL` | 自然语言转换为只读 SQL 查询(智能查数) | ## 分类/标签建议 @@ -75,7 +78,7 @@ **实时进度**:`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`)。 +## 生成账单关系名称 + +`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、是否成功、错误信息、耗时。 diff --git a/dev/api.md b/dev/api.md index 7ce4153..bc89bbe 100644 --- a/dev/api.md +++ b/dev/api.md @@ -28,21 +28,22 @@ | 控制器 | 路径前缀 | 说明 | |---|---|---| -| `UserController` | `/api/user` | 登录、注册、用户信息、注册开关 | -| `TransactionController` | `/api/transaction` | 收支记录增删改查、批量创建与操作、导出、交易渠道选项 | +| `UserController` | `/api/user` | 登录、注册、用户信息、注册开关、修改密码 | +| `TransactionController` | `/api/transaction` | 收支记录增删改查、批量创建与操作、备注隐藏、导出、交易渠道选项 | +| `TransactionGroupController` | `/api/transaction/group` | 账单关系组的查询、创建、更新、删除与名称历史 | | `BillImportController` | `/api/transaction/import` | 账单解析与确认导入 | | `ChannelController` | `/api/channel` | 用户自定义交易渠道的增删改查 | | `CategoryController` | `/api/category` | 分类管理 | | `TagController` | `/api/tag` | 标签管理 | -| `RuleController` | `/api/rule` | 自定义规则(列表、分页查询、增删改、启停) | +| `RuleController` | `/api/rule` | 自定义规则(列表、分页查询、增删改、启停、批量删除) | | `StatisticsController` | `/api/statistics` | 统计汇总、分类、趋势、分组 | -| `AiController` | `/api/ai` | AI 建议、查询、文字/图片记账、渠道生成、规则生成、统计搜索、用量 | +| `AiController` | `/api/ai` | AI 建议、查询、智能查数、文字/图片记账、渠道/关系/规则生成、统计搜索、用量 | | `NotificationController` | `/api/notification` | 通知配置、渠道、发送日志 | | `EmailBillController` | `/api/email-bill` | 邮箱配置、发件人规则、待处理账单 | | `LogController` | `/api/log` | 访问日志、登录日志、运行日志与 SSE | -| `DatabaseController` | `/api/database` | 数据库状态、测试与配置 | +| `DatabaseController` | `/api/database` | 数据库状态、测试与配置(含 SSE 进度) | | `AppConfigController` | `/api/app` | 应用配置(通知链接、日志保留) | -| `UserSettingController` | `/api/setting` | 用户偏好(账单来源统计默认值、不计入统计展示样式) | +| `UserSettingController` | `/api/setting` | 用户偏好(账单来源统计默认值、展示样式、隐藏备注显示、分页、导航) | ## 关键接口 @@ -50,16 +51,30 @@ |---|---|---| | GET | `/api/transaction/channels` | 获取交易渠道选项(内置 + 当前用户自定义,含 `code`/`name`/`builtin`) | | POST | `/api/transaction/batch` | 批量创建交易记录(逐条可带 `imageId`/`rawData`),导入时按「类型+金额+时间」跳过重复 | +| PUT | `/api/transaction/{id}/remark-hidden` | 隐藏 / 取消隐藏某条记录的备注 | | 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/stream` | 图片记账(SSE):实时推送识别进度,结束返回完整结果 | | GET | `/api/ai/image/{id}` | 查看本人上传的图片记账图片,`?thumbnail=true` 返回缩略图 | | 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`) | +| DELETE | `/api/rule/batch` | 批量删除规则(body `{ ids }`,返回实际删除数) | | POST | `/api/ai/rule/suggest` | 依据所选已打标签记录生成规则建议(含 `duplicate` 标记) | | POST | `/api/ai/rule/apply` | 批量创建规则(自动去重、按需新建分类/标签) | | 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`) | ## 统一响应结构 diff --git a/dev/architecture.md b/dev/architecture.md index af66d48..e67ba9d 100644 --- a/dev/architecture.md +++ b/dev/architecture.md @@ -12,7 +12,7 @@ | 认证 | JWT(HMAC) | | 对象映射 | MapStruct 1.6.3 | | 账单解析 | EasyExcel(xlsx)/ Apache POI(xls)+ commons-csv + PDFBox + Zip4j | -| AI | DeepSeek(OpenAI Java SDK) | +| AI | DeepSeek(OpenAI 兼容接口,Spring RestClient) | | 通知 | Jakarta Mail(SMTP / IMAP)+ Server 酱 | | API 文档 | Knife4j(OpenAPI 3) | @@ -91,8 +91,8 @@ src/ 1. **运行时动态数据源**:启动无需数据库,配置后热切换,见[动态数据源](/dev/database)。 2. **账单两阶段导入**:解析预览 → 确认导入,按交易号去重,见[账单解析](/dev/bill-parsing)。 3. **规则引擎**:分类取最高优先级、标签取并集,见[规则引擎](/dev/rule-engine)。 -4. **体系化 AI 调用**:11 类调用类型并记录 token 用量,见 [AI 集成](/dev/ai)。 -5. **SSE 实时能力**:运行日志、邮箱检查进度与图片记账识别进度实时推送。 +4. **体系化 AI 调用**:13 类调用类型并记录 token 用量,见 [AI 集成](/dev/ai)。 +5. **SSE 实时能力**:运行日志、邮箱检查进度、图片记账识别进度与数据库配置执行进度实时推送。 6. **用户偏好缓存**:`t_user_preference` 的读取走 Spring Cache 进程内缓存(`ConcurrentMapCacheManager`),写入时按用户失效,切换数据源时整体清空。 ## 相关文档 diff --git a/dev/bill-fields.md b/dev/bill-fields.md index a7ba4b9..3d28719 100644 --- a/dev/bill-fields.md +++ b/dev/bill-fields.md @@ -5,8 +5,8 @@ ## 样例与实现位置 - 编码:GBK(解析器兼容 UTF-8 / UTF-8 BOM) -- 解析器:`src/main/java/cn/rosercode/finance/importer/AlipayBillParser.java` -- 落库服务:`src/main/java/cn/rosercode/finance/service/impl/BillImportServiceImpl.java` +- 解析器:`src/main/java/cn/rosercode/ledgerly/importer/AlipayBillParser.java` +- 落库服务:`src/main/java/cn/rosercode/ledgerly/service/impl/BillImportServiceImpl.java` ## 文件结构 diff --git a/dev/bill-parsing.md b/dev/bill-parsing.md index 1d408ef..5bf5bfa 100644 --- a/dev/bill-parsing.md +++ b/dev/bill-parsing.md @@ -27,7 +27,7 @@ - **时间兼容**:微信账单兼容 Excel 日期序列号与文本时间;支付宝在付款时间缺失时回退交易创建时间;淘宝订单同样兼容文本时间与 Excel 日期序列号。 - **订单聚合**:淘宝订单数据中多商品订单的订单级列(订单号、提交时间、状态、店铺、实付金额、运费)以合并单元格呈现,仅首个商品行有值;解析器按订单号分组聚合,每个订单输出一条记录,金额优先取订单级实付金额,为空时回退「Σ(商品金额×数量)+运费」。 - **不计收支处理**:京东流水的「不计收支」行(白条还款、小金库转入、全额退款等)标记为可导入但**不计入统计且默认不勾选**,类型按「交易说明含『退款』→ INCOME,否则 EXPENSE」推断。 -- **计入统计默认值**:解析完成后按**账单来源**应用「计入统计」默认值(配置于系统设置,见 [动态数据源](/dev/database) 同级的应用配置)。采用「解析器判定 && 来源默认」语义——来源默认不计入时整批置为不计入;来源默认计入时保留解析器对「不计收支/中性」行的排除。确认导入时若请求未显式提供该字段,同样以来源默认兜底。 +- **计入统计默认值**:解析完成后按**账单来源**应用「计入统计」默认值(配置于设置页,见 [动态数据源](/dev/database) 同级的应用配置)。采用「解析器判定 && 来源默认」语义——来源默认不计入时整批置为不计入;来源默认计入时保留解析器对「不计收支/中性」行的排除。确认导入时若请求未显式提供该字段,同样以来源默认兜底。 ## 压缩包解密 diff --git a/dev/build-deploy.md b/dev/build-deploy.md index 6e73464..8b24be3 100644 --- a/dev/build-deploy.md +++ b/dev/build-deploy.md @@ -1,9 +1,12 @@ # 构建与部署 +本章介绍「汇账」的构建方式、分发形态与生产部署流程。 + ## 前置条件 -- **JDK 21**(Temurin 21 LTS) +- **JDK 21**(Temurin 21 LTS,含 `jpackage`) - **Node.js**(构建前端时需要) +- 依赖仓库:本地 Nexus(Maven)与 verdaccio(npm);构建时经 `-I nexus-init.gradle` 注入 Maven 私服地址 ## 开发模式 @@ -12,85 +15,138 @@ ```bash # 后端(默认 8080) cd ledgerly -set JAVA_HOME=D:\venv\jdk-21 -gradlew.bat bootRun +./gradlew bootRun # Windows: gradlew.bat bootRun # 前端(默认 5173,接口代理到 8080) cd ledgerly/frontend npm run dev ``` -- 后端地址:http://localhost:8080 -- 后端 API 文档:http://localhost:8080/doc.html -- 前端地址:http://localhost:5173,接口经 Vite 代理到后端 8080 +- 后端地址:`http://localhost:8080`,接口文档 `http://localhost:8080/doc.html` +- 前端地址:`http://localhost:5173`,`/api` 经 Vite 代理到后端 8080 -## 打包可执行 jar(前后端一体化) +## 构建可执行 jar(前后端一体化) `bootJar` 会先执行前端 `npm run build`,再把 `frontend/dist` 复制到 jar 内的 `BOOT-INF/classes/static/`,单文件同时提供页面与接口。 ```bash -gradle bootJar -# 或使用宿主机脚本 +# 普通 jar(无 GUI,不含 JavaFX) +./gradlew -I nexus-init.gradle bootJar + +# 带 GUI 启动器的 jar +./gradlew -I nexus-init.gradle bootGuiJar + +# 或使用宿主机脚本(一次产出两者) ./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,可另指定端口): ```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 提示 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` 未变化时跳过) -- `frontendBuild`:执行 `npm run build` -- `bootJar`:依赖 `frontendBuild`,并将前端产物打入静态资源目录 +运行时首次配置或切换数据库时,「测试连接」与「保存并应用」会通过 SSE **实时推送执行进度**(连接测试、建表、结构迁移、数据源切换等)。 -## 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/` | -| front | `nginx:alpine` | 15180→80 | 挂载 `run/front/dist` 与 Nginx 配置 | +| `jpackageServerAppImage` | `LedgerlyServer` | `bootJar` | `bin/LedgerlyServer`(Windows 为 `LedgerlyServer.exe`,附带控制台) | +| `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=` 指定本地源图。 ## 桌面 GUI 启动器 -`launcher` 包提供系统托盘 + 控制窗口的桌面启动器(启动/停止服务、打开浏览器、查看日志),通过 `bootGuiJar` 打包: - -```bash -gradle bootGuiJar -``` - -- 入口类:`cn.rosercode.finance.launcher.LauncherMain` -- 依赖 JavaFX 与 dorkbox SystemTray,仅加入带 GUI 的 jar(`guiRuntime` 配置),普通 jar 不含这些依赖 -- 平台默认 Windows(可通过 `-PjavafxPlatform=linux` 本地验证) -- JavaFX 与 JNA 原生库需 `requiresUnpack` 从嵌套 jar 解压 - -> 注意:该启动器为规划中的能力,具体实现以当前源码为准。 +`cn.rosercode.ledgerly.launcher` 提供系统托盘 + 控制窗口的桌面启动器(进程内启停服务、设置端口、打开浏览器、实时查看日志、单实例运行),仅打入带 GUI 的 jar,普通 jar 不含 JavaFX 依赖。 ## 分发形态 -| 形态 | 说明 | -|---|---| -| jar | `bootJar`,需 JDK 21 运行 | -| jar(带 GUI) | `bootGuiJar`,内含桌面启动器 | -| 可执行文件(控制台 / GUI) | 规划中,计划采用 `jpackage`(JDK 自带,各平台分别构建) | -| Docker 镜像 | 通过 `run/docker-compose.yml` 编排 | +| 形态 | 构建方式 | 说明 | +|---|---|---| +| 普通 jar | `bootJar` | 需 JDK 21 运行,跨平台 | +| 带 GUI 启动器 jar | `bootGuiJar` | 内含桌面启动器,跨平台(打包含 JavaFX 全平台原生库) | +| Windows 免安装包 | `jpackageAppImage` | 在 Windows 上构建,zip 分发 | +| 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/development) +- [下载与安装(使用文档)](/guide/download) - [快速开始(使用文档)](/guide/getting-started) diff --git a/dev/data-model.md b/dev/data-model.md index 026fb96..65a018e 100644 --- a/dev/data-model.md +++ b/dev/data-model.md @@ -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_tag` | 标签 | | 标签 | `t_transaction_tag` | 交易-标签关联(多对多) | +| 账单关系 | `t_transaction_group` | 账单关系组(父子/同级) | +| 账单关系 | `t_transaction_group_item` | 关系组成员(角色与顺序) | | 规则 | `t_rule` | 自定义归类规则 | | 规则 | `t_rule_tag` | 规则-标签关联(多对多) | | AI | `t_ai_config` | 每用户一条的 AI 配置(含视觉模型) | | AI | `t_ai_usage` | AI 调用用量记录 | | AI | `t_ai_image` | 图片记账上传的截图及其解析摘要 | | AI | `t_ai_image_transaction` | 图片与解析生成交易的关联 | +| AI | `t_ai_sql_history` | 智能查数提问历史(每用户多条) | | 通知 | `t_notification_config` | 每用户一条的提醒配置 | | 通知 | `t_notification_channel` | 通知渠道 | | 通知 | `t_notification_log` | 通知发送日志 | @@ -38,6 +41,7 @@ | t_user | 1 : N | t_transaction | user_id | | t_user | 1 : N | t_tag | user_id | | t_user | 1 : N | t_channel | user_id | +| t_user | 1 : N | t_transaction_group | user_id | | t_user | 1 : N | t_rule | user_id | | t_user | 1 : N | t_ai_image | user_id | | t_user | 1 : N | t_notification_channel | user_id | @@ -64,6 +68,8 @@ | t_rule | N : M | t_tag | 中间表 t_rule_tag(联合唯一) | | t_ai_image | 1 : N | t_ai_image_transaction | image_id | | t_transaction | 1 : N | t_ai_image_transaction | transaction_id | +| 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`(用户内唯一),因此渠道改名不影响历史记录。 @@ -106,6 +112,12 @@ `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(自定义交易渠道) | 字段 | 说明 | @@ -122,12 +134,13 @@ - `t_ai_config`:每用户一条,含 `enabled`、`api_key`、`base_url`、`model` 与 **`vision_model`**(图片记账使用的视觉模型,默认 `deepseek-flash`)。 - `t_ai_image`:图片记账上传的截图,一次上传可产生多条(最多 5 张),含 `user_id`、`file_path`(相对应用运行目录,落盘于 `data/ai-images/`)、`mime_type`、`file_size`、`width`、`height`、`model`(解析所用视觉模型)、`summary`(识别摘要)、`record_count`(该图识别出的记录数)。 - `t_ai_image_transaction`:图片与交易记录的关联,含 `image_id`、`transaction_id`,唯一索引 `uk_ai_image_transaction (image_id, transaction_id)`;每笔记录按模型标注的来源图分别建立关联。 +- `t_ai_sql_history`:智能查数(自然语言转 SQL)的提问历史,含 `user_id`、`query_text`(截断 500 字符)、`created_at`;索引 `(user_id, created_at)`。 > **去重键**:图片记账与批量导入使用「收支类型 + 金额 + 交易时间(到秒)」作为业务去重键(`TransactionDedupUtils`),用于标记疑似重复并在导入时跳过,弥补这类记录没有外部交易号的不足。 ### 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 diff --git a/dev/database.md b/dev/database.md index 1ad0c10..5f86376 100644 --- a/dev/database.md +++ b/dev/database.md @@ -45,7 +45,7 @@ MySQL 的 JDBC URL 会自动附带 UTF-8、关闭 SSL、`Asia/Shanghai` 时区 ## 切换行为 -登录后可在「系统设置 → 数据库配置」随时切换数据库。切换后原数据库的数据保留,**不做自动迁移**。 +登录后可在「设置 → 数据库配置」随时切换数据库;测试与保存时通过 SSE 实时推送执行进度。切换后原数据库的数据保留,**不做自动迁移**。 切换成功后会清空进程内的用户偏好缓存(见[整体架构](/dev/architecture)),避免读到旧库的数据。 diff --git a/dev/development.md b/dev/development.md index c1965ed..c4d9ec8 100644 --- a/dev/development.md +++ b/dev/development.md @@ -20,6 +20,8 @@ - 前端改动**不使用 `npm run build` 验证**,直接依赖 Vite 开发服务器的 HMR。 - `npm run build`(`vue-tsc -b && vite build`)的产物仅用于生产部署,与开发模式无关。 - 界面文案统一在 `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` - `spotlessCheck` 已挂到 `check`,`build` 时会校验。 - **日志**:统一使用 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 以 `;;` 分隔语句。 - **对象转换**:实体与 VO 之间统一使用 MapStruct(`converter.VoConverter`),不在 Service 中手写 setter。 - **定时任务**:基于 Spring `@Scheduled`,新增调度器直接标注即可。 @@ -49,8 +51,9 @@ ## Git 提交规范 - 提交范围仅包含源码与配置;个人账单原始数据、`docs/`、`reload.sh`、`.gitignore`、`nexus-init.gradle` 及 AI 测试环境基础设施不纳入提交。 -- commit message 使用 `feat: 中文描述` 风格;纯重构/规范调整用 `refactor:`,构建与依赖配置用 `chore:` 或 `build:`。 -- 远端为本地 Gitea,主分支 `master`;推送使用一次性内嵌令牌的 URL,避免令牌写入 `origin` 配置。 +- commit message 采用「标题 + 概述段 + 分组列表」结构;标题为 `: <中文简述>`,纯重构/规范调整用 `refactor:`,构建与依赖配置用 `chore:` 或 `build:`。 +- 远端为本地 Gitea,**提交与推送仅使用 `dev` 分支**(`master` 仅用于稳定/发布,不纳入日常开发);推送使用一次性内嵌令牌的 URL,避免令牌写入 `origin` 配置。 +- 每次创建 commit 后需执行 `./package.sh` 打包验证,并在 `frontend/` 执行一次 `npx vue-tsc -b --force` 类型检查。 ## 相关文档 diff --git a/dev/index.md b/dev/index.md index e03d457..007e437 100644 --- a/dev/index.md +++ b/dev/index.md @@ -5,10 +5,10 @@ ## 设计与实现 - [整体架构](/dev/architecture):技术栈、后端分层、请求流程、认证与前端结构 -- [数据模型](/dev/data-model):21 张表的字段与关系、双数据库脚本差异 +- [数据模型](/dev/data-model):24 张表的字段与关系、双数据库脚本差异 - [账单解析](/dev/bill-parsing):解析器策略模式、两阶段导入、去重与 zip 解密 - [规则引擎](/dev/rule-engine):规则模型与匹配逻辑 -- [AI 集成](/dev/ai):OpenAI SDK 客户端、11 类调用类型与用量记录 +- [AI 集成](/dev/ai):RestClient 客户端、13 类调用类型与用量记录 - [定时通知](/dev/notifications):调度、渠道与发送日志 - [动态数据源](/dev/database):SQLite / MySQL 运行时配置与切换 - [前端用户偏好](/dev/preferences):偏好的存储介质、读写时机与完整清单 @@ -16,7 +16,7 @@ ## 工程与规范 - [项目结构](/dev/project-structure):目录组织与各包职责 -- [构建与部署](/dev/build-deploy):`bootJar`、Docker 编排、桌面 GUI 启动器与 jpackage +- [构建与部署](/dev/build-deploy):`bootJar` / `bootGuiJar`、jpackage 免安装包、发布产物下载与云服务器部署 - [开发环境与规范](/dev/development):AI 测试环境、前后端开发流程、代码规范、Git 提交规范 - [账单字段说明](/dev/bill-fields):支付宝账单导入字段的读取与丢弃情况 - [API 文档](/dev/api):Knife4j(OpenAPI 3)接口文档 diff --git a/dev/preferences.md b/dev/preferences.md index a5a9838..fff07c9 100644 --- a/dev/preferences.md +++ b/dev/preferences.md @@ -8,7 +8,7 @@ 为此项目在 `ledgerly/AGENTS.md` 中约定: -> 多 Tab 或可切换的界面偏好需**持久化记忆**(`localStorage`),下次打开时恢复到上次选择;存储键统一以 `finance.` 前缀命名,读取时对非法值回退默认项,写入时忽略存储异常。 +> 多 Tab 或可切换的界面偏好需**持久化记忆**(`localStorage`),下次打开时恢复到上次选择;存储键统一以 `ledgerly.` 前缀命名,读取时对非法值回退默认项,写入时忽略存储异常。 据此形成三条实践原则: @@ -32,38 +32,45 @@ | 键名 | 偏好内容 | 文件 | 保存时机 | 读取时机 | |---|---|---|---|---| -| `locale` | 界面语言 `zh-CN`/`en-US`/`ja-JP` | `locales/index.ts:16` | 切换语言 | 模块初始化 | -| `finance.dashboard.prefs` | 统计面板时间范围、粒度与 11 项筛选 | `views/Dashboard.vue:470` | 变量 `watch` 自动 | `onMounted` | -| `finance.transaction.filterPrefs` | 收支列表筛选、分页、排序 | `views/TransactionList.vue:369` | 变量 `watch` 自动 | `onMounted` | -| `finance.transaction.aiScope` | 批量 AI 建议范围 | `views/TransactionList.vue:146` | 变量 `watch` 自动 | ref 初始化 | -| `finance.transaction.aiRecordTab` | AI 记账方式(文字/图片) | `views/TransactionList.vue:206` | 变量 `watch` 自动 | ref 初始化 | -| `finance.transaction.aiQueryHistory` | AI 自然语言查询历史 | `views/TransactionList.vue:195` | 提交查询时 | `onMounted` | -| `finance.transaction.remarkHistory` | 备注关键词历史 | `views/TransactionList.vue:300` | 备注失焦/回车 | `onMounted` | -| `finance.dashboard.aiSearchHistory` | 统计面板 AI 查询历史 | `views/Dashboard.vue:91` | 提交 AI 搜索时 | `onMounted` | -| `finance.billImport.billType` | 上次选择的账单来源类型 | `views/BillImportPanel.vue:25` | 切换类型时 | ref 初始化 | -| `finance.transaction.relationType` | 账单关系类型偏好 | `components/TransactionRelationEditorModal.vue:14` | 创建模式切换类型时 | 弹窗初始化 | -| `emailBill.tab` | 邮箱账单页标签页 | `views/EmailBillManage.vue:43` | Tab 切换时 | 路由重定向 + 组件 | -| `logs.tab` | 系统日志页标签页 | `views/Logs.vue:25` | Tab 切换时 | 路由重定向 + 组件 | +| `ledgerly.locale` | 界面语言 `zh-CN`/`en-US`/`ja-JP` | `locales/index.ts` | 切换语言 | 模块初始化 | +| `ledgerly.dashboard.prefs` | 统计面板时间范围、粒度与多项筛选 | `views/Dashboard.vue` | 变量 `watch` 自动 | `onMounted` | +| `ledgerly.transaction.filterPrefs` | 收支列表筛选、分页、排序 | `views/TransactionList.vue` | 变量 `watch` 自动 | `onMounted` | +| `ledgerly.transaction.aiScope` | 批量 AI 建议范围 | `views/TransactionList.vue` | 变量 `watch` 自动 | ref 初始化 | +| `ledgerly.transaction.aiRecordTab` | AI 记账方式(文字/图片) | `views/TransactionList.vue` | 变量 `watch` 自动 | ref 初始化 | +| `ledgerly.transaction.aiQueryHistory` | AI 自然语言查询历史 | `views/TransactionList.vue` / `components/AiQueryWindow.vue` | 提交查询时 | 浮窗聚焦时 | +| `ledgerly.dashboard.aiSearchHistory` | 统计面板 AI 查询历史 | `views/Dashboard.vue` | 提交 AI 搜索时 | 浮窗聚焦时 | +| `ledgerly.transaction.aiQueryWindow` | 收支记录 AI 查询浮窗位置与尺寸 | `components/FloatingPanel.vue` | 拖拽/缩放结束时 | 浮窗初始化 | +| `ledgerly.dashboard.aiQueryWindow` | 统计面板 AI 搜索浮窗位置与尺寸 | `components/FloatingPanel.vue` | 拖拽/缩放结束时 | 浮窗初始化 | +| `ledgerly.transaction.remarkHistory` | 备注关键词历史 | `views/TransactionList.vue` | 备注失焦/回车 | `onMounted` | +| `ledgerly.billImport.billType` | 上次选择的账单来源类型 | `views/BillImportPanel.vue` | 切换类型时 | ref 初始化 | +| `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 偏好 | 键名 | 偏好内容 | 文件 | |---|---|---| -| `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/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/ai/config` | AI 启用开关、密钥、模型 | `components/AiConfigPanel.vue` | ## localStorage 偏好详解 -### 界面语言 `locale` +### 界面语言 `ledgerly.locale` - **文件**:`frontend/src/locales/index.ts` - **取值**:`zh-CN`、`en-US`、`ja-JP` @@ -72,7 +79,7 @@ - **目的**:记住用户语言选择,应用启动与切换即时生效。 - **入口**:`components/LocaleSwitch.vue`。 -### 统计面板偏好 `finance.dashboard.prefs` +### 统计面板偏好 `ledgerly.dashboard.prefs` - **文件**:`frontend/src/views/Dashboard.vue`(键名第 470 行) - **数据结构**(JSON 对象,保存见 `savePrefs()` 第 499 行): @@ -95,7 +102,7 @@ - **跨页写入**:点击趋势图跳转收支列表时,`applyTrendNavigation()`(第 381 行)把统计口径与筛选**合并写入**收支列表的偏好键,并保留分页、排序等既有设置。 - **目的**:下次进入统计面板自动还原时间范围、粒度与全部过滤条件。 -### 收支列表筛选偏好 `finance.transaction.filterPrefs` +### 收支列表筛选偏好 `ledgerly.transaction.filterPrefs` - **文件**:`frontend/src/views/TransactionList.vue`(键名第 369 行) - **数据结构**(保存见 `saveFilterPrefs()` 第 396 行): @@ -121,17 +128,17 @@ | 键名 | 取值 | 说明 | |---|---|---| -| `finance.transaction.aiScope` | `BOTH`/`CATEGORY`/`TAG` | 批量 AI 建议范围,非法值回退 `BOTH`(第 146 行) | -| `finance.transaction.aiRecordTab` | `text`/`image` | AI 记账方式,非法值回退 `text`(第 206 行) | -| `finance.transaction.aiQueryHistory` | 字符串数组(≤10) | AI 自然语言查询历史,最新在前 | -| `finance.transaction.remarkHistory` | 字符串数组(≤10) | 备注关键词历史,失焦/回车记录 | +| `ledgerly.transaction.aiScope` | `BOTH`/`CATEGORY`/`TAG` | 批量 AI 建议范围,非法值回退 `BOTH`(第 146 行) | +| `ledgerly.transaction.aiRecordTab` | `text`/`image` | AI 记账方式,非法值回退 `text`(第 206 行) | +| `ledgerly.transaction.aiQueryHistory` | 字符串数组(≤10) | AI 自然语言查询历史,最新在前 | +| `ledgerly.transaction.remarkHistory` | 字符串数组(≤10) | 备注关键词历史,失焦/回车记录 | - **`aiRecordTab`** 是 `AGENTS.md` 点名的参考实现:记住用户上次用的记账方式(文字或图片),下次打开 AI 记账弹窗直接落到该 Tab。 - **关键词历史**统一由 `composables/useKeywordHistory.ts` 管理:去重(忽略大小写)、最新在前、上限 10 条,损坏数据自动忽略,下拉末尾提供「清空历史」项。 - 读取:`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 行) - **取值**:账单来源编码(`ALIPAY`、`WECHAT`、`CMB` 等) @@ -139,7 +146,7 @@ - **保存**:`watch(billType)`(第 226 行)在切换类型时写入,同时清空已选文件,避免格式与类型不匹配。 - **目的**:记住上次选择的账单来源。 -### 账单关系类型 `finance.transaction.relationType` +### 账单关系类型 `ledgerly.transaction.relationType` - **文件**:`frontend/src/components/TransactionRelationEditorModal.vue`(第 14 行) - **取值**:`PARENT_CHILD`(默认)、`SIBLING` @@ -147,18 +154,32 @@ - **保存**:`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` -- **读取**:路由 `/email-bills`、`/logs` 的 `redirect` 读取偏好并拼接子路径(`router/index.ts:69`、第 93 行),带白名单校验,无记录回退默认。 +- **读取**:路由 `/email-bills`、`/logs` 的 `redirect` 读取偏好并拼接子路径(`router/index.ts`),带白名单校验,无记录回退默认。 - **保存**:`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`,按表格键(`transaction`、`categoryExpense`、`rule`、`logsHttp`、`smart-query` 等)分别记录。 +- **优先级**:本机记录(且须在当前分页候选中)> 全局默认值(后端 `paginationPrefs`)> 20。 +- **保存**:切换某表格每页条数时写入;候选列表与全局默认值在[设置 → 外观设置](/guide/settings#外观设置)中配置(存后端)。 ## sessionStorage 偏好:滚动位置 -- **文件**:`frontend/src/views/Layout.vue`(前缀第 83 行,键为 `finance.scroll.<路由路径>`) +- **文件**:`frontend/src/views/Layout.vue`(前缀第 83 行,键为 `ledgerly.scroll.<路由路径>`) - **保存**:内容区 `scroll` 事件经 `requestAnimationFrame` 节流写入;路由切换前保存离开页;`beforeunload` 兜底(第 172 行)。 - **读取**:`restoreScroll()`(第 139 行)在 `onMounted` 与路由切换后恢复;因页面内容异步加载、切换后高度可能不足,用 `rAF` 轮询等待内容高度足够再定位,2 秒超时后按当前可达最大值定位。 - **目的**:刷新或页面切换后恢复阅读位置;仅当前会话有效,关闭浏览器即失效。 @@ -181,9 +202,33 @@ - **前端**:设置页 `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 配置 -- **接口**:`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` 留空表示不修改原值。 - **前端**:设置页 `components/AiConfigPanel.vue` 编辑保存。 - **目的**:用户级 AI 能力配置;其他页面的 AI 元素统一经 `getAiConfig().enabled` 控制显隐。 @@ -195,11 +240,10 @@ 以下常见界面偏好当前**没有**持久化实现,改动时无需假设其存在: - **主题 / 暗色模式**:无主题存储键,界面主题固定。 -- **侧边栏折叠 / 展开**:`Layout.vue` 使用固定 `NLayoutSider`,无折叠状态。 +- **侧边栏整体折叠 / 展开**:`Layout.vue` 使用固定 `NLayoutSider`,无折叠状态(但菜单项的**顺序与显隐**可配置,见上「导航顺序与显隐」)。 - **表格列显示 / 隐藏 / 顺序**:各页列配置为固定 `computed`。 - **货币 / 金额格式**:无货币偏好。 - **独立日期格式**:仅由语言 `locale` 间接驱动 Naive UI 日期本地化,无单独偏好。 -- **全局分页大小**:仅收支列表的 `pageSize` 持久化;日志、规则、邮箱账单等页面分页大小不持久化。 - **IndexedDB / cookie**:未使用。 - **Pinia 持久化插件 / vueuse `useStorage`**:未安装、未使用。 @@ -214,8 +258,9 @@ 新增一项用户界面偏好时: -1. 存储键以 `finance.` 前缀命名,按页面/模块分段(如 `finance.transaction.xxx`)。 -2. 优先用 `watch` 自动保存,避免要求用户手动点保存。 -3. 读取时对取值做白名单校验,非法或损坏数据回退默认项。 -4. 写入包 `try/catch`,忽略存储异常。 -5. 需要跨设备一致时,改存后端并按当前用户隔离,而非 `localStorage`。 +1. 存储键常量集中登记在 `frontend/src/utils/storageKeys.ts` 的 `STORAGE_KEYS`,以 `ledgerly.` 前缀命名、按页面/模块分段(如 `ledgerly.transaction.xxx`);涉及改名时在 `LEGACY_STORAGE_KEYS` 登记旧键并用 `migrateKey` 迁移。 +2. 读写统一经 `frontend/src/utils/storage.ts`,**禁止**在业务代码中直接调用 `localStorage`/`sessionStorage`。 +3. 优先用 `watch` 自动保存,避免要求用户手动点保存。 +4. 读取时对取值做白名单校验,非法或损坏数据回退默认项;写入包 `try/catch`,忽略存储异常。 +5. 组件内的字符串型偏好用 `useStringPreference`,关键词历史用 `useKeywordHistory`,浮窗位置尺寸用 `FloatingPanel`(键经 `storageKey` 传入)。 +6. 需要跨设备一致时,改存后端并按当前用户隔离,而非 `localStorage`。 diff --git a/dev/project-structure.md b/dev/project-structure.md index f9a853d..293cab9 100644 --- a/dev/project-structure.md +++ b/dev/project-structure.md @@ -19,7 +19,7 @@ ledgerly/ ## 后端包结构 -`src/main/java/cn/rosercode/finance/` +`src/main/java/cn/rosercode/ledgerly/` | 包 | 职责 | |---|---| @@ -39,7 +39,8 @@ ledgerly/ | `notification` | 通知渠道发送器 | | `scheduler` | 定时任务(通知、日志清理、邮箱账单) | | `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` | 响应对象 | | `launcher` | 桌面 GUI 启动器(仅打入带 GUI 的 jar) | @@ -67,11 +68,12 @@ App.vue # 应用根组件 main.ts # 入口(挂载应用与插件) style.css # 全局样式 api/ # 各模块 REST 接口封装(axios) -components/ # 复用组件(账单预览、导入结果、AI/通知/数据库配置面板等) +components/ # 复用组件(账单预览、导入结果、AI/通知/数据库配置面板、账单关系、AI 查询浮窗等) +composables/ # 组合式函数(AI 启用状态、导航配置、分页选项、关键词历史等) locales/ # 中/英/日三语文案(messages.ts) router/ # 路由与导航守卫 store/ # Pinia 状态(user / database / registration) -utils/ # 工具(交易渠道标签、SSE 封装、渲染辅助) +utils/ # 工具(交易渠道标签、SSE 封装、本地偏好存储、渲染辅助) views/ # 页面组件 ``` diff --git a/dev/rule-engine.md b/dev/rule-engine.md index e438c77..68f1ebf 100644 --- a/dev/rule-engine.md +++ b/dev/rule-engine.md @@ -34,6 +34,7 @@ - `GET /api/rule/list`:返回当前用户全部规则(按优先级排序),供内部复用。 - `GET /api/rule/page`:分页查询,支持 `keyword`(匹配规则名称或关键词)、`enabled`、`type` 筛选,返回 `PageResult`。 +- `DELETE /api/rule/batch`:批量删除(body `{ ids }`);先按 `userId` 过滤归属,清理规则-标签关联后删除,返回实际删除数。 ## AI 生成规则 diff --git a/guide/ai.md b/guide/ai.md index dd77a6d..ae87141 100644 --- a/guide/ai.md +++ b/guide/ai.md @@ -1,6 +1,6 @@ # AI 能力 -系统集成 **DeepSeek** 大模型,在分类打标、查询、记账等环节提供智能辅助。所有 AI 功能需先在「系统设置 → AI 模型配置」中启用并配置 API Key。 +系统集成 **DeepSeek** 大模型,在分类打标、查询、记账等环节提供智能辅助。所有 AI 功能需先在「设置 → AI 模型配置」中启用并配置 API Key。 ## 数据与隐私 @@ -19,6 +19,8 @@ - 智能记账:你输入的一句话; - 生成分类/标签、推荐 Emoji/颜色:你输入的名称或描述; - 生成规则:所选记录的类型、备注、交易渠道、已有分类与标签(不含金额与原始账单数据); +- 生成账单关系名称:所选账单的收支类型、交易渠道、分类名称与备注(不含金额与原始账单数据); +- 智能查数:你输入的自然语言,以及可供查询的表结构说明(不含账单原始字段); - 图片记账:你**主动上传**的账单/支付截图会**整张**发送给视觉模型(可能含金额、交易对方、订单号等),视为你主动提供的输入。 图片同时会保存在本地(`data/ai-images/`),仅本人可查看;请勿上传包含他人敏感信息的图片。 @@ -27,14 +29,14 @@ API Key 仅保存在本地数据库中,仅用于向你配置的接口地址发 ## 配置 -在「系统设置 → AI 模型配置」中: +在「设置 → AI 模型配置」中: - 开启「启用 AI 建议」。 - 填写 **API Key**。 - 可选修改 **接口地址**(默认 `https://api.deepseek.com`)、**模型**(默认 `deepseek-chat`)与**视觉模型**(默认 `deepseek-flash`,用于图片记账)。 - 点击「测试连接」验证配置。 -![系统设置](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/settings.png) +![设置](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/settings.png) ## 功能一览 @@ -51,6 +53,8 @@ API Key 仅保存在本地数据库中,仅用于向你配置的接口地址发 | 推荐颜色 | 标签管理 → AI 推荐 | 为标签推荐颜色 | | 生成规则 | 规则管理 → AI 生成规则 | 由已打标签的记录归纳分类与标签规则 | | 统计搜索 | 统计面板 → AI 搜索 | 自然语言解析为统计筛选条件与维度 | +| 生成关系名称 | 收支记录 → 账单关系 → AI 生成 | 依据关联账单的类型、渠道、分类与备注生成关系名称与备注 | +| 智能查数 | 左侧菜单 → 智能查数 | 自然语言转只读 SQL 查询并展示结果 | ## 分类与标签建议 @@ -60,14 +64,14 @@ API Key 仅保存在本地数据库中,仅用于向你配置的接口地址发 ## 智能查询 -在收支记录页的 AI 查询框中输入自然语言,例如: +在收支记录页点「AI 搜索」打开**可拖拽、可缩放的浮窗**,在输入框中用自然语言描述,例如: - 「上个月餐饮」→ 时间范围 + 分类 - 「超过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 搜索框中用一句自然语言描述统计意图,例如「上个月餐饮花了多少」「近半年交通支出趋势,按周看」。系统会解析为**时间范围、收支类型、分类、标签、交易渠道、金额区间、备注、统计口径与趋势粒度**并自动回填、刷新图表。详见[统计面板](/guide/dashboard)。 +在统计面板顶部点「AI 搜索」打开浮窗,用一句自然语言描述统计意图,例如「上个月餐饮花了多少」「近半年交通支出趋势,按周看」。系统会解析为**时间范围、收支类型、分类、标签、交易渠道、金额区间、备注、统计口径与趋势粒度**并自动回填、刷新图表。详见[统计面板](/guide/dashboard)。 + +> 想要更自由的问答式查询(直接生成查询并返回明细),可使用[智能查数](/guide/sql-query)。 ## AI 生成规则 @@ -111,7 +117,7 @@ API Key 仅保存在本地数据库中,仅用于向你配置的接口地址发 ## 用量统计 -「模型使用情况」页面(系统设置 → AI 模型配置 →「使用情况」)展示 AI 调用汇总与明细。 +「模型使用情况」页面(设置 → AI 模型配置 →「使用情况」)展示 AI 调用汇总与明细。 ![模型使用情况](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/ai-usage.png) diff --git a/guide/dashboard.md b/guide/dashboard.md index 1ad6c7b..8b64b45 100644 --- a/guide/dashboard.md +++ b/guide/dashboard.md @@ -23,15 +23,17 @@ ## AI 搜索 -启用 AI 后,页面顶部会出现「**AI 搜索**」按钮,点击打开搜索弹窗。用一句自然语言描述统计意图,系统会调用大模型解析为统计参数并自动回填、刷新图表,例如: +启用 AI 后,页面顶部会出现「**AI 搜索**」按钮,点击打开一个**可拖拽、可缩放的浮窗**(非模态)。用一句自然语言描述统计意图,系统会调用大模型解析为统计参数并自动回填、刷新图表,例如: - 「上个月餐饮花了多少」→ 时间范围 + 分类 - 「近半年交通支出趋势,按周看」→ 时间范围 + 分类 + 按周粒度 - 「今年各类支出占比」→ 时间范围 + 类型 -![AI 统计搜索弹窗](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/ai-search.png) +![AI 统计搜索浮窗](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/ai-query-window.png) -可解析并回填的参数包括:**时间范围、收支类型、分类、标签、交易渠道、金额区间、备注、统计口径**与**趋势粒度**。弹窗内提供示例问题,输入框会记住历史记录(可一键清空);回车或点击「AI 查询」即可执行,无可用条件时会提示且不改动当前筛选。详见 [AI 能力](/guide/ai)。 +可解析并回填的参数包括:**时间范围、收支类型、分类、标签、交易渠道、金额区间、备注、统计口径**与**趋势粒度**。浮窗内提供示例问题,输入框会记住历史记录(可一键清空);按 `Ctrl + Enter`(Mac 为 `⌘ + Enter`)或点击提交按钮执行,无可用条件时会提示且不改动当前筛选。详见 [AI 能力](/guide/ai)。 + +> 想要返回明细的自由问答式查询,可使用[智能查数](/guide/sql-query)。 ## 筛选条件 @@ -39,7 +41,7 @@ - **类型**:支出 / 收入(多选)。 - **分类**:多选,含「未分类」。 - **交易渠道**:多选。 -- **标签**:多选。 +- **标签**:多选,含「无标签」。 - **金额区间**:最小 ~ 最大。 - **备注搜索**。 @@ -55,28 +57,37 @@ | 图表 | 说明 | |---|---| -| 收支趋势(柱状图) | 按所选粒度展示收入(绿)与支出(红),点击柱子或该列空白处可跳转查看对应明细 | -| 支出分类占比(环形图) | 各分类支出金额占比 | -| 收入分类占比(环形图) | 各分类收入金额占比 | -| 支出交易渠道占比(环形图) | 仅统计支出,按交易渠道 | -| 收入交易渠道占比(环形图) | 仅统计收入,按交易渠道 | +| 收支趋势(柱状图) | 按所选粒度展示收入(绿)与支出(红),**点击柱子或该列空白处**跳转查看对应明细 | +| 支出分类占比(环形图) | 各分类支出金额占比,**点击扇区**跳转该分类明细 | +| 收入分类占比(环形图) | 各分类收入金额占比,**点击扇区**跳转该分类明细 | +| 支出交易渠道占比(环形图) | 仅统计支出,按交易渠道,**点击扇区**跳转该渠道明细 | +| 收入交易渠道占比(环形图) | 仅统计收入,按交易渠道,**点击扇区**跳转该渠道明细 | 切换语言时,图表标题、图例与交易渠道名称会实时重绘。 -### 点击趋势图查看明细 +### 点击图表查看明细 -「收支趋势」柱状图的**整列都可点击**:点中柱子,或点击柱子顶端上方的空白处均可。点击后跳转到[收支记录](/guide/transactions),并自动带入该列对应的筛选条件: +所有图表都可点击下钻,跳转到[收支记录](/guide/transactions)并自动带入对应筛选条件: -- **类型**:点中柱子时取该柱子的收入 / 支出;点击空白处时不限定类型(沿用面板当前类型筛选); -- **日期范围**:该列代表的时间桶——按日→当天、按周→周一至周日、按月→当月、按季→当季、按年→当年; -- **其他筛选**:统计面板当前的统计口径、分类、交易渠道、标签、金额区间与备注关键字。 +- **趋势柱状图**:整列可点击——点中柱子,或点击柱子顶端上方的空白处均可。 + - **类型**:点中柱子时取该柱子的收入 / 支出;点击空白处时不限定类型(沿用面板当前类型筛选); + - **日期范围**:该列代表的时间桶——按日→当天、按周→周一至周日、按月→当月、按季→当季、按年→当年。 +- **分类饼图**:点击某个扇区,按该分类(含「未分类」)与对应收支类型筛选。 +- **渠道饼图**:点击某个扇区,按该交易渠道与对应收支类型筛选。 + +无论从哪张图下钻,都会同时保留面板当前的统计口径、标签、金额区间与备注关键字等筛选。 ![点击趋势柱状图跳转收支记录](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/dashboard-trend-jump.png) -跳转后可直接查看该时间段内的对应明细;筛选条件会照常记忆,收支记录页原有的每页条数与排序偏好不受影响。 +页面底部的「**查看收支记录**」按钮则会携带**当前全部筛选条件**跳转,便于核对整体明细。 + +> 从[收支记录](/guide/transactions)点「查看统计」也会跳回本页,并还原收支记录当前的筛选条件(时间范围会切换为「自定义」)。 + +跳转后可直接查看对应明细;筛选条件会照常记忆,收支记录页原有的每页条数与排序偏好不受影响。 ## 小技巧 - 想知道「上个月餐饮花了多少」,可把时间范围设为「本月」或自定义,并在分类中只勾选「餐饮」。 - 不计入统计的记录(如借款、账户间转账)默认不会计入汇总;如需查看全部,把统计口径切换为「全部记录」。 - 想快速核对某月、某周或某天的明细,直接点击「收支趋势」上对应的列(柱子或该列空白处),即可带着时间范围跳到收支记录。 +- 点击分类或渠道饼图的某个扇区,可一键下钻到该分类 / 渠道的明细;点「查看收支记录」则带上当前全部筛选。 diff --git a/guide/download.md b/guide/download.md new file mode 100644 index 0000000..2305a4e --- /dev/null +++ b/guide/download.md @@ -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)。 diff --git a/guide/faq.md b/guide/faq.md index 8c84392..5662506 100644 --- a/guide/faq.md +++ b/guide/faq.md @@ -14,7 +14,7 @@ ### 支持哪些账单? -支付宝、微信、美团、招商银行、交通银行,详见[账单类型与获取](/guide/bills/overview)。 +支付宝、微信、美团、淘宝、京东、招商银行、交通银行,详见[账单类型与获取](/guide/bills/overview)。 ### 导入后重复了怎么办? @@ -50,7 +50,7 @@ ### 想让备注不显示在界面上? -可在记录行使用「隐藏」功能,备注会显示为 `******`,需要时再「查看」或「取消隐藏」。 +可在记录行使用「隐藏」功能,备注会显示为 `******`,需要时再「查看」或「取消隐藏」。默认这些记录会从收支记录列表与 Excel 导出中排除;如需让其显示,可在「设置 → 外观设置」开启「显示已隐藏备注的记录」。 ### 分类和标签有什么区别? @@ -60,7 +60,7 @@ ### AI 功能不可用? -请先在「系统设置 → AI 模型配置」中开启并填写 API Key,然后点「测试连接」验证。 +请先在「设置 → AI 模型配置」中开启并填写 API Key,然后点「测试连接」验证。 ### 邮箱账单会自动入库吗? @@ -78,7 +78,7 @@ ### 忘记密码怎么办? -当前版本未提供自助找回。可在数据库中重置对应用户的密码哈希,或新建账号后迁移数据。 +登录后可在「设置 → 账号安全」中,凭**原密码**修改密码(也支持一键随机生成新密码)。若已完全忘记密码,当前版本暂未提供自助找回,可在数据库中重置该用户的密码哈希,或新建账号后迁移数据。 ### 数据存在哪里? diff --git a/guide/getting-started.md b/guide/getting-started.md index c3a7191..2845f72 100644 --- a/guide/getting-started.md +++ b/guide/getting-started.md @@ -33,7 +33,7 @@ ![数据库初始化](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/settings.png) -首次进入时按引导选择数据库类型并填写连接信息,点击「保存并应用」即可。登录后可通过右上角用户菜单 →「数据库设置」随时切换数据库;切换后原数据库的数据保留,不做自动迁移。 +首次进入时按引导选择数据库类型并填写连接信息,点击「保存并应用」即可。登录后可通过右上角用户菜单 →「设置」→「数据库配置」随时切换数据库;切换后原数据库的数据保留,不做自动迁移。 ## 注册与登录 @@ -44,7 +44,7 @@ ![注册卡片](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/register-card.png) -注册功能可在「系统设置 → 注册设置」中开启或关闭。新注册用户会自动获得一套默认分类、标签与邮箱发件人规则。 +注册功能可在「设置 → 注册设置」中开启或关闭。新注册用户会自动获得一套默认分类、标签与邮箱发件人规则。 ## 界面语言 @@ -56,7 +56,8 @@ 2. 进入「分类管理」「标签管理」按自己的习惯调整分类与标签。 3. 在「规则管理」配置常用自动归类规则(如「星巴克 → 餐饮」)。 4. 导出并导入历史账单(见[账单类型与获取](/guide/bills/overview))。 -5. 按需配置「AI 模型」「定时通知」「邮箱账单」。 +5. 在「设置」中按需配置「AI 模型」「定时通知」「邮箱账单」,并可调整「外观设置」与「导航设置」。 +6. 启用 AI 后,可用左侧「智能查数」以自然语言查询账本。 ## 下一步 diff --git a/guide/index.md b/guide/index.md index aa06125..7043a5b 100644 --- a/guide/index.md +++ b/guide/index.md @@ -6,25 +6,28 @@ - [项目简介](/guide/introduction):系统定位、核心功能与技术栈 - [快速开始](/guide/getting-started):数据库配置、注册登录、界面语言与首次使用建议 +- [下载与安装](/guide/download):Windows / Linux 安装包与可执行 jar 的下载、运行与校验 - [统计面板](/guide/dashboard):时间范围、筛选条件与图表说明 ## 记账与整理 - [收支记录](/guide/transactions):记录管理、筛选、批量操作、账单导入、AI 建议与记账 +- [账单关系](/guide/relations):父子/同级关系、关系图与 AI 生成关系名称 - [分类与标签](/guide/categories-tags):分类与标签的维护与使用建议 - [交易渠道管理](/guide/channels):内置渠道与自定义渠道的新建、改名与删除 -- [规则管理](/guide/rules):按关键词/金额/类型自动归类打标,支持 AI 生成规则、搜索与分页 +- [规则管理](/guide/rules):按关键词/金额/类型自动归类打标,支持 AI 生成规则、搜索、分页与批量删除 - [邮箱账单](/guide/email-bills):IMAP 自动拉取账单邮件 ## 智能与自动化 -- [AI 能力](/guide/ai):分类标签与渠道建议、自然语言查询与记账、多图图片记账(实时进度)、AI 搜索与规则生成、用量统计 +- [AI 能力](/guide/ai):分类标签与渠道建议、自然语言查询与记账、多图图片记账(实时进度)、AI 搜索与规则生成、关系命名、用量统计 +- [智能查数](/guide/sql-query):用自然语言生成只读 SQL 查询账本,结果支持卡片/表格/图表 - [定时提醒与通知](/guide/notifications):多渠道账单导入提醒 ## 系统 -- [系统设置](/guide/settings):数据库、注册、账单来源统计、外观、AI、通知配置 -- [系统日志](/guide/logs):访问日志、登录日志、运行日志 +- [设置](/guide/settings):账号安全、数据库、注册、账单来源统计、外观、导航、AI、通知配置 +- [日志](/guide/logs):访问日志、登录日志、运行日志 ## 账单获取 diff --git a/guide/introduction.md b/guide/introduction.md index 1fd6b1e..71206dd 100644 --- a/guide/introduction.md +++ b/guide/introduction.md @@ -18,13 +18,14 @@ | 收支记录 | 增删改查、分页与多条件筛选、批量设置分类/标签、批量删除、计入统计开关、备注隐藏、导出 Excel | | 分类与标签 | 收入/支出分类与标签的增删改查,新用户自动获得默认数据,AI 推荐 Emoji 与颜色 | | 交易渠道 | 内置渠道 + 自定义渠道的新建/改名/删除,改名不影响历史账单 | -| 统计图表 | 月度收支、分类占比、收支趋势、交易渠道占比,支持按日/周/月/季/年粒度,点击趋势图可下钻对应明细 | +| 统计图表 | 月度收支、分类占比、收支趋势、交易渠道占比,支持按日/周/月/季/年粒度,图表可点击下钻对应明细 | +| 账单关系 | 父子/同级多笔账单关联、自动绘制关系图,AI 生成关系名称与备注 | | 账单导入 | 七类账单解析、zip 解密、解析预览、按交易号去重、规则建议 | -| 自定义规则 | 关键词/收支类型/金额区间组合,优先级排序、启用停用,导入时自动应用;支持 AI 生成规则、搜索与分页 | -| AI 能力 | 分类标签与渠道建议、自然语言查询、文字/多图图片记账、AI 统计搜索、AI 生成规则、分类/标签/渠道生成、Emoji/颜色推荐、用量统计 | +| 自定义规则 | 关键词/收支类型/金额区间组合,优先级排序、启用停用,导入时自动应用;支持 AI 生成规则、搜索、分页与批量删除 | +| AI 能力 | 分类标签与渠道建议、自然语言查询、智能查数(自然语言转只读 SQL)、文字/多图图片记账、AI 统计搜索、AI 生成规则、分类/标签/渠道生成、关系命名、Emoji/颜色推荐、用量统计 | | 邮箱账单 | 多邮箱 IMAP IDLE 监听、发件人规则识别账单类型、待处理列表先预览再导入 | | 定时通知 | 每天/每周/每月提醒,邮箱 SMTP 与 Server 酱渠道,发送日志 | -| 系统日志 | 访问日志、登录日志、运行日志在线查看,运行日志实时推送,自动清理 | +| 日志 | 访问日志、登录日志、运行日志在线查看,运行日志实时推送,自动清理 | | 账号与多语言 | JWT 登录、注册开关、运行时数据库配置(SQLite/MySQL)、中/英/日三语 | ## 技术栈 diff --git a/guide/logs.md b/guide/logs.md index df36fb9..e6b3541 100644 --- a/guide/logs.md +++ b/guide/logs.md @@ -1,6 +1,6 @@ -# 系统日志 +# 日志 -系统日志通过右上角用户菜单 →「系统日志」进入,包含三个标签页:**访问日志**、**登录日志**、**运行日志**。页头右上角可选择日志保留时长(30 天 / 90 天 / 180 天 / 永久保留)。 +日志通过右上角用户菜单 →「日志」进入,包含三个标签页:**访问日志**、**登录日志**、**运行日志**。页头右上角可选择日志保留时长(30 天 / 90 天 / 180 天 / 永久保留)。 ## 访问日志 diff --git a/guide/notifications.md b/guide/notifications.md index 81daab3..6c6499b 100644 --- a/guide/notifications.md +++ b/guide/notifications.md @@ -1,8 +1,8 @@ # 定时提醒与通知 -系统可按周期提醒你导入新账单,并通过邮箱或 Server 酱推送通知。配置入口为「系统设置 → 定时通知」。 +系统可按周期提醒你导入新账单,并通过邮箱或 Server 酱推送通知。配置入口为「设置 → 定时通知」。 -![系统设置](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/settings.png) +![设置](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/settings.png) ## 提醒周期 diff --git a/guide/pages.md b/guide/pages.md index 73c545c..433d455 100644 --- a/guide/pages.md +++ b/guide/pages.md @@ -10,30 +10,35 @@ | 注册 | `/register` | 创建账号 | 登录页「没有账号?去注册」(注册开启时) | | 数据库初始化 | `/database-setup` | 首次配置数据库 | 数据库未配置时自动引导 | | 统计面板 | `/dashboard` | 收支汇总与图表 | 登录后默认页;左侧菜单 | +| 智能查数 | `/smart-query` | 自然语言转只读 SQL 查询 | 左侧菜单(**AI 启用时**) | | 收支记录 | `/transactions` | 记录管理与**账单导入** | 左侧菜单 | | 分类管理 | `/categories` | 收入/支出分类维护 | 左侧菜单 | | 标签管理 | `/tags` | 标签维护 | 左侧菜单 | | 交易渠道管理 | `/channels` | 自定义交易渠道维护 | 收支记录页「渠道管理」按钮 | | 规则管理 | `/rules` | 自动归类规则 | 左侧菜单 | | 邮箱账单 | `/email-bills/*` | 邮箱配置 / 发件人规则 / 待处理账单 | 左侧菜单 | -| 系统设置 | `/settings` | 数据库 / 注册 / 账单来源统计 / 外观 / AI / 通知配置 | 顶部用户菜单 | +| 设置 | `/settings` | 账号安全 / 数据库 / 注册 / 账单来源统计 / 外观 / 导航 / AI / 通知配置 | 顶部用户菜单 | | 模型使用情况 | `/ai-usage` | AI 调用统计与明细 | 设置页「AI 模型配置」→ 使用情况 | -| 系统日志 | `/logs/*` | 访问 / 登录 / 运行日志 | 顶部用户菜单 | +| 日志 | `/logs/*` | 访问 / 登录 / 运行日志 | 顶部用户菜单 | | 账单导入(独立页) | `/bill-import` | 手机端上传账单 | **仅由通知邮件链接直达** | +> 「账单关系」不是独立页面,它是收支记录内的弹窗:由列表「关系」列或「批量操作 → 建立关系」进入,详见[账单关系](/guide/relations)。 + ## 全局导航 ### 左侧菜单 -统计面板 → 收支记录 → 分类管理 → 标签管理 → 规则管理 → 邮箱账单 +统计面板 → 智能查数(AI 启用时)→ 收支记录 → 分类管理 → 标签管理 → 规则管理 → 邮箱账单 + +左侧菜单的**顺序与显隐可自定义**(设置 → 导航设置),并在下次进入时还原。 ### 顶部用户菜单 -- **系统日志**:`/logs` -- **系统设置**:`/settings` +- **日志**:`/logs` +- **设置**:`/settings` - **退出**:清空登录态并返回 `/login` -> 「模型使用情况」没有独立菜单入口,需从「系统设置 → AI 模型配置 → 使用情况」进入。 +> 「模型使用情况」没有独立菜单入口,需从「设置 → AI 模型配置 → 使用情况」进入。 > > 「交易渠道管理」同样没有独立菜单入口,需从「收支记录 → 渠道管理」进入。 @@ -41,20 +46,23 @@ - **首次使用**:`/database-setup` → `/register` → `/login` → `/dashboard` - **手动记账**:收支记录 → 点击「新增记录」→ 表单弹窗 → 保存 -- **查看趋势明细**:统计面板 → 点击「收支趋势」某一列(柱子或该列空白处)→ 收支记录(自动带入时间范围、类型与当前筛选条件) +- **查看趋势明细**:统计面板 → 点击趋势柱、分类/渠道饼图或「查看收支记录」→ 收支记录(自动带入对应筛选条件) +- **反向跳转**:收支记录 → 点击「查看统计」→ 统计面板(还原当前筛选条件) - **账单导入**:收支记录 → 点击「导入账单」→ 解析预览 → 确认导入 → 回到收支记录 - **手机导入**:通知邮件链接 → `/bill-import`(独立页,非弹窗)→ 上传解析 → 确认导入 - **渠道维护**:收支记录 → 点击「渠道管理」→ 新建 / 改名 / 删除自定义渠道 → 返回收支记录 +- **账单关系**:收支记录「关系」列 → 关系弹窗;或「批量操作」勾选多条 → 建立关系 +- **智能查数**:左侧菜单「智能查数」→ 输入自然语言 → 查看结果、生成的 SQL 与历史提问 - **自动归类**:规则管理配置规则 → 账单导入预览时命中并给出建议 - **邮箱账单**:邮箱配置 → 发件人规则 → 待处理账单 → 解析 → 导入到收支记录 -- **AI 辅助**:系统设置启用 AI → 统计面板「AI 搜索」、收支记录「一键设置 / 文字·图片记账 / AI 查询」、编辑记录「分类·标签·渠道 AI 推荐与新建」、分类与标签「AI 推荐」、规则管理「AI 生成规则」 -- **系统维护**:顶部菜单 → 系统设置 / 系统日志 +- **AI 辅助**:设置启用 AI → 统计面板「AI 搜索」、收支记录「一键设置 / 文字·图片记账 / AI 查询」、编辑记录「分类·标签·渠道 AI 推荐与新建」、分类与标签「AI 推荐」、规则管理「AI 生成规则」、账单关系「AI 生成」、左侧「智能查数」 +- **系统维护**:顶部菜单 → 设置 / 日志 ## 页面关系说明 -- **收支记录**是核心页面:手动记账、账单导入、批量设置、AI 能力都从这里进入,导入完成后也回到该页查看。 +- **收支记录**是核心页面:手动记账、账单导入、批量操作、账单关系与 AI 能力都从这里进入,导入完成后也回到该页查看。 - **账单导入**有两条独立路径: - 收支记录页的「导入账单」**弹窗**(桌面端常用); - 通知邮件直达的 **`/bill-import` 独立页**(面向手机,非弹窗,元素精简,系统内无其他入口)。 -- **系统设置**聚合了数据库、注册、账单来源统计、外观、AI、通知六类配置;模型用量与日志分别从设置页与顶部菜单进入。 -- **交易渠道管理**是收支记录页的附属页,用于维护自定义渠道,维护完成后返回收支记录继续记账或筛选。 +- **设置**聚合了账号安全、数据库、注册、账单来源统计、外观、导航、AI、通知八类配置;模型用量与日志分别从设置页与顶部菜单进入。 +- **交易渠道管理**是收支记录页的附属页;**账单关系**是收支记录内的弹窗,两者都在整理完成后回到收支记录继续操作。 diff --git a/guide/relations.md b/guide/relations.md new file mode 100644 index 0000000..b9cf8d5 --- /dev/null +++ b/guide/relations.md @@ -0,0 +1,68 @@ +# 账单关系 + +「账单关系」用于把**互相关联的多笔收支**组合在一起,例如「交通卡充值」与后续的乘车扣款、分期还款的每一期、支付链路上的多笔记录。建立关系只做关联,不会改动金额,也不影响统计。 + +## 两种关系类型 + +| 类型 | 含义 | 典型场景 | +|---|---|---| +| **父子关系** | 一个「父账单」(资金池)对应多个「子账单」(从资金池流出) | 交通卡充值 → 多次乘车;账户充值 → 逐笔消费 | +| **同级关系** | 多笔地位平等的账单聚合在一起 | 一笔消费在多个平台留下的关联记录、分次归还 | + +- 父子关系中,**必须且只能有一条父账单**,其余为子账单。 +- 同级关系中,所有成员地位相同,按加入顺序依次连接。 + +## 入口 + +| 入口 | 说明 | +|---|---| +| 收支记录列表「**关系**」列 | 该行所属关系组数量,点击「N 个关系」打开关系管理弹窗 | +| 收支记录「**批量操作**」 | 进入多选模式,勾选**至少两条**后点「建立关系」,直接新建关系组 | +| 账单**详情**弹窗 | 「账单关系」分组内查看该笔账单的全部关系 | + +![新建账单关系弹窗](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/relation-editor.png) + +## 新建与编辑关系 + +1. 选择**关系类型**(父子关系 / 同级关系)。 +2. 填写**关系名称**(可选,如「交通卡充值」)与**备注**。 +3. 点「添加账单」,在分页选择弹窗中勾选要关联的账单(支持备注搜索)。 +4. 父子关系下,用单选指定其中一条为**父账单**(从批量操作进入时,默认以被操作的那条为父账单,且不可移除);同级关系无需指定,成员按列表顺序排列。 +5. 点击「保存」。 + +约束与说明: + +- 一个关系组**至少包含两条账单**,成员上限 50 条。 +- **一笔账单不能同时加入两个同类型的关系组**;如需更换,请先在原关系组中解除。 +- **关系名称会记住历史**:再次新建同名类型的关系时,名称输入框会自动给出历史名称建议(按最近使用、去重,最多 50 条)。 +- 关系类型会被**记忆**:下次新建时默认沿用上次选择的类型。 + +## 关系图 + +关系管理弹窗中,每个关系组都会以自绘的关系图展示: + +- 节点为圆形,**支出为红色、收入为绿色**;当前正在查看的账单以蓝色描边且节点更大。 +- **父子关系**:父账单居中在上,子账单分行排列,父 → 子连线带箭头。 +- **同级关系**:成员按顺序链式连接,边上标注「关联」。 +- 节点内显示金额,节点下方显示备注或分类;鼠标悬停可查看完整信息。成员较多时自动换行,图形自适应宽度。 + +![账单关系图](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/relation-panel.png) + +## AI 生成名称与备注 + +启用 AI 后,新建/编辑关系弹窗中会出现「**AI 生成**」按钮。系统会把成员账单的**收支类型、交易渠道、分类名称与备注**发送给模型(**不包含金额**),由其归纳出一个关系名称与简短备注,并自动填入;你可以在保存前修改。 + +> 成员少于两条时无法生成,会提示先添加账单。生成的名称会覆盖名称框(备注仅在返回非空时覆盖)。 + +## 删除关系 + +在关系组卡片上点「删除」,确认后**仅解除关联**,不会删除任何账单。删除单笔账单时,系统会自动把它从所属关系组中移除;若某个关系组剩余成员不足两条、或父子关系不再有父账单,该关系组会自动解散。 + +## 与统计的关系 + +账单关系只用于**查看与整理**,不参与统计口径。每笔账单是否计入统计,仍由各自的「计入统计」开关决定,详见[收支记录](/guide/transactions)。 + +## 相关 + +- [收支记录](/guide/transactions):记录管理与批量操作。 +- [AI 能力](/guide/ai):AI 生成关系名称、分类标签建议等。 diff --git a/guide/rules.md b/guide/rules.md index ed51580..305169d 100644 --- a/guide/rules.md +++ b/guide/rules.md @@ -52,6 +52,15 @@ 点击「搜索」按条件查询,「清空」重置全部条件。新增规则后回到第一页,便于查看新规则。 +## 多选与批量删除 + +规则列表每行前都有勾选框,表头可**全选当前页**: + +- 勾选若干规则后,点右上角「**批量删除**」并确认,即可一次性删除选中的规则。 +- 未勾选任何规则时「批量删除」不可点击。 + +> 删除规则只影响后续导入时的自动归类,已应用的历史记录分类与标签不会被撤销。 + ## AI 生成规则 启用 AI 后,规则管理页右上角会出现「**AI 生成规则**」按钮,可让 AI 依据**已打标签的记录**自动归纳出「关键词 → 分类/标签」规则,无需手工逐条编写。 diff --git a/guide/settings.md b/guide/settings.md index 00f015e..9c0105c 100644 --- a/guide/settings.md +++ b/guide/settings.md @@ -1,8 +1,20 @@ -# 系统设置 +# 设置 -系统设置通过右上角用户菜单 →「系统设置」进入,包含六个配置面板。 +「设置」通过右上角用户菜单 →「设置」进入,包含八个配置面板。 -![系统设置](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/settings.png) +![设置](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/settings.png) + +## 账号安全 + +![账号安全面板](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/panel-account-security.png) + +用于**修改当前登录账号的密码**: + +- 输入**原密码**、**新密码**与**确认新密码**,点「修改密码」提交。 +- 新密码长度需为 **6–50 个字符**,且不能与原密码相同。 +- 可点「**随机生成**」自动生成一个 12 位、含大小写字母与数字的密码(已排除易混淆字符),生成后会明文显示并可一键**复制**;修改成功后请使用新密码登录。 + +> 修改密码仅校验原密码,**不提供**通过邮箱等方式的密码找回。 ## 数据库配置 @@ -13,6 +25,8 @@ - **MySQL**:主机、端口、数据库名、用户名、密码。 - 提供「测试连接」与「保存并应用」。保存后立即生效并自动建表;已配置时密码留空表示不修改。 +测试与保存时会实时展示**执行进度**:连接测试、建表、结构迁移、数据源切换等步骤会以进度日志逐条推送,处理较久时请保持窗口打开,完成后点「关闭」即可。 + 系统支持启动后再配置数据库:未配置时前端会引导进入初始化页面,无需启动前准备。登录后也可随时切换数据库,切换后原数据库数据保留、不做自动迁移。 ## 注册设置 @@ -41,12 +55,37 @@ ![外观设置面板](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/panel-display.png) -选择「不计入统计」的记录在[收支记录](/guide/transactions)列表中的展示方式: +外观设置包含三组配置,**修改后自动保存**,无需再点保存按钮: + +**1. 不计入统计的记录样式**:选择「不计入统计」的记录在[收支记录](/guide/transactions)列表中的展示方式: - **删除线(默认)**:整行加删除线,类型与金额列保持原样。 - **灰色标签**:类型列显示灰色「不计入」标签,金额列置灰且不显示正负号。 -面板内提供**实时预览**,切换选项即可看到效果;该偏好按**当前账号**保存,登录后生效。 +面板内提供**实时预览**,切换选项即可看到效果。 + +**2. 显示已隐藏备注的记录**(默认关闭): + +- 关闭时,备注被隐藏的记录不会出现在收支记录列表与 Excel 导出中。 +- 开启后,这些记录会正常显示(备注仍为 `******`,可临时「查看」)。 + +**3. 表格每页条数**: + +- **表格每页条数选项**:所有表格分页共用的候选列表,默认 `10 / 20 / 50 / 100`,可自行增删(最多 10 项)。 +- **默认每页条数**:从候选中选择,作为各表格未单独记忆时的默认值(默认 20)。 +- 每张表格会分别记住上次选择的每页条数;本机记忆优先于全局默认值。 + +## 导航设置 + +![导航设置面板](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/panel-nav-menu.png) + +调整左侧导航的**显示顺序与显隐**(按当前账号保存,跨设备生效): + +- **拖动**每项左侧的手柄即可调整顺序,修改后自动保存。 +- 用开关控制各项的显示或隐藏。 +- 至少需保留一个可见项;「**恢复默认**」可还原为默认顺序与全部显示。 + +> 隐藏只移除菜单入口,页面仍可通过地址直接访问。当 AI 未启用时,依赖 AI 的菜单项(如「智能查数」)不会出现在此处,也不会显示在左侧导航。 ## AI 模型配置 @@ -68,5 +107,5 @@ ## 相关 -- [系统日志](/guide/logs):查看访问、登录与运行日志。 +- [日志](/guide/logs):查看访问、登录与运行日志。 - [快速开始](/guide/getting-started):数据库配置与注册登录。 diff --git a/guide/sql-query.md b/guide/sql-query.md new file mode 100644 index 0000000..4c3d7c8 --- /dev/null +++ b/guide/sql-query.md @@ -0,0 +1,74 @@ +# 智能查数 + +「智能查数」让你用**自然语言**直接查询账本,由 AI 生成一条**只读 SQL** 并执行,结果以统计卡片、表格或图表展示,无需自己拼筛选条件。 + +> 这是与[统计面板的「AI 搜索」](/guide/dashboard#ai-搜索)不同的能力:AI 搜索是「自然语言 → 统计面板筛选条件」,智能查数则是「自然语言 → SQL 查询」,适合更自由、更灵活的问答。 + +## 入口与前提 + +- 先在[设置](/guide/settings#ai-模型配置)中**启用 AI** 并配置 API Key。 +- 启用后,左侧菜单会出现「**智能查数**」(未启用时不显示)。 +- 直接访问路由 `/smart-query` 也可进入。 + +![智能查数](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/sql-query.png) + +## 提问 + +在页面顶部的输入框用一句自然语言描述想查的内容,例如: + +- 上个月一共花了多少钱 +- 按分类统计今年的支出 +- 今年收入最高的一个月 +- 各渠道的支出占比 +- 本月支出最高的 5 笔 + +输入框支持多行(自动增高),点击「查询」或按 `Ctrl + Enter`(Mac 为 `⌘ + Enter`)提交;页面也会给出示例问题,点击即可直接查询。 + +## 结果展示 + +系统会根据查询结果自动选择展示方式: + +| 结果形态 | 展示 | +|---|---| +| 只有一行 | **统计卡片**,逐项展示各列的数值 | +| 多行且含数值列 | **动态表格**,可切换为**柱状图**或**饼图** | +| 多行但无数值列 | 表格 | + +- 结果中的**渠道**列会自动显示渠道名称(含自定义渠道),而不是内部编码。 +- 结果最多返回 **1000 行**;超过时会提示「仅展示前 1000 行」。 +- 无结果的查询会显示空态提示。 + +## 生成的 SQL + +查询结果上方会给出 AI 生成的 SQL,默认折叠: + +- 使用 `sql-formatter` 缩进、`highlight.js` 语法高亮,便于阅读。 +- 可点「复制 SQL」把格式化后的语句复制到剪贴板。 +- 同时展示对查询意图的简要说明。 + +## 历史提问 + +页面下方是**历史提问**折叠面板,按账号在服务端持久化: + +- 展示提问内容与时间(精确到分钟),默认展示最近若干条。 +- 支持按关键字**搜索**历史。 +- 点击某条历史可**回填并立即重新查询**;也可复制该次提问。 +- 可一键**清空历史**。 + +> 当前会话内的提问与结果会缓存在浏览器会话中,切换账号时自动清除;结果超过 500 行时只缓存提问与视图,避免占用过多空间。 + +## 安全与隐私 + +智能查数始终以**只读**方式访问你自己的数据: + +- 仅允许**单条 `SELECT`** 查询,拒绝任何写操作、DDL、多语句、`SELECT INTO`、`FOR UPDATE` 与危险函数。 +- 只能查询**系统白名单内的数据表**,且每张表都会被强制注入当前用户的隔离条件,**只能看到本人数据**。 +- 查询**不返回原始数据(`rawData`)与交易号**等敏感字段。 +- 生成的 SQL 会经过安全网关校验后才执行,并限制执行时间与返回行数。 + +AI 本身收到的信息仅为你的自然语言提问与可查询的表结构说明,不包含账单原始字段。 + +## 相关 + +- [AI 能力](/guide/ai):系统全部 AI 功能一览。 +- [统计面板](/guide/dashboard):固定的统计汇总与图表。 diff --git a/guide/transactions.md b/guide/transactions.md index e7f51bf..200e263 100644 --- a/guide/transactions.md +++ b/guide/transactions.md @@ -9,8 +9,9 @@ | 按钮 | 功能 | |---|---| | 排序 | 交易时间(新→旧 / 旧→新)、金额(高→低 / 低→高) | +| 查看统计 | 携带当前筛选条件跳转到[统计面板](/guide/dashboard) | | 一键设置 | 对当前表格展示的记录批量设置分类、计入统计与标签 | -| 批量删除 | 删除表格中勾选的记录 | +| 批量操作 / 退出批量 | 进入多选模式,可批量删除或为选中的多条记录[建立账单关系](/guide/relations) | | 导入账单 | 打开账单导入弹窗 | | 渠道管理 | 进入[交易渠道管理](/guide/channels)页,维护自定义渠道 | | 导出 Excel | 按当前筛选条件导出 | @@ -18,6 +19,8 @@ | 智能记账 | 用一句话描述收支,AI 解析后预填表单(需启用 AI) | | 新增记录 | 手动新增一笔记录 | +> 默认为普通模式,表格不显示勾选列;点「批量操作」后才进入多选模式,勾选记录后可「批量删除」,或勾选至少两条后「建立关系」。 + ## 账单导入 「收支记录」页支持把支付宝、微信、美团、淘宝、京东、招商银行、交通银行的账单文件解析并导入为收支记录。 @@ -95,35 +98,42 @@ **更多筛选**:标签(含「无标签」)、计入统计、金额区间。 -启用 AI 后,顶部会出现 **AI 智能查询**输入框,可用自然语言查询,例如: +启用 AI 后,顶部会出现「**AI 搜索**」入口,点击打开一个**可拖拽、可缩放的浮窗**(非模态,打开后仍可继续操作主界面)。在浮窗中用自然语言查询,例如: - 「上个月餐饮」 - 「超过500的支出」 - 「待报销的支出」 - 「微信支付」 -解析结果会直接应用为筛选条件并立即查询。筛选条件与每页条数、排序方式会自动记忆,下次进入自动还原。 +浮窗输入框支持多行,按 `Ctrl + Enter`(Mac 为 `⌘ + Enter`)提交;解析结果会直接应用为筛选条件并立即查询。浮窗的位置与大小会被记住,输入时还能快速选择历史提问。筛选条件与每页条数、排序方式会自动记忆,下次进入自动还原。 + +![AI 查询浮窗](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/ai-query-window.png) ## 表格与行操作 -表格展示:交易时间、类型、分类、金额、交易渠道、备注、标签、来源图片、计入统计开关与操作。 +表格展示:交易时间、类型、分类、金额、交易渠道、备注、标签、计入统计开关、关系与操作。 -- **备注隐藏**:可将敏感备注持久隐藏为 `******`,需要时临时「查看」或「取消隐藏」。 -- **计入统计**:行内开关可直接切换;关闭后该记录不纳入月度统计(适合借款、转账等),行内会以删除线标示。 -- **来源图片**:图片记账生成的记录会显示「查看(N)」入口,弹窗内先加载缩略图,可按需「加载原图」查看对应截图。 -- **行操作**:编辑、查看「原始数据」(账单导入或图片记账时的原始字段,以 JSON 展示)、删除。 +- **备注隐藏**:可将敏感备注持久隐藏为 `******`,需要时临时「查看」或「取消隐藏」。默认这些记录会从列表与 Excel 导出中排除,可在[设置 → 外观设置](/guide/settings#外观设置)中开启「显示已隐藏备注的记录」。 +- **计入统计**:行内开关可直接切换;关闭后该记录不纳入月度统计(适合借款、转账等),行内会以删除线标示(样式可在外观设置中调整)。 +- **关系**:展示该记录所属[账单关系](/guide/relations)组的数量,点击可查看与管理。 +- **行操作**:编辑、详情、删除。 -![来源图片弹窗](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/source-images.png) +### 账单详情 -分页支持每页 10/20/50/100/200/500 条。 +点击行内「**详情**」打开「账单详情」弹窗,集中展示单条账单的以下信息: -## 查看原始数据 +- **基本信息**:类型、金额、分类、交易渠道、交易时间、是否计入统计、备注与创建时间。 +- **标签**:该记录的全部标签。 +- **账单关系**:所属的[账单关系](/guide/relations)组,可在弹窗内直接新建。 +- **来源图片**:图片记账生成的记录会展示其来源截图,默认加载缩略图,可按需点「加载原图」查看原图。 +- **原始数据**:账单导入或图片记账时保留的原始字段(以 JSON 展示),用于核对金额、交易状态、对方账号、交易类型等明细。 -账单导入或图片记账时,系统会完整保留每笔交易在原始账单中的字段(以 JSON 存储)。在记录行点击「**原始数据**」即可查看,用于核对金额、交易状态、对方账号、交易类型等明细。 +![账单详情弹窗](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/bill-detail.png) -- **账单导入**记录保留原始账单行字段;**图片记账**记录保留模型从截图提取的原始字段。 -- **手动新增**的记录没有原始数据,该按钮不可用。 -- 原始数据是排查导入异常、核对账单的重要依据。 +> - **账单导入**记录保留原始账单行字段;**图片记账**记录保留模型从截图提取的原始字段;**手动新增**的记录没有来源图片与原始数据。 +> - 原始数据是排查导入异常、核对账单的重要依据。 + +分页的每页条数**候选与默认值在[设置 → 外观设置](/guide/settings#外观设置)中统一配置**(默认候选 10 / 20 / 50 / 100,默认 20);各表格会分别记住上次选择的每页条数。 ## 一键设置(批量设置) diff --git a/index.md b/index.md index 2d387d1..11f1990 100644 --- a/index.md +++ b/index.md @@ -4,7 +4,7 @@ layout: home hero: name: 汇账 text: 个人收支管理 · 多来源账单导入 · AI 智能记账 - tagline: 支持支付宝、微信、美团、淘宝、京东、招商银行、交通银行账单导入,提供分类标签与交易渠道管理、自定义规则、AI 建议(含多图图片记账)与定时提醒 + tagline: 支持支付宝、微信、美团、淘宝、京东、招商银行、交通银行账单导入,提供分类标签与交易渠道管理、自定义规则、账单关系、AI 建议(含多图图片记账与智能查数)与定时提醒 image: src: https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/dashboard.png alt: 统计面板 @@ -22,6 +22,8 @@ hero: features: - title: 多来源账单导入 details: 支持支付宝、微信、美团、淘宝、京东、招商银行、交通银行账单,解析预览后确认导入,按交易号自动去重。 + - title: 账单关系 + details: 把父子/同级的多笔账单组合关联,自动绘制关系图,并可由 AI 生成关系名称与备注。 - title: 分类与标签 details: 自定义收入/支出分类与标签,支持批量设置,AI 可推荐 Emoji 图标与标签颜色。 - title: 交易渠道管理 @@ -29,14 +31,14 @@ features: - title: 自定义规则 details: 按关键词、收支类型、金额区间组合条件,命中后自动应用分类与标签,导入预览即展示建议。 - title: AI 智能能力 - details: 基于 DeepSeek 的分类/标签/渠道建议、自然语言查询、文字与多图图片记账(实时进度)、分类标签渠道生成,并统计 token 用量。 + details: 基于 DeepSeek 的分类/标签/渠道建议、自然语言查询与查数(智能查数)、文字与多图图片记账(实时进度)、分类标签渠道生成、关系命名,并统计 token 用量。 - title: 统计与图表 - details: 月度收支汇总、分类占比、收支趋势、支付方式占比,支持多维度筛选与自定义时间范围。 + details: 月度收支汇总、分类占比、收支趋势、交易渠道占比,支持多维度筛选与自定义时间范围,图表可点击下钻明细。 - title: 定时提醒与通知 details: 按每天/每周/每月周期提醒导入新账单,支持邮箱 SMTP 与 Server 酱多渠道推送。 - title: 邮箱账单自动拉取 details: 通过 IMAP IDLE 监听邮箱,新账单邮件到达后自动拉取附件,先预览再确认导入。 - - title: 系统日志 + - title: 日志 details: 接口访问日志、登录日志与后端运行日志在线查看,运行日志支持 SSE 实时推送。 --- diff --git a/package.json b/package.json index 84c8ebf..2c88703 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ledgerly-docs", "private": true, - "version": "0.0.1", + "version": "0.1", "type": "module", "scripts": { "docs:dev": "vitepress dev", diff --git a/scripts/ts/capture-elements.ts b/scripts/ts/capture-elements.ts index f959ff2..a01f5c7 100644 --- a/scripts/ts/capture-elements.ts +++ b/scripts/ts/capture-elements.ts @@ -3,7 +3,7 @@ import { resolve } from 'node:path' import { pathToFileURL } from 'node:url' import type { Page } from 'playwright-core' import { clickText, injectSession, launchBrowser, login, newContext, waitPage } from './browser.js' -import { BASE_URL, ELEM_DIR, SAMPLE_BILL, 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' @@ -160,26 +160,59 @@ export async function captureAiRecordImage(page: Page, importRecords = true): Pr } /** - * 截取「来源图片」查看弹窗(点击表格中图片记账记录的「查看(N)」)。 + * 截取「账单详情」弹窗(含基本信息、标签、账单关系、来源图片与原始数据)。 + * + * 优先选择「有来源图片、无账单关系」的记录,使弹窗聚焦来源图片与原始数据。 * * @param page Page 实例 */ -export async function captureSourceImages(page: Page): Promise { +export async function captureBillDetail(page: Page): Promise { try { - await page.setViewportSize({ width: 1440, height: 1000 }) + await page.setViewportSize({ width: 1440, height: 1200 }) await page.goto(BASE_URL + '/transactions') await waitPage(page, { selector: '.n-data-table-tbody tr' }) - const viewBtn = page.locator('button', { hasText: /查看(\d+)/ }).first() - await viewBtn.waitFor({ state: 'visible', timeout: 20000 }) - await viewBtn.evaluate((el) => (el as HTMLElement).click()) - const modal = page.locator('.n-modal', { hasText: '来源图片' }).first() - await modal.waitFor({ state: 'visible', timeout: 20000 }) - await page.waitForLoadState('networkidle', { timeout: 20000 }).catch(() => undefined) - await page.waitForTimeout(1000) - await modal.screenshot({ path: resolve(ELEM_DIR, 'source-images.png') }) - console.log('[OK] modal/source-images') + const rows = page.locator('.n-data-table-tbody tr') + const count = await rows.count() + for (let i = 0; i < count; i++) { + const detailBtn = rows.nth(i).locator('button', { hasText: '详情' }).first() + if ((await detailBtn.count()) === 0) continue + await detailBtn.evaluate((el) => (el as HTMLElement).click()) + const modal = page.locator('.n-modal', { hasText: '账单详情' }).first() + await modal.waitFor({ state: 'visible', timeout: 20000 }) + await 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('.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) { - 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 { await waitPage(page, { selector: 'canvas' }) // 切到「近12月 + 按月」,柱子更宽便于稳定点中;同时清空收支记录筛选偏好,避免旧条件干扰 await page.evaluate(() => { - localStorage.setItem('finance.dashboard.prefs', JSON.stringify({ + localStorage.setItem('ledgerly.dashboard.prefs', JSON.stringify({ rangeType: '12month', granularity: 'month', filterStatsScope: 'INCLUDED', filterType: [], filterCategory: [], filterChannel: [], filterTag: [], filterMinAmount: null, filterMaxAmount: null, filterRemark: '', })) - localStorage.removeItem('finance.transaction.filterPrefs') + localStorage.removeItem('ledgerly.transaction.filterPrefs') }) await page.reload() await waitPage(page, { selector: 'canvas' }) @@ -255,10 +288,12 @@ export async function captureTrendJump(page: Page): Promise { /** 系统设置面板:输出文件名与面板标题 */ const SETTINGS_PANELS: Array<[string, string]> = [ + ['panel-account-security', '账号安全'], ['panel-database', '数据库配置'], ['panel-registration', '注册设置'], ['panel-bill-source-stats', '账单来源统计默认值'], ['panel-display', '外观设置'], + ['panel-nav-menu', '导航设置'], ['panel-ai', 'AI 模型配置'], ['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 { + 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 { + 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 { + 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 { const browser = await launchBrowser() try { @@ -364,7 +482,7 @@ async function main(): Promise { await captureEditRecord(page) await captureAiRule(page) await captureAiRecordImage(page) - await captureSourceImages(page) + await captureBillDetail(page) await captureChannelAi(page) await captureSettingsPanels(page) await captureBillParse(page) diff --git a/scripts/ts/capture-pages.ts b/scripts/ts/capture-pages.ts index f103729..3f9386c 100644 --- a/scripts/ts/capture-pages.ts +++ b/scripts/ts/capture-pages.ts @@ -10,6 +10,7 @@ const PAGES: Array<[string, string, number, number, string]> = [ ['login', '/login', 1440, 900, '.n-card'], ['register', '/register', 1440, 900, '.n-card'], ['dashboard', '/dashboard', 1440, 1280, 'canvas'], + ['sql-query', '/smart-query', 1440, 1180, '.n-card'], ['transactions', '/transactions', 1720, 1200, '.n-data-table-tbody tr'], ['categories', '/categories', 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-messages', '/email-bills/messages', 1440, 1000, '.n-data-table-tbody tr'], ['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'], ['logs-http', '/logs/http', 1440, 1000, '.n-data-table-tbody tr'], ['logs-login', '/logs/login', 1440, 1000, '.n-data-table-tbody tr'], diff --git a/scripts/ts/upload-oss.ts b/scripts/ts/upload-oss.ts index ce95c4f..ded86f9 100644 --- a/scripts/ts/upload-oss.ts +++ b/scripts/ts/upload-oss.ts @@ -164,6 +164,9 @@ async function assumeRole(config: OssConfig): Promise { accessKeyId: config.accessKeyId, accessKeySecret: config.accessKeySecret, endpoint: STS_ENDPOINT, + // 默认连接超时较短,网络抖动时易 ConnectTimeout,这里放宽 + connectTimeout: 20000, + readTimeout: 20000, }), ) const request = new AssumeRoleRequest({