fix(web): 链相位语义色/失败可重试 + 模板填入中文标签/删除可撤销/工具卡文案 + 模板库 PageHeader

This commit is contained in:
Yaojia Wang
2026-06-30 08:56:23 +02:00
parent 6bb3559e25
commit 164e98887e
10 changed files with 235 additions and 74 deletions

View File

@@ -5,6 +5,7 @@ import { useCallback, useEffect, useState } from "react";
import { ConflictCard } from "@/components/review/ConflictCard";
import { useToast } from "@/components/Toast";
import { Button } from "@/components/ui/Button";
import { StatusNote } from "@/components/ui/StatusNote";
import { api } from "@/lib/api/client";
import { buildResumeRequest } from "@/lib/chain/chain";
import { friendlyFromApiError } from "@/lib/errors/messages";
@@ -46,36 +47,44 @@ export function ChainAdjudication({
const [drafts, setDrafts] = useState<DecisionDraft[]>([]);
const [resuming, setResuming] = useState(false);
// 拉取 awaiting 章最近审稿冲突。抽成可复用回调,供进场 effect 与「重新加载」按钮共用。
const load = useCallback(async (): Promise<boolean> => {
setLoadState("loading");
try {
const { data, error } = await api.GET(
"/projects/{project_id}/chapters/{chapter_no}/reviews",
{
params: {
path: { project_id: projectId, chapter_no: chapterNo },
},
},
);
if (error || !data) {
setLoadState("error");
return false;
}
const next = normalizeConflicts(latestReview(data.reviews));
setConflicts(next);
setDrafts(emptyDecisions(next.length));
setLoadState("ready");
return true;
} catch {
setLoadState("error");
return false;
}
}, [projectId, chapterNo]);
useEffect(() => {
let active = true;
setLoadState("loading");
void (async () => {
try {
const { data, error } = await api.GET(
"/projects/{project_id}/chapters/{chapter_no}/reviews",
{
params: {
path: { project_id: projectId, chapter_no: chapterNo },
},
},
);
if (!active) return;
if (error || !data) {
setLoadState("error");
return;
}
const next = normalizeConflicts(latestReview(data.reviews));
setConflicts(next);
setDrafts(emptyDecisions(next.length));
setLoadState("ready");
} catch {
if (active) setLoadState("error");
}
// 进场加载:组件已卸载则丢弃结果(避免 setState-after-unmount
if (!active) return;
await load();
})();
return () => {
active = false;
};
}, [projectId, chapterNo]);
}, [load]);
const onVerdict = useCallback((index: number, verdict: Verdict): void => {
setDrafts((prev) => setVerdict(prev, index, verdict));
@@ -116,9 +125,17 @@ export function ChainAdjudication({
}
if (loadState === "error") {
return (
<p className="text-sm text-conflict">
{chapterNo} 稿
</p>
<StatusNote variant="danger" title={`加载第 ${chapterNo} 章审稿冲突失败`}>
<p></p>
<Button
variant="outline"
size="sm"
onClick={() => void load()}
className="mt-3"
>
</Button>
</StatusNote>
);
}