بنقرة واحدة
imgx-template-generator
生成符合 Satori 渲染约束的 IMGX Vue 模板,将文本转换为精美的卡片图片
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
生成符合 Satori 渲染约束的 IMGX Vue 模板,将文本转换为精美的卡片图片
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
| name | imgx-template-generator |
| description | 生成符合 Satori 渲染约束的 IMGX Vue 模板,将文本转换为精美的卡片图片 |
生成符合 Satori 渲染约束的 IMGX Vue 模板,将文本转换为精美的卡片图片。
在开始之前,必须阅读以下文档:
| 文档 | 时长 | 内容 |
|---|---|---|
| 📖 架构说明 | 2 分钟 | 了解模板字符串是唯一真相源,避免创建无用的 Vue 组件 |
| ⚠️ Satori 约束 | 3 分钟 | 必须遵守的渲染规则(Flexbox、支持的 CSS) |
| 🔧 Props 系统 | 3 分钟 | 理解数据结构(content、styleProps) |
为什么必须先读?
根据用户需求,选择对应的模板蓝图:
| 蓝图类型 | 适用场景 | 阅读时长 | 文档链接 |
|---|---|---|---|
| 📝 简单文本 | 单行/多行纯文本,统一样式,居中布局 | 5 分钟 | blueprint-simple-text.md |
| 📋 多行布局 | 多行文本,每行不同颜色/大小/对齐方式 | 6 分钟 | blueprint-multi-line.md |
| 🎨 强调文本 | 需要高亮关键词(背景盒子/特殊颜色) | 7 分钟 | blueprint-with-accent.md |
| 🌈 渐变背景 | 使用线性/径向渐变,复杂背景效果 | 6 分钟 | blueprint-with-gradient.md |
| 🎯 带图标 | 需要显示图标/emoji/装饰元素 | 7 分钟 | blueprint-with-icons.md |
| 🧱 像素矩阵 | 像素艺术风格,用 emoji/颜色拼成文字或图案 | 5 分钟 | blueprint-pixel-matrix.md |
⚠️ 图标使用限制(重要)
IMGX 系统仅支持本地图标库(assets/icons/ 目录),不支持远程图标库:
| 支持 | 格式 | 说明 |
|---|---|---|
| ✅ 本地图标 | prefix:icon-name | 从 assets/icons/ 加载的图标集 |
| ❌ lucide | lucide:sparkles | 远程图标(网络不稳定) |
| ❌ material | material:home | 远程图标(未实现) |
| ❌ fa | fa:github | 远程图标(未实现) |
| ❌ heroicons | heroicons:check | 远程图标(未实现) |
当前可用图标集:
twemoji:beaming-face-with-smiling-eyes 😁, twemoji:cowboy-hat-face 🤠assets/icons/twemoji-face-icons.json如何添加新图标集:
assets/icons/lib/icons.ts 中导入并注册新图标集<prefix>:icon-name(prefix 为图标集的 prefix 字段)查看可用图标: 查看 assets/icons/ 目录下的 .json 文件
✨ 每个蓝图包含:
// server/templates/[Name].ts
export const [Name]Template = `<div class="w-full h-full flex">
<!-- 根据蓝图填充内容 -->
</div>`
要点:
[Name]Templatew-full h-full flexclass="flex"// server/utils/image.ts
import { [Name]Template } from '../templates/[Name]'
const templateStrings: Record<string, string> = {
// ... 其他模板
'[Name]': [Name]Template, // ← 新增这行
}
# 1. 检查下一个可用编号
ls presets/*.json | sort
# 2. 创建 preset 文件
# presets/[code].json
**📋 Preset 命名规范**:参考 [preset-naming-convention.md](references/preset-naming-convention.md)
- 文章封面(2.35:1)→ `1xx` (如 `101.json`)
- 小红书(3:4)→ `2xx` (如 `201.json`)
- 视频封面(16:9)→ `3xx` (如 `301.json`)
- 方形(1:1)→ `4xx` (如 `401.json`)
- OG 分享(1.91:1)→ `5xx` (如 `501.json`)
{
"code": "007",
"name": "My Template",
"size": { "width": 1200, "height": 630 },
"ratio": "1.91:1",
"template": "[Name]",
"contentProps": { "content": [[...]] },
"styleProps": {
"bgColor": "#FFFFFF",
"bgImage": "linear-gradient(...)",
"textWrapBgColor": "transparent",
"textWrapPadding": "0px",
"colors": ["#000000"],
"accentColors": ["#FF0000"],
"fontSizes": ["48px"],
"aligns": ["justify-center"],
"verticalAligns": ["center"],
"fontFamily": "YouSheBiaoTiHei",
"padding": "60px"
}
}
关键字段:
template: 必须与 templateStrings 的 key 一致styleProps: 必须包含所有 12 个必需字段(见蓝图文档)# 启动开发服务器(如未运行)
pnpm dev
# 访问测试 URL
http://localhost:4573/[code]/default
http://localhost:4573/[code]/测试文本*强调*内容
使用质量检查清单: checklist.md
atob() Errors in ProductionIf you encounter InvalidCharacterError: Invalid character at atob in production (but works locally), follow this diagnostic checklist:
Symptom:
[Image] Invalid base64 characters detected: 137,80,78,71,13,10...
Root Cause:
storage.getItemRaw<Buffer>() may return Uint8Array or plain objects in production.toString('base64') methodFix (server/utils/image-loader.ts):
// ❌ WRONG: Assumes buffer is always Buffer
const base64 = buffer.toString('base64')
// ✅ CORRECT: Defensive type conversion
const properBuffer = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer)
const base64 = properBuffer.toString('base64')
const dataUrl = `data:${mimeType};base64,${base64}`
// Add diagnostic logging
console.log('[ImageLoader] Base64 preview:', base64.substring(0, 50))
console.log(`[ImageLoader] type: ${properBuffer.constructor.name}`)
Symptom:
InvalidCharacterError: Invalid character
at atob (node:buffer:1292:13)
at ef (satori/dist/index.js:3:49379)
Root Cause:
backgroundImage: url(data:image/...;base64,...)atob() fails on certain base64 formatsFix: Use <img> instead of backgroundImage
// ❌ AVOID: CSS backgroundImage with data URLs
<div :style="{ backgroundImage: `url(${logoUrl})` }"></div>
// ✅ RECOMMENDED: Direct img element
<img :src="logoUrl" :style="{ objectFit: 'cover' }" />
Why <img> works better:
Symptom:
`nowrap` unknown or invalid utility
Fix:
// ❌ WRONG: Not a valid Tailwind class
<span class="text-nowrap flex">
// ✅ CORRECT: Valid Tailwind class
<span class="whitespace-nowrap flex">
Invalid CSS classes can interfere with Satori's template parsing.
Symptom:
atob() errors with SVG iconsRoot Cause:
btoa() is a browser API, unreliable in Node.jsFix (server/utils/icons.ts, lib/icons.ts):
// ❌ WRONG: Browser API in server code
const base64 = btoa(svgHTML)
// ✅ CORRECT: Node.js Buffer API
const base64 = Buffer.from(svgHTML, 'utf8').toString('base64')
Symptom:
?bgColor=transparent produces black background instead of transparentinherit, currentColor) also failRoot Cause:
paramNormalizer.ts auto-adds # prefix to all color valuestransparent becomes #transparent (invalid color)Fix (server/utils/paramNormalizer.ts):
// In normalizeValue() for COLOR_PROPS
if (COLOR_PROPS.has(key)) {
if (strValue.startsWith('#')) {
return strValue
}
// ✅ NEW: Preserve CSS color keywords
const cssColorKeywords = ['transparent', 'inherit', 'currentColor', 'none']
if (cssColorKeywords.includes(strValue.toLowerCase())) {
return strValue.toLowerCase()
}
return `#${strValue}`
}
Usage:
# ✅ Now works correctly
GET /api/104/text?bgColor=transparent
GET /api/104/text?bgColor=inherit
Symptom:
[Image] Base64 contains whitespace - sanitizing
Root Cause:
+ to spaceatob() rejects base64 with whitespaceFix (server/utils/image.ts):
// Add sanitization before Satori rendering
if (styleFinalProps.logoUrl?.startsWith('data:')) {
const idx = styleFinalProps.logoUrl.indexOf(',');
if (idx !== -1) {
const head = styleFinalProps.logoUrl.slice(0, idx + 1);
let body = styleFinalProps.logoUrl.slice(idx + 1);
// Remove whitespace corruption
if (/\s/.test(body)) {
console.warn('[Image] Base64 contains whitespace - sanitizing');
body = body.replace(/\s+/g, '');
}
// Validate charset
if (!/^[A-Za-z0-9+/=]+$/.test(body)) {
throw createError({
statusCode: 500,
statusMessage: 'Invalid image data encoding'
});
}
styleFinalProps.logoUrl = head + body;
}
}
Prevention:
logoPath=images/logo.png+ characters)1. Add Logging at Key Points:
// In image-loader.ts
console.log('[ImageLoader] Base64 preview:', base64.substring(0, 50))
console.log('[ImageLoader] Buffer type:', buffer.constructor.name)
// In image.ts
console.log('[Image] logoUrl preview:', styleFinalProps.logoUrl?.substring(0, 60))
// In satori.ts
console.log('[Satori] Rendering HTML length:', html.length)
2. Check Production Logs:
✅ Success Pattern:
[ImageLoader] Base64 preview: iVBORw0KGgoAAAANSUhEU...
[ImageLoader] type: Buffer
❌ Failure Pattern:
[Image] Invalid base64 characters: 137,80,78,71...
→ Indicates Buffer serialization issue
3. Verify Base64 Integrity:
# Extract base64 from logs and test decode
echo "iVBORw0KGgoAAAA..." | base64 -d | file -
# Should show: PNG image data
| Component | Best Practice | Avoid |
|---|---|---|
| Images | Use <img src="data:..."> | backgroundImage: url(...) |
| Buffer | Buffer.isBuffer() check | Assume storage returns Buffer |
| Base64 | Buffer.from().toString('base64') | btoa() on server |
| CSS | Valid Tailwind classes | text-nowrap (invalid) |
| Data URLs | Pass as asset paths | Query string parameters |
| Logging | Preview first 50 chars | Log full 10KB base64 |
If after applying all fixes you still see atob() errors:
npm ls satori)format=svg)class="flex"用户需求
↓
📖 阅读架构文档(architecture.md, satori-constraints.md, props-system.md)
↓
🎨 选择蓝图(simple-text / multi-line / accent / gradient / icons)
↓
📝 阅读蓝图文档(了解完整代码和配置)
↓
💻 创建 server/templates/[Name].ts
↓
⚙️ 注册到 server/utils/image.ts
↓
📦 创建 presets/[code].json
↓
🧪 测试 http://localhost:4573/[code]/default
↓
✅ 质量检查(checklist.md)
↓
🎉 完成!
问题 1: 是否需要强调特定文字(高亮/背景盒子)?
├─ 是 → 🎨 使用 blueprint-with-accent.md
└─ 否 → 继续
问题 2: 是否需要渐变背景或复杂背景效果?
├─ 是 → 🌈 使用 blueprint-with-gradient.md
└─ 否 → 继续
问题 3: 是否需要显示图标或 emoji?
├─ 是 → 🎯 使用 blueprint-with-icons.md
└─ 否 → 继续
问题 4: 是否需要像素艺术风格(用色块/emoji拼成图案)?
├─ 是 → 🧱 使用 blueprint-pixel-matrix.md
└─ 否 → 继续
问题 5: 是否需要多行文本且每行样式不同?
├─ 是 → 📋 使用 blueprint-multi-line.md
└─ 否 → 📝 使用 blueprint-simple-text.md
预计时间:30 分钟完成第一个模板
预计时间:15 分钟完成新模板
一个合格的模板必须满足:
/{code}/default 正常显示开始前必读: 架构说明 → Satori 约束 → 选择蓝图
遇到问题? 查阅 故障排除指南 或 Production Debugging