(阶段一:设计)面向商户,多模板 + 多模块的 Headless CMS 架构范式设计
多模板 + 多模块的 Headless CMS 架构范式设计
本文记录了我基于实际业务需求,思考并设计的一套面向商户的低代码页面配置系统 —— 一个支持多模板、多模块、可视化配置的 Headless CMS 架构。
一. 背景
在开发 ShopStack 过程中,我希望让商户能够:
- 自主选择模板(如酷炫风 Cool、通用风 General)
- 在模板内部自由组合页面模块(Hero Section、Category Section 等)
- 实时预览 + 配置文案/图片内容
从而形成一个面向商户的低代码模板系统。
目标不是 Webflow 这类开发者工具,而是像 Shopify Theme Editor 一样,让非技术用户也能配置官网界面。
为此,我从建模、数据流、前端注册机制等多个维度进行了深入设计。
二. 根基概念: 三层模型
整个架构以三层结构为核心:
Tenant -> ThemeOptions -> PageInstance -> SectionInstance
页面结构建议:Page Sections(大模块)
| 层级 | 说明 |
|---|---|
| ThemeOptions | 商户所选模板和页面映射配置 |
| PageInstance | 页面级别的容器(如 landing、shopping) |
| SectionInstance | 每个页面内的模块配置(如 HeroSection) |
| Section 名称 | 说明 |
|---|---|
HeroSection |
页首大图 + 主标题 + CTA 按钮等 |
FeaturesSection |
介绍功能/特点(如三列图标+文字说明) |
TestimonialsSection |
用户评价/推荐语 |
CarouselSection |
滚动展示项目/图文/客户等 |
StepsSection |
展示“如何使用/三步走”等流程内容 |
PricingSection |
展示套餐或付费选项 |
FAQSection |
常见问题解答区域 |
CallToActionSection |
收尾 CTA(如“现在开始”按钮) |
FooterSection |
页脚链接/版权/社媒 |
每个 Section 内部建议的原子组件
| 组件名(Component) | 说明 |
|---|---|
PrimaryTitle |
一级标题(大标题,吸睛) |
SecondaryTitle |
二级标题(副标题) |
Paragraph |
通用段落文本 |
FeatureCard |
图标 + 标题 + 描述(如三个功能介绍) |
IconBullet |
图标加一句话(适合小功能点列举) |
TestimonialCard |
用户头像 + 名字 + 引语 |
StepCard |
数字步骤 + 简短说明 |
CarouselItem |
可滚动卡片项 |
CTAButton |
带样式的 Call To Action 按钮 |
GradientOverlay |
用于 hero section 背景叠加的渐变层 |
BackgroundImage |
背景图片(通常绝对定位) |
三. ThemeOptions 设计
interface ThemeOptions {
tenantId: string;
themeId: string; // "cool", "general"
themeVariant?: string;
pageInstanceMap: Record<string, string | null>; // landing: "pageId1"
updatedAt: Date;
version?: number;
}
设计要点
themeId考虑名称规范化,无不同字面pageInstanceMap是指各类页面映射到对应的 PageInstanceversion是为未来做历史版本存储预留扩展合理
四. PageInstance & SectionInstance 建模
interface PageInstance {
id: string;
pageType: string; // 如 landing
sections: {
sectionId: string;
layout?: string;
order?: number;
}[];
}
interface SectionInstance {
id: string;
sectionName: string; // HeroSection
props: Record<string, any>; // 对应 jsonb 字段
}
设计要点
sections为结构体数组,支持拖拽排序和 layoutprops使用 PostgreSQLjsonb存储,高度灵活
五. SectionRegistry 前端注册机制
用于指导 UI 编辑器生成对应的表单结构
常规写法:
const SectionRegistry = {
cool: {
landing: {
HeroSection: {
primaryText: "string",
secondaryText: "string",
imageUrl: "string"
}
}
}
}
推荐写法:注册列表形式,选择性更好
type SectionRegistryEntry = {
templateId: string;
pageType: string;
sectionName: string;
schema: Record<string, string>;
};
const SectionRegistry: SectionRegistryEntry[] = [
{
templateId: "cool",
pageType: "landing",
sectionName: "HeroSection",
schema: {
primaryText: "string",
secondaryText: "string",
imageUrl: "string"
}
}
];
利点
- 适配 plugin 模式,便于后期扩展
- 可和后端配置表/文件相关联
六. 保存逻辑
简单方案: 清空重写 (Soft Reset)
- 清空 ThemeOptions 中当前 tenantId 下的 pageInstanceMap
- 利用级联删除 (ON DELETE CASCADE),从而删掉相关 PageInstance 和 SectionInstance
- 重新 insert SectionInstance
- 重新 insert PageInstance
- 重新 update ThemeOptions
可选扩展:版本史
- 使用
version、updatedAt等字段做历史存档 - 为后期回滚配置/历史系统基础
七. 后续行动
🔄 页面间实时预览联动(预研方向)
前端配置页面和预览页面通过 BroadcastChannel 实现跨 tab 联动:
表单发生 onChange 事件后 debounce
使用 channel.postMessage(formState) 广播整个 section 配置
预览页订阅 channel.onmessage 自动热更新 UI
具体:
-
页面 A 是配置页,用户点了勾、选了图片
-
页面 B 是实时预览页,能收到变化同步 UI
'use client'
import { useEffect, useState } from 'react'
export default function PreviewUpdater() {
const [data, setData] = useState(null)
useEffect(() => {
const channel = new BroadcastChannel("preview_channel")
channel.onmessage = (event) => {
console.log("🟢 收到更新:", event.data)
setData(event.data) // 更新本地状态
}
return () => channel.close()
}, [])
return <MyPreviewUI data={data} />
}
技术路径:新标签页打开 + BroadcastChannel 联动
// 在配置页中点击“预览”
const onPreviewClick = () => {
window.open('/preview', '_blank') // 打开预览页
}
// 发送实时配置
const channel = new BroadcastChannel("preview_channel")
const onFormChange = (formState) => {
channel.postMessage(formState)
}
💡 总结
本技术方案的特点:
| 范式 | 描述 |
|---|---|
| Headless CMS | 商户通过结构化数据组合页面 |
| 低代码平台 | 配置即 UI,模块可视化编辑 |
| 多模板/多模块 | 解耦主题与组件,支持扩展 |
| 动态 schema 注册 | 前端驱动配置 UI 表单 |
| jsonb + RDB 混合建模 | 保留自由度同时保障结构化查询 |

浙公网安备 33010602011771号