18. vue-router案例-tabBar導航

目標: 作一個導航tabbarjavascript

一. 分析

咱們的目標是作一個導航tabbar, 要求css

  1. 這個導航不只能夠在一個頁面使用, 能夠在多個頁面通用
  2. 每一個頁面的樣式可能不同
  3. 每一個頁面的圖標, 文字可能不同
  4. 每一個頁面導航的個數可能不同

要想實現上面的狀況, 須要進行功能拆解:html

  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目錄下vue

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

定義好了main.css文件, 須要將其引入到App.vue文件中java

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

引入css文件樣式使用的是@import '文件路徑', 而引入js文件使用的是import '文件路徑'webpack

第三步: 定義tabBar樣式

tabBar採用的是Flex彈性佈局的佈局方式.web

#tabBar {
        display: flex;
    }

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

上面這段代碼就指定了tabBar採用的佈局方式. 它會根據子元素的個數進行彈性佈局. 在子元素中咱們設置每一個元素的flex: 1
表示的是在空間可用的狀況下, 平均分配空間.vue-router

第四步: 定義其餘樣式

<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>

這裏面除了有佈局樣式,還有底部對齊, 導航的陰影等等.npm

總結: 這樣的導航不通用, 若是咱們要複用, 須要拷貝html內容, 還要拷貝css樣式. 咱們能夠把公共部分提成一個組件json

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之後, 要從新啓動服務才行. 不然不生效

相關文章
相關標籤/搜索