跳转到内容

Workbench 工作台

Workbench 是包含设计工具栏、图层管理、Dialog、状态反馈和三维查看器的完整 CustomForge 界面

不希望从零构建每一个控件时,可以直接把它嵌入业务应用

基础配置

ts
import { createWorkbench } from 'customforge/workbench'
import 'customforge/style.css'

const workbench = await createWorkbench({
  container: '#workbench',
  product: {
    modelUrl: 'https://cdn.example.com/product.glb',
    surfaceMesh: 'PrintArea',
  },
})

挂载容器必须拥有明确设置或从父元素继承的高度

配置界面

所有配置组都是可选项,未提供的字段会保留默认值

ts
const workbench = await createWorkbench({
  container: '#workbench',
  className: 'acme-customizer',
  historyLimit: 50,
  features: {
    loadRemoteProduct: false,
    saveDesign: false,
  },
  layout: {
    editorHeader: false,
    viewerHeader: false,
  },
  branding: {
    logoUrl: '/brand/logo.png',
    logoAlt: 'Acme',
    title: 'Acme 定制工作室',
    subtitle: '设计你的商品',
  },
  labels: {
    addText: '文字设计',
    addImage: '图片素材',
    productDialogTitle: '选择商品模型',
    loadingProduct: '正在加载商品……',
  },
  fontFamilies: [
    { value: 'Arial', label: '无衬线字体' },
    { value: 'Georgia', label: '衬线字体' },
  ],
  formatError: () => '资源加载失败,请检查文件或网络连接',
  theme: {
    accent: '#155eef',
    accentHover: '#004eeb',
    accentContrast: '#ffffff',
    controlRadius: '6px',
  },
  appearance: {
    editor: {
      controlSize: 7,
      objectBorder: '#155eef',
      uvBoundary: '#d92d20',
    },
    viewer: { backgroundColor: '#f4f4f5' },
  },
})

调整默认界面样式

theme 控制 Workbench 支持的设计变量。className 会向当前实例根元素添加一个或多个类,宿主 CSS 可以补充限定作用域的布局或组件规则,不会影响其他 Workbench 实例

css
.acme-customizer {
  --cfw-stage: #f7f7f8;
  min-height: 680px;
}

初始化完成后仍然可以读取或更新主题、功能和布局值

ts
const currentTheme = workbench.getTheme()
workbench.setTheme({ accent: '#0057b8', accentHover: '#003f87' })

const features = workbench.getFeatures()
const layout = workbench.getLayout()

appearance 用于配置 CSS 无法可靠控制的绘制表面,包括 Fabric 选择控件、UV 辅助颜色和 WebGL 清屏颜色。需要完全控制 DOM 和组件设计时,应使用 createCustomizer()ProductCustomizerApi,不要依赖 Workbench 内部实现

扩展按钮

通过 extensions 把业务命令放入 Workbench 的稳定位置。这样集成代码只依赖公开 API,不需要查询或移动内部 DOM 节点

ts
import type { WorkbenchExtensionButton } from 'customforge/workbench'

const actions: WorkbenchExtensionButton[] = [
  {
    id: 'acme.export-png',
    placement: 'globalActions',
    label: i18n.t('customizer.exportPng'),
    variant: 'primary',
    className: 'acme-export-command',
    order: 10,
    onClick: async ({ customizer, workbench, signal }) => {
      const blob = await customizer.getTextureBlob()
      if (!signal.aborted) {
        workbench.setStatus(`PNG 已生成(${blob.size} 字节)`)
      }
    },
  },
  {
    id: 'acme.straighten',
    placement: 'selectionToolbar',
    label: i18n.t('customizer.straighten'),
    visible: ({ selection }) => selection.length === 1,
    disabled: ({ selection }) => selection.some(({ locked }) => locked),
    onClick: ({ customizer, selection }) => {
      selection.forEach(({ id }) => {
        customizer.updateObjectTransform(id, { rotation: 0 })
      })
    },
  },
  {
    id: 'acme.center-layer',
    placement: 'layerActions',
    label: i18n.t('customizer.centerLayer'),
    iconUrl: '/icons/center.svg',
    onClick: ({ customizer, layer, state }) => {
      if (!layer) return
      customizer.updateObjectTransform(layer.id, {
        x: state.printableBounds.left + state.printableBounds.width / 2,
        y: state.printableBounds.top + state.printableBounds.height / 2,
      })
    },
  },
]

