Vite-Wechat网页聊天室|vite5.x+vue3+pinia+element-plus仿微信客户端

news/2024/9/24 7:22:46

基于Vue3+Pinia+ElementPlus仿微信网页聊天模板Vite5-Vue3-Wechat

vite-wechat使用最新前端技术vite5+vue3+vue-router@4+pinia+element-plus搭建网页端仿微信界面聊天系统。包含了聊天、通讯录、朋友圈、短视频、我的等功能模块。支持收缩侧边栏、背景壁纸换肤、锁屏、最大化等功能。

一、技术栈

  • 开发工具:vscode
  • 技术框架:vite5.2+vue3.4+vue-router4.3+pinia2
  • UI组件库:element-plus^2.7.5 (饿了么网页端vue3组件库)
  • 状态管理:pinia^2.1.7
  • 地图插件:@amap/amap-jsapi-loader(高德地图组件)
  • 视频滑动:swiper^11.1.4
  • 富文本编辑器:wangeditor^4.7.15(笔记/朋友圈富文本编辑器)
  • 样式编译:sass^1.77.4
  • 构建工具:vite^5.2.0

二、项目结构

vite-wechat聊天项目使用 vite5.x 构建工具搭建模板,采用 vue3 setup 语法糖编码开发模式。

main.js入口配置

import { createApp } from 'vue'
import './style.scss'
import App from './App.vue'// 引入组件库
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import VEPlus from 've-plus'
import 've-plus/dist/ve-plus.css'// 引入路由/状态管理
import Router from './router'
import Pinia from './pinia'const app = createApp(App)app
.use(ElementPlus)
.use(VEPlus)
.use(Router)
.use(Pinia)
.mount('#app')

目前该项目已经发布到我的原创作品集,感兴趣的话可以去看一看。

https://gf.bilibili.com/item/detail/1106226011

vue3实现上滑数字解锁

vue3-wechat项目没有使用传统的文本框输入验证,改为采用上滑数字密码解锁新模式。

<script setup>import { ref, computed, inject, nextTick } from 'vue'import { useRouter } from 'vue-router'import { authState } from '@/pinia/modules/auth'import { uuid, guid } from '@/utils'const authstate = authState()const router = useRouter()// 启动页
  const splashScreen = ref(true)const authPassed = ref(false)// 滑动距离
  const touchY = ref(0)const touchable = ref(false)// 数字键盘输入值
  const pwdValue = ref('')const keyNumbers = ref([{letter: 'a'},{letter: 'b'},{letter: 'c'},{letter: 'd'},{letter: 'e'},{letter: 'f'},{letter: 'g'},{letter: 'h'},{letter: 'i'},{letter: 'j'},{letter: 'k'},{letter: 'l'},{letter: 'm'},{letter: 'n'},{letter: 'o'},{letter: 'p'},{letter: 'q'},{letter: 'r'},{letter: 's'},{letter: 't'},{letter: 'u'},{letter: 'v'},{letter: 'w'},{letter: 'x'},{letter: 'y'},{letter: 'z'},{letter: '1'},{letter: '2'},{letter: '3'},{letter: '4'},{letter: '5'},{letter: '6'},{letter: '7'},{letter: '8'},{letter: '9'},{letter: '0'},{letter: '@'},{letter: '#'},{letter: '%'},{letter: '&'},{letter: '!'},{letter: '*'},])//...// 触摸事件(开始/更新)
  const handleTouchStart = (e) => {touchY.value = e.clientYtouchable.value = true}const handleTouchUpdate = (e) => {let swipeY = touchY.value - e.clientYif(touchable.value && swipeY > 100) {splashScreen.value = falsetouchable.value = false}}const handleTouchEnd = (e) => {touchY.value = 0touchable.value = false}// 点击数字键盘
  const handleClickNum = (num) => {let pwdLen = passwordArr.value.lengthif(pwdValue.value.length >= pwdLen) returnpwdValue.value += numif(pwdValue.value.length == pwdLen) {// 验证通过if(pwdValue.value == password.value) {// ...
      }else {setTimeout(() => {pwdValue.value = ''}, 200)}}}// 删除
  const handleDel = () => {let num = Array.from(pwdValue.value)num.splice(-1, 1)pwdValue.value = num.join('')}// 清空
  const handleClear = () => {pwdValue.value = ''}// 返回
  const handleBack = () => {splashScreen.value = true}
