Form 表单

Form 组件需要配合 FieldsetField 组件使用。

示例

排列模式

可选的 ui 属性值:inline

Default

Inline

<template>
<article>
  <section>
    <h3>Default</h3>
    <veui-form :data="formData">
      <veui-field label="状态:">
        <veui-select
          v-model="formData.statusSelected"
          :options="statusOptions"
        />
      </veui-field>

      <veui-field label="时间:">
        <veui-datepicker
          v-model="formData.range"
          range
        />
      </veui-field>

      <veui-field>
        <veui-searchbox
          v-model="formData.query"
          placeholder="请输入搜索内容"
        />
      </veui-field>
    </veui-form>
  </section>
  <section>
    <h3>Inline</h3>
    <veui-form
      :data="formData"
      ui="inline"
    >
      <veui-fieldset
        ui="alt"
        class="left"
      >
        <veui-field label="状态:">
          <veui-select
            v-model="formData.statusSelected"
            ui="alt"
            :options="statusOptions"
          />
        </veui-field>

        <veui-field label="时间:">
          <veui-datepicker
            v-model="formData.range"
            ui="alt"
            range
          />
        </veui-field>
      </veui-fieldset>

      <veui-fieldset class="right">
        <veui-field>
          <veui-searchbox
            v-model="formData.query"
            placeholder="请输入搜索内容"
          />
        </veui-field>
      </veui-fieldset>
    </veui-form>
  </section>
</article>
</template>

<script>
import moment from 'moment'
import { Form, Field, Fieldset, Select, DatePicker, Searchbox } from 'veui'

export default {
  components: {
    'veui-form': Form,
    'veui-fieldset': Fieldset,
    'veui-field': Field,
    'veui-datepicker': DatePicker,
    'veui-select': Select,
    'veui-searchbox': Searchbox
  },
  data () {
    return {
      statusOptions: [
        {
          label: '状态1',
          value: 1
        },
        {
          label: '状态2',
          value: 2
        },
        {
          label: '状态3',
          value: 3
        },
        {
          label: '状态4',
          value: 4
        }
      ],
      formData: {
        statusSelected: 1,
        query: '',
        range: [
          moment().toDate(),
          moment()
            .add(3, 'month')
            .toDate()
        ]
      }
    }
  }
}
</script>

<style lang="less" scoped>
.left {
  float: left;
}
.right {
  float: right;
}
</style>

只读状态

设置 readonly 来使内部表单项处于只读状态。

<template>
<article>
  <section>
    <veui-checkbox v-model="readonly">
      只读
    </veui-checkbox>
  </section>
  <veui-form
    :data="formData"
    :readonly="readonly"
  >
    <veui-field label="姓名:">
      <veui-input v-model="formData.name"/>
    </veui-field>
  </veui-form>
</article>
</template>

<script>
import { Form, Field, Input, Checkbox } from 'veui'

export default {
  components: {
    'veui-checkbox': Checkbox,
    'veui-form': Form,
    'veui-field': Field,
    'veui-input': Input
  },
  data () {
    return {
      readonly: true,
      formData: {
        name: ''
      }
    }
  }
}
</script>

禁用状态

设置 disabled 来使内部表单项处于禁用状态。

<template>
<article>
  <section>
    <veui-checkbox v-model="disabled">
      禁用
    </veui-checkbox>
  </section>
  <veui-form
    :data="formData"
    :disabled="disabled"
  >
    <veui-field label="姓名:">
      <veui-input v-model="formData.name"/>
    </veui-field>
  </veui-form>
</article>
</template>

<script>
import { Form, Field, Input, Checkbox } from 'veui'

export default {
  components: {
    'veui-checkbox': Checkbox,
    'veui-form': Form,
    'veui-field': Field,
    'veui-input': Input
  },
  data () {
    return {
      disabled: true,
      formData: {
        name: ''
      }
    }
  }
}
</script>

校验

