Form 表单

具有数据收集、校验和提交功能的表单,包含复选框、单选框、输入框、下拉选择框等元素。

表单#

我们为 form 提供了以下两种排列方式:

  • 水平排列:可以实现 label 标签和表单控件的水平排列;
  • 行内排列:使其表现为 inline-block 级别的控件。

表单域#

表单一定会包含表单域,表单域可以是输入控件,标准表单域,标签,下拉菜单,文本域等。

这里我们分别封装了表单域 <Form.Item /> 和输入控件 <Input />

<Form.Item {...props}>
  {children}
</Form.Item>

Input 输入框#

<Input {...props} />

注:标准表单中一律使用大号控件。

API#

Form#

更多示例参考 rc-form

参数 说明 类型 可选值 默认值
form Form.create() 包装过的组件会自带 this.props.form 属性,直接传给 Form 即可 object
horizontal 水平排列布局 boolean false
inline 行内排列布局 boolean false
onSubmit 数据验证成功后回调事件 Function(e:Event)
prefixCls 样式类名,默认为 ant-form,通常您不需要设置 string 'ant-form'

Form.create(options)#

使用方式如下:

class CustomizedForm extends React.Component {}

CustomizedForm = Form.create({})(CustomizedForm);

options 的配置项如下。

参数 说明 类型 可选值 默认值
onFieldsChange Form.Item 子节点的值发生改变时触发,可以把对应的值转存到 Redux store Function(props, fields)
mapPropsToFields 把 props 转为对应的值,可用于把 Redux store 中的值读出 Function(props)

经过 Form.create 包装的组件将会自带 this.props.form 属性,this.props.form 提供的 API 如下:

参数 说明 类型 可选值 默认值
getFieldsValue 获取一组输入控件的值,如不传入参数,则获取全部组件的值 Function([fieldNames: string[]])
getFieldValue 获取一个输入控件的值 Function(fieldName: string)
setFieldsValue 设置一组输入控件的值 Function(obj: object)
setFields 设置一组输入控件的值与 Error Function(obj: object)
validateFields 校验并获取一组输入域的值与 Error Function([fieldNames: string[]], [options: object], callback: Function(errors, values))
validateFieldsAndScroll validateFields 相似,但校验完后,如果校验不通过的菜单域不在可见范围内,则自动滚动进可见范围 参考 validateFields
getFieldError 获取某个输入控件的 Error Function(name)
isFieldValidating 判断一个输入控件是否在校验状态 Function(name)
resetFields 重置一组输入控件的值与状态,如不传入参数,则重置所有组件 Function([names: string[]])
getFieldProps 用于和表单进行双向绑定,详见下方描述

this.props.form.getFieldProps(id, options)#

getFieldProps 返回的属性包括 idvalue(或你设置的其它 valuePropName)、refonChange(或者你设置的其它 trigger validateTrigger),所以不应再设置同样的属性,以免冲突。如果对其返回值的细节有兴趣,可以 console.log 出来查看。

在表单中 defaultValue 也不应该被设置,请使用下面的 initialValue

参数 说明 类型 可选值 默认值
options.id 必填输入控件唯一标志 string
options.valuePropName 子节点的值的属性,如 Checkbox 的是 'checked' string 'value'
options.initialValue 子节点的初始值,类型、可选值均由子节点决定
options.trigger 收集子节点的值的时机 string 'onChange'
options.validateTrigger 校验子节点值的时机 string 'onChange'
options.rules 校验规则,参见 async-validator array

Form.Item#

参数 说明 类型 可选值 默认值
label label 标签的文本 string
labelCol label 标签布局,通 <Col> 组件,设置 span offset 值,如 {span: 3, offset: 12} object
wrapperCol 需要为输入控件设置布局样式时,使用该属性,用法同 labelCol object
help 提示信息,如不设置,则会根据校验规则自动生成 string
extra 额外的提示信息,和 help 类似,当需要错误信息和提示文案同时出现时,可以使用这个。 string
required 是否必填,如不设置,则会根据校验规则自动生成 bool false
validateStatus 校验状态,如不设置,则会根据校验规则自动生成 string 'success' 'warning' 'error' 'validating'
hasFeedback 配合 validateStatus 属性使用,是否展示校验状态图标 bool false
prefixCls 样式类名,默认为 ant-form,通常您不需要设置 string 'ant-form'

Input#

参数 说明 类型 可选值 默认值
type 【必须】声明 input 类型,同原生 input 标签的 type 属性 string 'text'
id id number 或 string
value value 值 any
defaultValue 设置初始默认值 any
size 控件大小,默认值为 default 。注:标准表单内的输入框大小限制为 large。 string {'large','default','small'} 'default'
disabled 是否禁用状态,默认为 false bool false
addonBefore 带标签的 input,设置前置标签 node
addonAfter 带标签的 input,设置后置标签 node
prefixCls 样式类名前缀,默认是 ant,通常您不需要设置 string 'ant'

如果 InputForm.Item 内,并且 Form.Item 设置了 idoptions 属性,则 value defaultValueid 属性会被自动设置。

Input.Group#

<Input.Group className={string}>
  <Input />
  <Input />
</Input.Group>

代码演示

import { Row, Col, Input } from 'antd';
const InputGroup = Input.Group;

