货无忧
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

305 lines
6.8 KiB

<template>
<template v-if="showTrans">
<hd-overlay
v-if="overlay"
:zIndex="zIndex - 1"
:show="show"
@click="overlayClick"
:duration="transDuration"
:customStyle="maskStyle"
:opacity="overlayOpacity"
></hd-overlay>
<hd-transition
:destroy="destroy"
:name="position"
:customStyle="contentStyle"
:duration="transDuration"
:show="show"
@click="clickHandler"
@after-leave="onAfterLeave"
>
<view :class="['hd-popup', `hd-popup-${type}`]" :style="{ backgroundColor: bg }" @tap.stop="noop">
<slot />
</view>
</hd-transition>
</template>
</template>
<script lang="ts" setup>
import { computed, inject, nextTick, ref, watch } from 'vue'
import { CommonUtil, popupDefaultKey } from '../../index'
/**
* PopUp 弹出层
* @description 弹出层组件,为了解决遮罩弹层的问题
* @property {String} type = [top|center|bottom|left|right] 弹出方式
* @value top 顶部弹出
* @value center 中间弹出
* @value bottom 底部弹出
* @value left 左侧弹出
* @value right 右侧弹出
* @property {Boolean} maskClick = [ture|false] 蒙版点击是否关闭弹窗
* @property {Boolean} animation = [ture|false] 是否开启动画
* @property {Boolean} overlay = [ture|false] 是否开启遮罩
* @property {Number} duration 动画时长,单位ms
* @property {String} backgroundColor 主窗口背景色
* @property {Number} overlayOpacity 遮罩的透明度,0-1之间
* @event {Function} change 打开关闭弹窗触发,e={show: false}
* @event {Function} maskClick 点击遮罩触发
*/
type PopupType = 'top' | 'bottom' | 'left' | 'right' | 'center'
interface Props {
// 开启动画
animation?: boolean
// 弹出层类型
// `'top( 顶部弹出层)'` / `'bottom(底部弹出层)'` / `'left(左侧弹出层)'`/ `'right(右侧弹出层)'`/ `'center(全屏弹出层)'`
type?: PopupType
// 是否销毁子节点
destroy?: boolean
// 是否显示遮罩
overlay?: boolean
// 是否允许点击蒙层关闭
maskClick?: boolean
// 背景颜色
backgroundColor?: string
// 动画时长,单位ms
duration?: number
// 遮罩的透明度,0-1之间
overlayOpacity?: number
// 自定义层级
zIndex?: number
// popup唯一标识
id?: string
}
const props = withDefaults(defineProps<Props>(), {
// 开启动画
animation: true,
// 弹出层类型
type: 'center',
// 是否销毁子节点
destroy: false,
// 是否显示遮罩
overlay: true,
// 是否允许点击蒙层关闭
maskClick: true,
// 背景颜色
backgroundColor: 'none',
// 动画时长,单位ms
duration: 300,
// 遮罩的透明度,0-1之间
overlayOpacity: 0.4,
// 自定义层级
zIndex: 999,
// popup唯一标识
id: ''
})
const emit = defineEmits(['change', 'maskClick', 'close', 'click', 'transitionEnd'])
const popupKey = props.id ? '__POPUP_' + props.id : popupDefaultKey
const popupShow = inject(popupKey) || ref<boolean>(false) // 是否展示popup组件
// 是否展示popup
const show = ref<boolean>(false)
// 是否展示动画
const showTrans = ref<boolean>(true)
// 定时器
const timer = ref<any>(null)
// 监听函数式调用是否展示弹出框
watch(
() => popupShow.value,
(newVal: boolean) => {
if (newVal) {
open()
} else {
close()
}
}
)
const bg = computed(() => {
if (props.backgroundColor === '' || props.backgroundColor === 'none') {
return 'transparent'
}
return props.backgroundColor
})
// 遮罩样式
const maskStyle = computed(() => {
const maskStyle = {
position: 'fixed',
bottom: 0,
top: 0,
left: 0,
right: 0,
'z-index': props.zIndex ? props.zIndex - 1 : 998
}
return CommonUtil.style(maskStyle)
})
// 内容样式
const contentStyle = computed(() => {
const style = {
zIndex: props.zIndex ? props.zIndex : 998,
position: 'fixed',
display: 'flex',
'background-color': bg.value
}
style[props.type] = 0
if (props.type === 'left') {
style['bottom'] = 0
style['top'] = 0
} else if (props.type === 'right') {
style['bottom'] = 0
style['top'] = 0
} else if (props.type === 'top') {
style['left'] = 0
style['right'] = 0
} else if (props.type === 'bottom') {
style['left'] = 0
style['right'] = 0
} else if (props.type === 'center') {
style['align-items'] = 'center'
style['justify-content'] = 'center'
style['top'] = 0
style['left'] = 0
style['right'] = 0
style['bottom'] = 0
}
return CommonUtil.style(style)
})
/**
* 弹出位置
*/
const position = computed(() => {
if (props.type === 'center') {
return ['zoom-out', 'fade']
}
if (props.type === 'left') {
return 'slide-left'
}
if (props.type === 'right') {
return 'slide-right'
}
if (props.type === 'bottom') {
return 'slide-up'
}
if (props.type === 'top') {
return 'slide-down'
}
return 'slide-up'
})
/**
* 过渡动画时长
*/
const transDuration = computed(() => {
if (props.animation === false) {
return 0
} else {
return props.duration
}
})
/**
* 过渡动画结束
*/
function onAfterLeave() {
emit('transitionEnd')
showTrans.value = false
nextTick(() => {
showTrans.value = true
})
}
function open(direction?: 'top' | 'center' | 'bottom' | 'left' | 'right') {
// timer.value && clearTimeout(timer.value)
const innerType = ['top', 'center', 'bottom', 'left', 'right']
if (!(direction && innerType.indexOf(direction) !== -1)) {
direction = props.type
}
if (show.value) {
show.value = false
nextTick(() => {
show.value = true
})
} else {
show.value = true
}
showTrans.value = true
emit('change', {
show: true,
type: direction
})
}
/**
* 关闭
*/
function close() {
show.value = false
/**
* 弹层状态变更
* @arg show:Boolean 弹层状态
* @arg type:String 弹层类型
*/
emit('change', {
show: false,
type: props.type
})
/**
* 弹层关闭时触发
*/
emit('close')
}
function overlayClick() {
/**
* 遮罩被点击时触发
*/
emit('maskClick')
if (!props.maskClick) return
close()
}
function clickHandler() {
if (props.type === 'center') {
overlayClick()
}
emit('click')
}
// 空操作
function noop(event) {
event && typeof event.stopPropagation === 'function' && event.stopPropagation()
}
</script>
<style lang="scss" scoped>
.hd-popup {
width: 100vw;
position: relative;
box-sizing: border-box;
max-height: 100%;
overflow-y: auto;
// position: fixed;
&-center {
display: inline-flex;
width: auto;
flex: 0 0 auto;
justify-content: center;
}
&-left {
width: auto;
}
&-right {
width: auto;
}
&-top {
/* #ifdef H5 */
padding-top: var(--window-top);
/* #endif */
/* #ifndef H5 */
padding-top: 0;
/* #endif */
}
}
</style>