express+vue在线im实现【二】

express+vue在线im实现【一】

在线体验

本期完成了:
1、心跳检测
2、支持发送表情与图片【这个目前还需要优化下,当图片上传后会被默认选中,需要点击一下旁边,使之失去选中效果,才能正常,留待下期优化吧】
3、新增了一些细节,消息固定位置,是否显示呢称,新消息来的闪烁提示等
4、将整个模块独立了出来,在博客页新增了全局挂载
5、如何处理图片的加载无法正常获取到准确高度,导致无法滚动到准确位置(这个是这期本人觉得最复杂的,等待图片加载完再获取高度,体验太差;设置固定高度,又无法兼容到小图片,大的图片有看不清;具体解决方案在下方)

下期功能

1、文件发送
2、在线语音

感兴趣的,可以私聊我,也可以点个收藏,关注,以下是核心代码示例

心跳检测

为何需要做这个,长连接不稳定,会自动断开,需要我们手动来做在线检测和重连

        // 轮询心跳检测setHeartBeat() {let { room_id } = thisclearTimeout(this.timer)this.timer = setTimeout(() => {im_heart({ room_id }).then((res) => {if (res && this.isObject(res.data) && this.isObject(res.data.data)) {let { status } = res.data.dataif (status == 2) {console.log('您已掉线,开始重新加入')this.socket.emit('join_room', {room_id,user_id: this.userdata._id,})}this.setHeartBeat()}}).catch(()=>{})}, this.heartTime)},

支持发送表情与图片

