diff --git a/.vitepress/config.mts b/.vitepress/config.mts index f2bfd3d..8b01fac 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -9,7 +9,7 @@ export default defineConfig({ cleanUrls: true, ignoreDeadLinks: true, // 维护类文档不参与站点构建 - srcExclude: ['AGENTS.md'], + srcExclude: ['AGENTS.md', 'todo.md'], // 文档站默认端口从 5173 调整为 5175,把 5173 让给 ledgerly 前端 vite: { server: { @@ -31,6 +31,7 @@ export default defineConfig({ { text: '项目简介', link: '/guide/introduction' }, { text: '快速开始', link: '/guide/getting-started' }, { text: '下载与安装', link: '/guide/download' }, + { text: '日历', link: '/guide/calendar' }, { text: '统计面板', link: '/guide/dashboard' }, { text: '页面总览', link: '/guide/pages' }, ], diff --git a/dev/preferences.md b/dev/preferences.md index fff07c9..4ef1a88 100644 --- a/dev/preferences.md +++ b/dev/preferences.md @@ -33,6 +33,10 @@ | 键名 | 偏好内容 | 文件 | 保存时机 | 读取时机 | |---|---|---|---|---| | `ledgerly.locale` | 界面语言 `zh-CN`/`en-US`/`ja-JP` | `locales/index.ts` | 切换语言 | 模块初始化 | +| `ledgerly.calendar.view` | 日历视图 `month`/`year` | `views/Calendar.vue` | `useStringPreference` 自动 | 组件初始化 | +| `ledgerly.calendar.year` | 日历最近浏览年份(四位数字符串) | `views/Calendar.vue` | 变量 `watch` 自动 | 组件初始化 | +| `ledgerly.calendar.month` | 日历月视图最近浏览月份(`1`-`12`) | `views/Calendar.vue` | 变量 `watch` 自动 | 组件初始化 | +| `ledgerly.calendar.statsScope` | 日历统计口径 `INCLUDED`/`ALL`/`EXCLUDED` | `views/Calendar.vue` | `useStringPreference` 自动 | 组件初始化 | | `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 初始化 | @@ -79,6 +83,18 @@ - **目的**:记住用户语言选择,应用启动与切换即时生效。 - **入口**:`components/LocaleSwitch.vue`。 +### 日历偏好 `ledgerly.calendar.view` / `ledgerly.calendar.year` / `ledgerly.calendar.month` / `ledgerly.calendar.statsScope` + +- **文件**:`frontend/src/views/Calendar.vue` +- **取值**: + - `ledgerly.calendar.view`:`month`(月视图,默认)/ `year`(年视图);非法值回退 `month`。 + - `ledgerly.calendar.year`:最近浏览的年份(四位数字符串),月视图面板年份与年视图年份共用;非法值回退当前年。 + - `ledgerly.calendar.month`:月视图最近浏览的月份(`1`-`12`);非法值回退当前月。 + - `ledgerly.calendar.statsScope`:`INCLUDED`(仅计入统计,默认)/ `ALL`(全部记录)/ `EXCLUDED`(仅不计入);非法值回退 `INCLUDED`。 +- **读取**:组件初始化时经 `useStringPreference` 读取;URL 中的 `view`/`y`/`m`(如 `?view=year&y=2026`)优先于偏好,无参数时按偏好恢复上次视图、年月与统计口径。 +- **保存**:`view`/`statsScope` 由 `useStringPreference` 内部 `watch` 自动写入;`year`/`month` 由组件内 `watch(panelYear/panelMonth)`(月视图)与 `watch(yearViewYear)`(年视图)写入。 +- **目的**:下次进入日历自动还原上次的月/年视图、年月与统计口径。 + ### 统计面板偏好 `ledgerly.dashboard.prefs` - **文件**:`frontend/src/views/Dashboard.vue`(键名第 470 行) diff --git a/guide/ai.md b/guide/ai.md index b64d199..f789ab0 100644 --- a/guide/ai.md +++ b/guide/ai.md @@ -55,6 +55,7 @@ API Key 仅保存在本地数据库中,仅用于向你配置的接口地址发 | 统计搜索 | 统计面板 → AI 搜索 | 自然语言解析为统计筛选条件与维度 | | 生成关系名称 | 收支记录 → 账单关系 → AI 生成 | 依据关联账单的类型、渠道、分类与备注生成关系名称与备注 | | 智能查数 | 左侧菜单 → 智能查数 | 自然语言转只读 SQL 查询并展示结果 | +| 手动新建分类/标签/渠道 | 新增/编辑记录 → 新建分类 / 新建标签 / 新建渠道 | 不依赖 AI,直接创建并选中(始终可用) | ## 分类与标签建议 @@ -128,5 +129,5 @@ API Key 仅保存在本地数据库中,仅用于向你配置的接口地址发 ## 说明 - AI 建议结果仅供参考,应用前可逐条取消勾选。 -- 关闭「启用 AI 建议」后,系统不会发起任何 AI 请求,相关入口也会隐藏。 +- 关闭「启用 AI 建议」后,系统不会发起任何 AI 请求,相关入口也会隐藏(分类/标签/渠道的**手动新建**入口不受影响,始终可用)。 - 账单类型识别(邮箱账单)使用的是**发件人规则**而非 AI,结果更确定、可预期。 diff --git a/guide/calendar.md b/guide/calendar.md new file mode 100644 index 0000000..1b923aa --- /dev/null +++ b/guide/calendar.md @@ -0,0 +1,66 @@ +# 日历 + +「日历」是登录后的默认首页,用月视图与年视图直观查看每天的收支情况。左侧菜单默认置于首项,访问 `/` 也会自动进入本页。 + +![日历(月视图)](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/calendar.png) + +## 月视图 + +- 每天展示当天**收入**(绿色,`+`)、**支出**(红色,`-`)与**结余**;同一天既有收入又有支出时显示三行,否则只显示对应的一侧。 +- **今天**以高亮标记;点击网格中相邻月份的灰色日期会自动切换到该月。 +- 顶部四张卡片显示**当月合计**:总收入、总支出、结余、交易笔数。 + +## 切换月份与视图 + +表头提供: + +- 「‹」「›」:切换上一月 / 下一月(年视图下为上一年 / 下一年); +- **年份 / 月份下拉**:直接跳转到任意年月; +- **今天**:回到当前月并选中今天; +- **月视图 / 年视图**:切换视图。 + +## 年视图 + +年视图以 12 个月的卡片展示每月收入、支出与结余,无数据的月份显示「无数据」,当前月高亮。点击任意月份卡片即可进入该月的月视图。 + +![日历年视图](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/calendar-year.png) + +某一年完全没有数据时,会显示「本年暂无交易数据」的空状态提示。 + +## 统计口径 + +表头的「**统计口径**」下拉控制参与汇总的记录范围: + +| 选项 | 含义 | +|---|---| +| 仅计入统计(默认) | 只统计「计入统计」的记录 | +| 全部记录 | 计入与不计入的记录都统计 | +| 仅不计入 | 只统计「不计入统计」的记录 | + +切换后日历中的金额、顶部合计与点击日期弹出的**当日明细**都会随之变化:明细只列出符合当前口径的记录,与汇总口径保持一致。 + +## 查看某天的明细 + +点击日历中的某一天,会弹出当天的**交易明细浮窗**: + +- 顶部显示**当日合计**(收入 / 支出 / 结余); +- 下方逐条列出当天交易,每条在主行显示时间、分类、渠道与**金额**(收绿支红); +- 点击任意条目可打开该笔账单的**完整详情**(标签、账单关系、来源图片、原始数据等,为只读); +- 底部「**在收支记录中查看**」会携带当天日期与当前统计口径跳转到[收支记录](/guide/transactions),便于继续编辑整理。 + +![当日明细浮窗](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/calendar-day-detail.png) + +> 当日明细(按当前统计口径)最多展示前 50 条,超出时浮窗内会提示总数,可点「在收支记录中查看」查看全部。 + +## 链接分享与偏好 + +- 当前视图与年月会同步到地址栏(形如 `?view=month&y=2026&m=9`),刷新页面或把链接分享给他人,都能还原到对应月份与视图。 +- **视图**(月 / 年)、**统计口径**与**最近浏览的年月**会被记住,下次进入自动还原;地址栏带参数时以参数为准。 + +## 小技巧 + +- 想知道某一天具体花在哪,直接点该日期,先看当日合计,再逐条核对明细。 +- 想看全年的分布,切到「年视图」浏览各月汇总,再点开支出最高的月份查看当天明细。 +- 不计入统计的记录(如借款、账户间转账)默认不参与汇总,当日明细也不会列出;如需一并查看,把统计口径切换为「全部记录」。 + +> 想从更大的时间跨度看趋势与占比,可使用[统计面板](/guide/dashboard)的图表与筛选。 diff --git a/guide/categories-tags.md b/guide/categories-tags.md index 179db24..d5d348e 100644 --- a/guide/categories-tags.md +++ b/guide/categories-tags.md @@ -53,3 +53,4 @@ - 建议为以下场景建立标签:报销/待报销、旅行、大额消费、订阅、礼物、家庭、工作等。 - 需要按规则自动归类时,配合[规则管理](/guide/rules)使用:规则命中后会自动应用分类与标签。 - 分类与标签都支持在[账单导入](/guide/transactions)预览阶段批量指定为默认值。 +- 在[新增/编辑记录](/guide/transactions#手动添加记录)弹窗中,可直接点分类/标签行右侧的「新建分类」「新建标签」创建并选用,无需先到本页维护。 diff --git a/guide/channels.md b/guide/channels.md index 8f4e9e7..19705c6 100644 --- a/guide/channels.md +++ b/guide/channels.md @@ -55,7 +55,7 @@ 自定义渠道创建后,可在以下场景直接选用: -- **新增 / 编辑记录**:交易渠道下拉中出现在内置渠道之后。 +- **新增 / 编辑记录**:交易渠道下拉中出现在内置渠道之后;也可点右侧「新建渠道」直接创建并选用(不受 AI 状态影响)。 - **筛选与统计**:收支记录页筛选、统计面板的渠道维度与筛选均可选择自定义渠道,图表与分组会展示其名称。 - **AI 能力**:智能查询、统计搜索与智能记账会把当前账号的自定义渠道一并作为可选渠道;新增/编辑记录时还可用「渠道 · AI 推荐」选择已有渠道,或「渠道 · AI 新建」由描述生成并创建渠道。 - **导出**:Excel 导出时渠道列展示自定义渠道名称。 diff --git a/guide/dashboard.md b/guide/dashboard.md index 8b64b45..9a79aaa 100644 --- a/guide/dashboard.md +++ b/guide/dashboard.md @@ -1,6 +1,6 @@ # 统计面板 -「统计面板」是登录后的默认首页,用于查看指定时间范围内的收支汇总与图表。 +「统计面板」用于查看指定时间范围内的收支汇总与图表,可从左侧菜单进入(登录后的默认首页为[日历](/guide/calendar))。 ![统计面板](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/dashboard.png) @@ -77,6 +77,8 @@ 无论从哪张图下钻,都会同时保留面板当前的统计口径、标签、金额区间与备注关键字等筛选。 +> 触屏设备上,点击趋势柱或饼图扇区会先弹出确认气泡(「查看明细」/「取消」),确认后才跳转,避免滑动或误触直接跳走;趋势图的**「整列可点」在触屏端禁用**,只能点击柱子本体。桌面端行为不变。 + ![点击趋势柱状图跳转收支记录](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/dashboard-trend-jump.png) 页面底部的「**查看收支记录**」按钮则会携带**当前全部筛选条件**跳转,便于核对整体明细。 diff --git a/guide/getting-started.md b/guide/getting-started.md index 2845f72..9870bfd 100644 --- a/guide/getting-started.md +++ b/guide/getting-started.md @@ -52,7 +52,7 @@ ## 首次使用建议 -1. 完成数据库配置并注册登录。 +1. 完成数据库配置并注册登录。登录后默认进入[日历](/guide/calendar),可先熟悉按天查看收支的用法。 2. 进入「分类管理」「标签管理」按自己的习惯调整分类与标签。 3. 在「规则管理」配置常用自动归类规则(如「星巴克 → 餐饮」)。 4. 导出并导入历史账单(见[账单类型与获取](/guide/bills/overview))。 @@ -61,6 +61,7 @@ ## 下一步 +- [日历](/guide/calendar):登录后的默认首页,按天查看每日收支 - [统计面板](/guide/dashboard) - [收支记录](/guide/transactions) - [账单导入](/guide/transactions) diff --git a/guide/index.md b/guide/index.md index 7043a5b..13bba1e 100644 --- a/guide/index.md +++ b/guide/index.md @@ -7,6 +7,7 @@ - [项目简介](/guide/introduction):系统定位、核心功能与技术栈 - [快速开始](/guide/getting-started):数据库配置、注册登录、界面语言与首次使用建议 - [下载与安装](/guide/download):Windows / Linux 安装包与可执行 jar 的下载、运行与校验 +- [日历](/guide/calendar):按天查看每日收支、月视图与年视图、当日明细 - [统计面板](/guide/dashboard):时间范围、筛选条件与图表说明 ## 记账与整理 diff --git a/guide/introduction.md b/guide/introduction.md index 71206dd..13ccfad 100644 --- a/guide/introduction.md +++ b/guide/introduction.md @@ -18,6 +18,7 @@ | 收支记录 | 增删改查、分页与多条件筛选、批量设置分类/标签、批量删除、计入统计开关、备注隐藏、导出 Excel | | 分类与标签 | 收入/支出分类与标签的增删改查,新用户自动获得默认数据,AI 推荐 Emoji 与颜色 | | 交易渠道 | 内置渠道 + 自定义渠道的新建/改名/删除,改名不影响历史账单 | +| 日历 | 默认首页,月视图按天展示每日收支与当月合计,年视图一览全年各月,支持统计口径过滤与当日明细下钻 | | 统计图表 | 月度收支、分类占比、收支趋势、交易渠道占比,支持按日/周/月/季/年粒度,图表可点击下钻对应明细 | | 账单关系 | 父子/同级多笔账单关联、自动绘制关系图,AI 生成关系名称与备注 | | 账单导入 | 七类账单解析、zip 解密、解析预览、按交易号去重、规则建议 | diff --git a/guide/pages.md b/guide/pages.md index 433d455..c535125 100644 --- a/guide/pages.md +++ b/guide/pages.md @@ -9,7 +9,8 @@ | 登录 | `/login` | 用户名密码登录 | 未登录访问受保护页面时自动跳转 | | 注册 | `/register` | 创建账号 | 登录页「没有账号?去注册」(注册开启时) | | 数据库初始化 | `/database-setup` | 首次配置数据库 | 数据库未配置时自动引导 | -| 统计面板 | `/dashboard` | 收支汇总与图表 | 登录后默认页;左侧菜单 | +| 日历 | `/calendar` | 每日收支日历、月/年视图与当日明细 | **登录后默认页**;左侧菜单(默认置顶) | +| 统计面板 | `/dashboard` | 收支汇总与图表 | 左侧菜单 | | 智能查数 | `/smart-query` | 自然语言转只读 SQL 查询 | 左侧菜单(**AI 启用时**) | | 收支记录 | `/transactions` | 记录管理与**账单导入** | 左侧菜单 | | 分类管理 | `/categories` | 收入/支出分类维护 | 左侧菜单 | @@ -28,7 +29,7 @@ ### 左侧菜单 -统计面板 → 智能查数(AI 启用时)→ 收支记录 → 分类管理 → 标签管理 → 规则管理 → 邮箱账单 +日历 → 统计面板 → 智能查数(AI 启用时)→ 收支记录 → 分类管理 → 标签管理 → 规则管理 → 邮箱账单 左侧菜单的**顺序与显隐可自定义**(设置 → 导航设置),并在下次进入时还原。 @@ -44,7 +45,8 @@ ## 典型跳转路径 -- **首次使用**:`/database-setup` → `/register` → `/login` → `/dashboard` +- **首次使用**:`/database-setup` → `/register` → `/login` → `/calendar` +- **日历看某天**:日历 → 点击某天 → 当日明细浮窗(按当前统计口径)→ 点条目查看账单详情 / 点「在收支记录中查看」跳转收支记录(按当日筛选并沿用统计口径) - **手动记账**:收支记录 → 点击「新增记录」→ 表单弹窗 → 保存 - **查看趋势明细**:统计面板 → 点击趋势柱、分类/渠道饼图或「查看收支记录」→ 收支记录(自动带入对应筛选条件) - **反向跳转**:收支记录 → 点击「查看统计」→ 统计面板(还原当前筛选条件) @@ -60,6 +62,7 @@ ## 页面关系说明 +- **日历**是登录后的默认落地页:按天查看每日收支、切换月/年视图与统计口径,并可点击某天下钻到当日明细与账单详情。 - **收支记录**是核心页面:手动记账、账单导入、批量操作、账单关系与 AI 能力都从这里进入,导入完成后也回到该页查看。 - **账单导入**有两条独立路径: - 收支记录页的「导入账单」**弹窗**(桌面端常用); diff --git a/guide/transactions.md b/guide/transactions.md index ff4dfad..df79ede 100644 --- a/guide/transactions.md +++ b/guide/transactions.md @@ -178,18 +178,28 @@ | 字段 | 说明 | |---|---| | 类型 | 支出 / 收入 | - | 分类 | 新增时必填 | + | 分类 | 新增时必填;可点右侧「新建分类」直接创建 | | 金额 | 大于 0 | - | 交易渠道 | 可选,含内置渠道与[自定义渠道](/guide/channels);启用 AI 时可一键推荐或新建 | + | 交易渠道 | 可选,含内置渠道与[自定义渠道](/guide/channels);可点右侧「新建渠道」直接创建;启用 AI 时可一键推荐或新建 | | 交易时间 | 默认当前时间 | | 备注 | 可选 | - | 标签 | 可选,可多选 | + | 标签 | 可选,可多选;可点右侧「新建标签」直接创建 | | 计入统计 | 默认开启;关闭后不纳入统计 | 3. 点击「确定」保存。 ![新增记录弹窗](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/add-record.png) +::: tip 记账时直接新建分类 / 标签 / 渠道 +新增与编辑弹窗中,**分类**、**标签**、**交易渠道** 三行在选择框右侧各有一个「**新建分类 / 新建标签 / 新建渠道**」按钮,**不受 AI 启用状态影响**,随时可用。点击后弹出精简表单,创建完成即自动填入当前账单: + +- **新建分类**:填写名称、类型(默认跟随当前账单类型)与图标;创建后自动选中,并把账单类型同步为该分类的类型。 +- **新建标签**:填写名称与颜色;创建后自动追加到已选标签。 +- **新建渠道**:填写名称;创建后自动选中。 + +![记账弹窗内新建分类](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/record-new-category.png) +::: + ## 编辑记录 在记录行点击「编辑」即可修改记录,弹窗字段与新增一致;编辑时允许不设置分类。 @@ -212,6 +222,8 @@ ![编辑记录中的渠道 AI 辅助](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/ai-channel-suggest.png) > 「AI 新建」需先填写备注或金额,便于模型理解;生成结果会显示确认条,点击「保存并选用」才真正创建。 +> +> 上述 AI 辅助按钮仅在[启用 AI](/guide/ai)后出现;**分类、标签、交易渠道三行始终各有一个手动「新建」按钮**(不受 AI 状态影响),可直接填写并创建,详见[手动添加记录](#手动添加记录)中的说明。 编辑完成后点击「确定」保存。其他 AI 能力(批量建议、智能记账、智能查询)见 [AI 能力](/guide/ai)。 diff --git a/index.md b/index.md index 11f1990..22ee246 100644 --- a/index.md +++ b/index.md @@ -6,8 +6,8 @@ hero: text: 个人收支管理 · 多来源账单导入 · AI 智能记账 tagline: 支持支付宝、微信、美团、淘宝、京东、招商银行、交通银行账单导入,提供分类标签与交易渠道管理、自定义规则、账单关系、AI 建议(含多图图片记账与智能查数)与定时提醒 image: - src: https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/dashboard.png - alt: 统计面板 + src: https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/calendar.png + alt: 日历 actions: - theme: brand text: 使用文档 @@ -32,6 +32,8 @@ features: details: 按关键词、收支类型、金额区间组合条件,命中后自动应用分类与标签,导入预览即展示建议。 - title: AI 智能能力 details: 基于 DeepSeek 的分类/标签/渠道建议、自然语言查询与查数(智能查数)、文字与多图图片记账(实时进度)、分类标签渠道生成、关系命名,并统计 token 用量。 + - title: 日历视图 + details: 默认首页,月视图按天展示每日收支与当月合计,年视图一览全年各月;点击某天查看当日明细并直达账单详情,支持统计口径过滤。 - title: 统计与图表 details: 月度收支汇总、分类占比、收支趋势、交易渠道占比,支持多维度筛选与自定义时间范围,图表可点击下钻明细。 - title: 定时提醒与通知 @@ -59,7 +61,7 @@ features: - 想开始使用:[快速开始](/guide/getting-started) - 想知道各类账单怎么导出:[账单类型与获取](/guide/bills/overview) -- 想了解各功能怎么用:[统计面板](/guide/dashboard) 起逐页阅读 +- 想了解各功能怎么用:[日历](/guide/calendar) 起逐页阅读 - 遇到问题:[常见问题](/guide/faq) ### 开发文档 diff --git a/scripts/ts/capture-all.ts b/scripts/ts/capture-all.ts index bddd986..80ffcc2 100644 --- a/scripts/ts/capture-all.ts +++ b/scripts/ts/capture-all.ts @@ -5,7 +5,7 @@ // - scripts/samples/alipay_sample.zip 样例账单存在。 import { mkdir } from 'node:fs/promises' import { injectSession, launchBrowser, login, newContext } from './browser.js' -import { captureAiRule, captureBillParse, captureCards, captureEditRecord, captureModals, captureSettingsPanels, captureTrendJump } from './capture-elements.js' +import { captureAddRecord, captureAiRule, captureBillParse, captureCalendarDetail, captureCalendarMonth, captureCalendarYear, captureCards, captureEditRecord, captureInlineCreate, captureModals, captureSettingsPanels, captureTrendJump } from './capture-elements.js' import { capturePages } from './capture-pages.js' import { ELEM_DIR } from './config.js' @@ -25,8 +25,13 @@ async function main(): Promise { await injectSession(context, token) const page = await context.newPage() await capturePages(page) + await captureCalendarMonth(page) + await captureCalendarYear(page) + await captureCalendarDetail(page) + await captureAddRecord(page) await captureModals(page) await captureEditRecord(page) + await captureInlineCreate(page) await captureAiRule(page) await captureSettingsPanels(page) await captureBillParse(page) diff --git a/scripts/ts/capture-elements.ts b/scripts/ts/capture-elements.ts index a01f5c7..3412dbd 100644 --- a/scripts/ts/capture-elements.ts +++ b/scripts/ts/capture-elements.ts @@ -16,7 +16,6 @@ const CARD_SCENES: Array<[string, string, string]> = [ /** 弹窗截图:文件名、路由、触发按钮文本 */ const MODAL_SCENES: Array<[string, string, string]> = [ - ['add-record', '/transactions', '新增记录'], ['batch-set', '/transactions', '一键设置'], ['import-bill', '/transactions', '导入账单'], ['ai-record', '/transactions', '智能记账'], @@ -58,9 +57,11 @@ export async function captureCards(page: Page): Promise { * 截取各功能弹窗。 * * @param page Page 实例 + * @param only 仅截取指定场景名(可选,缺省截取全部) */ -export async function captureModals(page: Page): Promise { - for (const [name, path, button] of MODAL_SCENES) { +export async function captureModals(page: Page, only?: string[]): Promise { + const scenes = only ? MODAL_SCENES.filter(([name]) => only.includes(name)) : MODAL_SCENES + for (const [name, path, button] of scenes) { try { await page.setViewportSize({ width: 1440, height: 1000 }) await page.goto(BASE_URL + path) @@ -85,6 +86,32 @@ export async function captureModals(page: Page): Promise { } } +/** + * 截取新增记录弹窗(elements/add-record.png)。 + * + * 先二次加载页面,确保模块级 AI 启用状态已就绪,使弹窗完整展示 AI 辅助按钮。 + * + * @param page Page 实例 + */ +export async function captureAddRecord(page: Page): Promise { + try { + await page.setViewportSize({ width: 1440, height: 1000 }) + await page.goto(BASE_URL + '/transactions') + await waitPage(page, { selector: PAGE_READY }) + await page.reload() + await waitPage(page, { selector: PAGE_READY }) + await page.waitForTimeout(1500) + if (!(await clickText(page, '新增记录'))) throw new Error('未找到「新增记录」按钮') + const modal = page.locator('.n-modal').first() + await modal.waitFor({ state: 'visible', timeout: 20000 }) + await page.waitForTimeout(900) + await modal.screenshot({ path: resolve(ELEM_DIR, 'add-record.png') }) + console.log('[OK] modal/add-record') + } catch (err) { + console.log(`[FAIL] modal/add-record: ${err}`) + } +} + /** * 截取账单解析预览:上传样例账单后解析。 * @@ -321,6 +348,31 @@ export async function captureEditRecord(page: Page): Promise { } } +/** + * 截取记账弹窗内「新建分类」子弹窗(体现新增/编辑记录时可直接新建分类)。 + * + * @param page Page 实例 + */ +export async function captureInlineCreate(page: Page): Promise { + try { + await page.setViewportSize({ width: 1440, height: 1000 }) + await page.goto(BASE_URL + '/transactions') + await waitPage(page, { selector: PAGE_READY }) + if (!(await clickText(page, '新增记录'))) throw new Error('未找到「新增记录」按钮') + const modal = page.locator('.n-modal').first() + await modal.waitFor({ state: 'visible', timeout: 20000 }) + if (!(await clickText(page, '新建分类'))) throw new Error('未找到「新建分类」按钮') + await page.waitForTimeout(800) + // 子弹窗为后打开的最后一层弹窗 + const sub = page.locator('.n-modal').last() + await sub.waitFor({ state: 'visible', timeout: 8000 }) + await sub.screenshot({ path: resolve(ELEM_DIR, 'record-new-category.png') }) + console.log('[OK] modal/record-new-category') + } catch (err) { + console.log(`[FAIL] modal/record-new-category: ${err}`) + } +} + /** * 截取「AI 生成规则」的两步弹窗:步骤一选择记录、步骤二预览规则建议。 * @@ -466,6 +518,76 @@ export async function captureSqlQuery(page: Page): Promise { } } +/** + * 截取日历「月视图」整页:写入 screenshots/calendar.png。 + * + * @param page Page 实例 + */ +export async function captureCalendarMonth(page: Page): Promise { + try { + await page.setViewportSize({ width: 1440, height: 1280 }) + await page.goto(BASE_URL + '/calendar?view=month') + await waitPage(page, { selector: '.n-calendar' }) + await page.locator('.day-cell__row').first().waitFor({ state: 'visible', timeout: 20000 }).catch(() => undefined) + await page.waitForTimeout(600) + await page.screenshot({ path: resolve(OUT_DIR, 'calendar.png') }) + console.log('[OK] calendar') + } catch (err) { + console.log(`[FAIL] calendar: ${err}`) + } +} + +/** + * 截取日历「年视图」:切换到年视图后截取。 + * + * @param page Page 实例 + */ +export async function captureCalendarYear(page: Page): Promise { + try { + await page.setViewportSize({ width: 1440, height: 1000 }) + await page.goto(BASE_URL + '/calendar?view=month') + await waitPage(page, { selector: '.n-calendar' }) + const viewBtn = page.locator('.cal-toolbar__view').first() + await viewBtn.waitFor({ state: 'visible', timeout: 15000 }) + await viewBtn.evaluate((el) => (el as HTMLElement).click()) + await page.locator('.year-cell').first().waitFor({ state: 'visible', timeout: 15000 }) + await page.waitForLoadState('networkidle', { timeout: 8000 }).catch(() => undefined) + await page.waitForTimeout(800) + await page.screenshot({ path: resolve(ELEM_DIR, 'calendar-year.png') }) + console.log('[OK] calendar-year') + } catch (err) { + console.log(`[FAIL] calendar-year: ${err}`) + } +} + +/** + * 截取日历「当日明细浮窗」:点击某天有数据的日期后截取浮窗。 + * + * @param page Page 实例 + */ +export async function captureCalendarDetail(page: Page): Promise { + try { + await page.setViewportSize({ width: 1440, height: 1100 }) + await page.goto(BASE_URL + '/calendar?view=month') + await waitPage(page, { selector: '.n-calendar' }) + await page.locator('.day-cell__row').first().waitFor({ state: 'visible', timeout: 20000 }) + const cell = page + .locator('.n-calendar-cell') + .filter({ has: page.locator('.day-cell__row') }) + .first() + await cell.evaluate((el) => (el as HTMLElement).click()) + const modal = page.locator('.n-modal', { hasText: '交易明细' }).first() + await modal.waitFor({ state: 'visible', timeout: 20000 }) + // 等待明细列表加载完成(出现条目后再截图,避免截到加载中状态) + await modal.locator('.detail-item').first().waitFor({ state: 'visible', timeout: 20000 }) + await page.waitForTimeout(600) + await modal.screenshot({ path: resolve(ELEM_DIR, 'calendar-day-detail.png') }) + console.log('[OK] calendar-day-detail') + } catch (err) { + console.log(`[FAIL] calendar-day-detail: ${err}`) + } +} + async function main(): Promise { const browser = await launchBrowser() try { @@ -478,8 +600,13 @@ async function main(): Promise { const context = await newContext(browser) await injectSession(context, token) const page = await context.newPage() + await captureCalendarMonth(page) + await captureCalendarYear(page) + await captureCalendarDetail(page) + await captureAddRecord(page) await captureModals(page) await captureEditRecord(page) + await captureInlineCreate(page) await captureAiRule(page) await captureAiRecordImage(page) await captureBillDetail(page) diff --git a/scripts/ts/capture-pages.ts b/scripts/ts/capture-pages.ts index 3f9386c..a1e312f 100644 --- a/scripts/ts/capture-pages.ts +++ b/scripts/ts/capture-pages.ts @@ -9,6 +9,7 @@ import { BASE_URL, OUT_DIR } from './config.js' const PAGES: Array<[string, string, number, number, string]> = [ ['login', '/login', 1440, 900, '.n-card'], ['register', '/register', 1440, 900, '.n-card'], + ['calendar', '/calendar', 1440, 1280, '.n-calendar'], ['dashboard', '/dashboard', 1440, 1280, 'canvas'], ['sql-query', '/smart-query', 1440, 1180, '.n-card'], ['transactions', '/transactions', 1720, 1200, '.n-data-table-tbody tr'], @@ -28,12 +29,14 @@ const PAGES: Array<[string, string, number, number, string]> = [ ] /** - * 截取全部整页。 + * 截取整页。 * * @param page Page 实例 + * @param only 仅截取指定页面名(可选,缺省截取全部) */ -export async function capturePages(page: Page): Promise { - for (const [name, path, width, height, selector] of PAGES) { +export async function capturePages(page: Page, only?: string[]): Promise { + const pages = only ? PAGES.filter(([name]) => only.includes(name)) : PAGES + for (const [name, path, width, height, selector] of pages) { try { await page.setViewportSize({ width, height }) await page.goto(BASE_URL + path)