vue项目中使用websocket和EventSource/es

2023-12-01 03:59:39
2024-05-08 14:32:11

websocket其他特点如下:

1. 建立在 TCP 协议之上,服务器端的实现比较容易。
2. 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

3. 数据格式比较轻量,性能开销小,通信高效。

4. 可以发送文本,也可以发送二进制数据。

5. 没有同源限制,客户端可以与任意服务器通信。

6. 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

封装WebSocket的hook

import { ref, onMounted, onUnmounted } from 'vue';
/**
 * @description websocket hook
 * @param url websocket地址:ws://xxxxxxx/
 * @param onMessage 收到消息的回调
 * @param heartbeatInterval 心跳间隔时间
 */
export default function useWebsocket(url:string, onMessage:(res:any)=>void,heartbeatInterval = 3000,) {
  // 用来存放websocket实例
  const socketTask = ref(null as any);
  // 连接是否处于断开状态的标识
  const isDisconnect = ref(true);
  // 心跳定时器
  let heartbeatTimer = null as any;
  // 连接
  const connect = () => {
    if (isDisconnect.value) {
      isDisconnect.value = false;
      console.log('WebSocket连接中...',url)
      console.log('socketTask',socketTask)
      socketTask.value = new WebSocket(url);
      socketTask.value.onopen = () => {
        console.log('WebSocket连接已打开');
        startHeartbeat();
      }

      socketTask.value.onclose = () => {
        console.log('WebSocket连接已关闭');
        if (isDisconnect.value) {
          setTimeout(() => {
            console.log('WebSocket尝试重新连接');
            connect();
          }, 1000);
        }
      } ;

      socketTask.value.onerror =(error:any) => {
        console.error('WebSocket连接发生错误:', error);
        isDisconnect.value = true;
      }

      socketTask.value.onmessage = (res:{data:string}) => {
        let data = JSON.parse(res.data);
        console.log('收到服务器消息:', data)
        if (onMessage) {
          onMessage(data);
        }
      };
    }
  };
  // 断开连接
  const disconnect = () => {
    console.log('主动断开')
    if (socketTask.value) {
      isDisconnect.value = false;
      socketTask.value.close();
      stopHeartbeat();
      socketTask.value = null;
    }
  };
  // 发送心跳
  const startHeartbeat = () => {
    heartbeatTimer = setInterval(() => {
      if (socketTask.value) {
        console.log('发送心跳')
        socketTask.value.send({});
      }
    }, heartbeatInterval);
  };
  // 停止心跳
  const stopHeartbeat = () => {
    clearInterval(heartbeatTimer);
  };
  // 发送消息
  const sendMessage = (message:any) => {
    console.log('发送消息:socketTask.value', socketTask.value)
    if (socketTask.value) {
      socketTask.value.send({
        data: JSON.stringify(message)
      });
    }
  };
  // 组件挂载时连接
  onMounted(() => {
    connect();
  });
  // 组件卸载时断开连接
  onUnmounted(() => {
    disconnect();
  });

  return {
    socketTask,
    connect,
    disconnect,
    sendMessage,
  };
}

EventSource

// Vue项目中,EventSource触发的事件中this指向变了
// 使用const that = this,然后在EventSource触发的事件中使用that
 
if (typeof (EventSource) !== 'undefined') {
    const evtSource = new EventSource('/log/print', { withCredentials: true }) // 后端接口,要配置允许跨域属性
    // 与事件源的连接刚打开时触发
    evtSource.onopen = function(e){
        console.log(e);
    }
    // 当从事件源接收到数据时触发
    evtSource.onmessage = function(e){
        console.log(e);
    }
    // 与事件源的连接无法打开时触发
    evtSource.onerror = function(e){
        console.log(e);
        evtSource.close(); // 关闭连接
    }
    // 也可以侦听命名事件,即自定义的事件
    evtSource.addEventListener('notice', function(e) {
        console.log(e.data)
    })
} else {
    console.log('当前浏览器不支持使用EventSource接收服务器推送事件!');
}

