美文网首页
vue3高仿el-dialog弹窗组件

vue3高仿el-dialog弹窗组件

作者: 硅谷干货 | 来源:发表于2021-11-23 20:26 被阅读0次

上一篇文章写了一版简化版自定义弹窗组件,已经满足绝大部分场景,但是当弹窗内容是个长列表,就需要鼠标滚动,才能看到全部内容,这种场景就支持的不太完美,于是就仿照 el-dialog 弹窗,再写了一版功能较多,支持更多复杂场景的弹窗,同学们按需拷贝即可。

创建MyDialog.vue 文件

<template>
<transition name="dialog">
<div class='modal-wrapper' ref='dialog-wrapper' v-show='visibleDialog' @click.self='handleWrapperClick' @mousewheel.prevent>
  <div ref='dialog' :class="['default-dialog',customClass]" v-if='dialogRender' @mousewheel.passive.stop>
    <!-- 对话框主体 -->
    <div class='modal-body'>
      <button type="button" class="close-btn" aria-label='close' v-if='displayClose' @click='handleClose'></button>
      <!-- 默认插槽 -->
      <slot></slot>
    </div>
  </div>
</div>
</transition>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';

export default defineComponent({
  name: 'MapDialog',
  setup() {
    return {
      visibleDialog: ref(false),
      dialogRender: ref(false),
    }
  },
  props: {
    visible: {
      type: Boolean,
      default: false
    },
    title: {
      type: String,
      default: ''
    },
    // 关闭弹窗前的回调(接收一个参数 done())
    beforeClose: Function,
    // 是否需要遮罩层
    modal: {
      type: Boolean,
      default: true
    },
    // 是否在 Dialog 出现时将 body 滚动锁定
    lockScroll: {
      type: Boolean,
      default: true
    },
    // 是否可以通过点击 modal 关闭 Dialog
    closeOnClickModal: {
      type: [Boolean,String],
      default: () => false
    },
    // 是否显示右上角关闭按钮
    displayClose: {
      type: Boolean,
      default: true
    },
    // 最大宽高
    width: String,
    height: String,
    top: {
      type: String,
      default: '5vh'
    },
    // 主题颜色 - 高亮(默认) | 夜间
    dark: {
      type: Boolean,
      default: false
    },
    // 自定义类
    customClass: {
      type: String,
      default: ''
    }
  },
  watch: {
    visible(newVal) {
      console.log('newVal:',newVal)
      if (newVal) {
        this.visibleDialog = true
        this.dialogRender = true

        // 依据props修改样式
        this.changeDialogStyle()
        //打开dialog回调
        this.$emit('open')
      } else {
        this.visibleDialog = false
        this.dialogRender = false
        document.body.style['overflow'] = 'auto'
        //关闭dialog回调
        this.$emit('close')
      }
    }
  },
  methods: {
    handleWrapperClick() {
      if (typeof this.closeOnClickModal == 'boolean' && this.closeOnClickModal) {
        this.handleClose()
      }
    },
    // 处理关闭对话框,若存在beforeClose则调用
    handleClose() {
      if (typeof this.beforeClose === 'function') {
        this.beforeClose(this.hide)
      } else {
        this.hide()
      }
    },
    hide() {
      this.$emit('update:visible', false);
    },
    // 根据Props值修改Dialog样式
    changeDialogStyle() {
      // lockScroll - 实现底层禁止滚动
      if (this.lockScroll) document.body.style['overflow'] = 'hidden'
      var that = this
      this.$nextTick(() => {
        var dialogWrapperStyle = that.$refs['dialog-wrapper'].style
        var dialogStyle = that.$refs.dialog.style
        if (that.width) dialogStyle.width = that.width 
        if (that.height) dialogStyle.height = that.height
        // if (that.top) dialogStyle.marginTop = that.top
        // 实现无遮罩层
        if (!that.modal) dialogWrapperStyle.background = 'transparent'
      })
    }
  }
})
</script>

<style lang="less" scoped>

.modal-wrapper {
    position: fixed;
    top: 0;
    bottom: 0;
    right: 0;
    left: 0;
    background: rgba(0, 0, 0, 0.6);
    z-index: 100;

    // 默认样式
    .default-dialog {
      position: absolute;
      border-radius: 5px;
      color: #303952;
      padding: 10px;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      display: flex;
      flex-direction: column;
      justify-content: space-between;
      min-width: 200px;
      min-height: 100px;

      .modal-body {
        height: 100%;
        position: relative;
        .close-btn {
          position: absolute;
          right: 10px;
          top: 10px;
          height: 50px;
          width: 50px;
          background-color: transparent;
          border-color: transparent;
          padding: 0;
          outline: none;
          cursor: pointer;
          text-align: center;
          z-index: 1;
          font-size:0px;
          display:block;
          box-sizing: content-box;
          &::before , &::after {
            content:'';
            width:50px;
            height:4px;
            background: gray;
            display:block;
            transition: all .25s ease-in-out;
          }
          &::before {
            transform: rotate(45deg);
          }
          &::after {
            transform:translateY(-4px) rotate(-45deg);
          }
          &:hover::before , &:hover::after {
            background: #0a59f7;
          }
        }
      }
    }
}

/* 过渡动画 */
.dialog-enter-active,
.dialog-leave-active {
  transition: all 0.25s ease;
}
.dialog-enter-from,
.dialog-leave-to {
  opacity: 0;
}
</style>

如果觉得vue3中这样写不够优雅,接下来我还准备了一版script setup语法糖的写法。

