当导出文件过大,不能同步下载,需要后端异步导出成功后通知前端,前端更新下载状态,这时可以使用websocket
要求在a、b、c、d几个页面中,无论打开哪一个都开启websocket,四个页面都关闭才能正常关闭websocket
1.下载插件,全局引入和配置
npm install --save vue-native-websocket
main.js 进行全局引入配置
import websocket from "vue-native-websocket";
Vue.use(websocket, "", {
connectManually: true, // 手动连接
format: "json", // json格式
reconnection: true, // 是否自动重连
reconnectionAttempts: 5, // 自动重连次数
reconnectionDelay: 2000, // 重连间隔时间
});
2.在src下创建socket文件夹
src/socket/index.js
import { getToken } from "@/utils/auth";
import { EventBus } from "@/utils/bus";
const socketService = {
socket: null,
timer: null,
url: undefined,
heartbeatTimer: null, // 添加心跳定时器
heartbeatInterval: 30000, // 30秒 // 心跳间隔时间(以毫秒为单位)
init(url) {
if (typeof WebSocket === "undefined") {
alert("您的浏览器不支持socket");
} else {
//daily-web-api.shpays.com
this.url = url;
let originalUrl = process.env.VUE_APP_BASE_API;
let webSocketUrl = originalUrl.replace(/^http/, "ws"); // websocket协议正式环境是wss://,测试环境是ws://
let path = webSocketUrl + "/websocket" + url; // 请求路径
let token = getToken();
this.socket = new WebSocket(path, [token]);
this.socket.onopen = this.open.bind(this); //连接成功建立的回调方法
this.socket.onerror = this.error.bind(this); //连接发生错误的回调方法
this.socket.onclose = this.closed.bind(this); //连接异常关闭的回调方法
}
},
// 开始心跳
startHeartbeat() {
// 设置一个定时器,每隔一定时间发送心跳消息
this.heartbeatTimer = setInterval(() => {
if (this.socket.readyState === WebSocket.OPEN) {
// 发送心跳消息,可以是一个特定的字符串,表示心跳
this.socket.send("heartbeat");
}
}, this.heartbeatInterval);
},
open() {
console.log("socket连接成功");
this.socket.onmessage = this.message;
this.startHeartbeat(); // 开始心跳
clearTimeout(this.timer);
},
error(e) {
console.log("连接错误", e);
clearTimeout(this.timer);
},
send(params) {
if (this.socket) {
this.socket.send(params);
}
},
close() {
if (this.socket) {
this.socket.close();
}
clearTimeout(this.timer);
},
message(event) {
// 如果是后端发送的心跳机制则直接返回
if (event.data == "HeartBeat") return;
const msg = JSON.parse(event.data);
EventBus.$emit("rebindSend", msg);
},
closed(evt) {
let reconnectInterval = 5000;
if (evt.code === 1000) {
console.log("正常关闭");
} else {
console.log("异常关闭");
this.timer = setTimeout(() => {
this.init(this.url);
}, reconnectInterval);
}
// 关闭连接前 关闭心跳
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
},
};
//最后导出
export default socketService;
utils/bus.js
import Vue from "vue";
export const EventBus = new Vue();
3.在App.vue中设置全局事件总线
由于在多个页面都可以开启websocket,所以使用EventBus使不同组件之间可以方便发送和接收事件。解耦组件,通过全局事件总线进行通信,减少组件之间的直接依赖
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
import socketService from "@/socket/index";
import { EventBus } from "@/utils/bus";
export default {
name: 'App',
data() {
return {
openPageArr:[],
}
},
methods: {
pageClosed(page) {
console.log("close", page)
this.openPageArr = this.openPageArr.filter(item => item !== page)
this.$nextTick(() => {
if (!this.openPageArr.length) {
socketService.close();
}
})
},
pageOpened(page) {
console.log("open", page)
if (!this.openPageArr.length && page) {
socketService.init("/export?module=order");
}
this.openPageArr.push(page)
}
},
mounted() {
EventBus.$on('pageClosed', this.pageClosed)
EventBus.$on('pageOpened', this.pageOpened)
},
beforeDestroy() {
EventBus.$off()
}
}
</script>
4.接收消息页面
<template>
<el-popover
popper-class="my-popper"
:visible-arrow="false"
placement="bottom-start"
title="导出记录"
width="300"
trigger="hover">
<div class="content">
<p v-for="item in exportList" :key="item.id">
<span>{{ item.fileName }}</span>
<el-button
@click="downloadFile(item.id,item.downloadUrl)"
v-if="item.percentage"
type="primary"
size="small"
style="padding: 5px 10px;float: right;"
>下载</el-button>
<el-progress :percentage="item.percentage" status="success"></el-progress>
</p>
</div>
<el-button
slot="reference"
v-hasPermi="['trans:payin:export']"
size="small"
type="primary"
@click="handleExport"
:loading="isLoading"
:disabled="!orderNum || $refs.tableSys.loading"
>导出{{ orderNum }}条订单</el-button>
</el-popover>
</template>
export default {
data() {
return {
websocketMsg: [], // websocket成功消息
exportList: [], // 历史导出记录
}
},
methods: {
// websocket获取到消息
rebindSendPayIn(msg) {
console.log("消息", msg)
if (msg.status == 'PENDING') return;
if (msg.transType == 'PAYIN' && msg.status == 'SUCCESS') {
this.websocketMsg.unshift(msg);
this.exportList = this.exportList.map( item => {
if (item.id == msg.id) item.percentage = 100;
return item
})
this.msgSuccess('导出成功,请前往代收历史订单下载')
}
if ( msg.transType=='PAYIN' && msg.status == 'EXPIRED') {
this.exportList = this.exportList.map( item => {
if (item.id == msg.id) item.percentage = 0;
return item
})
}
if ( msg.transType=='PAYIN' && msg.status == 'FAIL') {
this.msgError('导出失败')
}
},
//导出
handleExport() {
this.isLoading = true;
exportPayIn({ ...this.getSearchForm }).then(res => {
this.exportList.unshift(res.data)
this.eachSocketMsg()
}).finally(() => {
this.isLoading = false;
})
},
// 下载文件按钮
downloadFile(id, downloadUrl) {
// 如果阿里云有下载文件则直接使用阿里云路径下载
if (downloadUrl) window.open(downloadUrl,'_parent');
// 没有则接口下载
else {
downloadList({ taskId: id }).then(res => {
updateExportLink(res)
this.$message.success('下载成功')
})
}
},
// 导出成功遍历socket成功信息
eachSocketMsg() {
if (this.websocketMsg.length) {
this.exportList = this.exportList.map(project => {
const flag = this.websocketMsg.find(item => item.id == project.id)
if (flag) project.percentage = 100;
return project;
})
}
},
// 进入页面获取导出历史订单
getHistoryList() {
getExportList({ transType: "PAYIN" }).then(res => {
this.exportList = [...res.data].reverse()
this.exportList = this.exportList.filter(item => item.fileName);
this.exportList.forEach(item => {
if (item.status == 'SUCCESS') item.percentage = 100;
else if (item.ossKey) item.percentage = 100;
else item.percentage = 0;
})
})
},
},
created() {
EventBus.$emit('pageOpened', 'payIn')
},
mounted() {
EventBus.$on('rebindSend', this.rebindSendPayIn)
this.getHistoryList();
},
beforeDestroy() {
EventBus.$emit('pageClosed', 'payIn')
EventBus.$off('rebindSend', this.rebindSendPayIn)
},
}
api文件
// 导出文件下载
export function downloadList(query) {
return request({
url: "/api/export/task/download",
method: "get",
params: query,
responseType: "blob",
timeout: 1800000,
});
},
// 下载
export function updateExportLink(res) {
let nameAry = res.headers["content-disposition"].split("filename=");
const url = window.URL.createObjectURL(new Blob([res.data]));
const link = document.createElement("a");
link.style.display = "none";
link.href = url;
link.setAttribute("download", `${nameAry[1]}`);
document.body.appendChild(link);
link.click();
},
// 导出
export function exportPayIn(query) {
return request({
url: "/api/order/payIn/export",
method: "get",
params: query,
});
},
用户点击导出后,在ws里查看消息信息,如status为SUCCESS则导出成功,引导用户下载文件