特性:
- 支持在拖拽上传单个文件、多个文件、单个文件夹、多个文件夹
- 可自定义headers
- 可自定义过滤上传格式
- 可自定义上传API接口
- 支持显示/隐藏右下角上传队列托盘
<template>
<div :class="$options.name" :dragenter="isDragenter">
<!-- 上传按钮_________________________________________________________ -->
<!-- 上传文件 -->
<el-upload
ref="uploadFile"
:show-file-list="false"
:headers="headers"
:accept="accept.toString()"
:action="actionUrl"
:data="actionData"
:before-upload="beforeUpload"
:on-success="uploadSuccess"
:on-error="uploadError"
multiple
:name="name"
:auto-upload="autoUpload"
>
</el-upload>
<!-- 上传文件夹 -->
<el-upload
ref="uploadFolder"
:show-file-list="false"
:headers="headers"
:action="actionUrl"
:data="actionData"
:before-upload="beforeUpload"
:on-success="uploadSuccess"
:on-error="uploadError"
:on-exceed="exceed"
multiple
:drag="(drag === '' || drag) && !__checkDisabledDrag()"
:name="name"
:auto-upload="autoUpload"
>
</el-upload>
<!-- _________________________________________________________ -->
<!-- 上传托盘(右下角) -->
<sgUploadTray
v-model="showUploadTray"
:data="uploadList"
@stopUpload="stopUpload"
@dragStart="$emit(`dragUploadTrayStart`, true)"
@dragEnd="$emit(`dragUploadTrayEnd`, true)"
v-if="!(hideUploadTray === '' || hideUploadTray) && !sgUploadTray"
resizeable
/>
</div>
</template>
<script>
import sgUploadTray from "@/vue/components/admin/sgUploadTray";
export default {
name: "sgDragUploadFolder",
components: { sgUploadTray },
data() {
return {
// 上传----------------------------------------
name: "file", //上传的文件字段名
headers: { kkToken: localStorage.token }, //获取token(注意仔细看后端接受token的字段名是不是叫做“token”)
accept: `.${["png", "jpg", "jpeg", "bmp", "gif", "svg"].join(",.")}`, //默认只支持图片格式上传
// actionUrl: `${this.$d.API_ROOT_URL}/customer/importCustomerData`,
actionUrl: `#`,
actionData: {},
dur: 100,
percent: 100,
uploadList: [],
showUploadTray: false,
uploadFileBtn: null, //上传文件
uploadFolderBtn: null, //上传文件夹
isDragenter: false, //是否拖入
leaveEvents: [
"mouseenter",
"mouseover",
"mousemove",
"mouseout",
"blur",
"visibilitychange",
],
dragAreaDom: null, //拖拽放入区域
isDragTrigger: false, //是否为拖拽触发上传
maxSize: null, //支持最大上传文件大小
autoUpload: true, //是否在选取文件后立即进行上传
// ----------------------------------------
};
},
props: [
"data", //上传可选参数
"hideUploadTray", //不显示上传托盘
"hideUploadTrayWhenDrag", //拖拽上传的时候隐藏上传托盘(默认显示)
"drag", //是否支持拖拽文件、文件夹或多个文件
"disabledWhenShowSels", //当出现对应['sel','sel','sel',...]的时候,屏蔽拖拽上传(譬如出现element的v-modal)
"sgUploadTray", //引用外部公共托盘组件dom(这种方式主要是为了解决同一个页面有多个上传托盘组件导致冲突的问题)
],
watch: {
data: {
handler(d) {
if (d) {
d.name && (this.name = d.name);
d.headers && (this.headers = d.headers);
d.accept && (this.accept = d.accept);
d.actionUrl && (this.actionUrl = d.actionUrl);
d.actionData && (this.actionData = d.actionData);
d.maxSize && (this.maxSize = d.maxSize);
typeof d.autoUpload !== "undefined" && (this.autoUpload = d.autoUpload);
}
},
deep: true,
immediate: true,
},
drag: {
handler(d) {
if (d === "" || d) {
this.addEvents();
} else {
this.removeEvents();
}
},
deep: true,
immediate: true,
},
showUploadTray(newValue, oldValue) {
this.sgUploadTray && (this.sgUploadTray.show = newValue);
},
uploadList: {
handler(d) {
if (this.sgUploadTray) {
this.sgUploadTray.uploadList || (this.sgUploadTray.uploadList = []);
let uploadList = this.sgUploadTray.uploadList;
d.forEach((newVal) => {
// 避免重复添加到上传列表
if (!uploadList.some((oldVal) => oldVal.uid == newVal.uid)) {
uploadList.push(newVal);
if (this.hideUploadTrayWhenDrag === "" || this.hideUploadTrayWhenDrag) {
this.sgUploadTray.show = false; //不显示右下角上传托盘
} else {
this.sgUploadTray.show = true; //显示右下角上传托盘
}
}
});
}
},
deep: true,
// immediate: true,
},
},
mounted() {
this.$nextTick(() => {
this.uploadFileBtn = this.$refs.uploadFile.$children[0].$refs.input;
this.uploadFolderBtn = this.$refs.uploadFolder.$children[0].$refs.input;
this.uploadFolderBtn && (this.uploadFolderBtn.webkitdirectory = true); //让el-upload支持上传文件夹
this.dragAreaDom = this.$refs.uploadFolder.$el.querySelector(`.el-upload-dragger`);
this.dragAreaDom && this.dragAreaDom.addEventListener("drop", this.drop);
});
},
destroyed() {
this.removeEvents();
},
methods: {
__checkDisabledDrag(d) {
let aa = this.disabledWhenShowSels || [];
aa && (Array.isArray(aa) || (aa = [aa]));
let r = [];
for (let i = 0, len = aa.length; i < len; i++) {
let a = aa[i];
let dom = document.querySelector(a);
if (dom) {
r.push(dom);
return true;
}
}
return r.length !== 0;
},
// 监听----------------------------------------
addEvents(d) {
this.removeEvents();
addEventListener("dragenter", this.dragenter);
this.leaveEvents.forEach((v) => addEventListener(v, this.leave));
this.dragAreaDom && this.dragAreaDom.addEventListener("drop", this.drop);
},
removeEvents(d) {
removeEventListener("dragenter", this.dragenter);
this.leaveEvents.forEach((v) => removeEventListener(v, this.leave));
this.dragAreaDom && this.dragAreaDom.removeEventListener("drop", this.drop);
},
dragenter(d) {
this.isDragTrigger = true;
this.isDragenter = true;
},
leave(d) {
this.isDragenter = false;
},
drop(d) {
// 触发拖拽上传
this.uploadDragFiles(
d,
(file) => {
file.isDragFile = true;
this.beforeUpload(file);
},
(files) => {}
);
},
// 循环获取拖拽过来的file----------------------------------------
uploadDragFiles(e, uploadFunc, completeFunc) {
let files = [],
items = [].slice.call(e.dataTransfer.items);
items.forEach((v, i) => {
const webkitGetAsEntry = v.webkitGetAsEntry();
eval(webkitGetAsEntry.isDirectory ? "setfolder" : "setfile")(webkitGetAsEntry);
});
// 处理文件夹
function setfolder(webkitGetAsEntry) {
webkitGetAsEntry
.createReader()
.readEntries((entries) =>
entries.forEach((item) => (item.isFile ? setfile(item) : setfolder(item)))
);
}
// 处理文件
function setfile(webkitGetAsEntry) {
webkitGetAsEntry.file((file) => {
uploadFunc && uploadFunc(file);
files.push(file);
});
}
completeFunc && completeFunc(files);
},
// 上传按钮触发----------------------------------------
triggerUploadFile(d) {
this.isDragTrigger = false;
this.uploadFileBtn && this.uploadFileBtn.click();
},
triggerUploadFolder(d) {
this.isDragTrigger = false;
this.uploadFolderBtn && this.uploadFolderBtn.click();
},
// 判断是否相同uid
same_uid_lastModified(uid, file) {
return (
uid == file.uid ||
uid == file.lastModified ||
uid == (file.raw || {}).lastModified
);
},
// 获取uid
get_uid(file) {
return file.uid || file.lastModified || (file.raw || {}).lastModified;
},
// 上传文件----------------------------------------------------------------
showFakeLoading(file) {
this.$emit(`showFakeLoading`, file);
file.raw && (file = file.raw);
file = this.uploadList.find((v) => this.same_uid_lastModified(v.uid, file)); //拖拽上传的时候没有uid
clearInterval(file.interval);
file.percent = 0;
file.interval = setInterval(() => {
file.percent >= 99 ? this.hideFakeLoading(file) : file.percent++;
}, this.dur);
},
hideFakeLoading(file, { type, tip, color } = {}) {
this.$emit(`hideFakeLoading`, file);
// file.raw && (file = file.raw);//不需要2023.12.18
file = this.uploadList.find((v) => this.same_uid_lastModified(v.uid, file)); //拖拽上传的时候没有uid
clearInterval(file.interval);
switch (type) {
case "error":
file.percent = 0;
break;
case "success":
default:
file.percent = 100;
}
type && (file.type = type);
tip && (file.tip = tip);
color && (file.color = color);
},
exceed(file, fileList) {
this.$message.error("上传文件数量太大,分散上传吧!");
},
stopUpload(d) {
this.$refs.uploadFolder.abort();
},
//文件上传之前
beforeUpload(file) {
if ((this.drag === "" || this.drag) && this.isDragTrigger) {
if (!file.isDragFile) return; //拖拽模式下,如果不是原生js捕获到的拖拽文件,就不进入上传队列(区别开两个el-upload组件避免重复上传)
}
this.uploadList.unshift({
interval: null,
uid: this.get_uid(file), //拖拽上传的时候没有uid
percent: 0, //加载进度
name: file.name,
size: file.size,
type: file.type,
webkitRelativePath: file.webkitRelativePath,
type: "",
tip: "",
color: "",
});
if (this.hideUploadTrayWhenDrag === "" || this.hideUploadTrayWhenDrag) {
this.showUploadTray = false; //不显示右下角上传托盘
} else {
this.showUploadTray = true; //显示右下角上传托盘
}
// 判断是不是特定的格式________________________
let isFile =
this.accept === "*"
? true
: this.accept.includes(file.name.toLocaleLowerCase().split(".").pop());
const minSize = 0.001; //限制文件最小1KB
const isAllowMinSize = file.size / 1024 / 1024 > minSize;
isAllowMinSize ||
this.$message.error("上传文件大小不能小于" + minSize * 1000 + "KB");
const maxSize = this.maxSize || 500; //限制大小
const isAllowSize = file.size / 1024 / 1024 <= maxSize;
isFile || this.$message.error("上传文件只能是" + this.accept + "格式");
isAllowSize || this.$message.error("上传文件大小不能超过" + maxSize + "MB");
let allowUpload = isFile && isAllowSize && isAllowMinSize;
if (allowUpload) {
this.showFakeLoading(file);
this.$nextTick(() => {
this.$g.file2Base64Image(file, (d) => this.$emit(`resultBase64Image`, d));
this.$emit(`beforeUpload`, file);
});
} else {
this.hideFakeLoading(file, { type: "error", tip: "上传失败", color: "red" });
}
return allowUpload; //若返回false则停止上传
},
//上传成功
uploadSuccess(response, file, fileList) {
/* if ((this.drag === "" || this.drag) && this.isDragTrigger) {
if (!file.isDragFile) return; //拖拽模式下,如果不是原生js捕获到的拖拽文件,就不进入上传队列(区别开两个el-upload组件避免重复上传)
}*/
if (response.data && response.data.key) {
// 下载失败原因的描述文件
/* this.$d.customer_downloadImportCustomerExcel(
{ key: response.data.key },
{
s: (d) => {
this.$emit(`error`, response, file);
this.hideFakeLoading(file, {
type: "error",
tip: "上传失败",
color: "red",
});
this.$g.downloadFile(d, `${file.name}-上传失败原因`, ".xls");
this.$message.error(`${file.name}-上传失败,请查看失败原因`);
},
}
); */
} else if (response.success) {
this.$emit(`uploadSuccess`, response, file);
// 上传成功了
this.hideFakeLoading(file, { type: "success", tip: "上传成功", color: "green" });
this.$message.success(`${file.name}上传成功`);
} else {
this.$emit(`uploadError`, response, file);
// 其他失败原因
this.hideFakeLoading(file, { type: "error", tip: "上传失败", color: "red" });
}
},
//上传失败
uploadError(err, file, fileList) {
if (this.actionUrl === "#") return;
if ((this.drag === "" || this.drag) && this.isDragTrigger) {
if (!file.isDragFile) return; //拖拽模式下,如果不是原生js捕获到的拖拽文件,就不进入上传队列(区别开两个el-upload组件避免重复上传)
}
this.$emit(`uploadError`, err, file);
this.hideFakeLoading(file, { type: "error", tip: "上传失败", color: "red" });
this.$message.error("上传失败");
},
},
};
</script>
<style lang="scss">
.sgDragUploadFolder {
width: 0;
height: 0;
.el-upload-dragger {
z-index: 999999; //根据情况自己拿捏
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
display: none;
background-color: #ffffff99;
&::after {
content: "拖拽文件到此处";
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
color: #409eff;
font-size: 18px;
font-weight: bold;
line-height: 1.2;
}
}
&[dragenter] .el-upload-dragger {
display: block;
border-color: #409eff;
&.is-dragover {
background-color: #409eff22;
&::after {
content: "松掉鼠标上传文件";
}
}
}
}
</style>
<template>
<div>
<div style="width:300px;height:300px;position: relative;">
<img :src="src" style="width:100%;height:100%">
<!-- 上传组件 -->
<sgDragUploadFolder drag ref="sgDragUploadFolder" :data="{
accept: `*`,
actionUrl: `${$d.API_ROOT_URL}/customer/importCustomerData`,
}" @resultBase64Image="resultBase64Image" @success="uploadSuccess" @error="uploadError" hideUploadTray />
</div>
<el-button type="primary" icon="el-icon-upload2" @click="d => $refs.sgDragUploadFolder.triggerUploadFile()">上传</el-button>
</div>
</template>
<script>
import sgDragUploadFolder from "@/vue/components/admin/sgDragUploadFolder";
export default {
components: {
sgDragUploadFolder,
},
data() {
return {
src: '',
}
},
methods: {
resultBase64Image(d, f) {
this.src = d;
},
uploadSuccess(d, f) { }, uploadError(d, f) { },
}
};
</script>