Quellcode durchsuchen

调整 websocket 连接和 Chat.vue 页面

reghao vor 1 Jahr
Ursprung
Commit
a3e50a74cb

+ 2 - 2
src/api/contact.js

@@ -17,8 +17,8 @@ export function getApplyRecords(pageNumber) {
   return get(contactApi.applyContactApi + '?pageNumber=' + pageNumber)
 }
 
-export function getUserContact(userId) {
-  return get(contactApi.getContactApi, userId)
+export function getUserContact() {
+  return get(contactApi.getContactApi)
 }
 
 export function getApplyContactCount() {

+ 7 - 5
src/components/VideoPlayer.vue

@@ -45,7 +45,9 @@ export default {
         if (res.code === 0) {
           var event = false
           if (this.userToken != null) {
-            SocketInstance.connect()
+            const wsUrl = 'ws:' + process.env.VUE_APP_SERVER_URL + '/ws/progress?token=' + this.userToken
+            this.wsClient = new SocketInstance(wsUrl)
+            this.wsClient.connect()
             event = true
           }
 
@@ -125,7 +127,7 @@ export default {
           const jsonData = {}
           jsonData.event = 'progress'
           jsonData.payload = JSON.stringify(payload)
-          SocketInstance.send(jsonData)
+          this.wsClient.send(jsonData)
         }
       })
 
@@ -141,7 +143,7 @@ export default {
           jsonData.event = 'progress'
           jsonData.payload = JSON.stringify(payload)
           console.log(jsonData)
-          SocketInstance.send(jsonData)
+          this.wsClient.send(jsonData)
         }
       })
 
@@ -191,7 +193,7 @@ export default {
           const jsonData = {}
           jsonData.event = 'progress'
           jsonData.payload = JSON.stringify(payload)
-          SocketInstance.send(jsonData)
+          this.wsClient.send(jsonData)
         }
       })
 
@@ -206,7 +208,7 @@ export default {
           const jsonData = {}
           jsonData.event = 'progress'
           jsonData.payload = JSON.stringify(payload)
-          SocketInstance.send(jsonData)
+          this.wsClient.send(jsonData)
         }
       })
 

+ 17 - 4
src/components/layout/NavBar.vue

@@ -100,6 +100,11 @@
                 class="size"
                 @click.native="goToMall"
               >我的商城</el-dropdown-item>
+              <el-dropdown-item
+                icon="el-icon-chat-dot-round"
+                class="size"
+                @click.native="goToChat"
+              >我的IM</el-dropdown-item>
             </el-dropdown-menu>
           </el-dropdown>
           <span
@@ -354,6 +359,14 @@ export default {
       }
       this.$router.push('/exam')
     },
