又一款比肩阿里的表单生成器

5,263 阅读3分钟

Form表单组件配置模式一直以来颇受广大开发者争论,大家常争论的点在于:难维护,不直观,不灵活,节省不了什么代码量。这篇文章将颠覆传统JSON配置,以一种简洁,高效的配置方式推动更好的表单开发范式。让世界上没有难写的增删改查!让Web开发如喝咖啡一样优雅!

最简示例

简单的配置就可以驱动生成一个表单,简化了Dom相关代码。

<template>
    <zy-form
        :model="formData"
        :fields="fields"
    />
</template>
<script>
export default {
    name: 'App',
    data() {
        return {
            formData: {
                name: '',
                age: '',
            },
            fields: ({f1})=>{ 
                return [
                    f1('名称','name','input'),
                    f1('年龄','age','number'),
                ]
            }
        };
    }
};
</script>
表单项配置扩展

有过一定开发经验的小伙伴们,通过上面的示例,大家也能猜到该组件到底想做什么了。但是实际开发过程中,表单项的属性是比较复杂的,如element UI里面的 input Attributes内容就有10多项,还有select、radio、checkbox等等这些,那我们又该如何优雅的进行配置呢?

  1. input
<script>
export default {
    name: 'App',
    data() {
        return { 
            fields: ({f1})=>{
                //配置扩展可以调用config方法,传一个配置对象即可
                return [
                    f1('名称','name','input').config({
                        type: 'textarea', //类型
                        placeholder: '请输入您的名称', //占位文字
                        clearable:true, //是否可清空
                        maxlength:150 //最大输入长度
                        //...略
                    })
                ]
            }
        };
    }
};
</script>
  1. select
<script>
export default {
    name: 'App',
    data() {
        return { 
            fields: ({f1})=>{
                //select,radio,checkbox 都需要传选项集合,可以通过 l 方法
                //配置扩展可以调用config方法,传一个配置对象即可
                
                let sexList = [
                    {label:'男',value:1},
                    {label:'女',value:2}
                ]
                return [
                    f1('性别','sex','select').l(sexList).config({
                        multiple: false, //是否多选
                        placeholder: '请选择', //占位文字
                        clearable:true, //是否可清空
                        filterable:true //是否可搜索
                        //...略
                    })
                ]
            }
        };
    }
};
</script>

细心的小伙伴可能会说,select、radio、checkbox这些选项如果是通过后端API接口获取的,又该如果配置进去呢?

<script>
export default {
    name: 'App',
    data() {
        return { 
            //只需要在l方法里面传入函数体
            //这里需要注意的是l方法只解析[{label:'',value:''}]格式
            //假如接口数据是[{name:'',id:''}],则需要在l方法的第二次参数配置key与val
            fields: ({f1})=>{
                return [
                    f1('性别','sex','select').l(this.getList,{k:'name',v:'id'})
                ]
            }
        };
    },
    methods:{
        getList(){
            return this.$axios.get('xxx').then(res=>{
                return res.data //必须返回数组
            })
        }
    }
    
};
</script>
表单项方法注册
<script>
export default {
    name: 'App',
    data() {
        return { 
            //如果select需要加入change方法,那么只需要写入event
            fields: ({f1})=>{
                return [
                    f1('性别','sex','select').l(this.getList,{k:'name',v:'id'}).event({
                        change(val,self,row){
                            console.log('选中的值',val) // 1
                            console.log('当前选中的所有属性',self) // {name:'奥迪',id:'1',...}
                            console.log('当前配置项',row) 
                        }
                    }),
                    
                     f1('名称','name','input').event({
                        input(val){
                            console.log('输入的值',val) 
                        },
                        blur(val){
                            console.log('失去焦点',val) 
                        },
                        //...
                    }),
                ]
            }
        };
    },
    methods:{
        getList(){
            return this.$axios.get('xxx').then(res=>{
                return res.data //必须返回数组
            })
        }
    }
    
};
</script>
表单项校验规则
<script>
export default {
    name: 'App',
    data() {
        return {
            fields: ({f1})=>{ 
                //r方法默认开启了非空校验!
                return [
                    f1('名称','name','input').r()
                ]
            }
        };
    }
};
</script>

