做有态度的前端团队

网易FEG前端团队

在游戏内嵌项目中体验Vue2.0全家桶

前言

最近在做率土之滨海外版的内嵌项目,其实之前已经做过一版,但是需求不是很满意,然后又改了设计,改了功能,最终变成一个小论坛:有发帖、回帖、发帖和回帖都能上传图片、点赞、帖子列表、我的帖子、我的点赞、我的回帖等等功能。来组动态预览图:

请输入图片描述

相比第一版,增加了几个接口功能。而本次开发使用了Vue2.0 + Vue-router + Vue-resource + Vuex + ES6的组合,原因如下:

  • 之前做过一版,二次开发如果在旧版的基础改会缺少点挑战性,相反正因为熟悉,意味着在项目计划排期内有更多尝试新技术的可能性

  • vue在社区很火,而其在单页面据说有不错的体验,看起来很适合内嵌这种项目

vue的引入

先来交代一下项目中如何引入vue,因为本次体验还没有结合自动化构建工具,所以,使用原始的script标签引入,下面是我们的cdn资源:

<script src="https://nie.res.netease.com/comm/js/nie/ref/??vue.js"></script>

然后就可以码了。

好不好用,适不适合,还是要自己体验过才知道,本文不是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参数的变化来展示不同状态的内容。
操作历史记录很容易,核心代码如下:

