# 表单

## descr

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

## 案例演示

### 基本使用

该案例中，通过设置多个 `pattern` 校验规则，用于校验用户名的合法性

---demo
```js
import { Form, Input, Button, AmosAlert, Select } from 'amos-framework';

const FormItem = Form.Item;
const Option = Select.Option;

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      form: {
        score: 1,
        name: 'ilex_h',
        password: '',
        hobby: ''
      },
      rules: {
        name: [
          { required: true, message: '请输入名称' },
          { pattern: /^[a-zA-Z0-9_-]+$/, message: '用户名格式不正确!' },
          { pattern: /^((?!(sb|cnm)).)*$/g, message: '不能输入敏感词汇!' },
          { min: 5, message: '长度不够！' }
        ],
        password: [
          { required: true, message: '不能为空！' },
          { min: 6, message: '长度不够！' },
          { max: 14, message: '长度超出！' }
        ],
        score: [
          { required: true, message: '请输入分数' },
          { type: 'number', min: 0, max: 100, message: '分数范围不准确！0<= score <=100 ' }
        ],
        hobby: [
          { required: true, message: '请选择擅长项！' }
        ]
      }
    };
  }

  onChange = (key, e) => {
    const value = e.target.value;
    const newForm = Object.assign({}, this.state.form, { [key]: value });
    this.setState({
      form: newForm
    });
  }

  onScoreChange = (value) => {
    const { form } = this.state;
    // 直接修改 form （由于是引用类型，会自动将 prevProps 也修改）
    // form.score = value;
    // this.setState({
    //   form
    // });

    // this.setState({
    //   form: { ...form, score: value }
    // });

    const newForm = Object.assign({}, form, { score: value });
    this.setState({
      form: newForm
    });
  }

  onHobbyChange = (value, item) => {
    const newForm = Object.assign({}, this.state.form, { hobby: value });
    this.setState({
      form: newForm
    });
  }

  handleSubmit = (e) => {
    e.preventDefault();
    this.form.validate((valid, dataValues, errors) => {
      console.log('返回内容:', dataValues, valid, errors);
      if (valid) {
        AmosAlert.success('结果', JSON.stringify(dataValues));
      } else {
        console.log('error submit!!');
        return false;
      }
    });
  }

  render() {
    const { form, rules } = this.state;
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 4 },
        className: 'colspanlab'
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 14 },
        className: 'colspan'
      }
    };
    const wrapperCol = {
      wrapperCol: {
        xs: { span: 24, offset: 0 },
        sm: { span: 14, offset: 4 }
      }
    };
    return (
      <Form style={{ width: 500, padding: '30px 0 0 0' }} className="basic-demo" ref={component => this.form = component} model={form} rules={rules}>
        <FormItem label="用户名" tip="名称最小长度为5" field="name" {...formItemLayout} >
          <Input
            value={form.name}
            placeholder="请输入用户名"
            onChange={(e) => this.onChange('name', e)}
          />
        </FormItem>
        <FormItem label={<span>密码</span>} field="password" {...formItemLayout} >
          <Input
            value={form.password}
            type="password"
            placeholder="请输入密码"
            onChange={(e) => this.onChange('password', e)}
          />
        </FormItem>
        <FormItem label="分数" field="score" {...formItemLayout} >
          <InputNumber
            value={form.score}
            placeholder="请输入分数"
            onChange={this.onScoreChange}
          />
        </FormItem>
        <FormItem label="擅长" field="hobby" {...formItemLayout} >
          <Select value={form.hobby} onChange={this.onHobbyChange} hasClear>
            <Option value="c">c++</Option>
            <Option value="java">java</Option>
            <Option value="web">web</Option>
            <Option value="html">html</Option>
            <Option value="javascript">javascript</Option>
          </Select>
        </FormItem>
        <FormItem {...wrapperCol}>
          <Button onClick={this.handleSubmit}>提交</Button>
        </FormItem>
      </Form>
    );
  }
}

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### 设置 label 为 null

将 label 设置为 null，从而禁用 label Col

---demo
```js
import { Form, Input, Button, AmosAlert } from 'amos-framework';

const FormItem = Form.Item;

const formItemLayout = {
  wrapperCol: {
    xs: { span: 24 },
    sm: { span: 24 }
  }
};

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      form: {
        name: 'ilex_h',
        password: ''
      }
    };
  }

  onChange = (key, e) => {
    const value = e.target.value;
    const newForm = Object.assign({}, this.state.form, { [key]: value });
    this.setState({
      form: newForm
    });
  }

  render() {
    const { form, rules } = this.state;
    return (
      <Form style={{ width: 500, padding: '30px 0 0 0' }} className="basic-demo" ref={component => this.form = component} model={form} rules={rules}>
        <FormItem label={null} field="name" {...formItemLayout} >
          <Input
            value={form.name}
            placeholder="请输入用户名"
            onChange={(e) => this.onChange('name', e)}
          />
        </FormItem>
        <FormItem label={null} field="password" {...formItemLayout} >
          <Input
            value={form.password}
            type="password"
            placeholder="请输入密码"
            onChange={(e) => this.onChange('password', e)}
          />
        </FormItem>
      </Form>
    );
  }
}

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### 设置 labelWidth, 同时铺满

设置 labelWidth, 采用铺满模式 `filled=true`。此模式下，无需设置 col 配置项。

---demo
```js
import { Form, Input, Button, AmosAlert, InputNumber, Checkbox } from 'amos-framework';

const FormItem = Form.Item;

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      form: {
        uniqueKey: 'ilex_h',
        groupName: '',
        count: 1,
        count1: 2,
        count2: 3
      }
    };
  }

  onChange = (key, e) => {
    const value = e.target.value;
    const newForm = Object.assign({}, this.state.form, { [key]: value });
    this.setState({
      form: newForm
    });
  }

  onCheckedChange = (e) => {
    const value = e.target.checked;
    const newForm = Object.assign({}, this.state.form, { 'isRequired': value });
    this.setState({
      form: newForm
    });
  }

  onNumChange = (key, value) => {
    const newForm = Object.assign({}, this.state.form, { [key]: value });
    this.setState({
      form: newForm
    });
  }

  render() {
    const { form, rules } = this.state;
    return (
      <Form
        style={{ width: 500, padding: '30px 0 0 0' }}
        className="basic-demo"
        ref={component => this.form = component}
        model={form}
        rules={rules}
        labelWidth="8em"
        filled
      >
        <FormItem label="唯一标识" field="uniqueKey" >
          <Input
            value={form.name}
            placeholder="请输入"
            onChange={(e) => this.onChange('uniqueKey', e)}
          />
        </FormItem>
        <FormItem label="是否必填" field="isRequired" >
          <Checkbox checked={form.isRequired} onChange={this.onCheckedChange}></Checkbox>
        </FormItem>
        <FormItem label="分组名称" field="groupName" >
          <Input
            value={form.name}
            placeholder="请输入"
            onChange={(e) => this.onChange('groupName', e)}
          />
        </FormItem>
        <FormItem label="数量" field="count" >
          <InputNumber
            value={form.count}
            onChange={(e) => this.onNumChange('count', e)}
          />
        </FormItem>
        <FormItem label="数量1" field="count1" >
          <InputNumber
            value={form.count1}
            type="outer"
            onChange={(e) => this.onNumChange('count1', e)}
          />
        </FormItem>
        <FormItem label="数量2" field="count2" >
          <InputNumber
            value={form.count2}
            type="nocontrol"
            onChange={(e) => this.onNumChange('count2', e)}
          />
        </FormItem>
      </Form>
    );
  }
}

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### 表单集合

---demo
```js
import { Form, Input, Button, AmosAlert, Switch, TagSelect } from 'amos-framework';

