一、项目简介

基于vue3.x+vuex+vue-router+element-plus+v3layer+v3scroll等技术构建的仿微信web桌面端聊天实战项目Vue3-Webchat。基本上实现发送消息+emoj表情、图片/视频查看、链接预览、粘贴截图/拖拽发送图片、红包/朋友圈等功能。

二、使用技术

  • 编码器:Vscode
  • 技术框架:Vue3.0.5+Vuex4+VueRouter@4
  • UI组件库:Element-Plus (饿了么桌面端vue3组件库)
  • 弹窗组件:V3Layer(基于vue3.x自定义对话框组件)
  • 滚动条组件:V3Scroll(基于vue3.x自定义虚拟美化滚动条组件)
  • 字体图标:阿里iconfont图标库

三、项目结构目录

◆ 一览效果

◆ vue3.x封装自定义弹窗组件

为了整体效果一致性,项目中用到的所有弹窗功能均是自定义组件v3layer来实现。

V3Layer 基于vue3.0开发的pc端自定义弹窗组件,支持拖拽(自定义拖拽区)、缩放、最大化、全屏、置顶弹框等功能。

由于之前有过一篇详细的介绍分享,感兴趣的话可以去看下哈。

https://www.cnblogs.com/xiaoyan2017/p/14221729.html

其实v3layer弹窗是在原先的vue2版本中演变而来,专门为vue3项目而开发的,并且在功能及效果上和v2版的保持一致。

vue2.x pc端自定义全局弹窗组件|vue2桌面端对话框组件

◆ vue3.x自定义美化模拟滚动条

为了使得项目中页面滚动条更加精致,这里采用了自定义模拟滚动条vscroll组件来替代原生滚动条。

V3Scroll 基于vue3.0开发的小巧模拟滚动条组件。支持自定义滚动条大小、颜色、层级及自动隐藏等功能。

并且支持实时监测DOM尺寸改变来动态更新滚动条。

https://www.cnblogs.com/xiaoyan2017/p/14242983.html

◆ vue3.x聊天主面板

项目整体分为右上按钮、侧边栏、中间区、主体内容区三个模块。

<div :class="['vui__wrapper', store.state.isWinMaximize&&'maximize']">
<div class="vui__board flexbox">
<div class="flex1 flexbox">
<!-- 顶部按钮(最大、最小、关闭) -->
<WinBar v-if="!route.meta.hideWinBar" /> <!-- 侧边栏 -->
<SideBar v-if="!route.meta.hideSideBar" class="nt__sidebar flexbox flex-col" /> <!-- 中间栏 -->
<Middle v-show="!route.meta.hideMiddle" /> <!-- 主内容区 -->
<router-view class="nt__mainbox flex1 flexbox flex-col"></router-view>
</div>
</div>
</div>

◆ 引入|注册公共组件

// 引入饿了么vue3组件库
import ElementPlus from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css' // 引入vue3.x弹窗组件
import V3Layer from '../components/v3layer' // 引入vue3.x滚动条组件
import V3Scroll from '@components/v3scroll' // 引入公共组件
import WinBar from '../layouts/winbar.vue'
import SideBar from '../layouts/sidebar'
import Middle from '../layouts/middle' import Utils from './utils' const Plugins = app => {
app.use(ElementPlus)
app.use(V3Layer)
app.use(V3Scroll) // 注册公共组件
app.component('WinBar', WinBar)
app.component('SideBar', SideBar)
app.component('Middle', Middle) app.provide('utils', Utils)
}

项目中背景整体采用虚化毛玻璃效果。通过 svg filter 来实现。

<!-- //虚化背景(毛玻璃) -->
<div class="vui__bgblur">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" class="blur-svg" viewBox="0 0 1920 875" preserveAspectRatio="none">
<filter id="blur_mkvvpnf"><feGaussianBlur in="SourceGraphic" stdDeviation="50"></feGaussianBlur></filter>
<image :xlink:href="store.state.skin" x="0" y="0" width="100%" height="100%" externalResourcesRequired="true" xmlns:xlink="http://www.w3.org/1999/xlink" style="filter:url(#blur_mkvvpnf)" preserveAspectRatio="none"></image>
</svg>
<div class="blur-cover"></div>
</div>

◆ vue3.x表单验证/登录状态拦截

vue3中实现表单验证+60s倒计时操作。

