vue拖拽表单生成器

3,422 阅读4分钟

简易低代码表单生成器

预览地址git仓库,欢迎star

1.设计思路

1.将生成器分为左侧物料区,中间渲染区,右侧属性区域

2.左侧物料区组件可以通过拖拽的方式,将组件拖拽至目标渲染区

3.看上去是组件的拖拽,实际上是通过拖拽实现将相关组件的数据流转到渲染区,并在属性区控制数据属性

2.组件物料区

1.组件区渲染element-ui组件

2.定义configList,在里面配置组件的config,并在组件区遍历出来

  • 组件区的 config data
const componentList = {
    {
      type: 'input',
      name: '单行文本',
      icon: 'el-icon-share',
      options: {
        width: '100%',
        placeholder: '请输入文本',
        defaultValue: "",
        required: false,
        clearable: false,
        maxlength: ""
      }
    },
    {
      type: 'radio',
      name: '单选框组',
      icon: "el-icon-share",
      options: {
        inline: 'inline-block',
        valueData: [
          {value: '选项1', label: '选项1'},
          {value: '选项2', label: '选项2'}
        ],
        defaultValue: "",
        required: false
      }
    }
}

例如input组件,在外层定义type,name等描述组件的类型和名称,在options中定义组件的属性,options中的属性将会被属性区修改

3.物料区组件渲染

这里做了两件事:

  1. 将组件渲染到tag上
  2. 给tag包裹拖拽组件vue-draggable
<draggable
  :list="CommonUserComponents"
  :move="() => true"
  class="draggable-container"
  v-bind="{
    group: {
      name: 'people', // 同分组可以互相拖拽
      pull: 'clone', // 拖拽变为复制
      put: false, // 是否允许拖入当前组
    },
    sort: false,
    ghostClass: 'ghostClass',
  }"
>
  <el-tag
    v-for="(item, index) in CommonUserComponents"
    :key="index"
    effect="dark"
  >
    <i :class="item.icon"></i>
    {{item.name}}
  </el-tag>
</draggable>

3.拖拽物料区组件

  1. 本生成器使用vuedraggeable组件实现拖拽,更详细的文档查看
  2. 使用:
import draggable from "vuedraggable";
<draggable
  :list="CommonUserComponents"
  :move="() => true"
  class="draggable-container"
  v-bind="{
    group: {
      name: 'people', // 同分组可以互相拖拽
      pull: 'clone', // 拖拽变为复制
      put: false, // 是否允许拖入当前组
    },
    sort: false,
    ghostClass: 'ghostClass',
  }"
>

如上:

list: 实现拖拽的list
move: 这个函数将以类似于Sortable onMove回调的方式调用。返回false将取消拖动操作。
ghost: 当拖动列表单元时会生成一个副本作为影子单元来模拟被拖动单元排序的情况,此配置项就是来给这个影子单元添加一个class
sort: 默认true,拖动过程中会根据拖动位置重新排序list
group: {
      name: 'people', 同分组可以互相拖拽,渲染区也会添加该属性
      pull: 'clone', 拖拽变为复制,否则物料区组件会越来越少
      put: false, 是否允许拖入当前组
},

4.原生拖拽方法实现思路

1.给组件添加 draggable 属性,以及@dragstart事件,该事件会在拖拽开始时触发

2.监听渲染区被拖拽覆盖时的事件:dragenter, dragover, dragleave, drop

/**
 * 监听物料区
 * 拖拽第一次进入到物料区: dragenter
 * 拖拽在物料区上: dragover
 * 拖拽离开物料区: dragleave
 * 拖拽在物料区松开:drop
 * 拖拽物料区组件开始时
 */
const dragstart = (e, component) => {
  containerRef.value.addEventListener('dragenter', dragenter)
  containerRef.value.addEventListener('dragover', dragover)
  containerRef.value.addEventListener('dragleave', dragleave)
  containerRef.value.addEventListener('drop', drop)
  currentComponent = component
}

3.dragenter: 物料组件刚进入渲染区时

触发dragenter事件,修改拖动时的鼠标样式为move

let currentComponent = null
const dragenter = e => {
  // 添加拖动类型
  // h5拖动的图标
  e.dataTransfer.dropEffect = 'move'
}

4.dragover: 物料组件在渲染区经过时

阻止渲染区默认事件,否则无法修改拖动鼠标样式以及drop事件

const dragover = e => {
  // 经过时阻止默认行为
  e.preventDefault()
}

5.dragleave: 物料组件离开渲染区时

const dragleave = e => {
  // 离开时,将拖动图标改为禁止
  e.dataTransfer.dropEffect = 'none'
}

6.drop: 处理松手时drop事件

1.将当前拖拽的组件的 item config 记录到渲染区

2.将currentComponent置空

5.渲染区配置

1.渲染区接受一个表单的通用 form config 配置

// 渲染区表单config
configFormData: {
  // 拖拽区接收到的组件
  list: [],
  // 拖拽区el-form属性
  config: {
    labelWidth: 100,
    labelPosition: "right",
    size: "small"
  }
},

