Chrome Pointer

2023年12月12日 星期二

使用WebSocket結合vue.js實作一個程式碼簡單明瞭的線上聊天室

參考文章

💓基礎>>使用WebSocket結合node.js實作一個線上聊天室,只需要3個檔案就完成

💓進階>>使用WebSocket結合node.js實作一個線上聊天室(進階功能:即時時間動態顯示,表情符號emoji等)

💓超進階>>使用WebSocket結合vue.js實作一個程式碼簡單明瞭的線上聊天室


Root Directory
  ├── vue.js
  ├── server.js
  ├── styles.css
  └── index.html


這次進階教學是把原始的VanillaJS(原生Java script),轉換成vue.js的形式。

我們可以發現傳換成vue後,程式碼的function都是寫在methods:中,

而data() { return { }}裡面則是存放初始訊息,

像是.html中有使用大括號{{ }},它將資料動態地綁定到html的元素上,

使得資料變更時能夠自動更新。


因此只要使用vue.js,勢必你的.html也必須一起調整,因為這兩個會有滿多地方會關聯在一起。


vue.js

const app = Vue.createApp({
    data() {
      return {
socket: null,
        messages_qu: [], // 用來存放聊天訊息的陣列
        currentTime: '',
        username: 'empty'
      };
    },
    methods: {
      connectWebSocket() {
        this.socket = new WebSocket('ws://localhost:3000');
       
        this.socket.onopen = () => {
          console.log('已連接至 WebSocket 伺服器');
        };
 
        this.socket.onmessage = (event) => {
            // console.log('接收到伺服器訊息:', event.data);
            // 解析接收到的 JSON 資料
            console.log('only event接收到伺服器訊息:', event);
            console.log('before JSON.parse接收到伺服器訊息:', event.data);
            const data = JSON.parse(event.data);
            console.log('JSON.parse接收到伺服器訊息:', data.name+": "+data.message+";時間: "+data.time);

            this.messages_qu.push(data);
            // 將訊息放進messages_qu: [] 陣列中
        };
 
        this.socket.onclose = () => {
          console.log('WebSocket 連接已關閉');
        };
      },
      sendMessage() {
        let formattedTime = this.getTime();
        let inputName = document.getElementById('inputName').value;
        let inputMessage = document.getElementById('inputMessage').value;

         // 組合名稱和訊息成為一個物件,然後將其轉換成 JSON 格式的字串
        const data = JSON.stringify({ name: inputName, message: inputMessage, time: formattedTime });

        this.socket.send(data);
        document.getElementById('inputMessage').value = ''; // 清空輸入框

        //產生名字在頁面上
        this.username= inputName
        //document.querySelector('.user-name').textContent = "name: "+inputName
      },
      getTime() {
        // 獲取當前時間
        const currentTime = new Date();
        const hours = currentTime.getHours();
        const minutes = currentTime.getMinutes();
        const seconds = currentTime.getSeconds();

        // 將時間補零到兩位數
        // 它的作用是根據條件的真假來返回不同的值。如果條件成立,則返回冒號前面的值,否則返回冒號後面的值。
        const formattedHours = hours < 10 ? `0${hours}` : hours;
        const formattedMinutes = minutes < 10 ? `0${minutes}` : minutes;
        const formattedSeconds = seconds < 10 ? `0${seconds}` : seconds;
        const formattedTime = `${formattedHours}:${formattedMinutes}:${formattedSeconds}`; // 格式化為 HH:MM 格式

        // 將格式化後的時間值作為物件返回
        return formattedTime;
      },
      updateClock() {
        const formattedTime = this.getTime();
        this.currentTime = `Current Time: ${formattedTime}`;
      },
      insertEmoji(emoji) {
        const inputMessage = document.getElementById('inputMessage');
        inputMessage.value += emoji;
      },
    },
    mounted() {
      this.updateClock(); // 在 Vue 實例被掛載後先執行一次更新時間
 
      // 每秒更新一次時間
      setInterval(() => {
        this.updateClock();
      }, 1000);

    },
    created() {  //created 鉤子是在 Vue 實例被建立後立即觸發的
      this.connectWebSocket();
    }
  });
  app.mount('.chat-container');


💫💦

created 鉤子函數:

1. created 鉤子在 Vue 實例被創建後立即被調用,這時 Vue 實例的資料已經初始化,

但 DOM 元素還未生成,也無法存取到 DOM。

