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)
|
|||
|
|
})
|
|||
|
|
}
|