79 lines
2.4 KiB
TypeScript
79 lines
2.4 KiB
TypeScript
import { notFound } from "next/navigation";
|
||
|
||
import { ReviewReport } from "@/components/review/ReviewReport";
|
||
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 }>;
|
||
searchParams: Promise<{ chapter?: string }>;
|
||
}
|
||
|
||
const DEFAULT_CHAPTER_NO = 1;
|
||
|
||
// 审稿报告页(UX §6.4)。Server Component 取项目 + 审稿留痕历史(新→旧);
|
||
// ReviewReport(Client)承载 SSE 重审 / 裁决 / 验收交互。
|
||
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 {
|
||
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 基线沿用上一章。
|
||
<ReviewReport
|
||
key={chapterNo}
|
||
project={project}
|
||
chapterNo={chapterNo}
|
||
initialReview={initialReview}
|
||
initialDraft={initialDraft}
|
||
chapters={chapters}
|
||
/>
|
||
);
|
||
}
|
||
|
||
function parsePositiveInt(raw: string | undefined): number | null {
|
||
if (!raw) return null;
|
||
const n = Number.parseInt(raw, 10);
|
||
return Number.isInteger(n) && n > 0 ? n : null;
|
||
}
|