1. 电商主图详情页为什么总在重复劳动
做电商视觉的同行大概都有这种体验:一款新品上架,主图五张、详情页八到十屏,从抠图、排版、写文案到导出切图,一套流程走下来大半天没了。更难受的是,下一款 SKU 只是换了个颜色、换了个价格,前面那套排版逻辑几乎一模一样,却还得从头再摆一遍。这种重复劳动的本质,是设计稿和代码之间没有形成可复用的模板资产。
我试过把主图和详情页全部用 HTML+CSS 来写,再用 Playwright 做整页截图导出 PNG,配合 Codex 生成初版结构和文案,整个链路就变成了「改 JSON 配置 → 重新渲染 → 自动截图」的流水线。文字零错误、Logo 像素级对齐、改价改文案秒级重出,SKU 多的标品批量换皮尤其爽。这篇就聚焦用 Codex 生成电商主图与详情页 HTML 模板,并用 Playwright 做渲染截图与视觉回归验证,给出可复制的项目结构、提示词模板、截图脚本和对比阈值配置,最后演示一次从生成到校验的完整动作。
适合谁看:中小品牌做快速上新的运营和前端、需要批量出详情页的电商设计、想用代码替代重复排版的设计师。不适合:高端品牌主视觉、服装上身效果、3D 复杂合成这类必须实拍或专业合成的场景。
核心检索词先明确:Codex 电商主图与详情页 HTML 模板,本质是把「设计稿」翻译成「可配置的 HTML 组件」,再用 Playwright 把它渲染成图片并做视觉回归。下面从项目结构开始,一步步拆。
2. 项目结构与 Codex 提示词模板怎么搭
先规划目录,别一上来就让 Codex 乱写。一个能复用的电商视觉项目,我习惯这样分:
ecom-visual/ ├── templates/ │ ├── main-image.html # 主图模板 │ └── detail-page.html # 详情页模板 ├── data/ │ ├── sku-a.json # 每个 SKU 一份文案配置 │ └── sku-b.json ├── assets/ │ ├── product/ # 产品白底图 │ ├── logo/ │ └── fonts/ ├── scripts/ │ ├── render.mjs # Playwright 截图脚本 │ └── visual-diff.mjs # 视觉回归对比 ├── baseline/ # 基准截图 └── output/ # 本次产出关键点是文案全部抽成 JSON,HTML 里只留占位符和样式。这样换 SKU 时只改 JSON,模板一行不动。Codex 生成模板时,提示词要写清楚约束,否则它容易给你塞一堆内联样式和写死的文案。
我常用的提示词模板长这样:
用 HTML+CSS 实现一个 750px 宽的电商详情页模板,共 8 屏,每屏高度自适应但总高不超过 8000px。 要求:
- 所有文案从外部 JSON 读取,用
{{title}}、{{price}}、{{points}}这类占位符,不要写死任何中文。- 产品图用
<img src="{{productImage}}">,Logo 用{{logo}}。- 品牌色用 CSS 变量
--brand-primary,字体用--font-main。- 每屏结构:主标题 + 副文案 + 产品图/场景图 + 要点列表。
- 输出单个 HTML 文件,样式写在
<style>里,不要用外部 CDN。 先只输出 HTML 骨架和 CSS,我核对后再填内容。
这个提示词的重点是「先骨架后内容」。Codex 一次性生成完整页面时,很容易在文案上幻觉,比如编造不存在的参数。分两步走,先让它把结构和样式定下来,再单独喂 JSON 数据,出错率低很多。
主图模板同理,五张主图递进:首图核心卖点加价格锚、痛点场景、卖点两张、信任促销。每张主图其实是一个独立的 HTML 片段,用同一个 CSS 变量体系,保证风格统一。Codex 生成时,我会把「分镜宪法」先给它:
产品:低泡温和洁面乳 | 平台:淘宝 | 受众:敏感肌学生党 卖点:低泡温和、不紧绷、150g 大容量、到手价 79 风格:紫白电商风,强标题加价格区加白底产品图 任务:规划 5 张主图销售叙事,每张给出画面描述、主文案、副文案,先只输出方案。
方案核对没问题,再让它按方案生成 HTML。这一步别省,分镜是宪法,改一处后面照着局部调整,不推倒重来。
3. 可复制的配置片段与 Playwright 截图脚本
模板有了,接下来是配置和渲染。先看 SKU 的 JSON 配置,这是整个流水线的数据源:
{ "skuId": "cleanser-150g", "brand": { "primary": "#7C5CFF", "font": "PingFang SC" }, "mainImages": [ { "title": "低泡温和 敏感肌可用", "subtitle": "到手价 ¥79", "productImage": "assets/product/cleanser-front.png", "price": "79" } ], "detailScreens": [ { "role": "cover", "title": "温和洁面 从这一支开始", "points": ["低泡配方", "不紧绷", "150g 大容量"] }, { "role": "pain", "title": "洗完脸紧绷刺痛?", "points": ["皂基清洁力过强", "敏感肌屏障受损"] } ] }模板里用简单的占位符替换,或者用轻量模板引擎都行。我倾向在渲染脚本里做字符串替换,依赖少、可控。
Playwright 截图脚本是核心,scripts/render.mjs:
import { chromium } from 'playwright'; import fs from 'fs'; import path from 'path'; const skuFile = process.argv[2] || 'data/sku-a.json'; const sku = JSON.parse(fs.readFileSync(skuFile, 'utf-8')); const browser = await chromium.launch(); const page = await browser.newPage({ viewport: { width: 750, height: 1200 }, deviceScaleFactor: 2 }); // 渲染主图 for (let i = 0; i < sku.mainImages.length; i++) { const html = buildMainImageHtml(sku, i); await page.setContent(html, { waitUntil: 'networkidle' }); await page.screenshot({ path: `output/${sku.skuId}-main-${i + 1}.png`, fullPage: true }); } // 渲染详情页整页 const detailHtml = buildDetailHtml(sku); await page.setContent(detailHtml, { waitUntil: 'networkidle' }); await page.screenshot({ path: `output/${sku.skuId}-detail.png`, fullPage: true }); await browser.close(); console.log('渲染完成,输出到 output/');deviceScaleFactor: 2是为了导出高清图,电商平台对图片清晰度有要求。fullPage: true让详情页整页截成一张长图,省去手动拼接。
视觉回归对比脚本scripts/visual-diff.mjs,用像素差异做校验:
import { PNG } from 'pngjs'; import pixelmatch from 'pixelmatch'; import fs from 'fs'; const baselinePath = process.argv[2]; const currentPath = process.argv[3]; const threshold = 0.1; // 像素颜色容差 const baseline = PNG.sync.read(fs.readFileSync(baselinePath)); const current = PNG.sync.read(fs.readFileSync(currentPath)); const { width, height } = baseline; const diff = new PNG({ width, height }); const diffPixels = pixelmatch( baseline.data, current.data, diff.data, width, height, { threshold } ); const diffRatio = diffPixels / (width * height); fs.writeFileSync('output/diff.png', PNG.sync.write(diff)); if (diffRatio > 0.01) { console.error(`视觉回归失败:差异比例 ${(diffRatio * 100).toFixed(2)}%`); process.exit(1); } console.log(`视觉回归通过:差异比例 ${(diffRatio * 100).toFixed(2)}%`);阈值配置是重点。threshold: 0.1是单个像素的颜色容差,diffRatio > 0.01是整体差异比例上限。电商图里字体渲染、抗锯齿会有微小差异,阈值太严会天天误报。我的经验是:主图阈值设 0.5%,详情页设 1%,因为详情页长、元素多,累积误差大。
如果你用 Codex 做长期编码和 Agent 任务,可以把这套脚本挂到 Coding Plan 里跑,每次改模板自动触发回归。接入方式在 Coding Plan 页面有说明,Base URL、Key、Model ID 三件套配齐就能用。
4. 验证请求与成功结果长什么样
配置和脚本都齐了,跑一次完整动作。先确认 Codex 生成的模板能正常渲染,再验证截图和回归。
第一步,让 Codex 生成模板后,本地起个静态服务预览:
npx serve templates浏览器打开http://localhost:3000/main-image.html,检查布局有没有错位、占位符有没有漏替换。这一步是肉眼验收,别跳过。
第二步,跑渲染脚本:
node scripts/render.mjs data/sku-a.json成功的话终端输出:
渲染完成,输出到 output/output/目录下会出现cleanser-150g-main-1.png到main-5.png,以及cleanser-150g-detail.png。用图片查看器打开,确认文字清晰、价格数字正确、Logo 位置对齐。
第三步,把首次产出存为基准:
mkdir -p baseline cp output/cleanser-150g-detail.png baseline/第四步,改一处文案,比如把价格从 79 改成 69,重新渲染,再跑回归:
node scripts/render.mjs data/sku-a.json node scripts/visual-diff.mjs baseline/cleanser-150g-detail.png output/cleanser-150g-detail.png如果只改了价格区域,差异比例应该在 1% 以内,回归通过。如果模板被误改导致大面积错位,差异比例会飙升,脚本直接报错退出,CI 里就能拦住。
成功结果的标准:主图五张风格统一、详情页整页无断裂、文字零错别字、价格和参数与 JSON 一致、回归差异在阈值内。实测下来,一套八屏详情页从改 JSON 到出图,大概十几秒。
5. 本篇常见报错与排查
跑这套流程,几个报错几乎必踩,提前说清楚。
401 Unauthorized:调用模型接口时出现,多半是 Key 没配或配错。检查环境变量里的 Key 是否和 API Keys 页面生成的一致,注意别把 Base URL 和 Key 搞混。Base URL 用https://taotoken.net/api,不要带多余路径。
local proxy failed:本地代理配置冲突。如果你本地有开发代理,Playwright 启动浏览器时可能走错端口。在chromium.launch()里加proxy参数显式指定,或者临时关掉本地代理再跑。
reading 'choices' 报错:解析模型返回时字段不存在,通常是请求体格式不对,或者模型 ID 写错。确认 Model ID 和 Coding Plan 页面列的一致,请求体里messages结构别写错。
OAuth 相关报错:用 Claude Code 这类工具接入时,如果走 OAuth 流程失败,改用 API Key 方式。三件套配齐:Base URL 填https://taotoken.net/api,Key 填生成的密钥,Model ID 填对应模型。Claude Code 的配置在~/.claude/settings.json,Codex 的在~/.codex/auth.json,Cline 的在 MCP 配置里,路径别搞混。
截图空白或元素缺失:waitUntil: 'networkidle'有时不够,图片没加载完就截了。改成waitUntil: 'load'再加一个page.waitForTimeout(500)兜底。字体加载慢的话,用document.fonts.ready等字体就绪。
视觉回归误报:差异比例总是超标,先看output/diff.png,红色区域就是差异点。如果是字体抗锯齿导致,调大threshold;如果是真实错位,那就是模板问题,回去改 CSS。
排查顺序建议:先看终端报错关键词,再对照上面几条定位,最后看 diff 图确认是渲染问题还是数据问题。大部分报错集中在 Key 配置和等待时机上。
6. 把模板资产沉淀下来
这套流程跑通之后,真正的价值不在单次出图,而在模板资产的沉淀。每做完一个品类,把它的 HTML 模板、CSS 变量、JSON 结构存进templates/,下次同类产品直接复用。SKU 换皮时只改 JSON,模板一行不动,这才是批量出图的正确姿势。
几个实用技巧:主图模板按「首图/痛点/卖点/信任」拆成四个片段,组合时按需拼;详情页把每屏做成独立组件,Codex 生成新品类时只让它写新组件,别重写整个页面;基准图定期更新,产品迭代后旧基准就失效了,别拿过期基准跑回归。
如果你要长期跑这套流水线,把渲染和回归挂到 Coding Plan 上,每次提交自动触发,省得手动跑脚本。模型对话页面可以拿来快速验证 Codex 生成的提示词和文案,接入文档里有完整的接口说明。API Keys 页面生成密钥后,记得存到环境变量里,别硬编码进脚本。
最后一步,把output/里的成品图直接上传到电商后台,主图五张、详情页一张长图,齐活。整套动作从改 JSON 到上传,熟练之后十分钟以内。