在之前看redux-form
的时候, 只实现了两种比较简单的Field
组件, TextField
和DateTimeField
, 现在来看看一个麻烦一点的FileField
.
文件上传是比较常用的功能, 尽量做到简单易用的组件化形式为好, 首先定义FileField
组件.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| class FileField extends React.PureComponent { static propTypes = {}; render() { let { input, label, inputFile, labelClassName, inputDivClassName, className, inputClassName, meta: { touched, error, warning }, } = this.props; return ( <div className={classNames(className, 'has-feedback', { 'has-error': touched && error, 'has-warning': touched && !error && warning, 'has-success': touched && !error && !warning, })} > <label className={classNames('control-label', labelClassName)}>{label}</label> <div className={classNames(inputDivClassName, { 'hint--bottom-right hint-error': touched && error, 'hint--bottom-right hint-warning': touched && !error && warning, })} aria-label={error || warning} > <div className='input-group cursor-p'> <input type='text' readOnly className={classNames('form-control cursor-p', inputClassName)} {...input} /> <span className='input-group-addon'> <span className='glyphicon glyphicon-file'></span> </span> </div> </div> <input ref='selectFileInput' type='file' className='hide' {...inputFile} /> </div> ); } }
|
然后给组件加入点击事件和触发打开弹窗的事件.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <div className="input-group cursor-p" onClick={this.clickOpenSelectFileWindow}> <input ref="selectFileInput" type="file" className="hide" onChange={this.clickSelectFileChange} {...inputFile} />
const mapDispatchToProps = (dispatch) => ({ fn: { change: (form, field, value) => { dispatch(change(form, field, value)); }, }, }); @connect(null, mapDispatchToProps) class FileField extends React.PureComponent { constructor(props) { super(props); this.state = {}; this.clickOpenSelectFileWindow = this.clickOpenSelectFileWindow.bind(this); this.clickSelectFileChange = this.clickSelectFileChange.bind(this); } clickOpenSelectFileWindow(event) { this.refs.selectFileInput.click(); if (event) { event.preventDefault(); event.stopPropagation(); } } clickSelectFileChange(event) { let fileName = event.target.files.length > 0 ? event.target.files[0].name : ''; this.props.fn.change(this.props.formName, this.props.input.name, fileName); this.props.fn.change(this.props.formName, this.props.input.name + 'Files', event.target.files); } }
<Field name="SelectFile" component={FileField} label="选择文件" labelClassName="col-sm-2" inputDivClassName="col-sm-10" formName={toDoItemFormName} />
|
这样基本实现了点击输入框, 会弹出选择文件, 选择完成以后会显示选择文件的名字, 而选择到的文件保存在redux-form
的state
中同名field
加后缀Files
下. 在提交时只需要提取相应文件传入到后台即可. 这里使用jquery-form
来实现异步提交文件.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| var postFile = function (data, files) { var $form = $('<form><input name="CommandName" value="' + data.CommandName + '"/></form>'); var $newInput = $('<input name="myFile" type="file"/>'); $newInput[0].files = files; $form.append($newInput); var params = _.isString(data.params) ? data.params : JSON.stringify(data.params); $form.append($('<input type="text" name="data" value="' + encodeURIComponent(params) + '"/>'));
var promise = new Promise((resolve, reject) => { $form.ajaxSubmit({ url: url, type: "POST", dataType: "json", success: function (resultValue) { let action = resolveResultValue(resultValue); if (action.action === ActionType.SUCCESS) { return resolve(resultValue); } else { return reject(action); } }, error: function (resultValue) { return reject({ action: ActionType.NETWORK_ERROR, payload: resultValue.responseText }); }, }); }).then((resultValue) => { ...//log return resultValue.returnValue; }).catch(({ action, payload }) => { ...//异常处理 return Promise.reject(payload); }) return promise; } //service.js saveOrUpdateItemWithFile: (info) => { return commonAjaxService.postFile({ CommandName: 'Service$SaveOrUpdateInfoWithFiles', params: info, }, info.SelectFileFiles); }, //action.js static saveOrUpdateItemWithFile(info) { return (dipatch, getState) => { let action = { type: ActionType.SAVE_ITEM_START, payload: null, }; dipatch(action); dipatch(startSubmit(toDoItemFormName)); return Service.saveOrUpdateItemWithFile(info).then( (resultValue) => { dipatch(setSubmitSucceeded(toDoItemFormName)); let action = { type: ActionType.SAVE_ITEM_END, payload: resultValue, }; dipatch(action); }, (error) => { dipatch(setSubmitFailed(toDoItemFormName)); let action = { type: ActionType.SAVE_ITEM_END, payload: null, }; dipatch(action); } ); }; }
|
还有个情况是, 想在别的地方触发该文件选择框. 那么提供一个传入函数, 在函数返回当前组件实例(类似 ref), 在需要触发的时候执行ref.clickOpenSelectFileWindow(event)
即可.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| static propTypes = { componentRef: P.func.isRequired, } componentDidMount() { if (this.props.componentRef) { this.props.componentRef(this); } }
bindInputFileRef(ref){ this.inputFileRef = ref; } targetOpenFileSelectWindow(event){ this.inputFileRef.clickOpenSelectFileWindow(event); } <Field componentRef={bindInputFileRef} name="SelectFile" component={FileField} label="选择文件" labelClassName="col-sm-2" inputDivClassName="col-sm-10" formName={personToDoItemFormName} />
|