美文网首页VueWeex开发技巧前端
1-vuejs2.0实战:仿豆瓣app项目,创建自定义组件tab

1-vuejs2.0实战:仿豆瓣app项目,创建自定义组件tab

作者: 穆風 | 来源:发表于2017-02-23 22:45 被阅读8682次

    大家好,我给大家分享一下仿豆瓣app的教程。当然了,我们不是用原生去实现,而是用前端框架vuejs来实现豆瓣app。————第一次写文章,写得不好请见谅。

    为什么我们选择豆瓣app 来做这样一个教程?

    是因为我很早就接触豆瓣这个网站,我比较喜欢看豆瓣里面电影和文章的点评。并且豆瓣提供了非常丰富的一个api接口供我们使用。也就是说我们可以不通过后端,直接通过前端ajax来获取电影和图书的数据,来组装我们app。

    我们可以看一下豆瓣app首页是一个什么样子 gif

    Paste_Image.png

    以上就是豆瓣app的一个截图。

    我们先来分析一下

    首页分为四个部分。第一个就是顶部的搜索框。搜索框下面就是一个banner图切换。在下面就是一些热点的文章列表。最底部就是一个tab切换。在这篇教程中,我们通过vue的组件来实现这样一个首页的布局。


    创建豆瓣项目
    如果没有安装node,请到官网下载安装,或者通过淘宝npm镜像安装

    http://npm.taobao.org/
    

    我们通过官方vue-cli初始化项目,这里我们采用webpack示例

    vue init webpack douban
    

    填写项目描述,作者,安装vue-router

    ? Project name douban
    ? Project description douban
    ? Author afei
    ? Vue build standalone
    ? Install vue-router? Yes
    ? Use ESLint to lint your code? No
    ? Setup unit tests with Karma + Mocha? No
    ? Setup e2e tests with Nightwatch? No
    
       vue-cli · Generated "douban".
    
       To get started:
    
         cd douban
         npm install
         npm run dev
    
       Documentation can be found at https://vuejs-templates.github.io/webpack
    

    初始化后,通过npm install安装依赖

    cd douban
    npm install
    

    由于我们是做的移动端,所以在index.html里面加上meta,

    <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0">
    

    运行项目,可以看到基于官方vue-cli的模版就创建好了

    npm run dev
    
    Paste_Image.png

    将所需要用的资源,拷贝到项目中,这里我通过解压豆瓣app获得他的一些图片素材,拷入到src/assets/images目录里。

    css这里我用到了normaliz.css

    在src下,新建了一个pages目录,存放每一个页面组件,可以看一下我们的目录

    Paste_Image.png

    由于我们的首页更改了位置,所以在router里面的index.js需要更改为

    import Vue from 'vue'
    import Router from 'vue-router'
    import Index from '../pages/Index'
    
    Vue.use(Router)
    
    export default new Router({
      routes: [
        {
          path: '/',
          name: 'Index',
          component: Index
        }
      ]
    })
    
    

    每一个组件的css我们通过less来编写,所有需要通过npm安装less插件

    npm install less less-loader --save
    

    使用less预处理器需要在页面添加 lang='less'

    <style scoped lang="less">
        
    </style>
    

    第一个组件 tabbar

    如何创建自定义组件tabbar,也就是豆瓣app底部的工具栏。这里的结构我们参考了mint-ui

    这是我们将要实现的效果图。

    Paste_Image.png

    我们先来分析一下这个组件的结构。

    这个组件分为两部分:第一个是组件的外层容器,第二个是组件的子容器item,子组件里面又分为图片和文字组合。子组件有2个状态,一个默认灰色的状态,一个选中状态,我们来实现一下这个组件的布局。在index.vue里面

    template

    <div class="m-tabbar">
      <a class="m-tabbar-item is-active">
        <span class="m-tabbar-item-icon">
           < img src="../assets/images/ic_tab_home_normal.png" alt="">
        </span> 
        <span class="m-tabbar-item-text">  
              首页
        </span>
      </a> 
      <a class="m-tabbar-item">
        <span class="m-tabbar-item-icon">
           < img src="../assets/images/ic_tab_subject_normal.png" alt="">
        </span> 
        <span class="m-tabbar-item-text">  
              书影音
        </span>
      </a> 
      <a class="m-tabbar-item">
        <span class="m-tabbar-item-icon">
           < img src="../assets/images/ic_tab_status_normal.png" alt="">
        </span> 
        <span class="m-tabbar-item-text">  
              广播
        </span>
      </a> 
      <a class="m-tabbar-item">
        <span class="m-tabbar-item-icon">
           < img src="../assets/images/ic_tab_group_normal.png" alt="">
        </span> 
        <span class="m-tabbar-item-text">  
              小组
        </span>
      </a> 
      <a class="m-tabbar-item">
        <span class="m-tabbar-item-icon">
           < img src="../assets/images/ic_tab_profile_normal.png" alt="">
        </span> 
        <span class="m-tabbar-item-text">  
              我的
        </span>
      </a> 
    </div>
    

    style

    <style lang="less">
    .m-tabbar{
        display: flex;
        flex-direction: row;
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;
        width: 100%;
        overflow: hidden;
        height: 50px;
        background: #fff;
        border-top: 1px solid #e4e4e4;
        
        .m-tabbar-item{
          flex: 1;
          text-align: center;
          .m-tabbar-item-icon{
              display: block;
              padding-top: 2px;
              img{
                  width: 28px;
                  height: 28px;
              }
          }
          .m-tabbar-item-text{
              display: block;
              font-size: 10px;
              color:#949494;
          }
          &.is-active{
              .m-tabbar-item-text{
                  color: #42bd56;
              }
          }
      }
    }
    </style>
    

    布局大功告成~~~~

    前面我们说的是,通过组件的方式来实现这个app。

    如果像上面代码这样的话肯定是不行的!既然我们大体布局已经写好了,现在就可以通过组件的方式来调用。当然我们还要改造一下代码。

    先在components文件夹下面,新建两个组件,通过这两个组件来组合实现我们底部的tab组件:

    一个是tabbar-item.vue,实现子组件的item项,

    tabbar-item.vue

    <template>
        <a class="m-tabbar-item" >
            <span class="m-tabbar-item-icon"><slot name="icon-normal"></slot></span>
            <span class="m-tabbar-item-text"><slot></slot></span>
        </a>
    </template>
    
    <style lang="less">
    .m-tabbar-item{
        flex: 1;
        text-align: center;
        .m-tabbar-item-icon{
            display: block;
            padding-top: 2px;
            img{
                width: 28px;
                height: 28px;
            }
    
        }
        .m-tabbar-item-text{
            display: block;
            font-size: 10px;
            color:#949494;
        }
        &.is-active{
            .m-tabbar-item-text{
                color: #42bd56;
            }
        }
    }
    </style>
    

    一个是tabbar.vue,实现tab的外层容器,

    tabbar.vue

    <template>
        <div class="m-tabbar">
           <slot></slot>
        </div>
    </template>
    <style lang="less">
    .m-tabbar{
        display: flex;
        flex-direction: row;
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;
        width: 100%;
        overflow: hidden;
        height: 50px;
        background: #fff;
        border-top: 1px solid #e4e4e4;
    }
    </style>
    

    在Index.vue中组合这两个组件,实现tab组件效果

    <template>
      <div>
        <m-tabbar>
          <m-tabbar-item id='tab1'>
            < img src="../assets/images/ic_tab_home_normal.png" alt="" slot="icon-normal"> 
            首页
          </m-tabbar-item>
          <m-tabbar-item id='tab2'>
            < img src="../assets/images/ic_tab_subject_normal.png" alt="" slot="icon-normal"> 
            书影音
          </m-tabbar-item>
          <m-tabbar-item id='tab3'>
            < img src="../assets/images/ic_tab_status_normal.png" alt="" slot="icon-normal"> 
            广播
          </m-tabbar-item>
          <m-tabbar-item id='tab4'>
            ![](../assets/images/ic_tab_group_normal.png) 
            小组
          </m-tabbar-item>
           <m-tabbar-item id='tab5'>
            < img src="../assets/images/ic_tab_profile_normal.png" alt="" slot="icon-normal"> 
            我的
          </m-tabbar-item>
        </m-tabbar>
      </div>
    </template>
    
    <script>
      import mTabbar from '../components/tabbar'
      import mTabbarItem from '../components/tabbar-item'
      export default {
        name: 'index',
        components: {
          mTabbar,
          mTabbarItem
        }
      }
    </script>
    
    

    完成的效果。

    Paste_Image.png

    光有一个死的界面,没有点击切换的效果怎么能行?

    以下我们通过vue使用自定义事件的表单输入组件来实现点击切换的效果。


    先给Index.vue里面的tab组件加上v-model 来进行数据双向绑定,通过select来达到选择item,在item里面再添加一个选中的active图片

    <template>
      <div>
        测试
        <m-tabbar v-model="select">
          <m-tabbar-item id='tab1'>
            < img src="../assets/images/ic_tab_home_normal.png" alt="" slot="icon-normal"> 
            < img src="../assets/images/ic_tab_home_active.png" alt="" slot="icon-active"> 
            首页
          </m-tabbar-item>
          <m-tabbar-item id='tab2'>
            < img src="../assets/images/ic_tab_subject_normal.png" alt="" slot="icon-normal"> 
            < img src="../assets/images/ic_tab_subject_active.png" alt="" slot="icon-active"> 
            书影音
          </m-tabbar-item>
          <m-tabbar-item id='tab3'>
            < img src="../assets/images/ic_tab_status_normal.png" alt="" slot="icon-normal"> 
            < img src="../assets/images/ic_tab_status_active.png" alt="" slot="icon-active"> 
            广播
          </m-tabbar-item>
          <m-tabbar-item id='tab4'>
            < img src="../assets/images/ic_tab_group_normal.png" alt="" slot="icon-normal"> 
            < img src="../assets/images/ic_tab_group_active.png" alt="" slot="icon-normal"> 
            小组
          </m-tabbar-item>
           <m-tabbar-item id='tab5'>
            < img src="../assets/images/ic_tab_profile_normal.png" alt="" slot="icon-normal"> 
            < img src="../assets/images/ic_tab_profile_active.png" alt="" slot="icon-normal"> 
            我的
          </m-tabbar-item>
        </m-tabbar>
      </div>
    </template>
    
    <script>
      import mTabbar from '../components/tabbar'
      import mTabbarItem from '../components/tabbar-item'
      export default {
        name: 'index',
        components: {
          mTabbar,
          mTabbarItem
        },
        data() {
          return {
            select:"tab1"
          }
        }
      }
    </script>
    

    tabbar.vue里面通过props来传递数据vaule

    <template>
        <div class="m-tabbar">
           <slot></slot>
        </div>
    </template>
    <script>
        import mTabbarItem from './tabbar-item';
        export default {
            props: ['value']
        }
    </script>
    <style lang="less">
    .m-tabbar{
        display: flex;
        flex-direction: row;
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;
        width: 100%;
        overflow: hidden;
        height: 50px;
        background: #fff;
        border-top: 1px solid #e4e4e4;
    }
    </style>
    

    tabbar-item.vue组件:根据父组件的value和当前组件的id判断是否为选中状态,通过 $parent.$emit('input',id) - 触发父组件的自定义事件,添加选中的图片,根据isActive来显示隐藏

    <template>
        <a class="m-tabbar-item" :class="{'is-active':isActive}" @click="$parent.$emit('input',id)">
            <span class="m-tabbar-item-icon" v-show="!isActive"><slot name="icon-normal"></slot></span>
            <span class="m-tabbar-item-icon" v-show="isActive"><slot name="icon-active"></slot></span>
            <span class="m-tabbar-item-text"><slot></slot></span>
        </a>
    </template>
    <script>
        export default{
            props: ['id'],
            computed: {
               isActive(){
                   if(this.$parent.value===this.id){
                       return true;
                   }
               }
            }
        }
    </script>
    <style lang="less">
    .m-tabbar-item{
        flex: 1;
        text-align: center;
        .m-tabbar-item-icon{
            display: block;
            padding-top: 2px;
            img{
                width: 28px;
                height: 28px;
            }
    
        }
        .m-tabbar-item-text{
            display: block;
            font-size: 10px;
            color:#949494;
        }
        &.is-active{
            .m-tabbar-item-text{
                color: #42bd56;
            }
        }
    }
    </style>
    

    大功告成,tabbar组件就完成了~~~~~

    录像1_转.gif

    感谢饿了么团队给我们带来了这么好的ui组件!

    git地址:
    https://github.com/MrMoveon/doubanApp

    第一章
    源码下载 链接:http://pan.baidu.com/s/1qYlR8g0 密码:9yph

    下载安装

    npm install
    npm run dev
    

    相关文章

      网友评论

      • luln:感谢大神写的这么详细,让新手都能看懂了!
        更希望大神出个视频。
        测试效果出错了,查看了下,所有引用的组件要注册,所以tabbar.vue 里
        export default {
        components:{mTabbarItem},
        props: ['value']
        }
      • c620c43a7641:感谢博主,最近正在学vue
      • awesome_lewis:tabbar-item.vue第2行@click="$parent.$emit('input',id)"是什么意思?哪儿来的input
        一阵风fly:@爬坑的乌龟 一开始也看不太懂,后来重新翻了文档终于理解了
        87cdc170ba36:建议多看几遍官网和认真多博主的话,博主的前提原话 加上v-model进行双向数据绑定 v-mode其实是一个语法糖 这句话的意思是点击当前 组件 组件的父组件去触发在它父组件上绑定的input事件然后把ID值带出去 告诉改变select 里面的值 你可以试下去掉,这样你点击就不会有切换的效果
      • 70f33dfb5c84:请问下,tabbar.vue 里面用props传递的 value 的值是哪来的? 是子组件tabbar-item 自定义事件里面的 id 值麽? 小白一个 不是很懂哎,求大牛解答下
        莫言默言:传递的value就是index.vue的v-model="select"。
        加缪:同问!我也是这块不是特别明白。能否得到解答?
      • f5929cdd9490:我想问问,我用less之后,进行命令行run却产生这样的错误:node_modules\loader-runner\lib\loadLoader.js:35
        throw new Error("Module '" + loader.path + "' is not a l
        oader (must have normal or pitch function)");

        Error: Module 'F:\Dgn\tabbar_practice\node_modules\less\index.js' is not a loade
        r (must have normal or pitch function)
      • dab08cd3edcf:大赞啊
        dab08cd3edcf:为什么不用mint-ui自带的tabbar功能,还要自己写两个组件呢?可以请教一下吗
      • 6ae14418e235:坐等更新,已star
      • 水底鱼:这是单页面应用么,用webpack的话如果是多页面可行么。
      • 飘零之雪:关注了 希望有更好的文章发出来
        这里WEB前端 小白
        谢谢给的教程
      • 艾逗笔:你好,style里面写的less代码没有起作用。安装完less和less-loader之后需要在webpack配置文件中改什么地方吗?
        穆風: @艾逗笔 不需要,只需要在style加一个lang=less就行了
      • 5cc5d78aef6a:tab切换为什么不用路由😂
        虽然存放到data里边的这种做法我也试过
        穆風: @七天大圣_9517 就这两天
        2f0a6af5d0c4:老哥 什么时候出第二章?:flushed:
        穆風:@小智_4347 现在还没有做切换,只做了一个点击效果,第二章会分享用路由切换
      • f5929cdd9490:这个教程好,适合进坑的新手。
      • LYSEKY:改了前端怎么刷新页面?
        穆風:@LYSEKY 检查下路径,看看路由定义对没有,你可以下载源码跑一下,应该是没问题的
        LYSEKY:@穆風 a按照你的教程会报错
        [Vue warn]: Failed to mount component: template or render function not defined.
        (found in anonymous component at E:\workspace\test\vue_douban\src\pages\Index.vue)
        穆風:@LYSEKY 如果你用的是vue-cli webpack,改了会自动刷新
      • 艾尔cc:为什么不放到github上面去呢,大家可以一起维护,改进。:wink:
        咖啡止渴:作者的静态数据是从哪里获取到的,
        现在在写豆瓣,从哪里获取动态数据呢
        穆風:@艾尔cc 过一阵会放上去,先看看反响如何:smile:

      本文标题:1-vuejs2.0实战:仿豆瓣app项目,创建自定义组件tab

      本文链接:https://www.haomeiwen.com/subject/krkiwttx.html