feat: 小红书模板2026审美升级 · CSS变量驱动配色 · 几何光斑装饰 · 三种布局(default/quote/hero) · 支持预设风格切换
This commit is contained in:
parent
5785cd8895
commit
991452b544
@ -1,31 +1,32 @@
|
|||||||
/**
|
/**
|
||||||
* ═══════════════════════════════════════════════════
|
* ═══════════════════════════════════════════════════
|
||||||
* 小红书卡片模板 · 1080×1440 (3:4)
|
* 小红书卡片模板 · 2026审美版 · 1080×1440 (3:4)
|
||||||
* ═══════════════════════════════════════════════════
|
* ═══════════════════════════════════════════════════
|
||||||
*
|
*
|
||||||
* 支持:
|
* 风格:极简大标题 + 高对比度 + 大面积留白 + 渐变色块
|
||||||
* - 单页卡片(封面/金句/干货)
|
* 支持 CSS 变量驱动配色(从 registry 预设传入)
|
||||||
* - 多页轮播(长内容自动分页)
|
*
|
||||||
|
* 布局:
|
||||||
|
* - default: 标题 + 正文列表(教程/干货)
|
||||||
|
* - quote: 金句居中大字
|
||||||
|
* - hero: 大标题 + 一句话副标题(封面)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { STYLES } from '../config.js'
|
import { STYLES } from '../config.js'
|
||||||
|
|
||||||
const W = STYLES.sizes.xiaohongshu.width
|
const W = 1080
|
||||||
const H = STYLES.sizes.xiaohongshu.height
|
const H = 1440
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 CSS 变量对象转换为 :root 样式
|
||||||
|
*/
|
||||||
|
function cssVarBlock(vars = {}) {
|
||||||
|
const lines = Object.entries(vars).map(([k, v]) => ` ${k}: ${v};`)
|
||||||
|
return `:root {\n${lines.join('\n')}\n }`
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成小红书卡片 HTML
|
* 生成小红书卡片 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) {
|
export function xiaohongshuCard(data) {
|
||||||
const {
|
const {
|
||||||
@ -33,33 +34,46 @@ export function xiaohongshuCard(data) {
|
|||||||
body = '',
|
body = '',
|
||||||
subtitle = '',
|
subtitle = '',
|
||||||
tag = '',
|
tag = '',
|
||||||
quote = '',
|
|
||||||
footer = '',
|
|
||||||
layout = 'default',
|
layout = 'default',
|
||||||
pageNum = 1,
|
pageNum = 1,
|
||||||
totalPages = 1,
|
totalPages = 1,
|
||||||
|
cssVars = {},
|
||||||
} = data
|
} = data
|
||||||
|
|
||||||
const C = STYLES.colors
|
|
||||||
const F = STYLES.fonts
|
|
||||||
const T = STYLES.typography
|
|
||||||
|
|
||||||
// 正文按换行分段
|
|
||||||
const bodyLines = body.split('\n').filter(Boolean)
|
const bodyLines = body.split('\n').filter(Boolean)
|
||||||
|
|
||||||
|
// 渲染正文:支持列表、小标题、加粗
|
||||||
const bodyHtml = bodyLines.map(line => {
|
const bodyHtml = bodyLines.map(line => {
|
||||||
// 检测是否以 - 开头(列表项)
|
const trimmed = line.trimStart()
|
||||||
if (line.trimStart().startsWith('- ')) {
|
if (trimmed.startsWith('- ') || trimmed.startsWith('• ')) {
|
||||||
return `<li>${line.trimStart().slice(2)}</li>`
|
const text = trimmed.replace(/^[-•]\s*/, '')
|
||||||
|
return `<li>${text}</li>`
|
||||||
}
|
}
|
||||||
// 检测是否以 # 开头(小标题)
|
if (trimmed.startsWith('## ')) {
|
||||||
if (line.trimStart().startsWith('#')) {
|
return `<h3 class="sub-heading">${trimmed.slice(3)}</h3>`
|
||||||
return `<h3 class="sub-heading">${line.trimStart().replace(/^#+\s*/, '')}</h3>`
|
|
||||||
}
|
}
|
||||||
return `<p>${line}</p>`
|
// 内联加粗 **text**
|
||||||
|
const formatted = trimmed.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
||||||
|
return `<p>${formatted}</p>`
|
||||||
}).join('\n')
|
}).join('\n')
|
||||||
|
|
||||||
const hasList = bodyLines.some(l => l.trimStart().startsWith('- '))
|
const hasList = bodyLines.some(l => {
|
||||||
|
const t = l.trimStart()
|
||||||
|
return t.startsWith('- ') || t.startsWith('• ')
|
||||||
|
})
|
||||||
|
|
||||||
|
const C = {
|
||||||
|
bg: 'var(--bg, #faf8f5)',
|
||||||
|
primary: 'var(--primary, #1a1a2e)',
|
||||||
|
accent: 'var(--accent, #4f8cff)',
|
||||||
|
highlight: 'var(--highlight, #00d4ff)',
|
||||||
|
gold: 'var(--gold, #c9a96e)',
|
||||||
|
warm: 'var(--warm, #f5e6c8)',
|
||||||
|
text: 'var(--text, #1a1a2e)',
|
||||||
|
textMuted: 'var(--textMuted, #6b7280)',
|
||||||
|
cardBg: 'var(--cardBg, #ffffff)',
|
||||||
|
border: 'var(--border, #e5e7eb)',
|
||||||
|
}
|
||||||
|
|
||||||
return `<!DOCTYPE html>
|
return `<!DOCTYPE html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
@ -68,219 +82,263 @@ export function xiaohongshuCard(data) {
|
|||||||
<style>
|
<style>
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
* { 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');
|
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;600;700;900&family=Noto+Sans+SC:wght@300;400;500;700;900&display=swap');
|
||||||
|
|
||||||
|
${cssVarBlock(cssVars)}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
width: ${W}px;
|
width: ${W}px;
|
||||||
height: ${H}px;
|
height: ${H}px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: ${C.bg};
|
background: ${C.bg};
|
||||||
font-family: ${F.body};
|
font-family: 'Noto Sans SC', 'PingFang SC', -apple-system, sans-serif;
|
||||||
display: flex;
|
color: ${C.text};
|
||||||
flex-direction: column;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── 装饰角标 ── */
|
/* ── 装饰 · 几何光斑 ── */
|
||||||
.corner-tl, .corner-br {
|
.orb {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 60px;
|
border-radius: 50%;
|
||||||
height: 60px;
|
filter: blur(80px);
|
||||||
border-color: ${C.accent};
|
|
||||||
opacity: 0.15;
|
opacity: 0.15;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.corner-tl {
|
.orb-1 {
|
||||||
top: 24px; left: 24px;
|
width: 400px;
|
||||||
border-top: 2px solid;
|
height: 400px;
|
||||||
border-left: 2px solid;
|
background: ${C.accent};
|
||||||
|
top: -80px;
|
||||||
|
left: -120px;
|
||||||
}
|
}
|
||||||
.corner-br {
|
.orb-2 {
|
||||||
bottom: 24px; right: 24px;
|
width: 300px;
|
||||||
border-bottom: 2px solid;
|
height: 300px;
|
||||||
border-right: 2px solid;
|
background: ${C.highlight};
|
||||||
|
bottom: 100px;
|
||||||
|
right: -100px;
|
||||||
|
opacity: 0.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── 主容器 ── */
|
||||||
.container {
|
.container {
|
||||||
flex: 1;
|
position: relative;
|
||||||
padding: ${STYLES.spacing.paddingY}px ${STYLES.spacing.paddingX}px;
|
z-index: 1;
|
||||||
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
padding: 80px 72px 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 顶部渐变装饰线 ── */
|
||||||
|
.accent-line {
|
||||||
|
width: 80px;
|
||||||
|
height: 5px;
|
||||||
|
background: linear-gradient(90deg, ${C.accent}, ${C.highlight});
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── 标签 ── */
|
/* ── 标签 ── */
|
||||||
.tag {
|
.tag {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 8px 20px;
|
padding: 10px 24px;
|
||||||
background: ${C.accent};
|
background: ${C.cardBg};
|
||||||
color: ${C.textLight};
|
color: ${C.accent};
|
||||||
font-size: ${T.captionSize}px;
|
font-size: 18px;
|
||||||
border-radius: 20px;
|
font-weight: 700;
|
||||||
margin-bottom: 24px;
|
border-radius: 12px;
|
||||||
|
margin-bottom: 28px;
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
letter-spacing: 2px;
|
letter-spacing: 3px;
|
||||||
|
border: 2px solid ${C.accent}22;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── 标题 ── */
|
/* ── 大标题 · 2026审美核心 ── */
|
||||||
.title {
|
.title {
|
||||||
font-family: ${F.title};
|
font-family: 'Noto Serif SC', 'Source Han Serif SC', serif;
|
||||||
font-size: ${T.titleSize}px;
|
font-size: 68px;
|
||||||
font-weight: 700;
|
font-weight: 900;
|
||||||
line-height: 1.35;
|
line-height: 1.25;
|
||||||
color: ${C.primary};
|
color: ${C.primary};
|
||||||
margin-bottom: ${subtitle ? 16 : 32}px;
|
margin-bottom: 20px;
|
||||||
letter-spacing: 2px;
|
letter-spacing: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subtitle {
|
.subtitle {
|
||||||
font-size: ${T.subtitleSize}px;
|
font-size: 28px;
|
||||||
font-weight: 300;
|
font-weight: 400;
|
||||||
color: ${C.textMuted};
|
color: ${C.textMuted};
|
||||||
margin-bottom: 32px;
|
margin-bottom: 40px;
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
|
line-height: 1.5;
|
||||||
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── 正文 ── */
|
/* ── 正文 ── */
|
||||||
.body {
|
.body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-size: ${T.bodySize}px;
|
font-size: 26px;
|
||||||
line-height: ${T.lineHeight};
|
line-height: 1.8;
|
||||||
color: ${C.text};
|
color: ${C.text};
|
||||||
}
|
}
|
||||||
|
|
||||||
.body p {
|
.body p {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.body ul, .body ol {
|
.body ul, .body ol {
|
||||||
padding-left: 32px;
|
padding-left: 0;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 14px;
|
||||||
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.body li {
|
.body li {
|
||||||
font-size: ${T.bodySize}px;
|
font-size: 26px;
|
||||||
line-height: ${T.lineHeight};
|
line-height: 1.8;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 10px;
|
||||||
color: ${C.text};
|
color: ${C.text};
|
||||||
list-style: none;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 24px;
|
padding-left: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.body li::before {
|
.body li::before {
|
||||||
content: "◆";
|
content: "▸";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
color: ${C.highlight};
|
color: ${C.accent};
|
||||||
font-size: 14px;
|
font-size: 18px;
|
||||||
top: 6px;
|
top: 4px;
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-heading {
|
.sub-heading {
|
||||||
font-family: ${F.title};
|
font-family: 'Noto Serif SC', serif;
|
||||||
font-size: ${T.subtitleSize}px;
|
font-size: 32px;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
color: ${C.accent};
|
color: ${C.accent};
|
||||||
margin: 24px 0 12px 0;
|
margin: 32px 0 16px 0;
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.body strong {
|
||||||
|
color: ${C.highlight};
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── 金句布局 ── */
|
/* ── 金句布局 ── */
|
||||||
.quote-section {
|
.quote-wrapper {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 0 40px;
|
padding: 0 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quote-mark {
|
.quote-icon {
|
||||||
font-size: 80px;
|
font-size: 100px;
|
||||||
font-family: ${F.title};
|
|
||||||
color: ${C.highlight};
|
|
||||||
opacity: 0.3;
|
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
opacity: 0.2;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quote-text {
|
.quote-text {
|
||||||
font-family: ${F.title};
|
font-family: 'Noto Serif SC', serif;
|
||||||
font-size: 40px;
|
font-size: 52px;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: ${C.primary};
|
color: ${C.primary};
|
||||||
|
letter-spacing: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote-author {
|
||||||
|
margin-top: 40px;
|
||||||
|
font-size: 24px;
|
||||||
|
color: ${C.textMuted};
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Hero 封面布局 ── */
|
||||||
|
.hero-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-family: 'Noto Serif SC', serif;
|
||||||
|
font-size: 80px;
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: ${C.primary};
|
||||||
letter-spacing: 3px;
|
letter-spacing: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quote-author {
|
.hero-subtitle {
|
||||||
margin-top: 32px;
|
font-size: 30px;
|
||||||
font-size: ${T.smallSize}px;
|
|
||||||
color: ${C.textMuted};
|
color: ${C.textMuted};
|
||||||
|
margin-top: 24px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── 极简布局 ── */
|
.hero-decorator {
|
||||||
.minimal-title {
|
width: 120px;
|
||||||
font-family: ${F.title};
|
height: 4px;
|
||||||
font-size: 48px;
|
background: linear-gradient(90deg, ${C.accent}, ${C.highlight});
|
||||||
font-weight: 300;
|
margin: 32px 0;
|
||||||
line-height: 1.4;
|
border-radius: 2px;
|
||||||
color: ${C.primary};
|
|
||||||
letter-spacing: 4px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.minimal-divider {
|
|
||||||
width: 60px;
|
|
||||||
height: 1px;
|
|
||||||
background: ${C.gold};
|
|
||||||
margin: 32px auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── 底部 ── */
|
/* ── 底部 ── */
|
||||||
.footer {
|
.footer {
|
||||||
padding: 24px ${STYLES.spacing.paddingX}px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-top: 1px solid ${C.divider};
|
padding-top: 24px;
|
||||||
font-size: ${T.captionSize}px;
|
font-size: 16px;
|
||||||
color: ${C.textMuted};
|
color: ${C.textMuted};
|
||||||
|
opacity: 0.5;
|
||||||
|
letter-spacing: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand {
|
.footer-dot {
|
||||||
opacity: ${STYLES.brand.opacity};
|
display: inline-block;
|
||||||
}
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
.pagination {
|
background: ${C.accent};
|
||||||
font-size: ${T.captionSize}px;
|
border-radius: 50%;
|
||||||
color: ${C.textMuted};
|
margin: 0 8px;
|
||||||
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="corner-tl"></div>
|
<div class="orb orb-1"></div>
|
||||||
<div class="corner-br"></div>
|
<div class="orb orb-2"></div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
${tag ? `<div class="tag">${tag}</div>` : ''}
|
${tag ? `<div class="tag">${tag.toUpperCase()}</div>` : ''}
|
||||||
|
|
||||||
${layout === 'quote' ? `
|
${layout === 'quote' ? `
|
||||||
<div class="quote-section">
|
<div class="quote-wrapper">
|
||||||
<div class="quote-mark">"</div>
|
<div class="quote-icon">"</div>
|
||||||
<div class="quote-text">${quote || title}</div>
|
<div class="quote-text">${title}</div>
|
||||||
${subtitle ? `<div class="quote-author">${subtitle}</div>` : ''}
|
${subtitle ? `<div class="quote-author">—— ${subtitle}</div>` : ''}
|
||||||
</div>
|
</div>
|
||||||
` : layout === 'minimal' ? `
|
` : layout === 'hero' ? `
|
||||||
<div style="flex:1;display:flex;flex-direction:column;justify-content:center;align-items:center;">
|
<div class="hero-wrapper">
|
||||||
<div class="minimal-title">${title}</div>
|
<div class="hero-title">${title}</div>
|
||||||
<div class="minimal-divider"></div>
|
<div class="hero-decorator"></div>
|
||||||
${body ? `<div style="font-size:${T.bodySize}px;color:${C.textMuted};text-align:center;max-width:80%;">${body}</div>` : ''}
|
${subtitle ? `<div class="hero-subtitle">${subtitle}</div>` : ''}
|
||||||
</div>
|
</div>
|
||||||
` : `
|
` : `
|
||||||
|
<div class="accent-line"></div>
|
||||||
<div class="title">${title}</div>
|
<div class="title">${title}</div>
|
||||||
${subtitle ? `<div class="subtitle">${subtitle}</div>` : ''}
|
${subtitle ? `<div class="subtitle">${subtitle}</div>` : ''}
|
||||||
<div class="body">
|
<div class="body">
|
||||||
@ -288,11 +346,11 @@ export function xiaohongshuCard(data) {
|
|||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
|
|
||||||
</div>
|
<div class="footer">
|
||||||
|
<span>光湖 · 封面工作室</span>
|
||||||
|
${totalPages > 1 ? `<span>${pageNum}<span class="footer-dot"></span>${totalPages}</span>` : ''}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
<span class="brand">${STYLES.brand.text}</span>
|
|
||||||
${totalPages > 1 ? `<span class="pagination">${pageNum} / ${totalPages}</span>` : ''}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
@ -302,9 +360,6 @@ export function xiaohongshuCard(data) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成小红书轮播(多页)
|
* 生成小红书轮播(多页)
|
||||||
* @param {object} data - 同 xiaohongshuCard
|
|
||||||
* @param {string[]} pages - 每页正文内容数组
|
|
||||||
* @returns {string[]} HTML 数组
|
|
||||||
*/
|
*/
|
||||||
export function xiaohongshuCarousel(data, pages) {
|
export function xiaohongshuCarousel(data, pages) {
|
||||||
return pages.map((body, i) => xiaohongshuCard({
|
return pages.map((body, i) => xiaohongshuCard({
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user