const FormItem = Form.Item;
const TSOption = TagSelect.Option;

const datas = [
  { value: 'xian', label: '西安市', children: [
      { value: 'yantaqu', label: '雁塔区', children: [ { value: 'zhonglou', label: '大雁塔' } ]}
    ]
  },
  { value: 'shangluo', label: '商洛市', disabled: true, children: [
      { value: 'zhenan', label: '镇安', children: [ { value: 'tayunshan', label: '塔云山' } ] }
  ]}
];

const Tags = ({ categorys = [], defaultValue, onChange, expandable, single }) => {
  return (
    <TagSelect onChange={onChange} expandable={expandable} single={single} defaultValue={defaultValue} >
      {categorys.map(c => <TSOption key={c.key} value={c.value}>{c.text}</TSOption>)}
    </TagSelect>
  );
};

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      form: {
        name: 'ilex_h',
        password: '',
        email: '390***625@qq.com',
        online: true,
        category_radio: 'secrecy',
        category: [ 'html','javascript' ]
      },
      tagRadioOptions: [
        { key: 'tr-1', value: 'secrecy', text: '保密' },
        { key: 'tr-2', value: 'male', text: '男生' },
        { key: 'tr-3', value: 'fmale', text: '女生' }
      ],
      tagOptions: [
        { key: 'm-1', value: 'html', text: 'html' },
        { key: 'm-2', value: 'css', text: 'css' },
        { key: 'm-3', value: 'javascript', text: 'javascript' }
      ],
      rules: {
        name: [
          { required: true, message: '请输入活动名称' },
          { min: 4, message: '长度不够！' }
        ],
        password: [
          { required: true, message: '不能为空！' },
          { min: 6, message: '长度不够！' },
          { max: 14, message: '长度超出！' }
        ],
        email: [
          { type: 'email', message: '非法E-mail!' }
        ],
        category: [
          { required: true, message: '必须选择一个选项！' }
        ],
        region: [
          { required: true, message: '区域不能为空！' }
        ],
        depart: [
          { required: true, message: '部门不能为空！' }
        ],
        custom: [
          { required: true, message: '不能为空！' },
          {
            // 自定义校验规则 callback() 必须调用
            validator: (rule, value,callback)=>{
              if (value.length > 1){
                callback();
              } else {
                callback(new Error('至少选两个选项'));
              }
            }
          }
        ]
      }
    };
  }

  onChange = (key, e) => {
    const { form } = this.state;
    const value = e.target ? e.target.value : e;

    // const newForm = {
    //   ...form,
    //   [key]: value
    // };
    // this.setState({ form: newForm });
    form[key] = value;
    this.setState({ form });
  }

  onSwitchChange = (checked) => {
    const { form } = this.state;
    form['online'] = checked;
    this.setState({ form });
  }

  onTagChange = (key, checkedTags) => {
    const { form } = this.state;
    form[key] = checkedTags;
    this.setState({ form });
  }

  handleSubmit = (e) => {
    e.preventDefault();
    this.form.validate((valid,dataValues) => {
      console.log('返回内容:', dataValues, valid);
      if (valid) {
        AmosAlert.success('结果', JSON.stringify(dataValues));
      } else {
        console.log('error submit!!');
        return false;
      }
    });
  }

  handleReset = (e) => {
    e.preventDefault();
    console.log('--reset:', this.state.form);
    this.form.resetFields((model)=>{
      this.setState({ form: model });
    });
  }

  render() {
    const { form, rules } = this.state;
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 4 },
        className: 'colspanlab'
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 14 },
        className: 'colspan'
      }
    };
    const wrapperCol = {
      wrapperCol: {
        xs: { span: 24, offset: 0 },
        sm: { span: 14, offset: 4 }
      }
    };
    return (
      <Form style={{ width: 500 }} ref={component => this.form = component} model={form} rules={rules}>
        <FormItem label={<span>用户名</span>} field="name" {...formItemLayout} >
          <Input
            value={form.name}
            placeholder="请输入用户名"
            onChange={(e) => this.onChange('name', e)}
          />
        </FormItem>
        <FormItem label="密码" field="password" help="密码帮助信息，比如只能英文字母"  {...formItemLayout} >
          <Input
            value={form.password}
            type="password"
            placeholder="请输入密码"
            onChange={e => this.onChange('password', e)}
          />
        </FormItem>
        <FormItem label="是否在线" field="online" {...formItemLayout} >
          <Switch onOff={form.online} onChange={this.onSwitchChange} />
        </FormItem>
        <FormItem label="邮箱" field="email" {...formItemLayout} >
          <Input
            value={form.email}
            placeholder="请输入邮箱"
            onChange={e => this.onChange('email', e)}
          />
        </FormItem>
        <FormItem label="分类单选" field="category_radio" {...formItemLayout} >
          <Tags
            categorys={this.state.tagRadioOptions}
            single
            defaultValue={form.category_radio}
            onChange={(e) => this.onTagChange('category_radio', e)}
          />
        </FormItem>
        <FormItem label="分类多选" field="category" {...formItemLayout} >
          <Tags
            categorys={this.state.tagOptions}
            defaultValue={form.category}
            expandable={false}
            onChange={(e) => this.onTagChange('category', e)}
          />
        </FormItem>
        <FormItem label="区域" field="region" {...formItemLayout} >
          <Cascader
            options={datas}
            value={form.region}
            hasClear
            onChange={(value) => this.onChange('region', value)}
            placeholder="Please select"
          />
        </FormItem>
        <FormItem label="部门" field="depart" {...formItemLayout} >
          <Cascader
            options={datas}
            value={form.depart}
            hasClear
            enableParent
            onChange={(value) => this.onChange('depart', value)}
            placeholder="Please select"
          />
        </FormItem>
        <FormItem {...wrapperCol}>
          <Button size="sm" onClick={this.handleSubmit}>提交</Button>
          <Button size="sm" onClick={this.handleReset}>重置</Button>
          <Button size="sm" onClick={()=>{console.log(this.state.form);}}>查看State</Button>
        </FormItem>
      </Form>
    );
  }
}

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### layout 参数配置

注意：`layout='horizontal'` 依赖于 `labelCol 和 wrapCol` 配置。也可以使用 `labelPosition` 快速布局