如上,config中是表单的通用配置,如labelWidth以及labelPosition,list 中是从物料区接收到,需要渲染在渲染区的组件列表

2.渲染区

<el-form
  :size="configFormData.config.size"
  :label-position="configFormData.config.labelPosition"
  :label-width="configFormData.config.labelWidth + 'px'"
>
  <!--   拖拽展示组件, .movetag表示内部只有class为movetag才可以拖动   -->
  <draggable
    v-model="configFormData.list"
    @add="handleWidgetAdd"
    v-bind="{ group: 'people', animation: 200 }"
    class="container-form"
    handle=".movetag"
  >
    <div
      v-for="(item, index) in configFormData.list"
      :key="index"
      class="formitem-style"
    >
      <FormItem
        v-if="item && item.key"
        :select.sync="selectWidget"
        :eleItem="item"
        :eleIndex="index"
        :listdata="configFormData"
      />
    </div>
  </draggable>
</el-form>

如上,el-form接收config通用配置,draggable包括渲染区

<draggable
      v-model="configFormData.list"
      @add="handleWidgetAdd"
      v-bind="{ group: 'people', animation: 200 }"
      class="container-form"
      handle=".movetag"
>

v-model与form config 中的list形成双向绑定,由于物料区的draggable组件属性也配置过 group: 'people',所以支持相互拖拽,当物料区的组件拖拽到此区域时:

  1. v-model 的list会自动将组件绑定的数据传入
  2. 触发@add事件,传入event参数,其中包括该拖拽组件在原列表的index值以及到渲染区的新index值

3.渲染区根据form config 中的list 渲染组件

<div
  v-for="(item, index) in configFormData.list"
  :key="index"
  class="formitem-style"
>
  <FormItem
    v-if="item && item.key"
    :select.sync="selectWidget"
    :eleItem="item"
    :eleIndex="index"
    :listdata="configFormData"
  />
</div>

6.渲染区组件FormItem

FormItem组件接收如下属性

  • 当前选中组件属性
  • 组件属性
  • index值
  • 整个form config

1.预设所有的组件,通过组件type属性,判断渲染哪一个

<template v-if="eleItem.type==='input'">
  <el-input
    v-model="eleItem.options.defaultValue"
    :style="{ width: eleItem.options.width }"
    :placeholder="eleItem.options.placeholder"
    :disabled="eleItem.options.disabled"
    :show-password="eleItem.options.password"
    :clearable="eleItem.options.clearable"
    :maxlength="eleItem.options.maxlength"
    show-word-limit
    readonly
  ></el-input>
</template>
<!-- 单选 -->
<template v-if="eleItem.type === 'radio'">
  <el-radio-group v-model="eleItem.options.defaultValue">
    <el-radio
      v-for="(item, index) in eleItem.options.valueData"
      :key="index"
      :label="item.label"
      :style="{ display: eleItem.options.inline }"
      :class="{ radioLineHeight: true }"
      :disabled="eleItem.options.disabled"
      :border="eleItem.options.border"
    >{{ item.value }}</el-radio
    >
  </el-radio-group>
</template>

2.通过selectWidget判断,当前组件是否是选中的组件,选中的组件会与属性区进行联动

3.传入完整的form config列表作用:

  • 1.可以直接进行渲染区组件删除操作
  • 2.form config 可对单个组件进行全局设置,如labelPosition

7.属性区配置

属性区接受两个参数

  1. form config 配置,可以操作表单的属性
  2. 选中组件的数据,可以操作组件属性
<el-tabs v-model="activeName">
  <el-tab-pane label="组件设置" name="first">
    <WidgetConfig :eleItem.sync="widgetFormSelect" />
  </el-tab-pane>
  <el-tab-pane label="表单属性" name="second">
    <FormConfig :formdata="configFormData.config" />
  </el-tab-pane>
</el-tabs>

设置组件属性

<el-form-item label="数据绑定字段" class="form-borderbottom">
  <el-input v-model="eleItem.model"></el-input>
</el-form-item>
<!-- 设置表单宽度 -->
<template v-if="eleItem.options.hasOwnProperty('width')">
  <el-form-item label="表单宽度" class="form-borderbottom">
    <el-input v-model="eleItem.options.width"></el-input>
  </el-form-item>
</template>
<!-- 表单提示语句 -->
<template v-if="eleItem.options.placeholder">
  <el-form-item label="提示语句" class="form-borderbottom">
    <el-input v-model="eleItem.options.placeholder"></el-input>
  </el-form-item>
</template>

设置表单属性

<el-form-item label="组件尺寸" class="form-borderbottom">
  <el-radio-group v-model="formdata.size">
    <div class="positon-group">
      <el-radio label="medium"></el-radio>
      <el-radio label="small">中(默认值)</el-radio>
      <el-radio label="mini"></el-radio>
    </div>
  </el-radio-group>
</el-form-item>