feat: complete cmpp gateway delivery recovery workflows
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Search, Smartphone } from 'lucide-react';
|
||||
import { adminApi, type SmsMessageRecord, type SmsUplinkMessage } from '@/api/adminApi';
|
||||
import { adminApi, type SmsMessageRecord, type SmsUplinkMatchCandidate, type SmsUplinkMessage } from '@/api/adminApi';
|
||||
import {
|
||||
Breadcrumb,
|
||||
Button,
|
||||
@@ -20,19 +20,44 @@ function getTime(value?: string | null) {
|
||||
return value ? `${value.slice(0, 10)} ${value.slice(11, 19)}` : '-';
|
||||
}
|
||||
|
||||
function matchStatusText(status?: string | null) {
|
||||
const map: Record<string, string> = {
|
||||
matched: '已匹配',
|
||||
ambiguous: '待认领',
|
||||
unmatched: '未匹配',
|
||||
};
|
||||
return status ? (map[status] ?? status) : '-';
|
||||
}
|
||||
|
||||
function candidateStatusText(status?: string | null) {
|
||||
const map: Record<string, string> = {
|
||||
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 (
|
||||
<Modal
|
||||
footer={<Button onClick={onClose} variant="ghost">关闭</Button>}
|
||||
@@ -69,6 +94,10 @@ function UplinkDetailModal({
|
||||
<span>网关消息ID</span>
|
||||
<strong>{message.messageId || '-'}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>匹配状态</span>
|
||||
<strong>{matchStatusText(message.matchStatus)}</strong>
|
||||
</div>
|
||||
<div className="admin-uplink-info-grid__full">
|
||||
<span>上行内容</span>
|
||||
<strong>{message.content || '-'}</strong>
|
||||
@@ -76,6 +105,58 @@ function UplinkDetailModal({
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="admin-uplink-match-section">
|
||||
<h3>候选认领</h3>
|
||||
{claimError ? <p className="form-error">{claimError}</p> : null}
|
||||
{candidates.length === 0 ? <div className="admin-uplink-empty-match">暂无人工认领候选</div> : null}
|
||||
{candidates.map((candidate) => (
|
||||
<article className="admin-uplink-match-card" key={candidate.id}>
|
||||
<div className="admin-uplink-match-grid">
|
||||
<div>
|
||||
<span>候选企业</span>
|
||||
<strong>{candidate.tenant?.name ?? candidate.tenantId}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>候选应用</span>
|
||||
<strong>{candidate.application?.name ?? candidate.applicationId}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>候选来源</span>
|
||||
<strong>{candidate.matchSource === 'access_number' ? '接入号' : '手机号时间窗'}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>置信度</span>
|
||||
<strong>{candidate.confidence}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>状态</span>
|
||||
<strong>{candidateStatusText(candidate.status)}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>下发短信</span>
|
||||
<strong>{candidate.messageRecord?.messageId ?? '-'}</strong>
|
||||
</div>
|
||||
</div>
|
||||
{candidate.messageRecord ? (
|
||||
<div className="admin-uplink-match-content">
|
||||
<span>下发内容</span>
|
||||
<p>{candidate.messageRecord.content}</p>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="admin-uplink-candidate-footer">
|
||||
<span>{candidate.reason ?? '-'}</span>
|
||||
<Button
|
||||
disabled={claimingId === candidate.id || candidate.status === 'claimed' || candidate.status === 'rejected'}
|
||||
onClick={() => onClaim(candidate)}
|
||||
size="sm"
|
||||
>
|
||||
{candidate.status === 'claimed' ? '已认领' : claimingId === candidate.id ? '认领中...' : '认领并推送'}
|
||||
</Button>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<section className="admin-uplink-match-section">
|
||||
<h3>匹配发送记录</h3>
|
||||
{matching ? <p>正在查询真实下发记录...</p> : null}
|
||||
@@ -125,8 +206,10 @@ export function AdminSmsUplinkRecordsPage() {
|
||||
const [selectedMessage, setSelectedMessage] = useState<SmsUplinkMessage | null>(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);
|
||||
@@ -143,6 +226,7 @@ export function AdminSmsUplinkRecordsPage() {
|
||||
setSelectedMessage(message);
|
||||
setMatchedRecords([]);
|
||||
setDetailError('');
|
||||
setClaimError('');
|
||||
|
||||
if (!message.messageId) {
|
||||
return;
|
||||
@@ -177,6 +261,22 @@ export function AdminSmsUplinkRecordsPage() {
|
||||
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<TableColumn<SmsUplinkMessage>> = [
|
||||
{
|
||||
key: 'select',
|
||||
@@ -190,6 +290,7 @@ export function AdminSmsUplinkRecordsPage() {
|
||||
{ key: 'content', title: '上行内容', render: (record) => <span className="uplink-content">{record.content}</span> },
|
||||
{ key: 'channel', title: '上行通道', width: '260px', render: (record) => <strong>{record.channel?.name ?? record.channelId}</strong> },
|
||||
{ key: 'accessNo', title: '上行接入号', width: '180px', render: (record) => <strong>{record.destId}</strong> },
|
||||
{ key: 'matchStatus', title: '匹配状态', width: '140px', render: (record) => <strong>{matchStatusText(record.matchStatus)}</strong> },
|
||||
{
|
||||
key: 'actions',
|
||||
title: '操作',
|
||||
@@ -228,10 +329,13 @@ export function AdminSmsUplinkRecordsPage() {
|
||||
|
||||
{selectedMessage ? (
|
||||
<UplinkDetailModal
|
||||
claimError={claimError}
|
||||
claimingId={claimingId}
|
||||
detailError={detailError}
|
||||
matchedRecords={matchedRecords}
|
||||
matching={matching}
|
||||
message={selectedMessage}
|
||||
onClaim={handleClaim}
|
||||
onClose={() => setSelectedMessage(null)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
Reference in New Issue
Block a user