</script><template><div class="uv3__launch"><divv-if="splashScreen"class="uv3__launch-splash"@mousedown="handleTouchStart"@mousemove="handleTouchUpdate"@mouseup="handleTouchEnd"><div class="uv3__launch-splashwrap">...</div></div><div v-else class="uv3__launch-keyboard"><div class="uv3__launch-pwdwrap"><div class="text">密码解锁</div><div class="circle flexbox"><div v-for="(num, index) in passwordArr" :key="index" class="dot" :class="{'active': num <= pwdValue.length}"></div></div></div><div class="uv3__launch-numwrap"><div v-for="(item, index) in keyNumbers" :key="index" class="numbox flex-c" @click="handleClickNum(item.letter)"><div class="num">{{item.letter}}</div></div></div><div class="foot flexbox"><Button round icon="ve-icon-clean" @click="handleClear">清空</Button><Button type="danger" v-if="pwdValue" round icon="ve-icon-backspace" @click="handleDel">删除</Button><Button v-else round icon="ve-icon-rollback" @click="handleBack">返回</Button></div></div></div>
</template>

公共布局模板

整体布局模板分为左侧菜单操作栏+侧边栏+右侧内容主体区域三大模块。

<template><div class="vu__container" :style="{'--themeSkin': appstate.config.skin}"><div class="vu__layout"><div class="vu__layout-body"><!-- 菜单栏 --><slot v-if="!route?.meta?.hideMenuBar" name="menubar"><MenuBar /></slot><!-- 侧边栏 --><div v-if="route?.meta?.showSideBar" class="vu__layout-sidebar" :class="{'hidden': appstate.config.collapsed}"><aside class="vu__layout-sidebar__body flexbox flex-col"><slot name="sidebar"><SideBar /></slot><!-- 折叠按钮 --><Collapse /></aside></div><!-- 主内容区 --><div class="vu__layout-main flex1 flexbox flex-col"><Winbtn v-if="!route?.meta?.hideWinBar" /><router-view v-slot="{ Component, route }"><keep-alive><component :is="Component" :key="route.path" /></keep-alive></router-view></div></div></div></div>
</template>

vue3路由配置

左侧菜单栏、侧边栏可以通过配置路由meta参数控制是否显示。

/*** 路由管理Router* @author andy*/import { createRouter, createWebHashHistory } from 'vue-router'
import { authState } from '@/pinia/modules/auth'import Layout from '@/layouts/index.vue'// 批量导入路由
const modules = import.meta.glob('./modules/*.js', { eager: true })
const patchRouters = Object.keys(modules).map(key => modules[key].default).flat()/*** meta配置* @param meta.requireAuth 需登录验证页面* @param meta.hideWinBar 隐藏右上角按钮组* @param meta.hideMenuBar 隐藏菜单栏* @param meta.showSideBar 显示侧边栏* @param meta.canGoBack 是否可回退上一页*/
const routes = [...patchRouters,// 错误模块
  {path: '/:pathMatch(.*)*',redirect: '/404',component: Layout,meta: {title: '404error',hideMenuBar: true,hideWinBar: true,},children: [{path: '404',component: () => import('@/views/error/404.vue'),}]},
]const router = createRouter({history: createWebHashHistory(),routes,
})// 全局路由钩子拦截
router.beforeEach((to, from) => {const authstate = authState()// 登录验证if(to?.meta?.requireAuth && !authstate.authorization) {console.log('你还未登录!')return {path: '/login'}}
})router.afterEach((to, from) => {// 阻止浏览器回退if(to?.meta?.canGoBack == false && from.path != null) {history.pushState(history.state, '', document.URL)}
})

vue3状态管理

使用新的状态管理Pinia插件。配合 pinia-plugin-persistedstate 插件管理本地持久化存储服务。

/*** 状态管理Pinia* @author andy*/import { createPinia } from 'pinia'
// 引入pinia持久化存储
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)export default pinia

vue3短视频模块

vue3-wechat项目加入了短视频模块。使用swiper组件实现上下丝滑切换小视频。

底部mini播放进度条,采用Slider组件实现功能。支持实时显示当前播放进度、拖拽到指定时间点

<!-- 短视频模块 -->
<div class="vu__video-container"><!-- tabs操作栏 --><div class="vu__video-tabswrap flexbox"><el-tabs v-model="activeName" class="vu__video-tabs"><el-tab-pane label="关注" name="attention" /><el-tab-pane label="推荐" name="recommend" /></el-tabs></div><swiper-containerclass="vu__swiper"direction="vertical":speed="150":grabCursor="true":mousewheel="{invert: true}"@swiperslidechange="onSlideChange"><swiper-slide v-for="(item, index) in videoList" :key="index"><!-- 视频层 --><videoclass="vu__player":id="'vuplayer-' + index":src="item.src":poster="item.poster"looppreload="auto":autoplay="index == currentVideo"webkit-playsinline="true" x5-video-player-type="h5-page"x5-video-player-fullscreen="true"playsinline@click="handleVideoClicked"></video><div v-if="!isPlaying" class="vu__player-btn" @click="handleVideoClicked"></div><!-- 右侧操作栏 --><div class="vu__video-toolbar">...</div><!-- 底部信息区域 --><div class="vu__video-footinfo flexbox flex-col"><div class="name">@{{item.author}}</div><div class="content">{{item.desc}}</div></div></swiper-slide></swiper-container><!-- ///底部进度条 --><el-slider class="vu__video-progressbar" v-model="progressBar" @input="handleSlider" @change="handlePlay" /><div v-if="isDraging" class="vu__video-duration">{{videoTime}} / {{videoDuration}}</div>
</div>

