Vue.js 实战系列之实现视频类WebApp的项目开发——3. 底部状态栏TabBar的实现(实现原生APP底部导航栏)

如果想看该实战系列的其他内容,请移步至 Vue.js 实战系列之实现视频类WebApp的项目开发

项目仓库地址,欢迎 Star


实现效果

在这里插入图片描述


模块开发

  1. 路由配置

    当我们在创建一个复杂应用的时候,最开始应该做的不是具体业务功能,而是先捋顺整个应用的结构和思路,从路由入手就是一个很好的选择。

    根据项目需求得知,该 WebApp 底部有5个 TabBar 组成,所以我们需要构建5个页面,分别是首页、朋友、发布、消息、我的。而且底部 TabBar 在每个页面都有,所以我的做法是创建一个根页面,在这个页面通过子路由的方式加载各个主页。

    修改 router/index.js 文件进行路由配置:

    import Vue from 'vue';
    import VueRouter from 'vue-router';
    import Home from '../views/Home.vue';
    
    Vue.use(VueRouter);
    
    const routes = [
      {
        path: '/',
        redirect: 'index',
      },
      {
        path: '/',
        name: 'Home',
        component: Home,
        children: [
          {
            path: '/index',
            name: 'index',
            component: () => import(/* webpackChunkName: "index" */ '../views/index/index.vue'),
          },
          {
            path: '/friends',
            name: 'friends',
            component: () => import(/* webpackChunkName: "friends" */ '../views/friends/index.vue'),
          },
          {
            path: '/news',
            name: 'news',
            component: () => import(/* webpackChunkName: "news" */ '../views/news/index.vue'),
          },
          {
            path: '/mine',
            name: 'mine',
            component: () => import(/* webpackChunkName: "mine" */ '../views/mine/index.vue'),
          },
        ],
      },
      {
        path: '/release',
        name: 'release',
        component: () => import(/* webpackChunkName: "release" */ '../views/release/index.vue'),
      },
    ];
    
    const router = new VueRouter({
      mode: 'history',
      base: process.env.BASE_URL,
      routes,
    });
    
    export default router;
    

    路由配置解释:

    1. 查看上面的路由配置可知:发布页面的路由并没有添加到主页的子路由中,这是因为发布页面不需要展示底部 TabBar 组件。
    2. 路由采用懒加载的方式引入
    3. 引入路由时有: /* webpackChunkName: "index" */,这个的作用就是修改打包之后的js文件名。
    4. 为什么使用嵌套路由?
      很多时候我们的页面结构决定了我们可能需要嵌套路由,比如我们的当前项目,在进入主页之后有多个功能页面,然后当选择其中一个功能页面之后进入对应的页面,这个时候我们就可以用到嵌套路由。
      官方文档中给我们提供了一个 children 属性,这个属性是一个数组类型,里面实际放着一组路由;这个时候父子关系结构就出来了,所以 children 属性里面的是路由相对来说是 children 属性外部路由的子路由。
    5. 注意:以上路由配置的页面需提前创建好,否则会有报错。
  2. 底部导航栏TabBar实现

    新建 Tabbar.vue 组件文件:

    common/components/tab/Tabbar.vue

    <template>
      <div class="tab-bar">
        <div class="item" @click="changTab(0)">
          <router-link to="/index" tag="span" :class="tabIndex === 0 ? 'active' : ''">首页</router-link>
        </div>
        <div class="item" @click="changTab(1)">
          <router-link to="/friends" tag="span" :class="tabIndex === 1 ? 'active' : ''">朋友</router-link>
        </div>
        <div @click="changTab(0)">
          <router-link to="/release" tag="span">
            <img class="dy-btn" src="@/assets/images/dy-btn.png" alt="" />
          </router-link>
        </div>
        <div class="item" @click="changTab(3)">
          <router-link to="/news" tag="span" :class="tabIndex === 3 ? 'active' : ''">消息</router-link>
        </div>
        <div class="item" @click="changTab(4)">
          <router-link to="/mine" tag="span" :class="tabIndex === 4 ? 'active' : ''">我的</router-link>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      name: 'Tabbar',
      data() {
        return {
          tabIndex: 0,
        };
      },
    
      methods: {
        changTab(i) {
          this.tabIndex = i;
        },
      },
    };
    </script>
    
    <style lang="less" scoped>
    .tab-bar {
      background: #000000;
      height: 50px;
      width: 100%;
      line-height: 50px;
      position: fixed;
      bottom: 0;
      left: 0;
      display: flex;
      justify-content: space-around;
      color: #cccccc;
      font-size: 16px;
      .item {
        flex: 1;
        text-align: center;
        font-weight: 500;
      }
      .active {
        color: #ffff;
        font-weight: 600;
      }
      .dy-btn {
        width: 50px;
        height: 30px;
        margin: 10px;
      }
    }
    </style>
    
  3. 封装TabBar组件

    我们将上面的 Tabbar 组件进行拆分,使他变成一个复用性更高的组件。

    先对 Tabbar 进行拆分,拆分成 TabBar 和 TabItem 两部分:
    在这里插入图片描述

    1. TabItem:作为 TabBar 的单个 tab,可以实现较高的复用性。

      组件封装主要利用了 vue 的两个属性:
      props: 接收父组件给子组件的传值;
      computed:计算属性(当其依赖的属性的值发生变化时,计算属性会重新计算,反之,则使用缓存中的属性值)

      <template>
        <div class="item" @click="itemClick">
          <router-link
            :to="navPath"
            tag="span"
            :class="isActive ? 'active' : ''"
          >
            {{ tabTitle }}
            <slot></slot>
          </router-link>
        </div>
      </template>
      
      <script>
      export default {
          props: {
              tabTitle: {
                  type: String,
                  default: '',
              },
              navPath: {
                  type: String,
                  default: '/index',
              },
          },
          computed: {
              isActive() {
                  return this.$route.path === this.navPath;
              },
          },
          methods: {
              itemClick() {
                  console.log(this.$route.path);
              },
          },
      };
      </script>
      
      <style lang="less" scoped>
      .item {
        flex: 1;
        text-align: center;
        font-weight: 500;
      }
      .active {
        color: #ffff;
        font-weight: 600;
      }
      </style>
      
    2. TabBar :作为 TabItem 的父组件,对其进行使用。

      <template>
        <div class="tab-bar">
          <TabItem tab-title="首页" nav-path="/index"></TabItem>
          <TabItem tab-title="朋友" nav-path="/friends"></TabItem>
          <TabItem nav-path="/release">
            <img class="dy-btn" src="@/assets/images/dy-btn.png" alt="" />
          </TabItem>
          <TabItem tab-title="消息" nav-path="/news"></TabItem>
          <TabItem tab-title="我的" nav-path="/mine"></TabItem>
        </div>
      </template>
      
      <script>
      import TabItem from './TabItem.vue';
      
      export default {
        name: 'Tabbar',
        components: {
          TabItem,
        },
      };
      </script>
      
      <style lang="less" scoped>
      .tab-bar {
        background: #000000;
        height: 50px;
        width: 100%;
        line-height: 50px;
        position: fixed;
        bottom: 0;
        left: 0;
        display: flex;
        justify-content: space-around;
        color: #cccccc;
        font-size: 16px;
        .dy-btn {
          width: 50px;
          height: 30px;
          margin: 10px;
        }
      }
      </style>
      

问题记录

注意:由于我们使用了 Eslint 进行规范检查,所以在项目开发过程中一定要注意代码规范,下面我注意总结了在开发过程中会碰到的几个代码规范问题。

  1. Eslint 检测报错 error Expected linebreaks to be 'LF' but found 'CRLF' linebreak-style

    原因:
    项目代码做了 eslint 的规范检查,规定了换行需要以Lunix系统的换行方式,Linux下只有换行LF,而在 window 下换行默认是 CRLF。

    解决方案:
    .eslintrc 文件 rules 里面 配置 "linebreak-style": ["error", "windows"] 或者 linebreak-style': ["off", "windows"], 允许windows开发环境,记得重启项目。

  2. Eslint 提示 Newline required at end of file but not found

    原因:
    根据报错的字面意思就是文件的末尾需要有空的一行。

    解决方案:
    在最后加上一行空白就可以了。

  3. Eslint 提示 Trailing spaces not allowed no-trailing-spaces

    原因:
    空格多了,需删除多余空格。

    解决方案:
    删除多余的空格就可以了。


总结

  1. 本章节主要对底部状态栏TabBar进行组件开发,以及设置对应的路由配置。

  2. 对比模块开发中的第二、三部分 对TabBar实现 和 封装组件化处理,可以清晰的看出,使用组件化进行项目开发,可以简化代码以及提高代码的复用性,所以我们在项目开发中,应该尽可能的使用组件化进行开发。

  3. 总是提示错误,我们为什么还需要用 Eslint?

    在开始写这部分代码之前,一直没有注意到使用 Eslint 来进行代码规范,在后面的代码开发过程中,开始逐渐意识到代码规范的重要性,养成一个良好的代码规范可以让我们的代码减少不必要的错误。

    在团队协作时,良好的代码规范显得格外重要,因为这是保障一个团队代码风格相同、避免低级bug的途径之一。

    使用 EsLint 工具和代码风格检测工具,可以辅助编码规范执行,有效控制代码质量。

    关于 Eslint 具体的使用方法,请大家自行查阅。


