原文鏈接:https://www.sinye.xyz/front-end/729.html
我糾結(jié)了很長(zhǎng)時(shí)間要不要寫這篇文章禀梳。因?yàn)橹褂酰绻约洪_發(fā)著玩踏兜,需要學(xué)的東西太多了尺碰,如果真正開發(fā)項(xiàng)目缅帘,肯定不是一個(gè)人能完成的婶熬,所以我寫的這點(diǎn)皮毛根本不夠用为狸。
既然是我自己業(yè)余時(shí)間寫這篇文章巍棱,那我就按照我開發(fā)這個(gè)'即時(shí)通訊'項(xiàng)目的流程簡(jiǎn)單描述一下。
1黍少、頁(yè)面先行
作為前端開發(fā)寡夹,沒有頁(yè)面怎么行,功能都是在靜態(tài)頁(yè)面的基礎(chǔ)上添加的厂置。
html:
<div class="wrapper">
<div class="container">
<div :class="'mask '+(user_list_bool?'active':'')" @click="user_list_bool=false"></div>
<div :class="'left '+(user_list_bool?'active':'')">
<div class="top">
<div class="">在線人數(shù):<span id="numbers">{{userLength}}</span> 人
</div>
</div>
<ul class="people">
<li class="person flex_c_b" data-chat="person1" v-for="(item,index) in user_list" :key="index">
<div class="flex_c_s">
<img :src="item.headerimg" alt=""/>
<div>
<span class="name">{{item.username}}</span>
<span class="preview">在線</span>
</div>
</div>
<span class="time">{{item.login_time}}</span>
</li>
</ul>
</div>
<div class="right">
<div class="top flex_c_b">
<span>群名: <span class="name">又甘又刻</span></span>
<div class="chakang" @click="user_list_bool=true"><div class="numbers">{{userLength}}</div>在線</div>
</div>
<!-- <van-pull-refresh v-model="isLoading" @refresh="onRefresh"> -->
<div class="mesbox" ref="mesbox">
<div class="chat active-chat" data-chat="person1">
<van-loading type="spinner" size="18px" v-show="isLoading"/>
<div v-for="(item,index) in mes" :key="index" class="mg_t20">
<div v-if="item.type=='sys'">
<div class="conversation-start">
<span>{{item.msg}}</span>
</div>
</div>
<div v-else>
<div class="message">
<img :class="item.youMe=='me'?'me-header':''" :src="item.head" alt=""/>
<div :class="'bubble '+item.youMe" v-html="item.msg"></div>
</div>
</div>
</div>
</div>
</div>
<div class="write">
<a href="javascript:;" class="write-link attach"></a>
<input type="text" id="input-value" @keyup.enter="confirm()" v-model="inputValue"/>
<a href="javascript:;" class="write-link smiley"></a>
<a href="javascript:;" class="write-link send" @click="send()"></a>
</div>
</div>
</div>
</div>
css:
*, *:before, *:after {box-sizing: border-box;}html{height: 100%;}:root {--white: #fff;--black: #000;--bg: #f8f8f8;--grey: #999;--dark: #1a1a1a;--light: #e6e6e6;--wrapper: 100vw;--blue: #00b0ff;}body {background-color: var(--bg);-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-rendering: optimizeLegibility;font-family: 'Source Sans Pro', sans-serif;font-weight: 400;font-size: .16rem;background-image: url("../img/image.jpg");background-size: cover;background-repeat: none;height: 100%;}.wrapper {position: relative;width: var(--wrapper);height: 100%;}.container {position: relative;width: 100%;height: 100%;background-color: var(--white);}.container .left {position: absolute;left: -100%;width: 60%;max-width: 250px;height: 100%;border: 1px solid var(--light);background-color: var(--white);z-index: 3;-webkit-transition: ease .4s;-moz-transition: ease .4s;-o-transition: ease .4s;transition: ease .4s;}.container .left.active{left: 0%;}.mask{position: absolute;width: 100vw;height: 100%;left: 0;top: 0;z-index: 2;background-color: rgba(0, 0, 0, 0.3);display: none;}.mask.active{display: block;}.container .left .top {position: relative;width: 100%;height: 47px;padding: .29rem;font-size: 14px;}.container .left .top #numbers{font-size: 16px;}.container .left .top:after {position: absolute;bottom: 0;left: 50%;display: block;width: 90%;height: 1px;content: '';background-color: var(--light);-webkit-transform: translate(-50%, 0);transform: translate(-50%, 0);}.container .left input {float: left;width: 1.88rem;height: .42rem;padding: 0 .15rem;border: 1px solid var(--light);background-color: #eceff1;border-radius: .21rem;font-family: 'Source Sans Pro', sans-serif;font-weight: 400;}.container .left input:focus {outline: none;}.container .left a.search {display: block;float: left;width: .42rem;height: .42rem;margin-left: .1rem;border: 1px solid var(--light);background-color: var(--blue);background-image: url("../img//name-type.png");background-repeat: no-repeat;background-position: top .12rem left .14rem;border-radius: 50%;}.container .left .people {margin-left: -1px;border-right: 1px solid var(--light);border-left: 1px solid var(--light);width: calc(100% + .02rem);}.container .left .people .person {position: relative;width: 100%;padding: 10px 14px;cursor: pointer;background-color: var(--white);}.container .left .people .person:after {position: absolute;bottom: 0;left: 50%;display: block;width: 86%;height: 1px;content: '';background-color: var(--light);-webkit-transform: translate(-50%, 0);transform: translate(-50%, 0);}.container .left .people .person img {width: 40px;height: 40px;border-radius: 50%;border: 1px solid #eee;margin-right: 5px;}.container .left .people .person .name {font-size: 14px;line-height: .22rem;color: var(--dark);font-family: 'Source Sans Pro', sans-serif;font-weight: 600;}.container .left .people .person .time {font-size: 12px;color: var(--grey);background-color: var(--white);}.container .left .people .person .preview {font-size: 12px;color: green;display: block;margin-top: 3px;text-align: start;}.container .left .people .person.active, .container .left .people .person:hover {margin-top: -1px;margin-left: -1px;padding-top: .13rem;border: 0;background-color: var(--blue);width: calc(100% + .02rem);padding-left: calc(10% + 1px);}.container .left .people .person.active span, .container .left .people .person:hover span {color: var(--white);background: transparent;}.container .left .people .person.active:after, .container .left .people .person:hover:after {display: none;}.container .right {position: relative;float: left;width: 100%;height: 100%;}.container .right .top {width: 100%;height: 47px;padding: .15rem .29rem;background-color: #eceff1;font-size: 14px;}.container .right .top .chakang{display: flex;align-items: center;color: var(--blue);cursor: pointer;}.container .right .top span {color: var(--grey);}.container .right .top span .name {color: var(--dark);font-family: 'Source Sans Pro', sans-serif;font-weight: 600;}.container .right .mesbox{padding: 0 15px;height: calc(100% - 89px);overflow: auto;}.container .right .chat {position: relative;display: none;flex-direction: column;}.container .right .chat.active-chat {display: flex;text-align: left;}.container .right .chat .mg_t20:last-child{margin-bottom: 15px;}.container .right .write {position: absolute;bottom: 0;height: 42px;padding-left: 8px;border: 1px solid var(--light);background-color: #e8ebee;width: 100%;display: flex;align-items: center;padding: 0 10px;}.container .right .write input {font-size: 16px;width: 100%;height: 30px;margin: 5px 10px 5px 5px;padding: 0 10px;color: var(--dark);border: 0;outline: none;background-color: #f6f7f8;font-family: 'Source Sans Pro', sans-serif;font-weight: 400;border-radius: 4px;}.container .right .write .write-link.attach:before {display: inline-block;width: 20px;height: 42px;content: '';background-image: url("../img/attachment.png");background-repeat: no-repeat;background-position: center;}.container .right .write .write-link.smiley:before {display: flex;width: 20px;height: 42px;content: '';background-image: url("../img/smiley.png");background-repeat: no-repeat;background-position: center;}.container .right .write .write-link.send:before {display: flex;float: left;width: 20px;height: 42px;margin-left: 11px;content: '';background-image: url("../img/send.png");background-repeat: no-repeat;background-position: center;}.container .right .bubble {font-size: 16px;position: relative;display: inline-block;clear: both;padding: 10px 14px;vertical-align: top;border-radius: 5px;word-break: break-all }.container .right .bubble:before {position: absolute;top: 14px;display: block;width: 12px;height: 12px;content: '\00a0';-webkit-transform: rotate(29deg) skew(-35deg);transform: rotate(29deg) skew(-35deg);}.container .right .bubble.you {float: left;color: var(--dark);background-color: #eceff1;align-self: flex-start;-webkit-animation-name: slideFromLeft;animation-name: slideFromLeft;}.container .right .bubble.you:before {left: -4px;background-color: #eceff1;}.container .right .bubble.me {float: right;color: var(--white);background-color: var(--blue);align-self: flex-end;-webkit-animation-name: slideFromRight;animation-name: slideFromRight;}.container .right .bubble.me:before {right: -4px;background-color: var(--blue);}.container .right .conversation-start {position: relative;width: 100%;text-align: center;}.container .right .conversation-start span {font-size: 12px;display: inline-block;color: var(--grey);}.container .right .conversation-start span:before, .container .right .conversation-start span:after {position: absolute;top: 7px;display: inline-block;width: 20%;height: 1px;content: '';background-color: var(--light);}.container .right .conversation-start span:before {left: 0;}.container .right .conversation-start span:after {right: 0;}@keyframes slideFromLeft {0% {margin-left: -2rem;opacity: 0;}100% {margin-left: 0;opacity: 1;}}@-webkit-keyframes slideFromLeft {0% {margin-left: -2rem;opacity: 0;}100% {margin-left: 0;opacity: 1;}}@keyframes slideFromRight {0% {margin-right: -2rem;opacity: 0;}100% {margin-right: 0;opacity: 1;}}@-webkit-keyframes slideFromRight {0% {margin-right: -2rem;opacity: 0;}100% {margin-right: 0;opacity: 1;}}.message img {float: left;width: 40px;height: 40px;margin-right: 12px;border-radius: 50%;border: 1px solid #eee;}.you {margin-left: 60px;margin-top: -39px;}.me-header {float: right !important;margin-right: 0 !important;}.me {margin-right: 60px;margin-top: -39px;}.active-chat::-webkit-scrollbar, .left::-webkit-scrollbar {width: 2px;}
<span style="color: red">css里加載了一些圖片菩掏,自己找?guī)讖垐D片替換了</span>
2、請(qǐng)慢用js脫發(fā)劑
我先簡(jiǎn)單的說一下我的邏輯:由于沒有登入這一步操作昵济,就不能確認(rèn)是誰(shuí)發(fā)的消息智绸,所以我就用ip當(dāng)作用戶名(雖然ip會(huì)變,當(dāng)短時(shí)間不變砸紊,對(duì)短時(shí)間測(cè)試沒有影響)传于。然后前端需要操作的數(shù)據(jù)就是區(qū)分信息是在左側(cè)還是右側(cè)顯示以及歷史信息回顯,就是用v-if判斷ip顯示不同html就行了醉顽。
websocket關(guān)鍵代碼
connect() {
// 創(chuàng)建一個(gè) websocket 連接 ws://ip:端口號(hào)
this.ws = new WebSocket("ws://websockets.sinye.xyz/websocket");
// 連接狀態(tài) 1已建立連接
// console.log(this.ws.readyState)
// 連接建立時(shí)觸發(fā)
this.ws.onopen = ()=>{
this.onopen();
};
// 客戶端接收服務(wù)端數(shù)據(jù)時(shí)觸發(fā)
this.ws.onmessage = (event)=>{
this.onmessage(event)
};
// 連接關(guān)閉時(shí)觸發(fā)
this.ws.onclose = ()=>{
this.onclose();
};
// 通信發(fā)生錯(cuò)誤時(shí)觸發(fā)
this.ws.onerror = ()=>{
this.onerror();
};
},
// 通信建立成功
onopen(){
var data = "系統(tǒng)消息:建立連接成功";
console.log(data);
},
// 接收客戶端的數(shù)據(jù),發(fā)送數(shù)據(jù)
onmessage(e){
var data = JSON.parse(e.data);
// console.log(data)
switch (data.type) {
case 'handShake':
//首次登錄沼溜,發(fā)送登陸數(shù)據(jù)
var user_info = {'type': 'login', 'msg': this.uname, 'headerimg': this.headerimg};
this.sendMsg(user_info);
break;
case 'login':
this.userList(data.user_list);
this.systemMessage('系統(tǒng)消息: ' + data.msg + ' 已上線');
break;
case 'logout':
this.userList(data.user_list);
if (data.msg.length > 0) {
this.systemMessage('系統(tǒng)消息: ' + data.msg + ' 已下線');
}
break;
case 'user':
this.messageList1(data);
break;
case 'system':
this.systemMessage();
break;
}
},
// 關(guān)閉連接時(shí)觸發(fā)
onclose(){
console.log("連接關(guān)閉,定時(shí)重連");
this.connect();
},
// websocket 錯(cuò)誤事件
onerror(){
var data = "系統(tǒng)消息 : 出錯(cuò)了,請(qǐng)退出重試.";
console.log(data);
},
// 輸入框輸入后按回車游添,向服務(wù)器發(fā)送信息
confirm() {
this.send();
},
// 發(fā)送的數(shù)據(jù)先保存到數(shù)據(jù)庫(kù)
send() {
let msg = this.inputValue.replace(/^[\s+]$/g, ' ')
var data = {type: "user", msg: msg, id: this.userid}
if(msg!=''){
this.$axios.post(
'/api/InfoSave/save', data
).then((response)=>{
if(response.data !== ''){
// 保存成功后再通過websocket群發(fā)
this.sendMsg(data);
this.inputValue = "";
}
})
}
},
// 群發(fā)數(shù)據(jù)
sendMsg(msg) {
var data = JSON.stringify(msg);
this.ws.send(data);
},
發(fā)送和接收就已經(jīng)完成了系草,接下來就是回顯歷史信息
歷史信息回顯
// 追加數(shù)據(jù) 上下線的系統(tǒng)消息
systemMessage(msg) {
this.mes.push({type:'sys',msg:msg})
this.active_chat.scrollTop = this.active_chat.scrollHeight;
},
// 追加從服務(wù)端返回的數(shù)據(jù) 左側(cè)在線人數(shù)列表
userList(user) {
this.user_list = []
for (var i = 0; i < user.length; i++) {
this.user_list.push({
headerimg: user[i].headerimg,
username: user[i].username,
login_time: user[i].login_time,
})
}
this.userLength = user.length
},
// 右側(cè)聊天記錄列表
messageList1(data) {
// var html
// 判讀是不是自己發(fā)送的消息,對(duì)應(yīng)的樣式不同
if (data.ip == this.uname) {
// 如果當(dāng)前用戶名和feom的用戶名相同唆涝,就說明時(shí)自己發(fā)送的消息
this.mes.push({type:'info', youMe:'me', msg:data.msg, head: data.headerimg})
} else {
// 別人發(fā)送的信息列表
this.mes.push({type:'info', youMe:'you', msg:data.msg, head: data.headerimg})
}
this.$nextTick(() =>{
this.active_chat.scrollTop = this.active_chat.scrollHeight;
})
},
// 右側(cè)聊天記錄列表
messageList2(data,bool) {
// var html
// 判讀是不是自己發(fā)送的消息找都,對(duì)應(yīng)的樣式不同
if (data.ip == this.uname) {
this.mes.splice(0,0,{type:'info', youMe:'me', msg:data.msg, head: data.headerimg})
} else {
this.mes.splice(0,0,{type:'info', youMe:'you', msg:data.msg, head: data.headerimg})
}
if(bool){
this.$nextTick(() =>{
this.active_chat.scrollTop = this.active_chat.scrollHeight;
})
}
else{
this.$nextTick(() =>{
this.active_chat.scrollTop = this.active_chat.scrollHeight - this.oldH
})
}
},
// 獲取歷史信息ajax
onRefresh(bool) {
this.isLoading = true;
this.page++;
this.$axios.post(
'/api/InfoSave/get', {page: this.page}
).then((response)=>{
if(response.data){
for (let i = 0; i < response.data.length; i++) {
const data = response.data[i];
this.messageList2(data,bool)
}
this.isLoading = false;
}else{
this.page--
this.isLoading = false;
}
})
},
// 監(jiān)聽滾動(dòng)
handleScroll () {
if (this.active_chat.scrollTop <= 0) {
this.oldH = JSON.parse(JSON.stringify(this.active_chat.scrollHeight))
this.onRefresh(false)
}
},
// 獲取ip并連接服務(wù)器
login(){
this.$axios.get(
'/api/login'
).then((response)=>{
var data = response.data
this.uname = data.ip
this.headerimg = data.headerimg
this.userid = data.id
if(this.uname!=''){
this.connect()
this.onRefresh(true)
}else{
console.log('連接失敗廊酣!')
}
})
},
初始化還是必須的
mounted(){
this.login()
this.$nextTick(() =>{
this.active_chat = document.querySelector('.mesbox')
this.$refs.mesbox.addEventListener('scroll', this.handleScroll)
})
},
data() {
return {
userid: '',//用戶id
ws: {},
uname: '',//用戶名
headerimg: '',//用戶頭像
mes:[],//消息
inputValue: '',//輸入的信息
active_chat: '',//信息dome
user_list: [],//在線用戶列表
user_list_bool: false,//在線用戶列表彈窗是否顯示
userLength: '',//在線人數(shù)
page: 0,
isLoading: false,
oldH: '',//原來信息高度
}
},