xiaolongmr commited on
Commit ·
00a6ef3
1
Parent(s): e6c4018
导航加载失败时把原因显示出来,并支持点击重试
Browse files之前 /api/navigation 失败是静默的:整条导航直接空掉,只能翻控制台才知道
是 401、跨域还是网络层错误。现在失败时在导航位置显示一枚琥珀色徽标
「导航加载失败(HTTP 401 / Network Error …)· 点此重试」,点一下重新拉取。
成功时清空错误态;仍保留上次成功结果的缓存兜底。
web/src/components/top-nav.tsx
CHANGED
|
@@ -224,6 +224,10 @@ export function TopNav({ branding = DEFAULT_BRANDING }: { branding?: Branding })
|
|
| 224 |
const [isLoadingApiKey, setIsLoadingApiKey] = useState(false);
|
| 225 |
const [profile, setProfile] = useState<MyProfile | null>(null);
|
| 226 |
const [customNavigation, setCustomNavigation] = useState<NavigationItem[]>([]);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
const [openCustomNavigationId, setOpenCustomNavigationId] = useState("");
|
| 228 |
const [isProfileOpen, setIsProfileOpen] = useState(false);
|
| 229 |
const [nicknameValue, setNicknameValue] = useState("");
|
|
@@ -352,7 +356,10 @@ export function TopNav({ branding = DEFAULT_BRANDING }: { branding?: Branding })
|
|
| 352 |
try {
|
| 353 |
const data = await fetchNavigation();
|
| 354 |
const items = data.items || [];
|
| 355 |
-
if (active)
|
|
|
|
|
|
|
|
|
|
| 356 |
writeCachedNavigation(items);
|
| 357 |
return;
|
| 358 |
} catch (error) {
|
|
@@ -360,6 +367,7 @@ export function TopNav({ branding = DEFAULT_BRANDING }: { branding?: Branding })
|
|
| 360 |
const status = (error as { status?: number }).status;
|
| 361 |
if (status || attempt === 2) {
|
| 362 |
setCustomNavigation(readCachedNavigation());
|
|
|
|
| 363 |
return;
|
| 364 |
}
|
| 365 |
await new Promise((resolve) => window.setTimeout(resolve, 400 * (attempt + 1)));
|
|
@@ -370,7 +378,7 @@ export function TopNav({ branding = DEFAULT_BRANDING }: { branding?: Branding })
|
|
| 370 |
return () => {
|
| 371 |
active = false;
|
| 372 |
};
|
| 373 |
-
}, [session?.role, sessionKey]);
|
| 374 |
|
| 375 |
useEffect(() => {
|
| 376 |
if (!sessionKey) {
|
|
@@ -624,6 +632,19 @@ export function TopNav({ branding = DEFAULT_BRANDING }: { branding?: Branding })
|
|
| 624 |
opacity: activeIndicator.ready ? 1 : 0,
|
| 625 |
}}
|
| 626 |
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 627 |
{customNavigation.map((item) => {
|
| 628 |
const itemKey = `custom:${item.id}`;
|
| 629 |
const active = activeCustomNavigationKey === itemKey;
|
|
|
|
| 224 |
const [isLoadingApiKey, setIsLoadingApiKey] = useState(false);
|
| 225 |
const [profile, setProfile] = useState<MyProfile | null>(null);
|
| 226 |
const [customNavigation, setCustomNavigation] = useState<NavigationItem[]>([]);
|
| 227 |
+
// 导航加载失败时把真实原因摆到界面上:之前失败是静默的,整条导航直接空掉,
|
| 228 |
+
// 只能靠翻控制台才知道是 401、跨域还是网络层错误。
|
| 229 |
+
const [navError, setNavError] = useState<string | null>(null);
|
| 230 |
+
const [navReloadToken, setNavReloadToken] = useState(0);
|
| 231 |
const [openCustomNavigationId, setOpenCustomNavigationId] = useState("");
|
| 232 |
const [isProfileOpen, setIsProfileOpen] = useState(false);
|
| 233 |
const [nicknameValue, setNicknameValue] = useState("");
|
|
|
|
| 356 |
try {
|
| 357 |
const data = await fetchNavigation();
|
| 358 |
const items = data.items || [];
|
| 359 |
+
if (active) {
|
| 360 |
+
setCustomNavigation(items);
|
| 361 |
+
setNavError(null);
|
| 362 |
+
}
|
| 363 |
writeCachedNavigation(items);
|
| 364 |
return;
|
| 365 |
} catch (error) {
|
|
|
|
| 367 |
const status = (error as { status?: number }).status;
|
| 368 |
if (status || attempt === 2) {
|
| 369 |
setCustomNavigation(readCachedNavigation());
|
| 370 |
+
setNavError(status ? `HTTP ${status}` : (error as Error).message || "网络错误");
|
| 371 |
return;
|
| 372 |
}
|
| 373 |
await new Promise((resolve) => window.setTimeout(resolve, 400 * (attempt + 1)));
|
|
|
|
| 378 |
return () => {
|
| 379 |
active = false;
|
| 380 |
};
|
| 381 |
+
}, [session?.role, sessionKey, navReloadToken]);
|
| 382 |
|
| 383 |
useEffect(() => {
|
| 384 |
if (!sessionKey) {
|
|
|
|
| 632 |
opacity: activeIndicator.ready ? 1 : 0,
|
| 633 |
}}
|
| 634 |
/>
|
| 635 |
+
{customNavigation.length === 0 && navError ? (
|
| 636 |
+
<button
|
| 637 |
+
type="button"
|
| 638 |
+
onClick={() => {
|
| 639 |
+
setNavError(null);
|
| 640 |
+
setNavReloadToken((value) => value + 1);
|
| 641 |
+
}}
|
| 642 |
+
title={navError}
|
| 643 |
+
className="relative z-10 inline-flex h-9 shrink-0 items-center gap-1.5 whitespace-nowrap rounded-xl bg-amber-50 px-3 text-[13px] font-semibold text-amber-700 outline-none ring-1 ring-amber-200 transition-colors hover:bg-amber-100 focus-visible:ring-2 focus-visible:ring-amber-400"
|
| 644 |
+
>
|
| 645 |
+
导航加载失败({navError})· 点此重试
|
| 646 |
+
</button>
|
| 647 |
+
) : null}
|
| 648 |
{customNavigation.map((item) => {
|
| 649 |
const itemKey = `custom:${item.id}`;
|
| 650 |
const active = activeCustomNavigationKey === itemKey;
|