소스 검색

feat(critical-values): 添加骨架加载视图和数据加载失败提示功能

mcbaiyun 2 주 전
부모
커밋
b0bd138881
1개의 변경된 파일111개의 추가작업 그리고 56개의 파일을 삭제
  1. 111 56
      src/pages/doctor/manage/critical-values.vue

+ 111 - 56
src/pages/doctor/manage/critical-values.vue

@@ -1,6 +1,21 @@
 <template>
   <CustomNav title="危急值管理" leftType="back" />
   <view class="page-container">
+    <!-- 骨架加载视图 -->
+    <view v-if="isLoading" class="skeleton-container">
+      <view class="skeleton-card" />
+      <view class="skeleton-card" />
+      <view class="skeleton-card" />
+      <view class="skeleton-card" />
+    </view>
+
+    <!-- 数据加载失败时的内联提示 -->
+    <view v-if="isError" class="load-error">
+      <text class="error-text">加载数据失败:{{ loadError || '网络或服务器异常' }}</text>
+      <button class="btn-secondary" @click="retryLoad">重试加载</button>
+    </view>
+
+    <view v-if="!isLoading" class="content-root">
     <view class="card">
       <view class="card-header">体格(身高 / 体重 / BMI)</view>
       <view class="card-body">
@@ -118,12 +133,13 @@
         <!-- 按钮已移至页面底部全局操作区 -->
       </view>
     </view>
-  </view>
-  
+    </view>
+    </view>
+
     <!-- 全局操作区:仅保留保存 -->
     <view class="global-actions" style="padding: 20rpx 20rpx 40rpx; background: transparent;">
       <view class="actions">
-        <button class="btn-primary" @click="save">保存设置</button>
+        <button class="btn-primary" @click="save" :disabled="isLoading || isError">保存设置</button>
       </view>
     </view>
 </template>
@@ -163,9 +179,7 @@ interface CriticalValues {
   heartRate: { min: number | null; max: number | null }
 }
 
