前端封装WebSocket通信的进实时进度条

效果截图

进度条组件的代码

<template>
  <div>
    <div class="iconbox">
      <i class="el-icon-caret-left" @click="getProgress" v-if="!isShow"></i>
      <i class="el-icon-caret-right" @click="getClose" v-else></i>
    </div>
    <div class="progressbox" v-show="isShow">
      <div class="title">进 度 展 示</div>
      <div class="contbox">
        <div v-for="(item,index) in jobData" :key="index" style="width: 100%;">
          <!-- 进度完成的 -->
          <div class="item_box_true" v-if="item.completed">
            <div class="filename" :title="item.jobName">{{ item.jobName }}:</div>
            <div class="jindu"></div>
            <div class="progressnum">100%</div>
          </div>
          <!-- 进度进行的 -->
          <div class="item_box" v-show="!item.completed">
            <div class="filename" :title="item.jobName">{{ item.jobName }}:</div>
            <div class="jindu">
              <div class="planbox" :style="getStyle(item)" :id="`${item.id}`"></div>
            </div>
            <div class="progressnum">{{ item.percent }}%</div>
          </div>
        </div>
      </div>
    </div>
    <div id="showDiv" style="position: absolute; background-color: white; border: 1px solid black;">
    </div>
  </div>
</template>

<script>
  import {
    getjobAdd,
    getjobList,
  } from '@/api/purchaserecord/opentender.js'
  import {mapGetters} from "vuex";
export default {

  components: {
  },
  // 定义属性
  data() {
    return {
      isShow:false,
      queryParams:{
        batchName:undefined,
        batchCode:undefined
      },
      rateForm:{},
      jobData:[],
      oldJobData:[],
      clientId:undefined,
      jobForm:{}
    }
  },
  comments:{
    ...mapGetters['rateData']
  },
  watch: {
    "$store.getters.rateData": {
      //深度监听,可监听到对象、数组的变化
      handler(newValue,oldValue) {
        this.rateForm = newValue
        if(this.jobData && this.jobData.length>0){
          let arr = []
          this.jobData.forEach(item =>{
            // item.completed为true的表示进度为100%的,这里看后端返回数据怎么定义,看个人项目情况
            if(!item.completed){
              if(item.id === this.rateForm.jobId){
                item.percent = this.rateForm.percent;
                if(item.percent == 100){
                  item.completed = true;
                  if(this.rateForm.messageType == 'business' && this.rateForm.jobType == '1'){
                    // this.getList()
                  }
                }else{
                  this.updateProgress(item.id);
                }
              }
            }
            arr.push(item)
          })
          this.jobData = arr
          // 获取存入浏览器的任务列表,保证打开进度框的进度条是最新的(如果后端返回的jobData数据里有与推送消息同步的percent,可以忽略此步骤)
          sessionStorage.setItem('oldJobData',JSON.stringify(this.jobData))
        }
      },
      deep: true, //true 深度监听
    }
  },
  created() {
    if(sessionStorage.getItem('batchForm')){
        let form = JSON.parse(sessionStorage.getItem('batchForm'))
        this.queryParams.batchName = form.batchName
        this.queryParams.batchCode = form.batchCode
    }
    // 获取存入浏览器的唯一标识
    if(sessionStorage.getItem('clientId')){
        this.clientId = sessionStorage.getItem('clientId')
    }
    // 获取存入浏览器的任务列表,保证打开进度框的进度条是最新的
    if(sessionStorage.getItem('oldJobData')){
      this.oldJobData = JSON.parse(sessionStorage.getItem('oldJobData'));
    }
  },
  mounted() {
    
  },
  // 销毁时断开连接
  beforeDestroy(){
    if (this.dataForm) {
      this.dataForm.close();
    }
  },
  methods: {
    // 触发连接,接收消息
    getcreateLink(){
      this.$store.dispatch("CreateLink",this.clientId).then(res =>{
      // this.source = res
      })
    },
    //打开进度条弹框
    getProgress(){
      this.getjobList()
    },
    //关闭进度条弹框
    getClose(val){
      this.isShow = false
    },
    // job列表,进度条任务列表
    getjobList(){
      let form = {
        clientId:this.clientId,
        batchName:this.queryParams.batchName,
        batchCode:this.queryParams.batchCode
      }
        getjobList(form).then(res =>{
          this.jobData = res.data.data;
          this.jobData.forEach((item)=>{
              if(this.oldJobData.filter((a)=>a.id === item.id).length > 0){
                  item.percent = this.oldJobData.filter((a)=>a.id === item.id)[0].percent
              }else {
                item.percent = 0;
              }
          })
            this.isShow = true
        })
    },
    // 进度条进度绘制
    updateProgress(val){
        if(this.rateForm && this.rateForm.percent){
          let width = 0
          const progressBar = document.getElementById(`${val}`);
          width = this.rateForm.percent
          progressBar.style.width = width + '%';
        }
      },
      // 计算初始进度条进度
      getStyle(row){
          return `width:${row.percent?row.percent*2:0}px;`
      }
  },
}
</script>

