Files
jiang13-forum/scripts/capture-screenshots.mjs
2026-08-05 17:11:05 +08:00

156 lines
5.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { chromium } from 'playwright';
import { mkdir } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const outDir = path.join(__dirname, '..', 'docs', 'screenshots');
/** 默认抓取在线演示站;本地可用 J13_URL=http://localhost:3000 */
const base = (process.env.J13_URL || 'https://bbs.iioio.com').replace(/\/$/, '');
/** 用于发帖页截图的演示账号(本地开发时填写) */
const DEMO_USER = {
username: process.env.J13_USER || 'admin',
password: process.env.J13_PASS || 'admin123',
};
/** 帖子详情 / 富文本展示用帖 ID */
const DEMO_POST_ID = Number(process.env.J13_POST_ID || 1);
const RICH_POST_ID = Number(process.env.J13_RICH_POST_ID || 8);
/** 演示站开启永久链接时带 .html */
const permalinkExt = process.env.J13_PERMALINK_EXT || 'html';
function postPath(id) {
const ext = permalinkExt ? `.${permalinkExt.replace(/^\./, '')}` : '';
return `${base}/post/${id}${ext}`;
}
async function shot(page, name, opts = {}) {
const file = path.join(outDir, name);
await page.screenshot({ path: file, type: 'png', ...opts });
console.log('saved', file);
}
async function setTheme(page, theme) {
await page.addInitScript((t) => {
localStorage.setItem('j13-theme', t);
document.documentElement.classList.toggle('dark', t === 'dark');
document.documentElement.style.colorScheme = t;
}, theme);
}
async function waitFeed(page) {
await page.waitForSelector('.feed-sort-bar, .virtual-post-list, .post-list', { timeout: 15000 });
await page.waitForTimeout(1000);
}
/** 通过登录页写入 Cookie失败时返回 false演示站无账号时跳过发帖页 */
async function login(page) {
try {
await page.goto(`${base}/login`, { waitUntil: 'networkidle' });
await page.fill('input[name="username"], #username, input[autocomplete="username"]', DEMO_USER.username);
await page.fill('input[name="password"], #password, input[type="password"]', DEMO_USER.password);
await page.click('button[type="submit"]');
await page.waitForURL((url) => !url.pathname.endsWith('/login'), { timeout: 10000 });
await page.waitForTimeout(600);
return true;
} catch (err) {
console.warn('login skipped:', err.message);
return false;
}
}
const browser = await chromium.launch();
const context = await browser.newContext({ deviceScaleFactor: 2 });
try {
await mkdir(outDir, { recursive: true });
// 首页 · 浅色
{
const page = await context.newPage();
await page.setViewportSize({ width: 1440, height: 900 });
await setTheme(page, 'light');
await page.goto(`${base}/`, { waitUntil: 'networkidle' });
await waitFeed(page);
await shot(page, 'home-light.png');
await page.close();
}
// 首页 · 暗色
{
const page = await context.newPage();
await page.setViewportSize({ width: 1440, height: 900 });
await setTheme(page, 'dark');
await page.goto(`${base}/`, { waitUntil: 'networkidle' });
await waitFeed(page);
await shot(page, 'home-dark.png');
await page.close();
}
// 帖子详情(介绍文 / 目录)
{
const page = await context.newPage();
await page.setViewportSize({ width: 1440, height: 900 });
await setTheme(page, 'light');
await page.goto(postPath(DEMO_POST_ID), { waitUntil: 'networkidle' });
await page.waitForSelector('.post-detail-page, .post-detail-title, h1', { timeout: 15000 });
await page.waitForTimeout(1200);
await shot(page, 'post-detail.png');
await page.close();
}
// 富文本帖(图片 + 代码高亮,展示 TipTap 渲染)
{
const page = await context.newPage();
await page.setViewportSize({ width: 1440, height: 900 });
await setTheme(page, 'light');
await page.goto(postPath(RICH_POST_ID), { waitUntil: 'networkidle' });
await page.waitForSelector('.post-detail-page, .post-detail-title, h1', { timeout: 15000 });
const img = page.locator('.article-img, .post-detail-page img, article img').first();
if (await img.count()) {
await img.scrollIntoViewIfNeeded();
await page.waitForTimeout(400);
}
await shot(page, 'post-rich.png');
await page.close();
}
// 发帖编辑器(需登录;演示站无账号时自动跳过)
{
const page = await context.newPage();
await page.setViewportSize({ width: 1440, height: 900 });
await setTheme(page, 'light');
const ok = await login(page);
if (ok) {
await page.goto(`${base}/compose`, { waitUntil: 'networkidle' });
await page.waitForSelector('.compose-page, .compose-canvas, .article-editor', { timeout: 10000 });
await page.fill('.compose-title', '分享你的技术见解');
const editor = page.locator('.article-editor .ProseMirror, .article-editor [contenteditable="true"]').first();
if (await editor.count()) {
await editor.click();
await editor.fill('支持 **粗体**、列表、代码块与图片上传的 TipTap 编辑器。');
}
await page.waitForTimeout(800);
await shot(page, 'compose.png', {
clip: { x: 0, y: 56, width: 1440, height: 844 },
});
} else {
console.warn('compose.png 未生成:请设置 J13_USER / J13_PASS 后重试');
}
await page.close();
}
// 移动端首页
{
const page = await context.newPage();
await page.setViewportSize({ width: 390, height: 844 });
await setTheme(page, 'light');
await page.goto(`${base}/`, { waitUntil: 'networkidle' });
await waitFeed(page);
await shot(page, 'mobile-home.png');
await page.close();
}
} finally {
await browser.close();
}