Based on uniapp+vue3+uv-ui chat example | uni-app+vite4 imitation WeChat app application

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 .

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.

a06a2fefcfa82c8e4f6a6c20244e7cfb_1289798-20240429113354730-322257750.png

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!

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

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 .

image.png
5217ddaf31a633250d13d4cce0c80304_1289798-20240429125906607-2041570472.png
<!-- 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

image.png
d570ee84994708b43342b78615433e57_1289798-20240429132644347-83378864.png
<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 >
    

uniapp+vue3 custom Navbar+Tabbar component

image.png
0f43dfbf178b0c3524d910674854dba0_1289798-20240429123458911-774585181.png
<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

image.png
image.png
image.png
d2cfd678dd640e7b7bbe723885595b6a_1289798-20240429131427286-164806501.png

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

68c716c497f576240326637ee2c3b81a_1289798-20240429133559816-1045161084.png
image.png
e57c740eab77f4f6a47efaac9ecfafdf_1289798-20240429134115447-1886078050.png

image.png
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

https://segmentfault.com/a/1190000044741696

https://segmentfault.com/a/1190000044675519

Leave a Reply

Your email address will not be published. Required fields are marked *