uniapp_vue3_wechatuni-app+vue3+vite4+pinia2+uni-ui is a three-terminal (h5 + mini program + APP) imitation WeChat chat instance originally developed based on the latest cross-terminal technology .
Contents
Preview effect
The effect of compiling to h5+mini program+App is as follows:
The entire project vue3 setupwas developed using grammatical structure coding.
Use technology
- Development tools: HbuilderX 4.0.8
- Technical framework: Uniapp+Vue3+Pinia2+Vite4.x
- UI component library: uni-ui+uv-ui
- Pop-up component: uv3-popup (uniapp+vue3 multi-terminal custom pop-up component)
- Custom component: uv3-navbar+uv3-tabbar component
- Cache service: pinia-plugin-unistorage
- Compilation support: H5 + applet + APP
Project structure
Entrance main.js configuration
/**
* Entry file main.js
*/
import { createSSRApp } from 'vue'
import App from './App'
//Introduce pinia state management
import pinia from '@/pinia'
export function createApp () {
const app = createSSRApp ( App )
app. use (pinia)
return {
app,
pinia
}
}
In addition, it supports compiling to the web side and displaying it at 750px pixels.
This project has been updated and launched in the studio. If you happen to need it, you are welcome to take pictures~ Thank you for your support!
App.vue configuration
Use vue3 setup syntax to initialize some custom navigation bar parameters and page styles.
<script setup>
import { provide } from 'vue'
import { onLaunch, onShow, onHide, onPageNotFound } from '@dcloudio/uni-app'
onLaunch ( () => {
console . log ( 'App Launch' )
uni. hideTabBar ()
loadSystemInfo ()
})
onShow ( () => {
console . log ( 'App Show' )
})
onHide ( () => {
console . log ( 'App Hide' )
})
onPageNotFound ( ( e ) => {
console . warn ( 'Route Error: ' , ` ${e.path} ` )
})
// Get system device information
const loadSystemInfo = () => {
uni. getSystemInfo ({
success : ( e ) => {
// Get the phone status bar height
let statusBar = e. statusBarHeight
let customBar
// #ifndef MP
customBar = statusBar + (e. platform == 'android' ? 50 : 45 )
// #endif
// #ifdef MP-WEIXIN
// Get the layout position information of the capsule button
let menu = wx. getMenuButtonBoundingClientRect ()
// Navigation bar height = capsule bottom distance + capsule top distance - status bar height
customBar = menu. bottom + menu. top - statusBar
// #endif
// #ifdef MP-ALIPAY
customBar = statusBar + e. titleBarHeight
// #endif
// Due to compatibility issues with globalData in vue3 setup, the provide/inject alternative was changed
to provide ( 'globalData' , {
statusBarH : statusBar,
customBarH : customBar,
screenWidth : e. screenWidth ,
screenHeight : e. screenHeight ,
platform : e. platform
})
}
})
}
</script>
< style > /* #ifndef APP-NVUE */ @import 'static/fonts/iconfont.css' ;
/* #endif */ </ style >
< style lang = "scss" > @import 'styles/reset.scss ' ;
@import 'styles/layout.scss' ;
</ style >
uni-vue3-chat layout
The overall project structure adopts three large modules: top navigation area + main content area + bottom area .
<!-- Public layout template -->
<!-- #ifdef MP - WEIXIN -->
< script > export default {
/**
* Solve the problem of mini program class and ID transparent transmission
* Configure mergeVirtualHostAttributes: true in manifest.json. It does not take effect on the WeChat applet platform. The class passed in from outside the component is not hung on the root node of the component. Add options: { virtualHost: true } to the component.
* https://github.com/dcloudio/uni-ui/issues/753
*/
options : { virtualHost : true }
}
</script>
<!-- #endif -->
< script setup > const props = defineProps ({
// Whether to display a custom tabbar showTabBar : { type : [ Boolean , String ], default : false },
})
</script>
< template >
< view class = "uv3__container flexbox flex-col flex1" >
<!-- Top slot -->
< slot name = "header" />
<!-- Content area-->
< view class = "uv3__scrollview flex1" >
< slot />
</ view >
<!-- Bottom slot-->
< slot name = "footer" />
<!-- tabbar bar -->
< uv3-tabbar v-if = "showTabBar" hideTabBar fixed />
</ view >
</ template >
uniapp imitates WeChat Jiugongge image group
<script setup>
import { onMounted, ref, computed, watch, getCurrentInstance } from 'vue'
const props = defineProps ({
// Image group
avatar : { type : Array , default : null },
})
const instance = getCurrentInstance ()
const uuid = computed ( () => Math . floor ( Math . random () * 10000 ))
const avatarPainterId = ref ( 'canvasid' + uuid. value )
const createAvatar = () => {
const ctx = uni. createCanvasContext (avatarPainterId. value , instance)
// Calculate the coordinates of the image on the canvas
const avatarSize = 12
const gap = 2
for ( let i = 0 , len = props. avatar . length ; i < len; i++) {
const row = Math . floor (i / 3 )
const col = i % 3
const x = col * (avatarSize + gap)
const y = row * (avatarSize + gap)
ctx. drawImage (props. avatar [i], x, y, avatarSize, avatarSize)
}
ctx.draw ( false , ( ) => {
// Output temporary picture
/* uni.canvasToTempFilePath({
canvasId: avatarPainterId.value,
success: (res) => {
console.log(res.tempFilePath)
}
}) */
})
}
onMounted ( () => {
createAvatar ()
})
watch ( () => props. avatar , () => {
createAvatar ()
})
</script>
< template >
< template v-if = "avatar.length > 1" >
< view class = "uv3__avatarPainter" >
< canvas :canvas-id = "avatarPainterId" class = "uv3__avatarPainter-canvas" > </ canvas >
</ view >
</ template >
< template v-else >
< image class = "uv3__avatarOne" :src = "avatar[0]" />
</ template >
</ template >
< style lang = "scss" scoped > .uv3__avatarPainter { background-color : #eee ; border-radius : 5px ; overflow : hidden; padding : 2px ; height : 44px ; width : 44px ;}
.uv3__avatarPainter-canvas { height : 100 % ; width : 100% ;}
.uv3__avatarOne { border-radius : 5px ; height : 44px ; width : 44px ;}
</ style >
<uv3-navbar :back= "true" title= "Title content" bgcolor= "#07c160" color= "#fff" fixed zIndex= "1010" />
< uv3-navbar custom bgcolor = "linear-gradient(to right, #07c160, #0000ff)" color = "#fff" center transparent zIndex = "2024" >
< template # back > < uni-icons type = "close" /> </ template >
< template # backText > < text > Homepage </ text > </ template >
< template # title >
< image src = "/static/logo.jpg" style = "height:20px;width:20px ;" /> Admin
</ template >
< template # right >
< view class = "ml-20" @ click = "handleAdd" > < text class = "iconfont icon-tianjia" > </ text > </ view >
< view class = "ml-20" > < text class = "iconfont icon-msg" > </ text > </ view >
</ template >
</ uv3-navbar >
uniapp+vue3 customized mobile phone pop-up window
uv3-popup supports component + function calls.
<script setup>
import { onMounted, ref, computed, watch, nextTick, getCurrentInstance } from 'vue'
const props = defineProps ({
...
})
const emit = defineEmits ([
'update:modelValue' ,
'open' ,
'close'
])
const instance = getCurrentInstance ()
const opts = ref ({
...props
})
const visible = ref ( false )
const closeAnim = ref ( false )
const stopTimer = ref ( null )
const oIndex = ref (props. zIndex )
const uuid = computed ( () => Math . floor ( Math . random () * 10000 ))
const positionStyle = ref ({ position : 'absolute' , left : '-999px' , top : '-999px' })
const toastIcon = {
...
}
// Open the pop-up box
const open = ( options ) => {
if (visible. value ) return
opts. value = Object . assign ({}, props, options)
// console.log('-=-=Mixed parameters: ', opts.value)
visible. value = true
// Each component of nvue is transparent by default on the Android side. If background-color is not set, ghosting problems may occur
// #ifdef APP-NVUE
if (opts. value . customStyle && !opts. value . customStyle [ 'background' ] && !opts. value . customStyle [ 'background-color' ]) {
opts. value . customStyle [ 'background' ] = '#fff'
}
// #endif
let _index = ++index
oIndex. value = _index + parseInt (opts. value . zIndex )
emit ( 'open' )
typeof opts. value . onOpen === 'function' && opts. value . onOpen ()
// Long press processing
if (opts. value . follow ) {
nextTick ( () => {
let winW = uni. getSystemInfoSync (). windowWidth
let winH = uni. getSystemInfoSync (). windowHeight
// console.log('Coordinate point Information: ', opts.value.follow)
getDom (uuid. value ). then ( res => {
// console.log('Dom size information: ', res)
if (!res) return
let pos = getPos (opts. value . follow [ 0 ], opts. value . follow [ 1 ], res. width + 15 , res. height + 15 , winW, winH)
positionStyle. value . left = pos[ 0 ] + 'px'
positionStyle. value . top = pos[ 1 ] + 'px'
})
})
}
if (opts. value . time ) {
if (stopTimer. value ) clearTimeout (stopTimer. value )
stopTimer. value = setTimeout ( () => {
close ()
}, parseInt (opts. value . time ) * 1000 )
}
}
// Close the popup
const close = () => {
if (!visible. value ) return
closeAnim. value = true
setTimeout ( () => {
visible. value = false
closeAnim. value = false
emit ( 'update:modelValue' , false )
emit ( 'close' )
typeof opts. value . onClose === 'function' && opts. value . onClose ()
positionStyle. value . left = '-999px'
positionStyle. value . top = '-999px'
stopTimer. value && clearTimeout (stopTimer. value )
}, 200 )
}
// Click on the mask layer
const handleShadeClick = () => {
if ( JSON . parse (opts . value . shadeClose )) {
close ()
}
}
// Button event
const handleBtnClick = ( e, index ) => {
let btn = opts. value . btns [index]
if (!btn?. disabled ) {
console . log ( 'Button event type: ' , typeof btn. click )
typeof btn. click === 'function' && btn. click (e)
}
}
// Get the dom width and height
const getDom = ( id ) => {
return new Promise ( ( resolve, inject ) => {
// uni.createSelectorQuery().in(this) in uniapp vue3 will report an error __route__ is not defined https ://ask.dcloud.net.cn/question/140192
uni. createSelectorQuery (). in (instance). select ( '#uapopup-' + id). fields ({
size : true ,
}, data => {
resolve (data)
}). exec ()
})
}
// Adaptive coordinate point
const getPos = ( x, y, ow, oh, winW, winH ) => {
let l = (x + ow) > winW ? x - ow : x
let t = (y + oh) > winH ? y - oh : y
return [l, t]
}
onMounted ( () => {
if (props. modelValue ) {
open ()
}
})
watch ( () => props. modelValue , ( val ) => {
// console.log(val)
if (val) {
open ()
} else {
close ()
}
})
defineExpose ({
open,
close
})
</script>
uniapp+vue3 chat module
At present, the plug-in has been released to the plug-in market for free for download and use by friends who need it.
<!-- Voice Panel-->
< view v-if = "voicePanelEnable" class = "uv3__voicepanel-popup" >
< view class = "uv3__voicepanel-body flexbox flex-col" >
<!-- Cancel sending + voice to text -->
< view v-if = "!voiceToTransfer" class = "uv3__voicepanel-transfer" >
<!-- Tip animation -->
< view class = "animtips flexbox" :class = "voiceType == 2 ? 'left ' : voiceType == 3 ? 'right' : null" > < Waves :lines = "[2, 3].includes(voiceType) ? 10 : 20" /> </ view >
<!-- Action items -->
< view class = "icobtns flexbox" >
< view class = "vbtn cancel flexbox flex-col" :class = "{'hover': voiceType == 2}" @ click = "handleVoiceCancel" > < text class = "vicon uv3 -icon uv3-icon-close" > </ text > </ view >
< view class = "vbtn word flexbox flex-col" :class = "{'hover': voiceType == 3}" > < text class = " vicon uv3-icon uv3-icon-word" > </ text > </ view >
</ view >
</ view >
<!-- Voice to text (recognition result status) -->
< view v-if = "voiceToTransfer" class = "uv3__voicepanel-transfer result fail" >
<!-- Prompt animation -->
< view class = "animtips flexbox" > < uni-icons type = "info-filled" color = "#fff" size = "20" > </ uni-icons > < text class = "c-fff" > No text recognized </ text > </ view >
< view class = "icobtns flexbox" >
< view class = "vbtn cancel flexbox flex-col" @ click = "handleVoiceCancel" > < text class = "vicon uv3-icon uv3-icon-chexiao" > </ text > Cancel </ view >
< view class = "vbtn word flexbox flex-col" > < text class = "vicon uv3-icon uv3-icon-audio" > </ text > Send original voice </ view >
< view class = "vbtn check flexbox flex-col" > < text class = "vicon uv3-icon uv3-icon-duigou" > </ text > </ view >
</ view >
</ view >
<!-- Background voice image-->
< view class = "uv3__voicepanel-cover" >
< image v-if = "!voiceToTransfer" src = "/static/voice_bg.webp" :webp = "true" mode = "widthFix " style = "width: 100%;" />
</ view >
<!-- // Prompt text -->
< view v-if = "!voiceToTransfer" class = "uv3__voicepanel-tooltip" > {{voiceTypeMap[voiceType ]}} </ view >
<!-- Background icon -->
< view v-if = "!voiceToTransfer" class = "uv3__voicepanel-fixico" > < text class = "uv3-icon uv3-icon-audio fs-50 " > </ text > </ view >
</ view >
</ view >
Okay, the above is some sharing of the practical project of developing WeChat chat with uni-app+vue3. I hope it will be helpful to everyone~
https://segmentfault.com/a/1190000044796609
