当前位置:   article > 正文

【sgUploadTray_v2】自定义组件:升级版上传托盘自定义组件,可实时查看上传列表进度,可以通过选项卡切换上传中、成功、失败的队列,支持翻页,解决了列表内容太多导致卡顿的情况。

【sgUploadTray_v2】自定义组件:升级版上传托盘自定义组件,可实时查看上传列表进度,可以通过选项卡切换上传中、成功、失败的队列,支持翻页,解决了列表内容太多导致卡顿的情况。

2024年3月20日 更新特性:支持任务管理设置同时上传的最大任务数,满足个性化同时上传数量需求。

特性:

  1. 可以全屏
  2. 可以还原尺寸、拖拽调整托盘尺寸
  3. 可以最小化
  4. 可以回到右下角默认位置
  5. 支持删除队列数据(支持一键清除所有上传记录)
  6. 支持清空已经上传成功的记录
  7. 上传过程在百分号右侧会有旋转加载动画
  8. 支持提示上传超大文件的title
  9. 实时加载上传进度条(逼真的已加载数据大小)
  10. 支持清除队列中失败的记录
  11. 支持显示实时上传速度、已耗时间、剩余上传时间
  12. 支持项卡切换上传中、成功、失败的队列
  13. 支持列表翻页
  14. 支持动态设置【同时上传的最大任务数】