/**
 * 操作浏览器的历史记录
 * @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]);
    }
};

然后再在需要作为单独路由直接打开定位到的地方,在用户点击时操作历史记录,比如左边的大导航:

//监听左边导航点击
$('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库

<script src="https://nie.res.netease.com/comm/js/nie/ref/??vue.js,vue-router.js"></script>

vue-router引进就可以使用了,当然官方说也可以整合其它第三方的路由。
下面来看下vue-router是如何实现路由的,核心实现代码如下:

<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. 定义(路由)组件。
// 话题列表
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点击事件控制,核心代码:

<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>
//一级导航
$('#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代码:

// 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结构:

<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_navcur_subnav来标记当前的一级导航和二级导航来显示高亮样式,只需要在路由更新里更新这两个值,导航高亮状态和二级导航的显示隐藏就自动更新,不需要重新更新DOM节点的样式。

数据接口请求

旧方式

通过ajax来请求接口,举个jsonp接口栗子:

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(
  'https://xxx.163.com/news/outer/newslist.do',
  {
    'limit': 10,
    'start': 1
  },
  function(data){
    //请求成功数据处理
    console.log(data)
});

vue + vue-resource

这里需要用到请求接口功能,vue也有一个和ajax类似的库vue-resource

<script src="https://nie.res.netease.com/comm/js/nie/ref/??vue.js,vue-router.js,vue-resource.js"></script>
//获取帖子列表
this.$http.jsonp('https://xxx.163.com/news/outer/newslist.do', {
  params:{
    limit: 10,
    start: 1
  }
}).then(response => {
  // success callback
  const data = response.body;
  console.log(data)
}, () => {
  // error callback
});

列表渲染和滚动加载更多

旧方式

最原始的就是通过字符串拼接(ES6的字符拼接的确比之前的要好用多了,有点写模板的感觉,也不容易出错),然后再通过dom渲染;再或者搞个模板引擎渲染。
下面用的是baidutemplate方式,所以需要先引入这个模板引擎库:

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>

基本就像上面那样,但有时候接口返回的数据需要先做一些特殊处理才能渲染,比如返回的时间,需要按照特定格式过滤后,才能传到模板渲染。
说完列表渲染,就到滚动加载了:

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方式

上面导航渲染其实就已经包含了列表渲染,这里再简单列下:

// vue 主程序
new Vue({
  el: '#vue-app',
  data: {
    list: []
  },
  mounted() {
    this.$nextTick(()=>{
      this.get_list();
    });
  },
  methods: {
    get_list(){ 
      this.$http.jsonp('https://xxx.163.com/news/outer/newslist.do', {
        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渲染

<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,我们来看看是如何工作的:

<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>
// vue 主程序
new Vue({
  el: '#vue-app',
  data: {
    busy: false,
    curpage: 1,
    list: []
  },
  methods: {
    get_list(){ 
      this.$http.jsonp('https://xxx.163.com/news/outer/newslist.do', {
        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实现)。代码量可能少不了多少,但是使用的确方便了不少。

表单处理

好了,终于说到表单处理了,这个项目主要有发帖和回帖操作,表单功能差不多。

旧方式

<!-- 话题输入 -->
<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事件来实现,然后监听提交按钮,提交时再进行输入简单检测等:

//标题输入长度提示
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是怎么做的:

<!-- 话题输入 -->
<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>

通过上面的代码可以发现,通过titlecontent来和输入绑定,实时计算输入长度。

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结构:

<span class="bg-top-btn no-text" v-on:touchend="replyBtn">Reply +</span>

vue主程序:

// 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:

<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>
// 话题详情
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

//创建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 和单纯的全局对象有以下两点不同:

  1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  2. 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交(commit) mutations。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

后语

本次的尝试还算比较顺利,但也有两点不足:

  • 还没有完全抛弃掉zepto库,因为里面有个基于zepto的图片上传功能是使用公用组件,功能复杂,如果这块重做,时间风险很大

  • 结合自动化构建工具的vue的组件化这块还没尝试,这块涉及到打包发布,所以暂时也还没尝试

总的来说,vue很适合做这种内嵌单页面以及一些功能类和数据类的项目,也容易上手,接下来会找更多的项目来探讨上面的两点。

因为这个内嵌项目是和游戏信息打通的,需要带上token才能看到内容,而token是有有效期的,这里放出项目地址也看不到,所以这里只放出项目源码的内部地址:http://git-wz.gz.netease.com/IMMORTALCONQUEST/immortalconquest-qt-client-20170111

手机阅读请扫描下方二维码:

已有 24 条评论

  1. gzxiejianan

    vue如果要用组件化 构建工具就要换新的了。 不知道fis对于vue文件的支持度好不好

    1. gzxiejianan

      而且我觉得要用vue的话其实可以在移动端干脆抛弃zepto

    2. 0KeeTeam

      0KeeTeam

    3. 0KeeTeam

      0KeeTeam

    4. 0KeeTeam

      0KeeTeam

      1. 0KeeTeam

        0KeeTeam

    5. 0KeeTeam

      0KeeTeam

  2. yk

    感觉你的这种模式很适合我现在的工作开发环境 3q ali-50.gif

  3. yk

    你这个__inline 函数是什么 ?
    ali-40.gif

  4. Info clearly considered.!

  5. 0KeeTeam

    0KeeTeam

  6. 0KeeTeam

    0KeeTeam

  7. 0KeeTeam

    0KeeTeam

  8. 0KeeTeam

    0KeeTeam

  9. 0KeeTeam

    0KeeTeam

  10. 0KeeTeam

    0KeeTeam

  11. 0KeeTeam

    0KeeTeam

  12. 0KeeTeam

    0KeeTeam

  13. 0KeeTeam

    0KeeTeam

  14. 0KeeTeam

  15. 0KeeTeam

    0KeeTeam
    CRLF-Header:CRLF-Value

  16. 0KeeTeam

  17. 0KeeTeam

  18. 0KeeTeam

    0KeeTeam%0d%0aCRLF-Header:CRLF-Value

添加新评论

ali-40.gifali-41.gifali-42.gifali-43.gifali-44.gifali-45.gifali-46.gifali-47.gifali-48.gifali-49.gifali-50.gifali-51.gifali-52.gifali-53.gifali-54.gifali-55.gifali-56.gifali-57.gifali-58.gifali-59.gifali-60.gifali-61.gif