小张同学

创建组件-装备路由

(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://www.6hu.cc/wp-content/uploads/2024/04/241682-QXRC3Z.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://www.6hu.cc/wp-content/uploads/2024/04/241682-QXRC3Z.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应用场景:

  • 即时通讯、客服
  • 聊天室
  • 点餐

用vue2写一个聊天机器人

websocket运用-原生

用vue2写一个聊天机器人

根本过程

  1. 浏览器宣布链接恳求
  1. 服务器告知链接成功
  1. 双方进行双向通讯
  1. 封闭衔接

中心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>服务器说:${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>我说:${inputDom.value}</p>`
                    inputDom.value = ''
                }
            }
            // 封闭衔接
            btn3.onclick = function () {
                ws.close() // 封闭衔接
            }
        </script>
    </body>
</html>

socket.io 的运用

原生的 WebSocket 运用比较费事,所以推荐运用一个封装好的解决方案:socket.io

参阅:github.com/socketio/so…

  • 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
    }
},