docs: 新增日历页文档并补充记账直接新建分类/标签/渠道说明
补上此前已完成但未提交的文档更新:新增「日历」页面使用文档,并把登录后默认落地页由统计面板调整为日历,同步页面流程与偏好说明;同时补充记账弹窗内可直接新建分类/标签/渠道的说明,并登记相应截图场景。 变更内容: - 新增 guide/calendar.md,并在 .vitepress/config.mts 侧边栏与 guide/pages.md、guide/index.md 登记日历页 - index.md、guide/getting-started.md、guide/dashboard.md、guide/pages.md 同步日历为登录后默认落地页与页面流程 - dev/preferences.md 新增日历偏好 ledgerly.calendar.view/year/month/statsScope 说明 - guide/transactions.md 补充记账弹窗内直接新建分类/标签/渠道的说明,guide/ai.md、guide/categories-tags.md、guide/channels.md 同步相关描述 - scripts/ts/capture-all.ts、capture-pages.ts、capture-elements.ts 增加日历与新建分类等截图场景,captureModals 支持按场景过滤
This commit is contained in:
@@ -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' },
|
||||
],
|
||||
|
||||
@@ -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 行)
|
||||
|
||||
+2
-1
@@ -55,6 +55,7 @@ API Key 仅保存在本地数据库中,仅用于向你配置的接口地址发
|
||||
| 统计搜索 | 统计面板 → AI 搜索 | 自然语言解析为统计筛选条件与维度 |
|
||||
| 生成关系名称 | 收支记录 → 账单关系 → AI 生成 | 依据关联账单的类型、渠道、分类与备注生成关系名称与备注 |
|
||||
| 智能查数 | 左侧菜单 → 智能查数 | 自然语言转只读 SQL 查询并展示结果 |
|
||||
| 手动新建分类/标签/渠道 | 新增/编辑记录 → 新建分类 / 新建标签 / 新建渠道 | 不依赖 AI,直接创建并选中(始终可用) |
|
||||
|
||||
## 分类与标签建议
|
||||
|
||||
@@ -128,5 +129,5 @@ API Key 仅保存在本地数据库中,仅用于向你配置的接口地址发
|
||||
## 说明
|
||||
|
||||
- AI 建议结果仅供参考,应用前可逐条取消勾选。
|
||||
- 关闭「启用 AI 建议」后,系统不会发起任何 AI 请求,相关入口也会隐藏。
|
||||
- 关闭「启用 AI 建议」后,系统不会发起任何 AI 请求,相关入口也会隐藏(分类/标签/渠道的**手动新建**入口不受影响,始终可用)。
|
||||
- 账单类型识别(邮箱账单)使用的是**发件人规则**而非 AI,结果更确定、可预期。
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# 日历
|
||||
|
||||
「日历」是登录后的默认首页,用月视图与年视图直观查看每天的收支情况。左侧菜单默认置于首项,访问 `/` 也会自动进入本页。
|
||||
|
||||

|
||||
|
||||
## 月视图
|
||||
|
||||
- 每天展示当天**收入**(绿色,`+`)、**支出**(红色,`-`)与**结余**;同一天既有收入又有支出时显示三行,否则只显示对应的一侧。
|
||||
- **今天**以高亮标记;点击网格中相邻月份的灰色日期会自动切换到该月。
|
||||
- 顶部四张卡片显示**当月合计**:总收入、总支出、结余、交易笔数。
|
||||
|
||||
## 切换月份与视图
|
||||
|
||||
表头提供:
|
||||
|
||||
- 「‹」「›」:切换上一月 / 下一月(年视图下为上一年 / 下一年);
|
||||
- **年份 / 月份下拉**:直接跳转到任意年月;
|
||||
- **今天**:回到当前月并选中今天;
|
||||
- **月视图 / 年视图**:切换视图。
|
||||
|
||||
## 年视图
|
||||
|
||||
年视图以 12 个月的卡片展示每月收入、支出与结余,无数据的月份显示「无数据」,当前月高亮。点击任意月份卡片即可进入该月的月视图。
|
||||
|
||||