ReactDOM.render(
  <Row>
    <InputGroup>
      <Col span="6">
        <Input id="largeInput" size="large" placeholder="大尺寸" />
      </Col>
      <Col span="6">
        <Input id="defaultInput" placeholder="默认尺寸" />
      </Col>
      <Col span="6">
        <Input id="smallInput" placeholder="小尺寸" size="small" />
      </Col>
    </InputGroup>
  </Row>
, mountNode);

我们为 <Input /> 输入框定义了三种尺寸(大、默认、小),具体使用详见 API

注意: 在表单里面,我们只使用大尺寸, 即高度为 32px,作为唯一的尺寸。

import { Form, Input, Button, Checkbox } from 'antd';
const FormItem = Form.Item;

let Demo = React.createClass({
  handleSubmit(e) {
    e.preventDefault();
    console.log('收到表单值:', this.props.form.getFieldsValue());
  },

  render() {
    const { getFieldProps } = this.props.form;
    return (
      <Form inline onSubmit={this.handleSubmit}>
        <FormItem
          label="账户:">
          <Input placeholder="请输入账户名"
            {...getFieldProps('userName')} />
        </FormItem>
        <FormItem
          label="密码:">
          <Input type="password" placeholder="请输入密码"
            {...getFieldProps('password')} />
        </FormItem>
        <FormItem>
          <label className="ant-checkbox-inline">
            <Checkbox
              {...getFieldProps('agreement')} />记住我
          </label>
        </FormItem>
        <Button type="primary" htmlType="submit">登录</Button>
      </Form>
    );
  }
});

Demo = Form.create()(Demo);

ReactDOM.render(<Demo />, mountNode);

行内排列,常用于登录界面。

import { Form, Input, Button, Checkbox, Radio, Tooltip, Icon } from 'antd';
const FormItem = Form.Item;
const RadioGroup = Radio.Group;

let Demo = React.createClass({
  handleSubmit(e) {
    e.preventDefault();
    console.log('收到表单值:', this.props.form.getFieldsValue());
  },

  render() {
    const { getFieldProps } = this.props.form;
    const formItemLayout = {
      labelCol: { span: 6 },
      wrapperCol: { span: 14 },
    };
    return (
      <Form horizontal onSubmit={this.handleSubmit}>
        <FormItem
          {...formItemLayout}
          label="用户名:">
          <p className="ant-form-text" id="userName" name="userName">大眼萌 minion</p>
        </FormItem>
        <FormItem
          {...formItemLayout}
          label="密码:">
          <Input type="password" {...getFieldProps('pass')} placeholder="请输入密码" />
        </FormItem>
        <FormItem
          {...formItemLayout}
          label="您的性别:">
          <RadioGroup {...getFieldProps('gender', { initialValue: 'female' })}>
            <Radio value="male">男的</Radio>
            <Radio value="female">女的</Radio>
          </RadioGroup>
        </FormItem>
        <FormItem
          {...formItemLayout}
          label="备注:"
          help="随便写点什么">
          <Input type="textarea" placeholder="随便写" {...getFieldProps('remark')} />
        </FormItem>
        <FormItem
          {...formItemLayout}
          label={<span>卖身华府 <Tooltip title="我为秋香"><Icon type="question-circle-o" /></Tooltip></span>}>
          <label>
            <Checkbox {...getFieldProps('agreement')} />同意
          </label>
        </FormItem>
        <FormItem wrapperCol={{ span: 16, offset: 6 }} style={{ marginTop: 24 }}>
          <Button type="primary" htmlType="submit">确定</Button>
        </FormItem>
      </Form>
    );
  }
});

Demo = Form.create()(Demo);

ReactDOM.render(<Demo />, mountNode);

示例展示了如何通过使用 Form.create 来获取和更新表单提交的数值。

import { Form, Input, Select, Checkbox, Radio } from 'antd';
const FormItem = Form.Item;
const Option = Select.Option;
const RadioGroup = Radio.Group;

function handleSelectChange(value) {
  console.log(`selected ${value}`);
}

ReactDOM.render(
  <Form horizontal>
    <FormItem
      id="control-input"
      label="输入框:"
      labelCol={{ span: 6 }}
      wrapperCol={{ span: 14 }}>
      <Input id="control-input" placeholder="Please enter..." />
    </FormItem>

    <FormItem
      id="control-textarea"
      label="文本域:"
      labelCol={{ span: 6 }}
      wrapperCol={{ span: 14 }}>
      <Input type="textarea" id="control-textarea" rows="3" />
    </FormItem>

    <FormItem
      id="select"
      label="Select 选择器:"
      labelCol={{ span: 6 }}
      wrapperCol={{ span: 14 }}>
      <Select id="select" size="large" defaultValue="lucy" style={{ width: 200 }} onChange={handleSelectChange}>
        <Option value="jack">jack</Option>
        <Option value="lucy">lucy</Option>
        <Option value="disabled" disabled>disabled</Option>
        < Option value="yiminghe">yiminghe</Option>
      </Select>
    </FormItem>

    <FormItem
      label="Checkbox 多选框:"
      labelCol={{ span: 6 }}
      wrapperCol={{ span: 18 }} >
      <label className="ant-checkbox-vertical">
        <Checkbox />选项一
      </label>
      <label className="ant-checkbox-vertical">
        <Checkbox />选项二
      </label>
      <label className="ant-checkbox-vertical">
        <Checkbox disabled />选项三(不可选)
      </label>
    </FormItem>

    <FormItem
      label="Checkbox 多选框:"
      labelCol={{ span: 6 }}
      wrapperCol={{ span: 18 }} >
      <label className="ant-checkbox-inline">
        <Checkbox />选项一
      </label>
      <label className="ant-checkbox-inline">
        <Checkbox />选项二
      </label>
      <label className="ant-checkbox-inline">
        <Checkbox />选项三
      </label>
    </FormItem>

    <FormItem
      label="Radio 单选框:"
      labelCol={{ span: 6 }}
      wrapperCol={{ span: 18 }} >
      <RadioGroup>
        <Radio value="a">A</Radio>
        <Radio value="b">B</Radio>
        <Radio value="c">C</Radio>
        <Radio value="d">D</Radio>
      </RadioGroup>
    </FormItem>
  </Form>
, mountNode);

