vue 自定义指令

180 阅读3分钟

自定义指令

v-model、v-bind、v-on、v-show、v-if 等这些由 v- 开头的都是Vue中的核心内置指令,他们的作用就是在元素的整个生命周期的某个阶段对DOM进行内容或者样式上的操作。

1.全局自定义指令

(1)定义全局自定义指令 以下就是一个自定义指令让文本框获取焦点的实例:

//自定义全局的指令
Vue.directive('focus', {
     //第一个参数永远是el,表示原生的js对象
     bind: function (el) { //当指令绑定到元素上的时候,会立即执行bind函数,只执行一次,此时元素还没有插入到DOM中,focus聚焦此时不会生效
         el.focus()
     },
     inserted: function (el) { //当元素插入到DOM中的时候,会执行inserted函数,只执行一次
         el.focus()
     },
     updated: function () { //当VNode的时候,会执行updated函数,可能出发多次
     }
 });
 <input type="text" class="form-control" v-model="keywords" v-focus">

2.私有自定义指令

(1)和私有自定义过滤器类似,也是将作为和methods平级的属性定义在VM的实例中

directives: {
    'fontweight': {
        bind: function (el, bingding) {
            el.style.fontWeight = bingding.value;
        }
    },
    'fontsize': function (el, bingding) { //这个function等同于把代码写到了bind和update中去
        el.style.fontSize = parseInt(bingding.value) + 'px';
    }
}

(2)在dom中使用该指令

{{date | dateFormat}}

钩子函数

一个指令定义对象可以提供如下几个钩子函数 (均为可选):

1. inserted:

被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。

2. bind:

只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

3. update:

所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。

4. componentUpdated:

指令所在组件的 VNode 及其子 VNode 全部更新后调用。

5. unbind:

只调用一次,指令与元素解绑时调用。

钩子函数参数

1. el:

指令所绑定的元素,可以用来直接操作 DOM 。

2. binding:
  1. 一个对象,包含以下属性:
    1. name:指令名,不包括 v- 前缀。
    2. value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
    3. oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
    4. expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 “1 + 1”。
    5. arg:传给指令的参数,可选。例如 v-my-directive: foo 中,参数为 “foo”。
    6. modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
3. vnode:

Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。

4. oldVnode:

上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

注意:除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。

自定义指令分享

自定义指令 点击 元素内部外部

<div V-click-outside>
    <input type="text" :value="formatDate">
    <div class="panneu" v-if="isVisible">
        content 
    <button></button>
    /div>
