AI助力搭建 ant design 6 的 可视化编辑UI设计器
本文分享了如何借助AI技术从零开发基于Ant Design 6的可视化UI设计器。作者分析了现有低代码平台的痛点(私有格式锁定、组件不全、定制有限),提出双内核混合渲染架构(GrapesJS编辑内核+React渲染层),并详细介绍了代码生成策略:分层隔离生成代码与业务代码,确保可重复生成且不影响业务逻辑。此外还实现了历史版本管理功能,支持自动保存和手动快照。该方案解决了传统低代码平台的局限性,为前
·
AI 助力搭建 Ant Design 6 可视化 UI 设计器:从零到一的开发实战
在 AI 时代,前端开发的效率边界正在被重新定义。这篇文章记录了我如何借助 AI 协作,从零开始搭建一套基于 Ant Design 6 的可视化 UI 设计器,踩过的坑、解决的问题、以及对未来前端开发趋势的思考。
一、为什么要自己造轮子?
市面上已有不少低代码平台,但我遇到了几个痛点:
- 私有格式锁定:很多平台生成的是 JSON DSL,难以二次开发
- 组件覆盖不全:想用 Ant Design 6 的新组件,往往要等平台更新
- 定制能力有限:业务需要的特殊交互,平台往往不支持
于是决定:自己动手,丰衣足食。

