Form 表单

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

表单#

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

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

表单域#

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

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

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

Input 输入框#

<Input {...props} />

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

API#

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))
getFieldError 获取某个输入控件的 Error Function(name)
isFieldValidating 判断一个输入控件是否在校验状态 Function(name)
resetFields 重置一组输入控件的值与状态,如不传入参数,则重置所有组件 Function([names: string[]])
getFieldProps 详见下面描述

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

参数 说明 类型 可选值 默认值
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
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}>      // 样式类名前缀,默认是 ant-input-group,通常您不需要设置。
  {children}
</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, Row, Col } 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;
    return (
      <Form horizontal onSubmit={this.handleSubmit}>
        <FormItem
          label="用户名:"
          labelCol={{ span: 6 }}
          wrapperCol={{ span: 14 }}>
          <p className="ant-form-text" id="userName" name="userName">大眼萌 minion</p>
        </FormItem>
        <FormItem
          label="密码:"
          labelCol={{ span: 6 }}
          wrapperCol={{ span: 14 }}>
          <Input type="password" {...getFieldProps('pass')} placeholder="请输入密码" />
        </FormItem>
        <FormItem
          label="您的性别:"
          labelCol={{ span: 6 }}
          wrapperCol={{ span: 14 }}>
            <RadioGroup {...getFieldProps('gender', { initialValue: 'female' })}>
              <Radio value="male">男的</Radio>
              <Radio value="female">女的</Radio>
            </RadioGroup>
        </FormItem>
        <FormItem
          label="备注:"
          labelCol={{ span: 6 }}
          wrapperCol={{ span: 14 }}
          help="随便写点什么">
          <Input type="textarea" placeholder="随便写" {...getFieldProps('remark')} />
        </FormItem>
        <FormItem
          wrapperCol={{ span: 14, offset: 6 }}>
          <label>
            <Checkbox {...getFieldProps('agreement')} />同意
          </label>
        </FormItem>
        <Row>
          <Col span="16" offset="6">
            <Button type="primary" htmlType="submit">确定</Button>
          </Col>
        </Row>
      </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 value="b">
          <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, Row, 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>
        <Row style={{ marginTop: 24 }}>
          <Col span="16" offset="8">
            <Button type="primary" htmlType="submit">确定</Button>
          </Col>
        </Row>
      </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 { Row, Col, Button, Input, Form } from 'antd';
const FormItem = Form.Item;

ReactDOM.render(
  <Form horizontal>
    <FormItem
      label="单独禁用输入框:"
      labelCol={{ span: 5 }}
      wrapperCol={{ span: 12 }}>
      <Input defaultValue="我是禁用的" disabled />
    </FormItem>

    <fieldset disabled>
      <legend>禁用的 fieldset</legend>
      <FormItem
        id="userName"
        label="用户名:"
        labelCol={{ span: 5 }}
        wrapperCol={{ span: 12 }}
        required>
        <p className="ant-form-text">大眼萌 minion</p>
      </FormItem>
      <FormItem
        id="password"
        label="密码:"
        labelCol={{ span: 5 }}
        wrapperCol={{ span: 12 }}
        required>
        <Input type="password" defaultValue="123456" id="password" />
      </FormItem>
      <Row>
        <Col span="12" offset="5">
          <Button htmlType="submit" type="primary">确定</Button>
        </Col>
      </Row>
    </fieldset>
  </Form>
, mountNode);

1) 单独为输入控件设置 disabled 属性;

