看板
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import type { ExtractPropTypes, PropType } from 'vue';
|
||||
type LoadingMode = 'circle' | 'flower';
|
||||
|
||||
/**
|
||||
* u-loading-popup 组件 props 类型定义
|
||||
*/
|
||||
export const LoadingPopupProps = {
|
||||
/** 是否显示加载弹窗 */
|
||||
modelValue: { type: Boolean, default: false },
|
||||
/** 加载提示文本 */
|
||||
text: { type: String, default: '' },
|
||||
/** 方向,可选 'vertical' | 'horizontal' */
|
||||
direction: { type: String as PropType<'vertical' | 'horizontal'>, default: 'vertical' },
|
||||
/** 自动关闭的持续时间(ms),0为不自动关闭 */
|
||||
duration: { type: Number, default: 0 },
|
||||
/** 允许点击遮罩关闭的最短时间(ms) */
|
||||
cancelTime: { type: Number, default: 10000 },
|
||||
/** 动画的类型 */
|
||||
mode: { type: String as PropType<LoadingMode>, default: 'circle' },
|
||||
/** 动画的颜色 */
|
||||
color: { type: String, default: 'var(--u-light-color)' },
|
||||
/** 加载图标的大小,单位rpx */
|
||||
size: { type: [String, Number] as PropType<string | number>, default: '48' }
|
||||
};
|
||||
|
||||
export type LoadingPopupProps = ExtractPropTypes<typeof LoadingPopupProps>;
|
||||
@@ -0,0 +1,253 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user