---demo
```js
import { Form, Input, Button, AmosAlert, TagSelect } from 'amos-framework';

const FormItem = Form.Item;
const Option = TagSelect.Option;

const Tags = ({ options = [], defaultValue, onChange }) => {
  return (
    <TagSelect onChange={onChange} expandable={false} single defaultValue={defaultValue} >
      {options.map(opt => <Option key={opt} value={opt}>{opt}</Option>)}
    </TagSelect>
  );
};

const layouts = ['horizontal', 'vertical', 'inline'];

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      layout: 'inline',
      form: {
        name: 'ilex_h',
        password: ''
      },
      rules: {
        name: [
          { required: true, message: '请输入活动名称' },
          { min: 5, message: '长度不够！' }
        ],
        password: [
          { required: true, message: '不能为空！' },
          { min: 6, message: '长度不够！' },
          { max: 14, message: '长度超出！' }
        ]
      }
    };
  }

  onTagChange = (layout) => {
    this.setState({
      layout
    });
  }

  onChange = (key, e) => {
    const { form } = this.state;
    const value = e.target.value;
    form[key] = value;
    this.setState({ form });
  }

  handleSubmit = (e) => {
    e.preventDefault();
    this.form.validate((valid, dataValues) => {
      console.log('返回内容:', dataValues, valid);
      if (valid) {
        AmosAlert.success('结果', JSON.stringify(dataValues));
      } else {
        console.log('error submit!!');
        return false;
      }
    });
  }

  render() {
    const { form, rules, layout } = this.state;
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 4 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 14 },
        className: 'demo-classname'
      }
    };
    const wrapperCol = {
      wrapperCol: {
        xs: { span: 24, offset: 0 },
        sm: { span: 14, offset: 4 }
      }
    };
    return (
      <div>
        <Tags
          options={layouts}
          defaultValue='inline'
          onChange={this.onTagChange}
        />
        <Form
          layout={layout}
          style={{ width: 500, padding: '30px 0 0 0' }}
          className="layout-demo"
          ref={component => this.form = component}
          model={form}
          rules={rules}
        >
          <FormItem label={<span>用户名</span>} field="name" {...formItemLayout} >
            <Input
              value={form.name}
              placeholder="请输入用户名"
              onChange={(e) => this.onChange('name', e)}
            />
          </FormItem>
          <FormItem label="密码" field="password" {...formItemLayout} >
            <Input
              value={form.password}
              type="password"
              placeholder="请输入密码"
              onChange={(e) => this.onChange('password', e)}
            />
          </FormItem>
          <FormItem {...wrapperCol}>
            <Button onClick={this.handleSubmit}>提交</Button>
          </FormItem>
        </Form>
      </div>
    );
  }
}

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### labelPosition 参数配置

注意：设置了 labelPosition 时，layout 不能设置为 `horizontal` 和 `vertical`。最佳体验，需要将 layout 设置为 `none`

---demo
```js
import { Form, Input, Button, AmosAlert, TagSelect, StdForm } from 'amos-framework';

const FormItem = Form.Item;
const FormFooter = Form.Footer;
const Option = TagSelect.Option;

const Tags = ({ options = [], defaultValue, onChange }) => {
  return (
    <StdForm labelStyle={{ width: 100 }} style={{ width: 280 }} label="标签对齐方式">
      <TagSelect style={{ display: 'inline-block' }} onChange={onChange} expandable={false} single defaultValue={defaultValue} >
        {options.map(opt => <Option key={opt} value={opt}>{opt}</Option>)}
      </TagSelect>
    </StdForm>
  );
};

const layouts = ['left', 'right', 'top'];
const formItemStyle = { width: '100%' };

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      labelPosition: 'left',
      form: {
        name: 'ilex_h',
        password: ''
      },
      rules: {
        name: [
          { required: true, message: '请输入活动名称' },
          { min: 5, message: '长度不够！' }
        ],
        password: [
          { required: true, message: '不能为空！' },
          { min: 6, message: '长度不够！' },
          { max: 14, message: '长度超出！' }
        ]
      }
    };
  }

  onTagChange = (labelPosition) => {
    this.setState({
      labelPosition
    });
  }

  onChange = (key, e) => {
    const { form } = this.state;
    const value = e.target.value;
    form[key] = value;
    this.setState({ form });
  }

  handleSubmit = (e) => {
    e.preventDefault();
    this.form.validate((valid, dataValues) => {
      console.log('返回内容:', dataValues, valid);
      if (valid) {
        AmosAlert.success('结果', JSON.stringify(dataValues));
      } else {
        console.log('error submit!!');
        return false;
      }
    });
  }

  render() {
    const { form, rules, labelPosition } = this.state;

    return (
      <div>
        <Tags
          options={layouts}
          defaultValue='left'
          onChange={this.onTagChange}
        />
        <Form
          layout="none"
          labelPosition={labelPosition}
          labelWidth="100px"
          style={{ width: 600, padding: '30px 0 0 0' }}
          ref={component => this.form = component}
          model={form}
          rules={rules}
        >
          <FormItem label={<span>用户名</span>} field="name">
            <Input
              value={form.name}
              placeholder="请输入用户名"
              style={formItemStyle}
              onChange={(e) => this.onChange('name', e)}
            />
          </FormItem>
          <FormItem label="密码" field="password">
            <Input
              value={form.password}
              type="password"
              placeholder="请输入密码"
              style={formItemStyle}
              onChange={(e) => this.onChange('password', e)}
            />
          </FormItem>
          <FormFooter>
            <Button onClick={this.handleSubmit}>提交</Button>
          </FormFooter>
        </Form>
      </div>
    );
  }
}

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### 其它配置项

---demo
```js
import { Form, Input, AmosAlert } from 'amos-framework';

const FormItem = Form.Item;
const FormSubmit = Form.FormSubmit;

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      form: {
        name: 'ilex_h',
        password: '',
        other: '固定参数，非 form 表单控制项'
      },
      loading: false
    };
  }

  onChange = (key, e) => {
    const value = e.target.value;
    const newForm = Object.assign({}, this.state.form, { [key]: value });
    this.setState({
      form: newForm
    });
  }

  onHobbyChange = (value, item) => {
    const newForm = Object.assign({}, this.state.form, { hobby: value });
    this.setState({
      form: newForm
    });
  }

  onSubmit = (valid, dataValues) => {
    console.log('返回内容:', dataValues, valid);
    // 模拟加载
    setTimeout(() => {
      if (valid) {
        AmosAlert.success('结果', JSON.stringify(dataValues));
        this.setState({
          loading: false
        });
      } else {
        console.log('error submit!!');
        return false;
      }
    }, 3000);
  }

  onBeforeSubmit = () => {
    this.setState({
      loading: true
    });
  }

  render() {

    const { form, rules, loading } = this.state;
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 4 },
        className: 'colspanlab'
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 14 },
        className: 'colspan'
      }
    };

    return (
      <Form
        style={{ width: 500, padding: '30px 0 0 0' }}
        className="basic-demo"
        ref={component => this.form = component}
        model={form}
        rules={rules}
        onBeforeSubmit={this.onBeforeSubmit}
        onSubmit={this.onSubmit}
        labelWidth="100px"
      >
        <FormItem label={<span>用户名</span>} field="name" {...formItemLayout} >
          <Input
            value={form.name}
            placeholder="请输入用户名"
            onChange={(e) => this.onChange('name', e)}
          />
        </FormItem>
        <FormItem label="密码" field="password" {...formItemLayout} >
          <Input
            value={form.password}
            type="password"
            placeholder="请输入密码"
            onChange={(e) => this.onChange('password', e)}
          />
        </FormItem>
        <FormSubmit loading={loading}>保存</FormSubmit>
      </Form>
    );
  }
}

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### 自定义 rules

---demo
```js
import { Form, Input, Button, AmosAlert } from 'amos-framework';

