vxe.web.socket.mixins.js 7.02 KB
import store from '@/store/'
import { randomUUID } from '@/utils/util'
import Vue from 'vue'
import { ACCESS_TOKEN } from '@/store/mutation-types'

// vxe socket
const vs = {
  // 页面唯一 id,用于标识同一用户,不同页面的websocket
  pageId: randomUUID(),
  // webSocket 对象
  ws: null,
  // 一些常量
  constants: {
    // 消息类型
    TYPE: 'type',
    // 消息数据
    DATA: 'data',
    // 消息类型:心跳检测
    TYPE_HB: 'heart_beat',
    // 消息类型:通用数据传递
    TYPE_CSD: 'common_send_date',
    // 消息类型:更新vxe table数据
    TYPE_UVT: 'update_vxe_table',
  },
  // 心跳检测
  heartCheck: {
    // 间隔时间,间隔多久发送一次心跳消息
    interval: 10000,
    // 心跳消息超时时间,心跳消息多久没有回复后重连
    timeout: 6000,
    timeoutTimer: null,
    clear() {
      clearTimeout(this.timeoutTimer)
      return this
    },
    start() {
      vs.sendMessage(vs.constants.TYPE_HB, '')
      // 如果超过一定时间还没重置,说明后端主动断开了
      this.timeoutTimer = window.setTimeout(() => {
        vs.reconnect()
      }, this.timeout)
      return this
    },
    // 心跳消息返回
    back() {
      this.clear()
      window.setTimeout(() => this.start(), this.interval)
    },
  },

  /** 初始化 WebSocket */
  initialWebSocket() {
    if (this.ws === null) {
      const userId = store.getters.userInfo.id
      const domain = window._CONFIG['domianURL'].replace('https://', 'wss://').replace('http://', 'ws://')
      const url = `${domain}/vxeSocket/${userId}/${this.pageId}`

      //update-begin-author:taoyan date:2022-4-22 for:  v2.4.6 的 websocket 服务端,存在性能和安全问题。 #3278
      let token = Vue.ls.get(ACCESS_TOKEN)
      this.ws = new WebSocket(url, [token])
      //update-end-author:taoyan date:2022-4-22 for:  v2.4.6 的 websocket 服务端,存在性能和安全问题。 #3278
      this.ws.onopen = this.on.open.bind(this)
      this.ws.onerror = this.on.error.bind(this)
      this.ws.onmessage = this.on.message.bind(this)
      this.ws.onclose = this.on.close.bind(this)

      console.log('this.ws: ', this.ws)
    }
  },

  // 发送消息
  sendMessage(type, message) {
    try {
      let ws = this.ws
      if (ws != null && ws.readyState === ws.OPEN) {
        ws.send(JSON.stringify({
          type: type,
          data: message
        }))
      }
    } catch (err) {
      console.warn('【VXEWebSocket】发送消息失败:(' + err.code + ')')
    }
  },

  /** 绑定全局VXE表格 */
  tableMap: new Map(),
  CSDMap: new Map(),
  /** 添加绑定 */
  addBind(map, key, value) {
    let binds = map.get(key)
    if (Array.isArray(binds)) {
      binds.push(value)
    } else {
      map.set(key, [value])
    }
  },
  /** 移除绑定 */
  removeBind(map, key, value) {
    let binds = map.get(key)
    if (Array.isArray(binds)) {
      for (let i = 0; i < binds.length; i++) {
        let bind = binds[i]
        if (bind === value) {
          binds.splice(i, 1)
          break
        }
      }
      if (binds.length === 0) {
        map.delete(key)
      }
    } else {
      map.delete(key)
    }
  },
  // 呼叫绑定的表单
  callBind(map, key, callback) {
    let binds = map.get(key)
    if (Array.isArray(binds)) {
      binds.forEach(callback)
    }
  },

  lockReconnect: false,
  /** 尝试重连 */
  reconnect() {
    if (this.lockReconnect) return
    this.lockReconnect = true
    setTimeout(() => {
      if (this.ws && this.ws.close) {
        this.ws.close()
      }
      this.ws = null
      console.info('【VXEWebSocket】尝试重连...')
      this.initialWebSocket()
      this.lockReconnect = false
    }, 5000)
  },

  on: {
    open() {
      console.log('【VXEWebSocket】连接成功')
      this.heartCheck.start()
    },
    error(e) {
      console.warn('【VXEWebSocket】连接发生错误:', e)
      this.reconnect()
    },
    message(e) {
      // 解析消息
      let json
      try {
        json = JSON.parse(e.data)
      } catch (e) {
        console.warn('【VXEWebSocket】收到无法解析的消息:', e.data)
        return
      }
      let type = json[this.constants.TYPE]
      let data = json[this.constants.DATA]
      switch (type) {
        // 心跳检测
        case this.constants.TYPE_HB:
          this.heartCheck.back()
          break
        // 通用数据传递
        case this.constants.TYPE_CSD:
          this.callBind(this.CSDMap, data.key, (fn) => fn.apply(this, data.args))
          break
        // 更新form数据
        case this.constants.TYPE_UVT:
          this.callBind(this.tableMap, data.socketKey, (vm) => this.onVM['onUpdateTable'].apply(vm, data.args))
          break
        default:
          console.warn('【VXEWebSocket】收到不识别的消息类型:' + type)
          break
      }
    },
    close(e) {
      console.log('【VXEWebSocket】连接被关闭:', e)
      this.reconnect()
    },
  },

  onVM: {
    /** 收到更新表格的消息 */
    onUpdateTable(row, caseId) {
      // 判断是不是自己发的消息
      if (this.caseId !== caseId) {
        const tableRow = this.getIfRowById(row.id).row
        // 局部保更新数据
        if (tableRow) {
          // 特殊处理拖轮状态
          if (row['tug_status'] && tableRow['tug_status']) {
            row['tug_status'] = Object.assign({}, tableRow['tug_status'], row['tug_status'])
          }
          // 判断是否启用重载特效
          if (this.reloadEffect) {
            this.$set(this.reloadEffectRowKeysMap, row.id, true)
          }
          Object.keys(row).forEach(key => {
            if (key !== 'id') {
              this.$set(tableRow, key, row[key])
            }
          })
          this.$refs.vxe.reloadRow(tableRow)
        }
      }
    },
  },

}

