表单构建

This commit is contained in:
zhang zhuo 2025-11-28 17:53:35 +08:00
parent b363f2ffaa
commit 62b0743abd
7 changed files with 172 additions and 131 deletions

View File

@ -4,7 +4,8 @@
<el-main class="h100"> <el-main class="h100">
<el-form class="h100" :label-position="config.labelPosition" :label-width="config.labelWidth" <el-form class="h100" :label-position="config.labelPosition" :label-width="config.labelWidth"
:size="config.size" :disabled="config.disabled"> :size="config.size" :disabled="config.disabled">
<drag :fields="fields" @change="changeHandle" name="page" v-model:curId="curId"/> <drag :fields="fields" @change="changeHandle" name="page" v-model:curKey="curKey"
v-model:counter="counter"/>
</el-form> </el-form>
</el-main> </el-main>
</el-scrollbar> </el-scrollbar>
@ -12,16 +13,27 @@
</template> </template>
<script setup> <script setup>
import {ref} from "vue" import {computed, ref} from "vue"
import drag from "./drag.vue" import drag from "./drag.vue"
defineExpose({clickAddComp}) defineExpose({clickAddComp})
const props = defineProps({ const props = defineProps({
fields: {type: Array, default: []}, fields: {type: Array, default: []},
config: {type: Object, default: {}} config: {type: Object, default: {}},
counter: {type: Number, default: 0}
})
const emit = defineEmits(['change', "update:counter"])
let curKey = ref("")
const counter = computed({
get() {
return props.counter
},
set(value) {
emit('update:counter', value)
}
}) })
const emit = defineEmits(['change'])
let curId = ref("")
// //
function clickAddComp(e) { function clickAddComp(e) {

View File

@ -188,6 +188,7 @@ export const fieldEditors = {
{key: 'props.disabled', label: '是否禁用', type: 'switch'}, {key: 'props.disabled', label: '是否禁用', type: 'switch'},
], ],
button: [ button: [
{key: 'labelWidth', label: '标签宽度', type: 'number'},
{key: 'btnText', label: '按钮文字', type: 'input'}, {key: 'btnText', label: '按钮文字', type: 'input'},
{ {
key: 'props.type', label: '按钮类型', type: 'select', options: [ key: 'props.type', label: '按钮类型', type: 'select', options: [
@ -205,6 +206,9 @@ export const fieldEditors = {
{key: 'props.round', label: '圆角按钮', type: 'switch'}, {key: 'props.round', label: '圆角按钮', type: 'switch'},
{key: 'props.circle', label: '圆形按钮', type: 'switch'}, {key: 'props.circle', label: '圆形按钮', type: 'switch'},
{key: 'props.disabled', label: '是否禁用', type: 'switch'}, {key: 'props.disabled', label: '是否禁用', type: 'switch'},
],
layout: [
{key: 'props.gutter', label: '分栏间隔', type: 'number'},
] ]
} }
// 'year' | 'years' |'month' | 'months' | 'date' | 'dates' | 'datetime' | 'week' | 'datetimerange' | 'daterange' | 'monthrange' | 'yearrange' // 'year' | 'years' |'month' | 'months' | 'date' | 'dates' | 'datetime' | 'week' | 'datetimerange' | 'daterange' | 'monthrange' | 'yearrange'

View File

@ -1,25 +1,26 @@
<template> <template>
<draggable :list="fields" :scroll="true" animation="200" item-key="id" class="w100 h100" <draggable :list="fields" :scroll="true" animation="200" item-key="id" class="w100 h100"
:group="group" @update:list="fields = $event" @add="addComp" @sort="sortComp" :group="group" @update:list="fields = $event" @add="addComp" @sort="sortComp"
ghostClass="ghostClass" :class="{empty: fields.length === 0}"> ghostClass="ghostClass" :class="{empty: fields.length === 0, layout: isRow}">
<template #item="{ element, index }"> <template #item="{ element, index }">
<div class="box w100"> <el-col :span="element.span || 24" class="col">
<el-row v-if="element.name === 'layout'" class="item row" :class="{'active': curr===element.field}" <el-row v-if="element.name === 'layout'" class="item row" :class="{'active': curr===element.id}"
@click.stop="clickComp(element)" v-bind="element.props"> @click.stop="clickComp(element)" v-bind="element.props">
<span class="name">{{ element.field }}</span> <span class="name">{{ element.field_name }}</span>
<drag :fields="element.children" @change="changeHandle" :name="element.field" v-model:curId="curr"/> <drag :fields="element.children" v-model:curKey="curr" :name="element.field_name"
:is-row="true" v-model:counter="counter" @change="changeHandle"/>
</el-row> </el-row>
<item v-else class="item" :class="{'active': curr===element.field}" :field="element" <item v-else class="item" :class="{'active': curr===element.id}" :field="element"
@click.stop="clickComp(element)"/> @click.stop="clickComp(element)"/>
<div v-if="curr===element.field" class="tools-box"> <div v-if="curr===element.id" class="tools-box">
<el-icon size="22" class="icon-box remove" @click="delComp(index)"> <el-icon size="22" class="icon-box remove" @click.stop="delComp(index)">
<component :is="'pi-icon-shan-chu'"/> <component :is="'pi-icon-shan-chu'"/>
</el-icon> </el-icon>
<el-icon size="20" class="icon-box copy ml5" @click="copyComp(index)"> <el-icon size="20" class="icon-box copy ml5" @click.stop="copyComp(index)">
<component :is="'pi-icon-fu-zhi'"/> <component :is="'pi-icon-fu-zhi'"/>
</el-icon> </el-icon>
</div> </div>
</div> </el-col>
</template> </template>
</draggable> </draggable>
</template> </template>
@ -27,25 +28,36 @@
<script setup> <script setup>
import draggable from 'vuedraggable' import draggable from 'vuedraggable'
import {computed, nextTick} from "vue" import {computed, nextTick} from "vue"
import tools from "@/utils/tools"
import item from "./item.vue" import item from "./item.vue"
import drag from "./drag.vue" import drag from "./drag.vue"
import tools from "@/utils/tools.js"
const emit = defineEmits(['change', 'update:curId']) const emit = defineEmits(['change', 'update:curKey', 'update:counter'])
const props = defineProps({ const props = defineProps({
fields: {type: Array, default: []}, fields: {type: Array, default: []},
name: {type: String, default: 'page'}, curKey: {type: String, default: ''},
curId: {type: String, default: ''}, isRow: {type: Boolean, default: false},
counter: {type: Number, default: 0},
name: {type: String, default: 'page'}
}) })
const group = {name: props.name, pull: true, put: true} const group = {name: props.name, pull: true, put: true}
const curr = computed({ const curr = computed({
get() { get() {
return props.curId return props.curKey
}, },
set(value) { set(value) {
emit('update:curId', value) emit('update:curKey', value)
}
})
const counter = computed({
get() {
return props.counter
},
set(value) {
emit('update:counter', value)
} }
}) })
@ -53,7 +65,7 @@ const curr = computed({
function sortComp(e) { function sortComp(e) {
let element = props.fields[e.newIndex] let element = props.fields[e.newIndex]
if (element) { if (element) {
curr.value = element.field curr.value = element.id
emit('change', element) emit('change', element)
} }
} }
@ -61,16 +73,19 @@ function sortComp(e) {
// //
function addComp(e) { function addComp(e) {
let element = props.fields[e.newIndex] let element = props.fields[e.newIndex]
curr.value = element.field if (props.isRow) {
element.span = 12
}
curr.value = element.id
emit('change', element) emit('change', element)
} }
// //
function clickComp(element) { function clickComp(element) {
if (curr.value === element.field) { if (curr.value === element.id) {
return; return;
} }
curr.value = element.field curr.value = element.id
emit('change', element) emit('change', element)
} }
@ -91,7 +106,7 @@ function delComp(index) {
} }
} }
let element = props.fields[i] || {} let element = props.fields[i] || {}
curr.value = element.field || null curr.value = element.id || null
emit('change', element) emit('change', element)
}) })
} }
@ -100,23 +115,21 @@ function copyComp(index) {
nextTick(() => { nextTick(() => {
// //
const tmp = JSON.parse(JSON.stringify(props.fields[index])) const tmp = JSON.parse(JSON.stringify(props.fields[index]))
tmp.field = increaseNum(tmp.field) tmp.field_name = increaseNum(tmp.field_name)
tmp.id = increaseNum(tmp.id)
counter.value++
props.fields.push(tmp) props.fields.push(tmp)
// //
tools.array.zIndexTo(props.fields, props.fields.length - 1, index + 1) tools.array.zIndexTo(props.fields, props.fields.length - 1, index + 1)
let element = props.fields[index + 1] let element = props.fields[index + 1]
element.field = increaseNum(element.field) curr.value = element.id
curr.value = element.field
emit('change', element) emit('change', element)
}) })
} }
function increaseNum(str) { function increaseNum(str) {
return str.replace(/(\d+)(?!.*\d)/, (match) => { return str.replace(/(\d+)(?!.*\d)/, () => {
// 0 return props.counter.toString();
const len = match.length;
const num = (parseInt(match, 10) + 1).toString();
return num.padStart(len, '0');
}); });
} }
@ -139,6 +152,10 @@ function changeHandle(e) {
} }
} }
:deep(.el-form-item,.el-form-item--default) {
margin-bottom: 0 !important;
}
.w100 { .w100 {
width: 100%; width: 100%;
} }
@ -149,6 +166,11 @@ function changeHandle(e) {
.empty { .empty {
padding: 40px 0; padding: 40px 0;
display: block !important;
}
.layout {
display: contents;
} }
.active { .active {
@ -156,61 +178,60 @@ function changeHandle(e) {
color: #787be8; color: #787be8;
} }
.box { .col {
position: relative; position: relative;
cursor: pointer; }
.item { .item {
padding: 12px 10px; padding: 12px 10px;
border: 1px; border: 1px;
border-radius: 3px; border-radius: 3px;
position: relative; position: relative;
} margin-bottom: 18px !important;
}
.row { .row {
border: 1px dashed #c1c1c1; border: 1px dashed #c1c1c1;
margin-bottom: 18px; padding: 20px 12px;
padding: 20px 12px;
.name { .name {
position: absolute;
top: 0;
left: 0;
font-size: 12px;
color: #bbb;
display: inline-block;
padding: 0 6px;
}
}
.tools-box {
position: absolute; position: absolute;
right: 10px; top: 0;
top: -16px; left: 0;
font-size: 12px;
color: #bbb;
display: inline-block; display: inline-block;
padding: 5px; padding: 0 6px;
border-radius: 4px; }
}
.icon-box { .tools-box {
cursor: pointer; position: absolute;
} right: 10px;
top: -16px;
display: inline-block;
padding: 5px;
border-radius: 4px;
.icon-box.remove { .icon-box {
color: var(--el-color-danger); cursor: pointer;
} }
.icon-box.copy { .icon-box.remove {
color: var(--el-color-primary); color: var(--el-color-danger);
} }
.ml5 { .icon-box.copy {
margin-left: 5px; color: var(--el-color-primary);
} }
.disabled { .ml5 {
cursor: no-drop; margin-left: 5px;
color: #c3c3c3; }
}
.disabled {
cursor: no-drop;
color: #c3c3c3;
} }
} }
</style> </style>

View File

@ -1,9 +1,10 @@
<template> <template>
<el-container class="pi-panel"> <el-container class="pi-panel">
<el-main class="main"> <el-main class="main">
<left-panel @addField="addField"></left-panel> <left-panel @addField="addField" v-model:counter="counter"></left-panel>
<el-divider direction="vertical" style="height: 100%"/> <el-divider direction="vertical" style="height: 100%"/>
<center-panel ref="centerRef" :fields="fields" :config="config" @change="setField"></center-panel> <center-panel ref="centerRef" :fields="fields" :config="config" @change="setField"
v-model:counter="counter"></center-panel>
<el-divider direction="vertical" style="height: 100%"/> <el-divider direction="vertical" style="height: 100%"/>
<right-panel :config="config" :curField="curField" @save="tempSave"></right-panel> <right-panel :config="config" :curField="curField" @save="tempSave"></right-panel>
</el-main> </el-main>
@ -30,14 +31,19 @@ let config = ref({
disabled: false disabled: false
}) })
let curField = ref({}) let curField = ref({})
let counter = ref(0)
onMounted(() => { onMounted(() => {
fields.value = tools.data.get("FORM-DATA") || [] fields.value = tools.data.get("FORM-FIELDS-DATA") || []
const _config = tools.data.get("FORM-CONFIG-DATA")
if (_config) {
config.value = _config
}
counter.value = tools.data.get("FORM-COUNTER-DATA") || 0
}) })
function setField(v) { function setField(v) {
curField.value = v curField.value = v
curField.value.field = v.id
} }
function addField(e) { function addField(e) {
@ -46,7 +52,9 @@ function addField(e) {
function tempSave() { function tempSave() {
console.log("保存参数", fields.value) console.log("保存参数", fields.value)
tools.data.set("FORM-DATA", fields.value) tools.data.set("FORM-FIELDS-DATA", fields.value)
tools.data.set("FORM-CONFIG-DATA", config.value)
tools.data.set("FORM-COUNTER-DATA", counter.value)
proxy.$message.success("保存成功") proxy.$message.success("保存成功")
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<el-form-item :label="field.title" :required="field.required"> <el-form-item :label="field.title" :required="field.required" :label-width="field.labelWidth">
<component :is="getComponent(field.name)" v-model="field.value" v-bind="field.props" <component :is="getComponent(field.name)" v-model="field.value" v-bind="field.props"
:style="{width: field.width}"> :style="{width: field.width}">
<template v-if="field.name === 'select'" #default> <template v-if="field.name === 'select'" #default>
@ -13,7 +13,8 @@
<template v-else-if="field.name === 'checkbox'" #default> <template v-else-if="field.name === 'checkbox'" #default>
<el-checkbox-button v-if="field.style === 'button'" v-for="(op, idx) in field.options" :key="idx" <el-checkbox-button v-if="field.style === 'button'" v-for="(op, idx) in field.options" :key="idx"
:label="op.label" :value="op.value"/> :label="op.label" :value="op.value"/>
<el-checkbox v-else v-for="(op, id) in field.options" :key="id" :label="op.label" :value="op.value"/> <el-checkbox v-else v-for="(op, id) in field.options" :key="id" :label="op.label"
:value="op.value"/>
</template> </template>
<template <template
v-if="field.name === 'upload' && (field.props.listType === 'text' || field.props.listType === 'picture')" v-if="field.name === 'upload' && (field.props.listType === 'text' || field.props.listType === 'picture')"
@ -33,8 +34,7 @@
{{ field.tip }} {{ field.tip }}
</div> </div>
</template> </template>
<template v-if="field.name === 'button' && field.btnText" <template v-if="field.name === 'button' && field.btnText" #default>
#default>
{{ field.btnText }} {{ field.btnText }}
</template> </template>
</component> </component>
@ -49,6 +49,6 @@ const props = defineProps({
}) })
</script> </script>
<style scoped> <style lang="scss" scoped>
</style> </style>

View File

@ -9,7 +9,7 @@
输入组件 输入组件
</el-text> </el-text>
</div> </div>
<draggable v-model="inputComps" animation="200" item-key="id" class="go-base" :group="group" :sort="false" <draggable v-model="inputComps" animation="200" item-key="name" class="go-base" :group="group" :sort="false"
:clone="cloneField"> :clone="cloneField">
<template #item="{ element }"> <template #item="{ element }">
<div class="item" @click="handleClick(element)"> <div class="item" @click="handleClick(element)">
@ -31,7 +31,7 @@
选择组件 选择组件
</el-text> </el-text>
</div> </div>
<draggable v-model="choiceComps" animation="200" item-key="id" class="go-base" :group="group" :sort="false" <draggable v-model="choiceComps" animation="200" item-key="name" class="go-base" :group="group" :sort="false"
:clone="cloneField"> :clone="cloneField">
<template #item="{ element }"> <template #item="{ element }">
<div class="item" @click="handleClick(element)"> <div class="item" @click="handleClick(element)">
@ -53,7 +53,7 @@
布局组件 布局组件
</el-text> </el-text>
</div> </div>
<draggable v-model="layoutComps" animation="200" item-key="id" class="go-base" :group="group" :sort="false" <draggable v-model="layoutComps" animation="200" item-key="name" class="go-base" :group="group" :sort="false"
:clone="cloneField"> :clone="cloneField">
<template #item="{ element }"> <template #item="{ element }">
<div class="item" @click="handleClick(element)"> <div class="item" @click="handleClick(element)">
@ -73,13 +73,15 @@
<script setup> <script setup>
import draggable from 'vuedraggable' import draggable from 'vuedraggable'
import {ref} from "vue" import {computed, ref} from "vue"
const emit = defineEmits(['addField']) const emit = defineEmits(['addField', 'update:counter'])
const props = defineProps({
counter: {type: Number, default: 0}
})
const group = {name: 'base', pull: 'clone', put: false} const group = {name: 'base', pull: 'clone', put: false}
const inputComps = [{ const inputComps = [{
id: "i1",
title: "单行文本", title: "单行文本",
icon: "pi-icon-line-input", icon: "pi-icon-line-input",
name: "text", name: "text",
@ -90,7 +92,6 @@ const inputComps = [{
width: "100%", width: "100%",
required: false required: false
}, { }, {
id: "i2",
title: "多行文本", title: "多行文本",
icon: "pi-icon-multi-input", icon: "pi-icon-multi-input",
name: "textarea", name: "textarea",
@ -101,7 +102,6 @@ const inputComps = [{
width: "100%", width: "100%",
required: false required: false
}, { }, {
id: "i3",
title: "密码", title: "密码",
icon: "pi-icon-lock", icon: "pi-icon-lock",
name: "password", name: "password",
@ -113,7 +113,6 @@ const inputComps = [{
width: "100%", width: "100%",
required: false required: false
}, { }, {
id: "i4",
title: "计数器", title: "计数器",
icon: "pi-icon-number-input", icon: "pi-icon-number-input",
name: "number", name: "number",
@ -126,7 +125,6 @@ const inputComps = [{
value: 1 value: 1
}] }]
const choiceComps = [{ const choiceComps = [{
id: "c1",
title: "下拉组件", title: "下拉组件",
icon: "pi-icon-select", icon: "pi-icon-select",
name: "select", name: "select",
@ -136,7 +134,6 @@ const choiceComps = [{
width: "100%", width: "100%",
required: false, required: false,
}, { }, {
id: "c2",
title: "级联组件", title: "级联组件",
icon: "pi-icon-cascader", icon: "pi-icon-cascader",
name: "cascader", name: "cascader",
@ -152,7 +149,6 @@ const choiceComps = [{
width: "100%", width: "100%",
required: false, required: false,
}, { }, {
id: "c3",
title: "单选组件", title: "单选组件",
icon: "pi-icon-radio", icon: "pi-icon-radio",
name: "radio", name: "radio",
@ -163,7 +159,6 @@ const choiceComps = [{
style: '', style: '',
required: false, required: false,
}, { }, {
id: "c4",
title: "多选组件", title: "多选组件",
icon: "pi-icon-checkbox", icon: "pi-icon-checkbox",
name: "checkbox", name: "checkbox",
@ -173,7 +168,6 @@ const choiceComps = [{
style: '', style: '',
required: false, required: false,
}, { }, {
id: "c5",
title: "开关", title: "开关",
icon: "pi-icon-switch", icon: "pi-icon-switch",
name: "switch", name: "switch",
@ -181,7 +175,6 @@ const choiceComps = [{
rules: [], rules: [],
required: false, required: false,
}, { }, {
id: "c6",
title: "滑块", title: "滑块",
icon: "pi-icon-slider", icon: "pi-icon-slider",
name: "slider", name: "slider",
@ -190,7 +183,6 @@ const choiceComps = [{
width: "100%", width: "100%",
required: false, required: false,
}, { }, {
id: "c7",
title: "时间选择", title: "时间选择",
icon: "pi-icon-time-picker", icon: "pi-icon-time-picker",
name: "time", name: "time",
@ -200,7 +192,6 @@ const choiceComps = [{
}, },
rules: [] rules: []
}, { }, {
id: "c8",
title: "时间范围", title: "时间范围",
icon: "pi-icon-time-range", icon: "pi-icon-time-range",
name: "timerange", name: "timerange",
@ -213,7 +204,6 @@ const choiceComps = [{
width: "100%", width: "100%",
required: false, required: false,
}, { }, {
id: "c9",
title: "日期选择", title: "日期选择",
icon: "pi-icon-date-picker", icon: "pi-icon-date-picker",
name: "date", name: "date",
@ -226,7 +216,6 @@ const choiceComps = [{
width: "100%", width: "100%",
required: false, required: false,
}, { }, {
id: "c10",
title: "日期范围", title: "日期范围",
icon: "pi-icon-date-range", icon: "pi-icon-date-range",
name: "daterange", name: "daterange",
@ -239,7 +228,6 @@ const choiceComps = [{
width: "100%", width: "100%",
required: false, required: false,
}, { }, {
id: "c11",
title: "评分", title: "评分",
icon: "pi-icon-rate", icon: "pi-icon-rate",
name: "rate", name: "rate",
@ -247,7 +235,6 @@ const choiceComps = [{
rules: [], rules: [],
required: false, required: false,
}, { }, {
id: "c12",
title: "颜色选择", title: "颜色选择",
icon: "pi-icon-color-picker", icon: "pi-icon-color-picker",
name: "color", name: "color",
@ -255,7 +242,6 @@ const choiceComps = [{
rules: [], rules: [],
required: false, required: false,
}, { }, {
id: 'c13',
title: "上传", title: "上传",
icon: "pi-icon-upload-file", icon: "pi-icon-upload-file",
name: "upload", name: "upload",
@ -268,7 +254,6 @@ const choiceComps = [{
btnText: "点击上传" btnText: "点击上传"
}] }]
const layoutComps = [{ const layoutComps = [{
id: 'row',
title: "行容器", title: "行容器",
icon: "pi-icon-row-layout", icon: "pi-icon-row-layout",
name: "layout", name: "layout",
@ -277,7 +262,6 @@ const layoutComps = [{
}, },
children: [] children: []
}, { }, {
id: 'l2',
title: "按钮", title: "按钮",
icon: "pi-icon-button", icon: "pi-icon-button",
name: "button", name: "button",
@ -286,13 +270,20 @@ const layoutComps = [{
}, },
btnText: "按钮文字" btnText: "按钮文字"
}]; }];
let num = ref(100) const counter = computed({
get() {
return props.counter
},
set(value) {
emit('update:counter', value)
}
})
function cloneField(e) { function cloneField(e) {
const field = JSON.parse(JSON.stringify(e)); const field = JSON.parse(JSON.stringify(e));
field.id = field.id + num.value; field.field_name = field.name + "_" + counter.value;
field.index = num.value field.id = field.name + counter.value
num.value++ counter.value++
return field return field
} }

View File

@ -4,15 +4,18 @@
<el-tabs v-model="activeName" stretch> <el-tabs v-model="activeName" stretch>
<el-tab-pane label="组件属性" name="field"> <el-tab-pane label="组件属性" name="field">
<el-form v-model="fForm" label-width="90px"> <el-form v-model="fForm" label-width="90px">
<el-form-item v-if="fForm.name !== 'button'" label="字段名" prop="field"> <el-form-item v-if="fForm.name !== 'button' && fForm.name !== 'layout'" label="字段名" prop="field_name">
<el-input v-model="fForm.field"></el-input> <el-input v-model="fForm.field_name"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="标题" prop="title"> <el-form-item v-if="fForm.name !== 'layout'" label="标题" prop="title">
<el-input v-model="fForm.title"></el-input> <el-input v-model="fForm.title"></el-input>
</el-form-item> </el-form-item>
<el-form-item v-if="fForm.name !== 'button'" label="默认值" prop="value"> <el-form-item v-if="fForm.name !== 'button' && fForm.name !== 'layout'" label="默认值" prop="value">
<el-input v-model="fForm.value"></el-input> <el-input v-model="fForm.value"></el-input>
</el-form-item> </el-form-item>
<el-form-item v-if="fForm.span !== undefined" label="栅格列数" prop="span">
<el-slider v-model="fForm.span" :min="1" :max="24"/>
</el-form-item>
<template v-for="item in fieldEditors[fForm.name] || []" :key="item.key"> <template v-for="item in fieldEditors[fForm.name] || []" :key="item.key">
<el-form-item :label="item.label"> <el-form-item :label="item.label">
<el-input <el-input
@ -69,17 +72,19 @@
</template> </template>
</pi-draggable> </pi-draggable>
</template> </template>
<el-divider>正则校验</el-divider> <template v-if="fForm.name !== 'button' && fForm.name !== 'layout'">
<pi-draggable v-model="fForm.rules" item-key="regex" :template="{regex:'',message:''}"> <el-divider>正则校验</el-divider>
<template #default="scope"> <pi-draggable v-model="fForm.rules" item-key="regex" :template="{regex:'',message:''}">
<el-form-item label="表达式"> <template #default="scope">
<el-input v-model="scope.element.regex"/> <el-form-item label="表达式">
</el-form-item> <el-input v-model="scope.element.regex"/>
<el-form-item label="错误提示"> </el-form-item>
<el-input v-model="scope.element.message"/> <el-form-item label="错误提示">
</el-form-item> <el-input v-model="scope.element.message"/>
</template> </el-form-item>
</pi-draggable> </template>
</pi-draggable>
</template>
</el-form> </el-form>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="表单属性" name="form"> <el-tab-pane label="表单属性" name="form">