說說如何在 Vue.js 中實現(xiàn)標簽頁組件

標簽頁組件梅桩,即實現(xiàn)選項卡切換塔鳍,常用于平級內(nèi)容的收納與展示伯铣。

因為每個標簽頁的內(nèi)容是由使用組件的父級控制的,即這部分內(nèi)容為一個 slot轮纫。所以一般的設(shè)計方案是腔寡,在 slot 中定義多個 div,然后在接到切換消息時掌唾,再顯示或隱藏相關(guān)的 div放前。這里面就把相關(guān)的交互邏輯也編寫進來了,我們希望在組件中處理這些交互邏輯糯彬,slot 只單純處理業(yè)務(wù)邏輯凭语。這可以通過再定義一個 pane 組件來實現(xiàn),pane 組件嵌在 tabs 組件中撩扒。

1 基礎(chǔ)版

因為 tabs 組件中的標題是在 pane 組件中定義的似扔,所以在初始化或者動態(tài)變化標題時,tabs 組件需要從 pane 組件中獲取標題搓谆。

html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>標簽頁組件</title>
    <link rel="stylesheet" type="text/css" href="index.css">
</head>
<body>
<div id="app" v-cloak>
    <tabs v-model="activeIndex">
        <pane label="科技">
            火星疑似發(fā)現(xiàn)“外星人墓地”炒辉?至今無法解釋
        </pane>
        <pane label="體育">
            全美沸騰!湖人隊4年1.2億迎頂級后衛(wèi)挽拔,詹姆斯:有他就能奪冠
        </pane>
        <pane label="娛樂">
            阿米爾汗談中國武俠 想拍印度版《鹿鼎記》
        </pane>
    </tabs>
</div>
<script src="https://cdn.bootcss.com/vue/2.2.2/vue.min.js"></script>
<script src="tabs.js"></script>
<script>
    var app = new Vue({
        el: '#app',
        data: {
            activeIndex: 0
        }
    });
</script>
</body>
</html>

pane 組件:

Vue.component('pane', {
    name: 'pane',
    template: '\
    <div class="pane" v-show="isShow">\
        <slot></slot>\
    </div>\
    ',
    props: {
        //標題
        label: {
            type: String,
            default: ''
        }
    },
    data: function () {
        return {
            //顯示或隱藏
            isShow: true
        }
    },
    methods: {
        //通知父組件辆脸,更新標題
        init() {
            this.$parent.init();
        }
    },
    watch: {
        //當 label 值發(fā)生變化時,更新標題
        label() {
            this.init();
        }
    },
    //掛載時螃诅,更新標題
    mounted() {
        this.init();
    }
});

在 pane 組件中啡氢,我們做了以下設(shè)計:

  1. 因為 pane 組件需要控制標簽頁內(nèi)容的顯示與隱藏状囱,所以我們在 data 中定義了一個 isShow,并用 v-show 指令來控制內(nèi)容的顯示或隱藏倘是。當點擊這個 pane 所對應的標簽頁標題時厕鹃,它的 isShow 被設(shè)置為 true随闪。
  2. 我們需要一個標識來識別不同的標簽頁標題敛助,本示例用的是 pane 組件定義順序的索引秒梅。
  3. 在 props 中定義了 label,用于存放標題瘤睹。因為 label 可以動態(tài)變化升敲,所以必須在掛載 pane 以及當 label 值發(fā)生變化(通過監(jiān)聽實現(xiàn))時,通知父組件轰传,重新初始化標題驴党。因為 pane 是獨立組件,所以這里使用了 this.$parent 來調(diào)用父組件 tabs 的初始化方法获茬。

tabs 組件:

Vue.component('tabs', {
    template: '\
    <div class="tabs">\
        <div class="tabs-bar">\
            <!-- 標簽頁標題-->\
            <div :class="tabClass(item)"\
                v-for="(item, index) in titleList"\
                @click="change(index)">\
                {{ item.label }}\
                </div>\
            </div>\
            <div class="tabs-content">\
             <!-- pane 組件位置-->\
                <slot></slot>\
            </div>\
           </div>',
    props: {
        value: {
            type: [String, Number]
        }
    },
    data: function () {
        return {
            currentIndex: this.value,
            titleList: []//存放標題
        }
    },
    methods: {
        //設(shè)置樣式
        tabClass: function (item) {
            return ['tabs-tab', {
                //為當前選中的 tab 添加選中樣式
                'tabs-tab-active': (item.name === this.currentIndex)
            }]

        },
        //獲取定義的所有 pane 組件
        getTabs() {
            return this.$children.filter(function (item) {
                return item.$options.name === 'pane';
            })
        },
        //更新 pane 是否顯示狀態(tài)
        updateIsShowStatus() {
            var tabs = this.getTabs();
            var that = this;
            //迭代判斷并設(shè)置某個標簽頁是顯示還是隱藏狀態(tài)
            tabs.forEach(function (tab, index) {
                return tab.isShow = (index === that.currentIndex);
            })
        },
        //初始化
        init() {
            /**
             * 初始化標題數(shù)組
             */
            this.titleList = [];
            var that = this;//設(shè)置 this 引用
            this.getTabs().forEach(function (tab, index) {
                that.titleList.push({
                    label: tab.label,
                    name: index
                });

                //初始化默認選中的 tab 索引
                if (index === 0) {
                    if (!that.currentIndex) {
                        that.currentIndex = index;
                    }
                }
            });

            this.updateIsShowStatus();
        },
        //點擊 tab 標題時港庄,更新 value 值為相應的索引值
        change: function (index) {
            var nav = this.titleList[index];
            var name = nav.name;
            this.$emit('input', name);
        }
    },
    watch: {
        //當 value 值發(fā)生改變時,更新 currentIndex
        value: function (val) {
            this.currentIndex = val;
        },
        //當 currentIndex 值發(fā)生改變時恕曲,更新 pane 是否顯示狀態(tài)
        currentIndex: function () {
            this.updateIsShowStatus();
        }
    }
});
  1. getTabs() 中通過 this.$children 來獲取定義的所有 pane 組件鹏氧。因為很多地方都會用到getTabs() ,所以這里把它單獨定義出來佩谣。
  2. 注意: methods 中如果存在回調(diào)函數(shù)把还,那么需要在外層事先定義一個 var that = this;,在 that 中引用 Vue 實例本身茸俭,也可以使用 ES2015 的箭頭函數(shù)笨篷。
  3. 在初始化方法中,我們通過迭代 pane 組件瓣履,初始化了標題數(shù)組,label 取定義的標題练俐,name 取所在的索引袖迎。 標題數(shù)組用于模板定義中。
  4. updateIsShowStatus() 用于更新 tab 是否顯示狀態(tài)腺晾。之所以獨立出來燕锥,是為了在監(jiān)聽 currentIndex 發(fā)生變化時,也能調(diào)用該方法悯蝉。
  5. 在模板定義中归形,我們使用 v-for 指令渲染出標題,并綁定了 tabClass 函數(shù)鼻由,從而實現(xiàn)了動態(tài)設(shè)置樣式暇榴。因為需要傳參厚棵,所以不能使用計算屬性。
  6. 點擊每一個 tab 標題時蔼紧,會觸發(fā) change()婆硬,來更新 value 值為相應的索引值。在 watch 中奸例,我們監(jiān)聽了 value 值彬犯,當 value 值發(fā)生改變時,更新 currentIndex查吊。也監(jiān)聽了 currentIndex 值谐区,當 currentIndex 值發(fā)生改變時,更新 pane 是否顯示狀態(tài)逻卖。

總結(jié)如下:

  1. 使用組件嵌套方式宋列,將多個 pane 組件作為 tabs 組件的 slot。
  2. tabs 組件與 pane 組件箭阶,通過父子鏈(即 $parent$children)實現(xiàn)通信虚茶。

樣式:

[v-cloak] {
    display: none;
}

.tabs {
    font-size: 14px;
    color: #657180;
}

.tabs-bar:after {
    content: '';
    display: block;
    width: 100%;
    height: 1px;
    background: #d7dde4;
    margin-top: -1px;
}

.tabs-tab {
    display: inline-block;
    padding: 4px 16px;
    margin-right: 6px;
    background: #fff;
    border: 1px solid #d7dde4;
    cursor: pointer;
    position: relative;
}

.tabs-tab:hover {
    color: #336699;
    font-weight: bolder;
}

.tabs-tab-active {
    color: #336699;
    border-top: 1px solid #336699;
    border-bottom: 1px solid #fff;
}

.tabs-tab-active:before {
    content: '';
    display: block;
    height: 1px;
    background: #3399ff;
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
}

.tabs_content {
    padding: 8px 0;
}

.pane {
    margin-top: 26px;
    font-size: 16px;
    line-height: 24px;
    color: #333;
    text-align: justify;
}

效果:

2 關(guān)閉屬性

我們?yōu)?pane 組件新增一個 closable 屬性,用于控制該標簽是否可關(guān)閉仇参。

在子窗口組件的 props 中嘹叫,新增 closable 屬性:

props: {
    ...
    //是否可關(guān)閉
    closable: {
        type: Boolean,
        default: false
    }
}

在標簽頁組件中的模板中,新增關(guān)閉標簽:

...
template: '\
<div class="tabs">\
    <div class="tabs-bar">\
        <!-- 標簽頁標題-->\
        <div :class="tabClass(item)"\
            v-for="(item, index) in titleList"\
            @click="change(index)">\
            {{ item.label }}\
            <span v-if="item.closable" class="close" @click="close(index,item.name)"></span>\
            </div>\
        </div>\
        <div class="tabs-content">\
         <!-- pane 組件位置-->\
            <slot></slot>\
        </div>\
       </div>',
...
  1. 這里使用 v-if 指令诈乒,根據(jù) closable 的值來判斷是否構(gòu)建 “關(guān)閉” 標簽罩扇。
  2. 點擊事件綁定了 close() 函數(shù),傳入標簽所在索引以及標簽的名稱怕磨。

在標簽頁組件中的方法中喂饥,新增了 close(),用于執(zhí)行關(guān)閉標簽頁邏輯:

close: function (index, name) {
        //刪除對應的標題元素
    this.titleList.splice(index, 1);

    var tabs = this.getTabs();
    var that = this;
    //迭代判斷并設(shè)置點擊的標簽頁是隱藏狀態(tài)
    tabs.forEach(function (tab, index) {
        if (index === name) {
            return tab.isShow = false;
        }
    });
}
  1. 首先在標題數(shù)組中刪除對應的標題元素肠鲫,因為 Vue.js 的核心是數(shù)據(jù)與視圖的雙向綁定员帮。因此當我們修改數(shù)組時, Vue.js 就會檢測到數(shù)組發(fā)生了變化导饲,所以用 v-for 渲染的視圖也會同步更新 捞高。
  2. 接著,隱藏對應的 tab 內(nèi)容渣锦,我們通過傳入的 name 與某個 tab 中的 index硝岗,逐一比對,如果確定是我們需要關(guān)閉的標簽頁袋毙,那么就隱藏其內(nèi)容型檀。其實這里使用 key 來表達更合適。

新增的樣式:

.close{
    color: #FF6666;
}
.close::before {
    content: "\2716";
}

.close:hover {
    color: #990033;
    font-weight: bolder;
}

為需要添加關(guān)閉標簽的 pane 听盖,添加 closable 屬性:

<div id="app" v-cloak>
    <tabs v-model="activeIndex">
        <pane label="科技" closable="true">
            火星疑似發(fā)現(xiàn)“外星人墓地”胀溺?至今無法解釋
        </pane>
        <pane label="體育">
            全美沸騰裂七!湖人隊4年1.2億迎頂級后衛(wèi),詹姆斯:有他就能奪冠
        </pane>
        <pane label="娛樂" closable="true">
            阿米爾汗談中國武俠 想拍印度版《鹿鼎記》
        </pane>
    </tabs>
</div>

效果:

3 切換動畫

我們在切換標簽頁時月幌,加上滑動動畫吧碍讯,這很簡單,只要在激活的樣式中加上 transform 與 transition 樣式即可:

.tabs-tab-active {
    color: #336699;
    border-top: 1px solid #336699;
    border-bottom: 1px solid #fff;
    transform:translateY(-1px);
    transition: transform 0.5s;
}

效果:

我們讓標簽頁標題被點擊時扯躺,以動畫的形式往上移動 1 個像素捉兴。是不是很酷呀O(∩_∩)O~

本文示例代碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市录语,隨后出現(xiàn)的幾起案子倍啥,更是在濱河造成了極大的恐慌,老刑警劉巖澎埠,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虽缕,死亡現(xiàn)場離奇詭異,居然都是意外死亡蒲稳,警方通過查閱死者的電腦和手機氮趋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來江耀,“玉大人剩胁,你說我怎么就攤上這事∠楣” “怎么了昵观?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長舌稀。 經(jīng)常有香客問我啊犬,道長,這世上最難降的妖魔是什么壁查? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任觉至,我火速辦了婚禮,結(jié)果婚禮上睡腿,老公的妹妹穿的比我還像新娘康谆。我一直安慰自己,他們只是感情好嫉到,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著月洛,像睡著了一般何恶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嚼黔,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天细层,我揣著相機與錄音惜辑,去河邊找鬼。 笑死疫赎,一個胖子當著我的面吹牛盛撑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播捧搞,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼抵卫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了胎撇?” 一聲冷哼從身側(cè)響起介粘,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎晚树,沒想到半個月后姻采,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡爵憎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年慨亲,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宝鼓。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡刑棵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出席函,到底是詐尸還是另有隱情铐望,我是刑警寧澤,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布茂附,位于F島的核電站正蛙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏营曼。R本人自食惡果不足惜乒验,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蒂阱。 院中可真熱鬧锻全,春花似錦、人聲如沸录煤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽妈踊。三九已至了嚎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背歪泳。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工萝勤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人呐伞。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓敌卓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親伶氢。 傳聞我的和親對象是個殘疾皇子趟径,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內(nèi)容