需求:

  

vue element 表格拖拽 vue 拖拽表单_vue element 表格拖拽

 

 想要实现这样的一个需求,左边是组件库。中间是展示。拖拉组件到中间就形成一个组件。

刚开始用的form-create,发现不行,又用的form-create-design,但vue3版本的只有element plus库,系统刚开始用的ant-design,而且左边我只需要单行输入框和多行输入框组件就够了,有点用宰牛刀的感觉,而且右侧还不能自定义,看悟空CRM的源码看的头大,他还是用的vue2版本+element ui,只能自己撸一了。

刚开始也是看的vuedraggable文档,https://www.itxst.com/vue-draggable-next/tutorial.html,看了半天没看懂,写了个锤子。

后面找啊找,终于找到了一篇让我搞清了逻辑;,按这个一步步写最终也出来了,这个文章看一下,改下option,另一篇又看下又改下option。搞到最后啥都没有,空白页一个。啊!差点要疯了

安装

 

npm i -S vuedraggable@next


//导入
import draggable from 'vuedraggable'

 

直接在使用页面导入,因为刚开始文件改了删,删了改,结果一直报错,后面才发现没引入。。。

 

首先建立三个div,分别展示上图的内容

vue element 表格拖拽 vue 拖拽表单_vue element 表格拖拽_02

 

Vue3版本里不允许在里面进行for循环了,要用<template #item="{ element }"></template>,element 不能随意修改,就是for循环迭代的item

左侧draggable配置:

<draggable
     :list="leftMenu"
     ghost-class="ghost"
     :force-fallback="true"
     :group="{ name: 'list', pull: 'clone' }"
     :sort="false"
     itemKey="id"
   :clone="clone"

>
   <template #item="{ element }">
         <div class="item move">
               <a-button >
                   <template #icon v-if="element.type === 'Input'"><edit-outlined /></template>
                   <template #icon v-if="element.type === 'Textarea'"><ordered-list-outlined /></template>
                   {{ element.name }}
               </a-button>
         </div>
    </template>
</draggable>

JS

// 左侧组件菜单,不要响应式,不然右侧修改,按钮的名称也会改
const leftMenu =[
    { name: "单行文本", type: 'Input', isChecked: true, placeholder: '请输入'},
  { name: "多行文本",  type: 'Textarea',isChecked: false, placeholder: '请输入'}
]
// clone一个新的拖动组件的值
const clone =(obj :any)=> {
    // 深拷贝一个对象,否则三个数据指向的都是一个地址,
    const newObj =JSON.parse(JSON.stringify(obj))
    return newObj
}

然后这样左侧的菜单就显示了,a-button里是根据name生成不同的icon,icon使用记得引入下,element就看下他的按钮图标怎么用的

import {
    EditOutlined,
    OrderedListOutlined,
  DeleteOutlined,
  ExclamationCircleOutlined
} from '@ant-design/icons-vue'

 

vue element 表格拖拽 vue 拖拽表单_vue element 表格拖拽_03

 

 

 中间draggable:

<draggable
     :list="centerData"
     ghost-class="ghost"
     itemKey="id"
     :force-fallback="true"
     group="list"
     :fallback-class="true"
     :fallback-on-body="true"
     class="draggable"
>
   <template #item="{element}">
          <div class="item move">
              <label class="move">{{ element.name }}</label>
                    <div><a-input v-model:value="input" placeholder="请输入" @focus="focusInput(element.name)" v-if="element.type === 'Input'" /></div>

          </div> 

   </template> 

</draggable>

template写要展示的组件,也可以自定义。然后根据name来控制显隐,左边拖进去的数据就会添加到centerData里,然后遍历判断就可以了,组件少可以这么搞,组件多的建议使用component标签来映射,可以看上边的第二个链接,反正原理现在就弄明白了

 JS

// 中间数据
const centerData = reactive([])

// 鼠标聚焦输入框时,显示右侧内容
const focusInput = (item: any) => {
    rightData.value = item
}

 右侧就是自己来写就ok了,然后数据绑定成中间数据的字段就可以了,这样就可以实现右边改,中间变化的效果了。

右侧代码:

<div class="right" v-if="rightData">
            <div class="title">{{ rightData.name }}</div> 这个是根据左侧选择单行输入还是多行输入显示title文字
            <div class="rightContent">
                <a-form ref="formRef" :model="rightData" labelAlign="left">  把label设为在input上边,还要结合下css
                    <a-form-item name="name" label="标识名" :rules="[{ required: true, message: '标识名不能为空' }]">
                        <a-input v-model:value="rightData.name" :placeholder="input" />
                    </a-form-item>
                    <a-form-item name="placeholder" label="提示语"> 
                        <a-input v-model:value="rightData.placeholder" /> 
                    </a-form-item>
                    <a-form-item>
                        <a-checkbox v-model:checked="rightData.isChecked">是否必填</a-checkbox>
                    </a-form-item>
                </a-form>
            </div>
        </div>

JS

// 右侧
const rightData = ref()

// 鼠标聚焦输入框时,显示右侧内容
const focusInput = (item: any) => {
    rightData.value = item
}

CSS

.ant-form-item{
    display: block;
}

这样基本就完成了

vue element 表格拖拽 vue 拖拽表单_数据_04

 

 获取生成表单的内容直接获取centerData就可以了

基本原理就是左侧数据是非响应式,拖进去中间的数据变为响应式,且根据左侧的某一字段来判断显示那一个组件,点击中间组件的时候,将点击的数据传给rightData(也是响应式),然后修改右侧数据,就可以实现中间数据的变动了。中间的样式就直接修改要显示的组件样式就可以了

vue element 表格拖拽 vue 拖拽表单_ico_05

 

最后赋一下源码(具体以下面的代码为准,上面的代码可能会有出入,改bug之类没改到)