vite-wechat聊天模块

聊天模块编辑器封装为独立组件,支持多行文本输入、光标处插入gif图片、粘贴截图发送图片等功能。

<template><!-- 顶部导航 -->...<!-- 内容区 --><div class="vu__layout-main__body"><Scrollbar ref="scrollRef" autohide gap="2"><!-- 渲染聊天内容 --><div class="vu__chatview" @dragenter="handleDragEnter" @dragover="handleDragOver" @drop="handleDrop">...</div></Scrollbar></div><!-- 底部操作栏 --><div class="vu__footview"><div class="vu__toolbar flexbox">...</div><div class="vu__editor"><Editor ref="editorRef" v-model="editorValue" @paste="handleEditorPaste" /></div><div class="vu__submit"><button @click="handleSubmit">发送(S)</button></div></div>...
</template>

拖拽图片到聊天区域,实现本地上传预览图片。

/*** ==========拖拽上传模块==========*/
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.filesif(files.length >= 2) {Message.danger('仅允许拖拽一张图片')return false}console.log(files)for(let i = 0; i < files.length; i++) {if(files[i].type != '') {handleFileAdd(files[i])}else {Message.danger('不支持文件夹拖拽功能')}}
}
const handleFileAdd = (file) => {// 消息队列let message = {'id': guid(),'msgtype': 5,'isme': true,'avatar': '/static/avatar/uimg13.jpg','author': 'Andy','content': '','image': '','video': '',}if(file.type.indexOf('image') == -1) {Message.danger('不支持非图片拖拽功能')}else {let reader = new FileReader()reader.readAsDataURL(file)reader.onload = function() {let img = this.resultmessage['image'] = imgsendMessage(message)}}
}

vue3操作高德地图。定位当前位置和根据经纬度展示地图信息。

