• vue聊天室|h5+vue仿微信聊天界面|vue仿微信


    一、项目简介

    基于Vue2.0+Vuex+vue-router+webpack2.0+es6+vuePhotoPreview+wcPop等技术架构开发的仿微信界面聊天室——vueChatRoom,实现了微信聊天下拉刷新、发送消息、表情(动图),图片、视频预览,打赏、红包等功能。

    二、技术栈

    • MVVM框架:Vue.js 2.0
    • 状态管理:Vuex
    • 页面路由:Vue-router
    • 弹窗插件:wcPop
    • 打包工具:webpack 2.0
    • 环境配置:node.js + cnpm
    • 图片插件:vue-photo-preview

    <!--顶部模板-->
    <template>
        <div class="wcim__topBar" v-show="$route.meta.showHeader">
            <div class="inner flexbox flex-alignc">
                <!-- <a class="linkico wcim__ripple-fff" href="javascript:;" @click="$router.back(-1)"><i class="iconfont icon-back"></i></a> -->
                <h4 class="barTxt flex1">
                    <div class="barCell flexbox flex__direction-column"><em class="clamp1">Vue聊天室</em></div>
                </h4>
                <a class="linkico wcim__ripple-fff" href="javascript:;"><i class="iconfont icon-search"></i></a>
            </div>
        </div>
    </template>
    
    <!--底部tabBar模板-->
    <template>
        <div class="wcim__tabBar" v-show="$route.meta.showTabBar">
            <div class="bottomfixed wcim__borT">
                <ul class="flexbox flex-alignc">
                    <router-link class="flex1" active-class="on" tag="li" to="/" exact><span class="ico"><i class="iconfont icon-tabbar_xiaoxi"></i><em class="wcim__badge">15</em></span><span class="txt">消息</span></router-link>
                    <router-link class="flex1" active-class="on" tag="li" to="/contact"><span class="ico"><i class="iconfont icon-tabbar_tongxunlu"></i></span><span class="txt">通讯录</span></router-link>
                    <router-link class="flex1" active-class="on" tag="li" to="/ucenter"><span class="ico"><i class="iconfont icon-tabbar_wo"></i></span><span class="txt"></span></router-link>
                </ul>
            </div>
        </div>
    </template>

    ◆ vue-router页面地址路由、vue钩子拦截登录状态:

    /*
     *  页面地址路由js
     */ 
    import Vue from 'vue'
    import _router from 'vue-router'
    import store from '../vuex'
    
    Vue.use(_router) //应用路由
    
    const router = new _router({
        routes: [
            // 登录、注册
            {
                path: '/login',
                component: resolve => require(['../views/auth/login'], resolve),
            },
            {
                path: '/register',
                component: resolve => require(['../views/auth/register'], resolve),
            },
    
            // 首页、通讯录、我
            {
                path: '/',
                component: resolve => require(['../views/index'], resolve),
                meta: { showHeader: true, showTabBar: true, requireAuth: true }
            },
            {
                path: '/contact',
                component: resolve => require(['../views/contact'], resolve),
                meta: { showHeader: true, showTabBar: true, requireAuth: true },
            },
            {
                path: '/contact/uinfo',
                component: resolve => require(['../views/contact/uinfo'], resolve),
            },
            {
                path: '/ucenter',
                component: resolve => require(['../views/ucenter'], resolve),
                meta: { showHeader: true, showTabBar: true, requireAuth: true }
            },
            // 聊天页面
            {
                path: '/chat/group-chat',
                component: resolve => require(['../views/chat/group-chat'], resolve),
                meta: { requireAuth: true }
            },
            {
                path: '/chat/single-chat',
                component: resolve => require(['../views/chat/single-chat'], resolve),
                meta: { requireAuth: true }
            },
            {
                path: '/chat/group-info',
                component: resolve => require(['../views/chat/group-info'], resolve),
                meta: { requireAuth: true }
            }
    
            // ...
        ]
    })
    
    // 注册全局钩子拦截登录状态
    const that = this
    router.beforeEach((to, from, next) => {
        const token = store.state.token
        // 判断该路由地址是否需要登录权限
        if(to.meta.requireAuth){
            // 通过vuex state获取当前token是否存在
            if(token){
                next()
            }else{
                // console.log('还未登录授权!')
                next()
                wcPop({
                    content: '还未登录授权!', style: 'background:#e03b30;color:#fff;', time: 2,
                    end: function(){
                        next({ path: '/login' })
                    }
                });
            }
        }else{
            next()
        }
    })
    
    export default router

    ◆ 引入第三方组件库、插件:

    // >>>引入js
    import $ from 'jquery'
    import fontsize from './assets/js/fontsize'
    
    // >>>引入弹窗插件
    import wcPop from './assets/js/wcPop/wcPop'
    import './assets/js/wcPop/skin/wcPop.css'
    
    // >>>引入饿了么移动端vue组件库
    import MintUI, { Loadmore } from 'mint-ui'
    import 'mint-ui/lib/style.css'
    Vue.component(Loadmore.name, Loadmore)
    Vue.use(MintUI)
    
    // >>>引入图片预览插件
    import photoPreview from 'vue-photo-preview'
    import 'vue-photo-preview/dist/skin.css'
    Vue.use(photoPreview, {
      loop: false,
      fullscreenEl: false, //是否全屏
      arrowEl: false, //左右按钮
    })
    
    // >>>引入地址路由
    import router from './router'
    import store from './vuex'

    ◆ 登录、注册模块验证:

    import { setToken, checkTel } from '../../utils/filters'
    export default {
        data () {
            return {
                formObj: {},
    
                vcodeText: '获取验证码',
                tel: '',
                disabled: false,
                time: 0,
            }
        },
        methods: {
            handleSubmit(){
                // console.log(this.formObj)
                // console.log(JSON.stringify(this.formObj))
    
                var that = this;
                if(!this.formObj.tel){
                    wcPop({ content: '手机号不能为空!', style: 'background:#e03b30;color:#fff;', time: 2 });
                }else if(!checkTel(this.formObj.tel)){
                    wcPop({ content: '手机号格式不正确!', style: 'background:#e03b30;color:#fff;', time: 2 });
                }else if(!this.formObj.pwd){
                    wcPop({ content: '密码不能为空!', style: 'background:#e03b30;color:#fff;', time: 2 });
                }else if(!this.formObj.vcode){
                    wcPop({ content: '验证码不能为空!', style: 'background:#e03b30;color:#fff;', time: 2 });
                }else{
                    this.$store.commit('SET_TOKEN', setToken());
                    this.$store.commit('SET_USER', this.formObj.tel);
    
                    wcPop({
                        content: '注册成功!', style: 'background:#41b883;color:#fff;', time: 2,
                        end: function(){
                            that.$router.push('/');
                        }
                    });
                }
            },
            // 60s倒计时
            handleVcode(){
                if(!this.formObj.tel){
                    wcPop({ content: '手机号不能为空!', style: 'background:#e03b30;color:#fff;', time: 2 });
                }else if(!checkTel(this.formObj.tel)){
                    wcPop({ content: '手机号格式不正确!', style: 'background:#e03b30;color:#fff;', time: 2 });
                }else{
                    this.time = 60;
                    this.disabled = true;
                    this.countDown();
                }
            },
            countDown(){
                if(this.time > 0){
                    this.time--;
                    this.vcodeText = '获取验证码('+this.time+')';
                    setTimeout(this.countDown, 1000);
                }else{
                    this.time = 0;
                    this.vcodeText = '获取验证码';
                    this.disabled = false;
                }
            }
        }
    }

    ◆ 聊天页面模块:

    // >>> 【表情、动图swiper切换模块】--------------------------
    var emotionSwiper;
    function setEmotionSwiper(tmpl) {
        var _tmpl = tmpl ? tmpl : $("#J__emotionFootTab ul li.cur").attr("tmpl");
        $("#J__swiperEmotion .swiper-container").attr("id", _tmpl);
        $("#J__swiperEmotion .swiper-wrapper").html($("." + _tmpl).html());
    
        emotionSwiper = new Swiper('#' + _tmpl, {
            // loop: true,
            // autoplay: true,
            // 分页器
            pagination: {
                el: '.pagination-emotion', clickable: true,
            },
        });
    }
    // 表情模板切换
    $("body").on("click", "#J__emotionFootTab ul li.swiperTmpl", function () {
        // 先销毁swiper
        emotionSwiper && emotionSwiper.destroy(true, true);
        var _tmpl = $(this).attr("tmpl");
        $(this).addClass("cur").siblings().removeClass("cur");
    
        setEmotionSwiper(_tmpl);
    });
    
    
    // >>> 【视频预览模块】--------------------------
    $("body").on("click", "#J__chatMsgList li .video", function () {
        var _src = $(this).find("img").attr("videoUrl"), _video;
        var videoIdx = wcPop({
            id: 'wc__previewVideo',
            skin: 'fullscreen',
            // content: '<video id="J__videoPreview" width="100%" height="100%" controls="controls" x5-video-player-type="h5" x5-video-player-fullscreen="true" webkit-playsinline preload="auto"></video>',
            content: '<video id="J__videoPreview" width="100%" height="100%" controls="controls" preload="auto"></video>',
            shade: false,
            xclose: true,
            style: 'background: #000;padding-top:48px;',
            anim: 'scaleIn',
            show: function(){
                _video = document.getElementById("J__videoPreview");
                _video.src = _src;
                if (_video.paused) {
                    _video.play();
                } else {
                    _video.pause();
                }
                // 播放结束
                _video.addEventListener("ended", function(){
                    _video.currentTime = 0;
                });
                // 退出全屏
                _video.addEventListener("x5videoexitfullscreen", function(){
                    wcPop.close(videoIdx);
                })
            }
        });
    });
    
    
    // >>> 【编辑器+表情处理模块】------------------------------------------
    // ...处理编辑器信息
    function surrounds() {
        setTimeout(function () { //chrome
            var sel = window.getSelection();
            var anchorNode = sel.anchorNode;
            if (!anchorNode) return;
            if (sel.anchorNode === $(".J__wcEditor")[0] ||
                (sel.anchorNode.nodeType === 3 && sel.anchorNode.parentNode === $(".J__wcEditor")[0])) {
    
                var range = sel.getRangeAt(0);
                var p = document.createElement("p");
                range.surroundContents(p);
                range.selectNodeContents(p);
                range.insertNode(document.createElement("br")); //chrome
                sel.collapse(p, 0);
    
                (function clearBr() {
                    var elems = [].slice.call($(".J__wcEditor")[0].children);
                    for (var i = 0, len = elems.length; i < len; i++) {
                        var el = elems[i];
                        if (el.tagName.toLowerCase() == "br") {
                            $(".J__wcEditor")[0].removeChild(el);
                        }
                    }
                    elems.length = 0;
                })();
            }
        }, 10);
    }
    
    // 定义最后光标位置
    var _lastRange = null, _sel = window.getSelection && window.getSelection();
    var _rng = {
        getRange: function () {
            if (_sel && _sel.rangeCount > 0) {
                return _sel.getRangeAt(0);
            }
        },
        addRange: function () {
            if (_lastRange) {
                _sel.removeAllRanges();
                _sel.addRange(_lastRange);
            }
        }
    }
    
    // 格式化编辑器包含标签
    $("body").on("click", ".J__wcEditor", function(){
        $(".wc__choose-panel").hide();
    });
    $("body").on("focus", ".J__wcEditor", function(){
        surrounds();
    });
    $("body").on("input", ".J__wcEditor", function(){
        surrounds();
    });
    
    // 点击表情
    $("body").on("click", "#J__swiperEmotion .face-list span img", function () {
        var that = $(this), range;
    
        if (that.hasClass("face")) { //小表情
            var img = that[0].cloneNode(true);
            if (!$(".J__wcEditor")[0].childNodes.length) {
                $(".J__wcEditor")[0].focus();
            }
            $(".J__wcEditor")[0].blur(); //输入表情时禁止输入法
    
            setTimeout(function () {
                if (document.selection && document.selection.createRange) {
                    document.selection.createRange().pasteHTML(img);
                } else if (window.getSelection && window.getSelection().getRangeAt) {
                    range = _rng.getRange();
                    range.insertNode(img);
                    range.collapse(false);
    
                    _lastRange = range; //记录当前光标位置 (否则光标会跑到表情前面)
                    _rng.addRange();
                }
            }, 10);
        } else if (that.hasClass("del")) { //删除
            // _editor.focus();
            $(".J__wcEditor")[0].blur(); //输入表情时禁止输入法
    
            setTimeout(function () {
                range = _rng.getRange();
                range.collapse(false);
                document.execCommand("delete");
    
                _lastRange = range;
                _rng.addRange();
            }, 10);
        } else if (that.hasClass("lg-face")) { //大表情
            var _img = that.parent().html();
            var _tpl = [
                '<li class="me">
                    <div class="content">
                        <p class="author">王梅(Fine)</p>
                        <div class="msg lgface">'+ _img + '</div>
                    </div>
                    <a class="avatar" href="/contact/uinfo"><img src="src/assets/img/uimg/u__chat-img11.jpg" /></a>
                </li>'
            ].join("");
            $("#J__chatMsgList").append(_tpl);
    
            wchat_ToBottom();
        }
    });

  • 相关阅读:
    rust 实战 实现一个线程工作池 ThreadPool
    rust Cell 与 RefCell的区别
    使用pre标签显示原始文本并自动换行
    rust实战 newtype模式
    什么是幻读,怎么解决幻读
    rust match 模式匹配摘录
    Elasticsearch 8的版本来了;可以直接在 Elasticsearch 中使用 PyTorch Machine Learning 模型
    神奇的库 phone
    python中单例的实现
    斐波那契查找
  • 原文地址:https://www.cnblogs.com/xiaoyan2017/p/10657781.html
Copyright © 2020-2023  润新知