uniapp-vue3-wechat:基于uniapp+vue3仿微信app谈天实例(H5+小步调+App端) ...

打印 上一主题 下一主题

主题 503|帖子 503|积分 1509

uni-vue3-wchat:基于uni-app+vue3+pinia2高仿微信app谈天模板。

   原创基于最新跨端技能uni-app+vue3.x+pinia2+vite4+uv-ui构建三端仿微信app界面谈天实例。实现编辑框多行消息/emoj混合、长按触摸式仿微信语音面板、图片/视频预览、红包/朋友圈等功能。支持编译到H5+小步调端+App端
  

整个项目采用vue3 setup语法编码,支持编译到h5+小步调端+APP端。

使用技能



  • 编辑器:HbuilderX 4.0.8
  • 框架技能:Uniapp+Vue3+Pinia2+Vite4.x
  • 组件库:uni-ui+uv-ui
  • 弹窗组件:uv3-popup(uniapp+vue3多端自定义弹框组件)
  • 导航栏+菜单栏:uv3-navbar+uv3-tabbar组件
  • 本地缓存:pinia-plugin-unistorage
  • 编译支持:H5+小步调+APP端



uniapp+vue3实现类似微信朋友圈功能。

项目布局






















入口main.js设置

  1. /**
  2. * 入口文件 main.js
  3. */
  4. import { createSSRApp } from 'vue'
  5. import App from './App'
  6. // 引入pinia状态管理
  7. import pinia from '@/pinia'
  8. export function createApp() {
  9.         const app = createSSRApp(App)
  10.         app.use(pinia)
  11.         return {
  12.                 app,
  13.                 pinia
  14.         }
  15. }
复制代码

App.vue设置

采用 vue3 setup 语法开发。
  1. <script setup>
  2.     import { provide } from 'vue'
  3.     import { onLaunch, onShow, onHide, onPageNotFound } from '@dcloudio/uni-app'
  4.    
  5.     onLaunch(() => {
  6.         console.log('App Launch')
  7.         
  8.         uni.hideTabBar()
  9.         loadSystemInfo()
  10.     })
  11.    
  12.     onShow(() => {
  13.         console.log('App Show')
  14.     })
  15.    
  16.     onHide(() => {
  17.         console.log('App Hide')
  18.     })
  19.    
  20.     onPageNotFound((e) => {
  21.         console.warn('Route Error:', `${e.path}`)
  22.     })
  23.    
  24.     // 获取系统设备信息
  25.     const loadSystemInfo = () => {
  26.         uni.getSystemInfo({
  27.             success: (e) => {
  28.                 // 获取手机状态栏高度
  29.                 let statusBar = e.statusBarHeight
  30.                 let customBar
  31.                
  32.                 // #ifndef MP
  33.                 customBar = statusBar + (e.platform == 'android' ? 50 : 45)
  34.                 // #endif
  35.                
  36.                 // #ifdef MP-WEIXIN
  37.                 // 获取胶囊按钮的布局位置信息
  38.                 let menu = wx.getMenuButtonBoundingClientRect()
  39.                 // 导航栏高度 = 胶囊下距离 + 胶囊上距离 - 状态栏高度
  40.                 customBar = menu.bottom + menu.top - statusBar
  41.                 // #endif
  42.                
  43.                 // #ifdef MP-ALIPAY
  44.                 customBar = statusBar + e.titleBarHeight
  45.                 // #endif
  46.                
  47.                 // 由于globalData在vue3 setup存在兼容性问题,改为provide/inject替代方案
  48.                 provide('globalData', {
  49.                     statusBarH: statusBar,
  50.                     customBarH: customBar,
  51.                     screenWidth: e.screenWidth,
  52.                     screenHeight: e.screenHeight,
  53.                     platform: e.platform
  54.                 })
  55.             }
  56.         })
  57.     }
  58. </script>
  59. <style>
  60.     /* #ifndef APP-NVUE */
  61.     @import 'static/fonts/iconfont.css';
  62.     /* #endif */
  63. </style>
  64. <style lang="scss">
  65.     @import 'styles/reset.scss';
  66.     @import 'styles/layout.scss';
  67. </style>