上一章节: 2. 搭建项目基本骨架

下一章节: 4. 顶部导航条实现

项目整体介绍:Vue.js 项目实战之实现视频播放类WebApp的项目开发(仿抖音app)


项目仓库地址,欢迎 Star。

有任何问题欢迎评论区留言讨论。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Vue3中可以通过组合API和自定义组件等方式实现灵活的TabBar底部导航组件,以下是一个简单的实现示例: 1. 首先定义一个TabBarItem组件,用于渲染每个底部导航项的内容,包括图标和文本等。 ```javascript <template> <div class="tab-bar-item" :class="{ active: active }" @click="handleClick"> <span class="tab-bar-icon">{{ icon }}</span> <span class="tab-bar-text">{{ text }}</span> </div> </template> <script> export default { props: { icon: { type: String, required: true }, text: { type: String, required: true }, active: { type: Boolean, default: false } }, emits: ['change'], methods: { handleClick() { this.$emit('change'); } } }; </script> ``` 2. 然后定义一个TabBar组件,用于渲染整个底部导航,同时管理和切换每个TabBarItem的状态。 ```javascript <template> <div class="tab-bar"> <tab-bar-item v-for="(item, index) in tabs" :key="index" :icon="item.icon" :text="item.text" :active="index === activeIndex" @change="handleChange(index)" /> </div> </template> <script> import TabBarItem from './TabBarItem.vue'; export default { components: { TabBarItem }, props: { tabs: { type: Array, required: true }, defaultIndex: { type: Number, default: 0 } }, emits: ['change'], data() { return { activeIndex: this.defaultIndex }; }, methods: { handleChange(index) { if (index !== this.activeIndex) { this.activeIndex = index; this.$emit('change', index); } } } }; </script> ``` 3. 最后在父组件中使用TabBar组件,传入需要显示的底部导航项和对应的内容组件,实现灵活的底部导航功能。 ```javascript <template> <div class="app"> <router-view /> <tab-bar :tabs="tabs" @change="handleChange" /> </div> </template> <script> import TabBar from './TabBar.vue'; import Home from './Home.vue'; import About from './About.vue'; export default { components: { TabBar, Home, About }, data() { return { tabs: [ { icon: 'home', text: 'Home', component: Home }, { icon: 'about', text: 'About', component: About } ], activeComponent: null }; }, methods: { handleChange(index) { this.activeComponent = this.tabs[index].component; } } }; </script> ``` 在以上代码中,父组件通过监听TabBar组件的change事件,动态切换显示对应的内容组件,实现了灵活的底部导航功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值