// 预览页视觉验收截图(Electron 离屏捕获): // npx electron scripts/capture-preview.cjs // 产出 preview-shots/*.png:整页 / 侧栏折叠 / 搜索面板 / 选区工具条 const { app, BrowserWindow } = require('electron') const { mkdirSync, writeFileSync } = require('node:fs') const { join } = require('node:path') // 离屏捕获在 GPU 合成异常时会得到空白帧,改用软件合成 app.commandLine.appendSwitch('disable-gpu') const URL = 'http://localhost:4173/' const OUT = join(__dirname, '..', 'preview-shots') const sleep = (ms) => new Promise((r) => setTimeout(r, ms)) async function capture(win, name) { try { const image = await win.webContents.capturePage() writeFileSync(join(OUT, name), image.toPNG()) console.log('saved', name, `${image.getSize().width}x${image.getSize().height}`) } catch (e) { console.log('capture failed', name, e.message) } } async function js(win, code) { try { return await win.webContents.executeJavaScript(code) } catch (e) { console.log('js failed:', e.message) return undefined } } async function run() { mkdirSync(OUT, { recursive: true }) const win = new BrowserWindow({ width: 1280, height: 960, show: false, webPreferences: { paintWhenInitiallyHidden: true } }) await win.loadURL(URL) await sleep(1500) // 1) 整页(上半) await capture(win, '01-full.png') // 2) 折叠侧栏(等宽度过渡) await js(win, `document.querySelector('[aria-label="折叠侧栏"]')?.click()`) await sleep(700) await capture(win, '02-sidebar-collapsed.png') await js(win, `document.querySelector('[aria-label="展开侧栏"]')?.click()`) await sleep(700) // 3) 搜索面板(侧栏「搜索」行)+ 输入过滤 await js(win, `[...document.querySelectorAll('button')].find((b) => b.textContent.trim() === '搜索')?.click()`) await sleep(600) await js(win, `(() => { const i = document.querySelector('input[aria-label="搜索会话或命令"]'); if (!i) return 'no-input'; const set = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set; set.call(i, '供应'); i.dispatchEvent(new Event('input', { bubbles: true })); return 'ok' })()`) await sleep(400) await capture(win, '03-search-filtered.png') await js(win, `document.querySelector('input[aria-label="搜索会话或命令"]')?.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }))`) await sleep(400) // 4) 划选文本 → Selection Actions 工具条(展开「更多」) await js(win, `(() => { const p = [...document.querySelectorAll('p')].find((el) => el.textContent.includes('百香果')); if (!p) return 'no-p'; const textNode = p.firstChild; const range = document.createRange(); range.setStart(textNode, 0); range.setEnd(textNode, 30); const sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); document.dispatchEvent(new MouseEvent('mouseup', { bubbles: true })); return 'ok'; })()`) await sleep(600) await js(win, `document.querySelector('[aria-label="更多操作"]')?.click()`) await sleep(500) await capture(win, '04-selection-actions.png') // 5) 审批卡交互:选择「允许执行」(radio 自动前进 480ms 后提交) await js(win, `[...document.querySelectorAll('button')].find((b) => b.textContent.trim() === '允许执行')?.click()`) await sleep(900) await capture(win, '05-approval-approved.png') app.exit(0) } app.whenReady().then(run).catch((err) => { console.error(err) app.exit(1) })