fix: 明确退回原因并启用退回操作

This commit is contained in:
NathanAlex7
2026-09-15 11:43:15 +08:00
parent e44716b3ec
commit 090e849a9a
4 changed files with 7 additions and 2 deletions
+3
View File
@@ -10,6 +10,9 @@ assert.match(component, /aria-label=\{`\$\{topic\.title\}评审意见`\}/, "每
assert.match(component, /onChange=\{\(event\)=>setDraft\(event\.target\.value\)\}/, "输入框应允许修改节点意见");
assert.match(component, /draft\.trim\(\)\|\|"已复核本章节数据来源和AI检查结果"/, "正常节点应保存人工输入或明确的默认复核说明");
assert.match(component, /disabled=\{busy\|\|requiredOpinion\}/, "需要说明依据的评审动作不应允许空意见提交");
assert.match(component, /aria-label="退回补充材料原因"/, "退回操作旁应直接提供原因输入框");
assert.match(component, /disabled=\{busy\|\|!hasRun\|\|!returnReason\.trim\(\)\}/, "未填写退回原因时不应允许退回");
assert.match(component, /onComplete\("退回",returnReason\.trim\(\)\)/, "退回原因应随退回决定一并保存");
assert.match(page, /setReviewOpinion\]=useState\(""\)/, "常用模板默认不应冒充人工意见");
console.log("Reviewer workspace input behavior checks passed");
+1
View File
@@ -10,3 +10,4 @@
.xb-pool-card{cursor:default}.xb-pool-card:hover{border-color:#dfe7e2;box-shadow:none}.xb-pool-card.just-submitted{border-color:#55a778;box-shadow:0 0 0 3px #55a7781f}.xb-pool-card.just-submitted>span{background:#dcf3e5;color:#17633f}.xb-pool-actions{display:flex;gap:8px;margin-top:14px}.xb-pool-actions .ra-button{display:inline-flex;align-items:center;gap:6px;text-decoration:none}.xb-pool-card footer b{display:flex;align-items:center;gap:5px;font-size:11px}
.ra-heading-actions{display:flex;gap:10px;align-items:center}.ra-folder-input{display:none}@media(max-width:760px){.ra-heading-actions{width:100%;flex-wrap:wrap}}
.xb-decision-editor{display:block;margin-top:10px}.xb-decision-editor>span{display:block;margin-bottom:6px;color:#51675a;font-size:10px;font-weight:700}.xb-decision-editor textarea{box-sizing:border-box;width:100%;min-height:82px;padding:10px 11px;resize:vertical;border:1px solid #d9e2dc;border-radius:7px;background:#fff;color:#273c30;font:inherit;line-height:1.6;outline:none}.xb-decision-editor textarea:focus{border-color:#3b8d64;box-shadow:0 0 0 3px #3b8d6418}.xb-decision-editor>small{display:block;margin-top:4px;text-align:right;color:#8a958e;font-size:9px}
.xb-return-reason{display:block;width:min(360px,100%)}.xb-return-reason>span{display:block;margin-bottom:5px;color:#587063;font-size:9px;font-weight:700}.xb-return-reason textarea{box-sizing:border-box;width:100%;min-height:58px;padding:8px 10px;resize:vertical;border:1px solid #ccdcd2;border-radius:7px;background:#fff;color:#273c30;font:inherit;line-height:1.5;outline:none}.xb-return-reason textarea:focus{border-color:#3b8d64;box-shadow:0 0 0 3px #3b8d6418}@media(max-width:900px){.xb-return-reason{width:100%}}
+1 -1
View File
@@ -80,7 +80,7 @@ export default function XingbangWorkspace() {
}
async function reportAction(action: string, payload: any = {}) {if (["regenerate", "finalize", "submit"].includes(action) && Object.keys(sectionEdits).length) {setError("请先保存尚未提交的章节修改"); return;} if(action === "submit" && !window.confirm("确认提交正式评审?提交后当前AI预审报告将作为不可覆盖的送审版本保留。")) return; const saved = await act(`${base}/reports/${chosenReport.id}/actions`, {action, revision: chosenReport.revision, payload}, action === "submit" ? "AI预审报告已提交正式评审。" : "报告操作已记录"); if(saved&&action==="submit"){location.href=`/review-pool?submitted=${encodeURIComponent(project.id)}`;return;} if (saved && payload.section_id) setSectionEdits(previous => {const next = {...previous}; delete next[payload.section_id]; return next;});}
async function reviewDecision(findingId:string|null,decision:string,nodeId?:string,opinion?:string){return act(`${base}/review-decisions`,{finding_id:findingId,node_id:nodeId||null,decision,opinion:opinion??reviewOpinion},findingId||nodeId?"本项评审意见已保存。":decision==="批准"?"评审已批准,正在形成评审报告。":"项目已退回客户经理补件。");}
async function completeReview(decision:string){const saved=await reviewDecision(null,decision);if(!saved)return;if(decision==="批准"&&reportSourceRun){const report=await act(`${base}/reports`,{run_id:reportSourceRun.id,template_id:"template.independent-review"},"独立评审报告已生成,请确认后定稿。");if(report)setReportId(report.id);setTab("report");}}
async function completeReview(decision:string,opinion?:string){const saved=await reviewDecision(null,decision,undefined,opinion);if(!saved)return;if(decision==="批准"&&reportSourceRun){const report=await act(`${base}/reports`,{run_id:reportSourceRun.id,template_id:"template.independent-review"},"独立评审报告已生成,请确认后定稿。");if(report)setReportId(report.id);setTab("report");}}
return <div className="ra-app"><AgentNav active="overview" project={project.id} identity={identity}/><main className="xb-overview">
<button className="xb-back" onClick={back}>← 返回项目列表</button><div className="xb-overview-heading"><div><span className={`xb-role-banner role-${identity?.role}`}>当前身份 · {roleTitle}</span><h1>{project.customer}</h1><p>{project.id} · {project.industry} · {project.stage} · {project.data_domain === "demo" ? "演示数据" : "正式数据"}</p></div><button className="ra-button" onClick={() => open(project.id)}><RefreshCw size={15}/>刷新</button></div>{alerts}
{isReviewer&&!hasReviewClaim&&<section className="xb-task xb-claim-gate"><div><b>该项目仍在待评审项目池</b><small>领取后将停止等待计时,并记录由你开始办理。</small></div><button className="ra-button primary" disabled={busy} onClick={()=>claimAndOpen(project)}>领取并开始评审</button></section>}
@@ -20,6 +20,7 @@ function valueText(value:any){
export default function ReviewerWorkspace({topics,reviewed,blocking,busy,opinion,setOpinion,onDecision,onComplete,hasRun}:any){
const total=topics.length;
const [returnReason,setReturnReason]=useState("");
const issues=topics.filter((item:any)=>item.ai_status==="issue").length;
const incomplete=topics.filter((item:any)=>["missing","not_run"].includes(item.ai_status)).length;
const pending=topics.filter((item:any)=>!item.decision).map((item:any)=>item.title);
@@ -31,7 +32,7 @@ export default function ReviewerWorkspace({topics,reviewed,blocking,busy,opinion
<div className="xb-review-lanes" aria-hidden="true"><span><Database size={15}/>数据与来源</span><span><ShieldAlert size={15}/>AI辅助判断</span><span><CheckCircle2 size={15}/>评审员结论</span></div>
<div className="xb-topic-list">{topics.map((topic:any)=><ReviewTopic key={topic.id} topic={topic} busy={busy} opinion={opinion} onDecision={onDecision}/>)}</div>
{!hasRun&&<div className="xb-review-hint"><CircleDashed size={18}/><span>请先在页面上方发起一次独立评审,系统才会冻结本次数据与规则版本。</span></div>}
<div className="xb-review-final"><div><CheckCircle2 size={21}/><span><b>完成全链路核查</b><small>{pending.length?`待确认:${pending.join("、")}`:blocking.length?`尚未闭环:${blocking.map((item:any)=>item.title).join("、")}`:"所有必审领域均已形成可追溯结论"}</small></span></div><button className="ra-button" disabled={busy||!hasRun} onClick={()=>onComplete("退回")}>退回补充材料</button><button className="ra-button primary" disabled={busy||!canApprove} onClick={()=>onComplete("批准")}>批准并生成评审报告</button></div>
<div className="xb-review-final"><div><CheckCircle2 size={21}/><span><b>完成全链路核查</b><small>{pending.length?`待确认:${pending.join("、")}`:blocking.length?`尚未闭环:${blocking.map((item:any)=>item.title).join("、")}`:"所有必审领域均已形成可追溯结论"}</small></span></div><label className="xb-return-reason"><span>退回原因(退回时必填)</span><textarea aria-label="退回补充材料原因" value={returnReason} maxLength={2000} onChange={(event)=>setReturnReason(event.target.value)} placeholder="例如:请补充最新银行流水并说明回款口径"/></label><button className="ra-button" title={!hasRun?"请先完成独立评审":!returnReason.trim()?"请先填写退回原因":"退回客户经理补充材料"} disabled={busy||!hasRun||!returnReason.trim()} onClick={()=>onComplete("退回",returnReason.trim())}>退回补充材料</button><button className="ra-button primary" disabled={busy||!canApprove} onClick={()=>onComplete("批准")}>批准并生成评审报告</button></div>
</section>;
}