vuecli+AXdownload下載組件封裝?+css3下載懸浮球動畫效果
在之前我們寫過一個上傳組件,現(xiàn)在我們在寫一個下載組件,下面可能依賴了一些公用的工具類 有任何的使用疑問可以私信!?。?/p>
一、效果展示
1. 下載觸發(fā)效果
當(dāng)觸發(fā)下載功能的時候,會觸發(fā)一個下載動畫,下載懸浮球會自動彈出,并且閃爍提示有新的下載任務(wù)直到下載任務(wù)完場提示

在提示有新下載之后,懸浮球會半隱在屏幕右下角,如果所有文件的下載進度均完成,懸浮球會在1分鐘后小消失

2. 下載中的進度,詳情展示
點擊懸浮球可以看到下載的文件列表,并對文件的下載進度,速度等信息進行展示

3.下載失敗
當(dāng)文件下載過程中,由于各種原因?qū)е挛募〉臅r候,懸浮球會發(fā)生閃爍,并提示下載失敗

二、優(yōu)點
1.本組件將使用vuex對下載數(shù)據(jù)文件在session中做持久化,以防止數(shù)據(jù)在刷新頁面后丟失。
2.支持多文件下載展示
3.擁有假進度條和真進度條兩種,可根據(jù)業(yè)務(wù)需求,自行選擇
4.對大型文件的下載給予用戶動畫提示,增強用戶體驗,以防服務(wù)器響應(yīng)過長,導(dǎo)致頁面沒有響應(yīng),用戶會覺得系統(tǒng)功能沒有被觸發(fā)。
5.支持私有npm包引用
三、代碼展示
因為我這邊已經(jīng)封裝到了npm包中,所以有些代碼可能需要根據(jù)自己的項目進行自行調(diào)整(有任何的使用疑問可以私信?。?!)
代碼將分為兩個部分
1. 工具類
2. ui組件部分
3. 下載方法部分
UI組件部分
工具類 TableUtil.js
export const FileState = {
// 等待上傳或者下載
Waiting: 0,
// 上傳中或者下載中
uploadDownloadStatus: 1,
// 上傳成功
Success: 2,
// 上傳失敗
Error: 3,
// 等待服務(wù)器處理
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() : "";
// 文件擴展名和圖標(biāo)的映射關(guān)系
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ù)文件擴展名獲取對應(yīng)的圖標(biāo)
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, //是否是添加的新任務(wù)
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 ? `下載任務(wù)${"<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);
}
},
//添加新的下載任務(wù) 動畫
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)、等待服務(wù)器
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 = `下載任務(wù)完成`;
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 = `下載任務(wù)${"<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: "您添加了新的下載任務(wù)!",
});
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ù)可以調(diào)整 */
transform: translateX(0px); /* 初始轉(zhuǎn)換狀態(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; /* 初始轉(zhuǎn)換狀態(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); /* 應(yīng)用模糊效果 */
}
.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: {
// 對話框標(biāo)題
textMap: {
type: Object,
default: () => ({
add: "文件下載列表",
edit: "編輯",
}),
},
},
data() {
return {
fileTaskList: [],
// 對話框
dialog: {
// 對話框狀態(tài)
status: null,
// 對話框參數(shù),用于編輯時暫存id
params: {},
// 對話框是否顯示
visible: false,
},
errorCount: 0,
waitingOrUploadingCount: 0,
};
},
computed: {
// 對話框標(biāo)題
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 `下載任務(wù)完成,有
${this.errorCount}個失敗`;
}
return "所有下載任務(wù)完成";
},
},
methods: {
/**
* 顯示對話框,父元素調(diào)用
*
* @param {Object} param 對話框保存時的參數(shù)
* @param {Number} status 對話框狀態(tài)[添加:0,編輯:1],必須是STATUS枚舉
* @param {Object} formValues 編輯時傳入所有字段的默認(rèn)值
*/
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 = ["等待下載,請稍后...", "下載中", "下載成功", "下載失敗", "等待服務(wù)器處理"];
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; //文件大小轉(zhuǎn)義 類似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) {
//說明已經(jīng)進入了模擬進度
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(),
};
//判斷是否已經(jīng)存在
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 回調(diào)函數(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 代表導(dǎo)出失敗 此時讀取后端返回報錯信息
if (res.type === "application/json") {
const reader: any = new FileReader(); // 創(chuàng)建一個FileReader實例
reader.readAsText(res, "utf-8"); // 讀取文件,結(jié)果用字符串形式表示
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 || "任務(wù)進行中",
};
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);
}
}
});
}當(dāng)我們注意到再download的方法中多次使用了store,所以我們要使用到vuex來做持久化
對應(yīng)的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中配置對應(yīng)的屬性,用于獲取
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>在使用下載組件的時候會用到的一些內(nèi)部方法
import {download,downloadFile, fakeDownProgress, FileState } from 'download.ts';
使用例子 采用下列方法,可以明確傳遞的參數(shù)是什么,便于后續(xù)維護更新,復(fù)用
btnDownloadOnClick(row) {
const { fileName, fileExtension, pictureBase64Code, affixId } = row;
const url = `${this.API_URL}iqpQuery/file/flowAffixDownload`;
const params = { affixInfoId: affixId };
//采用下列方法,可以明確傳遞的參數(shù)是什么,便于后續(xù)維護更新,復(fù)用
downloadFile({
url,
name: `${fileName}.${fileExtension}`,
params,
});
},如果希望下載進度為真實進度,那么可以考慮上傳file這個對象,里面的size,把真實的文件大小傳入,或者由服務(wù)端在header加上contentLength
fakeDownProgress方法
此方法為虛假的進度展示,以便于一些沒有進度功能的長期方法的進度展示,
//使用fakeDownProgress方法進行進度展示,
//依次參數(shù)說明
//file:為FilePojo類型,可以傳遞文件id,也可以不傳遞,name必須傳遞
//func:需要等待的方法
//funcArgs:方法需要傳遞的對象,
//message:進度展示的文字信息
使用例子
//這是一個文件轉(zhuǎn)碼的方法,消耗時間的大小,不可計算,需要使用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, '文件轉(zhuǎn)碼中...');
},到此這篇關(guān)于vuecli+AXdownload下載組件封裝 +css3下載懸浮球動畫的文章就介紹到這了,更多相關(guān)vuecli+AXdownload下載組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue3 el-pagination 將組件中英文‘goto’ 修改 為&nbs
這篇文章主要介紹了vue3 el-pagination 將組件中英文‘goto’ 修改 為 中文到‘第幾’,通過實例代碼介紹了vue3項目之Pagination 組件,感興趣的朋友跟隨小編一起看看吧2024-02-02
通過vue提供的keep-alive減少對服務(wù)器的請求次數(shù)
這篇文章主要介紹了通過vue提供的keep-alive減少對服務(wù)器的請求次數(shù),文中給大家補充介紹了vue路由開啟keep-alive時的注意點,需要的朋友可以參考下2018-04-04

