docs: 补充管理页批量操作说明并同步规则/AI 规则文档

为规则、分类、标签管理页新增的「批量操作模式」与多选删除补充使用与开发文档,同时补上此前已完成但未同步的 AI 规则建议可编辑、编辑后实时重算去重等文档,并登记批量模式截图脚本。

变更内容:
- guide/categories-tags.md:补充分类与标签的批量删除说明,并回填 tags-batch.png、categories-batch.png 配图。
- guide/rules.md:批量删除与应用匹配账单改为需先进入「批量操作」模式(默认不显示勾选框)。
- dev/api.md:新增 DELETE /api/tag/batch、/api/category/batch,并补齐规则命中账单预览/一键应用、AI 规则去重校验等接口。
- dev/data-model.md:说明标签删除清理交易标签与规则标签关联、分类删除保留逻辑引用(账单显示未分类)。
- dev/rule-engine.md、dev/ai.md、dev/preferences.md、guide/ai.md、guide/transactions.md:同步 AI 规则建议可编辑、编辑后实时重算去重、应用开关默认开启并持久化等说明。
- scripts/ts/capture-elements.ts:新增 captureBatchModes()(标签/分类批量模式截图)并纳入 main()。

说明:
- 截图经测试环境(前端 5174 / 后端 8081,虚构数据)生成,已上传 OSS,正文以 OSS 长期地址引用;public/screenshots 目录不入库。
This commit is contained in:
2026-10-14 01:07:46 +08:00
parent d94d4e1ed5
commit 41d1a4d5a9
10 changed files with 145 additions and 17 deletions
+86 -1
View File
@@ -1,7 +1,7 @@
// 元素截图:截取登录/注册卡片、各功能弹窗与账单解析预览,写入 public/screenshots/elements/。
import { resolve } from 'node:path'
import { pathToFileURL } from 'node:url'
import type { Page } from 'playwright-core'
import type { Locator, Page } from 'playwright-core'
import { clickText, injectSession, launchBrowser, login, newContext, waitPage } from './browser.js'
import { BASE_URL, ELEM_DIR, OUT_DIR, SAMPLE_BILL, SAMPLE_IMAGE, SAMPLE_IMAGE2 } from './config.js'
@@ -480,6 +480,89 @@ export async function captureAiRuleFromTransactions(page: Page): Promise<void> {
}
}
/**
* 截取规则管理页「应用」命中的账单预览弹窗(elements/rule-apply-preview.png)。
*
* <p>打开首条规则的行内「应用」按钮,等待命中账单预览表加载后截图;只预览、不点击「确认应用」,不修改数据。
* 需目标环境存在至少一条可命中账单的规则。</p>
*
* @param page Page 实例
*/
export async function captureRuleApplyPreview(page: Page): Promise<void> {
try {
await page.setViewportSize({ width: 1440, height: 1000 })
await page.goto(BASE_URL + '/rules')
await waitPage(page, { selector: PAGE_READY })
// 首条规则行的「应用」按钮(避免误点顶部的「应用匹配账单」)
const applyBtn = page.locator('.n-data-table-tbody tr').first().locator('button', { hasText: '应用' }).first()
await applyBtn.waitFor({ state: 'visible', timeout: 20000 })
await applyBtn.evaluate((el) => (el as HTMLElement).click())
const modal = page.locator('.n-modal').first()
await modal.waitFor({ state: 'visible', timeout: 20000 })
// 命中账单表异步加载,等待数据行出现
await modal.locator('.n-data-table-tbody tr').first().waitFor({ state: 'visible', timeout: 20000 })
await page.waitForLoadState('networkidle', { timeout: 20000 }).catch(() => undefined)
await page.waitForTimeout(800)
await modal.screenshot({ path: resolve(ELEM_DIR, 'rule-apply-preview.png') })
console.log('[OK] modal/rule-apply-preview')
} catch (err) {
console.log(`[FAIL] modal/rule-apply-preview: ${err}`)
}
}
/**
* 勾选表格前 n 行(用于进入批量模式后展示多选效果)。
*
* @param page Page 实例
* @param table 目标表格 Locator
* @param n 勾选行数
*/
async function pickRows(page: Page, table: Locator, n: number): Promise<void> {
const rows = table.locator('.n-data-table-tbody tr')
const count = Math.min(n, await rows.count())
for (let i = 0; i < count; i++) {
await rows.nth(i).locator('.n-checkbox').first().click({ force: true }).catch(() => undefined)
}
await page.waitForTimeout(300)
}
/**
* 截取标签/分类管理页的「批量操作模式」(勾选列与批量删除入口)。
*
* <p>进入批量选择模式后勾选若干行,展示勾选列;仅截图,不执行任何删除。</p>
*
* @param page Page 实例
*/
export async function captureBatchModes(page: Page): Promise<void> {
// 标签管理批量模式
try {
await page.setViewportSize({ width: 1440, height: 900 })
await page.goto(BASE_URL + '/tags')
await waitPage(page, { selector: '.n-data-table-tbody tr' })
if (!(await clickText(page, '批量操作'))) throw new Error('未找到「批量操作」按钮')
await pickRows(page, page.locator('.n-data-table').first(), 2)
await page.screenshot({ path: resolve(OUT_DIR, 'tags-batch.png') })
console.log('[OK] page/tags-batch')
} catch (err) {
console.log(`[FAIL] tags-batch: ${err}`)
}
// 分类管理批量模式(支出/收入两表各勾选若干)
try {
await page.setViewportSize({ width: 1440, height: 1100 })
await page.goto(BASE_URL + '/categories')
await waitPage(page, { selector: '.n-data-table-tbody tr' })
if (!(await clickText(page, '批量操作'))) throw new Error('未找到「批量操作」按钮')
const tables = page.locator('.n-data-table')
await pickRows(page, tables.nth(0), 2)
await pickRows(page, tables.nth(1), 2)
await page.screenshot({ path: resolve(OUT_DIR, 'categories-batch.png') })
console.log('[OK] page/categories-batch')
} catch (err) {
console.log(`[FAIL] categories-batch: ${err}`)
}
}
/**
* 截取系统设置的配置面板。
*
@@ -680,6 +763,8 @@ async function main(): Promise<void> {
await captureInlineCreate(page)
await captureAiRule(page)
await captureAiRuleFromTransactions(page)
await captureRuleApplyPreview(page)
await captureBatchModes(page)
await captureAiRecordImage(page)
await captureBillDetail(page)
await captureChannelAi(page)