vue聊天室|h5+vue仿微信聊天界面|vue仿微信
程序员文章站
2022-05-25 17:34:21
一、项目简介 基于Vue2.0+Vuex+vue-router+webpack2.0+es6+vuePhotoPreview+wcPop等技术架构开发的仿微信界面聊天室——vueChatRoom,实现了微信聊天下拉刷新、发送消息、表情(动图),图片、视频预览,打赏、红包等功能。 二、技术栈 MVVM ......
一、项目简介
基于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(); } });
上一篇: numpy(二)
下一篇: python的id()函数介绍