zoukankan      html  css  js  c++  java
  • 18. vue-router案例-tabBar导航

    目标: 做一个导航tabbar

    一. 分析

    我们的目标是做一个导航tabbar, 要求

    1. 这个导航不仅可以在一个页面使用, 可以在多个页面通用
    2. 每个页面的样式可能不一样
    3. 每个页面的图标, 文字可能不一样
    4. 每个页面导航的个数可能不一样

    要想实现上面的情况, 需要进行功能拆解:

    1. 提炼出一个通用的tabBar, 然后在里面定义插槽, 根据需要放入tabBarItem,
    2. tabBarItem里面包含图片, 文字. 图片和文字也是插槽, 不同的tabBarItem显示的图片的文字都有可能不同.
    3. tabBarItem的数据结构需要定义为一个json, 指定跳转的url

    二. 框架实现

    1. 通常我们如何实现?

    第一步: 在App.vue中定义一段HTML, 外层div的id是tabBar, 内层div的class标签属性是tabBarItem.

    <template>
      <div id="app">
        <div id="tabBar">
          <div class="tabBarItem">首页</div>
          <div class="tabBarItem">分类</div>
          <div class="tabBarItem">购物车</div>
          <div class="tabBarItem">我的</div>
        </div>
      </div>
    </template>
    

    第二步: 定义body样式.

    通常body样式, 我们将其单独定义到main.css文件中. 放在assets目录下

    body {
      margin: 0px;
      padding: 0px;
    }
    

    定义好了main.css文件, 需要将其引入到App.vue文件中

    <style>
      @import "./assets/main.css";
    </style>
    

    引入css文件样式使用的是@import '文件路径', 而引入js文件使用的是import '文件路径'

    第三步: 定义tabBar样式

    tabBar采用的是Flex弹性布局的布局方式.

        #tabBar {
            display: flex;
        }
    
        .tabBarItem {
            flex: 1;
            text-align: center;
          }
    

    上面这段代码就指定了tabBar采用的布局方式. 它会根据子元素的个数进行弹性布局. 在子元素中我们设置每个元素的flex: 1
    表示的是在空间可用的情况下, 平均分配空间.

    第四步: 定义其他样式

    <style>
      @import "./assets/main.css";
    
      #tabBar {
        display: flex;
        background-color: #e6e6e6;
    
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
    
        box-shadow: 0 -3px 1px darkgrey;
      }
    
      .tabBarItem {
        flex: 1;
        text-align: center;
      }
    </style>
    

    这里面除了有布局样式,还有底部对齐, 导航的阴影等等.

    总结: 这样的导航不通用, 如果我们要复用, 需要拷贝html内容, 还要拷贝css样式. 我们可以把公共部分提成一个组件

    2. 抽象tabBarItem组件

    第一步: 在components中新建一个组件tabBarItem

    这个提取比较简单, 就是将我们刚刚在App.vue中的功能提取出一个单独的组件

    <template>
      <div id="tabBar">
        <div class="tabBarItem">首页</div>
        <div class="tabBarItem">分类</div>
        <div class="tabBarItem">购物车</div>
        <div class="tabBarItem">我的</div>
      </div>
    </template>
    
    <script>
        export default {
            name: "tabBarItem"
        }
    </script>
    
    <style scoped>
      #tabBar {
        display: flex;
        background-color: #e6e6e6;
    
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
    
        box-shadow: 0 -3px 1px darkgrey;
      }
    
      .tabBarItem {
        flex: 1;
        text-align: center;
      }
    </style>
    

    然后, 在App.vue中引入组件

    <script>
      import TabBar from "./components/TabBar"
      export default {
        name: 'App',
        components: {
          TabBar
        }
      }
    </script>
    

    vue里面, 可以使用组件的简称调用组件, 如下所示:

    <div id="app">
          <tab-bar></tab-bar>
      </div>
    

    这样, tabBarItem的可复用性就更强了.

    3. 完善tabBarItem组件

    我们知道tabBar除了有图片, 还有文字. 当我们鼠标点击的时候还有对应的图片或者蚊子样式的变化.
    下面我们来实现, 改变图片和文字.
    第一步: 在tabBarItem中放两张图片, 一张是未点击的, 另一张是点击后的图片. 图片自备, 什么图都可以

    <div class="tabBarItem">
        <slot name="item-pic"></slot>
        <slot name="item-pic-active"></slot>
        <div ><slot name="item-name"></slot></div>
      </div>
    

    如上代码: 比之前多了一个slot, 用来存放第二张图片的.
    在调用的地方传两张图片过来

    <tab-bar-item>
          <img slot="item-pic" src="../../assets/img/tabBar/1.jpeg">
          <img slot="item-pic-active" src="../../assets/img/tabBar/4.jpeg">
          <div slot="item-name">首页</div>
    </tab-bar-item>
    
    

    这里就传了两张图片, 并指定每张图片的插槽位置

    然后我们来看看效果:

    效果出来了,达到预期. 但我们希望:鼠标不点击,显示图一; 鼠标点击, 显示图二.
    这个容易实现, 使用一个isActive变量即可
    修改TabBarItem组件

    <template>
      <div class="tabBarItem">
        <div v-if="!isActive"><slot name="item-pic"></slot></div>
        <div v-else><slot name="item-pic-active"></slot></div>
        <div><slot name="item-name"></slot></div>
      </div>
    </template>
    
    <script>
        export default {
            name: "TabBarItem",
            data() {
              return {
                isActive: false
              }
            }
        }
    </script>
    

    在组件脚本中定义一个变量isActive, 然后对插槽使用v-if即可实现效果. 注意v-if和v-else的写法.

    这里我们有一个约定,通常不在插槽的里面写v-if或者v-else, 因为这部分内容后面会被替换掉. 所以, 在外层包一个div

    下面来看看效果:
    当我们设置isActive:false, 效果如下

    当我们设置isActive:true, 效果如下:

    可以看出, 我这里面就是把四张图片调换了一下顺序. 具体什么图片不重要, 重要的是效果出来了就好.

    第二步: 实现文字激活时变色.

    这个就更简单了.

    <template>
      <div class="tabBarItem">
        <div v-if="!isActive"><slot name="item-pic"></slot></div>
        <div v-else><slot name="item-pic-active"></slot></div>
        <div v-bind:class="{active:isActive}"><slot name="item-name"></slot></div>
      </div>
    </template>
    
    <style scoped>
     ......
      .active {
        color: red;
      }
    ......
    </style>
    

    直接绑定一个class样式, 当文字被激活时, 也就是isActive:true的时候, 文字显示红色
    来看看效果:

    以上就实现了tabBarItem的封装

    三. 导航路由功能实现

    现在tabBar导航已经完成了, 接下来, 我们点击首页, 应该展示首页组件内容. 点击分类应该展示分类组件内容.下面我们来具体实现这部分功能. 这就是导航的路由功能.

    第一步, 安装路由组件

    npm install vue-router --save
    

    vue-router是一个运行时依赖, 所以需要加上--save参数

    第二步: 创建router文件夹, 并创建index.js文件

    // 第一步: 引入vue 和 vue-router包
    import Vue from 'vue'
    import Router from 'vue-router'
    // 第二步: 安装Vue-router插件
    Vue.user(Router)
    // 第三步: 创建Router组件
    const route = [{
    
    }]
    
    const vueRouter = new Router({
        route: route
    })
    // 第四步: 导出Route组件
    export default vueRouter
    
    

    第三步: 在main.js文件中引入vueRouter

    import Vue from 'vue'
    import App from './App'
    import router from './router'
    
    Vue.config.productionTip = false
    
    /* eslint-disable no-new */
    new Vue({
      el: '#app',
      router,
      render: h => h(App)
    })
    

    第四步: 规划项目结构

    通常, 我们在components目录下放的都是所有公共组件模块. 那么页面相关的模块, 我们会在单独创建一个文件夹, 文件夹的名字可以叫views或者pages或者其他, 业务相关的页面都放着这个文件夹里面. 我们的项目目录结构如下:

    我们在views下面又创建了4个目录, 分别来存放每一个导航组件的路由内容.

    第五步: 添加路由

    接下来要为导航配置路由

    // 先引入组件
    const Home = () => import('../views/home/Home')
    const Cart = () => import('../views/cart/Cart')
    const Category = () => import('../views/category/category')
    const Profile = () => import('../views/profile/profile')
    
    // 然后配置路由
    const routes = [{
      path: "",
      redirect: "/home"
    },{
      path: "/home",
      components: Home
    },{
      path: "/category",
      components: Category
    },{
      path: "/cart",
      components: Cart
    },{
      path: "/profile",
      components: Profile
    }]
    const router = new VueRouter({
      routes
    })
    

    这里配置了4个路由, 分别路由到对应的组件, 当空路由的时候, 定位到/home路由.
    路由配好了, 接下来为按钮配置点击事件.
    我们的tabBarItem组件封装以后是这样

    <template>
      <div class="tabBarItem" v-on:click="clickItem">
        <div v-if="!isActive"><slot name="item-pic"></slot></div>
        <div v-else><slot name="item-pic-active"></slot></div>
        <div v-bind:class="{active:isActive}"><slot name="item-name"></slot></div>
      </div>
    </template>
    

    我们在组件级别上增加一个点击事件, 但跳转的url是不固定的, 我们要通过参数传过来.

    v-on:click="clickItem"
    

    组件间传递数据使用props属性

    <script>
        export default {
            name: "TabBarItem",
            props:["path"],
            data() {
              return {
                isActive: false
              }
            },
            methods: {
              clickItem() {
                this.$router.push(path);
              }
            }
        }
    </script>
    

    在组件中定义一个props属性, 使用itemUrl进行接收. 然后在组件调用的地方传递过来参数就可以了,
    在TabBar中增加参数item-url="/home", 其他三个组件调用方式相同.

        <tab-bar-item  path="/home" >
          <img slot="item-pic" src="../../assets/img/tabBar/1.jpeg">
          <img slot="item-pic-active" src="../../assets/img/tabBar/4.jpeg">
          <div slot="item-name">首页</div>
        </tab-bar-item>
    

    最后在App.vue中增加组件展示区域

    <template>
      <div id="app">
        <router-view></router-view>
        <tab-bar></tab-bar>
      </div>
    </template>
    

    来看看效果

    第六步: 设置按钮选中/取消选中的样式

    <script>
        export default {
            name: "TabBarItem",
            props:["path"],
            computed: {
              isActive(){
                return this.$route.path.indexOf(this.path) !== -1
              }
            },
            data() {
              return {
               // isActive: false
              }
            },
            methods: {
              clickItem() {
                this.$router.push(this.path);
              }
            }
        }
    </script>
    

    增加一个计算属性, 当路由和当前跳转路由的路径一致时,处于选中状态, 否则处于未选中状态. 效果如图:

    第七步: 抽取导航文字的样式

    现在, 我们设置了当导航激活的时候, 文字显示红色, 但是...并不是所有的导航激活的时候都是红色, 所以,我们需要将其动态设置. 也就是, 通过组件从调用方传递一个参数过来.如下所示:

    <tab-bar-item  path="/home" activeStyle="blue">
          <img slot="item-pic" src="../../assets/img/tabBar/1.jpeg">
          <img slot="item-pic-active" src="../../assets/img/tabBar/4.jpeg">
          <div slot="item-name">首页</div>
        </tab-bar-item>
    

    增加一个属性activeStyle="blue", 对应的, 我们需要在组件定义的位置增加一个prop属性

    props: {
      path: String,
      activeStyle: {
        type: String,
        default: 'red'
      }
    },
    

    在prop属性中增加 activeStyle的样式, 并且设置了默认样式red.

    computed: {
      isActive(){
        return this.$route.path.indexOf(this.path) !== -1
      },
      isActiveStyle() {
        return this.isActive ? {color: this.activeStyle}:{}
      }
    },
    

    在计算属性中增加 一个属性isActiveStyle, 如果当前导航处于激活状态, 则显示样式, 否则没有任何样式
    来看看效果, 主要注意文字颜色 变化:

    第八步: 进一步组件化

    我们来看看App.vue文件

    <template>
      <div id="app">
        <router-view></router-view>
        <tab-bar>
          <tab-bar-item  path="/home" activeStyle="blue">
            <img slot="item-pic" src="./assets/img/tabBar/1.jpeg">
            <img slot="item-pic-active" src="./assets/img/tabBar/4.jpeg">
            <div slot="item-name">首页</div>
          </tab-bar-item>
          <tab-bar-item path="/category" activeStyle="green">
            <img slot="item-pic" src="./assets/img/tabBar/2.jpeg">
            <img slot="item-pic-active" src="./assets/img/tabBar/3.jpeg">
            <div slot="item-name">分类</div>
          </tab-bar-item>
          <tab-bar-item path="/cart" activeStyle="pink">
            <img slot="item-pic" src="./assets/img/tabBar/3.jpeg">
            <img slot="item-pic-active" src="./assets/img/tabBar/2.jpeg">
            <div slot="item-name">购物车</div>
          </tab-bar-item>
          <tab-bar-item path="/profile" activeStyle="purple">
            <img slot="item-pic" src="./assets/img/tabBar/4.jpeg">
            <img slot="item-pic-active" src="./assets/img/tabBar/1.jpeg">
            <div slot="item-name">我的</div>
          </tab-bar-item>
        </tab-bar>
      </div>
    </template>
    
    <script>
      import TabBar from "./components/tabBar/TabBar"
      import TabBarItem from "./components/tabBar/TabBarItem";
      export default {
        name: 'App',
        components: {
          TabBar,
          TabBarItem
        }
      }
    </script>
    
    <style>
      @import "./assets/main.css";
    </style>
    
    

    在模板的部分, 内容特别多, 通常App.vue的内容是很简洁的, 所以, 我们还可以将这部分组件进行抽象
    将文件抽取到MainTabBar中, 抽取以后注意图片文件以及vue组件的路径

    <template>
      <tab-bar>
        <tab-bar-item  path="/home" activeStyle="blue">
          <img slot="item-pic" src="../../assets/img/tabBar/1.jpeg">
          <img slot="item-pic-active" src="../../assets/img/tabBar/4.jpeg">
          <div slot="item-name">首页</div>
        </tab-bar-item>
        <tab-bar-item path="/category" activeStyle="green">
          <img slot="item-pic" src="../../assets/img/tabBar/2.jpeg">
          <img slot="item-pic-active" src="../../assets/img/tabBar/3.jpeg">
          <div slot="item-name">分类</div>
        </tab-bar-item>
        <tab-bar-item path="/cart" activeStyle="pink">
          <img slot="item-pic" src="../../assets/img/tabBar/3.jpeg">
          <img slot="item-pic-active" src="../../assets/img/tabBar/2.jpeg">
          <div slot="item-name">购物车</div>
        </tab-bar-item>
        <tab-bar-item path="/profile" activeStyle="purple">
          <img slot="item-pic" src="../../assets/img/tabBar/4.jpeg">
          <img slot="item-pic-active" src="../../assets/img/tabBar/1.jpeg">
          <div slot="item-name">我的</div>
        </tab-bar-item>
      </tab-bar>
    </template>
    
    <script>
      import TabBar from "./TabBar";
      import TabBarItem from "./TabBarItem";
        export default {
            name: "MainTabBar",
            components: {
              TabBar,
              TabBarItem
            }
        }
    </script>
    
    <style scoped>
    
    </style>
    
    

    然后, 在App.vue中引入MainTabBar就可以了

    <template>
      <div id="app">
        <router-view></router-view>
        <main-tab-bar></main-tab-bar>
      </div>
    </template>
    

    效果和原来是一样

    四. 给文件起别名

    当我们的路径很多的时候, 比如上面我们抽象组件时候, 就发现, 文件位置换了, 很多路径都要跟着变. 在vue中可以设置路径的别名, 这样我们就不用在更换了文件位置以后更换路径了.
    在build/webpack.base.conf.js文件中, 有一个resolve选项

      resolve: {
        extensions: ['.js', '.vue', '.json'],
        alias: {
          '@': resolve('src'),
        }
      },
    
    • extensions: 表示引入路径的时候可以省略扩展名
    • alias: 表示给路径起一个别名. resolve('src')的含义是给src路径起一个别名.
      这样, 我们可以给其他文件夹也起一个别名.
      resolve: {
        extensions: ['.js', '.vue', '.json'],
        alias: {
          '@': resolve('src'),
          'components': resolve('@/components'),
          'assets': resolve('@/assets'),
          'router': resolve('@/router'),
          'views': resolve('@/views')
        }
      },
    

    起别名的时候, 比如src/components, 路径就可以使用@/components.
    后面使用到这个路径的文件, 直接使用@/components就可以了

    在使用的时候, 也分为几种场景

    1. 使用import引入组件中的路径
    2. 没有import, 比如图片路径
    3. 在路由导航中的import 路径

    1. 使用import引入组件

    我们在App.vue中引入了MainTabBar
    之前我们引入脚本是这么写的:

    import MainTabBar from "./components/tabBar/MainTabBar";
    

    现在我们配置了路径别名以后, 可以省去前面的./, 直接以components开头

    import MainTabBar from "components/tabBar/MainTabBar";
    

    在style样式引用中同样有效

    <style>
      @import "assets/main.css";
    </style>
    

    我们直接将./省略.

    2. 在图片等非import中引入

    比如我们在MainTabBar.vue组件中设置导航图标的时候, 有很多的src, 之前我们都是这么写的

        <tab-bar-item path="/profile" activeStyle="purple">
          <img slot="item-pic" src="../../assets/img/tabBar/4.jpeg">
          <img slot="item-pic-active" src="../../assets/img/tabBar/1.jpeg">
          <div slot="item-name">我的</div>
    

    在定义了路由别名以后, 我们可以使用如下写法:

    <tab-bar-item  path="/home" activeStyle="blue">
          <img slot="item-pic" src="~assets/img/tabBar/1.jpeg">
          <img slot="item-pic-active" src="~assets/img/tabBar/4.jpeg">
          <div slot="item-name">首页</div>
        </tab-bar-item>
    

    也就是使用别名assets, 但是需要在前面加一个~符合.

    3. 路由中的路径

    到目前为止, 我发现在路由中引入组件, 不能使用别名, 但是可以使用@符号来代表src

    //const Home = () => import('@/views/home/Home')
    import Home from '@/views/home/Home';
    const Cart = () => import('@/views/cart/Cart')
    const Category = () => import('@/views/category/category')
    const Profile = () => import('@/views/profile/profile')
    

    一旦使用别名, 就会报错. 无论是到还是不带都不行. 还需要继续探究

    这也是一个问题

    注意: 当我们修改了配置文件webpack.base.conf.js以后, 要重新启动服务才行. 否则不生效



  • 相关阅读:
    C/C++多文件之间的变量定义
    PKU POJ 2186 Popular Cows 强连通分量
    重载函数
    ZOJ 2763 Prison Break
    201357 训练赛总结
    hdu 4467 Graph 构造
    201356 训练赛总结
    201353 NEERC 2012, Eastern subregional contest
    2013512 CF 183 总结
    一道动态规划
  • 原文地址:https://www.cnblogs.com/ITPower/p/14638880.html
Copyright © 2011-2022 走看看