【开箱即用】开发了一个基于环信IM聊天室的Vue3插件,从而快速实现仿直播间聊天窗功能

前言

由于看到有部分的需求为在页面层,快速的引入一个包,并且以简单的配置,就可以快速实现一个聊天窗口,因此尝试以 Vue3 插件的形式开发一个轻量的聊天窗口。

这次简单分享一下此插件的实现思路,以及实现过程,并描述一下本次插件发布 npm 的过程。

技术栈

  • Vue3
  • pnpm
  • Typescript
  • Vite

插件核心目录设计

📦 emchat-chatroom-widget
┣ 📂 build // 插件打包输出的目录
┣ 📂 demo // 验证插件demo相关目录
┣ 📂 scripts // 打包脚本目录
┣ 📂 src // 插件源代码
┃   ┣ 📂 components // 组件目录
┃   ┣ 📂 container // 容器组件目录
┃   ┣ 📂 EaseIM // 环信IM相关目录
┃   ┣ 📂 utils // 工具相关目录
┃   ┣ 📜 index.ts // 插件入口文件
┃   ┗ 📜 install.ts // 插件初始化文件
┣ 📜 package.json // 项目配置文件
┣ 📜 vite.config.ts // vite配置文件
┗ 📜 README.md // 项目说明文档
...

实现过程

确认功能范围

首先确认本次插件实现的功能范围,从而围绕要实现的功能着手进行开发准备。

  1. Vue3 框架使用
  2. 轻量配置、仅配置少量参数即可立即使用聊天功能
  3. 页面大小自适应,给定容器宽高,插件内部宽高自适应。
  4. 仅聊天室类型消息支持基础文本,表情,图片。
    暂时第一期仅支持这些功能范围。

着手开发

1、创建空白项目

pnpm create vite emchat-chatroom-widget --template vue-ts

2、配置eslint pretter 等代码校验、以及代码风格工具。

pnpm i eslint eslint-plugin-vue @typescript-eslint/eslint-plugin @typescript-eslint/parser -D
pnpm i prettier eslint-config-prettier eslint-plugin-prettier -D

同时也不要忘了创建对应的 .eslintrc.cjs.prettierrc.cjs

这里遇到了一个问题:

这几个文件以 cjs 结尾是因为 package.json 创建时设置了"type": "module" 后你的所有 js 文件默认使用 ESM 模块规范,不支持 commonjs 规范,所以必须显式的声明成 xxx.cjs 才能标识这个是用 commonjs 规范的,把你的配置都改成.cjs 后缀。

3、配置 scripts 打包脚本

目录下新建一个文件夹命名为scripts,新加一个 build.js 或者为.ts 文件。

在该文件中引入vite进行打包时的配置。由于本次插件编写时使用了jsx语法进行编写,因此 vite 打包时也需要引入 jsx 打包插件。
安装@vitejs/plugin-vue-jsx插件。

const BASE_VITE_CONFIG = defineConfig({publicDir: false, //暂不需要打包静态资源到public文件夹plugins: [vue(),vueJSX(),// visualizer({//   emitFile: true,//   filename: "stats.html"// }),dts({outputDir: './build/types',insertTypesEntry: true, // 插入TS 入口copyDtsFiles: true, // 是否将源码里的 .d.ts 文件复制到 outputDir}),],
});

package.json中增加 build 脚本执行命令,

  "scripts": {"dev": "vite","build": "vue-tsc && vite build","preview": "vite preview","lint": "eslint src --fix","build:widget": "node ./scripts/build.js"},

整体 build.js 代码由于篇幅关系,可以后面查看文末的源码地址。

4、 编写 Vue3 插件入口函数

