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>

上一篇下一篇

猜你喜欢

热点阅读