fix(review): 验收冲突显示与闸门对齐 — 验收报 CONFLICT_UNRESOLVED 时回拉最新审稿并回灌冲突;未审稿不再误显「可直接验收」
This commit is contained in:
@@ -9,6 +9,9 @@ import { ThinkingIndicator } from "@/components/ThinkingIndicator";
|
|||||||
interface AcceptPanelProps {
|
interface AcceptPanelProps {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
chapterNo: number;
|
chapterNo: number;
|
||||||
|
// 本页是否已有审稿结果(进页带留痕 或 本次重审完成)。用于区分
|
||||||
|
// 「审过且 0 冲突→可直接验收」与「未审稿→先审稿」,避免把"未知"当"无冲突"。
|
||||||
|
reviewed: boolean;
|
||||||
conflictCount: number;
|
conflictCount: number;
|
||||||
unresolvedCount: number;
|
unresolvedCount: number;
|
||||||
// R3:当前伏笔建议数(验收前清单提醒,只读不自动落库)。
|
// R3:当前伏笔建议数(验收前清单提醒,只读不自动落库)。
|
||||||
@@ -22,6 +25,7 @@ interface AcceptPanelProps {
|
|||||||
export function AcceptPanel({
|
export function AcceptPanel({
|
||||||
projectId,
|
projectId,
|
||||||
chapterNo,
|
chapterNo,
|
||||||
|
reviewed,
|
||||||
conflictCount,
|
conflictCount,
|
||||||
unresolvedCount,
|
unresolvedCount,
|
||||||
foreshadowCount,
|
foreshadowCount,
|
||||||
@@ -89,7 +93,9 @@ export function AcceptPanel({
|
|||||||
<>
|
<>
|
||||||
<p className="mb-2 text-xs text-ink-soft">
|
<p className="mb-2 text-xs text-ink-soft">
|
||||||
{conflictCount === 0
|
{conflictCount === 0
|
||||||
|
? reviewed
|
||||||
? "无冲突,可直接验收。"
|
? "无冲突,可直接验收。"
|
||||||
|
: "尚未审稿——建议先点「重新审稿」确认本章无冲突,再验收。"
|
||||||
: "全部冲突已裁决,可验收本章。"}
|
: "全部冲突已裁决,可验收本章。"}
|
||||||
</p>
|
</p>
|
||||||
{/* R3:验收前「本次将更新」清单(预期,落库口径以验收回执为准)。 */}
|
{/* R3:验收前「本次将更新」清单(预期,落库口径以验收回执为准)。 */}
|
||||||
|
|||||||
@@ -16,10 +16,12 @@ import {
|
|||||||
type Verdict,
|
type Verdict,
|
||||||
} from "@/lib/review/decisions";
|
} from "@/lib/review/decisions";
|
||||||
import {
|
import {
|
||||||
|
latestReview,
|
||||||
normalizeConflicts,
|
normalizeConflicts,
|
||||||
normalizeForeshadowSug,
|
normalizeForeshadowSug,
|
||||||
normalizePace,
|
normalizePace,
|
||||||
} from "@/lib/review/history";
|
} from "@/lib/review/history";
|
||||||
|
import { api } from "@/lib/api/client";
|
||||||
import {
|
import {
|
||||||
displayOrder,
|
displayOrder,
|
||||||
groupConflicts,
|
groupConflicts,
|
||||||
@@ -315,9 +317,27 @@ export function ReviewReport({
|
|||||||
finalText,
|
finalText,
|
||||||
drafts,
|
drafts,
|
||||||
);
|
);
|
||||||
|
if (!outcome.conflictUnresolved) {
|
||||||
if (outcome.missingIndices.length > 0) {
|
if (outcome.missingIndices.length > 0) {
|
||||||
setMissing(new Set(outcome.missingIndices));
|
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");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 终稿按空行切段(大文本:仅在 finalText 变化时重算,不在每次 render split)。
|
// 终稿按空行切段(大文本:仅在 finalText 变化时重算,不在每次 render split)。
|
||||||
@@ -520,6 +540,7 @@ export function ReviewReport({
|
|||||||
<AcceptPanel
|
<AcceptPanel
|
||||||
projectId={project.id}
|
projectId={project.id}
|
||||||
chapterNo={chapterNo}
|
chapterNo={chapterNo}
|
||||||
|
reviewed={initialReview !== undefined || review.state.phase === "done"}
|
||||||
conflictCount={conflictCount}
|
conflictCount={conflictCount}
|
||||||
unresolvedCount={resolved ? 0 : unresolved}
|
unresolvedCount={resolved ? 0 : unresolved}
|
||||||
foreshadowCount={foreshadow.length}
|
foreshadowCount={foreshadow.length}
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ export interface AcceptOutcome {
|
|||||||
result: AcceptResponse | null;
|
result: AcceptResponse | null;
|
||||||
// 409 CONFLICT_UNRESOLVED 缺判下标(高亮报告卡)。
|
// 409 CONFLICT_UNRESOLVED 缺判下标(高亮报告卡)。
|
||||||
missingIndices: number[];
|
missingIndices: number[];
|
||||||
|
// 后端验收闸报「有未裁决冲突」——即便本页当前未显示冲突(快照过期/未审稿)。
|
||||||
|
// 调用方据此回拉持久化最新审稿、回灌冲突供裁决(修「显示 0 冲突却被拦」)。
|
||||||
|
conflictUnresolved: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UseAccept {
|
export interface UseAccept {
|
||||||
@@ -58,16 +61,15 @@ export function useAccept(): UseAccept {
|
|||||||
const code = env?.error?.code;
|
const code = env?.error?.code;
|
||||||
const missing = env?.error?.details?.missing_conflict_indices ?? [];
|
const missing = env?.error?.details?.missing_conflict_indices ?? [];
|
||||||
if (code === "CONFLICT_UNRESOLVED") {
|
if (code === "CONFLICT_UNRESOLVED") {
|
||||||
toast("尚有冲突未裁决,请先处理高亮项", "error");
|
return { result: null, missingIndices: missing, conflictUnresolved: true };
|
||||||
return { result: null, missingIndices: missing };
|
|
||||||
}
|
}
|
||||||
toast("验收失败,请重试(正文未丢失)", "error");
|
toast("验收失败,请重试(正文未丢失)", "error");
|
||||||
return { result: null, missingIndices: [] };
|
return { result: null, missingIndices: [], conflictUnresolved: false };
|
||||||
}
|
}
|
||||||
setResult(data);
|
setResult(data);
|
||||||
setStatus("accepted");
|
setStatus("accepted");
|
||||||
toast("本章已验收", "success");
|
toast("本章已验收", "success");
|
||||||
return { result: data, missingIndices: [] };
|
return { result: data, missingIndices: [], conflictUnresolved: false };
|
||||||
},
|
},
|
||||||
[toast],
|
[toast],
|
||||||
);
|
);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user