<script>
import { reactive, toRefs, inject, getCurrentInstance } from 'vue'
export default {
components: {},
setup() {
const { ctx } = getCurrentInstance()
const v3layer = inject('v3layer')
const utils = inject('utils') const formObj = reactive({})
const data = reactive({
vcodeText: '获取验证码',
disabled: false,
time: 0,
}) const VTips = (content) => {
v3layer({
content: content, layerStyle: 'background:#ff5151;color:#fff;', time: 2
})
} const handleSubmit = () => {
if(!formObj.tel){
VTips('手机号不能为空!')
}else if(!utils.checkTel(formObj.tel)){
VTips('手机号格式不正确!')
}else if(!formObj.pwd){
VTips('密码不能为空!')
}else if(!formObj.vcode){
VTips('验证码不能为空!')
}else{
ctx.$store.commit('SET_TOKEN', utils.setToken());
ctx.$store.commit('SET_USER', formObj.tel); // ...
}
} // 60s倒计时
const handleVcode = () => {
if(!formObj.tel) {
VTips('手机号不能为空!')
}else if(!utils.checkTel(formObj.tel)) {
VTips('手机号格式不正确!')
}else {
data.time = 60
data.disabled = true
countDown()
}
}
const countDown = () => {
if(data.time > 0) {
data.vcodeText = '获取验证码('+ data.time +')'
data.time--
setTimeout(countDown, 1000)
}else{
data.vcodeText = '获取验证码'
data.time = 0
data.disabled = false
}
} return {
formObj,
...toRefs(data),
handleSubmit,
handleVcode
}
}
}
</script>

vue3路由钩子实现全局登录状态拦截判断。

import { createRouter, createWebHistory } from 'vue-router'

import store from '../store'

import V3Layer from '@components/v3layer'

const routesLS = [
// 登录|注册
{
name: 'login', path: '/login',
component: () => import('../views/auth/login.vue'),
meta: { hideWinBar: true, hideSideBar: true, hideMiddle: true }
}, // ...
] const router = createRouter({
history: createWebHistory(),
routes: routesLS,
}) // 全局钩子拦截登录状态
router.beforeEach((to, from, next) => {
const token = store.state.token // 判断当前路由地址是否需要登录权限
if(to.meta.requireAuth) {
if(token) {
next()
}else {
// 未登录授权
V3Layer({
content: '还未登录授权!', position: 'top', time: 2,
onEnd: () => {
next({ path: '/login' })
}
})
}
}else {
next()
}
})

◆ vue3.x聊天模块

聊天编辑器模块继续采用分离公共调用方式。支持多行文本、文字+emoj表情混排、光标处插入表情、粘贴截图发送等功能。