const FormItem = Form.Item;

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      form: {
        name: 'ilex_h',
        password: '',
        verifyPassword: ''
      },
      rules: {
        name: [
          { required: true, message: '请输入活动名称' },
          { min: 5, message: '长度不够！' }
        ],
        password: [
          { required: true, message: '不能为空！' },
          { min: 6, message: '长度不够！' },
          { max: 14, message: '长度超出！' },
          { validator: (rule, value, callback) => {
            if (value === '') {
              // callback(new Error('请输入密码'));
              callback('请输入密码');
            } else {
              // 校验确认密码
              if (this.state.form.verifyPassword !== '') {
                this.form.validateField('verifyPassword');
              }
              callback();
            }
          } }
        ],
        verifyPassword: [
        { required: true, message: '请再次输入密码', trigger: 'blur' },
        { validator: (rule, value, callback) => {
          if (value === '') {
            // callback(new Error('请再次输入密码'));
            callback('请再次输入密码');
          } else if (value !== this.state.form.password) {
            // callback(new Error('两次输入密码不一致!'));
            callback('两次输入密码不一致!');
          } else {
            callback();
          }
        } }
      ]
      }
    };
  }

  onChange = (key, e) => {
    const value = e.target.value;
    this.setState({
      form: Object.assign({}, this.state.form, { [key]: value })
    });
  }

  handleSubmit = (e) => {
    e.preventDefault();
    this.form.validate((valid, dataValues) => {
      console.log('返回内容:', dataValues, valid);
      if (valid) {
        AmosAlert.success('结果', JSON.stringify(dataValues));
      } else {
        console.log('error submit!!');
        return false;
      }
    });
  }

  render() {
    const { form, rules } = this.state;
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 4 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 14 },
        className: 'demo-classname'
      }
    };
    const wrapperCol = {
      wrapperCol: {
        xs: { span: 24, offset: 0 },
        sm: { span: 14, offset: 4 }
      }
    };
    return (
      <Form
        style={{ width: 500, padding: '30px 0 0 0' }}
        className="layout-demo"
        ref={component => this.form = component}
        model={form}
        rules={rules}
      >
        <FormItem label={<span>用户名</span>} field="name" {...formItemLayout} >
          <Input
            value={form.name}
            placeholder="请输入用户名"
            onChange={(e) => this.onChange('name', e)}
          />
        </FormItem>
        <FormItem label="密码" field="password" {...formItemLayout} >
          <Input
            value={form.password}
            type="password"
            placeholder="请输入密码"
            onChange={(e) => this.onChange('password', e)}
          />
        </FormItem>
        <FormItem label="确认密码" field="verifyPassword" {...formItemLayout} >
          <Input
            value={form.verifyPassword}
            type="password"
            placeholder="请输入密码"
            onChange={(e) => this.onChange('verifyPassword', e)}
          />
        </FormItem>
        <FormItem {...wrapperCol}>
          <Button onClick={this.handleSubmit}>提交</Button>
        </FormItem>
      </Form>
    );
  }
}

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend


### 特殊组件的校验

上传组件

---demo
```js
import { Form, Button, AmosAlert, Upload, Icon } from 'amos-framework';

const FormItem = Form.Item;

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      form: {
      },
      rules: {
        file: [
          { required: true, message: '必填项' }
        ],
        pictures: [
          { required: true, message: '必填项' }
        ]
      }
    };
  }

  onChange = (key, value) => {
    const newForm = Object.assign({}, this.state.form, { [key]: value });
    this.setState({
      form: newForm
    });
  }

  // 处理单个文件
  handleFileChange = (info) => {
    if (info.file.status === 'uploading') {
      //
    }else if (info.file.status === 'done') {
      console.log('handleFileChange done！');
      // 文件上传完毕，需要将文件路径填充到 form 中
      this.onChange('file', info.file.name);
    } else if (info.file.status === 'error') {
      //
    } else if (info.file.status === 'removed') {
      // 移除
      this.onChange('file', null);
    }
  }

  // 多个文件
  handlePicturesChange = ({ fileList }) => {
    console.log('handlePicturesChange!');
    this.onChange('pictures', fileList);
  };


  handleSubmit = (e) => {
    e.preventDefault();
    this.form.validate((valid, dataValues, errors) => {
      console.log('返回内容:', dataValues, valid, errors);
      if (valid) {
        AmosAlert.success('结果', JSON.stringify(dataValues));
      } else {
        console.log('error submit!!');
        return false;
      }
    });
  }

  render() {
    const { form, rules } = this.state;

    const uploadButton = (
      <div>
        <Icon icon="plus" />
        <div className="amos-upload-text">Upload</div>
      </div>
    );

    return (
      <Form
        style={{ width: 500, padding: '30px 0 0 0' }}
        className="basic-demo"
        ref={component => this.form = component}
        model={form}
        rules={rules}
        labelWidth="8em"
        filled
      >
        <FormItem label="附件" field="file" >
          {
            form.file ? <span>{form.file}</span> :
            <Upload
              name="file"
              action="http://localhost:3100/file/upload"
              headers={{
                authorization: 'authorization-text'
              }}
              onChange={this.handleFileChange}
            >
              <Button>
                <Icon icon="cloudupload" /> 上传附件
              </Button>
            </Upload>
          }
        </FormItem>
        <FormItem label="照片墙" field="pictures" >
          <Upload
            action="http://localhost:3100/file/upload"
            listType="picture-card"
            fileList={form.pictures}
            onChange={this.handlePicturesChange}
          >
            {form.pictures?.length >= 3 ? null : uploadButton}
          </Upload>
        </FormItem>
        <FormItem>
          <Button onClick={this.handleSubmit}>提交</Button>
        </FormItem>
      </Form>
    );
  }
}

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### FormItem props 配置

FromItem 设置相关 props 属性。如果 rules 中设置了 `required=true`，则必填项为 true

---demo
```js
import { Form, Input, Button, AmosAlert, Checkbox } from 'amos-framework';

