git 10 years ago
parent 130bdf1b5e
commit f28c9f6e98

@ -0,0 +1,5 @@
{
"presets": ["es2015", "stage-2"],
"plugins": ["transform-runtime"],
"comments": false
}

@ -0,0 +1,97 @@
# vue-upload-component
> Vue.js file upload component, Support for multiple file uploads, progress, html4, ie9
**Html4 does not support the progress bar, file size, timeout, headers, response status code error of judgment**
## Install
``` bash
npm install vue-upload-component --save
```
## Demo
``` html
<div id="app">
<file-upload title="Add upload files"></file-upload>
</div>
<script type="text/javascript">
var FileUpload = require('./FileUpload.vue');
new Vue({
el:'#app',
components: {
FileUpload:FileUpload,
},
});
</script>
```
## Build Setup
``` bash
# install dependencies
npm install
# serve with hot reload at localhost:8080
npm run dev
# build for production with minification
npm run build
```
## Dispatch
addFileUpload
removeFileUpload
fileUploadProgress
beforeFileUpload
afterFileUpload
## Setting
### Data
``` json
{
files: [{
//
request: {
headers: {
"X-Csrf-Token": "xxxx",
},
data: {
"_csrf_token": "xxxxxx",
},
},
}],
// Global
request: {
headers: {
"X-Csrf-Token": "xxxx",
},
data: {
"_csrf_token": "xxxxxx",
},
},
}
```
### Props
``` html
<file-upload :title="Add upload files" :name="file" :action="./upload.php" :accept="accept" :multiple="multiple" :size="size" :timeout="3600000"></file-upload>
```

2
dist/build.js vendored

File diff suppressed because one or more lines are too long

1
dist/build.js.map vendored

File diff suppressed because one or more lines are too long

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>vue-upload-component</title>
<script src="http://cdnjs.cloudflare.com/ajax/libs/vue/1.0.24/vue.min.js"></script>
<style type="text/css">
.file-uploads-label span{
font-size: 18px;
padding: 1em;
font-weight: bold;
border: 1px solid #888;
}
</style>
</head>
<body>
<div id="app">
<table style="width: 100%; border: 1px solid #ddd;" v-if="$refs.upload && $refs.upload.files">
<thead>
<tr>
<th>Index</th>
<th>Name</th>
<th>Size</th>
<th>Progress</th>
<th>Active</th>
<th>Error</th>
<th>Errno</th>
<th>Success</th>
<th>Abort</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<tr v-for="file in $refs.upload.files">
<td>{{$index}}</td>
<td>{{file.name}}</td>
<td>{{file.size}}</td>
<td>{{file.progress}}</td>
<td>{{file.active}}</td>
<td>{{file.error}}</td>
<td>{{file.errno}}</td>
<td>{{file.success}}</td>
<td @click="file.active = false">Abort</td>
<td @click="remove(file)">x</td>
</tr>
</tbody>
</table>
<br/>
<br/>
<table style="width: 100%; border: 1px solid #ddd;" >
<tbody>
<tr>
<td>
<file-upload title="Add upload files" name="file" action="./upload.php" :accept="accept" :multiple="multiple ? 'true' : null" :size="size" v-ref:upload></file-upload>
</td>
<td>
<button v-if="$refs.upload && $refs.upload.active" type="submit" @click.prevent="$refs.upload.active = !$refs.upload.active">Stop upload</button>
<button v-else type="submit" @click.prevent="$refs.upload.active = !$refs.upload.active">Start upload</button>
</td>
<td>
Accept: <input type="text" v-model="accept">
</td>
<td>
Max file size: <input type="text" v-model="size">
</td>
<td>
Multiple: <input type="checkbox" id="checkbox" v-model="multiple">
</td>
<td>
Status: Active: {{$refs.upload.active}}, Uploaded: {{$refs.upload.uploaded}}
</td>
</tr>
</tbody>
</table>
</div>
<script src="./dist/build.js"></script>
</body>
</html>

@ -0,0 +1,50 @@
{
"name": "vue-upload-component",
"description": "Vue.js file upload component, Support for multiple file uploads, progress, html4, ie9",
"version": "0.1.0",
"author": "LianYue",
"scripts": {
"dev": "webpack-dev-server --inline --hot",
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
},
"main": "/src/FileUpload.vue",
"repository": {
"type": "git",
"url": "git+https://github.com/lian-yue/vue-upload-component.git"
},
"keywords": [
"Vue.js",
"File",
"Upload",
"Uploads",
"Multiple",
"Component"
],
"author": "LianYue",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/lian-yue/vue-upload-component/issues"
},
"homepage": "https://github.com/lian-yue/vue-upload-component#readme",
"dependencies": {
"babel-runtime": "^6.0.0"
},
"devDependencies": {
"babel-core": "^6.0.0",
"babel-loader": "^6.0.0",
"babel-plugin-transform-runtime": "^6.0.0",
"babel-preset-es2015": "^6.0.0",
"babel-preset-stage-2": "^6.0.0",
"cross-env": "^1.0.6",
"css-loader": "^0.23.0",
"file-loader": "^0.8.4",
"json-loader": "^0.5.4",
"url-loader": "^0.5.7",
"vue-hot-reload-api": "^1.2.0",
"vue-html-loader": "^1.0.0",
"vue-loader": "^8.2.1",
"vue-style-loader": "^1.0.0",
"webpack": "^1.12.2",
"webpack-dev-server": "^1.12.0"
}
}

