天天看点

写表单你还在复制粘贴吗?推荐你使用基于Vant-UI二次封装的数据驱动式表单自动生成组件

前言

在使用Vue写移动端开发的时候,难免会遇到需要写很多的表单,所以我在现在的项目里面集成了有赞的Vant-ui组件库,但是感觉Vant-ui对于表单组件的调用方式有点繁琐,实在不愿意去干这么一样的事情,就封装了一个基于Vant-ui的数据驱动式表单自动生成组件。

具体怎么玩,我们下面一起来看看吧~

正文

组件现在的状态

  • 目前已经集成的组件如下:

    Address、Checkbox、DatePicker、Input、Radio、Select、Text、Switch、Upload

其它组件还在完善中...

  • 组件的调用方式采取json配置的形式,具体参数见model数据说明
  • 校验规则已经集成VeeValidate插件,也可以自定义扩展规则,更多资料: https://logaretm.github.io/vee-validate

在线演示地址:

https://codesandbox.io/s/v-formshili-3hs2c?file=/src/main.js:53-383

安装

# yarn
yarn add @xuanmo/v-form
# npm
npm install @xuanmo/v-form -S           

复制

使用

配置

vue.config.js配置组件编译(注:如果组件引入采取的后编译需要配置这一项)

module.exports = {
  transpileDependencies: [
    '@xuanmo/v-form'
  ]
}           

复制

Props

字段名 说明 类型 默认值
v-model(value) 获取组件处理完成的数据 object {}
model 数据模型(具体类型参考后续文档) object {}
disabled 是否禁用表单 boolean false
label-width label宽度 string 20%
label-position label对齐方式,可选:left/right string left
label-color label文字颜色 string -
show-label 是否显示label boolean true

event

事件名 说明 回调参数
change 数据更改时触发 object{value,errorMsg,isValid}
event 数据发生改变所发送的事件 object{event,formModel}

slots

组件可接受多个slot,用于替换当前行的表单组件,会为该slot传入该组件的原始数据,每个slot的name为当前行的key

注:该slot不继承所有校验规则

<v-form :model="model">
  <template v-slot:text="{ data }">
    <van-field v-model="data.value"></van-field>
  </template>

  <!-- 行扩展字段slot,格式{key}-extra -->
  <template #text-extra>
    extra
  </template>
</v-form>           

复制

示例

main.js中全局注册

import "@vant/touch-emulator";

// 如果没有安装vant-ui可以采取这种方式引入组件
import VForm from "@xuanmo/v-form/dist/v-form.umd.js";
import "@xuanmo/v-form/dist/v-form.css";

// 项目已经引入vant-ui推荐使用这种方式引入,后编译
// import VForm from '@xuanmo/v-form'

Vue.use(VForm);

Vue.config.productionTip = false;

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

复制

页面中使用

<template>
  <div id="app">
    <div style="margin-bottom: 30px">
      <span style="vertical-align: super;">切换表单禁用状态</span>
      <van-switch v-model="disabled" size="20px" />
    </div>
    <v-form
      ref="vform"
      v-model="formValue"
      :model="model"
      :disabled="disabled"
      label-width="100px"
      @change="_change"
      @event="_event"
    >
      <template #text1-label>
        自定义label
      </template>
      <template #text1-extra>
        <!-- <van-field v-model="data.value"></van-field> -->
        extra
      </template>
    </v-form>
    <div style="margin: 20px 0;text-align: center;">
      <van-button type="primary" @click="_submit">提交数据</van-button>
    </div>
  </div>
</template>

