T O P

[资源分享]     Vue3.0聊天室|vue3+vant3仿微信聊天实例|vue3.x仿微信app界面

  • By - 楼主

  • 2021-01-08 12:20:09
  • 一、项目简介

    基于Vue3.0+Vant3.x+Vuex4.x+Vue-router4+V3Popup等技术开发实现的仿微信手机App聊天实例项目Vue3-Chatroom。实现了发送图文表情消息/gif图、图片/视频预览、网址查看、下拉刷新功能、红包/朋友圈等功能。

    二、技术选型

    • 编辑器:VScode/Sublime
    • 使用技术:Vue3.x+Vuex4.x+Vue-Router4
    • UI组件库:Vant-UI3.x (有赞移动端Vue3组件库)
    • 弹层组件:V3Popup(基于vue3.0封装自定义弹出层组件)
    • iconfont图标:阿里字体图标库
    • 自定义顶部导航栏+底部tabBar

    ◆ 项目结构

    ◆ Vue3自定义顶部Navbar+底部Tabbar

    项目中顶部导航条和底部tabbar组件均是在之前vue2版的基础上开发的vue3版。

     

     

    大家如果感兴趣,可以去看看之前的一篇分享文章。

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

    ◆ Vue3自定义弹层组件

    项目中使用到的弹框场景,均是最新开发的vue3.0自定义弹框组件v3popup来实现的。

    v3popup 一款汇集多种弹框类型及动画效果的vue3.x弹框组件。开发灵感来源于之前的vue2版,并在功能及效果上保持一致性。

    如果大家对具体的实现感兴趣的话,可以去看看之前的这篇文章。

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

    ◆ vue.config.js配置

    /**
     * Vue3基础配置文件
     */
    
    const path = require('path')
    
    module.exports = {
        // 基本路径
        // publicPath: '/',
    
        // 输出文件目录
        // outputDir: 'dist',
    
        // assetsDir: '',
    
        // 环境配置
        devServer: {
            // host: 'localhost',
            // port: 8080,
            // 是否开启https
            https: false,
            // 编译完是否打开网页
            open: false,
            
            // 代理配置
            // proxy: {
            //     '^/api': {
            //         target: '<url>',
            //         ws: true,
            //         changeOrigin: true
            //     },
            //     '^/foo': {
            //         target: '<other_url>'
            //     }
            // }
        },
    
        // webpack配置
        chainWebpack: config => {
            // 配置路径别名
            config.resolve.alias
                .set('@', path.join(__dirname, 'src'))
                .set('@assets', path.join(__dirname, 'src/assets'))
                .set('@components', path.join(__dirname, 'src/components'))
                .set('@views', path.join(__dirname, 'src/views'))
        }
    }

    ◆ Vue3.0主入口页面

    在main.js中配置一些状态管理、地址路由,引入一些js和公共组件。

    import { createApp } from 'vue'
    import App from './App.vue'
    
    // 引入vuex和地址路由
    import store from './store'
    import router from './router'
    
    // 引入js
    import '@assets/js/fontSize'
    
    // 引入公共组件
    import Plugins from './plugins'
    
    const app = createApp(App)
    
    app.use(store)
    app.use(router)
    app.use(Plugins)
    
    app.mount('#app')

    ◆ Vuex + 表单登录验证

    vue3中状态管理及表单验证操作。

    import { createStore } from 'vuex'
    
    export default createStore({
        state: {
            user: localStorage.getItem('user') || null,
            token: localStorage.getItem('token') || null
        },
        mutations: {
            SET_USER(state, data) {
                localStorage.setItem('user', data)
                state.user = data
            },
            SET_TOKEN(state, data) {
                localStorage.setItem('token', data)
                state.token = data
            },
            LOGOUT(state) {
                localStorage.removeItem('user')
                localStorage.removeItem('token')
                state.user = null
                state.token = null
            }
        },
        getters: {},
        actions: {}
    })
    <script>
    import { reactive, inject, getCurrentInstance } from 'vue'
    export default {
        components: {},
        setup() {
            const { ctx } = getCurrentInstance()
    
            const v3popup = inject('v3popup')
            const utils = inject('utils')
            const formObj = reactive({})
    
            // ...
    
            const handleSubmit = () => {
                if(!formObj.tel){
                    Snackbar('手机号不能为空!')
                }else if(!utils.checkTel(formObj.tel)){
                    Snackbar('手机号格式不正确!')
                }else if(!formObj.pwd){
                    Snackbar('密码不能为空!')
                }else{
                    ctx.$store.commit('SET_TOKEN', utils.setToken());
                    ctx.$store.commit('SET_USER', formObj.tel);
    
                    // ...
                }
            }
    
            return {
                formObj,
                handleSubmit
            }
        }
    }
    </script>

    ◆ 仿微信朋友圈透明导航实现

    vue3通过在onMounted中监听scroll事件来控制顶部导航透明显示。

      

    <!-- //朋友圈模板 -->
    <template>
        <div>
            <header-bar :bgcolor="headerBg" transparent zIndex="1010">
                <template #backIco><i class="iconfont icon-arrL"></i></template>
                <template v-slot:right><div @click="isShowPublish=true"><i class="iconfont icon-tianjia"></i></div></template>
            </header-bar>
    
            <div class="vui__scrollview flex1" ref="scrollview">
                ...
            </div>
        </div>
    </template>
    
    <script>
    import { onMounted, onBeforeUnmount, ref, reactive, toRefs, inject } from 'vue'
    import { ImagePreview } from 'vant'
    
    export default {
        components: {},
        setup() {
            const scrollview = ref(null)
    
            const data = reactive({
                headerBg: 'transparent',
                // ...
            })
    
            onMounted(() => {
                scrollview.value.addEventListener('scroll', handleScroll)
            })
    
            onBeforeUnmount(() => {
                scrollview.value.removeEventListener('scroll', handleScroll)
            })
    
            // 页面滚动处理
            const handleScroll = (e) => {
                if(e.target.scrollTop > 160) {
                    data.headerBg = 'linear-gradient(to right, #00d2ee, #00e077)'
                }else {
                    data.headerBg = 'transparent';
                }
            }
    
            // ...
    
            return {
                ...toRefs(data),
                scrollview,
                
                // ...
            }
        }
    }
    </script>

    ◆ Vue3聊天代码片段

    vue3中实现聊天功能实现,其中编辑器支持图文插入,并且单独抽离了一个Editor.vue组件。

    /**
     * @Desc     Vue3.0仿微信聊天实例
     * @Time     andy by 2021-01
     * @About    Q:282310962  wx:xy190310
     */
    <script>
    import { onMounted, onUnmounted, ref, reactive, toRefs, nextTick, inject } from 'vue'
    import { useRouter } from 'vue-router'
    import Editor from './editor.vue'
    import { ImagePreview } from 'vant'
    
    // ...
    
    export default {
        components: {
            Editor
        },
        setup() {
            const scrollview = ref(null)
            const editorRef = ref(null)
            const pickImageRef = ref(null)
            const pickVideoRef = ref(null)
            const playerRef = ref(null)
    
            const router = useRouter()
    
            const v3popup = inject('v3popup')
    
            const data = reactive({
                editorText: '',
    
                isShowFootBar: false,
                showFootBarIndex: 0,
    
                // 表情列表
                emojList: emoJSON,
    
                // 消息记录
                msgList: msgJSON,
    
                // 链接预览
                isShowLinkView: false,
                linkView: '',
    
                // ...
            })
    
            onMounted(() => {
                nextTick(() => {
                    imgLoaded(scrollview)
                })
            })
    
            onUnmounted(() => {
                window.removeEventListener('popstate', handlePopStateClosed, false)
            })
            // 监听地址打开
            const handlePopStateOpen = () => {
                if(window.history && window.history.pushState) {
                    history.pushState(null, null, document.URL)
                    window.addEventListener('popstate', handlePopStateClosed, false)
                }
            }
            // 监听地址关闭(手机回退按钮事件)
            const handlePopStateClosed = () => {
                // console.log('监听关闭视频事件!')
                data.isShowLinkView = false
                data.isShowVideoPlayer = false
            }
    
            /**
             * 滚动条到底部
             * @param ref 容器ref
             */
            const scrollBottom = (ref) => {
                let viewport = ref.value
                if(viewport) {
                    viewport.scrollTop = viewport.scrollHeight
                }
            }
    
            // 点击聊天消息区域
            const handleMsgPanelClicked = () => {
                if(!data.isShowFootBar) return
                data.isShowFootBar = false
            }
    
            /**
             * 表情|选择区切换
             * @param index 切换索引
             */
            const handleEmojChooseView = (index) => {
                data.isShowFootBar = true
                data.showFootBarIndex = index
    
                nextTick(() => { imgLoaded(scrollview) })
            }
            
            /**
             * 表情Tab切换
             * @param index 索引index
             */
            const handleEmojTab = (index) => {
                let emojLs = data.emojList
                for(var i = 0, len = emojLs.length; i < len; i++) {
                    emojLs[i].selected = false
                }
                emojLs[index].selected = true
                data.emojList = emojLs
            }
    
    
            /* ---------- { 编辑器|表情模块 } ---------- */
            // 点击编辑器
            const handleEditorClick = () => {
                // console.log('点击编辑器')
                data.isShowFootBar = false
            }
    
            // 编辑器获取焦点
            const handleEditorFocus = () => {
                // console.log('编辑器获取焦点')
            }
    
            // 编辑器失去焦点
            const handleEditorBlur = () => {
                // console.log('编辑器失去焦点')
            }
    
            // 判断编辑器是否为空
            const isEmpty = (html) => {
                html = html.replace(/<br[\s/]{0,2}>/ig, "\r\n")
                html = html.replace(/<[^img].*?>/ig, "")
                html = html.replace(/&nbsp;/ig, "")
                return html.replace(/\r\n|\n|\r/, "").replace(/(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g, "") == ""
            }
            // 匹配转换聊天消息中链接
            const transferHTML = (html) => {
                let reg = /(http:\/\/|https:\/\/)((\w|=|\?|\.|\/|&|-)+)/g
                return html.replace(reg, "<a href='$1$2'>$1$2</a>")
            }
    
            // ...
    
    
            /* ---------- { 选择功能模块 } ---------- */
            // 选择图片
            const handleChooseImage = () => {
                let msgLs = data.msgList
                let len = msgLs.length
                // 消息队列
                let arrLS = {
                    // ...
                }
    
                let file = pickImageRef.value.files[0]
                if(!file) return
                let size = Math.floor(file.size / 1024)
                if(size > 2*1024) {
                    v3popup({content: '请选择2MB以内的图片!'})
                    return false
                }
                var reader = new FileReader()
                reader.readAsDataURL(file)
                reader.onload = function() {
                    let img = this.result
    
                    // ...
                }
            }
    
            // 预览图片
            const handleImgPreview = (src) => {
                ImagePreview({
                    images: [
                        src
                    ],
                    showIndex: false,
                    showIndicators: true,
                    closeable: true,
                });
            }
    
    
            /* ---------- { 视频功能模块 } ---------- */
            // 播放视频
            const handleVideoPlayed = (item) => {
                data.isShowVideoPlayer = true
                data.videoList = item
    
                nextTick(() => {
                    playerRef.value.play()
                })
    
                // 监听手机回退按钮事件
                handlePopStateOpen()
            }
    
            // 抖一抖
            const handleShakeWin = () => {
                let ntbox = document.querySelector('.vui__container')
                ntbox.classList.add('shake')
                setTimeout(() => {
                    ntbox.classList.remove('shake')
                }, 1000);
            }
    
            // ...
    
            return {
                ...toRefs(data),
    
                scrollview,
                editorRef,
                pickImageRef,
                pickVideoRef,
                playerRef,
    
                handleMsgPanelClicked,
                handleMsgClicked,
    
                // ...
            }
        } 
    }
    </script>

    okey,以上就是基于vue3开发仿微信界面聊天室的介绍。希望大家能喜欢~~:muscle::muscle:

    最后附上一个Electron+Vue聊天实例

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

     

    本帖子中包含资源

    您需要 登录 才可以下载,没有帐号?立即注册