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以后, 要重新启动服务才行. 否则不生效



  • 相关阅读:
    hdu 1074
    hdu 4091
    hdu 4422
    hdu 3940
    hdu 2831
    hdu 1172
    hdu 3732
    hdu 1250
    hud 2073
    IOS socket基于tcp/udp的通信
  • 原文地址:https://www.cnblogs.com/ITPower/p/14638880.html
Copyright © 2011-2022 走看看