上一篇文章写了一版简化版自定义弹窗组件,已经满足绝大部分场景,但是当弹窗内容是个长列表,就需要鼠标滚动,才能看到全部内容,这种场景就支持的不太完美,于是就仿照
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自定义弹窗组件
点赞加关注,永远不迷路。
网友评论