diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx
index ea774bb..48d8def 100644
--- a/apps/web/app/page.tsx
+++ b/apps/web/app/page.tsx
@@ -2,6 +2,7 @@ import Link from "next/link";
import { BookOpen, Plus } from "lucide-react";
import { AppShell } from "@/components/AppShell";
+import { BackendDownNotice } from "@/components/BackendDownNotice";
import { ProjectLibrary } from "@/components/projects/ProjectLibrary";
import { EmptyState } from "@/components/ui/EmptyState";
import { PageHeader } from "@/components/ui/PageHeader";
@@ -38,9 +39,7 @@ export default async function DashboardPage() {
/>
{loadError ? (
-
- 无法连接后端服务,请确认 API 已启动后刷新。
-
+
) : projects.length === 0 ? (
);
} catch {
- notFound();
+ return ;
}
}
diff --git a/apps/web/app/projects/[id]/foreshadow/page.tsx b/apps/web/app/projects/[id]/foreshadow/page.tsx
index 832097c..8d80e12 100644
--- a/apps/web/app/projects/[id]/foreshadow/page.tsx
+++ b/apps/web/app/projects/[id]/foreshadow/page.tsx
@@ -1,7 +1,9 @@
import { notFound } from "next/navigation";
+import { BackendDownNotice } from "@/components/BackendDownNotice";
import { ForeshadowBoard } from "@/components/foreshadow/ForeshadowBoard";
import { fetchForeshadow, fetchProject } from "@/lib/api/server";
+import type { ForeshadowView, ProjectResponse } from "@/lib/api/types";
interface PageProps {
params: Promise<{ id: string }>;
@@ -11,13 +13,30 @@ interface PageProps {
// ForeshadowBoard(Client)按 status 分四泳道并承载登记/状态变更交互。
export default async function ForeshadowPage({ params }: PageProps) {
const { id } = await params;
+
+ // 仅当项目确实不存在(404)才判 notFound;后端不可用等其它失败降级为「后端不可达」提示,
+ // 不把整页变成「找不到页面」。
+ let project: ProjectResponse;
try {
- const project = await fetchProject(id);
- const board = await fetchForeshadow(id);
- return (
-
- );
- } catch {
- notFound();
+ project = await fetchProject(id);
+ } catch (err) {
+ if (isNotFoundError(err)) notFound();
+ return ;
}
+
+ // 伏笔看板(GET .../foreshadow)。瞬时错误降级为空看板,不阻塞进页。
+ let initialItems: ForeshadowView[] = [];
+ try {
+ const board = await fetchForeshadow(id);
+ initialItems = board.foreshadow ?? [];
+ } catch {
+ initialItems = [];
+ }
+
+ return ;
+}
+
+// fetchProject 抛错信息形如「请求失败 404: ...」;据此区分「项目不存在」与「后端不可用」。
+function isNotFoundError(err: unknown): boolean {
+ return err instanceof Error && err.message.includes("请求失败 404");
}
diff --git a/apps/web/app/projects/[id]/outline/page.tsx b/apps/web/app/projects/[id]/outline/page.tsx
index 3a12da2..0de0bd5 100644
--- a/apps/web/app/projects/[id]/outline/page.tsx
+++ b/apps/web/app/projects/[id]/outline/page.tsx
@@ -1,7 +1,9 @@
import { notFound } from "next/navigation";
+import { BackendDownNotice } from "@/components/BackendDownNotice";
import { OutlineEditor } from "@/components/outline/OutlineEditor";
import { fetchOutline, fetchProject } from "@/lib/api/server";
+import type { ProjectResponse } from "@/lib/api/types";
interface PageProps {
params: Promise<{ id: string }>;
@@ -12,13 +14,22 @@ interface PageProps {
// 仍按需触发 POST .../outline 生成/覆盖所显章节。
export default async function OutlinePage({ params }: PageProps) {
const { id } = await params;
+
+ // 仅当项目确实不存在(404)才判 notFound;后端不可用等其它失败降级为「后端不可达」提示,
+ // 不把整页变成「找不到页面」。次级拉取(大纲)的瞬时错误已在 fetchOutline 内降级为空。
+ let project: ProjectResponse;
try {
- const project = await fetchProject(id);
- const initialChapters = await fetchOutline(id);
- return (
-
- );
- } catch {
- notFound();
+ project = await fetchProject(id);
+ } catch (err) {
+ if (isNotFoundError(err)) notFound();
+ return ;
}
+
+ const initialChapters = await fetchOutline(id);
+ return ;
+}
+
+// fetchProject 抛错信息形如「请求失败 404: ...」;据此区分「项目不存在」与「后端不可用」。
+function isNotFoundError(err: unknown): boolean {
+ return err instanceof Error && err.message.includes("请求失败 404");
}
diff --git a/apps/web/app/projects/[id]/toolbox/page.tsx b/apps/web/app/projects/[id]/toolbox/page.tsx
index becf65d..9867bb5 100644
--- a/apps/web/app/projects/[id]/toolbox/page.tsx
+++ b/apps/web/app/projects/[id]/toolbox/page.tsx
@@ -1,5 +1,4 @@
-import { notFound } from "next/navigation";
-
+import { BackendDownNotice } from "@/components/BackendDownNotice";
import { ToolboxPage } from "@/components/toolbox/ToolboxPage";
import { fetchProject, fetchToolbox } from "@/lib/api/server";
@@ -29,6 +28,6 @@ export default async function ToolboxRoutePage({
/>
);
} catch {
- notFound();
+ return ;
}
}
diff --git a/apps/web/app/settings/providers/page.tsx b/apps/web/app/settings/providers/page.tsx
index 0c855fc..b7ebdee 100644
--- a/apps/web/app/settings/providers/page.tsx
+++ b/apps/web/app/settings/providers/page.tsx
@@ -1,4 +1,5 @@
import { AppShell } from "@/components/AppShell";
+import { BackendDownNotice } from "@/components/BackendDownNotice";
import { ProvidersSettings } from "@/components/settings/ProvidersSettings";
import { PageHeader } from "@/components/ui/PageHeader";
import { fetchKimiOauthStatus, fetchProviders } from "@/lib/api/server";
@@ -34,9 +35,7 @@ export default async function ProvidersSettingsPage() {
description="配置写手、分析、轻量三类能力档位的路由,并管理 API Key 与 Kimi Code OAuth。"
/>
{loadError ? (
-
- 无法连接后端服务,请确认 API 已启动后刷新。
-
+
) : (
)}