Compare commits
221 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
209a5725ba | 7 years ago |
|
|
cb7a75e4ad | 7 years ago |
|
|
1f440b74e1 | 7 years ago |
|
|
0d307c175a | 7 years ago |
|
|
8c2eba8d9a | 7 years ago |
|
|
5a7201e3d7 | 7 years ago |
|
|
60d1064205 | 7 years ago |
|
|
2d946569d1 | 7 years ago |
|
|
53db208074 | 7 years ago |
|
|
6e44782f93 | 7 years ago |
|
|
7e8f23204f | 7 years ago |
|
|
454109129d | 7 years ago |
|
|
aad7d18ab3 | 7 years ago |
|
|
3145c88d68 | 7 years ago |
|
|
b6e5bc3919 | 7 years ago |
|
|
fde412ee1a | 7 years ago |
|
|
8ad138d108 | 7 years ago |
|
|
098cc958ee | 7 years ago |
|
|
4b58d015e8 | 7 years ago |
|
|
c687bb14d8 | 7 years ago |
|
|
48a3a876b8 | 7 years ago |
|
|
6bbd4deb73 | 7 years ago |
|
|
f82e00393d | 7 years ago |
|
|
229c5830f4 | 7 years ago |
|
|
a969e16bc0 | 7 years ago |
|
|
ba5274c0a2 | 7 years ago |
|
|
0850fc0411 | 7 years ago |
|
|
b39e76d0f3 | 7 years ago |
|
|
d75ffa6369 | 7 years ago |
|
|
a194f9c9c8 | 7 years ago |
|
|
3940224a72 | 8 years ago |
|
|
a0ceec86d9 | 8 years ago |
|
|
47497baabe | 8 years ago |
|
|
644872010b | 8 years ago |
|
|
8a8c8c1cae | 8 years ago |
|
|
f45a552a33 | 8 years ago |
|
|
9ae5172387 | 8 years ago |
|
|
d1dc4baf80 | 8 years ago |
|
|
c507c32135 | 8 years ago |
|
|
70264884fa | 8 years ago |
|
|
6a7eac5c12 | 8 years ago |
|
|
f4df58000f | 8 years ago |
|
|
059ff1a15f | 8 years ago |
|
|
011d74de62 | 8 years ago |
|
|
49845c13bd | 8 years ago |
|
|
1a365fefdc | 8 years ago |
|
|
fe9dedbff6 | 8 years ago |
|
|
ea3767df64 | 8 years ago |
|
|
453a1a574d | 8 years ago |
|
|
41562987af | 8 years ago |
|
|
28c993544d | 8 years ago |
|
|
5740af9718 | 8 years ago |
|
|
32d87b1527 | 8 years ago |
|
|
560d99c8bc | 8 years ago |
|
|
43540ea35c | 8 years ago |
|
|
1ea52e241f | 8 years ago |
|
|
631ad4b16b | 8 years ago |
|
|
38f3da2996 | 8 years ago |
|
|
bd3a5072ae | 8 years ago |
|
|
c4f6a597db | 8 years ago |
|
|
a6bc6b58f3 | 8 years ago |
|
|
4d1a87991b | 8 years ago |
|
|
8417e11b3a | 8 years ago |
|
|
bca5bab289 | 8 years ago |
|
|
4e2530e492 | 8 years ago |
|
|
e291993e44 | 8 years ago |
|
|
29b541bfeb | 8 years ago |
|
|
586c2bb72b | 8 years ago |
|
|
3285dd30e6 | 8 years ago |
|
|
5a7a4fcea9 | 8 years ago |
|
|
8e557d0670 | 8 years ago |
|
|
a218cffefd | 8 years ago |
|
|
78ca7c569f | 8 years ago |
|
|
699c7476bf | 8 years ago |
|
|
ab1612d4cf | 8 years ago |
|
|
86b5b713e6 | 8 years ago |
|
|
48ce45257d | 8 years ago |
|
|
c01a3abb92 | 8 years ago |
|
|
bdf52d66ed | 8 years ago |
|
|
b81ab35d45 | 8 years ago |
|
|
54bf33da4a | 8 years ago |
|
|
902b3164f8 | 8 years ago |
|
|
961cc35f23 | 8 years ago |
|
|
5604efe979 | 8 years ago |
|
|
dfc1e65475 | 8 years ago |
|
|
28df5d3125 | 8 years ago |
|
|
2cfb93c04b | 8 years ago |
|
|
872c984eb2 | 8 years ago |
|
|
7d88edc716 | 8 years ago |
|
|
6af5af4af2 | 8 years ago |
|
|
fa5fe89c13 | 8 years ago |
|
|
dacb0cfdc3 | 8 years ago |
|
|
cbb8fa574d | 8 years ago |
|
|
1d0e93a23e | 8 years ago |
|
|
b7530b811e | 8 years ago |
|
|
9c9d8aafbc | 8 years ago |
|
|
b5743ad79f | 8 years ago |
|
|
bf34813501 | 8 years ago |
|
|
8f83f2bbe9 | 8 years ago |
|
|
1155bbe995 | 8 years ago |
|
|
ad06881ae2 | 8 years ago |
|
|
76c890de6c | 8 years ago |
|
|
fb0b1d3223 | 8 years ago |
|
|
e11c4e44d8 | 8 years ago |
|
|
828b5b4eb2 | 8 years ago |
|
|
09ec1eb755 | 8 years ago |
|
|
57cd9c4425 | 8 years ago |
|
|
d07f4bf5f2 | 8 years ago |
|
|
94a09ce045 | 8 years ago |
|
|
815500dbd6 | 8 years ago |
|
|
d4c117f01b | 8 years ago |
|
|
c0d5c65cf8 | 8 years ago |
|
|
9307218ecb | 8 years ago |
|
|
396e269034 | 8 years ago |
|
|
50f5785920 | 8 years ago |
|
|
b9534be58d | 8 years ago |
|
|
bb4cc184ed | 8 years ago |
|
|
081b2aca10 | 8 years ago |
|
|
d4c1a49402 | 8 years ago |
|
|
e1251ba4e5 | 8 years ago |
|
|
d4a85ca90b | 8 years ago |
|
|
be060d2b72 | 8 years ago |
|
|
28bb0768ee | 8 years ago |
|
|
ff2ecdafaf | 8 years ago |
|
|
b794ecb808 | 8 years ago |
|
|
17369cd847 | 8 years ago |
|
|
9493161003 | 8 years ago |
|
|
88047f60b0 | 8 years ago |
|
|
207a000970 | 8 years ago |
|
|
adfea6e8b1 | 8 years ago |
|
|
778122a9e5 | 8 years ago |
|
|
40790e8c7f | 8 years ago |
|
|
d25e9e2a3d | 8 years ago |
|
|
657fd2fb4e | 8 years ago |
|
|
bbba94e20b | 8 years ago |
|
|
a44e89a52a | 8 years ago |
|
|
f65a220435 | 8 years ago |
|
|
49eb5f6459 | 8 years ago |
|
|
27dca17f14 | 8 years ago |
|
|
f25f126bd1 | 8 years ago |
|
|
5ce75ac4e8 | 8 years ago |
|
|
ef614e59ad | 8 years ago |
|
|
9a5562e4ad | 8 years ago |
|
|
5ef5575450 | 8 years ago |
|
|
c6b1011615 | 8 years ago |
|
|
4207dc0665 | 8 years ago |
|
|
24bb304a8f | 8 years ago |
|
|
3a5a235bbd | 8 years ago |
|
|
71d98e0101 | 8 years ago |
|
|
6f3379f1ed | 8 years ago |
|
|
7a32970db7 | 8 years ago |
|
|
14cec0d533 | 8 years ago |
|
|
8b847bb50f | 8 years ago |
|
|
f3867fb8ea | 8 years ago |
|
|
9a4f07f8d2 | 8 years ago |
|
|
9615613a4c | 8 years ago |
|
|
9429278b65 | 8 years ago |
|
|
87ff993153 | 8 years ago |
|
|
104dbebc58 | 8 years ago |
|
|
c933a66773 | 8 years ago |
|
|
058c83858d | 8 years ago |
|
|
7b9dac4faa | 8 years ago |
|
|
7a3af5e0e6 | 8 years ago |
|
|
a5a7a7d694 | 8 years ago |
|
|
062b6fd547 | 8 years ago |
|
|
993b56aca7 | 8 years ago |
|
|
5202df85ce | 8 years ago |
|
|
f0972e48b3 | 8 years ago |
|
|
f6e09f2141 | 8 years ago |
|
|
c70dc2f347 | 8 years ago |
|
|
3d92f7730a | 8 years ago |
|
|
dae502d4ae | 8 years ago |
|
|
2738d7a90d | 8 years ago |
|
|
5aae7d586d | 9 years ago |
|
|
4d43a30f5c | 9 years ago |
|
|
5b26b07b2d | 9 years ago |
|
|
05d6158671 | 9 years ago |
|
|
e2f7e18b6b | 9 years ago |
|
|
cc404ad3bc | 9 years ago |
|
|
5be0900fdb | 9 years ago |
|
|
5076e381b2 | 9 years ago |
|
|
75fd49b119 | 9 years ago |
|
|
6cf882bf1e | 9 years ago |
|
|
e9f72b0e7b | 9 years ago |
|
|
7e8caa8861 | 9 years ago |
|
|
cab0135ed0 | 9 years ago |
|
|
44e033f466 | 9 years ago |
|
|
a61c424d99 | 9 years ago |
|
|
decbd7bc33 | 9 years ago |
|
|
cdfaf3f238 | 9 years ago |
|
|
bfa1949ded | 9 years ago |
|
|
fb6a9409e2 | 9 years ago |
|
|
5e80c29ee4 | 9 years ago |
|
|
77e13e0daf | 9 years ago |
|
|
0071bd56fd | 9 years ago |
|
|
cb7d7bc864 | 9 years ago |
|
|
656a9b6509 | 9 years ago |
|
|
25d906a858 | 9 years ago |
|
|
e4287f7e1f | 9 years ago |
|
|
a59a2f854a | 9 years ago |
|
|
b3da037d54 | 9 years ago |
|
|
7be691ba23 | 9 years ago |
|
|
26f530541d | 9 years ago |
|
|
b782af2df7 | 9 years ago |
|
|
e4705d3820 | 9 years ago |
|
|
0195eca288 | 9 years ago |
|
|
176ff0a8d8 | 9 years ago |
|
|
8a48f091e4 | 9 years ago |
|
|
2905fab59a | 9 years ago |
|
|
a179fda708 | 9 years ago |
|
|
dffce36537 | 9 years ago |
|
|
ea290eab1d | 9 years ago |
|
|
9f5babc692 | 9 years ago |
|
|
796cc72c6b | 9 years ago |
|
|
1ef765d7f4 | 9 years ago |
|
|
ae6907a892 | 9 years ago |
|
|
b9dd840a35 | 9 years ago |
|
|
6929d3b50f | 9 years ago |
|
|
511e31c40b | 9 years ago |
|
|
f401e1e4c8 | 9 years ago |
|
|
bc24562a12 | 9 years ago |
@ -1,5 +1,11 @@
|
||||
{
|
||||
"presets": ["es2015", "stage-2"],
|
||||
"plugins": ["transform-runtime"],
|
||||
"comments": false
|
||||
"presets": [
|
||||
[
|
||||
"env",
|
||||
{
|
||||
"modules": false
|
||||
}
|
||||
],
|
||||
"stage-0"
|
||||
]
|
||||
}
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
node_modules/*
|
||||
dist/*
|
||||
@ -0,0 +1,39 @@
|
||||
// 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,3 +1,4 @@
|
||||
.DS_Store
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
.idea
|
||||
@ -1,174 +1,75 @@
|
||||
# vue-upload-component
|
||||
[](https://www.npmjs.com/package/vue-upload-component) [](https://www.npmjs.com/package/vue-upload-component) [](https://www.npmjs.com/package/vue-upload-component) [](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
|
||||
|
||||
|
||||
## Install
|
||||
- [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
|
||||
|
||||
``` bash
|
||||
npm install vue-upload-component --save
|
||||
```
|
||||
### Vue 2.x
|
||||
|
||||
https://github.com/lian-yue/vue-upload-component/tree/2.0
|
||||
|
||||
``` 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
|
||||
# Example
|
||||
|
||||
https://lian-yue.github.io/vue-upload-component/
|
||||
|
||||
``` 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
|
||||
# Installation
|
||||
|
||||
``` bash
|
||||
# install dependencies
|
||||
npm install
|
||||
|
||||
# serve with hot reload at localhost:8080
|
||||
npm run dev
|
||||
|
||||
# build for production with minification
|
||||
npm run build
|
||||
npm install vue-upload-component --save
|
||||
```
|
||||
|
||||
# Documentation
|
||||
|
||||
## $dispatch, methods
|
||||
addFileUpload
|
||||
|
||||
removeFileUpload
|
||||
https://lian-yue.github.io/vue-upload-component/#/documents
|
||||
|
||||
fileUploadProgress
|
||||
|
||||
beforeFileUpload
|
||||
|
||||
afterFileUpload
|
||||
|
||||
|
||||
|
||||
|
||||
## Setting
|
||||
> Vue.js 文件上传组建
|
||||
> 组件只是一个按钮
|
||||
|
||||
### 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",
|
||||
},
|
||||
},
|
||||
}
|
||||
],
|
||||
- [x] 上传多文件
|
||||
- [x] 上传目录
|
||||
- [x] 拖拽
|
||||
- [x] 拖拽目录
|
||||
- [x] 多线程
|
||||
- [x] html4(IE 9)
|
||||
- [x] `PUT` 方法
|
||||
- [x] 自定义过滤器
|
||||
- [x] 缩略图
|
||||
|
||||
# 演示
|
||||
|
||||
// Global
|
||||
request: {
|
||||
headers: {
|
||||
"X-Csrf-Token": "xxxx",
|
||||
},
|
||||
data: {
|
||||
"_csrf_token": "xxxxxx",
|
||||
},
|
||||
},
|
||||
https://lian-yue.github.io/vue-upload-component/#/zh-cn/
|
||||
|
||||
|
||||
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"
|
||||
|
||||
extensions="Array or String or Regular" :post-action="./post.method"
|
||||
# 文档
|
||||
|
||||
post-action="./post.method"
|
||||
https://lian-yue.github.io/vue-upload-component/#/zh-cn/documents
|
||||
|
||||
put-action="./put.method"
|
||||
|
||||
accept="accept"
|
||||
|
||||
multiple="true or false"
|
||||
# Special thanks (特别感谢)
|
||||
|
||||
size="max Size"
|
||||
|
||||
timeout="3600000"
|
||||
|
||||
```
|
||||
- [@josec89](https://github.com/josec89)
|
||||
|
||||
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
@ -0,0 +1,7 @@
|
||||
|
||||
.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
@ -0,0 +1,31 @@
|
||||
<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
@ -0,0 +1,31 @@
|
||||
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',
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
// 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,
|
||||
}
|
||||
})
|
||||
@ -0,0 +1,31 @@
|
||||
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',
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
<!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>
|
||||
@ -0,0 +1,136 @@
|
||||
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')
|
||||
@ -0,0 +1,95 @@
|
||||
// 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
|
||||
@ -0,0 +1,20 @@
|
||||
// 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
|
||||
})
|
||||
@ -0,0 +1,192 @@
|
||||
<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>
|
||||
@ -0,0 +1,115 @@
|
||||
<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>
|
||||
@ -0,0 +1,34 @@
|
||||
<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>
|
||||
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
@ -0,0 +1,179 @@
|
||||
<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>
|
||||
@ -0,0 +1,245 @@
|
||||
<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>
|
||||
|
||||
@ -0,0 +1,102 @@
|
||||
<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>
|
||||
@ -0,0 +1,642 @@
|
||||
<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>×</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>×</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>
|
||||
@ -0,0 +1,101 @@
|
||||
<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>
|
||||
@ -0,0 +1,103 @@
|
||||
<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>
|
||||
@ -0,0 +1,72 @@
|
||||
<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>
|
||||
@ -0,0 +1,54 @@
|
||||
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 _
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,55 +1,84 @@
|
||||
{
|
||||
"name": "vue-upload-component",
|
||||
"description": "Vue.js file upload component, Support for multiple file uploads, progress, html5, html4, support ie9",
|
||||
"version": "0.3.9",
|
||||
"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",
|
||||
"author": "LianYue",
|
||||
"scripts": {
|
||||
"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"
|
||||
"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"
|
||||
},
|
||||
"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",
|
||||
"put",
|
||||
"upload",
|
||||
"uploads",
|
||||
"uploader",
|
||||
"directory",
|
||||
"multiple",
|
||||
"component",
|
||||
"upload",
|
||||
"upload-directory",
|
||||
"chunk",
|
||||
"drag",
|
||||
"drag-directory",
|
||||
"upload-directory",
|
||||
"vue-component",
|
||||
"vue-upload-component",
|
||||
"vue-file-upload",
|
||||
"vue-file-upload-component"
|
||||
"vue-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.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"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,98 @@
|
||||
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
@ -0,0 +1,38 @@
|
||||
<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>
|
||||
@ -0,0 +1,360 @@
|
||||
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')
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,77 +0,0 @@
|
||||
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');
|
||||
},
|
||||
}
|
||||
});
|
||||
@ -0,0 +1 @@
|
||||
module.exports = require('./FileUpload.vue')
|
||||
@ -1 +0,0 @@
|
||||
module.exports = require('./FileUpload.vue');
|
||||
@ -0,0 +1,59 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
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';
|
||||
@ -1,11 +0,0 @@
|
||||
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,66 +1,247 @@
|
||||
var path = require('path')
|
||||
var webpack = require('webpack')
|
||||
const path = require('path')
|
||||
const merge = require('webpack-merge')
|
||||
const webpack = require('webpack')
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
example: ['vue', './src/example.js'],
|
||||
},
|
||||
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: './dist',
|
||||
publicPath: '/dist/',
|
||||
filename: "[name].js",
|
||||
path: path.join(__dirname, 'dist'),
|
||||
publicPath: '/dist',
|
||||
filename: '[name].js',
|
||||
chunkFilename: '[chunkhash:8].[name].chunk.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'
|
||||
resolve: {
|
||||
modules: [
|
||||
path.join(__dirname, 'node_modules'),
|
||||
],
|
||||
|
||||
alias: {
|
||||
'vue-upload-component': path.join(__dirname, 'src'),
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif|svg)$/,
|
||||
loader: 'url',
|
||||
query: {
|
||||
limit: 10000,
|
||||
name: '[name].[ext]?[hash]'
|
||||
|
||||
extensions: [
|
||||
'.js',
|
||||
'.jsx',
|
||||
'.json',
|
||||
'.vue',
|
||||
'.md',
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
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',
|
||||
{
|
||||
modules: false
|
||||
}
|
||||
],
|
||||
'stage-0',
|
||||
],
|
||||
plugins: [
|
||||
[
|
||||
'transform-runtime',
|
||||
{
|
||||
helpers: false,
|
||||
polyfill: false,
|
||||
regenerator: true,
|
||||
moduleName: 'babel-runtime'
|
||||
}
|
||||
]
|
||||
],
|
||||
cacheDirectory: isDev
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'eslint-loader',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(md|txt)$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'raw-loader',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'vue-style-loader',
|
||||
},
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
minimize: !isDev,
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
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
|
||||
}
|
||||
],
|
||||
'stage-0'
|
||||
],
|
||||
plugins: [
|
||||
[
|
||||
'transform-runtime',
|
||||
{
|
||||
helpers: false,
|
||||
polyfill: false,
|
||||
regenerator: true,
|
||||
moduleName: 'babel-runtime'
|
||||
}
|
||||
]
|
||||
],
|
||||
cacheDirectory: isDev
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
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'
|
||||
}
|
||||
|
||||
if (isDev) {
|
||||
config.plugins.push(new webpack.HotModuleReplacementPlugin())
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = merge(baseConfig(), {
|
||||
entry: {
|
||||
index: [
|
||||
path.join(__dirname, 'docs/index.js'),
|
||||
],
|
||||
},
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
noInfo: true
|
||||
|
||||
output: {
|
||||
path: path.join(__dirname, 'docs/dist'),
|
||||
},
|
||||
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"'
|
||||
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…
Reference in new issue