feat: AI Directory Platform 底座升级 + 五套主题站点
- 数据模型: vote/review/submission/claim/use_case/audience/collection/event (+tool 冗余统计列) - 社区: 投票(限流+原子toggle+API) / 评论(评分+优缺点+Helpful+聚合回写) / 提交(重复检测+审批发布) / 认领(Owner白名单编辑) - Ranking Engine: trending/popular/most-voted/most-saved/highest-rated/new/editors-choice (纯函数可测) - 发现体系: Use Case + Audience + Collection 三维发现 / /ai-tools-for / /best SEO 页 - Programmatic SEO: 页面生成器 / sitemap 270条 / AggregateRating/FAQPage/Breadcrumb/ItemList schema / alternatives + vs 对比页 - Analytics: event 事件流 + 外链跳转埋点 + /admin/analytics CTR 与搜索词分析 - Admin: submissions/claims/reviews/discovery/vote-audit/analytics 六个新后台 + 仪表盘扩展 - 主题系统: themes registry + 五套站点 (classic/product/editorial/niche/minimal) 构建期注入 - 认证: GitHub OAuth (env门控) + 登录回跳 redirect - i18n: 新增 7 个命名空间中英文案 (546 键一致) - 测试: bun test 32 用例 (ranking/slug/去重/SEO生成器) + Playwright e2e 脚本 - 种子: 14分类/16用例/9受众/40工具/6用户/投票/评论/6合集/7718事件
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* # 脚本 screenshot-pages
|
||||
* 说明:用 Playwright 对五个主题站点与关键页面截图 + 控制台错误采集。
|
||||
* 用于交付前的视觉验收。
|
||||
*/
|
||||
import { chromium } from "playwright";
|
||||
import { mkdirSync } from "node:fs";
|
||||
|
||||
const OUT = "/tmp/aidir-shots";
|
||||
mkdirSync(OUT, { recursive: true });
|
||||
|
||||
const targets = [
|
||||
{ name: "classic-home", url: "http://localhost:3001/" },
|
||||
{ name: "classic-rankings", url: "http://localhost:3001/rankings" },
|
||||
{ name: "classic-tool-detail", url: "http://localhost:3001/tools/claude" },
|
||||
{ name: "classic-tools-list", url: "http://localhost:3001/tools" },
|
||||
{ name: "classic-best", url: "http://localhost:3001/best/best-ai-writing-tools" },
|
||||
{ name: "classic-usecase", url: "http://localhost:3001/ai-tools-for/blogging" },
|
||||
{ name: "classic-compare", url: "http://localhost:3001/tools/cursor-vs-windsurf" },
|
||||
{ name: "classic-alt", url: "http://localhost:3001/tools/midjourney-alternatives" },
|
||||
{ name: "classic-zh", url: "http://localhost:3001/zh" },
|
||||
{ name: "classic-submit", url: "http://localhost:3001/submit" },
|
||||
{ name: "product-home", url: "http://localhost:3002/" },
|
||||
{ name: "editorial-home", url: "http://localhost:3003/" },
|
||||
{ name: "niche-home", url: "http://localhost:3004/" },
|
||||
{ name: "minimal-home", url: "http://localhost:3005/" },
|
||||
{ name: "minimal-mobile", url: "http://localhost:3005/", mobile: true },
|
||||
{ name: "product-mobile", url: "http://localhost:3002/", mobile: true },
|
||||
];
|
||||
|
||||
const browser = await chromium.launch();
|
||||
const errors: string[] = [];
|
||||
|
||||
for (const target of targets) {
|
||||
const context = await browser.newContext(
|
||||
target.mobile
|
||||
? { viewport: { width: 390, height: 844 }, deviceScaleFactor: 2, isMobile: true, hasTouch: true }
|
||||
: { viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1 }
|
||||
);
|
||||
const page = await context.newPage();
|
||||
page.on("console", (msg) => {
|
||||
if (msg.type() === "error") errors.push(`${target.name}: ${msg.text().slice(0, 200)}`);
|
||||
});
|
||||
page.on("pageerror", (err) => errors.push(`${target.name}: PAGEERROR ${String(err).slice(0, 200)}`));
|
||||
|
||||
try {
|
||||
await page.goto(target.url, { waitUntil: "domcontentloaded", timeout: 30000 });
|
||||
await page.waitForTimeout(1200);
|
||||
await page.screenshot({ path: `${OUT}/${target.name}.png`, fullPage: false });
|
||||
// 顺便记录 H1
|
||||
const h1 = await page.locator("h1").first().textContent().catch(() => "(no h1)");
|
||||
console.log(`OK ${target.name} | h1: ${h1?.trim().slice(0, 60)}`);
|
||||
} catch (error) {
|
||||
console.log(`FAIL ${target.name}: ${String(error).slice(0, 120)}`);
|
||||
}
|
||||
await context.close();
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
console.log("\n=== 控制台错误 ===");
|
||||
console.log(errors.length === 0 ? "(none)" : errors.slice(0, 12).join("\n"));
|
||||
Reference in New Issue
Block a user