在游戏内嵌项目中体验Vue2.0全家桶
前言
最近在做率土之滨海外版的内嵌项目,其实之前已经做过一版,但是需求不是很满意,然后又改了设计,改了功能,最终变成一个小论坛:有发帖、回帖、发帖和回帖都能上传图片、点赞、帖子列表、我的帖子、我的点赞、我的回帖等等功能。来组动态预览图:
相比第一版,增加了几个接口功能。而本次开发使用了Vue2.0 + Vue-router + Vue-resource + Vuex + ES6
的组合,原因如下:
之前做过一版,二次开发如果在旧版的基础改会缺少点挑战性,相反正因为熟悉,意味着在项目计划排期内有更多尝试新技术的可能性
vue在社区很火,而其在单页面据说有不错的体验,看起来很适合内嵌这种项目
vue的引入
先来交代一下项目中如何引入vue,因为本次体验还没有结合自动化构建工具,所以,使用原始的script
标签引入,下面是我们的cdn资源:
1 |
然后就可以码了。
好不好用,适不适合,还是要自己体验过才知道,本文不是vue的入门教程,Hello World
教程在vue官网有,对各位看官来说没什么意义。本文将用zepto实现和vue实现同一个功能做对比的方式,直观的体现vue和旧方式在本项目实现上有什么不同,看看能否引起看官的兴趣去亲自体验下。
VS vue2.0
路由功能
路由功能是单页面的重要功能,我们先从搭起路由功能开始对比:
旧方式
其实也说不上什么旧的方式,就是自己用HTML5 History API
来操作浏览器的历史记录,需要自己去实现什么时候pushState
,什么时候replaceState
,然后在手动拼接不同的url,比如http://xxx.163.com?state=recommend&subnav=graphic
,通过url的不同参数(state,subnav)来代表不同的路由状态。最后通过监控url参数的变化来展示不同状态的内容。
操作历史记录很容易,核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | /** * 操作浏览器的历史记录 * @state 链接参数,如 'subnav=mytopic' 默认为空则使用不带任何参数的当前链接(选填) * @action 操作动作,有push和replace,默认为push(选填) */ var controlHistory = function (state, action){ action = action ? action : 'push' ; if (!history.pushState || !history.replaceState){ return ;} var loc = location.href, join = loc.split( '?' )[1] ? '&' : '?' ; if (state) { if (loc.indexOf(state.split( '=' )[0]) > -1) { var p = state.split( '=' )[0], new_s = params(p); if (new_s){ (action === 'push' ) && history.pushState( null , '' , loc.replace(new_s, state.split( '=' )[1])); (action === 'replace' ) && history.replaceState( null , '' , loc.replace(new_s, state.split( '=' )[1])); } else { (action === 'push' ) && history.pushState( null , '' , loc.replace(state.split( '=' )[0]+ '=' , state)); (action === 'replace' ) && history.replaceState( null , '' , loc.replace(state.split( '=' )[0]+ '=' , state)); } } else { (action === 'push' ) && history.pushState( null , '' , loc.split( '#' )[0] + join + state); (action === 'replace' ) && history.replaceState( null , '' , loc.split( '#' )[0] + join + state); } } else { (action === 'push' ) && history.pushState( null , '' , loc.split( '?' )[0]); (action === 'replace' ) && history.replaceState( null , '' , loc.split( '?' )[0]); } }; |
然后再在需要作为单独路由直接打开定位到的地方,在用户点击时操作历史记录,比如左边的大导航:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | //监听左边导航点击 $( 'Jleft' ).on( 'touchstart' , 'span[data-state]' , function (e){ e.preventDefault(); var $ this = $( this ), state = $ this .data( 'state' ); //展示当前路由状态的行为处理 triggerClick($ this , state); //更新历史记录 controlHistory( 'state=' + state); //当页面的路由状态很多的时候,比如除了state(左边大导航)还有subnav(大导航对应的子导航)和link(帖子链接),我们还有去手动去管理其它参数 //如果存在link参数则去掉,避免切换导航后刷新又打开link内容 if (_UTIL.params( 'link' )){ _UTIL.controlHistory( 'link=' , 'replace' ); } //如果存在subnav参数则去掉 if (_UTIL.params( 'subnav' )){ _UTIL.controlHistory( 'subnav=' , 'replace' ); } }); //监听后退 if (history.pushState) { window.addEventListener( 'popstate' , function () { //根据后退后的url去展示新的路由状态 initState(); }); } |
然后点击子导航
和帖子链接
也要做这些处理,这种方式随着状态的增加,管理起来不方便,很容易错漏。当然上面只是我实现的一种方式,不代表只能这样写,肯定还有其它方式,比如说不一定要用url的参数的方式。但是总的来说,都是需要自己实现路由管理,比较麻烦。
vue + vue-router方式
vue本身也不具备强大的路由功能,只是也可以结合HTML5 History API
,思路和旧方式差不多,只是一些实现细节有了vue的特性。
而vue是个渐进式的js框架,什么是渐进式?渐进式可以理解为提供了基础而必须的功能,然后再根据你的需求去加入新的库配合。并不是说用到vue的项目一定要路由功能,如果你需要路由,则可以加入官方推荐的vue-router库:
1 |
把vue-router
引进就可以使用了,当然官方说也可以整合其它第三方的路由。
下面来看下vue-router是如何实现路由的,核心实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | < div class = "main-left" id = "Jleft" > < div class = "main-left-nav" v-on:touchend = "changeNav('Discussion')" >Discussion</ div > < div class = "main-left-subnav" > < span v-on:touchend = "changeSubnav('My Topic')" >My Topic</ span > < span v-on:touchend = "changeSubnav('My Reply')" >My Reply</ span > < span v-on:touchend = "changeSubnav('My Favorite')" >My Favorite</ span > </ div > </ div > < div class = "main-right" id = "Jright" > <!-- 路由出口 --> <!-- 路由匹配到的组件将渲染在这里 --> < router-view ></ router-view > </ div > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | // 1. 定义(路由)组件。 // 话题列表 const Discussion = Vue.component( 'discussion-component' , { template: __inline( '../../inline/discussion.html' ) }); // 我的话题、我的回复、我的点赞列表 const MyList = Vue.component( 'mylist-component' , { template: __inline( '../../inline/mylist.html' ) }); //发表话题 const Post = Vue.component( 'post-component' , { template: __inline( '../../inline/post.html' ) }); // 话题详情 const Details = Vue.component( 'details-component' , { template: __inline( '../../inline/details.html' ) }); // 2. 定义路由和创建 router 实例 每个路由应该映射一个组件 const router = new VueRouter({ routes: [ { //默认是discussion列表界面 path: '/' , name: 'discussion' , component: Discussion }, { //发帖界面 path: '/discussion/post' , name: 'discussion_post' , component: Post }, { //某个帖子详情界面 path: '/discussion/details/:id' , name: 'discussion_details' , component: Details }, { //discussion下的某个子导航,比如 MyTopic path: '/discussion/:subnav' , name: 'discussion_subnav' , component: MyList } ] }); // 3. 创建和挂载根实例 // vue 主程序 new Vue({ el: '#vue-app' , mounted() { this .$nextTick(()=>{ this .routerUpdate(); //页面初始化时更新路由 }); }, watch: { //监听路由变化 $route: 'routerUpdate' }, methods: { changeNav(item) { //改变左边一级导航 router.push({ path: item.toLowerCase().replace(/discussion/ig, '/' )}); }, changeSubnav(item) { //改变左边二级导航 let n = item.toLowerCase().replace(/\s/ig, '' ); router.push({ params: {subnav: n}, name: 'discussion_subnav' }); }, routerUpdate() { //路由更新 if (! this .$route.name){ return ;} //根据路由实例 $route 可以获取路由元信息,比如name console.log( this .$route.name) // 这里就可以写路由更新后,配置每个路由独有的状态 } }, router }); |
左边导航
左边导航是一个一级导航包含二级导航的结构,然后在一级导航选中的时候二级导航才会展开的交互。
旧方式
旧方式,一般就是写死html结构,再加上js点击事件控制,核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | < div class = "main-left" id = "Jleft" > < div class = "main-left-item" > < div class = "main-left-nav" >一级导航1</ div > < div class = "main-left-subnav" > < span >二级导航1</ span > < span >二级导航1</ span > < span >二级导航1</ span > </ div > </ div > < div class = "main-left-item" > < div class = "main-left-nav" >一级导航2</ div > < div class = "main-left-subnav" > < span >二级导航2</ span > < span >二级导航2</ span > </ div > </ div > </ div > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //一级导航 $( '#Jleft' ).on( 'touchstart' , '.main-left-nav' , function (e){ e.preventDefault(); var $ this = $( this ); $ this .siblings( '.main-left-subnav' ).show(); $ this .parent().siblings( '.cur' ).removeClass( 'cur' ); $ this .parent().addClass( 'cur' ); }); //二级导航 $( '#Jleft' ).on( 'touchstart' , '.main-left-subnav span' , function (e){ e.preventDefault(); var $ this = $( this ); $ this .siblings( '.cur' ).removeClass( 'cur' ); $ this .addClass( 'cur' ); }); |
vue方式
因为考虑到vue的事件绑定方式不一样,所以把导航数据通过数组保存,然后再渲染html结构,所以先上js代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | // vue 主程序 new Vue({ el: '#vue-app' , data: { left_nav: [ { nav: '一级导航1' , subnav: [{ name: '二级导航1' },{ name: '二级导航1' },{ name: '二级导航1' }] }, { nav: '一级导航2' , subnav: [{ name: '二级导航2' },{ name: '二级导航2' },{ name: '二级导航2' }] } ], cur_nav: 'discussion' , cur_subnav: '' }, methods: { changeNav(item) { //改变左边一级导航 //do something... }, changeSubnav(item) { //改变左边二级导航 //do something... } } }); |
渲染html结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <div class="main-left" id="Jleft"> <div class="main-left-item" v-for="item in left_nav"> <div class="main-left-nav" v-bind:class="{cur: cur_nav == item.nav.toLowerCase()}" v-text="item.nav" v-on:touchend="changeNav(item)"></div> <div class="main-left-subnav" v-show="cur_nav == item.nav.toLowerCase()"> <span v-for="item_subnav in item.subnav" v-bind:class="{cur: cur_subnav == item_subnav.name.toLowerCase().replace(/\s/ig, '')}" v-text="item_subnav.name" v-on:touchend="changeSubnav(item_subnav)" > </span> </div> </div> </div> |
可以看到,通过两个遍历就能把导航结构渲染出来,这样后面导航拓展直接修改数组,也方便事件绑定(只需要写一次)。然后通过cur_nav
和cur_subnav
来标记当前的一级导航和二级导航来显示高亮样式,只需要在路由更新里更新这两个值,导航高亮状态和二级导航的显示隐藏就自动更新,不需要重新更新DOM节点的样式。
数据接口请求
旧方式
通过ajax来请求接口,举个jsonp接口栗子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | var getData = function (url,data,success,error){ $.ajax({ url: url , type: 'get' , data: data , dataType: 'jsonp' , success: function (data){ success(data); }, error: function (){ alert( '网络信号不好,请刷新再试' ); if (error){ error(); } } }); }; getData( { 'limit' : 10, 'start' : 1 }, function (data){ //请求成功数据处理 console.log(data) }); |
vue + vue-resource
这里需要用到请求接口功能,vue也有一个和ajax类似的库vue-resource
:
1 | < script src = "https://nie.res.netease.com/comm/js/nie/ref/??vue.js,vue-router.js,vue-resource.js" ></ script > |
1 2 3 4 5 6 7 8 9 10 11 12 13 | //获取帖子列表 params:{ limit: 10, start: 1 } }).then(response => { // success callback const data = response.body; console.log(data) }, () => { // error callback }); |
列表渲染和滚动加载更多
旧方式
最原始的就是通过字符串拼接(ES6的字符拼接的确比之前的要好用多了,有点写模板的感觉,也不容易出错),然后再通过dom渲染;再或者搞个模板引擎渲染。
下面用的是baidutemplate
方式,所以需要先引入这个模板引擎库:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | get_list({ 'limit' : 10, 'start' : 1 }, function (data){ var new_html = '' ; data.result.forEach( function (item){ var news_tmpl = __inline( '../../template/recommend_news.tmpl' ); //引入模板 new_html += news_tmpl({ item: item }); }); $( '#Jlist' )append(new_html); }); |
recommend_news.tmpl
模板:
<p class="bbs-list-item"> <% if(item.tag == 1) {%> <span class="bbs-list-item-icon vip"></span> <% } %> <% if(!!item.pic && !!item.pic[0]) {%> <span class="bbs-list-item-icon img"></span> <% } %> <span class="bbs-list-item-tit"><%= item.title %></span> <span class="bbs-list-item-author"><%= item.nick %></span> <span class="bbs-list-item-time"><%= item.op_time %></span> <span class="bbs-list-item-reply"><%= item.cm_cnt %></span> <span class="bbs-list-item-like"><%= item.thumbups %></span> </p>
基本就像上面那样,但有时候接口返回的数据需要先做一些特殊处理才能渲染,比如返回的时间,需要按照特定格式过滤后,才能传到模板渲染。
说完列表渲染,就到滚动加载了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | var busy = false , curpage = 1, $rigth = $( 'Jringt' ) $ringtscroll= $( 'Jringtscroll' ); $ringtscroll.bind( 'scroll' , function (){ //监听scroll事件 if (busy){ return ; } busy = true ; var $btn_getmore = $( '#Jbtn_getmore' ); //滚动加载文案提示 $btn_getmore.text( 'loading...' ) var win_top = $win.scrollTop(), h = $ringtscroll.height(), win_h = $rigth.height(); // 滚动到底部 if ((win_top + h) > win_h) { curpage ++; get_list(curpage); } }); |
上面是个简单版,监听滚动事件,每次滚动检测滚到底部时触发加载下一页。逻辑不是很复杂,但是在多个列表下,或者切换列表后,在同一个scroll
监听里处理不同的逻辑,记录不同的当前列表的当前页码等等,其实还是不简单的。
vue方式
上面导航渲染其实就已经包含了列表渲染,这里再简单列下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | // vue 主程序 new Vue({ el: '#vue-app' , data: { list: [] }, mounted() { this .$nextTick(()=>{ this .get_list(); }); }, methods: { get_list(){ params:{ limit: 10, start: 1 } }).then(response => { const data = response.body; if (data.success){ this .list = data; } else { //失败处理 } }, () => { //错误处理 }); } } }); // 过滤器 Vue.filter( 'formatNum' , value=>{ if (!value) return '0' ; if (value < 1000){ return value; } return '999+' ; }); Vue.filter( 'formatTime' , value=>{ if (!value) return '0' ; let time = value.split( ' ' )[0]; time = time.split( '-' ); return `${time[1]}/${time[2]}`; }); |
html渲染
1 2 3 4 5 6 7 8 9 | < p class = "bbs-list-item" v-for = "item in topicslist" > < span class = "bbs-list-item-icon vip" v-if = "item.tag == 1" ></ span > < span class = "bbs-list-item-icon img" v-if = "!!item.pic && !!item.pic[0]" ></ span > < span class = "bbs-list-item-tit" v-text = "item.title" ></ span > < span class = "bbs-list-item-author" v-text = "item.nick" ></ span > < span class = "bbs-list-item-time" >{{item.op_time | formatTime}}</ span > < span class = "bbs-list-item-reply" >{{item.cm_cnt | formatNum}}</ span > < span class = "bbs-list-item-like" >{{item.thumbups | formatNum}}</ span > </ p > |
而这里有个很方便的方式,就是过滤器,通过插值{{item.op_time | formatTime}}
就可以把插入的时间经过过滤器处理后再渲染。
vue本身也没有什么滚动加载的黑魔法,但是我找到了一个相关插件vue-infinite-scroll.js
,我们来看看是如何工作的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | < div class = "main-right-inner-mod main-right-inner-discussion" v-infinite-scroll = "loadMore" infinite-scroll-disabled = "busy" infinite-scroll-distance = "5" > < div class = "bbs-list Jpostlist" > < p class = "bbs-list-item" v-on:click = "viewDetail(item.apply_id)" v-for = "item in topicslist" > < span class = "bbs-list-item-icon vip" v-if = "item.tag == 1" ></ span > < span class = "bbs-list-item-icon img" v-if = "!!item.pic && !!item.pic[0]" ></ span > < span class = "bbs-list-item-tit" v-text = "item.title" ></ span > < span class = "bbs-list-item-author" v-text = "item.nick" ></ span > < span class = "bbs-list-item-time" >{{item.op_time | formatTime}}</ span > < span class = "bbs-list-item-reply" >{{item.cm_cnt | formatNum}}</ span > < span class = "bbs-list-item-like" >{{item.thumbups | formatNum}}</ span > </ p > </ div > < span class = "btn_getmore" v-text = "btn_getmore_text" >滚动加载</ span > </ div > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | // vue 主程序 new Vue({ el: '#vue-app' , data: { busy: false , curpage: 1, list: [] }, methods: { get_list(){ params:{ limit: 10, start: this .curpage } }).then(response => { const data = response.body; if (data.success){ this .list = data; } else { //失败处理 } }, () => { //错误处理 }); } }, loadMore(){ //滚动加载更多 this .busy = true ; this .btn_getmore_text = 'loading...' ; this .curpage ++; this .get_list(); } }); |
相比之前列表渲染,表面上就多了loadMore
的“几行代码”(实际上核心逻辑都在vue-infinite-scroll.js
实现)。代码量可能少不了多少,但是使用的确方便了不少。
表单处理
好了,终于说到表单处理了,这个项目主要有发帖和回帖操作,表单功能差不多。
旧方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <!-- 话题输入 --> < div class = "reply-input" id = "Jpostinput" > < div class = "reply-input-item" > < span >Title</ span > < span class = "s-input" > < input type = "text" class = "post-title" maxlength = "60" placeholder = "What do you want to talk about? (No more than 60 characters)" > </ span > < i class = "title-tips" >( 0 characters entered )</ i > </ div > < div class = "reply-input-item" > < span >Text</ span > < textarea class = "post-con" placeholder = "What do you want to share? (No more than 300 characters)" rows = "6" maxlength = "300" ></ textarea > < i class = "title-tips" >( 0 characters entered )</ i > </ div > < div class = "reply-input-bottom" > < span class = "post-submit-btn submit-btn no-text" >confirm</ span > </ div > </ div > |
在js,需要对当前输入的字数进行检测提示,可以通过onpropertychange
事件来实现,然后监听提交按钮,提交时再进行输入简单检测等:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | //标题输入长度提示 var $title = $( '#Jpostinput .post-title' ), //标题 $title_tips = $( '#Jpostinput .title-tips' ); $title[0]&&$title[0].addEventListener( "input" , function (){ var len = $title.val().length; $title_tips.text( '( ' + len + ' characters entered )' ); }, false ); //正文也是同样的做法,此处省略 //提交 $( '#Jpostinput .post-submit-btn' ).on( 'click' , function (){ if ($title.val().length < 1){ alert( '标题不能为空' ); } else if ($title.val().length > 60){ alert( '标题太长了' ); } ... //检测通过后才提交给接口 ... }) |
vue方式
双向数据绑定是这类框架的卖点之一,而体现这个卖点会经常用表单做栗子。来看下vue是怎么做的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <!-- 话题输入 --> < div class = "reply-input" id = "Jpostinput" > < div class = "reply-input-item" > < span >Title</ span > < span class = "s-input" > < input type = "text" class = "post-title" maxlength = "60" placeholder = "What do you want to talk about? (No more than 60 characters)" v-model = "title" > </ span > < i v-show="title.length > 0">( {{ title.length }} characters entered )</ i > </ div > < div class = "reply-input-item" > < span >Text</ span > < textarea class = "post-con" placeholder = "What do you want to share? (No more than 300 characters)" rows = "6" v-model = "content" maxlength = "300" ></ textarea > < i v-show="content.length > 0">( {{ content.length }} characters entered )</ i > </ div > < div class = "reply-input-bottom" > < span class = "post-submit-btn submit-btn no-text" v-on:touchend = "topic_post()" v-bind:class = "{disable: is_submit || title.length == 0 || content.length == 0}" >confirm</ span > </ div > </ div > |
通过上面的代码可以发现,通过title
和content
来和输入绑定,实时计算输入长度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | new Vue({ el: '#vue-app' , data: { title: '' , content: '' }, methods: { topic_post(){ if ( this .title.replace(/\s/ig, '' ).length < 1) { alert( '标题不能为空' ); return ; } else if ( this .title.length > 61) { alert( '标题太长了' ); return ; } if ( this .content.replace(/\s/ig, '' ).length < 1) { alert( '内容不能为空' ); return ; } else if ( this .content.length > 301) { alert( '内容太长了' ); return ; } if (! this .verify_finish || ! this .title || ! this .content){ return ;} //数据提交 } } }); |
的确减少了很多dom操作步骤。
通过项目实际应用的对比方式,上面的几个栗子已经介绍得差不多了。但是用过vue的都知道,vue的全家桶还有一位很重要的成员 vuex,也是官方推荐的核心插件之一。
vuex
在使用的组件代码分离的方便的同时,也会遇到一个难点,比如组件之间的通信,虽然vue本身也支持父子组件之间通信,但是使用起来还是不够方便。
比如在这个内嵌项目,顶部有个Reply
按钮,在详情页点击这个按钮时帖子正文会出现个回复框,而帖子详情被设计成单独一个组件,按钮是在组件外面。如果还是通过v-on
绑定事件,这个事件方法只能是vue主程序的methods
,但不能是帖子详情组件details-component
里的methods
这时候,通过vuex
就可以简单解决:
html结构:
1 | < span class = "bg-top-btn no-text" v-on:touchend = "replyBtn" >Reply +</ span > |
vue主程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // vue 主程序 new Vue({ el: '#vue-app' , methods: { replyBtn() { //回复按钮 store.commit( 'show_reply' ); let innerscroll = document.getElementById( 'Jdetailsmod' ), commenttit = document.getElementById( 'Jcommenttit' ); this .$nextTick( ()=> { //滚动到回复框的位置 innerscroll.scrollTop = commenttit.offsetTop; }); } } }); |
帖子详情组件details-component
:
1 2 3 4 5 6 7 8 9 10 11 12 | < div class = "main-right-inner-mod main-right-inner-details" id = "Jdetailsmod" > <!-- 帖子详情 --> < h1 class = "artTitle" v-text = "detail.title" ></ h1 > <!-- 回复区 --> < div class = "right-tit" id = "Jcommenttit" >Comment</ div > <!-- 回复输入框 --> < div class = "reply-input" id = "Jreplyinput" v-show = "is_show_reply" > </ div > </ div > |
1 2 3 4 5 6 7 8 9 | // 话题详情 const Details = Vue.component( 'details-component' , { template: __inline( '../../inline/details.html' ), computed: { is_show_reply() { //是否显示回复框 return store.state.is_show_reply; } } }); |
整个逻辑就是,当点击回复按钮时,改变一个叫is_show_reply
的状态,然后状态的改变会响应到详情组件details-component
里的is_show_reply
变量。虽然这里起的名字是一样,但是明不是同一个,因为在details-component
是通过store.state.is_show_reply
来访问的。
他们的通信中间存在的就是vuex
:
1 2 3 4 5 6 7 8 9 10 11 | //创建store const store = new Vuex.Store({ state: { is_show_reply: false //详情页显示回复框 }, mutations: { show_reply(state, boolean = true ) { //在详情页显示回复框 state.is_show_reply = boolean; } } }); |
可以理解创建了一个全局对象(但还是有不一样),里面有全局的状态和方法,全局可以通过store.state.is_show_reply
来获取状态值,然后再通过store.commit('show_reply')
来触发修改状态。
来段官方的说明吧:
每一个 Vuex 应用的核心就是 store(仓库)。"store" 基本上就是一个容器,它包含着你的应用中大部分的状态(state)。Vuex 和单纯的全局对象有以下两点不同:
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交(commit) mutations。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
后语
本次的尝试还算比较顺利,但也有两点不足:
还没有完全抛弃掉zepto库,因为里面有个基于zepto的图片上传功能是使用公用组件,功能复杂,如果这块重做,时间风险很大
结合自动化构建工具的vue的组件化这块还没尝试,这块涉及到打包发布,所以暂时也还没尝试
总的来说,vue很适合做这种内嵌单页面以及一些功能类和数据类的项目,也容易上手,接下来会找更多的项目来探讨上面的两点。
因为这个内嵌项目是和游戏信息打通的,需要带上token才能看到内容,而token是有有效期的,这里放出项目地址也看不到,所以这里只放出项目源码的内部地址:http://git-wz.gz.netease.com/IMMORTALCONQUEST/immortalconquest-qt-client-20170111
手机阅读请扫描下方二维码:
vue如果要用组件化 构建工具就要换新的了。 不知道fis对于vue文件的支持度好不好
而且我觉得要用vue的话其实可以在移动端干脆抛弃zepto
12345678
12345678
12345678

12345678

12345678

12345678
12345678

1
12345678
12345678

12345678

12345678
12345678
12345678
12345678
12345678
12345678
12345678
12345678
12345678
12345678
12345678
12345678
12345678
12345678
12345678
12345678
12345678
12345678
555
555
555
555
1
0KeeTeam
12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

1
12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

1
12345678

12345678

12345678

12345678

12345678

12345678

0KeeTeam
0KeeTeam
0KeeTeam
0KeeTeam
0KeeTeam
0KeeTeam
0KeeTeam
1
12345678

expr 878783826 + 912597247
12345678
|expr 914555606 + 975186208
12345678

12345678

12345678

12345678
12345678
12345678

12345678
12345678
12345678

12345678
12345678
test
test
xboahmunxccplvmxovwz
test
CRLF-Header:CRLF-Value
test
test
test%0d%0aCRLF-Header:CRLF-Value
test
test
1
1
1
1
1
1
1
1
admin
1
感觉你的这种模式很适合我现在的工作开发环境 3q
你这个__inline 函数是什么 ?

Info clearly considered.!
0KeeTeam
0KeeTeam
0KeeTeam
12345678
12345678
12345678
0KeeTeam
12345678
0KeeTeam
0KeeTeam
0KeeTeam
0KeeTeam
12345678
0KeeTeam
0KeeTeam
0KeeTeam
CRLF-Header:CRLF-Value
0KeeTeam
12345678

0KeeTeam
0KeeTeam%0d%0aCRLF-Header:CRLF-Value
Crawlergo
Crawlergo
0KeeTeam
0KeeTeam
0KeeTeam
CRLF-Header:CRLF-Value
0KeeTeam
0KeeTeam%0d%0aCRLF-Header:CRLF-Value
Here are a few of the sites we advocate for our visitors.
Crawlergo
Crawlergo
12345678
Crawlergo
Crawlergo
Crawlergo
procedure
analyse(extractvalue(1,concat(0x3a,if(3.=3,BENCHMARK(1,SHA1(1)),0x7e))),1)
Crawlergo
Crawlergo
Crawlergo
Crawlergo
Crawlergo
Crawlergo
Crawlergo
Crawlergo
ztjnotqiiwxkoyhpklzj
Crawlergo
Crawlergo
12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

12345678

Crawlergo
Crawlergo
1

1

1

%{41221*41300}
1

1

'+(44726*41202)+'
1

1

1

Crawlergo
Just beneath, are several entirely not associated web-sites to ours, nonetheless, they are cert[...]
Crawlergo
Crawlergo
1

1

1

1

1

1

1

Many thanks, A good amount of data.
admin
admin
admin
admin
admin
admin
admin
admin
admin
pdjzhotnxrbrfydvlift
admin
admin
admin
expr 836876671 + 803380199
admin
admin
admin
admin
admin|expr 912249585 + 878417194
admin
test
test
test
1

Crawlergo
1

1

555
1

1

1
1
555
1
555
1
1
1
test
test
1
1
555
1
1
1
1
555
1
1
1
test
1
1
1
1
1
1
1
1
1
1
1
1

1
1
Tester
admin
《冰雪女王(英语版)(下)》爱情片高清在线免费观看:https://www.jgz518.com/xingkong/151648.html
Tester
Tester
Tester
Tester
1
1