Files
zsw-jx-store/src/components/jx-popup/jx-popup.vue
2025-11-13 10:16:36 +08:00

233 lines
4.3 KiB
Vue

<template>
<view
class="jx-popup"
v-show="hideen"
:class="{ popupAnimation: isPopup }"
@tap="maskClice"
@touchmove.prevent
>
<!-- jx-popup 内容 -->
<view class="popup-center" :class="[type, popupActive]" @tap.stop>
<slot></slot>
</view>
</view>
</template>
<script lang="ts" setup>
/**
* jx-popup 组件
* @param (open) 打开popup
* @param (close) 关闭popup
* @emit (mask) mask 触发事件
* @param (isMask) 是否允许点击 mask 关闭
* @param (type) popup 打开方式
*/
import { ref, computed } from 'vue'
/**
* props
*/
interface PropType {
isMask?: boolean
time?: number
type: string
}
const props = withDefaults(defineProps<PropType>(), {
isMask: false,
time: 300,
})
/**
* emit
*/
const emit = defineEmits<{
(e: 'mask'): void
}>()
/**
* 打开popup
*/
// mask 动画
const isPopup = ref<boolean>(false)
// 动画 class
const popupActive = ref<string>('')
// 使用 v-show 避免重新渲染组件
const hideen = ref<boolean>(false)
let openTimer: any = null
function open() {
let ruler = ['top', 'right', 'bottom', 'left', 'center']
if (!ruler.includes(props.type)) {
return console.error(
'this is param need is top or right or bottom or left or center'
)
}
// 先显示了在执行动画
hideen.value = true
// 执行动画
clearTimeout(openTimer)
openTimer = setTimeout(() => {
isPopup.value = true
switch (props.type) {
case 'top':
popupActive.value = 'topAnimation'
break
case 'right':
popupActive.value = 'rightAnimation'
break
case 'bottom':
popupActive.value = 'bottomAnimation'
break
case 'left':
popupActive.value = 'leftAnimation'
break
case 'center':
popupActive.value = 'centerAnimation'
break
}
// 20 兼容 app 渲染过快
}, 20)
}
/**
* 关闭popup
*/
let closeTimer: any = null
function close(fun?: Function) {
isPopup.value = false
popupActive.value = ''
clearTimeout(closeTimer)
closeTimer = setTimeout(() => {
hideen.value = false
fun && fun()
}, props.time)
}
/**
* 点击 mask 是否允许关闭
*/
let maskTimer: any = null
function maskClice() {
if (props.isMask) return
// 遮罩层事件
emit('mask')
isPopup.value = false
popupActive.value = ''
// 动画执行完了在隐藏
clearTimeout(maskTimer)
maskTimer = setTimeout(() => {
hideen.value = false
}, props.time)
}
/**
* 暴露方法提供父组件调用
*/
defineExpose({
open, // 打开 popup
close, // 关闭 popup
})
const transitionTImer = computed(() => {
return `${props.time / 1000}s`
})
</script>
<style lang="scss" scoped>
.jx-popup {
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, 0.4);
opacity: 0;
transition: all v-bind(transitionTImer);
.popup-center {
transition: all v-bind(transitionTImer);
}
.top {
position: absolute;
top: 0;
width: 100%;
transform: translateY(-100%);
}
.right {
position: absolute;
right: 0;
height: 100%;
overflow: auto;
transform: translateX(100%);
}
.bottom {
position: absolute;
bottom: 0;
width: 100%;
transform: translateY(100%);
}
.left {
position: absolute;
left: 0;
height: 100%;
overflow: auto;
transform: translateX(-100%);
}
.center {
position: absolute;
width: auto;
height: auto;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0);
animation: centerScale v-bind(transitionTImer);
}
}
// mask 遮罩层
.popupAnimation {
z-index: 999999999999;
opacity: 1;
}
// top 顶部弹出
.topAnimation {
transform: translateY(0%) !important;
}
// right 右侧弹出
.rightAnimation {
transform: translateX(0%) !important;
}
// bottom 底部弹出
.bottomAnimation {
transform: translateY(0%) !important;
}
// left 左侧弹出
.leftAnimation {
transform: translateX(0%) !important;
}
// 居中弹出
.centerAnimation {
transform: translate(-50%, -50%) scale(1) !important;
}
@keyframes centerScale {
0% {
transform: translate(-50%, -50%) scale(0);
}
85% {
transform: translate(-50%, -50%) scale(1.02);
}
100% {
transform: translate(-50%, -50%) scale(1);
}
}
</style>