@ -0,0 +1,508 @@
<template>
<div>
<label :for="id||name" :class="{'file-uploads-label': true, 'file-uploads-html5': $mode == 'html5', 'file-uploads-html4': $mode == 'html4'}">
<span>{{title}}</span>
<input-file></input-file>
</label>
</div>
</template>
<style>
.file-uploads-label {
overflow: hidden;
position: relative;
text-align: center;
}
.file-uploads-label span{
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
}
.file-uploads-label 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-label.file-uploads-html5 input{
width: 1px !important;
height: 1px !important;
top:-1px !important;
left:-1px !important;
right:auto !important;
bottom:auto !important;
}
</style>
<script>
export default {
props: {
title: {
type: String,
default: 'Upload file',
},
name: {
type: String,
required: true
},
id: {
type:String,
},
action: {
type: String,
required: true
},
accept: {
type:String,
},
multiple: {
type:String,
},
timeout: {
type: Number,
},
size: {
type: Number,
},
},
components: {
inputFile : {
template: '<input type="file" :name="$parent.name" :id="$parent.id||$parent.name" :accept="$parent.accept" @change="change" :multiple="$parent.multiple && $parent.$mode == \'html5\'">',
methods: {
change(e) {
this.$parent._addFileUploads(e.target);
this.$destroy();
},
},
},
},
data() {
return {
files: [],
active: false,
uploaded: true,
request: {
data: {},
headers: {},
},
}
},
init() {
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 = {};
},
watch: {
files(files) {
var ids = [];
for (var i = 0; i < files.length; i++) {
var file = files[i];
if (!file.errno && !file.success) {
this.uploaded = false;
}
ids.push(file.id);
}
for (var id in this._files) {
if (ids.indexOf(id) != -1) {
continue;
}
var file = this._files[id]._file;
file.removed = true;
var xhr = this._files[id].xhr;
if (xhr) {
xhr.abort();
xhr.timeout = 1;
}
var iframe = this._files[id].iframe;
if (iframe) {
iframe.onabort({type:'abort'});
}
delete this._files[id];
this.$dispatch('removeFileUpload', file, this);
}
this._index = 0;
},
active(newValue, oldValue) {
if (newValue && !oldValue) {
this._fileUploads();
}
},
uploaded(uploaded) {
if (uploaded) {
this.active = false;
}
},
},
methods: {
_addFileUploads(el) {
var Component = this.$options.components.inputFile;
new Component({
parent: this,
el: el,
});
this.uploaded = false;
if (el.files) {
for (var i = 0; i < el.files.length; i++) {
var file = el.files[i];
var id = 'upload-file-' + Math.random().toString(36).substr(2);
var value = {id: id, size:file.size, name: file.name, progress: '0.00', active: false, error: '', errno: '', success: false, data: {}, request: {headers:{}, data:{}}};
this._files[id] = {el:el, file: file};
var len;
if (this.multiple) {
len = this.files.push(value);
} else {
this.files = [value];
len = 1;
}
this._files[id]._file = this.files[len-1];
this.$dispatch('addFileUpload', this.files[len-1], this);
}
} else {
var id = 'upload-file-' + Math.random().toString(36).substr(2);
var value = {id: id, size: -1, name: el.value.replace(/^.*?([^\/\\\r\n]+)$/, '$1'), progress: '0.00', active: false, error: '', errno: '', success: false, data: {}, request: {headers:{}, data:{}}};
this._files[id] = {el:el};
var len;
if (this.multiple) {
len = this.files.push(value);
} else {
this.files = [value];
len = 1;
}
var len = this.files.push(file);
this._files[id]._file = this.files[len-1];
this.$dispatch('addFileUpload', this.files[len-1], this);
}
},
_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.errno) {
continue;
}
if (this.size && this.size > 0 && file.size >= 0 && file.size > this.size) {
file.error = 'Size';
file.errno = 'size';
continue;
}
if (this.$mode == 'html5') {
this._fileUploadHtml5(file);
} else {
this._fileUploadHtml4(file);
}
return;
}
this.active = false;
this.uploaded = true;
},
_fileUploadHtml5(file) {
var _self = this;
var file2 = this._files[file.id];
var fileUploads = false;
var form = new window.FormData();
form.append(this.name, file2.file);
for (var key in this.request.data) {
form.append(key, this.request.data[key]);
}
for (var key in file.request.data) {
form.append(key, file.request.data[key]);
}
var xhr = new XMLHttpRequest();
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);
}
_self.$dispatch('fileUploadProgress', file, _self);
};
var callback = function(e) {
switch (e.type) {
case 'timeout':
file.errno = 'timeout';
file.error = 'Timeout';
break;
case 'abort':
file.errno = 'abort';
file.error = 'Abort';
break;
case 'error':
if (!xhr.status) {
file.errno = 'network';
file.error = 'Network';
} else if(xhr.status >= 500) {
file.errno = 'server';
file.error = 'Server';
} else if (xhr.status >= 400) {
file.errno = 'denied';
file.error = 'Denied';
}
break;
default:
if(xhr.status >= 500) {
file.errno = 'server';
file.error = 'Server';
} else if (xhr.status >= 400) {
file.errno = 'denied';
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.data = JSON.parse(xhr.responseText);
} else {
file.data = xhr.responseText;
}
}
if (!fileUploads) {
fileUploads = true;
if (!file.removed) {
_self.$dispatch('afterFileUpload', file, _self);
}
setTimeout(function() {
_self._fileUploads();
}, 50);
}
};
xhr.onload = callback;
xhr.onerror = callback;
xhr.onabort = callback;
xhr.ontimeout = callback;
if (this.timeout) {
xhr.timeout = this.timeout;
}
xhr.open('POST', this.action);
for (var key in this.request.headers) {
xhr.setRequestHeader(key, this.request.headers[key]);
}
for (var key in file.request.headers) {
xhr.setRequestHeader(key, file.request.headers[key]);
}
xhr.send(form);
file.active = true;
file2.xhr = xhr;
var interval = setInterval(function() {
if (!_self.active || !file.active || file.success || file.errno) {
clearInterval(interval);
if (!file.success && !file.errno) {
xhr.abort();
}
}
}, 50);
this.$dispatch('beforeFileUpload', file, this);
},
_fileUploadHtml4(file) {
var _self = this;
var file2 = this._files[file.id];
var fileUploads = 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';
iframe.style.height = '1px';
iframe.style.top = '-9999px';
iframe.style.left = '-9999px';
iframe.style.position = 'absolute';
iframe.style.marginTop = '-9999em';
var form = document.createElement('form');
form.action = this.action;
form.name = 'upload-form-' + file.id;
form.setAttribute('method', 'POST');
form.setAttribute('target', 'upload-iframe-' + file.id);
form.setAttribute('enctype', 'multipart/form-data');
form.appendChild(file2.el);
for (var key in this.request.data) {
var input = document.createElement('input');
input.type = 'hidden';
input.name = key;
input.value = this.request.data[key];
form.appendChild(input);
}
for (var key in file.request.data) {
var input = document.createElement('input');
input.type = 'hidden';
input.name = key;
input.value = file.request.data[key];
form.appendChild(input);
}
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.errno = 'abort';
file.error = 'Abort';
break;
case 'error':
var data = getDocumentData();
if (file.errno) {
} else if (data === null) {
file.errno = 'network';
file.error = 'Network';
} else {
file.errno = 'denied';
file.error = 'Denied';
}
break;
default:
var data = getDocumentData();
if (file.errno) {
} else if (data === null) {
file.errno = 'network';
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 (!fileUploads) {
document.body.removeEventListener('keydown', keydown);
document.body.removeEventListener('keydown', keydown);
fileUploads = true;
iframe.parentNode && iframe.parentNode.removeChild(iframe);
if (!file.removed) {
_self.$dispatch('afterFileUpload', file, _self);
}
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;
file2.iframe = iframe;
document.body.addEventListener('keydown', keydown);
var interval = setInterval(function() {
if (!_self.active || !file.active || file.success || file.errno) {
clearInterval(interval);
if (!file.success && !file.errno) {
iframe.onabort({type:'abort'});
}
}
}, 50);
_self.$dispatch('beforeFileUpload', file, this);
}, 10);
},
}
}
</script>

