fix(review): 验收冲突显示与闸门对齐 — 验收报 CONFLICT_UNRESOLVED 时回拉最新审稿并回灌冲突;未审稿不再误显「可直接验收」

This commit is contained in:
Yaojia Wang
2026-06-24 16:17:54 +02:00
parent d0c301349c
commit a4ef250fc9
4 changed files with 37 additions and 8 deletions

View File

@@ -9,6 +9,9 @@ import { ThinkingIndicator } from "@/components/ThinkingIndicator";
interface AcceptPanelProps {
projectId: string;
chapterNo: number;
// 本页是否已有审稿结果(进页带留痕 或 本次重审完成)。用于区分
// 「审过且 0 冲突→可直接验收」与「未审稿→先审稿」,避免把"未知"当"无冲突"。
reviewed: boolean;
conflictCount: number;
unresolvedCount: number;
// R3当前伏笔建议数验收前清单提醒只读不自动落库
@@ -22,6 +25,7 @@ interface AcceptPanelProps {
export function AcceptPanel({
projectId,
chapterNo,
reviewed,
conflictCount,
unresolvedCount,
foreshadowCount,
@@ -89,7 +93,9 @@ export function AcceptPanel({
<>
<p className="mb-2 text-xs text-ink-soft">
{conflictCount === 0
? "无冲突,可直接验收。"
? reviewed
? "无冲突,可直接验收。"
: "尚未审稿——建议先点「重新审稿」确认本章无冲突,再验收。"
: "全部冲突已裁决,可验收本章。"}
</p>
{/* R3验收前「本次将更新」清单预期落库口径以验收回执为准。 */}

View File

@@ -16,10 +16,12 @@ import {
type Verdict,
} from "@/lib/review/decisions";
import {
latestReview,
normalizeConflicts,
normalizeForeshadowSug,
normalizePace,
} from "@/lib/review/history";
import { api } from "@/lib/api/client";
import {
displayOrder,
groupConflicts,
@@ -315,8 +317,26 @@ export function ReviewReport({
finalText,
drafts,
);
if (outcome.missingIndices.length > 0) {
setMissing(new Set(outcome.missingIndices));
if (!outcome.conflictUnresolved) {
if (outcome.missingIndices.length > 0) {
setMissing(new Set(outcome.missingIndices));
}
return;
}
// 验收闸读的是「持久化的最新审稿」;本页冲突可能为空/过期(显示 0 冲突却被拦)。
// 回拉最新审稿留痕 → 回灌冲突 + 重置裁决草稿,把「看不见的冲突」显式呈现供裁决。
const { data } = await api.GET(
"/projects/{project_id}/chapters/{chapter_no}/reviews",
{ params: { path: { project_id: project.id, chapter_no: chapterNo } } },
);
const fresh = normalizeConflicts(latestReview(data?.reviews));
setMissing(new Set(outcome.missingIndices));
if (fresh.length > 0) {
review.seed({ conflicts: fresh, foreshadow, pace, style });
setDrafts(emptyDecisions(fresh.length));
toast("审稿报告已刷新:检测到未裁决冲突,请裁决后再验收", "error");
} else {
toast("尚有冲突未裁决,请先「重新审稿」查看", "error");
}
};
@@ -520,6 +540,7 @@ export function ReviewReport({
<AcceptPanel
projectId={project.id}
chapterNo={chapterNo}
reviewed={initialReview !== undefined || review.state.phase === "done"}
conflictCount={conflictCount}
unresolvedCount={resolved ? 0 : unresolved}
foreshadowCount={foreshadow.length}

View File

@@ -13,6 +13,9 @@ export interface AcceptOutcome {
result: AcceptResponse | null;
// 409 CONFLICT_UNRESOLVED 缺判下标(高亮报告卡)。
missingIndices: number[];
// 后端验收闸报「有未裁决冲突」——即便本页当前未显示冲突(快照过期/未审稿)。
// 调用方据此回拉持久化最新审稿、回灌冲突供裁决(修「显示 0 冲突却被拦」)。
conflictUnresolved: boolean;
}
export interface UseAccept {
@@ -58,16 +61,15 @@ export function useAccept(): UseAccept {
const code = env?.error?.code;
const missing = env?.error?.details?.missing_conflict_indices ?? [];
if (code === "CONFLICT_UNRESOLVED") {
toast("尚有冲突未裁决,请先处理高亮项", "error");
return { result: null, missingIndices: missing };
return { result: null, missingIndices: missing, conflictUnresolved: true };
}
toast("验收失败,请重试(正文未丢失)", "error");
return { result: null, missingIndices: [] };
return { result: null, missingIndices: [], conflictUnresolved: false };
}
setResult(data);
setStatus("accepted");
toast("本章已验收", "success");
return { result: data, missingIndices: [] };
return { result: data, missingIndices: [], conflictUnresolved: false };
},
[toast],
);

File diff suppressed because one or more lines are too long