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