vue3.2版本中的写法

<template>
  <transition name="dialog">
    <div
      class="modal-wrapper"
      ref="dialogWrapperRef"
      v-show="visibleDialog"
      @click.self="handleWrapperClick"
      @mousewheel.prevent.stop
    >
      <div
        ref="dialogRef"
        :class="['default-dialog', customClass]"
        v-if="dialogRender"
        @mousewheel.passive.stop
      >
        <!-- 对话框主体 -->
        <div class="modal-body">
          <button
            type="button"
            class="close-btn"
            aria-label="close"
            v-if="displayClose"
            @click="handleClose"
          ></button>
          <!-- 默认插槽 -->
          <slot></slot>
        </div>
      </div>
    </div>
  </transition>
</template>

<script setup lang="ts">
import { nextTick, watch, ref } from 'vue';

const visibleDialog = ref(false);
const dialogRender = ref(false);
const dialogWrapperRef = ref<HTMLElement | null>(null);
const dialogRef = ref<HTMLElement | null>(null);

// 采用定义props接口
interface Props {
  visible?: boolean;
  title?: string;
  // 关闭弹窗前的回调(接收一个参数 done())
  beforeClose?: () => {};
  modal?: boolean;
  lockScroll?: boolean;
  closeOnClickModal?: boolean | string;
  displayClose?: boolean;
  // 最大宽高
  width?: string;
  height?: string;
  top?: string;
  dark?: boolean;
  customClass?: string;
}

// 采用ts专有声明,有默认值
const props = withDefaults(defineProps<Props>(), {
  visible: false,
  title: '',
  // 是否需要遮罩层
  modal: true,
  // 是否在 Dialog 出现时将 body 滚动锁定
  lockScroll: true,
  // 是否可以通过点击 modal 关闭 Dialog
  closeOnClickModal: false,
  // 是否显示右上角关闭按钮
  displayClose: true,
  top: '5vh',
  // 主题颜色 - 高亮(默认) | 夜间
  dark: false,
  // 自定义类
  customClass: ''
});

// 解构props
const {
  visible,
  title,
  beforeClose,
  modal,
  lockScroll,
  closeOnClickModal,
  displayClose,
  width,
  height,
  top,
  dark,
  customClass,
} = props;

// 定义emit方法名称
const emit = defineEmits(['close', 'open', 'update:visible']);

// 监听visible改变
watch(
  () => props.visible,
  (newVal) => {
    if (newVal) {
      visibleDialog.value = true;
      dialogRender.value = true;
      // 依据props修改样式
      changeDialogStyle();
      emit('open');
    } else {
      visibleDialog.value = false;
      dialogRender.value = false;
      emit('close');
    }
  }
);

const handleWrapperClick = () => {
  if (typeof closeOnClickModal == 'boolean' && closeOnClickModal) {
    handleClose();
  }
};

// 处理关闭对话框,若存在beforeClose则调用
const handleClose = () => {
  if (typeof beforeClose === 'function') {
    beforeClose(updateVisible);
  } else {
    updateVisible();
  }
};

// 通知更新visible = false
const updateVisible = () => {
  emit('update:visible', false);
};

// 根据Props值修改Dialog样式
const changeDialogStyle = async () => {
  // lockScroll - 实现底层禁止滚动
  // if (this.lockScroll) {
  //   document.body.style['overflow'] = 'hidden'
  // }
  await nextTick(()=> {
    const dialogWrapperStyle = dialogWrapperRef.value.style;
    const dialogStyle = dialogRef.value.style;
    if (width) {
      dialogStyle.width = width;
    }
    if (height) {
      dialogStyle.height = height;
    }
    // if (top) {
    //   dialogStyle.marginTop = that.top
    // }
    // 实现无遮罩层
    if (!modal) {
      dialogWrapperStyle.background = 'transparent';
    }
  });
};
</script>

<style lang="less" scoped>
.modal-wrapper {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  left: 0;
  background: rgba(0, 0, 0, 0.6);
  z-index: 100;

  // 默认样式
  .default-dialog {
    position: absolute;
    border-radius: 5px;
    color: #303952;
    padding: 10px;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    min-width: 200px;
    min-height: 100px;

    .modal-body {
      height: 100%;
      position: relative;
      .close-btn {
        position: absolute;
        right: 10px;
        top: 10px;
        height: 40px;
        width: 40px;
        background-color: transparent;
        border-color: transparent;
        padding: 0;
        outline: none;
        cursor: pointer;
        text-align: center;
        z-index: 1;
        font-size: 0px;
        display: block;
        box-sizing: content-box;
        &::before,
        &::after {
          content: '';
          width: 40px;
          height: 2px;
          background: gray;
          display: block;
          transition: all 0.25s ease-in-out;
        }
        &::before {
          transform: rotate(45deg);
        }
        &::after {
          transform: translateY(-2px) rotate(-45deg);
        }
        &:hover::before,
        &:hover::after {
          background: #0a59f7;
        }
      }
    }
  }
}

/* 命名过渡动画 */
.dialog-enter-active,
.dialog-leave-active {
  transition: all 0.3s ease;
}
.dialog-enter-from,
.dialog-leave-to {
  opacity: 0;
}
</style>

上一篇文章:Vue3自定义弹窗组件

点赞加关注,永远不迷路。

相关文章

网友评论

      本文标题:vue3高仿el-dialog弹窗组件

      本文链接:https://www.haomeiwen.com/subject/bklwtrtx.html