在config/index.js文件中配置一下websocket
// websocket的域名和端口号的配置
const BASE_URL = 'localhost';
const WS_PORT = '8080';
const WS_ADDRESS = `ws://${BASE_URL}:${WS_PORT}`;
export const useWebsockt = (handleMessage) => {
const ws = new WebSocket(WS_ADDRESS );
//ws.binaryType = 'arraybuffer'; //可将 WebSocket 对象的 binaryType 属性设为“blob”或“arraybuffer”。默认格式为“blob”(您不必在发送时校正 binaryType 参数)。
const init = () => {
ws.addEventListener('open', handleOpen, false);
ws.addEventListener('close', handleClose, false);
ws.addEventListener('error', handleError, false);
ws.addEventListener('message', handleMessage, false);
};
function handleOpen(e) {
console.log('WebSocket open', e);
}
function handleClose(e) {
console.log('WebSocket close', e);
}
function handleError(e) {
console.log('WebSocket error', e);
}
init();
return ws;
};
在页面中使用websocket 页面代码如下:
<template>
<div class="body-content">
<div class="web-socket">
<header class="header">聊天室</header>
<main class="main-content">
<block v-for="(item, index) in msgListSelf" :key="index">
<!-- business -->
<div class="business" :key="index" v-if="!item.right">
<div class="business-left">
<img
class="business-img header-img"
src="https://qiyukf.com/sdk/res/skin/default/ico-kefu@2x.png?imageView&type=png%7CimageView&thumbnail=76y76&axis=5"
alt=""
/>
<ul>
<p class="icon business-icon"></p>
<p class="chat-content">{{ item.msg }}</p>
</ul>
<p class="curent-time">{{ getTime(item.curentTime) }}</p>
</div>
</div>
<!-- user -->
<div class="userSelf" :key="index + 10000" v-else>
<div class="user-right">
<ul>
<p class="chat-content">{{ item.msg }}</p>
<p class="icon userSelf-icon"></p>
</ul>
<img
class="userSelf-img header-img"
src="https://qiyukf.com/sdk/res/skin/default/ico-kefu@2x.png?imageView&type=png%7CimageView&thumbnail=76y76&axis=5"
alt=""
/>
<p class="curent-time">{{ getTime(item.curentTime) }}</p>
</div>
</div>
</block>
</main>
<footer class="footer">
<!-- contenteditable="true"标签可以编辑 -->
<textarea
contenteditable="true"
@keydown="keydownCode"
v-model="msg"
placeholder="请您输入内容"
></textarea>
<button @click="msgBtnClick">发送</button>
</footer>
</div>
</div>
</template>
<script >
import { reactive, ref, toRefs } from "vue";
import { useWebsockt } from "../../hooks";
import { useRoute } from "vue-router";
export default {
setup(params) {
// 接收路由跳转携带参数
const Route = useRoute();
const username = Route.query.username;
console.log("参数", username);
const state = reactive({
msg: "",
msgListSelf: [],
});
// 接收到socket的实时数据
const ws = useWebsockt(handleMessage);
// 解析Blob进制流
const reader = new FileReader();
// 接收实时的数据
function handleMessage(evt) {
if (evt.data) {
isBlob(evt.data);
}
}
// 获取时间
function getTime(time) {
const oDate = new Date(time); //实例一个时间对象;
const year = oDate.getFullYear(); //获取系统的年;
const month = oDate.getMonth() + 1; //获取系统月份,由于月份是从0开始计算,所以要加1
const date = oDate.getDate(); // 获取系统日,
const h = oDate.getHours(); //获取系统时,
const m = oDate.getMinutes(); //分
const s = oDate.getSeconds(); //秒
return `${year}年${month}月${date}日 ${h}:${m}`;
}
// 是否返回是二进制流 默认是Blob类型的
const isBlob = (data) => {
let result = null;
if (data instanceof Blob) {
reader.readAsText(data, "UTF-8");
reader.onload = (e) => {
result = JSON.parse(reader.result);
// 判断当前是本人 本人的话统一将数据排到右侧
if (username === result.username) {
result.right = true;
}
state.msgListSelf.push(result);
console.log(state.msgListSelf);
};
}
return result;
};
// 回车发送消息
const keydownCode = (e) => {
if (e.keyCode === 13) {
msgBtnClick();
}
};
// 点击发送消息
const msgBtnClick = () => {
const _msg = state.msg;
if (!_msg.trim().length) return;
//发送信息
ws.send(
JSON.stringify({
id: new Date().getTime(),
msg: _msg,
curentTime: new Date().getTime(),
username,
})
);
state.msg = "";
};
function binarystate(ev) {
return JSON.parse(
new TextDecoder("utf-8").decode(new Uint8Array(ev.state))
);
}
return {
...toRefs(state),
msgBtnClick,
getTime,
keydownCode,
};
},
};
</script>
<style scoped>
* {
padding: 0;
margin: 0;
}
.body-content {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.web-socket {
width: 500px;
height: 500px;
background: rgb(238, 235, 235);
display: flex;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
border: 1px solid #ccc;
}
.header {
height: 40px;
display: flex;
justify-content: center;
align-items: center;
background-color: #f96868;
font-size: 16px;
color: #fff;
}
.main-content {
flex: 1;
padding: 15px 10px;
overflow-y: auto;
}
.footer {
height: 100px;
border-top: 1px solid #ccc;
background-color: #fff;
display: flex;
flex-direction: column;
padding: 3px 6px;
/* box-sizing: border-box; */
}
.footer textarea {
min-height: 50px;
border: none;
resize: none;
cursor: pointer;
padding: 3px;
margin-bottom: 10px;
/* 去掉聚焦默认边框 */
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-user-modify: read-write-plaintext-only;
outline: none;
box-shadow: none;
}
.footer button {
width: 70px;
height: 26px;
background: crimson;
opacity: 0.7;
color: #fff;
font-size: 14px;
align-self: flex-end;
border: none;
border-radius: 5px;
}
.business {
display: flex;
justify-content: flex-start;
margin-bottom: 25px;
position: relative;
}
.userSelf {
display: flex;
justify-content: flex-end;
margin-bottom: 25px;
position: relative;
}
.user-right {
width: auto;
display: flex;
}
.business-left {
width: auto;
display: flex;
}
.header-img {
width: 38px;
height: 38px;
border-radius: 19px;
background: aquamarine;
}
.userSelf-img {
background: #f96868;
}
.business-left ul {
position: relative;
left: 10px;
/* display: flex; */
align-self: center;
max-width: 400px;
border-radius: 10px;
}
.user-right ul {
position: relative;
right: 10px;
max-width: 400px;
align-self: center;
background: #f96868;
border-radius: 10px;
}
.business-left .icon {
left: -6px;
border-right-width: 6px;
border-right-color: #fff;
}
.user-right .icon {
right: -6px;
border-left-width: 6px;
border-left-color: #f96868;
}
.icon {
position: absolute;
top: 10px;
width: 0;
height: 0;
border: 0 solid #fff;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
}
.chat-content {
background: #fff;
border-radius: 10px;
padding: 10px;
max-width: 100%;
}
.user-right .chat-content {
background: #f96868;
color: #fff;
}
.business-left .curent-time {
position: absolute;
bottom: -22px;
left: 48px;
color: #000;
opacity: 0.6;
font-size: 12px;
}
.business-left .curent-username {
left: 0;
color: #000;
}
.curent-username {
position: absolute;
top: 43px;
font-size: 12px;
opacity: 0.6;
}
.user-right .curent-username {
right: 0px;
color: #f96868;
}
.user-right .curent-time {
position: absolute;
bottom: -22px;
right: 48px;
color: #f96868;
opacity: 0.8;
font-size: 12px;
}
</style>
以上逻辑涉及到是否是当前用户判断,好展示信息,从而加载不同样式布局,图暂时添加一个简单的用户名称窗口,这窗口填完之后跳转聊天窗。实际开发是根据登录用户信息来处理。
<template>
<div class="chat-clients">
<p class="chat-box">
<input type="text" v-model="username" placeholder="请您输入聊天室昵称" />
<button @click="entryChatClient">进入聊天室</button>
</p>
</div>
</template>
<script>
import { reactive, toRefs } from "vue";
import { useRouter } from "vue-router";
export default {
setup() {
const state = reactive({
username: "",
});
const router = useRouter();
const entryChatClient = () => {
if (state.username) {
router.push({
path: "/websocket",
query: { username: state.username },
});
}
};
return { entryChatClient, ...toRefs(state) };
},
};
</script>
<style scoped>
.chat-clients {
width: 100%;
height: 100vh;
background: #000;
opacity: 0.8;
display: flex;
justify-content: center;
}
.chat-box {
margin-top: 100px;
width: 300px;
height: 200px;
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
}
.chat-box input {
height: 50px;
width: 200px;
}
.chat-box button {
height: 50px;
flex: 1;
}
</style>
|