Workbench 工作台
Workbench 是包含设计工具栏、图层管理、Dialog、状态反馈和三维查看器的完整 CustomForge 界面
不希望从零构建每一个控件时,可以直接把它嵌入业务应用
基础配置
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',
},
})挂载容器必须拥有明确设置或从父元素继承的高度
配置界面
所有配置组都是可选项,未提供的字段会保留默认值
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 实例
.acme-customizer {
--cfw-stage: #f7f7f8;
min-height: 680px;
}初始化完成后仍然可以读取或更新主题、功能和布局值
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 节点
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 为该行对象 |
所有状态判断函数和点击处理器都会收到 workbench、customizer、最新 state 与当前 selection。layerActions 还会收到当前行的 layer。点击处理器另外包含原始 event、可用于定位业务浮层的按钮 anchor,以及 Workbench 销毁时会中止的 signal
visible 和 disabled 可以传布尔值或判断函数。核心状态、选区、历史与视角事件发生后会自动重新计算;如果判断条件还读取仅由宿主应用维护的状态,应调用 refreshExtensions()
返回 Promise 的处理器会让按钮自动进入 loading 并禁用。被拒绝的处理器与状态判断异常会通过 formatError 转换,并显示到已有状态区域。同一个扩展不会并发执行两次;图层操作按不同图层独立记录执行状态
外观与多语言
label始终作为无障碍名称和 Tooltip,应由宿主 i18n 系统提供iconUrl支持普通 URL、Data URL 和 Blob URL;Blob URL 的生命周期由宿主管理showLabel默认为true;带图标的layerActions默认使用紧凑的纯图标按钮variant可选primary、secondary、plain或dangerorder按升序排列同一位置内的按钮className为按钮添加一个或多个类,供宿主编写限定作用域的样式
.acme-customizer .acme-export-command {
min-width: 148px;
text-transform: uppercase;
}图层面板本身较紧凑,建议使用短文案或纯图标操作。自定义 CSS 应通过 Workbench 的 className 与扩展的 className 限定作用域,不要查询内部 DOM
运行时注册
扩展可以在不重建 Workbench、不丢失编辑状态的前提下动态增删
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 文案、占位符、校验提示、状态、颜色名称、下载文件名和无障碍名称branding、fontFamilies、textPresets和assets提供由业务数据生成的可见名称formatError将模型、图片、UV 和 Design JSON 异常转换为面向用户的提示
这些配置应由应用自己的 i18n 系统根据当前语言生成。编写完整语言包时可使用导出的 WorkbenchLabels 类型检查字段完整性;只需修改少量词语时仍可以向 WorkbenchOptions.labels 传入部分字段
功能开关
功能开关只控制 Workbench 自带控件,不会移除底层 workbench.customizer 方法
| 功能 | 控制内容 |
|---|---|
addText、addImage、deleteSelection | 对象创建和删除 |
textFormatting | 上下文文字格式控件 |
undoRedo | 历史按钮和 Workbench 键盘快捷键 |
saveDesign、loadDesign | Design JSON 操作 |
loadRemoteProduct、resetView | 商品加载与预览操作 |
reorderObjects、toggleObjectVisibility、lockObjects、renameObjects | 图层管理 |
presetBackgrounds、presetElements | 图片 Dialog 中的预设页签 |
所有开关默认为 true
初始化完成后也可以修改单个开关
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 | 状态和对象数量栏 |
所有区域默认可见
workbench.setLayout('header', false)
workbench.setLayout('layers', true)预设素材
可以向图片 Dialog 添加业务自有的背景和装饰元素
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 要求
自定义文字预设
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
workbench.customizer.addText({ text: '限量版' })
const design = workbench.customizer.saveDesign()
await workbench.customizer.undo()也可以显示业务自定义的状态信息
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 和编辑器资源
