import { useState, type ReactNode } from 'react'; import { AlertTriangle, ShieldCheck, Trash2 } from 'lucide-react'; import { adminApi, clientApi, type DeletionPreflight, type DeletionResolutionAction, type DeletionResult, type DeletionTargetType } from '@/api/adminApi'; import { createUuid } from '@/utils/randomId'; import { Button } from './Button'; import { Modal } from './Modal'; import { Textarea } from './Textarea'; export function DeleteRiskAction({ portal, targetType, targetId, children = '删除', icon = , disabled, onCompleted }: { portal: 'admin' | 'client'; targetType: DeletionTargetType; targetId: string; children?: ReactNode; icon?: ReactNode; disabled?: boolean; onCompleted?: (result: DeletionResult) => void; }) { const [open, setOpen] = useState(false); const [loading, setLoading] = useState(false); const [submitting, setSubmitting] = useState(false); const [preflight, setPreflight] = useState(null); const [result, setResult] = useState(null); const [reason, setReason] = useState(''); const [selections, setSelections] = useState>(() => new Set()); const [error, setError] = useState(''); const [idempotencyKey, setIdempotencyKey] = useState(''); async function begin() { const key = `delete:${targetType}:${targetId}:${createUuid()}`; setOpen(true); setLoading(true); setPreflight(null); setResult(null); setReason(''); setSelections(new Set()); setError(''); setIdempotencyKey(key); try { const data = portal === 'admin' ? await adminApi.getDeletionPreflight(targetType, targetId) : await clientApi.getDeletionPreflight(targetType as Exclude, targetId); setPreflight(data); } catch (failure) { setError(failure instanceof Error ? failure.message : '删除资格预检失败'); } finally { setLoading(false); } } async function confirm() { if (!preflight?.allowedActions.includes('delete') || !allSelectionsConfirmed(preflight, selections)) return; setSubmitting(true); setError(''); try { const body = { expectedUpdatedAt: preflight.expectedUpdatedAt, idempotencyKey, reason: reason.trim() || undefined, deleteAssociatedTemplates: selections.has('delete_associated_templates'), deleteAssociatedDrainage: selections.has('delete_associated_drainage'), abandonAssociatedReportTasks: selections.has('abandon_associated_report_tasks'), }; const completed = portal === 'admin' ? await adminApi.deleteGovernedTarget(targetType, targetId, body) : await clientApi.deleteGovernedTarget(targetType as Exclude, targetId, body); setResult(completed); } catch (failure) { setError(failure instanceof Error ? failure.message : '删除失败,请重新检查依赖后重试'); } finally { setSubmitting(false); } } function close() { if (submitting) return; setOpen(false); if (result) onCompleted?.(result); } function toggleSelection(action: DeletionResolutionAction, checked: boolean) { setSelections((current) => { const next = new Set(current); if (checked) next.add(action); else next.delete(action); return next; }); } const blocked = Boolean(preflight && !preflight.allowedActions.includes('delete')); const selectionsConfirmed = Boolean(preflight && allSelectionsConfirmed(preflight, selections)); const footer = (requestClose: () => void) => result ? 关闭 : <> 取消 } onClick={() => void confirm()} variant="danger"> {submitting ? '删除处理中…' : '确认删除'} >; return <> void begin()} size="sm" variant="danger">{children} 0} footer={({ requestClose }) => footer(requestClose)} onClose={close} open={open} title="删除资格与影响确认"> {result ? ( 删除已完成操作单号:{result.operationId}{result.replayed ? '(幂等重放)' : ''} ) : ( <> {loading ? 正在从后台检查引用关系与当前状态… : null} {preflight ? <> {Object.entries(preflight.identity).map(([key, value]) => {identityLabels[key] ?? key}{value})} 依赖与资格检查{preflight.dependencies.filter((item) => item.detailsVisible).length ? {preflight.dependencies.filter((item) => item.detailsVisible).map((item) => {item.label}{item.count} 项{item.items.length ? {item.items.join(';')} : 无活动引用})} : null} {preflight.blockedReasons.length ? {preflight.blockedReasons.map((item) => {item})} : 资格检查通过,可以执行逻辑删除} {preflight.requiredSelections.length ? 关联数据处理确认{preflight.requiredSelections.map((selection) => ( toggleSelection(selection.action, event.target.checked)} type="checkbox" /> {selection.label}{selection.description} ))}发现的关联数据处理项必须全部勾选后,才可以确认删除。 : null} 影响范围{preflight.impacts.map((item) => {item})}{preflight.recoverability.description} {!blocked ? setReason(event.target.value)} placeholder="如填写,将写入审计记录" value={reason} /> : null} > : null} {error ? {error} : null} > )} >; } const identityLabels: Record = { name: '对象名称', id: '唯一标识', code: '通道编码', tenant: '所属企业', application: '短信应用', signature: '关联签名' }; function allSelectionsConfirmed(preflight: DeletionPreflight, selections: Set) { return preflight.requiredSelections.every((selection) => selections.has(selection.action)); }
正在从后台检查引用关系与当前状态…
资格检查通过,可以执行逻辑删除
发现的关联数据处理项必须全部勾选后,才可以确认删除。
{preflight.recoverability.description}
{error}