vue手寫一個組件

從事前端也算有幾個年頭,隨着作項目的愈來愈多,須要一些本身用的輪子可能要本身作一些,vue用了也有一年多點,初步算能用起來,如今想本身封裝一個級聯彈窗html

以前,用vue寫組件大部分是用的 :參數名 這樣傳入,可有些組件是有交互性的,可能要在裏面進行輸入或者變動一些值,那如何傳遞到父級上哪?你能夠回綁定一些回調方法,或者定義方法直接調用。但若是隻爲控制組件的開關或者展現的話就有點大材小用的感受,可用$refs的方法又必需要註明,感受好LOW。前端

以前看到別人用v-model綁定變量就能夠實現,因而研究了一下。vue

11.png

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

相關文章
相關標籤/搜索