1353 lines
32 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<span :class="className">
<slot></slot>
<label :for="inputId || name"></label>
<input-file></input-file>
</span>
</template>
<style>
.file-uploads {
overflow: hidden;
position: relative;
text-align: center;
display: inline-block;
}
.file-uploads.file-uploads-html4 input, .file-uploads.file-uploads-html5 label {
/* background fix ie click */
background: #fff;
opacity: 0;
font-size: 20em;
z-index: 1;
top: 0;
left: 0;
right: 0;
bottom: 0;
position: absolute;
width: 100%;
height: 100%;
}
.file-uploads.file-uploads-html5 input, .file-uploads.file-uploads-html4 label {
/* background fix ie click */
background: rgba(255, 255, 255, 0);
overflow: hidden;
position: fixed;
width: 1px;
height: 1px;
z-index: -1;
opacity: 0;
}
</style>
<script>
import ChunkUploadDefaultHandler from './chunk/ChunkUploadHandler'
import InputFile from './InputFile.vue'
const CHUNK_DEFAULT_OPTIONS = {
headers: {},
action: '',
minSize: 1048576,
maxActive: 3,
maxRetries: 5,
handler: ChunkUploadDefaultHandler
}
export default {
components: {
InputFile,
},
props: {
inputId: {
type: String,
},
name: {
type: String,
default: 'file',
},
accept: {
type: String,
},
capture: {
},
disabled: {
},
multiple: {
type: Boolean,
},
maximum: {
type: Number,
default(props) {
return props.multiple ? 0 : 1
}
},
addIndex: {
type: [Boolean, Number],
},
directory: {
type: Boolean,
},
postAction: {
type: String,
},
putAction: {
type: String,
},
customAction: {
type: Function,
},
headers: {
type: Object,
default: Object,
},
data: {
type: Object,
default: Object,
},
timeout: {
type: Number,
default: 0,
},
drop: {
default: false,
},
dropDirectory: {
type: Boolean,
default: true,
},
size: {
type: Number,
default: 0,
},
extensions: {
default: Array,
},
value: {
type: Array,
default: Array,
},
thread: {
type: Number,
default: 1,
},
// Chunk upload enabled
chunkEnabled: {
type: Boolean,
default: false
},
// Chunk upload properties
chunk: {
type: Object,
default: () => {
return CHUNK_DEFAULT_OPTIONS
}
}
},
data() {
return {
files: this.value,
features: {
html5: true,
directory: false,
drop: false,
},
active: false,
dropActive: false,
uploading: 0,
destroy: false,
}
},
/**
* mounted
* @return {[type]} [description]
*/
mounted() {
let input = document.createElement('input')
input.type = 'file'
input.multiple = true
// html5 特征
if (window.FormData && input.files) {
// 上传目录特征
if (typeof input.webkitdirectory === 'boolean' || typeof input.directory === 'boolean') {
this.features.directory = true
}
// 拖拽特征
if (this.features.html5 && typeof input.ondrop !== 'undefined') {
this.features.drop = true
}
} else {
this.features.html5 = false
}
// files 定位缓存
this.maps = {}
if (this.files) {
for (let i = 0; i < this.files.length; i++) {
let file = this.files[i]
this.maps[file.id] = file
}
}
this.$nextTick(function () {
// 更新下父级
if (this.$parent) {
this.$parent.$forceUpdate()
// 拖拽渲染
this.$parent.$nextTick(() => {
this.watchDrop(this.drop)
})
} else {
// 拖拽渲染
this.watchDrop(this.drop)
}
})
},
/**
* beforeDestroy
* @return {[type]} [description]
*/
beforeDestroy() {
// 已销毁
this.destroy = true
// 设置成不激活
this.active = false
// 销毁拖拽事件
this.watchDrop(false)
},
computed: {
/**
* uploading 正在上传的线程
* @return {[type]} [description]
*/
/**
* uploaded 文件列表是否全部已上传
* @return {[type]} [description]
*/
uploaded() {
let file
for (let i = 0; i < this.files.length; i++) {
file = this.files[i]
if (file.fileObject && !file.error && !file.success) {
return false
}
}
return true
},
chunkOptions () {
return Object.assign(CHUNK_DEFAULT_OPTIONS, this.chunk)
},
className() {
return [
'file-uploads',
this.features.html5 ? 'file-uploads-html5' : 'file-uploads-html4',
this.features.directory && this.directory ? 'file-uploads-directory' : undefined,
this.features.drop && this.drop ? 'file-uploads-drop' : undefined,
this.disabled ? 'file-uploads-disabled' : undefined,
]
}
},
watch: {
active(active) {
this.watchActive(active)
},
dropActive() {
if (this.$parent) {
this.$parent.$forceUpdate()
}
},
drop(value) {
this.watchDrop(value)
},
value(files) {
if (this.files === files) {
return
}
this.files = files
let oldMaps = this.maps
// 重写 maps 缓存
this.maps = {}
for (let i = 0; i < this.files.length; i++) {
let file = this.files[i]
this.maps[file.id] = file
}
// add, update
for (let key in this.maps) {
let newFile = this.maps[key]
let oldFile = oldMaps[key]
if (newFile !== oldFile) {
this.emitFile(newFile, oldFile)
}
}
// delete
for (let key in oldMaps) {
if (!this.maps[key]) {
this.emitFile(undefined, oldMaps[key])
}
}
},
},
methods: {
// 清空
clear() {
if (this.files.length) {
let files = this.files
this.files = []
// 定位
this.maps = {}
// 事件
this.emitInput()
for (let i = 0; i < files.length; i++) {
this.emitFile(undefined, files[i])
}
}
return true
},
// 选择
get(id) {
if (!id) {
return false
}
if (typeof id === 'object') {
return this.maps[id.id] || false
}
return this.maps[id] || false
},
// 添加
add(_files, index = this.addIndex) {
let files = _files
let isArray = files instanceof Array
// 不是数组整理成数组
if (!isArray) {
files = [files]
}
// 遍历规范对象
let addFiles = []
for (let i = 0; i < files.length; i++) {
let file = files[i]
if (this.features.html5 && file instanceof Blob) {
file = {
file,
size: file.size,
name: file.webkitRelativePath || file.relativePath || file.name || 'unknown',
type: file.type,
}
}
let fileObject = false
if (file.fileObject === false) {
// false
} else if (file.fileObject) {
fileObject = true
} else if (typeof Element !== 'undefined' && file.el instanceof Element) {
fileObject = true
} else if (typeof Blob !== 'undefined' && file.file instanceof Blob) {
fileObject = true
}
if (fileObject) {
file = {
fileObject: true,
size: -1,
name: 'Filename',
type: '',
active: false,
error: '',
success: false,
putAction: this.putAction,
postAction: this.postAction,
timeout: this.timeout,
...file,
response: {},
progress: '0.00', // 只读
speed: 0, // 只读
// xhr: false, // 只读
// iframe: false, // 只读
}
file.data = {
...this.data,
...file.data ? file.data : {},
}
file.headers = {
...this.headers,
...file.headers ? file.headers : {},
}
}
// 必须包含 id
if (!file.id) {
file.id = Math.random().toString(36).substr(2)
}
if (this.emitFilter(file, undefined)) {
continue
}
// 最大数量限制
if (this.maximum > 1 && (addFiles.length + this.files.length) >= this.maximum) {
break
}
addFiles.push(file)
// 最大数量限制
if (this.maximum === 1) {
break
}
}
// 没有文件
if (!addFiles.length) {
return false
}
// 如果是 1 清空
if (this.maximum === 1) {
this.clear()
}
// 添加进去 files
let newFiles
if (index === true || index === 0) {
newFiles = addFiles.concat(this.files)
} else if (index) {
newFiles = this.files.concat([])
newFiles.splice(index, 0, ...addFiles)
} else {
newFiles = this.files.concat(addFiles)
}
this.files = newFiles
// 定位
for (let i = 0; i < addFiles.length; i++) {
let file = addFiles[i]
this.maps[file.id] = file
}
// 事件
this.emitInput()
for (let i = 0; i < addFiles.length; i++) {
this.emitFile(addFiles[i], undefined)
}
return isArray ? addFiles : addFiles[0]
},
// 添加表单文件
addInputFile(el) {
let files = []
if (el.files) {
for (let i = 0; i < el.files.length; i++) {
let file = el.files[i]
files.push({
size: file.size,
name: file.webkitRelativePath || file.relativePath || file.name,
type: file.type,
file,
})
}
} else {
var names = el.value.replace(/\\/g, '/').split('/')
delete el.__vuex__
files.push({
name: names[names.length - 1],
el,
})
}
return this.add(files)
},
// 添加 DataTransfer
addDataTransfer(dataTransfer) {
let files = []
if (dataTransfer.items && dataTransfer.items.length) {
let items = []
for (let i = 0; i < dataTransfer.items.length; i++) {
let item = dataTransfer.items[i]
if (item.getAsEntry) {
item = item.getAsEntry() || item.getAsFile()
} else if (item.webkitGetAsEntry) {
item = item.webkitGetAsEntry() || item.getAsFile()
} else {
item = item.getAsFile()
}
if (item) {
items.push(item)
}
}
return new Promise((resolve, reject) => {
let forEach = (i) => {
let item = items[i]
// 结束 文件数量大于 最大数量
if (!item || (this.maximum > 0 && files.length >= this.maximum)) {
return resolve(this.add(files))
}
this.getEntry(item).then(function (results) {
files.push(...results)
forEach(i + 1)
})
}
forEach(0)
})
}
if (dataTransfer.files.length) {
for (let i = 0; i < dataTransfer.files.length; i++) {
files.push(dataTransfer.files[i])
if (this.maximum > 0 && files.length >= this.maximum) {
break
}
}
return Promise.resolve(this.add(files))
}
return Promise.resolve([])
},
// 获得 entry
getEntry(entry, path = '') {
return new Promise((resolve, reject) => {
if (entry.isFile) {
entry.file(function (file) {
resolve([
{
size: file.size,
name: path + file.name,
type: file.type,
file,
}
])
})
} else if (entry.isDirectory && this.dropDirectory) {
let files = []
let dirReader = entry.createReader()
let readEntries = () => {
dirReader.readEntries((entries) => {
let forEach = (i) => {
if ((!entries[i] && i === 0) || (this.maximum > 0 && files.length >= this.maximum)) {
return resolve(files)
}
if (!entries[i]) {
return readEntries()
}
this.getEntry(entries[i], path + entry.name + '/').then((results) => {
files.push(...results)
forEach(i + 1)
})
}
forEach(0)
})
}
readEntries()
} else {
resolve([])
}
})
},
replace(id1, id2) {
let file1 = this.get(id1)
let file2 = this.get(id2)
if (!file1 || !file2 || file1 === file2) {
return false
}
let files = this.files.concat([])
let index1 = files.indexOf(file1)
let index2 = files.indexOf(file2)
if (index1 === -1 || index2 === -1) {
return false
}
files[index1] = file2
files[index2] = file1
this.files = files
this.emitInput()
return true
},
// 移除
remove(id) {
let file = this.get(id)
if (file) {
if (this.emitFilter(undefined, file)) {
return false
}
let files = this.files.concat([])
let index = files.indexOf(file)
if (index === -1) {
console.error('remove', file)
return false
}
files.splice(index, 1)
this.files = files
// 定位
delete this.maps[file.id]
// 事件
this.emitInput()
this.emitFile(undefined, file)
}
return file
},
// 更新
update(id, data) {
let file = this.get(id)
if (file) {
let newFile = {
...file,
...data
}
// 停用必须加上错误
if (file.fileObject && file.active && !newFile.active && !newFile.error && !newFile.success) {
newFile.error = 'abort'
}
if (this.emitFilter(newFile, file)) {
return false
}
let files = this.files.concat([])
let index = files.indexOf(file)
if (index === -1) {
console.error('update', file)
return false
}
files.splice(index, 1, newFile)
this.files = files
// 删除 旧定位 写入 新定位 已便支持修改id)
delete this.maps[file.id]
this.maps[newFile.id] = newFile
// 事件
this.emitInput()
this.emitFile(newFile, file)
return newFile
}
return false
},
// 预处理 事件 过滤器
emitFilter(newFile, oldFile) {
let isPrevent = false
this.$emit('input-filter', newFile, oldFile, function () {
isPrevent = true
return isPrevent
})
return isPrevent
},
// 处理后 事件 分发
emitFile(newFile, oldFile) {
// 预处理文件 当文件被添加后
this.$emit('input-file', newFile, oldFile)
if (newFile && newFile.fileObject && newFile.active && (!oldFile || !oldFile.active)) {
this.uploading++
// 激活
this.$nextTick(function () {
setTimeout(() => {
this.upload(newFile).then(() => {
// eslint-disable-next-line
newFile = this.get(newFile)
if (newFile && newFile.fileObject) {
this.update(newFile, {
active: false,
success: !newFile.error
})
}
}).catch((e) => {
this.update(newFile, {
active: false,
success: false,
error: e.code || e.error || e.message || e
})
})
}, parseInt(Math.random() * 50 + 50, 10))
})
} else if ((!newFile || !newFile.fileObject || !newFile.active) && oldFile && oldFile.fileObject && oldFile.active) {
// 停止
this.uploading--
}
// 自动延续激活
if (this.active && (Boolean(newFile) !== Boolean(oldFile) || newFile.active !== oldFile.active)) {
this.watchActive(true)
}
},
emitInput() {
this.$emit('input', this.files)
},
// 上传
upload(id) {
let file = this.get(id)
// 被删除
if (!file) {
return Promise.reject('not_exists')
}
// 不是文件对象
if (!file.fileObject) {
return Promise.reject('file_object')
}
// 有错误直接响应
if (file.error) {
return Promise.reject(file.error)
}
// 已完成直接响应
if (file.success) {
return Promise.resolve(file)
}
// 后缀
let extensions = this.extensions
if (extensions && (extensions.length || typeof extensions.length === 'undefined')) {
if (typeof extensions !== 'object' || !(extensions instanceof RegExp)) {
if (typeof extensions === 'string') {
extensions = extensions.split(',').map(value => value.trim()).filter(value => value)
}
extensions = new RegExp('\\.(' + extensions.join('|').replace(/\./g, '\\.') + ')$', 'i')
}
if (file.name.search(extensions) === -1) {
return Promise.reject('extension')
}
}
// 大小
if (this.size > 0 && file.size >= 0 && file.size > this.size) {
return Promise.reject('size')
}
if (this.customAction) {
return this.customAction(file, this)
}
if (this.features.html5) {
if (this.shouldUseChunkUpload(file)) {
return this.uploadChunk(file)
}
if (file.putAction) {
return this.uploadPut(file)
}
if (file.postAction) {
return this.uploadHtml5(file)
}
}
if (file.postAction) {
return this.uploadHtml4(file)
}
return Promise.reject('No action configured')
},
/**
* Whether this file should be uploaded using chunk upload or not
*
* @param Object file
*/
shouldUseChunkUpload (file) {
return this.chunkEnabled &&
!!this.chunkOptions.handler &&
file.size > this.chunkOptions.minSize
},
/**
* Upload a file using Chunk method
*
* @param File file
*/
uploadChunk (file) {
const HandlerClass = this.chunkOptions.handler
file.chunk = new HandlerClass(file, this.chunkOptions)
return file.chunk.upload()
},
uploadPut(file) {
let querys = []
let value
for (let key in file.data) {
value = file.data[key]
if (value !== null && value !== undefined) {
querys.push(encodeURIComponent(key) + '=' + encodeURIComponent(value))
}
}
let queryString = querys.length ? (file.putAction.indexOf('?') === -1 ? '?' : '&') + querys.join('&') : ''
let xhr = new XMLHttpRequest()
xhr.open('PUT', file.putAction + queryString)
return this.uploadXhr(xhr, file, file.file)
},
uploadHtml5(file) {
let form = new window.FormData()
let value
for (let key in file.data) {
value = file.data[key]
if (value && typeof value === 'object' && typeof value.toString !== 'function') {
if (value instanceof File) {
form.append(key, value, value.name)
} else {
form.append(key, JSON.stringify(value))
}
} else if (value !== null && value !== undefined) {
form.append(key, value)
}
}
form.append(this.name, file.file, file.file.filename || file.name)
let xhr = new XMLHttpRequest()
xhr.open('POST', file.postAction)
return this.uploadXhr(xhr, file, form)
},
uploadXhr(xhr, _file, body) {
let file = _file
let speedTime = 0
let speedLoaded = 0
// 进度条
xhr.upload.onprogress = (e) => {
// 还未开始上传 已删除 未激活
file = this.get(file)
if (!e.lengthComputable || !file || !file.fileObject || !file.active) {
return
}
// 进度 速度 每秒更新一次
let speedTime2 = Math.round(Date.now() / 1000)
if (speedTime2 === speedTime) {
return
}
speedTime = speedTime2
file = this.update(file, {
progress: (e.loaded / e.total * 100).toFixed(2),
speed: e.loaded - speedLoaded,
})
speedLoaded = e.loaded
}
// 检查激活状态
let interval = setInterval(() => {
file = this.get(file)
if (file && file.fileObject && !file.success && !file.error && file.active) {
return
}
if (interval) {
clearInterval(interval)
interval = false
}
try {
xhr.abort()
xhr.timeout = 1
} catch (e) {
}
}, 100)
return new Promise((resolve, reject) => {
let complete
let fn = (e) => {
// 已经处理过了
if (complete) {
return
}
complete = true
if (interval) {
clearInterval(interval)
interval = false
}
file = this.get(file)
// 不存在直接响应
if (!file) {
return reject('not_exists')
}
// 不是文件对象
if (!file.fileObject) {
return reject('file_object')
}
// 有错误自动响应
if (file.error) {
return reject(file.error)
}
// 未激活
if (!file.active) {
return reject('abort')
}
// 已完成 直接相应
if (file.success) {
return resolve(file)
}
let data = {}
switch (e.type) {
case 'timeout':
case 'abort':
data.error = e.type
break
case 'error':
if (!xhr.status) {
data.error = 'network'
} else if (xhr.status >= 500) {
data.error = 'server'
} else if (xhr.status >= 400) {
data.error = 'denied'
}
break
default:
if (xhr.status >= 500) {
data.error = 'server'
} else if (xhr.status >= 400) {
data.error = 'denied'
} else {
data.progress = '100.00'
}
}
if (xhr.responseText) {
let contentType = xhr.getResponseHeader('Content-Type')
if (contentType && contentType.indexOf('/json') !== -1) {
data.response = JSON.parse(xhr.responseText)
} else {
data.response = xhr.responseText
}
}
// 更新
file = this.update(file, data)
// 相应错误
if (file.error) {
return reject(file.error)
}
// 响应
return resolve(file)
}
// 事件
xhr.onload = fn
xhr.onerror = fn
xhr.onabort = fn
xhr.ontimeout = fn
// 超时
if (file.timeout) {
xhr.timeout = file.timeout
}
// headers
for (let key in file.headers) {
xhr.setRequestHeader(key, file.headers[key])
}
// 更新 xhr
file = this.update(file, { xhr })
// 开始上传
xhr.send(body)
})
},
uploadHtml4(_file) {
let file = _file
let onKeydown = function (e) {
if (e.keyCode === 27) {
e.preventDefault()
}
}
let iframe = document.createElement('iframe')
iframe.id = 'upload-iframe-' + file.id
iframe.name = 'upload-iframe-' + file.id
iframe.src = 'about:blank'
iframe.setAttribute('style', 'width:1px;height:1px;top:-999em;position:absolute; margin-top:-999em;')
let form = document.createElement('form')
form.action = file.postAction
form.name = 'upload-form-' + file.id
form.setAttribute('method', 'POST')
form.setAttribute('target', 'upload-iframe-' + file.id)
form.setAttribute('enctype', 'multipart/form-data')
let value
let input
for (let key in file.data) {
value = file.data[key]
if (value && typeof value === 'object' && typeof value.toString !== 'function') {
value = JSON.stringify(value)
}
if (value !== null && value !== undefined) {
input = document.createElement('input')
input.type = 'hidden'
input.name = key
input.value = value
form.appendChild(input)
}
}
form.appendChild(file.el)
document.body.appendChild(iframe).appendChild(form)
let getResponseData = function () {
let doc
try {
if (iframe.contentWindow) {
doc = iframe.contentWindow.document
}
} catch (err) {
}
if (!doc) {
try {
doc = iframe.contentDocument ? iframe.contentDocument : iframe.document
} catch (err) {
doc = iframe.document
}
}
if (doc && doc.body) {
return doc.body.innerHTML
}
return null
}
return new Promise((resolve, reject) => {
setTimeout(() => {
file = this.update(file, { iframe })
// 不存在
if (!file) {
return reject('not_exists')
}
// 定时检查
let interval = setInterval(() => {
file = this.get(file)
if (file && file.fileObject && !file.success && !file.error && file.active) {
return
}
if (interval) {
clearInterval(interval)
interval = false
}
iframe.onabort({ type: file ? 'abort' : 'not_exists' })
}, 100)
let complete
let fn = (e) => {
// 已经处理过了
if (complete) {
return
}
complete = true
if (interval) {
clearInterval(interval)
interval = false
}
// 关闭 esc 事件
document.body.removeEventListener('keydown', onKeydown)
file = this.get(file)
// 不存在直接响应
if (!file) {
return reject('not_exists')
}
// 不是文件对象
if (!file.fileObject) {
return reject('file_object')
}
// 有错误自动响应
if (file.error) {
return reject(file.error)
}
// 未激活
if (!file.active) {
return reject('abort')
}
// 已完成 直接相应
if (file.success) {
return resolve(file)
}
let response = getResponseData()
let data = {}
switch (e.type) {
case 'abort':
data.error = 'abort'
break
case 'error':
if (file.error) {
data.error = file.error
} else if (response === null) {
data.error = 'network'
} else {
data.error = 'denied'
}
break
default:
if (file.error) {
data.error = file.error
} else if (data === null) {
data.error = 'network'
} else {
data.progress = '100.00'
}
}
if (response !== null) {
if (response && response.substr(0, 1) === '{' && response.substr(response.length - 1, 1) === '}') {
try {
response = JSON.parse(response)
} catch (err) {
}
}
data.response = response
}
// 更新
file = this.update(file, data)
if (file.error) {
return reject(file.error)
}
// 响应
return resolve(file)
}
// 添加事件
iframe.onload = fn
iframe.onerror = fn
iframe.onabort = fn
// 禁止 esc 键
document.body.addEventListener('keydown', onKeydown)
// 提交
form.submit()
}, 50)
}).then(function (res) {
iframe.parentNode && iframe.parentNode.removeChild(iframe)
return res
}).catch(function (res) {
iframe.parentNode && iframe.parentNode.removeChild(iframe)
return res
})
},
watchActive(active) {
let file
let index = 0
while ((file = this.files[index])) {
index++
if (!file.fileObject) {
// 不是文件对象
} else if (active && !this.destroy) {
if (this.uploading >= this.thread || (this.uploading && !this.features.html5)) {
break
}
if (!file.active && !file.error && !file.success) {
this.update(file, { active: true })
}
} else {
if (file.active) {
this.update(file, { active: false })
}
}
}
if (this.uploading === 0) {
this.active = false
}
},
watchDrop(_el) {
let el = _el
if (!this.features.drop) {
return
}
// 移除挂载
if (this.dropElement) {
try {
document.removeEventListener('dragenter', this.onDragenter, false)
document.removeEventListener('dragleave', this.onDragleave, false)
document.removeEventListener('drop', this.onDocumentDrop, false)
this.dropElement.removeEventListener('dragover', this.onDragover, false)
this.dropElement.removeEventListener('drop', this.onDrop, false)
} catch (e) {
}
}
if (!el) {
el = false
} else if (typeof el === 'string') {
el = document.querySelector(el) || this.$root.$el.querySelector(el)
} else if (el === true) {
el = this.$parent.$el
}
this.dropElement = el
if (this.dropElement) {
document.addEventListener('dragenter', this.onDragenter, false)
document.addEventListener('dragleave', this.onDragleave, false)
document.addEventListener('drop', this.onDocumentDrop, false)
this.dropElement.addEventListener('dragover', this.onDragover, false)
this.dropElement.addEventListener('drop', this.onDrop, false)
}
},
onDragenter(e) {
e.preventDefault()
if (this.dropActive) {
return
}
if (!e.dataTransfer) {
return
}
let dt = e.dataTransfer
if (dt.files && dt.files.length) {
this.dropActive = true
} else if (!dt.types) {
this.dropActive = true
} else if (dt.types.indexOf && dt.types.indexOf('Files') !== -1) {
this.dropActive = true
} else if (dt.types.contains && dt.types.contains('Files')) {
this.dropActive = true
}
},
onDragleave(e) {
e.preventDefault()
if (!this.dropActive) {
return
}
if (e.target.nodeName === 'HTML' || e.target === e.explicitOriginalTarget || (!e.fromElement && (e.clientX <= 0 || e.clientY <= 0 || e.clientX >= window.innerWidth || e.clientY >= window.innerHeight))) {
this.dropActive = false
}
},
onDragover(e) {
e.preventDefault()
},
onDocumentDrop() {
this.dropActive = false
},
onDrop(e) {
e.preventDefault()
this.addDataTransfer(e.dataTransfer)
},
}
}
</script>