展示所有支持的表单控件。

: 输入框:只有正确设置了 type 属性的输入控件才能被赋予正确的样式。

import { Form, Input, Select, Row, Col } from 'antd';
const FormItem = Form.Item;
const InputGroup = Input.Group;
const Option = Select.Option;

ReactDOM.render(
  <Form horizontal>
    <FormItem
      label="标签输入框:"
      labelCol={{ span: 6 }}
      wrapperCol={{ span: 16 }}>
      <Input addonBefore="Http://" defaultValue="mysite.com" id="site1" />
    </FormItem>

    <FormItem
      label="标签输入框:"
      labelCol={{ span: 6 }}
      wrapperCol={{ span: 16 }}>
      <Input addonBefore="Http://" addonAfter=".com" defaultValue="mysite" id="site2" />
    </FormItem>

    <FormItem
      label="select 标签输入框:"
      labelCol={{ span: 6 }}
      wrapperCol={{ span: 16 }}>
      <InputGroup>
        <Input id="site4" placeholder="www.mysite" />
        <div className="ant-input-group-wrap">
          <Select defaultValue=".com" style={{ width: 70 }}>
            <Option value=".com">.com</Option>
            <Option value=".jp">.jp</Option>
            <Option value=".cn">.cn</Option>
            <Option value=".org">.org</Option>
          </Select>
        </div>
      </InputGroup>
    </FormItem>

    <FormItem
      label="输入身份证:"
      labelCol={{ span: 6 }}
      wrapperCol={{ span: 16 }}>
      <InputGroup>
        <Col span="6">
          <Input id="certNo1" />
        </Col>
        <Col span="6">
          <Input id="certNo2" />
        </Col>
        <Col span="6">
          <Input id="certNo3" />
        </Col>
        <Col span="6">
          <Input id="certNo4" />
        </Col>
      </InputGroup>
    </FormItem>

    <FormItem
      label="电话号码:"
      labelCol={{ span: 6 }}
      wrapperCol={{ span: 16 }}>
      <Row>
        <Col span="4">
          <Input id="tel1" defaultValue="086" />
        </Col>
        <Col span="2">
          <p className="ant-form-split">--</p>
        </Col>
        <Col span="18">
          <InputGroup>
            <Col span="8">
              <Input id="tel1" />
            </Col>
            <Col span="8">
              <Input id="tel2" />
            </Col>
            <Col span="8">
              <Input id="tel3" />
            </Col>
          </InputGroup>
        </Col>
      </Row>
    </FormItem>
  </Form>

, mountNode);

各类输入框的组合展现。

import { Form, Select, InputNumber, DatePicker, TimePicker, Switch, Radio,
         Slider, Button, Col, Upload, Icon } from 'antd';
const FormItem = Form.Item;
const Option = Select.Option;
const RadioButton = Radio.Button;
const RadioGroup = Radio.Group;