|
||||
|
||||
某一年完全没有数据时,会显示「本年暂无交易数据」的空状态提示。
|
||||
|
||||
## 统计口径
|
||||
|
||||
表头的「**统计口径**」下拉控制参与汇总的记录范围:
|
||||
|
||||
| 选项 | 含义 |
|
||||
|---|---|
|
||||
| 仅计入统计(默认) | 只统计「计入统计」的记录 |
|
||||
| 全部记录 | 计入与不计入的记录都统计 |
|
||||
| 仅不计入 | 只统计「不计入统计」的记录 |
|
||||
|
||||
切换后日历中的金额、顶部合计与点击日期弹出的**当日明细**都会随之变化:明细只列出符合当前口径的记录,与汇总口径保持一致。
|
||||
|
||||
## 查看某天的明细
|
||||
|
||||
点击日历中的某一天,会弹出当天的**交易明细浮窗**:
|
||||
|
||||
- 顶部显示**当日合计**(收入 / 支出 / 结余);
|
||||
- 下方逐条列出当天交易,每条在主行显示时间、分类、渠道与**金额**(收绿支红);
|
||||
- 点击任意条目可打开该笔账单的**完整详情**(标签、账单关系、来源图片、原始数据等,为只读);
|
||||
- 底部「**在收支记录中查看**」会携带当天日期与当前统计口径跳转到[收支记录](/guide/transactions),便于继续编辑整理。
|
||||
|
||||