同时r方法还内置了常用的正则判断,如:手机号,身份证号,邮箱,大小写,数字等

类型功能
URLURL格式
LowerCase小写字母
UpperCase大写字母
Alphabets字母
Email邮箱
Phone手机号
Number数字
Fax传真
IntPlus正整数
Encode编码只能使用字母、数字、下划线、中划线
Encode2编码只能使用字母、数字
Encode3编码只能使用字母、数字、下划线
IdCard身份证号码
CarNum车牌号
CNandEN中文、英文
<script>
export default {
    name: 'App',
    data() {
        return {
            fields: ({f1})=>{ 
                return [
                    f1('邮箱','xxx','input').r('Email') //验证邮箱
                    f1('多重验证','xxx','input').r(['CNandEN','URL']) //数组形式传入多个
                    f1('自定义验证','xxx','input').r(/^\d+$/) //直接传入正则
                    f1('多个自定义验证','xxx','input').r([/^\d+$/,/^[a-zA-Z\u4e00-\u9fa5]+$/])
                    //直接传入正则数组
                ]
            }
        };
    }
};
</script>
效果

0.png

同时还一种验证情况,就是可以为空。但是如果不为空的情况下格式就必须校验。只需r方法传入第二个参数为false

<script>
export default {
    name: 'App',
    data() {
        return {
            fields: ({f1})=>{ 
                return [
                 //验证邮箱,邮箱可以为空,一旦输入就必须验证
                    f1('邮箱','email','input').r('Email',false) 
                   
                ]
            }
        };
    }
};
</script>
组件联动

不同表单项之间可能有一些联动关系,比如当姓名为空时禁用年龄的编辑,只需要通过配置表单项的disabled属性即可,disabled属性支持Boolean和Function两种,其中函数的参数对当前表单的model值。属性只要是布尔值的都支持Boolean和Function两种!

<script>
export default {
    name: 'App',
    data() {
        return {
            fields: ({f1})=>{ //函数式配置,返回数组
                return [
                    f1('姓名','name','input'),
                    f1('年龄','old','input').config({
                        disabled:(formData) => !formData.name
                    })
                ]
            }
        };
    }
};
</script>

如果想要配置没有输入name时隐藏age表单项也非常简单,只需要开启h方法,和disabled类似。

<script>
export default {
    name: 'App',
    data() {
        return {
            fields: ({f1})=>{ 
                return [
                    f1('姓名','name','input'),
                    f1('年龄','old','input').h((formData)=>{
                        return formData.name
                    })
                ]
            }
        };
    }
};
</script>
自定义表单项

如果某个表单项比较复杂,你也可以将其封装成单独的组件,只要支持双向绑定v-model,就可以和配置表单无缝结合。

<script>
import CustomComponent from './CustomComponent.vue'
export default {
    name: 'App',
    data() {
        return {
            fields: ({f1})=>{ //函数式配置,返回数组
                return [
                    f1('自定义组件','xxx',CustomComponent),
                ]
            }
        };
    }
};
</script>
插槽

我推荐大家能将特殊的表单项封装成组件进行使用,然而有时可能确实没必要,那么也支持插槽方式进行扩展。 如果要定制表单项label的实现,可以使用名为 '属性名_label'的插槽进行扩展,以表单项name为例,就是 slot="name_label"。 如果要定制表单项的实现,则使用名为属性名的插槽。

<template>
    <config-form
        :model="formData"
        :fields="fields"
    >
        <template #name_label>
            <span style="color:red;">您的大名</span>
        </template>
        <template #name>
            <input v-model="formData.name" /> <i class="el-icon-question"></i>
        </template>
    </config-form>
</template>
<script>
export default {
    name: 'App',
    data() {
        return {
            formData: {
                name: ''
            },
            fields: ({f1})=>{ //函数式配置,返回数组
                return [
                    f1('名称','name'),
                ]
            }
        };
    }
};
</script>
表单布局

默认的表单布局是一行两列。如果需要改变布局。只需要在 config-form 组件上面传入 row 属性,布局默认采用的是element ui的栅格(24/2)

<template>
    <config-form
        :model="formData"
        :fields="fields" 
        :row='3'
        >
    </config-form>
