Vue3-ViteWchat:基于vue3.x+pinia2+element-plus仿微信web版聊天室
vite5.x-webchat:原创vite5+vue3+pinia2+elementPlus
构建桌面web端聊天系统。
使用技术
- 开发工具: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
- 样式编译:sass^1.77.4
- 构建工具:vite^5.2.0
项目目录结构
使用vite5.x
搭建项目模板,采用vue3 setup
语法编码。
vite-webchat包含了聊天、通讯录、朋友圈、短视频、我的等功能模块。支持收缩侧边栏、背景壁纸换肤、锁屏、最大化等功能。
目前该项目已经正式发布到我的原创作品集,感兴趣的话,可以去看一看。
gf.bilibili.com/item/detail/110622...
入口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')
主布局模板
<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路由配置
/**
* 路由管理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短视频模板
<!-- 短视频模块 -->
<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-container
class="vu__swiper"
direction="vertical"
:speed="150"
:grabCursor="true"
:mousewheel="{invert: true}"
@swiperslidechange="onSlideChange"
>
<swiper-slide v-for="(item, index) in videoList" :key="index">
<!-- 视频层 -->
<video
class="vu__player"
:id="'vuplayer-' + index"
:src="item.src"
:poster="item.poster"
loop
preload="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>
vue3聊天模板
<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>
// 拾取地图位置
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({
// 是否使用高精度定位,默认:true
enableHighAccuracy: true,
// 设置定位超时时间,默认:无穷大
timeout: 10000,
// 定位按钮的停靠位置的偏移量,默认:Pixel(10, 20)
buttonOffset: new AMap.Pixel(10, 20),
// 定位成功后调整地图视野范围使定位位置及精度范围视野内可见,默认:false
zoomToAccuracy: 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
}
博客:uniapp-weos:原创uniapp+vue3手机版后台OA管理系统(h5+小程序+App端...
博客:flutter-dylive:基于flutter3.x+getx短视频直播|Flutter3仿抖音app
博客:基于flutter3+getx+bitsdojo_window仿微信客户端|flutter聊天EXE
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: