You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
vue-upload-component/src/FileUpload.vue

703 lines
17 KiB

<style>
.file-uploads {
overflow: hidden;
position: relative;
text-align: center;
}
.file-uploads span{
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
}
.file-uploads input{
z-index: 1;
opacity: 0;
font-size: 20em;
top: 0;
left: 0;
right: 0;
bottom: 0;
position: absolute;
width: 100%;
height: 100%;
}
.file-uploads.file-uploads-html5 input{
float: left;
width: 1px !important;
height: 1px !important;
top:-1px !important;
left:-1px !important;
right:auto !important;
bottom:auto !important;
}
</style>
<script>
import InputFile from './InputFile.vue';
export default {
components: {
InputFile,
},
props: {
title: {
type: String,
default: 'Upload file',
},
name: {
type: String,
default: 'file',
},
drop: {
default: false,
},
extensions: {
default: () => [],
},
postAction: {
type: String,
},
putAction: {
type: String,
},
accept: {
type:String,
},
multiple: {
type: Boolean,
},
timeout: {
type: Number,
default:0,
},
size: {
type: Number,
},
events: {
type: Object,
default: () => {},
},
headers: {
type: Object,
default: () => {},
},
data: {
type: Object,
default: () => {},
},
drop: {
type: Boolean,
default: false,
},
files: {
type: Array,
default: () => [],
},
},
data() {
return {
mode: 'html5',
active: false,
uploaded: true,
dropActive: false,
}
},
// 挂载后
mounted() {
var input = document.createElement('input');
input.type = 'file';
if (window.FormData && input.files) {
this.mode = 'html5';
} else {
this.mode = 'html4';
}
this._index = 0;
this._files = [];
this._dropActive = 0;
this._drop(this.drop)
this.$nextTick(() => {
this._drop(this.drop)
})
},
// 销毁前
beforeDestroy() {
this.active = false;
this.files.splice(0, this.files.length);
},
render (h) {
return (
<label class={{
'file-uploads': true,
'file-uploads-html5': this.mode == 'html5',
'file-uploads-html4': this.mode == 'html4'
}} >
<span>{this.title}</span>
<input-file></input-file>
</label>
)
},
watch: {
drop(value) {
this._drop(value);
},
files(files) {
var ids = [];
for (var i = 0; i < files.length; i++) {
let file = files[i];
if (!file.error && !file.success) {
this.uploaded = false;
}
ids.push(file.id);
}
for (var id in this._files) {
if (ids.indexOf(id) != -1) {
continue;
}
let file = this._files;
// 已移除的记录
file.removed = true;
// xhr abort
var xhr = file.xhr;
if (xhr) {
try {
xhr.abort();
xhr.timeout = 1;
} catch (e) {
}
}
// iframe abort
if (file.iframe) {
file.iframe.onabort({type:'abort'});
}
delete this._files[id];
this._uploadEvents('remove', file);
}
this._index = 0;
},
active(newValue, oldValue) {
if (newValue && !oldValue) {
this._fileUploads();
}
},
uploaded(uploaded) {
if (uploaded) {
this.active = false;
}
},
},
methods: {
clear() {
if (this.files.length) {
this.files.splice(0, this.files.length);
}
},
addFileUpload(file) {
this.uploaded = false;
var defaultFile = {
size: -1,
name: 'Filename',
progress: '0.00',
speed: 0,
active: false,
error: '',
success: false,
putAction: this.putAction,
postAction: this.postAction,
timeout: this.timeout,
data: Object.assign({}, this.data),
headers: Object.assign({}, this.headers),
response: {},
xhr: false,
iframe: false,
};
file = Object.assign(defaultFile, file)
if (!file.id) {
file.id = Math.random().toString(36).substr(2);
}
if (!this.multiple) {
this.clear();
}
file = this.files[this.files.push(file) - 1];
this._files[file.id] = file;
this._uploadEvents('add', file);
},
_uploadEvents(name, file) {
this.events && this.events[name] && this.events[name].call(this, file, this);
},
_drop(value) {
// 移除挂载
if (this.dropElement && this.mode === 'html5') {
try {
window.document.removeEventListener('dragenter', this._onDragenter, false);
window.document.removeEventListener('dragleave', this._onDragleave, false);
this.dropElement.removeEventListener('dragover', this._onDragover, false);
this.dropElement.removeEventListener('drop', this._onDrop, false);
} catch (e) {
}
}
if (!value) {
this.dropElement = false;
return;
}
if (typeof value == 'string') {
this.dropElement = document.querySelector(value) || this.$root.$el.querySelector(value);
} else if (typeof value == 'boolean') {
this.dropElement = this.$parent.$el;
} else {
this.dropElement = this.drop;
}
if (this.dropElement && this.mode === 'html5') {
window.document.addEventListener('dragenter', this._onDragenter, false);
window.document.addEventListener('dragleave', this._onDragleave, false);
this.dropElement.addEventListener('dragover', this._onDragover, false);
this.dropElement.addEventListener('drop', this._onDrop, false);
}
},
_onDragenter(e) {
this._dropActive++;
this.dropActive = !!this._dropActive;
e.preventDefault();
},
_onDragleave(e) {
e.preventDefault();
this._dropActive--;
if (e.target.nodeName == 'HTML' || (e.screenX == 0 && e.screenY == 0)) {
this.dropActive = !!this._dropActive;
}
},
_onDragover(e) {
e.preventDefault();
},
_onDrop(e) {
this._dropActive = 0;
this.dropActive = false;
e.preventDefault();
if (e.dataTransfer.files.length) {
for (let i = 0; i < e.dataTransfer.files.length; i++) {
let file = e.dataTransfer.files[i];
this.addFileUpload({file:file, size:file.size, name: file.name});
if (!this.multiple) {
break;
}
}
}
},
_addInputFileElement(el) {
if (el.files) {
for (let i = 0; i < el.files.length; i++) {
let file = el.files[i];
this.addFileUpload({size: file.size, name: file.name, file: file, el:el});
}
} else {
this.addFileUpload({name: el.value.replace(/^.*?([^\/\\\r\n]+)$/, '$1'), el:el});
}
var Component = this.$options.components.InputFile;
// vue 2.0.0 = Component
// vue 2.0.x = Component._Ctor
// vue 2.1.x = Component._Ctor[0]
if (!Component._Ctor) {
} else if (typeof Component._Ctor == 'function') { ///... 蠢死我 没加 typeof
Component = Component._Ctor
} else {
Component = Component._Ctor[0]
}
var inputFile = new Component({
parent: this,
el: el,
});
},
_fileUploads() {
if (!this.active) {
return;
}
for (; this._index < this.files.length; this._index++) {
var file = this.files[this._index];
if (file.active || file.success || file.error) {
continue;
}
if (this.size && this.size > 0 && file.size >= 0 && file.size > this.size) {
file.error = 'size';
continue;
}
if (this.extensions && (this.extensions.length || typeof this.extensions.length == 'undefined')) {
var extensions = this.extensions;
if (typeof extensions == 'object' && extensions instanceof RegExp) {
} else {
if (typeof extensions == 'string') {
extensions = extensions.split(',').map((value) => value.trim()).filter(value => value);
}
extensions = new RegExp('\\.('+ extensions.join('|').replace(/\./g, '\\.') +')$', 'i');
}
if (file.name.search(extensions) == -1) {
file.error = 'extension';
continue;
}
}
if (this.mode == 'html5') {
if (file.putAction) {
this._fileUploadPut(file);
} else if (file.postAction) {
this._fileUploadHtml5(file);
} else {
file.error = 'not_support';
continue;
}
} else {
if (file.postAction) {
this._fileUploadHtml4(file);
} else {
file.error = 'not_support';
continue;
}
}
return;
}
this.active = false;
this.uploaded = true;
},
_fileUploadXhr(xhr, file, data) {
var _self = this;
var complete = false;
var speedTime = 0;
var speedLoaded = 0;
xhr.upload.onprogress = function(e) {
// 已 移除
if (file.removed) {
xhr.abort();
return;
}
// 终止
if (!_self.active || !file.active) {
xhr.abort();
return;
}
// 进度
if (e.lengthComputable) {
file.progress = (e.loaded / e.total * 100).toFixed(2);
var speedTime2 = Math.round(Date.now() / 1000);
if (speedTime2 != speedTime) {
file.speed = e.loaded - speedLoaded;
speedLoaded = e.loaded;
speedTime = speedTime2;
}
}
_self._uploadEvents('progress', file);
}
var callback = function(e) {
switch (e.type) {
case 'timeout':
file.error = 'timeout';
break;
case 'abort':
file.error = 'abort';
break;
case 'error':
if (!xhr.status) {
file.error = 'network';
} else if(xhr.status >= 500) {
file.error = 'server';
} else if (xhr.status >= 400) {
file.error = 'denied';
}
break;
default:
if(xhr.status >= 500) {
file.error = 'server';
} else if (xhr.status >= 400) {
file.error = 'denied';
} else {
file.progress = '100.00';
file.success = true;
}
}
file.active = false;
if (xhr.responseText) {
var contentType = xhr.getResponseHeader('Content-Type');
if (contentType && contentType.indexOf('/json') != -1) {
file.response = JSON.parse(xhr.responseText);
} else {
file.response = xhr.responseText;
}
}
if (!complete) {
complete = true;
if (!file.removed) {
_self._uploadEvents('after', file);
}
setTimeout(function() {
_self._fileUploads();
}, 50);
}
};
// 事件
xhr.onload = callback;
xhr.onerror = callback;
xhr.onabort = callback;
xhr.ontimeout = callback;
// 超时
if (file.timeout) {
xhr.timeout = file.timeout;
}
// headers
// xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
for (let key in file.headers) {
xhr.setRequestHeader(key, file.headers[key]);
}
// 开始上传
xhr.send(data);
file.active = true;
file.xhr = xhr
// 定时执行检测
var interval = setInterval(function() {
if (!_self.active || !file.active || file.success || file.error) {
clearInterval(interval);
if (!file.success && !file.error) {
xhr.abort();
}
}
}, 100);
// 开始上传
this._uploadEvents('before', file);
},
_fileUploadPut(file) {
var querys = Object.assign({}, file.data)
var queryArray = [];
for (let key in querys) {
if (querys[key] !== null && typeof querys[key] !== 'undefined') {
queryArray.push(encodeURIComponent(key) + '=' + encodeURIComponent(querys[key]));
}
}
var queryString = queryArray.length ? (file.putAction.indexOf('?') == -1 ? '?' : '&') + queryArray.join('&') : '';
var xhr = new XMLHttpRequest();
xhr.open('PUT', file.putAction + queryString);
this._fileUploadXhr(xhr, file, file.file);
},
_fileUploadHtml5(file) {
var form = new window.FormData();
for (var key in file.data) {
form.append(key, file.data[key]);
}
form.append(this.name, file.file);
var xhr = new XMLHttpRequest();
xhr.open('POST', file.postAction);
this._fileUploadXhr(xhr, file, form);
},
_fileUploadHtml4(file) {
var _self = this;
var complete = false;
var keydown = function(e) {
if (e.keyCode == 27) {
e.preventDefault();
}
}
var iframe = document.createElement('iframe');
iframe.id = 'upload-iframe-' + file.id;
iframe.name = 'upload-iframe-' + file.id;
iframe.src = 'about:blank';
iframe.style = {
width: '1px',
height: '1px',
top: '-9999px',
left: '-9999px',
position: 'absolute',
marginTop: '-9999em',
}
var form = document.createElement('form');
form.action = file.postAction;
form.name = 'upload-form-' + file.id;
form.setAttribute('method', 'POST');
form.setAttribute('target', 'upload-iframe-' + file.id);
form.setAttribute('enctype', 'multipart/form-data');
for (let key in file.data) {
let input = document.createElement('input');
input.type = 'hidden';
input.name = key;
input.value = file[key];
form.appendChild(input);
}
form.appendChild(file.el);
var getDocumentData = function() {
var doc;
try {
if (iframe.contentWindow) {
doc = iframe.contentWindow.document;
}
} catch(err) {
}
if (!doc) {
try {
doc = iframe.contentDocument ? iframe.contentDocument : iframe.document;
} catch(err) {
doc = iframe.document;
}
}
if (doc && doc.body) {
return doc.body.innerHTML;
}
return null;
}
var callback = function(e) {
switch (e.type) {
case 'abort':
file.error = 'abort';
break;
case 'error':
var data = getDocumentData();
if (file.error) {
} else if (data === null) {
file.error = 'network';
} else {
file.error = 'denied';
}
break;
default:
var data = getDocumentData();
if (file.error) {
} else if (data === null) {
file.error = 'network';
} else {
file.progress = '100.00';
file.success = true;
}
}
file.active = false;
if (typeof data != "undefined") {
if (data && data.substr(0, 1) == '{' && data.substr(data.length - 1, 1) == '}') {
try {
data = JSON.parse(data);
} catch (err) {
}
}
file.data = data;
}
if (!complete) {
complete = true;
document.body.removeEventListener('keydown', keydown);
document.body.removeEventListener('keydown', keydown);
iframe.parentNode && iframe.parentNode.removeChild(iframe);
if (!file.removed) {
_self._uploadEvents('after', file);
}
setTimeout(function() {
_self._fileUploads();
}, 50);
}
};
setTimeout(function() {
document.body.appendChild(iframe).appendChild(form).submit();
iframe.onload = callback;
iframe.onerror = callback;
iframe.onabort = callback;
file.active = true;
file.iframe = iframe;
document.body.addEventListener('keydown', keydown);
var interval = setInterval(function() {
if (!_self.active || !file.active || file.success || file.error) {
clearInterval(interval);
if (!file.success && !file.error) {
iframe.onabort({type:'abort'});
}
}
}, 50);
_self._uploadEvents('before', file);
}, 10);
},
}
}
</script>