import { useEffect, useMemo, useState } from 'react'; import { Search, Smartphone } from 'lucide-react'; import { adminApi, type SmsMessageRecord, type SmsUplinkMatchCandidate, type SmsUplinkMessage } from '@/api/adminApi'; import { Breadcrumb, Button, DateRangeInput, Input, Modal, Table, type DateRangeValue, type TableColumn, } from '@/components/ui'; function getDate(value?: string | null) { return value ? value.slice(0, 10) : ''; } function getTime(value?: string | null) { return value ? `${value.slice(0, 10)} ${value.slice(11, 19)}` : '-'; } function matchStatusText(status?: string | null) { const map: Record = { matched: '已匹配', ambiguous: '待认领', unmatched: '未匹配', }; return status ? (map[status] ?? status) : '-'; } function candidateStatusText(status?: string | null) { const map: Record = { pending: '待认领', claimed: '已认领', rejected: '已排除', }; return status ? (map[status] ?? status) : '-'; } function UplinkDetailModal({ claimError, claimingId, detailError, matchedRecords, matching, message, onClaim, onClose, }: { claimError: string; claimingId: string; detailError: string; matchedRecords: SmsMessageRecord[]; matching: boolean; message: SmsUplinkMessage; onClaim: (candidate: SmsUplinkMatchCandidate) => void; onClose: () => void; }) { const candidates = message.matchCandidates ?? []; return ( 关闭} onClose={onClose} open size="xl" title="上行短信详情" >

上行信息

手机号码 {message.phoneNumber || '-'}
上行时间 {getTime(message.receivedAt)}
上行企业 {message.tenant?.name ?? message.tenantId ?? '-'}
上行通道 {message.channel?.name ?? message.channelId ?? '-'}
上行接入号 {message.destId || '-'}
网关消息ID {message.messageId || '-'}
匹配状态 {matchStatusText(message.matchStatus)}
上行内容 {message.content || '-'}

候选认领

{claimError ?

{claimError}

: null} {candidates.length === 0 ?
暂无人工认领候选
: null} {candidates.map((candidate) => (
候选企业 {candidate.tenant?.name ?? candidate.tenantId}
候选应用 {candidate.application?.name ?? candidate.applicationId}
候选来源 {candidate.matchSource === 'access_number' ? '接入号' : '手机号时间窗'}
置信度 {candidate.confidence}
状态 {candidateStatusText(candidate.status)}
下发短信 {candidate.messageRecord?.messageId ?? '-'}
{candidate.messageRecord ? (
下发内容

{candidate.messageRecord.content}

) : null}
{candidate.reason ?? '-'}
))}

匹配发送记录

{matching ?

正在查询真实下发记录...

: null} {detailError ?

{detailError}

: null} {!matching && !message.messageId ?
该上行记录没有网关消息ID,无法匹配下发记录
: null} {!matching && message.messageId && matchedRecords.length === 0 && !detailError ? (
暂无匹配发送记录
) : null} {matchedRecords.map((record) => (
发送时间 {getTime(record.queuedAt)}
发送企业 {record.tenant?.name ?? record.tenantId ?? '-'}
发送应用 {record.application?.name ?? record.applicationId ?? '-'}
客户提交接入号 {record.channel?.srcId ?? '-'}
下发内容

{record.content}

))}
); } export function AdminSmsUplinkRecordsPage() { const [messages, setMessages] = useState([]); const [matchedRecords, setMatchedRecords] = useState([]); const [dateRange, setDateRange] = useState({}); const [phoneKeyword, setPhoneKeyword] = useState(''); const [contentKeyword, setContentKeyword] = useState(''); const [selectedMessage, setSelectedMessage] = useState(null); const [loading, setLoading] = useState(true); const [matching, setMatching] = useState(false); const [claimingId, setClaimingId] = useState(''); const [error, setError] = useState(''); const [detailError, setDetailError] = useState(''); const [claimError, setClaimError] = useState(''); function loadData() { setLoading(true); adminApi.listAdminUplinkMessages() .then((items) => { setMessages(items); setError(''); }) .catch((reason: Error) => setError(reason.message || '短信上行记录加载失败')) .finally(() => setLoading(false)); } function openDetail(message: SmsUplinkMessage) { setSelectedMessage(message); setMatchedRecords([]); setDetailError(''); setClaimError(''); if (!message.messageId) { return; } setMatching(true); adminApi.listOperationMessages({ tenantId: message.tenantId ?? undefined, messageId: message.messageId }) .then((items) => setMatchedRecords(items)) .catch((reason: Error) => setDetailError(reason.message || '匹配发送记录加载失败')) .finally(() => setMatching(false)); } useEffect(() => { loadData(); }, []); const filteredMessages = useMemo( () => messages.filter((item) => { const receivedDate = getDate(item.receivedAt); const matchesStartDate = !dateRange.start || receivedDate >= dateRange.start; const matchesEndDate = !dateRange.end || receivedDate <= dateRange.end; const matchesPhone = !phoneKeyword || item.phoneNumber.includes(phoneKeyword); const matchesContent = !contentKeyword || item.content.includes(contentKeyword); return matchesStartDate && matchesEndDate && matchesPhone && matchesContent; }), [contentKeyword, dateRange.end, dateRange.start, messages, phoneKeyword], ); function resetFilters() { setDateRange({}); setPhoneKeyword(''); setContentKeyword(''); } function handleClaim(candidate: SmsUplinkMatchCandidate) { if (!selectedMessage) { return; } setClaimingId(candidate.id); setClaimError(''); adminApi.claimUplinkMatchCandidate(selectedMessage.id, { candidateId: candidate.id }) .then((updated) => { setMessages((items) => items.map((item) => (item.id === updated.id ? { ...item, ...updated } : item))); setSelectedMessage((current) => (current && current.id === updated.id ? { ...current, ...updated } : current)); loadData(); }) .catch((reason: Error) => setClaimError(reason.message || '上行认领失败')) .finally(() => setClaimingId('')); } const columns: Array> = [ { key: 'select', title: '', width: '72px', align: 'center', render: () => , }, { key: 'phoneNumber', title: '手机号码', width: '170px', render: (record) => {record.phoneNumber} }, { key: 'receivedAt', title: '上行时间', width: '220px', render: (record) => {getTime(record.receivedAt)} }, { key: 'content', title: '上行内容', width: '360px', render: (record) => {record.content} }, { key: 'channel', title: '上行通道', width: '260px', render: (record) => {record.channel?.name ?? record.channelId} }, { key: 'accessNo', title: '上行接入号', width: '180px', render: (record) => {record.destId} }, { key: 'matchStatus', title: '匹配状态', width: '140px', render: (record) => {matchStatusText(record.matchStatus)} }, { key: 'actions', title: '操作', width: '140px', align: 'center', render: (record) => ( ), }, ]; return (

短信上行记录

setPhoneKeyword(event.target.value)} prefix={} value={phoneKeyword} /> setContentKeyword(event.target.value)} value={contentKeyword} />
{error ?

{error}

: null}
{selectedMessage ? ( setSelectedMessage(null)} /> ) : null} ); }