二、整体架构设计
2.1 双内核混合渲染
┌─────────────────────────────────────────────────┐
│ 用户操作层 │
└─────────────────┬───────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ GrapesJS 编辑内核 │
│ - 拖拽物理引擎、DOM 选区管理、CSS 样式生成 │
└─────────────────┬───────────────────────────────┘
↓ Model 变更事件
┌─────────────────────────────────────────────────┐
│ Runtime Bridge │
│ - 监听 Model → 转换 React Props → 挂载组件 │
└─────────────────┬───────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ React + Ant Design 渲染层 │
└─────────────────────────────────────────────────┘
2.2 代码结构规划
src/
├── editor/
│ ├── components/ # 组件定义
│ │ ├── AndButton.ts # 按钮组件
│ │ ├── AndTable.ts # 表格组件
│ │ ├── AndIterator.ts # 迭代器组件
│ │ └── ...
│ ├── traits/ # 属性编辑器
│ │ ├── TableTrait.ts # 表格类型属性
│ │ ├── JsonEditorTrait.ts
│ │ └── ...
│ ├── utils/ # 工具函数
│ │ ├── slotInjection.ts # Slot 注入
│ │ └── propParser.ts # 属性解析
│ └── leiwoReactPlugin.ts # GrapesJS 插件入口
├── react-components/ # React 组件封装
│ ├── AndButton.tsx
│ ├── AndTable.tsx
│ └── ...
└── services/
├── storage.ts # 存储服务
├── history.ts # 历史版本
└── codeGenerator.ts # 代码生成
三、代码生成策略:可重复、可覆盖、不影响业务
3.1 核心原则:UI 归设计器,逻辑归业务
生成的代码采用分层隔离策略:
project/
├── generated/ # 设计器生成,可覆盖
│ ├── pages/
│ │ ├── HomePage.tsx
│ │ └── UserList.tsx
│ └── components/
│ └── UserCard.tsx
├── business/ # 业务代码,手动维护
│ ├── hooks/
│ │ └── useUserData.ts
│ ├── services/
│ │ └── api.ts
│ └── handlers/
│ └── userHandlers.ts
└── app.tsx # 入口,手动维护
3.2 生成代码的结构
// generated/pages/UserList.tsx
// ⚠️ 此文件由设计器生成,请勿手动修改
// 重新生成将覆盖此文件
import { useUserListHandlers } from '@/business/handlers/userListHandlers';
export const UserList: React.FC = () => {
// 业务逻辑通过 Hook 注入
const handlers = useUserListHandlers();
return (
<Card title="用户列表">
<Table
columns={...}
dataSource={handlers.data}
loading={handlers.loading}
onRow={(record) => ({
onClick: () => handlers.onRowClick(record)
})}
/>
<Button onClick={handlers.onAdd}>新增用户</Button>
</Card>
);
};
3.3 业务代码的结构
// business/handlers/userListHandlers.ts
// ✅ 此文件由开发者维护,设计器不会覆盖
export const useUserListHandlers = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const onRowClick = (record) => {
// 业务逻辑...
};
const onAdd = () => {
// 业务逻辑...
};
return { data, loading, onRowClick, onAdd };
};
3.4 代码生成器实现
class CodeGenerator {
generate(page: PageModel): string {
const imports = this.collectImports(page);
const handlers = this.extractHandlerRefs(page);
const jsx = this.generateJSX(page.rootComponent);
return `
// ⚠️ 此文件由设计器生成
${imports}
export const ${page.name}: React.FC = () => {
${handlers.map(h => `const ${h.name} = ${h.hook}();`).join('\n ')}
return (
${jsx}
);
};
`;
}
private generateJSX(component: ComponentModel): string {
const props = this.serializeProps(component);
const children = component.children
.map(c => this.generateJSX(c))
.join('\n');
return `<${component.type} ${props}>${children}</${component.type}>`;
}
}
关键设计:
- 生成的文件有明确标识,重新生成时直接覆盖
- 业务逻辑通过「约定接口」注入,不写在生成代码中
- 事件处理器引用
handlers.xxx,设计器只管绑定,不管实现
四、历史版本管理
4.1 版本存储结构
interface VersionRecord {
id: string;
pageId: string;
timestamp: Date;
snapshot: string; // GrapesJS 序列化的完整状态
thumbnail?: string; // 缩略图
description?: string; // 版本描述
author: string;
}
4.2 自动保存与手动快照
class HistoryManager {
private autoSaveInterval = 30000; // 30秒自动保存
constructor(private editor: Editor) {
// 自动保存
setInterval(() => this.autoSave(), this.autoSaveInterval);
// 监听重要操作,创建快照
editor.on('component:add', () => this.markDirty());
editor.on('component:remove', () => this.markDirty());
editor.on('component:update', () => this.markDirty());
}
// 手动创建版本
async createVersion(description: string) {
const snapshot = this.editor.getProjectData();
const thumbnail = await this.captureThumbnail();
return this.api.saveVersion({
pageId: this.currentPageId,
snapshot: JSON.stringify(snapshot),
thumbnail,
description,
});
}
// 恢复版本
async restoreVersion(versionId: string) {
const version = await this.api.getVersion(versionId);
const data = JSON.parse(version.snapshot);
this.editor.loadProjectData(data);
}
// 版本对比(可视化 Diff)
async compareVersions(v1: string, v2: string) {
// 加载两个版本的组件树,生成差异报告
}
}
4.3 版本面板 UI
┌─────────────────────────────────────┐
│ 版本历史 [新建] │
├─────────────────────────────────────┤
│ ┌─────┐ v3.0 - 添加用户表格 │
│ │ 📷 │ 2024-01-12 11:30 │
│ └─────┘ [恢复] [对比] [删除] │
├─────────────────────────────────────┤
│ ┌─────┐ v2.0 - 调整布局 │
│ │ 📷 │ 2024-01-12 10:15 │
│ └─────┘ [恢复] [对比] [删除] │
└─────────────────────────────────────┘

