vue项目中弹窗实现拖拽效果,附手写弹窗

133 阅读1分钟

我正在参加「掘金·启航计划」, 众所周知,elementUI弹窗没有拖拽效果,想要怎么实现拖拽呢?

解决方案:添加自定义指令;

1.自定义指令,创建文件utils/directive.js

import Vue from 'vue'// v-dialogDrag: 弹窗拖拽
Vue.directive('dialogDrag', {
  bind(el, binding, vnode, oldVnode) {
    // const dialogHeaderEl = el.querySelector('.el-dialog__header')
    // const dragDom = el.querySelector('.el-dialog')
    const dialogHeaderEl = el.querySelector('.htitle')
    const dragDom = el.querySelector('.container')
    
    dialogHeaderEl.style.cursor = 'move'
​
    // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
    const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null)
​
    dialogHeaderEl.onmousedown = (e) => {
      // 鼠标按下,计算当前元素距离可视区的距离
      const disX = e.clientX - dialogHeaderEl.offsetLeft
      const disY = e.clientY - dialogHeaderEl.offsetTop
​
      // 获取到的值带px 正则匹配替换
      let styL, styT
​
      // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
      if (sty.left.includes('%')) {
        styL = +document.body.clientWidth * (+sty.left.replace(/%/g, '') / 100)
        styT = +document.body.clientHeight * (+sty.top.replace(/%/g, '') / 100)
      } else {
        styL = +sty.left.replace(/px/g, '')
        styT = +sty.top.replace(/px/g, '')
      }
​
      document.onmousemove = function(e) {
        // 通过事件委托,计算移动的距离
        const l = e.clientX - disX
        const t = e.clientY - disY
​
        // 移动当前元素
        dragDom.style.left = `${l + styL}px`
        dragDom.style.top = `${t + styT}px`
​
        // 将此时的位置传出去
        // binding.value({x:e.pageX,y:e.pageY})
      }
​
      document.onmouseup = function(e) {
        document.onmousemove = null
        document.onmouseup = null
      }
    }
  }
})
​
// v-dialogDragWidth: 弹窗宽度拖大 拖小
Vue.directive('dialogDragWidth', {
  bind(el, binding, vnode, oldVnode) {
console.log(el,binding,111111);
​
    const dragDom = binding.value.$el.querySelector('.el-dialog')
    // const dragDom = el.querySelector('.container') //改造后
​
    el.onmousedown = (e) => {
      // 鼠标按下,计算当前元素距离可视区的距离
      const disX = e.clientX - el.offsetLeft
​
      document.onmousemove = function(e) {
        e.preventDefault() // 移动时禁用默认事件
​
        // 通过事件委托,计算移动的距离
        const l = e.clientX - disX
        dragDom.style.width = `${l}px`
      }
​
      document.onmouseup = function(e) {
        document.onmousemove = null
        document.onmouseup = null
      }
    }
  }
})
​

2.项目main.js文件中全局引入

import './utils/directive' //引入弹窗拖拽指令

3.弹窗组件添加自定义指令

<!-- 弹窗 -->
    <mydialog
      v-dialogDrag
      v-if="isresetPasswordDialog"
      ref="resetPassword"
      :node-data="multipleSelection"
      @toCloseTask="toCloseTask"
      @setMultipleSelection="setMultipleSelection"
    />

这样就可以实现拖拽效果啦!

一般elementUI弹窗的样式想要修改比较麻烦,不想修改我们可以自己写一个弹窗

封装弹窗组件

<template>
  <!-- 重置密码组件 -->
  <div class="mask">
    <div class="container">
      <!-- 标题 -->
      <div class="htitle">
        <h3>重置密码</h3>
        <div class="el-icon-close" @click="toCloseTask" />
      </div>
      <div class="form-container">
        <el-form
          ref="accountFrom"
          :model="formData"
          :rules="rules"
          label-width="100px"
          label-position="top"
        >
          <el-form-item label="已选用户">
            <!-- <div class="selected-title">已选用户</div> -->
            <template slot-scope="scope">
              <div class="selected-users">
                <div
                  v-for="item in formData.userData"
                  :key="item.id"
                  class="selected-item"
                >
                  <span>{{ item.personName }}</span>
                  <span>({{ item.account }})</span>
                  <span class="el-icon-close" @click="delSelectedItem(item)" />
                </div>
              </div>
            </template>
          </el-form-item>
          <el-form-item label="密码" prop="password">
            <el-input
              v-model="formData.password"
              style="width: 90%"
              placeholder="请输入密码,包含数字和英文大小写"
            />
          </el-form-item>
​
          <el-form-item label="确认密码" prop="confirmPassword">
            <el-input
              v-model="formData.confirmPassword"
              style="width: 90%"
              placeholder="请再次输入密码"
            />
          </el-form-item>
        </el-form>
      </div>
      <!-- 提交 取消 -->
      <div class="okCancel">
        <el-button @click="btnCancel">取 消</el-button>
        <el-button
          class="button-background-color"
          type="primary"
          @click="btnOK('ruleForm')"
          >提 交</el-button
        >
      </div>
    </div>
  </div>
