前面在寫傳送資料這一塊時,有些環節沒有思考妥當,導致問題叢生,所以這邊先進行部分重新調整。
登入和發送訊息本質都是事件,差別在於,當登入事件觸發時,必須告訴 server,現在登入的人是誰?他要登入哪個頻道?而訊息發送則是這兩個參數之上,要額外增加,現在送出的訊息是什麼?所以梳理如下:
logingChat()
sendMessage()
所以根據這個結構,調整下面的 HTML, client.js, server.js。
重新調整了 HTML 結構,當進入聊天室頁面時,左側放置聊天訊息,右側則顯示進入頻道的人和離開頻道者。同時替文字簡單加一些顏色,方便區分。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<div id="app">
<div v-if="isShow" class="login-wrapper">
<p>
暱稱 :
<input v-model="username" type="text" />
</p>
<p>
頻道 :
<input v-model="channelId" type="text" />
</p>
<button @click="logingChat()">進入聊天室</button>
</div>
<div v-else class="chat-wrapper">
<h2>目前線上人數 : {{ onlineNum }}</h2>
<div class="channel">
<div>
<input v-model="message" class="input-message" type="text" />
<button type="button" class="send-btn" @click="sendMessage()">
送出訊息
</button>
<ul class="message-wrapper">
<li v-for="(item, index) in messageList" :key="index + item">
<span class="user-name">{{ item.username }}</span> : {{
item.message }}
</li>
</ul>
</div>
<div class="user-list">
<div v-for="(item, index) in userList" :key="index + item">
<div v-if="item.event === 'login'">
歡迎 :
<span class="user-name">{{ item.username }}</span> 進入頻道
</div>
<div v-else>
<span class="user-name">{{ item.username }}</span> 已經離開頻道
</div>
</div>
</div>
</div>
</div>
</div>
<script src="./client.js"></script>
</body>
</html>
// styles.css
.channel {
display: flex;
}
.user-name {
color: #c37b89;
}
.user-list {
margin-left: 30px;
color: #bccc9a;
font-size: 12px;
}
data 的預設值中,區分出訊息列表和登入者列表
data: {
messageList: [],
userList: [],
},
methods: {
onMessage(event) {
let obj = JSON.parse(event.data);
this.onlineNum = obj.num;
if (obj.event === 'message') {
this.messageList.push(obj);
} else {
this.userList.push(obj);
}
},
}
另外送給 server 的參數,依照最前面所討論的結構去設定
logingChat() {
if (this.username.trim() === '') {
alert('請輸入姓名');
return;
}
this.isShow = false;
this.ws.send(
JSON.stringify({
event: 'login',
username: this.username,
channelId: this.channelId,
})
);
},
sendMessage() {
this.ws.send(
JSON.stringify({
event: 'message',
username: this.username,
channelId: this.channelId,
message: this.message,
})
);
this.message = '';
},
當 server 拿到參數資料後,除了回傳訊息外,如果監聽到使用者離線了,也要送出離線者的資料
wss.on('connection', function connection(ws) {
ws.on('message', function (data) {
const bufferData = Buffer.from(data).toString();
let formData = JSON.parse(bufferData);
ws.channelId = formData.channelId;
ws.username = formData.username;
wss.clients.forEach((client) => {
if (
client.readyState === WebSocket.OPEN &&
client.channelId === ws.channelId
) {
formData.num = wss.clients.size;
client.send(JSON.stringify(formData));
}
});
});
ws.on('close', (data) => {
let formData = {};
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN && ws.username) {
formData.event = 'out';
formData.num = wss.clients.size;
formData.username = ws.username;
client.send(JSON.stringify(formData));
}
});
});
});