用vue2写一个聊天机器人

317 阅读1分钟

小张同学

创建组件-配置路由

(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使用-原生

基本步骤

  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 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

参考: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
    }
},