最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

在vue3中使用webSocket構建聊天對話方式

 更新時間:2025年10月27日 09:41:57   作者:吃代碼長大的前端  
本文介紹了如何使用Electron、Vue3、ElementPlus、SCSS和TypeScript構建一個功能完善的聊天通信應用,包括項目初始化、WebSocket服務的封裝、心跳檢查、斷線重連機制,以及在Vue組件中使用該服務,此外,還展示了如何創(chuàng)建一個簡單的WebSocket后端服務器進行測試

我們來構建一個功能完善的聊天通信應用。

這個應用使用現(xiàn)代技術棧:Electron + Vue 3 + Element Plus + SCSS + TypeScript,并實現(xiàn)一個健壯的、封裝良好的 WebSocket 服務,包含心跳檢查斷線重連機制。

下面是詳細的步驟和代碼實現(xiàn)。

第一步:項目初始化

我們將使用 electron-vite 這個現(xiàn)代化的構建工具來快速搭建項目。

創(chuàng)建項目:

npm create @quick-start/electron my-chat-app -- --template vue-ts

進入項目并安裝依賴:

cd my-chat-app
npm install

安裝 Element Plus 和 SCSS/Sass:

npm install element-plus
npm install -D sass

在 src/main.ts 中引入 Element Plus:

// src/renderer/src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css' // 引入樣式

const app = createApp(App)

app.use(ElementPlus)
app.mount('#app')
```

### 第二步:封裝 WebSocket 服務 (核心)

這是整個應用的核心。我們將創(chuàng)建一個 `WebSocketService` 類來管理所有 WebSocket 相關的邏輯,包括連接、斷開、發(fā)送消息、心跳和重連。

在 `src/renderer/src/services` 目錄下創(chuàng)建一個新文件 `webSocketService.ts`。

```typescript
// src/renderer/src/services/webSocketService.ts

import { reactive } from 'vue'

// 定義消息格式
interface WebSocketMessage {
type: 'heartbeat' | 'message' | 'auth'
payload: any
}

// 定義服務狀態(tài)
type WebSocketStatus = 'connecting' | 'open' | 'closing' | 'closed'

// 使用 reactive 創(chuàng)建響應式狀態(tài),方便 Vue 組件直接使用
export const wsState = reactive({
status: 'closed' as WebSocketStatus,
messages: [] as { id: number; text: string; sender: 'me' | 'other' }[],
})

class WebSocketService {
private url: string
private ws: WebSocket | null = null
private token:string

// 心跳相關配置
private heartbeatInterval: number = 30000 // 30秒發(fā)送一次心跳
private heartbeatTimer: NodeJS.Timeout | null = null
private serverTimeoutTimer: NodeJS.Timeout | null = null

// 重連相關配置
private reconnectTimeout: number = 5000 // 5秒后重連
private reconnectTimer: NodeJS.Timeout | null = null
private reconnectAttempts: number = 0
private maxReconnectAttempts: number = 5

constructor(url: string,token:string) {
this.url = url,this.token=token
}

// --- Public API ---

public connect(): void {
if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
  console.log('WebSocket is already connected or connecting.')
  return
}

wsState.status = 'connecting'
console.log('WebSocket connecting...')

this.ws = new WebSocket(this.url,this.token)

this.ws.onopen = () => this.onOpen()
this.ws.onmessage = (event) => this.onMessage(event)
this.ws.onclose = () => this.onClose()
this.ws.onerror = (error) => this.onError(error)
}

public disconnect(): void {
if (this.ws) {
  console.log('WebSocket disconnecting...')
  wsState.status = 'closing'
  // 清理所有定時器
  this.clearTimers()
  this.ws.close()
}
}

public sendMessage(text: string): void {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
  const message: WebSocketMessage = { type: 'message', payload: text }
  this.ws.send(JSON.stringify(message))
  // 將自己發(fā)的消息也添加到消息列表
  wsState.messages.push({ id: Date.now(), text, sender: 'me' })
} else {
  console.error('WebSocket is not open. Cannot send message.')
}
}

// --- Private Event Handlers ---

