本文档对比分析了 uni-app 项目中两种常见的页面加载提示实现方式:全局加载提示和骨架屏加载动画。通过对比 my-patients.vue 和 message-detail.vue 两个页面的具体实现,总结各自的特点、适用场景和优缺点。
uni.showLoading() 和 uni.hideLoading() APIconst fetchPatients = async () => {
uni.showLoading({ title: '加载中...' })
try {
// 数据请求逻辑
const response = await listUserBindingsByBoundUser(...)
// 处理响应
} catch (error) {
// 错误处理
} finally {
uni.hideLoading()
}
}
v-if="loading"<template>
<!-- 骨架屏 -->
<view v-if="loading" class="skeleton-container">
<view class="skeleton-card" v-for="i in 3" :key="i">
<view class="skeleton-header">
<view class="skeleton-line skeleton-title"></view>
<view class="skeleton-line skeleton-small"></view>
</view>
<view class="skeleton-content">
<view class="skeleton-line skeleton-text"></view>
<view class="skeleton-line skeleton-text"></view>
<view class="skeleton-line skeleton-text skeleton-short"></view>
</view>
</view>
</view>
<!-- 实际内容 -->
<view v-else class="message-list">
<!-- 真实消息列表 -->
</view>
</template>
<script setup>
const loading = ref(true)
const fetchMessages = async () => {
loading.value = true
try {
// 数据请求逻辑
} finally {
loading.value = false
}
}
</script>
.skeleton-line {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
}
@keyframes skeleton-loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
| 特性 | 全局加载提示 | 骨架屏加载动画 |
|---|---|---|
| 实现复杂度 | 低 | 中等 |
| 用户体验 | 一般 | 优秀 |
| 视觉效果 | 简单 | 丰富 |
| 交互阻塞 | 是 | 否 |
| 自定义程度 | 低 | 高 |
| 性能影响 | 低 | 中等 |
| 适用数据量 | 小数据 | 大数据/复杂页面 |
在使用骨架屏加载动画时,从其他页面(如编辑页面)返回时,骨架屏下方残留有之前的实际列表内容,导致加载状态与旧数据同时显示,影响用户体验。
模板中骨架屏使用 v-if="loading" 条件显示,但实际内容列表没有相应的条件隐藏。在数据重新加载期间,loading 为 true 时骨架屏显示,但旧的列表数据仍然可见,因为没有 v-if="!loading" 限制。
将实际内容区域包装在 v-if="!loading" 条件中,确保加载时只显示骨架屏,加载完成后显示实际内容。
错误示例:
<view v-if="loading" class="skeleton-list">
<!-- 骨架屏 -->
</view>
<view class="news-card" v-for="item in list" :key="item.id">
<!-- 实际列表,无条件隐藏 -->
</view>
正确示例:
<view v-if="loading" class="skeleton-list">
<!-- 骨架屏 -->
</view>
<view v-if="!loading">
<view class="news-card" v-for="item in list" :key="item.id">
<!-- 实际列表 -->
</view>
<!-- 其他内容如空状态、加载更多等 -->
</view>
onShow 触发)时,确保旧数据被正确隐藏message-detail.vue 的实现模式,确保加载状态管理的完整性在对比 news.vue 和 message-detail.vue 的骨架屏实现时,发现 news.vue 的骨架屏看起来不够真实、缺乏动态感,而 message-detail.vue 的骨架屏更具真实感和流畅性。
news.vue 的骨架屏使用静态的渐变背景(linear-gradient(90deg, #eee, #f6f6f6)),没有动画效果,只是固定的颜色块;而 message-detail.vue 使用了动态的流动动画,通过 CSS animation 和 @keyframes 让背景从左到右移动,模拟内容正在加载的过程。
为骨架屏元素添加 CSS 动画效果,使其产生流动感,提升真实感。
具体修改步骤:
修改骨架元素的背景样式:
.skeleton-cover,
.skeleton-title,
.skeleton-author,
.skeleton-summary {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
}
添加动画定义:
@keyframes skeleton-loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
message-detail.vue),统一项目内的骨架屏风格在对比 news.vue 和 message-detail.vue 的骨架屏实现时,发现 news.vue 的骨架屏布局与实际内容结构不匹配,看起来不够真实,而 message-detail.vue 的骨架屏更贴近实际内容的布局。
news.vue 的骨架屏结构是封面占位符 + 标题 + 作者 + 摘要的水平布局,但实际新闻卡片的布局是标题在顶部,然后是图片 + 摘要 + 作者的垂直布局。骨架屏没有准确模拟真实内容的视觉层次和排列方式。
调整骨架屏的 HTML 结构和 CSS 样式,使其完全匹配实际内容的布局。
具体修改步骤:
修改骨架屏的 HTML 结构:
<!-- 修改前 -->
<view class="skeleton-card">
<view class="skeleton-cover" />
<view class="skeleton-meta">
<view class="skeleton-title" />
<view class="skeleton-author" />
<view class="skeleton-summary" />
</view>
</view>
<!-- 修改后 -->
<view class="skeleton-card">
<view class="skeleton-title" />
<view class="skeleton-content">
<view class="skeleton-cover" />
<view class="skeleton-meta">
<view class="skeleton-summary" />
<view class="skeleton-author" />
</view>
</view>
</view>
调整对应的 CSS 样式:
.skeleton-card {
background: #fff;
border-radius: 12rpx;
margin-bottom: 18rpx;
overflow: hidden;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15);
}
.skeleton-title {
width: 60%;
height: 34rpx;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
margin: 20rpx;
border-radius: 6rpx;
}
.skeleton-content {
display: flex;
padding: 20rpx;
gap: 20rpx;
align-items: center;
}
.skeleton-cover {
width: 220rpx;
height: 150rpx;
border-radius: 10rpx;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
}
.skeleton-meta {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.skeleton-summary {
width: 100%;
height: 28rpx;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
margin-bottom: 12rpx;
border-radius: 6rpx;
}
.skeleton-author {
width: 50%;
height: 24rpx;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
border-radius: 6rpx;
}
src/pages/doctor/index/my-patients.vue - 全局加载提示示例src/pages/public/message-detail.vue - 骨架屏加载动画示例src/pages/doctor/manage/news.vue - 骨架屏逻辑修复示例src/pages/doctor/index/index.vue - 骨架屏逻辑修复示例(用户信息和患者动态)