const workbench = await createWorkbench({
  container: '#workbench',
  extensions: actions,
})
挂载位置适用场景行为
globalActions商品、导出或流程级命令显示在全局顶栏操作组
editorToolbar不要求选中对象的设计命令与主要设计工具一起显示
selectionToolbar作用于选中对象的命令选区为空时自动隐藏
layerActions每个图层各自拥有的紧凑命令在每个图层重复显示,layer 为该行对象

所有状态判断函数和点击处理器都会收到 workbenchcustomizer、最新 state 与当前 selectionlayerActions 还会收到当前行的 layer。点击处理器另外包含原始 event、可用于定位业务浮层的按钮 anchor,以及 Workbench 销毁时会中止的 signal

visibledisabled 可以传布尔值或判断函数。核心状态、选区、历史与视角事件发生后会自动重新计算;如果判断条件还读取仅由宿主应用维护的状态,应调用 refreshExtensions()

返回 Promise 的处理器会让按钮自动进入 loading 并禁用。被拒绝的处理器与状态判断异常会通过 formatError 转换,并显示到已有状态区域。同一个扩展不会并发执行两次;图层操作按不同图层独立记录执行状态

外观与多语言

  • label 始终作为无障碍名称和 Tooltip,应由宿主 i18n 系统提供
  • iconUrl 支持普通 URL、Data URL 和 Blob URL;Blob URL 的生命周期由宿主管理
  • showLabel 默认为 true;带图标的 layerActions 默认使用紧凑的纯图标按钮
  • variant 可选 primarysecondaryplaindanger
  • order 按升序排列同一位置内的按钮
  • className 为按钮添加一个或多个类,供宿主编写限定作用域的样式
css
.acme-customizer .acme-export-command {
  min-width: 148px;
  text-transform: uppercase;
}

图层面板本身较紧凑,建议使用短文案或纯图标操作。自定义 CSS 应通过 Workbench 的 className 与扩展的 className 限定作用域,不要查询内部 DOM

运行时注册

扩展可以在不重建 Workbench、不丢失编辑状态的前提下动态增删

ts
const unregister = workbench.registerExtension({
  id: 'acme.approve',
  placement: 'globalActions',
  label: i18n.t('customizer.approve'),
  onClick: () => submitApproval(workbench.customizer.saveDesign()),
})

const registered = workbench.getExtensions()
workbench.refreshExtensions()
workbench.removeExtension('acme.approve')
unregister()

ID 在单个 Workbench 内必须唯一。清理函数只会在该次注册仍有效时移除对应扩展,因此显式删除后再次调用也是安全的

多语言

CustomForge 提供默认英文文案,但将当前语言状态和语言切换交给宿主应用管理

  • labels 覆盖所有固定标签、Dialog 文案、占位符、校验提示、状态、颜色名称、下载文件名和无障碍名称
  • brandingfontFamiliestextPresetsassets 提供由业务数据生成的可见名称
  • formatError 将模型、图片、UV 和 Design JSON 异常转换为面向用户的提示

这些配置应由应用自己的 i18n 系统根据当前语言生成。编写完整语言包时可使用导出的 WorkbenchLabels 类型检查字段完整性;只需修改少量词语时仍可以向 WorkbenchOptions.labels 传入部分字段

功能开关

功能开关只控制 Workbench 自带控件,不会移除底层 workbench.customizer 方法