disabled 值提交时会过滤
在 field 上边 disabled,提交时才会过滤掉,该项在 input 上 disalbed
选择则至少选三个
-
联合校验,下限必须小于上限
<template>
<article>
  <veui-form
    ref="form"
    :before-validate="beforeValidate"
    :after-validate="afterValidate"
    :readonly="isValidating"
    :data="formData"
    :validators="validators"
    @invalid="handleInvalid"
  >
    <veui-field
      disabled
      field="name"
      name="name1"
      label="姓名:"
      tip="disabled 值提交时会过滤"
    >
      <veui-input v-model="formData.name"/>
    </veui-field>

    <veui-field
      field="name1"
      name="name2"
      label="姓名1:"
      tip="在 field 上边 disabled,提交时才会过滤掉,该项在 input 上 disalbed"
    >
      <veui-input
        v-model="formData.name1"
        disabled
        placeholder="长度不能短于2"
      />
    </veui-field>

    <veui-field
      field="age"
      name="age1"
      :rules="ageRule"
      label="年龄:"
    >
      <veui-input
        v-model="formData.age"
        placeholder="错误提示优先出在右侧"
      />
    </veui-field>

    <veui-field
      field="desc"
      name="desc"
      rules="required"
      label="介绍:"
    >
      <veui-textarea
        v-model="formData.desc"
        rows="3"
      />
    </veui-field>

    <veui-fieldset
      name="phoneSet"
      label="电话:"
      :required="true"
    >
      <veui-field
        field="phoneType"
        name="phoneType"
      >
        <veui-select
          v-model="formData.phoneType"
          :options="phoneTypeOptions"
        />
      </veui-field>

      <veui-field
        field="phone"
        name="phone"
        :rules="numRequiredRule"
      >
        <veui-input v-model="formData.phone"/>
      </veui-field>
    </veui-fieldset>

    <veui-field
      field="hobby"
      name="hobby"
      :rules="hobbyRule"
      label="爱好:"
      tip="选择则至少选三个"
    >
      <veui-checkboxgroup
        v-model="formData.hobby"
        type="checkbox"
        :items="hobbyItems"
      />
    </veui-field>

    <veui-fieldset
      label="预期收入:"
      class="salary"
      tip="联合校验,下限必须小于上限"
      :required="true"
    >
      <veui-field
        field="start"
        name="start"
        :rules="numRequiredRule"
        class="start-field"
      >
        <veui-input v-model="formData.start"/>
      </veui-field>
      <veui-span>-</veui-span>
      <veui-field
        field="end"
        name="end"
        :rules="numRequiredRule"
      >
        <veui-input v-model="formData.end"/>
      </veui-field>
      <veui-span></veui-span>
    </veui-fieldset>

    <veui-field
      label="收入下限:"
      field="floor"
      name="floor"
      :rules="[
        {name: 'required', value: true},
        {name: 'min', value: 3500, message: '最低收入不小于 3500'}
      ]"
    >
      <veui-number-input v-model="formData.floor"/>
    </veui-field>

    <veui-field
      field="protocol"
      name="protocol"
      :rules="protocolRequiredRule"
      label="协议:"
    >
      <veui-checkbox
        v-model="formData.protocol"
        :true-value="true"
        :false-value="null"
      >
        我已阅读并同意工作协议
      </veui-checkbox>
    </veui-field>

    <div class="operation">
      <veui-button
        ui="primary"
        :loading="isValidating"
        type="submit"
      >
        提交
      </veui-button>
      <veui-button
        :loading="isValidating"
        @click="() => this.$refs.form.reset()"
      >
        重置
      </veui-button>
    </div>
  </veui-form>
</article>
</template>

<script>
import {
  Form,
  Fieldset,
  Field,
  Span,
  Input,
  Button,
  Select,
  Textarea,
  Checkbox,
  CheckboxGroup,
  NumberInput
} from 'veui'

