vuecli+AXdownload下載組件封裝?+css3下載懸浮球動畫效果
在之前我們寫過一個上傳組件,現(xiàn)在我們在寫一個下載組件,下面可能依賴了一些公用的工具類 有任何的使用疑問可以私信!??!
一、效果展示
1. 下載觸發(fā)效果
當觸發(fā)下載功能的時候,會觸發(fā)一個下載動畫,下載懸浮球會自動彈出,并且閃爍提示有新的下載任務直到下載任務完場提示
在提示有新下載之后,懸浮球會半隱在屏幕右下角,如果所有文件的下載進度均完成,懸浮球會在1分鐘后小消失
2. 下載中的進度,詳情展示
點擊懸浮球可以看到下載的文件列表,并對文件的下載進度,速度等信息進行展示
3.下載失敗
當文件下載過程中,由于各種原因導致文件失敗的時候,懸浮球會發(fā)生閃爍,并提示下載失敗
二、優(yōu)點
1.本組件將使用vuex對下載數(shù)據(jù)文件在session中做持久化,以防止數(shù)據(jù)在刷新頁面后丟失。
2.支持多文件下載展示
3.擁有假進度條和真進度條兩種,可根據(jù)業(yè)務需求,自行選擇
4.對大型文件的下載給予用戶動畫提示,增強用戶體驗,以防服務器響應過長,導致頁面沒有響應,用戶會覺得系統(tǒng)功能沒有被觸發(fā)。
5.支持私有npm包引用
三、代碼展示
因為我這邊已經封裝到了npm包中,所以有些代碼可能需要根據(jù)自己的項目進行自行調整(有任何的使用疑問可以私信?。?!)
代碼將分為兩個部分
1. 工具類
2. ui組件部分
3. 下載方法部分
UI組件部分
工具類 TableUtil.js
export const FileState = { // 等待上傳或者下載 Waiting: 0, // 上傳中或者下載中 uploadDownloadStatus: 1, // 上傳成功 Success: 2, // 上傳失敗 Error: 3, // 等待服務器處理 WaitServer: 4, }; export class TableUtils { static formatFileSize(fileSize) { if (fileSize < 1024) { return `${fileSize.toFixed(2)}B`; } if (fileSize < 1024 * 1024) { let temp = fileSize / 1024; temp = +temp.toFixed(2); return `${temp}KB`; } if (fileSize < 1024 * 1024 * 1024) { let temp = fileSize / (1024 * 1024); temp = +temp.toFixed(2); return `${temp}MB`; } let temp = fileSize / (1024 * 1024 * 1024); temp = +temp.toFixed(2); return `${temp}GB`; } } export function objectToFormData(obj) { const formData = new FormData(); Object.keys(obj).forEach(key => { formData.append(key, obj[key]); }); return formData; } export function getIconByFileName(file) { // 文件擴展名 const parts = file.name.split("."); const ext = parts.length > 1 ? parts[parts.length - 1].toLowerCase() : ""; // 文件擴展名和圖標的映射關系 const mapping = { audio: "mp3,wav,aac,flac,ogg,wma,m4a", doc: "doc,docx", pdf: "pdf", ppt: "ppt,pptx", txt: "txt", video: "mp4,avi,wmv,rmvb,mkv,mov,flv,f4v,m4v,rm,3gp,dat,ts,mts,vob", xls: "xls,xlsx", zip: "zip,rar,7z", pic: "jpg,jpeg,png,gif,bmp,webp", }; // 根據(jù)文件擴展名獲取對應的圖標 let icon = "file"; Object.keys(mapping).forEach(key => { const exts = mapping[key].split(","); if (exts.includes(ext)) { icon = key; } }); return `icon-${icon}-m`; }
ui組件部分 AXDownload.vue
主要是容納懸浮球和文件列表的主容器
<template> <div v-if="showBall"> <!-- 類名不要改,防止沖突 --> <div id="ax-private-download-continer" :class="{ 'ax-private-download-continer-add-newtask': addNewTask, }" @click="showFloatBall()" @mouseleave="hideFloatBall" @mouseenter="enterBall" > <div class="ax-private-download-text-content" :class="{ 'ax-private-circle-add-active': TaskAnminate === '添加', 'ax-private-circle-error-active': TaskAnminate === '失敗', }" > <div v-html="ballText"></div> </div> <DownloadFloatingBall :TaskAnminate="TaskAnminate"></DownloadFloatingBall> </div> <FileDownListDialog ref="fileDownListDialog"></FileDownListDialog> </div> </template> <script> import DownloadFloatingBall from "./components/DownloadFloatingBall.vue"; import FileDownListDialog from "./components/FileDownListDialog.vue"; import { FileState } from "../../../src/utils/TableUtil"; export default { name: "AxDownLoad", components: { DownloadFloatingBall, FileDownListDialog, }, data() { return { //顯示出 懸浮球 showDownloadBall: false, timer: null, //計時自動移入 //延遲移入移出 moveTimer: null, //移出時間器 addNewTask: false, //是否是添加的新任務 newTaskTimer: null, showBall: false, TaskAnminateTimer: null, balloldText: "我的下載", ballText: "", TaskAnminate: "", hideDownloadBallTimer: null, }; }, mounted() { const downloadList = this.$store.state.file.downloadList; this.showBall = downloadList.length > 0; this.ballText = downloadList.length > 0 ? `下載任務${"<br />"}${downloadList.length}個` : this.balloldText; }, methods: { hideFloatBall(event) { this.moveTimer = setTimeout(() => { if (this.timer) { clearInterval(this.timer); } document.getElementById("ax-private-download-continer").style.transform = "translateX(0px)"; this.showDownloadBall = false; }, 500); }, enterBall() { if (this.moveTimer) { clearTimeout(this.moveTimer); } }, showFloatBall() { if (!this.showDownloadBall) { //顯示出 懸浮球 this.showDownloadBall = true; document.getElementById("ax-private-download-continer").style.transform = "translateX(-100px)"; } else { //點擊懸浮球,展示下載的附件列表 this.$refs.fileDownListDialog.showDialog({}, 0); } }, //添加新的下載任務 動畫 addDownloadTask(text) { this.showDownloadBall = true; this.addNewTask = true; this.TaskAnminate = text; if (this.newTaskTimer) { clearInterval(this.newTaskTimer); } this.newTaskTimer = setTimeout(() => { this.addNewTask = false; this.TaskAnminate = ""; }, 3000); }, clearAnimateTask() { this.TaskAnminate = ""; this.ballText = this.balloldText; }, //延時動畫 delayAnimate(func) { if (this.TaskAnminateTimer) { clearInterval(this.TaskAnminateTimer); } this.TaskAnminateTimer = setTimeout(() => { func(); }, 500); }, isAllEnd(downloadList) { // 判斷下載列表中每一個文件的狀態(tài)是否為:等待、上傳下載狀態(tài)、等待服務器 const flag = downloadList.every( item => item.state !== FileState.Waiting && item.state !== FileState.uploadDownloadStatus && item.state !== FileState.WaitServer ); if (flag) { if (this.hideDownloadBallTimer) { clearInterval(this.hideDownloadBallTimer); } //下載全部完成,隱藏懸浮球 this.ballText = `下載任務完成`; this.hideDownloadBallTimer = setTimeout(() => { this.showBall = false; this.$store.commit("CLEAR_DOWNLOAD_LIST"); }, 60000); } else { if (this.hideDownloadBallTimer) { clearInterval(this.hideDownloadBallTimer); } } }, }, watch: { showDownloadBall(newVal, oldVal) { if (newVal) { this.timer = setTimeout(() => { this.hideFloatBall(); }, 5000); } }, "$store.state.file.downloadList": { handler(newVal, oldVal) { // 在這里處理變化 this.showBall = newVal.length > 0; this.balloldText = `下載任務${"<br />"}${newVal.length}個`; this.ballText = this.balloldText; this.isAllEnd(newVal); }, deep: true, }, "$store.state.file.errorEvent": { handler(newVal, oldVal) { this.addDownloadTask("失敗"); this.$message({ type: "warning", message: `${newVal.name}下載失敗了!`, }); this.ballText = "下載失敗!"; this.delayAnimate(this.clearAnimateTask); }, deep: true, }, "$store.state.file.downloadEventCount": { handler(newVal, oldVal) { this.addDownloadTask("添加"); this.$message({ type: "success", message: "您添加了新的下載任務!", }); this.ballText = "新下載!"; this.delayAnimate(this.clearAnimateTask); }, deep: true, }, }, }; </script> <style lang="scss" scoped> #ax-private-download-continer { position: fixed; transition: transform 0.3s ease; /* 持續(xù)時間和緩動函數(shù)可以調整 */ transform: translateX(0px); /* 初始轉換狀態(tài) */ right: -50px; bottom: 100px; width: 100px; height: 100px; z-index: 99999; border-radius: 100%; text-align: center; line-height: 100px; -webkit-user-select: none; /* Safari */ -moz-user-select: none; /* Firefox */ -ms-user-select: none; /* Internet Explorer/Edge */ user-select: none; /* 非前綴版本,適用于Chrome和Opera */ cursor: pointer; .ax-private-download-text-content { position: relative; color: #409eff; width: 90px; z-index: 2; /* 高于背景層 */ line-height: 21px; font-weight: 600; top: 50%; right: 50%; transform: translate(50px, -44%); } } .ax-private-download-continer-add-newtask { transform: translateX(-100px) !important; /* 初始轉換狀態(tài) */ } .ax-private-circle-add-active { animation: addTask 1s !important; } .ax-private-circle-error-active { animation: errorTask 1s !important; } @keyframes addTask { 10% { color: #67c23a; } 80% { color: #c9f6b2; } } @keyframes errorTask { 10% { color: white; } 80% { color: white; } } </style>
ui組件下載懸浮球 DownloadFloatingBall.vue
下載懸浮球的主體,以及懸浮球的動畫
<template> <!-- 類名不要改,防止沖突 --> <div class="ax-private-download-circle-container" :class="{ 'ax-private-download-circle-container-add-active': TaskAnminate == '添加', 'ax-private-download-circle-container-error-active': TaskAnminate == '失敗', }" > <div v-for="(item, index) in 4" :key="index" class="ax-private-circle" :class="{ 'ax-private-circle-active': TaskAnminate !== '', }" ></div> </div> </template> <script> export default { name: "DownloadFloatingBall", props: { TaskAnminate: { type: String, default: "", }, }, data() { return {}; }, }; </script> <style scoped> .ax-private-download-circle-container { position: absolute; top: 0; left: 0; bottom: 0; right: 0; width: 100px; height: 100px; border-radius: 50%; } .ax-private-download-circle-container-add-active { animation: addTaskcontainer 1s !important; } .ax-private-download-circle-container-error-active { animation: errorTaskcontainer 1s !important; } @keyframes addTaskcontainer { 10% { background-color: #2887e6; } 100% { background-color: transparent; } } @keyframes errorTaskcontainer { 10% { background-color: #f56c6c; } 100% { background-color: transparent; } } .ax-private-download-circle-container .ax-private-circle { position: absolute; margin: auto; top: 0; right: 0; bottom: 0; left: 0; border-radius: 50%; background: rgba(204, 180, 225, 0.02); backdrop-filter: blur(5px); /* 應用模糊效果 */ } .ax-private-circle-active { animation: addTask 1.5s !important; } .ax-private-circle-error-active { animation: errorTask 1.5s !important; } .ax-private-download-circle-container .ax-private-circle:nth-of-type(1) { width: 100px; height: 90px; animation: rt 6s infinite linear; box-shadow: 0 0 1px 0 #2887e6, inset 0 0 10px 0 #2887e6; } .ax-private-download-circle-container .ax-private-circle:nth-of-type(2) { width: 90px; height: 100px; animation: rt 10s infinite linear; box-shadow: 0 0 1px 0 #006edb, inset 0 0 10px 0 #006edb; } .ax-private-download-circle-container .ax-private-circle:nth-of-type(3) { width: 105px; height: 95px; animation: rt 5s infinite linear; /* box-shadow: 0 0 1px 0 #003c9b, inset 0 0 10px 0 #003c9b; */ box-shadow: 0 0 1px 0 #0148ba, inset 0 0 10px 0 #0148ba; } .ax-private-download-circle-container .ax-private-circle:nth-of-type(4) { width: 95px; height: 105px; animation: rt 15s infinite linear; box-shadow: 0 0 1px 0 #01acfc, inset 0 0 10px 0 #01acfc; } @keyframes rt { 100% { transform: rotate(360deg); } } @keyframes addTask { 10% { transform: scale(1.5); } 30% { transform: scale(0.6); } 60% { transform: scale(1); } } </style>
ui組件下載文件列表彈窗 FileDownListDialog
主要是點擊懸浮球之后的彈窗,用于展示文件的列表
<template> <!-- 對話框 --> <el-dialog v-if="dialog.visible" ref="dialog" :title="getHeaderText" :visible.sync="dialog.visible" width="70%" :close-on-click-modal="false" > <div class="ax-private-file-container"> <template v-if="fileTaskList.length > 0"> <div class="ax-private-file-item" v-for="(item, index) in fileTaskList" :key="index"> <div class="ax-file-progress" :style="{ width: `${item.process}%` }"></div> <div class="ax-file-content"> <div class="ax-file-type-icon"> <SvgIcon :icon-class="getIconByFileName({ name: item.name })"></SvgIcon> </div> <div class="ax-file-info"> <div class="ax-file-filename">{{ item.name }}</div> <div class="ax-file-loadinfo"> <span class="info-span">已下載:{{ item.loaded }}</span> <span class="info-span" v-if="item.size !== 'NaNGB'">文件大?。簕{ item.size }}</span> {{ getuploadStatus(item.state, item.message) }} <span style="color: #409eff; cursor: pointer" v-if="item.message && item.state == 3" @click="showError(item.message)" > 查看詳情</span > {{ getSpeed(item) }} </div> </div> <div class="ax-file-operate"> <i v-if="item.state == 0" class="el-icon-download" style="color: #909399"></i> <!-- 上傳中 --> <span v-else-if="item.state == 1 || item.state == 4"> {{ item.process }}%</span> <!-- 已完成 --> <i v-else-if="item.state == 2" class="el-icon-circle-check" style="color: #67c23a"></i> <i v-else-if="item.state == 3" class="el-icon-warning" style="color: #f56c6c"></i> </div> </div> </div> </template> <template v-else> <div class="ax-top-label">暫無下載文件記錄</div> </template> </div> <el-row type="flex" justify="end"> </el-row> </el-dialog> </template> <script> import { getIconByFileName, FileState } from "../../../../src/utils/TableUtil.js"; const STATUS = { CREATE: 0, UPDATE: 1, }; export default { name: "FileDownListDialog", props: { // 對話框標題 textMap: { type: Object, default: () => ({ add: "文件下載列表", edit: "編輯", }), }, }, data() { return { fileTaskList: [], // 對話框 dialog: { // 對話框狀態(tài) status: null, // 對話框參數(shù),用于編輯時暫存id params: {}, // 對話框是否顯示 visible: false, }, errorCount: 0, waitingOrUploadingCount: 0, }; }, computed: { // 對話框標題 dialogTitle() { return this.dialog.status === STATUS.CREATE ? this.textMap.add : this.textMap.edit; }, getHeaderText() { if (this.waitingOrUploadingCount > 0 || this.errorCount > 0) { if (this.waitingOrUploadingCount > 0) { return `正在下載,剩余 ${this.waitingOrUploadingCount} 個文件,其中(有${this.errorCount}個失敗)`; } return `下載任務完成,有 ${this.errorCount}個失敗`; } return "所有下載任務完成"; }, }, methods: { /** * 顯示對話框,父元素調用 * * @param {Object} param 對話框保存時的參數(shù) * @param {Number} status 對話框狀態(tài)[添加:0,編輯:1],必須是STATUS枚舉 * @param {Object} formValues 編輯時傳入所有字段的默認值 */ async showDialog(param = {}, status = STATUS.CREATE) { // 保存參數(shù)用于save方法 this.dialog.params = param; this.dialog.status = status; this.fileTaskList = this.$store.state.file.downloadList; this.getFileStatus(); this.dialog.visible = true; }, getIconByFileName(item) { const file = { name: item.name, }; return getIconByFileName(file); }, // 取消按鈕點擊 btnCancelOnClick() { this.dialog.visible = false; this.$emit("cancel"); }, showError(message) { this.$message.error(message); }, getuploadStatus(state, message) { const mapping = ["等待下載,請稍后...", "下載中", "下載成功", "下載失敗", "等待服務器處理"]; if (message) { return message.slice(0, 15); } return mapping[state]; }, getSpeed(item) { if (item.state === 2 || item.state === 3 || item.state === 4) { return ""; } return item.state === 1 && item.speed === "速度計算中..." ? "" : item.speed; }, getFileStatus() { // 計算state等于FileState.Waiting或FileState.Uploading的元素數(shù)量 this.waitingOrUploadingCount = this.fileTaskList.filter( item => item.state === FileState.WaitServer || item.state === FileState.Waiting || item.state === FileState.uploadDownloadStatus ).length; // 計算state等于FileState.Error的元素數(shù)量 this.errorCount = this.fileTaskList.filter(item => item.state === FileState.Error).length; }, }, watch: { "$store.state.file.downloadList": { handler(newVal, oldVal) { // 在這里處理變化 this.fileTaskList = newVal; this.getFileStatus(); }, deep: true, }, }, }; </script> <style lang="scss" scoped> ::v-deep .el-dialog__body { height: 680px; } .ax-private-file-container { width: 100%; height: 600px; overflow: auto; .ax-private-file-item { float: left; width: 100%; height: 100px; position: relative; .ax-file-progress { height: 100px; background-color: #f5f9ff; position: absolute; z-index: 0; left: 0px; } .ax-file-content { z-index: 9999; width: 100%; position: absolute; height: 100px; display: flex; align-items: center; border-bottom: 1px solid #e0e2e6; } .ax-file-type-icon { width: 70px; height: 70px; float: left; .SvgIcon { width: 100%; height: 100%; } } .ax-file-info { width: calc(100% - 170px); float: left; // background-color: red; .ax-file-filename { width: 100%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: 16px; font-weight: 600; color: black; margin-bottom: 5px; } .ax-file-loadinfo { width: 100%; font-weight: 400; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: #8e8e8e; .info-span { margin-right: 10px; } } } .ax-file-operate { width: 100px; height: 100px; display: flex; align-items: center; justify-content: center; font-size: 20px; float: right; } } } </style>
下載工具方法 download.ts
主要觸發(fā)ui動畫,觸發(fā)下載的方法
import Vue from "vue"; import { MessageBox } from "element-ui"; // eslint-disable-line import guid from "./generator"; import { FileState, TableUtils } from "./TableUtil.js"; // import store from "../store/index"; interface FileItem { name: string; state?: number; size: number | string; //文件大小轉義 類似10mb total?: number | string; //文件字節(jié)大小 114882037 loaded?: number | string; //已下載大小 process?: number; speed?: string; id: string; //唯一鍵 realId?: string; //真實文件id startTime?: number; message?: string; //文件下載提示一些文字或者錯誤 } interface FilePojo { name: string; //文件名稱 id?: string; //文件id size?: string | number; //文件大小 total?: string | number; //文件總大小 } function getRandomInt(min, max) { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min; } //模擬隨機進度 function getRandomProcess(fileItem) { let percentCompleted = 0; const randomInt = getRandomInt(1, 2); const randomMaxPro = getRandomInt(94, 97); if (fileItem.process < randomMaxPro) { fileItem.process += randomInt; percentCompleted = fileItem.process; } else { //無操作 percentCompleted = fileItem.process; } return percentCompleted; } //判斷total是否為未知 function isHasTotal(fileItem, loaded, total) { let percentCompleted = 0; //如果total為0 if (total === 0) { //如果文件大小為0,就說明文件的大小屬于未知狀態(tài),需要模擬進度條 percentCompleted = getRandomProcess(fileItem); } else { //如果文件大小不為0,就可以計算真實的下載進度 const realProcess = Math.round((loaded * 100) / total); if (realProcess > 80) { percentCompleted = getRandomProcess(fileItem); } else { percentCompleted = realProcess; } } return percentCompleted; } //監(jiān)聽下載進度 function onDownloadProgress(progressEvent, file) { //獲取下載列表 const downloadList = Vue.prototype.$store.getters.downloadList; //如果下載列表不為空,且下載列表長度大于0 if (downloadList && downloadList.length > 0) { //在下載列表中查找id與文件id相同的文件 const index = downloadList.findIndex(i => i.id === file.id); let percentCompleted = 0; percentCompleted = isHasTotal( downloadList[index], progressEvent.loaded, file.total === 0 ? progressEvent.total : file.total ); //如果索引大于-1,說明文件在下載列表中 if (index > -1) { const currentTime = new Date().getTime(); const timeInterval = (currentTime - downloadList[index].startTime) / 1000; const speed = progressEvent.loaded / timeInterval; downloadList[index].speed = `${TableUtils.formatFileSize(speed)}/秒`; const randomMaxPro = getRandomInt(94, 97); //更新進度條 downloadList[index].process = percentCompleted; downloadList[index].loaded = TableUtils.formatFileSize(progressEvent.loaded); //更新文件狀態(tài) downloadList[index].state = FileState.uploadDownloadStatus; if (percentCompleted >= randomMaxPro) { //說明已經進入了模擬進度 downloadList[index].state = FileState.WaitServer; } const fileItem = downloadList[index]; Vue.prototype.$store.commit("UPDATE_DOWNLOAD_ITEM", { item: fileItem, index: index }); } } } //獲取下載文件存進session function setFileSessionStorage(file) { const newFile: FileItem = { name: file.name, state: FileState.Waiting, size: file.size || "未知", total: file.total || "未知", loaded: 0 || "未知", //已下載大小 process: 0, speed: "速度計算中...", id: file.id, realId: file.realId, message: file.message || "", startTime: new Date().getTime(), }; //判斷是否已經存在 const downloadList = Vue.prototype.$store.getters.downloadList; // 如果下載列表存在且長度大于0 if (downloadList && downloadList.length > 0) { // 查找下載列表中是否有與文件id相同的文件 const index = downloadList.findIndex(i => i.id === file.id); // 如果沒有找到 if (index === -1) { // 將文件添加到下載列表中 Vue.prototype.$store.commit("ADD_DOWNLOAD_ITEM", newFile); } else { // 如果找到,更新下載列表中的文件 Vue.prototype.$store.commit("UPDATE_DOWNLOAD_ITEM", { item: newFile, index: index }); } } else { // 如果下載列表不存在或長度等于0,將文件添加到下載列表中 Vue.prototype.$store.commit("SET_DOWNLOAD_LIST", [newFile]); } Vue.prototype.$store.commit("ADD_DOWNLOAD_EVENT_COUNT"); } //判斷是get還是post function isMethod(file, url, method, data, params) { return Vue.prototype.axios({ url: url, method: method, responseType: "blob", // 確保以blob形式接收文件數(shù)據(jù) data: data, params: params, // 將查詢參數(shù)添加到請求中 onDownloadProgress: progressEvent => { onDownloadProgress(progressEvent, file); }, }); } function setFileName(name) { const date = new Date(); let fileName; if (/^.*\..{1,4}$/.test(name)) { fileName = name; } else { fileName = `${name} ${date.getFullYear()}年${date.getMonth() + 1}月 ${date.getDate()}日${date.getHours()}時${date.getMinutes()}分${date.getSeconds()}秒.xls`; } return fileName; } /** * 通用下載 老版本 * * @export * @param {String} url 請求地址 * @param {String} name 文件名 * @param {Object} params 請求參數(shù) * @param {String} requestType 請求方式(get,post) * @param {function} callBackFun 回調函數(shù) */ // eslint-disable-next-line export function download(url, name, data, requestType = 'get', params, callBackFun: Function = () => { },file?:FilePojo) { let axiosObj; const fileName = setFileName(name); let fileObj: FileItem = { name: fileName, id: guid(), size: "未知", realId: "", total: 0, }; if (file) { fileObj = { name: file.name || fileName, id: guid(), realId: file.id || "", size: TableUtils.formatFileSize(Number(file.size)) || "未知", total: Number(file.size) || 0, }; } //將即將要下載的文件存進session中 setFileSessionStorage(fileObj); if (requestType === "get") { axiosObj = isMethod(fileObj, url, "get", {}, params); } else { // axios.post(url, data, { responseType: "blob", params }); axiosObj = isMethod(fileObj, url, "post", data, params); } axiosObj .then(res => { //獲取下載列表 const downloadList = Vue.prototype.$store.getters.downloadList; const index = downloadList.findIndex(i => i.id === fileObj.id); if (!res) { //返回數(shù)據(jù)異常,附件要求失敗 if (index !== -1) { //更新文件狀態(tài) downloadList[index].state = FileState.Error; downloadList[index].message = res.message || res.data.message || "文件下載失敗"; const fileItem = downloadList[index]; Vue.prototype.$store.commit("UPDATE_DOWNLOAD_ITEM", { item: fileItem, index: index }); Vue.prototype.$store.commit("ERROR_EVENT", fileItem.name); } return; } // 如果返回類型為json 代表導出失敗 此時讀取后端返回報錯信息 if (res.type === "application/json") { const reader: any = new FileReader(); // 創(chuàng)建一個FileReader實例 reader.readAsText(res, "utf-8"); // 讀取文件,結果用字符串形式表示 reader.onload = () => { // 讀取完成后,**獲取reader.result** const { message } = JSON.parse(reader.result); downloadList[index].state = FileState.Error; downloadList[index].message = message || "文件下載失敗"; const fileItem = downloadList[index]; Vue.prototype.$store.commit("UPDATE_DOWNLOAD_ITEM", { item: fileItem, index: index }); Vue.prototype.$store.commit("ERROR_EVENT", fileItem.name); // 請求出錯 MessageBox.alert(`${message}`, "操作失敗", { confirmButtonText: "我知道了", type: "warning", showClose: true, }); }; if (callBackFun) callBackFun("error"); return; } const blob = new Blob([res]); let fileName; const date = new Date(); if (/^.*\..{1,4}$/.test(name)) { fileName = name; } else if (res.headers && res.headers.includes("fileName=")) { fileName = decodeURIComponent(res.headers.split("fileName=")[1]); } else if (res.headers && res.headers.includes(`fileName*=utf-8''`)) { fileName = decodeURIComponent(res.headers.split(`fileName*=utf-8''`)[1]); } else { fileName = `${name} ${date.getFullYear()}年${ date.getMonth() + 1 }月${date.getDate()}日${date.getHours()}時${date.getMinutes()}分${date.getSeconds()}秒.xls`; } downloadList[index].name = fileName; downloadList[index].state = FileState.Success; downloadList[index].process = 100; const fileItem = downloadList[index]; Vue.prototype.$store.commit("UPDATE_DOWNLOAD_ITEM", { item: fileItem, index: index }); const aTag = document.createElement("a"); aTag.style.display = "none"; aTag.download = fileName; aTag.href = URL.createObjectURL(blob); document.body.appendChild(aTag); aTag.click(); URL.revokeObjectURL(aTag.href); document.body.removeChild(aTag); if (callBackFun) callBackFun(); }) .catch(error => { // 處理錯誤 const downloadList = Vue.prototype.$store.getters.downloadList; const index = downloadList.findIndex(i => i.id === fileObj.id); if (index !== -1) { //更新文件狀態(tài) downloadList[index].state = FileState.Error; const msg = JSON.stringify(error); downloadList[index].message = error.message || `文件下載失敗!${msg}`; const fileItem = downloadList[index]; Vue.prototype.$store.commit("UPDATE_DOWNLOAD_ITEM", { item: fileItem, index: index }); Vue.prototype.$store.commit("ERROR_EVENT", fileItem.name); } }); } //新版本 推薦 export function downloadFile({ url, name, data, method, params, callBackFun, file }) { download(url, name, data, method, params, callBackFun, file); } //不走接口,虛假進度條 export function fakeDownProgress(file: FilePojo, func, funcArgs, message) { if (!file) { console.error("文件類型異常,file不能為null"); return; } const fileObj = { name: file.name, id: guid(), realId: file.id || "", size: TableUtils.formatFileSize(Number(file.size)) || "未知", total: Number(file.size) || 0, message: message || "任務進行中", }; setFileSessionStorage(fileObj); let timer; const downloadList = Vue.prototype.$store.getters.downloadList; const index = downloadList.findIndex(i => i.id === fileObj.id); if (index !== -1) { if (timer) { clearInterval(timer); } timer = setInterval(() => { downloadList[index].state = FileState.uploadDownloadStatus; const percentCompleted = isHasTotal(downloadList[index], 0, 0); downloadList[index].process = percentCompleted; const fileItem = downloadList[index]; Vue.prototype.$store.commit("UPDATE_DOWNLOAD_ITEM", { item: fileItem, index: index }); }, getRandomInt(800, 2000)); } // eslint-disable-next-line no-async-promise-executor new Promise(async (resolve, reject) => { const res = await func(funcArgs); console.log(res); resolve(res); }).then(state => { console.log("state", state); if (timer) { clearInterval(timer); } console.log(index); if (index !== -1) { downloadList[index].state = state; if (downloadList[index].state === FileState.Success) { downloadList[index].process = 100; downloadList[index].message = ""; const fileItem = downloadList[index]; Vue.prototype.$store.commit("UPDATE_DOWNLOAD_ITEM", { item: fileItem, index: index }); } if (downloadList[index].state === FileState.Error) { const fileItem = downloadList[index]; Vue.prototype.$store.commit("UPDATE_DOWNLOAD_ITEM", { item: fileItem, index: index }); Vue.prototype.$store.commit("ERROR_EVENT", fileItem.name); } } }); }
當我們注意到再download的方法中多次使用了store,所以我們要使用到vuex來做持久化
對應的store對象
const file = { state: { downloadList: [], //文件下載列表 downloadEventCount: 0, //文件下載觸發(fā)次數(shù) errorEvent: { count: 0, name: "", }, //錯誤事件觸發(fā) successEvent: 0, //成功事件觸發(fā) }, mutations: { SET_DOWNLOAD_LIST: (state, list) => { state.downloadList = list; }, ADD_DOWNLOAD_EVENT_COUNT: state => { state.downloadEventCount += 1; }, ADD_DOWNLOAD_ITEM: (state, item) => { state.downloadList = [...state.downloadList, item]; }, //修改downloadList其中的某個元素 UPDATE_DOWNLOAD_ITEM: (state, { item, index }) => { state.downloadList.splice(index, 1, item); }, //刪除downloadList所有元素 CLEAR_DOWNLOAD_LIST: state => { state.downloadList = []; }, CLEAR_ERROR_EVENT: state => { state.errorEvent.count = 0; state.errorEvent.name = ""; }, ERROR_EVENT: (state, name) => { state.errorEvent.count += 1; state.errorEvent.name = name; }, SUCCESS_EVENT: state => { state.successEvent += 1; }, }, actions: {}, }; export default file;
持久化vuex store對象的入口處
import Vue from "vue"; import Vuex from "vuex"; import createPersistedState from "vuex-persistedstate"; import app from "./modules/app"; import user from "./modules/user"; import file from "./modules/file"; import getters from "./getters"; Vue.use(Vuex); const store = new Vuex.Store({ // 注意:新增的modules如果需要持久化還需要在plugins配置一下 modules: { app, user, file, }, getters, // 局部持久化,之所以不能全部持久化,詳見src/permission.js plugins: [ createPersistedState({ paths: ["app", "file"], storage: window.sessionStorage, }), ], }); export default store;
getters中配置對應的屬性,用于獲取
const getters = { //文件管理 downloadList: state => state.file.downloadList, }; export default getters;
下載組件的使用
在使用download.ts中的方法觸發(fā)下載之前,需要引入ui組件,在App.vue中,引用
<template> <div id="app" v-loading.fullscreen.lock="$store.state.app.isLoading" element-loading-text="請稍候"> <router-view /> <AxDownLoad></AxDownLoad> </div> </template>
在使用下載組件的時候會用到的一些內部方法
import {download,downloadFile, fakeDownProgress, FileState } from 'download.ts';
使用例子 采用下列方法,可以明確傳遞的參數(shù)是什么,便于后續(xù)維護更新,復用
btnDownloadOnClick(row) { const { fileName, fileExtension, pictureBase64Code, affixId } = row; const url = `${this.API_URL}iqpQuery/file/flowAffixDownload`; const params = { affixInfoId: affixId }; //采用下列方法,可以明確傳遞的參數(shù)是什么,便于后續(xù)維護更新,復用 downloadFile({ url, name: `${fileName}.${fileExtension}`, params, }); },
如果希望下載進度為真實進度,那么可以考慮上傳file這個對象,里面的size,把真實的文件大小傳入,或者由服務端在header加上contentLength
fakeDownProgress方法
此方法為虛假的進度展示,以便于一些沒有進度功能的長期方法的進度展示,
//使用fakeDownProgress方法進行進度展示,
//依次參數(shù)說明
//file:為FilePojo類型,可以傳遞文件id,也可以不傳遞,name必須傳遞
//func:需要等待的方法
//funcArgs:方法需要傳遞的對象,
//message:進度展示的文字信息
使用例子
//這是一個文件轉碼的方法,消耗時間的大小,不可計算,需要使用Promise方法進行包裹,除此以外,可以再執(zhí)行完成后的使用 resolve(FileState.Success);,失敗同理!
// A code block var foo = 'bar';
Base64FileEvent({ id, base64String, fileName, fileExtension }) { return new Promise((resolve, reject) => { const byteCharacters = atob(base64String); const byteNumbers = new Array(byteCharacters.length); // eslint-disable-next-line no-plusplus for (let i = 0; i < byteCharacters.length; i++) { byteNumbers[i] = byteCharacters.charCodeAt(i); } const byteArray = new Uint8Array(byteNumbers); const blob = new Blob([byteArray], { type: 'application/octet-stream' }); const downloadLink = document.createElement('a'); const url = window.URL.createObjectURL(blob); console.log(url); downloadLink.href = url; downloadLink.download = `${fileName}.${fileExtension}`; downloadLink.click(); EventListener('click', () => { document.body.removeChild(downloadLink); window.URL.revokeObjectURL(url); resolve(FileState.Success); }); // setTimeout(() => { // resolve(2); // }, 2000); }); }, downloadBase64AsFile(id, base64String, fileName, fileExtension) { const data = { id, base64String, fileName, fileExtension, }; const file = { id, name: `${fileName}.${fileExtension}`, }; //使用fakeDownProgress方法進行進度展示, //依次參數(shù)說明 //file:為FilePojo類型,可以傳遞文件id,也可以不傳遞,name必須傳遞 //func:需要等待的方法 //funcArgs:方法需要傳遞的對象, //message:進度展示的文字信息 fakeDownProgress(file, this.Base64FileEvent, data, '文件轉碼中...'); },
到此這篇關于vuecli+AXdownload下載組件封裝 +css3下載懸浮球動畫的文章就介紹到這了,更多相關vuecli+AXdownload下載組件內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
vue3 el-pagination 將組件中英文‘goto’ 修改 為&nbs
這篇文章主要介紹了vue3 el-pagination 將組件中英文‘goto’ 修改 為 中文到‘第幾’,通過實例代碼介紹了vue3項目之Pagination 組件,感興趣的朋友跟隨小編一起看看吧2024-02-02通過vue提供的keep-alive減少對服務器的請求次數(shù)
這篇文章主要介紹了通過vue提供的keep-alive減少對服務器的請求次數(shù),文中給大家補充介紹了vue路由開啟keep-alive時的注意點,需要的朋友可以參考下2018-04-04