import { useState, type ReactNode } from 'react'; import { AlertTriangle, ShieldCheck, Trash2 } from 'lucide-react'; import { adminApi, clientApi, type DeletionPreflight, 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 [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(''); 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') || reason.trim().length < 4) return; setSubmitting(true); setError(''); try { const body = { expectedUpdatedAt: preflight.expectedUpdatedAt, idempotencyKey, reason: reason.trim() }; const completed = portal === 'admin' ? await adminApi.deleteGovernedTarget(targetType, targetId, body) : await clientApi.deleteGovernedTarget(targetType as Exclude, targetId, body); setResult(completed); onCompleted?.(completed); } catch (failure) { setError(failure instanceof Error ? failure.message : '删除失败,请重新检查依赖后重试'); } finally { setSubmitting(false); } } function close() { if (!submitting) setOpen(false); } const blocked = Boolean(preflight && !preflight.allowedActions.includes('delete')); 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="删除资格与影响确认"> {loading ? 正在从后台检查引用关系与当前状态… : null} {preflight ? <> {Object.entries(preflight.identity).map(([key, value]) => {identityLabels[key] ?? key}{value})} 依赖与资格检查{preflight.dependencies.length ? {preflight.dependencies.map((item) => {item.label}{item.count} 项{item.items.length ? {item.items.join(';')} : 无活动引用})} : null} {preflight.blockedReasons.length ? {preflight.blockedReasons.map((item) => {item})} : 资格检查通过,可以执行逻辑删除} 影响范围{preflight.impacts.map((item) => {item})}{preflight.recoverability.description} {!blocked ? setReason(event.target.value)} placeholder="至少填写 4 个字符,原因将写入审计记录" required value={reason} /> : null} > : null} {result ? 删除已完成操作单号:{result.operationId}{result.replayed ? '(幂等重放)' : ''} : null} {error ? {error} : null} >; } const identityLabels: Record = { name: '对象名称', id: '唯一标识', code: '通道编码', tenant: '所属企业', application: '短信应用', signature: '关联签名' };
正在从后台检查引用关系与当前状态…
资格检查通过,可以执行逻辑删除
{preflight.recoverability.description}
{error}