@ -0,0 +1,28 @@
var FileUpload = require('./FileUpload.vue');
new Vue({
el:'#app',
components: {
FileUpload:FileUpload,
},
data: {
accept: 'image/*',
size: 1024 * 1024 * 10,
multiple: true,
},
compiled: function() {
this.$refs.upload.request = {
headers: {
"X-Csrf-Token": "xxxx",
},
data: {
"_csrf_token": "xxxxxx",
},
};
},
methods: {
remove: function(file) {
this.$refs.upload.files.$remove(file);
},
}
});

@ -0,0 +1,66 @@
var path = require('path')
var webpack = require('webpack')
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js'
},
resolveLoader: {
root: path.join(__dirname, 'node_modules'),
},
module: {
loaders: [
{
test: /\.vue$/,
loader: 'vue'
},
{
test: /\.js$/,
loader: 'babel',
exclude: /node_modules/
},
{
test: /\.json$/,
loader: 'json'
},
{
test: /\.html$/,
loader: 'vue-html'
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'url',
query: {
limit: 10000,
name: '[name].[ext]?[hash]'
}
}
]
},
devServer: {
historyApiFallback: true,
noInfo: true
},
devtool: '#eval-source-map'
}
if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map'
// http://vuejs.github.io/vue-loader/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}),
new webpack.optimize.OccurenceOrderPlugin()
])
}
Loading…
Cancel
Save