# 动态表单设计器

动态表单，采用 `FormBuilder` 组件，灵活定制自己的动态表单。

## 动态表单支持的场景

- 表单项不固定，由数据生成
- 新增表单接口中的数据，不是固定的对象（前端数据、后端DAO）
- 快速创建通用表单
- 通用配置系统所需要的表单项

## 表单设计器案例

---demo
```js
import { Row, Col, Form, Input, Select, Switch, Group, Icon, Tooltip, Radio, InputNumber, MultiSelect, InputColor, FormBuilder, Button } from 'amos-framework';
import { deepCopy, utils } from 'amos-tool';

const FormItem = Form.Item;
const TextArea = Input.TextArea;
const Option = Select.Option;
const RadioGroup = Radio.Group;
const formMapper = FormBuilder.formFields;
const { toHexColor } = InputColor;

const formItemLayout = {
  labelCol: {
    xs: { span: 12 },
    sm: { span: 8 }
  },
  wrapperCol: {
    xs: { span: 12 },
    sm: { span: 16 }
  }
};

const frmItemStyle = {
  width: '100%'
};

// font family, 等号左侧用于 label显示，等号右侧是真实的 fontFamily 值
const defaultFontFamilies = [
  '黑体=andale mono,times',
  '宋体=SimSun',
  '新宋体=NSimSun',
  '仿宋=FangSong',
  '楷体=KaiTi',
  '微软雅黑=Microsoft YaHei',
  '隶书=LiSu',
  '幼圆=YouYuan',
  '华文细黑=STXihei',
  '华文楷体=STKaiti',
  '华文宋体=STSong',
  '华文中宋=STZhongsong',
  '华文仿宋=STFangsong',
  'Andale Mono=andale mono,times',
  'Arial=arial,helvetica,sans-serif',
  'Arial Black=arial black,avant garde',
  'Book Antiqua=book_antiquaregular,palatino',
  'Corda Light=CordaLight,sans-serif',
  'Courier New=courier_newregular,courier'
];

const defaultFontSizes = ['9px', '10px', '12px', '14px', '16px', '18px', '24px', '32px', '36px', '38px', '40px', '42px', '50px', '56px'];

const paperSizes = [
  { key: '1024X768', label: '1024X768', value: '1024X768' },
  { key: '1280X900', label: '1280X900', value: '1280X900' },
  { key: '1366X768', label: '1366X768', value: '1366X768' },
  { key: '1440X900', label: '1440X900', value: '1440X900' },
  { key: '1920X1080', label: '1920X1080', value: '1920X1080' },
  { key: '750X1260', label: '手机竖屏', value: '750X1260' },
  { key: '2048X1408', label: '平板横屏', value: '2048X1408' },
  { key: '100%X100%', label: '全屏', value: '100%X100%' }
];

/**
 * 支持的 form field 类型
 */
const fieldItemTypes = [
  { value: 'divider', name: 'divider' },
  { value: 'textSetting', name: 'textSetting', style: { width: '100%' } },
  { value: 'input', name: 'input', data: { inline: true } },
  { value: 'inputNumber', name: 'inputNumber', data: { min: 0, max: 100, step: 1, inline: true, type: 'outer' } },
  { value: 'checkbox', name: 'checkbox', data: { items: [{ key: '', text: '', value: '' }] } },
  { value: 'color', name: 'color', data: { direction: 'left' } },
  { value: 'iptColor', name: 'iptColor', data: { direction: 'left', useRgba: true, alpha: true } },
  {
    value: 'fontFamily', name: 'fontFamily', data: {
      items: defaultFontFamilies
    }
  },
  {
    value: 'fontSize',
    name: 'fontSize',
    data: {
      items: defaultFontSizes
    }
  },
  {
    value: 'fontSizeNumber',
    name: 'fontSizeNumber'
  },
  { value: 'margins', name: 'margins' },
  { value: 'position', name: 'position', data: { showUnit: true, unit: 'px' } },
  { value: 'radio', name: 'radio', data: { items: [{ key: '', text: '', value: '' }] } },
  { value: 'select', name: 'select', data: { items: [{ key: '', label: '', value: '' }] } },
  { value: 'size', name: 'size', data: { showUnit: true, unit: 'px' } },
  { value: 'slider', name: 'slider', data: { min: 0, max: 100, step: 1 } },
  { value: 'switch', name: 'switch', data: { onLabel: '', offLabel: '', inline: true }, style: { width: 60 } },
  { value: 'tags', name: 'tags', data: { items: [{ key: '', value: '', text: '' }] } },
  { value: 'textarea', name: 'textarea', data: { rows: 4 } },
  { value: 'bgPosition', name: 'bgPosition' },
  { value: 'bgImageModal', name: 'bgImageModal' },
  { value: 'boxShadow', name: 'boxShadow', data: { direction: 'left' } }, // 边框阴影
  { value: 'textShadow', name: 'textShadow', data: { direction: 'left' } }, // 文本阴影
  { value: 'paddingMargin', name: 'paddingMargin', data: { direction: 'left' } }, // 内边距
  { value: 'border', name: 'border', data: { direction: 'left' } }, // 边框
  { value: 'borderRadius', name: 'borderRadius', data: { direction: 'left' } }, // 内置圆角
  { value: 'opacity', name: 'opacity', type: 'slider', data: { min: 0, max: 100, step: 1 } }, // 透明
  { value: 'paperSize', name: 'paperSize', type: 'paperSize', data: {
    items: paperSizes,
    x: { min: 10, max: 10000, step: 5 },
    y: { min: 10, max: 10000, step: 5 }
  } },
  { value: 'borderStyle', name: 'borderStyle' },
];

/**
 * 类型说明
 */
const fieldTypeTips = {
  textSetting: '字体通用配置类型，包含字体颜色、大小、对齐方式、加粗、斜体、下划线',
  input: '文本输入类型',
  inputNumber: '数字输入类型',
  color: '选择颜色类型',
  iptColor: '选择/输入 颜色类型',
  fontFamily: '字体选择类型',
  fontSize: '选择内置字体大小类型',
  fontSizeNumber: '自定义输入字体大小类型',
  position: '位置输入类型(x, y)',
  bgPosition: '图片背景位置类型',
  bgImageModal: '选择图片以及上传图片类型'
};

/**
 * field item 数据配置说明
 */
const fieldDataTips = {
  input: {
    tip: '设置输入框配置项',
    inline: '输入框外层是否采用 div 进行包裹'
  },
  keyTextValue: {
    key: '动态表单项选项的唯一key值',
    text: '动态表单项选项的显示名称',
    value: '动态表单项选项的值'
  },
  keyLabelValue: {
    key: '动态表单项选项的唯一key值',
    label: '动态表单项选项的显示名称',
    value: '动态表单项选项的值'
  },
  checkbox: '配置多选框条目',
  radio: '配置单选框条目',
  select: '配置下拉框条目',
  tags: '配置标签项条目',
  textarea: '配置文本域行数',
  inputNumber: {
    min: '最小值',
    max: '最大值',
    step: '数字输入框步数',
    inline: '是否采用 div 包裹输入框'
  },
  slider: {
    min: '最小值',
    max: '最大值',
    step: '数字输入框步数'
  },
  switch: {
    tip: '动态表单style字段配置项数据',
    onLabel: '开关打开状态显示的文本',
    offLabel: '开关关闭状态显示的文本',
    inline: '开关外层是否采用 div 进行包裹',
    width: '宽度'
  },
  color: '颜色拾取面板朝向',
  iptColor: {
    direction: '颜色拾取面板朝向',
    alpha: '是否启用透明度'
  },
  poptip: '面板朝向',
  paddingMargin: '面板朝向',
  size: {
    unit: '单位',
    showUnit: '显示单位'
  }
};

function tileInfo(type) {
  let info = '动态表单data字段配置项数据';

  switch (type) {
    case 'checkbox':
    case 'radio':
    case 'select':
    case 'tags':
    case 'textarea':
    case 'color':
      info = fieldDataTips[type];
      break;
    case 'paddingMargin':
    case 'border':
    case 'borderRadius':
      info = fieldDataTips.paddingMargin;
      break;
    case 'input':
    case 'inputNumber':
      info = fieldDataTips.input.tip;
      break;

    default:
      break;
  }

  return info;
}

const rules = {
  key: [{ required: true, message: '标识不能为空！' }],
  type: [{ required: true, message: '类型不能为空！' }],
  label: [{ required: true, message: '标签不能为空！' }]
};

const groupStyle = {
  width: '25%'
};
const fullStyle = {
  width: '100%'
};

const formDataMapping = {
  checkboxs: { key: '', text: '', value: '' },
  radios: { key: '', text: '', value: '' },
  options: { key: '', label: '', value: '' },
  tags: { key: '', value: '', text: '' }
};

class Designer extends Component {
  constructor(props) {
    super(props);
    this.state = {
      form: {},
      data: {},
      style: {},
      // 动态表单参数
      fields: [],
      datas: {}
    };
  }

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

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

  handleDataChange = key => {
    return function(e) {
      const value = e && e.target ? e.target.value : e;
      const newFormData = Object.assign({}, this.state.form.data, { [key]: value });
      const newForm = Object.assign({}, this.state.form, { data: newFormData });

      this.setState({
        data: newFormData,
        form: newForm
      });
    }.bind(this);
  };

  handleStrArrayChange = (key, separate = ',') => {
    return function(e) {
      const value = e && e.target ? e.target.value : e;
      const arrVal = value.split(separate);
      const newFormData = Object.assign({}, this.state.form.data, { [key]: arrVal });

      const newForm = Object.assign({}, this.state.form, { data: newFormData });

      this.setState({
        data: newFormData,
        form: newForm
      });
    }.bind(this);
  };

  handleStyleChange = key => {
    return function(e) {
      const value = e && e.target ? e.target.value : e;
      const newFormData = Object.assign({}, this.state.form.style, { [key]: value });
      const newForm = Object.assign({}, this.state.form, { style: newFormData });

      this.setState({
        style: newFormData,
        form: newForm
      });
    }.bind(this);
  };

  handleTypeChange = type => {
    if (type !== this.state.form.type){
      const fit = deepCopy(fieldItemTypes);
      const fieldItemType = fit.find(item => item.value === type);
      const { data } = fieldItemType || {};

      const newFormState = { type, data, defaultValue: undefined };
      const newForm = Object.assign({}, this.state.form, newFormState);

      this.setState({
        data,
        form: newForm
      });
    }

  };

  handleGroupDataChange = (e, key, item) => {
    if (e && e.target) {
      const { data } = this.state;
      const value = e.target.value;

      const newItem = Object.assign({}, item, { [key]: value });

      const index = data.items.findIndex(_data => _data.key === item.key) || 0;

      data.items = data.items.map((di, i) => {
        return i === index ? newItem : di;
      });

      const newForm = Object.assign({}, this.state.form, { data });

      this.setState({
        form: newForm,
        data
      });
    }
  };

  addGroup = key => {
    const { data } = this.state;

    let dataItem = {};
    switch (key) {
      case 'checkboxs':
      case 'radios':
      case 'options':
      case 'tags':
        dataItem = deepCopy(formDataMapping[key]);
        break;
      default:
        break;
    }

    data.items.push(dataItem);

    this.setState({
      data
    });
  };

  removeGroup = (formType, item) => {
    const { data } = this.state;
    const index = data.items.findIndex(_data => _data.key === item.key) || 0;
    data.items = data.items.filter((_data, i) => i !== index);

    this.setState({
      data
    });
  };

  handleSubmit = e => {
    e && e.preventDefault();
    this.form.validate((valid, dataValues) => {
      if (valid) {
        this.setState((prevState) => ({
          fields: [
            // 使用 deepCopy 防止下方的 reset fields 时将数据还原
            ...prevState.fields,
            dataValues
          ]
        }));
      } else {
        console.log('error submit!!');
        return false;
      }
    });
  };

  showFieldsInfo = () => {
    console.log('fields:', this.state.fields);
  }

  handleFBChange = ({ key, value }) => {
    const { datas } = this.state;
    datas[key] = value;
    this.setState({
      datas
    });
    console.log('res datas:', datas);
  }

  renderKeyTextValueData = (data, type) => {
    return data?.items?.map(item => {
      return (
        <Group className="component-setting-attribute-form-group" enableSpace key={`${item.key}-${item.value}`}>
          <Tooltip title="key">
            <Input style={groupStyle} value={item.key} placeholder="key" onChange={e => this.handleGroupDataChange(e, 'key', item)} />
          </Tooltip>
          <Tooltip title="text">
            <Input style={groupStyle} value={item.text} placeholder="text" onChange={e => this.handleGroupDataChange(e, 'text', item)} />
          </Tooltip>
          <Tooltip title="value">
            <Input style={groupStyle} value={item.value} placeholder="value" onChange={e => this.handleGroupDataChange(e, 'value', item)} />
          </Tooltip>
          <Tooltip title="添加">
            <Icon icon="add" onClick={() => this.addGroup(type)} />
          </Tooltip>
          {data.items.length > 1 && (
            <Tooltip title="移除">
              <Icon icon="remove" onClick={() => this.removeGroup(type, item)} />
            </Tooltip>
          )}
        </Group>
      );
    });
  };

  rednerKeyLabelValueData = (data, type) => {
    return data?.items?.map(item => {
      return (
        <Group className="component-setting-attribute-form-group" enableSpace key={`${item.key}-${item.value}`}>
          <Tooltip title="key">
            <Input style={{ width: '25%' }} value={item.key} placeholder="key" onChange={e => this.handleGroupDataChange(e, 'key', item)} />
          </Tooltip>
          <Tooltip title="label">
            <Input style={{ width: '25%' }} value={item.label} placeholder="label" onChange={e => this.handleGroupDataChange(e, 'label', item)} />
          </Tooltip>
          <Tooltip title="value">
            <Input style={{ width: '25%' }} value={item.value} placeholder="value" onChange={e => this.handleGroupDataChange(e, 'value', item)} />
          </Tooltip>
          <Tooltip title="添加">
            <Icon icon="add" onClick={() => this.addGroup(type)} />
          </Tooltip>
          {data.items.length > 1 && (
            <Tooltip title="移除">
              <Icon icon="remove" onClick={() => this.removeGroup(type, item)} />
            </Tooltip>
          )}
        </Group>
      );
    });
  };

  renderDirection = (key, value, title) => {
    return (
      <RadioGroup defaultValue={value} onChange={this.handleDataChange(key)} title={title}>
        <Radio value="up">上</Radio>
        <Radio value="right">右</Radio>
        <Radio value="down">下</Radio>
        <Radio value="left">左</Radio>
      </RadioGroup>
    );
  };

  /** 根据 type 类型渲染数据（data）设置 */
  renderContentData = (type, data) => {
    switch (type) {
      case 'checkbox':
        return this.renderKeyTextValueData(data, 'checkboxs');
      case 'radio':
        return this.renderKeyTextValueData(data, 'radios');
      case 'select':
        return this.rednerKeyLabelValueData(data, 'options');
      case 'tags':
        return this.renderKeyTextValueData(data, 'tags');
      case 'color':
        return this.renderDirection('direction', data.direction, fieldDataTips.color);
      case 'textarea':
        return (
          <Tooltip title="rows">
            <Input style={groupStyle} value={data.rows} placeholder="rows" onChange={this.handleDataChange('rows')} />
          </Tooltip>
        );
      case 'input':
        return (
          <Tooltip title={fieldDataTips.input.inline}>
            <Switch style={{ width: 60 }} defaultOn={data.inline} onLabel="true" offLabel="false" onChange={this.handleDataChange('inline')} />
          </Tooltip>
        );
      case 'inputNumber':
        return (
          <Group enableSpace>
            <Tooltip title={fieldDataTips.inputNumber.min}>
              <Input style={groupStyle} value={data.min} placeholder="min" onChange={this.handleDataChange('min')} />
            </Tooltip>
            <Tooltip title={fieldDataTips.inputNumber.max}>
              <Input style={groupStyle} value={data.max} placeholder="max" onChange={this.handleDataChange('max')} />
            </Tooltip>
            <Tooltip title={fieldDataTips.inputNumber.step}>
              <Input style={groupStyle} value={data.step} placeholder="step" onChange={this.handleDataChange('step')} />
            </Tooltip>
            <Tooltip title={fieldDataTips.inputNumber.inline}>
              <Switch defaultOn={data.inline} onLabel="是" offLabel="否" onChange={this.handleDataChange('inline')} />
            </Tooltip>
          </Group>
        );
      case 'slider':
        return (
          <Group enableSpace>
            <Tooltip title={fieldDataTips.slider.min}>
              <Input style={groupStyle} value={data.min} placeholder="min" onChange={this.handleDataChange('min')} />
            </Tooltip>
            <Tooltip title={fieldDataTips.slider.max}>
              <Input style={groupStyle} value={data.max} placeholder="max" onChange={this.handleDataChange('max')} />
            </Tooltip>
            <Tooltip title={fieldDataTips.slider.step}>
              <Input style={groupStyle} value={data.step} placeholder="step" onChange={this.handleDataChange('step')} />
            </Tooltip>
          </Group>
        );
      case 'switch':
        return (
          <Group enableSpace>
            <Tooltip title={fieldDataTips.switch.onLabel}>
              <Input style={groupStyle} value={data.onLabel} placeholder="onLabel" onChange={this.handleDataChange('onLabel')} />
            </Tooltip>
            <Tooltip title={fieldDataTips.switch.offLabel}>
              <Input style={groupStyle} value={data.offLabel} placeholder="offLabel" onChange={this.handleDataChange('offLabel')} />
            </Tooltip>
            <Tooltip title={fieldDataTips.switch.inline}>
              <Switch style={groupStyle} defaultOn={data.inline} onLabel="是" offLabel="否" onChange={this.handleDataChange('inline')} />
            </Tooltip>
          </Group>
        );
      case 'iptColor':
        return (
          <Group enableSpace>
            {this.renderDirection('direction', data.direction, fieldDataTips.iptColor.direction)}
            <Tooltip title={fieldDataTips.iptColor.alpha}>
              <Switch style={{ width: 60 }} defaultOn={data.alpha} onLabel="是" offLabel="否" onChange={this.handleDataChange('alpha')} />
            </Tooltip>
          </Group>
        );
      case 'paddingMargin':
      case 'border':
      case 'borderRadius':
      case 'boxShadow':
      case 'textShadow':
        return this.renderDirection('direction', data.direction, fieldDataTips[type] || fieldDataTips.poptip);
      case 'fontSize':
        return (
          <Tooltip title={`${type} 数据集合,采用英文逗号隔开`}>
            <TextArea rows={6} style={fullStyle} value={data.items?.join(',\n')} disableResize onChange={this.handleStrArrayChange(type, ',')}/>
          </Tooltip>
        );
      case 'fontFamily': // 注意，fontFamily 数据中，包含有 , 因此，需要采用 ; 进行分割
        return (
          <Tooltip title={`${type} 数据集合,采用英文分号隔开`}>
            <TextArea rows={6} style={fullStyle} value={data.items?.join(';\n')} disableResize onChange={this.handleStrArrayChange(type, ';')}/>
          </Tooltip>
        );
      case 'size':
      case 'position':
        return (
          <Group enableSpace>
            <Tooltip title={fieldDataTips.size.unit}>
              <Input style={groupStyle} value={data.unit} placeholder="unit" onChange={this.handleDataChange('unit')} />
            </Tooltip>
            <Tooltip title={fieldDataTips.size.showUnit}>
              <Switch defaultOn={data.showUnit} onLabel="是" offLabel="否" onChange={this.handleDataChange('showUnit')} />
            </Tooltip>
          </Group>
        );
      default:
        break;
    }
    return null;
  };

  /** 渲染默认值表单项 */
  renderDefaultValue = (type, defaultValue, data) => {
    switch (type) {
      case 'switch':
        return (
          <RadioGroup defaultValue={defaultValue} onChange={this.genHandle('defaultValue')}>
            <Radio value>true</Radio>
            <Radio value={false}>false</Radio>
          </RadioGroup>
        );
      case 'slider':
      case 'inputNumber':
        return (
          <InputNumber
            value={defaultValue}
            style={fullStyle}
            min={data.min}
            step={data.step}
            max={data.max}
            type="outer"
            onChange={this.genHandle('defaultValue')}
          />
        );
      case 'radio':
      case 'tags':
        return (
          <Select
            style={frmItemStyle}
            data={data.items}
            defaultValue={defaultValue}
            renderOption={item => (
              <Option title={item.text} value={item.value}>
                {item.text}
              </Option>
            )}
            defaultOption={<Option value={undefined}>请选择</Option>}
            onChange={this.genHandle('defaultValue')}
          />
        );
      case 'checkbox':
        return (
          <MultiSelect
            defaultValues={defaultValue}
            data={data.items}
            onChange={this.genHandle('defaultValue')}
            renderOption={item => <Option value={item.value}>{item.text}</Option>}
          />
        );
      case 'select':
        return (
          <Select
            style={frmItemStyle}
            data={data.items}
            defaultValue={defaultValue}
            renderOption={item => (
              <Option title={item.label} value={item.value}>
                {item.label}
              </Option>
            )}
            defaultOption={<Option value={undefined}>请选择</Option>}
            onChange={this.genHandle('defaultValue')}
          />
        );
      case 'color':
      case 'iptColor':
        return <InputColor width="100%" value={defaultValue} onChange={(value) => this.genHandle('defaultValue')(toHexColor(value))} />;
      default:
        return <Input style={frmItemStyle} value={defaultValue} placeholder="请输入默认值" onChange={this.genEventHandle('defaultValue')} />;
    }
  };

  renderContent = () => {
    const { form, data, style } = this.state;
    return (
      <Form className="component-setting-attribute-form" ref={component => this.form = component} model={form} rules={rules} labelWidth="86px">
        <FormItem
          label={
            <Tooltip title="key">
              <span>标识</span>
            </Tooltip>
          }
          field="key"
          {...formItemLayout}
        >
          <Input style={frmItemStyle} value={form.key} placeholder="请输入标识" onChange={this.genEventHandle('key')} />
        </FormItem>
        <FormItem
          label={
            <Tooltip title="label">
              <span>标签</span>
            </Tooltip>
          }
          field="label"
          {...formItemLayout}
        >
          <Input style={frmItemStyle} value={form.label} placeholder="请输入标签" onChange={this.genEventHandle('label')} />
        </FormItem>
        <FormItem
          label={
            <Tooltip title="type">
              <span>类型</span>
            </Tooltip>
          }
          field="type"
          {...formItemLayout}
        >
          <Select
            style={frmItemStyle}
            data={fieldItemTypes}
            value={form.type}
            renderOption={item => (
              <Option title={fieldTypeTips[item.value]} value={item.value}>
                {item.name}
              </Option>
            )}
            defaultOption={<Option value="">请选择</Option>}
            onChange={this.handleTypeChange}
          />
        </FormItem>

        {data && !utils.isEmpty(data) && (
          <FormItem
            label={
              <Tooltip title="动态表单项data字段">
                <span>数据</span>
              </Tooltip>
            }
            field="data"
            {...formItemLayout}
            tip={tileInfo(form.type)}
          >
            {form.type && this.renderContentData(form.type, data)}
          </FormItem>
        )}

        {form.type && form.type === 'switch' && (
          <FormItem
            label={
              <Tooltip title="动态表单项style字段">
                <span>样式</span>
              </Tooltip>
            }
            field="style"
            {...formItemLayout}
            tip={fieldDataTips.switch.tip}
          >
            <Tooltip title={fieldDataTips.switch.width}>
              <InputNumber
                value={style.width}
                style={fullStyle}
                min={0}
                step={1}
                max={100}
                type="outer"
                placeholder="宽度"
                onChange={this.handleStyleChange('width')}
              />
            </Tooltip>
          </FormItem>
        )}

        <FormItem
          label={
            <Tooltip title="defaultValue">
              <span>默认值</span>
            </Tooltip>
          }
          field="defaultValue"
          {...formItemLayout}
        >
          {this.renderDefaultValue(form.type, form.defaultValue, data)}
        </FormItem>
        <FormItem
          label={
            <Tooltip title="tip">
              <span>提示</span>
            </Tooltip>
          }
          field="tip"
          {...formItemLayout}
        >
          <TextArea style={frmItemStyle} value={form.tip} rows={4} disableResize placeholder="请输入提示" onChange={this.genEventHandle('tip')} />
        </FormItem>
        <FormItem {...formItemLayout.wrapperCol}>
          <Button onClick={this.handleSubmit}>提交</Button>
        </FormItem>
      </Form>
    );
  };

  renderPreview() {
    const { fields, datas } = this.state;
    return (
      <div>
        <FormBuilder
          fields={fields}
          formMapper={formMapper}
          onChange={this.handleFBChange}
          datas={datas}
          labelWidth="8em"
        />
        <Button onClick={this.showFieldsInfo}>显示Fields数据</Button>
      </div>
    );
  }

  render() {
    const { visible, header, onCancel } = this.props;
    return (
      <Row>
        <Col span={12}>
          {this.renderContent()}
        </Col>
        <Col span={12}>
          {this.renderPreview()}
        </Col>
    </Row>
    );
  }
}

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