表单构建

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 { .add {
padding: 0 10px 10px 20px; padding: 0 10px 10px 20px;
text-align: center;
} }
</style> </style>

View File

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

View File

@ -11,22 +11,47 @@
<div class="box"> <div class="box">
<el-form-item class="item" :class="{'active': curIndex===index}" <el-form-item class="item" :class="{'active': curIndex===index}"
@click="clickComp(element,index)" :label="fields[index].title" @click="clickComp(element,index)" :label="fields[index].title"
:style="{width: fields[index].width}" :required="fields[index].required"> :required="fields[index].required">
<el-input v-if="fields[index].name==='text'" v-model="fields[index].value" <component :is="getComponent(fields[index].name)" v-model="fields[index].value"
v-bind="fields[index].props"></el-input> v-bind="fields[index].props" :style="{width: fields[index].width}">
<el-input v-if="fields[index].name==='textarea'" type="textarea" <template v-if="fields[index].name === 'select'" #default>
v-model="fields[index].value" v-bind="fields[index].props"></el-input> <el-option v-for="(op, idx) in fields[index].options"
<el-input v-if="fields[index].name==='password'" type="password" :key="idx" :label="op.label" :value="op.value"/>
v-model="fields[index].value" v-bind="fields[index].props" </template>
show-password></el-input> <template v-else-if="fields[index].name === 'radio'" #default>
<el-input-number v-if="fields[index].name==='number'" v-model="fields[index].value" <el-radio-button v-if="fields[index].style === 'button'"
v-bind="fields[index].props"></el-input-number> 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> </el-form-item>
<div v-if="curIndex===index" class="tools-box"> <div v-if="curIndex===index" class="tools-box">
<el-icon size="22" class="icon-box remove" @click="delComp(index)"> <el-icon size="22" class="icon-box remove" @click="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 mt5" @click="copyComp(index)"> <el-icon size="20" class="icon-box copy ml5" @click="copyComp(index)">
<component :is="'pi-icon-fu-zhi'"/> <component :is="'pi-icon-fu-zhi'"/>
</el-icon> </el-icon>
</div> </div>
@ -43,6 +68,11 @@
import draggable from 'vuedraggable' import draggable from 'vuedraggable'
import {nextTick, ref} from "vue"; import {nextTick, ref} from "vue";
import tools from "@/utils/tools" import tools from "@/utils/tools"
import {getComponent} from "./config"
import piIcon from "@/components/piIcon"
import PiAsset from "@/components/piAsset"
defineExpose({clickAddComp})
const props = defineProps({ const props = defineProps({
fields: {type: Array, default: []}, fields: {type: Array, default: []},
@ -68,6 +98,14 @@ function addComp(e) {
emit('change', curComp.value) 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) { function clickComp(element, index) {
if (curIndex.value === index) { if (curIndex.value === index) {
@ -155,17 +193,16 @@ function copyComp(index) {
.item { .item {
padding: 12px 10px; padding: 12px 10px;
background: #f6f7ff; border: 1px;
border: 1px dashed #f6f7ff;
border-radius: 3px; border-radius: 3px;
position: relative; position: relative;
} }
.tools-box { .tools-box {
position: absolute; position: absolute;
right: -20px; right: 10px;
top: 0; top: -16px;
display: inline-grid; display: inline-block;
padding: 5px; padding: 5px;
border-radius: 4px; border-radius: 4px;
@ -181,8 +218,8 @@ function copyComp(index) {
color: var(--el-color-primary); color: var(--el-color-primary);
} }
.mt5 { .ml5 {
margin-top: 5px; margin-left: 5px;
} }
.disabled { .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> <template>
<el-container class="pi-panel"> <el-container class="pi-panel">
<el-main class="main"> <el-main class="main">
<left-panel></left-panel> <left-panel @addField="addField"></left-panel>
<el-divider direction="vertical" style="height: 100%"/> <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%"/> <el-divider direction="vertical" style="height: 100%"/>
<right-panel :config="config" :curField="curField"></right-panel> <right-panel :config="config" :curField="curField"></right-panel>
</el-main> </el-main>
@ -27,12 +27,17 @@ let config = ref({
disabled: false disabled: false
}) })
let curField = ref({}) let curField = ref({})
const centerRef = ref(null)
function setField(v) { function setField(v) {
curField.value = v curField.value = v
curField.value.field = v.id curField.value.field = v.id
} }
function addField(e) {
centerRef.value.clickAddComp(e)
}
</script> </script>
<style scoped> <style scoped>

View File

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

View File

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