UNPKG

19.8 kBJavaScriptView Raw
1import _extends from 'babel-runtime/helpers/extends';
2import _objectWithoutProperties from 'babel-runtime/helpers/objectWithoutProperties';
3import _classCallCheck from 'babel-runtime/helpers/classCallCheck';
4import _possibleConstructorReturn from 'babel-runtime/helpers/possibleConstructorReturn';
5import _inherits from 'babel-runtime/helpers/inherits';
6
7var _class, _temp, _initialiseProps;
8
9import React from 'react';
10import PropTypes from 'prop-types';
11import classNames from 'classnames';
12import { polyfill } from 'react-lifecycles-compat';
13
14import { func, obj } from '../util';
15import Icon from '../icon';
16import Base from './base';
17import Uploader from './runtime/index';
18import html5Uploader from './runtime/html5-uploader';
19import List from './list';
20import { fileToObject, getFileItem, errorCode } from './util';
21
22var noop = func.noop;
23
24/**
25 * Upload
26 */
27var Upload = (_temp = _class = function (_Base) {
28 _inherits(Upload, _Base);
29
30 function Upload(props) {
31 _classCallCheck(this, Upload);
32
33 var _this = _possibleConstructorReturn(this, _Base.call(this, props));
34
35 _initialiseProps.call(_this);
36
37 var value = void 0;
38 if ('value' in props) {
39 value = props.value;
40 } else {
41 value = props.defaultValue;
42 }
43
44 _this.state = {
45 value: !Array.isArray(value) ? [] : value,
46 uploading: false
47 };
48 return _this;
49 }
50
51 Upload.getDerivedStateFromProps = function getDerivedStateFromProps(nextProps, prevState) {
52 // 上传中不允许做受控修改
53 if ('value' in nextProps && nextProps.value !== prevState.value && !prevState.uploading) {
54 return {
55 value: !Array.isArray(nextProps.value) ? [] : nextProps.value
56 };
57 }
58
59 return null;
60 };
61
62 /**
63 * 对外暴露API, 添加文件
64 * @param files
65 */
66 Upload.prototype.selectFiles = function selectFiles(files) {
67 var filesArr = files.length ? Array.prototype.slice.call(files) : [files];
68
69 this.onSelect(filesArr);
70 };
71
72 Upload.prototype.uploadFiles = function uploadFiles(files) {
73 // NOTE: drag上传,当鼠标松开的时候回执行 onDrop,但此时onChange还没出发所以 value=[], 必须提前标识上传中
74 this.state.uploading = true;
75 var fileList = files.filter(function (file) {
76 if (file.state === 'selected') {
77 file.state = 'uploading';
78 return true;
79 }
80 return false;
81 }).map(function (file) {
82 return file.originFileObj;
83 });
84
85 fileList.length && this.uploaderRef.startUpload(fileList);
86 };
87
88 /**
89 * 对外暴露api,控制文件上传
90 */
91
92
93 Upload.prototype.startUpload = function startUpload() {
94 this.uploadFiles(this.state.value);
95 };
96
97 Upload.prototype.replaceFiles = function replaceFiles(old, current) {
98 var targetItem = getFileItem(old, this.state.value);
99 if (!targetItem) {
100 return;
101 }
102
103 current.uid = old.uid;
104 targetItem.originFileObj = current;
105 };
106
107 // 替换掉队列里面的文件
108
109
110 Upload.prototype.isUploading = function isUploading() {
111 return this.state.uploading;
112 };
113
114 /**
115 * 删除文件
116 * @param {File} file
117 * @return {void}
118 */
119
120
121 /**
122 * 取消上传
123 * @param {File} file
124 * @return {void}
125 */
126
127
128 Upload.prototype.render = function render() {
129 var _classNames, _classNames2;
130
131 var _props = this.props,
132 listType = _props.listType,
133 prefix = _props.prefix,
134 dragable = _props.dragable,
135 shape = _props.shape,
136 className = _props.className,
137 style = _props.style,
138 useDataURL = _props.useDataURL,
139 disabled = _props.disabled,
140 limit = _props.limit,
141 closable = _props.closable,
142 beforeUpload = _props.beforeUpload,
143 readonly = _props.readonly,
144 onRemove = _props.onRemove,
145 onCancel = _props.onCancel,
146 onPreview = _props.onPreview,
147 list = _props.list,
148 extraRender = _props.extraRender,
149 progressProps = _props.progressProps,
150 rtl = _props.rtl,
151 isPreview = _props.isPreview,
152 renderPreview = _props.renderPreview,
153 name = _props.name,
154 _props$fileKeyName = _props.fileKeyName,
155 fileKeyName = _props$fileKeyName === undefined ? name : _props$fileKeyName,
156 fileNameRender = _props.fileNameRender,
157 actionRender = _props.actionRender,
158 previewOnFileName = _props.previewOnFileName,
159 others = _objectWithoutProperties(_props, ['listType', 'prefix', 'dragable', 'shape', 'className', 'style', 'useDataURL', 'disabled', 'limit', 'closable', 'beforeUpload', 'readonly', 'onRemove', 'onCancel', 'onPreview', 'list', 'extraRender', 'progressProps', 'rtl', 'isPreview', 'renderPreview', 'name', 'fileKeyName', 'fileNameRender', 'actionRender', 'previewOnFileName']);
160
161 var cls = classNames((_classNames = {}, _classNames[prefix + 'upload'] = true, _classNames[prefix + 'upload-dragable'] = dragable, _classNames[prefix + 'disabled'] = disabled, _classNames[prefix + 'readonly'] = readonly, _classNames[className] = className, _classNames));
162
163 var isExceedLimit = this.state.value.length >= limit;
164 var innerCls = classNames((_classNames2 = {}, _classNames2[prefix + 'upload-inner'] = true, _classNames2[prefix + 'hidden'] = isExceedLimit, _classNames2));
165
166 var children = this.props.children;
167 if (shape === 'card') {
168 var _classNames3;
169
170 var cardCls = classNames((_classNames3 = {}, _classNames3[prefix + 'upload-card'] = true, _classNames3[prefix + 'disabled'] = disabled, _classNames3));
171 children = React.createElement(
172 'div',
173 { className: cardCls },
174 React.createElement(Icon, { size: 'large', type: 'add', className: prefix + 'upload-add-icon' }),
175 React.createElement(
176 'div',
177 { tabIndex: '0', role: 'button', className: prefix + 'upload-text' },
178 children
179 )
180 );
181 }
182
183 if (isPreview) {
184 if (typeof renderPreview === 'function') {
185 var _classNames4;
186
187 var previewCls = classNames((_classNames4 = {}, _classNames4[prefix + 'form-preview'] = true, _classNames4[className] = !!className, _classNames4));
188 return React.createElement(
189 'div',
190 { style: style, className: previewCls },
191 renderPreview(this.state.value, this.props)
192 );
193 }
194
195 if (listType) {
196 return React.createElement(List, { isPreview: true, listType: listType, style: style, className: className, value: this.state.value });
197 }
198
199 return null;
200 }
201
202 // disabled 状态下把 remove函数替换成禁止 remove的函数
203 var onRemoveFunc = disabled ? func.prevent : onRemove;
204 var otherAttributes = obj.pickAttrsWith(this.props, 'data-');
205 return React.createElement(
206 'div',
207 _extends({ className: cls, style: style }, otherAttributes),
208 React.createElement(
209 Uploader,
210 _extends({}, others, {
211 name: fileKeyName,
212 beforeUpload: beforeUpload,
213 dragable: dragable,
214 disabled: disabled || isExceedLimit,
215 className: innerCls,
216 onSelect: this.onSelect,
217 onDrop: this.onDrop,
218 onProgress: this.onProgress,
219 onSuccess: this.onSuccess,
220 onError: this.onError,
221 ref: this.saveUploaderRef
222 }),
223 children
224 ),
225 listType || list ? React.createElement(List, {
226 useDataURL: useDataURL,
227 fileNameRender: fileNameRender,
228 actionRender: actionRender,
229 uploader: this,
230 listType: listType,
231 value: this.state.value,
232 closable: closable,
233 onRemove: onRemoveFunc,
234 progressProps: progressProps,
235 onCancel: onCancel,
236 onPreview: onPreview,
237 extraRender: extraRender,
238 rtl: rtl,
239 previewOnFileName: previewOnFileName
240 }) : null
241 );
242 };
243
244 return Upload;
245}(Base), _class.displayName = 'Upload', _class.propTypes = _extends({}, html5Uploader.propTypes, List.propTypes, {
246 /**
247 * 样式前缀
248 */
249 prefix: PropTypes.string.isRequired,
250 /**
251 * 上传的地址
252 */
253 action: PropTypes.string,
254 /**
255 * 文件列表
256 */
257 value: PropTypes.array,
258 /**
259 * 默认文件列表
260 */
261 defaultValue: PropTypes.array,
262 /**
263 * 上传按钮形状
264 */
265 shape: PropTypes.oneOf(['card']),
266 /**
267 * 上传列表的样式
268 * @enumdesc 文字, 图文, 卡片
269 */
270 listType: PropTypes.oneOf(['text', 'image', 'card']),
271 list: PropTypes.any,
272 /**
273 * 文件名字段
274 */
275 name: PropTypes.string,
276 /**
277 * 上传额外传参
278 */
279 data: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
280 /**
281 * 数据格式化函数,配合自定义 action 使用,参数为服务器的响应数据,详见 [formatter](#formater)
282 * @param {Object} response 返回
283 * @param {File} file 文件对象
284 */
285 formatter: PropTypes.func,
286 /**
287 * 最大文件上传个数
288 */
289 limit: PropTypes.number,
290 /**
291 * 设置上传超时,单位ms
292 */
293 timeout: PropTypes.number,
294 /**
295 * 可选参数,是否支持拖拽上传,`ie10+` 支持。
296 */
297 dragable: PropTypes.bool,
298 closable: PropTypes.bool,
299 /**
300 * 可选参数,是否本地预览
301 */
302 useDataURL: PropTypes.bool,
303 /**
304 * 可选参数,是否禁用上传功能
305 */
306 disabled: PropTypes.bool,
307 /**
308 * 选择文件回调
309 */
310 onSelect: PropTypes.func,
311 /**
312 * 上传中
313 */
314 onProgress: PropTypes.func,
315 /**
316 * 上传文件改变时的状态
317 * @param {Object} info 文件事件对象
318 */
319 onChange: PropTypes.func,
320 /**
321 * 可选参数,上传成功回调函数,参数为请求下响应信息以及文件
322 * @param {Object} file 文件
323 * @param {Array<Object>} value 值
324 */
325 onSuccess: PropTypes.func,
326 /**
327 * 可选参数, 用于校验文件,afterSelect仅在 autoUpload=false 的时候生效,autoUpload=true时,可以使用beforeUpload完全可以替代该功能.
328 * @param {Object} file
329 * @returns {Boolean} 返回false会阻止上传,其他则表示正常
330 */
331 afterSelect: PropTypes.func,
332 /**
333 * 移除文件回调函数
334 * @param {Object} file 文件
335 * @returns {Boolean|Promise} 返回 false、Promise.resolve(false)、 Promise.reject() 将阻止文件删除
336 */
337 onRemove: PropTypes.func,
338 /**
339 * 可选参数,上传失败回调函数,参数为上传失败的信息、响应信息以及文件
340 * @param {Object} file 出错的文件
341 * @param {Array} value 当前值
342 */
343 onError: PropTypes.func,
344 /**
345 * 可选参数, 详见 [beforeUpload](#beforeUpload)
346 * @param {Object} file 所有文件
347 * @param {Object} options 参数
348 * @returns {Boolean|Object|Promise} 返回值作用见demo
349 */
350 beforeUpload: PropTypes.func,
351 /**
352 * 放文件
353 */
354 onDrop: PropTypes.func,
355 /**
356 * 自定义class
357 */
358 className: PropTypes.string,
359 /**
360 * 自定义内联样式
361 */
362 style: PropTypes.object,
363 /**
364 * 子元素
365 */
366 children: PropTypes.node,
367 /**
368 * 自动上传
369 */
370 autoUpload: PropTypes.bool,
371 /**
372 * 自定义上传方法
373 * @param {Object} option
374 * @return {Object} object with abort method
375 */
376 request: PropTypes.func,
377 /**
378 * 透传给Progress props
379 */
380 progressProps: PropTypes.object,
381 rtl: PropTypes.bool,
382 /**
383 * 是否为预览态
384 */
385 isPreview: PropTypes.bool,
386 /**
387 * 预览态模式下渲染的内容
388 * @param {number} value 评分值
389 */
390 renderPreview: PropTypes.func,
391 /**
392 * 文件对象的 key name
393 * @version 1.21
394 */
395 fileKeyName: PropTypes.string,
396 /**
397 * list 的自定义文件名渲染
398 * @param {Object} file 文件
399 * @return {Node} react node
400 */
401 fileNameRender: PropTypes.func,
402 /**
403 * 操作区域额外渲染
404 * @param {Object} file 文件
405 * @return {Node} react node
406 */
407 actionRender: PropTypes.func,
408 /**
409 * 点击文件名时触发 onPreview
410 * @version 1.24
411 */
412 previewOnFileName: PropTypes.bool
413}), _class.defaultProps = _extends({}, html5Uploader.defaultProps, {
414 prefix: 'next-',
415 limit: Infinity,
416 autoUpload: true,
417 closable: true,
418 onSelect: noop,
419 onProgress: noop,
420 onChange: noop,
421 onSuccess: noop,
422 onRemove: noop,
423 onError: noop,
424 onDrop: noop,
425 beforeUpload: noop,
426 afterSelect: noop,
427 previewOnFileName: false
428}), _initialiseProps = function _initialiseProps() {
429 var _this2 = this;
430
431 this.onSelect = function (files) {
432 var _props2 = _this2.props,
433 autoUpload = _props2.autoUpload,
434 afterSelect = _props2.afterSelect,
435 onSelect = _props2.onSelect,
436 limit = _props2.limit;
437 // 总数
438
439 var total = _this2.state.value.length + files.length;
440 // 差额
441 var less = limit - _this2.state.value.length;
442 if (less <= 0) {
443 // 差额不足 则不上传
444 return;
445 }
446
447 var fileList = files.map(function (file) {
448 var objFile = fileToObject(file);
449 objFile.state = 'selected';
450 return objFile;
451 });
452
453 // 默认全量上传
454 var uploadFiles = fileList;
455 var discardFiles = [];
456 if (total > limit) {
457 // 全量上传总数会超过limit 但是 还有差额
458 uploadFiles = fileList.slice(0, less);
459 discardFiles = fileList.slice(less);
460 }
461
462 var value = _this2.state.value.concat(fileList);
463
464 /* eslint-disable-next */
465 _this2.state.value = value;
466
467 if (autoUpload) {
468 _this2.uploadFiles(uploadFiles);
469 }
470
471 onSelect(uploadFiles, value);
472 discardFiles.forEach(function (file) {
473 // 丢弃的文件
474 var err = new Error(errorCode.EXCEED_LIMIT);
475 err.code = errorCode.EXCEED_LIMIT;
476 _this2.onError(err, null, file);
477 });
478
479 if (!autoUpload) {
480 uploadFiles.forEach(function (file) {
481 var isPassed = afterSelect(file);
482 func.promiseCall(isPassed, func.noop, function (error) {
483 _this2.onError(error, null, file); // TODO: handle error message
484 });
485 });
486 _this2.onChange(value, uploadFiles);
487 }
488 };
489
490 this.onDrop = function (files) {
491 _this2.onSelect(files);
492 _this2.props.onDrop(files);
493 };
494
495 this.replaceWithNewFile = function (old, current) {
496 var newFile = fileToObject(current);
497 newFile.state = 'selected';
498
499 var matchKey = old.uid !== undefined ? 'uid' : 'name';
500
501 var fileList = _this2.state.value;
502 for (var i = 0; i < fileList.length; i++) {
503 var item = fileList[i];
504 if (item[matchKey] === old[matchKey]) {
505 fileList.splice(i, 1, newFile);
506 break;
507 }
508 }
509
510 _this2.uploadFiles([newFile]);
511 return newFile;
512 };
513
514 this.onProgress = function (e, file) {
515 _this2.state.uploading = true;
516
517 var value = _this2.state.value;
518 var targetItem = getFileItem(file, value);
519
520 if (!targetItem) {
521 return;
522 }
523
524 _extends(targetItem, {
525 state: 'uploading',
526 percent: e.percent
527 });
528
529 _this2.setState({
530 value: value
531 });
532
533 _this2.props.onProgress(value, targetItem);
534 };
535
536 this.onSuccess = function (response, file) {
537 var formatter = _this2.props.formatter;
538
539
540 if (formatter) {
541 response = formatter(response, file);
542 }
543
544 try {
545 if (typeof response === 'string') {
546 response = JSON.parse(response);
547 }
548 } catch (e) {
549 e.code = errorCode.RESPONSE_FAIL;
550 return _this2.onError(e, response, file);
551 }
552
553 if (response.success === false) {
554 var err = new Error(response.message || errorCode.RESPONSE_FAIL);
555 err.code = errorCode.RESPONSE_FAIL;
556 return _this2.onError(err, response, file);
557 }
558
559 var value = _this2.state.value;
560 var targetItem = getFileItem(file, value);
561
562 if (!targetItem) {
563 return;
564 }
565
566 _extends(targetItem, {
567 state: 'done',
568 response: response,
569 url: response.url,
570 downloadURL: response.downloadURL || response.url // 下载地址(可选)
571 });
572
573 if (!_this2.props.useDataURL) {
574 targetItem.imgURL = response.imgURL || response.url; // 缩略图地址(可选)
575 }
576
577 _this2.updateUploadingState();
578
579 _this2.onChange(value, targetItem);
580 _this2.props.onSuccess(targetItem, value);
581 };
582
583 this.onError = function (err, response, file) {
584 var value = _this2.state.value;
585 var targetItem = getFileItem(file, value);
586
587 if (!targetItem) {
588 return;
589 }
590
591 _extends(targetItem, {
592 state: 'error',
593 error: err,
594 response: response
595 });
596
597 _this2.updateUploadingState();
598
599 _this2.onChange(value, targetItem);
600 _this2.props.onError(targetItem, value);
601 };
602
603 this.removeFile = function (file) {
604 file.state = 'removed';
605 _this2.uploaderRef.abort(file); // 删除组件时调用组件的 `abort` 方法中断上传
606
607 var fileList = _this2.state.value;
608 var targetItem = getFileItem(file, fileList);
609 var index = fileList.indexOf(targetItem);
610 if (index !== -1) {
611 fileList.splice(index, 1);
612 _this2.onChange(fileList, targetItem);
613 }
614 };
615
616 this.updateUploadingState = function () {
617 var inProgress = _this2.state.value.some(function (i) {
618 return i.state === 'uploading';
619 });
620 if (!inProgress) {
621 _this2.state.uploading = false;
622 }
623 };
624
625 this.abort = function (file) {
626 var fileList = _this2.state.value;
627 var targetItem = getFileItem(file, fileList);
628 var index = fileList.indexOf(targetItem);
629 if (index !== -1) {
630 fileList.splice(index, 1);
631 _this2.onChange(fileList, targetItem);
632 }
633 _this2.uploaderRef.abort(file); // 取消上传时调用组件的 `abort` 方法中断上传
634 };
635
636 this.onChange = function (value, file) {
637 _this2.setState({
638 value: value
639 });
640 _this2.props.onChange(value, file);
641 };
642}, _temp);
643
644
645export default polyfill(Upload);
\No newline at end of file