为冰朔定制专属配图生成系统,支持三种卡片类型:
- 小红书竖版卡片 (1080×1440, 3:4)
- 即刻方卡 (1080×1080, 1:1)
- 海报 (1080×1920, 9:16)
特性:多套配色方案、多种布局风格、
Puppeteer + HTML/CSS 渲染引擎、
可部署 Web 服务 + 预览页面
使用方式:冰朔在对话里告诉铸渊内容和类型,
铸渊运行脚本生成,冰朔直接下载使用。
199 lines
6.4 KiB
JavaScript
199 lines
6.4 KiB
JavaScript
#!/usr/bin/env node
|
||
|
||
/**
|
||
* ═══════════════════════════════════════════════════
|
||
* 铸渊图片工作室 · 主入口
|
||
* ═══════════════════════════════════════════════════
|
||
*
|
||
* 冰朔,你告诉我内容和想要的类型,我帮你生成。
|
||
*
|
||
* 用法:
|
||
* node generate.js xiaohongshu --title "标题" --body "正文"
|
||
* node generate.js jike --title "观点" --body "内容"
|
||
* node generate.js poster --title "海报标题" --details "时间:5月1日" "地点:广州"
|
||
*
|
||
* 也可以直接在代码里调用 API:
|
||
* import { generate } from './generate.js'
|
||
* await generate({ type: 'xiaohongshu', title: '...', body: '...' })
|
||
*/
|
||
|
||
import { renderToImage, renderCarousel } from './renderer.js'
|
||
import { xiaohongshuCard, xiaohongshuCarousel } from './templates/xiaohongshu.js'
|
||
import { jikeCard } from './templates/jike.js'
|
||
import { posterCard } from './templates/poster.js'
|
||
import { STYLES, useScheme } from './config.js'
|
||
|
||
|
||
/**
|
||
* 统一生成入口
|
||
* @param {object} opts
|
||
* @param {'xiaohongshu'|'jike'|'poster'} opts.type - 卡片类型
|
||
* @param {string} opts.title - 标题
|
||
* @param {string} opts.body - 正文
|
||
* @param {string} opts.subtitle - 副标题
|
||
* @param {string} opts.tag - 标签
|
||
* @param {string} opts.quote - 金句(小红书 quote 布局用)
|
||
* @param {string} opts.emoji - Emoji(即刻用)
|
||
* @param {string} opts.footer - 底部文字
|
||
* @param {string[]} opts.details - 详情列表(海报用)
|
||
* @param {string} opts.cta - 行动号召(海报用)
|
||
* @param {'default'|'quote'|'list'|'minimal'} opts.layout - 布局
|
||
* @param {'default'|'notice'|'recruit'} opts.style - 海报风格
|
||
* @param {string} opts.scheme - 配色方案名
|
||
* @param {string} opts.output - 输出文件名(不含扩展名)
|
||
* @param {boolean} opts.carousel - 是否拆为多页轮播
|
||
* @param {string[]} opts.pages - 多页正文(轮播用)
|
||
* @returns {Promise<{files: string[], type: string}>}
|
||
*/
|
||
export async function generate(opts = {}) {
|
||
const {
|
||
type = 'xiaohongshu',
|
||
title = '',
|
||
body = '',
|
||
subtitle = '',
|
||
tag = '',
|
||
quote = '',
|
||
emoji = '',
|
||
footer = '',
|
||
details = [],
|
||
cta = '',
|
||
layout = 'default',
|
||
style = 'default',
|
||
scheme = '',
|
||
output = '',
|
||
carousel = false,
|
||
pages = [],
|
||
} = opts
|
||
|
||
// 切换配色
|
||
if (scheme) useScheme(scheme)
|
||
|
||
const baseName = output || `${type}_${Date.now()}`
|
||
const size = STYLES.sizes[type] || STYLES.sizes.xiaohongshu
|
||
|
||
let files = []
|
||
|
||
switch (type) {
|
||
case 'xiaohongshu': {
|
||
if (carousel && pages.length > 0) {
|
||
const htmls = xiaohongshuCarousel({ title, subtitle, tag, quote, layout }, pages)
|
||
files = await renderCarousel(htmls, { ...size, baseName })
|
||
} else {
|
||
const html = xiaohongshuCard({ title, body, subtitle, tag, quote, footer, layout })
|
||
files = [await renderToImage(html, { ...size, name: baseName })]
|
||
}
|
||
break
|
||
}
|
||
|
||
case 'jike': {
|
||
const html = jikeCard({ title, body, meta: subtitle, emoji, layout })
|
||
files = [await renderToImage(html, { ...size, name: baseName })]
|
||
break
|
||
}
|
||
|
||
case 'poster': {
|
||
const html = posterCard({ title, subtitle, body, details, cta, footer, tag, style })
|
||
files = [await renderToImage(html, { ...size, name: baseName })]
|
||
break
|
||
}
|
||
|
||
default:
|
||
throw new Error(`未知卡片类型: ${type}`)
|
||
}
|
||
|
||
return { files, type }
|
||
}
|
||
|
||
|
||
/* ── CLI 入口 ── */
|
||
async function cli() {
|
||
const args = process.argv.slice(2)
|
||
const type = args.find(a => !a.startsWith('--'))
|
||
|
||
if (!type || type === 'help') {
|
||
console.log(`
|
||
铸渊图片工作室 · 用法
|
||
|
||
node generate.js <类型> [选项]
|
||
|
||
类型:
|
||
xiaohongshu 小红书竖版卡片 (1080×1440)
|
||
jike 即刻方卡 (1080×1080)
|
||
poster 海报 (1080×1920)
|
||
|
||
选项:
|
||
--title 标题
|
||
--body 正文
|
||
--subtitle 副标题/元信息
|
||
--tag 标签
|
||
--quote 金句(小红书 quote 布局)
|
||
--emoji Emoji(即刻)
|
||
--details 详情(海报,可多个)
|
||
--cta 行动号召(海报)
|
||
--footer 底部文字
|
||
--layout 布局: default|quote|list|minimal
|
||
--style 海报风格: default|notice|recruit
|
||
--scheme 配色: guanghu|cream|night|green
|
||
--output 输出文件名
|
||
|
||
示例:
|
||
node generate.js xiaohongshu --title "如何高效学习" --body "第一点\\n第二点\\n第三点"
|
||
node generate.js jike --title "一个思考" --body "今天想到的..." --emoji "💡"
|
||
node generate.js poster --title "放假通知" --style notice --details "时间:5月1日" "地点:广州"
|
||
`)
|
||
process.exit(0)
|
||
}
|
||
|
||
function getArg(key) {
|
||
const idx = args.indexOf(key)
|
||
if (idx === -1) return undefined
|
||
// 收集后续所有非 -- 开头的参数,直到遇到下一个 --
|
||
const values = []
|
||
for (let i = idx + 1; i < args.length; i++) {
|
||
if (args[i].startsWith('--')) break
|
||
values.push(args[i])
|
||
}
|
||
return values.length === 0 ? undefined
|
||
: values.length === 1 ? values[0]
|
||
: values
|
||
}
|
||
|
||
const opts = {
|
||
type,
|
||
title: getArg('--title') || '',
|
||
body: (() => {
|
||
const b = getArg('--body')
|
||
const text = Array.isArray(b) ? b.join('\n') : (b || '')
|
||
// 将 \n 转义符转为真实换行
|
||
return text.replace(/\\n/g, '\n')
|
||
})(),
|
||
subtitle: getArg('--subtitle') || '',
|
||
tag: getArg('--tag') || '',
|
||
quote: getArg('--quote') || '',
|
||
emoji: getArg('--emoji') || '',
|
||
footer: getArg('--footer') || '',
|
||
details: (() => {
|
||
const d = getArg('--details')
|
||
return Array.isArray(d) ? d : (d ? [d] : [])
|
||
})(),
|
||
cta: getArg('--cta') || '',
|
||
layout: getArg('--layout') || 'default',
|
||
style: getArg('--style') || 'default',
|
||
scheme: getArg('--scheme') || '',
|
||
output: getArg('--output') || '',
|
||
}
|
||
|
||
console.log(`🎨 生成${type}卡片...`)
|
||
const result = await generate(opts)
|
||
console.log(`✅ 生成完成!`)
|
||
result.files.forEach(f => console.log(` 📄 ${f}`))
|
||
}
|
||
|
||
// 如果直接运行
|
||
if (process.argv[1] && (process.argv[1].includes('generate.js'))) {
|
||
cli().catch(err => {
|
||
console.error('❌ 生成失败:', err.message)
|
||
process.exit(1)
|
||
})
|
||
}
|