import { Fragment, startTransition, useEffect, useMemo, useState } from 'react'; import { FileText, Search, Smartphone } from 'lucide-react'; import { clientApi, type SmsMessageRecord } from '@/api/adminApi'; import { DateRangeInput, CarrierTag, Input, Pagination, QueryPanel, QueryButtons, Select, Tag, type DateRangeValue, } from '@/components/ui'; import { formatDateTime, recentBeijingDateRange } from '@/utils/dateTime'; const statusLabelMap: Record = { delivered: '成功', queued: '排队中', submitted: '已提交', accepted: '已受理', unknown: '未知', failed: '失败', rejected: '失败', timeout: '超时', pending_review: '待审核', scheduled: '等待定时发送', submit_queued: '等待提交', submit_failed: '提交失败', processing: '处理中', sending: '发送中', canceled: '已取消', cancelled: '已取消', }; const statusToneMap: Record = { delivered: 'success', queued: 'info', submitted: 'info', accepted: 'info', unknown: 'neutral', failed: 'danger', rejected: 'danger', timeout: 'danger', }; function getReceipt(record: SmsMessageRecord) { const latest = record.receiptRecords?.[0] as { rawStatus?: string; receiptStatus?: string; deliveredAt?: string } | undefined; return { status: receiptStatusLabel(record.receiptStatus ?? latest?.rawStatus ?? latest?.receiptStatus), time: record.deliveredAt ?? latest?.deliveredAt, }; } function receiptStatusLabel(status?: string | null) { if (!status) return '暂无回执'; const normalized = status.trim().toUpperCase(); return ( { DELIVERED: '送达成功', FAILED: '送达失败', TIMEOUT: '回执超时', DELIVRD: '送达成功', ACCEPTD: '已受理', UNDELIV: '未送达', REJECTD: '已拒绝', EXPIRED: '已过期', DELETED: '已删除', UNKNOWN: '状态未知', }[normalized] ?? statusLabelMap[status.toLowerCase()] ?? '状态未知' ); } export function ClientSendDetailPage() { const [records, setRecords] = useState([]); const [applicationId, setApplicationId] = useState('all'); const [status, setStatus] = useState('all'); const [dateRange, setDateRange] = useState(() => recentBeijingDateRange(7)); const [contentKeyword, setContentKeyword] = useState(''); const [phoneKeyword, setPhoneKeyword] = useState(''); const [applied, setApplied] = useState(() => ({ applicationId, status, dateRange, contentKeyword, phoneKeyword })); const [page, setPage] = useState(1); const [total, setTotal] = useState(0); const [applications, setApplications] = useState>([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); function loadData(targetPage = page) { setLoading(true); clientApi .listMessages({ applicationId: applied.applicationId === 'all' ? undefined : applied.applicationId, phoneNumber: applied.phoneKeyword.trim() || undefined, status: applied.status === 'all' ? undefined : applied.status, contentKeyword: applied.contentKeyword.trim() || undefined, queuedAtFrom: applied.dateRange.start || undefined, queuedAtTo: applied.dateRange.end || undefined, page: targetPage, pageSize: 10, }) .then((result) => { setRecords(result.items); setTotal(result.total); setError(''); }) .catch((reason: Error) => setError(reason.message || '短信发送详情加载失败')) .finally(() => setLoading(false)); } useEffect(() => { startTransition(() => loadData(page)); }, [applied, page]); useEffect(() => { clientApi .listApplicationOptions() .then((items) => setApplications(items.map((item) => ({ id: item.id, name: item.name })))) .catch((reason: Error) => setError(reason.message || '应用列表加载失败')); }, []); const applicationOptions = useMemo(() => { return [{ label: '全部应用', value: 'all' }, ...applications.map((item) => ({ label: item.name, value: item.id }))]; }, [applications]); const filteredRows = records; const pageSize = 10; const totalPages = Math.max(1, Math.ceil(total / pageSize)); const currentPage = Math.min(page, totalPages); const visibleRows = filteredRows; function query() { setPage(1); setApplied({ applicationId, status, dateRange, contentKeyword, phoneKeyword }); } function reset() { const defaults = { applicationId: 'all', status: 'all', dateRange: recentBeijingDateRange(7), contentKeyword: '', phoneKeyword: '', }; setApplicationId(defaults.applicationId); setStatus(defaults.status); setDateRange(defaults.dateRange); setContentKeyword(''); setPhoneKeyword(''); setPage(1); setApplied(defaults); } return (

短信发送详情

共找到 {total} 条发送记录 } > setStatus(event.target.value)} options={[ { label: '全部', value: 'all' }, { label: '成功', value: 'delivered' }, { label: '未知', value: 'unknown' }, { label: '失败', value: 'failed' }, ]} value={status} /> setContentKeyword(event.target.value)} placeholder="输入关键词搜索" prefix={} value={contentKeyword} /> setPhoneKeyword(event.target.value)} placeholder="输入手机号搜索" prefix={} value={phoneKeyword} /> {error ?

{error}

: null}
{loading ? ( ) : filteredRows.length === 0 ? ( ) : ( visibleRows.map((record) => { const receipt = getReceipt(record); const region = record.province ?? '-'; return ( ); }) )}
应用名称 发送时间 字符数/条数 手机号码 所属运营商 发送地区 发送状态 短信回执 回执时间
正在加载真实发送记录...
暂无发送记录
{record.application?.name ?? record.applicationId ?? '-'} {formatDateTime(record.queuedAt).slice(0, 10)} {formatDateTime(record.queuedAt).slice(11, 19)} {[...record.content].length}字 {record.billingUnits}条 {record.phoneNumber} {record.carrier ? : '-'} {region} {statusLabelMap[record.status] ?? '状态未知'} {receipt.status} {receipt.time ? ( {formatDateTime(receipt.time).slice(0, 10)} {formatDateTime(receipt.time).slice(11, 19)} ) : ( - )}
短信内容

{record.content}

{record.originalContent != null ? ( <> 原始短信内容

{record.originalContent}

) : null}
= totalPages} onNext={() => setPage((value) => Math.min(totalPages, value + 1))} onPrevious={() => setPage((value) => Math.max(1, value - 1))} page={currentPage} totalPages={totalPages} onPageChange={setPage} previousDisabled={currentPage <= 1} total={total} />
); }