- 這節(jié)課來對(duì)接
通訊錄好友列表接口和好友聊天記錄接口· -
對(duì)接通訊錄好友列表接口
- 打開接口文檔https://www.showdoc.com.cn/2035654172307363/9197156730533694
-
1、打開項(xiàng)目,復(fù)制對(duì)應(yīng)的接口路徑到項(xiàng)目的
src/api/index.js中
const api = {
GetMyFriends:'/api/myfriends'
}
-
2、在
src/api/user.js中新建api接口方法
// 獲取我的好友列表
export function getMyFriends(data) {
return request({
url: api.GetMyFriends,
method: 'post',
data,
})
}
-
3、復(fù)制你新建的api方法名到需要引入到頁面,也就是【通訊錄頁面】,通過
import導(dǎo)入
import { getMyFriends } from '@/api/user'
-
4、然后在
methods方法函數(shù)內(nèi),新建獲取好友列表的自定義方法,在該方法內(nèi)調(diào)用getMyFriends接口方法
methods:{
async getMyFriendsFun() {
try {
const res = await getMyFriends()
console.log(res)
this.list = res.data //注意:【list】要在data內(nèi)預(yù)定義成空數(shù)組,即 list:[]
} catch (error) {
console.log(error)
}
}
}
-
注意:【list】要在data內(nèi)預(yù)定義成空數(shù)組,即 list:[ ]
-
5、然后在
mounted生命周期函數(shù)內(nèi)調(diào)用getMyFriendsFun方法
mounted () {
this.getMyFriendsFun();
}
-
然后就獲取到數(shù)據(jù)了~
image.png
-
6、最后for循環(huán)這個(gè)list數(shù)組,進(jìn)行數(shù)據(jù)綁定就搞定了這個(gè)通訊錄好友列表了
image.png
-
對(duì)接好友聊天記錄接口
-
1、打開接口文檔https://www.showdoc.com.cn/2035654172307363/9197322103805778
-
2、復(fù)制對(duì)接的接口地址路徑到
src/api/index.js文件中粘貼
const api = {
GetChatMsg:'/api/query/msg'
}
-
3、打開
src/api/user.js文件,新建api接口方法,注意看清楚接口的請(qǐng)求方式,根據(jù)接口文檔來看
// 獲取好友列表記錄
export function getChatMsg(params) {
return request({
url: api.GetChatMsg,
method: 'get',
params,
})
}
-
4、然后復(fù)制接口方法名到需要引入的頁面,即
chatDetail頁面
import { getChatMsg } from '@/api/user';
-
5、因?yàn)檫@個(gè)聊天詳情頁面是動(dòng)態(tài)路由匹配的,所以我們?cè)谏蟼€(gè)頁面跳轉(zhuǎn)的時(shí)候,要傳入真實(shí)的id參數(shù)
-
6、打開通訊錄頁面,找到跳轉(zhuǎn)方法
toDetailPage,修改傳入的id參數(shù)
methods:{
toDetailPage(item) {
this.$router.push('/chatDetail/'+item._id)
},
}
-
7、然后在methods方法函數(shù)內(nèi)新建
獲取好友聊天記錄的自定義方法
methods:{
async getChatMsgFun() {
try {
const res = await getChatMsg({ toId: this.$route.params.id })
console.log(res);
this.list = res.data; //注意:【list】要在data內(nèi)預(yù)定義成空數(shù)組,即 list:[]
} catch (error) {
console.log(err)
}
}
}
-
注意:【list】要在data內(nèi)預(yù)定義成空數(shù)組,即 list:[]
-
8、然后在
mounted生命周期內(nèi)調(diào)用getChatMsgFun方法
mounted () {
this.getChatMsgFun();
},
-
然后就獲取到數(shù)據(jù)了~
image.png
-
9、下面就是要渲染該數(shù)據(jù)了,但是因?yàn)槲覀冎虚g的區(qū)域是引入的外部組件,所以要先把得到的list數(shù)組數(shù)據(jù),
通過父子組件傳值的形式,傳遞過去,子組件使用props來接收傳遞過去的數(shù)組 -
在子組件身上v-bind動(dòng)態(tài)綁定傳值
image.png
-
子組件內(nèi)部使用
props接收傳遞過來的list
image.png
-
子組件data中的list沒用了,就可以刪除掉了?。?!
image.png
-
10、下面就是要處理下渲染出來的數(shù)據(jù),根據(jù)實(shí)際情況渲染,目前是這個(gè)樣子的
image.png
-
先渲染右側(cè)數(shù)據(jù),【我是你師傅】和【悟空好繞啊】這兩條消息我們現(xiàn)在登陸的這個(gè)唐僧的賬號(hào)發(fā)出的,所以,根據(jù)接口返回?cái)?shù)據(jù)和接口返回字段對(duì)比
image.png
image.png
- 如果將我們當(dāng)前登錄賬戶人發(fā)送的消息渲染在右側(cè)呢?
- 其他
send_id是發(fā)送者的id,那誰是發(fā)送者呢?當(dāng)然是當(dāng)前登錄的賬戶人是發(fā)送者,那么將此send_id和當(dāng)前登錄賬戶人的個(gè)人id進(jìn)行對(duì)比,如果相等就是渲染在右側(cè)的! -
send_id有了,現(xiàn)在的問題是如何獲取到當(dāng)前登錄賬戶人的id呢? - 這就需要修改下
獲取個(gè)人信息接口存儲(chǔ)的數(shù)據(jù) - 打開
src/store/modules/app.js
image.png
getUserInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getUserInfo().then(response => {
const { data } = response
if (!data) {
return reject('Verification failed, please Login again.')
}
const { username } = data
commit('SET_USER_NAME', data)
resolve(data)
}).catch(error => {
reject(error)
})
})
},
-
這樣,我們vuex中state里的userName存儲(chǔ)的就是當(dāng)前登錄賬戶人的個(gè)人信息了,就是下圖,id就在里面
image.png
-
11、我們可以通過vuex的
mapGetters來拿到上圖中的對(duì)象數(shù)據(jù) -
先在
chatDetail頁面引入mapGetters
import { mapGetters } from 'vuex';
-
在通過計(jì)算屬性拿到
userName對(duì)象
computed: {
...mapGetters(['userName'])
},
-
12、下面就可以渲染右側(cè)的聊天記錄數(shù)據(jù)了
image.png
-
那么右側(cè)的數(shù)據(jù)就渲染好了,在渲染下頭像
send_avatar就搞定右側(cè)了
image.png
13、下面來分析下如何渲染左側(cè)數(shù)據(jù)
- 首先 我們的地址欄參數(shù),那個(gè)id就是對(duì)方的id,也就是和誰聊天的那個(gè)人的id,那么針對(duì)左側(cè)的聊天記錄來說,左側(cè)的消息是誰發(fā)的呢?是對(duì)方發(fā)的,那么對(duì)方就是發(fā)送方,那左側(cè)聊天信息的send_id就是發(fā)送方的id,那么既然
地址欄參數(shù),那個(gè)id就是對(duì)方的id,那地址欄的id==左側(cè)聊天信息的send_id是不是就可以渲染左側(cè)數(shù)據(jù)了呢
image.png
-
搞定?。?!
<template>
<div class="chatList">
<ul>
<template v-for="(item,index) in list" >
<!-- 左側(cè) -->
<li v-if="$route.params.id==item.send_id" :key="index">
<div class="left">
<img :src="item.send_avatar" alt="">
</div>
<div class="right">
<p class="time">{{item.date}}</p>
<p class="content">
{{item.message}}
</p>
</div>
</li>
<!-- 右側(cè) -->
<li class="r" v-if="item.send_id==userName._id" :key="index">
<div class="right">
<p class="time">{{item.date}}</p>
<p class="content">
{{item.message}}
</p>
</div>
<div class="left">
<img :src="item.send_avatar" alt="">
</div>
</li>
</template>
</ul>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
props:['list'],
data() {
return {
}
},
computed: {
...mapGetters(['userName'])
},
}
</script>
<style lang="scss" scoped>
.chatList{
ul{
padding: 20px;
box-sizing: border-box;
li{
display: flex;
margin-bottom: 40px;
.left{
width: 92px;
height: 92px;
margin-right: 16px;
img{
width: 100%;
height: 100%;
border-radius: 50%;
}
}
.right{
flex: 1;
p{
margin: 0;
}
p.time{
font-size: 24px;
margin-bottom: 8px;
}
p.content{
font-size: 28px;
background: #00ccb8;
padding: 12px;
border-radius: 12px;
color:#fff;
display: inline-block;
}
}
}
li.r{
p.time{
text-align: right;
}
p.content{
background: #f37d7d;
text-align: left;
}
.left{
margin-left: 16px;
margin-right: 0;
}
.right{
text-align: right;
}
}
}
}
</style>
image.png
-
還有一步就是,頂部的對(duì)方的名字 需要渲染出來,無非就是在上一級(jí)頁面跳轉(zhuǎn)的時(shí)候多傳一個(gè)參數(shù),然后這個(gè)頁面接收渲染一下即可,同學(xué)們自己來試試呢??
-
淺談socketIO雙向通信數(shù)據(jù)交換技術(shù)
-
何為socketIO?
-
Socket.IO是一個(gè)庫,基于 Node.js 的實(shí)時(shí)應(yīng)用程序框架。可以在瀏覽器和服務(wù)器之間實(shí)現(xiàn)實(shí)時(shí),雙向和基于事件的通信。它適用于每個(gè)平臺(tái)、瀏覽器或設(shè)備,同樣注重可靠性和速度。
-
起源:
-
WebSocket 的產(chǎn)生源于 Web 開發(fā)中日益增長(zhǎng)的實(shí)時(shí)通信需求,對(duì)比基于 http 的輪詢方式,它大大節(jié)省了網(wǎng)絡(luò)帶寬,同時(shí)也降低了服務(wù)器的性能消耗。
-
WebSocket 協(xié)議在2008年誕生,2011年成為國際標(biāo)準(zhǔn)。雖然主流瀏覽器都已經(jīng)支持,但仍然可能有不兼容的情況,為了兼容所有瀏覽器,就誕生SocketIO。
-
SocketIO將WebSocket、AJAX和其它的通信方式全部封裝成了統(tǒng)一的通信接口,也就是說,我們?cè)谑褂肧ocketIO時(shí),不用擔(dān)心兼容問題,底層會(huì)自動(dòng)選用最佳的通信方式。
-
Socket.io有什么特點(diǎn)?
-
易用性:Socket.io封裝了服務(wù)端和客戶端,使用起來非常簡(jiǎn)單方便。
-
跨平臺(tái):Socket.io是跨平臺(tái)的,可以實(shí)現(xiàn)多平臺(tái)的即時(shí)通訊,Socket.io支持跨平臺(tái),這就意味著你有了更多的選擇,可以在自己喜歡的平臺(tái)下開發(fā)實(shí)時(shí)應(yīng)用。由于 iOS 端進(jìn)行 socket 編程主要使用 GCDAsyncSocket 框架,但要實(shí)現(xiàn) Android、iOS、web 多平臺(tái)的通訊,還是選擇統(tǒng)一的框架或協(xié)議比較好。
-
自適應(yīng):Socket.io 實(shí)現(xiàn)了實(shí)時(shí)雙向的基于事件的通訊機(jī)制,是基于 webSocket 的封裝,但它不僅僅包括 webSocket,還對(duì)輪詢(Polling)機(jī)制以及其它的實(shí)時(shí)通信方式封裝成了通用的接口,并且在服務(wù)端實(shí)現(xiàn)了這些實(shí)時(shí)機(jī)制的相應(yīng)代碼,它會(huì)自動(dòng)根據(jù)瀏覽器從WebSocket、AJAX長(zhǎng)輪詢、Iframe流等等各種方式中選擇最佳的方式來實(shí)現(xiàn)網(wǎng)絡(luò)實(shí)時(shí)應(yīng)用,非常方便和人性化,而且支持的瀏覽器最低達(dá)IE5.5。
-
Socket.io 常用內(nèi)置事件、方法
-
connect 連接socket服務(wù)方法(客戶端可使用)
-
connection 監(jiān)聽客戶端連接的事件(服務(wù)端使用on監(jiān)聽使用)
-
disconnect 監(jiān)聽客戶端斷開鏈接的事件 (服務(wù)端使用on監(jiān)聽使用)
-
emit 注冊(cè)自定義事件方法(客戶端、服務(wù)端均可使用)
-
on 監(jiān)聽自定義事件方法(客戶端、服務(wù)端均可使用)
-
Scoket.io的實(shí)際應(yīng)用
-
(注意:前后端都需要使用Socket.io才能通信)
-
服務(wù)端,基于node.js搭建服務(wù)端,需要安裝
socket.io插件
cnpm install socket.io --save
---- 代碼演示過程
var express = require('express');
var router = express.Router();
/**
* socket.io
*/
var app = express();
var server = require('http').Server(app);
var io = require('socket.io')(server, {
transports: ['websocket']
});
server.listen(3001);
/**
* 監(jiān)聽客戶端連接
*/
io.on('connection', function (socket) {
console.log("客戶端連接成功~");
});
module.exports = router;
- app.js引入這個(gè)文件,如果引入過了 就不需要再重復(fù)引入了
require("./routes/index")
后端socket服務(wù)搭建完成,socket服務(wù)地址是:
ws://localhost:3001-
客戶端,基于vue.js搭建客戶端,需要安裝
socket.io-client插件和vue-socket.io-extended插件
cnpm install socket.io-client vue-socket.io-extended --save
-
在
main.js內(nèi)引入自定義js文件socketIo.js
// 引入socket
import './utils/socketIo'
-
socketIo.js
import Vue from 'vue'
import VueSocketIOExt from 'vue-socket.io-extended';
import io from 'socket.io-client';
// 鏈接websocket
const socket = io.connect('ws://localhost:3001', {
transports: ['websocket'],
})
Vue.use(VueSocketIOExt, socket);
image.png
image.png
-
上圖就是前后端建立了一個(gè)socket通訊服務(wù)
-
下面演示如果進(jìn)行事件的注冊(cè),事件的監(jiān)聽
-
比如在前端鏈接上socket服務(wù)后,就立馬推送一條消息給服務(wù)端,然后,服務(wù)端推送給所有客戶端,說【xxx上線了,并說:xxxxx】
比如在chatDetail頁面mounted生命周期內(nèi)直接執(zhí)行事件注冊(cè)方法
mounted () {
this.$socket.client.emit('sayHello', {
name: this.$route.query.name == '孫悟空' ? '唐僧' : '孫悟空',
message: this.$route.query.name == '孫悟空' ? '為師想留在女兒國~' : '俺老孫來也~'
})
},
image.png
-
服務(wù)端需要監(jiān)聽一下這個(gè)
sayHello事件,并廣播給除自己外的所有人
/**
* 監(jiān)聽客戶端連接
*/
io.on('connection', function (socket) {
console.log("客戶端連接成功~");
//廣播給除發(fā)送者外的所有人
socket.on("sayHello",data=>{
socket.broadcast.emit("broMsg",data)
})
});
image.png
-
vue前端監(jiān)聽服務(wù)端推送的信息
sockets:{
broMsg: function (data) {
this.info = data
}
},
-
然后簡(jiǎn)單處理下前端頁面
<div class="test" v-if="info" style="font-size: 12px;text-align: center;">
<span>{{info.name}}</span>上線了,并說了一句<span>{{info.message}}</span>
</div>
-
最終效果
image.png
-
這還是只是廣播形式的雙向通信,如果要實(shí)現(xiàn)一對(duì)一的的實(shí)時(shí)通訊,服務(wù)端在推送的時(shí)候就要使用
-
to(toId)這個(gè)方法了,
toId就是對(duì)方的唯一標(biāo)識(shí),只有這樣才能推給對(duì)方,廣播是推給除了自己外的所有人 -
下面來實(shí)現(xiàn)一對(duì)一通訊
-
1、就在
chatDetail頁面,點(diǎn)擊發(fā)送按鈕 ,發(fā)送消息
image.png
methods:{
sendMsg() {
if (this.value.trim() == '') {
this.$toast('請(qǐng)輸入聊天內(nèi)容')
return
}
let obj = {
come_id: this.$route.params.id, //接收方的id
send_id: this.userName._id, //發(fā)送方的id
type: 1, //消息類型 1 文字消息
send_avatar: this.userName.avatar, //發(fā)送者的頭像
message: this.value, //發(fā)送的文字內(nèi)容
send_name: this.userName.nickname, //發(fā)送者的昵稱
date:new Date(),//發(fā)送的當(dāng)前時(shí)間
}
this.$socket.client.emit('sayOne', obj)
}
}
-
前端部分暫時(shí)告一段落,下面是【服務(wù)端】監(jiān)聽
sayOne事件
image.png
-
服務(wù)端監(jiān)聽到前端發(fā)來的數(shù)據(jù),利用
to(toId)方法發(fā)送給對(duì)方 -
在開發(fā)服務(wù)端前,前端還是要注冊(cè)觸發(fā)一個(gè)
login事件,傳給后端來做【socket用戶組】 -
在剛進(jìn)入這個(gè)頁面的時(shí)候,也就是在
mounted生命周期內(nèi),注冊(cè)觸發(fā)login事件
this.$socket.client.emit('login', this.userName._id)
-
服務(wù)端來監(jiān)聽
login事件,作用域外部定義一個(gè)空對(duì)象hashName,用來存儲(chǔ)【socket用戶組】
socket.on("login", data => {
// console.log(data);
var _id = data;
hashName[_id] = socket.id;
})
-
服務(wù)端監(jiān)聽
sayOne事件
/** 監(jiān)聽sayOne事件 */
socket.on('sayOne', data => {
// console.log(data);
var toName = data.come_id;
// console.log(hashName);
// console.log(hashName[toName]);
if (hashName[toName]) {
socket.to(hashName[toName]).emit('message', data)
}
})
-
下面回到前端,在
sockets方法函數(shù)里,監(jiān)聽服務(wù)端注冊(cè)message事件,得到服務(wù)端推過來的消息
sockets: {
message:function(data){
// console.log(data);
this.$refs.childCom.list.push(data)
},
},
-
如下圖
image.png
-
最后一步,在push下list數(shù)組,渲染右側(cè)數(shù)據(jù)
image.png
sendMsg() {
if (this.value.trim() == '') {
this.$toast('請(qǐng)輸入聊天內(nèi)容')
return
}
let obj = {
come_id: this.$route.params.id, //接收方的id
send_id: this.userName._id, //發(fā)送方的id
type: 1, //消息類型 1 文字消息
send_avatar: this.userName.avatar, //發(fā)送者的頭像
message: this.value, //發(fā)送的文字內(nèi)容
send_name: this.userName.nickname, //發(fā)送者的昵稱
date:new Date(),//發(fā)送的當(dāng)前時(shí)間
}
this.$socket.client.emit('sayOne', obj)
this.$refs.childCom.list.push(obj)
}
-
如下圖,已經(jīng)實(shí)現(xiàn)了一對(duì)一通訊
image.png
-
在最后一步,格式化下時(shí)間即可
image.png
{{$formatDate(item.date,'yyyy-MM-dd hh:mm:ss')}}
image.png


























