screenshot.mjs 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. #!/usr/bin/env node
  2. /**
  3. * Darwin Skill - 高清截图脚本
  4. *
  5. * 用法: node scripts/screenshot.mjs [html文件路径] [输出png路径]
  6. *
  7. * 特性:
  8. * - 2x deviceScaleFactor,输出高清图
  9. * - 只截 .card 元素,无多余背景
  10. * - 等待字体加载完成
  11. * - 截完自动用 open 命令打开图片
  12. */
  13. import { createRequire } from 'module';
  14. import { execSync } from 'child_process';
  15. import { existsSync } from 'fs';
  16. import path from 'path';
  17. const require = createRequire(import.meta.url);
  18. // 健壮解析 playwright / playwright-core:跨机器、跨平台,不硬编码任何用户路径
  19. function loadPlaywright() {
  20. // 1) 标准解析(脚本目录或 NODE_PATH 里有的话)
  21. for (const m of ['playwright', 'playwright-core']) {
  22. try { return require(m); } catch {}
  23. }
  24. // 2) 动态定位全局 node_modules 并尝试候选路径
  25. const candidates = [];
  26. try {
  27. const groot = execSync('npm root -g', { encoding: 'utf8' }).trim();
  28. candidates.push(path.join(groot, 'playwright-core'));
  29. candidates.push(path.join(groot, 'playwright'));
  30. // openclaw 等把 playwright-core 作为嵌套依赖的情况
  31. candidates.push(path.join(groot, 'openclaw', 'node_modules', 'playwright-core'));
  32. } catch {}
  33. for (const c of candidates) {
  34. if (existsSync(c)) { try { return require(c); } catch {} }
  35. }
  36. throw new Error(
  37. 'playwright/playwright-core 未找到。请先安装:npm install -g playwright-core 且 npx playwright install chromium'
  38. );
  39. }
  40. const pw = loadPlaywright();
  41. const htmlPath = process.argv[2] || new URL('../templates/result-card.html', import.meta.url).pathname;
  42. const outputPath = process.argv[3] || new URL('../templates/result-card.png', import.meta.url).pathname;
  43. async function screenshot() {
  44. const browser = await pw.chromium.launch();
  45. try {
  46. const context = await browser.newContext({
  47. viewport: { width: 920, height: 1600 },
  48. deviceScaleFactor: 2,
  49. });
  50. const page = await context.newPage();
  51. await page.goto(`file://${htmlPath.replace(/\\/g,'/')}`, { waitUntil: 'networkidle' });
  52. // 等待字体加载
  53. await page.evaluate(() => document.fonts.ready);
  54. // 额外等待确保渲染完成
  55. await page.waitForTimeout(2000);
  56. // 只截 .card 元素
  57. const card = await page.locator('.card');
  58. await card.screenshot({
  59. path: outputPath,
  60. type: 'png',
  61. });
  62. console.log(`截图完成: ${outputPath}`);
  63. // 获取图片尺寸信息
  64. const box = await card.boundingBox();
  65. console.log(`卡片尺寸: ${Math.round(box.width)}x${Math.round(box.height)}px (CSS)`);
  66. console.log(`输出尺寸: ${Math.round(box.width * 2)}x${Math.round(box.height * 2)}px (2x高清)`);
  67. } finally {
  68. await browser.close();
  69. }
  70. // 自动打开图片(跨平台)
  71. try {
  72. if (process.platform === 'win32') {
  73. execSync(`start "" "${outputPath}"`);
  74. } else if (process.platform === 'darwin') {
  75. execSync(`open "${outputPath}"`);
  76. } else {
  77. execSync(`xdg-open "${outputPath}"`);
  78. }
  79. } catch {
  80. // 打不开也无妨,路径已打印
  81. console.log(`图片已保存: ${outputPath}`);
  82. }
  83. }
  84. screenshot().catch(err => {
  85. console.error('截图失败:', err.message);
  86. process.exit(1);
  87. });