2) 为 <fieldset> 设置 disabled 属性,可以禁用 <fieldset> 中包含的所有控件。

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} 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 type="ghost">清除条件</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;

    return (
      <Form horizontal form={this.props.form}>
        <FormItem
          label="用户名:"
          labelCol={{ span: 7 }}
          wrapperCol={{ span: 12 }}
          hasFeedback
          help={isFieldValidating('name') ? '校验中...' : (getFieldError('name') || []).join(', ')}>
          <Input placeholder="实时校验,输入 JasonWood 看看"
            {...getFieldProps('name', {
              rules: [
                { required: true, min: 5, message: '用户名至少为 5 个字符' },
                { validator: this.userExists },
              ],
            })} />
        </FormItem>

        <FormItem
          label="邮箱:"
          labelCol={{ span: 7 }}
          wrapperCol={{ span: 12 }}
          hasFeedback>
          <Input type="email" placeholder="onBlur 与 onChange 相结合"
            {...getFieldProps('email', {
              validate: [{
                rules: [
                  { required: true },
                ],
                trigger: 'onBlur',
              }, {
                rules: [
                  { type: 'email', message: '请输入正确的邮箱地址' },
                ],
                trigger: ['onBlur', 'onChange'],
              }]
            })}/>
        </FormItem>

        <FormItem
          label="密码:"
          labelCol={{ span: 7 }}
          wrapperCol={{ span: 12 }}
          hasFeedback>
          <Input type="password" autoComplete="off"
            {...getFieldProps('passwd', {
              rules: [
                { required: true, whitespace: true, message: '请填写密码' },
                { validator: this.checkPass.bind(this) },
              ],
            })}
            onContextMenu={noop} onPaste={noop} onCopy={noop} onCut={noop}/>
        </FormItem>

        <FormItem
          label="确认密码:"
          labelCol={{ span: 7 }}
          wrapperCol={{ span: 12 }}
          hasFeedback>
          <Input type="password" autoComplete="off" placeholder="两次输入密码保持一致"
            onContextMenu={noop} onPaste={noop} onCopy={noop} onCut={noop}
            {...getFieldProps('rePasswd', {
              rules: [{
                required: true,
                whitespace: true,
                message: '请再次输入密码',
              }, {
                validator: this.checkPass2.bind(this),
              }],
            })}/>
        </FormItem>

        <FormItem
          label="备注:"
          labelCol={{ span: 7 }}
          wrapperCol={{ span: 12 }}>
          <Input type="textarea" placeholder="随便写" id="textarea" name="textarea"
            {...getFieldProps('textarea', {
              rules: [
                { required: true, message: '真的不打算写点什么吗?' },
              ],
            })}/>
        </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, 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({
  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);
    });
  },

  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;
    return (
      <Form horizontal form={this.props.form}>
        <FormItem
          label="国籍:"
          labelCol={{ span: 7 }}
          wrapperCol={{ span: 12 }}>
          <Select placeholder="请选择国家" style={{ width: '100%' }}
            {...getFieldProps('select', {
              rules: [
                { required: true, message: '请选择您的国籍' }
              ],
            })}
          >
            <Option value="china">中国</Option>
            <Option value="use">美国</Option>
            <Option value="japan">日本</Option>
            <Option value="korean">韩国</Option>
            <Option value="Thailand">泰国</Option>
          </Select>
        </FormItem>

        <FormItem
          label="喜欢的颜色:"
          labelCol={{ span: 7 }}
          wrapperCol={{ span: 12 }}>
          <Select multiple placeholder="请选择颜色" style={{ width: '100%' }}
            {...getFieldProps('multiSelect', {
              rules: [
                { required: true, message: '请选择您喜欢的颜色', type: 'array' },
              ]
            })}
          >
            <Option value="red">红色</Option>
            <Option value="orange">橙色</Option>
            <Option value="yellow">黄色</Option>
            <Option value="green">绿色</Option>
            <Option value="blue">蓝色</Option>
          </Select>
        </FormItem>

        <FormItem
          label="性别:"
          labelCol={{ span: 7 }}
          wrapperCol={{ span: 12 }}>
          <RadioGroup
            {...getFieldProps('radio', {
              rules: [
                { required: true, message: '请选择您的性别' }
              ]
            })}
          >
            <Radio value="male"></Radio>
            <Radio value="female"></Radio>
          </RadioGroup>
        </FormItem>

        <FormItem
          label="生日:"
          labelCol={{ span: 7 }}
          wrapperCol={{ span: 12 }}>
          <DatePicker
            {...getFieldProps('birthday', {
              rules: [
                {
                  required: true,
                  type: 'date',
                  message: '你的生日是什么呢?',
                }, {
                  validator: this.checkBirthday,
                }
              ]
            })}
          />
        </FormItem>

        <FormItem
          label="8~12间的质数:"
          labelCol={{ span: 7 }}
          wrapperCol={{ span: 12 }}>
          <InputNumber min={8} max={12}
            {...getFieldProps('primeNumber', {
              rules: [{ validator: this.checkPrime }],
            })}
          />
        </FormItem>

        <FormItem
          label="选择地址:"
          labelCol={{ span: 7 }}
          wrapperCol={{ span: 12 }}>
          <Cascader options={address}
            {...getFieldProps('address', {
              rules: [{ required: true, type: 'array' }],
            })}
          />
        </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

import { Button, Form, Input, Row, Col, Modal } 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',
      visible: false,
    };
  },

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

  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 });
      }
    }
  },

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

  hideModal() {
    this.setState({ visible: 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;
    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}>
            <Row>
              <Col span="18">
                <FormItem
                  label="密码:"
                  labelCol={{ span: 6 }}
                  wrapperCol={{ span: 18 }}>
                  <Input type="password"
                    {...getFieldProps('pass', {
                      rules: [
                        { required: true, whitespace: true, message: '请填写密码' },
                        { validator: this.checkPass }
                      ]
                    })}
                    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
                  label="确认密码:"
                  labelCol={{ span: 6 }}
                  wrapperCol={{ span: 18 }}>
                  <Input type="password"
                    {...getFieldProps('rePass', {
                      rules: [{
                        required: true,
                        whitespace: true,
                        message: '请再次输入密码',
                      }, {
                        validator: this.checkPass2,
                      }],
                    })}
                    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>
          </Form>
        </Modal>
      </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;
}

密码校验实例。

这里使用了 validation 的 forceValidate(fields, callback) 方法,在对第一次输入的密码进行校验时会触发二次密码的校验。