效果顯示
WebSocket連接
使用全局變量
本小程序在用戶瀏覽首頁的時(shí)候創(chuàng)建WebSocket連接,并將連接獲得的WebSocket對(duì)象存儲(chǔ)到全局變量中,方便其他頁面來使用WebSocket
首先在項(xiàng)目的main.js文件中聲明全局變量socket
Vue.prototype.$socket = null
對(duì)全局變量進(jìn)行賦值
Vue.prototype.$socket = this.$socket;
后續(xù)如果需要使用全局變量,直接使用this.$socket
即可
WebSocket連接細(xì)節(jié)
下面的代碼中有一個(gè)headbeat方法,該方法主要用來定時(shí)給WebSocket服務(wù)器發(fā)送一個(gè)信號(hào),告訴WebSocket服務(wù)器當(dāng)前客戶端還處于連接狀態(tài)。當(dāng)心跳停止的時(shí)候(比如客戶端斷網(wǎng)),后端服務(wù)就會(huì)將用戶信息從連接中移除
/**
* 創(chuàng)建websocket連接
*/
initWebsocket() {
// console.log("this.socket:" + JSON.stringify(this.$socket))
// this.$socket == null,剛剛進(jìn)入首頁,還沒有建立過websocket連接
// this.$socket.readyState==0 表示正在連接當(dāng)中
// this.$socket.readyState==1 表示處于連接狀態(tài)
// this.$socket.readyState==2 表示連接正在關(guān)閉
// this.$socket.readyState==3 表示連接已經(jīng)關(guān)閉
if (this.$socket == null || (this.$socket.readyState != 1 && this.$socket.readyState != 0)) {
this.$socket = uni.connectSocket({
url: "ws://10.23.17.146:8085/websocket/" + uni.getStorageSync("curUser").userName,
success(res) {
console.log('WebSocket連接成功', res);
},
})
// console.log("this.socket:" + this.$socket)
// 監(jiān)聽WebSocket連接打開事件
this.$socket.onOpen((res) => {
console.log("websocket連接成功")
Vue.prototype.$socket = this.$socket;
// 連接成功,開啟心跳
this.headbeat();
});
// 連接異常
this.$socket.onError((res) => {
console.log("websocket連接出現(xiàn)異常");
// 重連
this.reconnect();
})
// 連接斷開
this.$socket.onClose((res) => {
console.log("websocket連接關(guān)閉");
// 重連
this.reconnect();
})
}
},
/**
* 重新連接
*/
reconnect() {
console.log("重連");
// 防止重復(fù)連接
if (this.lockReconnect == true) {
return;
}
// 鎖定,防止重復(fù)連接
this.lockReconnect = true;
// 間隔一秒再重連,避免后臺(tái)服務(wù)出錯(cuò)時(shí),客戶端連接太頻繁
setTimeout(() => {
this.initWebsocket();
}, 1000)
// 連接完成,設(shè)置為false
this.lockReconnect = false;
},
// 開啟心跳
headbeat() {
console.log("websocket心跳");
var that = this;
setTimeout(function() {
if (that.$socket.readyState == 1) {
// websocket已經(jīng)連接成功
that.$socket.send({
data: JSON.stringify({
status: "ping"
})
})
// 調(diào)用啟動(dòng)下一輪的心跳
that.headbeat();
} else {
// websocket還沒有連接成功,重連
that.reconnect();
}
}, that.heartbeatTime);
},
最近和自己聊天的用戶信息
界面效果
界面代碼
<template>
<view class="container">
<scroll-view @scrolltolower="getMoreChatUserVo">
<view v-for="(chatUserVo,index) in chatUserVoList" :key="index" @click="trunToChat(chatUserVo)">
<view style="height: 10px;"></view>
<view class="chatUserVoItem">
<view style="display: flex;align-items: center;">
<uni-badge class="uni-badge-left-margin" :text="chatUserVo.unReadChatNum" absolute="rightTop"
size="small">
<u--image :showLoading="true" :src="chatUserVo.userAvatar" width="50px" height="50px"
:fade="true" duration="450">
<view slot="error" style="font-size: 24rpx;">加載失敗</view>
</u--image>
</uni-badge>
</view>
<view style="margin: 10rpx;"></view>
<view
style="line-height: 20px;width: 100%;display: flex;justify-content: space-between;flex-direction: column;">
<view style="display: flex; justify-content: space-between;">
<view>
<view class="nickname">{{chatUserVo.userNickname}}
</view>
<view class="content">{{chatUserVo.lastChatContent}}</view>
</view>
<view class="date">{{formatDateToString(chatUserVo.lastChatDate)}}</view>
</view>
<!-- <view style="height: 10px;"></view> -->
<u-line></u-line>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import {
listChatUserVo
} from "@/api/market/chat.js";
import {
listChat
} from "@/api/market/chat.js"
export default {
data() {
return {
chatUserVoList: [],
page: {
pageNum: 1,
pageSize: 15
},
}
},
created() {
},
methods: {
/**
* 滑動(dòng)到底部,自動(dòng)加載新一頁的數(shù)據(jù)
*/
getMoreChatUserVo() {
this.page.pageNum++;
this.listChatUserVo();
},
listChatUserVo() {
listChatUserVo(this.page).then(res => {
// console.log("res:"+JSON.stringify(res.rows))
// this.chatUserVoList = res.rows;
for (var i = 0; i < res.rows.length; i++) {
this.chatUserVoList.push(res.rows[i]);
}
})
},
/**
* 格式化日期
* @param {Object} date
*/
formatDateToString(dateStr) {
let date = new Date(dateStr);
// 今天的日期
let curDate = new Date();
if (date.getFullYear() == curDate.getFullYear() && date.getMonth() == curDate.getMonth() && date
.getDate() == curDate.getDate()) {
// 如果和今天的年月日都一樣,那就只顯示時(shí)間
return this.toDoubleNum(date.getHours()) + ":" + this.toDoubleNum(date.getMinutes());
} else {
// 如果年份一樣,就只顯示月日
return (curDate.getFullYear() == date.getFullYear() ? "" : (date.getFullYear() + "-")) + this
.toDoubleNum((
date
.getMonth() + 1)) +
"-" +
this.toDoubleNum(date.getDate());
}
},
/**
* 如果傳入的數(shù)字是兩位數(shù),直接返回;
* 否則前面拼接一個(gè)0
* @param {Object} num
*/
toDoubleNum(num) {
if (num >= 10) {
return num;
} else {
return "0" + num;
}
},
/**
* 轉(zhuǎn)到私聊頁面
*/
trunToChat(chatUserVo) {
let you = {
avatar: chatUserVo.userAvatar,
nickname: chatUserVo.userNickname,
username: chatUserVo.userName
}
uni.navigateTo({
url: "/pages/chat/chat?you=" + encodeURIComponent(JSON.stringify(you))
})
},
/**
* 接收消息
*/
receiveMessage() {
this.$socket.onMessage((response) => {
// console.log("接收消息:" + response.data);
let message = JSON.parse(response.data);
// 收到消息,將未讀消息數(shù)量加一
for (var i = 0; i < this.chatUserVoList.length; i++) {
if (this.chatUserVoList[i].userName == message.from) {
this.chatUserVoList[i].unReadChatNum++;
// 顯示對(duì)方發(fā)送的最新消息
listChat(message.from, {
pageNum: 1,
pageSize: 1
}).then(res => {
this.chatUserVoList[i].lastChatContent = res.rows[0].content
});
break;
}
}
})
},
},
onLoad(e) {
this.receiveMessage();
},
onShow: function() {
this.chatUserVoList = [];
this.listChatUserVo();
},
}
</script>
<style lang="scss">
.container {
padding: 20rpx;
.chatUserVoItem {
display: flex;
margin: 0 5px;
.nickname {
font-weight: 700;
}
.content {
color: #A7A7A7;
font-size: 14px;
/* 讓消息只顯示1行,超出的文字內(nèi)容使用...來代替 */
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
.date {
color: #A7A7A7;
font-size: 12px;
}
}
// .uni-badge-left-margin {
// margin-left: 10px;
// }
}
</style>
最近的聊天內(nèi)容太長
當(dāng)最近的一條聊天內(nèi)容太長的時(shí)候,頁面不太美觀,缺少整齊的感覺
解決的方式非常簡單,只需要添加以下樣式即可
.content {
/* 讓消息只顯示1行,超出的文字內(nèi)容使用...來代替 */
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
日期時(shí)間顯示
本文顯示日期時(shí)間的時(shí)候,遵循以下原則:
- 如果上次聊天時(shí)間的年月日和今天一致,那就只顯示時(shí)間,即顯示
時(shí):分
- 如果上次聊天時(shí)間的年份和今年一致,那就只顯示
月-日
- 如果上面的條件都不滿足,就顯示
年-月-日
在顯示月、日、時(shí)、分的時(shí)候,如果數(shù)字是一位數(shù)字
,就在前面補(bǔ)一個(gè)零,具體操作如方法toDoubleNum
/**
* 格式化日期
* @param {Object} date
*/
formatDateToString(dateStr) {
let date = new Date(dateStr);
// 今天的日期
let curDate = new Date();
if (date.getFullYear() == curDate.getFullYear() && date.getMonth() == curDate.getMonth() && date
.getDate() == curDate.getDate()) {
// 如果和今天的年月日都一樣,那就只顯示時(shí)間
return this.toDoubleNum(date.getHours()) + ":" + this.toDoubleNum(date.getMinutes());
} else {
// 如果年份一樣,就只顯示月日
return (curDate.getFullYear() == date.getFullYear() ? "" : (date.getFullYear() + "-")) + this
.toDoubleNum((
date
.getMonth() + 1)) +
"-" +
this.toDoubleNum(date.getDate());
}
},
/**
* 如果傳入的數(shù)字是兩位數(shù),直接返回;
* 否則前面拼接一個(gè)0
* @param {Object} num
*/
toDoubleNum(num) {
if (num >= 10) {
return num;
} else {
return "0" + num;
}
},
未讀消息數(shù)量顯示
未讀消息數(shù)量顯示使用角標(biāo)組件,即uni-badge
,使用該組件需要下載安裝插件,下載鏈接,下載之前需要看廣告,哈哈哈,當(dāng)然有錢可以不看
顯示效果如下圖
<uni-badge class="uni-badge-left-margin" :text="chatUserVo.unReadChatNum" absolute="rightTop"
size="small">
<u--image :showLoading="true" :src="chatUserVo.userAvatar" width="50px" height="50px"
:fade="true" duration="450">
<view slot="error" style="font-size: 24rpx;">加載失敗</view>
</u--image>
</uni-badge>
私聊界面
界面展示
【微信公眾平臺(tái)模擬的手機(jī)界面】
【手機(jī)端,鍵盤呼出之后的聊天區(qū)域】
代碼實(shí)現(xiàn)
<template>
<view style="height:100vh;">
<!-- @scrolltoupper:上滑到頂部執(zhí)行事件,此處用來加載歷史消息 -->
<!-- scroll-with-animation="true" 設(shè)置滾動(dòng)條位置的時(shí)候使用動(dòng)畫過渡,讓動(dòng)作更加自然 -->
<scroll-view :scroll-into-view="scrollToView" scroll-y="true" class="messageListScrollView"
:style="{height:scrollViewHeight}" @scrolltoupper="getHistoryChat()"
:scroll-with-animation="!isFirstListChat" ref="chatScrollView">
<view v-for="(message,index) in messageList" :key="message.id" :id="`message`+message.id"
style="width: 750rpx;min-height: 60px;">
<view style="height: 10px;"></view>
<view v-if="message.type==0" class="messageItemLeft">
<view style="width: 8px;"></view>
<u--image :showLoading="true" :src="you.avatar" width="50px" height="50px" radius="3"></u--image>
<view style="width: 7px;"></view>
<view class="messageContent left">
{{message.content}}
</view>
</view>
<view v-if="message.type==1" class="messageItemRight">
<view class="messageContent right">
{{message.content}}
</view>
<view style="width: 7px;"></view>
<u--image :showLoading="true" :src="me.avatar" width="50px" height="50px" radius="3"></u--image>
<view style="width: 8px;"></view>
</view>
</view>
</scroll-view>
<view class="messageSend">
<view class="messageInput">
<u--textarea v-model="messageInput" placeholder="請(qǐng)輸入消息內(nèi)容" autoHeight>
</u--textarea>
</view>
<view style="width:5px"></view>
<view class="commmitButton" @click="send()">發(fā) 送</view>
</view>
</view>
</template>
<script>
import {
getUserProfileVo
} from "@/api/user";
import {
listChat
} from "@/api/market/chat.js"
let socket;
export default {
data() {
return {
webSocketUrl: "",
socket: null,
messageInput: '',
// 我自己的信息
me: {},
// 對(duì)方信息
you: {},
scrollViewHeight: undefined,
messageList: [],
// 底部滑動(dòng)到哪里
scrollToView: '',
page: {
pageNum: 1,
pageSize: 15
},
isFirstListChat: true,
loadHistory: false,
// 消息總條數(shù)
total: 0,
}
},
created() {
this.me = uni.getStorageSync("curUser");
},
beforeDestroy() {
console.log("執(zhí)行銷毀方法");
this.endChat();
},
onLoad(e) {
// 設(shè)置初始高度
this.scrollViewHeight = `calc(100vh - 20px - 44px)`;
this.you = JSON.parse(decodeURIComponent(e.you));
uni.setNavigationBarTitle({
title: this.you.nickname,
})
this.startChat();
this.listChat();
this.receiveMessage();
},
onReady() {
// 監(jiān)聽鍵盤高度變化,以便設(shè)置輸入框的高度
uni.onKeyboardHeightChange(res => {
let keyBoardHeight = res.height;
console.log("keyBoardHeight:" + keyBoardHeight);
this.scrollViewHeight = `calc(100vh - 20px - 44px - ${keyBoardHeight}px)`;
this.scrollToView = '';
setTimeout(() => {
this.scrollToView = 'message' + this.messageList[this
.messageList.length - 1].id;
}, 150)
})
},
methods: {
/**
* 發(fā)送消息
*/
send() {
if (this.messageInput != '') {
let message = {
from: this.me.userName,
to: this.you.username,
text: this.messageInput
}
// console.log("this.socket.send:" + this.$socket)
// 將組裝好的json發(fā)送給服務(wù)端,由服務(wù)端進(jìn)行轉(zhuǎn)發(fā)
this.$socket.send({
data: JSON.stringify(message)
});
this.total++;
let newMessage = {
// code: this.messageList.length,
type: 1,
content: this.messageInput
};
this.messageList.push(newMessage);
this.messageInput = '';
this.toBottom();
}
},
/**
* 開始聊天
*/
startChat() {
let message = {
from: this.me.userName,
to: this.you.username,
text: "",
status: "start"
}
// 告訴服務(wù)端要開始聊天了
this.$socket.send({
data: JSON.stringify(message)
});
},
/**
* 結(jié)束聊天
*/
endChat() {
let message = {
from: this.me.userName,
to: this.you.username,
text: "",
status: "end"
}
// 告訴服務(wù)端要結(jié)束聊天了
this.$socket.send({
data: JSON.stringify(message)
});
},
/**
* 接收消息
*/
receiveMessage() {
this.$socket.onMessage((response) => {
// console.log("接收消息:" + response.data);
let message = JSON.parse(response.data);
let newMessage = {
// code: this.messageList.length,
type: 0,
content: message.text
};
this.messageList.push(newMessage);
this.total++;
// 讓scroll-view自動(dòng)滾動(dòng)到最新的數(shù)據(jù)那里
// this.$nextTick(() => {
// // 滑動(dòng)到聊天區(qū)域最底部
// this.scrollToView = 'message' + this.messageList[this
// .messageList.length - 1].id;
// });
this.toBottom();
})
},
/**
* 查詢對(duì)方和自己最近的聊天數(shù)據(jù)
*/
listChat() {
return new Promise((resolve, reject) => {
listChat(this.you.username, this.page).then(res => {
for (var i = 0; i < res.rows.length; i++) {
this.total = res.total;
if (res.rows[i].fromWho == this.me.userName) {
res.rows[i].type = 1;
} else {
res.rows[i].type = 0;
}
// 將消息放到數(shù)組的首位
this.messageList.unshift(res.rows[i]);
}
if (this.isFirstListChat == true) {
// this.$nextTick(function() {
// // 滑動(dòng)到聊天區(qū)域最底部
// this.scrollToView = 'message' + this.messageList[this
// .messageList.length - 1].id;
// })
this.toBottom();
this.isFirstListChat = false;
}
resolve();
})
})
},
/**
* 滑到最頂端,分頁加一,拉取更早的數(shù)據(jù)
*/
getHistoryChat() {
// console.log("獲取歷史消息")
this.loadHistory = true;
if (this.messageList.length < this.total) {
// 當(dāng)目前的消息條數(shù)小于消息總量的時(shí)候,才去查歷史消息
this.page.pageNum++;
this.listChat().then(() => {})
}
},
/**
* 滑動(dòng)到聊天區(qū)域最底部
*/
toBottom() {
// 讓scroll-view自動(dòng)滾動(dòng)到最新的數(shù)據(jù)那里
this.scrollToView = '';
setTimeout(() => {
// 滑動(dòng)到聊天區(qū)域最底部
this.scrollToView = 'message' + this.messageList[this
.messageList.length - 1].id;
}, 150)
}
}
}
</script>
<style lang="scss">
.messageListScrollView {
background: #F5F5F5;
overflow: auto;
.messageItemLeft {
display: flex;
align-items: flex-start;
justify-content: flex-start;
.messageContent {
max-width: calc(750rpx - 10px - 50px - 15px - 10px - 50px - 15px);
padding: 10px;
// margin-top: 10px;
border-radius: 7px;
font-family: sans-serif;
// padding: 10px;
// 讓view只包裹文字
width: auto;
// display: inline-block !important;
// display: inline;
// 解決英文字符串、數(shù)字不換行的問題
word-break: break-all;
word-wrap: break-word;
}
}
.messageItemRight {
display: flex;
align-items: flex-start;
justify-content: flex-end;
.messageContent {
max-width: calc(750rpx - 10px - 50px - 15px - 10px - 50px - 15px);
padding: 10px;
// margin-top: 10px;
border-radius: 7px;
font-family: sans-serif;
// padding: 10px;
// 讓view只包裹文字
width: auto;
// display: inline-block !important;
// display: inline;
// 解決長英文字符串、數(shù)字不換行的問題
word-wrap: break-word;
}
}
.right {
background-color: #94EA68;
}
.left {
background-color: #ffffff;
}
}
.messageSend {
display: flex;
background: #ffffff;
padding-top: 5px;
padding-bottom: 15px;
.messageInput {
border: 1px #EBEDF0 solid;
border-radius: 5px;
width: calc(750rpx - 65px);
margin-left: 5px;
}
.commmitButton {
height: 38px;
border-radius: 5px;
width: 50px;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
background: #3C9CFF;
}
}
</style>
英文長串不換行問題
這個(gè)問題屬于是整串英文被以為是一個(gè)單詞了,所以沒有換行,看下面的句子,英文單詞可以比較短的,所以會(huì)自動(dòng)換行
解決這個(gè)問題只需要添加下面的css即可
// 解決長英文字符串、數(shù)字不換行的問題
word-wrap: break-word;
下面是添加之后的效果
聊天區(qū)域自動(dòng)滑動(dòng)到底部
在聊天的時(shí)候,無論是發(fā)送一條新的消息,還是接收到一條新的消息,聊天區(qū)域都需要自動(dòng)滑動(dòng)到最新的消息那里。本文使用scroll-view組件來包裹顯示聊天消息,在scroll-view組件中,可以通過給scroll-into-view屬性賦值來指定聊天區(qū)域所顯示到的位置。使用時(shí)需要注意如下問題:
- 需要先給每一條消息設(shè)置一個(gè)id屬性,id屬性存儲(chǔ)的內(nèi)容不能以數(shù)字開頭,因此本文在id之間拼接了一個(gè)字符串’message’
- scroll-view需要被設(shè)置好高度,本文通過綁定一個(gè)變量來設(shè)置高度,如
:style="{height:scrollViewHeight}"
,因?yàn)槭謾C(jī)端使用小程序打字時(shí)鍵盤呼出會(huì)影響聊天區(qū)域的高度
后續(xù)通過給scrollToView設(shè)置不同的值即可控制聊天區(qū)域的滑動(dòng),比如每接收到一條新的消息,就調(diào)用toBottom
方法,該方法通過設(shè)置scrollToView為'message' + this.messageList[this.messageList.length - 1].id
將聊天區(qū)域滑動(dòng)到最新的消息處。需要注意的是,在進(jìn)行該值的設(shè)置之前,需要延遲一段時(shí)間,否則滑動(dòng)可能不成功,本文延遲150ms,讀者也可以探索不同的值,該值不能太大或者太小。
通過設(shè)置scroll-view的屬性scroll-with-animation的值為true,可以讓消息區(qū)域在滑動(dòng)的時(shí)候使用動(dòng)畫過渡,這樣滑動(dòng)更加自然。
鍵盤呼出,聊天區(qū)域收縮,聊天區(qū)域滑動(dòng)到底部
當(dāng)鍵盤呼出時(shí),需要將聊天區(qū)域的高度減去鍵盤的高度。同時(shí)將scrollToView賦值為最后一條消息的id。需要注意的是,在設(shè)置scrollToView之前,需要先將scrollToView設(shè)置為空字符串,否則滑動(dòng)效果可能不成功
onReady() {
// 監(jiān)聽鍵盤高度變化,以便設(shè)置輸入框的高度
uni.onKeyboardHeightChange(res => {
let keyBoardHeight = res.height;
console.log("keyBoardHeight:" + keyBoardHeight);
this.scrollViewHeight = `calc(100vh - 20px - 44px - ${keyBoardHeight}px)`;
this.scrollToView = '';
setTimeout(() => {
this.scrollToView = 'message' + this.messageList[this
.messageList.length - 1].id;
}, 150)
})
},
通知WebSocket服務(wù)器哪兩個(gè)用戶開始聊天
為了便于后端在存儲(chǔ)聊天數(shù)據(jù)的時(shí)候辨別消息是否為已讀狀態(tài)。比如,在小王開始聊天之前,需要先告訴后端:“小王要開始和小明聊天了”,如果正好小明也告訴后端:“我要和小王聊天了”,那小王發(fā)出去的消息就會(huì)被設(shè)置為已讀狀態(tài),因?yàn)樗麄儍蓚€(gè)此時(shí)此刻正在同時(shí)和對(duì)方聊天,那小王發(fā)出去的消息就默認(rèn)被小明看到了,因此設(shè)置為已讀狀態(tài)文章來源:http://www.zghlxwxcb.cn/news/detail-699047.html
/**
* 開始聊天
*/
startChat() {
let message = {
from: this.me.userName,
to: this.you.username,
text: "",
status: "start"
}
// 告訴服務(wù)端要開始聊天了
this.$socket.send({
data: JSON.stringify(message)
});
},
/**
* 結(jié)束聊天
*/
endChat() {
let message = {
from: this.me.userName,
to: this.you.username,
text: "",
status: "end"
}
// 告訴服務(wù)端要結(jié)束聊天了
this.$socket.send({
data: JSON.stringify(message)
});
},
同項(xiàng)目其他文章
該項(xiàng)目的其他文章請(qǐng)查看【易售小程序項(xiàng)目】項(xiàng)目介紹、小程序頁面展示與系列文章集合文章來源地址http://www.zghlxwxcb.cn/news/detail-699047.html
到了這里,關(guān)于【易售小程序項(xiàng)目】私聊功能uniapp界面實(shí)現(xiàn) (買家、賣家 溝通商品信息)【后端基于若依管理系統(tǒng)開發(fā)】的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!