2. 在 created 鉤子中,你可以進行一些初始化的工作,例如資料的處理、非同步請求、

監聽事件等,但注意此時尚無法存取到模板中的 DOM 元素。


mounted 鉤子函數:

1. mounted 鉤子在 Vue 實例被掛載到 DOM 後調用,此時 Vue 實例已經和 DOM 元素建立了關聯,

可以存取到模板中的 DOM 元素。

2. 在 mounted 鉤子中,你可以執行需要操作 DOM 元素的任務,

例如修改 DOM、進行網路請求、啟動定時器等。

3. 通常在 mounted 鉤子函數中進行一些需要依賴 DOM 的初始化操作或啟動定時器、

監聽器等與介面互動相關的任務。


💫💦

>> created 鉤子中呼叫了 connectWebSocket 方法,

這會在 Vue 實例被建立後立即執行 WebSocket 的連線操作。

>> mounted 鉤子中呼叫了 updateClock 方法,並且設定了一個計時器每秒鐘更新一次時間。

這些操作在 Vue 實例被掛載到 DOM 後進行。


👉 

created 主要用於在實例被創建後進行一些初始化的資料處理和邏輯操作,

mounted 則用於操作已經渲染到 DOM 的 Vue 實例,

執行諸如 DOM 操作、定時器、網路請求等與介面交互相關的任務


index.html

<!DOCTYPE html>
<html>
<head>
  <title>WebSocket Example</title>
  <link rel="stylesheet" href="styles.css">
  <script src="https://unpkg.com/vue@3.3.0/dist/vue.global.js"></script>
</head>
<body>

  <div class="chat-container">
    <h1>WebSocket Chat Room</h1>
    <span class="user-name">name: {{ username }}</span>
 
    <!-- Message display area -->
    <div id="messageBox">
      <div v-for="message in messages_qu"  class="message_item">
        <span v-bind:class="{'nodeSpan1': message.name === username, 'otherClass': message.name !== username}">
          {{ message.name }}: {{ message.message }}
        </span>
        <span class="timestamp">{{ message.time }}</span>
      </div>
    </div>
    <!-- Input fields and send button -->
    <div>
      <label for="inputName">Name:</label>
      <input type="text" id="inputName" class="chat-input" placeholder="Enter your name..."><br/><br/>
    </div>
    <div>
      <label for="inputMessage">Text:</label>
      <input type="text" id="inputMessage" class="chat-input" placeholder="Enter your message...">
      <button id="smileyButton" class="emoji-button" v-on:click="insertEmoji('😊')">😊</button>
      <button id="heartButton" class="emoji-button" v-on:click="insertEmoji('❤️')">❤️</button>
    </div>
    <button id="text_btn" class="send-button" v-on:click="sendMessage">Send</button>

    <!-- Display area for showing real-time clock -->
    <div id="realTimeClock">{{ currentTime }}</div>
  </div>
  <script src="vue.js"></script>
  <!-- 載入vue.js -->
</body>
</html>


💫💦

messages_qu: [], // 用來存放聊天訊息的陣列

在這裡會把收到的訊息推進陣列中,

this.messages_qu.push(data);
            // 將訊息放進messages_qu: [] 陣列中

會使用v-for迴圈把訊息列印出來在DOM上面{{ message.name }}: {{ message.message }}

<div v-for="message in messages_qu"  class="message_item">


💫💦

>> v-bind:class

在這個範例中,message.name === username 的條件用於動態套用類別class名稱 .nodeSpan1,

這樣可以根據username動態地變更樣式。

當message.name === username時,class為nodeSpan1,

當message.name !== username時,class為otherClass。

然後,您可以在 CSS 中定義 .nodeSpan1 和otherClass的樣式,以便根據需要變更訊息內容的樣式。

<span v-bind:class="{'nodeSpan1': message.name === username, 'otherClass': message.name !== username}">

在css設定文字顏色樣式,當發送訊息的人,和使用者相同時,就變更訊息顏色。

.nodeSpan1{
    color: #c70202;
}


💫💦

當有使用到{{ }}時,就會需要設定初始值,不然html一開始run時可能會叫不到東西。

<span class="user-name">name: {{ username }}</span>
<!-- Message display area -->
<div id="messageBox">
  <div v-for="message in messages_qu"  class="message_item">
    <span v-bind:class="{'nodeSpan1': message.name === username, 'otherClass': message.name !== username}">
          {{ message.name }}: {{ message.message }}
    </span>
    <span class="timestamp">{{ message.time }}</span>
