capture-mockups.mjs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import puppeteer from "puppeteer-core";
  2. import path from "path";
  3. import fs from "fs";
  4. import { fileURLToPath } from "url";
  5. const __dirname = path.dirname(fileURLToPath(import.meta.url));
  6. const root = path.resolve(__dirname, "..");
  7. const outDir = path.join(root, "mockups");
  8. const html = path.join(root, "index.html");
  9. const chrome =
  10. "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
  11. fs.mkdirSync(outDir, { recursive: true });
  12. const pages = [
  13. { name: "01-home", hash: "home", prep: null, full: false },
  14. {
  15. name: "02-notice",
  16. hash: "notice",
  17. full: true,
  18. prep: async (page) => {
  19. await page.evaluate(() => {
  20. if (typeof startFeedback === "function") startFeedback();
  21. const btn = document.getElementById("btn-enter-feedback");
  22. const num = document.getElementById("countdown");
  23. if (btn) {
  24. btn.disabled = true;
  25. btn.textContent = "请阅读须知(5s)";
  26. }
  27. if (num) num.textContent = "5";
  28. });
  29. },
  30. },
  31. { name: "03-feedback", hash: "feedback", prep: null, full: true },
  32. { name: "04-suggest", hash: "suggest", prep: null, full: true },
  33. { name: "05-hotline", hash: "hotline", prep: null, full: true },
  34. {
  35. name: "06-share",
  36. hash: "home",
  37. full: false,
  38. prep: async (page) => {
  39. await page.evaluate(() => {
  40. if (typeof go === "function") go("home");
  41. if (typeof showShare === "function") showShare();
  42. const tip = document.getElementById("share-url");
  43. if (tip) tip.textContent = "https://jjb.ajyy.example.com";
  44. });
  45. await page.waitForSelector("#share-overlay.show", { timeout: 5000 });
  46. },
  47. },
  48. ];
  49. const browser = await puppeteer.launch({
  50. executablePath: chrome,
  51. headless: "new",
  52. args: ["--no-sandbox", "--hide-scrollbars"],
  53. defaultViewport: { width: 390, height: 844, deviceScaleFactor: 2 },
  54. });
  55. const page = await browser.newPage();
  56. const fileUrl = "file://" + html;
  57. for (const item of pages) {
  58. // Force full reload each time (hash-only nav would keep DOM mutations)
  59. await page.goto(fileUrl + "?t=" + Date.now() + "#" + item.hash, {
  60. waitUntil: "networkidle0",
  61. timeout: 30000,
  62. });
  63. await page.waitForSelector(".device", { timeout: 10000 });
  64. await page.evaluate(() => {
  65. const device = document.querySelector(".device");
  66. if (device) {
  67. device.style.height = "844px";
  68. device.style.minHeight = "844px";
  69. device.style.borderRadius = "0";
  70. }
  71. });
  72. await page
  73. .waitForFunction(() => {
  74. const img = document.querySelector(".logo-wrap img, .mine-avatar img");
  75. if (!img) return true;
  76. return img.complete && img.naturalWidth > 0;
  77. }, { timeout: 8000 })
  78. .catch(() => {});
  79. if (item.prep) await item.prep(page);
  80. await new Promise((r) => setTimeout(r, 400));
  81. const out = path.join(outDir, `mockup-${item.name}.png`);
  82. if (item.full) {
  83. await page.evaluate(() => {
  84. const device = document.querySelector(".device");
  85. const view = document.querySelector(".view.active");
  86. const scroll = view?.querySelector(".scroll");
  87. if (!device || !view || !scroll) return;
  88. device.style.height = "auto";
  89. device.style.minHeight = "0";
  90. device.style.borderRadius = "0";
  91. view.style.position = "relative";
  92. view.style.height = "auto";
  93. view.style.minHeight = "0";
  94. scroll.style.overflow = "visible";
  95. scroll.style.flex = "none";
  96. scroll.style.paddingBottom = "24px";
  97. const tab = view.querySelector(".tab-bar");
  98. if (tab) tab.style.display = "none";
  99. });
  100. await new Promise((r) => setTimeout(r, 200));
  101. }
  102. const el = await page.$(".device");
  103. const box = await el.boundingBox();
  104. if (!box || box.height < 2) {
  105. await page.screenshot({ path: out, type: "png", fullPage: true });
  106. } else {
  107. await el.screenshot({ path: out, type: "png" });
  108. }
  109. console.log("saved", out, box ? `${Math.round(box.width)}x${Math.round(box.height)}` : "fullPage");
  110. }
  111. await browser.close();
  112. console.log("done");