/**
* @Desc vue3.x仿微信桌面端聊天
* @Time andy by 2021-01
* @About Q:282310962 wx:xy190310
*/
<script>
import { onMounted, ref, reactive, toRefs, watch, nextTick, inject } from 'vue'
import { useRoute } from 'vue-router' import Editor from './editor.vue'
import SendRedPacket from './redPacket.vue'
import GroupSet from './groupInfo.vue' // ... export default {
components: {
Editor,
SendRedPacket,
GroupSet
},
setup() {
const scrollRef = ref(null)
const editorRef = ref(null) const route = useRoute() const v3layer = inject('v3layer') const data = reactive({
editorText: '', showEmojView: false, isSubmitDisabled: true, // ...
}) // ... // 获取群组信息
const getGroupJSON = () => {
msgJSON.map((item) => {
if(item.cid == route.query.id) {
data.groupLs = item
}
})
// 定位消息到底部
nextTick(() => {
imgLoaded(scrollRef)
})
} // ... /**
* 编辑器粘贴事件
* @param img 返回粘贴图片地址
*/
const handleEditorPaste = (img) => {
let msgLs = data.groupLs.msglist
let len = msgLs.length
// 消息队列
let arrLS = {
// ...
}
msgLs = msgLs.concat(arrLS)
data.groupLs.msglist = msgLs nextTick(() => { imgLoaded(scrollRef) })
} // 点击表情
const handleEmojClicked = (e) => {
let faceimg = e.target.cloneNode(true)
editorRef.value.insertHtmlAtCursor(faceimg)
data.showEmojView = false
} // 点击表情gif
const handleEmojGifClicked = (path) => {
let msgLs = data.groupLs.msglist
let len = msgLs.length
// 消息队列
let arrLS = {
// ...
}
msgLs = msgLs.concat(arrLS)
data.groupLs.msglist = msgLs
data.showEmojView = false nextTick(() => { imgLoaded(scrollRef) })
} /* ---------- { 选择功能模块 } ---------- */
// 选择视频
const handleChooseVideo = () => {
let msgLs = data.groupLs.msglist
let len = msgLs.length
// 消息队列
let arrLS = {
// ...
} let file = pickVideoRef.value.files[0]
if(!file) return
let size = Math.floor(file.size / 1024)
if(size > 5*1024) {
v3layer({content: '请选择5MB以内的视频!'})
return false
}
// 获取视频地址
let videoUrl
if(window.createObjectURL != undefined) {
videoUrl = window.createObjectURL(file)
} else if (window.URL != undefined) {
videoUrl = window.URL.createObjectURL(file)
} else if (window.webkitURL != undefined) {
videoUrl = window.webkitURL.createObjectURL(file)
} let $video = document.createElement('video')
$video.src = videoUrl
// 截取视频第一帧为封面
$video.addEventListener('loadeddata', function() {
setTimeout(() => {
var canvas = document.createElement('canvas')
canvas.width = $video.videoWidth * .8
canvas.height = $video.videoHeight * .8
canvas.getContext('2d').drawImage($video, 0, 0, canvas.width, canvas.height)
arrLS.imgsrc = canvas.toDataURL('image/png') arrLS.videosrc = videoUrl
msgLs = msgLs.concat(arrLS)
data.groupLs.msglist = msgLs nextTick(() => { imgLoaded(scrollRef) })
}, 16);
})
} /* ---------- { 拖拽功能模块 } ---------- */
const handleDragEnter = (e) => {
e.stopPropagation()
e.preventDefault()
}
const handleDragOver = (e) => {
e.stopPropagation()
e.preventDefault()
}
const handleDrop = (e) => {
e.stopPropagation()
e.preventDefault()
// console.log(e.dataTransfer) handleFileList(e.dataTransfer)
}
// 获取拖拽文件列表
const handleFileList = (filelist) => {
let files = filelist.files
if(files.length >= 2) {
v3layer.message({icon: 'error', content: '暂时支持拖拽一张图片', shade: true, layerStyle: {background:'#ffefe6',color:'#ff3838'}})
return false
}
for(let i = 0; i < files.length; i++) {
if(files[i].type != '') {
handleFileAdd(files[i])
}else {
v3layer.message({icon: 'error', content: '目前不支持文件夹拖拽功能', shade: true, layerStyle: {background:'#ffefe6',color:'#ff3838'}})
}
}
}
const handleFileAdd = (file) => {
let msgLs = data.groupLs.msglist
let len = msgLs.length
// 消息队列
let arrLS = {
// ...
} if(file.type.indexOf('image') == -1) {
v3layer.message({icon: 'error', content: '目前不支持非图片拖拽功能', shade: true, layerStyle: {background:'#ffefe6',color:'#ff3838'}})
}else {
let reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = function() {
let img = this.result // ...
}
}
} /* ---------- { 其他功能模块 } ---------- */
// 提示信息
const handleTipsLayer = (e) => {
let pos = [e.clientX+25, e.clientY-110]
v3layer.popover({
icon: 'info',
title: 'Tips',
content: '<div class="pb-10">编辑框支持<b class="bg-00e077 c-fff">拖拽</b>或<b class="bg-00e077 c-fff">截屏粘贴</b>发送图片!<br />支持自动<b class="bg-00e077 c-fff">链接</b>识别!</div>',
follow: pos,
shade: true,
opacity: .2,
})
} // 红包弹窗
const handleRedpacketLayer = (item) => {
data.isShowRedPacket = true
data.redPacketList = item
} // ... return {
...toRefs(data), scrollRef,
editorRef, handleMsgClicked, handleEmojView,
handleEmojTab, handleEditorClick,
handleEditorFocus,
handleEditorBlur,
handleEditorPaste,
handleEmojClicked, // ...
}
}
}
</script>

Ok,以上就是使用Vue3+ElementPlus开发网页端仿微信/QQ界面聊天的分享。

