feat(web): 文章编辑器重设计 — 公众号风格三栏布局 + styled-block 自定义模块
- 左栏样式组件库(标题/内容/区块 14 种模板,5 种配色主题) - 中间 Notion 风格编辑区(标题置顶 + wangEditor + 自定义 styled-block) - 右栏 iPhone 仿真预览(匹配小程序暖奶油配色) - 设置面板移至 Drawer 抽屉按需打开 - 注册 wangEditor 自定义模块保留模板内联样式 - 使用 snabbdom VNode + insertNode API 解决样式被剥离问题
This commit is contained in:
156
apps/web/src/pages/health/articleEditor/articleTemplates.ts
Normal file
156
apps/web/src/pages/health/articleEditor/articleTemplates.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* 文章编辑器样式模板数据
|
||||
* 所有 HTML 片段使用内联样式,通过 wangEditor 自定义 styled-block 模块保留样式。
|
||||
* 模板中使用 {{primary}} / {{primaryLight}} 占位符,由 applyTheme() 替换为实际颜色值。
|
||||
*/
|
||||
|
||||
export interface StyleTemplate {
|
||||
id: string;
|
||||
name: string;
|
||||
category: 'heading' | 'content' | 'block';
|
||||
preview: string;
|
||||
html: string;
|
||||
}
|
||||
|
||||
export interface ColorTheme {
|
||||
id: string;
|
||||
name: string;
|
||||
primary: string;
|
||||
primaryLight: string;
|
||||
}
|
||||
|
||||
export const COLOR_THEMES: ColorTheme[] = [
|
||||
{ id: 'green', name: '清新绿', primary: '#16a34a', primaryLight: '#dcfce7' },
|
||||
{ id: 'blue', name: '专业蓝', primary: '#2563eb', primaryLight: '#dbeafe' },
|
||||
{ id: 'red', name: '暖橘红', primary: '#C4623A', primaryLight: '#F0DDD4' },
|
||||
{ id: 'purple', name: '雅致紫', primary: '#7c3aed', primaryLight: '#ede9fe' },
|
||||
{ id: 'amber', name: '琥珀金', primary: '#d97706', primaryLight: '#fef3c7' },
|
||||
];
|
||||
|
||||
const W = 'data-w-e-type="styled-block"';
|
||||
|
||||
export const HEADING_TEMPLATES: StyleTemplate[] = [
|
||||
{
|
||||
id: 'heading-classic',
|
||||
name: '经典',
|
||||
category: 'heading',
|
||||
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>`,
|
||||
},
|
||||
{
|
||||
id: 'heading-simple',
|
||||
name: '简约',
|
||||
category: 'heading',
|
||||
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>`,
|
||||
},
|
||||
{
|
||||
id: 'heading-rounded',
|
||||
name: '圆标',
|
||||
category: 'heading',
|
||||
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>`,
|
||||
},
|
||||
{
|
||||
id: 'heading-centered',
|
||||
name: '居中',
|
||||
category: 'heading',
|
||||
preview: '居中标题',
|
||||
html: `<div ${W} style="text-align: center; font-size: 20px; font-weight: 700; color: #1a1a1a; margin: 28px 0 12px;">标题文本</div>`,
|
||||
},
|
||||
];
|
||||
|
||||
export const CONTENT_TEMPLATES: StyleTemplate[] = [
|
||||
{
|
||||
id: 'blockquote',
|
||||
name: '引用框',
|
||||
category: 'content',
|
||||
preview: '▎引用文字...',
|
||||
html: `<div ${W} style="border-left: 3px solid {{primary}}; padding: 12px 16px; margin: 16px 0; background: #f9fafb; border-radius: 0 8px 8px 0; font-size: 15px; line-height: 1.8; color: #5a554f; font-style: italic;">引用内容请在此处编辑</div>`,
|
||||
},
|
||||
{
|
||||
id: 'tip-warning',
|
||||
name: '提示框 · 警告',
|
||||
category: 'content',
|
||||
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>`,
|
||||
},
|
||||
{
|
||||
id: 'tip-info',
|
||||
name: '提示框 · 信息',
|
||||
category: 'content',
|
||||
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>`,
|
||||
},
|
||||
{
|
||||
id: 'list-ordered',
|
||||
name: '有序列表',
|
||||
category: 'content',
|
||||
preview: '① ② ③',
|
||||
html: `<div ${W} style="padding-left: 20px; margin: 16px 0; font-size: 16px; line-height: 2; color: #3a3a3c;"><div style="position: relative; padding-left: 8px; margin-bottom: 4px;"><span style="position: absolute; left: -20px; color: {{primary}}; font-weight: 600;">1.</span>第一项内容</div><div style="position: relative; padding-left: 8px; margin-bottom: 4px;"><span style="position: absolute; left: -20px; color: {{primary}}; font-weight: 600;">2.</span>第二项内容</div><div style="position: relative; padding-left: 8px; margin-bottom: 4px;"><span style="position: absolute; left: -20px; color: {{primary}}; font-weight: 600;">3.</span>第三项内容</div></div>`,
|
||||
},
|
||||
{
|
||||
id: 'list-unordered',
|
||||
name: '无序列表',
|
||||
category: 'content',
|
||||
preview: '• • •',
|
||||
html: `<div ${W} style="padding-left: 20px; margin: 16px 0; font-size: 16px; line-height: 2; color: #3a3a3c;"><div style="position: relative; padding-left: 8px; margin-bottom: 4px;"><span style="position: absolute; left: -14px; color: {{primary}};">●</span>第一项内容</div><div style="position: relative; padding-left: 8px; margin-bottom: 4px;"><span style="position: absolute; left: -14px; color: {{primary}};">●</span>第二项内容</div><div style="position: relative; padding-left: 8px; margin-bottom: 4px;"><span style="position: absolute; left: -14px; color: {{primary}};">●</span>第三项内容</div></div>`,
|
||||
},
|
||||
{
|
||||
id: 'card-image-text',
|
||||
name: '图文卡片',
|
||||
category: 'content',
|
||||
preview: '[图] 文字说明',
|
||||
html: `<div ${W} style="display: flex; gap: 12px; margin: 16px 0; padding: 12px; background: #f9fafb; border-radius: 8px; align-items: center;"><div style="width: 100px; height: 80px; background: #e5e7eb; border-radius: 6px; display: flex; align-items: center; justify-content: center; color: #9ca3af; font-size: 12px; flex-shrink: 0;">图片</div><div style="flex: 1; font-size: 14px; line-height: 1.8; color: #3a3a3c;">图文卡片的文字描述内容请在此处编辑。</div></div>`,
|
||||
},
|
||||
{
|
||||
id: 'card-data',
|
||||
name: '数据卡片',
|
||||
category: 'content',
|
||||
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>`,
|
||||
},
|
||||
];
|
||||
|
||||
export const BLOCK_TEMPLATES: StyleTemplate[] = [
|
||||
{
|
||||
id: 'divider',
|
||||
name: '分割线',
|
||||
category: 'block',
|
||||
preview: '─ ─ ─ ─',
|
||||
html: `<div ${W} style="border: none; border-top: 1px dashed #d1d5db; margin: 24px 0; height: 0;"></div>`,
|
||||
},
|
||||
{
|
||||
id: 'section-header',
|
||||
name: '章节标题',
|
||||
category: 'block',
|
||||
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>`,
|
||||
},
|
||||
{
|
||||
id: 'table',
|
||||
name: '数据表格',
|
||||
category: 'block',
|
||||
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>`,
|
||||
},
|
||||
];
|
||||
|
||||
/** 所有模板合并列表 */
|
||||
export const ALL_TEMPLATES: StyleTemplate[] = [
|
||||
...HEADING_TEMPLATES,
|
||||
...CONTENT_TEMPLATES,
|
||||
...BLOCK_TEMPLATES,
|
||||
];
|
||||
|
||||
/** 将模板 HTML 中的颜色占位符替换为主题实际颜色值 */
|
||||
export function applyTheme(html: string, theme: ColorTheme): string {
|
||||
return html
|
||||
.replaceAll('{{primary}}', theme.primary)
|
||||
.replaceAll('{{primaryLight}}', theme.primaryLight);
|
||||
}
|
||||
|
||||
/** 根据 ID 查找颜色主题 */
|
||||
export function getColorTheme(themeId: string): ColorTheme {
|
||||
return COLOR_THEMES.find((t) => t.id === themeId) ?? COLOR_THEMES[0];
|
||||
}
|
||||
Reference in New Issue
Block a user