export default {
  components: {
    'veui-span': Span,
    'veui-input': Input,
    'veui-number-input': NumberInput,
    'veui-button': Button,
    'veui-form': Form,
    'veui-fieldset': Fieldset,
    'veui-field': Field,
    'veui-select': Select,
    'veui-checkbox': Checkbox,
    'veui-checkboxgroup': CheckboxGroup,
    'veui-textarea': Textarea
  },
  data () {
    return {
      formData: {
        name: 'liyunteng1',
        name1: 'liyunteng2',
        age: null,
        desc: '',
        hobby: ['🏸'],
        phone: '18888888888',
        phoneType: 'mobile',
        start: null,
        end: null,
        protocol: null,
        floor: 3501
      },
      hobbyItems: [
        {
          value: '⚽️',
          label: '足球'
        },
        {
          value: '🏀',
          label: '篮球'
        },
        {
          value: '🏸',
          label: '羽毛球'
        },
        {
          value: '🎾',
          label: '网球'
        }
      ],
      phoneTypeOptions: [
        {
          label: '座机',
          value: 'phone'
        },
        {
          label: '手机',
          value: 'mobile'
        }
      ],
      requiredRule: [
        {
          name: 'required',
          value: true,
          triggers: 'blur,input'
        }
      ],
      numRequiredRule: [
        {
          name: 'numeric',
          value: true,
          triggers: 'blur,input'
        },
        {
          name: 'required',
          value: true,
          triggers: 'blur,input'
        }
      ],
      protocolRequiredRule: [
        {
          name: 'required',
          value: true,
          message: '请勾选阅读协议',
          triggers: 'change'
        }
      ],
      dynamicNameRule: [
        {
          name: 'required',
          value: true,
          triggers: 'blur,input'
        },
        {
          name: 'minLength',
          value: 2
        }
      ],
      ageRule: [
        {
          name: 'required',
          value: true,
          triggers: 'input'
        },
        {
          name: 'numeric',
          value: true,
          triggers: 'input'
        },
        {
          name: 'maxLength',
          value: 3,
          triggers: 'change'
        }
      ],
      hobbyRule: [
        {
          name: 'minLength',
          value: 3,
          message: '至少选择三个爱好',
          triggers: 'change'
        }
      ],
      isValidating: false,
      validators: [
        {
          fields: ['start', 'end'],
          handler (start, end) {
            if (start == null || end == null) {
              return true
            }

            if (parseInt(start, 10) >= parseInt(end, 10)) {
              return {
                start: '下限必须小于上限'
              }
            }
            return true
          },
          triggers: ['change', 'submit,input']
        },
        {
          fields: ['phone'],
          validate (phone) {
            return new Promise(function (resolve) {
              setTimeout(function () {
                let res
                if (phone === '18888888888') {
                  res = {
                    phone: '该手机已被注册'
                  }
                }
                return resolve(res)
              }, 3000)
            })
          },
          triggers: ['input']
        }
      ]
    }
  },
  methods: {
    beforeValidate () {
      this.isValidating = true
    },
    afterValidate () {
      this.isValidating = false
    },
    handleInvalid () {
      this.isValidating = false
    }
  }
}
</script>

API

属性

名称类型默认值描述
uistring-

Fieldset/Field 排列样式。默认垂直排列。

描述
inline行内模式,使内部 Fieldset/Field 水平排列。
readonlybooleanfalse内部输入组件是否为只读状态。
disabledbooleanfalse内部输入组件是否为禁用状态。
dataObject-

表单绑定的数据,和表单中的输入组件通过 v-model 绑定,也是表单校验时的数据源。

Field 如果处于 disabled 状态,提交流程中处理 data 副本时其对应的数据键值对会被过滤,校验时同理。

validatorsArray<Object>-

表单联合校验、异步校验器。项目类型为 {fields, validate, triggers}

