import { useEffect, useMemo, useState } from 'react' import { Alert, Checkbox, Input, Modal, Space, Tag, Typography } from 'antd' import { ExclamationCircleOutlined } from '@ant-design/icons' import { rockApi, type RockTokenDeletePreview, type RockTokenDeleteResult, type RockTokenDeleteStatusCount, type RockTokenItem, } from '../../api' const { Text } = Typography export type TokenDeleteMode = 'batch' | 'all' export interface TokenDeleteModalProps { open: boolean mode: TokenDeleteMode selectedTokens: RockTokenItem[] selectedTokenIds: number[] filterStatus: string searchKeyword: string onCancel: () => void onDeleted: (result: RockTokenDeleteResult) => void } const STATUS_LABELS: Record = { available: '可用库存', using: '使用中', bound: '已绑定', redeemed: '已发货', mismatch: '绑定错误', abnormal: '已上报失效', } const DANGEROUS_STATUSES = new Set(['using', 'bound', 'mismatch']) function statusLabel(status: string) { return STATUS_LABELS[status] || status || '-' } function statusTagColor(status: string) { switch (status) { case 'available': return 'success' case 'using': return 'processing' case 'bound': return 'cyan' case 'redeemed': return 'purple' case 'mismatch': return 'error' default: return 'default' } } function countSelectedByStatus(tokens: RockTokenItem[]) { const counts = new Map() for (const token of tokens) { counts.set(token.status, (counts.get(token.status) || 0) + 1) } return Array.from(counts.entries()).map(([status, count]) => ({ status, count })) } function renderStatusCounts(counts: RockTokenDeleteStatusCount[]) { if (!counts.length) return 无 return ( {counts.map((item) => ( {statusLabel(item.status)} {item.count} ))} ) } export function TokenDeleteModal({ open, mode, selectedTokens, selectedTokenIds, filterStatus, searchKeyword, onCancel, onDeleted, }: TokenDeleteModalProps) { const [preview, setPreview] = useState(null) const [loadingPreview, setLoadingPreview] = useState(false) const [submitting, setSubmitting] = useState(false) const [error, setError] = useState('') const [acknowledged, setAcknowledged] = useState(false) const [allConfirmText, setAllConfirmText] = useState('') const [forceConfirmText, setForceConfirmText] = useState('') const selectedStatusCounts = useMemo( () => countSelectedByStatus(selectedTokens), [selectedTokens], ) const selectedDangerous = useMemo( () => selectedTokens.filter((token) => DANGEROUS_STATUSES.has(token.status)).length, [selectedTokens], ) const selectedAvailable = useMemo( () => selectedTokens.filter((token) => token.status === 'available').length, [selectedTokens], ) const total = mode === 'batch' ? selectedTokenIds.length : preview?.total ?? 0 const dangerous = mode === 'batch' ? selectedDangerous : preview?.dangerous ?? 0 const available = mode === 'batch' ? selectedAvailable : preview?.available ?? 0 const statusCounts = mode === 'batch' ? selectedStatusCounts : preview?.status_counts ?? [] const requiresForce = dangerous > 0 const filterLabel = filterStatus && filterStatus !== 'all' ? statusLabel(filterStatus) : '全部状态' const canSubmit = total > 0 && acknowledged && !submitting && (!requiresForce || forceConfirmText.trim() === '强制删除') && (mode === 'batch' || allConfirmText.trim() === '全部删除') useEffect(() => { if (!open) return setAcknowledged(false) setAllConfirmText('') setForceConfirmText('') setError('') if (mode === 'all') { setLoadingPreview(true) setPreview(null) rockApi .previewTokenDelete({ status: filterStatus && filterStatus !== 'all' ? filterStatus : undefined, search: searchKeyword || undefined, }) .then((data) => setPreview(data)) .catch((e) => setError(e instanceof Error ? e.message : '预览失败')) .finally(() => setLoadingPreview(false)) } }, [open, mode, filterStatus, searchKeyword]) const handleSubmit = async () => { if (!canSubmit) return setSubmitting(true) setError('') try { const result = mode === 'batch' ? await rockApi.batchDeleteTokens({ ids: selectedTokenIds, force: requiresForce, force_confirm_text: requiresForce ? forceConfirmText.trim() : undefined, }) : await rockApi.deleteAllTokens({ status: filterStatus && filterStatus !== 'all' ? filterStatus : undefined, search: searchKeyword || undefined, expected_count: preview?.total ?? 0, force: requiresForce, confirm_text: allConfirmText.trim(), force_confirm_text: requiresForce ? forceConfirmText.trim() : undefined, }) onDeleted(result) } catch (e) { setError(e instanceof Error ? e.message : '删除失败') if (mode === 'all') { rockApi .previewTokenDelete({ status: filterStatus && filterStatus !== 'all' ? filterStatus : undefined, search: searchKeyword || undefined, }) .then((data) => setPreview(data)) .catch(() => {}) } } finally { setSubmitting(false) } } const title = mode === 'batch' ? '批量软删除 Token' : '按当前筛选全部软删除' return ( {mode === 'all' && (
删除范围:{filterLabel} {searchKeyword ? ` · 搜索「${searchKeyword}」` : ''}
)} {loadingPreview ? ( 正在统计当前筛选结果… ) : ( <>
共 {total} 条 {available > 0 && ( · 其中可用库存 {available} 条 )} {dangerous > 0 && ( · 使用中/已绑定/绑定错误 {dangerous} 条 )}
状态分布: {renderStatusCounts(statusCounts)}
)} {available > 0 && ( )} {dangerous > 0 && ( } message={`包含 ${dangerous} 条进行中的 Token`} description="使用中/已绑定/绑定错误的 Token 与订单履约相关。删除后它们会从列表和统计中消失,可能导致客服排查困难;请确认这些订单已无需处理,或已经人工接管。" /> )} {error && } setAcknowledged(e.target.checked)} > 我已确认上述影响,同意软删除 {mode === 'all' && (
请输入 全部删除 确认按当前筛选删除: setAllConfirmText(e.target.value)} placeholder="全部删除" style={{ marginTop: 6 }} autoComplete="off" />
)} {requiresForce && (
本次包含进行中的 Token,请再输入 强制删除 确认: setForceConfirmText(e.target.value)} placeholder="强制删除" style={{ marginTop: 6 }} autoComplete="off" status="error" />
)}
) }