功能控制内容
addTextaddImagedeleteSelection对象创建和删除
textFormatting上下文文字格式控件
undoRedo历史按钮和 Workbench 键盘快捷键
saveDesignloadDesignDesign JSON 操作
loadRemoteProductresetView商品加载与预览操作
reorderObjectstoggleObjectVisibilitylockObjectsrenameObjects图层管理
presetBackgroundspresetElements图片 Dialog 中的预设页签

所有开关默认为 true

初始化完成后也可以修改单个开关

ts
workbench.setFeature('addText', false)
workbench.setFeature('loadRemoteProduct', true)

商品 Dialog 支持上传自包含的本地 .glb 文件,也支持填写远程 GLB / GLTF 地址。基础底稿、可打印 Mesh 名称和纹理垂直翻转保留在 Advanced options 中。内置顶栏不再显示 PNG 导出按钮;需要时可由应用自己的操作入口调用 workbench.customizer.exportTexture()

上下文文字格式栏

选中一个或多个文字对象后,现有单行工具栏会显示 Office 风格的格式控件,提供字体、字号、粗体、斜体、下划线、对齐、文字色、高亮色、行高和字距,不会向下推动画布

多选文字的格式不一致时会显示混合状态。文字编辑命令可以让未锁定文字进入画布内编辑状态

布局开关

布局键控制区域
header品牌和全局商品操作
editorHeader二维编辑器标题栏
viewerHeader三维查看器标题栏
toolbar设计命令工具栏
layers对象图层面板
status状态和对象数量栏

所有区域默认可见

ts
workbench.setLayout('header', false)
workbench.setLayout('layers', true)

预设素材

可以向图片 Dialog 添加业务自有的背景和装饰元素

ts
const workbench = await createWorkbench({
  container: '#workbench',
  assets: {
    backgrounds: [
      {
        id: 'summer-blue',
        name: '夏日蓝',
        url: '/assets/backgrounds/summer-blue.png',
      },
    ],
    elements: [
      {
        id: 'brand-mark',
        name: '品牌标识',
        url: '/assets/elements/brand-mark.png',
        thumbnailUrl: '/assets/thumbnails/brand-mark.webp',
      },
    ],
  },
})

背景会铺满画布、替换之前的设计背景并锁定在最底层

装饰元素会作为普通可编辑图片对象添加

同一素材集合内的 ID 必须唯一,所有远程地址都必须满足 CORS 要求

自定义文字预设

ts
const workbench = await createWorkbench({
  container: '#workbench',
  textPresets: [
    {
      id: 'brand-display',
      name: '品牌展示',
      previewText: 'CustomForge',
      fontFamily: 'Arial',
      fontSize: 22,
      width: 220,
      color: '#17191c',
    },
  ],
})

传入空数组会隐藏预设区域,但仍然保留手动添加文字的能力

消费页面必须加载预设或 Design JSON 恢复过程所使用的字体

使用底层 customizer

Workbench 通过 workbench.customizer 暴露稳定的 ProductCustomizerApi

ts
workbench.customizer.addText({ text: '限量版' })

const design = workbench.customizer.saveDesign()
await workbench.customizer.undo()

也可以显示业务自定义的状态信息

ts
workbench.setStatus('设计已同步', 'ready')

Workbench API

createWorkbench() 返回 CustomForgeWorkbenchApi

方法或属性用途
customizer完整的无界面 ProductCustomizerApi
element用于实例级集成和限定样式的根元素
getFeatures()setFeature(name, enabled)读取或更新内置功能控件
getLayout()setLayout(name, visible)读取或更新内置布局区域
getTheme()setTheme(values)读取或合并实例主题变量
getExtensions()返回已注册扩展按钮的独立配置快照
registerExtension(extension)校验、注册并渲染一个扩展按钮
removeExtension(id)按稳定 ID 移除扩展按钮
refreshExtensions()重新计算扩展的显示与禁用状态
setStatus(message, mode?)设置内置预览状态反馈
destroy()释放界面和底层核心资源

清理资源

界面被移除时调用 workbench.destroy()

该方法也会销毁底层 customizer 并释放 WebGL 和编辑器资源