fix: align signature review and admin operations
This commit is contained in:
@@ -346,6 +346,7 @@ export type DashboardResponse = {
|
||||
alertCount: number;
|
||||
};
|
||||
accounts: Array<{ id: string; tenantId: string; balanceCents: number; creditCents: number; status: string; tenant?: TenantOption }>;
|
||||
enterpriseSpendRanks: Array<{ tenantId: string; tenantName: string; todaySpendCents: number; balanceCents: number; creditCents: number }>;
|
||||
recentTasks: Array<Record<string, unknown>>;
|
||||
recentRecharges: Array<RechargeOrder>;
|
||||
};
|
||||
@@ -374,6 +375,8 @@ export type SignatureQualityStat = {
|
||||
tenantName: string;
|
||||
hasDrainage: boolean;
|
||||
total: number;
|
||||
acceptedCount: number;
|
||||
submitFailureCount: number;
|
||||
successCount: number;
|
||||
unknownCount: number;
|
||||
failureCount: number;
|
||||
@@ -639,6 +642,7 @@ export type SmsMessageRecord = {
|
||||
export type SmsSubmitRecord = {
|
||||
id: string;
|
||||
channelId: string;
|
||||
channelGroupName?: string | null;
|
||||
submitId: string;
|
||||
sequenceId?: number | null;
|
||||
gatewayMessageId?: string | null;
|
||||
@@ -648,6 +652,7 @@ export type SmsSubmitRecord = {
|
||||
submittedAt?: string | null;
|
||||
createdAt: string;
|
||||
channel?: AdminChannel | null;
|
||||
channelGroup?: { id: string; name: string } | null;
|
||||
};
|
||||
|
||||
export type SmsReceiptRecord = {
|
||||
|
||||
@@ -332,6 +332,8 @@ function ChannelFormModal({
|
||||
<Input
|
||||
hint={modal.mode === 'edit' ? '已配置的密码不会回显;留空保持不变,填写新密码才更新。' : undefined}
|
||||
label="网关密码"
|
||||
autoComplete="new-password"
|
||||
name="cmpp-gateway-password"
|
||||
onChange={(event) => setPassword(event.target.value)}
|
||||
placeholder={modal.mode === 'edit' ? '留空不修改' : '请输入网关密码'}
|
||||
required={modal.mode === 'create'}
|
||||
@@ -339,7 +341,7 @@ function ChannelFormModal({
|
||||
value={password}
|
||||
/>
|
||||
<div className="sms-channel-inline-field">
|
||||
<Input label="* 接入号" onChange={(event) => setAccessNo(event.target.value)} placeholder="请输入通道接入号" value={accessNo} />
|
||||
<Input autoComplete="off" label="* 接入号" name="cmpp-access-number" onChange={(event) => setAccessNo(event.target.value)} placeholder="请输入通道接入号" value={accessNo} />
|
||||
<Input label="扩展位数" max="20" min="0" onChange={(event) => setExtensionDigits(event.target.value)} type="number" value={extensionDigits} />
|
||||
</div>
|
||||
<Input label="* 通道流速" max="2000" min="1" onChange={(event) => setFlowLimit(event.target.value)} suffix="条/秒" type="number" value={flowLimit} />
|
||||
@@ -446,7 +448,9 @@ function SmsTestModal({
|
||||
<strong>{content.length} 字符 <i /> 计费 {billingCount} 条</strong>
|
||||
</div>
|
||||
<Input
|
||||
autoComplete="off"
|
||||
label="接入号(选填)"
|
||||
name="channel-test-access-number"
|
||||
onChange={(event) => setAccessNo(event.target.value)}
|
||||
placeholder="请输入接入号"
|
||||
value={accessNo}
|
||||
|
||||
@@ -70,19 +70,17 @@ export function AdminHome() {
|
||||
}, []);
|
||||
|
||||
const enterpriseSpendRanks = useMemo<EnterpriseSpendRank[]>(() => {
|
||||
return (dashboard?.accounts ?? []).map((account) => {
|
||||
const todaySpend = Math.abs(dashboard?.recentRecharges
|
||||
.filter((item) => item.tenantId === account.tenantId)
|
||||
.reduce((sum, item) => sum + item.amountCents, 0) ?? 0) / 10_000;
|
||||
return (dashboard?.enterpriseSpendRanks ?? []).map((account) => {
|
||||
const todaySpend = moneyUnitsToYuan(account.todaySpendCents);
|
||||
const availableBalance = moneyUnitsToYuan(account.balanceCents + account.creditCents);
|
||||
return {
|
||||
id: account.tenantId,
|
||||
enterprise: account.tenant?.name ?? account.tenantId,
|
||||
enterprise: account.tenantName,
|
||||
todaySpend,
|
||||
availableBalance,
|
||||
balanceStatus: (availableBalance <= 0 ? '欠费' : availableBalance < 100 ? '紧张' : '充足') as EnterpriseSpendRank['balanceStatus'],
|
||||
};
|
||||
}).sort((left, right) => right.todaySpend - left.todaySpend);
|
||||
});
|
||||
}, [dashboard]);
|
||||
|
||||
const totalSend = dashboard?.today.sent ?? 0;
|
||||
@@ -144,6 +142,7 @@ export function AdminHome() {
|
||||
{ key: 'rank', title: '排名', width: '72px', render: (record) => record.rank },
|
||||
{ key: 'signatureName', title: '签名', render: (record) => <div><strong>{record.signatureName}</strong><p className="text-caption">{record.tenantName}</p></div> },
|
||||
{ key: 'total', title: '发送总数', align: 'right', render: (record) => formatCount(record.total) },
|
||||
{ key: 'submitFailureCount', title: '提交失败', align: 'right', render: (record) => formatCount(record.submitFailureCount) },
|
||||
{ key: 'successCount', title: '成功', align: 'right', render: (record) => formatCount(record.successCount) },
|
||||
{ key: 'unknownCount', title: '未知', align: 'right', render: (record) => formatCount(record.unknownCount) },
|
||||
{ key: 'failureCount', title: '失败', align: 'right', render: (record) => formatCount(record.failureCount) },
|
||||
|
||||
@@ -134,7 +134,7 @@ export function AdminRiskRulesPage() {
|
||||
return (
|
||||
<section className="page-stack">
|
||||
<div className="page-heading">
|
||||
<div><Breadcrumb items={['风控管理', '风控规则']} /><h1>风控规则</h1><p>维护全局阈值,并按企业应用覆盖;确定性号码校验和黑名单拦截不在此配置。</p></div>
|
||||
<div><Breadcrumb items={['审核中心', '风控规则']} /><h1>风控规则</h1><p>维护全局阈值,并按企业应用覆盖;确定性号码校验和黑名单拦截不在此配置。</p></div>
|
||||
<div className="page-heading__actions">
|
||||
<Button icon={<RefreshCw size={16} />} onClick={load} variant="ghost">刷新</Button>
|
||||
<Button icon={<Plus size={16} />} onClick={() => setEditor(editorFromRule())}>新增应用覆盖</Button>
|
||||
|
||||
@@ -44,6 +44,7 @@ const carrierLabelMap: Record<string, string> = {
|
||||
type RouteRow = {
|
||||
id: string;
|
||||
channel: string;
|
||||
channelGroup?: string | null;
|
||||
sentAt?: string | null;
|
||||
receiptAt?: string | null;
|
||||
receiptCode?: string | null;
|
||||
@@ -124,6 +125,7 @@ function getCarrierLabel(carrier?: string | null) {
|
||||
|
||||
function buildRouteRows(record: SmsMessageRecord, segmentAudits: SmsMessageSegmentAudit[]): RouteRow[] {
|
||||
if (segmentAudits.length > 0) {
|
||||
const submitById = new Map((record.submitRecords ?? []).map((submit) => [submit.submitId, submit]));
|
||||
const attempts = new Map<string, SmsMessageSegmentAudit[]>();
|
||||
segmentAudits.forEach((segment) => {
|
||||
const current = attempts.get(segment.submitId) ?? [];
|
||||
@@ -143,6 +145,7 @@ function buildRouteRows(record: SmsMessageRecord, segmentAudits: SmsMessageSegme
|
||||
channel: ordered.find((segment) => segment.channel?.name)?.channel?.name
|
||||
?? ordered.find((segment) => segment.channelId)?.channelId
|
||||
?? '-',
|
||||
channelGroup: submitById.get(submitId)?.channelGroupName ?? submitById.get(submitId)?.channelGroup?.name,
|
||||
sentAt: sentTimes.sort()[0],
|
||||
receiptAt: receiptTimes.sort()[receiptTimes.length - 1],
|
||||
receiptCode: receiptCodes.join(' / ') || undefined,
|
||||
@@ -163,6 +166,7 @@ function buildRouteRows(record: SmsMessageRecord, segmentAudits: SmsMessageSegme
|
||||
return {
|
||||
id: submit.id || String(index + 1),
|
||||
channel: submit.channel?.name ?? record.channel?.name ?? submit.channelId ?? '-',
|
||||
channelGroup: submit.channelGroupName ?? submit.channelGroup?.name,
|
||||
sentAt: submit.submittedAt ?? submit.createdAt,
|
||||
receiptAt: receipt?.deliveredAt,
|
||||
receiptCode: receipt?.rawStatus,
|
||||
@@ -237,6 +241,11 @@ function SendDetailModal({
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const routeRows = buildRouteRows(record, segmentAudits);
|
||||
const channelGroupNames = Array.from(new Set(routeRows.map((route) => route.channelGroup).filter(Boolean)));
|
||||
const orderedSegmentAudits = [...segmentAudits].sort((left, right) => {
|
||||
const timeDiff = new Date(left.createdAt).getTime() - new Date(right.createdAt).getTime();
|
||||
return timeDiff || left.segmentIndex - right.segmentIndex || left.id.localeCompare(right.id);
|
||||
});
|
||||
const sentAccessNumber = `${record.channel?.srcId ?? ''}${record.applicationExtension ?? ''}`;
|
||||
const displayStatus = getRecordStatus(record);
|
||||
const receiptNotice = getReceiptNotice(record);
|
||||
@@ -274,6 +283,10 @@ function SendDetailModal({
|
||||
<span>号码归属</span>
|
||||
<strong>{record.province ?? '-'} / {getCarrierLabel(record.carrier)}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>通道组</span>
|
||||
<strong>{channelGroupNames.join(' / ') || '-'}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>收到的接入号</span>
|
||||
<strong>{record.clientSrcId || '-'}</strong>
|
||||
@@ -302,6 +315,7 @@ function SendDetailModal({
|
||||
<span>{index + 1}</span>
|
||||
<div>
|
||||
<strong>{route.channel}</strong>
|
||||
<p className="muted">通道组:{route.channelGroup ?? '-'}</p>
|
||||
<dl>
|
||||
<div>
|
||||
<dt>发送时间</dt>
|
||||
@@ -334,7 +348,7 @@ function SendDetailModal({
|
||||
<div><span>提交状态</span><strong>{record.submitStatus ?? '-'}</strong></div>
|
||||
<div><span>回执状态</span><strong>{record.receiptStatus ?? '-'}</strong></div>
|
||||
</div>
|
||||
{['failed', 'rejected'].includes(record.status) || record.errorMessage || record.errorCode ? (
|
||||
{['submit_failed', 'failed', 'rejected'].includes(displayStatus) ? (
|
||||
<div className="admin-sms-detail-failure" role="alert">
|
||||
<AlertTriangle size={20} />
|
||||
<div><span>失败原因</span><strong>{record.errorMessage ?? record.errorCode ?? '未返回明确失败原因'}</strong></div>
|
||||
@@ -348,7 +362,7 @@ function SendDetailModal({
|
||||
<div className="ui-table__empty">暂无分片审计</div>
|
||||
) : (
|
||||
<div className="admin-sms-segment-list">
|
||||
{segmentAudits.map((segment) => (
|
||||
{orderedSegmentAudits.map((segment) => (
|
||||
<article className="admin-sms-segment-card" key={segment.id}>
|
||||
<header>
|
||||
<strong>分片 {segment.segmentIndex}/{segment.segmentTotal}</strong>
|
||||
@@ -363,6 +377,7 @@ function SendDetailModal({
|
||||
<div><dt>提交 ID</dt><dd>{segment.submitId}</dd></div>
|
||||
<div><dt>网关 MsgId</dt><dd>{segment.gatewayMessageId ?? '-'}</dd></div>
|
||||
<div><dt>补偿方式</dt><dd>{segment.compensationType ?? '-'}</dd></div>
|
||||
<div><dt>审计时间</dt><dd>{getTime(segment.createdAt)}</dd></div>
|
||||
<div><dt>错误信息</dt><dd>{segment.errorMessage ?? segment.errorCode ?? '-'}</dd></div>
|
||||
</dl>
|
||||
</article>
|
||||
|
||||
@@ -113,10 +113,10 @@ function AdminAuthenticatedLayout({ session }: { session: LoginSession }) {
|
||||
items: [
|
||||
{ label: '企业认证审核', to: '/admin/enterprise-audit', icon: ShieldCheck },
|
||||
{ label: '短信审核', to: '/admin/sms-audit', icon: MessageSquare },
|
||||
{ label: '风控规则', to: '/admin/risk-rules', icon: Shield },
|
||||
{ label: '短信模板审核', to: '/admin/templates', icon: FileCheck2 },
|
||||
{ label: '短信签名审核', to: '/admin/signatures', icon: FilePenLine },
|
||||
{ label: '引流信息审核', to: '/admin/drainage-audits', icon: FilePenLine },
|
||||
{ label: '风控规则', to: '/admin/risk-rules', icon: Shield },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -129,8 +129,10 @@
|
||||
}
|
||||
|
||||
.ui-field {
|
||||
align-content: start;
|
||||
display: grid;
|
||||
gap: var(--space-2);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ui-field__label {
|
||||
@@ -167,9 +169,11 @@
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
min-height: var(--control-height-md);
|
||||
min-width: 0;
|
||||
padding: 0 var(--control-padding-x);
|
||||
position: relative;
|
||||
transition: border-color var(--transition-fast), box-shadow var(--transition-fast), background var(--transition-fast);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ui-textarea {
|
||||
@@ -656,8 +660,12 @@
|
||||
font-size: var(--font-size-md);
|
||||
height: 36px;
|
||||
justify-content: flex-start;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
padding: 0 var(--space-3);
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -8732,6 +8732,12 @@ h3 {
|
||||
color: var(--color-text-strong);
|
||||
}
|
||||
|
||||
.admin-app-route-card > .ui-field {
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.downstream-attempt-timeline {
|
||||
border-top: 0 !important;
|
||||
display: grid !important;
|
||||
|
||||
Reference in New Issue
Block a user