const FormItem = Form.Item;

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isRequired: false,
      form: {
        name: 'ilex_h'
      },
      rules: {
        name: [
          // 可以通过 `启用/禁用` 该行代码，查看效果
          { required: false, message: '请输入活动名称' },
          { min: 5, message: '长度不够！' }
        ],
        descr: [
          { required: true, message: '请输入活动名称' }
        ]
      }
    };
  }

  onChange = (key, e) => {
    const value = e.target.value;
    this.setState({
      form: Object.assign({}, this.state.form, { [key]: value })
    });
  }

  handleSubmit = (e) => {
    e.preventDefault();
    this.form.validate((valid, dataValues) => {
      console.log('返回内容:', dataValues, valid);
      if (valid) {
        AmosAlert.success('结果', JSON.stringify(dataValues));
      } else {
        console.log('error submit!!');
        return false;
      }
    });
  }

  handleChange = (evt) => {
    this.setState({
      isRequired: evt.target.checked
    });
  }

  render() {
    const { form, rules, isRequired } = this.state;
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 4 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 14 }
      }
    };
    const wrapperCol = {
      wrapperCol: {
        xs: { span: 24, offset: 0 },
        sm: { span: 14, offset: 4 }
      }
    };
    return (
      <div>
        是否必填：<Checkbox checked={isRequired} onChange={this.handleChange} />
        <Form
          style={{ width: 500, padding: '30px 0 0 0' }}
          className="layout-demo"
          ref={component => this.form = component}
          model={form}
          rules={rules}
        >
          <FormItem
            label={<span>用户名</span>}
            field="name"
            isRequired={isRequired}
            rules={[{ max: 15, message: '超出长度！' }]}
            {...formItemLayout}
          >
            <Input
              value={form.name}
              placeholder="请输入用户名"
              onChange={(e) => this.onChange('name', e)}
            />
          </FormItem>
          <FormItem
            label={<span>描述</span>}
            field="descr"
            isRequired={isRequired}
            {...formItemLayout}
          >
            <Input
              value={form.name}
              placeholder="请输入描述信息"
              onChange={(e) => this.onChange('descr', e)}
            />
          </FormItem>
          <FormItem {...wrapperCol}>
            <Button onClick={this.handleSubmit}>提交</Button>
          </FormItem>
        </Form>
      </div>
    );
  }
}

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### 子组件独立state

---demo
```js
import { Form, Input, Button, AmosAlert, Select } from 'amos-framework';

const FormItem = Form.Item;
const Option = Select.Option;

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      form: {
        name: 'ilex_h',
        hobby: 'java'
      }
    };
  }

  onChange = (key, e) => {
    const value = e.target.value;
    const newForm = Object.assign({}, this.state.form, { [key]: value });
    this.setState({
      form: newForm
    });
  }

  onHobbyChange = (value, item) => {
    const newForm = Object.assign({}, this.state.form, { hobby: value });
    this.setState({
      form: newForm
    });
  }

  handleSubmit = (e) => {
    e.preventDefault();
    this.form.validate((valid, dataValues, errors) => {
      console.log('返回内容:', dataValues, valid, errors);
      if (valid) {
        AmosAlert.success('结果', JSON.stringify(dataValues));
      } else {
        console.log('error submit!!');
        return false;
      }
    });
  }

  render() {
    const { form, rules } = this.state;
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 4 },
        className: 'colspanlab'
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 14 },
        className: 'colspan'
      }
    };
    const wrapperCol = {
      wrapperCol: {
        xs: { span: 24, offset: 0 },
        sm: { span: 14, offset: 4 }
      }
    };
    return (
      <Form style={{ width: 500, padding: '30px 0 0 0' }} className="basic-demo" ref={component => this.form = component} model={form} rules={rules}>
        <FormItem label={<span>用户名</span>} field="name" {...formItemLayout} >
          <Input
            value={form.name}
            placeholder="请输入用户名"
            onChange={(e) => this.onChange('name', e)}
          />
        </FormItem>
        <FormItem label="擅长" field="hobby" {...formItemLayout} >
          <Hobby value={form.hobby} onChange={this.onHobbyChange} />
        </FormItem>
        <FormItem {...wrapperCol}>
          <Button onClick={this.handleSubmit}>提交</Button>
        </FormItem>
      </Form>
    );
  }
}

const datas = [
  'java', 'web', 'html', 'javascript'
];

// 如果子组件自行控制state，则需要进行数据同步
class Hobby extends Component {
  constructor(props) {
    super(props);
    this.state = {
      value: props.value
    };
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    if (prevState.value !== nextProps.value){
      console.log('asdfasdfasd');
      return {
        value: nextProps.value
      };
    }

    return null;
  }

  onChange = (value, item) => {
    this.setState({
      value
    }, () => {
      // 状态设置完毕之后，再触发父组件，以防 getDerivedStateFromProps 中重复设置
      this.props.onChange(value, item);
    });
  }

  render() {
    return (
      <Select
        data={datas}
        renderOption={item => <Option value={item}>{item}</Option>}
        defaultOption={<Option>请选择</Option>}
        onChange={this.onChange}
        value={this.state.value}
        searchable
      />);
  }
}

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### 异步校验的方式

自定义校验方法，返回值为 Promise

---demo
```js
import { Form, Input, Button, AmosAlert, Select } from 'amos-framework';

const FormItem = Form.Item;
const Option = Select.Option;

function validateAction(value){
  return new Promise((resolve, reject) => {
    // 模拟异步校验
    if (value && value.length < 10){
      resolve();
    } else {
      reject('异步校验：长度必须小于10');
    }
  });
}

function pwdValidateAction(value){
  return new Promise((resolve, reject) => {
    // 密码包含 abc 则校验失败
    if (value && value.includes('abc')){
      reject('密码不能包含abc')
    } else {
      resolve();
    }
  });
}

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      form: {
        name: 'ilex_h',
        password: '',
        hobby: ''
      },
      rules: {
        name: [
          { required: true, message: '请输入名称' },
          { min: 5, message: '长度不够！' },
          {
            // validator(rule, value, callback){
            asyncValidator(rule, value, callback){
              return validateAction(value);
              // validateAction(value).then(d => {
              //   // 校验成功
              //   callback();
              // }, err => {
              //   callback(new Error(err || '异步校验失败！'));
              // });
            }
          }
        ],
        password: [
          { required: true, message: '不能为空！' },
          { min: 6, message: '长度不够！' },
          { max: 14, message: '长度超出！' },
          {
            // validator: (rule, value, callback) => {
            asyncValidator: (rule, value, callback) => {
              return pwdValidateAction(value);
            }
          }
        ],
        hobby: [
          { required: true, message: '请选择擅长项！' }
        ]
      }
    };
  }

  onChange = (key, e) => {
    const value = e.target.value;
    const newForm = Object.assign({}, this.state.form, { [key]: value });
    this.setState({
      form: newForm
    });
  }

  onHobbyChange = (value, item) => {
    const newForm = Object.assign({}, this.state.form, { hobby: value });
    this.setState({
      form: newForm
    });
  }

  handleSubmit = (e) => {
    e.preventDefault();
    this.form.validate((valid, dataValues, errors) => {
      console.log('返回内容:', dataValues, valid, errors);
      if (valid) {
        AmosAlert.success('结果', JSON.stringify(dataValues));
      } else {
        console.log('error submit!!');
        return false;
      }
    });
  }

  render() {
    const { form, rules } = this.state;
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 4 },
        className: 'colspanlab'
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 14 },
        className: 'colspan'
      }
    };
    const wrapperCol = {
      wrapperCol: {
        xs: { span: 24, offset: 0 },
        sm: { span: 14, offset: 4 }
      }
    };
    return (
      <Form style={{ width: 500, padding: '30px 0 0 0' }} className="basic-demo" ref={component => this.form = component} model={form} rules={rules}>
        <FormItem label="用户名" tip="名称最小长度为5" field="name" {...formItemLayout} >
          <Input
            value={form.name}
            placeholder="请输入用户名"
            onChange={(e) => this.onChange('name', e)}
          />
        </FormItem>
        <FormItem label={<span>密码</span>} field="password" {...formItemLayout} >
          <Input
            value={form.password}
            type="password"
            placeholder="请输入密码"
            onChange={(e) => this.onChange('password', e)}
          />
        </FormItem>
        <FormItem label="擅长" field="hobby" {...formItemLayout} >
          <Select value={form.hobby} onChange={this.onHobbyChange}>
            <Option value="c">c++</Option>
            <Option value="java">java</Option>
            <Option value="web">web</Option>
            <Option value="html">html</Option>
            <Option value="javascript">javascript</Option>
          </Select>
        </FormItem>
        <FormItem {...wrapperCol}>
          <Button onClick={this.handleSubmit}>提交</Button>
        </FormItem>
      </Form>
    );
  }
}

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### 动态改变表单项