es封装与使用

// utils/sse.js
//如果加自定义参数可以使用三方插件event-source-polyfill(添加请求头token)
import { EventSourcePolyfill } from 'event-source-polyfill'

function createSSE(deviceName, handle) {
  const clientId = JSON.parse(localStorage.getItem('params')).id
  const token = JSON.parse(localStorage.getItem('params')).token
  const sse = new EventSourcePolyfill(`/monitor/sse/createConnect?sign=${deviceName}&clientId=${clientId}`, {
    headers: { token: token }
  })
  sse.addEventListener('message', env => {
    const obj = JSON.parse(env.data)
    handle(obj)
  })
  return sse
}

export default createSSE

// vue
<template>
  <div>
    <div class="dialog">
      <div class="dialogTltle">
        < img :src="
            require('@/assets/images/digitalTwin/' +
              (title == '采煤机'
                ? 'coal_cutter'
                : title == '破碎机'
                ? 'crushing_machine'
                : title == '运输机-机头'
                ? 'transport_machine'
                : title == '运输机-机尾'
                ? 'transport_machine'
                : title == '乳化液泵'
                ? 'emulsion_pump'
                : title == '清水泵'
                ? 'pump'
                : title == '液压支架'
                ? 'hydraulic_support'
                : title == '转载机'
                ? 'transport_machine'
                : title == '皮带机'
                ? 'transport_machine'
                : 'transport_machine') +
              '.png')
          "
          alt="" />
        <div>{{ title }}</div>
      </div>
      < img @click="handleEyeClick"
        v-show="state.eyeFlag"
        src="@/assets/images/digitalTwin/close_eye.png"
        alt="" />
      < img @click="handleEyeClick"
        v-show="!state.eyeFlag"
        src="@/assets/images/digitalTwin/eye.png"
        alt="" />
      <div class="dialogContent">
        <div v-for="item in Object.keys(newLabelObj)"
          :key="item">
          <div v-if="
              item.indexOf('VoltageSide') === -1 &&
              item != 'crusher' &&
              item != 'pumpRunningQ' &&
              item != 'pumpRunningR' &&
              item != 'bracketInfo'
            ">
            <div class="title">
              {{
                state.moduleType.find(val => val.typeDes === item)
                  ? state.moduleType.find(val => val.typeDes === item).title
                  : '未知'
              }}
            </div>
            <el-row :gutter="20">
              <el-col :span="12"
                v-for="item1 in Object.keys(newLabelObj[item])"
                :key="item1">
                <div class="dialogName">{{ item1 ? newLabelObj[item][item1] : '' }}</div>
                <div class="dialogNameR"
                  :class="
                    item1.indexOf('ArmMiningHeight') != -1 ||
                    item1 === 'communicationState' ||
                    item1 === 'runningDirection' ||
                    item1 === 'warningCode' ||
                    item1 === 'faultCode' ||
                    item1 === 'deviceState' ||
                    item1 === 'speedDisplay'
                      ? 'dialogNameBackgroundArm'
                      : item1 === 'totalFailure'
                      ? 'dialogVoltageQ'
                      : 'dialogNameBackground'
                  ">
                  <span>
                    <span>
                      {{
                        state.moduleType.find(val => val.typeDes === item).data[item1]
                          ? state.moduleType.find(val => val.typeDes === item).data[item1]
                          : '--'
                      }}
                    </span>
                  </span>
                </div>
              </el-col>
            </el-row>
          </div>
          <div
            v-if="item === 'crusher' || item === 'pumpRunningQ' || item === 'pumpRunningR' || item === 'bracketInfo'">
            <el-row :gutter="20"
              v-for="item1 in Object.keys(newLabelObj[item])"
              :key="item1">
              <el-col :span="14">
                <div class="dialogName">{{ item1 ? newLabelObj[item][item1] : '' }}</div>
                <div class="dialogNameR"
                  :class="
                    item1.indexOf('ArmMiningHeight') != -1 ||
                    item1 === 'communicationState' ||
                    item1 === 'runningDirection' ||
                    item1 === 'runState' ||
                    item1 === 'runningState' ||
                    item1 === 'bracketNumber' ||
                    item1 === 'miningHigh' ||
                    item1 === 'frontPillarPressure' ||
                    item1 === 'advanceDistance' ||
                    item1 === 'action' ||
                    item1 === 'frontBeamInclination'
                      ? 'dialogNameBackgroundArm'
                      : 'dialogNameBackground'
                  ">
                  <span>
                    <span>
                      {{
                        state.moduleType.find(val => val.typeDes === item).data[item1]
                          ? state.moduleType.find(val => val.typeDes === item).data[item1]
                          : '--'
                      }}
                    </span>
                  </span>
                </div>
              </el-col>
            </el-row>
          </div>
        </div>
        <div>
          <template v-if="Object.keys(newLabelObj).find(val => val.indexOf('VoltageSide') != -1)">
            <div class="title">移变参数</div>
            <el-row :gutter="20">
              <el-col :span="12">
                <div class="dialogVoltageNameH">高压侧</div>
              </el-col>
              <el-col :span="12">
                <div class="dialogVoltageNameH">低压侧</div>
              </el-col>
              <el-col :span="12">
                <div v-for="item in Object.keys(newLabelObj)"
                  :key="item">
                  <div v-if="item.indexOf('ShiftHighVoltageSide') != -1">
                    <div v-for="item1 in Object.keys(newLabelObj[item])"
                      :key="item1">
                      <div :class="item1 === 'communicationStatus' ? 'dialogVoltageState' : 'dialogVoltage'">
                        {{ item1 ? newLabelObj[item][item1] : '--' }}
                      </div>
                      <div class="dialogVoltageT"
                        :class="
                          item1 == 'faultCode'
                            ? 'dialogVoltageQ'
                            : item1 == 'communicationStatus'
                            ? 'dialogVoltageStateValue'
                            : 'dialogVoltage'
                        ">
                        <span>
                          <span>
                            {{
                              state.moduleType.find(val => val.typeDes === item).data[item1]
                                ? state.moduleType.find(val => val.typeDes === item).data[item1]
                                : '--'
                            }}
                          </span>
                        </span>
                      </div>
                    </div>
                  </div>
                </div>
              </el-col>
              <el-col :span="12">
                <div v-for="item in Object.keys(newLabelObj)"
                  :key="item">
                  <div v-if="item.indexOf('ShiftLowVoltageSide') != -1">
                    <div v-for="item1 in Object.keys(newLabelObj[item])"
                      :key="item1">
                      <div :class="item1 === 'communicationStatus' ? 'dialogVoltageState' : 'dialogVoltage'">
                        {{ item1 ? newLabelObj[item][item1] : '--' }}
                      </div>
                      <div class="dialogVoltageT"
                        :class="
                          item1 == 'faultCode'
                            ? 'dialogVoltageQ'
                            : item1 == 'communicationStatus'
                            ? 'dialogVoltageStateValue'
                            : 'dialogVoltage'
                        ">
                        <span>
                          <span>
                            {{
                              state.moduleType.find(val => val.typeDes === item).data[item1]
                                ? state.moduleType.find(val => val.typeDes === item).data[item1]
                                : '--'
                            }}
                          </span>
                        </span>
                      </div>
                    </div>
                  </div>
                </div>
              </el-col>
            </el-row>
          </template>
        </div>
      </div>
    </div>
  </div>
