Skip to content

保存草稿并预览运行时

在业务物料和页面配置可编辑后,草稿服务属于宿主应用。贯穿示例先定义一个可替换的仓储接口和内存实现:

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。

运行时容器只接收 variantregions,不使用 ContainerRegionOutletuseContainerRuntime()

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 事件保存接口、版本冲突、发布流程、生产渲染与服务端安全校验

不要在生产页面复用编辑态 RootRendererContainerRegionOutlet。它们包含选中、拖拽和编辑交互。

完成检查:保存后重新加载草稿,Schema 与公告状态恢复;切换预览时,运行时容器只使用 variantregions

下一步:完成检查