保存草稿并预览运行时
在业务物料和页面配置可编辑后,草稿服务属于宿主应用。贯穿示例先定义一个可替换的仓储接口和内存实现:
ts
export function createMemoryPageRepository(seed: SavedPage[] = []): PageRepository {
const pages = new Map(seed.map(page => [page.id, clonePersistedValue(page)]))
return {
async load(id) {
const page = pages.get(id)
return page ? clonePersistedValue(page) : null
},
async save(input) {
const current = pages.get(input.id)
if (current && current.revision !== input.revision)
throw new PageRevisionConflictError(input.id)
const page: SavedPage = {
id: input.id,
revision: (current?.revision ?? 0) + 1,
schema: clonePersistedValue(input.schema),
}
pages.set(page.id, page)
return clonePersistedValue(page)
},
}
}revision 是乐观锁版本。真实服务应在保存时验证页面归属、物料白名单、资源 URL 和业务字段;内存实现只用于让教程在浏览器中完成闭环。
生产运行时需要自己解释 Schema,尤其是容器的 regions:
ts
export function createRuntimeNodeRenderer(
componentMap: RuntimeComponentMap,
containerMap: RuntimeContainerMap,
): (node: SchemaNode) => VNodeChild {
const renderNode = (node: SchemaNode): VNodeChild => {
if (node.container) {
const Container = containerMap[node.type]
if (!Container)
return null
const regions = Object.fromEntries(
Object.entries(node.container.regions).map(([regionId, children]) => [
regionId,
children.map(renderNode),
]),
) as RuntimeRegions
return h(Container, {
node,
variant: node.container.variant,
regions,
})
}
const Widget = componentMap[node.type]
return Widget
? h('div', { class: 'guide-runtime-node', style: node.style?.container }, [
h(Widget, { ...node.props, style: node.style?.content }),
])
: null
}
return renderNode
}普通节点使用 componentMap 渲染。容器节点把递归渲染后的 region VNode 交给业务容器组件,因此业务应用仍拥有 flex、grid 和分栏的实际 DOM 与 CSS。
运行时容器只接收 variant 和 regions,不使用 ContainerRegionOutlet 或 useContainerRuntime():
ts
export const RuntimeColumnContainer = defineComponent({
name: 'GuideRuntimeColumnContainer',
props: {
variant: { type: String, default: 'single' },
regions: {
type: Object as PropType<RuntimeRegions>,
required: true,
},
},
setup(props) {
const renderRegion = (regionId: string) => h('section', {
class: 'guide-runtime-column-container__region',
}, props.regions[regionId] ?? [])
return () => props.variant === 'split'
? h('div', { class: 'guide-runtime-column-container guide-runtime-column-container--split' }, [
renderRegion('left'),
renderRegion('right'),
])
: h('div', { class: 'guide-runtime-column-container' }, [
renderRegion('content'),
])
},
})| 框架负责 | 宿主负责 |
|---|---|
| 导出快照、导入校验、历史与 Schema 事件 | 保存接口、版本冲突、发布流程、生产渲染与服务端安全校验 |
不要在生产页面复用编辑态 RootRenderer 或 ContainerRegionOutlet。它们包含选中、拖拽和编辑交互。
完成检查:保存后重新加载草稿,Schema 与公告状态恢复;切换预览时,运行时容器只使用 variant 和 regions。
下一步:完成检查。