|
||||
|
||||
> 当日明细(按当前统计口径)最多展示前 50 条,超出时浮窗内会提示总数,可点「在收支记录中查看」查看全部。
|
||||
|
||||
## 链接分享与偏好
|
||||
|
||||
- 当前视图与年月会同步到地址栏(形如 `?view=month&y=2026&m=9`),刷新页面或把链接分享给他人,都能还原到对应月份与视图。
|
||||
- **视图**(月 / 年)、**统计口径**与**最近浏览的年月**会被记住,下次进入自动还原;地址栏带参数时以参数为准。
|
||||
|
||||
## 小技巧
|
||||
|
||||
- 想知道某一天具体花在哪,直接点该日期,先看当日合计,再逐条核对明细。
|
||||
- 想看全年的分布,切到「年视图」浏览各月汇总,再点开支出最高的月份查看当天明细。
|
||||
- 不计入统计的记录(如借款、账户间转账)默认不参与汇总,当日明细也不会列出;如需一并查看,把统计口径切换为「全部记录」。
|
||||
|
||||
> 想从更大的时间跨度看趋势与占比,可使用[统计面板](/guide/dashboard)的图表与筛选。
|
||||
@@ -53,3 +53,4 @@
|
||||
- 建议为以下场景建立标签:报销/待报销、旅行、大额消费、订阅、礼物、家庭、工作等。
|
||||
- 需要按规则自动归类时,配合[规则管理](/guide/rules)使用:规则命中后会自动应用分类与标签。
|
||||
- 分类与标签都支持在[账单导入](/guide/transactions)预览阶段批量指定为默认值。
|
||||
- 在[新增/编辑记录](/guide/transactions#手动添加记录)弹窗中,可直接点分类/标签行右侧的「新建分类」「新建标签」创建并选用,无需先到本页维护。
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@
|
||||
|
||||
自定义渠道创建后,可在以下场景直接选用:
|
||||
|
||||
- **新增 / 编辑记录**:交易渠道下拉中出现在内置渠道之后。
|
||||
- **新增 / 编辑记录**:交易渠道下拉中出现在内置渠道之后;也可点右侧「新建渠道」直接创建并选用(不受 AI 状态影响)。
|
||||
- **筛选与统计**:收支记录页筛选、统计面板的渠道维度与筛选均可选择自定义渠道,图表与分组会展示其名称。
|
||||
- **AI 能力**:智能查询、统计搜索与智能记账会把当前账号的自定义渠道一并作为可选渠道;新增/编辑记录时还可用「渠道 · AI 推荐」选择已有渠道,或「渠道 · AI 新建」由描述生成并创建渠道。
|
||||
- **导出**:Excel 导出时渠道列展示自定义渠道名称。
|
||||
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
# 统计面板
|
||||
|
||||
「统计面板」是登录后的默认首页,用于查看指定时间范围内的收支汇总与图表。
|
||||
「统计面板」用于查看指定时间范围内的收支汇总与图表,可从左侧菜单进入(登录后的默认首页为[日历](/guide/calendar))。
|
||||
|
||||

|
||||
|
||||
@@ -77,6 +77,8 @@
|
||||
|
||||
无论从哪张图下钻,都会同时保留面板当前的统计口径、标签、金额区间与备注关键字等筛选。
|
||||
|
||||
> 触屏设备上,点击趋势柱或饼图扇区会先弹出确认气泡(「查看明细」/「取消」),确认后才跳转,避免滑动或误触直接跳走;趋势图的**「整列可点」在触屏端禁用**,只能点击柱子本体。桌面端行为不变。
|
||||
|
||||

|
||||
|
||||
页面底部的「**查看收支记录**」按钮则会携带**当前全部筛选条件**跳转,便于核对整体明细。
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
- [项目简介](/guide/introduction):系统定位、核心功能与技术栈
|
||||
- [快速开始](/guide/getting-started):数据库配置、注册登录、界面语言与首次使用建议
|
||||
- [下载与安装](/guide/download):Windows / Linux 安装包与可执行 jar 的下载、运行与校验
|
||||
- [日历](/guide/calendar):按天查看每日收支、月视图与年视图、当日明细
|
||||
- [统计面板](/guide/dashboard):时间范围、筛选条件与图表说明
|
||||
|
||||
## 记账与整理
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
| 收支记录 | 增删改查、分页与多条件筛选、批量设置分类/标签、批量删除、计入统计开关、备注隐藏、导出 Excel |
|
||||
| 分类与标签 | 收入/支出分类与标签的增删改查,新用户自动获得默认数据,AI 推荐 Emoji 与颜色 |
|
||||
| 交易渠道 | 内置渠道 + 自定义渠道的新建/改名/删除,改名不影响历史账单 |
|
||||
| 日历 | 默认首页,月视图按天展示每日收支与当月合计,年视图一览全年各月,支持统计口径过滤与当日明细下钻 |
|
||||
| 统计图表 | 月度收支、分类占比、收支趋势、交易渠道占比,支持按日/周/月/季/年粒度,图表可点击下钻对应明细 |
|
||||
| 账单关系 | 父子/同级多笔账单关联、自动绘制关系图,AI 生成关系名称与备注 |
|
||||
| 账单导入 | 七类账单解析、zip 解密、解析预览、按交易号去重、规则建议 |
|
||||
|
||||
+6
-3
@@ -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 能力都从这里进入,导入完成后也回到该页查看。
|
||||
- **账单导入**有两条独立路径:
|
||||
- 收支记录页的「导入账单」**弹窗**(桌面端常用);
|
||||
|
||||
+15
-3
@@ -178,18 +178,28 @@
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| 类型 | 支出 / 收入 |
|
||||
| 分类 | 新增时必填 |
|
||||
| 分类 | 新增时必填;可点右侧「新建分类」直接创建 |
|
||||
| 金额 | 大于 0 |
|
||||
| 交易渠道 | 可选,含内置渠道与[自定义渠道](/guide/channels);启用 AI 时可一键推荐或新建 |
|
||||
| 交易渠道 | 可选,含内置渠道与[自定义渠道](/guide/channels);可点右侧「新建渠道」直接创建;启用 AI 时可一键推荐或新建 |
|
||||
| 交易时间 | 默认当前时间 |
|
||||
| 备注 | 可选 |
|
||||
| 标签 | 可选,可多选 |
|
||||
| 标签 | 可选,可多选;可点右侧「新建标签」直接创建 |
|
||||
| 计入统计 | 默认开启;关闭后不纳入统计 |
|
||||
|
||||
3. 点击「确定」保存。
|
||||
|
||||

|
||||
|
||||
::: tip 记账时直接新建分类 / 标签 / 渠道
|
||||
新增与编辑弹窗中,**分类**、**标签**、**交易渠道** 三行在选择框右侧各有一个「**新建分类 / 新建标签 / 新建渠道**」按钮,**不受 AI 启用状态影响**,随时可用。点击后弹出精简表单,创建完成即自动填入当前账单:
|
||||
|
||||
- **新建分类**:填写名称、类型(默认跟随当前账单类型)与图标;创建后自动选中,并把账单类型同步为该分类的类型。
|
||||
- **新建标签**:填写名称与颜色;创建后自动追加到已选标签。
|
||||
- **新建渠道**:填写名称;创建后自动选中。
|
||||
|
||||

|
||||
:::
|
||||
|
||||
## 编辑记录
|
||||
|
||||
在记录行点击「编辑」即可修改记录,弹窗字段与新增一致;编辑时允许不设置分类。
|
||||
@@ -212,6 +222,8 @@
|
||||

|
||||
|
||||
> 「AI 新建」需先填写备注或金额,便于模型理解;生成结果会显示确认条,点击「保存并选用」才真正创建。
|
||||
>
|
||||
> 上述 AI 辅助按钮仅在[启用 AI](/guide/ai)后出现;**分类、标签、交易渠道三行始终各有一个手动「新建」按钮**(不受 AI 状态影响),可直接填写并创建,详见[手动添加记录](#手动添加记录)中的说明。
|
||||
|
||||
编辑完成后点击「确定」保存。其他 AI 能力(批量建议、智能记账、智能查询)见 [AI 能力](/guide/ai)。
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
### 开发文档
|
||||
|
||||
@@ -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<void> {
|
||||
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)
|
||||
|
||||
@@ -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<void> {
|
||||
* 截取各功能弹窗。
|
||||
*
|
||||
* @param page Page 实例
|
||||
* @param only 仅截取指定场景名(可选,缺省截取全部)
|
||||
*/
|
||||
export async function captureModals(page: Page): Promise<void> {
|
||||
for (const [name, path, button] of MODAL_SCENES) {
|
||||
export async function captureModals(page: Page, only?: string[]): Promise<void> {
|
||||
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<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取新增记录弹窗(elements/add-record.png)。
|
||||
*
|
||||
* 先二次加载页面,确保模块级 AI 启用状态已就绪,使弹窗完整展示 AI 辅助按钮。
|
||||
*
|
||||
* @param page Page 实例
|
||||
*/
|
||||
export async function captureAddRecord(page: Page): Promise<void> {
|
||||
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<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取记账弹窗内「新建分类」子弹窗(体现新增/编辑记录时可直接新建分类)。
|
||||
*
|
||||
* @param page Page 实例
|
||||
*/
|
||||
export async function captureInlineCreate(page: Page): Promise<void> {
|
||||
try {
|
||||
await page.setViewportSize({ width: 1440, height: 1000 })
|
||||
await page.goto(BASE_URL + '/transactions')
|
||||
await waitPage(page, { selector: PAGE_READY })
|
||||
if (!(await clickText(page, '新增记录'))) throw new Error('未找到「新增记录」按钮')
|
||||
const modal = page.locator('.n-modal').first()
|
||||
await modal.waitFor({ state: 'visible', timeout: 20000 })
|
||||
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<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取日历「月视图」整页:写入 screenshots/calendar.png。
|
||||
*
|
||||
* @param page Page 实例
|
||||
*/
|
||||
export async function captureCalendarMonth(page: Page): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
const browser = await launchBrowser()
|
||||
try {
|
||||
@@ -478,8 +600,13 @@ async function main(): Promise<void> {
|
||||
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)
|
||||
|
||||
@@ -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<void> {
|
||||
for (const [name, path, width, height, selector] of PAGES) {
|
||||
export async function capturePages(page: Page, only?: string[]): Promise<void> {
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user