feat: densify sms records and improve uplink matching

This commit is contained in:
hectorzhao
2026-08-27 10:15:08 +08:00
parent 898471423f
commit a280b4bb22
25 changed files with 859 additions and 154 deletions
+61 -9
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { Search, Smartphone } from 'lucide-react';
import { Search, Smartphone, UserX } from 'lucide-react';
import { adminApi, type SmsMessageRecord, type SmsUplinkMatchCandidate, type SmsUplinkMessage } from '@/api/adminApi';
import {
Breadcrumb,
@@ -40,28 +40,41 @@ function candidateStatusText(status?: string | null) {
}
function UplinkDetailModal({
blacklistFeedback,
blacklisting,
claimError,
claimingId,
detailError,
matchedRecords,
matching,
message,
onAddBlacklist,
onClaim,
onClose,
}: {
blacklistFeedback: string;
blacklisting: boolean;
claimError: string;
claimingId: string;
detailError: string;
matchedRecords: SmsMessageRecord[];
matching: boolean;
message: SmsUplinkMessage;
onAddBlacklist: () => void;
onClaim: (candidate: SmsUplinkMatchCandidate) => void;
onClose: () => void;
}) {
const candidates = message.matchCandidates ?? [];
return (
<Modal
footer={<Button onClick={onClose} variant="ghost"></Button>}
footer={<>
{message.tenantId && message.applicationId ? (
<Button disabled={blacklisting} icon={<UserX size={15} />} onClick={onAddBlacklist}>
{blacklisting ? '加入中...' : '加入应用黑名单'}
</Button>
) : null}
<Button onClick={onClose} variant="ghost"></Button>
</>}
onClose={onClose}
open
size="xl"
@@ -92,13 +105,21 @@ function UplinkDetailModal({
<strong>{message.destId || '-'}</strong>
</div>
<div>
<span>ID</span>
<strong>{message.messageId || '-'}</strong>
<span>ID</span>
<strong>{message.gatewayMessageId || '-'}</strong>
</div>
<div>
<span>ID</span>
<strong>{message.messageRecord?.messageId ?? message.messageId ?? '-'}</strong>
</div>
<div>
<span></span>
<strong>{matchStatusText(message.matchStatus)}</strong>
</div>
<div className="admin-uplink-info-grid__full">
<span></span>
<strong>{message.matchReason || '-'}</strong>
</div>
<div className="admin-uplink-info-grid__full">
<span></span>
<strong>{message.content || '-'}</strong>
@@ -160,11 +181,13 @@ function UplinkDetailModal({
<section className="admin-uplink-match-section">
<h3></h3>
{blacklistFeedback ? <p className={blacklistFeedback.startsWith('已') ? 'form-success' : 'form-error'}>{blacklistFeedback}</p> : null}
{matching ? <p>...</p> : null}
{detailError ? <p className="form-error">{detailError}</p> : null}
{!matching && !message.messageId ? <div className="admin-uplink-empty-match">ID</div> : null}
{!matching && message.messageId && matchedRecords.length === 0 && !detailError ? (
<div className="admin-uplink-empty-match"></div>
{!matching && matchedRecords.length === 0 && !detailError ? (
<div className="admin-uplink-empty-match">
{message.matchStatus === 'ambiguous' ? '存在多个候选,请先认领正确应用' : '暂无匹配发送记录'}
</div>
) : null}
{matchedRecords.map((record) => (
<article className="admin-uplink-match-card" key={record.id}>
@@ -211,6 +234,8 @@ export function AdminSmsUplinkRecordsPage() {
const [error, setError] = useState('');
const [detailError, setDetailError] = useState('');
const [claimError, setClaimError] = useState('');
const [blacklisting, setBlacklisting] = useState(false);
const [blacklistFeedback, setBlacklistFeedback] = useState('');
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const pageSize = 10;
@@ -236,11 +261,12 @@ export function AdminSmsUplinkRecordsPage() {
function openDetail(message: SmsUplinkMessage) {
setSelectedMessage(message);
setMatchedRecords([]);
setMatchedRecords(message.messageRecord ? [message.messageRecord] : []);
setDetailError('');
setClaimError('');
setBlacklistFeedback('');
if (!message.messageId) {
if (message.messageRecord || !message.messageId) {
return;
}
@@ -251,6 +277,28 @@ export function AdminSmsUplinkRecordsPage() {
.finally(() => setMatching(false));
}
function handleAddBlacklist() {
if (!selectedMessage?.tenantId || !selectedMessage.applicationId) {
setBlacklistFeedback('请先匹配或认领企业应用');
return;
}
if (!window.confirm(`确认将 ${selectedMessage.phoneNumber} 加入当前应用黑名单?`)) {
return;
}
setBlacklisting(true);
setBlacklistFeedback('');
adminApi.createEnterpriseBlacklist({
tenantId: selectedMessage.tenantId,
applicationId: selectedMessage.applicationId,
phoneNumber: selectedMessage.phoneNumber,
reason: '上行短信人工加入',
status: 'active',
})
.then(() => setBlacklistFeedback('已加入当前应用黑名单'))
.catch((reason: Error) => setBlacklistFeedback(reason.message || '加入应用黑名单失败'))
.finally(() => setBlacklisting(false));
}
useEffect(() => {
loadData(page);
}, [page]);
@@ -273,6 +321,7 @@ export function AdminSmsUplinkRecordsPage() {
.then((updated) => {
setMessages((items) => items.map((item) => (item.id === updated.id ? { ...item, ...updated } : item)));
setSelectedMessage((current) => (current && current.id === updated.id ? { ...current, ...updated } : current));
setMatchedRecords(updated.messageRecord ? [updated.messageRecord] : []);
loadData(page);
})
.catch((reason: Error) => setClaimError(reason.message || '上行认领失败'))
@@ -332,12 +381,15 @@ export function AdminSmsUplinkRecordsPage() {
{selectedMessage ? (
<UplinkDetailModal
blacklistFeedback={blacklistFeedback}
blacklisting={blacklisting}
claimError={claimError}
claimingId={claimingId}
detailError={detailError}
matchedRecords={matchedRecords}
matching={matching}
message={selectedMessage}
onAddBlacklist={handleAddBlacklist}
onClaim={handleClaim}
onClose={() => setSelectedMessage(null)}
/>