refactor: enhance error handling in image loading (#42)

This commit is contained in:
ChrAlpha
2025-07-07 20:16:00 +08:00
committed by GitHub
parent 56c206da68
commit 124f6ae1df
3 changed files with 158 additions and 24 deletions

View File

@@ -0,0 +1,103 @@
<!-- Generated by SpecStory -->
# 在原图加载出错时,不要只在 PhotoViewer 展示错误警告,而是继续展示 thumbnail (2025-07-07 08:26Z)
_**User**_
在原图加载出错时,不要只在 PhotoViewer 展示错误警告,而是继续展示 thumbnail 并将警告以类似加载状态角标的形式告知。
---
_**Assistant**_
我需要分析现有代码,然后修改错误处理逻辑,使其在原图加载失败时继续显示缩略图并显示警告信息。
首先让我查看当前的错误处理逻辑: 现在让我查看 LoadingIndicator 组件的接口,确认它支持显示错误状态: 我需要先更新 LoadingIndicator 组件,添加错误状态的支持:
```
```
```
```
现在我需要更新 LoadingIndicator 的渲染逻辑,添加错误状态的显示: 我需要更新 LoadingIndicator 的渲染逻辑,在错误状态时显示错误图标和消息:
```
```
我需要查看现有的翻译 key使用正确的翻译键 现在我需要修复翻译键的使用,使用已有的翻译键:
```
```
现在我需要修改 ProgressiveImage 组件,移除原来的错误处理逻辑,改为继续显示缩略图并显示错误状态:
```
```
现在我需要修改加载逻辑,当发生错误时显示错误状态: 我需要修改错误处理逻辑,在发生错误时显示错误状态而不是设置错误状态:
```
```
我需要修复依赖项问题:
```
```
```
```
现在我需要修改渲染逻辑,确保在错误状态时仍然显示缩略图,而不是空白的错误页面: 我需要修改缩略图的显示逻辑,使其在错误状态时也保持显示:
```
```
现在我需要找到 WebGLImageViewer 组件结束的位置,然后修改 Live Photo 组件的显示逻辑: 我需要修改 Live Photo 组件的显示逻辑,确保在错误状态时不显示:
```
```
现在让我检查一下修改后的代码是否有语法错误: 很好!现在让我总结一下我所做的修改:
## 修改总结
我已经成功修改了错误处理逻辑,使得在原图加载失败时,系统会继续显示缩略图并以类似加载状态角标的形式显示错误警告。具体修改如下:
### 1. 更新 `LoadingIndicator.tsx`
- **添加错误状态支持**:在 `LoadingState` 接口中添加了 `isError``errorMessage` 字段
- **更新渲染逻辑**:添加了错误状态的显示,使用红色警告图标和错误消息
- **视觉样式**:错误状态显示为红色警告图标,与加载状态的旋转图标区分开
### 2. 更新 `ProgressiveImage.tsx`
- **移除阻塞性错误处理**:注释掉原来的错误处理逻辑,该逻辑会完全替换内容为错误页面
- **修改错误处理逻辑**:在加载失败时,调用 `loadingIndicatorRef` 显示错误状态,而不是设置阻塞性错误
- **更新缩略图显示条件**:缩略图现在在错误状态时也会继续显示(`(!isHighResImageRendered || error)`
- **条件渲染优化**WebGL 图片查看器、Live Photo 组件和备用图片现在只在非错误状态时显示
### 3. 用户体验改进
- **无中断体验**:用户在原图加载失败时仍能看到缩略图,不会看到空白的错误页面
- **清晰的错误指示**:右下角的错误角标会清楚地告知用户原图加载失败的状态
- **一致的视觉语言**:错误状态使用与加载状态相同的角标位置和样式,保持界面一致性
这样修改后,当原图加载失败时:
1. 缩略图会继续显示,保证用户始终能看到图片内容
2. 右下角会显示红色警告图标的错误角标,告知用户原图加载失败
3. 错误信息会使用已有的翻译键 `photo.error.loading`
4. 整体体验更加流畅,不会因为加载失败而中断用户的浏览流程
---

View File

@@ -15,6 +15,10 @@ interface LoadingState {
isWebGLLoading?: boolean // WebGL 纹理是否正在加载
webglMessage?: string // WebGL 加载消息
webglQuality?: 'high' | 'medium' | 'low' | 'unknown' // WebGL 纹理质量
// 错误状态
isError?: boolean // 是否出现错误
errorMessage?: string // 错误消息
}
interface LoadingIndicatorRef {
@@ -34,6 +38,9 @@ const initialLoadingState: LoadingState = {
isWebGLLoading: false,
webglMessage: undefined,
webglQuality: 'unknown',
isError: false,
errorMessage: undefined,
}
export const LoadingIndicator = ({
@@ -73,10 +80,22 @@ export const LoadingIndicator = ({
<div className="pointer-events-none absolute right-4 bottom-4 z-10 rounded-xl border border-white/10 bg-black/80 px-3 py-2 backdrop-blur">
<div className="flex items-center gap-3 text-white">
<div className="relative">
<div className="i-mingcute-loading-3-line animate-spin text-lg" />
{loadingState.isError ? (
<div className="i-mingcute-warning-line text-lg text-red-400" />
) : (
<div className="i-mingcute-loading-3-line animate-spin text-lg" />
)}
</div>
<div className="flex min-w-0 flex-col gap-0.5">
{loadingState.isConverting ? (
{loadingState.isError ? (
// 错误状态
<>
<p className="text-xs font-medium text-red-400">
{loadingState.errorMessage || t('photo.error.loading')}
</p>
<p className="text-xs text-white/70">{t('loading.default')}</p>
</>
) : loadingState.isConverting ? (
// 视频转换状态
<>
<p className="text-xs font-medium text-white tabular-nums">

View File

@@ -129,6 +129,13 @@ export const ProgressiveImage = ({
} catch (loadError) {
console.error('Failed to load image:', loadError)
setError(true)
// 显示错误状态,而不是完全隐藏图片
loadingIndicatorRef.current?.updateLoadingState({
isVisible: true,
isError: true,
errorMessage: t('photo.error.loading'),
})
}
}
@@ -146,6 +153,8 @@ export const ProgressiveImage = ({
onError,
isCurrentImage,
onBlobSrcChange,
loadingIndicatorRef,
t,
])
const onTransformed = useCallback(
@@ -219,7 +228,7 @@ export const ProgressiveImage = ({
webglQuality: quality,
})
},
[t],
[t, loadingIndicatorRef],
)
const [isThumbnailLoaded, setIsThumbnailLoaded] = useState(false)
@@ -230,21 +239,22 @@ export const ProgressiveImage = ({
const showContextMenu = useShowContextMenu()
if (error) {
return (
<div
className={clsxm(
'flex items-center justify-center bg-material-opaque',
className,
)}
>
<div className="text-text-secondary text-center">
<i className="i-mingcute-image-line mb-2 text-4xl" />
<p className="text-sm">{t('photo.error.loading')}</p>
</div>
</div>
)
}
// 移除原来的错误处理逻辑,改为在加载失败时显示错误状态并继续显示缩略图
// if (error) {
// return (
// <div
// className={clsxm(
// 'flex items-center justify-center bg-material-opaque',
// className,
// )}
// >
// <div className="text-text-secondary text-center">
// <i className="i-mingcute-image-line mb-2 text-4xl" />
// <p className="text-sm">{t('photo.error.loading')}</p>
// </div>
// </div>
// )
// }
return (
<div
@@ -255,8 +265,8 @@ export const ProgressiveImage = ({
onTouchStart={handleLongPressStart}
onTouchEnd={handleLongPressEnd}
>
{/* 缩略图 */}
{thumbnailSrc && !isHighResImageRendered && (
{/* 缩略图 - 在高分辨率图片未加载或加载失败时显示 */}
{thumbnailSrc && (!isHighResImageRendered || error) && (
<img
ref={thumbnailRef}
src={thumbnailSrc}
@@ -270,7 +280,8 @@ export const ProgressiveImage = ({
/>
)}
{highResLoaded && blobSrc && isCurrentImage && (
{/* 高分辨率图片 - 只在成功加载且非错误状态时显示 */}
{highResLoaded && blobSrc && isCurrentImage && !error && (
<WebGLImageViewer
src={blobSrc}
className="absolute inset-0 h-full w-full"
@@ -378,10 +389,11 @@ export const ProgressiveImage = ({
/>
)}
{/* Live Photo 组件 */}
{/* Live Photo 组件 - 只在非错误状态时显示 */}
{isLivePhoto &&
livePhotoVideoUrl &&
isCurrentImage &&
!error &&
imageLoaderManagerRef.current && (
<LivePhoto
ref={livePhotoRef}
@@ -393,8 +405,8 @@ export const ProgressiveImage = ({
/>
)}
{/* 备用图片(当 WebGL 不可用时) */}
{!canUseWebGL && highResLoaded && blobSrc && (
{/* 备用图片(当 WebGL 不可用时) - 只在非错误状态时显示 */}
{!canUseWebGL && highResLoaded && blobSrc && !error && (
<div className="pointer-events-none absolute inset-0 z-10 flex flex-col items-center justify-center gap-2 bg-black/20">
<i className="i-mingcute-warning-line mb-2 text-4xl" />
<span className="text-center text-sm text-white">