从零打造企业级页面设计器: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 容器

十一、写在最后

这个页面设计器从设计到实现,经历了多次迭代优化。核心设计理念是:

  1. 配置驱动:所有组件通过配置定义,易于扩展
  2. 状态集中:使用 Composable 集中管理状态,便于维护
  3. 渲染统一:通过 ComponentRenderer 统一处理不同类型组件
  4. AI 赋能:利用 AI 降低使用门槛,提升效率

如果你也在做类似的低代码工具,希望这篇文章能给你一些启发。


视频教程https://www.bilibili.com/video/BV191SjB8ES1/

框架官网https://ruoyi.plus

如果觉得有帮助,欢迎点赞、在看、转发三连!有问题可以在评论区留言交流。


作者:抓蛙师

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