基於Vue2.0+Vuex+vue-router+webpack2.0+es6+vuePhotoPreview+wcPop等技術架構開發的仿微信界面聊天室——vueChatRoom,實現了微信聊天下拉刷新、發送消息、表情(動圖),圖片、視頻預覽,打賞、紅包等功能。javascript
<!--頂部模板--> <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鉤子攔截登陸狀態:css
/* * 頁面地址路由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
◆ 引入第三方組件庫、插件:html
// >>>引入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'
◆ 登陸、註冊模塊驗證:vue
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; } } } }
◆ 聊天頁面模塊:java
// >>> 【表情、動圖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(); } });