254 lines
5.2 KiB
Vue
254 lines
5.2 KiB
Vue
|
|
<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>
|