Skip to content

框架如何协作

DragCraft 的公开入口是一个可视化工作台,但页面数据始终由无 UI 的命令内核管理。理解这条边界后,你可以判断一个需求应该放进物料、字段、Designer 扩展还是宿主服务。

从一次属性修改开始

在右栏修改公告文案时,实际经过以下路径:

text
业务字段组件
  -> Field adapter 归一化值
  -> FormGenerator 发出 change
  -> Designer 将 bindTo 翻译为命令
  -> Core 校验并原子提交 Schema
  -> History 保存旧快照,EventHub 发出事件
  -> Renderer 读取新快照并更新画布

表单引擎不直接依赖 Core,Renderer 也不能直接修改 Schema。Designer 负责把 UI 意图翻译为命令,因此字段绑定、节点动作和拖放最终共享相同的历史与校验语义。

区分五类职责

模块持有什么不负责什么
Core EngineSchema、命令、历史、注册表、事件Vue 组件和 DOM
Designer三栏工作台、字段绑定、扩展点组装草稿服务和生产发布
Renderer设计态组件树、选择、拖拽、工具栏业务状态和生产页面
Form Generator字段状态、联动、验证、adapter 调用Schema 持久化和命令执行
宿主应用物料、权限、仓储、发布、生产运行时绕过命令修改编辑状态

公开应用只从 @dragcraft/designer 使用前四类能力的聚合接口。@dragcraft/device-frames@dragcraft/fields-* 是另外两类公开 adapter;其余 workspace package 属于实现模块。

创建实例时发生什么

完整活动页的组装代码如下:

ts
import type { ContainerShellSource, DesignerSchema } from '@dragcraft/designer'
import { createDesigner } from '@dragcraft/designer'
import { guideComponentMap, guideWidgetGroups, guideWidgetMetas } from '../domain/widgets'
import { createGuideFieldComponentMap } from '../forms'
import { createGuideActionInterceptors, guideCustomActions } from './actions'
import { createGuideExtensions } from './extensions'
import { guideGlobalConfigSchema } from './global-config'
import { createGuideSchema } from './initial-schema'
import { guideMessages } from './messages'
import { registerGuideSchemaMigrations } from './schema-migrations'

export interface CreatePageDesignerOptions {
  initialSchema?: DesignerSchema
  containerShell?: ContainerShellSource
}

export function createPageDesigner(options: CreatePageDesignerOptions = {}) {
  const initialSchema = options.initialSchema ?? createGuideSchema()
  const designer = createDesigner({
    engineOptions: {
      maxHistorySize: 50,
    },
    widgetMetas: guideWidgetMetas,
    componentMap: guideComponentMap,
    fieldComponentMap: createGuideFieldComponentMap(),
    widgetGroups: guideWidgetGroups,
    globalConfigSchema: guideGlobalConfigSchema,
    workspace: {
      compactBreakpoint: 1080,
      keyboardShortcuts: true,
    },
    customActions: guideCustomActions,
    actionInterceptors: createGuideActionInterceptors(),
    extensions: createGuideExtensions(options.containerShell),
    messages: guideMessages,
  })

  registerGuideSchemaMigrations(designer.engine)
  const result = designer.engine.importSchema(initialSchema)
  if (!result.ok) {
    designer.dispose()
    throw new Error(`Initial guide schema was rejected: ${result.diagnostics.map(item => item.code).join(', ')}`)
  }

  return designer
}
export { createGuideSchema } from './initial-schema'

实例创建遵循固定顺序:

  1. 创建 Designer 和空 Engine。
  2. 注册物料 metadata、组件、字段和工作台扩展。
  3. 注册 Schema migrations。
  4. 导入初始 Schema,并由当前注册表校验。
  5. 将实例传给 DcDesigner

如果直接把未注册的初始 Schema 交给 Engine,容器定义、物料类型和迁移结果无法按当前业务协议验证。

选择扩展位置

需求放置位置
改变页面业务内容物料 Vue 组件和 props
改变可编辑字段FormSchema 与字段 adapter
改变页面结构所有权Layout 或 ContainerDefinition
写入页面 Schema字段绑定、内置 command 或节点 action
增加确认、权限和审计actionInterceptors 与宿主服务
改变工作台视觉主题 token、公开 data hook 或扩展 renderer
保存和发布页面宿主仓储、校验与发布流程

组件内部需要修改自身节点时,可以使用 useWidgetRuntime()。它仍会执行 Core command,不应以本地 DOM 状态模拟应该持久化的页面状态。

不受支持的路径

  • 直接修改 engine.store.schema.value 或冻结快照。
  • 从公开应用导入内部 package,绕过 Designer 聚合入口。
  • 把设计态 RootRenderer 当成生产页面运行时。
  • 依赖私有 .dc-* class 修改交互结构。
  • 用自定义 command 替代已经存在的内置命令和字段绑定。

需要继续理解数据时,阅读 Schema 与样式作用域;需要理解写入保证时,阅读 状态、命令、历史与事件