UNPKG

hc-components-test

Version:

基于react的通用组件库

838 lines (804 loc) 26.2 kB
import React from 'react'; import PropTypes from 'prop-types'; import Form from 'antd/lib/form'; import Input from 'antd/lib/input'; import InputNumber from 'antd/lib/input-number'; import Select from 'antd/lib/select'; import DatePicker from 'antd/lib/date-picker'; import TimePicker from 'antd/lib/time-picker'; import Radio from 'antd/lib/radio'; import Checkbox from 'antd/lib/checkbox'; import Switch from 'antd/lib/switch'; import Card from 'antd/lib/card'; import Upload from 'antd/lib/upload'; import Button from 'antd/lib/button'; import Tabs from 'antd/lib/tabs'; import Icon from 'antd/lib/icon'; import message from 'antd/lib/message'; import isPlainObject from 'lodash/isPlainObject'; import moment from 'moment'; import localeContext from '../../localeContext'; import './index.less'; @localeContext('CustomForm', { illegal: '表单校验失败', dataIndex: '数据项', legend: '数据结构', dragDesc: '点击或者托转文件到上传区域', upload: '上传文件', save: '保存', cancel: '取消' }) class CustomFormBase extends React.PureComponent { static contextTypes = { router: PropTypes.object } static propTypes = { dataSource: PropTypes.object, parentDataSource: PropTypes.object, options: PropTypes.array.isRequired, formLayout: PropTypes.object, rules: PropTypes.object, onSubmit: PropTypes.func, onChange: PropTypes.func, layout: PropTypes.string, buttons: PropTypes.object, name: PropTypes.string, title: PropTypes.string, noButton: PropTypes.bool, noFlattern: PropTypes.bool, normalize: PropTypes.bool, loading: PropTypes.bool, className: PropTypes.string, keep: PropTypes.bool, form: PropTypes.object, compact: PropTypes.bool, disabled: PropTypes.bool } static defaultProps = { layout: 'horizontal', dataSource: {}, compact: true, rules: {}, normalize: true, formLayout: { labelCol: { span: 4 }, wrapperCol: { span: 19 }, hasFeedback: true, required: true } } constructor(props) { super(props); this._dataSource = this._asMutable(props.dataSource); this.optionsMap = {}; this.refsMap = {}; this._submitting = false; this._validating = false; } componentDidMount() { this.mounted = true; } componentWillReceiveProps(nextProps) { if (nextProps.dataSource !== this.props.dataSource) { this._dataSource = this._asMutable(nextProps.dataSource); } } _asMutable(obj) { if (Array.isArray(obj)) { const arr = []; obj.forEach(d => { arr.push(this._asMutable(d)); }); return arr; } else if (Object(obj) === obj) { const newObj = {}; for (let key in obj) { if (Object(obj[key]) === obj[key]) { newObj[key] = this._asMutable(obj[key]); } else { if (obj.hasOwnProperty(key)) { newObj[key] = obj[key]; } } } return newObj; } else { return obj; } } get submitting() { return this._submitting; } get validating() { return this._validating; } validate(keep, attrName) { this._validating = true; return new Promise((resolve, reject) => { this .props .form .validateFields((err, body) => { if (err) { this._validating = false; reject(err); message.error(this.getLocale('illegal')); } else { body = this.parseBody(body); const promises = []; for (let name in this.refs) { promises.push(this.refs[name].validate(keep, name)); } Promise .all(promises) .then(bodys => { this._validating = false; if (bodys.length) { body = bodys.reduce((p, c) => { if (Array.isArray(c)) { if (p[c[0]]) { p[c[0]].push(c[1]); } else { p[c[0]] = [c[1]]; } return p; } else { return Object.assign(p, c); } }, body); } if (keep && attrName) { const arr = attrName.split('@'); // 数组形式 if (arr.length === 2) { resolve([arr[0], body]); } else { resolve({ [attrName]: body }); } } else { resolve(body); } }, errs => { this._validating = false; message.error(this.getLocale('illegal')); reject(errs); }); } }); }); } handleSubmit(onSubmit, keep) { if (this.props.disabled) { return Promise.resolve(true); } this._submitting = true; return this .validate(keep) .then(body => { let result; try { if (onSubmit) { result = onSubmit(body, this.props.dataSource); } else if (this.props.onSubmit) { result = this .props .onSubmit(body, this.props.dataSource); } } catch (e) { window.console.error(e); } if (result && result.then) { result.then(() => { this._submitting = false; }, () => { this._submitting = false; }); } else { this._submitting = false; } return result; }); } parseBody(body) { if (this.props.normalize) { for (let name in body) { if (body[name] !== undefined) { // 组合成数组 if (this.optionsMap[name].getValue) { body[name] = this.optionsMap[name].getValue(body[name]); } if (this.optionsMap[name].originName) { let v = body[name]; delete body[name]; name = this.optionsMap[name].originName; body[name] = body[name] || []; body[name].push(v); } } } } if (this.props.name) { return { [this.props.name]: body }; } else { return body; } } handleCancel() { this .context .router .goBack(); } getFormLayout(option, extra) { return Object.assign({}, this.props.formLayout, { label: option.title || option.name || extra.title, required: option.required || extra.required, hasFeedback: option.hasFeedback || extra.hasFeedback, help: option.help || extra.help, className: option.className || extra.className, extra: option.extra ? option.extra : (option.icon || null) }); } getValueFromEvent(e, option) { let v; if (Object(e) === e) { if ((isPlainObject(e) || isPlainObject(e.__proto__))) { if (option.valuePropName) { v = e[option.valuePropName]; } else { v = e.target ? e.target.value : e; } } else { v = e.target && e.target.nodeName ? e.target.value : e; } } else { v = e; } return option.builtIn && option.getValue ? option.getValue(v) : v; } getInitialValue(option, defaultValue) { let initialValue = this._dataSource[option.name]; if (this.props.normalize && initialValue !== undefined) { switch (option.type) { case 'boolean': if (typeof initialValue !== 'boolean') { initialValue = [false, true][parseInt(initialValue, 10)]; option.getValue = (value) => value + 0; option.builtIn = true; } break; case 'array': if (!Array.isArray(initialValue)) { initialValue = initialValue.split(','); option.getValue = (value) => Array.isArray(value) ? value.join(',') : String(value); option.builtIn = true; } break; } } if (initialValue === undefined && !this.props.loading) { initialValue = defaultValue; } if (!this.optionsMap[option.name]) { this._dataSource[option.name] = initialValue; this.optionsMap[option.name] = option; } return initialValue; } renderInput(option, key, onChange) { if (!option.name) { return null; } const noFlattern = option.noFlattern === undefined ? this.props.noFlattern : option.noFlattern; const dataSource = noFlattern ? (option.dataSource || this._dataSource) : this._dataSource; if (option.children) { if (option.type === 'array') { const isArr = option.children[0].children; const dataSources = dataSource[option.name] || []; const tabs = []; const indexes = []; dataSources.forEach((d, index) => { indexes.push(index + ''); tabs.push({ index: index + '', data: d }); }); // 已经赋值过了 if (this.refsMap[option.name]) { // 发现传入的dataSources有值,说明外部dataSource有变化,需要重新赋值,提取是之前的值不一样 if (dataSources.length && dataSources !== this.refsMap[option.name].dataSources) { this.refsMap[option.name] = { indexes: indexes, activeKey: indexes[0], tabs: tabs, dataSources: dataSources }; } } else { // 赋值 this.refsMap[option.name] = { indexes: indexes, activeKey: indexes[0], tabs: tabs, dataSources: dataSources }; } var obj = this.refsMap[option.name]; return ( <Card className="j-com-customForm-card" key={key} title={option.title || option.name}> <Tabs onChange={(activeKey) => { obj.activeKey = activeKey; if (this.mounted) { this.forceUpdate(); } }} onEdit={(targetKey, action) => { let idx; if (action === 'add') { idx = obj.indexes.length ? 1 + parseInt(obj.indexes[obj.indexes.length - 1], 10) + '' : '0'; obj.indexes.push(idx); obj.tabs.push({ index: idx, data: {} }); obj.activeKey = idx + ''; } else { idx = obj.indexes.indexOf(targetKey); obj.indexes.splice(idx, 1); obj.tabs.splice(idx, 1); // 选中项小于触发项 if (obj.activeKey >= targetKey) { if (idx > 0) { obj.activeKey = idx - 1; } else if (obj.indexes.length) { obj.activeKey = idx + 1; } else { obj.activeKey = ''; } obj.activeKey = obj.activeKey + ''; } } if (this.mounted) { this.forceUpdate(); } }} activeKey={obj.activeKey} type="editable-card">{obj .tabs .map((item) => { const key = option.name + '@' + obj.indexes[item.index]; return ( <Tabs.TabPane tab={this.getLocale('dataIndex') + item.index} key={item.index} closable={true} forceRender={true}> {isArr ? (this.optionsMap[key] = Object.assign({ name: key, dataSource: dataSources[item.index], originName: option.name, title: this.getLocale('legend') }, option.children[item.index])) && this.renderInput(this.optionsMap[key], key, (e) => { if (onChange) { const arr = obj .tabs .map(d => d.data); const o = Object.assign({}, this.optionsMap[key]); obj.tabs[item.index].data = arr[item.index] = this.getValueFromEvent(e, o); o.name = o.originName; onChange(arr, o); } }) : (<CustomForm formLayout={this.props.formLayout} disabled={this.props.disabled} ref={key} onChange={(e, o) => { if (onChange) { const arr = obj .tabs .map(d => d.data); arr[item.index][o.name] = this.getValueFromEvent(e, o); onChange(arr, option); } }} options={option.children} parentDataSource={this._dataSource} dataSource={dataSources[item.index]} noButton={true} noFlattern={noFlattern} />)} </Tabs.TabPane> ); })} </Tabs> </Card> ); } else if (noFlattern) { const formElm = (<CustomForm formLayout={this.props.formLayout} disabled={this.props.disabled} key={key} ref={option.name} title={option.title} name={option.embed ? '' : option.name} onChange={(e, o) => { if (onChange) { const obj = this.refsMap[option.name] ? this.refsMap[option.name] : (this.refsMap[option.name] = {}); obj[o.name] = this.getValueFromEvent(e, o); onChange(obj, option); } }} options={option.children} parentDataSource={this._dataSource} dataSource={dataSource[option.name]} noButton={true} noFlattern={noFlattern} />); return option.embed ? ( <Form.Item key={key} {...this.props.formLayout} label={option.title || option.name}><div style={{ border: '1px solid #ddd', padding: 10 }}>{formElm}</div></Form.Item> ) : formElm; } else { const renderInput = this .renderInput .bind(this); return ( <Card className="j-com-customForm-card" key={key} title={option.title || option.name}>{option .children .map(renderInput)}</Card> ); } } const propsByState = option.getProps ? option.getProps.call(this, this._dataSource, () => this.forceUpdate(), this.props.form) : {}; if (propsByState.name === false) { return null; } const initialValue = this.getInitialValue(option, propsByState.default || option.default); const formItemProps = this.getFormLayout(option, propsByState); let rules = [].concat(this.props.rules[option.name] || []); const defaultProps = { disabled: option.disabled || this.props.disabled, placeholder: option.placeholder }; const decoratorProps = { initialValue: initialValue, rules: rules.concat(option.rules || propsByState.rules || []), getValueFromEvent: (e) => { const v = this.getValueFromEvent(e, option); this._dataSource[option.name] = v; if (onChange) { onChange(v, option); } if (option.onChange) { option.onChange(this._dataSource, (nextState) => { this .props .form .setFieldsValue(nextState); }); } return v; } }; let input; if (option.render) { return option.render(option, (node, dpros) => { return ( <Form.Item key={key} {...formItemProps}>{this .props .form .getFieldDecorator(option.name, Object.assign(decoratorProps, dpros))(node)}</Form.Item> ); }, Object.assign(defaultProps, propsByState), this.props.form); } else if (option.enum) { input = ( <Radio.Group {...defaultProps} {...propsByState}> {option .enum .map(item => ( <Radio key={item} value={item}>{item}</Radio> ))} </Radio.Group> ); } else { if (option.format) { switch (option.format) { case 'DATE_TIME': decoratorProps.initialValue = moment(decoratorProps.initialValue); input = (<DatePicker style={{ width: 250 }} showTime format="YYYY-MM-DD HH:mm:ss" {...defaultProps} {...propsByState} />); break; case 'DATE': decoratorProps.initialValue = moment(decoratorProps.initialValue); input = (<DatePicker style={{ width: 250 }} {...defaultProps} {...propsByState} />); break; case 'TIME': decoratorProps.initialValue = moment(decoratorProps.initialValue); input = (<TimePicker style={{ width: 250 }} {...defaultProps} {...propsByState} />); break; case 'DATE_RANGE': input = (<DatePicker.RangePicker {...defaultProps} {...propsByState} />); break; case 'CDN_PIC': decoratorProps.valuePropName = option.valuePropName = 'fileList'; if (option.dragger) { input = ( <Upload.Dragger {...defaultProps} {...propsByState} listType="picture" action={option.action}> <p className="ant-upload-drag-icon"> <Icon type="inbox" /> </p> <p className="ant-upload-text">{this.getLocale('dragDesc')}</p> </Upload.Dragger> ); } else { input = ( <Upload {...defaultProps} {...propsByState} action={option.action} listType="picture"> <Button> <Icon type="upload" /> {this.getLocale('upload')} </Button> </Upload> ); } break; case 'FILE': decoratorProps.valuePropName = option.valuePropName = 'fileList'; if (option.dragger) { input = ( <Upload.Dragger {...defaultProps} {...propsByState} listType="text" action={option.action}> <p className="ant-upload-drag-icon"> <Icon type="inbox" /> </p> <p className="ant-upload-text">{this.getLocale('dragDesc')}</p> </Upload.Dragger> ); } else { input = ( <Upload {...defaultProps} {...propsByState} action={option.action} listType="text"> <Button> <Icon type="upload" /> {this.getLocale('upload')} </Button> </Upload> ); } break; case 'SELECT': var options = propsByState.options || option.options; if (option.mode === 'radio') { input = ( <Radio.Group {...defaultProps} {...propsByState} > {options.map(item => ( <Radio key={item.value} value={item.value} disabled={item.disabled}>{this.getLabel(item, option)}</Radio> ))} </Radio.Group> ); } else if (option.mode === 'checkbox') { input = (<Checkbox.Group {...defaultProps} {...propsByState} options={options} />); } else { input = ( <Select mode={option.mode} {...defaultProps} {...propsByState}> {options.map(item => ( <Select.Option key={item.value || item.id} value={item.value || item.id} disabled={item.disabled}>{this.getLabel(item, option)}</Select.Option> ))} </Select> ); } break; case 'Component': input = (<option.Component {...defaultProps} {...propsByState} />); break; case 'Element': case 'TEXT': case 'Function': case 'JSON': input = (<Input.TextArea autosize={{ minRows: 4, maxRows: 10 }} {...defaultProps} onPressEnter={decoratorProps.getValueFromEvent} onBlur={decoratorProps.getValueFromEvent} {...propsByState} />); break; default: input = (<Input {...defaultProps} {...propsByState} />); } } else { switch (option.type) { case 'number': case 'integer': var max = option.max; if (!max) { var maxLength = maxLength = option.maxLength ? option.maxLength : 10; max = +new Array(maxLength).join(9); if (option.maximum) { max = Number(maxLength + '0.' + option.maximum); } } input = (<InputNumber style={{ width: 250 }} min={option.min ? option.min : 0} max={max} {...defaultProps} {...propsByState} />); break; case 'boolean': decoratorProps.valuePropName = option.valuePropName = 'checked'; var placeholder = [].concat(option.placeholder); input = (<Switch unCheckedChildren={placeholder[0]} checkedChildren={placeholder[1]} {...defaultProps} {...propsByState} />); break; case 'string': default: input = (<Input {...defaultProps} {...propsByState} />); break; } } } return ( <Form.Item key={key} {...formItemProps}>{this .props .form .getFieldDecorator(option.name, decoratorProps)(input)}</Form.Item> ); } clear() { this .props .form .resetFields(); this.refsMap = {}; for (let name in this.refs) { this.refs[name].clear(); } } getLabel(item, option) { if (option.getLabel) { return option.getLabel(item); } else { return option.lableKey ? item[option.lableKey] : item.label || item.name; } } render() { let buttons = this.props.buttons; if (buttons) { buttons = ( <Form.Item wrapperCol={{ offset: this.props.formLayout.labelCol.span, span: this.props.formLayout.wrapperCol.span }}>{buttons}</Form.Item> ); } else if (!this.props.noButton) { buttons = ( <Form.Item wrapperCol={{ offset: this.props.formLayout.labelCol.span, span: this.props.formLayout.wrapperCol.span }}> <Button onClick={() => this.handleSubmit(null, this.props.keep)} style={{ marginRight: 10 }} type="primary">{this.getLocale('save')}</Button> <Button onClick={() => this.handleCancel()} style={{ marginRight: 10 }} type="default">{this.getLocale('cancel')}</Button> </Form.Item> ); } const compact = this.props.compact ? 'j-com-customForm-compact ' : ''; const className = compact + (this.props.className || ''); const formElm = ( <Form className={'j-com-customForm ' + className} onSubmit={e => { e.preventDefault(); this.handleSubmit(null, this.props.keep); }} layout={this.props.layout}>{this .props .options .map((option, key) => this.renderInput(option, key, this.props.onChange))} {buttons} </Form> ); if (this.props.name) { return ( <Card className={'j-com-customForm-card ' + className} title={this.props.title || this.props.name}> {formElm} </Card> ); } else { return formElm; } } } const CustomForm = Form.create({withRef: true})(CustomFormBase); CustomForm.prototype.getRealInstance = function () { return this.refs.wrappedComponent; }; ['handleSubmit', 'clear', 'validate'].forEach(method => { CustomForm.prototype[method] = function (...args) { return this.refs.wrappedComponent[method](...args); }; }); Object.defineProperties(CustomForm.prototype, { submitting: { get() { return this.refs.wrappedComponent.submitting; } }, validating: { get() { return this.refs.wrappedComponent.validating; } } }); export {CustomForm};