export default {
  props: {
    // 是否开启使用 webSocket 无痕刷新
    socketReload: {
      type: Boolean,
      default: false
    },
    socketKey: {
      type: String,
      default: 'vxe-default'
    },
  },
  data() {
    return {}
  },
  mounted() {
    if (this.socketReload) {
      vs.initialWebSocket()
      vs.addBind(vs.tableMap, this.socketKey, this)
    }
  },
  methods: {

    /** 发送socket消息更新行 */
    socketSendUpdateRow(row) {
      vs.sendMessage(vs.constants.TYPE_UVT, {
        socketKey: this.socketKey,
        args: [row, this.caseId],
      })
    },

  },
  beforeDestroy() {
    vs.removeBind(vs.tableMap, this.socketKey, this)
  },
}

/**
 * 添加WebSocket通用数据传递绑定,相同的key可以添加多个方法绑定
 * @param key 唯一key
 * @param fn 当消息来的时候触发的回调方法
 */
export function addBindSocketCSD(key, fn) {
  if (typeof fn === 'function') {
    vs.addBind(vs.CSDMap, key, fn)
  }
}

/**
 * 移除WebSocket通用数据传递绑定
 * @param key 唯一key
 * @param fn 要移除的方法,必须和添加时的方法内存层面上保持一致才可以正确移除
 */
export function removeBindSocketCSD(key, fn) {
  if (typeof fn === 'function') {
    vs.removeBind(vs.CSDMap, key, fn)
  }
}