本文介绍了基于vue+elementui实现自定义表单,左侧字段,中间绘制表单项,右侧是相应控件修改。点击字段添加到中间表单,表单项和右侧select或radio类的内容选项排序通过vuedraggable实现。
效果图
以下是html代码片段
<template>
<div>
<el-row :gutter="20" class="container">
<!-- 左侧字段 -->
<el-col :span="5" class="left-side">
<div class="grid-content bg-purple">
<p class="title">基础字段</p>
<div class="leftMenu">
<div v-for="(item,index) in leftMenu" :key="item.id" class="item" @click="addItem(item,index)">
<el-button size="small">{{ item.label }}</el-button>
</div>
</div>
</div>
</el-col>
<!-- 表单项 -->
<el-col :span="14" class="center">
<template v-if=" 0 == (formList &&formList.length)">
<div class="tips">点击左边的字段进行自定义编辑</div>
</template>
<!-- style="height:calc(100vh - 220px);overflow-y:auto" -->
<!-- <el-form class="forms-content" :model="ruleForm" :rules="rules"> -->
<draggable
group="comp"
:options="{animation: 250}"
v-model="formList"
@end="end"
>
<div
class="temp-content"
@click="activeIndex=index"
:class="activeIndex==index?'temp-active':''"
v-for="(item, index) in formList"
:key="index"
>
<!-- {{item}} -->
<div style="padding:5px 3px 10px 3px">
<el-row style="display:flex;">
<el-col :span="23" class="label">
<span v-if="item.required" style="color:#ff4949; font-size:14px;">*</span>
{{item.label}}
</el-col>
<el-col :span="1" class="text-right pointer iconFont">
<template v-if="activeIndex==index">
<i class="el-icon-delete" @click.stop="delItem(index)"></i>
</template>
</el-col>
</el-row>
</div>
<div>
<!-- 单选 -->
<template v-if="item.type=='radio'">
<el-radio-group v-model="item.value">
<el-radio v-for="(it, idx) in item.options" :key="idx" :label="idx">{{it}}</el-radio>
</el-radio-group>
</template>
<!-- 下拉框 -->
<template v-else-if="item.type=='select'">
<el-select v-model="item.value" clearable :placeholder="'请选择'+item.label" size="small" style="width:100%;">
<el-option
v-for="(it, idx) in item.options"
:key="idx"
:label="it"
:value="idx">
</el-option>
</el-select>
</template>
<!-- 文本框 -->
<template v-else>
<el-input size="small" clearable v-model="item.value" :placeholder="item.placeholder?item.placeholder:'请输入'+item.label"></el-input>
</template>
</div>
</div>
</draggable>
<!-- </el-form> -->
</el-col>
<!-- 右侧自定义内容 -->
<el-col :span="5" class="right-side">
<template v-if="formList&&formList.length>0">
<!-- label-suffix=":" -->
<el-form class="forms" style="margin-top:10px">
<el-form-item label="字段名称">
<el-input clearable v-model="formList[activeIndex].label" size="small"></el-input>
</el-form-item>
<template v-if="formList[activeIndex].type != 'radio'">
<el-form-item label="默认内容">
<!-- 自定义选项 -->
<el-select v-model="dealValue" size="small" style="width:100%; margin-bottom:3px;">
<el-option
v-for="item in dealOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<!-- 默认内容 -->
<el-input v-model="formList[activeIndex].value" clearable placeholder="请输入默认内容" size="small"></el-input>
</el-form-item>
</template>
<!-- 选项 -->
<template v-if="formList[activeIndex].type == 'radio' || formList[activeIndex].type == 'select'">
<el-form-item label="内容选项">
<draggable
group="comptwo"
:options="{animation: 50}"
v-model="formList[activeIndex].options"
@end="end2"
>
<el-row
v-for="(item, index) in formList[activeIndex].options"
:key="index"
>
<el-col :span="2" class="iconFont" style="margin-right:8px">
<i class="el-icon-s-operation operation"></i>
</el-col>
<el-col :span="18">
<el-input clearable v-model="formList[activeIndex].options[index]" size="small"></el-input>
</el-col>
<el-col :span="2" class="iconFont" style="margin-left:8px">
<i class="el-icon-delete delete" @click="delOption(index)"></i>
</el-col>
</el-row>
</draggable>
<div>
<el-button style="width:100%" size="small" @click="addOption"><i class="el-icon-plus"></i>添加新选项</el-button>
</div>
</el-form-item>
</template>
<!-- 是否必填项 -->
<el-form-item label="是否必填" class="form-item">
<el-switch v-model="formList[activeIndex].required"></el-switch>
</el-form-item>
<!-- 文本框的字数 -->
<el-form-item label="字数" v-if="formList[activeIndex].type == 'text' && formList[activeIndex].required == true" style="text-align:center; margin-top:0;">
<el-input size="small" clearable v-model.number="formList[activeIndex].numStart" placeholder="不限" autocomplete="off" style="width:40%;"></el-input>
-
<el-input size="small" clearable v-model.number="formList[activeIndex].numEnd" placeholder="不限" autocomplete="off" style="width:40%;"></el-input>
</el-form-item>
</el-form>
</template>
</el-col>
</el-row>
<el-row :gutter="20" class="btn">
<el-button size="small">取消</el-button>
<el-button size="small" type="primary" @click="getFields">提交</el-button>
</el-row>
</div>
</template>
以下是script代码片段
vuedraggable安装语句npm install vuedraggable
<script>
import draggable from 'vuedraggable'
export default {
components:{
draggable
},
data() {
return {
dealValue:1,
dealOptions:[{label:'自定义', value:1}],
leftMenu: [
{id: "1", label: "名称", type: 'text'},
{id: "2", label: "性别", type: 'radio'},
{id: "3", label: "年龄", type: 'text'},
{id: "4", label: "手机号", type: 'text'},
{id: "5", label: "身份证号", type: 'select'}
],
activeIndex: 0, // 当前编辑的 index
formList: []
}
},
methods: {
// 获取字段数据
getFields() {
if (0 == this.formList.length) {
this.$message.error('需增加模板字段!')
return false
}
let pass = true
let formList = this.formList
for (let i = 0; i < formList.length; i++) {
formList[i]._vModel = 'field' + i
if (!formList[i].label) {
this.activeIndex = i;
this.$message.error('请输入字段名称!')
pass = false
break
}
if (!formList[i].value && formList[i].required) {
this.activeIndex = i;
if(formList[i].type == 'radio' || formList[i].type == 'select' ){
this.$message.error(`请选择${formList[i].label}!`);
} else {
this.$message.error(`请输入${formList[i].label}!`);
}
pass = false
break
}
if (formList[i].type == 'text') {
if(formList[i].numStart == "" || formList[i].value.length >= formList[i].numStart){
if(formList[i].numEnd == "" || formList[i].value.length <= formList[i].numEnd){} else {
this.$message.error(`${formList[i].label}内容需小于${formList[i].numEnd}字!`);
pass = false
break
}
} else {
this.$message.error(`${formList[i].label}内容需大于${formList[i].numStart}字!`);
pass = false
break
}
}
}
//
if (pass) {
console.log(this.formList,'zr')
return this.formList
} else {
return false
}
},
// 新增选项
addOption() {
let idx = this.formList[this.activeIndex].options.length + 1
this.formList[this.activeIndex].options.push('选项' + idx)
},
// 删除选项
delOption(index) {
this.formList[this.activeIndex].options.splice(index, 1)
},
// 删除模板字段
delItem(index) {
this.formList.splice(index, 1)
let leg = this.formList.length
if (leg == index && leg > 0) {
this.activeIndex = index - 1
}
},
// 根据类型获取字段
getFormByType(data) {
let item = {
id:data.id,//唯一值
label: data.label,
required: false,
type: data.type,
placeholder: '',
value:''
}
if(data.type == 'text'){
item.numStart = '';
item.numEnd = '';
}
// 是否单选项
if (data.type == 'radio') {
item.options = ['选项1', '选项2']
}
// 是否下拉选项
if (data.type == 'select') {
item.options = ['选项1', '选项2']
}
return item
},
//新增模板字段
addItem(item){
if(JSON.stringify(this.formList).indexOf(`"id":"${item.id}"`) > -1){return false;}//已存在则不重复
let data = this.getFormByType(item)
this.activeIndex = this.formList.length; //放入的位置,按顺序排下来
this.formList.splice(this.formList.length, 0, data);
},
end(e) {
// 判断移动的项目是否为编辑行
if (e.oldIndex != e.newIndex && this.activeIndex == e.oldIndex) {
this.activeIndex = e.newIndex
}
console.log(this.formList)
},
end2(e) {
console.log(this.formList[this.activeIndex].options)
},
}
}
</script>
以下是css样式片段
<style scoped>
.btn{
text-align: center;
margin:0;
}
.text-red{
color:#f56c6c
}
.iconFont i{
font-size:17px;
}
.forms .el-form-item {
margin-bottom:10px;
}
.forms .label{
font-weight: normal;
font-size:14px;
}
>>>.forms .el-form-item:first-child{
margin-top:0;
}
>>>.forms .el-form-item__label{
line-height:normal !important;
margin-bottom:10px;
font-weight: normal;
width:100%;
text-align: left;
}
>>>.forms .form-item .el-form-item__label{
line-height:40px !important;
width:auto;
}
.forms .el-form-item{
margin-top:20px;
}
.forms .el-input{
font-size:13px;
}
.text-right{
text-align:right;
}
.text-right:hover, .delete:hover{
cursor: pointer;
/* color:#f56c6c; */
}
.label{
font-size: 14px;
}
.tips{
line-height: 50px;
height: 50px;
border: 1px dashed #d9d9d9;
padding-left:20px;
margin-top:10px;
}
.container {
display: flex; /* 启用Flex布局 */
padding:20px;
}
.title{
font-size: 14px;
margin-top:5px;
margin-bottom:15px;
padding-left:5px;
}
.left-side, .right-side {
/* width: 245px; */
width: 24%;
border:1px solid #ccc;
border-radius: 5px;
padding:10px 6px;
}
.leftMenu{
display: flex;
flex-wrap: wrap; /* 可换行 */
text-align: center;
}
.leftMenu .item{
width:50%;
margin-bottom:10px;
}
.leftMenu .item button{
width:90%;
text-align: center;
}
.leftMenu .item button.special{
padding-left:0;
padding-right:0;
}
.center {
flex-grow: 1; /* 中间部分自适应剩余空间 */
min-width: 200px;
/* border:1px solid #ccc; */
/* border-radius: 5px; */
margin:0 15px;
padding:0px !important;
}
.temp-content{
margin-bottom:10px;
padding:10px;
border-radius: 6px;
/* background-color: #f6f6f6; */
border:1px solid #DCDFE6;
}
.temp-active{
background-color: #ecf5ff;
}
.temp-content:hover, .operation:hover, >>>.right-side .el-row{
cursor: move;
}
</style>