表单构建

This commit is contained in:
zhang zhuo 2025-11-27 16:25:03 +08:00
parent 013ebbbf13
commit 76e925b214
7 changed files with 433 additions and 172 deletions

View File

@ -93,5 +93,6 @@ function add() {
.add {
padding: 0 10px 10px 20px;
text-align: center;
}
</style>

View File

@ -11,14 +11,12 @@ import errorHandler from "@/utils/errorHandler";
import piDialog from "@/components/piDialog"
import piTable from "@/components/piTable"
import piUpload from "@/components/piUpload"
export default {
install(app: App) {
// 注册全局组件
app.component('piDialog', piDialog)
app.component('piTable', piTable)
app.component('piUpload', piUpload)
//注册全局指令
app.directive('auth', auth)

View File

@ -11,22 +11,47 @@
<div class="box">
<el-form-item class="item" :class="{'active': curIndex===index}"
@click="clickComp(element,index)" :label="fields[index].title"
:style="{width: fields[index].width}" :required="fields[index].required">
<el-input v-if="fields[index].name==='text'" v-model="fields[index].value"
v-bind="fields[index].props"></el-input>
<el-input v-if="fields[index].name==='textarea'" type="textarea"
v-model="fields[index].value" v-bind="fields[index].props"></el-input>
<el-input v-if="fields[index].name==='password'" type="password"
v-model="fields[index].value" v-bind="fields[index].props"
show-password></el-input>
<el-input-number v-if="fields[index].name==='number'" v-model="fields[index].value"
v-bind="fields[index].props"></el-input-number>
:required="fields[index].required">
<component :is="getComponent(fields[index].name)" v-model="fields[index].value"
v-bind="fields[index].props" :style="{width: fields[index].width}">
<template v-if="fields[index].name === 'select'" #default>
<el-option v-for="(op, idx) in fields[index].options"
:key="idx" :label="op.label" :value="op.value"/>
</template>
<template v-else-if="fields[index].name === 'radio'" #default>
<el-radio-button v-if="fields[index].style === 'button'"
v-for="(op, idx) in fields[index].options"
:key="idx" :label="op.label" :value="op.value"/>
<el-radio v-else v-for="(op, id) in fields[index].options"
:key="id" :label="op.label" :value="op.value"/>
</template>
<template v-else-if="fields[index].name === 'checkbox'" #default>
<el-checkbox-button v-if="fields[index].style === 'button'"
v-for="(op, idx) in fields[index].options"
:key="idx" :label="op.label" :value="op.value"/>
<el-checkbox v-else v-for="(op, id) in fields[index].options"
:key="id" :label="op.label" :value="op.value"/>
</template>
<template v-if="fields[index].name === 'upload' && (fields[index].props.listType === 'text' || fields[index].props.listType === 'picture')" #trigger>
<el-button type="primary" icon="pi-icon-upload-file">
{{ fields[index].btnText }}
</el-button>
</template>
<template v-if="fields[index].name === 'upload' && fields[index].props.listType === 'picture-card'" #trigger>
<el-icon><component :is="'el-icon-plus'"/></el-icon>
</template>
<template v-if="fields[index].name === 'upload' && fields[index].tip" #tip>
<div class="el-upload__tip">
{{ fields[index].tip }}
</div>
</template>
</component>
</el-form-item>
<div v-if="curIndex===index" class="tools-box">
<el-icon size="22" class="icon-box remove" @click="delComp(index)">
<component :is="'pi-icon-shan-chu'"/>
</el-icon>
<el-icon size="20" class="icon-box copy mt5" @click="copyComp(index)">
<el-icon size="20" class="icon-box copy ml5" @click="copyComp(index)">
<component :is="'pi-icon-fu-zhi'"/>
</el-icon>
</div>
@ -43,6 +68,11 @@
import draggable from 'vuedraggable'
import {nextTick, ref} from "vue";
import tools from "@/utils/tools"
import {getComponent} from "./config"
import piIcon from "@/components/piIcon"
import PiAsset from "@/components/piAsset"
defineExpose({clickAddComp})
const props = defineProps({
fields: {type: Array, default: []},
@ -68,6 +98,14 @@ function addComp(e) {
emit('change', curComp.value)
}
//
function clickAddComp(e) {
props.fields.push(e)
curIndex.value = props.fields.length-1
curComp.value = e
emit('change', curComp.value)
}
//
function clickComp(element, index) {
if (curIndex.value === index) {
@ -155,17 +193,16 @@ function copyComp(index) {
.item {
padding: 12px 10px;
background: #f6f7ff;
border: 1px dashed #f6f7ff;
border: 1px;
border-radius: 3px;
position: relative;
}
.tools-box {
position: absolute;
right: -20px;
top: 0;
display: inline-grid;
right: 10px;
top: -16px;
display: inline-block;
padding: 5px;
border-radius: 4px;
@ -181,8 +218,8 @@ function copyComp(index) {
color: var(--el-color-primary);
}
.mt5 {
margin-top: 5px;
.ml5 {
margin-left: 5px;
}
.disabled {

View File

@ -0,0 +1,226 @@
/**
* fForm.name
*/
export const fieldEditors = {
text: [
{key: 'width', label: '组件宽度', type: 'input'},
{key: 'props.placeholder', label: '占位提示', type: 'input'},
{key: 'props.prefixIcon', label: '前图标', type: 'icon'},
{key: 'props.suffixIcon', label: '后图标', type: 'icon'},
{key: 'props.minlength', label: '最少输入', type: 'number'},
{key: 'props.maxlength', label: '最多输入', type: 'number'},
{key: 'props.showWordLimit', label: '显示字数', type: 'switch'},
{key: 'props.clearable', label: '能否清空', type: 'switch'},
{key: 'props.readonly', label: '是否只读', type: 'switch'},
{key: 'props.disabled', label: '是否禁用', type: 'switch'},
],
textarea: [
{key: 'width', label: '组件宽度', type: 'input'},
{key: 'props.placeholder', label: '占位提示', type: 'input'},
{key: 'props.rows', label: '默认行数', type: 'number'},
{key: 'props.minlength', label: '最少输入', type: 'number'},
{key: 'props.maxlength', label: '最多输入', type: 'number'},
{key: 'props.showWordLimit', label: '显示字数', type: 'switch'},
{key: 'props.clearable', label: '能否清空', type: 'switch'},
{key: 'props.readonly', label: '是否只读', type: 'switch'},
{key: 'props.disabled', label: '是否禁用', type: 'switch'},
],
password: [
{key: 'width', label: '组件宽度', type: 'input'},
{key: 'props.placeholder', label: '占位提示', type: 'input'},
{key: 'props.prefixIcon', label: '前图标', type: 'icon'},
{key: 'props.suffixIcon', label: '后图标', type: 'icon'},
{key: 'props.minlength', label: '最少输入', type: 'number'},
{key: 'props.maxlength', label: '最多输入', type: 'number'},
{key: 'props.showWordLimit', label: '显示字数', type: 'switch'},
{key: 'props.clearable', label: '能否清空', type: 'switch'},
{key: 'props.readonly', label: '是否只读', type: 'switch'},
{key: 'props.disabled', label: '是否禁用', type: 'switch'},
],
number: [
{key: 'width', label: '组件宽度', type: 'input'},
{key: 'props.placeholder', label: '占位提示', type: 'input'},
{key: 'props.precision', label: '精度', type: 'number'},
{key: 'props.step', label: '步长', type: 'number'},
{key: 'props.stepStrictly', label: '严格步数', type: 'switch'},
{
key: 'props.controlsPosition', label: '按钮位置', type: 'radio', options: [
{label: '默认', value: ''},
{label: '右侧', value: 'right'},
]
},
{key: 'props.readonly', label: '是否只读', type: 'switch'},
{key: 'props.disabled', label: '是否禁用', type: 'switch'},
],
select: [
{key: 'width', label: '组件宽度', type: 'input'},
{key: 'props.placeholder', label: '占位提示', type: 'input'},
{key: 'props.multiple', label: '是否多选', type: 'switch'},
{key: 'props.filterable', label: '能否筛选', type: 'switch'},
{key: 'props.disabled', label: '是否禁用', type: 'switch'},
],
cascader: [
{key: 'width', label: '组件宽度', type: 'input'},
{key: 'props.placeholder', label: '占位提示', type: 'input'},
{key: 'props.separator', label: '分隔符', type: 'input'},
{key: 'props.showAllLevels', label: '展示全路径', type: 'switch'},
{key: 'props.clearable', label: '能否清空', type: 'switch'},
{key: 'props.props.multiple', label: '是否多选', type: 'switch'},
{key: 'props.filterable', label: '能否筛选', type: 'switch'},
{key: 'props.disabled', label: '是否禁用', type: 'switch'},
],
radio: [
{
key: 'style', label: '选项样式', type: 'radio', options: [
{label: '默认', value: ''},
{label: '按钮', value: 'button'},
]
},
{key: 'props.disabled', label: '是否禁用', type: 'switch'},
],
checkbox: [
{key: 'props.min', label: '最少应选', type: 'number'},
{key: 'props.max', label: '最多可选', type: 'number'},
{
key: 'style', label: '选项样式', type: 'radio', options: [
{label: '默认', value: ''},
{label: '按钮', value: 'button'},
]
},
{key: 'props.disabled', label: '是否禁用', type: 'switch'},
],
switch: [
{key: 'props.activeText', label: '打开提示', type: 'input'},
{key: 'props.inactiveText', label: '关闭提示', type: 'input'},
{key: 'props.activeValue', label: '打开值', type: 'input'},
{key: 'props.inactiveValue', label: '关闭值', type: 'input'},
{key: 'props.activeActionIcon', label: '打开图标', type: 'icon'},
{key: 'props.inactiveActionIcon', label: '关闭图标', type: 'icon'},
{key: 'props.disabled', label: '是否禁用', type: 'switch'},
],
slider: [
{key: 'width', label: '组件宽度', type: 'input'},
{key: 'props.min', label: '最大值', type: 'number'},
{key: 'props.max', label: '最小值', type: 'number'},
{key: 'props.step', label: '步长', type: 'number'},
{key: 'props.showStops', label: '显示间隔点', type: 'switch'},
{key: 'props.range', label: '范围选择', type: 'switch'},
{key: 'props.disabled', label: '是否禁用', type: 'switch'},
],
rate: [
{key: 'props.max', label: '最大值', type: 'number'},
{key: 'props.allowHalf', label: '是否半选', type: 'switch'},
{key: 'props.showScore', label: '显示分数', type: 'switch'},
{key: 'props.disabled', label: '是否禁用', type: 'switch'},
],
color: [
{
key: 'props.colorFormat', label: '颜色格式', type: 'select', options: [
{label: 'hsl', value: 'hsl'},
{label: 'hsv', value: 'hsv'},
{label: 'hex(when show-alpha is false)', value: 'hex'},
{label: 'rgb(when show-alpha is true)', value: 'rgb'},
]
},
{key: 'props.showAlpha', label: '透明度', type: 'switch'},
{key: 'props.disabled', label: '是否禁用', type: 'switch'},
],
upload: [
{key: 'props.name', label: '文件字段名', type: 'input'},
{key: 'btnText', label: '按钮文字', type: 'input'},
{key: 'props.accept', label: '文件类型', type: 'input'},
{
key: 'props.listType', label: '列表类型', type: 'radio', options: [
{label: 'text', value: 'text'},
{label: 'picture', value: 'picture'},
{label: 'picture-card', value: 'picture-card'}
]
},
{key: 'tip', label: '提示信息', type: 'input'},
{key: 'props.multiple', label: '是否多选', type: 'switch'},
{key: 'props.autoUpload', label: '自动上传', type: 'switch'},
{key: 'props.disabled', label: '是否禁用', type: 'switch'},
],
date: [
{key: 'width', label: '组件宽度', type: 'input'},
{
key: 'props.type', label: '时间类型', type: 'select', options: [
{label: '日', value: 'date'},
{label: '周', value: 'week'},
{label: '月', value: 'month'},
{label: '年', value: 'year'},
{label: '日期时间', value: 'datetime'},
]
},
{key: 'props.format', label: '显示格式', type: 'input'},
{key: 'props.valueFormat', label: '日期格式', type: 'input'},
{key: 'props.clearable', label: '能否清空', type: 'switch'},
{key: 'props.readonly', label: '是否只读', type: 'switch'},
{key: 'props.disabled', label: '是否禁用', type: 'switch'},
],
daterange: [
{
key: 'props.type', label: '时间类型', type: 'select', options: [
{label: '日期范围', value: 'daterange'},
{label: '月范围', value: 'monthrange'},
{label: '日期时间范围', value: 'datetimerange'},
]
},
{key: 'props.rangeSeparator', label: '分隔符', type: 'input'},
{key: 'props.format', label: '显示格式', type: 'input'},
{key: 'props.valueFormat', label: '日期格式', type: 'input'},
{key: 'props.clearable', label: '能否清空', type: 'switch'},
{key: 'props.readonly', label: '是否只读', type: 'switch'},
{key: 'props.disabled', label: '是否禁用', type: 'switch'},
],
time: [
{key: 'props.editable', label: '可输入', type: 'switch'},
{key: 'props.rangeSeparator', label: '分隔符', type: 'input'},
{key: 'props.clearable', label: '能否清空', type: 'switch'},
{key: 'props.readonly', label: '是否只读', type: 'switch'},
{key: 'props.disabled', label: '是否禁用', type: 'switch'},
],
timerange: [
{key: 'props.editable', label: '可输入', type: 'switch'},
{key: 'props.rangeSeparator', label: '分隔符', type: 'input'},
{key: 'props.clearable', label: '能否清空', type: 'switch'},
{key: 'props.readonly', label: '是否只读', type: 'switch'},
{key: 'props.disabled', label: '是否禁用', type: 'switch'},
]
}
// 'year' | 'years' |'month' | 'months' | 'date' | 'dates' | 'datetime' | 'week' | 'datetimerange' | 'daterange' | 'monthrange' | 'yearrange'
export const getComponent = (type) => {
const map = {
text: 'el-input',
textarea: 'el-input',
password: 'el-input',
number: 'el-input-number',
select: 'el-select',
cascader: 'el-cascader',
radio: 'el-radio-group',
checkbox: 'el-checkbox-group',
switch: 'el-switch',
slider: 'el-slider',
rate: 'el-rate',
color: 'el-color-picker',
upload: 'el-upload',
date: 'el-date-picker',
daterange: 'el-date-picker',
time: 'el-time-picker',
timerange: 'el-time-picker',
icon: 'pi-icon',
asset: 'pi-asset'
}
return map[type] || 'div'
}
export function getByPath(obj, path) {
return path.split('.').reduce((o, k) => o?.[k], obj)
}
export function setByPath(obj, path, value) {
const keys = path.split('.')
let o = obj
keys.slice(0, -1).forEach(k => o = o[k])
o[keys[keys.length - 1]] = value
}

View File

@ -1,9 +1,9 @@
<template>
<el-container class="pi-panel">
<el-main class="main">
<left-panel></left-panel>
<left-panel @addField="addField"></left-panel>
<el-divider direction="vertical" style="height: 100%"/>
<center-panel :fields="fields" :config="config" @change="setField"></center-panel>
<center-panel ref="centerRef" :fields="fields" :config="config" @change="setField"></center-panel>
<el-divider direction="vertical" style="height: 100%"/>
<right-panel :config="config" :curField="curField"></right-panel>
</el-main>
@ -27,12 +27,17 @@ let config = ref({
disabled: false
})
let curField = ref({})
const centerRef = ref(null)
function setField(v) {
curField.value = v
curField.value.field = v.id
}
function addField(e) {
centerRef.value.clickAddComp(e)
}
</script>
<style scoped>

View File

@ -12,7 +12,7 @@
<draggable v-model="inputComps" animation="200" item-key="id" class="go-base" :group="group" :sort="false"
:clone="cloneField">
<template #item="{ element }">
<div class="item">
<div class="item" @click="handleClick(element)">
<div class="tips">松开鼠标组件将添加到此处</div>
<div class="comp">
<el-icon size="20">
@ -34,7 +34,7 @@
<draggable v-model="choiceComps" animation="200" item-key="id" class="go-base" :group="group" :sort="false"
:clone="cloneField">
<template #item="{ element }">
<div class="item">
<div class="item" @click="handleClick(element)">
<div class="tips">松开鼠标组件将添加到此处</div>
<div class="comp">
<el-icon size="20">
@ -56,29 +56,7 @@
<draggable v-model="layoutComps" animation="200" item-key="id" class="go-base" :group="group" :sort="false"
:clone="cloneField">
<template #item="{ element }">
<div class="item">
<div class="tips">松开鼠标组件将添加到此处</div>
<div class="comp">
<el-icon size="20">
<component :is="element.icon"/>
</el-icon>
{{ element.title }}
</div>
</div>
</template>
</draggable>
<div class="title">
<el-text>
<el-icon>
<component :is="'pi-icon-zu-jian'"/>
</el-icon>
内置组件
</el-text>
</div>
<draggable v-model="systemComps" animation="200" item-key="id" class="go-base" :group="group" :sort="false"
:clone="cloneField">
<template #item="{ element }">
<div class="item">
<div class="item" @click="handleClick(element)">
<div class="tips">松开鼠标组件将添加到此处</div>
<div class="comp">
<el-icon size="20">
@ -97,6 +75,8 @@
import draggable from 'vuedraggable'
import {ref} from "vue"
const emit = defineEmits(['addField'])
const group = {name: 'base', pull: 'clone', put: false}
const inputComps = [{
id: "i1",
@ -104,19 +84,9 @@ const inputComps = [{
icon: "pi-icon-line-input",
name: "text",
props: {
placeholder: null,
labelWidth: null,
prefixIcon: null,
suffixIcon: null,
minlength: null,
maxlength: null,
clearable: false,
readonly: false,
disabled: false,
required: false,
showWordLimit: false
type: 'text'
},
rules: [{regex: '', message: '213123'}],
rules: [{regex: '', message: ''}],
width: "100%",
required: false
}, {
@ -125,15 +95,7 @@ const inputComps = [{
icon: "pi-icon-multi-input",
name: "textarea",
props: {
placeholder: null,
labelWidth: null,
minlength: null,
maxlength: null,
readonly: false,
disabled: false,
required: false,
showWordLimit: false,
rows: 3
type: 'textarea'
},
rules: [],
width: "100%",
@ -144,17 +106,8 @@ const inputComps = [{
icon: "pi-icon-lock",
name: "password",
props: {
placeholder: null,
labelWidth: null,
prefixIcon: null,
suffixIcon: null,
minlength: null,
maxlength: null,
clearable: false,
readonly: false,
disabled: false,
required: false,
showWordLimit: false
type: 'password',
showPassword: true
},
rules: [],
width: "100%",
@ -165,7 +118,7 @@ const inputComps = [{
icon: "pi-icon-number-input",
name: "number",
props: {
placeholder: null
controlsPosition: ''
},
rules: [],
width: "100%",
@ -178,91 +131,139 @@ const choiceComps = [{
icon: "pi-icon-select",
name: "select",
props: {},
rules: []
rules: [],
options: [],
width: "100%",
required: false,
}, {
id: "c2",
title: "级联组件",
icon: "pi-icon-cascader",
name: "cascader",
props: {},
rules: []
props: {
props: {
label: 'label',
value: 'value',
multiple: true,
},
options: [{label: '一号楼', value: 'r1', children: [{label: '二单元', value: 'c2'}]}]
},
rules: [],
width: "100%",
required: false,
}, {
id: "c3",
title: "单选组件",
icon: "pi-icon-radio",
name: "radio",
props: {},
rules: []
rules: [],
options: [],
width: "100%",
style: '',
required: false,
}, {
id: "c4",
title: "多选组件",
icon: "pi-icon-checkbox",
name: "checkbox",
props: {},
rules: []
rules: [],
options: [],
style: '',
required: false,
}, {
id: "c5",
title: "开关",
icon: "pi-icon-switch",
name: "switch",
props: {},
rules: []
rules: [],
required: false,
}, {
id: "c6",
title: "滑块",
icon: "pi-icon-slider",
name: "slider",
props: {},
rules: []
rules: [],
width: "100%",
required: false,
}, {
id: "c7",
title: "时间选择",
icon: "pi-icon-time-picker",
name: "time",
props: {},
props: {
format: 'HH:mm:ss',
valueFormat: 'HH:mm:ss'
},
rules: []
}, {
id: "c8",
title: "时间范围",
icon: "pi-icon-time-range",
name: "timerange",
props: {},
props: {
isRange: true,
format: 'HH:mm:ss',
valueFormat: 'HH:mm:ss'
},
rules: []
}, {
id: "c9",
title: "日期选择",
icon: "pi-icon-date-picker",
name: "date",
props: {},
rules: []
props: {
type: "date",
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD'
},
rules: [],
width: "100%",
required: false,
}, {
id: "c10",
title: "日期范围",
icon: "pi-icon-date-range",
name: "daterange",
props: {},
rules: []
props: {
type: "daterange",
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD'
},
rules: [],
width: "100%",
required: false,
}, {
id: "c11",
title: "评分",
icon: "pi-icon-rate",
name: "rate",
props: {},
rules: []
rules: [],
required: false,
}, {
id: "c12",
title: "颜色选择",
icon: "pi-icon-color-picker",
name: "color",
props: {},
rules: []
rules: [],
required: false,
}, {
id: 'c13',
title: "上传",
icon: "pi-icon-upload-file",
name: "upload",
props: {},
rules: []
props: {
listType: 'text',
name: 'file'
},
rules: [],
required: false,
btnText: "点击上传"
}]
const layoutComps = [{
id: 'l1',
@ -279,22 +280,6 @@ const layoutComps = [{
props: {},
rules: []
}];
const systemComps = [{
id: 's1',
title: "资源选择",
icon: "pi-icon-asset-choice",
name: "asset",
props: {},
rules: []
}, {
id: 's2',
title: "图标选择",
icon: "pi-icon-icon-choice",
name: "icon",
props: {},
rules: []
}]
let num = ref(100)
function cloneField(e) {
@ -305,6 +290,10 @@ function cloneField(e) {
return field
}
function handleClick(e) {
emit('addField', cloneField(e))
}
</script>
<style lang="scss" scoped>

View File

@ -10,68 +10,72 @@
<el-form-item label="标题" prop="title">
<el-input v-model="fForm.title"></el-input>
</el-form-item>
<el-form-item v-if="fForm.props?.placeholder !== undefined" label="占位提示"
prop="props.placeholder">
<el-input v-model="fForm.props.placeholder"></el-input>
</el-form-item>
<el-form-item v-if="fForm.props?.labelWidth !== undefined" label="标签宽度"
prop="props.labelWidth">
<el-input v-model="fForm.props.labelWidth"></el-input>
</el-form-item>
<el-form-item v-if="fForm.width !== undefined" label="组件宽度" prop="width">
<el-input v-model="fForm.width"></el-input>
</el-form-item>
<el-form-item label="默认值" prop="value">
<el-input v-model="fForm.value"></el-input>
</el-form-item>
<el-form-item v-if="fForm.props?.prefixIcon !== undefined" label="前图标"
prop="props.prefixIcon">
<pi-icon v-model="fForm.props.prefixIcon" style="width: 100%"></pi-icon>
</el-form-item>
<el-form-item v-if="fForm.props?.suffixIcon !== undefined" label="后图标"
prop="props.suffixIcon">
<pi-icon v-model="fForm.props.suffixIcon" style="width: 100%"></pi-icon>
</el-form-item>
<el-form-item v-if="fForm.props?.minlength !== undefined" label="最少输入"
prop="props.minlength">
<el-input v-model.number="fForm.props.minlength" type="number"></el-input>
</el-form-item>
<el-form-item v-if="fForm.props?.maxlength !== undefined" label="最多输入"
prop="props.maxlength">
<el-input v-model.number="fForm.props.maxlength" type="number"></el-input>
</el-form-item>
<el-form-item v-if="fForm.props?.rows !== undefined" label="默认行数" prop="props.rows">
<el-input v-model.number="fForm.props.rows" type="number"></el-input>
</el-form-item>
<el-form-item v-if="fForm.props?.showWordLimit !== undefined" label="显示字数"
prop="props.showWordLimit">
<el-switch v-model="fForm.props.showWordLimit"/>
</el-form-item>
<el-form-item v-if="fForm.props?.clearable !== undefined" label="能否清空"
prop="props.clearable">
<el-switch v-model="fForm.props.clearable"/>
</el-form-item>
<el-form-item v-if="fForm.props?.readonly !== undefined" label="是否只读" prop="props.readonly">
<el-switch v-model="fForm.props.readonly"/>
</el-form-item>
<el-form-item v-if="fForm.props?.disabled !== undefined" label="是否禁用" prop="props.disabled">
<el-switch v-model="fForm.props.disabled"/>
</el-form-item>
<el-form-item v-if="fForm.required !== undefined" label="是否必填" prop="required">
<template v-for="item in fieldEditors[fForm.name] || []" :key="item.key">
<el-form-item :label="item.label">
<el-input
v-if="item.type === 'input'"
:model-value="getByPath(fForm, item.key)"
@input="val => setByPath(fForm, item.key, val)"
/>
<el-input
v-else-if="item.type === 'number'"
type="number"
:model-value="getByPath(fForm, item.key)"
@input="val => setByPath(fForm, item.key, Number(val))"
/>
<el-switch
v-else-if="item.type === 'switch'"
:model-value="getByPath(fForm, item.key)"
@change="val => setByPath(fForm, item.key, val)"
/>
<el-color-picker v-else-if="item.type === 'color'" show-alpha
:model-value="getByPath(fForm, item.key)"
@change="val => setByPath(fForm, item.key, val)"/>
<el-radio-group v-else-if="item.type === 'radio'"
:model-value="getByPath(fForm, item.key)"
@change="val => setByPath(fForm, item.key, val)">
<el-radio-button v-for="op in item.options" :key="op.value"
:value="op.value">{{ op.label }}
</el-radio-button>
</el-radio-group>
<el-select v-else-if="item.type === 'select'" :model-value="getByPath(fForm, item.key)"
@change="val => setByPath(fForm, item.key, val)">
<el-option v-for="op in item.options" :key="op.value" :value="op.value"
:label="op.label">
</el-option>
</el-select>
<pi-icon
v-else-if="item.type === 'icon'"
:model-value="getByPath(fForm, item.key)"
@update:modelValue="val => setByPath(fForm, item.key, val)"
/>
</el-form-item>
</template>
<el-form-item label="是否必填" v-if="fForm.required !== undefined">
<el-switch v-model="fForm.required"/>
</el-form-item>
<el-divider>
<template #default>
正则校验
</template>
</el-divider>
<pi-draggable v-model="fForm.rules" item-key="regex">
<template v-if="fForm.options !== undefined">
<el-divider>选项</el-divider>
<pi-draggable v-model="fForm.options" item-key="label" :template="{label:'', value:''}">
<template #default="scope">
<div style="display:flex;">
<el-input v-model="scope.element.label" placeholder="label"/>
<el-input v-model="scope.element.value" placeholder="value"/>
</div>
</template>
</pi-draggable>
</template>
<el-divider>正则校验</el-divider>
<pi-draggable v-model="fForm.rules" item-key="regex" :template="{regex:'',message:''}">
<template #default="scope">
<el-form-item label="表达式" prop="regex">
<el-input v-model="scope.element.regex"></el-input>
<el-form-item label="表达式">
<el-input v-model="scope.element.regex"/>
</el-form-item>
<el-form-item label="错误提示" prop="message" style="margin-bottom: 0;">
<el-input v-model="scope.element.message"></el-input>
<el-form-item label="错误提示">
<el-input v-model="scope.element.message"/>
</el-form-item>
</template>
</pi-draggable>
@ -119,14 +123,14 @@
import {computed, ref} from "vue";
import piIcon from "@/components/piIcon"
import piDraggable from "@/components/piDraggable"
import {fieldEditors, getByPath, setByPath} from "./config"
let activeName = ref("field")
const props = defineProps({
curField: {type: Object, default: {}},
config: {type: Object, default: {}}
})
const emit = defineEmits(['update:modelValue'])
let activeName = ref("field")
let cForm = computed({
get() {
return props.config
@ -135,7 +139,6 @@ let cForm = computed({
this.$emit('update:modelValue', value)
}
})
let fForm = computed({
get() {
return props.curField
@ -144,19 +147,21 @@ let fForm = computed({
this.$emit('update:modelValue', value)
}
})
</script>
<style lang="scss" scoped>
.panel {
width: 350px
}
:deep(.el-scrollbar__bar) {
display: none !important;
}
:deep(.el-scrollbar__wrap) {
scrollbar-width: none;
}
:deep(.el-scrollbar__wrap::-webkit-scrollbar) {
display: none !important;
}