目標: 做一個導航tabbar
一. 分析
我們的目標是做一個導航tabbar, 要求
- 這個導航不僅可以在一個頁面使用, 可以在多個頁面通用
- 每個頁面的樣式可能不一樣
- 每個頁面的圖示, 文字可能不一樣
- 每個頁面導航的個數可能不一樣
要想實現上面的情況, 需要進行功能拆解:
- 提煉出一個通用的tabBar, 然後在裡面定義插槽, 根據需要放入tabBarItem,
- tabBarItem裡面包含圖片, 文字. 圖片和文字也是插槽, 不同的tabBarItem顯示的圖片的文字都有可能不同.
- 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就可以了
在使用的時候, 也分為幾種場景
- 使用import引入元件中的路徑
- 沒有import, 比如圖片路徑
- 在路由導航中的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以後, 要重新啟動服務才行. 否則不生效