2022-8.21日更新 avuejs.com有一个功能也是聊天功能,试了下还不错。
前言:
发现这篇文章写的有点多,我总结一下整体思路:
首先这个功能市面上挺多的,我是参考了几家公司的功能实现,发现他们的整体功能实现和下面我的截图类似。
首先核心功能是基于websocket实现用户输入文字服务器根据用户输入返回数据渲染在页面上,而这个功能我不推荐直接使用原生,而是使用封装好的,就像文章里封装的socket.js,这个文件很简单就对外提供三个方法,一个是和后端通信连接,再一个是接收后端的数据,最后一个是发送数据给后端。
其次,我们需要一个聊天框,我使用的Jwchat插件,优点是功能比较全类似QQ聊天,使用方法也很简单,但是话说回来,这个插件的很多样式需要修改,尤其对于要做页面适配的项目,所以这个插件不是特别推荐,还有一个点就是他没有关闭按钮,做的比较粗糙,我是通过在生命周期中使用原生JS添加的关闭图标,然后再通过watch动态的选择要不要创建删除按钮。
最后说一下动画,其实就是一个聊天框,一个小框,通过点击使v-show绑定的变量来实现切换效果。这个吸附效果是我用CSS动画将原本的一个长方形框框翻转180度后在平移模拟吸附效果,其实正儿八经做的话需要计算页面宽度和元素位置计算出一个比例然后再设置一个动画效果,这个GitHub上还是有挺多的,注意的是很多实现都是移动端的,PC端用不了,是个坑。再一个动画就是打开关闭时的效果,使用的是elementUI自带的动画效果。
1.效果如下:
2.主要功能:
2.1.基于websocket实现聊天功能,封装了一个socket.js文件
2.2使用Jwchat插件实现类似QQ、微信电脑端的功能(其实并不是很好用,但考虑到后续可能会使用其功能就先用了)
2.3动画效果(如关闭打开时动画、吸附效果及其他效果)
3.实现步骤:
3.1.实现websocket聊天功能
首先封装了一个socket.js文件;需要主要的是将socket.js中URL修改成自己的
封装的websocke暴露三个接口
- sendSock用于发送数据,发给后端
- createWebSocket用于创建连接、接收数据并进行处理
- closeSock 用于关闭连接
3.2.在页面中的使用方法:
第一步:导入文件
import { sendSock, createWebSocket, closeSock } from "@/api/socket";
第二步:初始化时建立websocket连接
created() { this.init(); ...... }, methods: { init() { createWebSocket(this.global_callback); ...... }, // websocket的回调函数,msg表示收到的消息 global_callback(msg) { console.log("收到服务器信息:" + msg); }, },
关闭连接
closeSock();
发送给后端的方法
sendSock(xxx)
var websock = null; var global_callback = null; var serverPort = "80"; // webSocket连接端口 var wsuri = "ws://" + window.location.hostname + ":" + serverPort; function createWebSocket(callback) { if (websock == null || typeof websock !== WebSocket) { initWebSocket(callback); } } function initWebSocket(callback) { global_callback = callback; // 初始化websocket websock = new WebSocket(wsuri); websock.onmessage = function (e) { websocketonmessage(e); }; websock.onclose = function (e) { websocketclose(e); }; websock.onopen = function () { websocketOpen(); }; // 连接发生错误的回调方法 websock.onerror = function () { console.log("WebSocket连接发生错误"); //createWebSocket();啊,发现这样写会创建多个连接,加延时也不行 }; } // 实际调用的方法 function sendSock(agentData ) { if (websock.readyState === websock.OPEN) { // 若是ws开启状态 websocketsend(agentData); } else if (websock.readyState === websock.CONNECTING) { // 若是 正在开启状态,则等待1s后重新调用 setTimeout(function () { sendSock(agentData); }, 1000); } else { // 若未开启 ,则等待1s后重新调用 setTimeout(function () { sendSock(agentData); }, 1000); } } function closeSock() { websock.close(); } // 数据接收 function websocketonmessage(msg) { // console.log("收到数据:"+JSON.parse(e.data)); // console.log("收到数据:"+msg); // global_callback(JSON.parse(msg.data)); // 收到信息为Blob类型时 let result = null; // debugger if (msg.data instanceof Blob) { const reader = new FileReader(); reader.readAsText(msg.data, "UTF-8"); reader.onload = (e) => { result = JSON.parse(reader.result); //console.log("websocket收到", result); global_callback(result); }; } else { result = JSON.parse(msg.data); //console.log("websocket收到", result); global_callback(result); } } // 数据发送 function websocketsend(agentData) { console.log("发送数据:" + agentData); websock.send(agentData); } // 关闭 function websocketclose(e) { console.log("connection closed (" + e.code + ")"); } function websocketOpen(e) { console.log("连接打开"); } export { sendSock, createWebSocket, closeSock };
4.使用Jwchat插件实现类似QQ、微信电脑端的功能
4.1步骤
安装依赖
npm i jwchat -S
main.js 引入配置
//element 必须引入 import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI); //聊天室-基于element import Chat from 'jwchat'; Vue.use(Chat)
组件中使用
<template> <div class="jwchat"> <!-- v-model 输入框中的文字 String - "" taleList 会话内容 Array - [] toolConfig 工具栏配置 Object - {} width JwChat界面框宽度 string - 750px height JwChat界面框高度 string - 570px config 组件配置 Object - {} scrollType 消息自动到低 String scroll noroll showRightBox 显示右边内容 Boolean false true winBarConfig 多窗口配置 quickList 自动匹配快捷回复 @enter 输入框点击就发送或者回车触发的事件 输入的原始数据 @clickTalk 点击聊天框列中的用户和昵称触发事件 当前对话数据 --> <JwChat-index v-model="inputMsg" :taleList="taleList" :config="config" :showRightBox="true" scrollType="scroll" :winBarConfig="winBarConfig" :quickList="config.quickList" @enter="bindEnter" @clickTalk="talkEvent" > <!-- 窗口右边栏 --> <JwChat-rightbox :config="rightConfig" @click="rightClick" /> <!-- 快捷回复 --> <!-- <JwChat-talk :Talelist="talk" :config="quickConfig" @event="bindTalk" /> --> <!-- 工具栏自定义插槽 --> <template slot="tools"> <div style="width: 20rem; text-align: right" @click="toolEvent(12)"> <JwChat-icon type="icon-lishi" title="自定义" /> </div> </template> </JwChat-index> </div> </template> <script> const img = "https://www.baidu.com/img/flexible/logo/pc/result.png"; const listData = [ { date: "2021/03/02 13:14:21", mine: false, name: "留恋人间不羡仙", img: "https://img0.baidu.com/it/u=3066115177,3339701526&fm=26&fmt=auto&gp=0.jpg", text: { system: { title: "在接入人工前,智能助手将为您首次应答。", subtitle: "猜您想问:", content: [ { id: `system1`, text: "组件如何使用", }, { id: `system2`, text: "组件参数在哪里查看", }, { id: "system", text: "我可不可把组件用在商业", }, ], }, }, }, ]; function getListArr(size) { const listSize = listData.length; if (!size) { size = listSize; } let result = []; for (let i = 0; i < size; i++) { const item = listData[(Math.random() * listSize) >> 0]; item.id = Math.random().toString(16).substr(-6); result.push(item); } return result; } export default { components: {}, data() { return { // 输入框中的文字 inputMsg: "", // 会话内容 taleList: [], // 工具栏配置 tool: { // show: ['file', 'history', 'img', ['文件1', '', '美图']], // showEmoji: false, callback: this.toolEvent, }, // 组件配置 config: { img: "https://img1.baidu.com/it/u=2109725846,3376113789&fm=26&fmt=auto&gp=0.jpg", name: "JwChat", dept: "最简单、最便捷", callback: this.bindCover, historyConfig: { show: true, tip: "滚动到顶时候显示的提示", callback: this.bindLoadHistory, }, // 自动匹配快捷回复 quickList: [ { text: "外面的烟花奋力的燃着,屋里的人激情的说着情话", id: 10 }, { text: "假如你是云,我就是雨,一生相伴,风风雨雨;", id: 11 }, { text: "即使泪水在眼中打转,我依旧可以笑的很美,这是你学不来的坚强。", id: 12, }, { text: " 因为不知来生来世会不会遇到你,所以今生今世我会加倍爱你。", id: 13, }, ], }, }; }, methods: { // 切换用户窗口,加载对应的历史记录 bindWinBar(play = {}) { const { type, data = {} } = play; console.log(play); if (type === "winBar") { const { id, dept, name, img } = data; this.config = { ...this.config, id, dept, name, img }; this.winBarConfig.active = id; if (id === "win00") { this.taleList = getListArr(); } else this.taleList = getListArr((Math.random() * 4) >> 0); } if (type === "winBtn") { const { target: { id } = {} } = data; const { list } = this.winBarConfig; this.winBarConfig.list = list.reduce((p, i) => { if (id != i.id) p.push(i); return p; }, []); } }, // 点击聊天框列中的用户和昵称触发事件 talkEvent(play) { console.log(play); }, // 输入框点击就发送或者回车触发的事件 bindEnter(e) { console.log(e); const msg = this.inputMsg; if (!msg) return; const msgObj = { date: "2020/05/20 23:19:07", text: { text: msg }, mine: true, name: "JwChat", img: "https://img1.baidu.com/it/u=31094377,222380373&fm=26&fmt=auto&gp=0.jpg", }; this.taleList.push(msgObj); }, /** * @description: 点击加载更多的回调函数 * @param {*} * @return {*} */ bindLoadHistory() { const history = new Array(3).fill().map((i, j) => { return { date: "2020/05/20 23:19:07", text: { text: j + new Date() }, mine: false, name: "JwChat", img: "https://img1.baidu.com/it/u=31094377,222380373&fm=26&fmt=auto&gp=0.jpg", }; }); let list = history.concat(this.list); this.taleList = list; console.log("加载历史", list, history); }, /** * @description: * @param {*} type 当前点击的按钮 * @param {*} plyload 附加文件或者需要处理的数据 * @return {*} */ toolEvent(type, plyload) { console.log("tools", type, plyload); }, bindCover(event) { console.log("header", event); }, rightClick(type) { console.log("rigth", type); }, }, mounted() { this.taleList = getListArr(); }, }; </script> <style> .jwchat { height: 100vh; font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
5.动画效果
吸附效果
使用v-show绑定变量控制显示隐藏
// 吸附效果 xiFu () { setTimeout(() => { //10秒后自动隐藏小空间转为吸附效果 this.isMouse = false }, 5000) },
@keyframes move { 0% { transform: translateX(0px) rotateY(20deg); } 100% { transform: translateX(1.0417rem) rotateY(180deg); } } .cssDongHua { animation: move 2s linear 1s 1 alternate forwards; } //template :class="isMouse ? '' : 'cssDongHua'" @click="isOpen = !isOpen" v-show="!isOpen" @mouseenter="isMouse = true"