</div>
directives:{
    clickOutside:[ // 指令的生命周期
        bind(el,bindings,vnode){ // context
    // 把事件绑定给document上 看一下点击的是否是当前这个元素内部let handler = (e)=>[
    if(el.contains(e.target)){
    // 判断一下是否当前面板已经显示出来了if(!vnode.context.isVisible){vnode.context.focus();
     }elsef
    if(vnode.context.isVisible){vnode.context.blur();
        el.handler = handler;
        document.addEventListener('click',handler)
    unbind(el){
        document.removeEventListener('click',el.handler)
    }
}

长摁事件

   <div v-longpress="longPressHandler"/>
  directives: {
    longpress: {
      inserted: function (el, binding, vnode) {
        let pressTimer = null;
        el.addEventListener("mousedown", (e) => {
          pressTimer = setTimeout(() => {
            binding.value();
          }, 300);
        });
        el.addEventListener("mouseup", (e) => {
          clearTimeout(pressTimer);
        });
      },
    },
  },

右键显示 全局点击左键关闭

      <div
        class="treeDiv"
        v-infinite-scroll="load"
        infinite-scroll-immediate="false"
      >
        <p
          v-for="item in data"
          :key="item.tableNames"
          :class="tableName == item.tableNames ? 'activeP' : ''"
          @click="handleNodeClick(item)"
          @contextmenu.prevent="RightNodeClick(item)"
          v-Rclick
        >
          {{ item.tableNames }}
        </p>
        <div class="modelDiv" ref="modelDiv">
          <p @click="rightLeftClick">设计表</p>
        </div>
      </div>
     directives: { 
         Rclick: {
      // 自定义指令  
      inserted: function (el, _binding, vnode) {
        let that = vnode.context; // 当前vue 实例
        el.oncontextmenu = function (e) {
          e.preventDefault();
          window.oncontextmenu = function (e) {
            let ny = e.clientY;
            let nx = e.clientX;
            that.$refs.modelDiv.style.top = ny + "px";
            that.$refs.modelDiv.style.left = nx + "px";
            that.$refs.modelDiv.style.display = "block";
            window.oncontextmenu = function () {}; // 指定元素使用完后 初始化 windowoncontextmenu 事件
          };
          window.onclick = function (e) {
            that.$refs.modelDiv.style.display = "none";
          };
        };
      },
    },
     }

自定义 拖拽边框放大缩小 和 自定义右键点击 然后显示在对应位置上

之间写项目的时候有遇到 精简代码 放在这

<template>
  <el-container style="overflow: hidden">
    <el-aside :width="asideWith">
      <el-input v-model="inputVal" placeholder="请输入内容"></el-input>
      <div
        class="treeDiv"
        v-infinite-scroll="load"
        infinite-scroll-immediate="false"
      >
        <p
          v-for="item in data"
          :key="item.tableNames"
          @click="handleNodeClick(item)"
          @contextmenu.prevent="RightNodeClick(item)"
          v-Rclick
        >
          {{ item.tableNames }}
        </p>
        <div class="modelDiv" ref="modelDiv">
          <p @click="rightLeftClick">设计表</p>
        </div>
      </div>
      <div class="borDiv" v-drag></div>
    </el-aside>
    <el-container style="height: 100vh; overflow: hidden">
      <el-header :height="HeaderWith">
        <div
          class="headerBox"
          :style="{ height: HeaderWith }"
        >
          <div class="flex-between">
            <div>
              <el-button type="primary">搜索</el-button>
            </div>
          </div>

          <div class="headerDiv" v-drag></div>
        </div>
      </el-header>
      <el-main>
        <!-- 右键 设计表 -->
        <div class="mainDiv" >
          
        </div>

    
      </el-main>
    </el-container>
  </el-container>
</template>
<script>
//工作流路径的配置
export default {
  name: "DataVisualizationTable",
  data() {
    return {
      inputVal:'',
      asideWith: "400px", // 左侧栏宽度
      HeaderWith: "200px", // 头部高度
      showLeftBox: true, // 控制左键点击时盒子显示
      data: [{tableNames:'asdasd'}], // 左侧数据
    };
  },
  methods: {
    load(){
      
    },
    async handleNodeClick(data) {
    },
    async rightLeftClick() {
    },
    RightNodeClick(item) {
    },
  },
  directives: {
    drag: {
      // 自定义指令  拖拽修改 左侧宽度
      inserted: function (el, _binding, vnode) {
        let name = el.className;
        let x = 0;
        let y = 0;
        let l = 0;
        let t = 0;
        let isDown = false;
        let that = vnode.context; // 当前vue 实例
        //鼠标按下事件
        el.onmousedown = function (n) {
          //获取x坐标和y坐标
          x = n.clientX;
          y = n.clientY;
          //获取左部和顶部的偏移量
          l = el.offsetLeft;
          t = el.offsetTop;
          //开关打开
          isDown = true;
          //鼠标移动
          window.onmousemove = function (e) {
            if (isDown == false) return;
            e.preventDefault();
            //获取x
            //计算移动后的左偏移量
            let nl = e.clientX - (x - l);
            let nt = e.clientY - (y - t);
            if (name == "borDiv") {
              if (nl > 100) that.asideWith = nl + "px";
            } else {
              if (nt > 87) that.HeaderWith = nt + "px";
            }
          };
          //鼠标抬起事件
          window.onmouseup = function () {
            if (isDown === false) return;
            //开关关闭
            isDown = false;
          };
        };
      },
    },
    Rclick: {
      // 自定义指令  拖拽修改 左侧宽度
      inserted: function (el, _binding, vnode) {
        let that = vnode.context; // 当前vue 实例
        el.oncontextmenu = function (e) {
          e.preventDefault();
          window.oncontextmenu = function (e) {
            let ny = e.clientY;
            let nx = e.clientX;
            that.$refs.modelDiv.style.top = ny + "px";
            that.$refs.modelDiv.style.left = nx + "px";
            that.$refs.modelDiv.style.display = "block";
            window.oncontextmenu = function () {}; // 指定元素使用完后 初始化 window oncontextmenu 事件
          };
          window.onclick = function (e) {
            that.$refs.modelDiv.style.display = "none";
          };
        };
      },
    },
  },
};
</script>
<style lang="scss" scoped>
.el-aside {
  position: relative;
  padding: 10px 5px 0 5px;
  text-align: center;
  height: 100vh;
  overflow: visible;
  ::v-deep .el-input {
    margin: 0 0 10px 0;
    input {
      padding: 0;
    }
  }
  .treeDiv {
    height: 94vh;
    overflow: auto;
    .modelDiv {
      position: absolute;
      width: 100px;
      height: 30px;
      background: rgba(193, 193, 193, 0.8);
      display: none;
      z-index: 9;
      p {
        text-align: center;
        padding: 2px;
      }
    }
    p {
      padding: 5px 0;
      text-align: left;
      &:hover {
        cursor: pointer;
        background: rgba(245, 238, 238, 0.5);
      }
    }

  }
  .borDiv {
    position: absolute;
    top: 0;
    right: 0;
    width: 3px;
    height: 100%;
    background: #333;
    cursor: e-resize;
  }
}
.el-header {
  position: relative;
  overflow: hidden;
  .headerBox {
    height: 200px;
    overflow: auto;
    padding-top: 10px;
    .headerDiv {
      position: absolute;
      bottom: 0;
      right: 0;
      width: 100%;
      height: 3px;
      background: #333;
      cursor: n-resize;
      z-index: 9;
    }
  }
}
.el-main {
  padding: 0 10px;
  .mainDiv {
    margin-top: 10px;
  }
}

.flex {
  width: 60%;
  display: flex;
  justify-content: space-around;
  align-items: end;
  ::v-deep .el-input,
  .el-select {
    margin: 5px 10px 0 0;
  }
  ::v-deep .el-checkbox__inner {
    margin: 5px;
  }
}
.flex-between {
  width: 100%;
  display: flex;
  justify-content: space-between;
}
::v-deep .el-table--border .el-table__cell {
  background: #fff !important;
}
::v-deep .el-table__body-wrapper {
  height: 568px !important;
}
::v-deep .el-table__empty-block {
  justify-content: flex-start;
}
</style>