</template>
<script>
export default {
    name: 'App',
    data() {
        return {
            formData: {
                name: ''
            },
            fields: ({f1})=>{ //函数式配置,返回数组
                //如果定义一行三列,栅格则是24/3
                return [
                    f1('名称','name'),
                    f1('名称','name'),
                    f1('名称','name'),
                    f1('名称','name'),
                    f1('名称','name'),
                    f1('名称','name'),
                ]
            }
        };
    }
};
</script>

如果想要灵活的自定义,比如希望第一行是1列,第二行是2列,第三行是3列。只需要把返回的数组改成二维数组。


<script>
export default {
    name: 'App',
    data() {
        return {
            fields: ({f1})=>{ //函数式配置,返回数组
                return [
                    [
                        f1('名称','name'),
                    ],
                    [
                        f1('名称','name'),
                        f1('名称','name'),
                    ],
                    [
                        f1('名称','name'),
                        f1('名称','name'),
                        f1('名称','name'),
                    ]
                ]
            }
        };
    }
};
</script>
效果

1111.png

自定义一些常用配置的方法

其实通过上面的案例,大家也熟悉了配置的一些规律,无非就是采用函数链式调用的形式,取代了JSON配置, 如插件内置了 r、l、h、config、event这些方法都可以以链式调用的形式,增强表单的一些功能属性。那么如果配置较多的情况下,config是不是会显得非常臃肿。其实这一点我也帮你们考虑好了。

<script>
export default {
    name: 'App',
    data() {
        return { 
            fields: ({f1})=>{
                //配置扩展可以调用config方法太臃肿了
                return [
                    f1('名称','name','input').config({
                        type: 'textarea', //类型
                        placeholder: '请输入您的名称', //占位文字
                        clearable:true, //是否可清空
                        maxlength:150 //最大输入长度
                        //...略10项
                    })
                ]
            }
        };
    }
};
</script>

main.js

<script>
import ConfigForm from "@zy-form/v2";
import ConfigFormPluginElement from '@config-form/plugin-element'

Vue.use(ConfigForm, {
    presets: [ConfigFormPluginElement], //组件预设
    componentMaps: {},//自定义组件支持的类型
    configuration: { //预设常用的方法配置项
        //占位
        p: function (value) {
          return {
            placeholder:value
          }
        },
        //最大长度
        ml: function (value) {
          return {
            maxlength:value
          }
        },
        //是否可清空
        c:function () {
          return {
            clearable:true
          }
        },
        //类型
        t:function(val){
           return {
             type:val
           }
        }
    },
    //设置每个组件的默认样式
    defaultProps: {
        form: {
            labelWidth: '120px'
        }
    }
})
</script>

app.vue

<script>
export default {
    name: 'App',
    data() {
        return { 
            fields: ({f1})=>{
                //通过入口文件的配置预设,减少业务代码的繁琐配置
                return [
                    f1('名称','name','input')
                    .p('请输入您的名称')
                    .c()
                    .ml(150)
                    .t('textarea').config({
                        //...略10项
                    })
                ]
            }
        };
    }
};
</script>

上面介绍了这么多,感兴趣的朋友一定想亲自试一试。由于组件还在测试调优阶段。很多功能还在陆续更新,胆子大家人们可以直接上项目,组件还算稳定。只是有些功能还需要加强设计与优化。同时也感谢该组件的初代设计者同时也是掘金优秀作者:@前端林叔

如何下载与使用

目前只支持vue2,目前需要借助ElementUI,确保项目中已经使用了ElementUI。

npm i @zy-form/v2 @config-form/plugin-element

main.js中引入:

import Vue from 'vue'
import App from './App.vue'

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

import ConfigForm from "@zy-form/v2";
import ConfigFormPluginElement from '@config-form/plugin-element'

Vue.config.productionTip = false

Vue.use(ElementUI);

Vue.use(ConfigForm, {
    presets: [ConfigFormPluginElement], //组件预设
    componentMaps: {
        //自定义组件支持的类型
    },
    //设置每个组件的默认样式
    defaultProps: {
        form: {
            labelWidth: '120px'
        }
    }
})

new Vue({
  render: h => h(App),
}).$mount('#app')