从事前端也算有几个年头,随着作项目的愈来愈多,须要一些本身用的轮子可能要本身作一些,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