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:
2026-10-13 12:52:08 +08:00
parent 4425a6ba04
commit b1fb2c68a9
16 changed files with 263 additions and 21 deletions
+6 -1
View File
@@ -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)
+130 -3
View File
@@ -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)
+6 -3
View File
@@ -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)