mirror of
https://github.com/Afilmory/afilmory
synced 2026-04-24 23:05:05 +00:00
refactor: enhance error handling in image loading (#42)
This commit is contained in:
103
.specstory/history/2025-07-07_08-26Z-在原图加载出错时继续展示缩略图.md
Normal file
103
.specstory/history/2025-07-07_08-26Z-在原图加载出错时继续展示缩略图.md
Normal 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. 整体体验更加流畅,不会因为加载失败而中断用户的浏览流程
|
||||
|
||||
---
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user