sgUploadTray_v2源码 

  1. <template>
  2. <!--
  3. 新特性:
  4. 1、支持用户自定义同时上传任务数量2024.03.19
  5. -->
  6. <div :class="$options.name" :show="show" :size="size" :style="style">
  7. <div class="upload-list-tray">
  8. <!-- 托盘头部 -->
  9. <div class="header" ref="header" @dblclick.stop.prevent="dblclickHeader">
  10. <div class="left">
  11. <div class="title">
  12. <span class="upload-count" slot="reference">上传队列</span>
  13. <div class="upload-info" v-if="liveSpeed && liveSpeed > 0">
  14. <el-divider :direction="`vertical`" />
  15. <div class="info-item live-speed">
  16. <label>速度</label>
  17. <span>{{ $g.getSize(liveSpeed) }}/s</span>
  18. </div>
  19. <div class="info-item taken-time" v-if="takenTime && takenTime > 0">
  20. <label>已耗时</label>
  21. <span>{{
  22. $g.date.toHourMinuteSecondByMillisecond(takenTime * 1000, {
  23. zh: true, //中文单位
  24. hideMilliSecond: true, //隐藏毫秒
  25. hideZero: true, //隐藏为0的时间单位
  26. })
  27. }}</span>
  28. </div>
  29. <div class="info-item remain-time" v-if="remainTime && remainTime > 0">
  30. <label>剩余</label
  31. ><span>{{
  32. $g.date.toHourMinuteSecondByMillisecond(remainTime * 1000, {
  33. zh: true, //中文单位
  34. hideMilliSecond: true, //隐藏毫秒
  35. hideZero: true, //隐藏为0的时间单位
  36. })
  37. }}</span>
  38. </div>
  39. </div>
  40. </div>
  41. </div>
  42. <div class="right" @mousedown.stop>
  43. <!-- 控制文件的图标按钮 -->
  44. <div class="file-btns" v-if="showDelSuccessIconBtn || showErrorIconBtn">
  45. <div
  46. class="icon-btn"
  47. v-if="showDelSuccessIconBtn"
  48. @dblclick.stop
  49. @click.stop="clearAllSuccessFile"
  50. title="清除所有已经成功的上传记录"
  51. >
  52. <i class="el-icon-delete" style="color: #67c23a"></i>
  53. </div>
  54. <div
  55. class="icon-btn"
  56. v-if="showErrorIconBtn"
  57. @dblclick.stop
  58. @click.stop="clearAllErrorFile"
  59. title="清除所有失败的上传记录"
  60. >
  61. <i class="el-icon-delete-solid" style="color: #f56c6c"></i>
  62. </div>
  63. <div
  64. class="icon-btn"
  65. v-if="showErrorIconBtn"
  66. @dblclick.stop
  67. @click.stop="uploadAllErrorFile"
  68. title="重新上传所有失败的文件"
  69. >
  70. <i class="el-icon-upload2" style="color: #409eff"></i>
  71. </div>
  72. </div>
  73. <!-- 控制托盘的图标按钮 -->
  74. <div class="tray-btns">
  75. <div
  76. class="icon-btn"
  77. v-if="size !== 'lg' && showRightBottomBtn"
  78. @dblclick.stop
  79. @click.stop="toRightBottomPosition"
  80. title="回到原来的位置"
  81. >
  82. <i class="el-icon-bottom-right"></i>
  83. </div>
  84. <div
  85. class="icon-btn"
  86. v-if="size !== 'mn'"
  87. @dblclick.stop
  88. @click.stop="size = 'mn'"
  89. title="最小化"
  90. >
  91. <i class="el-icon-minus"></i>
  92. </div>
  93. <div
  94. class="icon-btn"
  95. v-if="size !== 'md'"
  96. @dblclick.stop
  97. @click.stop="size = 'md'"
  98. title="还原"
  99. >
  100. <i :class="size === 'lg' ? 'el-icon-copy-document' : 'el-icon-d-caret'"></i>
  101. </div>
  102. <div
  103. class="icon-btn"
  104. v-if="size !== 'lg'"
  105. @dblclick.stop
  106. @click.stop="size = 'lg'"
  107. title="全屏"
  108. >
  109. <i class="el-icon-full-screen"></i>
  110. </div>
  111. <div class="icon-btn" @dblclick.stop @click.stop="close">
  112. <i class="el-icon-close"></i>
  113. </div>
  114. </div>
  115. </div>
  116. </div>
  117. <div class="body">
  118. <div class="left" :collapse="collapseMenu">
  119. <el-menu
  120. :show-timeout="0"
  121. :default-active="defaultMenuActive"
  122. :background-color="'white'"
  123. :text-color="'#333'"
  124. :active-text-color="'#409EFF'"
  125. :collapse="collapseMenu"
  126. :unique-opened="false"
  127. @select="menuSelect"
  128. >
  129. <template v-for="(a, i) in menuList">
  130. <!-- 有子栏目--->
  131. <el-submenu
  132. :key="i"
  133. :index="a.path"
  134. v-if="a.children && a.children.length"
  135. :disabled="a.disabled"
  136. >
  137. <template slot="title">
  138. <i :class="a.icon" v-if="a.icon" />
  139. <span>{{ a.label }}</span>
  140. </template>
  141. <el-menu-item-group>
  142. <el-menu-item
  143. v-for="(a, i) in a.children"
  144. :key="i"
  145. :index="a.path"
  146. :disabled="a.disabled"
  147. >
  148. <i :class="a.icon" v-if="a.icon" />
  149. <span>{{ a.label }}</span>
  150. </el-menu-item>
  151. </el-menu-item-group>
  152. </el-submenu>
  153. <!-- 没有子栏目-->
  154. <el-tooltip
  155. :content="a.label"
  156. :placement="`left`"
  157. :disabled="!collapseMenu"
  158. v-else
  159. >
  160. <el-menu-item :key="i" :index="a.path" :disabled="a.disabled">
  161. <i :class="a.icon" v-if="a.icon" />
  162. <span>{{ a.label }}</span>
  163. </el-menu-item>
  164. </el-tooltip>
  165. </template>
  166. </el-menu>
  167. <!-- 折叠按钮 -->
  168. <div class="collapseBtn" @click="collapseMenu = !collapseMenu">
  169. <i class="el-icon-caret-right" v-if="collapseMenu" />
  170. <i class="el-icon-caret-left" v-else />
  171. </div>
  172. </div>
  173. <!-- 设置 ------------------------------------------>
  174. <div class="right" v-if="defaultMenuActive === `set`">
  175. <div class="set-list">
  176. <el-collapse v-model="collapseActiveName_set" accordion>
  177. <el-collapse-item
  178. :name="parseInt(index)"
  179. v-for="(item, index) in collapseItems_set"
  180. :key="index"
  181. >
  182. <template slot="title">
  183. <h1>{{ item.title }}</h1>
  184. </template>
  185. <div class="form-body">
  186. <el-form @submit.native.prevent label-position="right" size="mini">
  187. <el-form-item :label="`同时上传的最大任务数`" label-width="">
  188. <el-input-number
  189. style="width: 100px"
  190. v-model.trim="setData.uploadMaxCount"
  191. :precision="0"
  192. :step="1"
  193. :min="0"
  194. :max="10"
  195. :controls-position="`left`"
  196. />
  197. </el-form-item>
  198. </el-form>
  199. </div>
  200. </el-collapse-item>
  201. </el-collapse>
  202. </div>
  203. </div>
  204. <!-- 上传中的文件列表 ------------------------------------------>
  205. <div class="right" v-else>
  206. <div class="upload-file-list">
  207. <ul v-if="tableData.length">
  208. <li
  209. v-for="(a, i) in tableData"
  210. :key="i"
  211. :title="
  212. a.size > 1024 * 1024 * 500
  213. ? `超大文件上传中,请耐心等待,切勿关闭或刷新浏览器!`
  214. : ''
  215. "
  216. >
  217. <div class="left">
  218. <div class="icon-btns">
  219. <el-button
  220. title="移出上传队列"
  221. :show="a.status === 'error'"
  222. class="remove-icon-btn icon-btn"
  223. type="danger"
  224. icon="el-icon-delete-solid"
  225. size="mini"
  226. plain
  227. circle
  228. @click.stop="removeUploadFile(a)"
  229. ></el-button>
  230. </div>
  231. <!-- 动画加载旋转 -->
  232. <div
  233. class="fileLoading"
  234. v-loading="a.percent <= 100"
  235. v-if="a.status !== 'error' && a.status !== 'success'"
  236. ></div>
  237. <!-- 上传成功icon -->
  238. <div class="loadingSuccessIcon" v-if="a.status === `success`">
  239. <i class="el-icon-success" style="color: #67c23a"></i>
  240. </div>
  241. <!-- 上传失败icon -->
  242. <div class="loadingEorrorIcon" v-if="a.status === 'error'">
  243. <i class="el-icon-error" style="color: #f56c6c"></i>
  244. </div>
  245. <span class="name" :title="a.filePath || a.name">
  246. {{ a.filePath || a.name }}
  247. <!-- {{ a.filePath && a.filePath.includes(`/`) ? `[路径:${a.filePath}]` : "" }} -->
  248. </span>
  249. <el-tag class="size" size="mini"
  250. >{{ $g.getSize(a.size * (a.percent / 100)) }}/{{
  251. $g.getSize(a.size)
  252. }}</el-tag
  253. >
  254. <!-- <el-progress class="progress" :percentage="a.percent"></el-progress> -->
  255. <el-progress
  256. class="progress"
  257. style="width: 100%"
  258. type="line"
  259. :percentage="parseInt(a.percent)"
  260. :show-text="true"
  261. :stroke-width="10"
  262. :text-inside="false"
  263. :color="'#409EFF'"
  264. :define-back-color="'#eee'"
  265. />
  266. </div>
  267. <div class="right">
  268. <span class="tip" :color="a.color">{{ a.tip }}</span>
  269. <div class="icon-btns">
  270. <el-button
  271. v-if="a.status !== 'uploading'"
  272. title="重新上传"
  273. class="upload-icon-btn icon-btn"
  274. type="primary"
  275. icon="el-icon-upload2"
  276. size="mini"
  277. plain
  278. circle
  279. @click.stop="startUploadFile(a)"
  280. ></el-button>
  281. </div>
  282. </div>
  283. </li>
  284. </ul>
  285. <!-- 自定义空状态 -->
  286. <el-empty v-else>
  287. <div slot="image"><img :src="require('@/assets/404.png')" /></div>
  288. <div slot="description">{{ getEmptyText() }}</div>
  289. </el-empty>
  290. </div>
  291. <el-pagination
  292. style="width: 100%; text-align: center; margin-top: 10px"
  293. background
  294. :hidden="total <= 10"
  295. :layout="`total, sizes, prev, pager, next, jumper`"
  296. :page-sizes="[10, 20, 50, 100]"
  297. :pager-count="7"
  298. :current-page.sync="currentPage"
  299. :page-size.sync="pageSize"
  300. :total="total"
  301. @size-change="initList"
  302. @current-change="initList"
  303. />
  304. </div>
  305. </div>
  306. <div class="footer">
  307. <div class="text" v-html="popoverContent"></div>
  308. <div class="progress" v-if="uploadList.length > 1 && totalPercentage < 100">
  309. <label>。总进度</label>
  310. <el-progress
  311. style="width: 100%"
  312. type="line"
  313. :percentage="parseInt(totalPercentage)"
  314. :show-text="true"
  315. :stroke-width="10"
  316. :text-inside="false"
  317. :color="'#409EFF'"
  318. :define-back-color="'#eee'"
  319. />
  320. </div>
  321. </div>
  322. </div>
  323. <!-- 拖拽移动窗体 -->
  324. <sgDragMove
  325. :data="dragMoveDoms"
  326. :cursor="{
  327. grab: 'default',
  328. grabbing: 'default',
  329. }"
  330. nearPadding="10"
  331. :disabled="size === 'lg' && disabledDragMove"
  332. @dragStart="$emit(`dragStart`, dragMoveDoms)"
  333. @dragging="
  334. showRightBottomBtn = true;
  335. $emit(`dragging`, dragMoveDoms);
  336. "
  337. @dragEnd="$emit(`dragEnd`, dragMoveDoms)"
  338. mousemoveNearSide
  339. />
  340. <!-- 拖拽改变窗体尺寸 -->
  341. <sgDragSize
  342. v-if="resizeable_"
  343. :disabled="size === 'lg'"
  344. @dragStart="disabledDragMove = true"
  345. @dragging="draggingSize"
  346. @dragEnd="disabledDragMove = false"
  347. :minWidth="minWidth"
  348. :minHeight="minHeight"
  349. />
  350. </div>
  351. </template>
  352. <script>
  353. import sgDragMove from "@/vue/components/admin/sgDragMove";
  354. import sgDragSize from "@/vue/components/admin/sgDragSize";
  355. export default {
  356. name: "sgUploadTray_v2",
  357. components: {
  358. sgDragMove,
  359. sgDragSize,
  360. },
  361. data() {
  362. return {
  363. currentPage: 1,
  364. pageSize: 10,
  365. total: 0,
  366. tableData: [], //当前显示队列(不代表所有的上传队列,只为不要太卡)
  367. // maxShowUploadFileCount: 10, //默认展示上传数量
  368. // expandAllUploadList: false, //默认折叠
  369. minWidth: 950,
  370. minHeight: 40,
  371. style_bk: null,
  372. style: {},
  373. resizeable_: true,
  374. disabledDragMove: false, //屏蔽移动
  375. show: false,
  376. showRightBottomBtn: false,
  377. size: "md", //lg全屏、md普通、mn最小
  378. uploadList: [],
  379. uploadingFiles: [], //真正上传中的文件对象数组
  380. dragMoveDoms: [
  381. /* {
  382. canDragDom: elementDOM,//可以拖拽的位置元素
  383. moveDom: elementDOM,//拖拽同步移动的元素
  384. } */
  385. ], //可以拖拽移动的物体
  386. lastUploadedTotalSize: 0, //记录上次已经下载完成的总大小
  387. liveSpeed: 0, //瞬时下载速度(单位B)
  388. takenTime: 0, //已耗时
  389. remainTime: 0, //剩余下载时长
  390. interval: null,
  391. second: 1, //轮训间隔秒钟
  392. successFileList: [], //成功文件列表
  393. errorFileList: [], //失败文件列表
  394. remainFileList: [], //剩余文件列表
  395. collapseActiveName_set: 0,
  396. collapseItems_set: [
  397. // { value: 1, title: "基本设置", content: "开发中" },
  398. { value: 2, title: "任务管理", content: "开发中" },
  399. // { value: 3, title: "上传设置", content: "开发中" },
  400. // { value: 4, title: "提醒", content: "开发中" },
  401. // { value: 5, title: "高级设置", content: "开发中" },
  402. ],
  403. // defaultMenuActive: `uploading`, //当前激活菜单的 index
  404. defaultMenuActive: `set`, //测试
  405. collapseMenu: false, //是否水平折叠收起菜单(仅在 mode 为 vertical 时可用)
  406. menuList: [
  407. {
  408. label: "上传中",
  409. path: "uploading",
  410. icon: "el-icon-upload2",
  411. },
  412. {
  413. label: "已完成",
  414. path: "success",
  415. icon: "el-icon-success",
  416. },
  417. {
  418. label: "失败",
  419. path: "error",
  420. icon: "el-icon-error",
  421. },
  422. {
  423. label: "设置",
  424. path: "set",
  425. icon: "el-icon-s-tools",
  426. },
  427. ],
  428. setData: {
  429. uploadMaxCount: 5, //同时上传的最大任务数
  430. }, //上传设置配置参数
  431. };
  432. },
  433. props: ["data", "value", "resizeable", "position"],
  434. watch: {
  435. setData: {
  436. handler(d) {
  437. this.$store.getters._global.uploadSet = d;
  438. },
  439. deep: true,
  440. immediate: true,
  441. },
  442. value: {
  443. handler(d) {
  444. this.show = d;
  445. },
  446. deep: true,
  447. immediate: true,
  448. },
  449. show: {
  450. handler(d) {
  451. d && (this.defaultMenuActive = `uploading`);
  452. this.$emit(`input`, d);
  453. },
  454. deep: true,
  455. immediate: true,
  456. },
  457. data: {
  458. handler(d) {
  459. this.uploadList = d || [];
  460. },
  461. deep: true,
  462. immediate: true,
  463. },
  464. uploadList: {
  465. handler(newValue, oldValue) {
  466. if (newValue && Object.keys(newValue).length) {
  467. this.interval || this.startUploadCalcLiveSpeed();
  468. this.successFileList = newValue.filter((v) => v.status === `success`); //成功队列
  469. this.errorFileList = newValue.filter((v) => v.status === "error"); //失败队列
  470. this.remainFileList = newValue.filter(
  471. (v) => v.status !== "error" && v.status !== "success"
  472. ); //还需要上传的队列
  473. } else {
  474. this.successFileList = [];
  475. this.errorFileList = [];
  476. this.remainFileList = [];
  477. }
  478. // 计算正上传中的文件对象数组----------------------------------------
  479. this.uploadingFiles = (newValue || []).filter((v) => v.status === "uploading");
  480. this.$emit(`changeUploadingListClose`, {
  481. path: this.position,
  482. close: this.close,
  483. });
  484. // ----------------------------------------
  485. this.initList();
  486. },
  487. deep: true, //深度监听
  488. immediate: true, //立即执行
  489. },
  490. resizeable: {
  491. handler(newValue, oldValue) {
  492. this.resizeable_ = newValue === "" || newValue;
  493. },
  494. deep: true, //深度监听
  495. immediate: true, //立即执行
  496. },
  497. size: {
  498. handler(newValue, oldValue) {
  499. switch (newValue) {
  500. case "lg":
  501. case "mn":
  502. this.style_bk = JSON.parse(JSON.stringify(this.style));
  503. delete this.style.width, delete this.style.height;
  504. break;
  505. case "md":
  506. this.style_bk && (this.style = JSON.parse(JSON.stringify(this.style_bk)));
  507. break;
  508. }
  509. },
  510. deep: true, //深度监听
  511. immediate: true, //立即执行
  512. },
  513. },
  514. computed: {
  515. showDelSuccessIconBtn(d) {
  516. return this.uploadList.some((v) => v.status === `success`);
  517. },
  518. showErrorIconBtn(d) {
  519. return this.uploadList.some((v) => v.status === "error");
  520. },
  521. popoverContent(d) {
  522. let r = [];
  523. this.successFileList.length &&
  524. r.push(
  525. `已上传成功<span style="color: #67C23A;">${this.successFileList.length}</span>个`
  526. );
  527. this.errorFileList.length &&
  528. r.push(`失败<span style="color: #F56C6C;">${this.errorFileList.length}</span>个`);
  529. this.remainFileList.length &&
  530. r.push(
  531. `剩余<span style="color: #409EFF;">${this.remainFileList.length}</span>个`
  532. );
  533. if (this.uploadList.length) {
  534. return `共计${this.uploadList.length}个文件,${
  535. r.length ? `${r.join(",")}文件` : ``
  536. }`;
  537. } else {
  538. return `暂无待上传文件`;
  539. }
  540. },
  541. // 总体进度
  542. totalPercentage() {
  543. if (this.uploadList.length) {
  544. return parseFloat(
  545. ((this.successFileList.length / this.uploadList.length) * 100).toFixed(2)
  546. );
  547. } else {
  548. return 0;
  549. }
  550. },
  551. },
  552. mounted() {
  553. this.$el.style.setProperty("--minWidth", `${this.minWidth}px`); //js往css传递局部参数
  554. this.$el.style.setProperty("--minHeight", `${this.minHeight}px`); //js往css传递局部参数
  555. this.dragMoveDoms = [
  556. {
  557. canDragDom: this.$refs.header, //托盘的头部可以拖拽
  558. moveDom: this.$el, //拖拽的时候,整个上传列表一起跟随移动
  559. },
  560. ];
  561. },
  562. destroyed() {
  563. clearInterval(this.interval);
  564. },
  565. methods: {
  566. saveSet(d) {},
  567. getEmptyText() {
  568. let label = (this.menuList.find((v) => v.path === this.defaultMenuActive) || {})
  569. .label;
  570. return `暂无${label ? `${label}的` : ``}文件`;
  571. },
  572. //菜单激活回调
  573. menuSelect(index, path) {
  574. this.defaultMenuActive = index;
  575. //做其他操作
  576. this.initList();
  577. },
  578. //静态数据翻页(支持筛选搜索)
  579. initList({ keyword = this.keyword } = {}) {
  580. let results = this.uploadList.filter((v) =>
  581. keyword ? v.name.includes(keyword) : true
  582. );
  583. switch (this.defaultMenuActive) {
  584. case "uploading":
  585. results = results.filter(
  586. (v, i, ar) => v.status === `uploading` || v.status === ``
  587. );
  588. break;
  589. case "success":
  590. case "error":
  591. results = results.filter((v, i, ar) => v.status === this.defaultMenuActive);
  592. break;
  593. default:
  594. }
  595. this.total = results.length;
  596. this.tableData = results.slice(
  597. (this.currentPage - 1) * this.pageSize,
  598. this.currentPage * this.pageSize
  599. );
  600. },
  601. // 开始计算瞬时下载速度
  602. startUploadCalcLiveSpeed() {
  603. clearInterval(this.interval);
  604. this.interval = setInterval(() => {
  605. this.calcLiveSpeed();
  606. }, 1000 * this.second);
  607. },
  608. // 结束计算瞬时下载速度
  609. endUploadCalcLiveSpeed(d) {
  610. clearInterval(this.interval);
  611. this.interval = null;
  612. this.liveSpeed = 0;
  613. this.takenTime = 0;
  614. this.remainTime = 0;
  615. },
  616. // 没有上传进程文件才结束计算速度
  617. ifNoUploadingFile_EndCalcLiveSpeed(d) {
  618. this.uploadingFiles.length || this.endUploadCalcLiveSpeed();
  619. },
  620. // 计算瞬时下载速度
  621. calcLiveSpeed(d) {
  622. this.takenTime++;
  623. let uploadList = this.uploadList;
  624. if (uploadList.length) {
  625. let totalSize = uploadList.reduce(
  626. (prevResult, current) => prevResult + current.size,
  627. 0
  628. ); //求和需要上传的文件总大小
  629. let uploadedTotalSize = uploadList.reduce(
  630. (prevResult, current) => prevResult + current.size * (0.01 * current.percent),
  631. 0
  632. ); //求和已经上传的文件总大小
  633. let remainTotalSize = totalSize - uploadedTotalSize; //剩余需要上传的文件
  634. if (this.lastUploadedTotalSize) {
  635. this.liveSpeed = (uploadedTotalSize - this.lastUploadedTotalSize) / this.second; //瞬时速度
  636. this.remainTime = remainTotalSize / this.liveSpeed; //瞬时剩余时长
  637. } else {
  638. this.liveSpeed = 0;
  639. }
  640. this.lastUploadedTotalSize = uploadedTotalSize; //记录本次已经上传的总大小
  641. } else {
  642. this.endUploadCalcLiveSpeed();
  643. }
  644. },
  645. clearAllSuccessFile() {
  646. let successFileList = this.uploadList.filter((v) => v.status === `success`);
  647. if (successFileList.length === 0)
  648. return this.$message(`暂无可以移除的成功记录,请稍后再试!`);
  649. this.$emit(`clearAllSuccessFile`, successFileList);
  650. // this.$nextTick(() => {
  651. successFileList.forEach((file) => file.removeFile()); //移除原始队列&托盘队列
  652. this.uploadList = this.uploadList.filter((v) => v.percent <= 100);
  653. // });
  654. },
  655. clearAllErrorFile() {
  656. let errorFileList = this.uploadList.filter((v) => v.status === "error");
  657. if (errorFileList.length === 0)
  658. return this.$message(`暂无可以移除的失败记录,请稍后再试!`);
  659. this.$emit(`clearAllErrorFile`, errorFileList);
  660. this.$nextTick(() => {
  661. errorFileList.forEach((file) => file.removeFile()); //移除原始队列&托盘队列
  662. this.uploadList = this.uploadList.filter((v) => v.status !== "error");
  663. });
  664. },
  665. uploadAllErrorFile(d) {
  666. let errorFileList = this.uploadList.filter((v) => v.status === "error");
  667. errorFileList.forEach((fileData) => this.startUploadFile(fileData));
  668. if (errorFileList.length === 0) return this.$message(`暂无失败记录,请稍后再试!`);
  669. this.$emit(`uploadAllErrorFile`, errorFileList);
  670. },
  671. draggingSize({ style }) {
  672. this.disabledDragMove = true;
  673. this.style = style;
  674. },
  675. toRightBottomPosition(d) {
  676. this.showRightBottomBtn = false;
  677. let rect = this.$el.getBoundingClientRect();
  678. this.$el.style.left = `${innerWidth - rect.width}px`;
  679. this.$el.style.top = `${innerHeight - rect.height}px`;
  680. // 用下面的写法会清除掉setProperty属性
  681. /* this.$el.style = {
  682. left: innerWidth - rect.width + "px",
  683. top: innerHeight - rect.height + "px",
  684. }; */
  685. },
  686. dblclickHeader(d) {
  687. switch (this.size) {
  688. case "lg":
  689. this.size = "md";
  690. break;
  691. case "md":
  692. this.size = "mn";
  693. break;
  694. case "mn":
  695. this.size = "md";
  696. break;
  697. default:
  698. F;
  699. }
  700. },
  701. removeAllFilesFromList() {
  702. this.uploadList &&
  703. this.uploadList.length &&
  704. this.uploadList.slice(-1)[0].removeAllFile(); //移除原始队列&托盘队列
  705. },
  706. removeFileFromList(d) {
  707. d.removeFile(); //移除原始队列&托盘队列
  708. },
  709. // 重新上传失败的记录
  710. startUploadFile(fileData) {
  711. fileData.startUpload({ handleTrigger: true });
  712. },
  713. // 移出某一个队列文件
  714. removeUploadFile(d) {
  715. if (d.status === "uploading") {
  716. this.$confirm(`${d.name}正在上传中,确定要取消吗?`, `提示`, {
  717. dangerouslyUseHTMLString: true,
  718. confirmButtonText: `确定`,
  719. cancelButtonText: `取消`,
  720. type: "warning",
  721. })
  722. .then(() => {
  723. this.$emit(`stopUpload`, [d]);
  724. this.$nextTick(() => {
  725. this.removeFileFromList(d);
  726. this.ifNoUploadingFile_EndCalcLiveSpeed();
  727. });
  728. })
  729. .catch(() => {});
  730. } else {
  731. this.removeFileFromList(d);
  732. }
  733. },
  734. // 关闭托盘
  735. close({ cb } = {}) {
  736. let stopUploadList = this.uploadingFiles;
  737. if (stopUploadList.length) {
  738. this.$confirm(`您还有正在上传中的文件,确定要取消吗?`, `提示`, {
  739. dangerouslyUseHTMLString: true,
  740. confirmButtonText: `确定`,
  741. cancelButtonText: `取消`,
  742. type: "warning",
  743. })
  744. .then(() => {
  745. this.doRemove({ stopUploadList, cb });
  746. })
  747. .catch(() => {});
  748. } else {
  749. this.doRemove({ stopUploadList, cb });
  750. }
  751. },
  752. doRemove({ stopUploadList, cb } = {}) {
  753. this.show = false;
  754. this.$emit(`stopUpload`, stopUploadList);
  755. this.$nextTick(() => {
  756. this.endUploadCalcLiveSpeed();
  757. this.removeAllFilesFromList(); //清空上传列表
  758. cb && cb(stopUploadList); //完成后回调
  759. });
  760. },
  761. },
  762. };
  763. </script>
  764. <style lang="scss" scoped>
  765. .sgUploadTray_v2 {
  766. $bodyLeftWidth: 150px; //左侧分类菜单宽度
  767. $headerHeight: 40px; //头部高度
  768. $footerHeight: 40px; //底部统计上传进度高度
  769. $minWidth: var(--minWidth); //托盘最小宽度
  770. $minHeight: var(--minHeight); //托盘最小高度
  771. $leftIconBtnWidth: 50px; //左侧删除按钮宽度
  772. $rightIconBtnWidth: 50px; //右侧重新上传按钮宽度
  773. $loadingWidth: 30px; //加载动画宽度(旋转全全)
  774. $rightWidth: 150px; //右侧宽度
  775. $sizeWidth: 200px; //文件大小宽度宽度
  776. $progressWidth: 100px; //进度条宽度
  777. $tipWidth: 100px; //提示文本宽度
  778. // ----------------------------------------
  779. z-index: 2001; //根据情况自己拿捏(太大了会遮住element的其他弹窗组件),v-loading默认是2000的z-index
  780. user-select: none;
  781. position: fixed;
  782. right: 10px;
  783. bottom: 10px;
  784. width: $minWidth;
  785. background-color: white;
  786. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  787. border-radius: 8px;
  788. overflow: hidden;
  789. border: 1px solid #eee;
  790. font-size: 14px;
  791. display: none;
  792. &[show] {
  793. display: block;
  794. }
  795. &[size="lg"] {
  796. left: 0 !important;
  797. top: 0 !important;
  798. width: 100vw;
  799. height: 100vh;
  800. transition: none;
  801. .upload-file-list {
  802. max-height: calc(100vh - 60px) !important;
  803. }
  804. }
  805. &[size="md"] {
  806. width: $minWidth;
  807. height: revert;
  808. }
  809. &[size="mn"] {
  810. width: $minWidth;
  811. height: $minHeight;
  812. }
  813. .upload-list-tray {
  814. display: flex;
  815. flex-direction: column;
  816. box-sizing: border-box;
  817. padding-bottom: 20px;
  818. width: 100%;
  819. height: 100%;
  820. position: relative;
  821. .header {
  822. flex-shrink: 0;
  823. font-size: 16px;
  824. font-weight: bold;
  825. width: 100%;
  826. height: $headerHeight;
  827. box-sizing: border-box;
  828. padding: 10px 20px;
  829. /*从上往下线性渐变背景*/
  830. background: linear-gradient(#409eff11, white);
  831. color: #409eff;
  832. display: flex;
  833. justify-content: space-between;
  834. align-items: center;
  835. .left {
  836. display: flex;
  837. align-items: center;
  838. flex-grow: 1;
  839. .title {
  840. display: flex;
  841. align-items: center;
  842. flex-wrap: nowrap;
  843. .upload-info {
  844. display: flex;
  845. align-items: center;
  846. color: black;
  847. flex-shrink: 0;
  848. align-items: center;
  849. font-weight: normal;
  850. .info-item {
  851. margin-right: 5px;
  852. &:last-of-type {
  853. margin-right: 0;
  854. }
  855. span {
  856. font-family: DIN-Light;
  857. color: #409eff;
  858. }
  859. &.live-speed {
  860. span {
  861. font-family: DIN-Black;
  862. }
  863. }
  864. }
  865. }
  866. }
  867. .icon-btns {
  868. display: flex;
  869. align-items: center;
  870. flex-wrap: nowrap;
  871. .icon-btn {
  872. cursor: pointer;
  873. margin-right: 5px;
  874. &:last-of-type {
  875. margin-right: 0;
  876. }
  877. i {
  878. pointer-events: none;
  879. }
  880. &:hover {
  881. opacity: 0.618;
  882. }
  883. }
  884. }
  885. }
  886. .right {
  887. display: flex;
  888. align-items: center;
  889. justify-content: flex-end;
  890. flex-shrink: 0;
  891. pointer-events: auto;
  892. .icon-btn {
  893. margin-left: 10px;
  894. cursor: pointer;
  895. i {
  896. pointer-events: none;
  897. }
  898. &:hover {
  899. opacity: 0.618;
  900. }
  901. &:first-of-type {
  902. margin-left: 0;
  903. }
  904. }
  905. .file-btns {
  906. margin-left: 10px;
  907. display: flex;
  908. flex-wrap: nowrap;
  909. justify-content: flex-end;
  910. box-sizing: border-box;
  911. padding: 0 10px;
  912. border-right: 1px solid #eee;
  913. }
  914. .tray-btns {
  915. margin-left: 10px;
  916. display: flex;
  917. flex-wrap: nowrap;
  918. justify-content: flex-end;
  919. }
  920. }
  921. }
  922. .body {
  923. display: flex;
  924. flex-wrap: nowrap;
  925. width: 100%;
  926. height: calc(100% - #{$headerHeight} - #{$footerHeight} + 20px);
  927. max-height: calc(100vh - #{$headerHeight} - #{$footerHeight} - 40px);
  928. & > .left {
  929. // transition: 0.382s;
  930. position: relative;
  931. flex-shrink: 0;
  932. width: $bodyLeftWidth;
  933. box-sizing: border-box;
  934. padding: 0 10px 0 20px;
  935. border-right: solid 1px #eff0f1;
  936. >>> .el-menu {
  937. transition: none;
  938. .el-menu-item {
  939. transition: none;
  940. margin-bottom: 5px;
  941. border-radius: 8px;
  942. &:last-of-type {
  943. margin-bottom: 0;
  944. }
  945. &:focus,
  946. &:hover {
  947. background-color: #f5f6f7 !important;
  948. }
  949. &.is-active {
  950. background-color: #e9effb !important;
  951. }
  952. }
  953. }
  954. .collapseBtn {
  955. transform: translateY(50%); //防止托盘最小高度的时候还冒出一小截
  956. width: 10px;
  957. height: 20px;
  958. display: flex;
  959. justify-content: center;
  960. align-items: center;
  961. color: white;
  962. background-color: #409eff;
  963. font-size: 12px;
  964. position: absolute;
  965. margin: auto;
  966. top: 0;
  967. right: -10px;
  968. bottom: 0;
  969. z-index: 1;
  970. border-radius: 0 4px 4px 0;
  971. box-sizing: border-box;
  972. padding: 20px 0;
  973. cursor: pointer;
  974. &:hover {
  975. filter: brightness(1.1);
  976. }
  977. }
  978. &[collapse] {
  979. width: 70px;
  980. >>> .el-menu {
  981. .el-menu-item {
  982. width: 40px;
  983. height: 40px;
  984. display: flex;
  985. justify-content: center;
  986. align-items: center;
  987. span {
  988. display: none;
  989. }
  990. }
  991. }
  992. }
  993. }
  994. & > .right {
  995. width: calc(100% - #{$bodyLeftWidth});
  996. flex-grow: 1;
  997. box-sizing: border-box;
  998. padding: 0 20px 0 10px;
  999. .upload-file-list {
  1000. width: 100%;
  1001. flex-grow: 1;
  1002. overflow-y: auto;
  1003. position: relative;
  1004. max-height: calc(100% - 42px);
  1005. min-height: 200px;
  1006. height: 100%;
  1007. ul {
  1008. width: 100%;
  1009. & > li {
  1010. line-height: 1.6;
  1011. box-sizing: border-box;
  1012. padding: 10px;
  1013. border-radius: 8px;
  1014. display: flex;
  1015. justify-content: space-between;
  1016. align-items: center;
  1017. width: 100%;
  1018. height: 50px;
  1019. & > .left {
  1020. width: calc(100% - #{$rightWidth});
  1021. display: flex;
  1022. align-items: center;
  1023. flex-grow: 1;
  1024. flex-shrink: 0;
  1025. // 上传队列记录左侧侧操作按钮
  1026. .icon-btns {
  1027. display: flex;
  1028. flex-wrap: nowrap;
  1029. .icon-btn {
  1030. display: none;
  1031. &[show] {
  1032. margin-right: 15px;
  1033. display: block;
  1034. }
  1035. }
  1036. }
  1037. .fileLoading {
  1038. flex-shrink: 0;
  1039. width: 30px;
  1040. margin-right: 5px;
  1041. height: 0;
  1042. transform: scale(0.5);
  1043. }
  1044. .loadingSuccessIcon,
  1045. .loadingEorrorIcon {
  1046. margin-right: 5px;
  1047. width: 30px;
  1048. height: 30px;
  1049. display: flex;
  1050. justify-content: center;
  1051. align-items: center;
  1052. flex-shrink: 0;
  1053. }
  1054. .name {
  1055. text-align: left;
  1056. margin-right: 10px;
  1057. width: calc(
  1058. 100% - #{$loadingWidth} - #{$sizeWidth} - #{$progressWidth} - #{$rightWidth} -
  1059. 20px
  1060. );
  1061. overflow: hidden;
  1062. white-space: nowrap;
  1063. text-overflow: ellipsis;
  1064. flex-shrink: 0;
  1065. flex-grow: 1;
  1066. }
  1067. .size {
  1068. margin-right: 10px;
  1069. max-width: $sizeWidth;
  1070. /*单行省略号*/
  1071. overflow: hidden;
  1072. white-space: nowrap;
  1073. text-overflow: ellipsis;
  1074. flex-shrink: 0;
  1075. }
  1076. .progress {
  1077. max-width: $progressWidth;
  1078. display: flex;
  1079. align-items: center;
  1080. flex-wrap: nowrap;
  1081. flex-shrink: 0;
  1082. }
  1083. }
  1084. & > .right {
  1085. display: flex;
  1086. align-items: center;
  1087. justify-content: flex-end;
  1088. width: $rightWidth;
  1089. .tip {
  1090. width: $tipWidth;
  1091. overflow: hidden;
  1092. white-space: nowrap;
  1093. text-overflow: ellipsis;
  1094. flex-shrink: 0;
  1095. text-align: right;
  1096. &[color="red"] {
  1097. color: #f56c6c;
  1098. }
  1099. &[color="green"] {
  1100. color: #67c23a;
  1101. }
  1102. &[color="blue"] {
  1103. color: #409eff;
  1104. }
  1105. }
  1106. // 上传队列记录右侧操作按钮
  1107. .icon-btns {
  1108. display: flex;
  1109. flex-wrap: nowrap;
  1110. .icon-btn {
  1111. display: none;
  1112. &[show] {
  1113. margin-left: 15px;
  1114. display: block;
  1115. }
  1116. }
  1117. }
  1118. }
  1119. &:hover {
  1120. background-color: #409eff11;
  1121. color: #409eff;
  1122. .left {
  1123. // 移入上传队列记录左侧操作按钮
  1124. .icon-btns {
  1125. .icon-btn {
  1126. display: block;
  1127. &:last-of-type {
  1128. margin-right: 10px;
  1129. }
  1130. }
  1131. }
  1132. /* .name {
  1133. width: calc(
  1134. 100% - #{$leftIconBtnWidth} - #{$loadingWidth} - #{$sizeWidth} - #{$progressWidth} -
  1135. #{$rightWidth} - #{$rightIconBtnWidth} - 20px
  1136. );
  1137. } */
  1138. }
  1139. .right {
  1140. .tip {
  1141. margin-right: 15px;
  1142. }
  1143. // 移入上传队列记录右侧操作按钮
  1144. .icon-btns {
  1145. .icon-btn {
  1146. display: block;
  1147. &:first-of-type {
  1148. margin-left: 0;
  1149. }
  1150. }
  1151. }
  1152. }
  1153. }
  1154. }
  1155. }
  1156. .el-empty {
  1157. width: max-content;
  1158. height: max-content;
  1159. position: absolute;
  1160. margin: auto;
  1161. top: 0;
  1162. left: 0;
  1163. right: 0;
  1164. bottom: 0;
  1165. }
  1166. }
  1167. }
  1168. }
  1169. .footer {
  1170. z-index: 1;
  1171. font-weight: normal;
  1172. flex-shrink: 0;
  1173. font-size: 14px;
  1174. font-weight: bold;
  1175. width: 100%;
  1176. height: $footerHeight;
  1177. box-sizing: border-box;
  1178. padding: 10px 20px;
  1179. margin-bottom: -20px;
  1180. background: linear-gradient(white, #eff2f7);
  1181. color: #909399;
  1182. display: flex;
  1183. align-items: center;
  1184. flex-wrap: nowrap;
  1185. white-space: nowrap;
  1186. * {
  1187. font-weight: normal;
  1188. }
  1189. .text {
  1190. white-space: nowrap;
  1191. }
  1192. .progress {
  1193. max-width: 200px;
  1194. flex-grow: 1;
  1195. white-space: nowrap;
  1196. display: flex;
  1197. align-items: center;
  1198. flex-wrap: nowrap;
  1199. label {
  1200. white-space: nowrap;
  1201. flex-shrink: 0;
  1202. margin-right: 5px;
  1203. }
  1204. >>> .el-progress {
  1205. white-space: nowrap;
  1206. .el-progress__text {
  1207. font-weight: normal;
  1208. font-size: 14px !important;
  1209. }
  1210. }
  1211. }
  1212. }
  1213. }
  1214. }
  1215. </style>

应用

  1. <template>
  2. <div :class="$options.name">
  3. <el-button type="primary" @click="$refs.sgUpload_v2.triggerUploadFolder()"
  4. >点击上传文件夹</el-button
  5. >
  6. <!-- 上传组件 -->
  7. <sgUpload_v2
  8. :noCheckFile="true"
  9. ref="sgUpload_v2"
  10. :data="uploadData"
  11. :sgUploadTray="sgUploadTray"
  12. hideMessageSuccessTip
  13. />
  14. <!-- 上传托盘(右下角) -->
  15. <sgUploadTray ref="sgUploadTray" resizeable />
  16. </div>
  17. </template>
  18. <script>
  19. import sgUpload_v2 from "@/vue/components/admin/sgUpload_v2";
  20. import sgUploadTray from "@/vue/components/admin/sgUploadTray_v2";
  21. export default {
  22. name: `demoUploadTray`,
  23. components: {
  24. sgUpload_v2,
  25. sgUploadTray,
  26. },
  27. data() {
  28. return {
  29. //上传相关变量----------------------------------------
  30. sgUploadTray: null,
  31. uploadData: {
  32. limit: 10000, //限制上传文件个数
  33. name: `FILE`,
  34. accept: `*`,
  35. actionURL: `${this.$d.API_ROOT_URL}/xxx/xxx`,
  36. actionData: {
  37. ZYGS: this.$g.getZYGS({
  38. BMID: this.$global.getBMID(),
  39. type: this.type,
  40. }),
  41. sgLog: `前端请求来源:${this.$options.name}资源上传`,
  42. },
  43. },
  44. // ----------------------------------------
  45. };
  46. },
  47. mounted(d) {
  48. this.sgUploadTray = this.$refs.sgUploadTray;
  49. },
  50. };
  51. </script>

基于sgUploadTray 1.0版本迭代【sgUploadTray】自定义组件:上传托盘自定义组件,可实时查看上传列表进度。-CSDN博客文章浏览阅读382次。【sgUploadTray】上传托盘自定义组件,可实时查看上传列表进度:1、可以全屏2、可以还原尺寸3、可以最小化4、可以回到右下角默认位置5、支持删除队列数据。https://blog.csdn.net/qq_37860634/article/details/131721614

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/274381
推荐阅读
相关标签
  

闽ICP备14008679号