1. 前言
本文記錄在 vue cli 2 中封裝 tabbar 組件詳細步驟,主要是為了感受 vue 的組件封裝思想,也是前面學習 vue 基礎的練習案例
2. 清理腳手架
刪除圖片: src/assets/logo.png
刪除 HelloWorld 組件: src/components/HelloWorld.vue
清理 HelloWorld 組件: src/router/index.js (HelloWorld 組件路由配置)
清理 APP 組件: src/App.vue, 去除樣式代碼和模板內(nèi)容,清理后內(nèi)容如下:
<template>
<div id="app"></div>
</template>
<script>
export default {
name: "App",
};
</script>
<style>
</style>
3. 搭建 tabbar 基本布局
APP 組件
<template>
<div id="app">
<div id="tab-bar">
<div>首頁</div>
<div>分類</div>
<div>購物車</div>
<div>我的</div>
</div>
</div>
</template>
tabbar 圖片存放目錄: src/assets/img/tabbar
基礎樣式文件: src/assets/css/base.css
body {
margin: 0;
padding: 0;
}
在 APP 組件的 style 標簽中導入基礎樣式文件
<style>
@import "./assets/css/base.css";
</style>
4. 書寫 tabbar 基本樣式
補充: tabbar 高度一般都是 49px,文字大小 14px
模板內(nèi)容
<template>
<div id="app">
<div id="tab-bar">
<div class="tab-bar-item">首頁</div>
<div class="tab-bar-item">分類</div>
<div class="tab-bar-item">購物車</div>
<div class="tab-bar-item">我的</div>
</div>
</div>
</template>
樣式代碼
@import "./assets/css/base.css";
#tab-bar {
display: flex;
position: fixed;
left: 0;
right: 0;
bottom: 0;
background-color: #f6f6f6;
box-shadow: 0 -1px 1px rgba(100, 100, 100, 0.1);
}
.tab-bar-item {
flex: 1;
height: 49px;
text-align: center;
font-size: 14px;
}
效果如下圖所示
5. TabBar 組件封裝
創(chuàng)建 TabBar 組件: src/components/tabbar/TabBar.vue
將 APP.vue 中的 tabbar 代碼抽離到 TabBar 組件中,TabBar 組件內(nèi)容:
<template>
<div id="tab-bar">
<div class="tab-bar-item">首頁</div>
<div class="tab-bar-item">分類</div>
<div class="tab-bar-item">購物車</div>
<div class="tab-bar-item">我的</div>
</div>
</template>
<script>
export default {
name: "TabBar",
};
</script>
<style scoped>
#tab-bar {
display: flex;
position: fixed;
left: 0;
right: 0;
bottom: 0;
background-color: #f6f6f6;
box-shadow: 0 -1px 1px rgba(100, 100, 100, 0.1);
}
.tab-bar-item {
flex: 1;
height: 49px;
text-align: center;
font-size: 14px;
}
</style>
調(diào)整 TabBar 組件
<div id="tab-bar">
<div class="tab-bar-item">
<img src="@/assets/img/tabbar/home.png" />
<div>首頁</div>
</div>
<div class="tab-bar-item">
<img src="@/assets/img/tabbar/category.png" />
<div>分類</div>
</div>
<div class="tab-bar-item">
<img src="@/assets/img/tabbar/cart.png" />
<div>購物車</div>
</div>
<div class="tab-bar-item">
<img src="@/assets/img/tabbar/profile.png" />
<div>我的</div>
</div>
</div>
增加樣式代碼 (tabbar 圖片大小一般是 24px)
.tab-bar-item img {
width: 24px;
height: 24px;
margin-top: 3px;
vertical-align: middle;
margin-bottom: 2px;
}
當前效果
6. TabBarItem 組件封裝
此時組件容器 (#tab-bar) 和子項 (.tab-bar-item) 都在 TabBar 組件中,而 TabBar 應該只處理組件容器中的邏輯和樣式,子項應被抽離出去
所以,我們可以在 TabBar 組件中定義一個插槽, 并將子項 (.tab-bar-item) 樣式先抽離到 APP 組件中
抽離后 TabBar 組件:
抽離后 APP 組件:
通過上圖可發(fā)現(xiàn) APP 組件就復雜了,所以需要再創(chuàng)建一個 TabBarItem 組件
然后在 APP 組件中導入 TabBarItem 組件,如下圖所示:
現(xiàn)在 TabBarItem 組件是抽離出來了,但組件內(nèi)容還是死的,需要在 TabBarItem 組件中也定義插槽
<template>
<div class="tab-bar-item">
<slot name="item-icon"></slot>
<slot name="item-text"></slot>
</div>
</template>
然后 APP 組件就可以像下面這樣寫了
<tab-bar-item>
<img slot="item-icon" src="@/assets/img/tabbar/home.png" />
<div slot="item-text">首頁</div>
</tab-bar-item>
7. 給 TabBarItem 組件傳入選中時的圖片
當前組件中只有一個傳圖片的插槽,需要在添加一個傳菜單選中時圖片的插槽 (item-icon-active)
<template>
<div class="tab-bar-item">
<slot name="item-icon"></slot>
<slot name="item-icon-active"></slot>
<slot name="item-text"></slot>
</div>
</template>
APP 組件
<tab-bar-item>
<img slot="item-icon" src="@/assets/img/tabbar/home.png" />
<img slot="item-icon-active" src="@/assets/img/tabbar/home-active.png"/>
<div slot="item-text">首頁</div>
</tab-bar-item>
當前效果
此時我們需要添加個 isActive 數(shù)據(jù),代表菜單選中狀態(tài), 調(diào)整 TabBarItem 組件
<template>
<div class="tab-bar-item">
<div v-if="!isActive">
<slot name="item-icon"></slot>
</div>
<div v-else>
<slot name="item-icon-active"></slot>
</div>
<div :class="{ active: isActive }">
<slot name="item-text"></slot>
</div>
</div>
</template>
data() {
return {
isActive: false,
};
},
.active {
color: red;
}
8. TabBarItem 組件和路由結合效果
創(chuàng)建底部菜單對應的組件: 首頁、分類、購物車、我的
<template>
<h2>首頁</h2>
</template>
<script>
export default {
name: "Home",
};
</script>
<style scoped>
</style>
添加路由映射配置: src/router/index.js
const routes = [
{
path: "/home",
component: () => import('@/views/home/Home')
},
{
path: "/category",
component: () => import('@/views/category/Category')
},
{
path: "/cart",
component: () => import('@/views/cart/Cart')
},
{
path: "/profile",
component: () => import('@/views/profile/Profile')
},
]
TabBarItem 組件 props 屬性定義個 path 用于接收菜單路由, 并且給菜單容器綁定一個點擊事件
<div class="tab-bar-item" @click="itemClick"></div>
export default {
name: "TabBarItem",
props: {
path: String,
},
data() {
return {
// isActive: false,
};
},
computed: {
isActive() {
return this.$route.path.indexOf(this.path) !== -1;
},
},
methods: {
itemClick() {
if (this.path !== this.$route.path) {
// 路由跳轉
this.$router.push(this.path);
}
},
},
};
調(diào)整APP 組件,將菜單路由用 path 屬性傳給子組件,并且 router-view 組件將路由組件內(nèi)容渲染出來
<tab-bar>
<tab-bar-item path="/home"></tab-bar-item>
</tab-bar>
<router-view></router-view>
9. TabBarItem 組件的顏色動態(tài)控制
TabBarItem 組件 props 中添加 activeColor 屬性用于接收菜單選中時的顏色,并且默認值為 red;添加計算屬性 activeStyle
props: {
activeColor: {
type: String,
default: "red",
}
},
computed: {
activeStyle() {
return this.isActive ? { color: this.activeColor } : {};
},
},
將
<div :class="{active: isActive}">
<slot name="item-text"></slot>
</div>
改為
<div :style="activeStyle">
<slot name="item-text"></slot>
</div>
APP 組件則可以通過 activeColor 屬性控制菜單選中時的顏色,省略時默認為 red
<tab-bar-item path="/home" activeColor="green"></tab-bar-item>