<style lang='scss' scoped>
.iconbox{
    position: fixed;
    width: 50px;
    height: 50px;
    border-radius: 50%;
    background-color: #ffffff;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
    right: 1%;
    bottom: 5%;
    z-index: 9999 !important;
    display: flex;
    justify-content: center;
    align-items: center;
    i{
      font-size: 24px;
      color: #009688;
    }
  }
  .progressbox{
    border-radius: 10px 10px 0 0;
    position: fixed;
    width: 420px;
    height: 260px;
    background-color: #ffffff;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
    right: 5%;
    bottom: 5%;
    z-index: 9999 !important;
    .title{
      width: 100%;
      height: 40px;
      line-height: 40px;
      text-align: center;
      color: #ffffff;
      border-radius: 10px 10px 0 0;
      background-color: #009688;
    }
    .contbox{
      width: 100%;
      height: 200px;
      overflow: hidden;
      overflow-y: auto;
      text-align: center;
      .item_box{
        width:400px;
        height: 30px;
        margin: 0 10px;
        display: flex;
        justify-content: center;
        align-items: center;
        .filename{
          font-size: 12px !important;
          width: 140px;
          margin-right: 10px;
          white-space: nowrap;      /* 保持文本在一行内显示 */
          overflow: hidden;         /* 超出容器部分隐藏 */
          text-overflow: ellipsis;  /* 使用省略号表示文本被截断 */
        }
        .jindu{
          width:200px;
          height: 10px;
          border-radius: 5px;
          background-color: #d9d9d9;
          .planbox{
            height: 10px;
            border-radius: 5px;
            background-color: #009688;
          }
        }
        .progressnum{
          width: 30px;
          font-size: 12px !important;
          margin-left: 10px;
        }
      }
      .item_box_true{
        width:400px;
        height: 30px;
        margin: 0 10px;
        display: flex;
        justify-content: center;
        align-items: center;
        .filename{
          font-size: 12px !important;
          width: 140px;
          margin-right: 10px;
          white-space: nowrap;      /* 保持文本在一行内显示 */
          overflow: hidden;         /* 超出容器部分隐藏 */
          text-overflow: ellipsis;  /* 使用省略号表示文本被截断 */
        }
        .jindu{
          width:200px;
          height: 10px;
          border-radius: 5px;
          background-color: #009688;
        }
        .progressnum{
          width: 30px;
          font-size: 12px !important;
          margin-left: 10px;
        }
      }
    }
  }
</style>

存入vueX中的触发方法(这里是我项目里方法,可根据自己项目进行更改)

let ws = null;
let wsState = false
async function createWebSocket(commit,id) {
    const wsUri = '/ws/'+id;
      if ('WebSocket' in window) {
        ws = new WebSocket(wsUri);
      } else {
        console.log('您的浏览器不支持websocket协议');
      }
}
function initEventHandle( commit,id) {
  console.log('ws=>',ws);
  ws.onmessage = (event) => {
      // console.log('接收消息=>:' + event.data + ' 时间:' + new Date());
      if(event.data && event.data.length>0){
        const rateForm = JSON.parse(event.data)
        commit('SET_RATEDATA',rateForm)
      }
  };
  ws.onerror = function () {
    console.log('ws连接错误!');
  };
}




const user = {
  state: {
    rateData:null,
    sourceid:[],
  },
  actions:{
   CreateLink({commit},id) {
      return new Promise((resolve) => {
        let strid = sessionStorage.getItem('sourceid')?sessionStorage.getItem('sourceid'):null
        const issource = sessionStorage.getItem('issource')
        createWebSocket(commit,id);
        if(strid !== id){
          sessionStorage.setItem('sourceid',id)
          if(issource != 1){
            ws.onopen = (event) => {
              console.log('建立onopen链接=>',event.data);
              sessionStorage.setItem('issource',1) 
              setInterval(()=>{
                ws.send('11')
              },2000)
            }
          }
        }
        initEventHandle(commit,id);
      })
    }
  },
  mutations:{
    SET_RATEDATA: (state,rateData) => {
      state.rateData = rateData;
    },
    SET_SOURCEID: (state,sourceid) => {
      state.sourceid = sourceid;
    },
  }
}

app.vue中的代码(这里是我项目里方法,可根据自己项目进行更改。此处作用是保证刷新或者切换项目其他页面,也能保证接收到新的消息推送)

data() {
    return {
      sk:'',
      dataForm:null,
      rateForm:{}
    };
  },
  watch: {},
  created() {
    // 创建浏览器唯一标识id,建立sse通信实时连接
    FingerprintJS.load().then(fp => {
      fp.get().then(result => {
          const clientId = result.visitorId;
          sessionStorage.setItem('clientId',clientId)
          this.createLink(clientId)
      });
    });
  },
  beforeDestroy(){
    if (this.dataForm) {
      this.dataForm.close();
    }
  },
  methods: {
    // sse消息推送建立链接
    createLink(id) {
        this.$store.dispatch("CreateLink",id).then(res =>{
          this.dataForm = res
        })
    }
  },
  computed: {
    ...mapGetters(['source'])
  },

其他页面使用本组件时