let Demo = React.createClass({
  handleSubmit(e) {
    e.preventDefault();
    console.log('收到表单值:', this.props.form.getFieldsValue());
  },

  normFile(e) {
    if (Array.isArray(e)) {
      return e;
    }
    return e && e.fileList;
  },

  render() {
    const { getFieldProps } = this.props.form;
    return (
      <Form horizontal onSubmit={this.handleSubmit} >
        <FormItem
          label="InputNumber 数字输入框:"
          labelCol={{ span: 8 }}
          wrapperCol={{ span: 10 }}>
          <InputNumber min={1} max={10} style={{ width: 100 }}
            {...getFieldProps('inputNumber', { initialValue: 3 })} />
          <span className="ant-form-text"> 台机器</span>
        </FormItem>

        <FormItem
          label="我是标题:"
          labelCol={{ span: 8 }}
          wrapperCol={{ span: 10 }}>
          <p className="ant-form-text" id="static" name="static">唧唧复唧唧木兰当户织呀</p>
          <p className="ant-form-text">
            <a href="#">链接文字</a>
          </p>
        </FormItem>

        <FormItem
          label="Switch 开关:"
          labelCol={{ span: 8 }}
          wrapperCol={{ span: 10 }}
          required>
          <Switch {...getFieldProps('switch')} />
        </FormItem>

        <FormItem
          label="Slider 滑动输入条:"
          labelCol={{ span: 8 }}
          wrapperCol={{ span: 10 }}
          required>
          <Slider marks={['A', 'B', 'C', 'D', 'E', 'F', 'G']} {...getFieldProps('slider')} />
        </FormItem>

        <FormItem
          label="Select 选择器:"
          labelCol={{ span: 8 }}
          wrapperCol={{ span: 16 }}
          required>
          <Select style={{ width: 200 }}
            {...getFieldProps('select')}>
            <Option value="jack">jack</Option>
            <Option value="lucy">lucy</Option>
            <Option value="disabled" disabled>disabled</Option>
            <Option value="yiminghe">yiminghe</Option>
          </Select>
        </FormItem>

        <FormItem
          label="DatePicker 日期选择框:"
          labelCol={{ span: 8 }}
          required>
          <Col span="6">
            <DatePicker {...getFieldProps('startDate')} />
          </Col>
          <Col span="1">
            <p className="ant-form-split">-</p>
          </Col>
          <Col span="6">
            <DatePicker {...getFieldProps('endDate')} />
          </Col>
        </FormItem>

        <FormItem
          label="TimePicker 时间选择器:"
          labelCol={{ span: 8 }}
          wrapperCol={{ span: 16 }}
          required>
          <TimePicker {...getFieldProps('time')} />
        </FormItem>

        <FormItem
          label="选项:"
          labelCol={{ span: 8 }}>
          <RadioGroup {...getFieldProps('rg')}>
            <RadioButton value="a">选项一</RadioButton>
            <RadioButton value="b">选项二</RadioButton>
            <RadioButton value="c">选项三</RadioButton>
          </RadioGroup>
        </FormItem>

        <FormItem
          label="logo图:"
          labelCol={{ span: 8 }}
          wrapperCol={{ span: 16 }}
          help="提示信息要长长长长长长长长长长长长长长">
          <Upload name="logo" action="/upload.do" listType="picture" onChange={this.handleUpload}
            {...getFieldProps('upload', {
              valuePropName: 'fileList',
              normalize: this.normFile
            })}
          >
            <Button type="ghost">
              <Icon type="upload" /> 点击上传
            </Button>
          </Upload>
        </FormItem>

        <FormItem wrapperCol={{ span: 16, offset: 8 }} style={{ marginTop: 24 }}>
          <Button type="primary" htmlType="submit">确定</Button>
        </FormItem>
      </Form>
    );
  }
});

Demo = Form.create()(Demo);

ReactDOM.render(<Demo />, mountNode);

集中营,展示和表单相关的其他 ant-design 组件。

import { Form, Input, DatePicker, Col } from 'antd';
const FormItem = Form.Item;

ReactDOM.render(
  <Form horizontal>
    <FormItem
      label="失败校验:"
      labelCol={{ span: 5 }}
      wrapperCol={{ span: 12 }}
      validateStatus="error"
      help="请输入数字和字母组合">
      <Input defaultValue="无效选择" id="error" />
    </FormItem>

    <FormItem
      label="警告校验:"
      labelCol={{ span: 5 }}
      wrapperCol={{ span: 12 }}
      validateStatus="warning">
      <Input defaultValue="前方高能预警" id="warning" />
    </FormItem>

    <FormItem
      label="校验中:"
      labelCol={{ span: 5 }}
      wrapperCol={{ span: 12 }}
      hasFeedback
      validateStatus="validating"
      help="信息审核中...">
      <Input defaultValue="我是被校验的内容" id="validating" />
    </FormItem>

    <FormItem
      label="成功校验:"
      labelCol={{ span: 5 }}
      wrapperCol={{ span: 12 }}
      hasFeedback
      validateStatus="success">
      <Input defaultValue="我是正文" id="success" />
    </FormItem>

    <FormItem
      label="警告校验:"
      labelCol={{ span: 5 }}
      wrapperCol={{ span: 12 }}
      hasFeedback
      validateStatus="warning">
      <Input defaultValue="前方高能预警" id="warning" />
    </FormItem>

    <FormItem
      label="失败校验:"
      labelCol={{ span: 5 }}
      wrapperCol={{ span: 12 }}
      hasFeedback
      validateStatus="error"
      help="请输入数字和字母组合">
      <Input defaultValue="无效选择" id="error" />
    </FormItem>

    <FormItem
      label="Datepicker:"
      labelCol={{ span: 5 }}
      help>
      <Col span="6">
        <FormItem validateStatus="error" help="请选择正确日期">
          <DatePicker />
        </FormItem>
      </Col>
      <Col span="1">
        <p className="ant-form-split">-</p>
      </Col>
      <Col span="6">
        <FormItem>
          <DatePicker />
        </FormItem>
      </Col>
    </FormItem>

    <FormItem
      label="Datepicker:"
      labelCol={{ span: 5 }}
      validateStatus="error"
      help>
      <Col span="6">
        <DatePicker />
      </Col>
      <Col span="1">
        <p className="ant-form-split">-</p>
      </Col>
      <Col span="6">
        <DatePicker />
      </Col>
      <Col span="19" offset="5">
        <p className="ant-form-explain">请选择正确日期</p>
      </Col>
    </FormItem>
  </Form>
, mountNode);

我们为表单控件定义了三种校验状态,为 <FormItem> 定义 validateStatus 属性即可。

validateStatus: ['success', 'warning', 'error', 'validating']。

另外为输入框添加反馈图标,设置 <FormItem>hasFeedback 属性值为 true 即可。

注意: 反馈图标只对 <Input /> 有效。

import { Icon, Input, Button } from 'antd';
import classNames from 'classnames';
const InputGroup = Input.Group;