<template>
    <div class="page">
        <div class="left">
            <div class="title">字段库</div>
            <draggable
                :list="leftMenu"
                ghost-class="ghost"
                :force-fallback="true"
                :group="{ name: 'list', pull: 'clone' }"
                :clone="clone"
                :sort="false"
                itemKey="id"
                animation="300"
            >
                <template #item="{ element, index }">
                    <a-button class="btns move" :key="index">
                        <template #icon v-if="element.type === 'Input'"><edit-outlined /></template>
                        <template #icon v-if="element.type === 'Textarea'"><ordered-list-outlined /></template>
                        {{ element.name }}
                    </a-button>
                </template>
            </draggable>
        </div>
        <div class="center">
            <div class="center-title">编辑字段</div>
            <draggable
                :list="centerData"
                ghost-class="ghost"
                itemKey="id"
                :force-fallback="true"
                group="list"
                :fallback-class="true"
                :fallback-on-body="true"
                class="draggable"
                animation="300"
            >
                <template #item="{element, index}">
                    <div class="inputItem move" @click="focusInput(element)">
                        <div class="form-title move">{{ element.name }}</div>
                        <div><a-input v-model:value="input" @focus="focusInput(element)" v-if="element.type === 'Input'" :placeholder="element.placeholder" :key="index"/></div>
                        <div>
                            <a-textarea
                                :key="index"
                                v-model:value="textarea"
                                show-count :maxlength="200"
                                @focus="focusInput(element)"
                                :placeholder="element.placeholder"
                                v-if="element.type === 'Textarea'"
                            />
                        </div>
                        <div class="delItem" @click="deleteComponent(index)">
                            <delete-outlined style="font-size: 14px; opacity: 0.8"/>
                        </div>
                    </div>
                </template>
            </draggable>
        </div>
        <div class="right" v-if="rightData">
            <div class="title">{{ rightData.name }}</div>
            <div class="rightContent">
                <a-form ref="formRef" :model="rightData" labelAlign="left">
                    <a-form-item name="name" label="标识名" :rules="[{ required: true, message: '标识名不能为空' }]">
                        <a-input v-model:value="rightData.name" :placeholder="input" />
                    </a-form-item>
                    <a-form-item name="placeholder" label="提示语">
                        <a-input v-model:value="rightData.placeholder" />
                    </a-form-item>
                    <a-form-item>
                        <a-checkbox v-model:checked="rightData.isChecked">是否必填</a-checkbox>
                    </a-form-item>
                </a-form>
            </div>
        </div>
    </div>

</template>

<script lang="ts" setup>
import {
    EditOutlined,
    OrderedListOutlined,
    DeleteOutlined,
    ExclamationCircleOutlined
} from '@ant-design/icons-vue'
import { message, Modal } from "ant-design-vue";
import {ref, defineExpose, reactive, createVNode} from "vue";

import draggable from 'vuedraggable'

const formRef = ref()
const input = ref('')
const textarea = ref('')


// 左侧组件菜单
const leftMenu = [
    {
        name: "单行文本",
        type: 'Input',
        isChecked: true,
        placeholder: '请输入'
    },
    {
        name: "多行文本",
        type: 'Textarea',
        isChecked: false,
        placeholder: '请输入'
    }
]

// clone一个新的拖动组件的值
const clone =(obj :any)=> {
    // 深拷贝一个对象,否则三个数据指向的都是一个地址,
    const newObj =JSON.parse(JSON.stringify(obj))
    return newObj
}
// 中间数据
const centerData = reactive([])
// 右侧
let rightData = ref()

// 鼠标聚焦输入框时,显示右侧内容
const focusInput = (item: any) => {
    rightData.value = item
}
// 删除
const deleteComponent = (index: number) => {
    Modal.confirm({
        title: '确定要删除该组件吗?',
        icon: createVNode(ExclamationCircleOutlined),
        onOk() {
            centerData.splice(index,1)
        }
    })
}

// 表单校验
const checkForm = () => {
    formRef.value.validateFields().then( async () => {
       // console.log(centerData)
   })
}
// 获取中间内容
defineExpose({
    checkForm
})
</script>

<style scoped lang="scss">
.ant-form-item{
    display: block;
}
.page{
    position: relative;
    display: flex;
    justify-content: center;
    .left{
        width: 20%;
        position: absolute;
        left: 10px;
        .btns{
            margin: 0 10px;
        }
    }
    .center{
       background-color: #ffffff;
        width: 35%;
        height: 100%;
        border-radius: 8px;
        padding: 20px 30px;
        &-title{
            font-size: 16px;
            font-weight: 700;
        }
        .inputItem{
            padding: 10px 20px 30px 20px;
            background-color: #deebff;
            border-radius: 5px;
            margin: 15px;
            cursor: move;
            position: relative;
            .form-title{
                margin-bottom: 10px;
                margin-left: 5px;
            }
            .delItem{
                width: 30px;
                height: 30px;
                display: flex;
                justify-content: center;
                align-items: center;
                border-radius: 50%;
                box-shadow: 0 2px 4px 0 hsla(0,0%,63.9%,.5);
                background-color: #ffffff;
                color: #001529;
                text-align: center;
                position: absolute;
                bottom: -10px;
                right: 10px;
            }
            .delItem:hover{
                cursor: pointer;
                background-color: #4169E1;
                color: #ffffff;
            }
        }
        .draggable{
            height: 100%;
        }
    }
    .right{
        width: 15%;
        height: 100%;
        position: absolute;
        right: 0;
        .rightContent{
            background-color: #ffffff;
            padding: 10px 20px;
        }
    }
}

.title{
    font-size: 20px;
    font-weight: bold;
    margin-bottom: 20px;
}
</style>