跳到主要内容

定制化

@ydesign/react-editor 从第一天起就是为"二次开发"而设计的:你可以把它当作一个完整的、开箱即用的设计编辑器直接接入业务,也可以逐层下钻,替换、删除、新增任意一块 UI 或画布能力,最终得到一个完全属于你自己产品的编辑器。

本文介绍开发中最常见的 8 类定制场景,以及对应的真实 API。

💡 所有下文提到的 store 都是通过 createStore({ key })createDesignEditorApp(...) 返回的 MobX-State-Tree 实例;所有提到的 store.editor 则是 @ydesign/coreEditor 实例,持有全部画布 Handler。


1. 侧边面板(SidePanel)的完全控制

左侧面板是业务差异最大的区域。@ydesign/react-editor 不把它写死,而是以 Section 数组 的形式暴露,所有内置 Section 都可以按名称选用、自由排序、或整体替换。

1.1 只显示需要的面板

createDesignEditorApp<SidePanel /> 传一个名字数组即可:

import { createDesignEditorApp } from '@ydesign/react-editor';

createDesignEditorApp({
container: document.getElementById('root')!,
key: 'YOUR_API_KEY',
// 只显示这 5 个面板
sections: ['templates', 'text', 'photos', 'shapes', 'background'],
});

内置可选的 Section 名称:

名称说明
templates模板中心
text文字(字体、样式、预设)
photos图片素材库
shapes形状
upload上传本地素材
background背景色 / 背景图
layers图层管理
size画布尺寸
idphoto证件照
inpaintAI 消除笔

1.2 移除某个默认面板

import {
SidePanel,
TemplatesSection,
TextSection,
PhotosSection,
ShapesSection,
LayersSection,
// 不再 import BackgroundSection —— 它就不会出现
} from '@ydesign/react-editor/side-panel';

const sections = [TemplatesSection, TextSection, PhotosSection, ShapesSection, LayersSection];

<SidePanel store={store} sections={sections} />;

1.3 增加一个自定义面板

一个 Section 只需要三件事:nameTab(侧边竖向的图标按钮)、Panel(点击后右侧展开的内容)。

import { observer } from 'mobx-react-lite';
import { SectionTab } from '@ydesign/react-editor/side-panel';
import type { Section } from '@ydesign/react-editor/side-panel';
import { Sparkles } from 'lucide-react';

const AIPanelSection: Section = {
name: 'ai',
Tab: observer(props => (
<SectionTab name="AI 助手" {...props}>
<Sparkles size={20} />
</SectionTab>
)),
Panel: observer(({ store }) => {
return (
<div>
<h3>从 AI 生成图片</h3>
<button
onClick={async () => {
const url = await callYourAIService();
// 直接通过 store.editor 往画布添加元素
store.editor?.objectsHandler.addImage({ src: url });
}}
>
生成
</button>
</div>
);
}),
};

// 与内置 Section 组合
import { DEFAULT_SECTIONS } from '@ydesign/react-editor/side-panel';
<SidePanel store={store} sections={[...DEFAULT_SECTIONS, AIPanelSection]} />;

1.4 替换某个默认面板(比如接自家素材库)

替换 PhotosSection 的内容非常简单 —— 自己造一个 name: 'photos' 的 Section,放在 DEFAULT_SECTIONS 之前就会自动覆盖:

import { DEFAULT_SECTIONS } from '@ydesign/react-editor/side-panel';

const MyPhotosSection: Section = {
name: 'photos',
Tab: /* ... */,
Panel: observer(({ store }) => {
const [list, setList] = useState([]);
useEffect(() => {
fetch('/my-api/photos').then(r => r.json()).then(setList);
}, []);
return (
<div>
{list.map(item => (
<img
key={item.id}
src={item.thumbnail}
onClick={() => store.editor?.objectsHandler.addImage({ src: item.url })}
/>
))}
</div>
);
}),
};

const sections = DEFAULT_SECTIONS.map(s => (s.name === 'photos' ? MyPhotosSection : s));

2. 工具栏(Toolbar)定制

顶部工具栏会根据当前选中的元素类型(text / image / path / 多选等)自动切换。可以通过 components 插槽在默认工具栏右侧追加自己的按钮:

import Toolbar from '@ydesign/react-editor/toolbar';
import { DownloadButton } from '@ydesign/react-editor/toolbar/download-button';

