2024年3月20日 更新特性:支持任务管理设置同时上传的最大任务数,满足个性化同时上传数量需求。
特性:
- 可以全屏
- 可以还原尺寸、拖拽调整托盘尺寸
- 可以最小化
- 可以回到右下角默认位置
- 支持删除队列数据(支持一键清除所有上传记录)
- 支持清空已经上传成功的记录
- 上传过程在百分号右侧会有旋转加载动画
- 支持提示上传超大文件的title
- 实时加载上传进度条(逼真的已加载数据大小)
- 支持清除队列中失败的记录
- 支持显示实时上传速度、已耗时间、剩余上传时间
- 支持项卡切换上传中、成功、失败的队列
- 支持列表翻页
- 支持动态设置【同时上传的最大任务数】
sgUploadTray_v2源码
<template> <!-- 新特性: 1、支持用户自定义同时上传任务数量2024.03.19 --> <div :class="$options.name" :show="show" :size="size" :style="style"> <div class="upload-list-tray"> <!-- 托盘头部 --> <div class="header" ref="header" @dblclick.stop.prevent="dblclickHeader"> <div class="left"> <div class="title"> <span class="upload-count" slot="reference">上传队列</span> <div class="upload-info" v-if="liveSpeed && liveSpeed > 0"> <el-divider :direction="`vertical`" /> <div class="info-item live-speed"> <label>速度</label> <span>{{ $g.getSize(liveSpeed) }}/s</span> </div> <div class="info-item taken-time" v-if="takenTime && takenTime > 0"> <label>已耗时</label> <span>{{ $g.date.toHourMinuteSecondByMillisecond(takenTime * 1000, { zh: true, //中文单位 hideMilliSecond: true, //隐藏毫秒 hideZero: true, //隐藏为0的时间单位 }) }}</span> </div> <div class="info-item remain-time" v-if="remainTime && remainTime > 0"> <label>剩余</label ><span>{{ $g.date.toHourMinuteSecondByMillisecond(remainTime * 1000, { zh: true, //中文单位 hideMilliSecond: true, //隐藏毫秒 hideZero: true, //隐藏为0的时间单位 }) }}</span> </div> </div> </div> </div> <div class="right" @mousedown.stop> <!-- 控制文件的图标按钮 --> <div class="file-btns" v-if="showDelSuccessIconBtn || showErrorIconBtn"> <div class="icon-btn" v-if="showDelSuccessIconBtn" @dblclick.stop @click.stop="clearAllSuccessFile" title="清除所有已经成功的上传记录" > <i class="el-icon-delete" style="color: #67c23a"></i> </div> <div class="icon-btn" v-if="showErrorIconBtn" @dblclick.stop @click.stop="clearAllErrorFile" title="清除所有失败的上传记录" > <i class="el-icon-delete-solid" style="color: #f56c6c"></i> </div> <div class="icon-btn" v-if="showErrorIconBtn" @dblclick.stop @click.stop="uploadAllErrorFile" title="重新上传所有失败的文件" > <i class="el-icon-upload2" style="color: #409eff"></i> </div> </div> <!-- 控制托盘的图标按钮 --> <div class="tray-btns"> <div class="icon-btn" v-if="size !== 'lg' && showRightBottomBtn" @dblclick.stop @click.stop="toRightBottomPosition" title="回到原来的位置" > <i class="el-icon-bottom-right"></i> </div> <div class="icon-btn" v-if="size !== 'mn'" @dblclick.stop @click.stop="size = 'mn'" title="最小化" > <i class="el-icon-minus"></i> </div> <div class="icon-btn" v-if="size !== 'md'" @dblclick.stop @click.stop="size = 'md'" title="还原" > <i :class="size === 'lg' ? 'el-icon-copy-document' : 'el-icon-d-caret'"></i> </div> <div class="icon-btn" v-if="size !== 'lg'" @dblclick.stop @click.stop="size = 'lg'" title="全屏" > <i class="el-icon-full-screen"></i> </div> <div class="icon-btn" @dblclick.stop @click.stop="close"> <i class="el-icon-close"></i> </div> </div> </div> </div> <div class="body"> <div class="left" :collapse="collapseMenu"> <el-menu :show-timeout="0" :default-active="defaultMenuActive" :background-color="'white'" :text-color="'#333'" :active-text-color="'#409EFF'" :collapse="collapseMenu" :unique-opened="false" @select="menuSelect" > <template v-for="(a, i) in menuList"> <!-- 有子栏目---> <el-submenu :key="i" :index="a.path" v-if="a.children && a.children.length" :disabled="a.disabled" > <template slot="title"> <i :class="a.icon" v-if="a.icon" /> <span>{{ a.label }}</span> </template> <el-menu-item-group> <el-menu-item v-for="(a, i) in a.children" :key="i" :index="a.path" :disabled="a.disabled" > <i :class="a.icon" v-if="a.icon" /> <span>{{ a.label }}</span> </el-menu-item> </el-menu-item-group> </el-submenu> <!-- 没有子栏目--> <el-tooltip :content="a.label" :placement="`left`" :disabled="!collapseMenu" v-else > <el-menu-item :key="i" :index="a.path" :disabled="a.disabled"> <i :class="a.icon" v-if="a.icon" /> <span>{{ a.label }}</span> </el-menu-item> </el-tooltip> </template> </el-menu> <!-- 折叠按钮 --> <div class="collapseBtn" @click="collapseMenu = !collapseMenu"> <i class="el-icon-caret-right" v-if="collapseMenu" /> <i class="el-icon-caret-left" v-else /> </div> </div> <!-- 设置 ------------------------------------------> <div class="right" v-if="defaultMenuActive === `set`"> <div class="set-list"> <el-collapse v-model="collapseActiveName_set" accordion> <el-collapse-item :name="parseInt(index)" v-for="(item, index) in collapseItems_set" :key="index" > <template slot="title"> <h1>{{ item.title }}</h1> </template> <div class="form-body"> <el-form @submit.native.prevent label-position="right" size="mini"> <el-form-item :label="`同时上传的最大任务数`" label-width=""> <el-input-number style="width: 100px" v-model.trim="setData.uploadMaxCount" :precision="0" :step="1" :min="1" :max="10" :controls-position="`left`" /> </el-form-item> </el-form> </div> </el-collapse-item> </el-collapse> </div> </div> <!-- 上传中的文件列表 ------------------------------------------> <div class="right" v-else> <div class="upload-file-list"> <ul v-if="tableData.length"> <li v-for="(a, i) in tableData" :key="i" :title=" a.size > 1024 * 1024 * 500 ? `超大文件上传中,请耐心等待,切勿关闭或刷新浏览器!` : '' " > <div class="left"> <div class="icon-btns"> <el-button title="移出上传队列" :show="a.status === 'error'" class="remove-icon-btn icon-btn" type="danger" icon="el-icon-delete-solid" size="mini" plain circle @click.stop="removeUploadFile(a)" ></el-button> </div> <!-- 动画加载旋转 --> <div class="fileLoading" v-loading="a.percent <= 100" v-if="a.status !== 'error' && a.status !== 'success'" ></div> <!-- 上传成功icon --> <div class="loadingSuccessIcon" v-if="a.status === `success`"> <i class="el-icon-success" style="color: #67c23a"></i> </div> <!-- 上传失败icon --> <div class="loadingEorrorIcon" v-if="a.status === 'error'"> <i class="el-icon-error" style="color: #f56c6c"></i> </div> <span class="name" :title="a.filePath || a.name"> {{ a.filePath || a.name }} <!-- {{ a.filePath && a.filePath.includes(`/`) ? `[路径:${a.filePath}]` : "" }} --> </span> <el-tag class="size" size="mini" >{{ $g.getSize(a.size * (a.percent / 100)) }}/{{ $g.getSize(a.size) }}</el-tag > <!-- <el-progress class="progress" :percentage="a.percent"></el-progress> --> <el-progress class="progress" style="width: 100%" type="line" :percentage="parseInt(a.percent)" :show-text="true" :stroke-width="10" :text-inside="false" :color="'#409EFF'" :define-back-color="'#eee'" /> </div> <div class="right"> <span class="tip" :color="a.color">{{ a.tip }}</span> <div class="icon-btns" v-if="a.status !== `uploading` && a.status !== `success`" > <el-button title="重新上传" class="upload-icon-btn icon-btn" type="primary" icon="el-icon-upload2" size="mini" plain circle @click.stop="startUploadFile(a)" ></el-button> </div> </div> </li> </ul> <!-- 自定义空状态 --> <el-empty v-else> <div slot="image"><img :src="require('@/assets/404.png')" /></div> <div slot="description">{{ getEmptyText() }}</div> </el-empty> </div> <el-pagination style="width: 100%; text-align: center; margin-top: 10px" background :hidden="total <= 10" :layout="`total, sizes, prev, pager, next, jumper`" :page-sizes="[10, 20, 50, 100]" :pager-count="7" :current-page.sync="currentPage" :page-size.sync="pageSize" :total="total" @size-change="initList" @current-change="initList" /> </div> </div> <div class="footer"> <div class="text" v-html="popoverContent"></div> <div class="progress" v-if="uploadList.length > 1 && totalPercentage < 100"> <label>。总进度</label> <el-progress style="width: 100%" type="line" :percentage="parseInt(totalPercentage)" :show-text="true" :stroke-width="10" :text-inside="false" :color="'#409EFF'" :define-back-color="'#eee'" /> </div> </div> </div> <!-- 拖拽移动窗体 --> <sgDragMove :data="dragMoveDoms" :cursor="{ grab: 'default', grabbing: 'default', }" nearPadding="10" :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="minWidth" :minHeight="minHeight" /> </div> </template> <script> import $g from "@/js/sg"; //必须要单独引入,否者用$g.mount挂在该组件的时候无法获取到$g import sgDragMove from "@/vue/components/admin/sgDragMove"; import sgDragSize from "@/vue/components/admin/sgDragSize"; export default { name: "sgUploadTray_v2", components: { sgDragMove, sgDragSize, }, data() { return { currentPage: 1, pageSize: 10, total: 0, tableData: [], //当前显示队列(不代表所有的上传队列,只为不要太卡) // maxShowUploadFileCount: 10, //默认展示上传数量 // expandAllUploadList: false, //默认折叠 minWidth: 950, minHeight: 40, style_bk: null, style: {}, resizeable_: true, disabledDragMove: false, //屏蔽移动 show: false, showRightBottomBtn: false, size: "md", //lg全屏、md普通、mn最小 uploadList: [], uploadingFiles: [], //真正上传中的文件对象数组 dragMoveDoms: [ /* { canDragDom: elementDOM,//可以拖拽的位置元素 moveDom: elementDOM,//拖拽同步移动的元素 } */ ], //可以拖拽移动的物体 lastUploadedTotalSize: 0, //记录上次已经下载完成的总大小 liveSpeed: 0, //瞬时下载速度(单位B) takenTime: 0, //已耗时 remainTime: 0, //剩余下载时长 interval: null, second: 1, //轮训间隔秒钟 successFileList: [], //成功文件列表 errorFileList: [], //失败文件列表 remainFileList: [], //剩余文件列表 collapseActiveName_set: 0, collapseItems_set: [ // { value: 1, title: "基本设置", content: "开发中" }, { value: 2, title: "任务管理", content: "开发中" }, // { value: 3, title: "上传设置", content: "开发中" }, // { value: 4, title: "提醒", content: "开发中" }, // { value: 5, title: "高级设置", content: "开发中" }, ], // defaultMenuActive: `uploading`, //当前激活菜单的 index defaultMenuActive: `set`, //测试 collapseMenu: false, //是否水平折叠收起菜单(仅在 mode 为 vertical 时可用) menuList: [ { label: "上传中", path: "uploading", icon: "el-icon-upload2", }, { label: "已完成", path: "success", icon: "el-icon-success", }, { label: "失败", path: "error", icon: "el-icon-error", }, { label: "设置", path: "set", icon: "el-icon-s-tools", }, ], setData: { uploadMaxCount: 5, //同时上传的最大任务数 }, //上传设置配置参数 }; }, props: ["data", "value", "resizeable", "position", "store"], watch: { setData: { handler(d) { if (d && Object.keys(d).length) { // console.log(this.store, this.$store); this.store && (this.store.getters._global.uploadSetData = d); //用于$g.mount挂在该组件的时候无法获取到$store this.$store && (this.$store.getters._global.uploadSetData = d); } }, deep: true, immediate: true, }, value: { handler(d) { this.show = d; }, deep: true, immediate: true, }, show: { handler(d) { d && (this.defaultMenuActive = `uploading`); this.$emit(`input`, d); }, deep: true, immediate: true, }, data: { handler(d) { this.uploadList = d || []; }, deep: true, immediate: true, }, uploadList: { handler(newValue, oldValue) { if (newValue && Object.keys(newValue).length) { this.interval || this.startUploadCalcLiveSpeed(); this.successFileList = newValue.filter((v) => v.status === `success`); //成功队列 this.errorFileList = newValue.filter((v) => v.status === "error"); //失败队列 this.remainFileList = newValue.filter( (v) => v.status !== "error" && v.status !== "success" ); //还需要上传的队列 } else { this.successFileList = []; this.errorFileList = []; this.remainFileList = []; } // 计算正上传中的文件对象数组---------------------------------------- this.uploadingFiles = (newValue || []).filter((v) => v.status === "uploading"); this.$emit(`changeUploadingListClose`, { path: this.position, close: this.close, }); // ---------------------------------------- this.initList(); }, 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, //立即执行 }, }, computed: { showDelSuccessIconBtn(d) { return this.uploadList.some((v) => v.status === `success`); }, showErrorIconBtn(d) { return this.uploadList.some((v) => v.status === "error"); }, popoverContent(d) { let r = []; this.successFileList.length && r.push( `已上传成功<span style="color: #67C23A;">${this.successFileList.length}</span>个` ); this.errorFileList.length && r.push(`失败<span style="color: #F56C6C;">${this.errorFileList.length}</span>个`); this.remainFileList.length && r.push( `剩余<span style="color: #409EFF;">${this.remainFileList.length}</span>个` ); if (this.uploadList.length) { return `共计${this.uploadList.length}个文件,${ r.length ? `${r.join(",")}文件` : `` }`; } else { return `暂无待上传文件`; } }, // 总体进度 totalPercentage() { if (this.uploadList.length) { return parseFloat( ((this.successFileList.length / this.uploadList.length) * 100).toFixed(2) ); } else { return 0; } }, }, mounted() { this.$el.style.setProperty("--minWidth", `${this.minWidth}px`); //js往css传递局部参数 this.$el.style.setProperty("--minHeight", `${this.minHeight}px`); //js往css传递局部参数 this.dragMoveDoms = [ { canDragDom: this.$refs.header, //托盘的头部可以拖拽 moveDom: this.$el, //拖拽的时候,整个上传列表一起跟随移动 }, ]; }, destroyed() { clearInterval(this.interval); }, methods: { saveSet(d) {}, getEmptyText() { let label = (this.menuList.find((v) => v.path === this.defaultMenuActive) || {}) .label; return `暂无${label ? `${label}的` : ``}文件`; }, //菜单激活回调 menuSelect(index, path) { this.defaultMenuActive = index; //做其他操作 this.initList(); }, //静态数据翻页(支持筛选搜索) initList({ keyword = this.keyword } = {}) { let results = this.uploadList.filter((v) => keyword ? v.name.includes(keyword) : true ); switch (this.defaultMenuActive) { case "uploading": results = results.filter( (v, i, ar) => v.status === `uploading` || v.status === `` ); break; case "success": case "error": results = results.filter((v, i, ar) => v.status === this.defaultMenuActive); break; default: } this.total = results.length; this.tableData = results.slice( (this.currentPage - 1) * this.pageSize, this.currentPage * this.pageSize ); }, // 开始计算瞬时下载速度 startUploadCalcLiveSpeed() { clearInterval(this.interval); this.interval = setInterval(() => { this.calcLiveSpeed(); }, 1000 * this.second); }, // 结束计算瞬时下载速度 endUploadCalcLiveSpeed(d) { clearInterval(this.interval); this.interval = null; this.liveSpeed = 0; this.takenTime = 0; this.remainTime = 0; }, // 没有上传进程文件才结束计算速度 ifNoUploadingFile_EndCalcLiveSpeed(d) { this.uploadingFiles.length || this.endUploadCalcLiveSpeed(); }, // 计算瞬时下载速度 calcLiveSpeed(d) { this.takenTime++; let uploadList = this.uploadList; if (uploadList.length) { let totalSize = uploadList.reduce( (prevResult, current) => prevResult + current.size, 0 ); //求和需要上传的文件总大小 let uploadedTotalSize = uploadList.reduce( (prevResult, current) => prevResult + current.size * (0.01 * current.percent), 0 ); //求和已经上传的文件总大小 let remainTotalSize = totalSize - uploadedTotalSize; //剩余需要上传的文件 if (this.lastUploadedTotalSize) { this.liveSpeed = (uploadedTotalSize - this.lastUploadedTotalSize) / this.second; //瞬时速度 this.remainTime = remainTotalSize / this.liveSpeed; //瞬时剩余时长 } else { this.liveSpeed = 0; } this.lastUploadedTotalSize = uploadedTotalSize; //记录本次已经上传的总大小 } else { this.endUploadCalcLiveSpeed(); } }, clearAllSuccessFile() { let successFileList = this.uploadList.filter((v) => v.status === `success`); if (successFileList.length === 0) return this.$message(`暂无可以移除的成功记录,请稍后再试!`); this.$emit(`clearAllSuccessFile`, successFileList); // this.$nextTick(() => { successFileList.forEach((file) => file.removeFile()); //移除原始队列&托盘队列 this.uploadList = this.uploadList.filter((v) => v.percent <= 100); // }); }, clearAllErrorFile() { let errorFileList = this.uploadList.filter((v) => v.status === "error"); if (errorFileList.length === 0) return this.$message(`暂无可以移除的失败记录,请稍后再试!`); this.$emit(`clearAllErrorFile`, errorFileList); this.$nextTick(() => { errorFileList.forEach((file) => file.removeFile()); //移除原始队列&托盘队列 this.uploadList = this.uploadList.filter((v) => v.status !== "error"); }); }, uploadAllErrorFile(d) { let errorFileList = this.uploadList.filter((v) => v.status === "error"); errorFileList.forEach((fileData) => this.startUploadFile(fileData)); if (errorFileList.length === 0) return this.$message(`暂无失败记录,请稍后再试!`); this.$emit(`uploadAllErrorFile`, errorFileList); }, draggingSize({ style }) { this.disabledDragMove = true; this.style = style; }, toRightBottomPosition(d) { this.showRightBottomBtn = false; let rect = this.$el.getBoundingClientRect(); this.$el.style.left = `${innerWidth - rect.width}px`; this.$el.style.top = `${innerHeight - rect.height}px`; // 用下面的写法会清除掉setProperty属性 /* 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: F; } }, removeAllFilesFromList() { this.uploadList && this.uploadList.length && this.uploadList.slice(-1)[0].removeAllFile(); //移除原始队列&托盘队列 }, removeFileFromList(d) { d.removeFile(); //移除原始队列&托盘队列 }, // 重新上传失败的记录 startUploadFile(fileData) { fileData.startUpload({ handleTrigger: true }); }, // 移出某一个队列文件 removeUploadFile(d) { if (d.status === "uploading") { this.$confirm(`${d.name}正在上传中,确定要取消吗?`, `提示`, { dangerouslyUseHTMLString: true, confirmButtonText: `确定`, cancelButtonText: `取消`, type: "warning", }) .then(() => { this.$emit(`stopUpload`, [d]); this.$nextTick(() => { this.removeFileFromList(d); this.ifNoUploadingFile_EndCalcLiveSpeed(); }); }) .catch(() => {}); } else { this.removeFileFromList(d); } }, // 关闭托盘 close({ cb } = {}) { let stopUploadList = this.uploadingFiles; if (stopUploadList.length) { this.$confirm(`您还有正在上传中的文件,确定要取消吗?`, `提示`, { dangerouslyUseHTMLString: true, confirmButtonText: `确定`, cancelButtonText: `取消`, type: "warning", }) .then(() => { this.doRemove({ stopUploadList, cb }); }) .catch(() => {}); } else { this.doRemove({ stopUploadList, cb }); } }, doRemove({ stopUploadList, cb } = {}) { this.show = false; this.$emit(`stopUpload`, stopUploadList); this.$nextTick(() => { this.endUploadCalcLiveSpeed(); this.removeAllFilesFromList(); //清空上传列表 cb && cb(stopUploadList); //完成后回调 }); }, }, }; </script> <style lang="scss" scoped> .sgUploadTray_v2 { $bodyLeftWidth: 150px; //左侧分类菜单宽度 $headerHeight: 40px; //头部高度 $footerHeight: 40px; //底部统计上传进度高度 $minWidth: var(--minWidth); //托盘最小宽度 $minHeight: var(--minHeight); //托盘最小高度 $leftIconBtnWidth: 50px; //左侧删除按钮宽度 $rightIconBtnWidth: 50px; //右侧重新上传按钮宽度 $loadingWidth: 30px; //加载动画宽度(旋转全全) $rightWidth: 150px; //右侧宽度 $sizeWidth: 200px; //文件大小宽度宽度 $progressWidth: 100px; //进度条宽度 $tipWidth: 100px; //提示文本宽度 // ---------------------------------------- z-index: 2001; //根据情况自己拿捏(太大了会遮住element的其他弹窗组件),v-loading默认是2000的z-index user-select: none; position: fixed; right: 10px; bottom: 10px; width: $minWidth; background-color: white; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); border-radius: 8px; 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; .upload-file-list { max-height: calc(100vh - 60px) !important; } } &[size="md"] { width: $minWidth; height: revert; } &[size="mn"] { width: $minWidth; height: $minHeight; } .upload-list-tray { display: flex; flex-direction: column; box-sizing: border-box; padding-bottom: 20px; width: 100%; height: 100%; position: relative; .header { flex-shrink: 0; font-size: 16px; font-weight: bold; width: 100%; height: $headerHeight; box-sizing: border-box; padding: 10px 20px; /*从上往下线性渐变背景*/ background: linear-gradient(#409eff11, white); color: #409eff; display: flex; justify-content: space-between; align-items: center; .left { display: flex; align-items: center; flex-grow: 1; .title { display: flex; align-items: center; flex-wrap: nowrap; .upload-info { display: flex; align-items: center; color: black; flex-shrink: 0; align-items: center; font-weight: normal; .info-item { margin-right: 5px; &:last-of-type { margin-right: 0; } span { font-family: DIN-Light; color: #409eff; } &.live-speed { span { font-family: DIN-Black; } } } } } .icon-btns { display: flex; align-items: center; flex-wrap: nowrap; .icon-btn { cursor: pointer; margin-right: 5px; &:last-of-type { margin-right: 0; } i { pointer-events: none; } &:hover { opacity: 0.618; } } } } .right { display: flex; align-items: center; justify-content: flex-end; flex-shrink: 0; pointer-events: auto; .icon-btn { margin-left: 10px; cursor: pointer; i { pointer-events: none; } &:hover { opacity: 0.618; } &:first-of-type { margin-left: 0; } } .file-btns { margin-left: 10px; display: flex; flex-wrap: nowrap; justify-content: flex-end; box-sizing: border-box; padding: 0 10px; border-right: 1px solid #eee; } .tray-btns { margin-left: 10px; display: flex; flex-wrap: nowrap; justify-content: flex-end; } } } .body { display: flex; flex-wrap: nowrap; width: 100%; height: calc(100% - #{$headerHeight} - #{$footerHeight} + 20px); max-height: calc(100vh - #{$headerHeight} - #{$footerHeight} - 40px); & > .left { // transition: 0.382s; position: relative; flex-shrink: 0; width: $bodyLeftWidth; box-sizing: border-box; padding: 0 10px 0 20px; border-right: solid 1px #eff0f1; >>> .el-menu { transition: none; .el-menu-item { transition: none; margin-bottom: 5px; border-radius: 8px; &:last-of-type { margin-bottom: 0; } &:focus, &:hover { background-color: #f5f6f7 !important; } &.is-active { background-color: #e9effb !important; } } } .collapseBtn { transform: translateY(50%); //防止托盘最小高度的时候还冒出一小截 width: 10px; height: 20px; display: flex; justify-content: center; align-items: center; color: white; background-color: #409eff; font-size: 12px; position: absolute; margin: auto; top: 0; right: -10px; bottom: 0; z-index: 1; border-radius: 0 4px 4px 0; box-sizing: border-box; padding: 20px 0; cursor: pointer; &:hover { filter: brightness(1.1); } } &[collapse] { width: 70px; >>> .el-menu { .el-menu-item { width: 40px; height: 40px; display: flex; justify-content: center; align-items: center; span { display: none; } } } } } & > .right { width: calc(100% - #{$bodyLeftWidth}); flex-grow: 1; box-sizing: border-box; padding: 0 20px 0 10px; .upload-file-list { width: 100%; flex-grow: 1; overflow-y: auto; position: relative; max-height: calc(100% - 42px); min-height: 200px; height: 100%; ul { width: 100%; & > li { line-height: 1.6; box-sizing: border-box; padding: 10px; border-radius: 8px; display: flex; justify-content: space-between; align-items: center; width: 100%; height: 50px; & > .left { width: calc(100% - #{$rightWidth}); display: flex; align-items: center; flex-grow: 1; flex-shrink: 0; // 上传队列记录左侧侧操作按钮 .icon-btns { display: flex; flex-wrap: nowrap; .icon-btn { display: none; &[show] { margin-right: 15px; display: block; } } } .fileLoading { flex-shrink: 0; width: 30px; margin-right: 5px; height: 0; transform: scale(0.5); } .loadingSuccessIcon, .loadingEorrorIcon { margin-right: 5px; width: 30px; height: 30px; display: flex; justify-content: center; align-items: center; flex-shrink: 0; } .name { text-align: left; margin-right: 10px; width: calc( 100% - #{$loadingWidth} - #{$sizeWidth} - #{$progressWidth} - #{$rightWidth} - 20px ); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; flex-shrink: 0; flex-grow: 1; } .size { margin-right: 10px; max-width: $sizeWidth; /*单行省略号*/ overflow: hidden; white-space: nowrap; text-overflow: ellipsis; flex-shrink: 0; } >>> .progress { max-width: $progressWidth; display: flex; align-items: center; flex-wrap: nowrap; flex-shrink: 0; .el-progress-bar__inner { transition: none; } } } & > .right { display: flex; align-items: center; justify-content: flex-end; width: $rightWidth; .tip { width: $tipWidth; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; flex-shrink: 0; text-align: right; &[color="red"] { color: #f56c6c; } &[color="green"] { color: #67c23a; } &[color="blue"] { color: #409eff; } } // 上传队列记录右侧操作按钮 .icon-btns { display: flex; flex-wrap: nowrap; .icon-btn { display: none; &[show] { margin-left: 15px; display: block; } } } } &:hover { background-color: #409eff11; color: #409eff; .left { // 移入上传队列记录左侧操作按钮 .icon-btns { .icon-btn { display: block; &:last-of-type { margin-right: 10px; } } } /* .name { width: calc( 100% - #{$leftIconBtnWidth} - #{$loadingWidth} - #{$sizeWidth} - #{$progressWidth} - #{$rightWidth} - #{$rightIconBtnWidth} - 20px ); } */ } .right { .tip { // margin-right: 15px; } // 移入上传队列记录右侧操作按钮 .icon-btns { margin-left: 15px; .icon-btn { display: block; &:first-of-type { margin-left: 0; } } } } } } } .el-empty { width: max-content; height: max-content; position: absolute; margin: auto; top: 0; left: 0; right: 0; bottom: 0; } } } } .footer { z-index: 1; font-weight: normal; flex-shrink: 0; font-size: 14px; font-weight: bold; width: 100%; height: $footerHeight; box-sizing: border-box; padding: 10px 20px; margin-bottom: -20px; background: linear-gradient(white, #eff2f7); color: #909399; display: flex; align-items: center; flex-wrap: nowrap; white-space: nowrap; * { font-weight: normal; } .text { white-space: nowrap; } .progress { max-width: 200px; flex-grow: 1; white-space: nowrap; display: flex; align-items: center; flex-wrap: nowrap; label { white-space: nowrap; flex-shrink: 0; margin-right: 5px; } >>> .el-progress { white-space: nowrap; .el-progress__text { font-weight: normal; font-size: 14px !important; } } } } } } </style>
应用
<template> <div :class="$options.name"> <el-button type="primary" @click="$refs.sgUpload_v2.triggerUploadFolder()" >点击上传文件夹</el-button > <!-- 上传组件 --> <sgUpload_v2 :noCheckFile="true" ref="sgUpload_v2" :data="uploadData" :sgUploadTray="sgUploadTray" hideMessageSuccessTip /> <!-- 上传托盘(右下角) --> <sgUploadTray ref="sgUploadTray" resizeable /> </div> </template> <script> import sgUpload_v2 from "@/vue/components/admin/sgUpload_v2"; import sgUploadTray from "@/vue/components/admin/sgUploadTray_v2"; export default { name: `demoUploadTray`, components: { sgUpload_v2, sgUploadTray, }, data() { return { //上传相关变量---------------------------------------- sgUploadTray: null, uploadData: { limit: 10000, //限制上传文件个数 name: `FILE`, accept: `*`, actionURL: `${this.$d.API_ROOT_URL}/xxx/xxx`, actionData: { ZYGS: this.$g.getZYGS({ BMID: this.$global.getBMID(), type: this.type, }), sgLog: `前端请求来源:${this.$options.name}资源上传`, }, }, // ---------------------------------------- }; }, mounted(d) { this.sgUploadTray = this.$refs.sgUploadTray; }, }; </script>
基于sgUploadTray 1.0版本迭代