193 lines
4.6 KiB
JavaScript
193 lines
4.6 KiB
JavaScript
|
|
/**
|
|||
|
|
* ═══════════════════════════════════════════════════
|
|||
|
|
* 万能动态模板 · 自适应任何内容
|
|||
|
|
* ═══════════════════════════════════════════════════
|
|||
|
|
*
|
|||
|
|
* 不是固定类型。是根据冰朔的文字内容,现场设计。
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import { STYLES } from '../config.js'
|
|||
|
|
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 万能动态卡片
|
|||
|
|
* @param {object} data
|
|||
|
|
* @param {string} data.title - 提炼的标题
|
|||
|
|
* @param {string} data.body - 正文
|
|||
|
|
* @param {string[]} data.lines - 原文行数组
|
|||
|
|
* @param {string} data.type - 内容类型(analyzeText 的结果)
|
|||
|
|
*/
|
|||
|
|
export function dynamicCard(data) {
|
|||
|
|
const { title, body, lines = [], type = 'general' } = data
|
|||
|
|
const C = STYLES.colors
|
|||
|
|
const F = STYLES.fonts
|
|||
|
|
const T = STYLES.typography
|
|||
|
|
const W = 1080
|
|||
|
|
const H = 1080
|
|||
|
|
|
|||
|
|
// 动态计算字号:内容越多字越小
|
|||
|
|
const totalChars = (title + body).length
|
|||
|
|
const titleSize = totalChars < 20 ? 52 : totalChars < 50 ? 44 : 36
|
|||
|
|
const bodySize = totalChars < 50 ? 32 : totalChars < 150 ? 26 : 22
|
|||
|
|
|
|||
|
|
// 检测有没有列表
|
|||
|
|
const hasList = lines.some(l => /^[-•·*]\s/.test(l.trim()))
|
|||
|
|
// 检测是不是一句话
|
|||
|
|
const isSingleLine = lines.length <= 2 && totalChars < 40
|
|||
|
|
|
|||
|
|
/* ── 生成内容 HTML ── */
|
|||
|
|
let contentHtml = ''
|
|||
|
|
|
|||
|
|
if (isSingleLine) {
|
|||
|
|
/* 一句话 · 大字居中 */
|
|||
|
|
contentHtml = `
|
|||
|
|
<div class="single-line">${escapeHtml(title)}</div>
|
|||
|
|
`
|
|||
|
|
} else if (hasList) {
|
|||
|
|
/* 列表内容 */
|
|||
|
|
const items = lines
|
|||
|
|
.filter(l => /^[-•·*]\s/.test(l.trim()))
|
|||
|
|
.map(l => `<li>${escapeHtml(l.replace(/^[-•·*]\s*/, ''))}</li>`)
|
|||
|
|
.join('\n')
|
|||
|
|
contentHtml = `
|
|||
|
|
<div class="title" style="font-size:${titleSize}px">${escapeHtml(title)}</div>
|
|||
|
|
<ul class="dynamic-list">${items}</ul>
|
|||
|
|
`
|
|||
|
|
} else {
|
|||
|
|
/* 一般段落 */
|
|||
|
|
const paragraphs = lines
|
|||
|
|
.filter(Boolean)
|
|||
|
|
.map(l => `<p>${escapeHtml(l)}</p>`)
|
|||
|
|
.join('\n')
|
|||
|
|
contentHtml = `
|
|||
|
|
<div class="title" style="font-size:${titleSize}px">${escapeHtml(title)}</div>
|
|||
|
|
<div class="body-text" style="font-size:${bodySize}px">${paragraphs}</div>
|
|||
|
|
`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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;
|
|||
|
|
position: relative;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 动态背景装饰 */
|
|||
|
|
.bg-circle {
|
|||
|
|
position: absolute;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
opacity: 0.04;
|
|||
|
|
}
|
|||
|
|
.bg-c1 {
|
|||
|
|
width: 400px; height: 400px;
|
|||
|
|
background: ${C.accent};
|
|||
|
|
top: -100px; right: -100px;
|
|||
|
|
}
|
|||
|
|
.bg-c2 {
|
|||
|
|
width: 250px; height: 250px;
|
|||
|
|
background: ${C.highlight};
|
|||
|
|
bottom: -60px; left: -60px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.container {
|
|||
|
|
flex: 1;
|
|||
|
|
padding: 56px 60px;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
justify-content: center;
|
|||
|
|
position: relative;
|
|||
|
|
z-index: 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.single-line {
|
|||
|
|
font-family: ${F.title};
|
|||
|
|
font-size: ${titleSize}px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
line-height: 1.5;
|
|||
|
|
color: ${C.primary};
|
|||
|
|
text-align: center;
|
|||
|
|
letter-spacing: 3px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.title {
|
|||
|
|
font-family: ${F.title};
|
|||
|
|
font-weight: 700;
|
|||
|
|
line-height: 1.35;
|
|||
|
|
color: ${C.primary};
|
|||
|
|
margin-bottom: 24px;
|
|||
|
|
letter-spacing: 1px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.body-text {
|
|||
|
|
line-height: 1.7;
|
|||
|
|
color: ${C.text};
|
|||
|
|
}
|
|||
|
|
.body-text p {
|
|||
|
|
margin-bottom: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.dynamic-list {
|
|||
|
|
list-style: none;
|
|||
|
|
padding: 0;
|
|||
|
|
}
|
|||
|
|
.dynamic-list li {
|
|||
|
|
font-size: ${bodySize}px;
|
|||
|
|
line-height: 1.5;
|
|||
|
|
color: ${C.text};
|
|||
|
|
padding: 12px 20px;
|
|||
|
|
margin-bottom: 8px;
|
|||
|
|
background: ${C.warm}40;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
border-left: 3px solid ${C.accent};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 底部品牌 */
|
|||
|
|
.brand-bar {
|
|||
|
|
position: absolute;
|
|||
|
|
bottom: 24px;
|
|||
|
|
left: 0;
|
|||
|
|
right: 0;
|
|||
|
|
text-align: center;
|
|||
|
|
font-size: 14px;
|
|||
|
|
color: ${C.textMuted};
|
|||
|
|
opacity: ${STYLES.brand.opacity};
|
|||
|
|
z-index: 1;
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
|
|||
|
|
<div class="bg-circle bg-c1"></div>
|
|||
|
|
<div class="bg-circle bg-c2"></div>
|
|||
|
|
|
|||
|
|
<div class="container">
|
|||
|
|
${contentHtml}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="brand-bar">${STYLES.brand.text}</div>
|
|||
|
|
|
|||
|
|
</body>
|
|||
|
|
</html>`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
function escapeHtml(str) {
|
|||
|
|
return String(str)
|
|||
|
|
.replace(/&/g, '&')
|
|||
|
|
.replace(/</g, '<')
|
|||
|
|
.replace(/>/g, '>')
|
|||
|
|
.replace(/"/g, '"')
|
|||
|
|
}
|