CNCEC_APP/uni_modules/uview-pro/components/u-loading-popup/u-loading-popup.vue

254 lines
5.2 KiB
Vue
Raw Normal View History

2026-03-25 14:54:15 +08:00
<template>
<view @touchmove.stop.prevent>
<view v-if="popupValue" class="u-loading-init" :class="[direction]">
<u-loading :mode="mode" :color="color" :size="size" />
<view v-if="currentText" class="u-loading-tips">
{{ currentText }}
</view>
</view>
<view class="u-loading-mask" :class="[popupValue ? 'u-mask-show' : '']" @click="onMaskClick" />
</view>
</template>
<script lang="ts">
export default {
name: 'u-loading-popup',
options: {
addGlobalClass: true,
// #ifndef MP-TOUTIAO
virtualHost: true,
// #endif
styleIsolation: 'shared'
}
};
</script>
<script lang="ts" setup>
import { computed, onUnmounted, ref, watch } from 'vue';
import { LoadingPopupProps } from './types';
// 组件props类型
const props = defineProps(LoadingPopupProps);
const emit = defineEmits(['update:modelValue', 'cancel']);
// 自动关闭的持续时间定时器
let durationTimer: ReturnType<typeof setTimeout> | null = null;
// 关闭按钮倒计时定时器
let cancelTimer: ReturnType<typeof setTimeout> | null = null;
// 记录弹窗显示的时间戳
const now = ref(0);
// 点击遮罩层是否可关闭(超时后)
const canClose = ref(false);
// 当前显示的text优先级loading参数 > props.text
const currentText = ref(props.text);
const popupValue = computed({
get: () => props.modelValue,
set: (val: boolean) => emit('update:modelValue', val)
});
watch(
() => popupValue.value,
val => {
if (val) {
doOpen(currentText.value);
} else {
doClose();
}
}
);
// 响应 props 变更,自动刷新当前 text
watch(
() => props.text,
val => {
currentText.value = val;
}
);
// 响应 cancelTime/duration 变化,重启定时器
watch(
() => [props.cancelTime, props.duration],
() => {
if (popupValue.value) {
clearDurationTimer();
startCancelTime();
startDurationTime();
}
}
);
/**
* 启动超时关闭按钮计时
*/
function startCancelTime() {
clearCancelTimer();
canClose.value = false;
if (props.cancelTime > 0) {
cancelTimer = setTimeout(() => {
canClose.value = true;
}, props.cancelTime);
}
}
/**
* 启动持续时间计时
*/
function startDurationTime() {
clearDurationTimer();
if (props.duration) {
durationTimer = setTimeout(() => {
close();
}, props.duration);
}
}
/**
* 内部显示逻辑初始化所有状态
*/
function doOpen(text?: string) {
canClose.value = false;
clearDurationTimer();
clearCancelTimer();
now.value = Date.now();
if (typeof text === 'string' && text !== '') {
currentText.value = text;
} else {
currentText.value = props.text;
}
startCancelTime();
startDurationTime();
}
/**
* 内部关闭逻辑重置所有状态
*/
function doClose() {
canClose.value = false;
currentText.value = props.text;
clearDurationTimer();
clearCancelTimer();
}
/**
* 显示弹窗
* @param text 可选优先显示的文案
*/
function open(text?: string) {
currentText.value = text || props.text;
popupValue.value = true;
}
/**
* 隐藏弹窗
*/
function close() {
popupValue.value = false;
}
// 清理定时器
function clearDurationTimer() {
if (durationTimer) {
clearTimeout(durationTimer);
durationTimer = null;
}
}
function clearCancelTimer() {
if (cancelTimer) {
clearTimeout(cancelTimer);
cancelTimer = null;
}
}
// 遮罩点击事件
function onMaskClick() {
// 只有显示关闭按钮时才允许关闭
if (canClose.value) {
emit('cancel');
close();
}
}
onUnmounted(() => {
clearDurationTimer();
clearCancelTimer();
});
defineExpose({
open,
close
});
</script>
<style lang="scss">
.u-loading-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0);
z-index: 9999996;
transition: all 0.3s ease-in-out;
opacity: 0;
visibility: hidden;
}
.u-mask-show {
visibility: visible;
opacity: 1;
}
.u-loading-init {
position: relative;
min-width: 200rpx;
min-height: 200rpx;
max-width: 500rpx;
padding: 15rpx 0;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 9999;
font-size: 30rpx;
color: var(--u-white-color);
background: rgba(0, 0, 0, 0.7);
border-radius: 7px;
.u-icon-close {
position: absolute;
top: 4rpx;
right: 2rpx;
color: var(--u-white-color);
opacity: 0.8;
}
&.horizontal {
flex-direction: row;
align-items: center;
justify-content: left;
width: 600rpx;
max-width: 600rpx;
min-height: 150rpx;
padding-left: 40rpx;
.u-loading-tips {
margin-top: 0;
margin-left: 20rpx;
font-size: 32rpx;
}
}
}
.u-loading-tips {
text-align: center;
padding: 0 20rpx;
box-sizing: border-box;
margin-top: 36rpx;
}
</style>