├── .eslintignore ├── .browserslistrc ├── docs ├── views │ ├── Router.vue │ ├── Example.vue │ ├── examples │ │ ├── Vuex.vue │ │ ├── Drag.vue │ │ ├── Simple.vue │ │ ├── Typescript.vue │ │ ├── Avatar.vue │ │ ├── AsyncEvents.vue │ │ ├── Multiple.vue │ │ ├── Chunk.vue │ │ └── Full.vue │ ├── Document.vue │ └── App.vue ├── i18n │ ├── index.js │ ├── zh-cn.js │ └── en.js ├── store.js ├── Vuex.vue ├── dist │ └── docs.js.LICENSE.txt ├── index.template.html ├── router.js └── entry.js ├── src ├── index.d.ts ├── index.ts ├── index.ts.d.ts ├── utils │ ├── chunkUpload.js │ └── request.js ├── chunk │ └── ChunkUploadHandler.js └── FileUpload.vue.d.ts ├── .gitignore ├── .babelrc ├── dist ├── vue-upload-component.part.css ├── vue-upload-component.esm.part.css ├── vue-upload-component.d.ts ├── vue-upload-component.esm.min.js └── vue-upload-component.min.js ├── prettier.config.js ├── tsconfig.json ├── index.html ├── .eslintrc.js ├── README.md ├── esm.html ├── package.json ├── rollup.config.js ├── webpack.config.js └── LICENSE /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | dist/* -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 0.5% 2 | IE 9 3 | last 5 versions 4 | -------------------------------------------------------------------------------- /docs/views/Router.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | import FileUpload from './FileUpload.vue'; 2 | export default FileUpload; 3 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import FileUpload from './FileUpload.vue' 2 | 3 | export default FileUpload 4 | 5 | -------------------------------------------------------------------------------- /src/index.ts.d.ts: -------------------------------------------------------------------------------- 1 | import FileUpload from './FileUpload.vue'; 2 | export default FileUpload; 3 | -------------------------------------------------------------------------------- /docs/i18n/index.js: -------------------------------------------------------------------------------- 1 | import {createI18n} from 'vue-i18n' 2 | import en from './en' 3 | import zhCN from './zh-cn' 4 | 5 | export default createI18n({ 6 | locale: 'en', 7 | fallbackLocale: 'en', 8 | messages: { 9 | 'zh-cn': zhCN, 10 | en, 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.tmp 3 | *.db 4 | *.ini 5 | *.zip 6 | *.rar 7 | *.tar 8 | *.gz 9 | .* 10 | *.dev.js 11 | !.gitignore 12 | !.eslintrc* 13 | !.eslintrc.js 14 | !.eslintignore 15 | !.babelrc 16 | !.flowconfig 17 | !.vscode 18 | !.browserslistrc 19 | Icon 20 | node_modules 21 | _* 22 | -------------------------------------------------------------------------------- /docs/store.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'vuex' 2 | 3 | 4 | const state = { 5 | files: [], 6 | } 7 | 8 | const mutations = { 9 | updateFiles(state, files) { 10 | state.files = files 11 | } 12 | } 13 | export default createStore({ 14 | strict: true, 15 | state, 16 | mutations 17 | }) 18 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "modules": false, 7 | "useBuiltIns": false, 8 | "targets": "ie >= 9", 9 | "exclude": [ 10 | "transform-async-to-generator", 11 | "proposal-async-generator-functions", 12 | "transform-regenerator" 13 | ] 14 | } 15 | ] 16 | ], 17 | "sourceType": "unambiguous", 18 | "plugins": [ 19 | ["@babel/plugin-transform-runtime", {"helpers": false, "corejs": false, "regenerator": false, "useESModules": false, "absoluteRuntime": false}] 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /docs/i18n/zh-cn.js: -------------------------------------------------------------------------------- 1 | export default { 2 | header: { 3 | logo: 'Vuejs', 4 | home: '首页', 5 | examples: '演示', 6 | documents: '文档', 7 | blog: 'Blog', 8 | locale: 'Language(语言)', 9 | issues: 'Issues', 10 | github: 'Github', 11 | }, 12 | 13 | locale: { 14 | en: 'English', 15 | 'zh-cn': '中文(简体)', 16 | }, 17 | 18 | document: { 19 | title: '文档', 20 | }, 21 | 22 | 23 | example: { 24 | full: '完整例子', 25 | simple: '简单例子', 26 | avatar: '上传头像', 27 | drag: '拖拽上传', 28 | multiple: '多个实例', 29 | vuex: 'Vuex', 30 | typescript: 'Typescript', 31 | asyncevents: '异步事件', 32 | } 33 | } -------------------------------------------------------------------------------- /docs/Vuex.vue: -------------------------------------------------------------------------------- 1 | 12 | 32 | -------------------------------------------------------------------------------- /docs/i18n/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | header: { 3 | logo: 'Vuejs', 4 | home: 'Home', 5 | examples: 'Examples', 6 | documents: 'Documentation', 7 | blog: 'Blog', 8 | locale: 'Language(语言)', 9 | issues: 'Issues', 10 | github: 'Github', 11 | }, 12 | 13 | locale: { 14 | en: 'English', 15 | 'zh-cn': '中文(简体)', 16 | }, 17 | 18 | document: { 19 | title: 'Documentation', 20 | }, 21 | 22 | example: { 23 | full: 'Full Example', 24 | simple: 'Simple', 25 | avatar: 'Upload avatar', 26 | drag: 'Drag and drop', 27 | multiple: 'Multiple instances', 28 | chunk: 'Chunk upload', 29 | vuex: 'Vuex', 30 | typescript: 'Typescript', 31 | asyncevents: 'Async Events', 32 | } 33 | } -------------------------------------------------------------------------------- /dist/vue-upload-component.part.css: -------------------------------------------------------------------------------- 1 | 2 | .file-uploads { 3 | overflow: hidden; 4 | position: relative; 5 | text-align: center; 6 | display: inline-block; 7 | } 8 | 9 | .file-uploads.file-uploads-html4 input, 10 | .file-uploads.file-uploads-html5 label { 11 | /* background fix ie click */ 12 | background: #fff; 13 | opacity: 0; 14 | font-size: 20em; 15 | z-index: 1; 16 | top: 0; 17 | left: 0; 18 | right: 0; 19 | bottom: 0; 20 | position: absolute; 21 | width: 100%; 22 | height: 100%; 23 | } 24 | 25 | .file-uploads.file-uploads-html5 input, 26 | .file-uploads.file-uploads-html4 label { 27 | /* background fix ie click */ 28 | position: absolute; 29 | background: rgba(255, 255, 255, 0); 30 | overflow: hidden; 31 | position: fixed; 32 | width: 1px; 33 | height: 1px; 34 | z-index: -1; 35 | opacity: 0; 36 | } 37 | -------------------------------------------------------------------------------- /dist/vue-upload-component.esm.part.css: -------------------------------------------------------------------------------- 1 | 2 | .file-uploads { 3 | overflow: hidden; 4 | position: relative; 5 | text-align: center; 6 | display: inline-block; 7 | } 8 | 9 | .file-uploads.file-uploads-html4 input, 10 | .file-uploads.file-uploads-html5 label { 11 | /* background fix ie click */ 12 | background: #fff; 13 | opacity: 0; 14 | font-size: 20em; 15 | z-index: 1; 16 | top: 0; 17 | left: 0; 18 | right: 0; 19 | bottom: 0; 20 | position: absolute; 21 | width: 100%; 22 | height: 100%; 23 | } 24 | 25 | .file-uploads.file-uploads-html5 input, 26 | .file-uploads.file-uploads-html4 label { 27 | /* background fix ie click */ 28 | position: absolute; 29 | background: rgba(255, 255, 255, 0); 30 | overflow: hidden; 31 | position: fixed; 32 | width: 1px; 33 | height: 1px; 34 | z-index: -1; 35 | opacity: 0; 36 | } 37 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // 一行最多 100 字符 3 | printWidth: 100, 4 | // 使用 4 个空格缩进 5 | tabWidth: 2, 6 | // 不使用缩进符,而使用空格 7 | useTabs: false, 8 | // 行尾需要有分号 9 | semi: false, 10 | // 使用单引号 11 | singleQuote: true, 12 | // 对象的 key 仅在必要时用引号 13 | quoteProps: 'as-needed', 14 | // jsx 不使用单引号,而使用双引号 15 | jsxSingleQuote: false, 16 | // 末尾不需要逗号 17 | trailingComma: 'none', 18 | // 大括号内的首尾需要空格 19 | bracketSpacing: true, 20 | // jsx 标签的反尖括号需要换行 21 | jsxBracketSameLine: false, 22 | // 箭头函数,只有一个参数的时候,也需要括号 23 | arrowParens: 'always', 24 | // 每个文件格式化的范围是文件的全部内容 25 | rangeStart: 0, 26 | rangeEnd: Infinity, 27 | // 不需要写文件开头的 @prettier 28 | requirePragma: true, 29 | // 不需要自动在文件开头插入 @prettier 30 | insertPragma: false, 31 | // 使用默认的折行标准 32 | proseWrap: 'preserve', 33 | // 根据显示样式决定 html 要不要折行 34 | htmlWhitespaceSensitivity: 'css', 35 | // 换行符使用 lf 36 | endOfLine: 'lf' 37 | } 38 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "declaration": true, 14 | "baseUrl": ".", 15 | "types": [ 16 | "node", 17 | "webpack-env" 18 | ], 19 | "paths": { 20 | "@/*": [ 21 | "./src/*" 22 | ], 23 | }, 24 | "lib": [ 25 | "esnext", 26 | "dom", 27 | "scripthost" 28 | ] 29 | }, 30 | "include": [ 31 | "./src/*.ts", 32 | "./src/*.vue", 33 | "./src/**/*.ts", 34 | "./src/**/*.tsx", 35 | "./src/**/*.vue", 36 | "./src/views/*.vue" 37 | ], 38 | "exclude": [ 39 | "node_modules" 40 | ] 41 | } -------------------------------------------------------------------------------- /docs/dist/docs.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * devtools-if v9.2.0-beta.30 3 | * (c) 2022 kazuya kawaguchi 4 | * Released under the MIT License. 5 | */ 6 | 7 | /*! 8 | * message-compiler v9.2.0-beta.30 9 | * (c) 2022 kazuya kawaguchi 10 | * Released under the MIT License. 11 | */ 12 | 13 | /*! 14 | * vue-devtools v9.2.0-beta.30 15 | * (c) 2022 kazuya kawaguchi 16 | * Released under the MIT License. 17 | */ 18 | 19 | /*! 20 | * Name: vue-upload-component 21 | * Component URI: https://github.com/lian-yue/vue-upload-component#readme 22 | * Version: 3.1.17 23 | * Author: LianYue 24 | * License: Apache-2.0 25 | * 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 26 | */ 27 | 28 | /*! 29 | Name: vue-upload-component 30 | Component URI: https://github.com/lian-yue/vue-upload-component#readme 31 | Version: 3.1.16 32 | Author: LianYue 33 | License: Apache-2.0 34 | 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 35 | */ 36 | 37 | /*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh */ 38 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | vue-upload-component- Upload Component - Uploader
-------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: [ 7 | 'alloy', 8 | 'alloy/typescript', 9 | 'plugin:vue/essential', 10 | '@vue/typescript', 11 | 12 | 'prettier', 13 | 'plugin:prettier/recommended', 14 | ], 15 | rules: { 16 | 'no-console': 'off', 17 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 18 | 'complexity': ["error", 40], 19 | 'max-params': ["error", 10], 20 | '@typescript-eslint/no-empty-interface': 'off', 21 | '@typescript-eslint/prefer-for-of': 'off', 22 | 'guard-for-in': 'off', 23 | "@typescript-eslint/explicit-member-accessibility": "off" 24 | }, 25 | parserOptions: { 26 | parser: '@typescript-eslint/parser' 27 | }, 28 | plugins: ['@typescript-eslint', 'prettier'], 29 | overrides: [{ 30 | files: [ 31 | '**/__tests__/*.{j,t}s?(x)', 32 | '**/tests/unit/**/*.spec.{j,t}s?(x)' 33 | ], 34 | env: { 35 | jest: true 36 | } 37 | }, 38 | // { 39 | // // enable the rule specifically for TypeScript files 40 | // "files": ["*.ts", "*.tsx"], 41 | // "rules": { 42 | // "@typescript-eslint/explicit-member-accessibility": ["error"] 43 | // } 44 | // }, 45 | { 46 | "files": ["*.ts", "*.tsx"], 47 | "rules": { 48 | "@typescript-eslint/explicit-member-accessibility": ["error"] 49 | } 50 | }, 51 | ] 52 | } -------------------------------------------------------------------------------- /src/utils/chunkUpload.js: -------------------------------------------------------------------------------- 1 | const CHUNK_SIZE = 1048576 2 | const ChunkActiveUploads = {} 3 | 4 | const chunkUploadStart = (req, res) => { 5 | const uuid = Math.floor((1 + Math.random()) * 0x10000) 6 | .toString(16) 7 | .substring(1) 8 | ChunkActiveUploads[uuid] = {} 9 | 10 | return res.json({ 11 | status: 'success', 12 | data: { 13 | session_id: uuid, 14 | start_offset: 0, 15 | end_offset: CHUNK_SIZE 16 | } 17 | }) 18 | } 19 | 20 | const chunkUploadPart = (req, res) => { 21 | setTimeout(() => { 22 | const rand = Math.random() 23 | if (rand <= 0.25) { 24 | res.status(500) 25 | res.json({ status: 'error', error: 'server' }) 26 | } else { 27 | res.send({ status: 'success' }) 28 | } 29 | }, 100 + parseInt(Math.random() * 2000, 10)) 30 | } 31 | 32 | const chunkUploadFinish = (req, res) => { 33 | setTimeout(() => { 34 | const rand = Math.random() 35 | if (rand <= 0.25) { 36 | res.status(500) 37 | res.json({ status: 'error', error: 'server' }) 38 | } else { 39 | res.send({ status: 'success' }) 40 | } 41 | }, 100 + parseInt(Math.random() * 2000, 10)) 42 | } 43 | 44 | module.exports = (req, res) => { 45 | if (!req.body.phase) { 46 | return chunkUploadPart(req, res) 47 | } 48 | 49 | switch (req.body.phase) { 50 | case 'start': 51 | return chunkUploadStart(req, res) 52 | 53 | case 'upload': 54 | return chunkUploadPart(req, res) 55 | 56 | case 'finish': 57 | return chunkUploadFinish(req, res) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /docs/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | vue-upload-component- Upload Component - Uploader 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-upload-component 2 | [![npm](https://img.shields.io/npm/dm/vue-upload-component.svg?style=flat-square)](https://www.npmjs.com/package/vue-upload-component) [![npm](https://img.shields.io/npm/v/vue-upload-component.svg?style=flat-square)](https://www.npmjs.com/package/vue-upload-component) [![license](https://img.shields.io/github/license/lian-yue/vue-upload-component.svg?style=flat-square)](https://www.npmjs.com/package/vue-upload-component) [![gzip](http://img.badgesize.io/lian-yue/vue-upload-component/master/dist/vue-upload-component.min.js.svg?compression=gzip&style=flat-square)](https://github.com/lian-yue/vue-upload-component) 3 | 4 | 5 | > Vue.js file upload component 6 | > The component is just a button 7 | 8 | 9 | - [x] Multi-file upload 10 | - [x] Upload directory 11 | - [x] Drag upload 12 | - [x] Drag the directory 13 | - [x] Upload multiple files at the same time 14 | - [x] html4 (IE 9) 15 | - [x] `PUT` method 16 | - [x] Customize the filter 17 | - [x] thumbnails 18 | - [x] Chunk upload 19 | 20 | 21 | 22 | # Example 23 | 24 | https://lian-yue.github.io/vue-upload-component/ 25 | 26 | # Installation 27 | 28 | ``` bash 29 | npm install vue-upload-component --save 30 | ``` 31 | ## Vue3 32 | ``` bash 33 | npm install vue-upload-component@next --save 34 | ``` 35 | 36 | # Documentation 37 | 38 | https://lian-yue.github.io/vue-upload-component/#/documents 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | > Vue.js 文件上传组件 47 | > 组件只是一个按钮 48 | 49 | - [x] 上传多文件 50 | - [x] 上传目录 51 | - [x] 拖拽 52 | - [x] 拖拽目录 53 | - [x] 多线程 54 | - [x] html4(IE 9) 55 | - [x] `PUT` 方法 56 | - [x] 自定义过滤器 57 | - [x] 缩略图 58 | 59 | # 演示 60 | 61 | https://lian-yue.github.io/vue-upload-component/#/zh-cn/ 62 | 63 | 64 | 65 | # 安装 66 | 67 | ``` bash 68 | npm install vue-upload-component --save 69 | ``` 70 | ## Vue3 71 | ``` bash 72 | npm install vue-upload-component@next --save 73 | ``` 74 | 75 | # 文档 76 | 77 | https://lian-yue.github.io/vue-upload-component/#/zh-cn/documents 78 | 79 | 80 | 81 | # Special thanks (特别感谢) 82 | 83 | - [@josec89](https://github.com/josec89) 84 | -------------------------------------------------------------------------------- /docs/views/Example.vue: -------------------------------------------------------------------------------- 1 | 41 | -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates a XHR request 3 | * 4 | * @param {Object} options 5 | */ 6 | export const createRequest = (options) => { 7 | const xhr = new XMLHttpRequest() 8 | xhr.open(options.method || 'GET', options.url) 9 | xhr.responseType = 'json' 10 | if (options.headers) { 11 | Object.keys(options.headers).forEach(key => { 12 | xhr.setRequestHeader(key, options.headers[key]) 13 | }) 14 | } 15 | 16 | return xhr 17 | } 18 | 19 | /** 20 | * Sends a XHR request with certain body 21 | * 22 | * @param {XMLHttpRequest} xhr 23 | * @param {Object} body 24 | */ 25 | export const sendRequest = (xhr, body) => { 26 | return new Promise((resolve, reject) => { 27 | xhr.onload = () => { 28 | if (xhr.status >= 200 && xhr.status < 300) { 29 | let response 30 | try { 31 | response = JSON.parse(xhr.response) 32 | } catch (err) { 33 | response = xhr.response 34 | } 35 | resolve(response) 36 | } else { 37 | reject(xhr.response) 38 | } 39 | } 40 | xhr.onerror = () => reject(xhr.response) 41 | xhr.send(JSON.stringify(body)) 42 | }) 43 | } 44 | 45 | /** 46 | * Sends a XHR request with certain form data 47 | * 48 | * @param {XMLHttpRequest} xhr 49 | * @param {Object} data 50 | */ 51 | export const sendFormRequest = (xhr, data) => { 52 | const body = new FormData() 53 | for (let name in data) { 54 | body.append(name, data[name]) 55 | } 56 | 57 | return new Promise((resolve, reject) => { 58 | xhr.onload = () => { 59 | if (xhr.status >= 200 && xhr.status < 300) { 60 | let response 61 | try { 62 | response = JSON.parse(xhr.response) 63 | } catch (err) { 64 | response = xhr.response 65 | } 66 | resolve(response) 67 | } else { 68 | reject(xhr.response) 69 | } 70 | } 71 | xhr.onerror = () => reject(xhr.response) 72 | xhr.send(body) 73 | }) 74 | } 75 | 76 | /** 77 | * Creates and sends XHR request 78 | * 79 | * @param {Object} options 80 | * 81 | * @returns Promise 82 | */ 83 | export default function (options) { 84 | const xhr = createRequest(options) 85 | 86 | return sendRequest(xhr, options.body) 87 | } 88 | -------------------------------------------------------------------------------- /docs/views/examples/Vuex.vue: -------------------------------------------------------------------------------- 1 | 44 | 50 | 51 | 72 | -------------------------------------------------------------------------------- /esm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue-upload-component Test 6 | 14 | 15 | 16 |
17 | 20 | 28 | Upload file 29 | 30 | 31 | 32 |
33 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /docs/router.js: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory } from 'vue-router' 2 | 3 | import i18n from './i18n' 4 | import RouterComponent from './views/Router' 5 | import DocumentComponent from './views/Document' 6 | import ExampleComponent from './views/Example' 7 | 8 | import FullExampleComponent from './views/examples/Full' 9 | import SimpleExampleComponent from './views/examples/Simple' 10 | import AvatarExampleComponent from './views/examples/Avatar' 11 | import DragExampleComponent from './views/examples/Drag' 12 | import MultipleExampleComponent from './views/examples/Multiple' 13 | import ChunkExampleComponent from './views/examples/Chunk' 14 | import VuexExampleComponent from './views/examples/Vuex' 15 | import TypescriptExampleComponent from './views/examples/Typescript' 16 | import AsyncEventsExampleComponent from './views/examples/AsyncEvents' 17 | 18 | 19 | // console.log(i18n) 20 | 21 | 22 | let examples = [{ 23 | path: '', 24 | component: FullExampleComponent, 25 | }, 26 | { 27 | path: 'full', 28 | component: FullExampleComponent, 29 | }, 30 | { 31 | path: '', 32 | component: SimpleExampleComponent, 33 | }, 34 | { 35 | path: 'simple', 36 | component: SimpleExampleComponent, 37 | }, 38 | { 39 | path: 'avatar', 40 | component: AvatarExampleComponent, 41 | }, 42 | { 43 | path: 'drag', 44 | component: DragExampleComponent, 45 | }, 46 | { 47 | path: 'multiple', 48 | component: MultipleExampleComponent, 49 | }, 50 | { 51 | path: 'chunk', 52 | component: ChunkExampleComponent, 53 | }, 54 | { 55 | path: 'vuex', 56 | component: VuexExampleComponent, 57 | }, 58 | { 59 | path: 'typescript', 60 | component: TypescriptExampleComponent, 61 | }, 62 | { 63 | path: 'asyncevents', 64 | component: AsyncEventsExampleComponent, 65 | }, 66 | ] 67 | 68 | 69 | const router = createRouter({ 70 | history: createWebHashHistory(), 71 | scrollBehavior(to, from, savedPosition) { 72 | if (savedPosition) { 73 | return savedPosition 74 | } else if (to.hash) { 75 | return { el: to.hash, top: document.querySelector('#header').offsetHeight } 76 | } else { 77 | return { x: 0, y: 0 } 78 | } 79 | }, 80 | routes: [{ 81 | path: '/:locale(' + i18n.global.availableLocales.join('|') + ')?', 82 | component: RouterComponent, 83 | children: [{ 84 | path: 'documents', 85 | component: DocumentComponent, 86 | }, 87 | { 88 | path: 'examples', 89 | component: ExampleComponent, 90 | children: examples, 91 | }, 92 | { 93 | path: '', 94 | component: ExampleComponent, 95 | children: examples, 96 | }, 97 | ] 98 | }, ] 99 | }) 100 | export default router -------------------------------------------------------------------------------- /docs/views/examples/Drag.vue: -------------------------------------------------------------------------------- 1 | 55 | 88 | 89 | 103 | -------------------------------------------------------------------------------- /docs/views/examples/Simple.vue: -------------------------------------------------------------------------------- 1 | 46 | 52 | 53 | 110 | -------------------------------------------------------------------------------- /docs/views/Document.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 55 | 116 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-upload-component", 3 | "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", 4 | "version": "3.1.17", 5 | "author": "LianYue", 6 | "scripts": { 7 | "dev": "cross-env NODE_ENV=development webpack serve --hot", 8 | "build": "npm run build-rollup && npm run build-webpack", 9 | "build-webpack": "cross-env NODE_ENV=production webpack --no-watch --progress", 10 | "build-rollup": "cross-env NODE_ENV=production rollup --config && mv ./dist/dist/vue-upload-component.part.css ./dist/vue-upload-component.part.css && mv ./dist/dist/vue-upload-component.esm.part.css ./dist/vue-upload-component.esm.part.css && rmdir ./dist/dist && mv ./dist/FileUpload.vue.d.ts ./dist/vue-upload-component.d.ts && cp ./dist/vue-upload-component.d.ts ./src/FileUpload.vue.d.ts && rm ./dist/index.d.ts" 11 | }, 12 | "main": "dist/vue-upload-component.js", 13 | "module": "dist/vue-upload-component.js", 14 | "unpkg": "dist/vue-upload-component.js", 15 | "jsdelivr": "dist/vue-upload-component.js", 16 | "typings": "dist/vue-upload-component.d.ts", 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/lian-yue/vue-upload-component.git" 20 | }, 21 | "keywords": [ 22 | "javascript", 23 | "vue3", 24 | "vue 3", 25 | "vue", 26 | "vue.js", 27 | "component", 28 | "file", 29 | "uploader", 30 | "directory", 31 | "multiple", 32 | "upload", 33 | "upload-directory", 34 | "chunk", 35 | "drag", 36 | "drag-directory", 37 | "upload-directory", 38 | "vue-component", 39 | "vue-upload-component" 40 | ], 41 | "license": "Apache-2.0", 42 | "bugs": { 43 | "url": "https://github.com/lian-yue/vue-upload-component/issues" 44 | }, 45 | "homepage": "https://github.com/lian-yue/vue-upload-component#readme", 46 | "devDependencies": { 47 | "@babel/cli": "^7.16.8", 48 | "@babel/core": "^7.16.12", 49 | "@babel/plugin-transform-runtime": "^7.16.10", 50 | "@babel/preset-env": "^7.16.11", 51 | "@babel/runtime": "^7.16.7", 52 | "@babel/runtime-corejs2": "^7.16.7", 53 | "@rollup/plugin-babel": "^5.3.0", 54 | "@rollup/plugin-commonjs": "^21.0.1", 55 | "@rollup/plugin-node-resolve": "^13.1.3", 56 | "@types/webpack-env": "^1.16.3", 57 | "@typescript-eslint/eslint-plugin": "^5.10.1", 58 | "@typescript-eslint/parser": "^5.10.1", 59 | "@vue/compiler-sfc": "^3.2.29", 60 | "@vue/eslint-config-typescript": "^10.0.0", 61 | "babel-loader": "^8.2.2", 62 | "cross-env": "^7.0.2", 63 | "css-loader": "^5.0.1", 64 | "css-minimizer-webpack-plugin": "^3.4.1", 65 | "eslint": "^8.7.0", 66 | "eslint-config-alloy": "^4.4.0", 67 | "eslint-config-prettier": "^8.3.0", 68 | "eslint-config-standard": "^17.0.0-0", 69 | "eslint-plugin-prettier": "^4.0.0", 70 | "eslint-plugin-vue": "^8.4.0", 71 | "eslint-webpack-plugin": "^3.1.1", 72 | "file-type": "^19.4.0", 73 | "html-loader": "^3.1.0", 74 | "html-webpack-plugin": "^5.5.0", 75 | "mini-css-extract-plugin": "^2.5.3", 76 | "prettier": "^2.5.1", 77 | "raw-loader": "^4.0.2", 78 | "rollup": "^2.66.1", 79 | "rollup-plugin-css-only": "^3.1.0", 80 | "rollup-plugin-postcss": "^4.0.2", 81 | "rollup-plugin-terser": "^7.0.2", 82 | "rollup-plugin-typescript2": "^0.31.1", 83 | "rollup-plugin-vue": "^6.0.0", 84 | "style-loader": "^2.0.0", 85 | "ts-loader": "^8.0.12", 86 | "typescript": "^4.5.5", 87 | "vue": "^3.2.29", 88 | "vue-i18n": "^9.2.0-beta.30", 89 | "vue-loader": "^17.0.0", 90 | "vue-router": "^4.0.12", 91 | "vuex": "^4.0.2", 92 | "webpack": "^5.67.0", 93 | "webpack-body-parser": "^1.11.110", 94 | "webpack-cli": "^4.9.2", 95 | "webpack-dev-server": "^4.7.3", 96 | "webpack-env": "^0.8.0" 97 | } 98 | } -------------------------------------------------------------------------------- /docs/views/examples/Typescript.vue: -------------------------------------------------------------------------------- 1 | 46 | 52 | 53 | 54 | 113 | 114 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import resolve from '@rollup/plugin-node-resolve' 3 | import commonjs from '@rollup/plugin-commonjs' 4 | import { babel, getBabelOutputPlugin } from '@rollup/plugin-babel'; 5 | import { terser } from 'rollup-plugin-terser' 6 | import postcss from 'rollup-plugin-postcss' 7 | import vue from 'rollup-plugin-vue' 8 | import packageInfo from './package.json' 9 | import typescript from 'rollup-plugin-typescript2'; 10 | import pluginCSS from 'rollup-plugin-css-only'; 11 | 12 | 13 | function baseConfig(css, ssr, umd, min) { 14 | let res = { 15 | input: 'src/FileUpload.vue', 16 | output: { 17 | format: umd ? 'umd' : 'esm', 18 | // format: 'iife', 19 | sourcemap: true, 20 | banner: umd ? `/*!\n Name: ${ packageInfo.name } \nComponent URI: ${ packageInfo.homepage } \nVersion: ${ packageInfo.version } \nAuthor: ${ packageInfo.author } \nLicense: ${ packageInfo.license } \nDescription: ${ packageInfo.description } \n */` : '', 21 | globals: { 22 | vue: 'Vue', 23 | }, 24 | name: "VueUploadComponent", 25 | }, 26 | external: ['vue'], 27 | plugins: [ 28 | resolve({ 29 | jsnext: true, 30 | main: true, 31 | browser: true, 32 | }), 33 | css && pluginCSS(), 34 | vue({ 35 | preprocessStyles: true, 36 | css: css, 37 | target: ssr ? 'node' : 'browser', 38 | }), 39 | typescript({ 40 | declaration: true, 41 | check: true, 42 | tsconfig: path.resolve(__dirname, 'tsconfig.json'), 43 | cacheRoot: path.resolve(__dirname, 'node_modules/.rts2_cache'), 44 | }), 45 | postcss(), 46 | ssr || umd ? false : babel({ 47 | exclude: 'node_modules/**' 48 | }), 49 | ssr || umd ? false : getBabelOutputPlugin({ 50 | "presets": [ 51 | ["@babel/preset-env", { "modules": false, "useBuiltIns": false, "targets": "ie >= 9", "exclude": ["transform-async-to-generator", "proposal-async-generator-functions", "transform-regenerator"] }] 52 | ], 53 | "sourceType": "unambiguous", 54 | "plugins": [ 55 | ["@babel/plugin-transform-runtime", { "helpers": false, "corejs": false, "regenerator": false, "useESModules": false, "absoluteRuntime": false }] 56 | ], 57 | }), 58 | min ? terser({ 59 | output: { 60 | comments: /^!/, 61 | } 62 | }) : false, 63 | commonjs({ 64 | extensions: [ 65 | '.js', 66 | '.ts', 67 | '.vue' 68 | ], 69 | }), 70 | ], 71 | } 72 | 73 | return res 74 | } 75 | 76 | let config = baseConfig(false, false) 77 | config.output.file = 'dist/vue-upload-component.js' 78 | 79 | let configPart = baseConfig(true, false) 80 | configPart.output.file = 'dist/vue-upload-component.part.js' 81 | 82 | let configSSR = baseConfig(false, true, true) 83 | configSSR.output.file = 'dist/vue-upload-component.ssr.js' 84 | 85 | let configUmd = baseConfig(false, false, true) 86 | configUmd.input = config.output.file 87 | configUmd.output.file = config.output.file 88 | 89 | let configPartUmd = baseConfig(false, false, true) 90 | configPartUmd.input = configPart.output.file 91 | configPartUmd.output.file = configPart.output.file 92 | 93 | 94 | let configMin = baseConfig(false, false, true, true) 95 | configMin.input = config.output.file 96 | configMin.output.file = 'dist/vue-upload-component.min.js' 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | let configEsm = baseConfig(false, false, false) 107 | configEsm.output.file = 'dist/vue-upload-component.esm.js' 108 | 109 | let configEsmPart = baseConfig(true, false, false) 110 | configEsmPart.output.file = 'dist/vue-upload-component.esm.part.js' 111 | 112 | let configEsmSSR = baseConfig(false, true, false) 113 | configEsmSSR.output.file = 'dist/vue-upload-component.esm.ssr.js' 114 | 115 | let configEsmUmd = baseConfig(false, false, false) 116 | configEsmUmd.input = configEsm.output.file 117 | configEsmUmd.output.file = configEsm.output.file 118 | 119 | let configEsmPartUmd = baseConfig(false, false, false) 120 | configEsmPartUmd.input = configEsmPart.output.file 121 | configEsmPartUmd.output.file = configEsmPart.output.file 122 | 123 | 124 | let configEsmMin = baseConfig(false, false, false, true) 125 | configEsmMin.input = configEsm.output.file 126 | configEsmMin.output.file = 'dist/vue-upload-component.esm.min.js' 127 | 128 | 129 | 130 | module.exports = [ 131 | config, 132 | configPart, 133 | configSSR, 134 | configUmd, 135 | configPartUmd, 136 | configMin, 137 | 138 | configEsm, 139 | configEsmPart, 140 | configEsmSSR, 141 | configEsmUmd, 142 | configEsmPartUmd, 143 | configEsmMin, 144 | ] -------------------------------------------------------------------------------- /docs/views/examples/Avatar.vue: -------------------------------------------------------------------------------- 1 | 44 | 85 | 86 | 87 | 181 | -------------------------------------------------------------------------------- /docs/entry.js: -------------------------------------------------------------------------------- 1 | import { createApp, h } from 'vue' 2 | import marked from 'marked' 3 | import highlightjs from 'highlight.js' 4 | import store from './store' 5 | import router from './router' 6 | import i18n from './i18n' 7 | import App from './views/App' 8 | import { stringifyQuery } from 'vue-router' 9 | 10 | class Renderer extends marked.Renderer { 11 | heading(text, level, raw) { 12 | let rawName = raw.toLowerCase().replace(/([\u0000-\u002F\u003A-\u0060\u007B-\u007F]+)/g, '-').replace(/^\-+|\-+$/, '') 13 | 14 | if (!this.options.headers) { 15 | this.options.headers = [] 16 | } 17 | while (this.options.headers.length >= level) { 18 | this.options.headers.pop() 19 | } 20 | let parent = this.options.headers.filter(value => !!value).join('-') 21 | if (parent) { 22 | parent = parent + '-' 23 | } 24 | while (this.options.headers.length < (level - 1)) { 25 | this.options.headers.push('') 26 | } 27 | this.options.headers.push(rawName) 28 | return '' + 35 | text + 36 | '\n' 39 | } 40 | } 41 | 42 | marked.setOptions({ 43 | renderer: new Renderer(), 44 | gfm: true, 45 | tables: true, 46 | breaks: false, 47 | pedantic: false, 48 | sanitize: false, 49 | smartLists: true, 50 | smartypants: false, 51 | highlight(code, lang) { 52 | if (lang) { 53 | return highlightjs.highlight(lang, code).value 54 | } else { 55 | return highlightjs.highlightAuto(code).value 56 | } 57 | } 58 | }) 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | const app = createApp({ 67 | render() { 68 | return h(App) 69 | }, 70 | }) 71 | 72 | app.use(store) 73 | app.use(i18n) 74 | app.use(router) 75 | 76 | 77 | // 设置当前 url 信息 78 | router.push(window.location.hash ? window.location.hash.substr(1) : '') 79 | 80 | // 载入 81 | router.isReady().then(function() { 82 | app.mount('#app') 83 | }) 84 | 85 | 86 | app.directive('markdown', { 87 | mounted(el, binding, vnode) { 88 | if (!el.className || !/vue-markdown/.test(el.className)) { 89 | el.className += ' vue-markdown' 90 | } 91 | 92 | let text = '' 93 | if (typeof vnode.children === 'string') { 94 | text = vnode.children 95 | } else { 96 | for (let i = 0; i < vnode.children.length; i++) { 97 | text += vnode.children[i].text || vnode.children[i].children || '' 98 | } 99 | } 100 | 101 | if (el.markdown === text) { 102 | return 103 | } 104 | 105 | 106 | el.markdown = text 107 | el.innerHTML = marked(text) 108 | let selectorList = el.querySelectorAll('a') 109 | for (let i = 0; i < selectorList.length; i++) { 110 | selectorList[i].onclick = function(e) { 111 | if (e.metaKey || e.ctrlKey || e.shiftKey) { 112 | return 113 | } 114 | if (e.defaultPrevented) { 115 | return 116 | } 117 | if (e.button !== undefined && e.button !== 0) { 118 | return 119 | } 120 | 121 | if (this.host !== window.location.host) { 122 | return 123 | } 124 | 125 | let href = this.getAttribute('href') 126 | if (!href) { 127 | return 128 | } 129 | 130 | if (href.charAt(0) !== '#') { 131 | return 132 | } 133 | 134 | e.preventDefault() 135 | router.push(href) 136 | } 137 | } 138 | }, 139 | beforeUpdate(el, binding, vnode) { 140 | if (el.classList.contains("vue-markdown")) { 141 | el.classList.remove("vue-markdown"); 142 | } 143 | }, 144 | updated(el, binding, vnode) { 145 | if (!el.className || !/vue-markdown/.test(el.className)) { 146 | el.className += ' vue-markdown' 147 | } 148 | let text = '' 149 | if (typeof vnode.children === 'string') { 150 | text = vnode.children 151 | } else { 152 | for (let i = 0; i < vnode.children.length; i++) { 153 | text += vnode.children[i].text || vnode.children[i].children || '' 154 | } 155 | } 156 | if (el.markdown === text) { 157 | return 158 | } 159 | 160 | el.markdown = text 161 | el.innerHTML = marked(text) 162 | let selectorList = el.querySelectorAll('a') 163 | for (let i = 0; i < selectorList.length; i++) { 164 | selectorList[i].onclick = function(e) { 165 | if (e.metaKey || e.ctrlKey || e.shiftKey) { 166 | return 167 | } 168 | if (e.defaultPrevented) { 169 | return 170 | } 171 | if (e.button !== undefined && e.button !== 0) { 172 | return 173 | } 174 | 175 | if (this.host !== window.location.host) { 176 | return 177 | } 178 | 179 | let href = this.getAttribute('href') 180 | if (!href) { 181 | return 182 | } 183 | 184 | if (href.charAt(0) !== '#') { 185 | return 186 | } 187 | 188 | e.preventDefault() 189 | router.push(href) 190 | } 191 | } 192 | } 193 | }) 194 | 195 | 196 | 197 | app.config.globalProperties.$toLocale = function(to) { 198 | return '/' + i18n.global.locale + to 199 | } 200 | 201 | app.config.globalProperties.$formatSize = function(size) { 202 | if (size > 1024 * 1024 * 1024 * 1024) { 203 | return (size / 1024 / 1024 / 1024 / 1024).toFixed(2) + ' TB' 204 | } else if (size > 1024 * 1024 * 1024) { 205 | return (size / 1024 / 1024 / 1024).toFixed(2) + ' GB' 206 | } else if (size > 1024 * 1024) { 207 | return (size / 1024 / 1024).toFixed(2) + ' MB' 208 | } else if (size > 1024) { 209 | return (size / 1024).toFixed(2) + ' KB' 210 | } 211 | return size.toString() + ' B' 212 | } 213 | 214 | 215 | 216 | // new Vue({ 217 | // store, 218 | // // router, 219 | // // i18n, 220 | // // ...App 221 | // }).$mount('#app') -------------------------------------------------------------------------------- /docs/views/App.vue: -------------------------------------------------------------------------------- 1 | 48 | 161 | 198 | -------------------------------------------------------------------------------- /docs/views/examples/AsyncEvents.vue: -------------------------------------------------------------------------------- 1 | 41 | 47 | 48 | 171 | -------------------------------------------------------------------------------- /docs/views/examples/Multiple.vue: -------------------------------------------------------------------------------- 1 | 107 | 166 | 167 | 182 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint @typescript-eslint/no-require-imports: 0 */ 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const webpack = require('webpack') 6 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 7 | const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin'); 8 | const chunkUpload = require('./src/utils/chunkUpload') 9 | const bodyParser = require('webpack-body-parser') 10 | const HtmlWebpackPlugin = require('html-webpack-plugin') 11 | const packageInfo = require('./package.json') 12 | const { VueLoaderPlugin } = require('vue-loader') 13 | const isDev = process.env.NODE_ENV === 'development' 14 | const ESLintPlugin = require('eslint-webpack-plugin'); 15 | 16 | const IsCssExtract = !isDev 17 | 18 | if (!isDev) { 19 | let version = packageInfo.version.split('.'); 20 | version[version.length - 1] = parseInt(version[version.length - 1], 10) + 1; 21 | packageInfo.version = version.join('.'); 22 | fs.writeFileSync('./package.json', JSON.stringify(packageInfo, null, 2), { flags: 'utf8' }) 23 | } 24 | 25 | 26 | module.exports = { 27 | mode: process.env.NODE_ENV, 28 | devtool: isDev ? 'eval-source-map' : 'source-map', 29 | 30 | entry: { 31 | docs: [ 32 | path.join(__dirname, 'docs/entry.js'), 33 | ], 34 | }, 35 | 36 | output: { 37 | path: __dirname, 38 | publicPath: './', 39 | filename: 'docs/dist/[name].js', 40 | chunkFilename: 'docs/dist/[chunkhash:8].[name].chunk.js', 41 | }, 42 | 43 | resolve: { 44 | modules: [ 45 | path.join(__dirname, 'node_modules'), 46 | path.join(__dirname, 'docs'), 47 | ], 48 | alias: { 49 | // "vue": "@vue/runtime-dom", 50 | "@": path.join(__dirname, 'src'), 51 | "@/": path.join(__dirname, 'src/'), 52 | 'vue-upload-component': path.join(__dirname, isDev ? 'src' : 'dist/vue-upload-component.js'), 53 | }, 54 | extensions: [ 55 | '.js', 56 | '.ts', 57 | '.tsx', 58 | '.json', 59 | '.vue', 60 | '.md', 61 | ], 62 | }, 63 | externals: { 64 | // vue: 'Vue', 65 | // vuex: 'Vuex', 66 | // 'vue-router': 'VueRouter', 67 | // 'vue-i18n': 'VueI18n', 68 | 'marked': 'marked', 69 | 'highlight.js': 'hljs', 70 | 'cropperjs': 'Cropper', 71 | 'crypto-js': 'CryptoJS', 72 | '@xkeshi/image-compressor': 'ImageCompressor', 73 | }, 74 | 75 | // cache: false, 76 | devServer: { 77 | hot: true, 78 | liveReload: true, 79 | allowedHosts: "all", 80 | client: { 81 | logging: 'error', 82 | overlay: true, 83 | }, 84 | 85 | 86 | setupMiddlewares(middlewares, devServer) { 87 | if (!devServer) { 88 | throw new Error('webpack-dev-server is not defined'); 89 | } 90 | let id = 1000000 91 | let put = function(req, res) { 92 | setTimeout(function() { 93 | let rand = Math.random() 94 | if (rand <= 0.1) { 95 | res.status(500) 96 | res.json({ error: 'server', success: false }) 97 | } else if (rand <= 0.25) { 98 | res.status(403) 99 | res.json({ error: 'failure', success: false }) 100 | } else { 101 | res.json({ url: 'https://vuejs.org/images/logo.png?id=' + id, name: 'filename.ext', id: id++, success: true }) 102 | } 103 | }, 200 + parseInt(Math.random() * 4000, 10)) 104 | } 105 | let del = function(req, res) { 106 | res.json({ success: true }) 107 | } 108 | 109 | // Chunk upload 110 | devServer.app.post('/upload/chunk', bodyParser.json(), chunkUpload) 111 | 112 | devServer.app.post('/upload/post', put) 113 | devServer.app.put('/upload/put', put) 114 | devServer.app.post('/upload/delete', del) 115 | devServer.app.delete('/upload/delete', del) 116 | 117 | return middlewares 118 | }, 119 | host: '127.0.0.1', 120 | static: { 121 | directory: __dirname, 122 | }, 123 | 124 | compress: true, 125 | 126 | devMiddleware: { 127 | index: true, 128 | mimeTypes: { phtml: 'text/html' }, 129 | publicPath: '/', 130 | serverSideRender: true, 131 | writeToDisk: false, 132 | }, 133 | 134 | 135 | // noInfo: true, 136 | historyApiFallback: true, 137 | }, 138 | 139 | target: 'web', 140 | 141 | module: { 142 | rules: [{ 143 | test: /\.vue$/, 144 | use: [{ 145 | loader: 'vue-loader', 146 | options: { 147 | compilerOptions: { 148 | whitespace: 'condense', 149 | compatConfig: { 150 | FEATURE_ID_A: false, 151 | FEATURE_ID_B: false 152 | 153 | }, 154 | }, 155 | transpileOptions: { 156 | transforms: { 157 | dangerousTaggedTemplateString: true 158 | } 159 | }, 160 | }, 161 | }], 162 | }, 163 | { 164 | test: /\.html?$/, 165 | use: [{ 166 | loader: 'html-loader', 167 | }] 168 | }, 169 | { 170 | test: /\.(js|jsx)$/, 171 | use: [{ 172 | loader: 'babel-loader', 173 | }, ], 174 | }, 175 | { 176 | test: /\.(md|txt)$/, 177 | use: [{ 178 | loader: 'raw-loader', 179 | }, ] 180 | }, 181 | { 182 | test: /\.tsx?$/, 183 | use: [{ 184 | loader: 'ts-loader', 185 | options: { 186 | appendTsSuffixTo: [/\.(vue|tsx?)$/], 187 | transpileOnly: true, 188 | } 189 | }], 190 | }, 191 | { 192 | test: /(\.css)$/, 193 | use: [{ 194 | loader: 'style-loader', 195 | }, 196 | { 197 | loader: "css-loader", 198 | options: { 199 | importLoaders: 1, 200 | }, 201 | }, 202 | ], 203 | }, 204 | ] 205 | }, 206 | 207 | 208 | plugins: [ 209 | // new webpack.HotModuleReplacementPlugin(), 210 | new webpack.BannerPlugin(`Name: ${packageInfo.name}\nComponent URI: ${packageInfo.homepage}\nVersion: ${packageInfo.version}\nAuthor: ${packageInfo.author}\nLicense: ${packageInfo.license}\nDescription: ${packageInfo.description}`), 211 | new webpack.DefinePlugin({ 212 | 'process.version': JSON.stringify(packageInfo.version), 213 | __VUE_OPTIONS_API__: true, 214 | __VUE_PROD_DEVTOOLS__: true, 215 | }), 216 | 217 | 218 | // new MiniCssExtractPlugin({ 219 | // filename: 'assets/[name].css', 220 | // chunkFilename: 'assets/[name].[id].css', 221 | // }), 222 | new ESLintPlugin(), 223 | new VueLoaderPlugin(), 224 | 225 | new CssMinimizerWebpackPlugin(), 226 | 227 | new HtmlWebpackPlugin({ 228 | filename: 'index.html', 229 | template: path.join(__dirname, 'docs/index.template.html'), 230 | minify: { 231 | collapseWhitespace: true, 232 | collapseInlineTagWhitespace: true, 233 | removeComments: true, 234 | removeRedundantAttributes: true, 235 | removeScriptTypeAttributes: true, 236 | removeStyleLinkTypeAttributes: true, 237 | useShortDoctype: true, 238 | html5: true, 239 | }, 240 | xhtml: true, 241 | inlineSource: '.(js|css)$', 242 | }), 243 | ], 244 | }; -------------------------------------------------------------------------------- /docs/views/examples/Chunk.vue: -------------------------------------------------------------------------------- 1 | 157 | 163 | 164 | 223 | 224 | 245 | 246 | -------------------------------------------------------------------------------- /src/chunk/ChunkUploadHandler.js: -------------------------------------------------------------------------------- 1 | import { 2 | default as request, 3 | createRequest, 4 | sendFormRequest 5 | } from '../utils/request' 6 | 7 | export default class ChunkUploadHandler { 8 | /** 9 | * Constructor 10 | * 11 | * @param {File} file 12 | * @param {Object} options 13 | */ 14 | constructor(file, options) { 15 | this.file = file 16 | this.options = options 17 | this.chunks = [] 18 | this.sessionId = null 19 | this.chunkSize = null 20 | this.speedInterval = null 21 | } 22 | 23 | /** 24 | * Gets the max retries from options 25 | */ 26 | get maxRetries() { 27 | return parseInt(this.options.maxRetries, 10) 28 | } 29 | 30 | /** 31 | * Gets the max number of active chunks being uploaded at once from options 32 | */ 33 | get maxActiveChunks() { 34 | return parseInt(this.options.maxActive, 10) 35 | } 36 | 37 | /** 38 | * Gets the file type 39 | */ 40 | get fileType() { 41 | return this.file.type 42 | } 43 | 44 | /** 45 | * Gets the file size 46 | */ 47 | get fileSize() { 48 | return this.file.size 49 | } 50 | 51 | /** 52 | * Gets the file name 53 | */ 54 | get fileName() { 55 | return this.file.name 56 | } 57 | 58 | /** 59 | * Gets action (url) to upload the file 60 | */ 61 | get action() { 62 | return this.options.action || null 63 | } 64 | 65 | /** 66 | * Gets the body to be merged when sending the request in start phase 67 | */ 68 | get startBody() { 69 | return this.options.startBody || {} 70 | } 71 | 72 | /** 73 | * Gets the body to be merged when sending the request in upload phase 74 | */ 75 | get uploadBody() { 76 | return this.options.uploadBody || {} 77 | } 78 | 79 | /** 80 | * Gets the body to be merged when sending the request in finish phase 81 | */ 82 | get finishBody() { 83 | return this.options.finishBody || {} 84 | } 85 | 86 | /** 87 | * Gets the headers of the requests from options 88 | */ 89 | get headers() { 90 | return this.options.headers || {} 91 | } 92 | 93 | /** 94 | * Whether it's ready to upload files or not 95 | */ 96 | get readyToUpload() { 97 | return !!this.chunks 98 | } 99 | 100 | /** 101 | * Gets the progress of the chunk upload 102 | * - Gets all the completed chunks 103 | * - Gets the progress of all the chunks that are being uploaded 104 | */ 105 | get progress() { 106 | const completedProgress = (this.chunksUploaded.length / this.chunks.length) * 100 107 | const uploadingProgress = this.chunksUploading.reduce((progress, chunk) => { 108 | return progress + ((chunk.progress | 0) / this.chunks.length) 109 | }, 0) 110 | 111 | return Math.min(completedProgress + uploadingProgress, 100) 112 | } 113 | 114 | /** 115 | * Gets all the chunks that are pending to be uploaded 116 | */ 117 | get chunksToUpload() { 118 | return this.chunks.filter(chunk => { 119 | return !chunk.active && !chunk.uploaded 120 | }) 121 | } 122 | 123 | /** 124 | * Whether there are chunks to upload or not 125 | */ 126 | get hasChunksToUpload() { 127 | return this.chunksToUpload.length > 0 128 | } 129 | 130 | /** 131 | * Gets all the chunks that are uploading 132 | */ 133 | get chunksUploading() { 134 | return this.chunks.filter(chunk => { 135 | return !!chunk.xhr && !!chunk.active 136 | }) 137 | } 138 | 139 | /** 140 | * Gets all the chunks that have finished uploading 141 | */ 142 | get chunksUploaded() { 143 | return this.chunks.filter(chunk => { 144 | return !!chunk.uploaded 145 | }) 146 | } 147 | 148 | /** 149 | * Creates all the chunks in the initial state 150 | */ 151 | createChunks() { 152 | this.chunks = [] 153 | 154 | let start = 0 155 | let end = this.chunkSize 156 | while (start < this.fileSize) { 157 | this.chunks.push({ 158 | blob: this.file.file.slice(start, end), 159 | startOffset: start, 160 | active: false, 161 | retries: this.maxRetries 162 | }) 163 | start = end 164 | end = start + this.chunkSize 165 | } 166 | } 167 | 168 | /** 169 | * Updates the progress of the file with the handler's progress 170 | */ 171 | updateFileProgress() { 172 | this.file.progress = this.progress 173 | } 174 | 175 | /** 176 | * Paues the upload process 177 | * - Stops all active requests 178 | * - Sets the file not active 179 | */ 180 | pause() { 181 | this.file.active = false 182 | this.stopChunks() 183 | } 184 | 185 | /** 186 | * Stops all the current chunks 187 | */ 188 | stopChunks() { 189 | this.chunksUploading.forEach(chunk => { 190 | chunk.xhr.abort() 191 | chunk.active = false 192 | }) 193 | 194 | this.stopSpeedCalc() 195 | } 196 | 197 | /** 198 | * Resumes the file upload 199 | * - Sets the file active 200 | * - Starts the following chunks 201 | */ 202 | resume() { 203 | this.file.active = true 204 | this.startChunking() 205 | } 206 | 207 | /** 208 | * Starts the file upload 209 | * 210 | * @returns Promise 211 | * - resolve The file was uploaded 212 | * - reject The file upload failed 213 | */ 214 | upload() { 215 | this.promise = new Promise((resolve, reject) => { 216 | this.resolve = resolve 217 | this.reject = reject 218 | }) 219 | this.start() 220 | 221 | return this.promise 222 | } 223 | 224 | /** 225 | * Start phase 226 | * Sends a request to the backend to initialise the chunks 227 | */ 228 | start() { 229 | request({ 230 | method: 'POST', 231 | headers: { ...this.headers, 'Content-Type': 'application/json'}, 232 | url: this.action, 233 | body: Object.assign(this.startBody, { 234 | phase: 'start', 235 | mime_type: this.fileType, 236 | size: this.fileSize, 237 | name: this.fileName 238 | }) 239 | }).then(res => { 240 | if (res.status !== 'success') { 241 | this.file.response = res 242 | return this.reject('server') 243 | } 244 | 245 | this.sessionId = res.data.session_id 246 | this.chunkSize = res.data.end_offset 247 | 248 | this.createChunks() 249 | this.startChunking() 250 | }).catch(res => { 251 | this.file.response = res 252 | this.reject('server') 253 | }) 254 | } 255 | 256 | /** 257 | * Starts to upload chunks 258 | */ 259 | startChunking() { 260 | for (let i = 0; i < this.maxActiveChunks; i++) { 261 | this.uploadNextChunk() 262 | } 263 | 264 | this.startSpeedCalc() 265 | } 266 | 267 | /** 268 | * Uploads the next chunk 269 | * - Won't do anything if the process is paused 270 | * - Will start finish phase if there are no more chunks to upload 271 | */ 272 | uploadNextChunk() { 273 | if (this.file.active) { 274 | if (this.hasChunksToUpload) { 275 | return this.uploadChunk(this.chunksToUpload[0]) 276 | } 277 | 278 | if (this.chunksUploading.length === 0) { 279 | return this.finish() 280 | } 281 | } 282 | } 283 | 284 | /** 285 | * Uploads a chunk 286 | * - Sends the chunk to the backend 287 | * - Sets the chunk as uploaded if everything went well 288 | * - Decreases the number of retries if anything went wrong 289 | * - Fails if there are no more retries 290 | * 291 | * @param {Object} chunk 292 | */ 293 | uploadChunk(chunk) { 294 | chunk.progress = 0 295 | chunk.active = true 296 | this.updateFileProgress() 297 | chunk.xhr = createRequest({ 298 | method: 'POST', 299 | headers: this.headers, 300 | url: this.action 301 | }) 302 | 303 | chunk.xhr.upload.addEventListener('progress', function (evt) { 304 | if (evt.lengthComputable) { 305 | chunk.progress = Math.round(evt.loaded / evt.total * 100) 306 | } 307 | }, false) 308 | 309 | sendFormRequest(chunk.xhr, Object.assign(this.uploadBody, { 310 | phase: 'upload', 311 | session_id: this.sessionId, 312 | start_offset: chunk.startOffset, 313 | chunk: chunk.blob 314 | })).then(res => { 315 | chunk.active = false 316 | if (res.status === 'success') { 317 | chunk.uploaded = true 318 | } else { 319 | if (chunk.retries-- <= 0) { 320 | this.stopChunks() 321 | return this.reject('upload') 322 | } 323 | } 324 | 325 | this.uploadNextChunk() 326 | }).catch(() => { 327 | chunk.active = false 328 | if (chunk.retries-- <= 0) { 329 | this.stopChunks() 330 | return this.reject('upload') 331 | } 332 | 333 | this.uploadNextChunk() 334 | }) 335 | } 336 | 337 | /** 338 | * Finish phase 339 | * Sends a request to the backend to finish the process 340 | */ 341 | finish() { 342 | this.updateFileProgress() 343 | this.stopSpeedCalc() 344 | 345 | request({ 346 | method: 'POST', 347 | headers: { ...this.headers, 'Content-Type': 'application/json' }, 348 | url: this.action, 349 | body: Object.assign(this.finishBody, { 350 | phase: 'finish', 351 | session_id: this.sessionId 352 | }) 353 | }).then(res => { 354 | this.file.response = res 355 | if (res.status !== 'success') { 356 | return this.reject('server') 357 | } 358 | 359 | this.resolve(res) 360 | }).catch(res => { 361 | this.file.response = res 362 | this.reject('server') 363 | }) 364 | } 365 | 366 | 367 | /** 368 | * Sets an interval to calculate and 369 | * set upload speed every 3 seconds 370 | */ 371 | startSpeedCalc() { 372 | this.file.speed = 0 373 | let lastUploadedBytes = 0 374 | if (!this.speedInterval) { 375 | this.speedInterval = window.setInterval(() => { 376 | let uploadedBytes = (this.progress / 100) * this.fileSize 377 | this.file.speed = (uploadedBytes - lastUploadedBytes) 378 | lastUploadedBytes = uploadedBytes 379 | }, 1000) 380 | } 381 | } 382 | 383 | /** 384 | * Removes the upload speed interval 385 | */ 386 | stopSpeedCalc() { 387 | this.speedInterval && window.clearInterval(this.speedInterval) 388 | this.speedInterval = null 389 | this.file.speed = 0 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /src/FileUpload.vue.d.ts: -------------------------------------------------------------------------------- 1 | import { PropType } from "vue"; 2 | export interface ChunkOptions { 3 | headers: { 4 | [key: string]: any; 5 | }; 6 | action: string; 7 | minSize: number; 8 | maxActive: number; 9 | maxRetries: number; 10 | handler: any; 11 | } 12 | export interface Data { 13 | active: boolean; 14 | dropActive: boolean; 15 | dropElementActive: boolean; 16 | files: VueUploadItem[]; 17 | maps: { 18 | [key: string]: VueUploadItem; 19 | }; 20 | destroy: boolean; 21 | uploading: number; 22 | features: Features; 23 | dropElement: null | HTMLElement; 24 | dropTimeout: null | number; 25 | reload: boolean; 26 | } 27 | export interface Features { 28 | html5: boolean; 29 | directory: boolean; 30 | drop: boolean; 31 | } 32 | export interface VueUploadItem { 33 | id: string; 34 | readonly fileObject?: boolean; 35 | name?: string; 36 | size?: number; 37 | type?: string; 38 | active?: boolean; 39 | error?: Error | string; 40 | success?: boolean; 41 | postAction?: string; 42 | putAction?: string; 43 | timeout?: number; 44 | data?: { 45 | [key: string]: any; 46 | }; 47 | headers?: { 48 | [key: string]: any; 49 | }; 50 | response?: { 51 | [key: string]: any; 52 | }; 53 | progress?: string; 54 | speed?: 0; 55 | file?: Blob; 56 | xhr?: XMLHttpRequest; 57 | el?: HTMLInputElement; 58 | iframe?: HTMLElement; 59 | [key: string]: any; 60 | } 61 | declare const _default: import("vue").DefineComponent<{ 62 | inputId: { 63 | type: StringConstructor; 64 | }; 65 | name: { 66 | type: StringConstructor; 67 | default: string; 68 | }; 69 | accept: { 70 | type: StringConstructor; 71 | }; 72 | capture: {}; 73 | disabled: { 74 | default: boolean; 75 | }; 76 | multiple: { 77 | type: BooleanConstructor; 78 | default: boolean; 79 | }; 80 | maximum: { 81 | type: NumberConstructor; 82 | }; 83 | addIndex: { 84 | type: (BooleanConstructor | NumberConstructor)[]; 85 | }; 86 | directory: { 87 | type: BooleanConstructor; 88 | }; 89 | createDirectory: { 90 | type: BooleanConstructor; 91 | default: boolean; 92 | }; 93 | postAction: { 94 | type: StringConstructor; 95 | }; 96 | putAction: { 97 | type: StringConstructor; 98 | }; 99 | customAction: { 100 | type: PropType<(file: VueUploadItem, self: any) => Promise>; 101 | }; 102 | headers: { 103 | type: PropType<{ 104 | [key: string]: any; 105 | }>; 106 | default: () => {}; 107 | }; 108 | data: { 109 | type: PropType<{ 110 | [key: string]: any; 111 | }>; 112 | default: () => {}; 113 | }; 114 | timeout: { 115 | type: NumberConstructor; 116 | default: number; 117 | }; 118 | drop: { 119 | type: PropType; 120 | default: () => boolean; 121 | }; 122 | dropDirectory: { 123 | type: BooleanConstructor; 124 | default: boolean; 125 | }; 126 | size: { 127 | type: NumberConstructor; 128 | default: number; 129 | }; 130 | extensions: { 131 | type: PropType; 132 | default: () => never[]; 133 | }; 134 | modelValue: { 135 | type: PropType; 136 | default: () => never[]; 137 | }; 138 | thread: { 139 | type: NumberConstructor; 140 | default: number; 141 | }; 142 | chunkEnabled: { 143 | type: BooleanConstructor; 144 | default: boolean; 145 | }; 146 | chunk: { 147 | type: PropType<{ 148 | headers?: { 149 | [key: string]: any; 150 | } | undefined; 151 | action?: string | undefined; 152 | minSize?: number | undefined; 153 | maxActive?: number | undefined; 154 | maxRetries?: number | undefined; 155 | handler?: any; 156 | }>; 157 | default: () => ChunkOptions; 158 | }; 159 | }, unknown, Data, { 160 | /** 161 | * uploading 正在上传的线程 162 | * @return {[type]} [description] 163 | */ 164 | /** 165 | * uploaded 文件列表是否全部已上传 166 | * @return {[type]} [description] 167 | */ 168 | uploaded(): boolean; 169 | chunkOptions(): ChunkOptions; 170 | className(): Array; 171 | forId(): string; 172 | iMaximum(): number; 173 | iExtensions(): RegExp | undefined; 174 | iDirectory(): any; 175 | }, { 176 | newId(): string; 177 | clear(): true; 178 | get(id: string | VueUploadItem): VueUploadItem | false; 179 | add(_files: VueUploadItem | Blob | Array, index?: number | boolean | undefined): VueUploadItem | VueUploadItem[] | undefined; 180 | addInputFile(el: HTMLInputElement): Promise; 181 | addDataTransfer(dataTransfer: DataTransfer): Promise; 182 | getFileSystemEntry(entry: Array | File | FileSystemEntry, path?: string): Promise; 183 | replace(id1: VueUploadItem | string, id2: VueUploadItem | string): boolean; 184 | remove(id: VueUploadItem | string): VueUploadItem | false; 185 | update(id: VueUploadItem | string, data: { 186 | [key: string]: any; 187 | }): VueUploadItem | false; 188 | emitFilter(newFile: VueUploadItem | undefined, oldFile: VueUploadItem | undefined): boolean; 189 | emitFile(newFile: VueUploadItem | undefined, oldFile: VueUploadItem | undefined): void; 190 | emitInput(): void; 191 | upload(id: VueUploadItem | string): Promise; 192 | /** 193 | * Whether this file should be uploaded using chunk upload or not 194 | * 195 | * @param Object file 196 | */ 197 | shouldUseChunkUpload(file: VueUploadItem): boolean | 0 | undefined; 198 | /** 199 | * Upload a file using Chunk method 200 | * 201 | * @param File file 202 | */ 203 | uploadChunk(file: VueUploadItem): Promise; 204 | uploadPut(file: VueUploadItem): Promise; 205 | uploadHtml5(file: VueUploadItem): Promise; 206 | uploadXhr(xhr: XMLHttpRequest, ufile: VueUploadItem | undefined | false, body: FormData | Blob): Promise; 207 | uploadHtml4(ufile: VueUploadItem | undefined | false): Promise; 208 | watchActive(active: boolean): void; 209 | watchDrop(newDrop: boolean | string | HTMLElement | null, oldDrop?: boolean | string | HTMLElement | undefined): void; 210 | watchDropActive(newDropActive: boolean, oldDropActive?: boolean | undefined): void; 211 | onDocumentDragenter(e: DragEvent): void; 212 | onDocumentDragleave(e: DragEvent): void; 213 | onDocumentDragover(): void; 214 | onDocumentDrop(): void; 215 | onDragenter(e: DragEvent): void; 216 | onDragleave(e: DragEvent): void; 217 | onDragover(e: DragEvent): void; 218 | onDrop(e: DragEvent): void; 219 | inputOnChange(e: Event): Promise; 220 | isRelatedTargetSupported(): boolean; 221 | }, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("update:modelValue" | "input-filter" | "input-file")[], "update:modelValue" | "input-filter" | "input-file", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly Promise>; 261 | }; 262 | headers: { 263 | type: PropType<{ 264 | [key: string]: any; 265 | }>; 266 | default: () => {}; 267 | }; 268 | data: { 269 | type: PropType<{ 270 | [key: string]: any; 271 | }>; 272 | default: () => {}; 273 | }; 274 | timeout: { 275 | type: NumberConstructor; 276 | default: number; 277 | }; 278 | drop: { 279 | type: PropType; 280 | default: () => boolean; 281 | }; 282 | dropDirectory: { 283 | type: BooleanConstructor; 284 | default: boolean; 285 | }; 286 | size: { 287 | type: NumberConstructor; 288 | default: number; 289 | }; 290 | extensions: { 291 | type: PropType; 292 | default: () => never[]; 293 | }; 294 | modelValue: { 295 | type: PropType; 296 | default: () => never[]; 297 | }; 298 | thread: { 299 | type: NumberConstructor; 300 | default: number; 301 | }; 302 | chunkEnabled: { 303 | type: BooleanConstructor; 304 | default: boolean; 305 | }; 306 | chunk: { 307 | type: PropType<{ 308 | headers?: { 309 | [key: string]: any; 310 | } | undefined; 311 | action?: string | undefined; 312 | minSize?: number | undefined; 313 | maxActive?: number | undefined; 314 | maxRetries?: number | undefined; 315 | handler?: any; 316 | }>; 317 | default: () => ChunkOptions; 318 | }; 319 | }>> & { 320 | "onUpdate:modelValue"?: ((...args: any[]) => any) | undefined; 321 | "onInput-filter"?: ((...args: any[]) => any) | undefined; 322 | "onInput-file"?: ((...args: any[]) => any) | undefined; 323 | }, { 324 | name: string; 325 | size: number; 326 | timeout: number; 327 | data: { 328 | [key: string]: any; 329 | }; 330 | headers: { 331 | [key: string]: any; 332 | }; 333 | drop: string | boolean | HTMLElement | null; 334 | modelValue: VueUploadItem[]; 335 | disabled: boolean; 336 | multiple: boolean; 337 | directory: boolean; 338 | createDirectory: boolean; 339 | dropDirectory: boolean; 340 | extensions: string | RegExp | string[]; 341 | thread: number; 342 | chunkEnabled: boolean; 343 | chunk: { 344 | headers?: { 345 | [key: string]: any; 346 | } | undefined; 347 | action?: string | undefined; 348 | minSize?: number | undefined; 349 | maxActive?: number | undefined; 350 | maxRetries?: number | undefined; 351 | handler?: any; 352 | }; 353 | }>; 354 | export default _default; 355 | -------------------------------------------------------------------------------- /dist/vue-upload-component.d.ts: -------------------------------------------------------------------------------- 1 | import { PropType } from "vue"; 2 | export interface ChunkOptions { 3 | headers: { 4 | [key: string]: any; 5 | }; 6 | action: string; 7 | minSize: number; 8 | maxActive: number; 9 | maxRetries: number; 10 | handler: any; 11 | } 12 | export interface Data { 13 | active: boolean; 14 | dropActive: boolean; 15 | dropElementActive: boolean; 16 | files: VueUploadItem[]; 17 | maps: { 18 | [key: string]: VueUploadItem; 19 | }; 20 | destroy: boolean; 21 | uploading: number; 22 | features: Features; 23 | dropElement: null | HTMLElement; 24 | dropTimeout: null | number; 25 | reload: boolean; 26 | } 27 | export interface Features { 28 | html5: boolean; 29 | directory: boolean; 30 | drop: boolean; 31 | } 32 | export interface VueUploadItem { 33 | id: string; 34 | readonly fileObject?: boolean; 35 | name?: string; 36 | size?: number; 37 | type?: string; 38 | active?: boolean; 39 | error?: Error | string; 40 | success?: boolean; 41 | postAction?: string; 42 | putAction?: string; 43 | timeout?: number; 44 | data?: { 45 | [key: string]: any; 46 | }; 47 | headers?: { 48 | [key: string]: any; 49 | }; 50 | response?: { 51 | [key: string]: any; 52 | }; 53 | progress?: string; 54 | speed?: 0; 55 | file?: Blob; 56 | xhr?: XMLHttpRequest; 57 | el?: HTMLInputElement; 58 | iframe?: HTMLElement; 59 | [key: string]: any; 60 | } 61 | declare const _default: import("vue").DefineComponent<{ 62 | inputId: { 63 | type: StringConstructor; 64 | }; 65 | name: { 66 | type: StringConstructor; 67 | default: string; 68 | }; 69 | accept: { 70 | type: StringConstructor; 71 | }; 72 | capture: {}; 73 | disabled: { 74 | default: boolean; 75 | }; 76 | multiple: { 77 | type: BooleanConstructor; 78 | default: boolean; 79 | }; 80 | maximum: { 81 | type: NumberConstructor; 82 | }; 83 | addIndex: { 84 | type: (BooleanConstructor | NumberConstructor)[]; 85 | }; 86 | directory: { 87 | type: BooleanConstructor; 88 | }; 89 | createDirectory: { 90 | type: BooleanConstructor; 91 | default: boolean; 92 | }; 93 | postAction: { 94 | type: StringConstructor; 95 | }; 96 | putAction: { 97 | type: StringConstructor; 98 | }; 99 | customAction: { 100 | type: PropType<(file: VueUploadItem, self: any) => Promise>; 101 | }; 102 | headers: { 103 | type: PropType<{ 104 | [key: string]: any; 105 | }>; 106 | default: () => {}; 107 | }; 108 | data: { 109 | type: PropType<{ 110 | [key: string]: any; 111 | }>; 112 | default: () => {}; 113 | }; 114 | timeout: { 115 | type: NumberConstructor; 116 | default: number; 117 | }; 118 | drop: { 119 | type: PropType; 120 | default: () => boolean; 121 | }; 122 | dropDirectory: { 123 | type: BooleanConstructor; 124 | default: boolean; 125 | }; 126 | size: { 127 | type: NumberConstructor; 128 | default: number; 129 | }; 130 | extensions: { 131 | type: PropType; 132 | default: () => never[]; 133 | }; 134 | modelValue: { 135 | type: PropType; 136 | default: () => never[]; 137 | }; 138 | thread: { 139 | type: NumberConstructor; 140 | default: number; 141 | }; 142 | chunkEnabled: { 143 | type: BooleanConstructor; 144 | default: boolean; 145 | }; 146 | chunk: { 147 | type: PropType<{ 148 | headers?: { 149 | [key: string]: any; 150 | } | undefined; 151 | action?: string | undefined; 152 | minSize?: number | undefined; 153 | maxActive?: number | undefined; 154 | maxRetries?: number | undefined; 155 | handler?: any; 156 | }>; 157 | default: () => ChunkOptions; 158 | }; 159 | }, unknown, Data, { 160 | /** 161 | * uploading 正在上传的线程 162 | * @return {[type]} [description] 163 | */ 164 | /** 165 | * uploaded 文件列表是否全部已上传 166 | * @return {[type]} [description] 167 | */ 168 | uploaded(): boolean; 169 | chunkOptions(): ChunkOptions; 170 | className(): Array; 171 | forId(): string; 172 | iMaximum(): number; 173 | iExtensions(): RegExp | undefined; 174 | iDirectory(): any; 175 | }, { 176 | newId(): string; 177 | clear(): true; 178 | get(id: string | VueUploadItem): VueUploadItem | false; 179 | add(_files: VueUploadItem | Blob | Array, index?: number | boolean | undefined): VueUploadItem | VueUploadItem[] | undefined; 180 | addInputFile(el: HTMLInputElement): Promise; 181 | addDataTransfer(dataTransfer: DataTransfer): Promise; 182 | getFileSystemEntry(entry: Array | File | FileSystemEntry, path?: string): Promise; 183 | replace(id1: VueUploadItem | string, id2: VueUploadItem | string): boolean; 184 | remove(id: VueUploadItem | string): VueUploadItem | false; 185 | update(id: VueUploadItem | string, data: { 186 | [key: string]: any; 187 | }): VueUploadItem | false; 188 | emitFilter(newFile: VueUploadItem | undefined, oldFile: VueUploadItem | undefined): boolean; 189 | emitFile(newFile: VueUploadItem | undefined, oldFile: VueUploadItem | undefined): void; 190 | emitInput(): void; 191 | upload(id: VueUploadItem | string): Promise; 192 | /** 193 | * Whether this file should be uploaded using chunk upload or not 194 | * 195 | * @param Object file 196 | */ 197 | shouldUseChunkUpload(file: VueUploadItem): boolean | 0 | undefined; 198 | /** 199 | * Upload a file using Chunk method 200 | * 201 | * @param File file 202 | */ 203 | uploadChunk(file: VueUploadItem): Promise; 204 | uploadPut(file: VueUploadItem): Promise; 205 | uploadHtml5(file: VueUploadItem): Promise; 206 | uploadXhr(xhr: XMLHttpRequest, ufile: VueUploadItem | undefined | false, body: FormData | Blob): Promise; 207 | uploadHtml4(ufile: VueUploadItem | undefined | false): Promise; 208 | watchActive(active: boolean): void; 209 | watchDrop(newDrop: boolean | string | HTMLElement | null, oldDrop?: boolean | string | HTMLElement | undefined): void; 210 | watchDropActive(newDropActive: boolean, oldDropActive?: boolean | undefined): void; 211 | onDocumentDragenter(e: DragEvent): void; 212 | onDocumentDragleave(e: DragEvent): void; 213 | onDocumentDragover(): void; 214 | onDocumentDrop(): void; 215 | onDragenter(e: DragEvent): void; 216 | onDragleave(e: DragEvent): void; 217 | onDragover(e: DragEvent): void; 218 | onDrop(e: DragEvent): void; 219 | inputOnChange(e: Event): Promise; 220 | isRelatedTargetSupported(): boolean; 221 | }, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("update:modelValue" | "input-filter" | "input-file")[], "update:modelValue" | "input-filter" | "input-file", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly Promise>; 261 | }; 262 | headers: { 263 | type: PropType<{ 264 | [key: string]: any; 265 | }>; 266 | default: () => {}; 267 | }; 268 | data: { 269 | type: PropType<{ 270 | [key: string]: any; 271 | }>; 272 | default: () => {}; 273 | }; 274 | timeout: { 275 | type: NumberConstructor; 276 | default: number; 277 | }; 278 | drop: { 279 | type: PropType; 280 | default: () => boolean; 281 | }; 282 | dropDirectory: { 283 | type: BooleanConstructor; 284 | default: boolean; 285 | }; 286 | size: { 287 | type: NumberConstructor; 288 | default: number; 289 | }; 290 | extensions: { 291 | type: PropType; 292 | default: () => never[]; 293 | }; 294 | modelValue: { 295 | type: PropType; 296 | default: () => never[]; 297 | }; 298 | thread: { 299 | type: NumberConstructor; 300 | default: number; 301 | }; 302 | chunkEnabled: { 303 | type: BooleanConstructor; 304 | default: boolean; 305 | }; 306 | chunk: { 307 | type: PropType<{ 308 | headers?: { 309 | [key: string]: any; 310 | } | undefined; 311 | action?: string | undefined; 312 | minSize?: number | undefined; 313 | maxActive?: number | undefined; 314 | maxRetries?: number | undefined; 315 | handler?: any; 316 | }>; 317 | default: () => ChunkOptions; 318 | }; 319 | }>> & { 320 | "onUpdate:modelValue"?: ((...args: any[]) => any) | undefined; 321 | "onInput-filter"?: ((...args: any[]) => any) | undefined; 322 | "onInput-file"?: ((...args: any[]) => any) | undefined; 323 | }, { 324 | name: string; 325 | size: number; 326 | timeout: number; 327 | data: { 328 | [key: string]: any; 329 | }; 330 | headers: { 331 | [key: string]: any; 332 | }; 333 | drop: string | boolean | HTMLElement | null; 334 | modelValue: VueUploadItem[]; 335 | disabled: boolean; 336 | multiple: boolean; 337 | directory: boolean; 338 | createDirectory: boolean; 339 | dropDirectory: boolean; 340 | extensions: string | RegExp | string[]; 341 | thread: number; 342 | chunkEnabled: boolean; 343 | chunk: { 344 | headers?: { 345 | [key: string]: any; 346 | } | undefined; 347 | action?: string | undefined; 348 | minSize?: number | undefined; 349 | maxActive?: number | undefined; 350 | maxRetries?: number | undefined; 351 | handler?: any; 352 | }; 353 | }>; 354 | export default _default; 355 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /dist/vue-upload-component.esm.min.js: -------------------------------------------------------------------------------- 1 | import{defineComponent as e,openBlock as t,createElementBlock as r,normalizeClass as n,renderSlot as i,createElementVNode as o,createCommentVNode as s}from"vue";function a(e){return function(e){if(Array.isArray(e))return u(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return u(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);"Object"===r&&e.constructor&&(r=e.constructor.name);if("Map"===r||"Set"===r)return Array.from(e);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return u(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function u(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=new Array(t);r=200&&e.status<300){var t;try{t=JSON.parse(e.response)}catch(r){t=e.response}r(t)}else n(e.response)},e.onerror=function(){return n(e.response)},e.send(JSON.stringify(t))}))}(y(e),e.body)}var b={headers:{},action:"",minSize:1048576,maxActive:3,maxRetries:5,handler:function(){function e(t,r){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.file=t,this.options=r,this.chunks=[],this.sessionId=null,this.chunkSize=null,this.speedInterval=null}var t,r,n;return t=e,(r=[{key:"maxRetries",get:function(){return parseInt(this.options.maxRetries,10)}},{key:"maxActiveChunks",get:function(){return parseInt(this.options.maxActive,10)}},{key:"fileType",get:function(){return this.file.type}},{key:"fileSize",get:function(){return this.file.size}},{key:"fileName",get:function(){return this.file.name}},{key:"action",get:function(){return this.options.action||null}},{key:"startBody",get:function(){return this.options.startBody||{}}},{key:"uploadBody",get:function(){return this.options.uploadBody||{}}},{key:"finishBody",get:function(){return this.options.finishBody||{}}},{key:"headers",get:function(){return this.options.headers||{}}},{key:"readyToUpload",get:function(){return!!this.chunks}},{key:"progress",get:function(){var e=this,t=this.chunksUploaded.length/this.chunks.length*100,r=this.chunksUploading.reduce((function(t,r){return t+(0|r.progress)/e.chunks.length}),0);return Math.min(t+r,100)}},{key:"chunksToUpload",get:function(){return this.chunks.filter((function(e){return!e.active&&!e.uploaded}))}},{key:"hasChunksToUpload",get:function(){return this.chunksToUpload.length>0}},{key:"chunksUploading",get:function(){return this.chunks.filter((function(e){return!!e.xhr&&!!e.active}))}},{key:"chunksUploaded",get:function(){return this.chunks.filter((function(e){return!!e.uploaded}))}},{key:"createChunks",value:function(){this.chunks=[];for(var e=0,t=this.chunkSize;e=200&&e.status<300){var r;try{r=JSON.parse(e.response)}catch(t){r=e.response}t(r)}else n(e.response)},e.onerror=function(){return n(e.response)},e.send(r)}))}(e.xhr,Object.assign(this.uploadBody,{phase:"upload",session_id:this.sessionId,start_offset:e.startOffset,chunk:e.blob})).then((function(r){if(e.active=!1,"success"===r.status)e.uploaded=!0;else if(e.retries--<=0)return t.stopChunks(),t.reject("upload");t.uploadNextChunk()})).catch((function(){if(e.active=!1,e.retries--<=0)return t.stopChunks(),t.reject("upload");t.uploadNextChunk()}))}},{key:"finish",value:function(){var e=this;this.updateFileProgress(),this.stopSpeedCalc(),g({method:"POST",headers:p(p({},this.headers),{},{"Content-Type":"application/json"}),url:this.action,body:Object.assign(this.finishBody,{phase:"finish",session_id:this.sessionId})}).then((function(t){if(e.file.response=t,"success"!==t.status)return e.reject("server");e.resolve(t)})).catch((function(t){e.file.response=t,e.reject("server")}))}},{key:"startSpeedCalc",value:function(){var e=this;this.file.speed=0;var t=0;this.speedInterval||(this.speedInterval=window.setInterval((function(){var r=e.progress/100*e.fileSize;e.file.speed=r-t,t=r}),1e3))}},{key:"stopSpeedCalc",value:function(){this.speedInterval&&window.clearInterval(this.speedInterval),this.speedInterval=null,this.file.speed=0}}])&&m(t.prototype,r),n&&m(t,n),Object.defineProperty(t,"prototype",{writable:!1}),e}()},k=e({compatConfig:{MODE:3},props:{inputId:{type:String},name:{type:String,default:"file"},accept:{type:String},capture:{},disabled:{default:!1},multiple:{type:Boolean,default:!1},maximum:{type:Number},addIndex:{type:[Boolean,Number]},directory:{type:Boolean},createDirectory:{type:Boolean,default:!1},postAction:{type:String},putAction:{type:String},customAction:{type:Function},headers:{type:Object,default:function(){return{}}},data:{type:Object,default:function(){return{}}},timeout:{type:Number,default:0},drop:{type:[Boolean,String,HTMLElement],default:function(){return!1}},dropDirectory:{type:Boolean,default:!0},size:{type:Number,default:0},extensions:{type:[RegExp,String,Array],default:function(){return[]}},modelValue:{type:Array,default:function(){return[]}},thread:{type:Number,default:1},chunkEnabled:{type:Boolean,default:!1},chunk:{type:Object,default:function(){return b}}},emits:["update:modelValue","input-filter","input-file"],data:function(){return{files:this.modelValue,features:{html5:!0,directory:!1,drop:!1},active:!1,dropActive:!1,dropElementActive:!1,uploading:0,destroy:!1,maps:{},dropElement:null,dropTimeout:null,reload:!1}},mounted:function(){var e=this,t=document.createElement("input");if(t.type="file",t.multiple=!0,window.FormData&&t.files?("boolean"!=typeof t.webkitdirectory&&"boolean"!=typeof t.directory||(this.features.directory=!0),this.features.html5&&void 0!==t.ondrop&&this.isRelatedTargetSupported()&&(this.features.drop=!0)):this.features.html5=!1,this.maps={},this.files)for(var r=0;r1&&n.length+this.files.length>=this.iMaximum)break;if(n.push(o),1===this.iMaximum)break}}if(n.length){var u;if(1===this.iMaximum&&this.clear(),!0===t||0===t)u=n.concat(this.files);else if(t){var l;(l=u=this.files.concat([])).splice.apply(l,[t,0].concat(a(n)))}else u=this.files.concat(n);this.files=u;var d=0;!0===t||0===t?d=0:t?t>=0?d=t+n.length>this.files.length?this.files.length-n.length:t:(d=this.files.length-n.length+t)<0&&(d=0):d=this.files.length-n.length,n=this.files.slice(d,d+n.length);for(var h=0;h0&&u.length>=a));l++);return Promise.resolve(this.add(u))}return Promise.resolve([])},getFileSystemEntry:function(e){var t=this,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return new Promise((function(n){var i=t.iMaximum;if(e)if(e instanceof Array){var o=[];(function s(u){var l=e[u];if(!l||i>0&&o.length>=i)return n(o);t.getFileSystemEntry(l,r).then((function(e){o.push.apply(o,a(e)),s(u+1)}))})(0)}else{if(e instanceof Blob)n([{id:"",size:e.size,name:r+e.name,type:e.type,file:e}]);else if(e.isFile)e.file((function(e){n([{id:"",size:e.size,name:r+e.name,type:e.type,file:e}])}));else if(e.isDirectory&&t.dropDirectory){var s=e,u=[];t.createDirectory&&u.push({id:"",name:r+s.name,size:0,type:"text/directory",file:new File([],r+s.name,{type:"text/directory"})});var l=s.createReader();(function e(){l.readEntries((function(o){!function l(c){return!o[c]&&0===c||i>0&&u.length>=i?n(u):o[c]?void t.getFileSystemEntry(o[c],r+s.name+"/").then((function(e){u.push.apply(u,a(e)),l(c+1)})):e()}(0)}))})()}else n([])}else n([])}))},replace:function(e,t){var r=this.get(e),n=this.get(t);if(!r||!n||r===n)return!1;var i=this.files.concat([]),o=i.indexOf(r),s=i.indexOf(n);return-1!==o&&-1!==s&&(i[o]=n,i[s]=r,this.files=i,this.emitInput(),!0)},remove:function(e){var t=this.get(e);if(t){if(this.emitFilter(void 0,t))return!1;var r=this.files.concat([]),n=r.indexOf(t);if(-1===n)return console.error("remove",t),!1;r.splice(n,1),this.files=r,delete this.maps[t.id],this.emitInput(),this.emitFile(void 0,t)}return t},update:function(e,t){var r=this.get(e);if(r){var n=c(c({},r),t);if(!r.fileObject||!r.active||n.active||n.error||n.success||(n.error="abort"),this.emitFilter(n,r))return!1;var i=this.files.concat([]),o=i.indexOf(r);return-1===o?(console.error("update",r),!1):(i.splice(o,1,n),this.files=i,n=this.files[o],delete this.maps[r.id],this.maps[n.id]=n,this.emitInput(),this.emitFile(n,r),n)}return!1},emitFilter:function(e,t){var r=!1;return this.$emit("input-filter",e,t,(function(){var e=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];return r=e})),r},emitFile:function(e,t){var r,n=this;this.$emit("input-file",e,t),null===(r=e)||void 0===r||!r.fileObject||!e.active||t&&t.active?e&&e.fileObject&&e.active||!t||!t.fileObject||!t.active||this.uploading--:(this.uploading++,this.$nextTick((function(){setTimeout((function(){e&&n.upload(e).then((function(){var t;e&&(e=n.get(e)||void 0),null!==(t=e)&&void 0!==t&&t.fileObject&&n.update(e,{active:!1,success:!e.error})})).catch((function(t){e&&n.update(e,{active:!1,success:!1,error:t.code||t.error||t.message||t})}))}),Math.ceil(50*Math.random()+50))}))),!this.active||Boolean(e)===Boolean(t)&&e.active===t.active||this.watchActive(!0)},emitInput:function(){this.$emit("update:modelValue",this.files)},upload:function(e){var t=this.get(e);if(!t)return Promise.reject(new Error("not_exists"));if(!t.fileObject)return Promise.reject(new Error("file_object"));if(t.error)return t.error instanceof Error?Promise.reject(t.error):Promise.reject(new Error(t.error));if(t.success)return Promise.resolve(t);if(t.name&&this.iExtensions&&"text/directory"!==t.type&&-1===t.name.search(this.iExtensions))return Promise.reject(new Error("extension"));if(this.size>0&&void 0!==t.size&&t.size>=0&&t.size>this.size&&"text/directory"!==t.type)return Promise.reject(new Error("size"));if(this.customAction)return this.customAction(t,this);if(this.features.html5){if(this.shouldUseChunkUpload(t))return this.uploadChunk(t);if(t.putAction)return this.uploadPut(t);if(t.postAction)return this.uploadHtml5(t)}return t.postAction?this.uploadHtml4(t):Promise.reject(new Error("No action configured"))},shouldUseChunkUpload:function(e){return this.chunkEnabled&&!!this.chunkOptions.handler&&e.size&&e.size>this.chunkOptions.minSize},uploadChunk:function(e){var t=this.chunkOptions.handler;return e.chunk=new t(e,this.chunkOptions),e.chunk.upload().then((function(t){return e}))},uploadPut:function(e){var t,r=[];for(var n in e.data)null!=(t=e.data[n])&&r.push(encodeURIComponent(n)+"="+encodeURIComponent(t));var i=e.putAction||"",o=r.length?(-1===i.indexOf("?")?"?":"&")+r.join("&"):"",s=new XMLHttpRequest;return s.open("PUT",i+o),this.uploadXhr(s,e,e.file)},uploadHtml5:function(e){var t,r=new window.FormData;for(var n in e.data)(t=e.data[n])&&"object"===h(t)&&"function"!=typeof t.toString?t instanceof File?r.append(n,t,t.name):r.append(n,JSON.stringify(t)):null!=t&&r.append(n,t);r.append(this.name,e.file,e.name||e.file.name||e.file.filename);var i=new XMLHttpRequest;return i.open("POST",e.postAction||""),this.uploadXhr(i,e,r)},uploadXhr:function(e,t,r){var n=this,i=t,o=0,s=0;e.upload.onprogress=function(e){if(i&&(i=n.get(i),e.lengthComputable&&i&&i.fileObject&&i.active)){var t=Math.round(Date.now()/1e3);t!==o&&(o=t,i=n.update(i,{progress:(e.loaded/e.total*100).toFixed(2),speed:e.loaded-s}),s=e.loaded)}};var a=window.setInterval((function(){var t;if(i&&((i=n.get(i))&&null!==(t=i)&&void 0!==t&&t.fileObject&&!i.success&&!i.error&&i.active))return;a&&(clearInterval(a),a=void 0);try{e.abort(),e.timeout=1}catch(e){}}),100);return new Promise((function(t,o){if(i){var s,u=function(r){if(!s){if(s=!0,a&&(clearInterval(a),a=void 0),!i)return o(new Error("not_exists"));if(!(i=n.get(i)))return o(new Error("not_exists"));if(!i.fileObject)return o(new Error("file_object"));if(i.error)return i.error instanceof Error?o(i.error):o(new Error(i.error));if(!i.active)return o(new Error("abort"));if(i.success)return t(i);var u={};switch(r.type){case"timeout":case"abort":u.error=r.type;break;case"error":e.status?e.status>=500?u.error="server":e.status>=400&&(u.error="denied"):u.error="network";break;default:e.status>=500?u.error="server":e.status>=400?u.error="denied":u.progress="100.00"}if(e.responseText){var l=e.getResponseHeader("Content-Type");l&&-1!==l.indexOf("/json")?u.response=JSON.parse(e.responseText):u.response=e.responseText}return(i=n.update(i,u))?i.error?i.error instanceof Error?o(i.error):o(new Error(i.error)):t(i):o(new Error("abort"))}};for(var l in e.onload=u,e.onerror=u,e.onabort=u,e.ontimeout=u,i.timeout&&(e.timeout=i.timeout),i.headers)e.setRequestHeader(l,i.headers[l]);(i=n.update(i,{xhr:e}))&&e.send(r)}else o(new Error("not_exists"))}))},uploadHtml4:function(e){var t=this,r=e;if(!r)return Promise.reject(new Error("not_exists"));var n=function(e){27===e.keyCode&&e.preventDefault()},i=document.createElement("iframe");i.id="upload-iframe-"+r.id,i.name="upload-iframe-"+r.id,i.src="about:blank",i.setAttribute("style","width:1px;height:1px;top:-999em;position:absolute; margin-top:-999em;");var o=document.createElement("form");for(var s in o.setAttribute("action",r.postAction||""),o.name="upload-form-"+r.id,o.setAttribute("method","POST"),o.setAttribute("target","upload-iframe-"+r.id),o.setAttribute("enctype","multipart/form-data"),r.data){var a=r.data[s];if(a&&"object"===h(a)&&"function"!=typeof a.toString&&(a=JSON.stringify(a)),null!=a){var u=document.createElement("input");u.type="hidden",u.name=s,u.value=a,o.appendChild(u)}}o.appendChild(r.el),document.body.appendChild(i).appendChild(o);return new Promise((function(e,s){setTimeout((function(){if(r){if(!(r=t.update(r,{iframe:i})))return s(new Error("not_exists"));var a,u=window.setInterval((function(){r&&(r=t.get(r))&&r.fileObject&&!r.success&&!r.error&&r.active||(u&&(clearInterval(u),u=void 0),i.onabort({type:r?"abort":"not_exists"}))}),100),l=function(o){var l;if(!a){if(a=!0,u&&(clearInterval(u),u=void 0),document.body.removeEventListener("keydown",n),!r)return s(new Error("not_exists"));if(!(r=t.get(r)))return s(new Error("not_exists"));if(!r.fileObject)return s(new Error("file_object"));if(r.error)return r.error instanceof Error?s(r.error):s(new Error(r.error));if(!r.active)return s(new Error("abort"));if(r.success)return e(r);var c=function(){var e,t;try{i.contentWindow&&(t=i.contentWindow.document)}catch(e){}if(!t)try{t=i.contentDocument?i.contentDocument:i.document}catch(e){t=i.document}return null!==(e=t)&&void 0!==e&&e.body?t.body.innerHTML:null}(),d={};if("string"==typeof o)return s(new Error(o));switch(o.type){case"abort":d.error="abort";break;case"error":r.error?d.error=r.error:d.error=null===c?"network":"denied";break;default:r.error?d.error=r.error:null===c?d.error="network":d.progress="100.00"}if(null!==c){if(c&&"{"===c.substr(0,1)&&"}"===c.substr(c.length-1,1))try{c=JSON.parse(c)}catch(e){}d.response=c}return(r=t.update(r,d))?null!==(l=r)&&void 0!==l&&l.error?r.error instanceof Error?s(r.error):s(new Error(r.error)):e(r):s(new Error("not_exists"))}};i.onload=l,i.onerror=l,i.onabort=l,document.body.addEventListener("keydown",n),o.submit()}else s(new Error("not_exists"))}),50)})).then((function(e){var t;return null==i||null===(t=i.parentNode)||void 0===t||t.removeChild(i),e})).catch((function(e){var t;return null==i||null===(t=i.parentNode)||void 0===t||t.removeChild(i),e}))},watchActive:function(e){for(var t,r=0;t=this.files[r];)if(r++,t.fileObject)if(e&&!this.destroy){if(this.uploading>=this.thread||this.uploading&&!this.features.html5)break;t.active||t.error||t.success||this.update(t,{active:!0})}else t.active&&this.update(t,{active:!1});else;0===this.uploading&&(this.active=!1)},watchDrop:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:void 0;if(this.features.drop&&e!==t){if(this.dropElement)try{document.removeEventListener("dragenter",this.onDocumentDragenter,!1),document.removeEventListener("dragleave",this.onDocumentDragleave,!1),document.removeEventListener("dragover",this.onDocumentDragover,!1),document.removeEventListener("drop",this.onDocumentDrop,!1),this.dropElement.removeEventListener("dragenter",this.onDragenter,!1),this.dropElement.removeEventListener("dragleave",this.onDragleave,!1),this.dropElement.removeEventListener("dragover",this.onDragover,!1),this.dropElement.removeEventListener("drop",this.onDrop,!1)}catch(e){}var r=null;if(e)if("string"==typeof e)r=document.querySelector(e)||this.$root.$el.querySelector(e);else if(!0===e){var n,i;if(!(r=this.$parent.$el)||8===(null===(n=r)||void 0===n?void 0:n.nodeType))(r=this.$root.$el)&&8!==(null===(i=r)||void 0===i?void 0:i.nodeType)||(r=document.body)}else r=e;else;this.dropElement=r,this.dropElement&&(document.addEventListener("dragenter",this.onDocumentDragenter,!1),document.addEventListener("dragleave",this.onDocumentDragleave,!1),document.addEventListener("dragover",this.onDocumentDragover,!1),document.addEventListener("drop",this.onDocumentDrop,!1),this.dropElement.addEventListener("dragenter",this.onDragenter,!1),this.dropElement.addEventListener("dragleave",this.onDragleave,!1),this.dropElement.addEventListener("dragover",this.onDragover,!1),this.dropElement.addEventListener("drop",this.onDrop,!1))}},watchDropActive:function(e,t){e!==t&&(!e&&this.dropElementActive&&(this.dropElementActive=!1),this.dropTimeout&&(clearTimeout(this.dropTimeout),this.dropTimeout=null),e&&(this.dropTimeout=setTimeout(this.onDocumentDrop,1e3)))},onDocumentDragenter:function(e){var t,r;if(!this.dropActive&&e.dataTransfer){var n=e.dataTransfer;null!=n&&null!==(t=n.files)&&void 0!==t&&t.length?this.dropActive=!0:n.types?(n.types.indexOf&&-1!==n.types.indexOf("Files")||null!==(r=n.types)&&void 0!==r&&r.contains&&n.types.contains("Files"))&&(this.dropActive=!0):this.dropActive=!0,this.dropActive&&this.watchDropActive(!0)}},onDocumentDragleave:function(e){this.dropActive&&(e.target===e.explicitOriginalTarget||!e.fromElement&&(e.clientX<=0||e.clientY<=0||e.clientX>=window.innerWidth||e.clientY>=window.innerHeight))&&(this.dropActive=!1,this.watchDropActive(!1))},onDocumentDragover:function(){this.watchDropActive(!0)},onDocumentDrop:function(){this.dropActive=!1,this.watchDropActive(!1)},onDragenter:function(e){this.dropActive&&!this.dropElementActive&&(this.dropElementActive=!0)},onDragleave:function(e){var t;if(this.dropElementActive){var r=e.relatedTarget;if(r)if(null!==(t=this.dropElement)&&void 0!==t&&t.contains)this.dropElement.contains(r)||(this.dropElementActive=!1);else{for(var n=r;n&&n!==this.dropElement;)n=n.parentNode;n!==this.dropElement&&(this.dropElementActive=!1)}else this.dropElementActive=!1}},onDragover:function(e){e.preventDefault()},onDrop:function(e){e.preventDefault(),e.dataTransfer&&this.addDataTransfer(e.dataTransfer)},inputOnChange:async function(e){var t=this;if(!(e.target instanceof HTMLInputElement))return Promise.reject(new Error("not HTMLInputElement"));e.target;var r=function(e){return t.reload=!0,t.$nextTick((function(){t.reload=!1})),e};return this.addInputFile(e.target).then(r).catch(r)},isRelatedTargetSupported:function(){try{return"relatedTarget"in new MouseEvent("mouseout",{relatedTarget:document.body})}catch(e){return!1}}}}),w=["for"],E=["name","id","accept","capture","disabled","webkitdirectory","allowdirs","directory","multiple"];!function(e,t){void 0===t&&(t={});var r=t.insertAt;if(e&&"undefined"!=typeof document){var n=document.head||document.getElementsByTagName("head")[0],i=document.createElement("style");i.type="text/css","top"===r&&n.firstChild?n.insertBefore(i,n.firstChild):n.appendChild(i),i.styleSheet?i.styleSheet.cssText=e:i.appendChild(document.createTextNode(e))}}("\n.file-uploads {\n overflow: hidden;\n position: relative;\n text-align: center;\n display: inline-block;\n}\n.file-uploads.file-uploads-html4 input,\n.file-uploads.file-uploads-html5 label {\n /* background fix ie click */\n background: #fff;\n opacity: 0;\n font-size: 20em;\n z-index: 1;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n position: absolute;\n width: 100%;\n height: 100%;\n}\n.file-uploads.file-uploads-html5 input,\n.file-uploads.file-uploads-html4 label {\n /* background fix ie click */\n position: absolute;\n background: rgba(255, 255, 255, 0);\n overflow: hidden;\n position: fixed;\n width: 1px;\n height: 1px;\n z-index: -1;\n opacity: 0;\n}\n"),k.render=function(e,a,u,l,c,d){return t(),r("span",{class:n(e.className)},[i(e.$slots,"default"),o("label",{for:e.forId},null,8,w),e.reload?s("",!0):(t(),r("input",{key:0,ref:"input",type:"file",name:e.name,id:e.forId,accept:e.accept,capture:e.capture,disabled:e.disabled,webkitdirectory:e.iDirectory,allowdirs:e.iDirectory,directory:e.iDirectory,multiple:e.multiple&&e.features.html5,onChange:a[0]||(a[0]=function(){return e.inputOnChange&&e.inputOnChange.apply(e,arguments)})},null,40,E))],2)};export{k as default}; 2 | //# sourceMappingURL=vue-upload-component.esm.min.js.map 3 | -------------------------------------------------------------------------------- /docs/views/examples/Full.vue: -------------------------------------------------------------------------------- 1 | 335 | 416 | 417 | 715 | -------------------------------------------------------------------------------- /dist/vue-upload-component.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Name: vue-upload-component 3 | Component URI: https://github.com/lian-yue/vue-upload-component#readme 4 | Version: 3.1.16 5 | Author: LianYue 6 | License: Apache-2.0 7 | 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 8 | */ 9 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("vue")):"function"==typeof define&&define.amd?define(["vue"],t):(e="undefined"!=typeof globalThis?globalThis:e||self).VueUploadComponent=t(e.Vue)}(this,(function(e){"use strict";function t(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var r=t(e),n=("undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self&&self,{exports:{}}); 10 | /*! 11 | Name: vue-upload-component 12 | Component URI: https://github.com/lian-yue/vue-upload-component#readme 13 | Version: 3.1.16 14 | Author: LianYue 15 | License: Apache-2.0 16 | 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 17 | */ 18 | return function(e,t){e.exports=function(e){function t(e){return o(e)||i(e)||n(e)||r()}function r(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function n(e,t){if(e){if("string"==typeof e)return s(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?s(e,t):void 0}}function i(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}function o(e){if(Array.isArray(e))return s(e)}function s(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=new Array(t);r=200&&e.status<300){var t;try{t=JSON.parse(e.response)}catch(r){t=e.response}r(t)}else n(e.response)},e.onerror=function(){return n(e.response)},e.send(JSON.stringify(t))}))},b=function(e,t){var r=new FormData;for(var n in t)r.append(n,t[n]);return new Promise((function(t,n){e.onload=function(){if(e.status>=200&&e.status<300){var r;try{r=JSON.parse(e.response)}catch(t){r=e.response}t(r)}else n(e.response)},e.onerror=function(){return n(e.response)},e.send(r)}))};function k(e){var t=y(e);return g(t,e.body)}var w={headers:{},action:"",minSize:1048576,maxActive:3,maxRetries:5,handler:function(){function e(t,r){h(this,e),this.file=t,this.options=r,this.chunks=[],this.sessionId=null,this.chunkSize=null,this.speedInterval=null}return m(e,[{key:"maxRetries",get:function(){return parseInt(this.options.maxRetries,10)}},{key:"maxActiveChunks",get:function(){return parseInt(this.options.maxActive,10)}},{key:"fileType",get:function(){return this.file.type}},{key:"fileSize",get:function(){return this.file.size}},{key:"fileName",get:function(){return this.file.name}},{key:"action",get:function(){return this.options.action||null}},{key:"startBody",get:function(){return this.options.startBody||{}}},{key:"uploadBody",get:function(){return this.options.uploadBody||{}}},{key:"finishBody",get:function(){return this.options.finishBody||{}}},{key:"headers",get:function(){return this.options.headers||{}}},{key:"readyToUpload",get:function(){return!!this.chunks}},{key:"progress",get:function(){var e=this,t=this.chunksUploaded.length/this.chunks.length*100,r=this.chunksUploading.reduce((function(t,r){return t+(0|r.progress)/e.chunks.length}),0);return Math.min(t+r,100)}},{key:"chunksToUpload",get:function(){return this.chunks.filter((function(e){return!e.active&&!e.uploaded}))}},{key:"hasChunksToUpload",get:function(){return this.chunksToUpload.length>0}},{key:"chunksUploading",get:function(){return this.chunks.filter((function(e){return!!e.xhr&&!!e.active}))}},{key:"chunksUploaded",get:function(){return this.chunks.filter((function(e){return!!e.uploaded}))}},{key:"createChunks",value:function(){this.chunks=[];for(var e=0,t=this.chunkSize;e1&&i.length+this.files.length>=this.iMaximum)break;if(i.push(s),1===this.iMaximum)break}}if(i.length){var l;if(1===this.iMaximum&&this.clear(),!0===r||0===r)l=i.concat(this.files);else if(r){var c;(c=l=this.files.concat([])).splice.apply(c,[r,0].concat(t(i)))}else l=this.files.concat(i);this.files=l;var d=0;!0===r||0===r?d=0:r?r>=0?d=r+i.length>this.files.length?this.files.length-i.length:r:(d=this.files.length-i.length+r)<0&&(d=0):d=this.files.length-i.length,i=this.files.slice(d,d+i.length);for(var f=0;f0&&u.length>=a));l++);return Promise.resolve(this.add(u))}return Promise.resolve([])},getFileSystemEntry:function(e){var r=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return new Promise((function(i){var o=r.iMaximum;if(e)if(e instanceof Array){var s=[];!function a(u){var l=e[u];if(!l||o>0&&s.length>=o)return i(s);r.getFileSystemEntry(l,n).then((function(e){s.push.apply(s,t(e)),a(u+1)}))}(0)}else if(e instanceof Blob)i([{id:"",size:e.size,name:n+e.name,type:e.type,file:e}]);else if(e.isFile)e.file((function(e){i([{id:"",size:e.size,name:n+e.name,type:e.type,file:e}])}));else if(e.isDirectory&&r.dropDirectory){var a=e,u=[];r.createDirectory&&u.push({id:"",name:n+a.name,size:0,type:"text/directory",file:new File([],n+a.name,{type:"text/directory"})});var l=a.createReader(),c=function e(){l.readEntries((function(s){!function l(c){return!s[c]&&0===c||o>0&&u.length>=o?i(u):s[c]?void r.getFileSystemEntry(s[c],n+a.name+"/").then((function(e){u.push.apply(u,t(e)),l(c+1)})):e()}(0)}))};c()}else i([]);else i([])}))},replace:function(e,t){var r=this.get(e),n=this.get(t);if(!r||!n||r===n)return!1;var i=this.files.concat([]),o=i.indexOf(r),s=i.indexOf(n);return-1!==o&&-1!==s&&(i[o]=n,i[s]=r,this.files=i,this.emitInput(),!0)},remove:function(e){var t=this.get(e);if(t){if(this.emitFilter(void 0,t))return!1;var r=this.files.concat([]),n=r.indexOf(t);if(-1===n)return console.error("remove",t),!1;r.splice(n,1),this.files=r,delete this.maps[t.id],this.emitInput(),this.emitFile(void 0,t)}return t},update:function(e,t){var r=this.get(e);if(r){var n=u(u({},r),t);if(!r.fileObject||!r.active||n.active||n.error||n.success||(n.error="abort"),this.emitFilter(n,r))return!1;var i=this.files.concat([]),o=i.indexOf(r);return-1===o?(console.error("update",r),!1):(i.splice(o,1,n),this.files=i,n=this.files[o],delete this.maps[r.id],this.maps[n.id]=n,this.emitInput(),this.emitFile(n,r),n)}return!1},emitFilter:function(e,t){var r=!1;return this.$emit("input-filter",e,t,(function(){return r=!(arguments.length>0&&void 0!==arguments[0])||arguments[0]})),r},emitFile:function(e,t){var r,n=this;this.$emit("input-file",e,t),null===(r=e)||void 0===r||!r.fileObject||!e.active||t&&t.active?e&&e.fileObject&&e.active||!t||!t.fileObject||!t.active||this.uploading--:(this.uploading++,this.$nextTick((function(){setTimeout((function(){e&&n.upload(e).then((function(){var t;e&&(e=n.get(e)||void 0),null!==(t=e)&&void 0!==t&&t.fileObject&&n.update(e,{active:!1,success:!e.error})})).catch((function(t){e&&n.update(e,{active:!1,success:!1,error:t.code||t.error||t.message||t})}))}),Math.ceil(50*Math.random()+50))}))),!this.active||Boolean(e)===Boolean(t)&&e.active===t.active||this.watchActive(!0)},emitInput:function(){this.$emit("update:modelValue",this.files)},upload:function(e){var t=this.get(e);if(!t)return Promise.reject(new Error("not_exists"));if(!t.fileObject)return Promise.reject(new Error("file_object"));if(t.error)return t.error instanceof Error?Promise.reject(t.error):Promise.reject(new Error(t.error));if(t.success)return Promise.resolve(t);if(t.name&&this.iExtensions&&"text/directory"!==t.type&&-1===t.name.search(this.iExtensions))return Promise.reject(new Error("extension"));if(this.size>0&&void 0!==t.size&&t.size>=0&&t.size>this.size&&"text/directory"!==t.type)return Promise.reject(new Error("size"));if(this.customAction)return this.customAction(t,this);if(this.features.html5){if(this.shouldUseChunkUpload(t))return this.uploadChunk(t);if(t.putAction)return this.uploadPut(t);if(t.postAction)return this.uploadHtml5(t)}return t.postAction?this.uploadHtml4(t):Promise.reject(new Error("No action configured"))},shouldUseChunkUpload:function(e){return this.chunkEnabled&&!!this.chunkOptions.handler&&e.size&&e.size>this.chunkOptions.minSize},uploadChunk:function(e){var t=this.chunkOptions.handler;return e.chunk=new t(e,this.chunkOptions),e.chunk.upload().then((function(t){return e}))},uploadPut:function(e){var t,r=[];for(var n in e.data)null!=(t=e.data[n])&&r.push(encodeURIComponent(n)+"="+encodeURIComponent(t));var i=e.putAction||"",o=r.length?(-1===i.indexOf("?")?"?":"&")+r.join("&"):"",s=new XMLHttpRequest;return s.open("PUT",i+o),this.uploadXhr(s,e,e.file)},uploadHtml5:function(e){var t,r=new window.FormData;for(var n in e.data)(t=e.data[n])&&"object"===c(t)&&"function"!=typeof t.toString?t instanceof File?r.append(n,t,t.name):r.append(n,JSON.stringify(t)):null!=t&&r.append(n,t);r.append(this.name,e.file,e.name||e.file.name||e.file.filename);var i=new XMLHttpRequest;return i.open("POST",e.postAction||""),this.uploadXhr(i,e,r)},uploadXhr:function(e,t,r){var n=this,i=t,o=0,s=0;e.upload.onprogress=function(e){if(i&&(i=n.get(i),e.lengthComputable&&i&&i.fileObject&&i.active)){var t=Math.round(Date.now()/1e3);t!==o&&(o=t,i=n.update(i,{progress:(e.loaded/e.total*100).toFixed(2),speed:e.loaded-s}),s=e.loaded)}};var a=window.setInterval((function(){var t;if(!(i&&(i=n.get(i))&&null!==(t=i)&&void 0!==t&&t.fileObject)||i.success||i.error||!i.active){a&&(clearInterval(a),a=void 0);try{e.abort(),e.timeout=1}catch(e){}}}),100);return new Promise((function(t,o){if(i){var s,u=function(r){if(!s){if(s=!0,a&&(clearInterval(a),a=void 0),!i)return o(new Error("not_exists"));if(!(i=n.get(i)))return o(new Error("not_exists"));if(!i.fileObject)return o(new Error("file_object"));if(i.error)return i.error instanceof Error?o(i.error):o(new Error(i.error));if(!i.active)return o(new Error("abort"));if(i.success)return t(i);var u={};switch(r.type){case"timeout":case"abort":u.error=r.type;break;case"error":e.status?e.status>=500?u.error="server":e.status>=400&&(u.error="denied"):u.error="network";break;default:e.status>=500?u.error="server":e.status>=400?u.error="denied":u.progress="100.00"}if(e.responseText){var l=e.getResponseHeader("Content-Type");l&&-1!==l.indexOf("/json")?u.response=JSON.parse(e.responseText):u.response=e.responseText}return(i=n.update(i,u))?i.error?i.error instanceof Error?o(i.error):o(new Error(i.error)):t(i):o(new Error("abort"))}};for(var l in e.onload=u,e.onerror=u,e.onabort=u,e.ontimeout=u,i.timeout&&(e.timeout=i.timeout),i.headers)e.setRequestHeader(l,i.headers[l]);(i=n.update(i,{xhr:e}))&&e.send(r)}else o(new Error("not_exists"))}))},uploadHtml4:function(e){var t=this,r=e;if(!r)return Promise.reject(new Error("not_exists"));var n=function(e){27===e.keyCode&&e.preventDefault()},i=document.createElement("iframe");i.id="upload-iframe-"+r.id,i.name="upload-iframe-"+r.id,i.src="about:blank",i.setAttribute("style","width:1px;height:1px;top:-999em;position:absolute; margin-top:-999em;");var o=document.createElement("form");for(var s in o.setAttribute("action",r.postAction||""),o.name="upload-form-"+r.id,o.setAttribute("method","POST"),o.setAttribute("target","upload-iframe-"+r.id),o.setAttribute("enctype","multipart/form-data"),r.data){var a=r.data[s];if(a&&"object"===c(a)&&"function"!=typeof a.toString&&(a=JSON.stringify(a)),null!=a){var u=document.createElement("input");u.type="hidden",u.name=s,u.value=a,o.appendChild(u)}}o.appendChild(r.el),document.body.appendChild(i).appendChild(o);var l=function(){var e,t;try{i.contentWindow&&(t=i.contentWindow.document)}catch(e){}if(!t)try{t=i.contentDocument?i.contentDocument:i.document}catch(e){t=i.document}return null!==(e=t)&&void 0!==e&&e.body?t.body.innerHTML:null};return new Promise((function(e,s){setTimeout((function(){if(r){if(!(r=t.update(r,{iframe:i})))return s(new Error("not_exists"));var a,u=window.setInterval((function(){r&&(r=t.get(r))&&r.fileObject&&!r.success&&!r.error&&r.active||(u&&(clearInterval(u),u=void 0),i.onabort({type:r?"abort":"not_exists"}))}),100),c=function(i){var o;if(!a){if(a=!0,u&&(clearInterval(u),u=void 0),document.body.removeEventListener("keydown",n),!r)return s(new Error("not_exists"));if(!(r=t.get(r)))return s(new Error("not_exists"));if(!r.fileObject)return s(new Error("file_object"));if(r.error)return r.error instanceof Error?s(r.error):s(new Error(r.error));if(!r.active)return s(new Error("abort"));if(r.success)return e(r);var c=l(),d={};if("string"==typeof i)return s(new Error(i));switch(i.type){case"abort":d.error="abort";break;case"error":r.error?d.error=r.error:d.error=null===c?"network":"denied";break;default:r.error?d.error=r.error:null===c?d.error="network":d.progress="100.00"}if(null!==c){if(c&&"{"===c.substr(0,1)&&"}"===c.substr(c.length-1,1))try{c=JSON.parse(c)}catch(e){}d.response=c}return(r=t.update(r,d))?null!==(o=r)&&void 0!==o&&o.error?r.error instanceof Error?s(r.error):s(new Error(r.error)):e(r):s(new Error("not_exists"))}};i.onload=c,i.onerror=c,i.onabort=c,document.body.addEventListener("keydown",n),o.submit()}else s(new Error("not_exists"))}),50)})).then((function(e){var t;return null==i||null===(t=i.parentNode)||void 0===t||t.removeChild(i),e})).catch((function(e){var t;return null==i||null===(t=i.parentNode)||void 0===t||t.removeChild(i),e}))},watchActive:function(e){for(var t,r=0;t=this.files[r];)if(r++,t.fileObject)if(e&&!this.destroy){if(this.uploading>=this.thread||this.uploading&&!this.features.html5)break;t.active||t.error||t.success||this.update(t,{active:!0})}else t.active&&this.update(t,{active:!1});0===this.uploading&&(this.active=!1)},watchDrop:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:void 0;if(this.features.drop&&e!==t){if(this.dropElement)try{document.removeEventListener("dragenter",this.onDocumentDragenter,!1),document.removeEventListener("dragleave",this.onDocumentDragleave,!1),document.removeEventListener("dragover",this.onDocumentDragover,!1),document.removeEventListener("drop",this.onDocumentDrop,!1),this.dropElement.removeEventListener("dragenter",this.onDragenter,!1),this.dropElement.removeEventListener("dragleave",this.onDragleave,!1),this.dropElement.removeEventListener("dragover",this.onDragover,!1),this.dropElement.removeEventListener("drop",this.onDrop,!1)}catch(e){}var r=null;if(e)if("string"==typeof e)r=document.querySelector(e)||this.$root.$el.querySelector(e);else if(!0===e){var n,i;(r=this.$parent.$el)&&8!==(null===(n=r)||void 0===n?void 0:n.nodeType)||(r=this.$root.$el)&&8!==(null===(i=r)||void 0===i?void 0:i.nodeType)||(r=document.body)}else r=e;this.dropElement=r,this.dropElement&&(document.addEventListener("dragenter",this.onDocumentDragenter,!1),document.addEventListener("dragleave",this.onDocumentDragleave,!1),document.addEventListener("dragover",this.onDocumentDragover,!1),document.addEventListener("drop",this.onDocumentDrop,!1),this.dropElement.addEventListener("dragenter",this.onDragenter,!1),this.dropElement.addEventListener("dragleave",this.onDragleave,!1),this.dropElement.addEventListener("dragover",this.onDragover,!1),this.dropElement.addEventListener("drop",this.onDrop,!1))}},watchDropActive:function(e,t){e!==t&&(!e&&this.dropElementActive&&(this.dropElementActive=!1),this.dropTimeout&&(clearTimeout(this.dropTimeout),this.dropTimeout=null),e&&(this.dropTimeout=setTimeout(this.onDocumentDrop,1e3)))},onDocumentDragenter:function(e){var t,r;if(!this.dropActive&&e.dataTransfer){var n=e.dataTransfer;null!=n&&null!==(t=n.files)&&void 0!==t&&t.length?this.dropActive=!0:n.types?(n.types.indexOf&&-1!==n.types.indexOf("Files")||null!==(r=n.types)&&void 0!==r&&r.contains&&n.types.contains("Files"))&&(this.dropActive=!0):this.dropActive=!0,this.dropActive&&this.watchDropActive(!0)}},onDocumentDragleave:function(e){this.dropActive&&(e.target===e.explicitOriginalTarget||!e.fromElement&&(e.clientX<=0||e.clientY<=0||e.clientX>=window.innerWidth||e.clientY>=window.innerHeight))&&(this.dropActive=!1,this.watchDropActive(!1))},onDocumentDragover:function(){this.watchDropActive(!0)},onDocumentDrop:function(){this.dropActive=!1,this.watchDropActive(!1)},onDragenter:function(e){this.dropActive&&!this.dropElementActive&&(this.dropElementActive=!0)},onDragleave:function(e){var t;if(this.dropElementActive){var r=e.relatedTarget;if(r)if(null!==(t=this.dropElement)&&void 0!==t&&t.contains)this.dropElement.contains(r)||(this.dropElementActive=!1);else{for(var n=r;n&&n!==this.dropElement;)n=n.parentNode;n!==this.dropElement&&(this.dropElementActive=!1)}else this.dropElementActive=!1}},onDragover:function(e){e.preventDefault()},onDrop:function(e){e.preventDefault(),e.dataTransfer&&this.addDataTransfer(e.dataTransfer)},inputOnChange:async function(e){var t=this;if(!(e.target instanceof HTMLInputElement))return Promise.reject(new Error("not HTMLInputElement"));e.target;var r=function(e){return t.reload=!0,t.$nextTick((function(){t.reload=!1})),e};return this.addInputFile(e.target).then(r).catch(r)},isRelatedTargetSupported:function(){try{return"relatedTarget"in new MouseEvent("mouseout",{relatedTarget:document.body})}catch(e){return!1}}}}),x=["for"],O=["name","id","accept","capture","disabled","webkitdirectory","allowdirs","directory","multiple"];function j(t,r,n,i,o,s){return e.openBlock(),e.createElementBlock("span",{class:e.normalizeClass(t.className)},[e.renderSlot(t.$slots,"default"),e.createElementVNode("label",{for:t.forId},null,8,x),t.reload?e.createCommentVNode("",!0):(e.openBlock(),e.createElementBlock("input",{key:0,ref:"input",type:"file",name:t.name,id:t.forId,accept:t.accept,capture:t.capture,disabled:t.disabled,webkitdirectory:t.iDirectory,allowdirs:t.iDirectory,directory:t.iDirectory,multiple:t.multiple&&t.features.html5,onChange:r[0]||(r[0]=function(){return t.inputOnChange&&t.inputOnChange.apply(t,arguments)})},null,40,O))],2)}function D(e,t){void 0===t&&(t={});var r=t.insertAt;if(e&&"undefined"!=typeof document){var n=document.head||document.getElementsByTagName("head")[0],i=document.createElement("style");i.type="text/css","top"===r&&n.firstChild?n.insertBefore(i,n.firstChild):n.appendChild(i),i.styleSheet?i.styleSheet.cssText=e:i.appendChild(document.createTextNode(e))}}return D("\n.file-uploads {\n overflow: hidden;\n position: relative;\n text-align: center;\n display: inline-block;\n}\n.file-uploads.file-uploads-html4 input,\n.file-uploads.file-uploads-html5 label {\n /* background fix ie click */\n background: #fff;\n opacity: 0;\n font-size: 20em;\n z-index: 1;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n position: absolute;\n width: 100%;\n height: 100%;\n}\n.file-uploads.file-uploads-html5 input,\n.file-uploads.file-uploads-html4 label {\n /* background fix ie click */\n position: absolute;\n background: rgba(255, 255, 255, 0);\n overflow: hidden;\n position: fixed;\n width: 1px;\n height: 1px;\n z-index: -1;\n opacity: 0;\n}\n"),E.render=j,E}(r.default)}(n),n.exports})); 19 | //# sourceMappingURL=vue-upload-component.min.js.map 20 | --------------------------------------------------------------------------------