从零打造企业级页面设计器:Vue3 + AI 的完美融合
从零打造企业级页面设计器:Vue3 + AI 的完美融合
本文将深入剖析一个完整的低代码页面设计器实现方案,涵盖拖拽交互、代码生成、AI 智能辅助等核心功能。无论你是想了解低代码原理,还是准备开发自己的设计器,这篇文章都值得收藏。
一、为什么需要页面设计器?
在企业级应用开发中,80% 的页面都是表单和数据展示页面。这些页面结构相似,但每次都要手写代码,效率低下且容易出错。
页面设计器的价值在于:
- 提效:拖拽配置,分钟级完成页面开发
- 降本:减少重复劳动,让开发者专注核心业务
- 标准化:统一的组件库和代码规范
- 可维护:配置即文档,一目了然
今天,我将完整拆解我们项目中的页面设计器实现。
二、整体架构设计
2.1 经典三栏布局
┌──────────────────────────────────────────────────────────────┐
│ 页面设计器 │
├────────────┬──────────────────────────┬─────────────────────┤
│ │ │ │
│ 组件面板 │ 设计画布 │ 属性面板 │
│ │ │ │
│ ┌──────┐ │ ┌──────────────────┐ │ ┌───────────────┐ │
│ │ 输入框 │ │ │ │ │ │ 页面配置 │ │
│ │ 下拉框 │ │ │ 拖拽区域 │ │ │ │ │
│ │ 单选框 │ │ │ │ │ │ 组件属性 │ │
│ │ ... │ │ │ │ │ │ │ │
│ └──────┘ │ └──────────────────┘ │ └───────────────┘ │
│ │ │ │
├────────────┴──────────────────────────┴─────────────────────┤
│ 工具栏: [撤销] [重做] [清空] | [AI生成] [预览] [生成代码] │
└──────────────────────────────────────────────────────────────┘
2.2 四层架构模式
┌─────────────────────────────────────────────┐
│ UI Layer(展示层) │
│ pageDesigner.vue │
│ ├─ ComponentPanel.vue (组件面板) │
│ ├─ DesignCanvas.vue (设计画布) │
│ ├─ PropertyPanel.vue (属性面板) │
│ └─ 弹窗组件(预览/代码/AI) │
├─────────────────────────────────────────────┤
│ Logic Layer(业务逻辑层) │
│ useFormSchema.ts (状态管理) │
│ useCodeGenerator.ts (代码生成) │
├─────────────────────────────────────────────┤
│ Config Layer(配置层) │
│ componentConfig.ts (组件配置库) │
│ types/index.ts (类型定义) │
├─────────────────────────────────────────────┤
│ Renderer Layer(组件渲染层) │
│ ComponentRenderer.vue (通用渲染器) │
└─────────────────────────────────────────────┘
这种分层设计的好处:
- UI 层只关心交互,不处理业务逻辑
- 逻辑层集中管理状态和数据转换
- 配置层让组件扩展变得简单
- 渲染层统一处理不同类型组件的渲染
三、核心数据结构设计
3.1 Schema 配置结构
整个设计器的核心就是一个 JSON Schema:
interface FormSchema {
name: string // 页面名称
description?: string // 页面描述
labelWidth: string // 标签宽度(如 100px)
labelPosition: 'left' | 'right' | 'top'
layout: 'dialog' | 'drawer' | 'page' // 生成类型
dialogSize?: 'small' | 'medium' | 'large' | 'xl'
items: FormItemSchema[] // 组件列表(核心)
gutter?: number // 栅格间距
}
3.2 组件配置结构
每个组件都是一个 FormItemSchema:
interface FormItemSchema {
id: string // 唯一标识
type: FormItemType // 组件类型:input/select/radio...
prop: string // 字段名
label: string // 标签
span?: number | 'auto' // 栅格占位(24列系统)
required?: boolean // 是否必填
placeholder?: string // 占位符
props?: Record<string, any> // 组件特有属性
options?: OptionItem[] // 选项数据(下拉框等)
children?: FormItemSchema[] // 嵌套子组件(row/col)
}
3.3 支持的组件类型
我们支持 30+ 种组件,涵盖常见场景:
| 分类 | 组件类型 |
|---|---|
| 基础组件 | input, textarea, number, password |
| 选择组件 | select, radio, checkbox, switch, cascader, treeSelect |
| 日期组件 | date, datetime, daterange, datetimerange, time |
| 上传组件 | imgUpload, fileUpload |
| 高级组件 | editor(富文本), colorPicker |
| 卡片组件 | statsCard, lineChartCard, pieChartCard, barChartCard, dataCard |
| 图表组件 | lineChart, barChart, pieChart, radarChart |
| 布局组件 | row, col, divider, alert, collapse |
四、拖拽交互实现
拖拽是设计器最核心的交互,我们使用 HTML5 原生 Drag & Drop API,无需引入第三方库。
4.1 三种拖拽场景
场景1:从组件面板拖入画布
// ComponentPanel.vue - 开始拖拽
function handleDragStart(event: DragEvent, type: FormItemType) {
// 存储组件类型到 dataTransfer
event.dataTransfer?.setData('componentType', type)
event.dataTransfer!.effectAllowed = 'copy'
emit('drag-start', type)
}
// DesignCanvas.vue - 放置组件
function handleDrop(event: DragEvent) {
const type = event.dataTransfer?.getData('componentType')
if (type) {
emit('add', type, dropIndex.value)
}
}
场景2:画布内部排序
// 计算放置位置(根据鼠标位置判断插入上方还是下方)
function handleItemDragOver(event: DragEvent, index: number) {
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect()
const midY = rect.top + rect.height / 2
if (event.clientY < midY) {
dropIndex.value = index // 放在上面
} else {
dropIndex.value = index + 1 // 放在下面
}
}
场景3:向容器组件添加子组件
// 行容器特殊处理
function handleRowDrop(event: DragEvent, rowId: string) {
const type = event.dataTransfer?.getData('componentType')
if (type) {
emit('add-to-container', rowId, type)
}
}
4.2 视觉反馈
良好的视觉反馈让拖拽体验更流畅:
/* 拖拽源透明度降低 */
.is-drag-source {
opacity: 0.5;
}
/* 拖拽目标高亮 */
.is-drop-target {
border-color: var(--el-color-success);
background: var(--el-color-success-light-9);
}
/* 放置指示线 */
.drop-indicator {
position: absolute;
height: 2px;
background: var(--el-color-primary);
&::before {
content: '';
width: 6px;
height: 6px;
border-radius: 50%;
background: inherit;
}
}
五、状态管理与撤销重做
5.1 Composable 模式的状态管理
我们使用 Vue 3 的 Composition API 封装状态管理:
// useFormSchema.ts
export function useFormSchema() {
// 核心状态
const schema = ref<FormSchema>(loadFromSession() || defaultFormSchema())
const selectedId = ref<string | null>(null)
// 历史记录
const history = ref<HistoryRecord[]>([])
const historyIndex = ref(-1)
// 计算属性
const selectedItem = computed(() =>
findItemByIdInItems(selectedId.value, schema.value.items)
)
const canUndo = computed(() => historyIndex.value > 0)
const canRedo = computed(() => historyIndex.value < history.value.length - 1)
// 方法
return {
schema, selectedId, selectedItem,
canUndo, canRedo,
addItem, removeItem, copyItem, moveItem,
undo, redo, saveHistory,
// ...
}
}
5.2 撤销/重做的实现
采用完整快照的方式实现撤销重做:
const MAX_HISTORY = 50
function saveHistory(action: string) {
// 1. 删除当前位置之后的历史(新操作会丢弃"未来")
if (historyIndex.value < history.value.length - 1) {
history.value = history.value.slice(0, historyIndex.value + 1)
}
// 2. 添加新记录(深拷贝完整快照)
history.value.push({
timestamp: Date.now(),
schema: JSON.parse(JSON.stringify(schema.value)),
action
})
// 3. 限制历史数量
if (history.value.length > MAX_HISTORY) {
history.value.shift()
} else {
historyIndex.value++
}
// 4. 自动保存到 sessionStorage
sessionCache.setJSON('page-designer-schema', schema.value)
}
function undo() {
if (!canUndo.value) return
historyIndex.value--
schema.value = JSON.parse(JSON.stringify(history.value[historyIndex.value].schema))
}
function redo() {
if (!canRedo.value) return
historyIndex.value++
schema.value = JSON.parse(JSON.stringify(history.value[historyIndex.value].schema))
}
5.3 键盘快捷键
function handleKeydown(e: KeyboardEvent) {
// Ctrl+Z 撤销
if (e.ctrlKey && e.key === 'z' && !e.shiftKey) {
e.preventDefault()
undo()
}
// Ctrl+Y 或 Ctrl+Shift+Z 重做
if ((e.ctrlKey && e.key === 'y') || (e.ctrlKey && e.shiftKey && e.key === 'z')) {
e.preventDefault()
redo()
}
// Delete 删除选中组件
if (e.key === 'Delete' && selectedId.value) {
e.preventDefault()
removeItem(selectedId.value)
}
}
六、代码生成引擎
代码生成是设计器的核心价值,我们生成的是可直接使用的 Vue 单文件组件。
6.1 生成流程
Schema(JSON配置)
↓
分析组件类型
↓
┌─────────────────┬─────────────────┐
│ 有表单组件 │ 无表单组件 │
├─────────────────┼─────────────────┤
│ 生成 el-form │ 生成网格布局 │
│ 生成校验规则 │ 渲染卡片/图表 │
│ 生成选项数据 │ │
└─────────────────┴─────────────────┘
↓
组装完整 .vue 文件
6.2 模板生成示例
function generateTemplate(): string {
const items = schema.value.items
// 生成表单项
const formItemsCode = items.map(item => {
const component = componentMap[item.type] // input -> AFormInput
const props = generateProps(item)
return `<el-col :span="${item.span || 12}">
<${component}
v-model="form.${item.prop}"
label="${item.label}"
prop="${item.prop}"
${props}
/>
</el-col>`
}).join('\n')
return `<template>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="${schema.value.labelWidth}"
>
<el-row :gutter="${schema.value.gutter || 20}">
${formItemsCode}
</el-row>
</el-form>
</template>`
}
6.3 脚本生成示例
function generateScript(): string {
const items = collectFormItems(schema.value.items)
// 生成初始数据
const formFields = items.map(item =>
` ${item.prop}: ${getDefaultValueCode(item)}`
).join(',\n')
// 生成校验规则
const rulesCode = items
.filter(item => item.required)
.map(item => ` ${item.prop}: [
{ required: true, message: '请${getActionText(item.type)}${item.label}', trigger: '${getTrigger(item.type)}' }
]`).join(',\n')
return `<script setup lang="ts">
import { ref } from 'vue'
const formRef = ref()
const form = ref({
${formFields}
})
const rules = ref({
${rulesCode}
})
const handleSubmit = async () => {
await formRef.value?.validate()
console.log('提交数据:', form.value)
}
</script>`
}
6.4 组件映射表
const componentMap: Record<FormItemType, string> = {
// 表单组件
input: 'AFormInput',
textarea: 'AFormInput',
select: 'AFormSelect',
radio: 'AFormRadio',
checkbox: 'AFormCheckbox',
date: 'AFormDate',
// 卡片组件
statsCard: 'AStatsCard',
lineChartCard: 'ALineChartCard',
pieChartCard: 'APieChartCard',
// 布局组件
row: 'el-row',
col: 'el-col',
divider: 'el-divider'
}
七、AI 智能辅助
这是我们设计器的差异化亮点——集成 AI 能力,让页面生成更智能。
7.1 AI 生成组件
用户只需描述需求,AI 自动生成完整的组件配置:
// 系统提示词(定义 AI 的能力)
const systemPrompt = `
你是一个页面设计专家,能根据用户描述生成 Vue 组件配置。
支持的组件类型:
- 表单组件: input, select, radio, checkbox, switch, date...
- 卡片组件: statsCard, lineChartCard, pieChartCard...
- 布局组件: row, col, divider...
重要规则:
1. 卡片/图表组件必须放在 row > col 结构中
2. 表单组件不需要 row/col,系统会自动包裹
3. 字段名使用驼峰命名
输出格式:JSON 数组
`
// 用户输入示例
const userPrompt = "生成数据统计看板,包含4个统计卡片和2个图表"
// AI 返回
[
{
"id": "row_1", "type": "row", "label": "统计卡片",
"props": { "gutter": 16 },
"children": [
{ "id": "col_1", "type": "col", "props": { "span": 6 }, "children": [
{ "id": "stats_1", "type": "statsCard", "label": "访问量",
"props": { "title": "今日访问", "value": 8520, "icon": "view" }
}
]},
// ... 更多卡片
]
},
// ... 图表行
]
7.2 AI 一键优化
自动优化字段名、占位文本等:
// 优化前
{ label: "用户名", prop: "field_1", placeholder: "" }
{ label: "手机号", prop: "field_2", placeholder: "" }
{ label: "邮箱", prop: "field_3", placeholder: "" }
// AI 优化后
{ label: "用户名", prop: "username", placeholder: "请输入用户名" }
{ label: "手机号", prop: "phone", placeholder: "请输入11位手机号" }
{ label: "邮箱", prop: "email", placeholder: "请输入邮箱地址" }
7.3 快捷示例
预置常用场景,一键生成:
const quickExamples = [
{ label: '用户登录表单', prompt: '生成登录表单,包含用户名、密码和记住我' },
{ label: '用户注册表单', prompt: '生成注册表单,包含用户名、密码、确认密码、邮箱、手机号' },
{ label: '数据统计看板', prompt: '生成数据看板,4个统计卡片 + 折线图 + 饼图' },
{ label: '商品信息表单', prompt: '生成商品表单,包含名称、价格、库存、分类、图片上传' },
{ label: '搜索筛选表单', prompt: '生成搜索表单,包含关键词、日期范围、状态筛选' },
{ label: '订单管理看板', prompt: '生成订单看板,展示订单状态统计和趋势图' }
]
八、预览功能实现
8.1 三种预览模式
// 模式1:页面预览
<div class="page-preview">
<el-form>...</el-form>
</div>
// 模式2:弹窗预览
<AModal v-model="visible" title="表单弹窗">
<el-form>...</el-form>
</AModal>
// 模式3:抽屉预览
<AModal v-model="visible" mode="drawer">
<el-form>...</el-form>
</AModal>
8.2 实时数据初始化
// 监听配置变化,自动初始化表单数据
watch(
() => props.items,
(items) => {
items.forEach((item) => {
if (!(item.prop in formData.value)) {
formData.value[item.prop] = getDefaultValue(item.type)
}
})
},
{ immediate: true, deep: true }
)
function getDefaultValue(type: FormItemType): any {
switch (type) {
case 'checkbox':
case 'daterange':
return []
case 'switch':
return false
case 'number':
return null
default:
return ''
}
}
8.3 表单验证
预览时自动生成验证规则:
const rules = computed<FormRules>(() => {
const result: FormRules = {}
collectFormItems(props.items)
.filter(item => item.required)
.forEach(item => {
result[item.prop] = [{
required: true,
message: `请${item.type === 'select' ? '选择' : '输入'}${item.label}`,
trigger: item.type === 'select' ? 'change' : 'blur'
}]
})
return result
})
九、组件配置扩展机制
9.1 组件配置结构
每个组件都有统一的配置结构:
interface ComponentConfig {
type: FormItemType // 组件类型
name: string // 显示名称
icon: string // 图标
category: ComponentCategory // 分类
defaultProps: Record<string, any> // 默认属性
propsConfig: PropConfig[] // 属性配置列表
}
interface PropConfig {
prop: string // 属性名
label: string // 显示名称
type: PropConfigType // 编辑器类型
defaultValue?: any // 默认值
options?: OptionItem[] // 选项列表(select/radio)
showCondition?: (item: FormItemSchema) => boolean // 条件显示
}
9.2 添加新组件的步骤
步骤1:添加类型定义
// types/index.ts
export type FormItemType =
| 'input'
| 'select'
| 'myNewComponent' // 新增
步骤2:添加组件配置
// config/componentConfig.ts
{
type: 'myNewComponent',
name: '我的组件',
icon: 'component',
category: '高级组件',
defaultProps: {
size: 'medium'
},
propsConfig: [
{
prop: 'size',
label: '大小',
type: 'select',
options: [
{ label: '小', value: 'small' },
{ label: '中', value: 'medium' },
{ label: '大', value: 'large' }
]
}
]
}
步骤3:添加渲染逻辑
// ComponentRenderer.vue
<template v-else-if="item.type === 'myNewComponent'">
<AMyNewComponent
v-model="formData[item.prop]"
:size="item.props?.size"
/>
</template>
步骤4:添加代码生成映射
// useCodeGenerator.ts
const componentMap = {
myNewComponent: 'AMyNewComponent'
}
十、技术亮点总结
| 特性 | 实现方式 | 优势 |
|---|---|---|
| 拖拽交互 | HTML5 Drag & Drop | 原生支持,无依赖 |
| 状态管理 | Vue 3 Composition API | 逻辑复用,类型安全 |
| 数据持久化 | sessionStorage | 刷新不丢失 |
| 撤销重做 | 完整快照 + 栈结构 | 实现简单,无限撤销 |
| 代码生成 | 字符串模板 | 生成可直接使用的代码 |
| AI 辅助 | LangChain4j 集成 | 智能生成,降低门槛 |
| 组件扩展 | 配置驱动 | 新增组件只需加配置 |
| 嵌套布局 | 递归渲染 | 支持 row/col 容器 |
十一、写在最后
这个页面设计器从设计到实现,经历了多次迭代优化。核心设计理念是:
- 配置驱动:所有组件通过配置定义,易于扩展
- 状态集中:使用 Composable 集中管理状态,便于维护
- 渲染统一:通过 ComponentRenderer 统一处理不同类型组件
- AI 赋能:利用 AI 降低使用门槛,提升效率
如果你也在做类似的低代码工具,希望这篇文章能给你一些启发。
视频教程:https://www.bilibili.com/video/BV191SjB8ES1/
框架官网:https://ruoyi.plus
如果觉得有帮助,欢迎点赞、在看、转发三连!有问题可以在评论区留言交流。
作者:抓蛙师
更多推荐


所有评论(0)