From ab3f9e17b1b56c5aa6d09a7a7668c453ea9957dc Mon Sep 17 00:00:00 2001 From: Yaojia Wang Date: Mon, 29 Jun 2026 16:52:43 +0200 Subject: [PATCH] =?UTF-8?q?fix(web):=20=E5=AE=A1=E7=A8=BF=E9=A1=B5?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=88=87=E6=8D=A2=E7=AB=A0=E8=8A=82=E5=B9=B6?= =?UTF-8?q?=E6=89=93=E9=80=9A=E9=AA=8C=E6=94=B6=E5=90=8E=E5=AE=A1=E4=B8=8B?= =?UTF-8?q?=E4=B8=80=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/app/projects/[id]/review/page.tsx | 62 ++++++++++++++++----- apps/web/components/review/AcceptPanel.tsx | 10 +++- apps/web/components/review/ReviewReport.tsx | 25 +++++++++ 3 files changed, 83 insertions(+), 14 deletions(-) diff --git a/apps/web/app/projects/[id]/review/page.tsx b/apps/web/app/projects/[id]/review/page.tsx index 5706c80..4903df8 100644 --- a/apps/web/app/projects/[id]/review/page.tsx +++ b/apps/web/app/projects/[id]/review/page.tsx @@ -1,8 +1,15 @@ import { notFound } from "next/navigation"; import { ReviewReport } from "@/components/review/ReviewReport"; -import { fetchDraft, fetchProject, fetchReviews } from "@/lib/api/server"; +import { + fetchDraft, + fetchOutline, + fetchProject, + fetchReviews, +} from "@/lib/api/server"; +import type { ProjectResponse } from "@/lib/api/types"; import { latestReview } from "@/lib/review/history"; +import type { ChapterEntry } from "@/lib/workbench/chapter"; interface PageProps { params: Promise<{ id: string }>; @@ -17,22 +24,51 @@ export default async function ReviewPage({ params, searchParams }: PageProps) { const { id } = await params; const { chapter } = await searchParams; const chapterNo = parsePositiveInt(chapter) ?? DEFAULT_CHAPTER_NO; + + // 仅当项目确实取不到时才判 404;审稿/草稿/大纲等次级拉取的瞬时错误不应把整页变成「找不到」。 + let project: ProjectResponse; try { - const project = await fetchProject(id); - const history = await fetchReviews(id, chapterNo); - // 终稿初值取已存草稿(GET .../draft,404→null→"");否则 final_text 为空 → accept 422。 - const draft = await fetchDraft(id, chapterNo); - return ( - - ); + project = await fetchProject(id); } catch { notFound(); } + + // 审稿留痕(GET .../reviews,失败→空历史)。 + let initialReview; + try { + const history = await fetchReviews(id, chapterNo); + initialReview = latestReview(history.reviews); + } catch { + initialReview = undefined; + } + + // 终稿初值取已存草稿(GET .../draft,404→null→"");否则 final_text 为空 → accept 422。 + let initialDraft = ""; + try { + const draft = await fetchDraft(id, chapterNo); + initialDraft = draft?.content ?? ""; + } catch { + initialDraft = ""; + } + + // 大纲章节作章节导航目录(fetchOutline 已对空/错误降级为 []);首个节拍当短标题。 + const chapters: ChapterEntry[] = (await fetchOutline(id)).map((c) => ({ + no: c.no, + title: c.beats?.[0], + })); + + return ( + // key=章号:客户端切章(?chapter=N 变化)时强制重挂载,用新章审稿/草稿刷新状态, + // 避免 ReviewReport 内 useState 基线沿用上一章。 + + ); } function parsePositiveInt(raw: string | undefined): number | null { diff --git a/apps/web/components/review/AcceptPanel.tsx b/apps/web/components/review/AcceptPanel.tsx index 614c085..a16b332 100644 --- a/apps/web/components/review/AcceptPanel.tsx +++ b/apps/web/components/review/AcceptPanel.tsx @@ -1,13 +1,14 @@ "use client"; import Link from "next/link"; -import { ArrowRight, CheckCircle2, ListTree } from "lucide-react"; +import { ArrowRight, CheckCircle2, ClipboardCheck, ListTree } from "lucide-react"; import { Button } from "@/components/ui/Button"; import { StatusNote } from "@/components/ui/StatusNote"; import type { AcceptResponse } from "@/lib/api/types"; import { buildAcceptPreview } from "@/lib/review/accept-preview"; import { ThinkingIndicator } from "@/components/ThinkingIndicator"; +import { reviewChapterHref } from "@/lib/review/chapterNav"; import { buttonClass } from "@/lib/ui/variants"; interface AcceptPanelProps { @@ -85,6 +86,13 @@ export function AcceptPanel({