const SearchInput = React.createClass({
  getInitialState() {
    return {
      value: '',
      focus: false,
    };
  },
  handleInputChange(e) {
    this.setState({
      value: e.target.value,
    });
  },
  handleFocusBlur(e) {
    this.setState({
      focus: e.target === document.activeElement,
    });
  },
  handleSearch() {
    if (this.props.onSearch) {
      this.props.onSearch();
    }
  },
  render() {
    const btnCls = classNames({
      'ant-search-btn': true,
      'ant-search-btn-noempty': !!this.state.value.trim(),
    });
    const searchCls = classNames({
      'ant-search-input': true,
      'ant-search-input-focus': this.state.focus,
    });
    return (
      <InputGroup className={searchCls} style={this.props.style}>
        <Input {...this.props} value={this.state.value} onChange={this.handleInputChange}
          onFocus={this.handleFocusBlur} onBlur={this.handleFocusBlur} />
        <div className="ant-input-group-wrap">
          <Button className={btnCls} size={this.props.size} onClick={this.handleSearch}>
            <Icon type="search" />
          </Button>
        </div>
      </InputGroup>
    );
  }
});

ReactDOM.render(
  <SearchInput placeholder="input search text" style={{ width: 200 }} />
, mountNode);

带有搜索按钮。

import { Form, Input, Row, Col, Button } from 'antd';
const FormItem = Form.Item;

ReactDOM.render(
<Form horizontal className="advanced-search-form">
  <Row>
    <Col span="8">
      <FormItem
        label="搜索名称:"
        labelCol={{ span: 10 }}
        wrapperCol={{ span: 14 }}>
        <Input placeholder="请输入搜索名称" />
      </FormItem>
      <FormItem
        label="较长搜索名称:"
        labelCol={{ span: 10 }}
        wrapperCol={{ span: 14 }}>
        <Input placeholder="请输入搜索名称" />
      </FormItem>
      <FormItem
        label="搜索名称:"
        labelCol={{ span: 10 }}
        wrapperCol={{ span: 14 }}>
        <Input placeholder="请输入搜索名称" />
      </FormItem>
    </Col>
    <Col span="8">
      <FormItem
        label="搜索名称:"
        labelCol={{ span: 10 }}
        wrapperCol={{ span: 14 }}>
        <Input placeholder="请输入搜索名称" />
      </FormItem>
      <FormItem
        label="较长搜索名称:"
        labelCol={{ span: 10 }}
        wrapperCol={{ span: 14 }}>
        <Input placeholder="请输入搜索名称" />
      </FormItem>
      <FormItem
        label="搜索名称:"
        labelCol={{ span: 10 }}
        wrapperCol={{ span: 14 }}>
        <Input placeholder="请输入搜索名称" />
      </FormItem>
    </Col>
    <Col span="8">
      <FormItem
        label="搜索名称:"
        labelCol={{ span: 10 }}
        wrapperCol={{ span: 14 }}>
        <Input placeholder="请输入搜索名称" />
      </FormItem>
      <FormItem
        label="较长搜索名称:"
        labelCol={{ span: 10 }}
        wrapperCol={{ span: 14 }}>
        <Input placeholder="请输入搜索名称" />
      </FormItem>
      <FormItem
        label="搜索名称:"
        labelCol={{ span: 10 }}
        wrapperCol={{ span: 14 }}>
        <Input placeholder="请输入搜索名称" />
      </FormItem>
    </Col>
  </Row>
  <Row>
    <Col span="8" offset="16" style={{ textAlign: 'right' }}>
      <Button type="primary" htmlType="submit">搜索</Button>
      <Button>清除条件</Button>
    </Col>
  </Row>
</Form>
, mountNode);
/* 定制样式 */

.advanced-search-form {
  padding: 16px 8px;
  background: #f8f8f8;
  border: 1px solid #d9d9d9;
  border-radius: 6px;
}

/* 由于输入标签长度不确定,所以需要微调使之看上去居中 */
.advanced-search-form > .row {
  margin-left: -10px;
}

.advanced-search-form > .row > .col-8 {
  padding: 0 8px;
}

.advanced-search-form .ant-form-item {
  margin-bottom: 16px;
}

.advanced-search-form .ant-btn + .ant-btn {
  margin-left: 8px;
}

三列栅格式的表单排列方式,常用于数据表格的高级搜索。

有部分定制的样式代码,由于输入标签长度不确定,需要根据具体情况自行调整。

import { Button, Form, Input } from 'antd';
const createForm = Form.create;
const FormItem = Form.Item;

function noop() {
  return false;
}

class BasicDemo extends React.Component {
  getValidateStatus(field) {
    const { isFieldValidating, getFieldError, getFieldValue } = this.props.form;

    if (isFieldValidating(field)) {
      return 'validating';
    } else if (!!getFieldError(field)) {
      return 'error';
    } else if (getFieldValue(field)) {
      return 'success';
    }
  }

  handleReset(e) {
    e.preventDefault();
    this.props.form.resetFields();
  }

  handleSubmit(e) {
    e.preventDefault();
    this.props.form.validateFields((errors, values) => {
      if (!!errors) {
        console.log('Errors in form!!!');
        return;
      }
      console.log('Submit!!!');
      console.log(values);
    });
  }

  userExists(rule, value, callback) {
    if (!value) {
      callback();
    } else {
      setTimeout(() => {
        if (value === 'JasonWood') {
          callback([new Error('抱歉,该用户名已被占用。')]);
        } else {
          callback();
        }
      }, 800);
    }
  }