import type { App } from 'vue';
import EasemobChatroom from './container';
import { initEMClient } from './EaseIM';
export interface IEeasemobOptions {appKey: string;
}export default {install: (app: App, options: IEeasemobOptions) => {// 在这里编写插件代码console.log(app);console.log('options', options);if (options && options?.appKey) {initEMClient(options.appKey);} else {throw console.error('appKey不能为空');}app.component(EasemobChatroom.name, EasemobChatroom);},
};

5、聊天插件入口代码

聊天插件入口组件主要用来接收插件使用者所传递进来的一些必要参数,比如登录用户 id、密码、token、聊天室 id,以及针对初始化插件的初始状态。

import { defineComponent, onMounted } from "vue"
import { EMClient } from "../EaseIM"
import { useManageChatroom } from "../EaseIM/mangeChatroom"
import { manageEasemobApis } from "../EaseIM/imApis"
import "./style/index.css"
/* components */
import MessageContainer from "./message"
import InputBarContainer from "./inputbar"
console.log("EMClient", EMClient)
export default defineComponent({name: "EasemobChatroom",props: {username: {type: String,default: "",required: true},password: {type: String,default: ""},accessToken: {type: String,default: ""},chatroomId: {type: String,default: "",required: true}},setup(props) {const { setCurrentChatroomId } = useManageChatroom()const { loginIMWithPassword, loginIMWithAccessToken } = manageEasemobApis()const loginIM = async (): Promise<void> => {if (!EMClient) returntry {if (props.accessToken) {await loginIMWithAccessToken(props.username, props.accessToken)} else {await loginIMWithPassword(props.username, props.password)}} catch (error: any) {throw `${error.data.message}`}}const closeIM = async (): Promise<void> => {console.log(">>>>>断开连接")//   EMClient.close()}onMounted(() => {loginIM()if (props.chatroomId) {setCurrentChatroomId(props.chatroomId)}})return {loginIM,closeIM}},render() {return (<><div class={"easemob_chatroom_container"}><MessageContainer /><InputBarContainer /></div></>)}
})

6、输入框组件代码

主要处理插件输入框功能,实现消息文本内容,图片内容的发送。

import { defineComponent, ref } from "vue"
import { EasemobChat } from "easemob-websdk"
import { EMClient } from "../EaseIM"
import { useManageChatroom } from "../EaseIM/mangeChatroom"
/* compoents */
import InputEmojiComponent from "../components/InputEmojiComponent"
import UploadImageComponent from "../components/UploadImageComponent"
import "./style/inputbar.css"
export enum PLACE_HOLDER_TEXT {TEXT = "Enter 发送输入的内容..."
}
export default defineComponent({name: "InputBarContainer",setup() {//基础文本发送const inputContent = ref("")const setInputContent = (event: Event) => {inputContent.value = (event.target as HTMLInputElement).value}const { currentChatroomId, loginUserInfo, sendDisplayMessage } =useManageChatroom()const sendMessage = async (event: KeyboardEvent) => {if (inputContent.value.match(/^\s*$/)) returnif (event.code === "Enter" && !event.shiftKey) {event.preventDefault()console.log(">>>>>>调用发送方法")const param: EasemobChat.CreateTextMsgParameters = {chatType: "chatRoom",type: "txt",to: currentChatroomId.value,msg: inputContent.value,from: EMClient.user,ext: {nickname: loginUserInfo.nickname}}try {await sendDisplayMessage(param)inputContent.value = ""} catch (error) {console.log(">>>>>消息发送失败", error)}}}const appendEmojitoInput = (emoji: string) => {inputContent.value = inputContent.value + emoji}return () => (<><div class={"input_bar_container"}><div class={"control_strip_container"}><InputEmojiComponent onAppendEmojitoInput={appendEmojitoInput} /><UploadImageComponent /></div><div class={"message_content_input_box"}><inputclass={"message_content_input"}type="text"value={inputContent.value}onInput={setInputContent}placeholder={PLACE_HOLDER_TEXT.TEXT}onKeyup={sendMessage}/></div></div></>)}
})

7、消息列表组件代码

渲染聊天室内收发的消息代码,以及列表滚动。

import { defineComponent, nextTick, watch } from 'vue';
import { useManageChatroom } from '../EaseIM/mangeChatroom';
import { scrollBottom } from '../utils';
import './style/message.css';
import { EasemobChat } from 'easemob-websdk';
const { messageCollect } = useManageChatroom();const MessageList = () => {const downloadSourceImage = (message: EasemobChat.MessageBody) => {if (message.type === 'img') {window.open(message.url);}};return (<>{messageCollect.length > 0 &&messageCollect.map((msgItem) => {return (<div class={'message_item_box'} key={msgItem.id}><div class={'message_item_nickname'}>{msgItem?.ext?.nickname || msgItem.from}</div>{msgItem.type === 'txt' && (<p class={'message_item_textmsg'}>{msgItem.msg}</p>)}{msgItem.type === 'img' && (<imgstyle={'cursor: pointer;'}onClick={() => {downloadSourceImage(msgItem);}}src={msgItem.thumb}/>)}</div>);})}</>);
};
export default defineComponent({name: 'MessageContainer',setup() {watch(messageCollect, () => {console.log('>>>>>>监听到消息列表改变');nextTick(() => {const messageContainer = document.querySelector('.message_container');setTimeout(() => {messageContainer && scrollBottom(messageContainer);}, 300);});});return () => {return (<><div class='message_container'><MessageList /></div></>);};},
});

8、聊天室内核心方法

聊天室内部分状态管理

import { EasemobChat } from "easemob-websdk"
import { reactive, ref } from "vue"
import { DisplayMessageType, ILoginUserInfo } from "../types/index"
import { manageEasemobApis } from "../imApis/"
const messageCollect = reactive<DisplayMessageType[]>([])
const loginUserInfo: ILoginUserInfo = {loginUserId: "",nickname: ""
}
const currentChatroomId = ref("")
export const useManageChatroom = () => {const setCurrentChatroomId = (roomId: string) => {currentChatroomId.value = roomId}const setLoginUserInfo = async (loginUserId: string) => {const { fetchLoginUserNickname } = manageEasemobApis()loginUserInfo.loginUserId = loginUserIdtry {const res = await fetchLoginUserNickname(loginUserId)loginUserInfo.nickname = res[loginUserId].nicknameconsole.log(">>>>>>获取到用户属性", loginUserInfo.nickname)} catch (error) {console.log(">>>>>>获取失败")}}const pushMessageToList = (message: DisplayMessageType) => {messageCollect.push(message)}const sendDisplayMessage = async (payload: EasemobChat.CreateMsgType) => {const { sendTextMessage, sendImageMessage } = manageEasemobApis()return new Promise((resolve, reject) => {if (payload.type === "txt") {sendTextMessage(payload).then(res => {messageCollect.push(res as unknown as EasemobChat.TextMsgBody)resolve(res)}).catch(err => {reject(err)})}if (payload.type === "img") {sendImageMessage(payload).then(res => {messageCollect.push(res as unknown as EasemobChat.ImgMsgBody)resolve(res)}).catch(err => {reject(err)})}})}return {messageCollect,currentChatroomId,loginUserInfo,setCurrentChatroomId,sendDisplayMessage,pushMessageToList,setLoginUserInfo}
}

实例化 IM SDK

import EaseSDK, { EasemobChat } from "easemob-websdk"
import { mountEaseIMListener } from "./listener"
export let EMClient = {} as EasemobChat.Connection
export const EMCreateMessage = EaseSDK.message.create
export const initEMClient = (appKey: string) => {EMClient = new EaseSDK.connection({appKey: appKey})mountEaseIMListener(EMClient)return EMClient
}

挂载聊天室相关监听监听

import { EasemobChat } from 'easemob-websdk';
import { useManageChatroom } from '../mangeChatroom';
import { manageEasemobApis } from '../imApis';
export const mountEaseIMListener = (EMClient: EasemobChat.Connection) => {const { pushMessageToList, setLoginUserInfo, currentChatroomId } =useManageChatroom();const { joinChatroom } = manageEasemobApis();console.log('>>>mountEaseIMListener');EMClient.addEventHandler('connection', {onConnected: () => {console.log('>>>>>onConnected');joinChatroom();setLoginUserInfo(EMClient.user);},onDisconnected: () => {console.log('>>>>>Disconnected');},onError: (error: any) => {console.log('>>>>>>Error', error);},});EMClient.addEventHandler('message', {onTextMessage(msg) {if (msg.chatType === 'chatRoom' && msg.to === currentChatroomId.value) {pushMessageToList(msg);}},onImageMessage(msg) {if (msg.chatType === 'chatRoom' && msg.to === currentChatroomId.value) {pushMessageToList(msg);}},});EMClient.addEventHandler('chatroomEvent', {onChatroomEvent(eventData) {console.log('>>>>chatroomEvent', eventData);},});
};

使用方式

npm install emchat-chatroom-widget
import EMChatroom from "emchat-chatroom-widget/emchat-chatroom-widget.esm.js"
//引入插件内部样式
import "emchat-chatroom-widget/style.css"
//appKey 需从环信申请
createApp(App).use(EMChatroom, {appKey: "easemob#XXX"}).mount("#app")//模版组件内使用/*** @param {username} string* @param {password} string* @param {accessToken} string* @param {chatroomId} string*/<EasemobChatroom:username="'hfp'":password="'1'":chatroomId="'208712152186885'"></EasemobChatroom>

最终效果

image.png

相关代码

Github 源码地址

npm 相关包地址

参考资料

注册环信

环信官方 Web 端相关文档

【前端工程化-组件库】从 0-1 构建 Vue3 组件库(组件开发)

使用 TSX 编写 Vue3 组件

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

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

相关文章

OpenCV(二十二):均值滤波、方框滤波和高斯滤波

目录 1.均值滤波 2.方框滤波 3.高斯滤波 1.均值滤波 OpenCV中的均值滤波&#xff08;Mean Filter&#xff09;是一种简单的滤波技术&#xff0c;用于平滑图像并减少噪声。它的原理非常简单&#xff1a;对于每个像素&#xff0c;将其与其周围邻域内像素的平均值作为新的像素值…

5.9.Webrtc线程事件处理

在前面的课程中呢&#xff0c;我已经向你介绍了事件处理的一些基础知识&#xff0c;那今天呢&#xff0c;我们再来看一下外边儿rtc下事件处理的基本逻辑是什么&#xff1f; 那首先呢&#xff0c;我们来看一下事件是如何协调线程工作的&#xff0c;那就如果这张图所展示的有两个…

简单5步骤搞定windows server2019 配置IIS支持PHP

测试成功&#xff0c;记录一笔&#xff0c;感谢网上各位大佬的技术支持。 一、安装vcredist_x64.exe 否则可能会出现 FastCGI进程意外退出 二、IIS开启CGI&#xff08;可能需要重启&#xff09; 控制面板&#xff0c;启用或关闭windows程序&#xff0c;IIS--应用程序开发--CGI…

java封装国密SM4为 jar包,PHP调用

java封装国密SM4为 jar包,PHP调用 创建java工程引入SM4 jar包封装CMD可调用jar包PHP 传参调用刚用java弄了个class给php调用,本以为项目上用到java封装功能的事情就结束了,没想到又来了java的加密需求,这玩意上头,毕竟不是强项,没办法,只好再次封装。 但是这次的有点不…

CSS笔记(黑马程序员pink老师前端)盒子阴影,文字阴影

盒子阴影 属性值为box-shadow,盒子阴影不占空间,不影响盒子之间的距离. 值说明h-shadow必需,水平阴影位置,允许为负值v-shadow必需,水平阴影位置,允许为负值blur可选,模糊距离,数值越大影子越模糊spread可选,影子的尺寸color可选,影子的颜色inset可选, 将外阴影改为内阴影(省…

GO语言网络编程(并发编程)Goroutine池

GO语言网络编程&#xff08;并发编程&#xff09;Goroutine池 1. Goroutine池 1.1.1. worker pool&#xff08;goroutine池&#xff09; 本质上是生产者消费者模型可以有效控制goroutine数量&#xff0c;防止暴涨需求&#xff1a; 计算一个数字的各个位数之和&#xff0c;例…

router-link 和 router-view的区别

router-link 实现路由之间的跳转 router-view&#xff08;路由出口组件 -> 渲染路径匹配到的视图组件&#xff09; 当你访问的地址与路由path相符时&#xff0c;会将指定的组件替换该router-view router-link router-link 点击实现路由跳转&#xff0c;to属性指向目标地址&…

VM+Ubuntu+Xshell+Xftp安装教程

目录 VM17安装教程 检查网络连接 Ubuntu环境搭建 UBUNTU 系统配置 1、 SSH 服务器配置 服务端&#xff08;必须&#xff09; 1.安装 ssh 服务端 2.确认 sshserver 是否启动了&#xff08;看见 sshd 说明已启动&#xff09; 3.启动 sshserver 4.SSH 配置&#xff08;如果…

【科普向】Jmeter 如何测试接口保姆式教程

现在对测试人员的要求越来越高&#xff0c;不仅仅要做好功能测试&#xff0c;对接口测试的需求也越来越多&#xff01;所以也越来越多的同学问&#xff0c;怎样才能做好接口测试&#xff1f; 要真正的做好接口测试&#xff0c;并且弄懂如何测试接口&#xff0c;需要从如下几个…

【数据结构面试题】栈与队列的相互实现

目录 1.队列实现栈 1.1创建栈 1.2判断是否为空 1.3入栈 1.4出栈 1.5获取栈顶元素 1.6完整代码 2. 用栈实现队列 2.1创建队列 2.2判断是否为空 2.3入队列 2.4出队列 2.5获取队头元素 2.6完整代码 1.队列实现栈 用队列实现栈https://leetcode.cn/problems/impleme…

软件设计模式(三):责任链模式

前言 前面荔枝梳理了有关单例模式、策略模式的相关知识&#xff0c;这篇文章荔枝将沿用之前的写法根据示例demo来体会这种责任链设计模式&#xff0c;希望对有需要的小伙伴有帮助吧哈哈哈哈哈哈~~~ 文章目录 前言 责任链模式 1 简单场景 2 责任链模式理解 3 Java下servl…

【MFC】实现简单UDP通信

创建项目&#xff0c;初始化套接字 创建一个基于对话框的MFC项目&#xff08;名称为UDP&#xff09;&#xff0c;高级功能选中Windows套接字 这个时候在CUDP类的InitInstance()方法中就会出现这样的代码用来初始化套接字 if (!AfxSocketInit()) {AfxMessageBox(IDP_SOCKETS_…

嵌入式基础知识-信息安全与加密

本篇来介绍计算机领域的信息安全以及加密相关基础知识&#xff0c;这些在嵌入式软件开发中也同样会用到。 1 信息安全 1.1 信息安全的基本要素 保密性&#xff1a;确保信息不被泄露给未授权的实体。包括最小授权原则、防暴露、信息加密、物理加密。完整性&#xff1a;保证数…

让GPT成为您的科研加速器丨GPT引领前沿与应用突破之GPT4科研实践技术与AI绘图

GPT对于每个科研人员已经成为不可或缺的辅助工具&#xff0c;不同的研究领域和项目具有不同的需求。如在科研编程、绘图领域&#xff1a;1、编程建议和示例代码:无论你使用的编程语言是Python、R、MATLAB还是其他语言&#xff0c;都可以为你提供相关的代码示例。​2、数据可视化…

LeetCode 34. 在排序数组中查找元素的第一个和最后一个位置

题目链接 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 题目解析 使用二分查找的方式&#xff0c;分左右端进行查找。首先使用二分找到左端的端点下标&#xff0c;然后在使用二分找到右端的端点下标。 注意事项 求mid注意事项 在使用二分找左端…

jvm 程序计算器 程序计数器是否溢出 程序计数器是做什么的 java程序计数器会内存溢出吗 程序计数器作用与用处 jvm内存模型 jvm合集(一)

1. jvm内存模型&#xff1a; 内存模型&#xff1a; 程序计数器 堆 栈 本地方法栈 方法区 2. java代码编译为class文件&#xff0c;由类加载器加载到jvm&#xff0c;然后由解释器,jit即时编译到机器码&#xff0c;机器码再到cpu执行 3. 程序计数器&#xff1a; 是一块较小的内存…

记录一次IDEA非法字符‘\ufeff‘报错

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 报错以及Bug ✨特色专栏&#xff1a; …

Hadoop生态之hive

一 概述与特点 之所以把Hive放在Hadoop生态里面去写,是因为它本身依赖Hadoop。Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供类 SQL 查询功能。 其本质是将 SQL 转换为 MapReduce/Spark 的任务进行运算,底层由 HDFS 来提供…

[uniapp]踩坑日记 unexpected character > 1或‘=’>1 报错

在红色报错文档里下滑&#xff0c;找到Show more 根据提示看是缺少标签&#xff0c;如果不是缺少标签&#xff0c;看看view标签内容是否含有<、>、>、<号,把以上符合都进行以<号为例做{{“<”}}处理

动态表单设计

动态表单设计 背景方案讨论基于上面分析&#xff0c;对比调研&#xff0c;自定义动态表单数据模型表单详解&#xff08;一&#xff09; 表单模板&#xff1a;jim_dynamic_form&#xff08;二&#xff09;表单数据类型&#xff1a;jim_form_data_type&#xff08;三&#xff09;…