名称类型描述
fieldsArray对应 Fieldfield 描述的集合。事件会绑定到对应 Field 中的输入组件上。
validatefunction自定义校验函数,传入参数为 (data[fields[0]], data[fields[1]], ...)data 为表单 data 属性值的引用。返回 undefined/true 代表校验成功,返回 {[field]: message, ...} 表示校验失败信息,详见表单 › 表单校验逻辑
triggersstring|Array<string>事件名称集合。
fieldstriggers绑定事件情况
['a']['change', 'blur,input,xxx', 'submit']a(change)
['a','b','c']['change', 'blur,input,xxx', 'submit']a(change), b(blur,input,xxx), c(submit)
['a','b','c']'blur'a(blur), b(submit), c(submit)
['a','b','c']'blur,input'a(blur,input), b(blur,input), c(blur,input)
before-validatefunction-表单进入提交流程后,进行校验之前的 hook,传入参数为 (data)data 为表单 data 属性值的副本。支持返回 Promise,返回值或 Promise.resolve 的值为 trueundefined 表示流程继续,其它返回值表示中断流程并触发 invalid 事件。
after-validatefunction-表单校验成功后,触发 submit 事件之前的 hook,传入参数为 (data),与 beforeValidate 的入参是同一个引用。支持返回 Promise,返回值或 Promise.resolve 的值为 trueundefined 表示流程继续,其它返回值表示中断流程并触发 invalid 事件。

插槽

名称描述
default可直接内联 FieldsetField 组件。无默认内容。

事件

名称描述
submit

在原生 submit 事件之后触发,回调参数为 (data, event)。具体提交流程请参考表单 › 表单提交流程

名称类型描述
dataObject表单 data 属性值的引用。
eventEvent原生事件对象。
invalid

beforeValidatevalidateafterValidate 流程中某一项返回中断时触发,回调参数为流程 function 的返回值,参数为 (result),表示流程中断的信息,具体返回值类型由流程返回决定。具体提交流程请参考表单 › 表单提交流程validate 逻辑见表单 › 表单校验逻辑

表单提交流程

not set / resolved
Y
not set / reslved
rejected
N
rejected
Native submit event
Clone data object
beforeValidate(data)
validate()
isValid?
afterValidate(data)
$emit('submit', data, event')
$emit('invalid', result)

beforeValidateafterValidate 以及 sumbit 事件操作的 data 均为 props data 的同一个副本。考虑到校验信息和 UI 中数据的一致性,validate 的目标数据是 props data 的源数据。因此在 beforeValidate不建议修改 data 副本。

表单校验逻辑

表单校验内部分为 Fieldrule 校验和 validators 的校验。

  1. Fieldrule 是单值、同步校验。详见表单项
  2. validators 可以是多值、异步的校验。
validators: [
  {
    fields: ['start', 'end'],
    validate (start, end) {
      if (start == null || end == null) {
        return true
      }

      if (parseInt(start, 10) >= parseInt(end, 10)) {
        return {
          start: '下限必须小于上限'
        }
      }
      return true
    },
    triggers: ['change', 'submit,input']
  },
  {
    fields: ['phone'],
    validate (phone) {
      return new Promise(function (resolve) {
        setTimeout(function () {
          let res
          if (phone === '18888888888') {
            res = {
              phone: '该手机已被注册'
            }
          }
          return resolve(res)
        }, 3000)
      })
    },
    triggers: ['input']
  }
]

Fieldrulevalidators 的优先级

校验失败的信息会添加到对应的 Fieldvalidities 信息中。由于同个操作触发的校验,validators 的校验结果优先级大于 Fieldrule,不同操作触发的校验,展现最后一个结果。Fieldrule 内部的优先级,参考表单项 › 属性

交互过程的校验

Y
N
Interaction events
Field#validate
validators.validate
isValid?
Hide validities
Show errors

提交过程的校验

Y
N
validate
Field#validate
validators.validate
Merge validities
isValid?
Promise.resolve(true)
Promise.resolve(error)

提交时,其中一个过程的校验失败不会导致整个校验终止,校验信息将在两个过程执行完毕后进行整合,并传递到 invalid 事件中去。