五、全局复制粘贴
5.1 跨页面复制的挑战
普通的复制粘贴只能在当前编辑器内使用。要实现跨页面、跨项目复制,需要:
- 序列化组件为独立的 JSON
- 处理组件 ID 冲突
- 处理样式和资源引用
5.2 实现方案
class ClipboardManager {
private storageKey = 'ui-designer-clipboard';
async copy(components: Component[]) {
const data = components.map(c => ({
type: c.get('type'),
attributes: c.getAttributes(),
style: c.getStyle(),
children: this.serializeChildren(c),
// 不复制 ID,粘贴时重新生成
}));
// 存到 localStorage,支持跨 Tab 页
localStorage.setItem(this.storageKey, JSON.stringify({
timestamp: Date.now(),
components: data,
}));
// 同时写入系统剪贴板(可选)
await navigator.clipboard.writeText(JSON.stringify(data));
}
async paste(targetContainer: Component) {
const raw = localStorage.getItem(this.storageKey);
if (!raw) return;
const { components } = JSON.parse(raw);
components.forEach(compData => {
// 递归创建组件,ID 自动生成
targetContainer.components().add(compData);
});
}
}
5.3 快捷键绑定
// 注册快捷键
editor.Commands.add('clipboard:copy', {
run: (editor) => {
const selected = editor.getSelected();
if (selected) {
clipboardManager.copy([selected]);
}
}
});
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'c') {
editor.runCommand('clipboard:copy');
}
if (e.ctrlKey && e.key === 'v') {
editor.runCommand('clipboard:paste');
}
});
六、多页面 (Multi-Page) 管理
6.1 页面模型设计
interface PageModel {
id: string;
name: string;
path: string; // 路由路径
icon?: string;
order: number;
parentId?: string; // 支持页面分组
content: string; // GrapesJS 序列化内容
meta: {
title: string;
layout: 'default' | 'full' | 'blank';
};
}
6.2 页面切换实现
class PageManager {
private pages: Map<string, PageModel> = new Map();
private currentPageId: string | null = null;
async switchPage(pageId: string) {
// 1. 保存当前页面
if (this.currentPageId) {
await this.savePage(this.currentPageId);
}
// 2. 加载目标页面
const page = this.pages.get(pageId);
if (!page) throw new Error('Page not found');
const data = JSON.parse(page.content);
this.editor.loadProjectData(data);
this.currentPageId = pageId;
this.emit('page:change', page);
}
async createPage(name: string): Promise<PageModel> {
const page: PageModel = {
id: generateId(),
name,
path: `/${slugify(name)}`,
order: this.pages.size,
content: JSON.stringify(this.getEmptyPageData()),
meta: { title: name, layout: 'default' },
};
await this.api.savePage(page);
this.pages.set(page.id, page);
return page;
}
}
6.3 页面导航面板
┌─────────────────────────────┐
│ 页面管理 [+ 新建] │
├─────────────────────────────┤
│ 📄 首页 /home │
│ 📄 用户管理 /users │
│ 📁 系统设置 │
│ ├─ 📄 基础设置 /settings │
│ └─ 📄 权限管理 /auth │
└─────────────────────────────┘
七、高级组件实现
7.1 动态容器 (DynamicContainer)
动态容器支持 Flex 布局的可视化配置:
editor.Components.addType('and-dynamic-container', {
model: {
defaults: {
tagName: 'div',
droppable: true,
traits: [
{ name: 'direction', type: 'select',
options: [
{ id: 'row', label: '水平' },
{ id: 'column', label: '垂直' }
]},
{ name: 'justify', type: 'select',
options: [
{ id: 'flex-start', label: '起始' },
{ id: 'center', label: '居中' },
{ id: 'flex-end', label: '末尾' },
{ id: 'space-between', label: '两端对齐' },
]},
{ name: 'align', type: 'select', options: [...] },
{ name: 'gap', type: 'number', label: '间距' },
{ name: 'wrap', type: 'checkbox', label: '换行' },
],
},
},
view: {
onRender({ el, model }) {
const style = {
display: 'flex',
flexDirection: model.get('direction') || 'row',
justifyContent: model.get('justify') || 'flex-start',
alignItems: model.get('align') || 'stretch',
gap: `${model.get('gap') || 0}px`,
flexWrap: model.get('wrap') ? 'wrap' : 'nowrap',
};
Object.assign(el.style, style);
}
}
});
7.2 迭代器 (Iterator) 组件
迭代器是最强大的高级组件——根据数据源动态渲染子组件:
editor.Components.addType('and-iterator', {
model: {
defaults: {
tagName: 'div',
droppable: true, // 设计时可拖入模板组件
traits: [
{ name: 'dataSource', type: 'text', label: '数据源' },
{ name: 'itemKey', type: 'text', label: '唯一键', default: 'id' },
{ name: 'emptyText', type: 'text', label: '空状态文案' },
],
},
},
view: {
onRender({ el, model }) {
// 设计模式:显示模板 + 预览数据
if (el.hasAttribute('editing')) {
this.renderDesignMode(el, model);
}
},
renderDesignMode(el, model) {
// 获取模板(第一个子组件)
const template = model.components().at(0);
if (!template) {
el.innerHTML = '<div class="empty-tip">拖入组件作为循环模板</div>';
return;
}
// 用示例数据预览
const sampleData = [
{ id: 1, name: '示例1' },
{ id: 2, name: '示例2' },
];
// 渲染预览
el.innerHTML = '';
sampleData.forEach((item, index) => {
const clone = template.clone();
// 绑定数据到模板
this.bindDataToTemplate(clone, item, index);
el.appendChild(clone.getEl());
});
},
},
});
运行时渲染:
// react-components/AndIterator.tsx
export const AndIterator: React.FC<{
dataSource: any[];
itemKey: string;
template: React.ReactNode;
emptyText?: string;
}> = ({ dataSource, itemKey, template, emptyText }) => {
if (!dataSource?.length) {
return <Empty description={emptyText || '暂无数据'} />;
}
return (
<>
{dataSource.map((item, index) => (
<IteratorContext.Provider
key={item[itemKey] || index}
value={{ item, index, total: dataSource.length }}
>
{template}
</IteratorContext.Provider>
))}
</>
);
};
7.3 条件渲染组件
editor.Components.addType('and-condition', {
model: {
defaults: {
traits: [
{ name: 'condition', type: 'text', label: '条件表达式' },
{ name: 'showElse', type: 'checkbox', label: '显示 else 分支' },
],
},
},
});
设计时展示:
┌────────────────────────────────┐
│ IF: user.role === 'admin' │
│ ┌──────────────────────────┐ │
│ │ [管理员才能看到的内容] │ │
│ └──────────────────────────┘ │
│ ELSE: │
│ ┌──────────────────────────┐ │
│ │ [普通用户看到的内容] │ │
│ └──────────────────────────┘ │
└────────────────────────────────┘
八、AI 在开发过程中的价值
8.1 问题诊断加速
场景:页面刷新后组件不渲染
AI 分析链路:
- 检查 [onRender]是否执行 ✓
- 检查 React 挂载是否完成 ✓
- 检查 Slot 注入是否成功 ✗ → 发现 slot 元素不存在
- 结论:React 18 并发渲染时序问题
解决方案:实现重试机制等待 slot 出现
8.2 模式识别与抽象
AI 发现:多个组件有相似的 slot 注入逻辑
建议的抽象:
// 公共工具函数
export function injectChildrenToSlot(options) { ... }
export function waitForSlots(options) { ... }
8.3 边界情况覆盖
AI 主动提问:
- “这个组件在 iframe 预览模式下能正常工作吗?”
- “如果用户快速连续点击会怎样?”
- “移动端触摸事件考虑了吗?”
九、AI 时代的前端开发趋势
趋势 1:人机分工明确化
| 任务 | 谁更擅长 |
|---|---|
| UI 布局 | 人类(视觉判断) |
| 业务逻辑 | AI(模式化) |
| Bug 诊断 | AI 定位 + 人类决策 |
| 代码重构 | AI(模式识别) |
趋势 2:可视化 + AI 协作
- 人类 用设计器搭建 UI 骨架
- AI 填充业务逻辑和数据绑定
- 人类 微调和验收
趋势 3:开发者技能转移
- 从「写 CSS」→「描述需求」
- 从「记 API」→「理解架构」
- 从「独立开发」→「人机协作」
十、总结
核心收获:
- GrapesJS + React 混合架构可行,但要处理 DOM 主权冲突
- 代码生成要分层,UI 可覆盖,业务不受影响
- 版本管理是必须,给用户「后悔药」
- 高级组件如迭代器,是提升设计器能力的关键
- AI 是效率倍增器,尤其在调试和重构环节
💡 AI 时代造轮子的成本已大大降低。想清楚架构,剩下的有 AI 帮你填空。
更多推荐


所有评论(0)