This commit is contained in:
2026-09-24 16:25:22 +08:00
commit 7428184f01
1198 changed files with 314515 additions and 0 deletions
@@ -0,0 +1,591 @@
<script lang="ts" setup>
import { getCurrentInstance, ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from "vue"
import { getUid } from '../../core/util/xCoreUtil.uts'
import { xConfig,xProvitae } from "../../config/xConfig.uts"
import { xAnimate } from "../../core/util/xAnimate.uts"
// #ifdef APP||WEB
import { xTween } from "@/uni_modules/tmx-ui/index.uts"
import { XANIMATE_OPIONS, xTweenAnimate, xTweenCallbackFunType, xTweenEventCallFunType } from "@/uni_modules/tmx-ui/interface.uts"
// #endif
/**
* @name 遮罩 xOverlay
* @description 旋转制作弹层类页面。
* @page /pages/index/overlay
* @category 反馈组件
* @constant 平台兼容
* | Harmony | H5 | andriod | IOS | 小程序 | UTS | UNIAPP-X SDK | version |
| --- | --- | --- | --- | --- | --- | --- | --- |
| ☑ | ☑ | ☑️ | ☑️ | ☑️ | ☑️ | 4.76+ | 1.1.18 |
*/
defineOptions({name:"xOverlay"})
const proxy = getCurrentInstance()?.proxy??null;
defineSlots<{
trigger(props: { show: boolean }): any
}>()
const emits = defineEmits([
/**
* 点击遮罩事件
*/
'click',
/**
* 关闭是触发
*/
'close',
/**
* 打开时触发
*/
'open',
/**
* 打开前执行
*/
'beforeOpen',
/**
* 关闭前执行
*/
'beforeClose',
/**
* 等同v-model:show
*/
'update:show',
])
export type xOverlayPropsType = {
/**
* 自定义遮罩样式
*/
customStyle: string,
/**
* 内容层样式
*/
customContentStyle: string,
/**
* 是否显示底部关闭按钮
*/
showClose: boolean,
/**
* 遮罩是否允许点击被关闭
*/
overlayClick: boolean,
/**
* 禁用弹跳动画,overlayClick设置为false时,点底部会有弹跳.
*/
disabledAni: boolean,
/**
* 显示可v-model:show双向绑定
*/
show: boolean,
/**
* 动画时间
*/
duration: number,
/**
* 打开dom的延迟量,如果你打开 弹窗在ios正常。
* 请不要修改此值。如果遇到打不开,或者 打开 后没动画,关闭不了等可能是sdk bug导致
* 此时需要加大值来避免。具体加多少以你弹窗内的节点复杂度有关,需要你自行压力测试。
* 此值仅在ios下生效。
*/
watiDuration: number,
/**
* 层级
*/
zIndex: number
}
const props = withDefaults(defineProps<xOverlayPropsType>(), {
customStyle: "",
customContentStyle: "",
showClose: false,
overlayClick: true,
disabledAni: false,
show: false,
duration: 300,
watiDuration: 120,
zIndex: 1100
})
// 响应式数据
const _width = ref(0)
const _height = ref(0)
const showOverflay = ref(false)
const xoverflayRef = ref<UniElement | null>(null)
const xoverflayWrapBoxRef = ref<UniElement | null>(null)
//是否动画中
const actioning = ref(false)
const status = ref("")
const id = ref("xoverflay" + getUid())
const tid = ref(0)
const windtop = ref(0)
const isOpenedDefault = ref(false)
const xani = ref<xAnimate | null>(null)
// #ifdef H5
const teleportElH5 = ref("uni-page")
const teleportTarget = ref<string | null>(null)
const getTeleportTarget = () => {
try {
if(status.value == ''||status.value=='close') return 'uni-app'
// 优先尝试 uni-page
if (document.querySelector('uni-page')) {
return 'uni-page'
}
// 优先尝试 uni-app
if (document.querySelector('uni-app')) {
return 'uni-app'
}
// 备用方案:尝试 app
if (document.querySelector('#app')) {
return '#app'
}
// 最后备用:body
return 'body'
} catch (error) {
console.warn('Failed to get teleport target:', error)
return 'body'
}
}
// 检查teleport目标是否可用
const isTeleportTargetValid = (target: string) => {
try {
return !!document.querySelector(target)
} catch {
return false
}
}
// #endif
// #ifdef APP||WEB
const tw = ref(new xTween())
// #endif
// 计算属性
const _customStyle = computed((): string => props.customStyle)
const _customContentStyle = computed((): string => props.customContentStyle)
const _show = computed((): boolean => props.show)
const _showClose = computed((): boolean => props.showClose)
const _duration = computed((): number => props.duration)
const _animationFun = computed((): string => xConfig.animationFun)
const __height = computed((): string => {
let h = '100%';
// #ifdef WEB
h = `calc(100% - ${windtop.value}px)`
// #endif
return h;
})
// 方法
function overflayMoveTouch(evt : UniTouchEvent) {
evt.preventDefault()
}
function closeAlert() {
// #ifdef APP||WEB
tw.value.startRender()
// #endif
if (actioning.value) return;
if (status.value == 'close') return;
actioning.value = true;
status.value = 'close'
/**
* 关闭前执行
*/
emits('beforeClose')
// #ifdef APP||WEB
let element = xoverflayRef.value as UniElement
let elementBody = xoverflayWrapBoxRef.value! as UniElement
tw.value.stop()
tw.value.removeAnimate()
// tmxEase
tw.value
.addAnimate({
duration: _duration.value,
ease: 'linear',
enter: (item : xTweenEventCallFunType) => {
element.style.setProperty('background-color', `rgba(0,0,0,${Math.max(0, ((1-item.progress)*0.4))})`)
},
complete: (item : xTweenEventCallFunType) => {
element.style.setProperty('background-color', `rgba(0,0,0,0)`)
}
} as xTweenAnimate
)
tw.value
.addAnimate({
duration: _duration.value,
ease: 'tmxEase',
enter: (item : xTweenEventCallFunType) => {
elementBody.style.setProperty('opacity', `${1-item.progress}`)
elementBody.style.setProperty('transform', `scale(${1-item.progress})`)
},
complete: (item : xTweenEventCallFunType) => {
actioning.value = false;
/**
* 关闭时执行
*/
emits('close')
/**
* 等同v-model:show
*/
emits('update:show', false)
showOverflay.value = false;
tw.value.destroy()
// #ifdef WEB
teleportTarget.value = getTeleportTarget()
// #endif
}
} as xTweenAnimate
)
tw.value.play()
// #endif
// #ifdef MP-WEIXIN
setTimeout(function() {
actioning.value = false;
/**
* 关闭时执行
*/
emits('close')
/**
* 等同v-model:show
*/
emits('update:show', false)
showOverflay.value = false;
}, 250);
// #endif
}
function showAlert() {
// #ifdef APP||WEB
tw.value.startRender()
// #endif
if (actioning.value) return;
if (status.value == 'open') return;
showOverflay.value = true;
actioning.value = true;
status.value = 'open'
// #ifdef WEB
teleportTarget.value = getTeleportTarget()
// #endif
/**
* 打开前执行
*/
emits('beforeOpen')
clearTimeout(tid.value)
// #ifdef APP||WEB
tid.value = setTimeout(function () {
let element = xoverflayRef.value! as UniElement
let elementBody = xoverflayWrapBoxRef.value! as UniElement
tw.value.stop()
tw.value.removeAnimate()
// tmxEase
tw.value
.addAnimate({
duration: _duration.value,
ease: 'linear',
enter: (item : xTweenEventCallFunType) => {
element.style.setProperty('background-color', `rgba(0,0,0,${Math.min(0.4, item.progress)})`)
},
complete: (item : xTweenEventCallFunType) => {
actioning.value = false;
/**
* 打开执行的事件
*/
emits('open')
}
} as xTweenAnimate
)
tw.value
.addAnimate({
duration: _duration.value,
ease: 'tmxEase',
enter: (item : xTweenEventCallFunType) => {
elementBody.style.setProperty('opacity', `${item.progress}`)
elementBody.style.setProperty('transform', `scale(${item.progress})`)
},
complete: (item : xTweenEventCallFunType) => {
elementBody.style.setProperty('opacity', `${1}`)
elementBody.style.setProperty('transform', `scale(${1})`)
tw.value.destroy()
}
} as xTweenAnimate
)
tw.value.play()
},50)
// #endif
// #ifdef MP-WEIXIN
actioning.value = false;
/**
* 打开执行的事件
*/
emits('open')
// #endif
}
function onClickOverflowy() {
/**
* 点击遮罩事件
*/
emits("click")
if (!props.overlayClick) {
if(!props.disabledAni){
// #ifdef APP || WEB
let el = xoverflayWrapBoxRef.value as UniElement | null;
if (xani.value != null) {
xani.value!.stop();
}
if (el != null) {
xani.value = new xAnimate(el!, { duration: 50, isDescPlay: true } as XANIMATE_OPIONS)
xani.value!.attr('scale', '1', '0.95', false)
xani.value!.attr('scale', '0.95', '1.05', false)
xani.value!.attr('scale', '1.05', '1', false)
.play()
}
// #endif
}
return;
}
closeAlert();
}
function openOverlay() {
showAlert();
}
function wrapClick(evt : UniPointerEvent) {
// evt.stopPropagation()
}
// 监听器
watch(():boolean => props.show, (newval : boolean) => {
if (newval) {
showAlert()
} else {
closeAlert()
}
})
// 生命周期
onMounted(() => {
// #ifdef H5
nextTick(() => {
teleportTarget.value = getTeleportTarget()
})
// #endif
function oninitready(){
let sys = uni.getWindowInfo()
// #ifndef APP
_width.value = sys.windowWidth
_height.value = sys.windowHeight;
windtop.value = sys.windowTop;
// #endif
// #ifdef APP
_width.value = sys.windowWidth
_height.value = sys.windowHeight + 44;
// #endif
if (_show.value) {
showAlert();
}
}
oninitready()
})
onUpdated(() => {
// #ifdef H5
if (teleportTarget.value && !isTeleportTargetValid(teleportTarget.value)) {
teleportTarget.value = getTeleportTarget()
}
// #endif
})
onBeforeUnmount(() => {
// #ifdef APP|WEB
tw.value.destroy()
// #endif
clearTimeout(tid.value)
})
defineExpose({
/** 打开 **/
open:()=>showAlert(),
/** 关闭 **/
close:()=>closeAlert()
})
</script>
<template>
<view>
<view @click="openOverlay">
<!--
@slot 标签触发显示遮罩,免于使用变量控制
@prop {Boolean} show - 当前是否已显示
-->
<slot name="trigger" :show="show"></slot>
</view>
<!-- #ifdef H5 -->
<teleport :to="teleportTarget || teleportElH5" :disabled="!teleportTarget">
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<root-portal >
<!-- #endif -->
<view v-if="showOverflay" :id="id" ref="xoverflayRef" class="xoverflay"
:style="[{width:'100%',height:__height,top:windtop+'px',zIndex:props.zIndex}]">
<view @click.stop="onClickOverflowy"
:class="[
status=='open'?'xoverflayMaskShow':'',
status=='close'?'xoverflayMaskHide':'',
]"
class="xoverflayWrapBoxContent" :style="_customStyle">
<view ref="xoverflayWrapBoxRef"
:class="[
status=='open'?'xoverflayShow':'',
status=='close'?'xoverflayHide':'',
]"
class="xoverflayWrapBox" @click.stop="" :style="_customContentStyle">
<!--
默认插槽
-->
<slot name="default"></slot>
<view v-if="_showClose" class="closeOverBtn">
<x-icon @click="closeAlert" color="white" font-size="38" name="close-circle-fill"></x-icon>
</view>
</view>
</view>
<!-- <view @click.stop="onClickOverflowy" class="xoverflayWrapBoxMasker" @touchmove.stop="overflayMoveTouch"></view> -->
</view>
<!-- #ifdef MP-WEIXIN -->
</root-portal>
<!-- #endif -->
<!-- #ifdef H5 -->
</teleport>
<!-- #endif -->
</view>
</template>
<style scoped>
.xoverflayWrapBox {
pointer-events: auto;
transform: scale(0);
opacity: 0;
}
.closeOverBtn {
display: flex;
flex-direction: row;
justify-content: center;
margin-top: 32rpx;
z-index: 105;
opacity: 0.7;
}
.xoverflayWrapBoxMasker {
width: 100%;
height: 100%;
left: 0px;
top: 0px;
position: absolute;
z-index: 1;
}
.xoverflayWrapBoxContent {
width: 100%;
height: 100%;
left: 0px;
top: 0px;
position: absolute;
z-index: 3;
}
.xoverflay {
background-color: rgba(0, 0, 0, 0);
/* opacity: 0; */
position: fixed;
/* z-index: 1100; */
left: 0px;
top: 0px;
/* transition-duration: 350ms;
transition-property: opacity; */
/* #ifndef APP */
backdrop-filter: blur(5px);
/* #endif */
}
/* #ifdef MP-WEIXIN */
.xoverflayShow{
animation:showAniMation 250ms cubic-bezier(.42,.38,.15,.93) forwards ;
}
.xoverflayHide{
animation:hideAniMation 250ms cubic-bezier(.42,.38,.15,.93) forwards ;
}
.xoverflayMaskShow{
background-color: rgba(0, 0, 0, 0.4);
animation:showAniMaskMation 200ms linear ;
opacity:1;
}
.xoverflayMaskHide{
background-color: rgba(0, 0, 0, 0);
opacity:0;
transition-duration: 250ms;
transition-property: background-color, opacity;
transition-timing-function: linear;
/* animation:hideAniMaskMation 200ms linear ; */
}
@keyframes showAniMaskMation {
0%{
background-color: rgba(0, 0, 0, 0);
}
100%{
background-color: rgba(0, 0, 0, 0.4);
}
}
@keyframes hideAniMaskMation {
0%{
background-color: rgba(0, 0, 0, 0.4);
}
100%{
background-color: rgba(0, 0, 0, 0);
}
}
@keyframes showAniMation {
0%{
transform: scale(0.6);
opacity: 0;
}
100%{
transform: scale(1);
opacity: 1;
}
}
@keyframes hideAniMation {
0%{
transform: scale(1);
opacity: 1;
}
100%{
transform: scale(0.6);
opacity: 0;
}
}
/* #endif */
</style>