<template>
    <div>
      <basic-container>
        <el-row :gutter="10">
            <el-col :span="1.5">
                <el-button
                  type="primary" 
                  size="small" 
                  icon="el-icon-refresh" 
                  @click="handleRenew"
                >更新</el-button
                >
            </el-col>
        </el-row>
        <Progress ref="progs"></Progress>
      </basic-container>
    </div>
</template>

methods: {
   handleRenew(){
          this.$confirm("是否更新数据?", "提示", {
            confirmButtonText: "确定",
            cancelButtonText: "取消",
            type: "warning"
          }).then(() => {
            let jobform = {
              jobName:'供应商标书信息',
              jobType:3,
              clientId:this.clientId,
              batchCode:this.queryParams.batchCode,
              batchName:this.queryParams.batchName
            }
            // 触发连接接收消息
            this.$refs['progs'].getcreateLink()
            // 打开进度框
            this.$refs['progs'].getProgress()
            // 创建任务id,生成任务
            getjobAdd(jobform).then(res =>{
              getMerge(jobform).then(res =>{

              })
            })
          });
        },
}

vue.config.js里的修改

//开发模式反向代理配置,生产模式请使用Nginx部署并配置反向代理
  devServer: {
    port: 1888,
    proxy: {
      '/api': {
        //本地服务接口地址
        target: 'http://192.168.2.17:28825',
        //远程演示服务地址,可用于直接启动项目
        //target: 'https://saber.bladex.vip/api',
        ws: true,
        pathRewrite: {
          '^/api': '/'
        }
      },
      '/ws': {
        //本地服务接口地址
        target: 'ws://192.168.2.17:28825',
        //远程演示服务地址,可用于直接启动项目
        //target: 'https://saber.bladex.vip/api',
        ws: true
      },
      
    },
    disableHostCheck:true
  }

接下来的为借鉴大佬的WebSocket代码(放在vuex里的)

const state = {
  wsState: false,
};

let ws = null;
async function createWebSocket(commit) {
  const res = await getHeartbeatFlag();
  const isHeartbeat = res.data === 'true';
  if (isHeartbeat) {
    let ip = document.location.protocol + '//' + window.location.host;
    ip = ip.replace('http', 'ws').replace('https', 'wss');
    const wsUri = ip + process.env.VUE_APP_BASE_API + defaultSettings.heartWsApi + state.token;
    // console.log(wsUri)
    try {
      if ('WebSocket' in window) {
        ws = new WebSocket(wsUri);
      } else {
        console.log('您的浏览器不支持websocket协议');
      }
      initEventHandle(wsUri, commit);
    } catch (e) {
      console.log(e);
    }
  }
}
function initEventHandle(wsUri, commit) {
  ws.onclose = function () {
    console.log('Ws is Close, Re WebSocket!' + new Date() + ' wsState: ' + state.wsState);
    if (state.wsState) {
      createWebSocket(commit);
    }
  };
  ws.onopen = function () {
    // console.log('wsSuccess!' + new Date())
  };
  ws.onmessage = function (event) {
    if (event.data === '1') {
      console.log('heartbeat:' + event.data + ' 时间:' + new Date());
    } else if (event.data === '-1') {
      console.log('服务器发送关闭');
      state.wsState = false;
      if (ws !== null) {
        ws.close();
      }
      Modal.confirm({
        title: '注销成功',
        content: '您已注销,请您重新登录!',
        closable: false,
        okText: '重新登录',
        cancelText: '取消',
        onOk() {
          console.log('request error -> resetToken');
          store.dispatch('user/resetToken').then(() => {
            location.reload();
          });
        },
      });
    }
  };
  ws.onerror = function () {
    console.log('ws连接错误!');
  };
}

const actions = {
    getInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      getInfo(state.token)
        .then((response) => {
          const { data } = response;

          if (!data) {
            reject('验证失败,请重新登录。');
          }

          const {
            roles,
            name,
            userList,
            userId,
            newUser,
            avatar,
            introduction,
            rolesMenus,
            userFunctions,
            currentRole,
            passOutDay,
            isValidate,
            menus,
          } = data;
          // roles must be a non-empty array
          if (!roles || roles.length <= 0) {
            reject('getInfo: 角色必须是非零数组!');
          }
          commit('SET_ROLES', roles);
          commit('SET_NAME', name);
          commit('SET_UserId', userId);
          commit('SET_NewUser', newUser);
          commit('SET_UserList', userList);
          commit('SET_AVATAR', avatar);
          commit('SET_INTRODUCTION', introduction);
          commit('SET_ROLES_MENUS', rolesMenus);
          commit('SET_USER_FUNCTIONS', userFunctions);
          commit('SET_CURRENT_ROLE', currentRole);
          commit('SET_PASSOUTDAY', passOutDay);
          commit('SET_VALIDATE', isValidate);
          commit('SET_NAVLIST', {
            roles,
            menus,
          });
          // heartbeatFlag(commit)
          // 开启ws连接
          state.wsState = true;
          createWebSocket(commit);
          resolve(data);
        })
        .catch((error) => {
          reject(error);
        });
    });
  },
}

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值