#自定义编辑器
基于 @tangramino/base-editor 构建功能完备的可视化编辑器。
#架构概览
EditorProvider // 全局状态管理
├── Operation // 顶部操作栏
├── Sidebar // 左侧面板(物料、大纲)
├── MainContent // 主内容区
│ ├── CanvasEditor // 核心画布
│ └── AttributePanel // 属性面板
└── DragOverlay // 拖拽预览层#基础框架
import React from 'react';
import {
EditorProvider,
DragOverlay,
useEditorCore,
} from '@tangramino/base-editor';
const Editor = () => {
const { dragElement } = useEditorCore();
return (
<EditorProvider materials={materials} schema={initialSchema} plugins={[]}>
<div className="flex flex-col h-screen">
{/* 顶部操作栏 */}
<header className="h-12 border-b">
<Operation />
</header>
{/* 主体区域 */}
<div className="flex flex-1 overflow-hidden">
<Sidebar />
<MainContent />
</div>
</div>
{/* 拖拽预览 */}
<DragOverlay>
<div className="px-3 py-1 bg-white border rounded shadow">
{dragElement?.title}
</div>
</DragOverlay>
</EditorProvider>
);
};#画布区域
import { CanvasEditor } from '@tangramino/base-editor';
const MainContent = () => {
return (
<div className="flex flex-1">
{/* 画布 */}
<div className="flex-1 p-4 overflow-auto">
<CanvasEditor
className="min-h-full bg-white border"
renderElement={renderCustomElement}
renderDropIndicator={renderDropIndicator}
/>
</div>
{/* 属性面板 */}
<AttributePanel className="w-72 border-l" />
</div>
);
};#自定义元素渲染
通过 renderElement 控制画布中元素的渲染方式:
import type { EnhancedComponentProps } from '@tangramino/base-editor';
const renderCustomElement = (props: EnhancedComponentProps) => {
const { children, elementProps, element, isActive } = props;
return (
<div
{...elementProps}
className={`
relative group
${isActive ? 'ring-2 ring-blue-500' : ''}
`}
>
{children}
{/* 选中时显示操作按钮 */}
{isActive && (
<div className="absolute -top-8 left-0 flex gap-1">
<button onClick={() => handleCopy(element)}>复制</button>
<button onClick={() => handleDelete(element)}>删除</button>
</div>
)}
</div>
);
};#自定义拖放指示器
const renderDropIndicator = (props) => {
const { position, direction } = props;
const style = {
position: 'absolute',
backgroundColor: '#3b82f6',
...(direction === 'horizontal'
? { height: 2, width: '100%', top: position }
: { width: 2, height: '100%', left: position }),
};
return <div style={style} />;
};#侧边栏
import {
useEditorContext,
MaterialPanel,
OutlineTree,
} from '@tangramino/base-editor';
const Sidebar = () => {
const [activeTab, setActiveTab] = useState('material');
return (
<aside className="w-64 border-r bg-gray-50">
{/* Tab 切换 */}
<nav className="flex border-b">
<button
className={activeTab === 'material' ? 'active' : ''}
onClick={() => setActiveTab('material')}
>
物料
</button>
<button
className={activeTab === 'outline' ? 'active' : ''}
onClick={() => setActiveTab('outline')}
>
大纲
</button>
</nav>
{/* 面板内容 */}
<div className="p-3">
{activeTab === 'material' && <MaterialPanel groups={materialGroups} />}
{activeTab === 'outline' && <OutlineTree />}
</div>
</aside>
);
};#属性面板
利用物料的 editorConfig 自动生成:
import { useActiveElement, AttributeForm } from '@tangramino/base-editor';
const AttributePanel = () => {
const { activeElement, activeMaterial } = useActiveElement();
if (!activeElement) {
return <div className="p-4 text-gray-400">请选择元素</div>;
}
return (
<div className="p-4">
<h3 className="font-medium mb-3">{activeMaterial?.title}</h3>
<AttributeForm
element={activeElement}
config={activeMaterial?.editorConfig}
/>
</div>
);
};#操作栏
import { useHistory, useSchema } from '@tangramino/base-editor';
const Operation = () => {
const { undo, redo, canUndo, canRedo } = useHistory();
const { schema, exportSchema, importSchema } = useSchema();
const handleSave = () => {
const data = exportSchema();
console.log('保存:', JSON.stringify(data));
};
return (
<div className="flex items-center gap-2 px-4 h-full">
<button disabled={!canUndo} onClick={undo}>
撤销
</button>
<button disabled={!canRedo} onClick={redo}>
重做
</button>
<button onClick={handleSave}>保存</button>
<button onClick={() => window.open('/preview')}>预览</button>
</div>
);
};#完整示例结构
src/editor/
├── index.tsx # 编辑器入口
├── materials/ # 物料定义
│ ├── index.ts
│ ├── button.ts
│ └── input.ts
├── components/
│ ├── operation.tsx # 操作栏
│ ├── sidebar.tsx # 侧边栏
│ ├── main-content.tsx
│ └── attribute-panel.tsx
└── plugins/ # 自定义插件
└── auto-save.ts#最佳实践
- 状态管理:利用
EditorProvider统一管理,避免外部状态 - 性能优化:大型画布使用虚拟滚动
- 错误边界:为画布和属性面板添加错误边界
- 响应式:考虑不同屏幕尺寸的布局适配
