2026-02-15 18:15:10 +08:00

264 lines
6.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="qy-loading">
<view class="qy-loading-inner">
<!-- ========== 加载中模式 ========== -->
<template v-if="mode === 'loading'">
<image
v-if="!loadError"
class="qy-loading-img"
:style="imgStyle"
:src="loadingSrc"
mode="aspectFit"
@error="onImageError"
/>
<view v-else class="qy-loading-fallback">
<view class="qy-loading-placeholder"></view>
</view>
<text v-if="text" class="qy-loading-text">{{ text }}</text>
</template>
<!-- ========== 空数据模式 ========== -->
<template v-else-if="mode === 'empty'">
<view class="qy-empty-illustration">
<!-- 外圈光晕 -->
<view class="qy-empty-glow"></view>
<!-- 主图空文档 -->
<view class="qy-empty-doc">
<view class="qy-empty-doc-fold"></view>
<view class="qy-empty-doc-body">
<view class="qy-empty-doc-line long"></view>
<view class="qy-empty-doc-line medium"></view>
<view class="qy-empty-doc-line short"></view>
</view>
<!-- 放大镜 -->
<view class="qy-empty-magnifier">
<view class="qy-empty-magnifier-glass"></view>
<view class="qy-empty-magnifier-handle"></view>
</view>
</view>
</view>
<text class="qy-empty-text">{{ text || '暂无数据' }}</text>
<text v-if="subText" class="qy-empty-subtext">{{ subText }}</text>
</template>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
// 直接引用 static 下的 loading.webp由构建输出正确路径
import defaultLoadingImg from '@/static/loading.gif';
const props = withDefaults(
defineProps<{
/** 模式loading 加载中(默认)| empty 空数据 */
mode?: 'loading' | 'empty';
/** 提示文字loading 模式不传则不显示empty 模式默认"暂无数据" */
text?: string;
/** 副文本提示(仅 empty 模式),如"请稍后再试" */
subText?: string;
/** 尺寸,单位 rpx默认 120仅 loading 模式下的动画尺寸) */
size?: number;
/** 自定义图片路径(仅 loading 模式),不传则使用 static/loading.gif */
src?: string;
}>(),
{
mode: 'loading',
text: '',
subText: '',
size: 120,
src: '',
},
);
const loadError = ref(false);
const loadingSrc = computed(() => {
if (props.src) return props.src;
return defaultLoadingImg;
});
const imgStyle = computed(() => ({
width: props.size + 'rpx',
height: props.size + 'rpx',
}));
function onImageError() {
loadError.value = true;
}
</script>
<style lang="scss" scoped>
/* ========== 通用容器 ========== */
.qy-loading {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
min-height: 120rpx;
padding: 24rpx 0;
}
.qy-loading-inner {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
/* ========== Loading 模式 ========== */
.qy-loading-img {
flex-shrink: 0;
}
.qy-loading-fallback {
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
}
.qy-loading-placeholder {
width: 56rpx;
height: 56rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.25);
}
.qy-loading-text {
margin-top: 20rpx;
font-size: 28rpx;
color: #fff;
text-align: center;
}
/* ========== Empty 模式 ========== */
.qy-empty-illustration {
position: relative;
width: 100rpx;
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
animation: emptyFloat 3s ease-in-out infinite;
}
/* 背景光晕 */
.qy-empty-glow {
position: absolute;
width: 90rpx;
height: 90rpx;
border-radius: 50%;
background: radial-gradient(circle, rgba(143, 157, 247, 0.15) 0%, rgba(143, 157, 247, 0) 70%);
}
/* 文档主体 */
.qy-empty-doc {
position: relative;
width: 55rpx;
height: 65rpx;
background: #fff;
border-radius: 6rpx;
box-shadow: 0 4rpx 16rpx rgba(100, 116, 200, 0.15), 0 1rpx 4rpx rgba(100, 116, 200, 0.08);
overflow: visible;
}
/* 文档折角 */
.qy-empty-doc-fold {
position: absolute;
top: 0;
right: 0;
width: 14rpx;
height: 14rpx;
background: linear-gradient(135deg, transparent 50%, #e8ecf4 50%);
border-radius: 0 6rpx 0 0;
&::before {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 14rpx;
height: 14rpx;
background: linear-gradient(135deg, transparent 50%, #dce1ed 50%);
border-bottom-right-radius: 4rpx;
}
}
/* 文档内容行 */
.qy-empty-doc-body {
padding: 18rpx 7rpx 7rpx;
display: flex;
flex-direction: column;
gap: 6rpx;
}
.qy-empty-doc-line {
height: 4rpx;
border-radius: 2rpx;
background: #e8ecf4;
&.long {
width: 100%;
}
&.medium {
width: 70%;
}
&.short {
width: 45%;
}
}
/* 放大镜 */
.qy-empty-magnifier {
position: absolute;
right: -9rpx;
bottom: -7rpx;
transform: rotate(-45deg);
}
.qy-empty-magnifier-glass {
width: 20rpx;
height: 20rpx;
border-radius: 50%;
border: 3rpx solid #8f9df7;
background: rgba(143, 157, 247, 0.08);
}
.qy-empty-magnifier-handle {
width: 3rpx;
height: 10rpx;
background: linear-gradient(to bottom, #8f9df7, #b5bfff);
border-radius: 0 0 2rpx 2rpx;
margin-left: 9rpx;
margin-top: -1rpx;
}
/* 文字 */
.qy-empty-text {
margin-top: 12rpx;
font-size: 16rpx;
color: #fff;
text-align: center;
letter-spacing: 1rpx;
}
.qy-empty-subtext {
margin-top: 4rpx;
font-size: 20rpx;
color: #b5bfcf;
text-align: center;
}
/* 悬浮动画 */
@keyframes emptyFloat {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-5rpx);
}
}
</style>