  checkPass(rule, value, callback) {
    const { validateFields } = this.props.form;
    if (value) {
      validateFields(['rePasswd']);
    }
    callback();
  }

  checkPass2(rule, value, callback) {
    const { getFieldValue } = this.props.form;
    if (value && value !== getFieldValue('passwd')) {
      callback('两次输入密码不一致!');
    } else {
      callback();
    }
  }

  render() {
    const { getFieldProps, getFieldError, isFieldValidating } = this.props.form;
    const nameProps = getFieldProps('name', {
      rules: [
        { required: true, min: 5, message: '用户名至少为 5 个字符' },
        { validator: this.userExists },
      ],
    });
    const emailProps = getFieldProps('email', {
      validate: [{
        rules: [
          { required: true },
        ],
        trigger: 'onBlur',
      }, {
        rules: [
          { type: 'email', message: '请输入正确的邮箱地址' },
        ],
        trigger: ['onBlur', 'onChange'],
      }]
    });
    const passwdProps = getFieldProps('passwd', {
      rules: [
        { required: true, whitespace: true, message: '请填写密码' },
        { validator: this.checkPass.bind(this) },
      ],
    });
    const rePasswdProps = getFieldProps('rePasswd', {
      rules: [{
        required: true,
        whitespace: true,
        message: '请再次输入密码',
      }, {
        validator: this.checkPass2.bind(this),
      }],
    });
    const textareaProps = getFieldProps('textarea', {
      rules: [
        { required: true, message: '真的不打算写点什么吗?' },
      ],
    });
    const formItemLayout = {
      labelCol: { span: 7 },
      wrapperCol: { span: 12 },
    };
    return (
      <Form horizontal form={this.props.form}>
        <FormItem
          {...formItemLayout}
          label="用户名:"
          hasFeedback
          help={isFieldValidating('name') ? '校验中...' : (getFieldError('name') || []).join(', ')}>
          <Input {...nameProps} placeholder="实时校验,输入 JasonWood 看看" />
        </FormItem>

        <FormItem
          {...formItemLayout}
          label="邮箱:"
          hasFeedback>
          <Input {...emailProps} type="email" placeholder="onBlur 与 onChange 相结合" />
        </FormItem>

        <FormItem
          {...formItemLayout}
          label="密码:"
          hasFeedback>
          <Input {...passwdProps} type="password" autoComplete="off"
            onContextMenu={noop} onPaste={noop} onCopy={noop} onCut={noop} />
        </FormItem>

        <FormItem
          {...formItemLayout}
          label="确认密码:"
          hasFeedback>
          <Input {...rePasswdProps} type="password" autoComplete="off" placeholder="两次输入密码保持一致"
            onContextMenu={noop} onPaste={noop} onCopy={noop} onCut={noop} />
        </FormItem>

        <FormItem
          {...formItemLayout}
          label="备注:">
          <Input {...textareaProps} type="textarea" placeholder="随便写" id="textarea" name="textarea" />
        </FormItem>

        <FormItem wrapperCol={{ span: 12, offset: 7 }}>
          <Button type="primary" onClick={this.handleSubmit.bind(this)}>确定</Button>
          &nbsp;&nbsp;&nbsp;
          <Button type="ghost" onClick={this.handleReset.bind(this)}>重置</Button>
        </FormItem>
      </Form>
    );
  }
}

BasicDemo = createForm()(BasicDemo);

ReactDOM.render(<BasicDemo />, mountNode);

基本的表单校验例子。

import { Select, Radio, Checkbox, Button, DatePicker, InputNumber, Form, Cascader } from 'antd';
const Option = Select.Option;
const RadioGroup = Radio.Group;
const createForm = Form.create;
const FormItem = Form.Item;