<Toolbar
store={store}
components={{
ActionControls: () => (
<>
<MyCloudSaveButton store={store} />
<DownloadButton store={store} />
</>
),
}}
/>;

想替换整条工具栏?直接用自己的组件代替 <Toolbar /> 即可,所有内部按钮(DownloadButtonDuplicateButtonRemoveButtonLockButtonHistoryButtons 等)都是独立导出的,可以自由组合:

import { HistoryButtons } from '@ydesign/react-editor/toolbar/history-buttons';
import { DuplicateButton } from '@ydesign/react-editor/toolbar/duplicate-button';
import { RemoveButton } from '@ydesign/react-editor/toolbar/remove-button';

const MyToolbar = ({ store }) => (
<div className="my-toolbar">
<HistoryButtons store={store} />
<DuplicateButton store={store} />
<RemoveButton store={store} />
{/* ...自定义按钮 */}
</div>
);

3. 画布(Workspace)定制

<Workspace /> 本身作为画布容器,外观通过 CSS 调整,行为通过 store.editor 里的 Handler 调整。

3.1 调整画布外观

/* 你的全局 CSS */
.polotno-app-container {
background: #1e1e1e; /* 编辑器整体背景 */
}

.polotno-workspace-container {
background: radial-gradient(circle, #2a2a2a, #181818);
}

3.2 调整画布行为

通过 @ydesign/core 的 Handler 即可:

// 切换缩放策略
store.editor?.zoomHandler.zoomToFit();
store.editor?.zoomHandler.setZoom(1.5);

// 开关辅助线
store.editor?.guidelinesHandler.enable();
store.editor?.guidelinesHandler.disable();

// 开关标尺
store.editor?.workareaHandler.setRulesVisible(true);

// 出血位
store.editor?.workareaHandler.setBleed(3); // mm
store.editor?.workareaHandler.setBleedVisible(true);

完整的 Handler 列表见 概览 一节。


4. 多语言(i18n)

编辑器内部文案通过一个响应式的翻译字典管理。默认是英文,切换或增补只需要一次调用。

import { setTranslations, getTranslations } from '@ydesign/react-editor';

// 1) 完整覆盖 —— 切换到中文
setTranslations({
sidePanel: {
text: '文字',
templates: '模板',
photos: '图片',
shapes: '形状',
upload: '上传',
background: '背景',
layers: '图层',
size: '尺寸',
myFonts: '我的字体',
uploadTip: '将文件拖到此处,或点击上传按钮',
},
toolbar: {
undo: '撤销',
redo: '重做',
opacity: '透明度',
position: '位置',
layering: '图层',
alignLeft: '左对齐',
flipHorizontally: '水平翻转',
// ...
},
});

// 2) 想知道完整的 key 结构?
console.log(getTranslations());

自定义的组件里也可以用同一套系统:

import { observer } from 'mobx-react-lite';
import { translate, t } from '@ydesign/react-editor/utils/l10n';

const MyPanel = observer(() => {
return <div>{translate('sidePanel.myCustomLabel')}</div>;
// t 是 translate 的别名
});

5. 素材与资源:上传、API、后端对接

5.1 自定义图片上传

默认情况下本地上传会转成 base64,对生产环境 JSON 会越来越臃肿。强烈建议把图片上传到你自己的对象存储:

import { setUploadFunc } from '@ydesign/react-editor/side-panel/upload-panel';

setUploadFunc(async (file: File) => {
const formData = new FormData();
formData.append('file', file);
const res = await fetch('https://your-api.com/upload', {
method: 'POST',
body: formData,
});
const { url } = await res.json();
return url; // 返回可在 <img> 直接使用的短链
});

5.2 切换后端域名 / 替换接口实现

@ydesign/react-editor 内置了几条 API(字体列表、模板列表、图片上传、AI 消除笔等)。你可以一次性切换域名:

import { setBaseURL } from '@ydesign/react-editor';

setBaseURL('https://api.your-company.com');

或者逐条替换某个接口的具体实现:

import { setAPI } from '@ydesign/react-editor';

// 把模板列表接口指向自家系统
setAPI('templateList', () => ({
url: 'https://your-api.com/v2/design-templates',
}));

// 上传图片 —— 支持 method / url / data
setAPI('uploadImage', () => ({
method: 'POST',
url: 'https://your-api.com/assets/upload',
}));

当前可替换的 API key:fontListtemplateListtemplateDetailuploadImagepsdParsecreateRecordupdateRecordrecordDetailresourcesDetailteamFontListinpaint

5.3 自定义背景色预设

import { setBackgroundColorsPreset } from '@ydesign/react-editor/side-panel/background-panel';

setBackgroundColorsPreset(['#ffffff', '#000000', '#f5f5f5', '#ff4d4f', '#1677ff', '#52c41a']);

5.4 资源加载超时

用于大图片 / 自定义字体加载等慢资源场景:

import { setAssetLoadTimeout, setFontLoadTimeout, setFontLoadTimeoutCallback } from '@ydesign/react-editor';

setAssetLoadTimeout(30_000); // 30s
setFontLoadTimeout(15_000);
setFontLoadTimeoutCallback(msg => message.warning(msg));

6. 数据加载与导出

store 本身就是你的数据边界。加载、保存、导出、协同都通过它完成。

6.1 从后端加载设计

const json = await fetch('/api/designs/42').then(r => r.json());
store.loadJSON(json);

6.2 保存 / 自动同步

import { reaction } from 'mobx';

// 任意状态变化都自动 debounce 上报
let timer: any;
reaction(
() => store.toJSON(),
json => {
clearTimeout(timer);
timer = setTimeout(() => {
fetch('/api/designs/42', {
method: 'PUT',
body: JSON.stringify(json),
});
}, 1000);
}
);

6.3 导出图片 / PDF

所有导出能力都挂在 storeeditor 上:

// 导出当前画布为 PNG
const dataUrl = await store.toDataURL({ multiplier: 2, format: 'png' });

// 导出为 Blob 并下载
const blob = await store.toBlob({ multiplier: 2 });

导出可接收 ExportOptionsmultiplier / format ('jpeg' 'png' 'webp') / quality / width / height / filter 等。


7. 主题与样式

@ydesign/react-editor 使用 Ant Design v6 + styled-components + Tailwind v4 构建 UI。

7.1 Antd 主题(色板、圆角、字体)

把整个编辑器包在 <ConfigProvider /> 里:

import { ConfigProvider, theme } from 'antd';
import { DesignEditorApp } from '@ydesign/react-editor';

<ConfigProvider
theme={{
algorithm: theme.darkAlgorithm, // 暗色主题
token: {
colorPrimary: '#ff6a00',
borderRadius: 6,
fontFamily: 'Inter, -apple-system, sans-serif',
},
}}
>
<DesignEditorApp store={store} />
</ConfigProvider>;

7.2 覆盖局部样式

编辑器的容器类名是稳定的,直接在全局 CSS 里覆盖即可:

.polotno-app-container {
font-family: 'Inter', sans-serif;
}

.polotno-side-panel-tab {
color: #999;
}
.polotno-side-panel-tab.active {
color: #ff6a00;
}

8. 下钻到 Core:扩展画布能力

如果你要做的事情连 Handler 层都没提供(例如新增一类自定义元素,接入二维码 / 条形码 / 图表),可以直接扩展 @ydesign/core

8.1 自定义快捷键

// 默认快捷键是内置的,增补自己的一套
store.editor?.hotkeyHandler.bind({
'ctrl+s, command+s': e => {
e.preventDefault();
saveToServer();
},
'ctrl+shift+d, command+shift+d': () => {
store.editor?.objectsHandler.duplicate();
},
});

8.2 监听画布事件

Editor 继承自 EventManager,可以订阅内部事件:

store.editor?.on('object:added', obj => {
console.log('新增元素', obj);
});

store.editor?.on('selection:changed', ids => {
console.log('选中变化', ids);
});

8.3 写一个自己的 Handler(进阶)

所有内置 Handler 都继承自 Base,共享 editor / state / config / canvas 四个上下文。照同样的模式写一个挂到 Editor 上,就能享受和内置 Handler 一致的生命周期管理:

import { Base } from '@ydesign/core';

export class QRCodeHandler extends Base {
add(text: string) {
// 调用自家 QR 生成逻辑,再通过 canvas 插入元素
const svg = generateQRCode(text);
this.editor.objectsHandler.addSVG({ src: svg });
}
}

延伸阅读

如果你的定制场景在本文没有覆盖,欢迎去 GitHub 提 Issue,或直接 PR 补充这份文档。