Compare commits

..

6 Commits
master ... 1.0

Author SHA1 Message Date
git f1f9732a27 fix #29
9 years ago
git 03278cb454 update Vue 2.x link
9 years ago
git e9eb18d590 add Vue v2.0.0
9 years ago
git 9f28b9c901 fix #10 title to interpolate html
9 years ago
LianYue 8b11eb56f2 Merge pull request #10 from alexeyMohnatkin/fix-title
9 years ago
dizzy fb9e5b4b97 fix title to interpolate html
9 years ago

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

@ -1,2 +0,0 @@
node_modules/*
dist/*

@ -1,39 +0,0 @@
// http://eslint.org/docs/user-guide/configuring
module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module'
},
env: {
browser: true,
jest: true
},
// https://github.com/standard/standard/blob/master/docs/RULES-en.md
extends: "standard",
// required to lint *.vue files
plugins: [
'html'
],
// add your custom rules here
'rules': {
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await
'generator-star-spacing': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'no-control-regex': 0,
'no-useless-escape': 0,
'comma-dangle': 0,
'space-before-function-paren': 0,
'no-multiple-empty-lines': 0,
'no-multi-spaces': 0,
'padded-blocks': 0,
'prefer-promise-reject-errors': 0,
'operator-linebreak': 0
}
}

1
.gitignore vendored

@ -1,4 +1,3 @@
.DS_Store
node_modules/
npm-debug.log
.idea

@ -1,75 +1,174 @@
# vue-upload-component
[![npm](https://img.shields.io/npm/dm/vue-upload-component.svg?style=flat-square)](https://www.npmjs.com/package/vue-upload-component) [![npm](https://img.shields.io/npm/v/vue-upload-component.svg?style=flat-square)](https://www.npmjs.com/package/vue-upload-component) [![license](https://img.shields.io/github/license/lian-yue/vue-upload-component.svg?style=flat-square)](https://www.npmjs.com/package/vue-upload-component) [![gzip](http://img.badgesize.io/lian-yue/vue-upload-component/master/dist/vue-upload-component.min.js.svg?compression=gzip&style=flat-square)](https://github.com/lian-yue/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, accept, timeout, headers, response status code error of judgment**
> Vue.js file upload component
> The component is just a button
- [x] Multi-file upload
- [x] Upload directory
- [x] Drag upload
- [x] Drag the directory
- [x] Upload multiple files at the same time
- [x] html4 (IE 9)
- [x] `PUT` method
- [x] Customize the filter
- [x] thumbnails
- [x] Chunk upload
## Install
``` bash
npm install vue-upload-component --save
```
### Vue 2.x
https://github.com/lian-yue/vue-upload-component/tree/2.0
# Example
``` bash
npm install vue-upload-component@next --save
```
### CommonJS
```js
var FileUpload = require('vue-upload-component');
new Vue({
template: '<file-upload post-action="/post.method" put-action="/put.method"></file-upload>',
components: {
FileUpload: FileUpload
}
})
```
### ES6
```js
import FileUpload from 'vue-upload-component'
new Vue({
template: '<file-upload post-action="/post.method" put-action="/put.method"></file-upload>',
components: {
FileUpload
}
})
```
## Example
https://lian-yue.github.io/vue-upload-component/
# Installation
``` html
<!-- Example file ./index.html -->
<!-- Example file ./src/example.js -->
<div id="app">
<file-upload title="Add upload files"></file-upload>
</div>
<script type="text/javascript">
var FileUpload = require('vue-upload-component');
new Vue({
el:'#app',
components: {
FileUpload:FileUpload,
},
});
</script>
```
## Build Setup
``` bash
npm install vue-upload-component --save
# install dependencies
npm install
# serve with hot reload at localhost:8080
npm run dev
# build for production with minification
npm run build
```
# Documentation
https://lian-yue.github.io/vue-upload-component/#/documents
## $dispatch, methods
addFileUpload
removeFileUpload
fileUploadProgress
beforeFileUpload
afterFileUpload
> Vue.js 文件上传组建
> 组件只是一个按钮
- [x] 上传多文件
- [x] 上传目录
- [x] 拖拽
- [x] 拖拽目录
- [x] 多线程
- [x] html4(IE 9)
- [x] `PUT` 方法
- [x] 自定义过滤器
- [x] 缩略图
## Setting
# 演示
### Data
``` js
{
files: [
{
id: 'String', // Read only
name: 'filename String',
size: 'filesize Number',
progress: 'progress String', // Read only
speed: "Speed Number", // Read only
active: 'active Boolean',
error: 'error String',
errno: 'errno String',
success: 'success Boolean', // Read only
data: 'Response data Object or String', // Read only
request: {
headers: {
"X-Csrf-Token": "xxxx",
},
data: {
"_csrf_token": "xxxxxx",
},
},
}
],
https://lian-yue.github.io/vue-upload-component/#/zh-cn/
// Global
request: {
headers: {
"X-Csrf-Token": "xxxx",
},
data: {
"_csrf_token": "xxxxxx",
},
},
# 安装
active: false,
uploaded: true, // Read only
dropActive: false, // Read only
}
```
### Props
``` html
<file-upload :title="Add upload files" :name="file" :drop="Boolean (true = $parent) or Element or Css Selector" :extensions="Array or String or Regular" :post-action="./post.method" :put-action="./put.method" :accept="accept" :multiple="true" :size="size" :timeout="3600000"></file-upload>
```
``` bash
npm install vue-upload-component --save
```
title="Add upload files"
name="post file name"
# 文档
drop="Boolean (true = $parent) or Element or Css Selector"
https://lian-yue.github.io/vue-upload-component/#/zh-cn/documents
extensions="Array or String or Regular" :post-action="./post.method"
post-action="./post.method"
put-action="./put.method"
# Special thanks (特别感谢)
accept="accept"
- [@josec89](https://github.com/josec89)
multiple="true or false"
size="max Size"
timeout="3600000"
```

12647
dist/example.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,7 +0,0 @@
.file-uploads{overflow:hidden;position:relative;text-align:center;display:inline-block
}
.file-uploads.file-uploads-html4 input,.file-uploads.file-uploads-html5 label{background:#fff;opacity:0;font-size:20em;z-index:1;top:0;left:0;right:0;bottom:0;position:absolute;width:100%;height:100%
}
.file-uploads.file-uploads-html4 label,.file-uploads.file-uploads-html5 input{background:rgba(255,255,255,0);overflow:hidden;position:fixed;width:1px;height:1px;z-index:-1;opacity:0
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -1,31 +0,0 @@
<template>
<file-upload
name="file"
post-action="/post"
put-action="/put"
:value="files"
@input="input"
ref="upload">
Add upload files
</file-upload>
</template>
<script>
import FileUpload from '../src'
import { mapState } from 'vuex'
export default {
components: {
FileUpload,
},
computed: mapState({
files: state => state.files
}),
methods: {
// Files Event
input(files) {
this.$store.commit('updateFiles', files)
},
},
}
</script>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,31 +0,0 @@
export default {
header: {
logo: 'Vuejs',
home: 'Home',
examples: 'Examples',
documents: 'Documentation',
blog: 'Blog',
locale: 'Language(语言)',
issues: 'Issues',
github: 'Github',
},
locale: {
en: 'English',
'zh-cn': '中文(简体)',
},
document: {
title: 'Documentation',
},
example: {
full: 'Full Example',
simple: 'Simple',
avatar: 'Upload avatar',
drag: 'Drag and drop',
multiple: 'Multiple instances',
chunk: 'Chunk upload',
vuex: 'Vuex',
}
}

@ -1,14 +0,0 @@
// import Vue from 'vue'
import VueI18n from 'vue-i18n'
import en from './en'
import zhCN from './zh-cn'
// Vue.use(VueI18n)
export default new VueI18n({
locale: 'en',
messages: {
'zh-cn': zhCN,
en,
}
})

@ -1,31 +0,0 @@
export default {
header: {
logo: 'Vuejs',
home: '首页',
examples: '演示',
documents: '文档',
blog: 'Blog',
locale: 'Language(语言)',
issues: 'Issues',
github: 'Github',
},
locale: {
en: 'English',
'zh-cn': '中文(简体)',
},
document: {
title: '文档',
},
example: {
full: '完整例子',
simple: '简单例子',
avatar: '上传头像',
drag: '拖拽上传',
multiple: '多个实例',
vuex: 'Vuex',
}
}

@ -1,30 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="Cache-Control" content="no-siteapp">
<meta http-equiv="Cache-Control" content="no-transform">
<meta http-equiv="X-UA-Compatible" content="IE=Edge, chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue-upload-component- Upload Component - Uploader</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0-beta/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/font-awesome/css/font-awesome.min.css" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/styles/atom-one-light.min.css" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/cropperjs/dist/cropper.css" rel="stylesheet" />
</head>
<body>
<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/es6-promise/dist/es6-promise.auto.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuex/dist/vuex.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-router/dist/vue-router.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-i18n/dist/vue-i18n.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/lib/marked.js"></script>
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/highlight.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/xkeshi/image-compressor/dist/image-compressor.js"></script>
<script src="https://cdn.jsdelivr.net/npm/cropperjs/dist/cropper.js"></script>
<script src="./dist/index.js"></script>
</body>
</html>

@ -1,136 +0,0 @@
import Vue from 'vue'
import marked from 'marked'
import highlightjs from 'highlight.js'
import store from './store'
import router from './router'
import i18n from './i18n'
import App from './views/App'
Vue.config.silent = false
Vue.config.devtools = true
class Renderer extends marked.Renderer {
heading(text, level, raw) {
let rawName = raw.toLowerCase().replace(/([\u0000-\u002F\u003A-\u0060\u007B-\u007F]+)/g, '-').replace(/^\-+|\-+$/, '')
if (!this.options.headers) {
this.options.headers = []
}
while (this.options.headers.length >= level) {
this.options.headers.pop()
}
let parent = this.options.headers.filter(value => !!value).join('-')
if (parent) {
parent = parent + '-'
}
while (this.options.headers.length < (level - 1)) {
this.options.headers.push('')
}
this.options.headers.push(rawName)
return '<h'
+ level
+ ' id="'
+ this.options.headerPrefix
+ parent
+ rawName
+ '">'
+ text
+ '</h'
+ level
+ '>\n'
}
}
marked.setOptions({
renderer: new Renderer(),
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: false,
smartLists: true,
smartypants: false,
highlight(code, lang) {
if (lang) {
return highlightjs.highlight(lang, code).value
} else {
return highlightjs.highlightAuto(code).value
}
}
})
Vue.directive('markdown', function (el, binding, vnode) {
if (!el.className || !/vue-markdown/.test(el.className)) {
el.className += ' vue-markdown'
}
let text = ''
for (let i = 0; i < vnode.children.length; i++) {
text += vnode.children[i].text || ''
}
if (el.markdown === text) {
return
}
el.markdown = text
el.innerHTML = marked(text)
let selectorList = el.querySelectorAll('a')
for (let i = 0; i < selectorList.length; i++) {
selectorList[i].onclick = function (e) {
if (e.metaKey || e.ctrlKey || e.shiftKey) {
return
}
if (e.defaultPrevented) {
return
}
if (e.button !== undefined && e.button !== 0) {
return
}
if (this.host !== window.location.host) {
return
}
let href = this.getAttribute('href')
if (!href) {
return
}
if (href.charAt(0) !== '#') {
return
}
e.preventDefault()
router.push(href)
}
}
})
Vue.filter('formatSize', function (size) {
if (size > 1024 * 1024 * 1024 * 1024) {
return (size / 1024 / 1024 / 1024 / 1024).toFixed(2) + ' TB'
} else if (size > 1024 * 1024 * 1024) {
return (size / 1024 / 1024 / 1024).toFixed(2) + ' GB'
} else if (size > 1024 * 1024) {
return (size / 1024 / 1024).toFixed(2) + ' MB'
} else if (size > 1024) {
return (size / 1024).toFixed(2) + ' KB'
}
return size.toString() + ' B'
})
Vue.filter('toLocale', function (to) {
return '/' + i18n.locale + to
})
new Vue({
store,
router,
i18n,
...App
}).$mount('#app')

@ -1,95 +0,0 @@
// import Vue from 'vue'
import VueRouter from 'vue-router'
import i18n from './i18n'
import RouterComponent from './views/Router'
import DocumentComponent from './views/Document'
import ExampleComponent from './views/Example'
import FullExampleComponent from './views/examples/Full'
import SimpleExampleComponent from './views/examples/Simple'
import AvatarExampleComponent from './views/examples/Avatar'
import DragExampleComponent from './views/examples/Drag'
import MultipleExampleComponent from './views/examples/Multiple'
import ChunkExampleComponent from './views/examples/Chunk'
import VuexExampleComponent from './views/examples/Vuex'
// Vue.use(VueRouter)
let examples = [
{
path: '',
component: FullExampleComponent,
},
{
path: 'full',
component: FullExampleComponent,
},
{
path: 'simple',
component: SimpleExampleComponent,
},
{
path: 'avatar',
component: AvatarExampleComponent,
},
{
path: 'drag',
component: DragExampleComponent,
},
{
path: 'multiple',
component: MultipleExampleComponent,
},
{
path: 'chunk',
component: ChunkExampleComponent,
},
{
path: 'vuex',
component: VuexExampleComponent,
},
]
const router = new VueRouter({
mode: 'hash',
fallback: false,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else if (to.hash) {
let el = document.querySelector(to.hash)
return { x: 0, y: el ? el.offsetTop : 0 }
} else {
return { x: 0, y: 0 }
}
},
routes: [
{
path: '/:locale(' + Object.keys(i18n.messages).join('|') + ')?',
component: RouterComponent,
children: [
{
path: 'documents',
component: DocumentComponent,
},
{
path: 'examples',
component: ExampleComponent,
children: examples,
},
{
path: '',
component: ExampleComponent,
children: examples,
},
]
},
]
})
export default router

@ -1,20 +0,0 @@
// import Vue from 'vue'
import Vuex from 'vuex'
//
// Vue.use(Vuex)
const state = {
files: [],
}
const mutations = {
updateFiles(state, files) {
state.files = files
}
}
export default new Vuex.Store({
strict: true,
state,
mutations
})

@ -1,192 +0,0 @@
<template>
<div>
<header id="header" class="navbar navbar-expand-lg navbar-dark bg-dark">
<router-link :exact="true" class="navbar-brand" :to="'/' | toLocale">{{$t('header.logo')}}</router-link>
<button class="navbar-toggler" type="button" @click.prevent="showNav = !showNav">
<span class="navbar-toggler-icon"></span>
</button>
<nav :class="{collapse: true, 'navbar-collapse': true, show: showNav}" id="navbar">
<ul class="navbar-nav">
<li class="nav-item">
<router-link active-class="active" :exact="true" :class="'nav-link' + ($route.path === '/' ? ' active' : '')" :to="'/' | toLocale">{{$t('header.home')}}</router-link>
</li>
<li class="nav-item">
<router-link active-class="active" class="nav-link" :to="'/documents' | toLocale">{{$t('header.documents')}}</router-link>
</li>
<li class="nav-item">
<router-link active-class="active" class="nav-link" :to="'/examples' | toLocale">{{$t('header.examples')}}</router-link>
</li>
<li class="nav-item">
<a rel="license noopener" class="nav-link" href="https://www.lianyue.org" target="_blank">{{$t('header.blog')}}</a>
</li>
</ul>
<ul class="navbar-nav ml-md-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" @click.prevent="onLocale(true)" @focus="onLocale(true)" @blur="onLocale(false)">
{{$t('header.locale')}}
</a>
<div :class="{'dropdown-menu': true, show: showLocale}" @blur="onLocale(false)">
<router-link class="dropdown-item" :to="'/' + name + ($route.params.locale ? $route.fullPath.substr($route.params.locale.length + 1) : $route.fullPath)" v-for="(value, name) in locale" :key="name">{{value}}</router-link>
</div>
</li>
<li class="nav-item">
<a class="nav-link" href="https://github.com/lian-yue/vue-upload-component/issues">
{{$t('header.issues')}}
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://github.com/lian-yue/vue-upload-component">
{{$t('header.github')}}
</a>
</li>
</ul>
</nav>
</header>
<router-view></router-view>
</div>
</template>
<style>
#header {
position: -webkit-sticky;
position: sticky;
top: 0;
z-index: 1071;
}
#sidebar {
background: #fff;
border-right: 1px solid #e5e5e5;
border-bottom: 1px solid #e5e5e5;
}
@media (min-width: 768px) {
#sidebar {
position: -webkit-sticky;
position: sticky;
top: 3.5rem;
z-index: 1000;
max-height: calc(100vh - 3.5rem);
border-right: 1px solid #e5e5e5;
border-bottom: 1px solid #e5e5e5;
}
}
#sidebar-nav {
padding-top: 1rem;
padding-bottom: 1rem;
margin-right: -15px;
margin-left: -15px;
max-height: 100%;
overflow-y: auto;
}
#sidebar-nav .nav {
display: block;
}
#sidebar-nav .nav .nav-item .nav {
display: none;
margin-bottom: 1rem;
}
#sidebar-nav .nav .nav-item .nav {
display: none;
margin-bottom: 1rem;
}
#sidebar-nav .nav .nav-item.active .nav, #sidebar-nav .nav .active + .nav {
display: block;
}
@media (min-width: 768px) {
#sidebar-nav .nav .nav-item .nav {
display: block;
}
}
#sidebar-nav .nav .nav-link.active, #sidebar-nav .nav .active > .nav-link{
color: #262626;
font-weight: 500;
}
#sidebar-nav .nav-item .nav-link {
padding: .25rem 1rem;
font-weight: 500;
color: #666
}
#sidebar-nav .nav-item .nav-item .nav-link {
font-weight: 400;
font-size: 85%;
margin-left: 1rem
}
#main {
padding-top: 1rem;
margin-bottom: 2rem
}
blockquote {
margin-bottom: 1rem;
font-size: 1.25rem;
padding: 0 1em;
color: #6a737d;
border-left: 0.25em solid #dfe2e5;
}
pre {
padding: 16px;
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: #f6f8fa;
border-radius: 3px;
}
.modal-backdrop.fade {
visibility: hidden;
}
.modal-backdrop.fade.show {
visibility: visible;
}
.fade.show {
display: block;
z-index: 1072;
}
</style>
<script>
export default {
data() {
return {
showLocale: false,
showNav: false,
}
},
beforeCreate() {
if (this.$route.params.locale && this.$route.params.locale !== this.$i18n.locale) {
this.$i18n.locale = this.$route.params.locale
}
},
beforeUpdate() {
if (this.$route.params.locale && this.$route.params.locale !== this.$i18n.locale) {
this.$i18n.locale = this.$route.params.locale
}
},
computed: {
locale() {
let i18n = this.$i18n
return i18n.messages[i18n.locale].locale
},
},
methods: {
onLocale(show) {
if (show) {
this.showLocale = show
} else {
setTimeout(() => {
this.showLocale = show
}, 128)
}
}
}
}
</script>

@ -1,115 +0,0 @@
<template>
<div class="container-fluid">
<div class="row flex-xl-nowrap">
<div class="col-12 col-md-3 col-xl-2" id="sidebar">
<nav id="sidebar-nav" class="collapse show">
<ul class="nav">
<li :class="{'nav-item': true, active: (!$route.hash && !index) || $route.hash.indexOf(group.hash) === 1}" v-for="(group, index) in navs">
<router-link active-class="active" :class="{'nav-link': true, active: $route.hash.indexOf(group.hash) === 1}" :to="'#' + group.hash">{{group.name}}</router-link>
<ul class="nav" v-if="group.children.length">
<li class="nav-item" v-for="child in group.children">
<router-link active-class="active" class="nav-link" :to="'#' + group.hash + '-' + child.hash">{{child.name}}</router-link>
</li>
</ul>
</li>
</ul>
</nav>
</div>
<main class="col-12 col-md-9 col-xl-10 py-md-3 pr-md-5 pl-md-5" id="main" role="main">
<h1 class="document-title" id="document-title">{{$t('document.title')}}</h1>
<div class="document-content" v-markdown>{{document}}</div>
</main>
</div>
</div>
</div>
</template>
<style>
.document-title {
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid #ddd;
}
.document-content h2 {
padding-top: 1rem;
padding-bottom: 1rem;
margin-top: 4rem;
border-bottom: 1px solid #eaecef;
}
.document-content h2:first-child {
margin-top: 0;
}
.document-content h2 + h3 {
margin-top: 0rem;
}
.document-content h3 {
margin-top: 1.5rem;
padding-top: 1rem;
margin-bottom: 1rem;
}
</style>
<script>
import marked from 'marked'
export default {
mounted() {
// auto scrollTo hash
if (this.$route.hash) {
let el = document.querySelector(decodeURIComponent(this.$route.hash))
if (el) {
window.scrollTo(0, el.offsetTop)
}
}
},
computed: {
document() {
return require('../docs/' + this.$i18n.locale)
},
navs() {
let tokens = marked.lexer(this.document)
let rootNode = {
name: '',
children: [],
level: 1,
}
let parent = rootNode
let navPrev
for (let i = 0; i < tokens.length; i++) {
let token = tokens[i]
if (token.type !== 'heading') {
continue
}
let nav = {
name: token.text,
hash: token.text.toLowerCase().replace(/([\u0000-\u002F\u003A-\u0060\u007B-\u007F]+)/g, '-').replace(/^\-+|\-+$/, ''),
level: token.depth,
children: [],
}
if (!navPrev || nav.level === navPrev.level) {
// empty
} else if (nav.level > navPrev.level) {
// next
parent = navPrev
} else {
while (nav.level < navPrev.level && navPrev.parent) {
navPrev = navPrev.parent
parent = navPrev.parent
}
}
nav.parent = parent
parent.children.push(nav)
navPrev = nav
}
return rootNode.children
},
},
}
</script>

@ -1,34 +0,0 @@
<template>
<div class="container-fluid">
<div class="row flex-xl-nowrap">
<div class="col-12 col-md-3 col-xl-2" id="sidebar">
<nav id="sidebar-nav" class="collapse show">
<ul class="nav">
<li class="nav-item">
<router-link active-class="active" class="nav-link" :to="'/examples/full' | toLocale">{{$t('example.full')}}</router-link>
</li>
<li class="nav-item">
<router-link active-class="active" class="nav-link" :to="'/examples/simple' | toLocale">{{$t('example.simple')}}</router-link>
</li>
<li class="nav-item">
<router-link active-class="active" class="nav-link" :to="'/examples/avatar' | toLocale">{{$t('example.avatar')}}</router-link>
</li>
<li class="nav-item">
<router-link active-class="active" class="nav-link" :to="'/examples/drag' | toLocale">{{$t('example.drag')}}</router-link>
</li>
<li class="nav-item">
<router-link active-class="active" class="nav-link" :to="'/examples/multiple' | toLocale">{{$t('example.multiple')}}</router-link>
</li>
<li class="nav-item">
<router-link active-class="active" class="nav-link" :to="'/examples/chunk' | toLocale">{{$t('example.chunk')}}</router-link>
</li>
<li class="nav-item">
<router-link active-class="active" class="nav-link" :to="'/examples/vuex' | toLocale">{{$t('example.vuex')}}</router-link>
</li>
</ul>
</nav>
</div>
<main class="col-12 col-md-9 col-xl-10 py-md-3 pr-md-5 pl-md-5" id="main" role="main"><router-view></router-view></main>
</div>
</div>
</template>

@ -1,3 +0,0 @@
<template>
<router-view></router-view>
</template>

@ -1,179 +0,0 @@
<template>
<div class="example-avatar">
<div v-show="$refs.upload && $refs.upload.dropActive" class="drop-active">
<h3>Drop files to upload</h3>
</div>
<div class="avatar-upload" v-show="!edit">
<div class="text-center p-2">
<label for="avatar">
<img :src="files.length ? files[0].url : 'https://www.gravatar.com/avatar/default?s=200&r=pg&d=mm'" class="rounded-circle" />
<h4 class="pt-2">or<br/>Drop files anywhere to upload</h4>
</label>
</div>
<div class="text-center p-2">
<file-upload
extensions="gif,jpg,jpeg,png,webp"
accept="image/png,image/gif,image/jpeg,image/webp"
name="avatar"
class="btn btn-primary"
post-action="/upload/post"
:drop="!edit"
v-model="files"
@input-filter="inputFilter"
@input-file="inputFile"
ref="upload">
Upload avatar
</file-upload>
</div>
</div>
<div class="avatar-edit" v-show="files.length && edit">
<div class="avatar-edit-image" v-if="files.length">
<img ref="editImage" :src="files[0].url" />
</div>
<div class="text-center p-4">
<button type="button" class="btn btn-secondary" @click.prevent="$refs.upload.clear">Cancel</button>
<button type="submit" class="btn btn-primary" @click.prevent="editSave">Save</button>
</div>
</div>
<div class="pt-5">
Source code: <a href="https://github.com/lian-yue/vue-upload-component/blob/master/docs/views/examples/Avatar.vue">/docs/views/examples/Avatar.vue</a>
</div>
</div>
</template>
<style>
.example-avatar .avatar-upload .rounded-circle {
width: 200px;
height: 200px;
}
.example-avatar .text-center .btn {
margin: 0 .5rem
}
.example-avatar .avatar-edit-image {
max-width: 100%
}
.example-avatar .drop-active {
top: 0;
bottom: 0;
right: 0;
left: 0;
position: fixed;
z-index: 9999;
opacity: .6;
text-align: center;
background: #000;
}
.example-avatar .drop-active h3 {
margin: -.5em 0 0;
position: absolute;
top: 50%;
left: 0;
right: 0;
-webkit-transform: translateY(-50%);
-ms-transform: translateY(-50%);
transform: translateY(-50%);
font-size: 40px;
color: #fff;
padding: 0;
}
</style>
<script>
import Cropper from 'cropperjs'
import FileUpload from 'vue-upload-component'
export default {
components: {
FileUpload,
},
data() {
return {
files: [],
edit: false,
cropper: false,
}
},
watch: {
edit(value) {
if (value) {
this.$nextTick(function () {
if (!this.$refs.editImage) {
return
}
let cropper = new Cropper(this.$refs.editImage, {
aspectRatio: 1 / 1,
viewMode: 1,
})
this.cropper = cropper
})
} else {
if (this.cropper) {
this.cropper.destroy()
this.cropper = false
}
}
}
},
methods: {
editSave() {
this.edit = false
let oldFile = this.files[0]
let binStr = atob(this.cropper.getCroppedCanvas().toDataURL(oldFile.type).split(',')[1])
let arr = new Uint8Array(binStr.length)
for (let i = 0; i < binStr.length; i++) {
arr[i] = binStr.charCodeAt(i)
}
let file = new File([arr], oldFile.name, { type: oldFile.type })
this.$refs.upload.update(oldFile.id, {
file,
type: file.type,
size: file.size,
active: true,
})
},
alert(message) {
alert(message)
},
inputFile(newFile, oldFile, prevent) {
if (newFile && !oldFile) {
this.$nextTick(function () {
this.edit = true
})
}
if (!newFile && oldFile) {
this.edit = false
}
},
inputFilter(newFile, oldFile, prevent) {
if (newFile && !oldFile) {
if (!/\.(gif|jpg|jpeg|png|webp)$/i.test(newFile.name)) {
this.alert('Your choice is not a picture')
return prevent()
}
}
if (newFile && (!oldFile || newFile.file !== oldFile.file)) {
newFile.url = ''
let URL = window.URL || window.webkitURL
if (URL && URL.createObjectURL) {
newFile.url = URL.createObjectURL(newFile.file)
}
}
}
}
}
</script>

@ -1,245 +0,0 @@
<template>
<div class="example-simple">
<h1 id="example-title" class="example-title">Chunk Upload Example</h1>
<p>When using chunk uploads, the file will be uploaded in different parts (or chunks). All the files with a size higher than the set in the input will be uploaded using this method.</p>
<p>You will be able to see the different parts being uploaded individually. Some parts might fail, and the package is prepared to <em>retry</em> up to a certain amount of times.</p>
<p>You can also pause / resume the upload process.</p>
<div class="upload">
<div class="form-horizontal">
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label>
<input v-model="chunkEnabled" type="checkbox"> Use chunk upload
</label>
</div>
</div>
</div>
<div class="form-group">
<label for="inputMinSize" class="col-sm-2 control-label">Min Size</label>
<div class="col-sm-10">
<div class="input-group">
<input id="inputMinSize" v-model="chunkMinSize" type="number" class="form-control">
<span class="input-group-addon">MB</span>
</div>
</div>
</div>
<div class="form-group">
<label for="inputMaxActive" class="col-sm-2 control-label">Max Active Chunks</label>
<div class="col-sm-10">
<input id="inputMaxActive" v-model="chunkMaxActive" type="number" class="form-control">
</div>
</div>
<div class="form-group">
<label for="inputMaxRetries" class="col-sm-2 control-label">Max Chunk Retries</label>
<div class="col-sm-10">
<input id="inputMaxRetries" v-model="chunkMaxRetries" type="number" class="form-control">
</div>
</div>
</div>
<table class="table table-striped table-condensed">
<thead class="thead-dark">
<tr>
<th>Name</th>
<th class="text-right">Size</th>
<th class="text-right">Progress</th>
<th>Status</th>
<th>Pause</th>
<th colspan="3" class="text-center">Chunks</th>
</tr>
<tr>
<th colspan="5"></th>
<th class="text-right">Total</th>
<th class="text-right">Active</th>
<th class="text-right">Completed</th>
</tr>
</thead>
<tbody>
<template v-for="file in files">
<tr :key="file.id + '-info'">
<td>{{ file.name }}</td>
<td class="text-right">{{ file.size | formatSize }}</td>
<td class="text-right">{{ file.progress }}%</td>
<td v-if="file.error">{{ file.error }}</td>
<td v-else-if="file.success">Success</td>
<td v-else-if="file.active">Active</td>
<td v-else> - </td>
<td>
<template v-if="file.chunk">
<button
class="btn btn-sm btn-danger"
v-if="file.active"
@click="file.chunk.pause()"
>
<i class="fa fa-pause"/>
</button>
<button
class="btn btn-sm btn-primary"
v-if="!file.active && file.chunk.hasChunksToUpload"
@click="file.chunk.resume()"
>
<i class="fa fa-play"/>
</button>
</template>
</td>
<template v-if="file.chunk">
<td class="text-right">{{ file.chunk.chunks.length }}</td>
<td class="text-right">{{ file.chunk.chunksUploading.length }}</td>
<td class="text-right">{{ file.chunk.chunksUploaded.length }}</td>
</template>
<template v-else>
<td class="text-right"> - </td>
<td class="text-right"> - </td>
<td class="text-right"> - </td>
</template>
</tr>
<tr :key="file.id + '-loading'">
<td colspan="8">
<div class="chunk-loading" v-if="file.chunk">
<span
v-for="(chunk, index) in file.chunk.chunks"
:key="index"
class="chunk-loading-part"
:class="{'chunk-loading-part__uploaded': chunk.uploaded}"
>
<template v-if="chunk.retries != file.chunk.maxRetries">
{{ file.chunk.maxRetries - chunk.retries }} error(s)
</template>
</span>
</div>
</td>
</tr>
</template>
</tbody>
</table>
<div class="example-btn">
<file-upload
class="btn btn-primary"
post-action="/upload/post"
:chunk-enabled="chunkEnabled"
:chunk="{
action: '/upload/chunk',
minSize: chunkMinSize * 1048576,
maxActive: chunkMaxActive,
maxRetries: chunkMaxRetries
}"
extensions="gif,jpg,jpeg,png,webp"
accept="image/png,image/gif,image/jpeg,image/webp"
:multiple="true"
:size="1024 * 1024 * 10"
v-model="files"
@input-filter="inputFilter"
@input-file="inputFile"
ref="upload">
<i class="fa fa-plus"></i>
Select files
</file-upload>
</div>
</div>
<div class="pt-5">
Source code: <a href="https://github.com/lian-yue/vue-upload-component/blob/master/docs/views/examples/Chunk.vue">/docs/views/examples/Chunk.vue</a>
</div>
</div>
</template>
<style>
.example-simple label.btn {
margin-bottom: 0;
margin-right: 1rem;
}
</style>
<script>
import FileUpload from 'vue-upload-component'
export default {
components: {
FileUpload,
},
data() {
return {
files: [],
chunkEnabled: true,
// 1MB by default
chunkMinSize: 1,
chunkMaxActive: 3,
chunkMaxRetries: 5
}
},
methods: {
inputFilter(newFile, oldFile, prevent) {
if (newFile && !oldFile) {
// Before adding a file
//
// Filter system files or hide files
//
if (/(\/|^)(Thumbs\.db|desktop\.ini|\..+)$/.test(newFile.name)) {
return prevent()
}
// Filter php html js file
// php html js
if (/\.(php5?|html?|jsx?)$/i.test(newFile.name)) {
return prevent()
}
}
},
inputFile(newFile, oldFile) {
if (newFile && !oldFile) {
// add
console.log('add', newFile)
this.$refs.upload.active = true
}
if (newFile && oldFile) {
// update
console.log('update', newFile)
}
if (!newFile && oldFile) {
// remove
console.log('remove', oldFile)
}
}
}
}
</script>
<style scoped>
.chunk-loading {
margin: -12px;
display: flex;
width: calc(100% + 24px);
}
.chunk-loading .chunk-loading-part {
height: 25px;
line-height: 25px;
flex: 1;
background: #ccc;
font-size: 14px;
color: white;
text-align: center;
}
.chunk-loading .chunk-loading-part.chunk-loading-part__uploaded {
background: #28A745;
}
</style>

@ -1,102 +0,0 @@
<template>
<div class="example-drag">
<div class="upload">
<ul v-if="files.length">
<li v-for="(file, index) in files" :key="file.id">
<span>{{file.name}}</span> -
<span>{{file.size | formatSize}}</span> -
<span v-if="file.error">{{file.error}}</span>
<span v-else-if="file.success">success</span>
<span v-else-if="file.active">active</span>
<span v-else></span>
</li>
</ul>
<ul v-else>
<td colspan="7">
<div class="text-center p-5">
<h4>Drop files anywhere to upload<br/>or</h4>
<label for="file" class="btn btn-lg btn-primary">Select Files</label>
</div>
</td>
</ul>
<div v-show="$refs.upload && $refs.upload.dropActive" class="drop-active">
<h3>Drop files to upload</h3>
</div>
<div class="example-btn">
<file-upload
class="btn btn-primary"
post-action="/upload/post"
:multiple="true"
:drop="true"
:drop-directory="true"
v-model="files"
ref="upload">
<i class="fa fa-plus"></i>
Select files
</file-upload>
<button type="button" class="btn btn-success" v-if="!$refs.upload || !$refs.upload.active" @click.prevent="$refs.upload.active = true">
<i class="fa fa-arrow-up" aria-hidden="true"></i>
Start Upload
</button>
<button type="button" class="btn btn-danger" v-else @click.prevent="$refs.upload.active = false">
<i class="fa fa-stop" aria-hidden="true"></i>
Stop Upload
</button>
</div>
</div>
<div class="pt-5">
Source code: <a href="https://github.com/lian-yue/vue-upload-component/blob/master/docs/views/examples/Drag.vue">/docs/views/examples/Drag.vue</a>
</div>
</div>
</template>
<style>
.example-drag label.btn {
margin-bottom: 0;
margin-right: 1rem;
}
.example-drag .drop-active {
top: 0;
bottom: 0;
right: 0;
left: 0;
position: fixed;
z-index: 9999;
opacity: .6;
text-align: center;
background: #000;
}
.example-drag .drop-active h3 {
margin: -.5em 0 0;
position: absolute;
top: 50%;
left: 0;
right: 0;
-webkit-transform: translateY(-50%);
-ms-transform: translateY(-50%);
transform: translateY(-50%);
font-size: 40px;
color: #fff;
padding: 0;
}
</style>
<script>
import FileUpload from 'vue-upload-component'
export default {
components: {
FileUpload,
},
data() {
return {
files: [],
}
}
}
</script>

@ -1,642 +0,0 @@
<template>
<div class="example-full">
<button type="button" class="btn btn-danger float-right btn-is-option" @click.prevent="isOption = !isOption">
<i class="fa fa-cog" aria-hidden="true"></i>
Options
</button>
<h1 id="example-title" class="example-title">Full Example</h1>
<div v-show="$refs.upload && $refs.upload.dropActive" class="drop-active">
<h3>Drop files to upload</h3>
</div>
<div class="upload" v-show="!isOption">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>#</th>
<th>Thumb</th>
<th>Name</th>
<th>Size</th>
<th>Speed</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr v-if="!files.length">
<td colspan="7">
<div class="text-center p-5">
<h4>Drop files anywhere to upload<br/>or</h4>
<label :for="name" class="btn btn-lg btn-primary">Select Files</label>
</div>
</td>
</tr>
<tr v-for="(file, index) in files" :key="file.id">
<td>{{index}}</td>
<td>
<img v-if="file.thumb" :src="file.thumb" width="40" height="auto" />
<span v-else>No Image</span>
</td>
<td>
<div class="filename">
{{file.name}}
</div>
<div class="progress" v-if="file.active || file.progress !== '0.00'">
<div :class="{'progress-bar': true, 'progress-bar-striped': true, 'bg-danger': file.error, 'progress-bar-animated': file.active}" role="progressbar" :style="{width: file.progress + '%'}">{{file.progress}}%</div>
</div>
</td>
<td>{{file.size | formatSize}}</td>
<td>{{file.speed | formatSize}}</td>
<td v-if="file.error">{{file.error}}</td>
<td v-else-if="file.success">success</td>
<td v-else-if="file.active">active</td>
<td v-else></td>
<td>
<div class="btn-group">
<button class="btn btn-secondary btn-sm dropdown-toggle" type="button">
Action
</button>
<div class="dropdown-menu">
<a :class="{'dropdown-item': true, disabled: file.active || file.success || file.error === 'compressing'}" href="#" @click.prevent="file.active || file.success || file.error === 'compressing' ? false : onEditFileShow(file)">Edit</a>
<a :class="{'dropdown-item': true, disabled: !file.active}" href="#" @click.prevent="file.active ? $refs.upload.update(file, {error: 'cancel'}) : false">Cancel</a>
<a class="dropdown-item" href="#" v-if="file.active" @click.prevent="$refs.upload.update(file, {active: false})">Abort</a>
<a class="dropdown-item" href="#" v-else-if="file.error && file.error !== 'compressing' && $refs.upload.features.html5" @click.prevent="$refs.upload.update(file, {active: true, error: '', progress: '0.00'})">Retry upload</a>
<a :class="{'dropdown-item': true, disabled: file.success || file.error === 'compressing'}" href="#" v-else @click.prevent="file.success || file.error === 'compressing' ? false : $refs.upload.update(file, {active: true})">Upload</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" @click.prevent="$refs.upload.remove(file)">Remove</a>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="example-foorer">
<div class="footer-status float-right">
Drop: {{$refs.upload ? $refs.upload.drop : false}},
Active: {{$refs.upload ? $refs.upload.active : false}},
Uploaded: {{$refs.upload ? $refs.upload.uploaded : true}},
Drop active: {{$refs.upload ? $refs.upload.dropActive : false}}
</div>
<div class="btn-group">
<file-upload
class="btn btn-primary dropdown-toggle"
:post-action="postAction"
:put-action="putAction"
:extensions="extensions"
:accept="accept"
:multiple="multiple"
:directory="directory"
:size="size || 0"
:thread="thread < 1 ? 1 : (thread > 5 ? 5 : thread)"
:headers="headers"
:data="data"
:drop="drop"
:drop-directory="dropDirectory"
:add-index="addIndex"
v-model="files"
@input-filter="inputFilter"
@input-file="inputFile"
ref="upload">
<i class="fa fa-plus"></i>
Select
</file-upload>
<div class="dropdown-menu">
<label class="dropdown-item" :for="name">Add files</label>
<a class="dropdown-item" href="#" @click="onAddFolader">Add folder</a>
<a class="dropdown-item" href="#" @click.prevent="addData.show = true">Add data</a>
</div>
</div>
<button type="button" class="btn btn-success" v-if="!$refs.upload || !$refs.upload.active" @click.prevent="$refs.upload.active = true">
<i class="fa fa-arrow-up" aria-hidden="true"></i>
Start Upload
</button>
<button type="button" class="btn btn-danger" v-else @click.prevent="$refs.upload.active = false">
<i class="fa fa-stop" aria-hidden="true"></i>
Stop Upload
</button>
</div>
</div>
<div class="option" v-show="isOption">
<div class="form-group">
<label for="accept">Accept:</label>
<input type="text" id="accept" class="form-control" v-model="accept">
<small class="form-text text-muted">Allow upload mime type</small>
</div>
<div class="form-group">
<label for="extensions">Extensions:</label>
<input type="text" id="extensions" class="form-control" v-model="extensions">
<small class="form-text text-muted">Allow upload file extension</small>
</div>
<div class="form-group">
<label>PUT Upload:</label>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="put-action" id="put-action" value="" v-model="putAction"> Off
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="put-action" id="put-action" value="/upload/put" v-model="putAction"> On
</label>
</div>
<small class="form-text text-muted">After the shutdown, use the POST method to upload</small>
</div>
<div class="form-group">
<label for="thread">Thread:</label>
<input type="number" max="5" min="1" id="thread" class="form-control" v-model.number="thread">
<small class="form-text text-muted">Also upload the number of files at the same time (number of threads)</small>
</div>
<div class="form-group">
<label for="size">Max size:</label>
<input type="number" min="0" id="size" class="form-control" v-model.number="size">
</div>
<div class="form-group">
<label for="minSize">Min size:</label>
<input type="number" min="0" id="minSize" class="form-control" v-model.number="minSize">
</div>
<div class="form-group">
<label for="autoCompress">Automatically compress:</label>
<input type="number" min="0" id="autoCompress" class="form-control" v-model.number="autoCompress">
<small class="form-text text-muted" v-if="autoCompress > 0">More than {{autoCompress | formatSize}} files are automatically compressed</small>
<small class="form-text text-muted" v-else>Set up automatic compression</small>
</div>
<div class="form-group">
<div class="form-check">
<label class="form-check-label">
<input type="checkbox" id="add-index" class="form-check-input" v-model="addIndex"> Start position to add
</label>
</div>
<small class="form-text text-muted">Add a file list to start the location to add</small>
</div>
<div class="form-group">
<div class="form-check">
<label class="form-check-label">
<input type="checkbox" id="drop" class="form-check-input" v-model="drop"> Drop
</label>
</div>
<small class="form-text text-muted">Drag and drop upload</small>
</div>
<div class="form-group">
<div class="form-check">
<label class="form-check-label">
<input type="checkbox" id="drop-directory" class="form-check-input" v-model="dropDirectory"> Drop directory
</label>
</div>
<small class="form-text text-muted">Not checked, filter the dragged folder</small>
</div>
<div class="form-group">
<div class="form-check">
<label class="form-check-label">
<input type="checkbox" id="upload-auto" class="form-check-input" v-model="uploadAuto"> Auto start
</label>
</div>
<small class="form-text text-muted">Automatically activate upload</small>
</div>
<div class="form-group">
<button type="button" class="btn btn-primary btn-lg btn-block" @click.prevent="isOption = !isOption">Confirm</button>
</div>
</div>
<div :class="{'modal-backdrop': true, 'fade': true, show: addData.show}"></div>
<div :class="{modal: true, fade: true, show: addData.show}" id="modal-add-data" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add data</h5>
<button type="button" class="close" @click.prevent="addData.show = false">
<span>&times;</span>
</button>
</div>
<form @submit.prevent="onAddData">
<div class="modal-body">
<div class="form-group">
<label for="name">Name:</label>
<input type="text" class="form-control" required id="name" placeholder="Please enter a file name" v-model="addData.name">
<small class="form-text text-muted">Such as <code>filename.txt</code></small>
</div>
<div class="form-group">
<label for="type">Type:</label>
<input type="text" class="form-control" required id="type" placeholder="Please enter the MIME type" v-model="addData.type">
<small class="form-text text-muted">Such as <code>text/plain</code></small>
</div>
<div class="form-group">
<label for="content">Content:</label>
<textarea class="form-control" required id="content" rows="3" placeholder="Please enter the file contents" v-model="addData.content"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @click.prevent="addData.show = false">Close</button>
<button type="submit" class="btn btn-primary">Save</button>
</div>
</form>
</div>
</div>
</div>
<div :class="{'modal-backdrop': true, 'fade': true, show: editFile.show}"></div>
<div :class="{modal: true, fade: true, show: editFile.show}" id="modal-edit-file" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edit file</h5>
<button type="button" class="close" @click.prevent="editFile.show = false">
<span>&times;</span>
</button>
</div>
<form @submit.prevent="onEditorFile">
<div class="modal-body">
<div class="form-group">
<label for="name">Name:</label>
<input type="text" class="form-control" required id="name" placeholder="Please enter a file name" v-model="editFile.name">
</div>
<div class="form-group" v-if="editFile.show && editFile.blob && editFile.type && editFile.type.substr(0, 6) === 'image/'">
<label>Image: </label>
<div class="edit-image">
<img :src="editFile.blob" ref="editImage" />
</div>
<div class="edit-image-tool">
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary" @click="editFile.cropper.rotate(-90)" title="cropper.rotate(-90)"><i class="fa fa-undo" aria-hidden="true"></i></button>
<button type="button" class="btn btn-primary" @click="editFile.cropper.rotate(90)" title="cropper.rotate(90)"><i class="fa fa-repeat" aria-hidden="true"></i></button>
</div>
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary" @click="editFile.cropper.crop()" title="cropper.crop()"><i class="fa fa-check" aria-hidden="true"></i></button>
<button type="button" class="btn btn-primary" @click="editFile.cropper.clear()" title="cropper.clear()"><i class="fa fa-remove" aria-hidden="true"></i></button>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @click.prevent="editFile.show = false">Close</button>
<button type="submit" class="btn btn-primary">Save</button>
</div>
</form>
</div>
</div>
</div>
<div class="pt-5">
Source code: <a href="https://github.com/lian-yue/vue-upload-component/blob/master/docs/views/examples/Full.vue">/docs/views/examples/Full.vue</a>
</div>
</div>
</template>
<style>
.example-full .btn-group .dropdown-menu {
display: block;
visibility: hidden;
transition: all .2s
}
.example-full .btn-group:hover > .dropdown-menu {
visibility: visible;
}
.example-full label.dropdown-item {
margin-bottom: 0;
}
.example-full .btn-group .dropdown-toggle {
margin-right: .6rem
}
.example-full .filename {
margin-bottom: .3rem
}
.example-full .btn-is-option {
margin-top: 0.25rem;
}
.example-full .example-foorer {
padding: .5rem 0;
border-top: 1px solid #e9ecef;
border-bottom: 1px solid #e9ecef;
}
.example-full .edit-image img {
max-width: 100%;
}
.example-full .edit-image-tool {
margin-top: .6rem;
}
.example-full .edit-image-tool .btn-group{
margin-right: .6rem;
}
.example-full .footer-status {
padding-top: .4rem;
}
.example-full .drop-active {
top: 0;
bottom: 0;
right: 0;
left: 0;
position: fixed;
z-index: 9999;
opacity: .6;
text-align: center;
background: #000;
}
.example-full .drop-active h3 {
margin: -.5em 0 0;
position: absolute;
top: 50%;
left: 0;
right: 0;
-webkit-transform: translateY(-50%);
-ms-transform: translateY(-50%);
transform: translateY(-50%);
font-size: 40px;
color: #fff;
padding: 0;
}
</style>
<script>
import Cropper from 'cropperjs'
import ImageCompressor from '@xkeshi/image-compressor'
import FileUpload from 'vue-upload-component'
export default {
components: {
FileUpload,
},
data() {
return {
files: [],
accept: 'image/png,image/gif,image/jpeg,image/webp',
extensions: 'gif,jpg,jpeg,png,webp',
// extensions: ['gif', 'jpg', 'jpeg','png', 'webp'],
// extensions: /\.(gif|jpe?g|png|webp)$/i,
minSize: 1024,
size: 1024 * 1024 * 10,
multiple: true,
directory: false,
drop: true,
dropDirectory: true,
addIndex: false,
thread: 3,
name: 'file',
postAction: '/upload/post',
putAction: '/upload/put',
headers: {
'X-Csrf-Token': 'xxxx',
},
data: {
'_csrf_token': 'xxxxxx',
},
autoCompress: 1024 * 1024,
uploadAuto: false,
isOption: false,
addData: {
show: false,
name: '',
type: '',
content: '',
},
editFile: {
show: false,
name: '',
}
}
},
watch: {
'editFile.show'(newValue, oldValue) {
// error
if (!newValue && oldValue) {
this.$refs.upload.update(this.editFile.id, { error: this.editFile.error || '' })
}
if (newValue) {
this.$nextTick(function () {
if (!this.$refs.editImage) {
return
}
let cropper = new Cropper(this.$refs.editImage, {
autoCrop: false,
})
this.editFile = {
...this.editFile,
cropper
}
})
}
},
'addData.show'(show) {
if (show) {
this.addData.name = ''
this.addData.type = ''
this.addData.content = ''
}
},
},
methods: {
inputFilter(newFile, oldFile, prevent) {
if (newFile && !oldFile) {
// Before adding a file
//
// Filter system files or hide files
//
if (/(\/|^)(Thumbs\.db|desktop\.ini|\..+)$/.test(newFile.name)) {
return prevent()
}
// Filter php html js file
// php html js
if (/\.(php5?|html?|jsx?)$/i.test(newFile.name)) {
return prevent()
}
// Automatic compression
//
if (newFile.file && newFile.type.substr(0, 6) === 'image/' && this.autoCompress > 0 && this.autoCompress < newFile.size) {
newFile.error = 'compressing'
const imageCompressor = new ImageCompressor(null, {
convertSize: Infinity,
maxWidth: 512,
maxHeight: 512,
})
imageCompressor.compress(newFile.file)
.then((file) => {
this.$refs.upload.update(newFile, { error: '', file, size: file.size, type: file.type })
})
.catch((err) => {
this.$refs.upload.update(newFile, { error: err.message || 'compress' })
})
}
}
if (newFile && (!oldFile || newFile.file !== oldFile.file)) {
// Create a blob field
// blob
newFile.blob = ''
let URL = window.URL || window.webkitURL
if (URL && URL.createObjectURL) {
newFile.blob = URL.createObjectURL(newFile.file)
}
// Thumbnails
//
newFile.thumb = ''
if (newFile.blob && newFile.type.substr(0, 6) === 'image/') {
newFile.thumb = newFile.blob
}
}
},
// add, update, remove File Event
inputFile(newFile, oldFile) {
if (newFile && oldFile) {
// update
if (newFile.active && !oldFile.active) {
// beforeSend
// min size
if (newFile.size >= 0 && this.minSize > 0 && newFile.size < this.minSize) {
this.$refs.upload.update(newFile, { error: 'size' })
}
}
if (newFile.progress !== oldFile.progress) {
// progress
}
if (newFile.error && !oldFile.error) {
// error
}
if (newFile.success && !oldFile.success) {
// success
}
}
if (!newFile && oldFile) {
// remove
if (oldFile.success && oldFile.response.id) {
// $.ajax({
// type: 'DELETE',
// url: '/upload/delete?id=' + oldFile.response.id,
// })
}
}
// Automatically activate upload
if (Boolean(newFile) !== Boolean(oldFile) || oldFile.error !== newFile.error) {
if (this.uploadAuto && !this.$refs.upload.active) {
this.$refs.upload.active = true
}
}
},
alert(message) {
alert(message)
},
onEditFileShow(file) {
this.editFile = { ...file, show: true }
this.$refs.upload.update(file, { error: 'edit' })
},
onEditorFile() {
if (!this.$refs.upload.features.html5) {
this.alert('Your browser does not support')
this.editFile.show = false
return
}
let data = {
name: this.editFile.name,
}
if (this.editFile.cropper) {
let binStr = atob(this.editFile.cropper.getCroppedCanvas().toDataURL(this.editFile.type).split(',')[1])
let arr = new Uint8Array(binStr.length)
for (let i = 0; i < binStr.length; i++) {
arr[i] = binStr.charCodeAt(i)
}
data.file = new File([arr], data.name, { type: this.editFile.type })
data.size = data.file.size
}
this.$refs.upload.update(this.editFile.id, data)
this.editFile.error = ''
this.editFile.show = false
},
// add folader
onAddFolader() {
if (!this.$refs.upload.features.directory) {
this.alert('Your browser does not support')
return
}
let input = this.$refs.upload.$el.querySelector('input')
input.directory = true
input.webkitdirectory = true
this.directory = true
input.onclick = null
input.click()
input.onclick = (e) => {
this.directory = false
input.directory = false
input.webkitdirectory = false
}
},
onAddData() {
this.addData.show = false
if (!this.$refs.upload.features.html5) {
this.alert('Your browser does not support')
return
}
let file = new window.File([this.addData.content], this.addData.name, {
type: this.addData.type,
})
this.$refs.upload.add(file)
}
}
}
</script>

@ -1,101 +0,0 @@
<template>
<div class="example-multiple">
<h1 id="example-title" class="example-title">Multiple instances</h1>
<div class="upload">
<ul>
<li v-for="(file, index) in files1" :key="file.id">
<span>{{file.name}}</span> -
<span>{{file.size | formatSize}}</span> -
<span v-if="file.error">{{file.error}}</span>
<span v-else-if="file.success">success</span>
<span v-else-if="file.active">active</span>
<span v-else-if="file.active">active</span>
<span v-else></span>
</li>
</ul>
<div class="example-btn">
<file-upload
class="btn btn-primary"
input-id="file1"
post-action="/upload/post"
v-model="files1"
ref="upload1">
<i class="fa fa-plus"></i>
Select files
</file-upload>
<label for="file1" class="btn btn-primary">Label Select files</label>
<button type="button" class="btn btn-success" v-if="!$refs.upload1 || !$refs.upload1.active" @click.prevent="$refs.upload1.active = true">
<i class="fa fa-arrow-up" aria-hidden="true"></i>
Start Upload
</button>
<button type="button" class="btn btn-danger" v-else @click.prevent="$refs.upload1.active = false">
<i class="fa fa-stop" aria-hidden="true"></i>
Stop Upload
</button>
</div>
</div>
<div class="upload">
<ul>
<li v-for="(file, index) in files2" :key="file.id">
<span>{{file.name}}</span> -
<span>{{file.size | formatSize}}</span> -
<span v-if="file.error">{{file.error}}</span>
<span v-else-if="file.success">success</span>
<span v-else-if="file.active">active</span>
<span v-else-if="file.active">active</span>
<span v-else></span>
</li>
</ul>
<div class="example-btn">
<file-upload
class="btn btn-primary"
input-id="file2"
post-action="/upload/post"
v-model="files2"
ref="upload2">
<i class="fa fa-plus"></i>
Select files
</file-upload>
<label for="file2" class="btn btn-primary">Label Select files</label>
<button type="button" class="btn btn-success" v-if="!$refs.upload2 || !$refs.upload2.active" @click.prevent="$refs.upload2.active = true">
<i class="fa fa-arrow-up" aria-hidden="true"></i>
Start Upload
</button>
<button type="button" class="btn btn-danger" v-else @click.prevent="$refs.upload2.active = false">
<i class="fa fa-stop" aria-hidden="true"></i>
Stop Upload
</button>
</div>
</div>
<div class="pt-5">
Source code: <a href="https://github.com/lian-yue/vue-upload-component/blob/master/docs/views/examples/Multiple.vue">/docs/views/examples/Multiple.vue</a>
</div>
</div>
</template>
<style>
.example-multiple label.btn {
margin-bottom: 0;
margin-right: 1rem;
}
.example-multiple .upload {
margin-bottom: 1rem;
}
</style>
<script>
import FileUpload from 'vue-upload-component'
export default {
components: {
FileUpload,
},
data() {
return {
files1: [],
files2: [],
}
},
}
</script>

@ -1,103 +0,0 @@
<template>
<div class="example-simple">
<h1 id="example-title" class="example-title">Simple Example</h1>
<div class="upload">
<ul>
<li v-for="(file, index) in files" :key="file.id">
<span>{{file.name}}</span> -
<span>{{file.size | formatSize}}</span> -
<span v-if="file.error">{{file.error}}</span>
<span v-else-if="file.success">success</span>
<span v-else-if="file.active">active</span>
<span v-else-if="file.active">active</span>
<span v-else></span>
</li>
</ul>
<div class="example-btn">
<file-upload
class="btn btn-primary"
post-action="/upload/post"
extensions="gif,jpg,jpeg,png,webp"
accept="image/png,image/gif,image/jpeg,image/webp"
:multiple="true"
:size="1024 * 1024 * 10"
v-model="files"
@input-filter="inputFilter"
@input-file="inputFile"
ref="upload">
<i class="fa fa-plus"></i>
Select files
</file-upload>
<button type="button" class="btn btn-success" v-if="!$refs.upload || !$refs.upload.active" @click.prevent="$refs.upload.active = true">
<i class="fa fa-arrow-up" aria-hidden="true"></i>
Start Upload
</button>
<button type="button" class="btn btn-danger" v-else @click.prevent="$refs.upload.active = false">
<i class="fa fa-stop" aria-hidden="true"></i>
Stop Upload
</button>
</div>
</div>
<div class="pt-5">
Source code: <a href="https://github.com/lian-yue/vue-upload-component/blob/master/docs/views/examples/Simple.vue">/docs/views/examples/Simple.vue</a>
</div>
</div>
</template>
<style>
.example-simple label.btn {
margin-bottom: 0;
margin-right: 1rem;
}
</style>
<script>
import FileUpload from 'vue-upload-component'
export default {
components: {
FileUpload,
},
data() {
return {
files: [],
}
},
methods: {
inputFilter(newFile, oldFile, prevent) {
if (newFile && !oldFile) {
// Before adding a file
//
// Filter system files or hide files
//
if (/(\/|^)(Thumbs\.db|desktop\.ini|\..+)$/.test(newFile.name)) {
return prevent()
}
// Filter php html js file
// php html js
if (/\.(php5?|html?|jsx?)$/i.test(newFile.name)) {
return prevent()
}
}
},
inputFile(newFile, oldFile) {
if (newFile && !oldFile) {
// add
console.log('add', newFile)
}
if (newFile && oldFile) {
// update
console.log('update', newFile)
}
if (!newFile && oldFile) {
// remove
console.log('remove', oldFile)
}
}
}
}
</script>

@ -1,72 +0,0 @@
<template>
<div class="example-vuex">
<h1 id="example-title" class="example-title">Vuex Example</h1>
<div class="upload">
<ul>
<li v-for="(file, index) in files" :key="file.id">
<span>{{file.name}}</span> -
<span>{{file.size | formatSize}}</span> -
<span v-if="file.error">{{file.error}}</span>
<span v-else-if="file.success">success</span>
<span v-else-if="file.active">active</span>
<span v-else-if="file.active">active</span>
<span v-else></span>
</li>
</ul>
<div class="example-btn">
<file-upload
class="btn btn-primary"
post-action="/upload/post"
extensions="gif,jpg,jpeg,png,webp"
accept="image/png,image/gif,image/jpeg,image/webp"
:multiple="true"
:size="1024 * 1024 * 10"
:value="files"
@input="inputUpdate"
ref="upload">
<i class="fa fa-plus"></i>
Select files
</file-upload>
<button type="button" class="btn btn-success" v-if="!$refs.upload || !$refs.upload.active" @click.prevent="$refs.upload.active = true">
<i class="fa fa-arrow-up" aria-hidden="true"></i>
Start Upload
</button>
<button type="button" class="btn btn-danger" v-else @click.prevent="$refs.upload.active = false">
<i class="fa fa-stop" aria-hidden="true"></i>
Stop Upload
</button>
</div>
</div>
<div class="pt-5">
Source code: <a href="https://github.com/lian-yue/vue-upload-component/blob/master/docs/views/examples/Vuex.vue">/docs/views/examples/Vuex.vue</a>, <a href="https://github.com/lian-yue/vue-upload-component/blob/master/docs/store.js">/docs/store.js</a>
</div>
</div>
</template>
<style>
.example-vuex label.btn {
margin-bottom: 0;
margin-right: 1rem;
}
</style>
<script>
import { mapState } from 'vuex'
import FileUpload from 'vue-upload-component'
export default {
components: {
FileUpload,
},
computed: {
...mapState([
'files',
])
},
methods: {
inputUpdate(files) {
this.$store.commit('updateFiles', files)
},
}
}
</script>

54
index.d.ts vendored

@ -1,54 +0,0 @@
import Vue from 'vue'
// Instance / File
declare global {
interface VUFile {
file: File
readonly fileObject: boolean
id: string | number
size: number
name: string
type: string
active: boolean
error: string
success: boolean
putAction: string
postAction: string
headers: object
data: object
timeout: number
response: object | string
progress: string
speed: number
xhr: XMLHttpRequest
iframe: Element
}
}
declare class _ extends Vue {
// Instance / Methods
get(id: VUFile | object | string): VUFile | object | boolean
add(files: Array<VUFile | File | object> | VUFile | File | object): object | Array<VUFile | object> | boolean
addInputFile(el: HTMLInputElement): Array<VUFile>
addDataTransfer(dataTransfer: DataTransfer): Promise<Array<VUFile>>
update(id: VUFile | object | string, data: object): object | boolean
remove(id: VUFile | object | string): object | boolean
replace(id1: VUFile | object | string, id2: VUFile | object | string): boolean
clear(): boolean
// Instance / Data
readonly files: Array<VUFile>
readonly features: { html5?: boolean; directory?: boolean; drag?: boolean }
active: boolean
readonly dropActive: true
readonly uploaded: true
}
// module 'vue/types/vue' {
// https://stackoverflow.com/a/41286276/5221998
// interface Vue {
// readonly $refs: { [key: string]: VueUploadComponent };
// }
// }
export default _

@ -2,29 +2,131 @@
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="Cache-Control" content="no-siteapp">
<meta http-equiv="Cache-Control" content="no-transform">
<meta http-equiv="X-UA-Compatible" content="IE=Edge, chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue-upload-component- Upload Component - Uploader</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0-beta/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/font-awesome/css/font-awesome.min.css" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/styles/atom-one-light.min.css" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/cropperjs/dist/cropper.css" rel="stylesheet" />
<title>vue-upload-component</title>
<style type="text/css">
#app {
position: relative;
}
.file-upload {
display: block;
}
.file-upload span{
display: block;
font-size: 18px;
padding: 1em;
font-weight: bold;
border: 1px solid #888;
}
.drop-active {
top: 0;
bottom: 0;
right: 0;
left: 0;
position: absolute;
opacity: .4;
background: #000;
}
</style>
</head>
<body>
<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/es6-promise/dist/es6-promise.auto.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuex/dist/vuex.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-router/dist/vue-router.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-i18n/dist/vue-i18n.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/lib/marked.js"></script>
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/highlight.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/xkeshi/image-compressor/dist/image-compressor.js"></script>
<script src="https://cdn.jsdelivr.net/npm/cropperjs/dist/cropper.js"></script>
<script src="./docs/dist/index.js"></script>
<div id="app">
<table style="width: 100%; border: 1px solid #ddd;">
<thead>
<tr>
<th>Index</th>
<th>ID</th>
<th>Name</th>
<th>Size</th>
<th>Progress</th>
<th>Speed</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 files">
<td>{{$index}}</td>
<td>{{file.id}}</td>
<td>{{file.name}}</td>
<td>{{file.size | formatSize}}</td>
<td>{{file.progress}}</td>
<td>{{file.speed | formatSize}}</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="<strong>Add upload files</strong>" class="file-upload" name="file" post-action="./post.php" put-action="./put.php" :extensions="extensions" :accept="accept" :multiple="multiple" :size="size" v-ref:upload :drop="drop"></file-upload>
</td>
<td>
<button v-if="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>
<label>
Auto start: <input type="checkbox" id="checkbox" v-model="auto">
</label>
</td>
<td>
<label>
Accept: <input type="text" v-model="accept">
</label>
</td>
<td>
<label>
Extensions: <input type="text" v-model="extensions">
</label>
</td>
<td>
<label>
Drop: <input type="checkbox" id="checkbox" v-model="drop">
</label>
</td>
<td>
<label>
Max file size: <input type="text" v-model="size">
</label>
</td>
<td>
<label>
Multiple: <input type="checkbox" id="checkbox" v-model="multiple">
</label>
</td>
</tr>
<tr>
<td>
Auto start: {{auto}}
</td>
<td>
Active: {{$refs.upload.active}}
</td>
<td>
Uploaded: {{$refs.upload.uploaded}}
</td>
<td>
Drop active: {{$refs.upload.dropActive}}
</td>
</tr>
</tbody>
</table>
<div v-show="$refs.upload.dropActive" class="drop-active">
Drop ing
</div>
</div>
<script src="./dist/example.js"></script>
</body>
</html>

10498
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,84 +1,55 @@
{
"name": "vue-upload-component",
"description": "Vue.js file upload component, Multi-file upload, Upload directory, Drag upload, Drag the directory, Upload multiple files at the same time, html4 (IE 9), `PUT` method, Customize the filter",
"version": "2.8.20",
"description": "Vue.js file upload component, Support for multiple file uploads, progress, html5, html4, support ie9",
"version": "0.3.9",
"author": "LianYue",
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server",
"build": "npm run build-webpack && npm run build-rollup",
"build-webpack": "cross-env NODE_ENV=production webpack --progress --hide-modules",
"build-rollup": "cross-env NODE_ENV=production rollup --config"
"dev": "webpack-dev-server --inline --hot",
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules --config webpack.config.build.min.js && cross-env NODE_ENV=production webpack --progress --hide-modules --config webpack.config.build.js && cross-env NODE_ENV=production webpack --progress --hide-modules --config webpack.config.js"
},
"main": "dist/vue-upload-component.js",
"module": "dist/vue-upload-component.js",
"unpkg": "dist/vue-upload-component.js",
"jsdelivr": "dist/vue-upload-component.js",
"typings": "index.d.ts",
"repository": {
"type": "git",
"url": "git+https://github.com/lian-yue/vue-upload-component.git"
},
"keywords": [
"javascript",
"vue",
"vue.js",
"component",
"file",
"uploader",
"directory",
"multiple",
"put",
"upload",
"upload-directory",
"chunk",
"drag",
"drag-directory",
"upload-directory",
"uploads",
"multiple",
"component",
"vue-component",
"vue-upload-component"
"vue-upload-component",
"vue-file-upload",
"vue-file-upload-component"
],
"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": {},
"dependencies": {
},
"devDependencies": {
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.6",
"babel-loader": "^7.1.5",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.7.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-es2017": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"babel-runtime": "^6.26.0",
"cross-env": "^5.2.0",
"css-loader": "^1.0.0",
"eslint": "^5.1.0",
"eslint-config-standard": "^11.0.0",
"eslint-loader": "^2.0.0",
"eslint-plugin-html": "^4.0.5",
"eslint-plugin-import": "^2.13.0",
"eslint-plugin-node": "^7.0.1",
"eslint-plugin-promise": "^3.8.0",
"eslint-plugin-standard": "^3.1.0",
"eslint-plugin-vue": "^4.7.0",
"postcss": "^7.0.0",
"raw-loader": "^0.5.1",
"rollup": "^0.63.0",
"rollup-plugin-babel": "^3.0.7",
"rollup-plugin-commonjs": "^9.1.3",
"rollup-plugin-css-only": "^0.4.0",
"rollup-plugin-node-resolve": "^3.3.0",
"rollup-plugin-uglify": "^4.0.0",
"rollup-plugin-vue": "^4.3.1",
"vue-hot-reload-api": "^2.3.0",
"vue-loader": "^15.2.5",
"vue-template-compiler": "^2.5.16",
"webpack": "^4.16.1",
"webpack-body-parser": "^1.11.110",
"webpack-cli": "^3.0.8",
"webpack-dev-server": "^3.1.4",
"webpack-merge": "^4.1.3"
"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",
"babel-runtime": "^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": "^1.0.26",
"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"
}
}

@ -1,98 +0,0 @@
import resolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs'
import babel from 'rollup-plugin-babel'
import { uglify } from 'rollup-plugin-uglify'
import vue from 'rollup-plugin-vue'
import packageInfo from './package.json'
const pluginCSS = require('rollup-plugin-css-only')
// const isDev = process.env.NODE_ENV === 'development'
function baseConfig() {
return {
output: {
format: 'umd',
sourcemap: true,
banner: `/*!\n * Name: ${packageInfo.name}\n * Version: ${packageInfo.version}\n * Author: ${packageInfo.author}\n */`,
},
plugins: [
resolve({
jsnext: true,
main: true,
browser: true,
}),
commonjs({
extensions: [
'.js',
'.jsx',
'.json',
// '.vue'
],
}),
],
}
}
let config = baseConfig()
config.input = 'src/index.js'
config.output.file = 'dist/vue-upload-component.js'
config.output.name = 'VueUploadComponent'
config.plugins.push(
vue({
template: {
isProduction: true,
},
css: true,
}),
babel()
)
let configMin = baseConfig()
configMin.input = 'src/index.js'
configMin.output.file = 'dist/vue-upload-component.min.js'
configMin.output.name = 'VueUploadComponent'
configMin.plugins.push(
vue({
style: {
trim: true,
},
template: {
isProduction: true,
},
css: true,
}),
babel(),
uglify({
output: {
comments: /^!/,
}
})
)
let configPart = baseConfig()
configPart.input = 'src/index.js'
configPart.output.file = 'dist/vue-upload-component.part.js'
configPart.output.name = 'VueUploadComponent'
configPart.plugins.push(
pluginCSS(),
vue({
style: {
trim: true,
},
template: {
isProduction: true,
},
css: false,
}),
babel()
)
module.exports = [
config,
configMin,
configPart,
]

File diff suppressed because it is too large Load Diff

@ -1,38 +0,0 @@
<template>
<input
type="file"
:name="$parent.name"
:id="$parent.inputId || $parent.name"
:accept="$parent.accept"
:capture="$parent.capture"
:disabled="$parent.disabled"
@change="change"
:webkitdirectory="$parent.directory && $parent.features.directory"
:directory="$parent.directory && $parent.features.directory"
:multiple="$parent.multiple && $parent.features.html5"
/>
</template>
<script>
export default {
methods: {
change(e) {
this.$parent.addInputFile(e.target)
if (e.target.files) {
e.target.value = ''
if (e.target.files.length && !/safari/i.test(navigator.userAgent)) {
e.target.type = ''
e.target.type = 'file'
}
} else {
// ie9 fix #219
this.$destroy()
// eslint-disable-next-line
new this.constructor({
parent: this.$parent,
el: this.$el,
})
}
}
}
}
</script>

@ -1,360 +0,0 @@
import {
default as request,
createRequest,
sendFormRequest
} from '../utils/request'
export default class ChunkUploadHandler {
/**
* Constructor
*
* @param {File} file
* @param {Object} options
*/
constructor (file, options) {
this.file = file
this.options = options
}
/**
* Gets the max retries from options
*/
get maxRetries () {
return parseInt(this.options.maxRetries)
}
/**
* Gets the max number of active chunks being uploaded at once from options
*/
get maxActiveChunks () {
return parseInt(this.options.maxActive)
}
/**
* Gets the file type
*/
get fileType () {
return this.file.type
}
/**
* Gets the file size
*/
get fileSize () {
return this.file.size
}
/**
* Gets the file name
*/
get fileName () {
return this.file.name
}
/**
* Gets action (url) to upload the file
*/
get action () {
return this.options.action || null
}
/**
* Gets the body to be merged when sending the request in start phase
*/
get startBody () {
return this.options.startBody || {}
}
/**
* Gets the body to be merged when sending the request in upload phase
*/
get uploadBody () {
return this.options.uploadBody || {}
}
/**
* Gets the body to be merged when sending the request in finish phase
*/
get finishBody () {
return this.options.finishBody || {}
}
/**
* Gets the headers of the requests from options
*/
get headers () {
return this.options.headers || {}
}
/**
* Whether it's ready to upload files or not
*/
get readyToUpload () {
return !!this.chunks
}
/**
* Gets the progress of the chunk upload
* - Gets all the completed chunks
* - Gets the progress of all the chunks that are being uploaded
*/
get progress () {
const completedProgress = (this.chunksUploaded.length / this.chunks.length) * 100
const uploadingProgress = this.chunksUploading.reduce((progress, chunk) => {
return progress + ((chunk.progress | 0) / this.chunks.length)
}, 0)
return Math.min(completedProgress + uploadingProgress, 100)
}
/**
* Gets all the chunks that are pending to be uploaded
*/
get chunksToUpload () {
return this.chunks.filter(chunk => {
return !chunk.active && !chunk.uploaded
})
}
/**
* Whether there are chunks to upload or not
*/
get hasChunksToUpload () {
return this.chunksToUpload.length > 0
}
/**
* Gets all the chunks that are uploading
*/
get chunksUploading () {
return this.chunks.filter(chunk => {
return !!chunk.xhr && !!chunk.active
})
}
/**
* Gets all the chunks that have finished uploading
*/
get chunksUploaded () {
return this.chunks.filter(chunk => {
return !!chunk.uploaded
})
}
/**
* Creates all the chunks in the initial state
*/
createChunks () {
this.chunks = []
let start = 0
let end = this.chunkSize
while (start < this.fileSize) {
this.chunks.push({
blob: this.file.file.slice(start, end),
startOffset: start,
active: false,
retries: this.maxRetries
})
start = end
end = start + this.chunkSize
}
}
/**
* Updates the progress of the file with the handler's progress
*/
updateFileProgress () {
this.file.progress = this.progress
}
/**
* Paues the upload process
* - Stops all active requests
* - Sets the file not active
*/
pause () {
this.file.active = false
this.stopChunks()
}
/**
* Stops all the current chunks
*/
stopChunks () {
this.chunksUploading.forEach(chunk => {
chunk.xhr.abort()
chunk.active = false
})
}
/**
* Resumes the file upload
* - Sets the file active
* - Starts the following chunks
*/
resume () {
this.file.active = true
this.startChunking()
}
/**
* Starts the file upload
*
* @returns Promise
* - resolve The file was uploaded
* - reject The file upload failed
*/
upload () {
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve
this.reject = reject
})
this.start()
return this.promise
}
/**
* Start phase
* Sends a request to the backend to initialise the chunks
*/
start () {
request({
method: 'POST',
headers: Object.assign({}, this.headers, {
'Content-Type': 'application/json'
}),
url: this.action,
body: Object.assign(this.startBody, {
phase: 'start',
mime_type: this.fileType,
size: this.fileSize,
name: this.fileName
})
}).then(res => {
if (res.status !== 'success') {
this.file.response = res
return this.reject('server')
}
this.sessionId = res.data.session_id
this.chunkSize = res.data.end_offset
this.createChunks()
this.startChunking()
}).catch(res => {
this.file.response = res
this.reject('server')
})
}
/**
* Starts to upload chunks
*/
startChunking () {
for (let i = 0; i < this.maxActiveChunks; i++) {
this.uploadNextChunk()
}
}
/**
* Uploads the next chunk
* - Won't do anything if the process is paused
* - Will start finish phase if there are no more chunks to upload
*/
uploadNextChunk () {
if (this.file.active) {
if (this.hasChunksToUpload) {
return this.uploadChunk(this.chunksToUpload[0])
}
if (this.chunksUploading.length === 0) {
return this.finish()
}
}
}
/**
* Uploads a chunk
* - Sends the chunk to the backend
* - Sets the chunk as uploaded if everything went well
* - Decreases the number of retries if anything went wrong
* - Fails if there are no more retries
*
* @param {Object} chunk
*/
uploadChunk (chunk) {
chunk.progress = 0
chunk.active = true
this.updateFileProgress()
chunk.xhr = createRequest({
method: 'POST',
headers: this.headers,
url: this.action
})
chunk.xhr.upload.addEventListener('progress', function (evt) {
if (evt.lengthComputable) {
chunk.progress = Math.round(evt.loaded / evt.total * 100)
}
}, false)
sendFormRequest(chunk.xhr, Object.assign(this.uploadBody, {
phase: 'upload',
session_id: this.sessionId,
start_offset: chunk.startOffset,
chunk: chunk.blob
})).then(res => {
chunk.active = false
if (res.status === 'success') {
chunk.uploaded = true
} else {
if (chunk.retries-- <= 0) {
this.stopChunks()
return this.reject('upload')
}
}
this.uploadNextChunk()
}).catch(() => {
chunk.active = false
if (chunk.retries-- <= 0) {
this.stopChunks()
return this.reject('upload')
}
this.uploadNextChunk()
})
}
/**
* Finish phase
* Sends a request to the backend to finish the process
*/
finish () {
this.updateFileProgress()
request({
method: 'POST',
headers: Object.assign({}, this.headers, {
'Content-Type': 'application/json'
}),
url: this.action,
body: Object.assign(this.finishBody, {
phase: 'finish',
session_id: this.sessionId
})
}).then(res => {
this.file.response = res
if (res.status !== 'success') {
return this.reject('server')
}
this.resolve(res)
}).catch(res => {
this.file.response = res
this.reject('server')
})
}
}

@ -0,0 +1,77 @@
import Vue from 'vue';
// var FileUpload = require('./FileUpload.vue');
import FileUpload from './FileUpload.vue';
Vue.config.debug = true;
Vue.config.silent = false;
Vue.config.async = false;
Vue.config.devtools = true;
Vue.filter('formatSize', function(size) {
if (size > 1024 * 1024 * 1024 * 1024) {
return (size / 1024 / 1024 / 1024 / 1024).toFixed(2) + ' TB';
} else if (size > 1024 * 1024 * 1024) {
return (size / 1024 / 1024 / 1024).toFixed(2) + ' GB';
} else if (size > 1024 * 1024) {
return (size / 1024 / 1024).toFixed(2) + ' MB';
} else if (size > 1024) {
return (size / 1024).toFixed(2) + ' KB';
}
return size.toString() + ' B';
});
new Vue({
el:'#app',
components: {
FileUpload:FileUpload,
},
data: {
accept: 'image/*',
size: 1024 * 1024 * 10,
multiple: true,
extensions: 'gif,jpg,png',
// extensions: ['gif','jpg','png'],
// extensions: /\.(gif|png|jpg)$/i,
files: [],
upload: {},
drop: true,
auto: false,
},
compiled() {
this.upload = this.$refs.upload;
this.upload.request = {
headers: {
"X-Csrf-Token": "xxxx",
},
data: {
"_csrf_token": "xxxxxx",
},
};
this.files = this.$refs.upload.files;
},
methods: {
remove(file) {
this.$refs.upload.files.$remove(file);
},
},
events: {
addFileUpload(file, component) {
console.log('addFileUpload');
if (this.auto) {
component.active = true;
}
},
fileUploadProgress(file, component) {
console.log('fileUploadProgress ' + file.progress);
},
afterFileUpload(file, component) {
console.log('afterFileUpload');
},
beforeFileUpload(file, component) {
console.log('beforeFileUpload');
},
}
});

@ -1 +0,0 @@
module.exports = require('./FileUpload.vue')

@ -0,0 +1 @@
module.exports = require('./FileUpload.vue');

@ -1,59 +0,0 @@
const CHUNK_SIZE = 1048576
const ChunkActiveUploads = {}
const chunkUploadStart = (req, res) => {
const uuid = Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1)
ChunkActiveUploads[uuid] = {}
return res.json({
status: 'success',
data: {
session_id: uuid,
start_offset: 0,
end_offset: CHUNK_SIZE
}
})
}
const chunkUploadPart = (req, res) => {
setTimeout(() => {
const rand = Math.random()
if (rand <= 0.25) {
res.status(500)
res.json({ status: 'error', error: 'server' })
} else {
res.send({ status: 'success' })
}
}, 100 + parseInt(Math.random() * 2000, 10))
}
const chunkUploadFinish = (req, res) => {
setTimeout(() => {
const rand = Math.random()
if (rand <= 0.25) {
res.status(500)
res.json({ status: 'error', error: 'server' })
} else {
res.send({ status: 'success' })
}
}, 100 + parseInt(Math.random() * 2000, 10))
}
module.exports = (req, res) => {
if (!req.body.phase) {
return chunkUploadPart(req, res)
}
switch (req.body.phase) {
case 'start':
return chunkUploadStart(req, res)
case 'upload':
return chunkUploadPart(req, res)
case 'finish':
return chunkUploadFinish(req, res)
}
}

@ -1,87 +0,0 @@
/**
* Creates a XHR request
*
* @param {Object} options
*/
export const createRequest = (options) => {
const xhr = new XMLHttpRequest()
xhr.open(options.method || 'GET', options.url)
xhr.responseType = 'json'
if (options.headers) {
Object.keys(options.headers).forEach(key => {
xhr.setRequestHeader(key, options.headers[key])
})
}
return xhr
}
/**
* Sends a XHR request with certain body
*
* @param {XMLHttpRequest} xhr
* @param {Object} body
*/
export const sendRequest = (xhr, body) => {
return new Promise((resolve, reject) => {
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
var response
try {
response = JSON.parse(xhr.response)
} catch (err) {
response = xhr.response
}
resolve(response)
} else {
reject(xhr.response)
}
}
xhr.onerror = () => reject(xhr.response)
xhr.send(JSON.stringify(body))
})
}
/**
* Sends a XHR request with certain form data
*
* @param {XMLHttpRequest} xhr
* @param {Object} data
*/
export const sendFormRequest = (xhr, data) => {
const body = new FormData()
for (var name in data) {
body.append(name, data[name])
}
return new Promise((resolve, reject) => {
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
var response
try {
response = JSON.parse(xhr.response)
} catch (err) {
response = xhr.response
}
resolve(response)
} else {
reject(xhr.response)
}
}
xhr.onerror = () => reject(xhr.response)
xhr.send(body)
})
}
/**
* Creates and sends XHR request
*
* @param {Object} options
*
* @returns Promise
*/
export default function (options) {
const xhr = createRequest(options)
return sendRequest(xhr, options.body)
}

@ -0,0 +1,12 @@
var webpack = require('webpack');
module.exports = require('./webpack.config.js');
module.exports.entry = {
'vue-upload-component': './src/main.js',
}
module.exports.output.library = 'VueUploadComponent';
module.exports.output.libraryTarget = 'umd';

@ -0,0 +1,11 @@
var webpack = require('webpack');
module.exports = require('./webpack.config.build.js');
module.exports.output.filename = "[name].min.js";
module.exports.plugins.push(new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
}
}));

@ -1,247 +1,66 @@
const path = require('path')
const merge = require('webpack-merge')
const webpack = require('webpack')
var path = require('path')
var webpack = require('webpack')
const packageInfo = require('./package')
const bodyParser = require('webpack-body-parser')
const chunkUpload = require('./src/utils/chunkUpload')
const { VueLoaderPlugin } = require('vue-loader')
process.env.NODE_ENV = process.env.NODE_ENV || 'production'
const isDev = process.env.NODE_ENV === 'development'
function baseConfig() {
let config = {
mode: process.env.NODE_ENV === 'development' ? 'development' : 'production',
output: {
path: path.join(__dirname, 'dist'),
publicPath: '/dist',
filename: '[name].js',
chunkFilename: '[chunkhash:8].[name].chunk.js',
module.exports = {
entry: {
example: ['vue', './src/example.js'],
},
resolve: {
modules: [
path.join(__dirname, 'node_modules'),
],
alias: {
'vue-upload-component': path.join(__dirname, 'src'),
output: {
path: './dist',
publicPath: '/dist/',
filename: "[name].js",
},
extensions: [
'.js',
'.jsx',
'.json',
'.vue',
'.md',
],
resolveLoader: {
root: path.join(__dirname, 'node_modules'),
},
externals: {
vue: 'Vue',
vuex: 'Vuex',
'vue-router': 'VueRouter',
'vue-i18n': 'VueI18n',
'marked': 'marked',
'highlight.js': 'hljs',
'cropperjs': 'Cropper',
'@xkeshi/image-compressor': 'ImageCompressor',
},
module: {
rules: [
{
test: /\.jsx?$/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
[
'env',
loaders: [
{
modules: false
}
],
'stage-0',
],
plugins: [
[
'transform-runtime',
{
helpers: false,
polyfill: false,
regenerator: true,
moduleName: 'babel-runtime'
}
]
],
cacheDirectory: isDev
},
test: /\.vue$/,
loader: 'vue'
},
{
loader: 'eslint-loader',
},
],
test: /\.js$/,
loader: 'babel',
exclude: /node_modules/
},
{
test: /\.(md|txt)$/,
use: [
{
loader: 'raw-loader',
},
]
},
{
test: /\.css$/,
use: [
{
loader: 'vue-style-loader',
test: /\.json$/,
loader: 'json'
},
{
loader: 'css-loader',
options: {
minimize: !isDev,
test: /\.html$/,
loader: 'vue-html'
},
},
]
},
{
test: /\.vue$/,
use: [
{
loader: 'vue-loader',
options: {
preserveWhitespace: false,
cssModules: {
localIdentName: '[hash:base64:8]',
camelCase: true,
},
},
},
],
},
/*
{
test: /\.vue$/,
use: [
{
loader: 'vue-loader',
options: {
preserveWhitespace: false,
loaders: {
js: [
{
loader: 'babel-loader',
options: {
presets: [
[
'env',
{
modules: false
test: /\.(png|jpg|gif|svg)$/,
loader: 'url',
query: {
limit: 10000,
name: '[name].[ext]?[hash]'
}
],
'stage-0'
],
plugins: [
[
'transform-runtime',
{
helpers: false,
polyfill: false,
regenerator: true,
moduleName: 'babel-runtime'
}
]
],
cacheDirectory: isDev
}
},
],
}
},
devServer: {
historyApiFallback: true,
noInfo: true
},
{
loader: 'eslint-loader',
},
]
}
*/
]
},
plugins: [
new webpack.BannerPlugin(`Name: ${packageInfo.name}\nVersion: ${packageInfo.version}\nAuthor: ${packageInfo.author}`),
new VueLoaderPlugin(),
],
devtool: isDev ? 'eval-source-map' : 'source-map'
devtool: '#eval-source-map'
}
if (isDev) {
config.plugins.push(new webpack.HotModuleReplacementPlugin())
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"'
}
return config
}
module.exports = merge(baseConfig(), {
entry: {
index: [
path.join(__dirname, 'docs/index.js'),
],
},
output: {
path: path.join(__dirname, 'docs/dist'),
},
}),
devServer: {
before(app) {
let id = 1000000
let put = function (req, res) {
setTimeout(function () {
let rand = Math.random()
if (rand <= 0.1) {
res.status(500)
res.json({ error: 'server', success: false })
} else if (rand <= 0.25) {
res.status(403)
res.json({ error: 'failure', success: false })
} else {
res.json({ url: 'https://vuejs.org/images/logo.png?id=' + id, name: 'filename.ext', id: id++, success: true })
}
}, 200 + parseInt(Math.random() * 4000, 10))
}
let del = function (req, res) {
res.json({ success: true })
new webpack.optimize.OccurenceOrderPlugin()
])
}
// Chunk upload
app.post('/upload/chunk', bodyParser.json(), chunkUpload)
app.post('/upload/post', put)
app.put('/upload/put', put)
app.post('/upload/delete', del)
app.delete('/upload/delete', del)
},
// host: '0.0.0.0',
hot: true,
contentBase: path.join(__dirname, 'docs'),
clientLogLevel: 'error',
noInfo: true,
publicPath: '/dist',
inline: true,
historyApiFallback: true,
overlay: {
warnings: true,
errors: true
},
// host: '172.16.23.1'
},
})

Loading…
Cancel
Save