</template><script>export default {
​
  props: {
    // 已选行账号信息
    nodeData: {
      type: Array,
      required: true
    }
  },
  data () {
    // 校验两次密码是否一致
    const validatePass = (rule, value, callback) => {
      if (value !== this.formData.password) {
        callback(new Error('两次密码不一致'))
      } else {
        callback()
      }
    }
    return {
      // 已选用户数据
      // userData: [],
      // 表单数据
      formData: {
        // 已选用户数据
        userData: [],
        password: '', // 密码
        confirmPassword: '' // 确认密码
      },
      rules: {
        password: [{ required: true, message: '请输入新密码', trigger: 'blur' },
        { pattern: /^(?:(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])).{6,18}$/, message: '密码应该为6-18位字母、数字组合', trigger: 'blur' }],
        confirmPassword: [{ validator: validatePass, trigger: 'blur' }]
      }
    }
  },
  mounted () {
    this.setUserData()
  },
  methods: {
    // 关闭弹窗
    toCloseTask () {
      this.$emit('toCloseTask')
    },
    // 获取用户数据-回填数据
    setUserData () {
      this.formData.userData = [...this.nodeData]
      // console.log(this.formData.userData, '回填')
    },
    // 新增 点击确定按钮
    btnOK () {
      // 手动校验表单
      this.$refs.accountFrom.validate(async isOK => {
        if (isOK) {
          this.$emit('toCloseTask')
        }
      })
    },
    // 点击取消按钮 关闭弹窗
    btnCancel () {
      // this.userData = [],
      // 重置数据 因为resetFields 只能重置表单上的数据 编辑中id不能重置
      this.formData = {
        userData: [],
        password: '', // 密码
        confirmPassword: '' // 确认密码
      }
      this.$emit('toCloseTask')
      // 清除之前的校验
      this.$refs.accountFrom.resetFields()
    },
    // 删除已选择用户
    delSelectedItem (v) {
      console.log(v, '删除')
      this.formData.userData.forEach((item, index) => {
        if (item.id === v.id) { this.formData.userData.splice(index, 1) }
      })
      this.$emit('setMultipleSelection', [v]) // 取消勾选项
    }
  }
}
</script><style lang="scss" scoped>
.mask {
  position: fixed;
  z-index: 99;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.3);
  .container {
    width: 470px;
    height: 493px;
    // position: fixed;
    // right: 428px;
    // top: 192px;
    position: fixed;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    overflow-y: auto;
​
    background: #fff;
    box-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.15);
    border-radius: 4px 4px 4px 4px;
​
    .htitle {
      width: 100%;
      background-color: #f5f6f8;
      border-radius: 4px 4px 0px 0px;
​
      height: 52px;
      display: flex;
      justify-content: space-between;
      padding: 15px 24px;
      h3 {
        font-size: 16px;
        font-weight: bold;
      }
      .el-icon-close {
        margin-top: 3px;
        font-size: 16px;
        font-weight: bold;
        color: #3372ff;
      }
        //鼠标悬停变小手
      .el-icon-close:hover {
        cursor: pointer;
      }
    }
  }
}
.form-container {
  padding: 16px 80px 0;
  .selected-title {
    margin-bottom: 6px;
    width: 48px;
    height: 17px;
    font-size: 12px;
    font-family: PingFang SC-Medium, PingFang SC;
    font-weight: 500;
    color: #262626;
    line-height: 17px;
  }
  .selected-users {
    width: 310px;
    height: 96px;
    background: #ffffff;
    border-radius: 4px 4px 4px 4px;
    opacity: 1;
    border: 1px solid rgba(187, 187, 187, 0.5);
    padding: 0 20px;
    // display: flex;
    overflow-y: auto;
​
    .selected-item {
      margin: 6px 6px 6px 0;
      padding: 4px 19px 3px 12px;
      // width: 97px;
      height: 24px;
      background: #f2f2f2;
      border-radius: 12px 12px 12px 12px;
      opacity: 1;
      display: inline-block;
      font-size: 12px;
      font-family: PingFang SC-Medium, PingFang SC;
      font-weight: 500;
      color: #262626;
      line-height: 17px;
      .el-icon-close {
        margin-left: 3px;
        width: 8px;
        height: 8px;
        color: #1c69d4;
        opacity: 1;
      }
      .el-icon-close:hover {
        cursor: pointer;
      }
    }
  }
  //表单item样式
  .el-form-item {
    margin: 24px 0 0 0;
    ::v-deep .el-form-item__label {
      height: 17px;
      font-size: 12px;
      font-family: PingFang SC-Medium, PingFang SC !important;
      font-weight: 500;
      color: #262626;
      line-height: 17px;
      width: 100%;
      margin-bottom: 6px;
      padding: 0;
    }
    ::v-deep .el-input__inner {
      width: 310px;
      height: 36px;
      background: #ffffff;
      border-radius: 4px 4px 4px 4px;
      opacity: 1;
      border: 1px solid #d9d9d9;
      font-size: 13px;
      font-family: PingFang SC-Medium, PingFang SC;
      font-weight: 500;
      color: #bbbbbb;
      line-height: 18px;
    }
  }
}
//按钮样式start
.okCancel {
  margin-left: 254px;
  margin-top: 50px;
  width: 192px;
  height: 36px;
}
.el-button {
  width: 90px;
  height: 36px;
  padding: 9px 16px;
}
.button-background-color {
  background-color: #3372ff;
  margin-left: 12px;
}
//按钮样式end
</style>
​
​

主要关注结构和样式的代码:

结构:

 <div class="mask">
    <div class="container">
      <!-- 标题 -->
      <div class="htitle">
        <h3>重置密码</h3>
        <div class="el-icon-close" @click="toCloseTask" />
      </div>
      <div class="form-container">
           </div>
      <!-- 提交 取消 -->
      <div class="okCancel">
        <el-button @click="btnCancel">取 消</el-button>
        <el-button
          class="button-background-color"
          type="primary"
          @click="btnOK('ruleForm')"
          >提 交</el-button
        >
      </div>
    </div>
  </div>
  </div>

样式上面代码自行复制。