private onOpen(): void {
wsState.status = 'open'
console.log('WebSocket connection established.')
// 連接成功后,重置重連嘗試次數(shù)
this.reconnectAttempts = 0
// 清除可能存在的重連定時器
if (this.reconnectTimer) {
  clearTimeout(this.reconnectTimer)
  this.reconnectTimer = null
}
// 開啟心跳
this.startHeartbeat()
}

private onMessage(event: MessageEvent): void {
// 收到任何消息都代表連接正常,重置心跳
this.resetHeartbeat()

const message = JSON.parse(event.data)

if (message.type === 'heartbeat' && message.payload === 'pong') {
  // 收到心跳響應,不做處理,因為 resetHeartbeat 已經重置了定時器
  console.log('Received pong from server.')
  return
}

// 處理普通消息
if (message.type === 'message') {
    wsState.messages.push({ id: Date.now(), text: message.payload, sender: 'other' })
}
}

private onClose(): void {
wsState.status = 'closed'
console.log('WebSocket connection closed.')
this.clearTimers()
// 觸發(fā)重連機制
this.handleReconnect()
}

private onError(error: Event): void {
console.error('WebSocket error:', error)
// 錯誤發(fā)生時,ws.onclose 也通常會被調用,所以重連邏輯放在 onclose 中處理
}

// --- Heartbeat Mechanism ---

private startHeartbeat(): void {
console.log('Starting heartbeat...')
this.heartbeatTimer = setInterval(() => {
  if (this.ws && this.ws.readyState === WebSocket.OPEN) {
    const heartbeatMessage: WebSocketMessage = { type: 'heartbeat', payload: 'ping' }
    this.ws.send(JSON.stringify(heartbeatMessage))
    
    // 設置一個服務器超時定時器,如果規(guī)定時間內沒收到 pong,則認為連接已斷開
    this.serverTimeoutTimer = setTimeout(() => {
      console.warn("Server timeout. Closing connection.")
      this.ws?.close() // 這會觸發(fā) onClose,然后由 onClose 觸發(fā)重連
    }, 5000) // 5秒內必須收到 pong
  }
}, this.heartbeatInterval)
}

private resetHeartbeat(): void {
// 清除上一個心跳的服務器超時定時器
if (this.serverTimeoutTimer) {
    clearTimeout(this.serverTimeoutTimer)
}
}

// --- Reconnection Mechanism ---

private handleReconnect(): void {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
  this.reconnectAttempts++
  console.log(`Attempting to reconnect... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`)
  this.reconnectTimer = setTimeout(() => {
    this.connect()
  }, this.reconnectTimeout)
} else {
  console.error('Max reconnection attempts reached. Please check the server or network.')
}
}

// --- Utility ---

private clearTimers(): void {
if (this.heartbeatTimer) {
  clearInterval(this.heartbeatTimer)
  this.heartbeatTimer = null
}
if (this.serverTimeoutTimer) {
    clearTimeout(this.serverTimeoutTimer)
    this.serverTimeoutTimer = null
}
if (this.reconnectTimer) {
  clearTimeout(this.reconnectTimer)
  this.reconnectTimer = null
}
}
}

// 創(chuàng)建并導出一個單例
token從store獲取
// 這里的 URL 應該指向你的 WebSocket 服務器
const wsService = new WebSocketService('ws://localhost:8080',token)

export default wsService

代碼解析:

狀態(tài)管理 (wsState): 使用 Vue 3 的 reactive API 創(chuàng)建了一個響應式對象。任何對 wsState.status 或 wsState.messages 的修改都會自動更新到 Vue 組件的 UI 上。

核心方法 (connectdisconnectsendMessage):提供清晰的公共 API 給外部調用。

心跳機制 (startHeartbeatresetHeartbeat):

  • startHeartbeat: 連接成功后,每隔 30 秒向服務器發(fā)送一個 ping 包。
  • 在發(fā)送 ping 的同時,啟動一個 5 秒的超時定時器 (serverTimeoutTimer)。
  • resetHeartbeat: 當收到服務器的任何消息(包括 pong),就清除這個超時定時器。
  • 如果 5 秒內沒有收到任何消息,超時定時器會觸發(fā),主動關閉連接,從而觸發(fā) onClose 中的重連邏輯。這能有效檢測到“假死”連接。

