el-form动态表单

效果

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@0.1.4

// 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>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,682评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,277评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,083评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,763评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,785评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,624评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,358评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,261评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,722评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,900评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,030评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,737评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,360评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,941评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,057评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,237评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,976评论 2 355

推荐阅读更多精彩内容