复制代码

uniapp+vue3自定义navbar+tabbar组件



  1. <uv3-navbar :back="true" title="标题内容" bgcolor="#07c160" color="#fff" fixed zIndex="1010" />
  2. <uv3-navbar custom bgcolor="linear-gradient(to right, #07c160, #0000ff)" color="#fff" center transparent zIndex="2024">
  3.     <template #back><uni-icons type="close" /></template>
  4.     <template #backText><text>首页</text></template>
  5.     <template #title>
  6.         <image src="/static/logo.jpg" style="height:20px;width:20px;" /> Admin
  7.     </template>
  8.     <template #right>
  9.         <view class="ml-20" @click="handleAdd"><text class="iconfont icon-tianjia"></text></view>
  10.         <view class="ml-20"><text class="iconfont icon-msg"></text></view>
  11.     </template>
  12. </uv3-navbar>
复制代码
公共布局模板

整体项目布局采用顶部导航地域+主体内容区+底部地域


  1. <!-- 公共布局模板 -->
  2. <!-- #ifdef MP-WEIXIN -->
  3. <script>
  4.     export default {
  5.         /**
  6.          * 解决小程序class、id透传问题
  7.          * manifest.json中配置mergeVirtualHostAttributes: true, 在微信小程序平台不生效,组件外部传入的class没有挂到组件根节点上,在组件中增加options: { virtualHost: true }
  8.          * https://github.com/dcloudio/uni-ui/issues/753
  9.          */
  10.         options: { virtualHost: true }
  11.     }
  12. </script>
  13. <!-- #endif -->
  14. <script setup>
  15.     const props = defineProps({
  16.         // 是否显示自定义tabbar
  17.         showTabBar: { type: [Boolean, String], default: false },
  18.     })
  19. </script>
  20. <template>
  21.     <view class="uv3__container flexbox flex-col flex1">
  22.         <!-- 顶部插槽 -->
  23.         <slot name="header" />
  24.         
  25.         <!-- 内容区 -->
  26.         <view class="uv3__scrollview flex1">
  27.             <slot />
  28.         </view>
  29.         
  30.         <!-- 底部插槽 -->
  31.         <slot name="footer" />
  32.         
  33.         <!-- tabbar栏 -->
  34.         <uv3-tabbar v-if="showTabBar" hideTabBar fixed />
  35.     </view>
  36. </template>
