el-form动态表单
2023-03-23 本文已影响0人
Xbbing
效果
image.png组件使用
<sd-form ref="formref" :config="config" size="mini" border v-model="formData">
<!-- 具名插槽 -->
<template #testSlot>
<el-input v-model="formData.slotName" placeholder="这是自定义表单"></el-input>
</template>
<el-button type="primary" @click="formSave">保 存</el-button>
</sd-form>
数据校验
// 测试保存
formSave() {
this.$refs.formref.validate((valid) => {
if(valid) {
console.log(this.formData)
}
})
}
参数结构
// config为表单数据
// formData为绑定数据结构
created() {
this.config = {
labelWidth: '120px', // label宽度
labelPosition: 'right', // label对齐方式
size: 'medium', // 表单尺寸
formItems: [ // 表单元素
{
label: "自定义表单", //表单名称
name: "slotName", // formData绑定的key
span: 8, // el-col的span
slotName: 'testSlot', // 具名插槽
rules: [ // 校验规则
{required: true, message: "Please input Activity name", trigger: "blur"}
],
},
{
label: "输入框",
name: "name",
component: "input", // 表单类型
span: 8,
options: {
maxlength: "20",
placeholder: "Activity name",
},
rules: [
{required: true, message: "Please input Activity name", trigger: "blur"}
],
requiredHandle: "$.required==true", // 是否需要校验
},
{
label: "栅格(12/24)",
name: "name2",
component: "input",
span: 8,
options: {
placeholder: "span: 12",
}
},
{
label: "栅格(12/24)",
name: "name3",
component: "input",
span: 24,
options: {
placeholder: "span: 12",
}
},
{
label: "级联选择器",
name: "cascader",
component: "cascader",
span: 12,
options: {
items:[
{
label: "Guide",
value: "guide",
children: [
{
label: "Disciplines",
value: "disciplines"
},
{
label: "Consistency",
value: "consistency"
},
]
},
{
label: "Resource",
value: "resource",
children: [
{
label: "Axure Components",
value: "axure"
},
{
label: "Sketch Templates",
value: "sketch"
},
{
label: "Design Documentation",
value: "docs"
}
]
},
{
label: "Component",
value: "component"
},
]
}
},
{
label: "多选框",
name: "checkbox",
component: "checkbox",
span: 12,
tips: "多选框配置加上 name 表示拥有嵌套关系。否则将值“平铺”在form对象",
options: { // 表单具体属性
items:[
{
label: "选项1",
name: "option1"
},
{
label: "选项2",
name: "option2"
}
]
},
hideHandle: "$.required==true"
},
{
label: "多选框组",
name: "checkboxGroup",
component: "checkboxGroup",
span: 24,
options: {
items:[
{
label: "选项1",
value: "option1"
},
{
label: "选项2",
value: "option2"
}
]
},
hideHandle: "$.required==true" // 动态显示隐藏此表单
},
{
label: "单选",
name: "radio",
component: "radio",
options: {
items:[
{
label: "选项1",
value: "1"
},
{
label: "选项2",
value: "2"
}
]
},
hideHandle: "$.required==true"
},
{
label: "开关",
name: "required",
span: 12,
message: "演示如何使用表达式动态显隐和必填,试试打开和关闭开关",
component: "switch",
},
{
label: "日期/时间",
name: "date",
span: 12,
component: "date",
options: {
type: "datetime",
valueFormat: "yyyy-MM-dd HH:mm:ss",
},
rules: [
{required: true, message: "Please input Data", trigger: "change"}
],
},
{
label: "数值",
name: "number",
component: "number",
},
{
label: "颜色",
name: "color",
component: "color",
},
{
label: "评分",
name: "rate",
component: "rate",
}
]
}
this.formData = {
slotName: '自定义表单',
name: '',
name2: '',
name3: '',
cascader: '',
checkbox: {},
checkboxGroup: [],
radio: '1',
required: false,
date: '',
slider: 8,
number: 0,
color: '',
rate: 0
}
}
组件封装
<template>
<el-form class="sd-form" :class="border && 'sd-form-border'" ref="form" :model="form" :size="size" :label-width="config.labelWidth" :label-position="config.labelPosition" v-loading="loading" element-loading-text="Loading...">
<el-row>
<template v-for="(item, index) in config.formItems">
<el-col :span="item.span || 24" v-if="!hideHandle(item)" :key="index">
<sd-title v-if="item.component=='title'" :title="item.label"></sd-title>
<el-form-item v-else :prop="item.name" :rules="rulesHandle(item)">
<template #label>
<span ref="refFormItem" class="refFormItem">{{item.label}}</span>
<el-tooltip v-if="item.tips" :content="item.tips">
<el-icon><el-icon-question-filled /></el-icon>
</el-tooltip>
</template>
<template v-if="item.slotName">
<slot :name="item.slotName"></slot>
</template>
<!-- input -->
<template v-else-if="item.component=='input'" >
<el-input v-model="form[item.name]" :placeholder="item.options.placeholder" clearable :maxlength="item.options.maxlength" show-word-limit></el-input>
</template>
<!-- checkbox -->
<template v-else-if="item.component=='checkbox'" >
<template v-if="item.name" >
<el-checkbox v-model="form[item.name][_item.name]" :label="_item.label" v-for="(_item, _index) in item.options.items" :key="_index"></el-checkbox>
</template>
<template v-else >
<el-checkbox v-model="form[_item.name]" :label="_item.label" v-for="(_item, _index) in item.options.items" :key="_index"></el-checkbox>
</template>
</template>
<!-- checkboxGroup -->
<template v-else-if="item.component=='checkboxGroup'" >
<el-checkbox-group v-model="form[item.name]">
<el-checkbox v-for="_item in item.options.items" :key="_item.value" :label="_item.value">{{_item.label}}</el-checkbox>
</el-checkbox-group>
</template>
<!-- switch -->
<template v-else-if="item.component=='switch'" >
<el-switch v-model="form[item.name]" />
</template>
<!-- select -->
<template v-else-if="item.component=='select'" >
<el-select v-model="form[item.name]" :multiple="item.options.multiple" :placeholder="item.options.placeholder" clearable filterable style="width: 100%;">
<el-option v-for="option in item.options.items" :key="option.value" :label="option.label" :value="option.value"></el-option>
</el-select>
</template>
<!-- cascader -->
<template v-else-if="item.component=='cascader'" >
<el-cascader v-model="form[item.name]" :options="item.options.items" clearable></el-cascader>
</template>
<!-- date -->
<template v-else-if="item.component=='date'" >
<el-date-picker v-model="form[item.name]" :type="item.options.type" :shortcuts="item.options.shortcuts" :default-time="item.options.defaultTime" :value-format="item.options.valueFormat" :placeholder="item.options.placeholder || '请选择'"></el-date-picker>
</template>
<!-- number -->
<template v-else-if="item.component=='number'" >
<el-input-number v-model="form[item.name]" controls-position="right"></el-input-number>
</template>
<!-- radio -->
<template v-else-if="item.component=='radio'" >
<el-radio-group v-model="form[item.name]">
<el-radio v-for="_item in item.options.items" :key="_item.value" :label="_item.value">{{_item.label}}</el-radio>
</el-radio-group>
</template>
<!-- color -->
<template v-else-if="item.component=='color'" >
<el-color-picker v-model="form[item.name]" />
</template>
<!-- rate -->
<template v-else-if="item.component=='rate'" >
<el-rate style="margin-top: 6px;" v-model="form[item.name]"></el-rate>
</template>
<!-- slider -->
<template v-else-if="item.component=='slider'" >
<el-slider v-model="form[item.name]" :marks="item.options.marks"></el-slider>
</template>
<!-- noComponent -->
<template v-else>
<el-tag type="danger">[{{item.component}}] Component not found</el-tag>
</template>
<div v-if="item.message" class="el-form-item-msg">{{item.message}}</div>
</el-form-item>
</el-col>
</template>
<el-col :span="24">
<el-form-item>
<slot>
<el-button type="primary" @click="submit">提交</el-button>
</slot>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script>
import http from "@/utils/request"
import SdTitle from './components/scTitle.vue'
export default {
props: {
modelValue: { type: Object, default: () => {} },
config: { type: Object, default: () => {} },
loading: { type: Boolean, default: false },
size: { type: String, default: 'medium' },
border: { type: Boolean, default: false },
},
data() {
return {
form: {}
}
},
components: {
SdTitle
},
// 给modelValue绑定事件
model: {
prop: 'modelValue',
event: 'getValue'
},
watch:{
// 监听modelValue变化复制给当前绑定元素实现绑定
modelValue: {
immediate: true,
handler(val) {
this.form = val;
}
},
form:{
handler(val){
this.$emit("getValue", val)
},
deep: true,
immediate: true
}
},
computed: {
hasConfig(){
return Object.keys(this.config).length>0
},
hasValue(){
return Object.keys(this.modelValue).length>0
}
},
created() {
},
mounted() {
this.getHeight()
},
methods: {
// 初始化label高度
getHeight() {
this.$nextTick(() => {
if(this.$refs.refFormItem) {
this.$refs.refFormItem.forEach((item, index) => {
const height = item.parentNode.parentNode.offsetHeight
document.querySelectorAll('.refFormItem')[index].style.height = height + 'px'
// this.$forceUpdate()
})
}
})
},
//处理远程选项数据
getData() {
var remoteData = []
this.config.formItems.forEach((item) => {
if(item.options && item.options.remote){
var req = http.get(item.options.remote.api, item.options.remote.data).then(res=>{
item.options.items = res.data
})
remoteData.push(req)
}
})
},
//合并深结构对象
deepMerge(obj1, obj2) {
let key;
for (key in obj2) {
obj1[key] = obj1[key] && obj1[key].toString() === "[object Object]" && (obj2[key] && obj2[key].toString() === "[object Object]") ? this.deepMerge(obj1[key], obj2[key]) : (obj1[key] = obj2[key])
}
return obj1
//return JSON.parse(JSON.stringify(obj1))
},
//处理动态隐藏
hideHandle(item){
if(item.hideHandle){
const exp = eval(item.hideHandle.replace(/\$/g,"this.form"))
return exp
}
return false
},
//处理动态必填
rulesHandle(item){
if(item.requiredHandle){
const exp = eval(item.requiredHandle.replace(/\$/g,"this.form"))
var requiredRule = item.rules.find(t => 'required' in t)
requiredRule.required = exp
}
return item.rules
},
//数据验证
validate(valid, obj){
return this.$refs.form.validate(valid, obj)
},
scrollToField(prop){
return this.$refs.form.scrollToField(prop)
},
resetFields(){
return this.$refs.form.resetFields()
},
//提交
submit(){
this.$emit("submit", this.form)
}
}
}
</script>
<style lang="scss">
.sd-form {
.el-row {
display: flex;
flex-wrap: wrap;
.el-col {
padding: 5px !important;
box-sizing: border-box;
}
}
.el-form-item {
margin: 0;
display: flex;
align-items: center;
width: 100% !important;
height: 100%;
.el-form-item__label {
background: #EAF2FF;
display: flex;
justify-content: flex-end;
align-items: center;
span {
display: flex;
align-items: center;
justify-content: flex-end;
}
}
.el-form-item__content {
margin: 0 !important;
padding: 0 5px;
box-sizing: border-box;
flex: 1;
}
}
}
.sd-form-border {
.el-row {
border: 1px solid #e5e5e5;
.el-col {
border-right: 1px solid #e5e5e5;
border-bottom: 1px solid #e5e5e5;
}
}
}
</style>
npm使用
npm i web-bing
// main.js
import componentsBing from 'web-bing'
import 'web-bing/dist/web-bing.css'
Vue.use(componentsBing)
组件使用
<template>
<div id="app">
<sd-form ref="formref" :config="config" size="mini" border v-model="formData">
<!-- 具名插槽 -->
<template #testSlot>
<el-input v-model="formData.slotName" placeholder="这是自定义表单"></el-input>
</template>
<!-- 具名插槽 -->
<template #testSlot1>
<el-input v-model="formData.slotName1" placeholder="这是自定义表单1"></el-input>
</template>
<el-button type="primary" @click="formSave">保 存</el-button>
</sd-form>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
config: {},
formData: {}
}
},
// config为表单数据
// formData为绑定数据结构
created() {
this.config = {
labelWidth: '120px', // label宽度
labelPosition: 'right', // label对齐方式
size: 'medium', // 表单尺寸
formItems: [ // 表单元素
{
label: "自定义表单", //表单名称
name: "slotName", // formData绑定的key
span: 8, // el-col的span
slotName: 'testSlot', // 具名插槽
rules: [ // 校验规则
{required: true, message: "Please input Activity name", trigger: "blur"}
],
},
{
label: "输入框",
name: "name",
component: "input", // 表单类型
span: 8,
options: {
maxlength: "20",
placeholder: "Activity name",
},
rules: [
{required: true, message: "Please input Activity name", trigger: "blur"}
],
requiredHandle: "$.required==true", // 是否需要校验
},
{
label: "栅格(12/24)",
name: "name2",
component: "input",
span: 8,
options: {
placeholder: "span: 12",
}
},
{
label: "栅格(12/24)",
name: "name3",
component: "input",
span: 24,
options: {
placeholder: "span: 12",
}
},
{
label: "自定义表单1", //表单名称
name: "slotName1", // formData绑定的key
span: 12, // el-col的span
slotName: 'testSlot1', // 具名插槽
rules: [ // 校验规则
{required: true, message: "Please input Activity name", trigger: "blur"}
],
},
{
label: "级联选择器",
name: "cascader",
component: "cascader",
span: 12,
options: {
items:[
{
label: "Guide",
value: "guide",
children: [
{
label: "Disciplines",
value: "disciplines"
},
{
label: "Consistency",
value: "consistency"
},
]
},
{
label: "Resource",
value: "resource",
children: [
{
label: "Axure Components",
value: "axure"
},
{
label: "Sketch Templates",
value: "sketch"
},
{
label: "Design Documentation",
value: "docs"
}
]
},
{
label: "Component",
value: "component"
},
]
}
},
{
label: "多选框",
name: "checkbox",
component: "checkbox",
span: 12,
tips: "多选框配置加上 name 表示拥有嵌套关系。否则将值“平铺”在form对象",
options: { // 表单具体属性
items:[
{
label: "选项1",
name: "option1"
},
{
label: "选项2",
name: "option2"
}
]
},
hideHandle: "$.required==true"
},
{
label: "多选框组",
name: "checkboxGroup",
component: "checkboxGroup",
span: 24,
options: {
items:[
{
label: "选项1",
value: "option1"
},
{
label: "选项2",
value: "option2"
}
]
},
hideHandle: "$.required==true" // 动态显示隐藏此表单
},
{
label: "单选",
name: "radio",
component: "radio",
options: {
items:[
{
label: "选项1",
value: "1"
},
{
label: "选项2",
value: "2"
}
]
},
hideHandle: "$.required==true"
},
{
label: "开关",
name: "required",
span: 12,
message: "演示如何使用表达式动态显隐和必填,试试打开和关闭开关",
component: "switch",
},
{
label: "日期/时间",
name: "date",
span: 12,
component: "date",
options: {
type: "datetime",
valueFormat: "yyyy-MM-dd HH:mm:ss",
},
rules: [
{required: true, message: "Please input Data", trigger: "change"}
],
},
{
label: "数值",
name: "number",
component: "number",
},
{
label: "颜色",
name: "color",
component: "color",
},
{
label: "评分",
name: "rate",
component: "rate",
}
]
}
this.formData = {
slotName: '自定义表单',
slotName1: '123',
name: '',
name2: '',
name3: '',
cascader: '',
checkbox: {},
checkboxGroup: [],
radio: '1',
required: false,
date: '',
slider: 8,
number: 0,
color: '',
rate: 0
}
},
methods: {
formSave() {
}
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>