重連機制 (handleReconnect):

  • 在 onClose 事件中被調用。
  • 設置了最大重連次數(shù),避免無限重連。
  • 使用 setTimeout 延遲重連,給服務器和網絡緩沖時間。

第二步:在 Vue 組件中使用 WebSocket 服務

現(xiàn)在,我們在 Vue 組件中使用這個封裝好的服務。

修改 src/renderer/src/App.vue,或者創(chuàng)建一個新的聊天組件 Chat.vue。這里我們直接修改 App.vue。

<!-- src/renderer/src/App.vue -->
<template>
  <div class="chat-container">
    <el-card class="box-card">
      <template #header>
        <div class="card-header">
          <span>WebSocket Chat</span>
          <el-badge :value="statusText" :type="statusType" class="status-badge" />
        </div>
      </template>

      <el-scrollbar ref="scrollbarRef" class="message-area">
        <div v-for="msg in messages" :key="msg.id" class="message-item" :class="`is-${msg.sender}`">
          <div class="message-bubble">{{ msg.text }}</div>
        </div>
      </el-scrollbar>

      <div class="input-area">
        <el-input
          v-model="newMessage"
          placeholder="Type a message..."
          @keyup.enter="handleSendMessage"
          :disabled="wsState.status !== 'open'"
        />
        <el-button
          type="primary"
          @click="handleSendMessage"
          :disabled="wsState.status !== 'open'"
        >
          Send
        </el-button>
      </div>

      <div class="controls">
        <el-button @click="wsService.connect()" :disabled="wsState.status === 'open' || wsState.status === 'connecting'">Connect</el-button>
        <el-button @click="wsService.disconnect()" :disabled="wsState.status !== 'open'">Disconnect</el-button>
      </div>
    </el-card>
  </div>
</template>

<script setup lang="ts">
import { ref, watch, nextTick, computed, onMounted } from 'vue'
import wsService, { wsState } from './services/webSocketService'
import type { ElScrollbar } from 'element-plus'

const newMessage = ref('')
const scrollbarRef = ref<InstanceType<typeof ElScrollbar>>()

// 直接從 service 中解構出響應式數(shù)據(jù)
const { messages } = wsState

// 自動滾動到底部
watch(messages, () => {
  nextTick(() => {
    scrollbarRef.value?.setScrollTop(scrollbarRef.value.wrapRef!.scrollHeight)
  })
}, { deep: true })

const handleSendMessage = () => {
  if (newMessage.value.trim()) {
    wsService.sendMessage(newMessage.value)
    newMessage.value = ''
  }
}

// 根據(jù)連接狀態(tài)顯示不同的文本和顏色
const statusText = computed(() => {
  switch (wsState.status) {
    case 'open':
      return 'Connected'
    case 'connecting':
      return 'Connecting'
    case 'closing':
      return 'Closing'
    case 'closed':
      return 'Disconnected'
    default:
      return 'Unknown'
  }
})

const statusType = computed(() => {
  switch (wsState.status) {
    case 'open':
      return 'success'
    case 'connecting':
      return 'warning'
    default:
      return 'info'
  }
})

// 組件掛載后自動連接
onMounted(() => {
  wsService.connect()
})
</script>

<style lang="scss">
body, html, #app {
  height: 100%;
  margin: 0;
  background-color: #f0f2f5;
}

.chat-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  padding: 20px;
}

.box-card {
  width: 600px;
  height: 80vh;
  display: flex;
  flex-direction: column;

  .el-card__header {
    flex-shrink: 0;
  }

  .el-card__body {
    flex-grow: 1;
    display: flex;
    flex-direction: column;
    padding: 10px;
    overflow: hidden;
  }
}

.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.message-area {
  flex-grow: 1;
  padding: 10px;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  margin-bottom: 10px;
}

.message-item {
  display: flex;
  margin-bottom: 10px;

  &.is-me {
    justify-content: flex-end;
    .message-bubble {
      background-color: #409eff;
      color: white;
    }
  }

  &.is-other {
    justify-content: flex-start;
    .message-bubble {
      background-color: #e9e9eb;
      color: #333;
    }
  }

  .message-bubble {
    padding: 8px 12px;
    border-radius: 10px;
    max-width: 70%;
  }
}

.input-area {
  display: flex;
  gap: 10px;
  flex-shrink: 0;
}