<script>
import { Switch, Button } from 'vant'
export default {
  name: 'App',
  components: {
    // 'van-field': Field,
    'van-switch': Switch,
    'van-button': Button
  },
  data () {
    return {
      formValue: {
        text: 1
      },
      formData: {},
      formError: [],
      isValid: false,
      disabled: false,
      model: {
        numberKeyboard: {
          value: '',
          rules: {
            label: '数字键盘',
            type: 'VNumberKeyboard',
            placeholder: '点击输入',
            // theme: 'custom',
            extraKey: '.',
            // closeButtonText: '完成'
          }
        },
        file: {
          value: [{ path: 'https://www.xuanmo.xin/wp-content/uploads/2019/10/xuanmo_avatar.JPG' }],
          rules: {
            label: '文件上传',
            type: 'VUpload',
            action: 'xxx',
            accept: 'image/png',
            multiple: true,
            name: 'file',
            data: {
              dir: 'test'
            },
            props: {
              url: 'path'
            }
          }
        },
        switch: {
          value: true,
          rules: {
            label: '是否启用编辑',
            type: 'VSwitch'
          }
        },
        text: {
          value: '',
          rules: {
            label: '文字',
            type: 'VInput',
            vRules: 'required|max:2',
            placeholder: '请输入文字',
            errorMsg: '请输入文字',
            extra: 'extra'
          }
        },
        text1: {
          value: '文字内容',
          rules: {
            label: '文字1',
            type: 'VText'
          }
        },
        checkbox: {
          value: ['a'],
          rules: {
            label: '复选框',
            type: 'VCheckbox',
            vRules: 'required',
            placeholder: '请输入复选框',
            errorMsg: '请输入复选框',
            direction: 'horizontal',
            options: [
              { label: '复选框 a', value: 'a' },
              { label: '复选框 b', value: 'b' },
              { label: '复选框 c', value: 'c' }
            ]
          }
        },
        radio: {
          value: 'b',
          rules: {
            label: '单选框',
            type: 'VRadio',
            vRules: 'required',
            disabled: true,
            placeholder: '请输入单选框',
            errorMsg: '请输入单选框',
            direction: 'horizontal',
            options: [
              { label: '复选框 a', value: 'a' },
              { label: '复选框 b', value: 'b' },
              { label: '复选框 c', value: 'c' }
            ]
          }
        },
        date: {
          value: Date.now(),
          rules: {
            label: '时间',
            type: 'VDatePicker|datetime',
            valueFormat: 'timestamp'
          }
        },
        dateRange: {
          value: [Date.now(), Date.now()],
          rules: {
            label: '时间',
            type: 'VDatePickerRange|time',
            valueFormat: 'timestamp',
            rangeSeparator: '至'
          }
        },
        number: {
          value: '',
          rules: {
            label: '数字',
            type: 'VInput|digit',
            vRules: 'required',
            placeholder: '请输入数字',
            errorMsg: '请输入数字'
          }
        },
        address: {
          value: '110000,110100,110114',
          rules: {
            label: '地址选择',
            type: 'VAddress',
            vRules: 'required',
            placeholder: '请输入地址',
            errorMsg: '请输入地址'
          }
        },
        textarea: {
          value: '',
          rules: {
            label: '文本域',
            type: 'VInput|textarea',
            vRules: 'required',
            placeholder: '文本域',
            errorMsg: '文本域'
          }
        },
        select: {
          value: '4',
          rules: {
            label: '选择器',
            type: 'VSelect',
            placeholder: 'picker选择器',
            errorMsg: 'picker选择器',
            vRules: 'required',
            options: [
              { text: '杭州', value: 1 },
              { text: '宁波', value: 2 },
              { text: '温州', value: 3 },
              { text: '嘉兴', value: 4 },
              { text: '湖州', value: 5 }
            ]
          }
        },
        selectMultiple: {
          value: '4,2',
          rules: {
            label: '多列选择器',
            type: 'VSelect',
            placeholder: 'picker选择器',
            errorMsg: 'picker选择器',
            options: [
              [
                { text: '杭州', value: '1' },
                { text: '宁波', value: '2' },
                { text: '温州', value: '3' },
                { text: '嘉兴', value: '4' },
                { text: '湖州', value: '5' }
              ],
              [
                { text: '杭州', value: '1' },
                { text: '宁波', value: '2' },
                { text: '温州', value: '3' },
                { text: '嘉兴', value: '4' },
                { text: '湖州', value: '5' }
              ]
            ]
          }
        }
      }
    }
  },
  methods: {
    _change ({ value, errorMsg, isValid }) {
      this.formData = value
      this.formError = errorMsg
      this.isValid = isValid
    },
    _event ({ type, value }) {
      console.log(type, value)
    },
    _submit () {
      if (!this.isValid) {
        this.$toast(this.formError[0].errorMsg)
        return
      }
      this.$toast('提交成功')
    }
  }
}
</script>

<style>
#app {
  font-size: 14px;
}
</style>           

复制

最终效果

写表单你还在复制粘贴吗?推荐你使用基于Vant-UI二次封装的数据驱动式表单自动生成组件

结语

说实话,自从封了这个组件就感觉对于表单的处理就只是一串数据而已,其它的基本不用管,最后直接取填完表单后的数据就可以了,减少了不少繁琐,不用到处复制粘贴,还可以自己去更好的扩展自己想要的功能。如果各位同学表示认同,可以去我的Github帮点个Star,表示支持吧~

仓库地址:

https://github.com/xuanmos/v-form