-// 新的保存键,包含多模块
-const STORAGE_KEY = 'criticalValues_v1'
-const LEGACY_PHYSICAL_KEY = 'criticalValues_physical'
+// 危急值配置页面
 
 const form = ref<CriticalValues>({
   physical: { heightMin: null, heightMax: null, weightMin: null, weightMax: null },
@@ -175,11 +189,19 @@ const form = ref<CriticalValues>({
   heartRate: { min: null, max: null }
 })
 
+// 加载状态
+const isLoading = ref(true)
+const isError = ref(false)
+const loadError = ref('')
+
 onMounted(async () => {
-  // 尝试从后端加载整表配置,若失败再回退到本地缓存
+  // 尝试从后端加载整表配置(显示骨架直到完成)
+  isLoading.value = true
+  isError.value = false
+  loadError.value = ''
   try {
     const res: any = await getCriticalValuesForm()
-    if (res.statusCode === 401) {
+    if (res && res.statusCode === 401) {
       uni.removeStorageSync('token')
       uni.removeStorageSync('role')
       uni.reLaunch({ url: '/pages/public/login/index' })
@@ -189,45 +211,49 @@ onMounted(async () => {
       const payload = (res.data as any).data
       if (payload) {
         form.value = { ...form.value, ...payload }
-        // 更新本地缓存以便离线使用
-        try { uni.setStorageSync(STORAGE_KEY, JSON.stringify(form.value)) } catch (e) { /* ignore */ }
-        return
       }
+    } else {
+      // 后端返回非200的错误信息
+      const msg = (res && res.data && (res.data as any).message) || '服务器返回异常'
+      isError.value = true
+      loadError.value = msg
+      uni.showToast({ title: `加载失败:${msg}`, icon: 'none' })
     }
-  } catch (e) {
-    // 网络或其他异常,回退到本地缓存
-  }
-
-  // 回退:优先读取新键
-  const savedNew: any = uni.getStorageSync(STORAGE_KEY)
-  if (savedNew) {
-    try {
-      const parsed = typeof savedNew === 'string' ? JSON.parse(savedNew) : savedNew
-      form.value = { ...form.value, ...parsed }
-    } catch (e) {
-      // ignore
-    }
-    return
+  } catch (e: any) {
+    // 网络或其他异常:展示错误并允许重试
+    isError.value = true
+    loadError.value = e && e.message ? e.message : String(e)
+    uni.showToast({ title: '加载失败,点击重试', icon: 'none' })
+  } finally {
+    isLoading.value = false
   }
+})
 
-  // 兼容老的 physical 键,如果存在则迁移
-  const savedLegacy: any = uni.getStorageSync(LEGACY_PHYSICAL_KEY)
-  if (savedLegacy) {
-    try {
-      const parsed = typeof savedLegacy === 'string' ? JSON.parse(savedLegacy) : savedLegacy
-      form.value.physical = {
-        heightMin: parsed.heightMin ?? null,
-        heightMax: parsed.heightMax ?? null,
-        weightMin: parsed.weightMin ?? null,
-        weightMax: parsed.weightMax ?? null
-      }
-      // 保存到新键以完成迁移
-      uni.setStorageSync(STORAGE_KEY, JSON.stringify(form.value))
-    } catch (e) {
-      // ignore
+const retryLoad = async () => {
+  isLoading.value = true
+  isError.value = false
+  loadError.value = ''
+  try {
+    const res: any = await getCriticalValuesForm()
+    if ((res.data as any) && (res.data as any).code === 200) {
+      const payload = (res.data as any).data
+      if (payload) form.value = { ...form.value, ...payload }
+      isError.value = false
+      uni.showToast({ title: '加载成功', icon: 'success' })
+    } else {
+      const msg = (res && res.data && (res.data as any).message) || '服务器返回异常'
+      isError.value = true
+      loadError.value = msg
+      uni.showToast({ title: `加载失败:${msg}`, icon: 'none' })
     }
+  } catch (e: any) {
+    isError.value = true
+    loadError.value = e && e.message ? e.message : String(e)
+    uni.showToast({ title: '加载失败,点击重试', icon: 'none' })
+  } finally {
+    isLoading.value = false
   }
-})
+}
 
 const validatePairs = (min: any, max: any, label: string) => {
   const isEmpty = (v: any) => v === null || v === undefined || v === ''
@@ -308,20 +334,13 @@ const save = async () => {
     // 先尝试调用后端接口保存(使用规范化后的 payload)
     const res: any = await saveCriticalValuesForm(payload)
     if ((res.data as any) && (res.data as any).code === 200) {
-      // 更新本地缓存
-      try { uni.setStorageSync(STORAGE_KEY, JSON.stringify(form.value)) } catch (e) { /* ignore */ }
       uni.showToast({ title: '保存成功', icon: 'success' })
       return
     }
     uni.showToast({ title: '保存失败', icon: 'none' })
   } catch (e) {
-    // 网络或其它错误,回退到本地缓存保存
-    try {
-      uni.setStorageSync(STORAGE_KEY, JSON.stringify(form.value))
-      uni.showToast({ title: '已保存到本地(离线)', icon: 'success' })
-    } catch (err) {
-      uni.showToast({ title: '保存失败', icon: 'none' })
-    }
+    // 网络或其它错误
+    uni.showToast({ title: '保存失败', icon: 'none' })
   }
 }
 
@@ -336,7 +355,6 @@ const resetForm = async () => {
         bloodGlucose: { fastingMin: null, fastingMax: null, randomMin: null, randomMax: null },
         heartRate: { min: null, max: null }
       }
-      try { uni.removeStorageSync(STORAGE_KEY) } catch (e) { /* ignore */ }
       uni.showToast({ title: '已重置', icon: 'success' })
       return
     }
@@ -352,11 +370,7 @@ const resetForm = async () => {
     bloodGlucose: { fastingMin: null, fastingMax: null, randomMin: null, randomMax: null },
     heartRate: { min: null, max: null }
   }
-  try {
-    uni.removeStorageSync(STORAGE_KEY)
-  } catch (e) {
-    // ignore
-  }
+  // 本地清理完成
   uni.showToast({ title: '已重置', icon: 'success' })
 }
 </script>
@@ -437,4 +451,45 @@ const resetForm = async () => {
   border-radius: 10rpx;
   font-size: 28rpx;
 }
+
+/* Skeleton styles */
+.skeleton-container {
+  padding: 20rpx;
+  display: flex;
+  flex-direction: column;
+  gap: 20rpx;
+}
+.skeleton-card {
+  height: 240rpx;
+  border-radius: 12rpx;
+  background: linear-gradient(90deg, #f0f0f0 25%, #e6e6e6 37%, #f0f0f0 63%);
+  background-size: 400% 100%;
+  animation: shimmer 1.4s ease infinite;
+}
+@keyframes shimmer {
+  0% { background-position: 100% 0 }
+  100% { background-position: -100% 0 }
+}
+
+.load-error {
+  padding: 20rpx;
+  margin: 20rpx;
+  background: #fff7f7;
+  border: 1rpx solid #ffd2d2;
+  border-radius: 10rpx;
+  display:flex;
+  align-items:center;
+  gap: 20rpx;
+}
+.error-text {
+  color: #d93025;
+  font-size: 28rpx;
+  flex:1;
+}
+
+/* Save 按钮禁用样式 */
+.btn-primary[disabled], .btn-primary.disabled {
+  background-color: #9ecbff;
+  opacity: 0.7;
+}
 </style>