You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
247 lines
5.8 KiB
Vue
247 lines
5.8 KiB
Vue
<template>
|
|
<wd-overlay
|
|
v-if="modal"
|
|
:show="modelValue"
|
|
:z-index="zIndex"
|
|
:lock-scroll="lockScroll"
|
|
:duration="duration"
|
|
:custom-style="modalStyle"
|
|
@click="handleClickModal"
|
|
@touchmove="noop"
|
|
/>
|
|
<view v-if="!lazyRender || inited" :class="rootClass" :style="style" @transitionend="onTransitionEnd">
|
|
<slot />
|
|
<wd-icon v-if="closable" custom-class="wd-popup__close" name="add" @click="close" />
|
|
</view>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
export default {
|
|
name: 'wd-popup',
|
|
options: {
|
|
virtualHost: true,
|
|
addGlobalClass: true,
|
|
styleIsolation: 'shared'
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<script lang="ts" setup>
|
|
import { computed, onBeforeMount, ref, watch } from 'vue'
|
|
import { isObj, requestAnimationFrame } from '../common/util'
|
|
import type { PopupType } from './type'
|
|
|
|
interface Props {
|
|
transition?: string
|
|
closable?: boolean
|
|
position?: PopupType
|
|
closeOnClickModal?: boolean
|
|
duration?: number | boolean
|
|
modal?: boolean
|
|
zIndex?: number
|
|
hideWhenClose?: boolean
|
|
modalStyle?: string
|
|
safeAreaInsetBottom?: boolean
|
|
modelValue: boolean
|
|
customStyle?: string
|
|
lazyRender?: boolean
|
|
lockScroll?: boolean
|
|
customClass?: string
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
customClass: '',
|
|
customStyle: '',
|
|
modalStyle: '',
|
|
position: 'center',
|
|
closeOnClickModal: true,
|
|
modal: true,
|
|
closable: false,
|
|
duration: 300,
|
|
zIndex: 10,
|
|
hideWhenClose: true,
|
|
lazyRender: true,
|
|
lockScroll: true,
|
|
safeAreaInsetBottom: false,
|
|
modelValue: false
|
|
})
|
|
|
|
const getClassNames = (name) => {
|
|
if (!name) {
|
|
return {
|
|
enter: 'enter-class enter-active-class',
|
|
'enter-to': 'enter-to-class enter-active-class',
|
|
leave: 'leave-class leave-active-class',
|
|
'leave-to': 'leave-to-class leave-active-class'
|
|
}
|
|
}
|
|
|
|
return {
|
|
enter: `wd-${name}-enter wd-${name}-enter-active`,
|
|
'enter-to': `wd-${name}-enter-to wd-${name}-enter-active`,
|
|
leave: `wd-${name}-leave wd-${name}-leave-active`,
|
|
'leave-to': `wd-${name}-leave-to wd-${name}-leave-active`
|
|
}
|
|
}
|
|
|
|
// 初始化是否完成
|
|
const inited = ref<boolean>(false)
|
|
// 是否显示
|
|
const display = ref<boolean>(false)
|
|
// 当前动画状态
|
|
const status = ref<string>('')
|
|
// 动画是否结束
|
|
const transitionEnded = ref<boolean>(false)
|
|
// 动画持续时间
|
|
const currentDuration = ref<number>(300)
|
|
// 类名
|
|
const classes = ref<string>('')
|
|
|
|
const safeBottom = ref<number>(0)
|
|
|
|
const name = ref<string>('') // 动画名
|
|
|
|
const emit = defineEmits([
|
|
'update:modelValue',
|
|
'before-enter',
|
|
'enter',
|
|
'before-leave',
|
|
'leave',
|
|
'after-leave',
|
|
'after-enter',
|
|
'click-modal',
|
|
'close'
|
|
])
|
|
|
|
const style = computed(() => {
|
|
return `z-index: ${props.zIndex}; padding-bottom: ${safeBottom.value}px; -webkit-transition-duration: ${
|
|
currentDuration.value
|
|
}ms; transition-duration: ${currentDuration.value}ms; ${display.value || !props.hideWhenClose ? '' : 'display: none;'} ${props.customStyle}`
|
|
})
|
|
|
|
const rootClass = computed(() => {
|
|
return `wd-popup wd-popup--${props.position} ${props.customClass || ''} ${classes.value || ''}`
|
|
})
|
|
|
|
onBeforeMount(() => {
|
|
observerTransition()
|
|
if (props.safeAreaInsetBottom) {
|
|
const { safeArea, screenHeight, safeAreaInsets } = uni.getSystemInfoSync()
|
|
|
|
if (safeArea) {
|
|
// #ifdef MP-WEIXIN
|
|
safeBottom.value = screenHeight - (safeArea!.bottom || 0)
|
|
// #endif
|
|
// #ifndef MP-WEIXIN
|
|
safeBottom.value = safeAreaInsets ? safeAreaInsets.bottom : 0
|
|
// #endif
|
|
} else {
|
|
safeBottom.value = 0
|
|
}
|
|
}
|
|
if (props.modelValue) {
|
|
enter()
|
|
}
|
|
})
|
|
|
|
watch(
|
|
() => props.modelValue,
|
|
(newVal) => {
|
|
observermodelValue(newVal)
|
|
},
|
|
{ deep: true, immediate: true }
|
|
)
|
|
|
|
watch(
|
|
[() => props.position, () => props.transition],
|
|
() => {
|
|
observerTransition()
|
|
},
|
|
{ deep: true, immediate: true }
|
|
)
|
|
|
|
function observermodelValue(value: boolean) {
|
|
value ? enter() : leave()
|
|
}
|
|
|
|
function enter() {
|
|
const classNames = getClassNames(props.transition || props.position)
|
|
const duration = props.transition === 'none' ? 0 : isObj(props.duration) ? (props.duration as any).enter : props.duration
|
|
status.value = 'enter'
|
|
emit('before-enter')
|
|
|
|
requestAnimationFrame(() => {
|
|
emit('enter')
|
|
classes.value = classNames.enter
|
|
currentDuration.value = duration
|
|
requestAnimationFrame(() => {
|
|
inited.value = true
|
|
display.value = true
|
|
requestAnimationFrame(() => {
|
|
transitionEnded.value = false
|
|
classes.value = classNames['enter-to']
|
|
})
|
|
})
|
|
})
|
|
}
|
|
function leave() {
|
|
if (!display.value) return
|
|
const classNames = getClassNames(props.transition || props.position)
|
|
const duration = props.transition === 'none' ? 0 : isObj(props.duration) ? (props.duration as any).leave : props.duration
|
|
status.value = 'leave'
|
|
emit('before-leave')
|
|
|
|
requestAnimationFrame(() => {
|
|
emit('leave')
|
|
classes.value = classNames.leave
|
|
currentDuration.value = duration
|
|
|
|
requestAnimationFrame(() => {
|
|
transitionEnded.value = false
|
|
const timer = setTimeout(() => {
|
|
onTransitionEnd()
|
|
clearTimeout(timer)
|
|
}, currentDuration.value)
|
|
classes.value = classNames['leave-to']
|
|
})
|
|
})
|
|
}
|
|
|
|
function onTransitionEnd() {
|
|
if (transitionEnded.value) return
|
|
|
|
transitionEnded.value = true
|
|
if (status.value === 'leave') {
|
|
// 离开后触发
|
|
emit('after-leave')
|
|
} else if (status.value === 'enter') {
|
|
// 进入后触发
|
|
emit('after-enter')
|
|
}
|
|
if (!props.modelValue && display.value) {
|
|
display.value = false
|
|
}
|
|
}
|
|
|
|
function observerTransition() {
|
|
const { transition, position } = props
|
|
name.value = transition || position
|
|
}
|
|
|
|
function handleClickModal() {
|
|
emit('click-modal')
|
|
if (props.closeOnClickModal) {
|
|
close()
|
|
}
|
|
}
|
|
|
|
function close() {
|
|
emit('close')
|
|
emit('update:modelValue', false)
|
|
}
|
|
function noop() {}
|
|
</script>
|
|
<style lang="scss" scoped>
|
|
@import './index.scss';
|
|
</style>
|