---demo
```js
import { Form, Input, Button, AmosAlert, Select } from 'amos-framework';

const FormItem = Form.Item;
const Option = Select.Option;

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      form: {
        name: 'ilex_h',
        hobby: ''
      },
      rules: {
        name: [
          { required: true, message: '请输入名称' }
        ],
        hobby: [
          { required: true, message: '请选择擅长项！' }
        ]
      }
    };
  }

  onChange = (key, e) => {
    const value = e.target.value;
    const newForm = Object.assign({}, this.state.form, { [key]: value });
    this.setState({
      form: newForm
    });
  }

  onHobbyChange = (value, item) => {
    const newForm = Object.assign({}, this.state.form, { hobby: value });
    this.setState({
      form: newForm
    });
  }

  handleSubmit = (e) => {
    e.preventDefault();
    this.form.validate((valid, dataValues, errors) => {
      console.log('返回内容:', dataValues, valid, errors);
      if (valid) {
        AmosAlert.success('结果', JSON.stringify(dataValues));
      } else {
        console.log('error submit!!');
        return false;
      }
    });
  }

  render() {
    const { form, rules } = this.state;
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 4 },
        className: 'colspanlab'
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 14 },
        className: 'colspan'
      }
    };
    const wrapperCol = {
      wrapperCol: {
        xs: { span: 24, offset: 0 },
        sm: { span: 14, offset: 4 }
      }
    };
    return (
      <Form style={{ width: 500, padding: '30px 0 0 0' }} className="basic-demo" ref={component => this.form = component} model={form} rules={rules}>
        <FormItem label="用户名" tip="名称最小长度为5" field="name" {...formItemLayout} >
          <Input
            value={form.name}
            placeholder="请输入用户名"
            onChange={(e) => this.onChange('name', e)}
          />
        </FormItem>
        <FormItem label="擅长" field="hobby" {...formItemLayout} >
          <Select value={form.hobby} onChange={this.onHobbyChange} hasClear>
            <Option value="c">c++</Option>
            <Option value="java">java</Option>
            <Option value="web">web</Option>
            <Option value="html">html</Option>
            <Option value="javascript">javascript</Option>
          </Select>
        </FormItem>
        <FormItem {...wrapperCol}>
          <Button onClick={this.handleSubmit}>提交</Button>
        </FormItem>
      </Form>
    );
  }
}

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### 使用 FieldSet 实现分组

---demo
```js
import { Form, Input, Button, AmosAlert, Select } from 'amos-framework';

const FormItem = Form.Item;
const FormFieldSet = Form.FormFieldSet;
const Option = Select.Option;

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      form: {
        name: 'ilex_h',
        password: '',
        hobby: ''
      },
      rules: {
        name: [
          { required: true, message: '请输入名称' }
        ],
        password: [
          { required: true, message: '不能为空！' }
        ],
        hobby: [
          { required: true, message: '请选择擅长项！' }
        ]
      }
    };
  }

  onChange = (key, e) => {
    const value = e.target.value;
    const newForm = Object.assign({}, this.state.form, { [key]: value });
    this.setState({
      form: newForm
    });
  }

  onHobbyChange = (value, item) => {
    const newForm = Object.assign({}, this.state.form, { hobby: value });
    this.setState({
      form: newForm
    });
  }

  handleSubmit = (e) => {
    e.preventDefault();
    this.form.validate((valid, dataValues, errors) => {
      console.log('返回内容:', dataValues, valid, errors);
      if (valid) {
        AmosAlert.success('结果', JSON.stringify(dataValues));
      } else {
        console.log('error submit!!');
        return false;
      }
    });
  }

  render() {
    const { form, rules } = this.state;
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 4 },
        className: 'colspanlab'
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 14 },
        className: 'colspan'
      }
    };
    const wrapperCol = {
      wrapperCol: {
        xs: { span: 24, offset: 0 },
        sm: { span: 14, offset: 4 }
      }
    };
    return (
      <Form style={{ width: 500, padding: '30px 0 0 0' }} className="basic-demo" ref={component => this.form = component} model={form} rules={rules}>
        <FormFieldSet title="基础信息">
          <FormItem label="用户名" tip="名称最小长度为5" field="name" {...formItemLayout} >
            <Input
              value={form.name}
              placeholder="请输入用户名"
              onChange={(e) => this.onChange('name', e)}
            />
          </FormItem>
          <FormItem label={<span>密码</span>} field="password" {...formItemLayout} >
            <Input
              value={form.password}
              type="password"
              placeholder="请输入密码"
              onChange={(e) => this.onChange('password', e)}
            />
          </FormItem>
        </FormFieldSet>
        <FormItem label="擅长" field="hobby" {...formItemLayout} >
          <Select value={form.hobby} onChange={this.onHobbyChange} hasClear>
            <Option value="c">c++</Option>
            <Option value="java">java</Option>
            <Option value="web">web</Option>
            <Option value="html">html</Option>
            <Option value="javascript">javascript</Option>
          </Select>
        </FormItem>
        <FormItem {...wrapperCol}>
          <Button onClick={this.handleSubmit}>提交</Button>
        </FormItem>
      </Form>
    );
  }
}

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### 强调显示

表单项添加强调显示，注意与必填同时存在时的显示。

