|
|
@@ -3,7 +3,31 @@
|
|
|
<view class="page-container">
|
|
|
<view class="content">
|
|
|
<view class="user-info">
|
|
|
- <view class="avatar-section">
|
|
|
+ <!-- 用户信息骨架屏 -->
|
|
|
+ <view v-if="userInfoLoading" class="user-info-skeleton">
|
|
|
+ <view class="avatar-section">
|
|
|
+ <view class="avatar">
|
|
|
+ <view class="avatar-frame">
|
|
|
+ <view class="skeleton-avatar-placeholder"></view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="user-details">
|
|
|
+ <view class="skeleton-line skeleton-nickname"></view>
|
|
|
+ <view class="skeleton-line skeleton-title"></view>
|
|
|
+ </view>
|
|
|
+ <view class="message-button" @click="onMessageClick">
|
|
|
+ <image src="/static/icons/remixicon/message-3-line.svg" class="message-icon" />
|
|
|
+ <view class="badge" v-if="unreadMessageCount > 0">{{ unreadMessageCount > 99 ? '99+' : unreadMessageCount }}
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="qr-button" @click="onQrClick">
|
|
|
+ <image src="/static/icons/remixicon/qr-code-line.svg" class="qr-icon" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 实际用户信息 -->
|
|
|
+ <view v-if="!userInfoLoading" class="avatar-section">
|
|
|
<view class="avatar">
|
|
|
<view class="avatar-frame">
|
|
|
<image class="avatar-img" :src="avatarSrc" mode="aspectFill" />
|
|
|
@@ -100,17 +124,31 @@
|
|
|
<text class="card-title">患者动态</text>
|
|
|
</view>
|
|
|
<view class="activity-card-content">
|
|
|
- <view class="activity-item" v-for="(activity, index) in patientActivities" :key="index">
|
|
|
- <view class="activity-avatar">
|
|
|
- <image :src="activity.patientAvatar" class="avatar-img" mode="aspectFill" />
|
|
|
- </view>
|
|
|
- <view class="activity-text">
|
|
|
- <text class="activity-desc">{{ activity.desc }}</text>
|
|
|
- <text class="activity-time">{{ activity.time }}</text>
|
|
|
+ <!-- 骨架屏 -->
|
|
|
+ <view v-if="patientActivitiesLoading" class="skeleton-container">
|
|
|
+ <view class="skeleton-item" v-for="i in 3" :key="i">
|
|
|
+ <view class="skeleton-avatar"></view>
|
|
|
+ <view class="skeleton-text">
|
|
|
+ <view class="skeleton-line skeleton-desc"></view>
|
|
|
+ <view class="skeleton-line skeleton-time"></view>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
</view>
|
|
|
- <view v-if="patientActivities.length === 0" class="no-activity">
|
|
|
- <text>暂无患者动态</text>
|
|
|
+
|
|
|
+ <!-- 实际内容 -->
|
|
|
+ <view v-if="!patientActivitiesLoading">
|
|
|
+ <view class="activity-item" v-for="(activity, index) in patientActivities" :key="index">
|
|
|
+ <view class="activity-avatar">
|
|
|
+ <image :src="activity.patientAvatar" class="avatar-img" mode="aspectFill" />
|
|
|
+ </view>
|
|
|
+ <view class="activity-text">
|
|
|
+ <text class="activity-desc">{{ activity.desc }}</text>
|
|
|
+ <text class="activity-time">{{ activity.time }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-if="patientActivities.length === 0" class="no-activity">
|
|
|
+ <text>暂无患者动态</text>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
@@ -134,6 +172,9 @@ import { getUnreadMessageCount, getMessageList, markMessageAsRead, type MessageQ
|
|
|
|
|
|
const user = ref<{ avatar?: string; nickname?: string; title?: string }>({})
|
|
|
|
|
|
+// 用户信息加载状态
|
|
|
+const userInfoLoading = ref(true)
|
|
|
+
|
|
|
// 未读消息数量
|
|
|
const unreadMessageCount = ref(0)
|
|
|
|
|
|
@@ -170,6 +211,9 @@ const patientActivities = ref<Array<{
|
|
|
patientAvatar: string
|
|
|
}>>([])
|
|
|
|
|
|
+// 患者动态加载状态
|
|
|
+const patientActivitiesLoading = ref(true)
|
|
|
+
|
|
|
const loadUser = () => {
|
|
|
try {
|
|
|
const u = uni.getStorageSync('user_info')
|
|
|
@@ -183,8 +227,12 @@ const loadUser = () => {
|
|
|
|
|
|
const fetchUserInfo = async () => {
|
|
|
try {
|
|
|
+ userInfoLoading.value = true
|
|
|
const token = uni.getStorageSync('token')
|
|
|
- if (!token) return
|
|
|
+ if (!token) {
|
|
|
+ userInfoLoading.value = false
|
|
|
+ return
|
|
|
+ }
|
|
|
const response = await fetchUserInfoApi()
|
|
|
uni.hideLoading()
|
|
|
console.log('User info response:', response)
|
|
|
@@ -195,6 +243,7 @@ const fetchUserInfo = async () => {
|
|
|
uni.removeStorageSync('role')
|
|
|
user.value = {}
|
|
|
uni.reLaunch({ url: '/pages/public/login/index' })
|
|
|
+ userInfoLoading.value = false
|
|
|
return
|
|
|
}
|
|
|
if (resp && resp.code === 200 && resp.data) {
|
|
|
@@ -237,6 +286,8 @@ const fetchUserInfo = async () => {
|
|
|
} catch (err) {
|
|
|
uni.hideLoading()
|
|
|
console.error('Fetch user info error:', err)
|
|
|
+ } finally {
|
|
|
+ userInfoLoading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -259,9 +310,11 @@ const fetchTodayReminders = async () => {
|
|
|
|
|
|
const fetchPatientActivities = async () => {
|
|
|
try {
|
|
|
+ patientActivitiesLoading.value = true
|
|
|
const token = uni.getStorageSync('token')
|
|
|
if (!token) {
|
|
|
console.log('No token found, skipping fetchPatientActivities')
|
|
|
+ patientActivitiesLoading.value = false
|
|
|
return
|
|
|
}
|
|
|
|
|
|
@@ -299,6 +352,8 @@ const fetchPatientActivities = async () => {
|
|
|
console.error('Fetch patient activities error:', err)
|
|
|
// 如果接口调用失败,显示空数据
|
|
|
patientActivities.value = []
|
|
|
+ } finally {
|
|
|
+ patientActivitiesLoading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -856,4 +911,112 @@ const formatActivityDescription = (activity: any) => {
|
|
|
text-align: center;
|
|
|
color: #999;
|
|
|
}
|
|
|
+
|
|
|
+/* 骨架屏样式 */
|
|
|
+.skeleton-container {
|
|
|
+ padding: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.skeleton-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ padding: 20rpx;
|
|
|
+ border-bottom: 1rpx solid #eee;
|
|
|
+}
|
|
|
+
|
|
|
+.skeleton-item:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+ border-bottom: none;
|
|
|
+}
|
|
|
+
|
|
|
+.skeleton-avatar {
|
|
|
+ width: 60rpx;
|
|
|
+ height: 60rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
|
|
+ background-size: 200% 100%;
|
|
|
+ animation: skeleton-loading 1.5s infinite;
|
|
|
+ margin-right: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.skeleton-text {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.skeleton-line {
|
|
|
+ background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
|
|
+ background-size: 200% 100%;
|
|
|
+ animation: skeleton-loading 1.5s infinite;
|
|
|
+ border-radius: 6rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.skeleton-desc {
|
|
|
+ width: 70%;
|
|
|
+ height: 32rpx;
|
|
|
+ margin-bottom: 10rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.skeleton-time {
|
|
|
+ width: 40%;
|
|
|
+ height: 28rpx;
|
|
|
+}
|
|
|
+
|
|
|
+/* 用户信息骨架屏样式 */
|
|
|
+.user-info-skeleton .avatar-section {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.user-info-skeleton .avatar {
|
|
|
+ width: 120rpx;
|
|
|
+ height: 120rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ border: 1px solid rgba(128, 128, 128, 0.5);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-right: 30rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.skeleton-avatar-placeholder {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
|
|
+ background-size: 200% 100%;
|
|
|
+ animation: skeleton-loading 1.5s infinite;
|
|
|
+}
|
|
|
+
|
|
|
+.user-info-skeleton .user-details {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.skeleton-nickname {
|
|
|
+ width: 60%;
|
|
|
+ height: 36rpx;
|
|
|
+ background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
|
|
+ background-size: 200% 100%;
|
|
|
+ animation: skeleton-loading 1.5s infinite;
|
|
|
+ border-radius: 6rpx;
|
|
|
+ margin-bottom: 10rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.skeleton-title {
|
|
|
+ width: 40%;
|
|
|
+ height: 28rpx;
|
|
|
+ background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
|
|
+ background-size: 200% 100%;
|
|
|
+ animation: skeleton-loading 1.5s infinite;
|
|
|
+ border-radius: 6rpx;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes skeleton-loading {
|
|
|
+ 0% {
|
|
|
+ background-position: 200% 0;
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ background-position: -200% 0;
|
|
|
+ }
|
|
|
+}
|
|
|
</style>
|