</div>
</div>

初始值設定在vue的data(){return{ }}中。

 data() {
      return {
        messages_qu: [], // 用來存放聊天訊息的陣列
        currentTime: '',
        username: 'empty'
      };
    },


💫💦

按下按鈕的監聽事件在原始的JS中是要使用addEventListener來進行撰寫。

document.getElementById('smileyButton').addEventListener('click', () => {
  insertEmoji('😊'); // 插入笑臉符號
});

document.getElementById('heartButton').addEventListener('click', () => {
  insertEmoji('❤️'); // 插入心形符號
});

但在vue中,直接在.html中加上v-on:click或@click,並輸入你要前往的函式,此例為insertEmoji(''),

就可以讓程式碼變得更簡潔。

<button id="smileyButton" class="emoji-button" v-on:click="insertEmoji('😊')">😊</button>
<button id="heartButton" class="emoji-button" v-on:click="insertEmoji('❤️')">❤️</button>


styles.css

/* Reset some default browser styles */
body, ul {
    margin: 0;
    padding: 0;
}

/* Chat room container */
.chat-container {
    width: 80%;
    margin: 20px auto;
    border: 1px solid #ccc;
    padding: 20px;
    border-radius: 5px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

/* Style for chat messages */
#messageBox {
    background-color: #f4f4f4;
    padding: 10px;
    margin-bottom: 10px;
    border-radius: 5px;
    border: 1px dashed #7a0404;
    justify-content: space-between;
}

/* Style for input fields */
.chat-input {
    width: calc(100% - 20px);
    padding: 8px;
    margin-top: 10px;
    border-radius: 5px;
    border: 1px solid #ccc;
}

/* Style for the send button */
.send-button {
    padding: 8px 20px;
    margin-top: 10px;
    border-radius: 5px;
    background-color: #007bff;
    color: #fff;
    border: none;
    cursor: pointer;
}

p {
    color: #460606;
}

/* Style for user names */
.user-name {
    font-weight: bold;
    color: #333;
    font-size: 18px;
}

/* Style for timestamps */
.timestamp {
    color: #500a0a;
}

#realTimeClock {
    font-size: 20px;
    font-weight: bold;
    margin-top: 20px;
    padding: 10px;
    background-color: #f0c6c6;
    border-radius: 5px;
}

.message_item{
    display: flex;
    justify-content: space-between;
}

.nodeSpan1{
    color: #c70202;
}


💫💦

.message_item中的space-between;可以達到讓裡面元素分開的效果,詳情可參考上一篇。

💓進階>>使用WebSocket結合node.js實作一個線上聊天室(進階功能:即時時間動態顯示,表情符號emoji等)

.message_item{
    display: flex;
    justify-content: space-between;
}


💫💦

.nodeSpan1是當message.name 等於username時,把訊息的顏色進行改變。

.nodeSpan1{
    color: #c70202;
}

在.html中, 若message.name 等於username, CLASS為nodeSpan1。

<span v-bind:class="{'nodeSpan1': message.name === username, 'otherClass': message.name !== username}">
          {{ message.name }}: {{ message.message }}
</span>


server.js

const express = require('express');
const http = require('http');
const WebSocket = require('ws');

const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

// 提供靜態資源(HTML 檔案)
app.use(express.static('.'));

wss.on('connection', function connection(ws) {
  console.log('有新的 WebSocket 連接');

  ws.on('message', function incoming(message) {
    const data = JSON.parse(message);
    //message = message.toString('utf-8')
    console.log('server接收到Name:'+data.name+" ; "+'server接收到訊息:'+data.message+" ; "+
    'server接收到Time:'+data.time);

    // 廣播訊息給所有客戶端
    wss.clients.forEach(function each(client) {
      if (client.readyState === WebSocket.OPEN) {  //加了client !== ws && 代表訊息不傳給自己
        console.log("(沒JSON.parse)-message: ", message );
        console.log("(有JSON.parse)-data: ", data );
        client.send(JSON.stringify(data)); //重點!! 這裡需要再把data轉成JSON檔案一次,不然front.js前端拿不到
      }
    });
  });
});

// 啟動伺服器監聽指定的埠口
const PORT = process.env.PORT || 3000;
server.listen(PORT, function() {
  console.log(`伺服器正在監聽埠口 ${PORT}`);
});


沒有留言:

張貼留言

喜歡我的文章嗎? 喜歡的話可以留言回應我喔! ^^