为了账号安全,请及时绑定邮箱和手机立即绑定

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

大家好,我给大家分享一下仿豆瓣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-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 class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="../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 class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="../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 class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="../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 class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="../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 class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="../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 class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="../assets/images/ic_tab_home_normal.png" alt="" slot="icon-normal"> 
        首页
      </m-tabbar-item>
      <m-tabbar-item id='tab2'>
        < img class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="../assets/images/ic_tab_subject_normal.png" alt="" slot="icon-normal"> 
        书影音
      </m-tabbar-item>
      <m-tabbar-item id='tab3'>
        < img class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="../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 class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="../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 class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="../assets/images/ic_tab_home_normal.png" alt="" slot="icon-normal"> 
        < img class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="../assets/images/ic_tab_home_active.png" alt="" slot="icon-active"> 
        首页
      </m-tabbar-item>
      <m-tabbar-item id='tab2'>
        < img class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="../assets/images/ic_tab_subject_normal.png" alt="" slot="icon-normal"> 
        < img class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="../assets/images/ic_tab_subject_active.png" alt="" slot="icon-active"> 
        书影音
      </m-tabbar-item>
      <m-tabbar-item id='tab3'>
        < img class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="../assets/images/ic_tab_status_normal.png" alt="" slot="icon-normal"> 
        < img class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="../assets/images/ic_tab_status_active.png" alt="" slot="icon-active"> 
        广播
      </m-tabbar-item>
      <m-tabbar-item id='tab4'>
        < img class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="../assets/images/ic_tab_group_normal.png" alt="" slot="icon-normal"> 
        < img class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="../assets/images/ic_tab_group_active.png" alt="" slot="icon-normal"> 
        小组
      </m-tabbar-item>
       <m-tabbar-item id='tab5'>
        < img class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="../assets/images/ic_tab_profile_normal.png" alt="" slot="icon-normal"> 
        < img class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="../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
点击查看更多内容
24人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
Web前端工程师
手记
粉丝
9
获赞与收藏
104

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消