從事前端也算有幾個年頭,隨着作項目的愈來愈多,須要一些本身用的輪子可能要本身作一些,vue用了也有一年多點,初步算能用起來,如今想本身封裝一個級聯彈窗html
以前,用vue寫組件大部分是用的 :參數名 這樣傳入,可有些組件是有交互性的,可能要在裏面進行輸入或者變動一些值,那如何傳遞到父級上哪?你能夠回綁定一些回調方法,或者定義方法直接調用。但若是隻爲控制組件的開關或者展現的話就有點大材小用的感受,可用$refs的方法又必需要註明,感受好LOW。前端
以前看到別人用v-model綁定變量就能夠實現,因而研究了一下。vue
vue自己是支持v-model用於自定義組件。不廢話直接上代碼less
parent學習
<div class="view" @contextmenu="rightClick($event)"> <Menu v-model="menuShow" ref="menuComponent" :menuList="menuList" :x="menuX" :y="menuY" /> export default { name: 'demo', data: function(){ return { menuShow: false, // 菜單列表 menuList: [ { id: 1, key: '菜單一菜單一菜單一菜單一菜單一菜單一菜單一', children: [ { id: 11, key: '1-1', children: [ { id: 111, key: '1-1-1', },{ id: 112, key: '1-1-2', } ] } ] },{ id: 2, key: '菜單二', children: [ { id: 21, key: '2-1', children: [ { id: 111, key: '2-1-1', } ] },{ id: 22, key: '2-2', children: [ { id: 111, key: '2-2-1', } ] },{ id: 23, key: '2-3', } ] },{ id: 3, key: '菜單三', } ], // 菜單座標 menuX: 0, menuY: 0, } }, components: { Menu, }, methods: { rightClick: function (e) { e.preventDefault(); console.log("當前被右擊了"); // 傳入點擊時的視口座標 this.menuX = e.clientX; this.menuY = e.clientY; this.menuShow = true; } } }
childthis
<template> <div v-if="value" :style="{top: positionY, left: positionX}" class="menuMain" :class="direction==1?'right':'left'" @mouseleave="leaveMenu" @mouseover="subTitEnter($event)"> <ul v-html="menuDomStr" @click="selectClick($event)"> </ul> </div> </template> <script> export default { name: 'menuMain', props: { value: Boolean, // menuShow: Boolean, menuList: Array, callback: Function, x: Number, y: Number, }, data: function() { return { // 解析後的菜單DOM字符串 menuDomStr: "", // 是左彈仍是右彈 1 右彈 2 左彈 direction: 1, // 菜單最大深度 depth: 1, // 單層菜單的最大寬度 maxWidth: 102, // 定位座標 positionX: '0px', positionY: '0px', } }, methods: { // 解析傳入菜單數據 遞歸解析菜單數據 parsing: function(data, dep) { var html = ""; this.depth = this.depth>dep?this.depth:dep; for(var i=0; i<data.length; i++){ var one = data[i]; // console.log(one); if(one.children && one.children.length){ // 有子菜單 html += "<li class='item subMenuItem'><span class='tit' title='"+one.key+"'>" + one.key + "</span><ul class='subMenu'>" + this.parsing(one.children, dep+1) + "</ul></li>"; }else{ // 當前是最底層菜單 html += "<li class='item'><span class='menuNode' title='"+one.key+"' data-id='" + one.id + "'>" + one.key + "</span></li>"; } } return html; }, // 選中事件 selectClick: function(e) { // console.log(e); var targetEl = e.target; if(targetEl.className == 'menuNode'){ console.log("當前最後子節點"); var itemId = targetEl.getAttribute("data-id"); if(this.callback){ this.callback(itemId); } this.show = false; console.log(targetEl.getAttribute("data-id")); }else{ console.log("子菜標題"); } }, // 鼠標移出菜單 leaveMenu: function() { // this.value = false; this.$emit('input', false); }, // 子級菜單 標題進入 subTitEnter: function(e) { var targetEl = e.target; // console.log(e); if(targetEl.className == "tit"){ console.log(targetEl.getClientRects()); } } }, watch: { // 自定義組件 v-model傳入的值是value value: function(newValue, oldValue) { if(newValue){ // 當要展現菜單時 // 解析傳入菜單數據 this.menuDomStr = this.parsing(this.menuList, 1); // 當前視口寬度 var viewW = window.innerWidth; var viewH = window.innerHeight; // 當前菜單最大深度展開寬度 var menuMaxWidth = this.maxWidth * this.depth; // 橫向超屏檢測 if(this.x+this.maxWidth > viewW){ // 一層都展不開的超屏 向左展開菜單 this.direction = 2; this.positionX = viewW - (this.maxWidth+5)+'px'; }else if(this.x+(this.maxWidth*this.depth) > viewW){ // 能展開第一層,但展不開最大深度 this.direction = 2; this.positionX = (this.x?this.x-5:0)+'px'; }else{ // 都沒有問題 this.direction = 1; this.positionX = (this.x?this.x:0)+'px'; } // 縱向超屏檢測 this.positionY = viewH>this.menuList.length*32+this.y?this.y-5+'px':(viewH-this.menuList.length*32-5+'px'); }else{ // 自定義組件要用 父級input回傳數據 this.$emit("input", false); } } } } </script> <style lang="less" > ul,li{ list-style: none; padding: 0; margin: 0; background-color: #fff; } .menuMain{ position: fixed; z-index: 99999; ul{ border: 1px solid #aaa; &.subMenu{ display: none; position: absolute; top: -1px; } } &.right ul.subMenu{ left: 100%; } &.left ul.subMenu{ right: 100%; } li{ position: relative; white-space: nowrap; font-size: 12px; cursor: pointer; &:not(:last-child){ padding-bottom: 1px; &:after{ content: ""; display: block; position: absolute; left: 5px; right: 5px; bottom: 0; height: 1px; background-color: #aaa; } } >span{ display: block; padding: 0 10px; height: 30px; line-height: 30px; max-width: 100px; text-overflow: ellipsis; overflow: hidden; } &.subMenuItem:hover>ul{ display: block; } } } </style>
這裏面有幾個比較坑的地方spa
一、自定義級件若是用v-model接收傳值的話,prop 接收的值是value,等同於input,多是vue一開始v-model的傳值是給input這樣的表單組件設置的吧code
二、當value值發生變化後,要用 $emit('input', value) 進行回傳,回傳的事件然必定是input,緣由我也不知道component
這是我寫的一個很初步的彈窗組件,寫的仍是很low,但中間踩過了坑着時很多,在這裏但願和我要樣技術不高的同行能夠互相學習指證,共同提升。htm