+    // 我的地图
+    goToMap() {
+      if (this.$route.path === '/map') {
+        this.$router.go(0)
+        return
+      }
+      this.$router.push('/map')
+    },
     // 我的商城
     goToMall() {
       if (this.$route.path === '/mall') {
@@ -362,13 +375,13 @@ export default {
       }
       this.$router.push('/mall')
     },
-    // 我的地图
-    goToMap() {
-      if (this.$route.path === '/map') {
+    // 我的IM
+    goToChat() {
+      if (this.$route.path === '/chat') {
         this.$router.go(0)
         return
       }
-      this.$router.push('/map')
+      this.$router.push('/chat')
     }
     // ************************************************************************
   }

+ 4 - 4
src/router/index.js

@@ -24,7 +24,7 @@ const AudioPage = () => import('views/home/AudioPage')
 const ImagePage = () => import('views/home/ImagePage')
 const ArticleIndex = () => import('views/home/Article')
 const ArticlePage = () => import('views/home/ArticlePage')
-const MessageStream = () => import('views/home/MessageStream')
+const Chat = () => import('views/home/Chat')
 const Search = () => import('views/home/Search')
 const PlaylistIndex = () => import('views/home/PlaylistIndex')
 const PlaylistView = () => import('views/home/PlaylistView')
@@ -82,9 +82,9 @@ const routes = [
         meta: { needAuth: true }
       },
       {
-        path: '/stream',
-        name: 'MessageStream',
-        component: MessageStream,
+        path: '/chat',
+        name: 'Chat',
+        component: Chat,
         meta: { needAuth: false }
       },
       {

+ 3 - 1
src/store/getters.js

@@ -1,6 +1,8 @@
 const getters = {
   // 用户登录状态
-  uid: state => state.user.uid
+  uid: state => state.user.uid,
+  // websocket 连接状态
+  socketStatus: state => state.socketStatus
 }
 
 export default getters

+ 7 - 30
src/store/index.js

@@ -2,43 +2,20 @@ import Vuex from 'vuex'
 import Vue from 'vue'
 
 import user from './modules/user'
+import chat from './modules/talk'
 import getters from './getters'
+import state from './state'
+import mutations from './mutations'
 
 Vue.use(Vuex)
 const store = new Vuex.Store({
   modules: {
-    user
+    user,
+    chat
   },
   getters,
-  state: {
-    pageBean: Object,
-    activePage: 1,
-    cid: 1,
-    value: String // 搜索框的value
-  },
-  // 同步操作
-  mutations: {
-    // 获取分页视频
-    getPageBean(state, pageBean) {
-      state.pageBean = pageBean
-    },
-    // 存入cid
-    saveCid(state, cid) {
-      state.cid = cid
-    },
-    // 存入value
-    saveValue(state, value) {
-      state.value = value
-    },
-    // 存入playerVideo
-    savePlayerVideo(state, palyerVideo) {
-      state.playerVideo = palyerVideo
-    },
-    // 改变页数
-    updatePage(state, newPage) {
-      state.activePage = newPage
-    }
-  },
+  state,
+  mutations,
   // 异步操作
   actions: {
     getPageBean(context) {

+ 86 - 0
src/store/modules/talk.js

@@ -0,0 +1,86 @@
+const Talk = {
+  state: {
+    // 会话列表
+    items: []
+  },
+  getters: {
+    talkItems: state => {
+      return state.items
+    },
+    talkNum: state => state.items.length
+  },
+  mutations: {
+    // 新增对话节点
+    PUSH_TALK_ITEM(state, resource) {
+      state.items.push(resource)
+    },
+    // 移除对话节点
+    REMOVE_TALK_ITEM(state) {
+      state.items = []
+    },
+    SET_LOAD_STATUS(state, resource) {
+      state.loadStatus = resource
+    },
+    // 设置对话列表
+    SET_TALK_ITEMS(state, resource) {
+      state.items = resource.items
+    },
+    // 更新对话节点
+    UPDATE_TALK_ITEM(state, resource) {
+      for (const iterator of state.items) {
+        if (iterator.indexName === resource.indexName) {
+          Object.assign(iterator, resource)
+          break
+        }
+      }
+    },
+    // 更新对话消息
+    UPDATE_TALK_MESSAGE(state, resource) {
+      for (const iterator of state.items) {
+        if (iterator.indexName !== resource.indexName) {
+          continue
+        }
+
+        iterator.unreadNum++
+        iterator.msgText = resource.msgText
+        iterator.updatedAt = resource.updatedAt
+        break
+      }
+    },
+
+    SET_TLAK_UNREAD_MESSAGE(state, resource) {
+      state.unreadMessage.num++
+      state.unreadMessage.nickname = resource.nickname
+      state.unreadMessage.content = resource.content
+    },
+
+    // 清除最后一条未读消息
+    CLEAR_TLAK_UNREAD_MESSAGE(state) {
+      state.unreadMessage = {
+        num: 0,
+        nickname: '未知',
+        content: '...'
+      }
+    }
+  },
+  actions: {
+    // 加载会话列表
+    LOAD_TALK_ITEMS(context) {
+      context.commit('SET_LOAD_STATUS', 2)
+      /* ServeGetTalkList().then(({ code, data }) => {
+          if (code !== 0) return
+
+          context.commit('SET_TALK_ITEMS', {
+            items: data.map(item => formateTalkItem(item)),
+          })
+
+          context.commit('SET_LOAD_STATUS', 3)
+        })
+        .catch(() => {
+          context.commit('SET_LOAD_STATUS', 4)
+        })*/
+    }
+  }
+}
+
+export default Talk

+ 29 - 0
src/store/mutations.js

@@ -0,0 +1,29 @@
+// 根级别的 mutation
+const mutations = {
+  // 更新socket 连接状态
+  UPDATE_SOCKET_STATUS(state, status) {
+    state.socketStatus = status
+  },
+  // 获取分页视频
+  getPageBean(state, pageBean) {
+    state.pageBean = pageBean
+  },
+  // 存入cid
+  saveCid(state, cid) {
+    state.cid = cid
+  },
+  // 存入value
+  saveValue(state, value) {
+    state.value = value
+  },
+  // 存入playerVideo
+  savePlayerVideo(state, palyerVideo) {
+    state.playerVideo = palyerVideo
+  },
+  // 改变页数
+  updatePage(state, newPage) {
+    state.activePage = newPage
+  }
+}
+
+export default mutations

+ 10 - 0
src/store/state.js

@@ -0,0 +1,10 @@
+// 根级别的 state
+const state = {
+  socketStatus: 'Offline',
+  pageBean: Object,
+  activePage: 1,
+  cid: 1,
+  value: String // 搜索框的value
+}
+
+export default state

+ 61 - 0
src/utils/ws/event/base.js

@@ -0,0 +1,61 @@
+import store from '@/store'
+import router from '@/router'
+import { Notification } from 'element-ui'
+
+class Base {
+  /**
+   * 初始化
+   */
+  constructor() {
+    this.$notify = Notification
+  }
+
+  getStoreInstance() {
+    return store
+  }
+
+  /**
+   * 获取当前登录用户的ID
+   */
+  getAccountId() {
+    return store.state.user.uid
+  }
+
+  getTalkParams() {
+    let { talkType, receiverId, indexName } = store.state.dialogue
+
+    return { talkType, receiverId, indexName }
+  }
+
+  /**
+   * 判断消息是否来自当前对话
+   *
+   * @param {Number} talkType 聊天消息类型[1:私信;2:群聊;]
+   * @param {Number} senderId 发送者ID
+   * @param {Number} receiverId 接收者ID
+   */
+  isTalk(talkType, senderId, receiverId) {
+    let params = this.getTalkParams()
+
+    if (talkType != params.talkType) {
+      return false
+    } else if (
+      params.receiverId == receiverId ||
+      params.receiverId == senderId
+    ) {
+      return true
+    }
+
+    return false
+  }
+
+  /**
+   * 判断用户是否打开对话页
+   */
+  isTalkPage() {
+    let path = router.currentRoute.path
+    return !(path != '/message' && path != '/')
+  }
+}
+
+export default Base

+ 69 - 0
src/utils/ws/event/talk.js

@@ -0,0 +1,69 @@
+import store from '@/store'
+import Base from './base'
+
+/**
+ * 好友状态事件
+ */
+class Talk extends Base {
+  /**
+   * @var resource 资源
+   */
+  resource
+
+  /**
+   * 发送者ID
+   */
+  senderId = 0
+
+  /**
+   * 接收者ID
+   */
+  receiverId = 0
+
+  /**
+   * 聊天类型[1:私聊;2:群聊;]
+   */
+  talkType = 0
+
+  /**
+   * 初始化构造方法
+   *
+   * @param {Object} resource Socket消息
+   */
+  constructor(resource) {
+    super()
+    this.senderId = resource.senderId
+    this.receiverId = resource.receiverId
+    this.talkType = resource.talkType
+    this.resource = resource
+  }
+
+  /**
+   * 处理 event
+   * @returns
+   */
+  handle() {
+    console.log(this.resource)
+    var eventMessage = JSON.parse(this.resource)
+    var event = eventMessage.event
+    var payload = eventMessage.payload
+
+    const receiverId = payload.receiverId
+    var justify = 'start'
+    if (receiverId === this.userId) {
+      justify = 'end'
+    }
+
+    var msg = {}
+    msg.senderId = payload.senderId
+    msg.content = payload.content
+    var item = {
+      content: msg,
+      justify: justify
+    }
+    console.log(item)
+    store.commit('PUSH_TALK_ITEM', item)
+  }
+}
+
+export default Talk

+ 15 - 26
src/utils/ws/socket-instance.js

@@ -1,6 +1,6 @@
+import store from '@/store'
 import WsSocket from '@/utils/ws/ws-socket'
-import { Notification } from 'element-ui'
-import { getAccessToken } from '@/utils/auth'
+import TalkEvent from '@/utils/ws/event/talk'
 
 /**
  * SocketInstance 连接实例
@@ -16,34 +16,24 @@ class SocketInstance {
   /**
    * SocketInstance 初始化实例
    */
-  constructor() {
+  constructor(wsUrl) {
     this.socket = new WsSocket(
-      () => {
-        const protocol = location.protocol
-        let prefix
-        if (protocol === 'https') {
-          prefix = 'wss://'
-        } else {
-          prefix = 'ws://'
-        }
-
-        const token = getAccessToken()
-        // var url = 'wss:' + process.env.VUE_APP_SERVER_URL + '/ws/progress?token=' + token
-        const url = prefix + process.env.VUE_APP_SERVER_URL + '/ws/progress?token=' + token
-        return url
-      },
+      () => { return wsUrl },
       {
         onError: evt => {
           console.log('Websocket 连接失败回调方法')
+          store.commit('UPDATE_SOCKET_STATUS', 'Offline')
         },
         // Websocket 连接成功回调方法
         onOpen: evt => {
           // 更新 WebSocket 连接状态
+          store.commit('UPDATE_SOCKET_STATUS', 'Online')
           console.log('ws 连接成功')
         },
         // Websocket 断开连接回调方法
         onClose: evt => {
           // 更新 WebSocket 连接状态
+          store.commit('UPDATE_SOCKET_STATUS', 'Offline')
           console.log('ws 连接断开')
         }
       }
@@ -61,24 +51,23 @@ class SocketInstance {
    * 注册回调消息处理事件
    */
   registerEvents() {
-    this.socket.on('heartbeat', data => {})
-
-    this.socket.on('event_error', data => {
+    this.socket.on('heartbeat', (payload, data) => {
+      console.log(data)
     })
-
-    this.socket.on('event_content', (payload, data) => {
+    this.socket.on('event_error', (payload, data) => {})
+    this.socket.on('event_talk', (payload, data) => {
+      new TalkEvent(data).handle()
       /* const title = payload.title
       const content = payload.content
-      this.sendNotification(title, content)*/
+      this.sendNotification(title, content)
       Notification({
         title: payload.title,
         message: payload.content.substring(0, 30),
         type: 'warning',
         duration: 5000
-      })
+      })*/
     })
   }
-
   /* sendNotification(title, content) {
     new Notification(title, {
       body: content,
@@ -106,4 +95,4 @@ class SocketInstance {
   }
 }
 
-export default new SocketInstance()
+export default SocketInstance

+ 7 - 4
src/utils/ws/ws-socket.js

@@ -54,8 +54,8 @@ class WsSocket {
     this.events = Object.assign({}, this.defaultEvent, events)
 
     this.on('connect', data => {
-      this.config.heartbeat.pingInterval = data.ping_interval * 1000
-      this.config.heartbeat.pingTimeout = data.ping_timeout * 1000
+      this.config.heartbeat.pingInterval = data.interval * 1000
+      this.config.heartbeat.pingTimeout = data.timeout * 1000
       this.heartbeat()
     })
   }
@@ -184,9 +184,12 @@ class WsSocket {
       }
     }, this.config.heartbeat.pingInterval)
   }
-
   ping() {
-    this.connect.send('{"event":"heartbeat","payload":"ping"}')
+    var heartBeat = {
+      event: 'heartbeat',
+      payload: new Date().getTime()
+    }
+    this.connect.send(JSON.stringify(heartBeat))
   }
 
   /**

+ 87 - 86
src/views/home/MessageStream.vue → src/views/home/Chat.vue

@@ -2,11 +2,17 @@
   <el-row class="movie-list">
     <el-col :md="18">
       <div class="movie-list" style="height: 70vh;">
+        <el-button
+          size="mini"
+          type="submit"
+          class="el-icon-delete"
+          @click="onClear"
+        >清空</el-button>
         <!-- 注意需要给 el-scrollbar 设置高度,判断是否滚动是看它的height判断的 -->
-        <el-scrollbar ref="myScrollbar" style="width: 100%; height: 100%;">
+        <el-scrollbar ref="myScrollbar" style="width: 100%; height: 100%; background-color: #B3C0D1">
           <el-row class="movie-list">
             <div
-              v-for="(item, index) in dataList"
+              v-for="(item, index) in talkItems"
               :key="index"
               :md="6"
               :sm="12"
@@ -35,8 +41,7 @@
     </el-col>
     <el-col :md="6">
       <el-row style="padding-right: 5px; padding-left: 5px; padding-bottom: 5px">
-        <span v-if="online" style="color: forestgreen">在线</span>
-        <span v-else style="color: orangered">离线</span>
+        <span style="color: forestgreen">{{socketStatus}}</span>
 <!--        <el-col>
           <el-button
             size="mini"
@@ -45,11 +50,13 @@
             @click="clear"
           >清空</el-button>
         </el-col>-->
-        <el-select v-model="receiverId" placeholder="选择用户">
-          <el-option label="西瓜" value="10001" />
-          <el-option label="土豆" value="10002" />
-          <el-option label="番茄" value="10003" />
-          <el-option label="芒果" value="10004" />
+        <el-select v-model="receiverId" placeholder="选择联系人">
+          <el-option
+            v-for="(item, index) in contactList"
+            :key="index"
+            :label="item.label"
+            :value="item.value"
+          />
         </el-select>
       </el-row>
       <el-row style="padding-right: 5px; padding-left: 5px; padding-bottom: 5px">
@@ -82,10 +89,14 @@
 </template>
 
 <script>
+import { mapState, mapGetters } from 'vuex'
 import { getAccessToken, getAuthedUser } from '@/utils/auth'
+import { getUserContact } from '@/api/contact'
+import SocketInstance from '@/utils/ws/socket-instance'
+import store from '@/store'
 
 export default {
-  name: 'WebSocket',
+  name: 'Chat',
   filters: {
     ellipsis(value) {
       if (!value) return ''
@@ -98,21 +109,66 @@ export default {
   },
   data() {
     return {
+      wsClient: null,
+      heartbeatConfig: {
+        setInterval: null,
+        pingInterval: 20000,
+        pingTimeout: 60000
+      },
+      reconnectConfig: {
+        lockReconnect: false,
+        setTimeout: null, // 计时器对象
+        time: 5000, // 重连间隔时间
+        number: 1000 // 重连次数
+      },
+      // 最后心跳时间
+      lastTime: 0,
       user: null,
-      dataList: [],
+      contactList: [],
       text: '',
-      receiverId: '10001',
+      receiverId: null,
       websocket: null,
       token: null,
       online: false
     }
   },
+  computed: {
+    ...mapState({
+      socketStatus: state => state.socketStatus
+    }),
+    ...mapGetters(['talkItems'])
+  },
+  mounted() {
+    const token = getAccessToken()
+    if (token === null) {
+      this.$message.error('当前未登入')
+      return
+    } else {
+      this.token = token
+    }
+
+    const wsUrl = 'ws:' + process.env.VUE_APP_SERVER_URL + '/ws/chat?token=' + this.token
+    this.wsClient = new SocketInstance(wsUrl)
+    this.wsClient.connect()
+  },
   created() {
-    document.title = 'MessageStream'
+    document.title = 'Chat'
     const userInfo = getAuthedUser()
-    if (userInfo !== null) {
-      this.user = userInfo
+    if (userInfo === null) {
+      this.$message.error('当前未登入')
+      return
     }
+    this.user = userInfo
+    getUserContact().then(resp => {
+      if (resp.code === 0) {
+        for (const item of resp.data) {
+          var option = {}
+          option.label = item.nickname
+          option.value = item.id
+          this.contactList.push(option)
+        }
+      }
+    })
 
     const token = getAccessToken()
     if (token === null) {
@@ -120,7 +176,6 @@ export default {
     } else {
       this.token = token
     }
-    this.initWebSocket()
   },
   methods: {
     keyDown(e) {
@@ -137,92 +192,38 @@ export default {
     },
     clear() {
       this.text = ''
-      // this.dataList = []
+    },
+    onClear() {
+      store.commit('REMOVE_TALK_ITEM')
     },
     onSubmit() {
+      if (this.receiverId === null) {
+        this.$message.error('没有选择联系人')
+        return
+      }
+
       var jsonData = {}
       jsonData.event = 'event_talk'
       jsonData.data = {}
       jsonData.data.receiverId = this.receiverId
       jsonData.data.senderId = this.user.userId
       jsonData.data.content = this.text
-      this.websocketSend(jsonData)
+      this.wsClient.send(jsonData)
       this.text = ''
-    },
-    initWebSocket: function() {
-      var url = 'ws:' + process.env.VUE_APP_SERVER_URL + '/ws/chat?token=' + this.token
-      this.websock = new WebSocket(url)
-      this.websock.onopen = this.websocketOnopen
-      this.websock.onerror = this.websocketOnerror
-      this.websock.onmessage = this.websocketOnmessage
-      this.websock.onclose = this.websocketOnclose
-    },
-    websocketOnopen: function() {
-      console.log('WebSocket连接成功')
-      this.online = true
-      // 心跳检测重置
-      // this.heartCheck.reset().start();
-    },
-    websocketOnerror: function(e) {
-      console.log('WebSocket连接发生错误')
-      this.online = false
-      this.reconnect()
-    },
-    websocketOnclose: function(e) {
-      console.log('connection closed (' + e.code + ')')
-      this.online = false
-      this.reconnect()
-    },
-    websocketOnmessage: function(e) {
-      const evtData = JSON.parse(e.data)
-      const receiverId = evtData.receiverId
-      const senderId = evtData.senderId
-      const payload = evtData.payload
 
       var justify = 'end'
-      if (receiverId === this.user.userId) {
+      /* if (jsonData.data.senderId === this.user.userId) {
         justify = 'start'
-      }
-
+      }*/
       var msg = {}
-      msg.senderId = evtData.senderId
-      msg.content = evtData.payload
-      this.dataList.push({
+      msg.receiverId = jsonData.data.receiverId
+      msg.content = jsonData.data.content
+      var item = {
         content: msg,
         justify: justify
-      })
-    },
-    websocketSend(jsonData) { // 数据发送
-      try {
-        this.websock.send(JSON.stringify(jsonData))
-        this.$message.success('消息已发送')
-
-        var justify = 'start'
-        if (jsonData.data.senderId === this.user.userId) {
-          justify = 'end'
-        }
-
-        var msg = {}
-        msg.receiverId = jsonData.data.receiverId
-        msg.content = jsonData.data.content
-        this.dataList.push({
-          content: msg,
-          justify: justify
-        })
-      } catch (err) {
-        this.$message.error('send failed (' + err.code + ')')
       }
-    },
-    reconnect() {
-      const that = this
-      if (that.lockReconnect) return
-      that.lockReconnect = true
-      // 没连接上会一直重连,设置延迟避免请求过多
-      setTimeout(function() {
-        console.info('尝试重连...')
-        that.initWebSocket()
-        that.lockReconnect = false
-      }, 5000)
+
+      store.commit('PUSH_TALK_ITEM', item)
     }
   }
 }

+ 5 - 3
src/views/home/PlaylistView.vue

@@ -404,7 +404,9 @@ export default {
         if (res.code === 0) {
           const token = getAccessToken()
           if (token != null) {
-            SocketInstance.connect()
+            const wsUrl = 'ws:' + process.env.VUE_APP_SERVER_URL + '/ws/progress?token=' + token
+            this.wsClient = new SocketInstance(wsUrl)
+            this.wsClient.connect()
           }
 
           const urlType = res.data.type
@@ -471,14 +473,14 @@ export default {
         const jsonData = {}
         jsonData.videoId = videoId
         jsonData.currentTime = player.video.currentTime
-        SocketInstance.send(jsonData)
+        this.wsClient.send(jsonData)
       })
 
       player.on('ended', () => {
         const jsonData = {}
         jsonData.videoId = videoId
         jsonData.currentTime = player.video.currentTime
-        SocketInstance.send(jsonData)
+        this.wsClient.send(jsonData)
         // this.getNextPath(videoId)
         this.$message.info('当前视频播放完成')
       })