.controls {
  margin-top: 10px;
  display: flex;
  justify-content: center;
  gap: 10px;
  flex-shrink: 0;
}
</style>

組件解析:

  • 導入服務import wsService, { wsState } from './services/webSocketService',我們導入了服務實例和它的響應式狀態(tài)。
  • 響應式綁定v-for="msg in wsState.messages" 和 wsState.status 直接在模板中使用,當 WebSocketService 內部更新這些狀態(tài)時,UI 會自動更新。
  • 自動滾動: 使用 watch 和 nextTick 確保每次有新消息時,聊天窗口都會自動滾動到底部。
  • 狀態(tài)展示: 使用 computed 屬性根據(jù) wsState.status 動態(tài)地改變狀態(tài)徽章的文本和顏色。
  • 交互: "Send", "Connect", "Disconnect" 按鈕直接調用 wsService 上的方法。

第三步:創(chuàng)建一個簡單的 WebSocket 后端服務器

為了測試,我們需要一個 WebSocket 服務器。你可以使用 Node.js 和 ws 包快速創(chuàng)建一個。

在項目根目錄安裝 ws:

npm install ws
npm install -D @types/ws

在項目根目錄創(chuàng)建 server.js:

// server.js
const { WebSocketServer } = require('ws')

const wss = new WebSocketServer({ port: 8080 })

console.log('WebSocket server is running on ws://localhost:8080')

wss.on('connection', function connection(ws) {
  console.log('A new client connected!')
  ws.send(JSON.stringify({ type: 'message', payload: 'Welcome to the chat!' }))

  ws.on('message', function message(data) {
    console.log('received: %s', data)
    const parsedData = JSON.parse(data)

    // 心跳處理
    if (parsedData.type === 'heartbeat' && parsedData.payload === 'ping') {
      ws.send(JSON.stringify({ type: 'heartbeat', payload: 'pong' }))
      return
    }

    // 廣播消息給所有客戶端
    wss.clients.forEach(function each(client) {
      // 只發(fā)送給其他客戶端
      if (client !== ws && client.readyState === 1) { // 1 表示 WebSocket.OPEN
        client.send(JSON.stringify({ type: 'message', payload: parsedData.payload }))
      }
    })
  })

  ws.on('close', () => {
    console.log('Client disconnected.')
  })

  ws.on('error', (error) => {
    console.error('WebSocket error:', error)
  })
})
```

### 第五步:運行和測試

1.  **啟動 WebSocket 服務器:**
```bash
node server.js

你會看到 WebSocket server is running on ws://localhost:8080。

啟動 Electron 應用:

npm run dev

測試場景:

1.正常通信:打開兩個 Electron 應用實例,它們應該能互相發(fā)送和接收消息。

2.心跳檢查:在服務器控制臺,你會看到每隔 30 秒收到 "ping" 消息。

3.斷線重連

  • 手動點擊 "Disconnect" 按鈕,狀態(tài)會變?yōu)?"Disconnected"。再點擊 "Connect" 重新連接。
  • 關鍵測試:運行應用后,關閉 node server.js 進程。你會看到客戶端 UI 上的狀態(tài)變?yōu)?"Connecting",并嘗試 5 次重連。在控制臺可以看到重連日志。如果在它放棄之前重新啟動服務器,客戶端應該會自動連接成功。
  • 心跳超時測試:如果你注釋掉服務器代碼中 ws.send(JSON.stringify({ type: 'heartbeat', payload: 'pong' })) 這一行,客戶端會在發(fā)送 ping 后 5 秒內因為收不到 pong 而主動斷開并嘗試重連。

總結與展望

這個方案提供了一個非常堅實的基礎:

  • 高內聚,低耦合: WebSocket 的所有復雜邏輯(狀態(tài)、心跳、重連)都封裝在 WebSocketService 中,Vue 組件只負責展示 UI 和調用簡單的 API,非常清晰。
  • 響應式: 利用 Vue 3 的 reactive,數(shù)據(jù)流是單向且自動的,無需手動操作 DOM 或通過事件總線傳遞狀態(tài)。
  • 健壯性: 心跳機制能檢測到網絡假死,而自動重連則提升了用戶體驗,使應用能從臨時的網絡問題中恢復。