---demo
```js
import { Form, Input, Button, AmosAlert, Select, Icon } from 'amos-framework';

const FormItem = Form.Item;
const Option = Select.Option;

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      form: {
        score: 1,
        name: 'ilex_h',
        password: '',
        hobby: ''
      },
      rules: {
        name: [
          { required: true, message: '请输入名称' },
          { pattern: /^[a-zA-Z0-9_-]+$/, message: '用户名格式不正确!' },
          { pattern: /^((?!(sb|cnm)).)*$/g, message: '不能输入敏感词汇!' },
          { min: 5, message: '长度不够！' }
        ],
        password: [
          { required: true, message: '不能为空！' },
          { min: 6, message: '长度不够！' },
          { max: 14, message: '长度超出！' }
        ]
      }
    };
  }

  onChange = (key, e) => {
    const value = e.target.value;
    const newForm = Object.assign({}, this.state.form, { [key]: value });
    this.setState({
      form: newForm
    });
  }

  onScoreChange = (value) => {
    const { form } = this.state;
    // 直接修改 form （由于是引用类型，会自动将 prevProps 也修改）
    // form.score = value;
    // this.setState({
    //   form
    // });

    // this.setState({
    //   form: { ...form, score: value }
    // });

    const newForm = Object.assign({}, form, { score: value });
    this.setState({
      form: newForm
    });
  }

  onHobbyChange = (value, item) => {
    const newForm = Object.assign({}, this.state.form, { hobby: value });
    this.setState({
      form: newForm
    });
  }

  handleSubmit = (e) => {
    e.preventDefault();
    this.form.validate((valid, dataValues, errors) => {
      console.log('返回内容:', dataValues, valid, errors);
      if (valid) {
        AmosAlert.success('结果', JSON.stringify(dataValues));
      } else {
        console.log('error submit!!');
        return false;
      }
    });
  }

  render() {
    const { form, rules } = this.state;
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 4 },
        className: 'colspanlab'
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 14 },
        className: 'colspan'
      }
    };
    const wrapperCol = {
      wrapperCol: {
        xs: { span: 24, offset: 0 },
        sm: { span: 14, offset: 4 }
      }
    };
    return (
      <Form style={{ width: 500, padding: '30px 0 0 0' }} className="basic-demo" ref={component => this.form = component} model={form} rules={rules}>
        <FormItem highlight label="用户名" tip="名称最小长度为5" field="name" {...formItemLayout} >
          <Input
            value={form.name}
            placeholder="请输入用户名"
            onChange={(e) => this.onChange('name', e)}
          />
        </FormItem>
        <FormItem highlight highlightSymbol="※" label={<span>密码</span>} field="password" {...formItemLayout} >
          <Input
            value={form.password}
            type="password"
            placeholder="请输入密码"
            onChange={(e) => this.onChange('password', e)}
          />
        </FormItem>
        <FormItem highlight label="分数" field="score" {...formItemLayout} >
          <InputNumber
            value={form.score}
            placeholder="请输入分数"
            onChange={this.onScoreChange}
          />
        </FormItem>
        <FormItem highlight highlightSymbol={<Icon icon="bug" color="orange" />} label="擅长" field="hobby" {...formItemLayout} >
          <Select value={form.hobby} onChange={this.onHobbyChange} hasClear>
            <Option value="c">c++</Option>
            <Option value="java">java</Option>
            <Option value="web">web</Option>
            <Option value="html">html</Option>
            <Option value="javascript">javascript</Option>
          </Select>
        </FormItem>
        <FormItem {...wrapperCol}>
          <Button onClick={this.handleSubmit}>提交</Button>
        </FormItem>
      </Form>
    );
  }
}

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend


## 排列方式

 form 有以下三种排列方式：

* 水平排列(horizontal)：标签和表单控件水平排列；（默认）

* 垂直排列(vertical)：标签和表单控件上下垂直排列；

* 行内排列(inline)：表单项水平行内排列。

## 表单域

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

这里封装了表单域 `<Form.Item />`。

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

## API

### Form

| params  | type | default | description |
|--------- |-------- |--------- |-------- |
| className | String | - | 自定义样式名 |
| children | ReactNode | - | 表单域 |
| onSubmit | `Function: (valid, dataValues) => {}` | - | 表单提交方法 |
| ref | String or function | - | React方法，提供Form组件内部方法调用 |
| model | Object | - | 表单数据对象，以及验证规则 |
| layout | String | `horizontal` | 表单布局 `horizontal`、`vertical`、`inline`、`none` |
| labelPosition | String | - | 标签对齐方式，可选值为 `top`、`left`、`right`，注意，与 `layout='horizontal|vertical'` 不能同时使用 |
| rules | object | - | 表单验证规则 |
| labelWidth | string or number | - | 表单域标签的宽度，所有的 form-item 都会继承 form 组件的 labelWidth 的值 |
| filled | Boolean | - | layout 为 horizontal 时，铺满。如果设置 labelWidth 宽度，则 control 的宽度自动为 100% - labelWidth |
| onBeforeSubmit | Function | - | 在调用 onSubmit 之前调用，常见用途： 点击提交按钮，设置加载状态。注意，当校验通过时，才会执行该方法 |

> onSubmit 回调，参数为校验结果值，第一个参数 valid 表示是否校验成功，dataValues 参数为表单数据。
> onBeforeSubmit 方法执行的前提：所有 rules 均验证通过。

> `v1.8.4` 版本新增 `layout=none` 和 `labelPosition` 属性。labelPosition='left|right' 时，需要设置 `labelWidth` 才能达到最佳效果

### Form refs

```js
<Form ref={component => this.form = component}></Form>
```

上面定义`<Form/>`的ref值赋值给`this.form`

```js
// 表单重置
this.form.resetFields(model=>{
  this.setState({ form: model })
});

// 表单验证
this.form.validate((valid, dataValues, errors) => {
  console.log("返回内容:", dataValues, valid);
  if (valid) {
    console.log('submit!');
  } else {
    console.log('error submit!!');
    return false;
  }
});

// 表单验证
this.form.validateField('username', (errors) => {
  if (errors){
    // 校验失败
  } else {
  // 校验成功
  }
});

// or
this.form.validateField('username').then(() => {
  // 校验成功
}, error => {
  // 校验失败
});
```

使用 onSubmit 进行表单提交

```js
onSubmit = (valid, dataValues) => {
  console.log('返回内容:', dataValues, valid);
  if (valid) {
    AmosAlert.success('结果', JSON.stringify(dataValues));
  } else {
    console.log('error submit!!');
    return false;
  }
}
```

* 可用的 From Methods

| params  | type | default | description |
|--------- |-------- |--------- |-------- |
| validate | `Function(valid:Boolean, dataValues:Object, errors: any[])` | - | 对整个表单进行校验的方法 |
| resetFields | `Function(model: Object)` | - | 对整个表单进行重置，将所有字段值重置为空并移除校验结果 |
| validateField | `validateField(field: string, cb: Function): void` | - | 对部分表单字段进行校验的方法 |

> 注意，validate 的回调函数中，当参数 valid 为 true 时，表示验证通过。此时，参数 errors 中没有数据。反之，验证失败，errors 中存放所有失败信息。

通常进行校验时，只需要关注 `valid` 参数即可。errors 参数使用场景：当有隐藏域验证失败需要提示时，可以用 errors 参数。

`errors` 数据格式

```js
const errors = [
  { field: '', message: '' },
  { field: '', message: '' },
  { field: '', message: '' },
  { field: '', message: '' },
  ...
];
// errors 每一项中，field 为 FormItem 的 field 值，message 为 rules 中配置的信息

// 使用方式

