使用SemanticUI和vue做一個menubar組件,實現方法大概是這樣的:
<template> <div class="ui menu"> <template v-for="item in leftItems"> <a " v-if="!item.children" @click="item.click"> <i class="{{ item.icon }} icon" v-if="item.icon"></i>{{item.text}} <div class="ui mini {{item.labelColor }} label" v-if="item.label"> {{item.label}} </div> </a> //如果有有children則說明是下拉式功能表項,然後遞迴調用自身 <template v-else="item.children.length > 0"> <div class="ui dropdown item"> <i class="{{ item.icon }} icon" v-if="item.icon"></i> <div class="text"> {{item.text}}</div> <menubar :items="item.children" ></menubar> </div> </template> </template> //顯示在右側的功能表項目,也是遞迴調用自身 <menubar :items="rightItems" v-if="rightItems.length > 0"></menubar></div></template>
使用時,假如父組件app使用到了menubar組件,那麼data中需要定義一下items資料,例 :
menubar:[ {id:"a",text:"首頁1",icon:"home",tips:"提示",label:"33",labelColor:"red",url:"#"}, {id:"b",text:"菜單",icon:"edit",tips:"提示",url:"#",children:[ {id:"a",text:"菜單1",click:"test3",icon:"home",url:"#"}, {id:"a",text:"菜單2",click:"test3",icon:"home",url:"#"} ]}, {id:"bb",text:"編輯",tab:"a",icon:"user",vlink:"#"}, {id:"bb",text:"檔案",tab:"b",icon:"user",click:"test1"}, {id:"bb",text:"協助",tab:"c",icon:"help",click:"test2"}, {id:"bb",text:"工具",icon:"user",url:"www.baidu.com"}, {id:"c",text:"設定",icon:"home",tips:"提示",enabled:true,color:"blue",url:"#",right:true,label:"",children:[ {id:"bbb",text:"配置",icon:"home",tips:"提示",click:"test3"}, {id:"adsd",text:"退出",icon:"home",tips:"提示",vlink:"/workdesk",url:"#"} ] } ]
裡面的click事件是定義了,當在工具列中單擊時的事件,理想的情況應該是事件定義在父組件app的events裡面,像這樣:
events:{ eventa:function(){....}, eventb:function(){....},}
工具列組件是根據傳入的items來產生的,包括裡面的子組件。最終工具列組件的結構就是一個樹狀結構,例似這樣的:
MenuBar
--MenuBar
----MenuBar
-----MenuBar
--Menubar
由於每個工具列組件裡面的每個Menubar均有自己的上下文,這樣當子組件Menubar的click事件觸發時並不會調用到頂層app組件events裡面定義的事件,而只是調用了父Menubar的events事件。
但是在使用體驗上,很明顯,工具列組件的點擊事件定義應該是定義在app組件的events裡面的才是合理。我們希望menubar:[]定義功能表項目時,不管多少級嵌套,事件的觸發均可以冒泡到最上面的menubar的父上面。
因此,要實現該機制,目前是採用組件之間的通訊機制來實現的:
<a @click="onMenuItemClick(item,$event)" data-tab="{{item.tab}}" v-link="item.vlink" href="{{item.url}}" v-if="!item.children" :class="[{'active':item.active==true,'disabled':item.enabled==false},item.color,'item']" title="{{item.tips}}"> <i class="{{ item.icon }} icon" v-if="item.icon"></i>{{item.text}} <div class="ui mini {{item.labelColor }} label" v-if="item.label"> {{item.label}} </div> </a>
上面定義一個事件@click="onMenuItemClick(item,$event)"
methods:{ onMenuItemClick:function(item,$event){ if(this.subMenu){ this.$dispatch("menuItemClick",item,$event) }else{ if(item.click){ this.$parent.$emit(item.click,item) } } } }
在onMenuItemClick觸發時,我們根據傳入的subMenu來確認點擊事件如何處理,如果Menubar是作為子功能表欄處理,則我們就直接向上冒泡事件,否則就在上層父組件觸發事件。
<menubar @menuItemClick="onMenuItemClick" :items="rightItems" sub-menu="true" v-if="rightItems.length > 0"></menubar>
在menubar組件內部調用時就傳入submenu=true,並且偵聽事件menuItemClick,menuItemClick事件代碼這樣:
events:{ menuItemClick:function(item,$event){ if(!this.subMenu){ this.$parent.$emit(item.click,item) }else{ return true } } },
小結一下:
在處理嵌套結構的組件,如具有下拉式功能表的工具列、樹形組件等時,由於組件內部均具有各自獨立的上下文,因此必須使用組件通訊機制來處理內部組件間的通訊。
但如此處理方式,我覺得還是比較麻煩的,理想的方式,我覺得最好的官方可以為組件提供一個直接使用父組件內容相關的機制,例如:
<MenuBar> <button scoped="false"></button><button scoped="false"></button></MenuBar>
這樣上面的button就沒有自己的上下文,而可以直接引入父組件的上下文,這樣模式應該在很多場合均會使用到的。