小张同学
创建组件-配置路由
(1) 创建组件user/chat.vue
<template>
<div class="chat">小张机器人</div>
</template>
<script>
export default {}
</script>
<style></style>
(2) 配置路由
{
path: '/user/chat',
component: Chat
},
(3) 基本结构与样式
<template>
<div class="container">
<van-nav-bar
fixed
left-arrow
@click-left="$router.back()"
title="小张同学"
></van-nav-bar>
<div class="chat-list">
<!-- 左侧是机器人小张 -->
<div class="chat-item left">
<van-image fit="cover" round src="https://img.yzcdn.cn/vant/cat.jpeg" />
<div class="chat-pao">hi,你好!</div>
</div>
<!-- 右侧是当前用户 -->
<div class="chat-item right">
<div class="chat-pao my">ewqewq</div>
<van-image fit="cover" round src="https://img.yzcdn.cn/vant/cat.jpeg" />
</div>
</div>
<div class="reply-container van-hairline--top">
<van-field v-model.trim="word" placeholder="说点什么...">
<span @click="send()" slot="button" style="font-size:12px;color:#999">
提交
</span>
</van-field>
</div>
</div>
</template>
<script>
export default {
name: 'UserChat',
data() {
return {
word: ''
}
},
methods: {
send() {
console.log(this.word)
}
}
}
</script>
<style lang="less" scoped>
.container {
height: 100%;
width: 100%;
position: absolute;
left: 0;
top: 0;
box-sizing: border-box;
background: #fafafa;
padding: 46px 0 50px 0;
.chat-list {
height: 100%;
overflow-y: scroll;
.chat-item {
padding: 10px;
.van-image {
vertical-align: top;
width: 40px;
height: 40px;
}
.chat-pao {
vertical-align: top;
display: inline-block;
min-width: 40px;
max-width: 70%;
min-height: 40px;
line-height: 38px;
border: 0.5px solid #c2d9ea;
border-radius: 4px;
position: relative;
padding: 0 10px;
background-color: #e0effb;
word-break: break-all;
font-size: 14px;
color: #333;
&::before {
content: '';
width: 10px;
height: 10px;
position: absolute;
top: 12px;
border-top: 0.5px solid #c2d9ea;
border-right: 0.5px solid #c2d9ea;
background: #e0effb;
}
}
.chat-pao.my {
background-color: #9eea6a;
&::before {
content: '';
background: #9eea6a;
}
}
}
}
}
.chat-item.right {
text-align: right;
.chat-pao {
margin-left: 0;
margin-right: 15px;
&::before {
right: -6px;
transform: rotate(45deg);
}
}
}
.chat-item.left {
text-align: left;
.chat-pao {
margin-left: 15px;
margin-right: 0;
&::before {
left: -5px;
transform: rotate(-135deg);
}
}
}
.reply-container {
position: fixed;
left: 0;
bottom: 0;
height: 44px;
width: 100%;
background: #f5f5f5;
z-index: 9999;
}
</style>
头像处理
(1) 小张头像
vant-contrib.gitee.io/vant/#/zh-C…
使用van-image处理本地资源时,需要额外处理。
<van-image fit="cover" round :src="require('@/assets/avatar.png')" />
(2) 处理自己的头像
<van-image fit="cover" round :src="$store.state.user.userInfo.photo" />
(3)进入组件,获取个人信息
created() {
if (!this.userInfo.photo) {
this.Actions_getUserInfo()
}
},
methods: {
...mapActions('user', ['Actions_getUserInfo']),
}
聊天信息数据结构且渲染
-
约定数据结构
list: [
// type: 1 小张的消息
// type: 2 用户发的消息
{
type: 1,
msg: '你好,我是小张',
timestamp: Date.now()
},
{
type: 2,
msg: '我是编程小王子!',
timestamp: Date.now()
},
{
type: 1,
msg: '你以为会在百度上抄代码,就是程序员了吗?',
timestamp: Date.now()
},
{
type: 2,
msg: '不是这样吗?',
timestamp: Date.now()
}
]
-
渲染
<div class="chat-list">
<div v-for="(item, index) in list" :key="index">
<!-- 左侧是机器人小张 -->
<div class="chat-item left" v-if="item.type === 1">
<van-image fit="cover" round :src="require('@/assets/avatar.png')" />
<div class="chat-pao">{{item.msg}}</div>
</div>
<!-- 右侧是当前用户 -->
<div class="chat-item right" v-else>
<div class="chat-pao my">{{item.msg}}</div>
<van-image fit="cover" round :src="$store.state.user.userInfo.photo" />
</div>
</div>
</div>
websocket
WebSocket 是一种数据通信协议,类似于我们常见的 http 协议。
为什么需要 WebSocket?
初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?
答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起。
举例来说,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。
这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询":每隔一段时候,就发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室。
轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。
websocket简介
WebSocket 协议在2008年诞生,2011年成为国际标准。所有现代浏览器都已经支持了。
它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
典型的websocket应用场景:
- 即时通讯、客服
- 聊天室
-
点餐
websocket使用-原生
基本步骤
- 浏览器发出链接请求
- 服务器告知链接成功
- 双方进行双向通讯
-
关闭连接
核心api
// 打开websocket连接
// WebSocket 是浏览器的内置对象
var ws = new WebSocket('wss://echo.websocket.org') // 建立与服务端地址的连接
// 如果与服务器建立连接成功, 调用 websocket实例的 回调函数 onopen
ws.onopen = function () {
// 如果执行此函数 表示与服务器建立关系成功
}
// 发送消息
ws.send('消息')
// 接收消息
ws.onmessage = function (event) {
// event中的data就是服务器发过来的消息
}
// 关闭连接
ws.close()
// 关闭连接成功
ws.onclose = function () {
}
示例demo
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>体验websocket</title>
<style>
#contanier {
width: 500px;
height: 400px;
border: 2px dashed #7575e7;
overflow-y: auto;
}
</style>
</head>
<body>
<div id="contanier"></div>
<!-- 1 建立连接 (拨号) -->
<!-- 2 发消息 接消息 -->
<!-- 3 关闭连接 -->
<input type="text" id="message" />
<button id="btn1" >建立连接</button>
<button id="btn2" >发送消息</button>
<button id="btn3" >关闭连接</button>
<script>
var dom = document.getElementById('contanier')
var inputDom = document.getElementById('message')
var btn1 = document.getElementById('btn1')
var btn2 = document.getElementById('btn2')
var btn3 = document.getElementById('btn3')
var isOpen = false // 表示是否已经建立了拨号
var ws // 别的方法 也需要使用ws
// 打开websocket连接
btn1.onclick = function () {
/// 网络上提供的一个测试websocket功能的服务器地址。
/// 它的效果是,你向服务器发什么消息 ,它就完全回复还给你。
ws = new WebSocket('wss://echo.websocket.org') // 建立与服务器的联系
// onopen是webSocket约定事件名
// 当本地客户端浏览器与服务器建立连接之后,就会执行onopen的回调
ws.onopen = function (event) {
isOpen = true
// 建立成功
dom.innerHTML = dom.innerHTML + `<p>与服务器成功建立连接</p>`
}
// 接收消息
// onmessage是webSocket约定事件名
// 如果从服务器上发过来了消息,则会进入onmessage的回调
ws.onmessage = function (event) {
// 由于 我们先给服务器发了消息 服务器给我们回了消息
dom.innerHTML =
dom.innerHTML + `<p style='color: blue'>服务器说:${event.data}</p>`
}
// onclose是webSocket约定事件名
ws.onclose = function () {
// 此函数表示 关闭连接成功
isOpen = false // 把状态关闭掉
dom.innerHTML = dom.innerHTML + `<p>与服务器连接关闭</p>`
}
}
// 发送消息 接收消息
btn2.onclick = function () {
if (inputDom.value && isOpen) {
// 发消息 要等到 连接成功才能发 而且内容不为空
// 发消息就是send
ws.send(inputDom.value) // 发送消息
// 发完之后 添加到 当前视图上
dom.innerHTML =
dom.innerHTML + `<p style='color: red'>我说:${inputDom.value}</p>`
inputDom.value = ''
}
}
// 关闭连接
btn3.onclick = function () {
ws.close() // 关闭连接
}
</script>
</body>
</html>
socket.io 的使用
原生的 WebSocket 使用比较麻烦,所以推荐使用一个封装好的解决方案:socket.io
- socket.io是一套解决方案:即有前端也有后端;对各种不同的语言都有支持。
- 在写前端代码时,只要引入它的客户端即可。
- 安装包 socket.io-client 导入使用
import io from 'socket.io-client'
- 建立连接
const socket = io('地址',{额外传参})等同于 原生websocketnew WebSocket()
- 发消息:
socket.emit('自定义消息名', '内容');
- 收消息:
socket.on('自定义消息名', function(msg){}
-
关闭链接:
socket.close()
小张同学-通讯
基本实现
-
安装依赖包, 本项目中,不用考虑服务器端代码,因此只需要安装客户端的包
npm i socket.io-client
# or
yarn add socket.io-client
-
导入依赖包
import io from 'socket.io-client'
-
组件初始化时,连接websocket
created() {
this.$store.dispatch('user/getUserInfo')
// 链接服务器
const socket = io('http://toutiao.itheima.net', {
query: {
token: this.$store.state.user.token.token
},
transports: ['websocket']
})
this.socket = socket
// 当连接上服务器就会触发
socket.on('connect', () => {
this.$toast.success('连接服务器成功')
})
// 当服务器给我们发送消息会触发
socket.on('message', (data) => {
console.log('服务器给我的消息', data)
this.list.push({
type: 1,
msg: data.msg,
timestamp: data.timestamp
})
})
}
-
点击按钮时,发送消息给小张
send() {
if (!this.word) {
return this.$toast('请输入聊天的内容')
}
this.list.push({
type: 2,
msg: this.word,
timestamp: Date.now()
})
this.socket.emit('message', {
msg: this.word,
timestamp: Date.now()
})
this.word = ''
}
}
-
回车发送功能
<div class="reply-container van-hairline--top">
<van-field
@keyup.enter="send"
v-model.trim="word"
placeholder="说点什么..."
>
<span @click="send" slot="button" style="font-size:12px;color:#999">
提交
</span>
</van-field>
</div>
组件销毁的时候
beforeDestroy() {
this.socket.close()
},
滚动到底部
在对话时,内容区域滚动条不能自己到达底部
watch: {
async list() {
// 监听list数组的变化 ==> 一旦list变化了,把chat-list滚动到最底部 ==> 修改其chat-list的scrollTop,给一个大值
// console.log('list 改变了')
await this.$nextTick()
// this.$refs.chatBox.scrollTop = 1000000000000000
// 以上代码换种写法
this.$refs.chatBox.scrollTop = this.$refs.chatBox.scrollHeight
}
},