可擴展方向:

  • 狀態(tài)管理 (Pinia): 對于更復雜的應用,可以將 wsState 移入 Pinia store,以便在多個組件和模塊中更方便地共享和管理狀態(tài)。
  • 消息格式: 定義更豐富的消息類型,如用戶列表更新、圖片消息、文件消息等。
  • 認證: 在 onOpen 時,客戶端可以發(fā)送一個認證令牌,服務器驗證通過后才允許后續(xù)通信。
  • 錯誤處理: 在 UI 上向用戶展示更友好的錯誤信息(如“無法連接到服務器”)。
  • WSS: 在生產環(huán)境中,應使用 wss:// (WebSocket Secure) 協(xié)議來加密通信。

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • vue中動態(tài)控制btn的disabled屬性方式

    vue中動態(tài)控制btn的disabled屬性方式

    這篇文章主要介紹了vue中動態(tài)控制btn的disabled屬性方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • vue二級菜單導航點擊選中事件的方法

    vue二級菜單導航點擊選中事件的方法

    今天小編就為大家分享一篇vue二級菜單導航點擊選中事件的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-09-09
  • vue實現(xiàn)列表倒計時

    vue實現(xiàn)列表倒計時

    這篇文章主要為大家詳細介紹了vue實現(xiàn)列表倒計時,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-09-09
  • Vue.js實現(xiàn)一個自定義分頁組件vue-paginaiton

    Vue.js實現(xiàn)一個自定義分頁組件vue-paginaiton

    這篇文章主要為大家詳細介紹了Vue.js實現(xiàn)一個自定義分頁組件vue-paginaiton的具體代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-09-09
  • 解決VUE項目在IIS部署出現(xiàn):Uncaught SyntaxError: Unexpected token < 報錯

    解決VUE項目在IIS部署出現(xiàn):Uncaught SyntaxError: Unexpected&n

    這篇文章介紹了解決VUE項目在IIS部署出現(xiàn):Uncaught SyntaxError: Unexpected token < 報錯的方法,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-04-04
  • 從零開始在vue-cli4配置自適應vw布局的實現(xiàn)

    從零開始在vue-cli4配置自適應vw布局的實現(xiàn)

    這篇文章主要介紹了從零開始在vue-cli4配置自適應vw布局,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-06-06
  • Vue-cli3項目配置Vue.config.js實戰(zhàn)記錄

    Vue-cli3項目配置Vue.config.js實戰(zhàn)記錄

    這篇文章主要給大家介紹了關于Vue-cli3項目配置Vue.config.js的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2018-07-07
  • vue-router4版本第一次打開界面不匹配路由問題解決

    vue-router4版本第一次打開界面不匹配路由問題解決

    本文主要介紹了vue-router4版本第一次打開界面不匹配路由問題解決,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-04-04
  • Vue3中的toRef和toRefs的區(qū)別和用法示例小結

    Vue3中的toRef和toRefs的區(qū)別和用法示例小結

    toRef和toRefs可以用來復制reactive里面的屬性然后轉成 ref,它保留了響應式,也保留了引用,也就是你從 reactive 復制過來的屬性進行修改后,除了視圖會更新,原有 ractive 里面對應的值也會跟著更新,本文介紹Vue3中toRef和toRefs的區(qū)別和用法,需要的朋友可以參考下
    2024-08-08
  • vue多次打包后出現(xiàn)瀏覽器緩存的問題及解決

    vue多次打包后出現(xiàn)瀏覽器緩存的問題及解決

    這篇文章主要介紹了vue多次打包后出現(xiàn)瀏覽器緩存的問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08

最新評論

曲水县| 北川| 专栏| 邵武市| 田东县| 二手房| 顺昌县| 新郑市| 武隆县| 江门市| 宣城市| 肥西县| 竹北市| 平利县| 阳西县| 新竹市| 清丰县| 新余市| 南澳县| 寿阳县| 凤山县| 临城县| 东乌珠穆沁旗| 贡觉县| 北票市| 桂东县| 马龙县| 成安县| 金乡县| 军事| 米林县| 包头市| 射阳县| 安达市| 嘉定区| 洪江市| 渑池县| 蒙阴县| 泰顺县| 桐城市| 郸城县|