为冰朔定制专属配图生成系统,支持三种卡片类型:
- 小红书竖版卡片 (1080×1440, 3:4)
- 即刻方卡 (1080×1080, 1:1)
- 海报 (1080×1920, 9:16)
特性:多套配色方案、多种布局风格、
Puppeteer + HTML/CSS 渲染引擎、
可部署 Web 服务 + 预览页面
使用方式:冰朔在对话里告诉铸渊内容和类型,
铸渊运行脚本生成,冰朔直接下载使用。
317 lines
7.4 KiB
JavaScript
317 lines
7.4 KiB
JavaScript
/**
|
||
* ═══════════════════════════════════════════════════
|
||
* 小红书卡片模板 · 1080×1440 (3:4)
|
||
* ═══════════════════════════════════════════════════
|
||
*
|
||
* 支持:
|
||
* - 单页卡片(封面/金句/干货)
|
||
* - 多页轮播(长内容自动分页)
|
||
*/
|
||
|
||
import { STYLES } from '../config.js'
|
||
|
||
const W = STYLES.sizes.xiaohongshu.width
|
||
const H = STYLES.sizes.xiaohongshu.height
|
||
|
||
|
||
/**
|
||
* 生成小红书卡片 HTML
|
||
* @param {object} data
|
||
* @param {string} data.title - 标题
|
||
* @param {string} data.body - 正文(支持 \n 换行)
|
||
* @param {string} data.subtitle - 副标题(可选)
|
||
* @param {string} data.tag - 标签(可选,如「干货·教程」)
|
||
* @param {string} data.quote - 金句/引用(可选)
|
||
* @param {string} data.footer - 底部文字(可选,如账号名)
|
||
* @param {string} data.layout - 布局: 'default' | 'quote' | 'list' | 'minimal'
|
||
* @param {number} data.pageNum - 当前页码(多页时)
|
||
* @param {number} data.totalPages - 总页数(多页时)
|
||
*/
|
||
export function xiaohongshuCard(data) {
|
||
const {
|
||
title = '',
|
||
body = '',
|
||
subtitle = '',
|
||
tag = '',
|
||
quote = '',
|
||
footer = '',
|
||
layout = 'default',
|
||
pageNum = 1,
|
||
totalPages = 1,
|
||
} = data
|
||
|
||
const C = STYLES.colors
|
||
const F = STYLES.fonts
|
||
const T = STYLES.typography
|
||
|
||
// 正文按换行分段
|
||
const bodyLines = body.split('\n').filter(Boolean)
|
||
|
||
const bodyHtml = bodyLines.map(line => {
|
||
// 检测是否以 - 开头(列表项)
|
||
if (line.trimStart().startsWith('- ')) {
|
||
return `<li>${line.trimStart().slice(2)}</li>`
|
||
}
|
||
// 检测是否以 # 开头(小标题)
|
||
if (line.trimStart().startsWith('#')) {
|
||
return `<h3 class="sub-heading">${line.trimStart().replace(/^#+\s*/, '')}</h3>`
|
||
}
|
||
return `<p>${line}</p>`
|
||
}).join('\n')
|
||
|
||
const hasList = bodyLines.some(l => l.trimStart().startsWith('- '))
|
||
|
||
return `<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
|
||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap');
|
||
|
||
body {
|
||
width: ${W}px;
|
||
height: ${H}px;
|
||
overflow: hidden;
|
||
background: ${C.bg};
|
||
font-family: ${F.body};
|
||
display: flex;
|
||
flex-direction: column;
|
||
position: relative;
|
||
}
|
||
|
||
/* ── 装饰角标 ── */
|
||
.corner-tl, .corner-br {
|
||
position: absolute;
|
||
width: 60px;
|
||
height: 60px;
|
||
border-color: ${C.accent};
|
||
opacity: 0.15;
|
||
}
|
||
.corner-tl {
|
||
top: 24px; left: 24px;
|
||
border-top: 2px solid;
|
||
border-left: 2px solid;
|
||
}
|
||
.corner-br {
|
||
bottom: 24px; right: 24px;
|
||
border-bottom: 2px solid;
|
||
border-right: 2px solid;
|
||
}
|
||
|
||
.container {
|
||
flex: 1;
|
||
padding: ${STYLES.spacing.paddingY}px ${STYLES.spacing.paddingX}px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
}
|
||
|
||
/* ── 标签 ── */
|
||
.tag {
|
||
display: inline-block;
|
||
padding: 8px 20px;
|
||
background: ${C.accent};
|
||
color: ${C.textLight};
|
||
font-size: ${T.captionSize}px;
|
||
border-radius: 20px;
|
||
margin-bottom: 24px;
|
||
align-self: flex-start;
|
||
letter-spacing: 2px;
|
||
}
|
||
|
||
/* ── 标题 ── */
|
||
.title {
|
||
font-family: ${F.title};
|
||
font-size: ${T.titleSize}px;
|
||
font-weight: 700;
|
||
line-height: 1.35;
|
||
color: ${C.primary};
|
||
margin-bottom: ${subtitle ? 16 : 32}px;
|
||
letter-spacing: 2px;
|
||
}
|
||
|
||
.subtitle {
|
||
font-size: ${T.subtitleSize}px;
|
||
font-weight: 300;
|
||
color: ${C.textMuted};
|
||
margin-bottom: 32px;
|
||
letter-spacing: 1px;
|
||
}
|
||
|
||
/* ── 正文 ── */
|
||
.body {
|
||
flex: 1;
|
||
font-size: ${T.bodySize}px;
|
||
line-height: ${T.lineHeight};
|
||
color: ${C.text};
|
||
}
|
||
|
||
.body p {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.body ul, .body ol {
|
||
padding-left: 32px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.body li {
|
||
font-size: ${T.bodySize}px;
|
||
line-height: ${T.lineHeight};
|
||
margin-bottom: 8px;
|
||
color: ${C.text};
|
||
list-style: none;
|
||
position: relative;
|
||
padding-left: 24px;
|
||
}
|
||
|
||
.body li::before {
|
||
content: "◆";
|
||
position: absolute;
|
||
left: 0;
|
||
color: ${C.highlight};
|
||
font-size: 14px;
|
||
top: 6px;
|
||
}
|
||
|
||
.sub-heading {
|
||
font-family: ${F.title};
|
||
font-size: ${T.subtitleSize}px;
|
||
font-weight: 600;
|
||
color: ${C.accent};
|
||
margin: 24px 0 12px 0;
|
||
letter-spacing: 1px;
|
||
}
|
||
|
||
/* ── 金句布局 ── */
|
||
.quote-section {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
text-align: center;
|
||
padding: 0 40px;
|
||
}
|
||
|
||
.quote-mark {
|
||
font-size: 80px;
|
||
font-family: ${F.title};
|
||
color: ${C.highlight};
|
||
opacity: 0.3;
|
||
line-height: 1;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.quote-text {
|
||
font-family: ${F.title};
|
||
font-size: 40px;
|
||
font-weight: 600;
|
||
line-height: 1.5;
|
||
color: ${C.primary};
|
||
letter-spacing: 3px;
|
||
}
|
||
|
||
.quote-author {
|
||
margin-top: 32px;
|
||
font-size: ${T.smallSize}px;
|
||
color: ${C.textMuted};
|
||
}
|
||
|
||
/* ── 极简布局 ── */
|
||
.minimal-title {
|
||
font-family: ${F.title};
|
||
font-size: 48px;
|
||
font-weight: 300;
|
||
line-height: 1.4;
|
||
color: ${C.primary};
|
||
letter-spacing: 4px;
|
||
text-align: center;
|
||
}
|
||
|
||
.minimal-divider {
|
||
width: 60px;
|
||
height: 1px;
|
||
background: ${C.gold};
|
||
margin: 32px auto;
|
||
}
|
||
|
||
/* ── 底部 ── */
|
||
.footer {
|
||
padding: 24px ${STYLES.spacing.paddingX}px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
border-top: 1px solid ${C.divider};
|
||
font-size: ${T.captionSize}px;
|
||
color: ${C.textMuted};
|
||
}
|
||
|
||
.brand {
|
||
opacity: ${STYLES.brand.opacity};
|
||
}
|
||
|
||
.pagination {
|
||
font-size: ${T.captionSize}px;
|
||
color: ${C.textMuted};
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="corner-tl"></div>
|
||
<div class="corner-br"></div>
|
||
|
||
<div class="container">
|
||
|
||
${tag ? `<div class="tag">${tag}</div>` : ''}
|
||
|
||
${layout === 'quote' ? `
|
||
<div class="quote-section">
|
||
<div class="quote-mark">"</div>
|
||
<div class="quote-text">${quote || title}</div>
|
||
${subtitle ? `<div class="quote-author">${subtitle}</div>` : ''}
|
||
</div>
|
||
` : layout === 'minimal' ? `
|
||
<div style="flex:1;display:flex;flex-direction:column;justify-content:center;align-items:center;">
|
||
<div class="minimal-title">${title}</div>
|
||
<div class="minimal-divider"></div>
|
||
${body ? `<div style="font-size:${T.bodySize}px;color:${C.textMuted};text-align:center;max-width:80%;">${body}</div>` : ''}
|
||
</div>
|
||
` : `
|
||
<div class="title">${title}</div>
|
||
${subtitle ? `<div class="subtitle">${subtitle}</div>` : ''}
|
||
<div class="body">
|
||
${bodyHtml}
|
||
</div>
|
||
`}
|
||
|
||
</div>
|
||
|
||
<div class="footer">
|
||
<span class="brand">${STYLES.brand.text}</span>
|
||
${totalPages > 1 ? `<span class="pagination">${pageNum} / ${totalPages}</span>` : ''}
|
||
</div>
|
||
|
||
</body>
|
||
</html>`
|
||
}
|
||
|
||
|
||
/**
|
||
* 生成小红书轮播(多页)
|
||
* @param {object} data - 同 xiaohongshuCard
|
||
* @param {string[]} pages - 每页正文内容数组
|
||
* @returns {string[]} HTML 数组
|
||
*/
|
||
export function xiaohongshuCarousel(data, pages) {
|
||
return pages.map((body, i) => xiaohongshuCard({
|
||
...data,
|
||
body,
|
||
pageNum: i + 1,
|
||
totalPages: pages.length,
|
||
}))
|
||
}
|