feat(web): 文章编辑器 — iPhone 15 Pro Max 高保真预览 + 丰富样式模板

- 手机预览按 iPhone 15 Pro Max 真实比例 (2.084:1) 重设计
- 钛金属渐变边框 + Dynamic Island + 前置摄像头 + 侧边按钮
- 背面摄像头模组微阴影暗示 + 多层投影深度效果
- iOS 17 风格状态栏 (信号/WiFi/电池 SVG 图标)
- 样式模板从 14 种扩展到 27 种
- 新增: 成功/危险/强调提示框、时间线、步骤流程、对比卡片、问答、进度条、引言卡片等
This commit is contained in:
iven
2026-05-11 02:41:30 +08:00
parent f4b09858c4
commit c716cc0f7b
2 changed files with 690 additions and 164 deletions

View File

@@ -10,9 +10,16 @@ interface ArticlePhonePreviewProps {
} }
/** /**
* 手机端实时预览组件 * iPhone 15 Pro Max 高保真仿真预览
* 模拟微信小程序文章阅读效果,匹配小程序 article/detail 页面样式。 *
* 预览内容始终亮色(不跟暗黑模式),外框背景跟随主题。 * 真实参数比例:
* 物理尺寸 159.9mm × 76.7mm (aspect 2.084)
* Natural Titanium 钛金属直边边框
* Dynamic Island: 药丸形 + 前置摄像头 + 接近传感器
* 侧边按钮: 音量×2 / Action Button / 电源
* Home Indicator: 底部圆角横条
*
* 内容始终亮色(不跟暗黑模式),外框背景跟随主题。
*/ */
export default function ArticlePhonePreview({ export default function ArticlePhonePreview({
title, title,
@@ -22,7 +29,6 @@ export default function ArticlePhonePreview({
coverImage, coverImage,
isDark, isDark,
}: ArticlePhonePreviewProps) { }: ArticlePhonePreviewProps) {
// 小程序 design tokens
const mpColors = useMemo( const mpColors = useMemo(
() => ({ () => ({
bg: '#F5F0EB', bg: '#F5F0EB',
@@ -37,152 +43,496 @@ export default function ArticlePhonePreview({
const today = useMemo(() => new Date().toLocaleDateString('zh-CN'), []); const today = useMemo(() => new Date().toLocaleDateString('zh-CN'), []);
// ── iPhone 15 Pro Max 缩放参数 ──
// 物理: 159.9 × 76.7 mm → 比例 2.084:1
const PHONE_W = 256;
const PHONE_H = Math.round(PHONE_W * 2.084); // 533
const PHONE_R = 50;
const BEZEL = 4; // 边框到屏幕的距离
const SCREEN_R = PHONE_R - BEZEL; // 46
// Dynamic Island (物理约 25.4mm × 8.8mm)
const ISLAND_W = 82;
const ISLAND_H = 25;
const ISLAND_R = 12.5;
const ISLAND_TOP = 14;
// 前置摄像头
const CAM_SIZE = 7;
const CAM_RIGHT = 16;
// 接近传感器
const SENSOR_SIZE = 3;
const SENSOR_LEFT = 16;
// 状态栏
const STATUS_H = 50;
// Home Indicator
const HOME_W = 84;
const HOME_H = 4;
const HOME_BOTTOM = 10;
// 侧边按钮突出距离
const BTN_OUT = 2.5;
return ( return (
<div <div
style={{ style={{
width: 400, width: 400,
flexShrink: 0, flexShrink: 0,
background: isDark ? '#0f172a' : '#f0f2f5', background: isDark ? '#0f172a' : '#e8e8ed',
borderLeft: `1px solid ${isDark ? '#1e293b' : '#e5e5ea'}`, borderLeft: `1px solid ${isDark ? '#1e293b' : '#d2d2d7'}`,
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
padding: '16px 0', padding: '12px 0',
overflow: 'auto', overflow: 'auto',
}} }}
> >
{/* 标签 */}
<div <div
style={{ style={{
fontSize: 12, fontSize: 11,
color: isDark ? '#64748b' : '#86868b', color: isDark ? '#64748b' : '#86868b',
marginBottom: 12, marginBottom: 10,
fontWeight: 500, fontWeight: 500,
letterSpacing: 0.3,
}} }}
> >
📱 · · iPhone 15 Pro Max
</div> </div>
{/* iPhone 外壳 */} {/* ══════ iPhone 15 Pro Max 外壳 ══════ */}
{/* 外层: 钛金属渐变边框效果 */}
<div <div
style={{ style={{
width: 375, width: PHONE_W + BEZEL * 2,
height: 680, height: PHONE_H + BEZEL * 2,
borderRadius: 40, borderRadius: PHONE_R + BEZEL,
border: '4px solid #1a1a1a', background:
background: '#000', 'linear-gradient(145deg, #A0A0A4 0%, #7A7A7E 25%, #929296 50%, #6E6E72 75%, #88888C 100%)',
padding: 0,
position: 'relative', position: 'relative',
overflow: 'hidden', boxShadow: [
boxShadow: '0 8px 40px rgba(0,0,0,0.15)', // 主阴影 - 桌面投影
'0 1px 2px rgba(0,0,0,0.08)',
'0 4px 12px rgba(0,0,0,0.10)',
'0 12px 32px rgba(0,0,0,0.12)',
'0 24px 64px rgba(0,0,0,0.08)',
// 钛金属高光 - 顶部边缘
'inset 0 1px 0 rgba(255,255,255,0.25)',
'inset 1px 0 0 rgba(255,255,255,0.10)',
// 钛金属暗部 - 底部边缘
'inset 0 -1px 0 rgba(0,0,0,0.20)',
'inset -1px 0 0 rgba(0,0,0,0.10)',
].join(', '),
}} }}
> >
{/* 灵动岛 */} {/* ── 侧边按钮 ── */}
{/* 音量+ (左侧) */}
<div <div
style={{ style={{
position: 'absolute', position: 'absolute',
top: 8, left: -BTN_OUT,
left: '50%', top: 115,
transform: 'translateX(-50%)', width: BTN_OUT + 1,
width: 120,
height: 28, height: 28,
background: '#000', background:
borderRadius: 14, 'linear-gradient(180deg, #A0A0A4, #78787C, #88888C)',
zIndex: 20, borderRadius: '2px 0 0 2px',
zIndex: 5,
}}
/>
{/* 音量- (左侧) */}
<div
style={{
position: 'absolute',
left: -BTN_OUT,
top: 150,
width: BTN_OUT + 1,
height: 28,
background:
'linear-gradient(180deg, #A0A0A4, #78787C, #88888C)',
borderRadius: '2px 0 0 2px',
zIndex: 5,
}}
/>
{/* Action Button (左侧) */}
<div
style={{
position: 'absolute',
left: -BTN_OUT - 1,
top: 188,
width: 11,
height: 11,
background:
'radial-gradient(circle at 40% 40%, #A8A8AC, #78787C)',
borderRadius: '50%',
zIndex: 5,
}}
/>
{/* 电源键 (右侧) */}
<div
style={{
position: 'absolute',
right: -BTN_OUT,
top: 145,
width: BTN_OUT + 1,
height: 38,
background:
'linear-gradient(180deg, #A0A0A4, #78787C, #88888C)',
borderRadius: '0 2px 2px 0',
zIndex: 5,
}} }}
/> />
{/* 状态栏 */} {/* ── 屏幕容器 (黑边 + 圆角) ── */}
<div
style={{
position: 'absolute',
top: BEZEL,
left: BEZEL,
right: BEZEL,
bottom: BEZEL,
borderRadius: SCREEN_R,
background: '#000',
overflow: 'hidden',
}}
>
{/* ── Dynamic Island ── */}
<div
style={{
position: 'absolute',
top: ISLAND_TOP,
left: '50%',
transform: 'translateX(-50%)',
width: ISLAND_W,
height: ISLAND_H,
background: '#000',
borderRadius: ISLAND_R,
zIndex: 30,
// Dynamic Island 微妙的凹陷感
boxShadow: 'inset 0 0.5px 1px rgba(0,0,0,0.5)',
}}
>
{/* 接近传感器 (左侧小点) */}
<div
style={{
position: 'absolute',
left: SENSOR_LEFT,
top: '50%',
transform: 'translateY(-50%)',
width: SENSOR_SIZE,
height: SENSOR_SIZE,
borderRadius: '50%',
background: '#1a1a2e',
boxShadow:
'inset 0 0 1px rgba(80,80,120,0.4)',
}}
/>
{/* 前置摄像头 (右侧) */}
<div
style={{
position: 'absolute',
right: CAM_RIGHT,
top: '50%',
transform: 'translateY(-50%)',
width: CAM_SIZE,
height: CAM_SIZE,
borderRadius: '50%',
// 摄像头镜头效果: 外环 + 内部蓝紫反光
background:
'radial-gradient(circle at 35% 35%, #3a3a5c, #1a1a2e 50%, #0d0d1a)',
boxShadow: [
'0 0 0 1px rgba(60,60,80,0.5)',
'inset 0 0 2px rgba(100,100,180,0.3)',
'0 0 1px rgba(120,120,200,0.2)',
].join(', '),
}}
/>
</div>
{/* ── 状态栏 (iOS 17 风格) ── */}
<div <div
style={{ style={{
position: 'absolute', position: 'absolute',
top: 0, top: 0,
left: 0, left: 0,
right: 0, right: 0,
height: 48, height: STATUS_H,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
padding: '0 28px', padding: '0 26px',
fontSize: 13, fontSize: 11.5,
fontWeight: 600, fontWeight: 600,
color: '#fff', color: '#fff',
zIndex: 10, zIndex: 20,
fontFamily:
'-apple-system, "SF Pro Text", "Helvetica Neue", sans-serif',
}} }}
> >
<span>9:41</span> {/* 左侧: 时间 */}
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}> <span style={{ width: 42, letterSpacing: 0.2 }}>9:41</span>
<svg width="14" height="10" viewBox="0 0 16 12" fill="none">
<path d="M8 11.5a1 1 0 100-2 1 1 0 000 2z" fill="#fff" /> {/* 右侧: 信号 + WiFi + 电池 */}
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 5,
width: 78,
justifyContent: 'flex-end',
}}
>
{/* 蜂窝信号 (4格) */}
<svg
width="15"
height="11"
viewBox="0 0 17 12"
fill="none"
>
<rect
x="0"
y="9"
width="3"
height="3"
rx="0.6"
fill="#fff"
/>
<rect
x="4.5"
y="6"
width="3"
height="6"
rx="0.6"
fill="#fff"
/>
<rect
x="9"
y="3"
width="3"
height="9"
rx="0.6"
fill="#fff"
/>
<rect
x="13.5"
y="0"
width="3"
height="12"
rx="0.6"
fill="#fff"
/>
</svg>
{/* WiFi */}
<svg
width="14"
height="10"
viewBox="0 0 20 15"
fill="#fff"
>
<circle cx="10" cy="13" r="1.4" />
<path <path
d="M3 7.5a7 7 0 0110 0" d="M6 10.5a5.5 5.5 0 018 0"
stroke="#fff" stroke="#fff"
strokeWidth="1.3" strokeWidth="1.6"
fill="none"
strokeLinecap="round"
/>
<path
d="M2.5 7a10 10 0 0115 0"
stroke="#fff"
strokeWidth="1.6"
fill="none" fill="none"
strokeLinecap="round" strokeLinecap="round"
/> />
</svg> </svg>
<div
style={{ {/* 电池 */}
width: 20, <svg
height: 9, width="24"
border: '1px solid #fff', height="11"
borderRadius: 2, viewBox="0 0 27 13"
padding: 1, fill="none"
}}
> >
<div {/* 电池外壳 */}
style={{ <rect
width: '80%', x="0.5"
height: '100%', y="0.5"
background: '#fff', width="22"
borderRadius: 1, height="12"
}} rx="2.5"
stroke="rgba(255,255,255,0.55)"
strokeWidth="1"
/> />
</div> {/* 电量 */}
<rect
x="2"
y="2"
width="17"
height="9"
rx="1.2"
fill="#fff"
/>
{/* 电池头 */}
<rect
x="23.5"
y="3.5"
width="2.5"
height="6"
rx="1"
fill="rgba(255,255,255,0.35)"
/>
</svg>
</div> </div>
</div> </div>
{/* 内容区 */} {/* ── 内容区域 ── */}
<div <div
style={{ style={{
position: 'absolute', position: 'absolute',
top: 48, top: STATUS_H - 4,
left: 0, left: 0,
right: 0, right: 0,
bottom: 24, bottom: HOME_BOTTOM + HOME_H + 8,
overflowY: 'auto', overflowY: 'auto',
overflowX: 'hidden',
background: mpColors.bg, background: mpColors.bg,
borderRadius: '0 0 36px 36px', borderRadius: `0 0 ${SCREEN_R - 4}px ${SCREEN_R - 4}px`,
WebkitOverflowScrolling: 'touch',
}} }}
> >
{/* 作用域样式 — 匹配小程序 article/detail/index.scss */} {/* 作用域样式 — 匹配小程序 article/detail */}
<style>{` <style>{`
.mp-preview { background: ${mpColors.bg}; padding-bottom: 32px; } .mp-preview {
.mp-preview .mp-header { background: ${mpColors.card}; padding: 24px 20px; margin-bottom: 2px; } background: ${mpColors.bg};
.mp-preview .mp-title { font-size: 22px; font-weight: 700; color: ${mpColors.text}; line-height: 1.4; margin-bottom: 12px; display: block; } padding-bottom: 28px;
.mp-preview .mp-meta { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; } min-height: 100%;
.mp-preview .mp-category { font-size: 13px; color: ${mpColors.primary}; background: ${mpColors.primaryLight}; padding: 3px 10px; border-radius: 12px; } }
.mp-preview .mp-author { font-size: 13px; color: ${mpColors.textSecondary}; } .mp-preview .mp-header {
.mp-preview .mp-date { font-size: 12px; color: #9ca3af; } background: ${mpColors.card};
.mp-preview .mp-cover { width: 100%; border-radius: 8px; margin: 0 0 12px; max-height: 160px; object-fit: cover; } padding: 18px 14px;
.mp-preview .mp-summary { background: ${mpColors.card}; padding: 18px 20px; margin-bottom: 2px; } margin-bottom: 1px;
.mp-preview .mp-summary-text { font-size: 14px; color: ${mpColors.textSecondary}; line-height: 1.7; border-left: 3px solid ${mpColors.primary}; padding-left: 12px; } }
.mp-preview .mp-content { background: ${mpColors.card}; padding: 20px; } .mp-preview .mp-title {
.mp-preview .mp-content p { font-size: 16px; color: ${mpColors.text}; line-height: 1.8; margin-bottom: 14px; } font-size: 17px;
.mp-preview .mp-content h1, .mp-preview .mp-content h2, .mp-preview .mp-content h3 { font-weight: 700; color: ${mpColors.text}; margin: 20px 0 10px; } font-weight: 700;
.mp-preview .mp-content h1 { font-size: 20px; } color: ${mpColors.text};
.mp-preview .mp-content h2 { font-size: 18px; } line-height: 1.45;
.mp-preview .mp-content h3 { font-size: 16px; } margin-bottom: 8px;
.mp-preview .mp-content img { max-width: 100%; border-radius: 8px; margin: 10px 0; } display: block;
.mp-preview .mp-content blockquote { border-left: 3px solid ${mpColors.primary}; padding: 8px 12px; color: ${mpColors.textSecondary}; margin: 12px 0; } }
.mp-preview .mp-content ul, .mp-preview .mp-content ol { padding-left: 20px; margin: 12px 0; font-size: 15px; line-height: 2; color: ${mpColors.text}; } .mp-preview .mp-meta {
.mp-preview .mp-content table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 14px; } display: flex;
.mp-preview .mp-content td, .mp-preview .mp-content th { border: 1px solid #e5e7eb; padding: 8px 10px; } align-items: center;
.mp-preview .mp-content hr { border: none; border-top: 1px dashed #d1d5db; margin: 16px 0; } gap: 6px;
.mp-preview .mp-empty { padding: 60px 20px; text-align: center; color: #9ca3af; font-size: 14px; } flex-wrap: wrap;
}
.mp-preview .mp-category {
font-size: 10px;
color: ${mpColors.primary};
background: ${mpColors.primaryLight};
padding: 2px 7px;
border-radius: 8px;
}
.mp-preview .mp-author {
font-size: 10px;
color: ${mpColors.textSecondary};
}
.mp-preview .mp-date {
font-size: 10px;
color: #9ca3af;
}
.mp-preview .mp-cover {
width: 100%;
border-radius: 6px;
margin: 0 0 8px;
max-height: 110px;
object-fit: cover;
}
.mp-preview .mp-summary {
background: ${mpColors.card};
padding: 12px 14px;
margin-bottom: 1px;
}
.mp-preview .mp-summary-text {
font-size: 12px;
color: ${mpColors.textSecondary};
line-height: 1.7;
border-left: 2.5px solid ${mpColors.primary};
padding-left: 10px;
}
.mp-preview .mp-content {
background: ${mpColors.card};
padding: 14px;
}
.mp-preview .mp-content p {
font-size: 13px;
color: ${mpColors.text};
line-height: 1.85;
margin-bottom: 10px;
}
.mp-preview .mp-content h1,
.mp-preview .mp-content h2,
.mp-preview .mp-content h3 {
font-weight: 700;
color: ${mpColors.text};
margin: 14px 0 8px;
}
.mp-preview .mp-content h1 { font-size: 16px; }
.mp-preview .mp-content h2 { font-size: 14px; }
.mp-preview .mp-content h3 { font-size: 13px; }
.mp-preview .mp-content img {
max-width: 100%;
border-radius: 6px;
margin: 8px 0;
}
.mp-preview .mp-content blockquote {
border-left: 2.5px solid ${mpColors.primary};
padding: 5px 10px;
color: ${mpColors.textSecondary};
margin: 10px 0;
}
.mp-preview .mp-content ul,
.mp-preview .mp-content ol {
padding-left: 16px;
margin: 8px 0;
font-size: 13px;
line-height: 1.9;
color: ${mpColors.text};
}
.mp-preview .mp-content table {
width: 100%;
border-collapse: collapse;
margin: 8px 0;
font-size: 12px;
}
.mp-preview .mp-content td,
.mp-preview .mp-content th {
border: 1px solid #e5e7eb;
padding: 5px 7px;
}
.mp-preview .mp-content hr {
border: none;
border-top: 1px dashed #d1d5db;
margin: 12px 0;
}
.mp-preview .mp-empty {
padding: 40px 14px;
text-align: center;
color: #9ca3af;
font-size: 12px;
line-height: 1.8;
}
.mp-preview .mp-content div[data-w-e-type="styled-block"] {
max-width: 100%;
box-sizing: border-box;
}
`}</style> `}</style>
<div className="mp-preview"> <div className="mp-preview">
{/* 头部:标题+元信息 */}
<div className="mp-header"> <div className="mp-header">
{coverImage && ( {coverImage && (
<img <img
@@ -190,50 +540,116 @@ export default function ArticlePhonePreview({
src={coverImage} src={coverImage}
alt="封面" alt="封面"
onError={(e) => { onError={(e) => {
(e.target as HTMLImageElement).style.display = 'none'; (e.target as HTMLImageElement).style.display =
'none';
}} }}
/> />
)} )}
<span className="mp-title">{title || '文章标题'}</span> <span className="mp-title">
{title || '文章标题'}
</span>
<div className="mp-meta"> <div className="mp-meta">
{category && <span className="mp-category">{category}</span>} {category && (
<span className="mp-category">{category}</span>
)}
<span className="mp-author"></span> <span className="mp-author"></span>
<span className="mp-date">{today}</span> <span className="mp-date">{today}</span>
</div> </div>
</div> </div>
{/* 摘要 */}
{summary && ( {summary && (
<div className="mp-summary"> <div className="mp-summary">
<div className="mp-summary-text">{summary}</div> <div className="mp-summary-text">{summary}</div>
</div> </div>
)} )}
{/* 正文内容 */}
<div className="mp-content"> <div className="mp-content">
{content && content !== '<p><br></p>' ? ( {content && content !== '<p><br></p>' ? (
<div dangerouslySetInnerHTML={{ __html: content }} /> <div
dangerouslySetInnerHTML={{ __html: content }}
/>
) : ( ) : (
<div className="mp-empty">...</div> <div className="mp-empty">
<br />
</div>
)} )}
</div> </div>
</div> </div>
</div> </div>
{/* Home Indicator */} {/* ── Home Indicator ── */}
<div <div
style={{ style={{
position: 'absolute', position: 'absolute',
bottom: 6, bottom: HOME_BOTTOM,
left: '50%', left: '50%',
transform: 'translateX(-50%)', transform: 'translateX(-50%)',
width: 120, width: HOME_W,
height: 4, height: HOME_H,
background: 'rgba(255,255,255,0.3)', background: 'rgba(255,255,255,0.22)',
borderRadius: 999, borderRadius: 99,
zIndex: 20,
}}
/>
</div>
{/* ── 背面摄像头模组暗示 (左侧上方微阴影) ── */}
<div
style={{
position: 'absolute',
top: BEZEL + 8,
left: BEZEL + 8,
width: 68,
height: 68,
borderRadius: 16,
// 通过微妙的内阴影在正面也能感受到背面的摄像头凸起
boxShadow:
'inset 0 0 6px rgba(0,0,0,0.06), 0 0 3px rgba(0,0,0,0.04)',
zIndex: 0,
pointerEvents: 'none',
}}
>
{/* 三个镜头位置的微暗圆点 */}
<div
style={{
position: 'absolute',
top: 8,
left: 8,
width: 18,
height: 18,
borderRadius: '50%',
background:
'radial-gradient(circle, rgba(0,0,0,0.08), transparent)',
}}
/>
<div
style={{
position: 'absolute',
top: 8,
right: 10,
width: 18,
height: 18,
borderRadius: '50%',
background:
'radial-gradient(circle, rgba(0,0,0,0.08), transparent)',
}}
/>
<div
style={{
position: 'absolute',
bottom: 10,
left: 8,
width: 18,
height: 18,
borderRadius: '50%',
background:
'radial-gradient(circle, rgba(0,0,0,0.08), transparent)',
}} }}
/> />
</div> </div>
</div> </div>
</div>
); );
} }

View File

@@ -29,37 +29,59 @@ export const COLOR_THEMES: ColorTheme[] = [
const W = 'data-w-e-type="styled-block"'; const W = 'data-w-e-type="styled-block"';
// ═══════════════════════════════════════
// 标题样式 (6 种)
// ═══════════════════════════════════════
export const HEADING_TEMPLATES: StyleTemplate[] = [ export const HEADING_TEMPLATES: StyleTemplate[] = [
{ {
id: 'heading-classic', id: 'heading-classic',
name: '经典', name: '经典 · 左边框',
category: 'heading', category: 'heading',
preview: '▎左边框标题', preview: '▎左边框标题',
html: `<div ${W} style="border-left: 4px solid {{primary}}; padding-left: 12px; font-size: 20px; font-weight: 700; color: #1a1a1a; margin: 24px 0 12px;">标题文本</div>`, html: `<div ${W} style="border-left: 4px solid {{primary}}; padding-left: 12px; font-size: 20px; font-weight: 700; color: #1a1a1a; margin: 24px 0 12px;">标题文本</div>`,
}, },
{ {
id: 'heading-simple', id: 'heading-underline',
name: '简约', name: '简约 · 下划线',
category: 'heading', category: 'heading',
preview: '下划线标题 ──', preview: '标题 ──────',
html: `<div ${W} style="border-bottom: 2px solid {{primary}}; padding-bottom: 8px; font-size: 20px; font-weight: 700; color: #1a1a1a; margin: 24px 0 12px; display: inline-block;">标题文本</div>`, html: `<div ${W} style="border-bottom: 2px solid {{primary}}; padding-bottom: 8px; font-size: 20px; font-weight: 700; color: #1a1a1a; margin: 24px 0 12px; display: inline-block;">标题文本</div>`,
}, },
{ {
id: 'heading-rounded', id: 'heading-badge',
name: '圆标', name: '圆标 · 标签式',
category: 'heading', category: 'heading',
preview: '■ 标签式标题', preview: '■ 标签式标题',
html: `<div ${W} style="display: inline-block; background: {{primaryLight}}; color: {{primary}}; padding: 4px 14px; border-radius: 4px; font-size: 18px; font-weight: 700; margin: 24px 0 12px;">标题文本</div>`, html: `<div ${W} style="display: inline-block; background: {{primaryLight}}; color: {{primary}}; padding: 4px 14px; border-radius: 4px; font-size: 18px; font-weight: 700; margin: 24px 0 12px;">标题文本</div>`,
}, },
{ {
id: 'heading-centered', id: 'heading-centered',
name: '居中', name: '居中标题',
category: 'heading', category: 'heading',
preview: '居中标题', preview: '── 居中标题 ──',
html: `<div ${W} style="text-align: center; font-size: 20px; font-weight: 700; color: #1a1a1a; margin: 28px 0 12px;">标题文本</div>`, html: `<div ${W} style="text-align: center; font-size: 20px; font-weight: 700; color: #1a1a1a; margin: 28px 0 12px;">标题文本</div>`,
}, },
{
id: 'heading-double-line',
name: '双线 · 上下框',
category: 'heading',
preview: '───────\n标题\n───────',
html: `<div ${W} style="text-align: center; margin: 24px 0 12px; padding: 10px 0; border-top: 2px solid {{primary}}; border-bottom: 2px solid {{primary}}; font-size: 20px; font-weight: 700; color: #1a1a1a;">标题文本</div>`,
},
{
id: 'heading-accent-bar',
name: '色带 · 顶部色条',
category: 'heading',
preview: '━━━\n标题文本',
html: `<div ${W} style="margin: 24px 0 12px; padding: 12px 0 0; border-top: 4px solid {{primary}}; font-size: 20px; font-weight: 700; color: #1a1a1a;">标题文本</div>`,
},
]; ];
// ═══════════════════════════════════════
// 内容模板 (13 种)
// ═══════════════════════════════════════
export const CONTENT_TEMPLATES: StyleTemplate[] = [ export const CONTENT_TEMPLATES: StyleTemplate[] = [
{ {
id: 'blockquote', id: 'blockquote',
@@ -72,15 +94,36 @@ export const CONTENT_TEMPLATES: StyleTemplate[] = [
id: 'tip-warning', id: 'tip-warning',
name: '提示框 · 警告', name: '提示框 · 警告',
category: 'content', category: 'content',
preview: '⚠ 温馨提示', preview: '⚠ 注意事项',
html: `<div ${W} style="background: #fffbeb; border: 1px solid #fde68a; border-radius: 8px; padding: 14px 16px; margin: 16px 0; font-size: 15px; line-height: 1.8; color: #92400e;"><strong>⚠ 温馨提示</strong>请在此处编辑警告内容。</div>`, html: `<div ${W} style="background: #fffbeb; border: 1px solid #fde68a; border-radius: 8px; padding: 14px 16px; margin: 16px 0; font-size: 15px; line-height: 1.8; color: #92400e;"><strong>⚠ 注意事项</strong>请在此处编辑警告内容。</div>`,
}, },
{ {
id: 'tip-info', id: 'tip-info',
name: '提示框 · 信息', name: '提示框 · 信息',
category: 'content', category: 'content',
preview: ' 补充说明', preview: ' 补充说明',
html: `<div ${W} style="background: #eff6ff; border: 1px solid #bfdbfe; border-radius: 8px; padding: 14px 16px; margin: 16px 0; font-size: 15px; line-height: 1.8; color: #1e40af;"><strong> 补充说明:</strong>请在此处编辑信息内容。</div>`, html: `<div ${W} style="background: #eff6ff; border: 1px solid #bfdbfe; border-radius: 8px; padding: 14px 16px; margin: 16px 0; font-size: 15px; line-height: 1.8; color: #1e40af;"><strong> 补充说明:</strong>请在此处编辑信息内容。</div>`,
},
{
id: 'tip-success',
name: '提示框 · 正确',
category: 'content',
preview: '✓ 正确做法',
html: `<div ${W} style="background: #f0fdf4; border: 1px solid #bbf7d0; border-radius: 8px; padding: 14px 16px; margin: 16px 0; font-size: 15px; line-height: 1.8; color: #166534;"><strong>✓ 正确做法:</strong>请在此处编辑推荐内容。</div>`,
},
{
id: 'tip-danger',
name: '提示框 · 危险',
category: 'content',
preview: '✕ 严禁事项',
html: `<div ${W} style="background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; padding: 14px 16px; margin: 16px 0; font-size: 15px; line-height: 1.8; color: #991b1b;"><strong>✕ 严禁事项:</strong>请在此处编辑危险警告内容。</div>`,
},
{
id: 'tip-primary',
name: '提示框 · 强调',
category: 'content',
preview: '★ 重点强调',
html: `<div ${W} style="background: {{primaryLight}}; border: 1px solid {{primary}}; border-radius: 8px; padding: 14px 16px; margin: 16px 0; font-size: 15px; line-height: 1.8; color: #1a1a1a;"><strong style="color: {{primary}};">★ 重点强调:</strong>请在此处编辑重点内容。</div>`,
}, },
{ {
id: 'list-ordered', id: 'list-ordered',
@@ -110,19 +153,72 @@ export const CONTENT_TEMPLATES: StyleTemplate[] = [
preview: '血压 120/80', preview: '血压 120/80',
html: `<div ${W} style="display: flex; gap: 12px; margin: 16px 0; flex-wrap: wrap;"><div style="flex: 1; min-width: 120px; background: {{primaryLight}}; border-radius: 8px; padding: 14px; text-align: center;"><div style="font-size: 24px; font-weight: 700; color: {{primary}};">120/80</div><div style="font-size: 13px; color: #5a554f; margin-top: 4px;">血压 (mmHg)</div></div><div style="flex: 1; min-width: 120px; background: #f3f4f6; border-radius: 8px; padding: 14px; text-align: center;"><div style="font-size: 24px; font-weight: 700; color: #1a1a1a;">72</div><div style="font-size: 13px; color: #5a554f; margin-top: 4px;">心率 (bpm)</div></div></div>`, html: `<div ${W} style="display: flex; gap: 12px; margin: 16px 0; flex-wrap: wrap;"><div style="flex: 1; min-width: 120px; background: {{primaryLight}}; border-radius: 8px; padding: 14px; text-align: center;"><div style="font-size: 24px; font-weight: 700; color: {{primary}};">120/80</div><div style="font-size: 13px; color: #5a554f; margin-top: 4px;">血压 (mmHg)</div></div><div style="flex: 1; min-width: 120px; background: #f3f4f6; border-radius: 8px; padding: 14px; text-align: center;"><div style="font-size: 24px; font-weight: 700; color: #1a1a1a;">72</div><div style="font-size: 13px; color: #5a554f; margin-top: 4px;">心率 (bpm)</div></div></div>`,
}, },
{
id: 'timeline',
name: '时间线',
category: 'content',
preview: '●── 第一步\n●── 第二步',
html: `<div ${W} style="margin: 16px 0; padding-left: 20px; border-left: 2px solid {{primary}};"><div style="position: relative; padding: 0 0 20px 16px;"><div style="position: absolute; left: -27px; top: 2px; width: 12px; height: 12px; border-radius: 50%; background: {{primary}};"></div><div style="font-size: 14px; font-weight: 600; color: #1a1a1a; margin-bottom: 2px;">2024年1月</div><div style="font-size: 14px; color: #5a554f; line-height: 1.6;">第一阶段内容描述</div></div><div style="position: relative; padding: 0 0 20px 16px;"><div style="position: absolute; left: -27px; top: 2px; width: 12px; height: 12px; border-radius: 50%; background: {{primary}};"></div><div style="font-size: 14px; font-weight: 600; color: #1a1a1a; margin-bottom: 2px;">2024年3月</div><div style="font-size: 14px; color: #5a554f; line-height: 1.6;">第二阶段内容描述</div></div><div style="position: relative; padding: 0 0 0 16px;"><div style="position: absolute; left: -27px; top: 2px; width: 12px; height: 12px; border-radius: 50%; background: {{primary}};"></div><div style="font-size: 14px; font-weight: 600; color: #1a1a1a; margin-bottom: 2px;">2024年6月</div><div style="font-size: 14px; color: #5a554f; line-height: 1.6;">第三阶段内容描述</div></div></div>`,
},
{
id: 'steps',
name: '步骤流程',
category: 'content',
preview: '➊ → ➋ → ➌',
html: `<div ${W} style="margin: 16px 0;"><div style="display: flex; align-items: flex-start; margin-bottom: 16px;"><div style="width: 28px; height: 28px; background: {{primary}}; color: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 700; flex-shrink: 0; margin-right: 12px;">1</div><div style="flex: 1; padding-top: 4px;"><div style="font-size: 15px; font-weight: 600; color: #1a1a1a; margin-bottom: 2px;">第一步标题</div><div style="font-size: 14px; color: #5a554f; line-height: 1.6;">步骤一的详细说明内容</div></div></div><div style="display: flex; align-items: flex-start; margin-bottom: 16px;"><div style="width: 28px; height: 28px; background: {{primary}}; color: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 700; flex-shrink: 0; margin-right: 12px;">2</div><div style="flex: 1; padding-top: 4px;"><div style="font-size: 15px; font-weight: 600; color: #1a1a1a; margin-bottom: 2px;">第二步标题</div><div style="font-size: 14px; color: #5a554f; line-height: 1.6;">步骤二的详细说明内容</div></div></div><div style="display: flex; align-items: flex-start;"><div style="width: 28px; height: 28px; background: {{primary}}; color: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 700; flex-shrink: 0; margin-right: 12px;">3</div><div style="flex: 1; padding-top: 4px;"><div style="font-size: 15px; font-weight: 600; color: #1a1a1a; margin-bottom: 2px;">第三步标题</div><div style="font-size: 14px; color: #5a554f; line-height: 1.6;">步骤三的详细说明内容</div></div></div></div>`,
},
{
id: 'comparison',
name: '对比卡片',
category: 'content',
preview: '✓ 推荐 ✕ 避免',
html: `<div ${W} style="display: flex; gap: 12px; margin: 16px 0; flex-wrap: wrap;"><div style="flex: 1; min-width: 140px; background: #f0fdf4; border: 1px solid #bbf7d0; border-radius: 8px; padding: 14px;"><div style="font-size: 15px; font-weight: 700; color: #166534; margin-bottom: 8px;">✓ 推荐做法</div><div style="font-size: 14px; color: #166534; line-height: 1.8;">推荐内容第一项<br/>推荐内容第二项<br/>推荐内容第三项</div></div><div style="flex: 1; min-width: 140px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; padding: 14px;"><div style="font-size: 15px; font-weight: 700; color: #991b1b; margin-bottom: 8px;">✕ 应该避免</div><div style="font-size: 14px; color: #991b1b; line-height: 1.8;">避免内容第一项<br/>避免内容第二项<br/>避免内容第三项</div></div></div>`,
},
{
id: 'faq',
name: '问答卡片',
category: 'content',
preview: 'Q: 问题\nA: 回答',
html: `<div ${W} style="margin: 16px 0; background: #f9fafb; border-radius: 8px; overflow: hidden;"><div style="background: {{primaryLight}}; padding: 10px 16px; font-size: 15px; font-weight: 600; color: {{primary}};">Q这里填写常见问题</div><div style="padding: 12px 16px; font-size: 14px; line-height: 1.8; color: #3a3a3c;">A这里填写问题的详细回答内容帮助读者理解相关知识。</div></div>`,
},
{
id: 'highlight-text',
name: '重点高亮',
category: 'content',
preview: '▸ 高亮重点内容',
html: `<div ${W} style="margin: 16px 0; padding: 12px 16px; background: {{primaryLight}}; border-radius: 8px; font-size: 15px; line-height: 1.8; color: #1a1a1a; border-left: 4px solid {{primary}};"><strong>重点:</strong>这里是需要高亮强调的关键内容。</div>`,
},
{
id: 'key-value',
name: '键值对列表',
category: 'content',
preview: '指标: 数值',
html: `<div ${W} style="margin: 16px 0; background: #f9fafb; border-radius: 8px; overflow: hidden; font-size: 14px;"><div style="display: flex; padding: 10px 16px; border-bottom: 1px solid #e5e7eb;"><div style="width: 100px; color: #5a554f; flex-shrink: 0;">检查项目</div><div style="flex: 1; font-weight: 600; color: #1a1a1a;">检测结果</div></div><div style="display: flex; padding: 10px 16px; border-bottom: 1px solid #e5e7eb; background: #fff;"><div style="width: 100px; color: #5a554f; flex-shrink: 0;">血红蛋白</div><div style="flex: 1; font-weight: 600; color: {{primary}};">110 g/L</div></div><div style="display: flex; padding: 10px 16px; border-bottom: 1px solid #e5e7eb;"><div style="width: 100px; color: #5a554f; flex-shrink: 0;">血清白蛋白</div><div style="flex: 1; font-weight: 600; color: {{primary}};">38 g/L</div></div><div style="display: flex; padding: 10px 16px; background: #fff;"><div style="width: 100px; color: #5a554f; flex-shrink: 0;">血钾</div><div style="flex: 1; font-weight: 600; color: #1a1a1a;">4.2 mmol/L</div></div></div>`,
},
]; ];
// ═══════════════════════════════════════
// 区块组件 (6 种)
// ═══════════════════════════════════════
export const BLOCK_TEMPLATES: StyleTemplate[] = [ export const BLOCK_TEMPLATES: StyleTemplate[] = [
{ {
id: 'divider', id: 'divider',
name: '分割线', name: '分割线 · 虚线',
category: 'block', category: 'block',
preview: '─ ─ ─ ─', preview: '─ ─ ─ ─',
html: `<div ${W} style="border: none; border-top: 1px dashed #d1d5db; margin: 24px 0; height: 0;"></div>`, html: `<div ${W} style="border: none; border-top: 1px dashed #d1d5db; margin: 24px 0; height: 0;"></div>`,
}, },
{
id: 'divider-gradient',
name: '分割线 · 渐变',
category: 'block',
preview: '━━━━━━━━',
html: `<div ${W} style="border: none; height: 2px; margin: 24px 0; background: linear-gradient(90deg, transparent, {{primary}}, transparent);"></div>`,
},
{ {
id: 'section-header', id: 'section-header',
name: '章节标题', name: '章节编号',
category: 'block', category: 'block',
preview: '§ 带编号章节', preview: '§ 带编号章节',
html: `<div ${W} style="margin: 24px 0 12px; display: flex; align-items: center; gap: 10px;"><span style="display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; background: {{primary}}; color: #fff; border-radius: 50%; font-size: 14px; font-weight: 700; flex-shrink: 0;">1</span><span style="font-size: 18px; font-weight: 700; color: #1a1a1a;">章节标题</span></div>`, html: `<div ${W} style="margin: 24px 0 12px; display: flex; align-items: center; gap: 10px;"><span style="display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; background: {{primary}}; color: #fff; border-radius: 50%; font-size: 14px; font-weight: 700; flex-shrink: 0;">1</span><span style="font-size: 18px; font-weight: 700; color: #1a1a1a;">章节标题</span></div>`,
@@ -134,6 +230,20 @@ export const BLOCK_TEMPLATES: StyleTemplate[] = [
preview: '⊞ 3×2 表格', preview: '⊞ 3×2 表格',
html: `<div ${W} style="display: grid; grid-template-columns: 1fr 1fr 1fr; border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden; margin: 16px 0; font-size: 14px;"><div style="background: {{primaryLight}}; padding: 10px 12px; font-weight: 600; color: {{primary}}; border-bottom: 1px solid #e5e7eb; border-right: 1px solid #e5e7eb;">项目</div><div style="background: {{primaryLight}}; padding: 10px 12px; font-weight: 600; color: {{primary}}; border-bottom: 1px solid #e5e7eb; border-right: 1px solid #e5e7eb;">数值</div><div style="background: {{primaryLight}}; padding: 10px 12px; font-weight: 600; color: {{primary}}; border-bottom: 1px solid #e5e7eb;">备注</div><div style="padding: 10px 12px; border-bottom: 1px solid #e5e7eb; border-right: 1px solid #e5e7eb;">收缩压</div><div style="padding: 10px 12px; border-bottom: 1px solid #e5e7eb; border-right: 1px solid #e5e7eb;">120 mmHg</div><div style="padding: 10px 12px; border-bottom: 1px solid #e5e7eb;">正常</div><div style="background: #f9fafb; padding: 10px 12px; border-right: 1px solid #e5e7eb;">舒张压</div><div style="background: #f9fafb; padding: 10px 12px; border-right: 1px solid #e5e7eb;">80 mmHg</div><div style="background: #f9fafb; padding: 10px 12px;">正常</div></div>`, html: `<div ${W} style="display: grid; grid-template-columns: 1fr 1fr 1fr; border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden; margin: 16px 0; font-size: 14px;"><div style="background: {{primaryLight}}; padding: 10px 12px; font-weight: 600; color: {{primary}}; border-bottom: 1px solid #e5e7eb; border-right: 1px solid #e5e7eb;">项目</div><div style="background: {{primaryLight}}; padding: 10px 12px; font-weight: 600; color: {{primary}}; border-bottom: 1px solid #e5e7eb; border-right: 1px solid #e5e7eb;">数值</div><div style="background: {{primaryLight}}; padding: 10px 12px; font-weight: 600; color: {{primary}}; border-bottom: 1px solid #e5e7eb;">备注</div><div style="padding: 10px 12px; border-bottom: 1px solid #e5e7eb; border-right: 1px solid #e5e7eb;">收缩压</div><div style="padding: 10px 12px; border-bottom: 1px solid #e5e7eb; border-right: 1px solid #e5e7eb;">120 mmHg</div><div style="padding: 10px 12px; border-bottom: 1px solid #e5e7eb;">正常</div><div style="background: #f9fafb; padding: 10px 12px; border-right: 1px solid #e5e7eb;">舒张压</div><div style="background: #f9fafb; padding: 10px 12px; border-right: 1px solid #e5e7eb;">80 mmHg</div><div style="background: #f9fafb; padding: 10px 12px;">正常</div></div>`,
}, },
{
id: 'progress',
name: '进度指示',
category: 'block',
preview: '█████░░░ 65%',
html: `<div ${W} style="margin: 16px 0;"><div style="display: flex; justify-content: space-between; font-size: 13px; margin-bottom: 6px;"><span style="color: #1a1a1a; font-weight: 600;">治疗进度</span><span style="color: {{primary}}; font-weight: 700;">65%</span></div><div style="background: #e5e7eb; border-radius: 99px; height: 8px; overflow: hidden;"><div style="background: {{primary}}; height: 100%; width: 65%; border-radius: 99px;"></div></div></div>`,
},
{
id: 'quote-card',
name: '引言卡片',
category: 'block',
preview: '「」引用名言',
html: `<div ${W} style="margin: 20px 0; padding: 20px; background: linear-gradient(135deg, {{primaryLight}}, #f9fafb); border-radius: 12px; position: relative;"><div style="font-size: 36px; color: {{primary}}; opacity: 0.3; line-height: 1; font-family: Georgia, serif;">"</div><div style="font-size: 15px; color: #3a3a3c; line-height: 1.8; margin-top: -8px; font-style: italic;">这里填写引用的名言或重要语句。</div><div style="margin-top: 10px; font-size: 13px; color: #5a554f; font-weight: 500;">—— 作者名</div></div>`,
},
]; ];
/** 所有模板合并列表 */ /** 所有模板合并列表 */