let Demo = React.createClass({
  componentDidMount() {
    this.props.form.setFieldsValue({
      eat: true,
      sleep: true,
      beat: true,
    });
  },

  handleReset(e) {
    e.preventDefault();
    this.props.form.resetFields();
  },

  handleSubmit(e) {
    e.preventDefault();
    this.props.form.validateFieldsAndScroll((errors, values) => {
      if (!!errors) {
        console.log('Errors in form!!!');
        return;
      }
      console.log('Submit!!!');
      console.log(values);
    });
  },

  checkBirthday(rule, value, callback) {
    if (value && value.getTime() >= Date.now()) {
      callback(new Error('你不可能在未来出生吧!'));
    } else {
      callback();
    }
  },

  checkPrime(rule, value, callback) {
    if (value !== 11) {
      callback(new Error('8~12之间的质数明明是11啊!'));
    } else {
      callback();
    }
  },

  render() {
    const address = [{
      value: 'zhejiang',
      label: '浙江',
      children: [{
        value: 'hangzhou',
        label: '杭州',
      }],
    }];
    const { getFieldProps } = this.props.form;
    const selectProps = getFieldProps('select', {
      rules: [
        { required: true, message: '请选择您的国籍' }
      ],
    });
    const multiSelectProps = getFieldProps('multiSelect', {
      rules: [
        { required: true, message: '请选择您喜欢的颜色', type: 'array' },
      ]
    });
    const radioProps = getFieldProps('radio', {
      rules: [
        { required: true, message: '请选择您的性别' }
      ]
    });
    const birthdayProps = getFieldProps('birthday', {
      rules: [
        {
          required: true,
          type: 'date',
          message: '你的生日是什么呢?',
        }, {
          validator: this.checkBirthday,
        }
      ]
    });
    const primeNumberProps = getFieldProps('primeNumber', {
      rules: [{ validator: this.checkPrime }],
    });
    const addressProps = getFieldProps('address', {
      rules: [{ required: true, type: 'array' }],
    });
    const formItemLayout = {
      labelCol: { span: 7 },
      wrapperCol: { span: 12 },
    };
    return (
      <Form horizontal form={this.props.form}>
        <FormItem
          {...formItemLayout}
          label="国籍:">
          <Select {...selectProps} placeholder="请选择国家" style={{ width: '100%' }}>
            <Option value="china">中国</Option>
            <Option value="use">美国</Option>
            <Option value="japan">日本</Option>
            <Option value="korean">韩国</Option>
            <Option value="Thailand">泰国</Option>
          </Select>
        </FormItem>

        <FormItem
          {...formItemLayout}
          label="喜欢的颜色:">
          <Select {...multiSelectProps} multiple placeholder="请选择颜色" style={{ width: '100%' }}>
            <Option value="red">红色</Option>
            <Option value="orange">橙色</Option>
            <Option value="yellow">黄色</Option>
            <Option value="green">绿色</Option>
            <Option value="blue">蓝色</Option>
          </Select>
        </FormItem>

        <FormItem
          {...formItemLayout}
          label="性别:">
          <RadioGroup {...radioProps}>
            <Radio value="male"></Radio>
            <Radio value="female"></Radio>
          </RadioGroup>
        </FormItem>

        <FormItem
          {...formItemLayout}
          label="兴趣爱好:">
          <Checkbox {...getFieldProps('eat', {
            valuePropName: 'checked',
          })} />吃饭饭 &nbsp;
          <Checkbox {...getFieldProps('sleep', {
            valuePropName: 'checked',
          })} />睡觉觉 &nbsp;
          <Checkbox {...getFieldProps('beat', {
            valuePropName: 'checked',
          })} />打豆豆 &nbsp;
        </FormItem>

        <FormItem
          {...formItemLayout}
          label="生日:">
          <DatePicker {...birthdayProps} />
        </FormItem>

        <FormItem
          {...formItemLayout}
          label="8~12间的质数:">
          <InputNumber {...primeNumberProps} min={8} max={12} />
        </FormItem>

        <FormItem
          {...formItemLayout}
          label="选择地址:">
          <Cascader {...addressProps} options={address} />
        </FormItem>

        <FormItem
          wrapperCol={{ span: 12, offset: 7 }} >
          <Button type="primary" onClick={this.handleSubmit}>确定</Button>
          &nbsp;&nbsp;&nbsp;
          <Button type="ghost" onClick={this.handleReset}>重置</Button>
        </FormItem>
      </Form>
    );
  },
});

Demo = createForm()(Demo);
ReactDOM.render(<Demo />, mountNode);

提供以下组件表单域的校验:Select Radio DatePicker InputNumber Cascader。在 submit 时使用 validateFieldsAndScroll,进行校验,可以自动把不在可见范围内的校验不通过的菜单域滚动进可见范围。

import { Button, Form, Input, Row, Col } from 'antd';
import classNames from 'classnames';
const createForm = Form.create;
const FormItem = Form.Item;

function noop() {
  return false;
}

