1
0
mirror of https://github.com/ialley-workshop-open/uni-halo.git synced 2026-09-12 16:40:40 +08:00

refactor(uh-data-loading): 重构数据加载组件,新增多状态优化与样式升级

1. 新增副文案支持与默认文案优化
2. 重构状态渲染逻辑,统一三态UI结构
3. 添加氛围动效:光晕呼吸、装饰点漂浮、差异化表情动画
4. 调整测试页与首页默认配置,关闭开发模式
This commit is contained in:
小莫唐尼
2026-09-04 03:11:54 +08:00
parent 5f22f4fa01
commit 3c84661d0e
3 changed files with 226 additions and 40 deletions
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { computed } from 'vue'
import type { DataLoadingStatus } from '@/hooks/useDataLoading'
interface IProps {
@@ -9,76 +10,261 @@ interface IProps {
loadingText?: string
errorText?: string
emptyText?: string
/** 各态副文案(留空则不显示副行) */
loadingSubText?: string
errorSubText?: string
emptySubText?: string
}
const props = withDefaults(defineProps<IProps>(), {
loadingStatus: 'loading',
minHeight: '60vh',
loadingText: '稍等,正在努力加载中哦...',
loadingText: '稍等,正在加载中哦...',
errorText: '哎呀,加载失败了呢~',
emptyText: '啊偶,暂时没有数据呢~',
loadingSubText: '',
errorSubText: '请检查网络连接,或稍后再试',
emptySubText: '稍后再来看看吧~',
})
const emit = defineEmits<{ (e: 'refresh'): void }>()
const isLoading = computed(() => props.loadingStatus === 'loading')
const statusScene = computed(() => {
switch (props.loadingStatus) {
case 'error':
return {
icon: '-injury',
stageClass: 'stage-error',
mainTextClass: 'text-red-400',
mainText: props.errorText,
subText: props.errorSubText,
}
case 'empty':
return {
icon: '-confused',
stageClass: 'stage-empty',
mainTextClass: 'text-gray-600',
mainText: props.emptyText,
subText: props.emptySubText,
}
default:
return {
icon: '-happy-1',
stageClass: 'stage-loading',
mainTextClass: 'text-primary',
mainText: props.loadingText,
subText: props.loadingSubText,
}
}
})
</script>
<template>
<view
class="w-full flex flex-col items-center justify-center gap-y-4 text-sm"
class="w-full flex flex-col items-center justify-center gap-y-8 text-sm"
:style="{ minHeight: props.minHeight }"
>
<!-- 加载中 -->
<template v-if="props.loadingStatus === 'loading'">
<!-- 表情上下漂浮动画 -->
<text class="bob-icon">
<wd-icon class-prefix="uhemoji-icon" name="-happy-1" size="100rpx" />
</text>
<view class="text-primary">
{{ props.loadingText }}
<!-- 状态舞台:光晕 + 漂浮装饰点 + 毛玻璃表情珠 -->
<view class="scene relative h-[250rpx] w-[250rpx] flex items-center justify-center" :class="statusScene.stageClass">
<view class="glow absolute inset-0 m-auto h-[220rpx] w-[220rpx] rounded-full" />
<view class="deco-dot dot-a absolute rounded-full" />
<view class="deco-dot dot-b absolute rounded-full" />
<view class="bubble relative h-[150rpx] w-[150rpx] flex items-center justify-center rounded-full">
<text class="bubble-icon">
<wd-icon class-prefix="uhemoji-icon" :name="statusScene.icon" size="120rpx" />
</text>
</view>
</template>
</view>
<!-- 加载错误(可重试) -->
<template v-else-if="props.loadingStatus === 'error'">
<!-- 表情上下漂浮动画 -->
<text class="bob-icon">
<wd-icon class-prefix="uhemoji-icon" name="-injury" size="100rpx" />
</text>
<view class="text-red-400">
{{ props.errorText }}
<!-- 文案区 -->
<view class="flex flex-col items-center">
<view class="flex items-center justify-center text-[28rpx] font-bold" :class="statusScene.mainTextClass">
<text>{{ statusScene.mainText }}</text>
<!-- 加载中三点跳动 -->
<view v-if="isLoading" class="ml-3 flex items-end gap-1">
<view v-for="n in 3" :key="n" class="typing-dot" />
</view>
</view>
<uh-button @click="emit('refresh')">
<text v-if="statusScene.subText" class="mt-3 text-[24rpx] text-gray-400">
{{ statusScene.subText }}
</text>
<uh-button class="mt-5" @click="emit('refresh')">
刷新试试
</uh-button>
</template>
<!-- 无数据 -->
<template v-else>
<!-- 表情上下漂浮动画 -->
<text class="bob-icon">
<wd-icon class-prefix="uhemoji-icon" name="-confused" size="120rpx" />
</text>
<view class="text-gray-900">
{{ props.emptyText }}
</view>
</template>
</view>
</view>
</template>
<style scoped lang="scss">
/* 状态表情上下漂浮动画:@keyframes 无法用 UnoCSS / wot-ui 原子类表达保留 scoped 样式 */
@keyframes bob-icon-float {
/* 氛围化状态占位:keyframes/状态配色无法用原子类表达,保留 scoped 样式 */
/* —— 毛玻璃表情珠(三态共用,缓慢上下漂浮) —— */
.bubble {
animation: bubble-float 2s ease-in-out infinite;
}
.bubble-icon {
display: inline-block;
}
/* —— 光晕(状态色,缓慢呼吸) —— */
.glow {
animation: glow-pulse 2.4s ease-in-out infinite;
}
.stage-loading .glow {
background: rgba(185, 228, 36, 0.32);
}
.stage-error .glow {
background: rgba(248, 113, 113, 0.24);
}
.stage-empty .glow {
background: rgba(217, 249, 157, 0.5);
}
/* —— 漂浮装饰点(品牌双色,错峰漂浮) —— */
.deco-dot {
animation: deco-float 2s ease-in-out infinite;
}
.dot-a {
top: 16rpx;
left: 10rpx;
width: 22rpx;
height: 22rpx;
background: rgba(163, 230, 53, 0.9);
}
.dot-b {
top: 4rpx;
right: 14rpx;
width: 14rpx;
height: 14rpx;
background: rgba(217, 249, 157, 0.95);
animation-delay: -0.7s;
}
/* —— 三态表情差异化动效(均无限循环,柔和不抢眼) —— */
.stage-loading .bubble-icon {
animation: sway 1.6s ease-in-out infinite;
}
.stage-error .bubble-icon {
animation: head-shake 2.8s ease-in-out infinite;
transform-origin: 50% 85%;
}
.stage-empty .bubble-icon {
animation: sigh 3s ease-in-out infinite;
transform-origin: 50% 85%;
}
/* —— 加载中三点跳动 —— */
.typing-dot {
width: 10rpx;
height: 10rpx;
border-radius: 50%;
background: rgba(163, 230, 53, 0.95);
animation: dot-jump 1s ease-in-out infinite;
&:nth-child(2) {
animation-delay: 0.15s;
}
&:nth-child(3) {
animation-delay: 0.3s;
}
}
/* —— keyframes —— */
@keyframes bubble-float {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-18rpx);
transform: translateY(-14rpx);
}
}
.bob-icon {
display: inline-block;
animation: bob-icon-float 2s ease-in-out infinite;
@keyframes glow-pulse {
0%,
100% {
transform: scale(1);
opacity: 0.55;
}
50% {
transform: scale(1.1);
opacity: 0.9;
}
}
@keyframes deco-float {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-12rpx);
}
}
@keyframes dot-jump {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-8rpx);
}
}
/* 歪头左右打量(loading) */
@keyframes sway {
0%,
100% {
transform: rotate(-4deg);
}
50% {
transform: rotate(4deg);
}
}
/* 缓慢左右摇头(error) */
@keyframes head-shake {
0%,
100% {
transform: rotate(0);
}
25% {
transform: rotate(6deg);
}
75% {
transform: rotate(-6deg);
}
}
/* 轻轻叹气缩肩(empty) */
@keyframes sigh {
0%,
100% {
transform: scaleY(1);
}
30%,
70% {
transform: scaleY(0.92);
}
}
</style>
+1 -1
View File
@@ -9,6 +9,6 @@
<template>
<view class="w-full h-screen">
<uh-data-loading :loading-status="DataLoadingStatusEnum.Empty"></uh-data-loading>
<uh-data-loading :loading-status="DataLoadingStatusEnum.Loading"></uh-data-loading>
</view>
</template>
+1 -1
View File
@@ -26,7 +26,7 @@ const homePagePath = '/pages/tabbar/home/home'
const articleDetailPath = '/pages-blog/article-detail/article-detail'
// 本地开发快速跳转页面,发布请置为 false
const DEV_MODE = true
const DEV_MODE = false
const DEV_TO_TYPE = 'page' as 'page' | 'tabbar'
const DEV_TO_PATH = `/pages-blog/test/test`