Form 表单

Form 组件需要配合 Fieldset 及 Field 组件使用。

示例

排列模式

可选的 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对应 Field 的 field 描述的集合。事件会绑定到对应 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 的值为 true 或 undefined 表示流程继续,其它返回值表示中断流程并触发 invalid 事件。
after-validatefunction-表单校验成功后,触发 submit 事件之前的 hook,传入参数为 (data),与 beforeValidate 的入参是同一个引用。支持返回 Promise,返回值或 Promise.resolve 的值为 true 或 undefined 表示流程继续,其它返回值表示中断流程并触发 invalid 事件。

插槽

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

事件

名称描述
submit

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

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

beforeValidate、validate、afterValidate 流程中某一项返回中断时触发,回调参数为流程 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)

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

表单校验逻辑

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

  1. Field 的 rule 是单值、同步校验。详见表单项。
  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']
  }
]

Field 的 rule 和 validators 的优先级

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

交互过程的校验

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 事件中去。