feat(web): 用户工作台 - 产品化壳层、内容库布局与积分入口

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ddCat
2026-09-16 22:59:45 +08:00
parent f52702b70f
commit 969e2831b2
46 changed files with 5355 additions and 944 deletions

View File

@@ -8,6 +8,7 @@ type CachedResourceImageProps = Omit<ImgHTMLAttributes<HTMLImageElement>, "src">
storageKey?: string;
src?: string;
fallback?: ReactNode;
loadingFallback?: ReactNode;
eager?: boolean;
};
@@ -15,7 +16,7 @@ type CachedResourceImageProps = Omit<ImgHTMLAttributes<HTMLImageElement>, "src">
* 资源图片优先读取按用户隔离的本地 Blob 缓存,避免刷新后再次从对象存储下载。
* 本地 image: 类型的 storageKey 也会自动从 LocalForage 恢复有效的 Object URL。
*/
export function CachedResourceImage({ storageKey, src = "", fallback = null, eager = false, onError, ...props }: CachedResourceImageProps) {
export function CachedResourceImage({ storageKey, src = "", fallback = null, loadingFallback = fallback, eager = false, onError, ...props }: CachedResourceImageProps) {
const remoteResource = Boolean(resourceIdFromStorageKey(storageKey));
const localImageResource = Boolean(storageKey && storageKey.startsWith("image:"));
const targetRef = useRef<HTMLSpanElement>(null);
@@ -61,11 +62,15 @@ export function CachedResourceImage({ storageKey, src = "", fallback = null, eag
const resolve = cacheResourceObjectUrl(storageKey);
void resolve
.then((url) => {
if (!cancelled) setCachedSrc(url || src);
if (!cancelled) {
setCachedSrc(url || src);
setCacheFailed(!url && !src);
}
})
.catch(() => {
if (!cancelled) {
setCacheFailed(true);
// 缓存读取失败时仍允许原始地址加载,真正的解码失败再显示占位。
setCacheFailed(!src);
setCachedSrc(src);
}
});
@@ -120,7 +125,7 @@ export function CachedResourceImage({ storageKey, src = "", fallback = null, eag
}
return (
<span ref={targetRef} className="cached-resource-image-shell">
{cachedSrc && !(cacheFailed && !src) ? <img {...props} src={cachedSrc} onError={handleImgError} /> : fallback}
{cachedSrc && !cacheFailed ? <img {...props} src={cachedSrc} onError={handleImgError} /> : cacheFailed ? fallback : loadingFallback}
</span>
);
}