From 7f9d01a3b65732230c5782bc7f6e3918aef8378c Mon Sep 17 00:00:00 2001 From: weichen Date: Mon, 28 Sep 2026 01:35:46 +0000 Subject: [PATCH] =?UTF-8?q?fix(web):=20=E6=BF=80=E6=B4=BB=E5=89=8D?= =?UTF-8?q?=E9=A2=84=E6=A3=80=E4=BA=BA=E5=B7=A5=E5=AE=A1=E6=89=B9=E5=B9=B6?= =?UTF-8?q?=E6=B6=88=E9=99=A4=E5=AF=86=E9=92=A5=E6=A1=86=E5=AF=86=E7=A0=81?= =?UTF-8?q?=E8=AD=A6=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 未纳入人工审批的计划会被服务端拒绝为 400。配置页在请求前拦截并提示;管理台密钥输入放入表单,避免浏览器把密码框当成未托管字段。 --- .../console/sections/EnvSettingsSection.tsx | 15 ++++++--- .../project/ProjectSettingsModal.tsx | 20 ++++++++++++ web/src/lib/projectSettingsDuties.test.ts | 31 +++++++++++++++++++ web/src/lib/projectSettingsDuties.ts | 11 +++++++ 4 files changed, 72 insertions(+), 5 deletions(-) diff --git a/web/src/components/admin/console/sections/EnvSettingsSection.tsx b/web/src/components/admin/console/sections/EnvSettingsSection.tsx index e337ec7a..94f4ca1a 100644 --- a/web/src/components/admin/console/sections/EnvSettingsSection.tsx +++ b/web/src/components/admin/console/sections/EnvSettingsSection.tsx @@ -181,13 +181,19 @@ export const EnvSettingsSection: React.FC = () => { {s.secret && !s.hasValue ? ' · 未配置' : ''} -
+
{ + e.preventDefault(); + if (canSave && savingKey !== s.key) void handleSave(s); + }} + style={{ display: 'flex', alignItems: 'center', gap: '10px', flexShrink: 0 }} + > setDrafts((prev) => ({ ...prev, [s.key]: e.target.value }))} style={{ width: '260px', @@ -202,9 +208,8 @@ export const EnvSettingsSection: React.FC = () => { /> {s.writable && ( )} -
+ ); })} diff --git a/web/src/components/project/ProjectSettingsModal.tsx b/web/src/components/project/ProjectSettingsModal.tsx index d2786e92..965f78b9 100644 --- a/web/src/components/project/ProjectSettingsModal.tsx +++ b/web/src/components/project/ProjectSettingsModal.tsx @@ -33,7 +33,9 @@ import { clientDutyIssues, formatDutyIssues, locateDutyIssue, + needsGovernedDocument, needsThreeMembersForDuties, + NO_GOVERNED_DOCUMENT_MESSAGE, resolveClientDocTaskDuty, toggleAuthorOnly, toggleHumanApproval, @@ -226,6 +228,10 @@ export const ProjectSettingsModal: React.FC = ({ setError(`当前项目只有 ${members.length} 名有效成员。纳入人工审批的文档需要分别指定编制、审核、批准负责人。`); return; } + if (!liveEdit && !needsGovernedDocument(plan)) { + setError(NO_GOVERNED_DOCUMENT_MESSAGE); + return; + } setLoading(true); setError(null); @@ -641,6 +647,20 @@ export const ProjectSettingsModal: React.FC = ({

允许裁剪的文档可选择是否纳入人工审批。关闭后仍会生成并进入版本账本,只是不走三权。

+ {!liveEdit && !needsGovernedDocument(plan) && ( +
+ {NO_GOVERNED_DOCUMENT_MESSAGE}。请勾选「纳入人工审批」并指定负责人;成员不足三人时可用「由编制人兼任审核、批准」。 +
+ )} {needsThreeMembersForDuties(plan, requireApprovalKeys) && members.length < 3 && (
{ assert.equal(locateDutyIssue(switchedOn, 'research')?.code, 'DUTY_ASSIGNMENT_REQUIRED'); }); + it('blocks activation preview when no document is under human approval', () => { + const unmanaged = plan([ + { task_key: 'research', enabled: true, approval_configurable: true, duties: {} }, + { task_key: 'quote', enabled: true, approval_configurable: true, duties: {} }, + ]); + assert.equal(needsGovernedDocument(unmanaged), false); + assert.deepEqual(clientDutyIssues(unmanaged, [{ userId: 1 }]), []); + + const authorOnly = plan([ + { + task_key: 'intake', + enabled: true, + approval_configurable: true, + duty_policy: 'author_only', + duties: { author_user_id: 7 }, + }, + ]); + assert.equal(needsGovernedDocument(authorOnly), true); + + const separated = plan([ + { + task_key: 'intake', + enabled: true, + approval_configurable: true, + duties: { author_user_id: 1, reviewer_user_id: 2, approver_user_id: 3 }, + }, + ]); + assert.equal(needsGovernedDocument(separated), true); + }); + it('allows an all-none configurable draft without three members', () => { const draft = plan([ { task_key: 'research', enabled: true, approval_configurable: true, duties: {} }, diff --git a/web/src/lib/projectSettingsDuties.ts b/web/src/lib/projectSettingsDuties.ts index a6c08abd..0baf14ff 100644 --- a/web/src/lib/projectSettingsDuties.ts +++ b/web/src/lib/projectSettingsDuties.ts @@ -189,6 +189,17 @@ export function clientDutyIssues( return issues; } +/** 对齐服务端 validateActivationPlan:激活前至少一份文档为完整人工审批(含兼任派生的 A/A/A)。 */ +export const NO_GOVERNED_DOCUMENT_MESSAGE = '至少一份文档必须纳入人工审批才能激活工作流'; + +export function needsGovernedDocument(plan: WorkflowPlan): boolean { + return plan.stages.some( + (stage) => + stage.enabled && + stage.doc_tasks.some((task) => task.enabled && resolveClientDocTaskDuty(task).mode === 'full'), + ); +} + export function needsThreeMembersForDuties(plan: WorkflowPlan, requireApprovalKeys: string[] = []): boolean { return plan.stages.some( (stage) =>