// 拾取地图位置
let map = null
const handlePickMapLocation = () => {popoverChooseRef?.value?.hide()mapLocationVisible.value = true// 初始化地图
  AMapLoader.load({key: "af10789c28b6ef1929677bc5a2a3d443", // 申请好的Web端开发者Key,首次调用 load 时必填version: "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15}).then((AMap) => {// JS API 加载完成后获取AMap对象map = new AMap.Map("vu__mapcontainer", {viewMode: "3D", // 默认使用 2D 模式zoom: 10, // 初始化地图级别resizeEnable: true,})// 获取当前位置AMap.plugin('AMap.Geolocation', function() {var geolocation = new AMap.Geolocation({// 是否使用高精度定位,默认:trueenableHighAccuracy: true,// 设置定位超时时间,默认:无穷大timeout: 10000,// 定位按钮的停靠位置的偏移量,默认:Pixel(10, 20)buttonOffset: new AMap.Pixel(10, 20),// 定位成功后调整地图视野范围使定位位置及精度范围视野内可见,默认:falsezoomToAccuracy: true,// 定位按钮的排放位置, RB表示右下buttonPosition: 'RB'})map.addControl(geolocation)geolocation.getCurrentPosition(function(status, result){if(status == 'complete'){onComplete(result)}else{onError(result)}})})// 定位成功的回调函数function onComplete(data) {var str = ['定位成功']str.push('经度:' + data.position.getLng())str.push('纬度:' + data.position.getLat())if(data.accuracy){str.push('精度:' + data.accuracy + ' 米')}// 可以将获取到的经纬度信息进行使用console.log(str.join('<br>'))}// 定位失败的回调函数function onError(data) {console.log('定位失败:' + data.message)}}).catch((e) => {// 加载错误提示console.log('amapinfo', e)})
}// 打开预览地图位置
const handleOpenMapLocation = (data) => {mapLocationVisible.value = true// 初始化地图
  AMapLoader.load({key: "af10789c28b6ef1929677bc5a2a3d443", // 申请好的Web端开发者Key,首次调用 load 时必填version: "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15}).then((AMap) => {// JS API 加载完成后获取AMap对象map = new AMap.Map("vu__mapcontainer", {viewMode: "3D", // 默认使用 2D 模式zoom: 13, // 初始化地图级别center: [data.longitude, data.latitude], // 初始化地图中心点位置
    })// 添加插件AMap.plugin(["AMap.ToolBar", "AMap.Scale", "AMap.HawkEye"], function () {//异步同时加载多个插件map.addControl(new AMap.ToolBar()) // 缩放工具条map.addControl(new AMap.HawkEye()) // 显示缩略图map.addControl(new AMap.Scale()) // 显示当前地图中心的比例尺
    })mapPosition.value = [data.longitude, data.latitude]addMarker()// 实例化点标记function addMarker() {const marker = new AMap.Marker({icon: "//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-default.png",position: mapPosition.value,offset: new AMap.Pixel(-26, -54),})marker.setMap(map)/* marker.setLabel({direction:'top',offset: new AMap.Pixel(0, -10),  //设置文本标注偏移量content: "<div class='info'>我是 marker 的 label 标签</div>", //设置文本标注内容}) *///鼠标点击marker弹出自定义的信息窗体marker.on('click', function () {infoWindow.open(map, marker.getPosition())})const infoWindow = new AMap.InfoWindow({offset: new AMap.Pixel(0, -60),content: `<div style="padding: 10px;"><p style="font-size: 14px;">${data.name}</p><p style="color: #999; font-size: 12px;">${data.address}</p></div>
        `})}}).catch((e) => {// 加载错误提示console.log('amapinfo', e)})
}
// 关闭预览地图位置
const handleCloseMapLocation = () => {map?.destroy()mapLocationVisible.value = false
}

Okey,综上就是vite5+pinia+element-plus开发网页聊天项目的一些知识分享,希望对大家有所帮助。✍🏻

最后附上两个最新flutter3.x实例项目

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

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

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

6月27日云技术研讨会 | 中央集中架构新车型功能和网络测试解决方案

会议摘要“软件定义汽车”新时代下,整车电气电气架构向中央-区域集中式发展已成为行业共识,车型架构的变革带来更复杂的整车功能定义、更多的新技术的应用(如SOA服务化、TSN等)和更短的车型研发周期,对整车和新产品研发的质量验证提出更高的要求。本次研讨会经纬恒润将结合…

k8s集群搭建及对一些组件的简单理解(一)

背景 k8s的学习环境(用kubeadm方式搭建),我也搭过几次了,但都有点问题。 要么在云服务器上弄,这个的问题是就只有一台轻量服务器,只能搭个单节点的;后来买了一台便宜的,所以就有了两台,但是不在一个zone,一个是广州,一个是成都,内网不通,感觉搭起来很麻烦,还没试…

Qt版本选择01

嵌入式推荐用Qt4.8,打包的程序小:Qt4.8.7是Qt4的终结版本,是Qt4系列版本中最稳定最经典的 最后支持xp系统的长期支持版本:Qt5.6.3;Qt5.7.0是最后支持xp系统的非长期支持版本。 最后提供mysql数据库插件的版本:Qt5.12.3。 最后支持win7的版本:Qt5.15系列。Qt6不支持win7 …

架构师必知的11种API性能优化方法

前言 接口性能优化是后端开发人员经常碰到的一道面试题,因为它是一个跟开发语言无关的公共问题。 这个问题既可以很简单,也可以相当复杂。有时候,只需要添加一个索引就能解决。 有时候,代码需要进行重构。 有时候,必须增加缓存。 有时候,需要引入一些中间件,例如消息队列…

接口面试题

postman接口测试,它有一个功能可以设置参数化,你有用过吗?多接口怎么测? (1)有 (2){{}}、a、设置环境变量、b、在run中通过导入csv文件引用变量 (3)postman里面有一个批量处理,将多个接口放至一个项目文件夹中,点击run,选择环境变量、修改运行次数和延迟秒数、选中c…

全网最好看的单细胞umap图绘制教程

作者按 大家或许都曾被Nature, Science上的单细胞umap图吸引过,不免心生崇拜。在这里,我们将介绍一种简单方便的顶刊级umap图可视化 全文字数|预计阅读时间: 2000|5min ——Starlitnightly(星夜)环境加载 我们先导入一些必须的依赖包 import omicverse as ov import scanpy…

pycharm中运行jupyter notebook

进入anaconda prompt,进入对应的虚拟环境输入jupyter notebook,找到路径和token这两个随便复制一个,注意是包括token也要复制到 然后打开pycharm,并建立一个jupyter notebook文件选择下面这个然后在里面输入刚刚复制的然后运行一个cell,会弹出输入刚刚复制jupyter noteboo…

从JDK8升级到JDK17

一、概述 鉴于JDK8已经是老古董,还有性能问题,兼且各个公司已经不再维护1.8的JDK,所以升级公司的核心产品之一的后端到JDK到17是相对要紧的事情。 通过升级到jdk17,具有以下好处:不要在头疼同时适应两个jdk,放下适应JDK8的负担 在生产环境基本上只需要部署一个jdk即可 具…