ant-design-vue/components/vc-upload/src/AjaxUploader.jsx

264 lines
7.0 KiB
Vue

import PropTypes from '../../_util/vue-types';
import BaseMixin from '../../_util/BaseMixin';
import partition from 'lodash/partition';
import classNames from 'classnames';
import defaultRequest from './request';
import getUid from './uid';
import attrAccept from './attr-accept';
import traverseFileTree from './traverseFileTree';
import { getListeners } from '../../_util/props-util';
const upLoadPropTypes = {
componentTag: PropTypes.string,
// style: PropTypes.object,
prefixCls: PropTypes.string,
name: PropTypes.string,
// className: PropTypes.string,
multiple: PropTypes.bool,
directory: PropTypes.bool,
disabled: PropTypes.bool,
accept: PropTypes.string,
// children: PropTypes.any,
// onStart: PropTypes.func,
data: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
action: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
headers: PropTypes.object,
beforeUpload: PropTypes.func,
customRequest: PropTypes.func,
// onProgress: PropTypes.func,
withCredentials: PropTypes.bool,
openFileDialogOnClick: PropTypes.bool,
transformFile: PropTypes.func,
};
const AjaxUploader = {
inheritAttrs: false,
name: 'ajaxUploader',
mixins: [BaseMixin],
props: upLoadPropTypes,
data() {
this.reqs = {};
return {
uid: getUid(),
};
},
mounted() {
this._isMounted = true;
},
beforeDestroy() {
this._isMounted = false;
this.abort();
},
methods: {
onChange(e) {
const files = e.target.files;
this.uploadFiles(files);
this.reset();
},
onClick() {
const el = this.$refs.fileInputRef;
if (!el) {
return;
}
el.click();
},
onKeyDown(e) {
if (e.key === 'Enter') {
this.onClick();
}
},
onFileDrop(e) {
const { multiple } = this.$props;
e.preventDefault();
if (e.type === 'dragover') {
return;
}
if (this.directory) {
traverseFileTree(e.dataTransfer.items, this.uploadFiles, _file =>
attrAccept(_file, this.accept),
);
} else {
let files = partition(Array.prototype.slice.call(e.dataTransfer.files), file =>
attrAccept(file, this.accept),
);
let successFiles = files[0];
const errorFiles = files[1];
if (multiple === false) {
successFiles = successFiles.slice(0, 1);
}
this.uploadFiles(successFiles);
if (errorFiles.length) {
this.$emit('reject', errorFiles);
}
}
},
uploadFiles(files) {
const postFiles = Array.prototype.slice.call(files);
postFiles
.map(file => {
file.uid = getUid();
return file;
})
.forEach(file => {
this.upload(file, postFiles);
});
},
upload(file, fileList) {
if (!this.beforeUpload) {
// always async in case use react state to keep fileList
return setTimeout(() => this.post(file), 0);
}
const before = this.beforeUpload(file, fileList);
if (before && before.then) {
before
.then(processedFile => {
const processedFileType = Object.prototype.toString.call(processedFile);
if (processedFileType === '[object File]' || processedFileType === '[object Blob]') {
return this.post(processedFile);
}
return this.post(file);
})
.catch(e => {
console && console.log(e); // eslint-disable-line
});
} else if (before !== false) {
setTimeout(() => this.post(file), 0);
}
},
post(file) {
if (!this._isMounted) {
return;
}
const { $props: props } = this;
let { data } = props;
const { transformFile = originFile => originFile } = props;
new Promise(resolve => {
const { action } = this;
if (typeof action === 'function') {
return resolve(action(file));
}
resolve(action);
}).then(action => {
const { uid } = file;
const request = this.customRequest || defaultRequest;
const transform = Promise.resolve(transformFile(file)).catch(e => {
console.error(e); // eslint-disable-line no-console
});
transform.then(transformedFile => {
if (typeof data === 'function') {
data = data(file);
}
const requestOption = {
action,
filename: this.name,
data,
file: transformedFile,
headers: this.headers,
withCredentials: this.withCredentials,
method: props.method || 'post',
onProgress: e => {
this.$emit('progress', e, file);
},
onSuccess: (ret, xhr) => {
delete this.reqs[uid];
this.$emit('success', ret, file, xhr);
},
onError: (err, ret) => {
delete this.reqs[uid];
this.$emit('error', err, ret, file);
},
};
this.reqs[uid] = request(requestOption);
this.$emit('start', file);
});
});
},
reset() {
this.setState({
uid: getUid(),
});
},
abort(file) {
const { reqs } = this;
if (file) {
let uid = file;
if (file && file.uid) {
uid = file.uid;
}
if (reqs[uid] && reqs[uid].abort) {
reqs[uid].abort();
}
delete reqs[uid];
} else {
Object.keys(reqs).forEach(uid => {
if (reqs[uid] && reqs[uid].abort) {
reqs[uid].abort();
}
delete reqs[uid];
});
}
},
},
render() {
const { $props, $attrs } = this;
const {
componentTag: Tag,
prefixCls,
disabled,
multiple,
accept,
directory,
openFileDialogOnClick,
} = $props;
const cls = classNames({
[prefixCls]: true,
[`${prefixCls}-disabled`]: disabled,
});
const events = disabled
? {}
: {
click: openFileDialogOnClick ? this.onClick : () => {},
keydown: openFileDialogOnClick ? this.onKeyDown : () => {},
drop: this.onFileDrop,
dragover: this.onFileDrop,
};
const tagProps = {
on: {
...getListeners(this),
...events,
},
attrs: {
role: 'button',
tabIndex: disabled ? null : '0',
},
class: cls,
};
return (
<Tag {...tagProps}>
<input
id={$attrs.id}
type="file"
ref="fileInputRef"
onClick={e => e.stopPropagation()} // https://github.com/ant-design/ant-design/issues/19948
key={this.uid}
style={{ display: 'none' }}
accept={accept}
directory={directory ? 'directory' : null}
webkitdirectory={directory ? 'webkitdirectory' : null}
multiple={multiple}
onChange={this.onChange}
/>
{this.$slots.default}
</Tag>
);
},
};
export default AjaxUploader;