低代码平台设计:我是如何构建可视化表单编辑器的
大家好,我是蔓蔓。在大厂工作时,我参与过一个低代码平台的开发,其中最核心的功能就是可视化表单编辑器。今天我来和大家分享如何从零开始构建一个可视化表单编辑器。
需求分析
目标用户
- 业务人员:不需要写代码,通过拖拽创建表单
- 开发人员:可以扩展组件和自定义逻辑
- 运维人员:需要管理表单版本和权限
核心功能
- 可视化编辑:拖拽式操作,所见即所得
- 组件库:丰富的表单组件(输入框、选择框、日期选择器等)
- 表单验证:支持自定义验证规则
- 数据联动:字段之间的关联和联动
- 表单预览:实时预览表单效果
- 表单发布:一键发布到生产环境
技术选型
前端框架
json
{
"react": "^18.0.0",
"vue": "^3.0.0",
"@ant-design/icons": "^5.0.0",
"tailwindcss": "^3.0.0"
}
状态管理
javascript
// 使用 Vue3 的 Composition API
import { ref, reactive, computed } from 'vue';
// 表单状态
const formSchema = reactive({
fields: [],
validations: {},
layout: 'vertical'
});
// 当前选中的组件
const selectedComponent = ref(null);
// 组件库
const componentLibrary = ref([
{ type: 'input', label: '输入框', icon: 'Input' },
{ type: 'select', label: '选择框', icon: 'Select' },
{ type: 'date', label: '日期选择器', icon: 'Calendar' },
// ...
]);
架构设计
整体架构
┌─────────────────────────────────────────────────────────────┐
│ 表单编辑器 │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌───────────────┐ ┌──────────────┐ │
│ │ 组件面板 │ │ 画布区域 │ │ 属性面板 │ │
│ │ (Library) │ │ (Canvas) │ │ (Properties) │ │
│ └─────────────┘ └───────────────┘ └──────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────────┐│
│ │ 状态管理层 ││
│ │ formSchema, selectedComponent, history ││
│ └──────────────────────────────────────────────────────────┘│
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐│
│ │ 工具函数层 ││
│ │ validate, generateCode, exportJSON, importJSON ││
│ └──────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
组件设计
组件基类
javascript
class FormComponent {
constructor(config) {
this.id = config.id || generateId();
this.type = config.type;
this.label = config.label || '';
this.name = config.name || '';
this.required = config.required || false;
this.placeholder = config.placeholder || '';
this.validations = config.validations || [];
}
toJSON() {
return {
id: this.id,
type: this.type,
label: this.label,
name: this.name,
required: this.required,
placeholder: this.placeholder,
validations: this.validations
};
}
}
组件工厂
javascript
class ComponentFactory {
static create(type, config = {}) {
switch (type) {
case 'input':
return new InputComponent(config);
case 'select':
return new SelectComponent(config);
case 'date':
return new DateComponent(config);
case 'checkbox':
return new CheckboxComponent(config);
case 'radio':
return new RadioComponent(config);
default:
throw new Error(`Unknown component type: ${type}`);
}
}
}
核心功能实现
拖拽功能
javascript
// 拖拽开始
function onDragStart(e, componentType) {
e.dataTransfer.setData('componentType', componentType);
e.dataTransfer.effectAllowed = 'copy';
}
// 拖拽结束
function onDragEnd(e) {
e.dataTransfer.clearData();
}
// 拖拽经过
function onDragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
}
// 放置
function onDrop(e) {
e.preventDefault();
const componentType = e.dataTransfer.getData('componentType');
if (componentType) {
const newComponent = ComponentFactory.create(componentType, {
label: getDefaultLabel(componentType),
name: generateName()
});
formSchema.fields.push(newComponent);
}
}
属性面板
javascript
// 属性面板组件
const PropertyPanel = {
props: ['component'],
template: `
<div class="property-panel">
<div class="panel-header">属性设置</div>
<div class="form-item">
<label>字段标签</label>
<input
v-model="component.label"
class="form-control"
/>
</div>
<div class="form-item">
<label>字段名称</label>
<input
v-model="component.name"
class="form-control"
/>
</div>
<div class="form-item">
<label>
<input type="checkbox" v-model="component.required" />
必填字段
</label>
</div>
<div class="form-item">
<label>占位符</label>
<input
v-model="component.placeholder"
class="form-control"
/>
</div>
<div class="form-item">
<label>验证规则</label>
<validation-rules :rules="component.validations" />
</div>
</div>
`
};
表单验证
javascript
// 验证规则引擎
class ValidationEngine {
static validate(field, value) {
const errors = [];
// 必填验证
if (field.required && !value) {
errors.push(`${field.label}不能为空`);
}
// 自定义验证规则
for (const rule of field.validations) {
const result = this.executeRule(rule, value);
if (!result.valid) {
errors.push(result.message);
}
}
return errors;
}
static executeRule(rule, value) {
switch (rule.type) {
case 'minLength':
return {
valid: String(value).length >= rule.value,
message: `最少需要${rule.value}个字符`
};
case 'maxLength':
return {
valid: String(value).length <= rule.value,
message: `最多允许${rule.value}个字符`
};
case 'pattern':
return {
valid: new RegExp(rule.value).test(value),
message: rule.message || '格式不正确'
};
case 'email':
return {
valid: /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
message: '请输入有效的邮箱地址'
};
default:
return { valid: true };
}
}
}
数据联动
javascript
// 数据联动处理器
class LinkageHandler {
constructor(formData) {
this.formData = formData;
this.listeners = {};
}
// 注册联动规则
register(fieldName, condition, action) {
if (!this.listeners[fieldName]) {
this.listeners[fieldName] = [];
}
this.listeners[fieldName].push({ condition, action });
}
// 触发联动
trigger(fieldName, value) {
const rules = this.listeners[fieldName] || [];
for (const { condition, action } of rules) {
if (condition(value, this.formData)) {
action(this.formData);
}
}
}
}
// 使用示例
const linkage = new LinkageHandler(formData);
// 当选择"其他"时,显示自定义输入框
linkage.register('reason',
(value) => value === 'other',
(data) => { data.showOtherInput = true; }
);
代码生成
生成 Vue 组件
javascript
function generateVueComponent(schema) {
const fields = schema.fields.map(field => {
const props = Object.entries(field)
.filter(([key]) => !['id', 'type', 'label', 'name'].includes(key))
.map(([key, value]) => {
if (typeof value === 'boolean') {
return key;
}
return `${key}="${value}"`;
})
.join(' ');
return `<${field.type}
label="${field.label}"
v-model="formData.${field.name}"
${props}
/>`;
}).join('\n ');
return `
<template>
<form @submit.prevent="handleSubmit">
${fields}
<button type="submit">提交</button>
</form>
</template>
<script setup>
import { reactive } from 'vue';
const formData = reactive(${JSON.stringify(getDefaultData(schema))});
function handleSubmit() {
// 提交逻辑
}
<\/script>
`.trim();
}
生成 JSON Schema
javascript
function generateJSONSchema(schema) {
const properties = {};
const required = [];
for (const field of schema.fields) {
properties[field.name] = {
type: getJSONType(field.type),
title: field.label,
...(field.required && { nullable: false })
};
if (field.required) {
required.push(field.name);
}
}
return {
type: 'object',
properties,
required
};
}
状态历史
撤销/重做功能
javascript
class HistoryManager {
constructor() {
this.history = [];
this.currentIndex = -1;
}
push(state) {
// 清除当前位置之后的历史
this.history = this.history.slice(0, this.currentIndex + 1);
this.history.push(JSON.parse(JSON.stringify(state)));
this.currentIndex++;
}
undo() {
if (this.currentIndex > 0) {
this.currentIndex--;
return this.history[this.currentIndex];
}
return null;
}
redo() {
if (this.currentIndex < this.history.length - 1) {
this.currentIndex++;
return this.history[this.currentIndex];
}
return null;
}
canUndo() {
return this.currentIndex > 0;
}
canRedo() {
return this.currentIndex < this.history.length - 1;
}
}
// 使用示例
const history = new HistoryManager();
// 保存状态
history.push(formSchema);
// 撤销
const previousState = history.undo();
if (previousState) {
Object.assign(formSchema, previousState);
}
总结
构建一个可视化表单编辑器需要考虑以下几个方面:
- 组件系统:设计可扩展的组件体系
- 状态管理:管理表单结构和用户操作
- 交互体验:拖拽、点击、编辑等操作
- 验证引擎:支持各种验证规则
- 数据联动:字段之间的关联
- 代码生成:导出不同格式的代码
- 版本控制:撤销/重做功能
技术应当有温度,一个好的低代码平台能够让业务人员也能参与到表单开发中,大大提高工作效率。
你在低代码平台方面有什么经验?欢迎在评论区交流~