let Demo = React.createClass({
  getInitialState() {
    return {
      passBarShow: false, // 是否显示密码强度提示条
      rePassBarShow: false,
      passStrength: 'L', // 密码强度
      rePassStrength: 'L',
    };
  },

  handleSubmit() {
    this.props.form.validateFields((errors, values) => {
      if (!!errors) {
        console.log('Errors in form!!!');
        return;
      }
      console.log('Submit!!!');
      console.log(values);
    });
  },

  getPassStrenth(value, type) {
    if (value) {
      let strength;
      // 密码强度的校验规则自定义,这里只是做个简单的示例
      if (value.length < 6) {
        strength = 'L';
      } else if (value.length <= 9) {
        strength = 'M';
      } else {
        strength = 'H';
      }
      if (type === 'pass') {
        this.setState({ passBarShow: true, passStrength: strength });
      } else {
        this.setState({ rePassBarShow: true, rePassStrength: strength });
      }
    } else {
      if (type === 'pass') {
        this.setState({ passBarShow: false });
      } else {
        this.setState({ rePassBarShow: false });
      }
    }
  },

  checkPass(rule, value, callback) {
    const form = this.props.form;
    this.getPassStrenth(value, 'pass');

    if (form.getFieldValue('pass')) {
      form.validateFields(['rePass'], { force: true });
    }

    callback();
  },

  checkPass2(rule, value, callback) {
    const form = this.props.form;
    this.getPassStrenth(value, 'rePass');

    if (value && value !== form.getFieldValue('pass')) {
      callback('两次输入密码不一致!');
    } else {
      callback();
    }
  },

  renderPassStrengthBar(type) {
    const strength = type === 'pass' ? this.state.passStrength : this.state.rePassStrength;
    const classSet = classNames({
      'ant-pwd-strength': true,
      'ant-pwd-strength-low': strength === 'L',
      'ant-pwd-strength-medium': strength === 'M',
      'ant-pwd-strength-high': strength === 'H'
    });
    const level = {
      L: '低',
      M: '中',
      H: '高'
    };

    return (
      <div>
        <ul className={classSet}>
          <li className="ant-pwd-strength-item ant-pwd-strength-item-1"></li>
          <li className="ant-pwd-strength-item ant-pwd-strength-item-2"></li>
          <li className="ant-pwd-strength-item ant-pwd-strength-item-3"></li>
          <span className="ant-form-text">
            {level[strength]}
          </span>
        </ul>
      </div>
    );
  },

  render() {
    const { getFieldProps } = this.props.form;

    const passProps = getFieldProps('pass', {
      rules: [
        { required: true, whitespace: true, message: '请填写密码' },
        { validator: this.checkPass }
      ]
    });
    const rePassProps = getFieldProps('rePass', {
      rules: [{
        required: true,
        whitespace: true,
        message: '请再次输入密码',
      }, {
        validator: this.checkPass2,
      }],
    });
    const formItemLayout = {
      labelCol: { span: 6 },
      wrapperCol: { span: 18 },
    };
    return (
      <div>
        <Form horizontal form={this.props.form}>
          <Row>
            <Col span="18">
              <FormItem
                {...formItemLayout}
                label="密码:">
                <Input {...passProps} type="password"
                  onContextMenu={noop} onPaste={noop} onCopy={noop} onCut={noop}
                  autoComplete="off" id="pass"
                />
              </FormItem>
            </Col>
            <Col span="6">
              {this.state.passBarShow ? this.renderPassStrengthBar('pass') : null}
            </Col>
          </Row>

          <Row>
            <Col span="18">
              <FormItem
                {...formItemLayout}
                label="确认密码:">
                <Input {...rePassProps} type="password"
                  onContextMenu={noop} onPaste={noop} onCopy={noop} onCut={noop}
                  autoComplete="off" id="rePass"
                />
              </FormItem>
            </Col>
            <Col span="6">
              {this.state.rePassBarShow ? this.renderPassStrengthBar('rePass') : null}
            </Col>
          </Row>
          <Row>
            <Col span="18">
              <Col span="18" offset="6">
                <Button type="primary" onClick={this.handleSubmit}>提交</Button>
              </Col>
            </Col>
          </Row>
        </Form>
      </div>
    );
  }
});

Demo = createForm()(Demo);

ReactDOM.render(<Demo />, mountNode);
.ant-pwd-strength {
  display: inline-block;
  margin-left: 8px;
  line-height: 32px;
  height: 32px;
  vertical-align: middle;
}

.ant-pwd-strength-item {
  float: left;
  margin-right: 1px;
  margin-top: 12px;
  width: 19px;
  height: 8px;
  line-height: 8px;
  list-style: none;
  background-color: #f3f3f3;
  transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
}

.ant-pwd-strength-item-1 {
  border-top-left-radius: 6px;
  border-bottom-left-radius: 6px;
}

.ant-pwd-strength-item-2 {
  width: 20px;
}

.ant-pwd-strength-item-3 {
  border-top-right-radius: 6px;
  border-bottom-right-radius: 6px;
  margin-right: 8px;
}

.ant-pwd-strength-low .ant-pwd-strength-item-1, .ant-pwd-strength-medium .ant-pwd-strength-item-1, .ant-pwd-strength-high .ant-pwd-strength-item-1 {
  background-color: #FAC450;
}

.ant-pwd-strength-medium .ant-pwd-strength-item-2, .ant-pwd-strength-high .ant-pwd-strength-item-2 {
  background-color: rgba(135, 208, 104, .6);
  filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#9987D068,endColorstr=#9987D068);
}

.ant-pwd-strength-high .ant-pwd-strength-item-3 {
  background-color: #87D068;
}

密码校验实例。

这里使用了 this.props.form.validateFields 方法,在对第一次输入的密码进行校验时会触发二次密码的校验。

import { Button, Form, Input, Modal } from 'antd';
const createForm = Form.create;
const FormItem = Form.Item;

let Demo = React.createClass({
  getInitialState() {
    return { visible: false };
  },

  handleSubmit() {
    console.log(this.props.form.getFieldsValue());
    this.hideModal();
  },

  showModal() {
    this.setState({ visible: true });
  },

  hideModal() {
    this.setState({ visible: false });
  },

  render() {
    const { getFieldProps } = this.props.form;

    const formItemLayout = {
      labelCol: { span: 4 },
      wrapperCol: { span: 20 },
    };
    return (
      <div>
        <Button type="primary" onClick={this.showModal}>点击有惊喜</Button>
        <Modal title="登录" visible={this.state.visible} onOk={this.handleSubmit} onCancel={this.hideModal}>
          <Form horizontal form={this.props.form}>
            <FormItem
              {...formItemLayout}
              label="用户名:">
              <Input {...getFieldProps('username', {})} type="text" autoComplete="off" />
            </FormItem>
            <FormItem
              {...formItemLayout}
              label="密码:">
              <Input {...getFieldProps('password', {})} type="password" autoComplete="off" />
            </FormItem>
          </Form>
        </Modal>
      </div>
    );
  }
});

Demo = createForm()(Demo);

ReactDOM.render(<Demo />, mountNode);

在 Modal 中使用 Form,当点击 Modal 的确定时,调用 this.props.form.getFieldsValue 获取表单内的值。