264 lines
6.4 KiB
Vue
264 lines
6.4 KiB
Vue
<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>
|