背景:因为项目需要。自写设计器。遇到的坑在此记录
- 使用的拖拽组件时vuedraggable。下面放上局部示例截图。
- 坑1。draggable标签在拖拽时可以获取到被拖拽的对象属性定义
- 要使用 :clone, 而不是@clone。我想应该是因为draggable标签比较特。
- 另外在使用**:clone时要将此响应添加到from draggable标签中,也就是如果从左向右拖拽标签复制,要放到左侧标签中才会起作用。意思是在赋值时对被赋值的组件对象加工处理后才会被放到右侧标签**
js
<template>
<div class="form-designer">
<!-- 左侧面板:可拖拽的控件 -->
<div class="component-panel">
<h3>控件库</h3>
<draggable
:list="availableComponents"
:group="{ name: 'components', pull: 'clone' }"
:clone="handleComponentClone">
<div v-for="(comp, index) in availableComponents" :key="index" class="component-item">
{{ comp.label }}
</div>
</draggable>
</div>
<!-- 中间画布:设计区域 -->
<div class="design-canvas">
<h3>表单设计区</h3>
<draggable
v-model="formStructure"
:group="{ name: 'components', put: true }"
@add="(item) => onComponentAdded(item)">
<transition-group name="fade" tag="div" class="form-items">
<div
v-for="item in formStructure"
:key="item.id"
class="form-item"
:class="{ selected: selectedItem === item }"
@click="selectComponent(item)">
<el-form>
<component
:is="item.type"
v-model="formData[item.vModel]"
:label="item.label"
:options="item.options"
:required="item.required" />
</el-form>
<!-- {{ item.label }} -->
</div>
</transition-group>
</draggable>
</div>
<!-- 右边配置面板:属性编辑 -->
<div class="config-panel">
<h3>属性设置</h3>
<div v-if="selectedItem">
<el-form label-position="top">
<el-form-item label="字段名称">
<el-input v-model="selectedItem.label" />
</el-form-item>
<el-form-item label="绑定字段名">
<el-input v-model="selectedItem.vModel" />
</el-form-item>
<el-form-item label="默认值">
<el-input v-model="formData[selectedItem.vModel]" />
</el-form-item>
<el-form-item label="是否必填">
<el-switch v-model="selectedItem.required" />
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>
<script>
import draggable from 'vuedraggable';
import DynamicInput from './components/Input.vue';
import DynamicSelect from './components/Select.vue';
import DynamicCheckboxGroup from './components/CheckboxGroup.vue';
import DynamicDatePicker from './components/DatePicker.vue';
import DynamicUpload from './components/UploadFile.vue';
import DynamicImageUpload from './components/UploadImage.vue';
import { options } from 'runjs';
import { start } from 'nprogress';
import cloneDeep from 'lodash.clonedeep';
export default {
name: 'FormDesigner',
components: {
draggable,
DynamicInput,
DynamicSelect,
DynamicCheckboxGroup,
DynamicDatePicker,
DynamicUpload,
DynamicImageUpload
},
data() {
return {
// textarray: ['1', '2', '3'],
// 可拖拽控件列表
availableComponents: [
{ type: 'DynamicInput', label: '单行文本', required: false, vModel: 'text' },
{ type: 'DynamicDatePicker', label: '日期时间', required: false, vModel: 'datetime' },
{ type: 'DynamicUpload', label: '文件上传', required: false, vModel: 'c' },
{ type: 'DynamicImageUpload', label: '图片上传', required: false, vModel: 'imageupload' },
{
type: 'DynamicSelect',
label: '下拉选择',
required: false,
vModel: 'select',
label: '选项',
options: ['选项1', '选项2']
},
{
type: 'DynamicCheckboxGroup',
label: '多选框组',
required: false,
vModel: 'checkboxgroup',
label: '多选',
options: ['选项A', '选项B', '选项C']
}
],
// availableComponents: [
// { id: 1, label: '张三', type:'1', vModel:'' },
// { id: 2, label: '李四', type:'2', vModel:'' }
// ],
// 当前设计的表单结构
formStructure: [
// { id: 3, label: '王五', type: '3', vModel:''},
],
// 表单数据模型
formData: {},
// 当前选中项
selectedItem: null
};
},
methods: {
// 控件被添加到设计区时触发
onComponentAdded(event) {
// console.log('add:', event);
const newComponent = this.formStructure[event.newIndex];
// 动态添加 id 字段
if (!newComponent.id) {
newComponent.id = Date.now() + Math.random().toString(36).substring(2);
}
this.formData[newComponent.vModel] = '';
this.selectedItem = newComponent;
console.log('index:', event.newIndex, 'newComponent:', newComponent, newComponent.id);
console.log('formStructure:', this.formStructure[event.newIndex]);
},
// 点击控件时更新右侧配置
selectComponent(item) {
this.selectedItem = item;
},
handleComponentClone(component) {
console.log('component:', component);
console.log('component.label:', component.label);
// 创建副本,避免修改原始 availableComponents 中的数据
const clone = cloneDeep(component);
clone.id = Date.now() + Math.random().toString(36).substring(2);
console.log('clone:', clone);
console.log('clone.id:', clone.id);
console.log('clone.type:', clone.type);
console.log('clone.label:', clone.label);
console.log('clone.vModel:', clone.vModel);
return clone;
},
deepClone(parm) {
let dataType = Object.prototype.toString.call(parm);
let result;
if (dataType === '[object Array]') {
result = [];
for (let i = 0; i < parm.length; i++) {
result[i] = deepClone(parm[i]);
}
} else if (dataType === '[object Object]') {
result = {};
for (const key in parm) {
if (Object.hasOwnProperty.call(parm, key)) {
result[key] = deepClone(parm[key]);
}
}
} else {
result = parm;
}
return result;
}
}
};
</script>
<style scoped>
.form-designer {
display: flex;
height: 100vh;
}
.component-panel,
.config-panel {
width: 250px;
padding: 10px;
background-color: #f9f9f9;
border-right: 1px solid #e4e4e4;
}
.design-canvas {
flex: 1;
padding: 10px;
overflow-y: auto;
}
.component-item {
padding: 8px;
margin-bottom: 6px;
background-color: #fff;
border: 1px solid #ddd;
cursor: move;
text-align: center;
}
.form-items {
min-height: 100px;
border: 1px dashed #ccc;
padding: 10px;
}
.form-item {
border: 1px solid #eee;
padding: 10px;
margin-bottom: 10px;
}
.form-item.selected {
border-color: #409eff; /* Element UI 主色调 */
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.5); /* 可选:添加外发光效果 */
}
</style>