全局安装vue脚手架 npm install @vue/cli -g
创建 vue3 + ts 脚手架 vue create vue3-chatroom
后端代码
src 同级目录下建 server:
const express = require('express');
const app = express();
const http = require('http');
const server = http.createServer(app);
const io = require('socket.io')(server, { cors: true })
io.on('connection', (socket) => {
console.log('socket 已连接');
socket.on('sendToServer', data => {
console.log(`收到了前端发来的消息: ${data}`);
io.emit("sendToClient", data);
})
socket.on('disconnect', () => {
console.log('断开连接');
});
});
server.listen(3000, () => {
console.log('listening on *:3000');
});
前端代码
核心代码:
import io from 'socket.io-client'
var socket = io('ws://localhost:3000')
socket.on("sendToClient", data => {
console.log(`收到了后端发来的数据:${data}`);
records.value.push(JSON.parse(data))
})
const sendMessage = () => {
if (!message.value.trim()) return
const record: IRecord = reactive({
message: message.value,
nickname,
userId: new Date().getTime() + '',
color: '',
sendTime: getYMDHMS(new Date().getTime())
})
socket.emit('sendToServer', JSON.stringify(record));
message.value = '';
}
完整代码:
<template>
<div class="chat-room">
<div class="nav"></div>
<div class="main">
<div class="title">
<span>图灵聊天室({{ userCount }})</span>
</div>
<div class="content" ref="recordsContent">
<div v-for="(itm, inx) in records" :key="inx">
<div
class="item"
:class="[itm.nickname === nickname ? 'item' : 'item-other']"
>
<div class="info">[ {{ itm.nickname }}:{{ itm.sendTime }} ]</div>
<span class="message">{{ itm.message }}</span>
</div>
</div>
</div>
<div class="input-box">
<div class="text">
<textarea :rows="8" v-model="message" @keydown="onKeydown"></textarea>
</div>
<div class="opt">
<button ghost @click="sendMessage">发 送</button>
</div>
</div>
</div>
</div>
</template>
<script setup >
import io from 'socket.io-client'
import { reactive, ref } from 'vue'
interface IRecord {
nickname: string,
userId: string,
color: string,
message: string,
sendTime: string
}
const userCount = ref(2)
const records = ref<IRecord[]>([])
const message = ref('')
const nickname = localStorage.getItem('username') || '匿名用户'
var socket = io('ws://localhost:3000')
socket.on("sendToClient", data => {
console.log(`收到了后端发来的数据:${data}`);
records.value.push(JSON.parse(data))
})
const sendMessage = () => {
if (!message.value.trim()) return
const record: IRecord = reactive({
message: message.value,
nickname,
userId: new Date().getTime() + '',
color: '',
sendTime: getYMDHMS(new Date().getTime())
})
socket.emit('sendToServer', JSON.stringify(record));
message.value = '';
}
const onKeydown = (event: any) => {
if (event.keyCode === 13) {
sendMessage()
}
}
function getYMDHMS(timestamp:number) {
let time = new Date(timestamp)
let year = time.getFullYear()
let month:any = time.getMonth() + 1
let date:any = time.getDate()
let hours:any = time.getHours()
let minute:any = time.getMinutes()
let second:any = time.getSeconds()
if (month < 10) { month = '0' + month }
if (date < 10) { date = '0' + date }
if (hours < 10) { hours = '0' + hours }
if (minute < 10) { minute = '0' + minute }
if (second < 10) { second = '0' + second }
return year + '-' + month + '-' + date + ' ' + hours + ':' + minute + ':' + second
}
</script>
<style scoped >
.chat-room {
margin: 0px auto;
width: 100%;
height: 100vh;
display: flex;
flex-direction: row;
border: 1px solid #ccc;
overflow: hidden;
.nav {
width: 66px;
background: #363636;
flex-shrink: 0;
}
.main {
display: flex;
background: #efefef;
flex: 1;
width: 0;
display: flex;
flex-direction: column;
.title {
height: 60px;
display: flex;
align-items: center;
font-size: 16px;
font-weight: 700;
padding-left: 20px;
border-bottom: 1px solid #c3c3c3;
flex-shrink: 0;
}
.content {
flex: 1;
height: 0px;
position: relative;
overflow-y: auto;
padding: 10px;
.item {
text-align: right;
.info {
font-size: 14px;
color: #666;
}
.message {
font-size: 18px;
background-color: rgba(110, 89, 228, 0.579);
margin: 10px;
padding: 8px 12px;
border-radius: 8px;
display: inline-block;
color: #333;
}
}
.item-other {
text-align: left;
.message {
background-color: rgb(218, 197, 112);
}
}
}
.input-box {
height: 230px;
border-top: 1px solid #c3c3c3;
flex-shrink: 0;
display: flex;
flex-direction: column;
.text {
flex: 1;
textarea {
width: 94%;
height: 160px;
font-size: 16px;
resize: none;
border: none;
padding: 8px 24px;
background: #efefef;
&:focus {
outline: none;
}
&:focus-visible {
outline: none;
}
}
}
.opt {
height: 60px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 20px;
}
}
}
}
</style>