</template>

<script >
import { ref, reactive, onMounted, onUnmounted, watch } from 'vue'
import { labelObj, symbolObj } from '@/assets/contant/digitalTwinLabel'
import createSSE from '@/utils/sse'
let sse = null
const moduleType = [
  // 采煤机
  {
    typeDes: 'shearer',
    showType: 'flexList',
    title: '实时数据',
    voltage: false,
    data: {}
  },
  {
    typeDes: 'shearerShiftHighVoltageSide',
    showType: 'flexList',
    title: '移变参数高',
    voltage: true,
    data: {}
  }
]
var transObj = {}
export default {
  components: {},
  props: {
    armData: {
      type: Object,
      default: () => { }
    },
    title: {
      type: String,
      default: ''
    },
    bracketInfoIndex: {
      type: String,
      default: '2'
    }
  },
  setup(props, { emit }) {
    const newLabelObj = ref({ ...labelObj })
    const state = reactive({
      title: '数字孪生',
      titlekey: '',
      eyeFlag: false,
      moduleType
    })
    watch(
      () => props.title,
      newProps => {
        state.titlekey =
          newProps === '皮带机'
            ? 'belt'
            : newProps === '清水泵'
              ? 'pumpRunningQ'
              : newProps === '乳化液泵'
                ? 'pumpRunningR'
                : newProps === '破碎机'
                  ? 'crusher'
                  : newProps === '转载机'
                    ? 'transfer'
                    : newProps === '运输机-机头'
                      ? 'transport'
                      : newProps === '运输机-机尾'
                        ? 'transporter'
                        : newProps === '液压支架'
                          ? 'bracketInfo'
                          : newProps === '采煤机'
                            ? 'shearer'
                            : ''
        transObj = {}
        Object.keys(labelObj).forEach(item => {
          // 除了采煤机的实时数据需要两个地方获取
          if (state.titlekey === 'transport') {
            item.indexOf(state.titlekey) === 0 && item !== 'transporterTailInfo' && (transObj[item] = labelObj[item])
          } else if (state.titlekey === 'transporter') {
            item.indexOf(state.titlekey) === 0 && item !== 'transporterHeaderInfo' && (transObj[item] = labelObj[item])
          } else if (state.titlekey === 'pumpRunningQ') {
            item.indexOf('pumpRunningQ') === 0 && (transObj[item] = labelObj[item])
          } else if (state.titlekey === 'pumpRunningR') {
            item.indexOf('pumpRunningR') === 0 && (transObj[item] = labelObj[item])
          } else {
            item.indexOf(state.titlekey) === 0 && (transObj[item] = labelObj[item])
          }
        })
        console.log(transObj)
        newLabelObj.value = []
        newLabelObj.value = transObj
      }
    )

    sse = createSSE('digitalTwin', data => {
      state.moduleType.forEach(item => {
        const type = item.typeDes
        if (data.type === type) {
          if (type === 'bracketInfo') {
            item.data = Object.assign(item.data, { ...data.data[props.bracketInfoIndex] })
          } else {
            item.data = Object.assign(item.data, { ...data.data })
          }
        }
        if (type === 'shearer') {
          if (data.type === 'shearerLeftArm' || data.type === 'shearerRightArm') {
            item.data = Object.assign(item.data, { ...data.data })
          }
        }
        if (data.type === 'pumpRunning') {
          if (type === 'pumpRunningR') {
            item.data = Object.assign(item.data, { ...data.data[4] })
          }
          if (type === 'pumpRunningQ') {
            item.data = Object.assign(item.data, { ...data.data[0] })
          }
        }
      })
      emit('realDta', data)
    })
    const handleEyeClick = () => {
      emit('closeEye')
    }

    const initData = JSON.parse(JSON.stringify(labelObj))
    for (const p in initData) {
      for (const k in initData[p]) {
        initData[p][k] = '--'
      }
    }
    state.moduleType.forEach(item => {
      item.data = initData[item.typeDes]
    })
    state.moduleType = JSON.parse(JSON.stringify(state.moduleType))

    onMounted(() => { })
    onUnmounted(() => {
      sse && sse.close()
    })
    return {
      state,
      symbolObj,
      labelObj,
      newLabelObj,
      handleEyeClick
    }
  }
}
</script>
目录
暂无评论,欢迎留下你的评论

运营需要亿点资金维持,您的支持,是小白龙创作的动力!!!

昵称
留言
赞赏金额