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,一经查实,立即删除!

相关文章

Gone框架介绍27 - 再讲 Goner 和 依赖注入

gone是可以高效开发Web服务的Golang依赖注入框架 github地址&#xff1a;https://github.com/gone-io/gone 文档地址&#xff1a;https://goner.fun/zh/ 文章目录 Goner 和 依赖注入Goner的定义依赖标记Goners 注册Priest函数 Goner 和 依赖注入 Gone 作为一个依赖注入框架&am…

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

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

矩阵补全学习笔记

目录 矩阵补全概念原理 ICLR 2020 | 超越传统&#xff0c;基于图神经网络的归纳矩阵补全 因果面板数据模型的矩阵补全方法 矩阵补全概念原理 【综述】矩阵补全问题-CSDN博客 GitHub - XLearning-SCU/2022-CVPR-AirNet: PyTorch implementation for All-In-One Image Restor…

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

pdb restore flashback recover 的三个办法 + CDB 级还原 注意数据库实际时间

Recover可以drop掉PDB&#xff0c;另外两个不行&#xff01;&#xff01; 除非CDB级还原 千万要注意好数据库时间 RMAN> recover pluggable database pdb until time "to_date(16-JUN-2024 19:00:00,DD-MON-YYYY HH24:MI:SS)" auxiliary destination data1; S…

鸿蒙应用开发

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

wifiphisher详细安装教程

仅用于学习&#xff1a; 1、先进入root权限&#xff1b; 2、下载roguehostpad文件&#xff1b; git clone https://github.com/wifiphisher/roguehostapd.git3、进入roguehostpad文件夹&#xff1b; cd roguehostapd4、检查roguehostpad文件夹内的setup.py颜色是否变绿&#…

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最核…

springboot整合redis以及使用

在Spring Boot中整合Redis&#xff0c;并使用Redis作为缓存或数据存储&#xff0c;是非常常见和有用的场景。Redis作为一种高性能的键值存储系统&#xff0c;可以用来加速数据访问、会话管理、消息队列等多种用途。下面是整合和使用Redis的基本步骤&#xff1a; 1. 添加Redis依…

数学中常用希腊字母发音读法

序号大写小写英文注音国际音标注音中文注音1Ααalphaa:lf阿尔法2Ββbetabet贝塔3Γγgammaga:m伽马4Δδdeltadelt德尔塔5Εεepsilonepsilon伊普西龙6Ζζzetazat截塔7Ηηetaeit艾塔8Θθthetθit西塔9Ιιiotaiot约塔10Κκkappakap卡帕11∧λlambdalambd兰布达12Μμmumj…

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

一、什么是数据仓库二、什么是数据库三、数据仓库和数据库有什么区别 一、什么是数据仓库 数据仓库&#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() {// 直接传递进来…

测试基础(一)

测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础测试基础…

Outlook 邮箱使用技巧大全

前言 Microsoft Outlook 不仅是一个电子邮件客户端&#xff0c;还是一个强大的时间管理工具。无论你是个人用户还是企业用户&#xff0c;了解如何高效使用Outlook都可以帮助你提升工作效率。下面&#xff0c;我将为你介绍几个实用的Outlook邮箱使用技巧。 技巧汇总 1. 邮件分…

论文阅读: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…

代码随想录算法训练营第七天| 454.四数相加II |383. 赎金信 |15. 三数之和 |18. 四数之和

454.四数相加II 文档讲解&#xff1a;代码随想录 视频讲解&#xff1a;学透哈希表&#xff0c;map使用有技巧&#xff01;LeetCode&#xff1a;454.四数相加II_哔哩哔哩_bilibili 1. 暴力算法。 2. 先两个循环将和放到map中&#xff0c;再两个循环求和查询map&#xff0c;计算…