fix(web): 审稿页支持切换章节并打通验收后审下一章

This commit is contained in:
Yaojia Wang
2026-06-29 16:52:43 +02:00
parent a2849bf6b7
commit ab3f9e17b1
3 changed files with 83 additions and 14 deletions

View File

@@ -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 .../draft404→null→"");否则 final_text 为空 → accept 422。
const draft = await fetchDraft(id, chapterNo);
return (
<ReviewReport
project={project}
chapterNo={chapterNo}
initialReview={latestReview(history.reviews)}
initialDraft={draft?.content ?? ""}
/>
);
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 .../draft404→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 {

View File

@@ -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({
<ArrowRight className="h-4 w-4" aria-hidden="true" />
{chapterNo + 1}
</Link>
<Link
href={reviewChapterHref(projectId, chapterNo + 1)}
className={buttonClass({ variant: "secondary" })}
>
<ClipboardCheck className="h-4 w-4" aria-hidden="true" />
{chapterNo + 1}
</Link>
<Link
href={`/projects/${projectId}/outline`}
className={buttonClass({ variant: "secondary" })}

View File

@@ -2,6 +2,7 @@
import { useEffect, useMemo, useRef, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import {
AlertTriangle,
CheckCircle2,
@@ -12,6 +13,9 @@ import {
import { AppShell } from "@/components/AppShell";
import { Badge } from "@/components/ui/Badge";
import { Button } from "@/components/ui/Button";
import { Select } from "@/components/ui/Select";
import { reviewChapterHref, reviewChapterOptions } from "@/lib/review/chapterNav";
import type { ChapterEntry } from "@/lib/workbench/chapter";
import type { ProjectResponse, ReviewHistoryItem } from "@/lib/api/types";
import { friendlyError } from "@/lib/errors/messages";
import {
@@ -62,6 +66,8 @@ interface ReviewReportProps {
chapterNo: number;
initialReview: ReviewHistoryItem | undefined;
initialDraft: string;
// 章节导航目录(大纲章节):用于报告头部的「切换审稿章节」选择器。
chapters?: ChapterEntry[];
}
// 审稿报告页主体UX §6.4 / §8.3 / §9
@@ -72,7 +78,9 @@ export function ReviewReport({
chapterNo,
initialReview,
initialDraft,
chapters,
}: ReviewReportProps) {
const router = useRouter();
const review = useReviewStream();
const accept = useAccept();
const conflictFix = useRefine(); // 采纳无补丁冲突时,临时调 AI 重写定位段
@@ -403,6 +411,23 @@ export function ReviewReport({
<h1 className="min-w-0 flex-1 truncate font-serif text-lg text-ink">
{chapterNo} 稿
</h1>
<Select
controlSize="sm"
aria-label="切换审稿章节"
className="hidden max-w-[12rem] shrink-0 sm:block"
value={chapterNo}
onChange={(e) =>
router.push(
reviewChapterHref(project.id, Number(e.target.value)),
)
}
>
{reviewChapterOptions(chapters ?? [], chapterNo).map((opt) => (
<option key={opt.no} value={opt.no}>
{opt.label}
</option>
))}
</Select>
<span className="hidden shrink-0 font-mono text-xs text-ink-soft sm:inline">
{conflictCount}
</span>