if (!valid){
  // 校验失败，弹出自定义的错误
  if (!utils.isEmpty(errors)){
    let msgs = '';
    errors.forEach(err => {
      if (!utils.isEmpty(err)){
        msgs += err.message
      }
    });
    console.log(msgs);
  }
}
```

### Form model

| params  | type | default | description |
|--------- |-------- |--------- |-------- |
| value | `Objec、String、Array` | - | 表单数据对象 |
| rules | `Object[]` | - | 校验规则，参考下方文档 |

### Form.Item

| params  | type | default | description |
|--------- |-------- |--------- |-------- |
| style | Object | - | Item 自定义样式, 作用于 `amos-form-item` 节点 |
| label | `String、ReactNode` | - | `label` 标签的文本。可以采用 ReactNode 的方式创建 label 与其它信息 |
| labelCol | Object | - | `label` 标签布局，同 `<Col>` 组件，设置 `span` `offset` 值，如 `{ span: 3, offset: 12 }` 或 `sm: { span: 3, offset: 12 }` |
| labelWidth | string or number | - | 表单域标签的宽度，将会覆盖 Form 中该值 |
| labelStyle | Object | - | label 自定义样式， 作用于 `amos-form-item-label` 节点 |
| wrapperCol | Object | - | 需要为输入控件设置布局样式时，使用该属性，用法同 `labelCol` |
| wrapperStyle | Object | - | form field content 自定义样式， 作用于 `amos-form-item-control` 节点 |
| isRequired | boolean | - | 是否必填，如不设置，则会根据校验规则自动生成。 |
| field | `String、ReactNode` | - | `field` 域的名称 |
| rules | `Object or Object[]` | - | 当前表单 rules 配置，优先级高于全局的 rules 配置。 如果组件内部配置该属性，则全局的不起效 |
| help | `String、ReactNode` | - | 提示信息，如不设置，则会根据校验规则自动生成 |
| tip | String | - | 快速创建表单说明信息，默认显示在 label 位置 |
| filled | boolean | - | 是否为 铺满，仅在 parent layout === 'horizontal' 起效，并且会覆盖 parent filled |
| onlyShowRequiredStar | boolean | - | 部分场景下，选择的值可以是 `null、''、undefined` 中的一种，此时如果采用 `required: true` 校验，会出错，需要自定义 validator 方式校验，此时可以设置 `onlyShowRequiredStar=true` 用于显示必填 `*`。优先级最低，仅当无 `isRequired=true` 和 `rules: [{ required: true }]` 时起效 |
| highlight | boolean | - | 是否强调表单项，设置为true，则在 label 字段前加上默认字符，用以强调。注意，`label` 为 `null` 时，不展示 |
| highlightSymbol | ReactNode | - | 自定义强调显示符号，默认使用 `*` |

> isRequired 和 rules 中的 `{ required }` 优先级关系：如果 rules 中未设置 `required` 项，但是 props 中 isRequired 为 true，此时需要自动填充，`props.isRequired` 优先级低于 rules 中的 required

FormItem 默认通过 context 给其子组件 Field 注入 `formItem`，自定义的form（无法冒泡onChange）可通过该属性进值校验。如：

```js
class MyFormField extends Component {
  handleChange = e => {
    this.props.onChange && this.props.onChange(e);
    this.context.formItem && this.context.formItem.onFieldChange();
  }
}

MyFormField.contextTypes = {
  formItem: PropTypes.any
};
```

### Form.Footer

自定义 form footer，可以定义提交按钮、取消按钮等

| params  | type | default | description |
|--------- |-------- |--------- |-------- |
| className | String | - | 自定义样式名 |
| children | ReactNode | - | 表单 footer 内容 |

### Form.FormSubmit

采用 form 自定义的 onSubmit，用于控制 loading 等

| params  | type | default | description |
|--------- |-------- |--------- |-------- |
| className | String | - | 自定义样式名 |
| style | object | - | 自定义样式 |
| children | ReactNode | - | 提交按钮内容 |
| onClick | Function | - | 按钮自定义事件，提交表单时触发，默认会触发 `Form.onSubmit` 事件 |
| loading | Boolean | - | 提交过程中，加载效果，用于防止多次提交 |

### Form.FormFieldSet

| params  | type | default | description |
|--------- |-------- |--------- |-------- |
| className | String | - | 自定义样式名 |
| style | object | - | 自定义样式 |
| title | ReactNode | - | fieldset 中的 legend （表单组标题） |
| children | ReactNode | - | 表单项组 |

### 校验规则

```js
{
    rules:[
      { required: true, message: "请输入活动名称"},
      { min: 12, message: "长度不够！"}
    ]
}
```

`rules` 校验规则实例，校验使用的包[ray-async-validator]

| params  | type | default | description |
|--------- |-------- |--------- |-------- |
| message | string | - | 校验文案，错误提示信息  |
| type    | string | `string` | 内建校验类型，`string/number/boolean/method/regexp/integer/float/array/object/enum/date/url/hex/email` |
| required | boolean | `false` | 是否必选  |
| whitespace | boolean | false | 必选时，空格是否会被视为错误 |
| len | number | - | 字段长度  |
| min | number | - | 最小长度  |
| max | number | - | 最大长度  |
| enum | string | - | 枚举类型  |
| pattern | `RegExp` | - | 正则表达式校验 RegExp |
| trigger | string | - | 触发方式，默认为 `change`，可选值为 `blur` |
| validator | `Function(rule, value,callback)` | - | 自定义校验（注意，callback 必须被调用） |

## 常见 form 问题介绍

### Form 中单个 Input 时，按下回车键会自动刷新页面内容。以下两种方式均可解决

1. 方式一: 添加 onSubmit 属性，设置 `e.preventDefault();`，在 amos-framework@1.0.64 版本之后，Form 组件已经默认添加。
2. 方式二：给 Form 添加一个隐藏的输入框即可。

除以上方式外，当然，也可以不用 form 标签。

### 后端校验

早期版本，由于所有的 rules 均是同步执行，不支持混合校验。有异步存在时，只能采用 `async/await` 的方式处理。

新版本，新增 `asyncValidator` 自定义异步校验，专门处理异步校验，同时 `validator` 如果有返回值，则直接返回同步校验结果。

内置 `onChange` 的校验，默认采用混合校验，异步与同步均支持。

### formItem 的 onChange 事件不同处理导致的问题

1. 方式一： 直接修改 form model 的情景

该方式在执行setState 之前，就已经改变了 form 的 model 数据，因此，在 `onChange` 模式触发 validate 的校验时，使用的是最新的数据。

```js
state = {
  form: {
    userName: ''
  }
};

onChange = (evt) => {
  const form = this.state.form;
  form.userName = evt.target.value;
  this.setState({
    form
  });
}
```

> 该方式，适用于只有用户输入时改变数据的情景

2. 方式二： 重新创建新的 form

该方式在执行 setState 执行之后，才会改变 form 的 model 数据，因此，在 `onChange` 模式触发 validate 的校验时，使用的是旧数据。

*会导致所有的 FormItem 重新渲染*

```js
state = {
  form: {
    userName: ''
  }
};

onChange = (evt) => {
  const value = evt.target.value;
  this.setState({
    // form: Object.assign({}, this.state.form, { userName: value })
    // or
    form: {
      ...this.state.form,
      userName: value
    }
  });
}
```

> 该方式适用于多个地方能修改 form model 的情景

取舍：

> 为了兼容上述两种情境下校验的准确性，代码中设置临时 `oldValue` 变量，用于 props 改变时，强制进行校验一次，
> 这样就会导致每次校验都执行两遍。当然，如果选择 `方式一` 处理 onChange 事件，就会避免该问题。

> 注意：since v1.8.0 版本起，将不再设置 `oldValue`，方式一与方式二均可（已知bug：Select 选中切换不触发校验）。since v1.8.7 版本，彻底去除 `oldValue` 方式。

> since v 1.8.6 版本，内部 `Select` 已添加 FormItem context，同时自动在其 onChange 时间中，手动调用 `formItem.onFieldChange();` 已实现切换校验

#### formItem 的 onChange 最终解决方案

采用 `setTimeout` 处理，让 `react` 生命周期执行完毕，从而进行校验, 可以避免上述的问题。如：

```js
setTimeout(() => {
  this.validate('change');
});
```
