vue2如何使用websocket异步导出文件

当导出文件过大,不能同步下载,需要后端异步导出成功后通知前端,前端更新下载状态,这时可以使用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则导出成功,引导用户下载文件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值