复制代码
uni-app+vue3微信九宫格图像组



  1. <script setup>
  2.     import { onMounted, ref, computed, watch, getCurrentInstance } from 'vue'
  3.    
  4.     const props = defineProps({
  5.         // 图像组
  6.         avatar: { type: Array, default: null },
  7.     })
  8.    
  9.     const instance = getCurrentInstance()
  10.    
  11.     const uuid = computed(() => Math.floor(Math.random() * 10000))
  12.     const avatarPainterId = ref('canvasid' + uuid.value)
  13.    
  14.     const createAvatar = () => {
  15.         const ctx = uni.createCanvasContext(avatarPainterId.value, instance)
  16.         // 计算图像在画布上的坐标
  17.         const avatarSize = 12
  18.         const gap = 2
  19.         for(let i = 0, len = props.avatar.length; i < len; i++) {
  20.             const row = Math.floor(i / 3)
  21.             const col = i % 3
  22.             const x = col * (avatarSize + gap)
  23.             const y = row * (avatarSize + gap)
  24.             
  25.             ctx.drawImage(props.avatar[i], x, y, avatarSize, avatarSize)
  26.         }
  27.         ctx.draw(false, () => {
  28.             // 输出临时图片
  29.             /* uni.canvasToTempFilePath({
  30.                 canvasId: avatarPainterId.value,
  31.                 success: (res) => {
  32.                     console.log(res.tempFilePath)
  33.                 }
  34.             }) */
  35.         })
  36.     }
  37.    
  38.     onMounted(() => {
  39.         createAvatar()
  40.     })
  41.    
  42.     watch(() => props.avatar, () => {
  43.         createAvatar()
  44.     })
  45. </script>
  46. <template>
  47.     <template v-if="avatar.length > 1">
  48.         <view class="uv3__avatarPainter">
  49.             <canvas :canvas-id="avatarPainterId" class="uv3__avatarPainter-canvas"></canvas>
  50.         </view>
  51.     </template>
  52.     <template v-else>
  53.         <image class="uv3__avatarOne" :src="avatar[0]" />
  54.     </template>
  55. </template>
  56. <style lang="scss" scoped>
  57.     .uv3__avatarPainter {background-color: #eee; border-radius: 5px; overflow: hidden; padding: 2px; height: 44px; width: 44px;}
  58.     .uv3__avatarPainter-canvas {height: 100%; width: 100%;}
  59.     .uv3__avatarOne {border-radius: 5px; height: 44px; width: 44px;}
  60. </style>
复制代码
uni-app+vue3自定义弹出框组件



  1. v-model        当前组件是否显示
  2. title          标题(支持富文本div标签、自定义插槽内容)
  3. content        内容(支持富文本div标签、自定义插槽内容)
  4. type           弹窗类型(toast | footer | actionsheet | actionsheetPicker | android/ios)
  5. customStyle    自定义弹窗样式
  6. icon           toast图标(loading | success | fail | warn | info)
  7. shade          是否显示遮罩层
  8. shadeClose     是否点击遮罩时关闭弹窗
  9. opacity        遮罩层透明度
  10. round          是否显示圆角
  11. xclose         是否显示关闭图标
  12. xposition      关闭图标位置(left | right | top | bottom)
  13. xcolor         关闭图标颜色
  14. anim           弹窗动画(scaleIn | fadeIn | footer | fadeInUp | fadeInDown)
  15. position       弹出位置(top | right | bottom | left)
  16. follow         长按/右键弹窗(坐标点)
  17. time           弹窗自动关闭秒数(1、2、3)
  18. zIndex         弹窗层叠(默认202107)
  19. btns           弹窗按钮(参数:text|style|disabled|click)
  20. ------------------------------------------
  21. ## slot [插槽]
  22. <template #title></template>
  23. <template #content></template>
  24. ------------------------------------------
  25. ## emit
  26. open        打开弹出层时触发(@open="xxx")
  27. close       关闭弹出层时触发(@close="xxx")
复制代码
uv3-popup支持函数式+组件式两种调用方式。
  1. <script setup>
  2.     import { onMounted, ref, computed, watch, nextTick, getCurrentInstance } from 'vue'
  3.    
  4.     const props = defineProps({
  5.         ...
  6.     })
  7.     const emit = defineEmits([
  8.         'update:modelValue',
  9.         'open',
  10.         'close'
  11.     ])
  12.    
  13.     const instance = getCurrentInstance()
  14.    
  15.     const opts = ref({
  16.         ...props
  17.     })
  18.     const visible = ref(false)
  19.     const closeAnim = ref(false)
  20.     const stopTimer = ref(null)
  21.     const oIndex = ref(props.zIndex)
  22.     const uuid = computed(() => Math.floor(Math.random() * 10000))
  23.    
  24.     const positionStyle = ref({ position: 'absolute', left: '-999px', top: '-999px' })
  25.    
  26.     const toastIcon = {
  27.         ...
  28.     }
  29.    
  30.     // 打开弹框
  31.     const open = (options) => {
  32.         if(visible.value) return
  33.         opts.value = Object.assign({}, props, options)
  34.         // console.log('-=-=混入参数:', opts.value)
  35.         visible.value = true
  36.         
  37.         // nvue 的各组件在安卓端默认是透明的,如果不设置background-color,可能会导致出现重影的问题
  38.         // #ifdef APP-NVUE
  39.         if(opts.value.customStyle && !opts.value.customStyle['background'] && !opts.value.customStyle['background-color']) {
  40.             opts.value.customStyle['background'] = '#fff'
  41.         }
  42.         // #endif
  43.         
  44.         let _index = ++index
  45.         oIndex.value = _index + parseInt(opts.value.zIndex)
  46.         
  47.         emit('open')
  48.         typeof opts.value.onOpen === 'function' && opts.value.onOpen()
  49.         
  50.         // 长按处理
  51.         if(opts.value.follow) {
  52.             nextTick(() => {
  53.                 let winW = uni.getSystemInfoSync().windowWidth
  54.                 let winH = uni.getSystemInfoSync().windowHeight
  55.                 // console.log('坐标点信息:', opts.value.follow)
  56.                 getDom(uuid.value).then(res => {
  57.                     // console.log('Dom尺寸信息:', res)
  58.                     if(!res) return
  59.                     
  60.                     let pos = getPos(opts.value.follow[0], opts.value.follow[1], res.width+15, res.height+15, winW, winH)
  61.                     positionStyle.value.left = pos[0] + 'px'
  62.                     positionStyle.value.top = pos[1] + 'px'
  63.                 })
  64.             })
  65.         }
  66.         
  67.         if(opts.value.time) {
  68.             if(stopTimer.value) clearTimeout(stopTimer.value)
  69.             stopTimer.value = setTimeout(() => {
  70.                 close()
  71.             }, parseInt(opts.value.time) * 1000)
  72.         }
  73.     }
  74.    
  75.     // 关闭弹框
  76.     const close = () => {
  77.         if(!visible.value) return
  78.         
  79.         closeAnim.value = true
  80.         setTimeout(() => {
  81.             visible.value = false
  82.             closeAnim.value = false
  83.             
  84.             emit('update:modelValue', false)
  85.             emit('close')
  86.             typeof opts.value.onClose === 'function' && opts.value.onClose()
  87.             
  88.             positionStyle.value.left = '-999px'
  89.             positionStyle.value.top = '-999px'
  90.             
  91.             stopTimer.value && clearTimeout(stopTimer.value)
  92.         }, 200)
  93.     }
  94.    
  95.     // 点击遮罩层
  96.     const handleShadeClick = () => {
  97.         if(JSON.parse(opts.value.shadeClose)) {
  98.             close()
  99.         }
  100.     }
  101.    
  102.     // 按钮事件
  103.     const handleBtnClick = (e, index) => {
  104.         let btn = opts.value.btns[index]
  105.         if(!btn?.disabled) {
  106.             console.log('按钮事件类型:', typeof btn.click)
  107.             
  108.             typeof btn.click === 'function' && btn.click(e)
  109.         }
  110.     }
  111.    
  112.     // 获取dom宽高
  113.     const getDom = (id) => {
  114.         return new Promise((resolve, inject) => {
  115.             // uniapp vue3中 uni.createSelectorQuery().in(this) 会报错__route__未定义  https://ask.dcloud.net.cn/question/140192
  116.             uni.createSelectorQuery().in(instance).select('#uapopup-' + id).fields({
  117.                 size: true,
  118.             }, data => {
  119.                 resolve(data)
  120.             }).exec()
  121.         })
  122.     }
  123.    
  124.     // 自适应坐标点
  125.     const getPos = (x, y, ow, oh, winW, winH) => {
  126.         let l = (x + ow) > winW ? x - ow : x
  127.         let t = (y + oh) > winH ? y - oh : y
  128.         return [l, t]
  129.     }
  130.    
  131.     onMounted(() => {
  132.         if(props.modelValue) {
  133.             open()
  134.         }
  135.     })
  136.     watch(() => props.modelValue, (val) => {
  137.         // console.log(val)
  138.         if(val) {
  139.             open()
  140.         }else {
  141.             close()
  142.         }
  143.     })
  144.    
  145.     defineExpose({
  146.         open,
  147.         close
  148.     })
  149. </script>
复制代码
uni-app+vue3谈天功能




目前该插件已经免费发布到插件市场,接待去下载使用。

https://ext.dcloud.net.cn/plugin?id=13275

  1. <!-- 语音操作面板 -->
  2. <view v-if="voicePanelEnable" class="uv3__voicepanel-popup">
  3.     <view class="uv3__voicepanel-body flexbox flex-col">
  4.         <!-- 取消发送+语音转文字 -->
  5.         <view v-if="!voiceToTransfer" class="uv3__voicepanel-transfer">
  6.             <!-- 提示动效 -->
  7.             <view class="animtips flexbox" :class="voiceType == 2 ? 'left' : voiceType == 3 ? 'right' : null"><Waves :lines="[2, 3].includes(voiceType) ? 10 : 20" /></view>
  8.             <!-- 操作项 -->
  9.             <view class="icobtns flexbox">
  10.                 <view class="vbtn cancel flexbox flex-col" :class="{'hover': voiceType == 2}" @click="handleVoiceCancel"><text class="vicon uv3-icon uv3-icon-close"></text></view>
  11.                 <view class="vbtn word flexbox flex-col" :class="{'hover': voiceType == 3}"><text class="vicon uv3-icon uv3-icon-word"></text></view>
  12.             </view>
  13.         </view>
  14.         
  15.         <!-- 语音转文字(识别结果状态) -->
  16.         <view v-if="voiceToTransfer" class="uv3__voicepanel-transfer result fail">
  17.             <!-- 提示动效 -->
  18.             <view class="animtips flexbox"><uni-icons type="info-filled" color="#fff" size="20"></uni-icons><text class="c-fff">未识别到文字</text></view>
  19.             <!-- 操作项 -->
  20.             <view class="icobtns flexbox">
  21.                 <view class="vbtn cancel flexbox flex-col" @click="handleVoiceCancel"><text class="vicon uv3-icon uv3-icon-chexiao"></text>取消</view>
  22.                 <view class="vbtn word flexbox flex-col"><text class="vicon uv3-icon uv3-icon-audio"></text>发送原语音</view>
  23.                 <view class="vbtn check flexbox flex-col"><text class="vicon uv3-icon uv3-icon-duigou"></text></view>
  24.             </view>
  25.         </view>
  26.         
  27.         <!-- 背景语音图 -->
  28.         <view class="uv3__voicepanel-cover">
  29.             <image v-if="!voiceToTransfer" src="/static/voice_bg.webp" :webp="true" mode="widthFix" style="width: 100%;" />
  30.         </view>
  31.         <!-- // 提示文字(操作状态) -->
  32.         <view v-if="!voiceToTransfer" class="uv3__voicepanel-tooltip">{{voiceTypeMap[voiceType]}}</view>
  33.         <!-- 背景小图标 -->
  34.         <view v-if="!voiceToTransfer" class="uv3__voicepanel-fixico"><text class="uv3-icon uv3-icon-audio fs-50"></text></view>
  35.     </view>
  36. </view>
复制代码
目前该项目已经同步到工房,如果有必要,接待自行去拍哈~
https://gf.bilibili.com/item/detail/1105801011
好了,综上就是uniapp+vue3跨端谈天实例的一些分享,盼望大家能喜欢!
末了附上几个最新实战项目


  • flutter3-winchat基于flutter3.x+bitsdojo_window桌面端仿微信
    https://blog.csdn.net/yanxinyun1990/article/details/136410049
  • flutter3_douyin基于flutter3.19仿抖音短视频/直播
    https://blog.csdn.net/yanxinyun1990/article/details/136996521
  • flutter3_macos基于flutter3.x+window_manager桌面端仿macOS系统
    https://blog.csdn.net/yanxinyun1990/article/details/137697164


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

灌篮少年

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表