feat(web): add per-document author_only toggle in project settings

Show a single author picker when兼任 is enabled, respect the system
capability flag, and surface author_only docs in activation preview.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
weichen
2026-09-17 13:15:05 +00:00
co-authored by Cursor
parent be128602e4
commit 72686d13cb
@@ -29,11 +29,12 @@ import {
} from '../../lib/projectSettingsMode';
import {
canToggleHumanApproval,
classifyClientDuty,
clientDutyIssues,
formatDutyIssues,
locateDutyIssue,
needsThreeMembersForDuties,
resolveClientDocTaskDuty,
toggleAuthorOnly,
toggleHumanApproval,
} from '../../lib/projectSettingsDuties';
import { ACTIVATION_UNMANAGED_FREEZE_HINT } from '../../lib/blockedGovernanceCopy';
@@ -502,7 +503,13 @@ export const ProjectSettingsModal: React.FC<ProjectSettingsModalProps> = ({
<div key={st.stage_key} style={{ border: '1px solid var(--border)', borderRadius: '8px', padding: '12px' }}>
<div style={{ fontWeight: 600, marginBottom: '8px' }}>{st.stage_name || st.stage_key}</div>
{st.doc_tasks.map((dt, ti) => {
const managed = classifyClientDuty(dt.duties) !== 'none' || requireApprovalKeys.includes(dt.task_key);
const resolved = resolveClientDocTaskDuty(dt);
const managed =
resolved.mode !== 'none' ||
dt.duty_policy === 'author_only' ||
requireApprovalKeys.includes(dt.task_key);
const authorOnly = dt.duty_policy === 'author_only';
const authorOnlyAllowed = Boolean(settings?.author_only_policy?.allowed);
const inline = locateDutyIssue(dutyIssues, dt.task_key);
return (
<div key={dt.task_key} style={{ marginBottom: '10px', fontSize: '12px' }}>
@@ -530,7 +537,63 @@ export const ProjectSettingsModal: React.FC<ProjectSettingsModalProps> = ({
纳入人工审批
</label>
)}
{dt.enabled && managed && (
<label
style={{
display: 'flex',
alignItems: 'center',
gap: '6px',
marginBottom: '6px',
opacity: authorOnlyAllowed || authorOnly ? 1 : 0.7,
}}
title={
authorOnlyAllowed
? undefined
: settings?.author_only_policy?.blocked_reason ||
'系统管理员尚未开启「允许编制人兼任审核、批准」'
}
>
<input
type="checkbox"
checked={authorOnly}
disabled={!authorOnlyAllowed && !authorOnly}
onChange={(e) => updateDocTask(si, ti, toggleAuthorOnly(dt, e.target.checked))}
/>
由编制人兼任审核、批准
{!authorOnlyAllowed && (
<span style={{ color: 'var(--text-muted)', fontSize: '11px' }}>
(总开关未开启)
</span>
)}
</label>
)}
{dt.enabled && (managed || !dt.approval_configurable) && (
authorOnly ? (
<div style={{ display: 'grid', gridTemplateColumns: '1fr', gap: '6px' }}>
<select
value={dt.duties.author_user_id || ''}
onChange={(e) =>
updateDocTask(si, ti, {
duties: { author_user_id: Number(e.target.value) || undefined },
duty_policy: 'author_only',
})
}
style={{ fontSize: '11px', padding: '4px' }}
>
<option value="">编制人(兼任审核与批准)</option>
{members.map((m) => (
<option key={m.userId} value={m.userId}>
{m.name}
</option>
))}
</select>
{dt.duties.author_user_id ? (
<div style={{ color: 'var(--text-muted)', fontSize: '11px' }}>
审核、批准:同编制人(自动承担)
</div>
) : null}
</div>
) : (
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '6px' }}>
{(['author', 'reviewer', 'approver'] as const).map((role) => (
<select
@@ -555,6 +618,7 @@ export const ProjectSettingsModal: React.FC<ProjectSettingsModalProps> = ({
</select>
))}
</div>
)
)}
{inline?.message && (
<div style={{ color: 'var(--danger-700)', marginTop: '4px' }}>{inline.message}</div>
@@ -587,6 +651,16 @@ export const ProjectSettingsModal: React.FC<ProjectSettingsModalProps> = ({
纳入人工审批:{preview.summary.governed_doc_tasks ?? preview.summary.enabled_doc_tasks} · 不纳入:{preview.summary.unmanaged_doc_tasks ?? 0}
</div>
<div>职责指派:{preview.summary.assigned_duties} 项</div>
{(preview.summary.author_only_doc_tasks ?? 0) > 0 && (
<div>
兼任审批:{preview.summary.author_only_doc_tasks} 份
{(preview.summary.author_only_documents || []).length > 0
? `(${(preview.summary.author_only_documents || [])
.map((doc) => doc.title || doc.task_key)
.join('、')})`
: ''}
</div>
)}
{(preview.summary.unmanaged_documents || []).length > 0 && (
<div>
例外文档:{(preview.summary.unmanaged_documents || []).map((doc) => doc.title || doc.task_key).join('、')}