这个使用了高级css3属性来完成

    <div:id="myInputId"class="im-input kl-contenteditable-input flex-1 no-select f-14"contenteditable="true"@paste="pasteEvent($event)"@blur="blurEvent"></div>
  async pasteEvent(event) {// 尝试从 event.clipboardData 获取粘贴的项if (event.clipboardData && event.clipboardData.items) {for (let index in event.clipboardData.items) {const item = event.clipboardData.items[index]if (item.kind === 'file') {event.preventDefault()// 文件类型,将数据收集为fillet file = item.getAsFile()try {let {file: miniFile,newWidth,newHeight,} = await this.compressImg(file, 0.85)const formData = new FormData()formData.append('file', miniFile)const  devicePixelRatioa = window.devicePixelRatio || 1// 上传图片,同时需要上传图片的宽高upload_imgs_im(formData, {type: this.isIm ? 'im' : 'sys',devicePixelRatioa,width: Math.floor(newWidth / devicePixelRatioa),height: Math.floor(newHeight / devicePixelRatioa),}).then((res) => {if (res.code != 200) {return this.$message.error(res.msg || '请重新上传')}// 将返回的图片链接替换到输入框中let imgUrl = baseURL + this.filePath + res.data[0]?.filenamethis.textContent = `<img src="${imgUrl}" class="contenteditable-unpload-img" />`this.insertHtmlAtCaret(this.textContent)})} catch (err) {this.$message.warning('请重新上传')}}}}},insertHtmlAtCaret(html, element = document.querySelector('.my-input')) {// 获取当前元素的选中范围let range, selectionif (window.getSelection) {// 大多数浏览器,包括IE9+selection = window.getSelection()if (selection.rangeCount > 0) {range = selection.getRangeAt(0)} else {// 如果没有选中范围,则创建一个新的范围range = document.createRange()range.selectNodeContents(element)range.collapse(false) // 将范围设置在元素内容的末尾selection.addRange(range)}} else if (document.selection && document.selection.createRange) {// 旧版本的IErange = document.selection.createRange()}// 删除选中范围的内容(如果有的话)if (range) {range.deleteContents()// 创建一个临时元素来保存HTMLconst tempEl = document.createElement('div')tempEl.innerHTML = html// 将临时元素的内容复制到范围中while (tempEl.firstChild) {range.insertNode(tempEl.firstChild)}}},

如何解决图片高度问题

前端部分

上传:可以看到我们在上传图片时同时上传了图片的高度与宽度

  // 上传图片,同时需要上传图片的宽高upload_imgs_im(formData, {type: this.isIm ? 'im' : 'sys',devicePixelRatioa,width: Math.floor(newWidth / devicePixelRatioa),height: Math.floor(newHeight / devicePixelRatioa),}).then((res) => {if (res.code != 200) {return this.$message.error(res.msg || '请重新上传')}// 将返回的图片链接替换到输入框中let imgUrl = baseURL + this.filePath + res.data[0]?.filenamethis.textContent = `<img src="${imgUrl}" class="contenteditable-unpload-img" />`this.insertHtmlAtCaret(this.textContent)})} catch (err) {this.$message.warning('请重新上传')}

回显:直接读取链接上的宽高,来计算出需要呈现的最终宽高,这样就可以不用等到图片加载完毕,就能自动滚动到准确位置

    mounted() {let { chatItemClassName,maxWidth } = thislet imgs = document.querySelectorAll(`.${chatItemClassName} .contenteditable-unpload-img`)if (imgs && imgs.length > 0) {for (let i = 0; i < imgs.length; i++) {const item = imgs[i]item.onclick = () => {this.prevewImg(item)}// 重新设置图片的宽高const src = $(item).attr('src')let arr = src.split('~')arr = arr.filter((item) => !isNaN(+item))if (Array.isArray(arr)) {let len = arr.lengthif (len === 3) {let width = +arr[1]let height = +arr[2]if (isNaN(width) || isNaN(height)) returnif (width > maxWidth) {let scale = maxWidth / widthwidth = maxWidthheight = height * scale}$(item).css({width: width + 'px',height: height + 'px',})}}}}},

express的上传代码

这边我们需要接收宽高,并将宽高信息放到文件名上

const path = require("path");
const multer = require("multer");
module.exports = (limit = 1, file_type_name = "blog") => {let storage = multer.diskStorage({destination: function (req, file, cb) {let { type } = req.query;if (type) {file_type_name = type;}const file_path = path.resolve(__dirname,"../../public/",file_type_name);cb(null, file_path);},filename: function (req, file, cb) {let { user_id, devicePixelRatioa = 1, width = 0, height = 0 } = req.query;let fileOption = {author_id: user_id,netdisk_url: "",netdisk_name: "",netdisk_save_name: "",netdisk_size: "",netdisk_create_time: "",};let fileFormat = file.originalname.split(".");let old_name = "";fileFormat.forEach((item, index) => {if (index < fileFormat.length - 1) {old_name += item;}});let file_type = fileFormat[fileFormat.length - 1];let netdisk_save_name = `${old_name}-${Date.now()}~${devicePixelRatioa}~`;if (width && height) {netdisk_save_name += `${width}~${height}~`;}netdisk_save_name += `.${file_type}`;// 存储相关数据到自定义项fileOption.netdisk_url = file_type_name + "/" + netdisk_save_name;fileOption.netdisk_name = file.originalname || "";fileOption.netdisk_save_name = netdisk_save_name || "";fileOption.netdisk_size = file.size || 0;fileOption.netdisk_create_time = Date.now();req.fileOption = fileOption;cb(null, netdisk_save_name);},});let upload = multer({ storage: storage });// file 前端上传key也必须 都是 filelet result = upload.array("file", limit);return result;
};

本期示例

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/28408.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

桂电人工智能学院大数据实验,使用 Docker 搭建 hadoop 集群

桂电人工智能学院大数据实验&#xff0c;使用 Docker 搭建 hadoop 集群 第一步 安装 Docker, Windows 上可以使用 Docker Desktop 下载地址&#xff1a;https://www.docker.com/products/docker-desktop/ 安装过程自行谷歌 安装好的标志&#xff1a;打开终端 运行docker p…

windows11子系统Ubuntu 22.04.4子安装图形化界面

1、windows11家庭版本设置 打开虚拟机安装许可 2、Microsoft Store下载安装ubuntu 我使用的是22.04.4 LTS版本 3、 打开ubuntu 命令窗口 1、打开win11的命令行&#xff0c;在下拉三角下标&#xff0c;打开&#xff0c;可以看到有Ubuntu 的选项&#xff0c;点击即可进入linux命…

鸿蒙应用开发

学习视频&#xff1a; 00.课程介绍_哔哩哔哩_bilibili 官网&#xff1a;开发者文档中心 | 华为开发者联盟 (huawei.com) 开发工具 &#xff1a;DevEcoStudio &#xff0c; 类似Jetbrains 全家桶 ArkTS开发语言 &#xff1a;&#xff08;基于TS,集成了前端语言&#xf…

MySQL日志(二):MySQL抖动

一条SQL语句&#xff0c; 正常执行的时候特别快&#xff0c; 但是有时也不知道怎么回事&#xff0c; 它就会变得特别慢&#xff0c; 并且这样的场景很难复现&#xff0c; 它不只随机&#xff0c; 而且持续时间还很短。 看上去&#xff0c; 这就像是数据库“抖”了一下。 今天&…

c++编程(18)——deque的模拟实现(2)容器篇

欢迎来到博主的专栏——c编程 博主ID&#xff1a;代码小豪 文章目录 deque的数据结构deque的构造默认构造填充构造 deque的其他操作deque的插入、删除push_back和push_frontpop_back和pop_frontclear、erase和insert操作 传送门 在上一篇中&#xff0c;我们已经实现了deque最核…

数据仓库和数据库有什么区别?

一、什么是数据仓库二、什么是数据库三、数据仓库和数据库有什么区别 一、什么是数据仓库 数据仓库&#xff08;Data Warehouse&#xff09;是一种专门用于存储和管理大量结构化数据的信息系统。它通过整合来自不同来源的数据&#xff0c;为企业提供统一、一致的数据视图&…

【最新鸿蒙应用开发】——鸿蒙中的“Slot插槽”?@BuilderParam

构建函数-BuilderParam 传递 UI 1. 引言 BuilderParam 该装饰器用于声明任意UI描述的一个元素&#xff0c;类似slot占位符。 简而言之&#xff1a;就是自定义组件允许外部传递 UI Entry Component struct Index {build() {Column({ space: 15 }) {SonCom() {// 直接传递进来…

论文阅读:RAM++ | Open-Set Image Tagging with Multi-Grained Text Supervision

发表时间&#xff1a;2023年11月16 论文地址&#xff1a;https://arxiv.org/pdf/2310.15200 项目地址&#xff1a;https://github.com/xinyu1205/recognize-anything Recognize Anything Plus Model&#xff08;RAM&#xff09;&#xff0c;这是一种有效利用多粒度文本监督的开…

博通加速向Nvidia发起进攻 为何连iPhone 15都不能用“苹果智能”?

博通加速向Nvidia发起进攻 博通强调的一项优势是其 XPU 的能效。其功耗不到 600 瓦&#xff0c;是业内功耗最低的 AI 加速器之一。 Nvidia 的许多竞争对手都想抢占其市场主导地位。其中一个不断出现的名字是 Broadcom。仔细观察就会知道原因。其 XPU 功耗不到 600 瓦&#xff…

GPT-4并非世界模型,LeCun双手赞同!ACL力证LLM无法模拟真实世界

一直以来&#xff0c;支持LLM的观点之一是模型可以集成海量事实知识&#xff0c;作为通往「世界模拟器」的基础。虽然也有不少反对意见&#xff0c;但缺乏实证依据。那么&#xff0c;LLM能否作为世界模拟器&#xff1f; 最近&#xff0c;亚利桑那大学、微软、霍普金斯大学等机构…

飞腾银河麒麟V10安装Todesk

下载安装包 下载地址 https://www.todesk.com/linux.html 安装 yum makecache yum install libappindicator-gtk3-devel.aarch64 rpm -ivh 下载的安装包文件后台启动 service todeskd start修改配置 编辑 /opt/todesk/config/config.ini 移除自动更新临时密码 passupda…

英伟达发布Nemotron-4 340B通用模型:专为生成合成数据设计的突破性AI

引言 2023年6月14日&#xff0c;英伟达发布了Nemotron-4 340B通用模型&#xff0c;专为生成训练大语言模型的合成数据而设计。这一模型可能彻底改变训练大模型时合成数据的生成方式&#xff0c;标志着AI行业的一个重要里程碑。本文将详细介绍Nemotron-4 340B的各个方面&#x…

Vue43-单文件组件

一、脚手架的作用 单文件组件&#xff1a;xxx.vue&#xff0c;浏览器不能直接运行&#xff01;&#xff01;&#xff01; 脚手架去调用webpack等第三方工具。 二、vue文件的命名规则 建议用下面的两种方式。&#xff08;首字母大写&#xff01;&#xff01;&#xff01;&#x…

CDN简介

CDN 的基本概念 CDN&#xff08;Content Delivery Network&#xff09;&#xff0c;即内容分发网络。 CDN是一种分布式网络架构&#xff1a;它由分布在不同地理位置的服务器组成网络&#xff0c;这些服务器协同工作以提供内容服务。 内容分发的核心目标 确保用户能够快速、可…

Matlab自学笔记三十一:结构数组的创建、索引和预分配内存

1.概念 结构&#xff08;structure array&#xff09;是一种具有容器特性的数据类型&#xff0c;它使用称为字段的数据容器对相关数据进行分组&#xff0c;每个字段可以包含任何类型或大小的数据&#xff0c;所有元素都具有相同数量的字段和相同的字段名称。&#xff08;与元胞…

Java锁之舞:性能分析与优化之路

目录 一、同步锁性能分析 &#xff08;一&#xff09;性能验证说明 1. 使用同步锁的代码示例 2. 不使用同步锁的代码示例 3. 结果与讨论 &#xff08;二&#xff09;案例初步优化分析说明 1. 使用AtomicInteger原子类尝试优化分析 2. 对AtomicInteger原子类进一步优化 …

机器学习面试-常见题目

文章目录 一、框架问题1. 监督学习和无监督学习有什么不同&#xff1f;2. 什么是深度学习&#xff0c;它与机器学习算法之间有什么联系&#xff1f;3. 如何评估机器学习模型的有效性&#xff1f;4. 如何确保模型没有过拟合&#xff1f;5. 什么是核技巧&#xff0c;有什么用处&a…

4_机械臂坐标系简介

一、坐标系的标准命名 为了规范起见&#xff0c;有必要给机器人和工作空间专门命名和确定专门的“标准”坐标系。 图3-27为一种典型的工况&#xff0c;机器人抓持某种工具&#xff0c;并把工具末端移动到操作者指定的位置。图3-27所示的5个坐标系就是需要命名的坐标系。这五个坐…

7z及7zip-cpp最高压缩比的免费开源压缩软件

7z介绍 7z是一种主流高效的压缩格式&#xff0c;它拥有极高的压缩比。在计算机科学中&#xff0c;7z是一种可以使用多种压缩算法进行数据压缩的档案格式。该格式最初由7-Zip实现并采用&#xff0c;但这种档案格式是公有的&#xff0c;并且7-Zip软件本身亦在GNU宽通用公共许可证…