Vue3.0网页版聊天|Vue3.x+ElementPlus仿微信/QQ界面|vue3聊天实例的更多相关文章

  1. electron聊天室|vue+electron-vue仿微信客户端|electron桌面聊天

    一.项目概况 基于Electron+vue+electron-vue+vuex+Nodejs+vueVideoPlayer+electron-builder等技术仿制微信电脑端界面聊天室实例,实现消息 ...

  2. Tauri-Vue3桌面端聊天室|tauri+vite3仿微信|tauri聊天程序EXE

    基于tauri+vue3.js+vite3跨桌面端仿微信聊天实例TauriVue3Chat. tauri-chat 运用最新tauri+vue3+vite3+element-plus+v3layer等 ...

  3. Taro聊天室|react+taro仿微信聊天App界面|taro聊天实例

    一.项目简述 taro-chatroom是基于Taro多端实例聊天项目,运用Taro+react+react-redux+taroPop+react-native等技术开发的仿微信App界面聊天室,实 ...

  4. Svelte3聊天室|svelte+svelteKit仿微信聊天实例|svelte.js开发App

    基于svelte3.x+svelteKit构建仿微信App聊天应用svelte-chatroom. svelte-chatroom 基于svelte.js+svelteKit+mescroll.js+ ...

  5. Vue3.0聊天室|vue3+vant3仿微信聊天实例|vue3.x仿微信app界面

    一.项目简介 基于Vue3.0+Vant3.x+Vuex4.x+Vue-router4+V3Popup等技术开发实现的仿微信手机App聊天实例项目Vue3-Chatroom.实现了发送图文表情消息/g ...

  6. Vue3.0短视频+直播|vue3+vite2+vant3仿抖音界面|vue3.x小视频实例

    基于vue3.0构建移动端仿抖音/快手短视频+直播实战项目Vue3-DouYin. 5G时代已来,短视频也越来越成为新一代年轻人的娱乐方式,在这个特殊之年,又将再一次成为新年俗! 基于vue3.x+v ...

  7. 网页闯关游戏(riddle webgame)--仿微信聊天的前端页面设计和难点

    前言: 之前编写了一个网页闯关游戏(类似Riddle Game), 除了希望大家能够体验一下我的游戏外. 也愿意分享编写这个网页游戏过程中, 学到的一些知识. 本文讲描述, 如何在网页端实现一个仿微信 ...

  8. vue聊天室|h5+vue仿微信聊天界面|vue仿微信

    一.项目简介 基于Vue2.0+Vuex+vue-router+webpack2.0+es6+vuePhotoPreview+wcPop等技术架构开发的仿微信界面聊天室——vueChatRoom,实现 ...

  9. html5聊天案例|趣聊h5|仿微信界面聊天|红包|语音聊天|地图

    之前有开发过一个h5微直播项目,当时里面也用到过聊天模块部分,今天就在之前聊天部分的基础上重新抽离模块,开发了这个h5趣聊项目,功能效果比较类似微信聊天界面.采用html5+css3+Zepto+sw ...

随机推荐

  1. 如何理解java枚举,看例子

    先来看一下不用枚举怎么表示常量: //常量类 class Num { public static String ONE = "ONE"; public static String ...

  2. 这一次,彻底理解XSS攻击

    希望读完本文大家彻底理解XSS攻击,如果读完本文还不清楚,我请你吃饭慢慢告诉你~ 话不多说,我们进入正题. 一.简述 跨站脚本(Cross-site scripting,简称为:CSS, 但这会与层叠 ...

  3. 仿小米logo案例

    效果:做一个具有logo能过渡切换效果,类似于小米网站的logo 思路:将两个用于切换的logo以文字形式嵌入活动块banner的左右半,活动块banner的上级是主展示块box,初态只展示右半log ...

  4. 第二章节 BJROBOT IMU 自动校正 【ROS全开源阿克曼转向智能网联无人驾驶车】

    1.把小车平放在地板上,用资料里的虚拟机,打开一个终端 ssh 过去主控端启动roslaunch znjrobot bringup.launch . 2.再打开一个终端,ssh 过去主控端,在 ~/c ...

  5. 震惊!java中日期格式化的大坑!

    前言 我们都知道在java中进行日期格式化使用simpledateformat.通过格式 yyyy-MM-dd 等来进行格式化,但是你知道其中微小的坑吗? yyyy 和 YYYY 示例代码 @Test ...

  6. 壁球小游戏详解(附有源码.cpp)

    1.在python中安装pygame 2.将下列源码复制过去,运行. #引用 import pygame, sys #初始化 pygame.init() size = width, height = ...

  7. node解决跨域和服务器代理详解代码

    node中有很多解决服务器代理的插件,这里简介一个:express-http-proxy 之前网上查的使用node解决跨域的插件,有很多,例如,cors,koa2,这里解决跨域问题我拿原生解决的,ex ...

  8. tomcat控制台运行窗口中文乱码

    启动tomcat时出来的运行窗口中文乱码, 如图所示:看得有点不舒服 解决方法:找到Tomcat安装路径下的 /conf/logging.properties 文件 文件末尾添加语句: java.ut ...

  9. 【Linux】snmp在message中报错: /etc/snmp/snmpd.conf: line 311: Error: ERROR: This output format has been de

    Apr 17 17:36:17 localhost snmpd[2810]: /etc/snmp/snmpd.conf: line 311: Error: ERROR: This output for ...

  10. MAVEN编译NIFI源码

    场景: 由于项目需求,需要借用NIFI进行二次开发,因此需要将NIFI源码进行修改,然后编译,办公环境无外网. 步骤: (1)   找一台可以上网(外网)的机器,安装java环境和maven环境,安装 ...