【sgUploadTray】上传托盘自定义组件,可实时查看上传列表进度
特性:
- 可以全屏
- 可以还原尺寸、拖拽调整托盘尺寸
- 可以最小化
- 可以回到右下角默认位置
- 支持删除队列数据
sgUploadTray源码
<template> <div :class="$options.name" :show="show" :size="size" :style="style"> <div class="upload-list"> <div class="header" ref="header" @dblclick.stop.prevent="dblclickHeader"> <div class="left"> <div class="title"><span>上传队列:{{ uploadList.length }}个文件</span></div> </div> <div class="right" @mousedown.stop> <div v-if="size !== 'lg' && showRightBottomBtn" @click.stop="toRightBottomPosition" title="回到原来的位置"> <i class="el-icon-bottom-right"></i> </div> <div v-if="size !== 'mn'" @click.stop="size = 'mn'" title="最小化"> <i class="el-icon-minus"></i> </div> <div v-if="size !== 'md'" @click.stop="size = 'md'" title="还原"> <i :class="size === 'lg' ? 'el-icon-copy-document' : 'el-icon-d-caret'"></i> </div> <div v-if="size !== 'lg'" @click.stop="size = 'lg'" title="全屏"> <i class="el-icon-full-screen"></i> </div> <div @click.stop="close"> <i class="el-icon-close"></i> </div> </div> </div> <div class="file-list"> <ul> <li v-for="(a, i) in uploadList" :key="i"> <div class="left"> <span class="name" :title="a.name">{{ a.name }}</span> <el-tag class="size" size="mini">{{ getSize(a.size) }}</el-tag> <el-progress class="progress" :percentage="a.percent"></el-progress> </div> <div class="right"> <span class="tip" :color="a.color">{{ a.tip }}</span> <el-button class="remove-icon-btn" type="primary" icon="el-icon-close" size="mini" plain circle @click.stop="remove(a)"></el-button> </div> </li> </ul> </div> </div> <!-- 拖拽移动窗体 --> <sgDragMove :data="dragMoveDoms" :cursor="{ grab: 'default', grabbing: 'default' }" nearPadding="50" :disabled="size === 'lg' && disabledDragMove" @dragStart="$emit(`dragStart`, dragMoveDoms)" @dragging="showRightBottomBtn = true; $emit(`dragging`, dragMoveDoms)" @dragEnd="$emit(`dragEnd`, dragMoveDoms)" mousemoveNearSide /> <!-- 拖拽改变窗体尺寸 --> <sgDragSize v-if="resizeable_" :disabled="size === 'lg'" @dragStart="disabledDragMove = true" @dragging="draggingSize" @dragEnd="disabledDragMove = false" :minWidth="600" :minHeight="56" /> </div> </template> <script> import sgDragMove from "@/vue/components/admin/sgDragMove"; import sgDragSize from "@/vue/components/admin/sgDragSize"; export default { name: 'sgUploadTray', components: { sgDragMove, sgDragSize, }, data() { return { style_bk: null, style: {}, resizeable_: true, disabledDragMove: false,//屏蔽移动 show: false, showRightBottomBtn: false, size: 'md',//lg全屏、md普通、mn最小 uploadList: [], dragMoveDoms: [ /* { canDragDom: elementDOM,//可以拖拽的位置元素 moveDom: elementDOM,//拖拽同步移动的元素 } */ ],//可以拖拽移动的物体 } }, props: ["data", "value", 'resizeable'], watch: { value: { handler(d) { this.show = d }, deep: true, immediate: true, }, show: { handler(d) { this.$emit(`input`, d); }, deep: true, immediate: true, }, data: { handler(d) { this.uploadList = d; }, deep: true, immediate: true, }, resizeable: { handler(newValue, oldValue) { this.resizeable_ = newValue === '' || newValue; }, deep: true,//深度监听 immediate: true,//立即执行 }, size: { handler(newValue, oldValue) { switch (newValue) { case 'lg': case 'mn': this.style_bk = JSON.parse(JSON.stringify(this.style)); delete this.style.width, delete this.style.height; break; case 'md': this.style_bk && (this.style = JSON.parse(JSON.stringify(this.style_bk))); break; } }, deep: true,//深度监听 immediate: true,//立即执行 }, }, mounted() { this.dragMoveDoms = [ { canDragDom: this.$refs.header,//托盘的头部可以拖拽 moveDom: this.$el,//拖拽的时候,整个上传列表一起跟随移动 } ]; }, methods: { draggingSize({ style }) { this.disabledDragMove = true; this.style = style; }, /* dragSizeStart(d) { this.disabledDragMove = true; }, dragSizeEnd(d) { this.disabledDragMove = false; }, */ toRightBottomPosition(d) { this.showRightBottomBtn = false; let rect = this.$el.getBoundingClientRect(); this.$el.style = { left: innerWidth - rect.width + "px", top: innerHeight - rect.height + "px", } }, dblclickHeader(d) { switch (this.size) { case 'lg': this.size = 'md'; break; case 'md': this.size = 'mn'; break; case 'mn': this.size = 'md'; break; default: } }, close(d) { let stopUploadList = this.uploadList.filter(v => v.percent < 100 && (v.type !== 'error' && v.type !== 'success')); if (stopUploadList && stopUploadList.length) { this.$confirm(`您还有正在上传中的文件,确定要取消吗?`, `提示`, { dangerouslyUseHTMLString: true, confirmButtonText: `确定`, cancelButtonText: `取消`, type: "warning" }).then(() => { this.show = false; this.$emit(`stopUpload`, stopUploadList); }).catch(() => { }); } else { this.show = false; } }, remove(d) { if (d.type === 'error' || d.type === 'success') { this.uploadList.splice(this.uploadList.findIndex(v => v.uid == d.uid), 1) } else if (d.percent < 100) { this.$confirm(`${d.name}正在上传中,确定要取消吗?`, `提示`, { dangerouslyUseHTMLString: true, confirmButtonText: `确定`, cancelButtonText: `取消`, type: "warning" }).then(() => { this.$emit(`stopUpload`, [d]); }).catch(() => { }); } else { this.uploadList.splice(this.uploadList.findIndex(v => v.uid == d.uid), 1) } }, getSize(d) { return this.$g.getSize(d); }, } }; </script> <style lang="scss" scoped> .sgUploadTray { /*禁止选中文本*/ user-select: none; $width: 600px; //托盘宽度 $listMaxHeight: $width; //托盘宽度 $rightWidth: 200px; //右侧宽度 $sizeWidth: 100px; //文件大小宽度宽度 $progressWidth: 50px; //进度条宽度 $tipWidth: 100px; //提示文本宽度 // ---------------------------------------- z-index: 999999; //根据情况自己拿捏 position: fixed; right: 10px; bottom: 10px; width: $width; background-color: white; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); border-radius: 4px; overflow: hidden; border: 1px solid #eee; font-size: 14px; display: none; &[show] { display: block; } &[size="lg"] { left: 0 !important; top: 0 !important; width: 100vw; height: 100vh; transition: none; } &[size="md"] { width: $width; height: revert; } &[size="mn"] { width: $width; height: 56px; } .upload-list { display: flex; flex-direction: column; box-sizing: border-box; padding-bottom: 20px; width: 100%; .header { flex-shrink: 0; font-size: 16px; font-weight: bold; width: 100%; box-sizing: border-box; padding: 20px; /*从上往下线性渐变背景*/ background: linear-gradient(#409EFF11, white); color: #409EFF; display: flex; justify-content: space-between; align-items: center; .left { flex-grow: 1; .title {} } .right { display: flex; align-items: center; justify-content: flex-end; flex-shrink: 0; pointer-events: auto; &>* { margin-left: 10px; cursor: pointer; i { pointer-events: none; } &:hover { opacity: 0.618; } } } } .file-list { width: 100%; flex-grow: 1; overflow-y: auto; max-height: $listMaxHeight; box-sizing: border-box; padding: 0 20px; ul { width: 100%; li { line-height: 1.6; box-sizing: border-box; padding: 10px; border-radius: 4px; display: flex; justify-content: space-between; align-items: center; width: 100%; .left { display: flex; align-items: center; .name { margin-right: 10px; max-width: $width - $sizeWidth - $progressWidth - $rightWidth - 20px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .size { margin-right: 10px; max-width: $sizeWidth; /*单行省略号*/ overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .progress { max-width: $progressWidth; } } .right { display: flex; align-items: center; justify-content: flex-end; max-width: $rightWidth; .tip { margin-right: 10px; max-width: $tipWidth; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; &[color="red"] { color: #F56C6C; } &[color="green"] { color: #67C23A; } &[color="blue"] { color: #409EFF; } } .btns { .remove-icon-btn { width: 20px; height: 20px; } } } // cursor: pointer; &:hover { background-color: #409EFF11; color: #409EFF; } } } } } } </style>
应用
<template> <div class="sg-body"> <el-upload ref="uploadFolder" :show-file-list="false" :headers="headers" :action="actionURL" :before-upload="beforeUpload" :on-success="uploadSuccess" :on-error="uploadError" :on-exceed="exceed" multiple /> <el-button type="primary" @click="uploadBtn.click()">点击上传文件夹</el-button> <!-- 上传托盘(右下角) --> <sgUploadTray v-model="showUploadTray" :data="uploadList" @stopUpload="stopUpload" resizeable/> </div> </template> <script> import sgUploadTray from "@/vue/components/admin/sgUploadTray"; export default { components: { sgUploadTray, }, data() { return { //上传相关变量---------------------------------------- headers: { kkToken: localStorage.token, }, //获取token(注意仔细看后端接受token的字段名是不是叫做“token”) actionURL: `${this.$d.API_ROOT_URL}/customer/importCustomerData`, fmt: this.$global.resourceTypes.flatMap(v => v.suffixs).filter(Boolean),//所有后缀名 dur: 100, percent: 100, uploadBtn: null,//上传按钮 uploadList: [], showUploadTray: false, // ---------------------------------------- } }, mounted(d) { this.$nextTick(() => { this.uploadBtn = this.$refs.uploadFolder.$children[0].$refs.input; this.uploadBtn.webkitdirectory = true;//让el-upload支持上传文件夹 }) }, methods: { // 上传文件---------------------------------------------------------------- showFakeLoading(file) { file = this.uploadList.find(v => v.uid == file.uid); clearInterval(file.interval); file.percent = 0; file.interval = setInterval(() => { file.percent++; file.percent >= 99 && this.hideFakeLoading(file); }, this.dur); }, hideFakeLoading(file, { type, tip, color } = {}) { file = this.uploadList.find(v => 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(); //console.log(`取消上传`, d); }, //文件上传之前 beforeUpload(file, id) { this.uploadList.unshift({ interval: false, uid: file.uid, percent: 0,//加载进度 name: file.name, size: file.size, type: file.type, webkitRelativePath: file.webkitRelativePath, type: '', tip: '', color: '', }); this.showUploadTray = true; // 判断是不是特定的格式________________________ let isFile = this.fmt.includes(file.name.toLocaleLowerCase().split(".").pop()); const maxSize = 50; //限制大小 const isAllowSize = file.size / 1024 / 1024 <= maxSize; isFile || this.$message.error("上传文件只能是" + this.fmt + "格式"); isAllowSize || this.$message.error("上传文件大小不能超过" + maxSize + "MB"); let allowUpload = isFile && isAllowSize; allowUpload ? this.showFakeLoading(file) : this.hideFakeLoading(file, { type: 'error', tip: "上传失败", color: "red" }); return allowUpload; //若返回false则停止上传 }, //上传成功 uploadSuccess(response, file, fileList) { if (response.data && response.data.key) { // 下载失败原因的描述文件 this.$d.customer_downloadImportCustomerExcel({ key: response.data.key }, { s: (d) => { this.hideFakeLoading(file, { type: 'error', tip: "上传失败", color: "red" }); this.$g.downloadFile(d, `${file.name}-上传失败原因`, '.xls'); this.$message.error(`${file.name}-上传失败,请查看失败原因`); // this.initList();//刷新列表 //console.log('上传失败', response, file, fileList); } }); } else if (response.success) { // 上传成功了 this.hideFakeLoading(file, { type: 'success', tip: "上传成功", color: "green" }); this.$message.success(`“${file.name}上传成功`); // this.initList();//刷新列表 //console.log('上传成功', response, file, fileList); } else { // 其他失败原因 this.hideFakeLoading(file, { type: 'error', tip: "上传失败", color: "red" }); // this.$message.error(response.msg); //console.log('上传失败', response, file, fileList); } }, //上传失败 uploadError(err, file, fileList) { this.hideFakeLoading(file, { type: 'error', tip: "上传失败", color: "red" }); this.$message.error("上传失败"); //console.log('上传失败', err, file, fileList); }, // ---------------------------------------- }, }; </script> <style lang="scss" scoped> .sg-body { width: 100vw; height: 100vh; overflow: hidden; overflow-y: auto; } </style>
这里面用到的sgDragMove组件在这里