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.
703 lines
17 KiB
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>
|