@alifd/next
Version:
A configurable component library for web built on React.
645 lines (562 loc) • 19.8 kB
JavaScript
import _extends from 'babel-runtime/helpers/extends';
import _objectWithoutProperties from 'babel-runtime/helpers/objectWithoutProperties';
import _classCallCheck from 'babel-runtime/helpers/classCallCheck';
import _possibleConstructorReturn from 'babel-runtime/helpers/possibleConstructorReturn';
import _inherits from 'babel-runtime/helpers/inherits';
var _class, _temp, _initialiseProps;
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { polyfill } from 'react-lifecycles-compat';
import { func, obj } from '../util';
import Icon from '../icon';
import Base from './base';
import Uploader from './runtime/index';
import html5Uploader from './runtime/html5-uploader';
import List from './list';
import { fileToObject, getFileItem, errorCode } from './util';
var noop = func.noop;
/**
* Upload
*/
var Upload = (_temp = _class = function (_Base) {
_inherits(Upload, _Base);
function Upload(props) {
_classCallCheck(this, Upload);
var _this = _possibleConstructorReturn(this, _Base.call(this, props));
_initialiseProps.call(_this);
var value = void 0;
if ('value' in props) {
value = props.value;
} else {
value = props.defaultValue;
}
_this.state = {
value: !Array.isArray(value) ? [] : value,
uploading: false
};
return _this;
}
Upload.getDerivedStateFromProps = function getDerivedStateFromProps(nextProps, prevState) {
// 上传中不允许做受控修改
if ('value' in nextProps && nextProps.value !== prevState.value && !prevState.uploading) {
return {
value: !Array.isArray(nextProps.value) ? [] : nextProps.value
};
}
return null;
};
/**
* 对外暴露API, 添加文件
* @param files
*/
Upload.prototype.selectFiles = function selectFiles(files) {
var filesArr = files.length ? Array.prototype.slice.call(files) : [files];
this.onSelect(filesArr);
};
Upload.prototype.uploadFiles = function uploadFiles(files) {
// NOTE: drag上传,当鼠标松开的时候回执行 onDrop,但此时onChange还没出发所以 value=[], 必须提前标识上传中
this.state.uploading = true;
var fileList = files.filter(function (file) {
if (file.state === 'selected') {
file.state = 'uploading';
return true;
}
return false;
}).map(function (file) {
return file.originFileObj;
});
fileList.length && this.uploaderRef.startUpload(fileList);
};
/**
* 对外暴露api,控制文件上传
*/
Upload.prototype.startUpload = function startUpload() {
this.uploadFiles(this.state.value);
};
Upload.prototype.replaceFiles = function replaceFiles(old, current) {
var targetItem = getFileItem(old, this.state.value);
if (!targetItem) {
return;
}
current.uid = old.uid;
targetItem.originFileObj = current;
};
// 替换掉队列里面的文件
Upload.prototype.isUploading = function isUploading() {
return this.state.uploading;
};
/**
* 删除文件
* @param {File} file
* @return {void}
*/
/**
* 取消上传
* @param {File} file
* @return {void}
*/
Upload.prototype.render = function render() {
var _classNames, _classNames2;
var _props = this.props,
listType = _props.listType,
prefix = _props.prefix,
dragable = _props.dragable,
shape = _props.shape,
className = _props.className,
style = _props.style,
useDataURL = _props.useDataURL,
disabled = _props.disabled,
limit = _props.limit,
closable = _props.closable,
beforeUpload = _props.beforeUpload,
readonly = _props.readonly,
onRemove = _props.onRemove,
onCancel = _props.onCancel,
onPreview = _props.onPreview,
list = _props.list,
extraRender = _props.extraRender,
progressProps = _props.progressProps,
rtl = _props.rtl,
isPreview = _props.isPreview,
renderPreview = _props.renderPreview,
name = _props.name,
_props$fileKeyName = _props.fileKeyName,
fileKeyName = _props$fileKeyName === undefined ? name : _props$fileKeyName,
fileNameRender = _props.fileNameRender,
actionRender = _props.actionRender,
previewOnFileName = _props.previewOnFileName,
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']);
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));
var isExceedLimit = this.state.value.length >= limit;
var innerCls = classNames((_classNames2 = {}, _classNames2[prefix + 'upload-inner'] = true, _classNames2[prefix + 'hidden'] = isExceedLimit, _classNames2));
var children = this.props.children;
if (shape === 'card') {
var _classNames3;
var cardCls = classNames((_classNames3 = {}, _classNames3[prefix + 'upload-card'] = true, _classNames3[prefix + 'disabled'] = disabled, _classNames3));
children = React.createElement(
'div',
{ className: cardCls },
React.createElement(Icon, { size: 'large', type: 'add', className: prefix + 'upload-add-icon' }),
React.createElement(
'div',
{ tabIndex: '0', role: 'button', className: prefix + 'upload-text' },
children
)
);
}
if (isPreview) {
if (typeof renderPreview === 'function') {
var _classNames4;
var previewCls = classNames((_classNames4 = {}, _classNames4[prefix + 'form-preview'] = true, _classNames4[className] = !!className, _classNames4));
return React.createElement(
'div',
{ style: style, className: previewCls },
renderPreview(this.state.value, this.props)
);
}
if (listType) {
return React.createElement(List, { isPreview: true, listType: listType, style: style, className: className, value: this.state.value });
}
return null;
}
// disabled 状态下把 remove函数替换成禁止 remove的函数
var onRemoveFunc = disabled ? func.prevent : onRemove;
var otherAttributes = obj.pickAttrsWith(this.props, 'data-');
return React.createElement(
'div',
_extends({ className: cls, style: style }, otherAttributes),
React.createElement(
Uploader,
_extends({}, others, {
name: fileKeyName,
beforeUpload: beforeUpload,
dragable: dragable,
disabled: disabled || isExceedLimit,
className: innerCls,
onSelect: this.onSelect,
onDrop: this.onDrop,
onProgress: this.onProgress,
onSuccess: this.onSuccess,
onError: this.onError,
ref: this.saveUploaderRef
}),
children
),
listType || list ? React.createElement(List, {
useDataURL: useDataURL,
fileNameRender: fileNameRender,
actionRender: actionRender,
uploader: this,
listType: listType,
value: this.state.value,
closable: closable,
onRemove: onRemoveFunc,
progressProps: progressProps,
onCancel: onCancel,
onPreview: onPreview,
extraRender: extraRender,
rtl: rtl,
previewOnFileName: previewOnFileName
}) : null
);
};
return Upload;
}(Base), _class.displayName = 'Upload', _class.propTypes = _extends({}, html5Uploader.propTypes, List.propTypes, {
/**
* 样式前缀
*/
prefix: PropTypes.string.isRequired,
/**
* 上传的地址
*/
action: PropTypes.string,
/**
* 文件列表
*/
value: PropTypes.array,
/**
* 默认文件列表
*/
defaultValue: PropTypes.array,
/**
* 上传按钮形状
*/
shape: PropTypes.oneOf(['card']),
/**
* 上传列表的样式
* @enumdesc 文字, 图文, 卡片
*/
listType: PropTypes.oneOf(['text', 'image', 'card']),
list: PropTypes.any,
/**
* 文件名字段
*/
name: PropTypes.string,
/**
* 上传额外传参
*/
data: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
/**
* 数据格式化函数,配合自定义 action 使用,参数为服务器的响应数据,详见 [formatter](#formater)
* @param {Object} response 返回
* @param {File} file 文件对象
*/
formatter: PropTypes.func,
/**
* 最大文件上传个数
*/
limit: PropTypes.number,
/**
* 设置上传超时,单位ms
*/
timeout: PropTypes.number,
/**
* 可选参数,是否支持拖拽上传,`ie10+` 支持。
*/
dragable: PropTypes.bool,
closable: PropTypes.bool,
/**
* 可选参数,是否本地预览
*/
useDataURL: PropTypes.bool,
/**
* 可选参数,是否禁用上传功能
*/
disabled: PropTypes.bool,
/**
* 选择文件回调
*/
onSelect: PropTypes.func,
/**
* 上传中
*/
onProgress: PropTypes.func,
/**
* 上传文件改变时的状态
* @param {Object} info 文件事件对象
*/
onChange: PropTypes.func,
/**
* 可选参数,上传成功回调函数,参数为请求下响应信息以及文件
* @param {Object} file 文件
* @param {Array<Object>} value 值
*/
onSuccess: PropTypes.func,
/**
* 可选参数, 用于校验文件,afterSelect仅在 autoUpload=false 的时候生效,autoUpload=true时,可以使用beforeUpload完全可以替代该功能.
* @param {Object} file
* @returns {Boolean} 返回false会阻止上传,其他则表示正常
*/
afterSelect: PropTypes.func,
/**
* 移除文件回调函数
* @param {Object} file 文件
* @returns {Boolean|Promise} 返回 false、Promise.resolve(false)、 Promise.reject() 将阻止文件删除
*/
onRemove: PropTypes.func,
/**
* 可选参数,上传失败回调函数,参数为上传失败的信息、响应信息以及文件
* @param {Object} file 出错的文件
* @param {Array} value 当前值
*/
onError: PropTypes.func,
/**
* 可选参数, 详见 [beforeUpload](#beforeUpload)
* @param {Object} file 所有文件
* @param {Object} options 参数
* @returns {Boolean|Object|Promise} 返回值作用见demo
*/
beforeUpload: PropTypes.func,
/**
* 放文件
*/
onDrop: PropTypes.func,
/**
* 自定义class
*/
className: PropTypes.string,
/**
* 自定义内联样式
*/
style: PropTypes.object,
/**
* 子元素
*/
children: PropTypes.node,
/**
* 自动上传
*/
autoUpload: PropTypes.bool,
/**
* 自定义上传方法
* @param {Object} option
* @return {Object} object with abort method
*/
request: PropTypes.func,
/**
* 透传给Progress props
*/
progressProps: PropTypes.object,
rtl: PropTypes.bool,
/**
* 是否为预览态
*/
isPreview: PropTypes.bool,
/**
* 预览态模式下渲染的内容
* @param {number} value 评分值
*/
renderPreview: PropTypes.func,
/**
* 文件对象的 key name
* @version 1.21
*/
fileKeyName: PropTypes.string,
/**
* list 的自定义文件名渲染
* @param {Object} file 文件
* @return {Node} react node
*/
fileNameRender: PropTypes.func,
/**
* 操作区域额外渲染
* @param {Object} file 文件
* @return {Node} react node
*/
actionRender: PropTypes.func,
/**
* 点击文件名时触发 onPreview
* @version 1.24
*/
previewOnFileName: PropTypes.bool
}), _class.defaultProps = _extends({}, html5Uploader.defaultProps, {
prefix: 'next-',
limit: Infinity,
autoUpload: true,
closable: true,
onSelect: noop,
onProgress: noop,
onChange: noop,
onSuccess: noop,
onRemove: noop,
onError: noop,
onDrop: noop,
beforeUpload: noop,
afterSelect: noop,
previewOnFileName: false
}), _initialiseProps = function _initialiseProps() {
var _this2 = this;
this.onSelect = function (files) {
var _props2 = _this2.props,
autoUpload = _props2.autoUpload,
afterSelect = _props2.afterSelect,
onSelect = _props2.onSelect,
limit = _props2.limit;
// 总数
var total = _this2.state.value.length + files.length;
// 差额
var less = limit - _this2.state.value.length;
if (less <= 0) {
// 差额不足 则不上传
return;
}
var fileList = files.map(function (file) {
var objFile = fileToObject(file);
objFile.state = 'selected';
return objFile;
});
// 默认全量上传
var uploadFiles = fileList;
var discardFiles = [];
if (total > limit) {
// 全量上传总数会超过limit 但是 还有差额
uploadFiles = fileList.slice(0, less);
discardFiles = fileList.slice(less);
}
var value = _this2.state.value.concat(fileList);
/* eslint-disable-next */
_this2.state.value = value;
if (autoUpload) {
_this2.uploadFiles(uploadFiles);
}
onSelect(uploadFiles, value);
discardFiles.forEach(function (file) {
// 丢弃的文件
var err = new Error(errorCode.EXCEED_LIMIT);
err.code = errorCode.EXCEED_LIMIT;
_this2.onError(err, null, file);
});
if (!autoUpload) {
uploadFiles.forEach(function (file) {
var isPassed = afterSelect(file);
func.promiseCall(isPassed, func.noop, function (error) {
_this2.onError(error, null, file); // TODO: handle error message
});
});
_this2.onChange(value, uploadFiles);
}
};
this.onDrop = function (files) {
_this2.onSelect(files);
_this2.props.onDrop(files);
};
this.replaceWithNewFile = function (old, current) {
var newFile = fileToObject(current);
newFile.state = 'selected';
var matchKey = old.uid !== undefined ? 'uid' : 'name';
var fileList = _this2.state.value;
for (var i = 0; i < fileList.length; i++) {
var item = fileList[i];
if (item[matchKey] === old[matchKey]) {
fileList.splice(i, 1, newFile);
break;
}
}
_this2.uploadFiles([newFile]);
return newFile;
};
this.onProgress = function (e, file) {
_this2.state.uploading = true;
var value = _this2.state.value;
var targetItem = getFileItem(file, value);
if (!targetItem) {
return;
}
_extends(targetItem, {
state: 'uploading',
percent: e.percent
});
_this2.setState({
value: value
});
_this2.props.onProgress(value, targetItem);
};
this.onSuccess = function (response, file) {
var formatter = _this2.props.formatter;
if (formatter) {
response = formatter(response, file);
}
try {
if (typeof response === 'string') {
response = JSON.parse(response);
}
} catch (e) {
e.code = errorCode.RESPONSE_FAIL;
return _this2.onError(e, response, file);
}
if (response.success === false) {
var err = new Error(response.message || errorCode.RESPONSE_FAIL);
err.code = errorCode.RESPONSE_FAIL;
return _this2.onError(err, response, file);
}
var value = _this2.state.value;
var targetItem = getFileItem(file, value);
if (!targetItem) {
return;
}
_extends(targetItem, {
state: 'done',
response: response,
url: response.url,
downloadURL: response.downloadURL || response.url // 下载地址(可选)
});
if (!_this2.props.useDataURL) {
targetItem.imgURL = response.imgURL || response.url; // 缩略图地址(可选)
}
_this2.updateUploadingState();
_this2.onChange(value, targetItem);
_this2.props.onSuccess(targetItem, value);
};
this.onError = function (err, response, file) {
var value = _this2.state.value;
var targetItem = getFileItem(file, value);
if (!targetItem) {
return;
}
_extends(targetItem, {
state: 'error',
error: err,
response: response
});
_this2.updateUploadingState();
_this2.onChange(value, targetItem);
_this2.props.onError(targetItem, value);
};
this.removeFile = function (file) {
file.state = 'removed';
_this2.uploaderRef.abort(file); // 删除组件时调用组件的 `abort` 方法中断上传
var fileList = _this2.state.value;
var targetItem = getFileItem(file, fileList);
var index = fileList.indexOf(targetItem);
if (index !== -1) {
fileList.splice(index, 1);
_this2.onChange(fileList, targetItem);
}
};
this.updateUploadingState = function () {
var inProgress = _this2.state.value.some(function (i) {
return i.state === 'uploading';
});
if (!inProgress) {
_this2.state.uploading = false;
}
};
this.abort = function (file) {
var fileList = _this2.state.value;
var targetItem = getFileItem(file, fileList);
var index = fileList.indexOf(targetItem);
if (index !== -1) {
fileList.splice(index, 1);
_this2.onChange(fileList, targetItem);
}
_this2.uploaderRef.abort(file); // 取消上传时调用组件的 `abort` 方法中断上传
};
this.onChange = function (value, file) {
_this2.setState({
value: value
});
_this2.props.onChange(value, file);
};
}, _temp);
export default polyfill(Upload);