fix: hide deleted signatures and fit uplink table

This commit is contained in:
hectorzhao
2026-08-26 16:10:46 +08:00
parent 444c65288c
commit 898471423f
4 changed files with 45 additions and 11 deletions
@@ -142,6 +142,19 @@ describe('SignatureRetirementService dimensions', () => {
page: 2, page: 2,
pageSize: 10, pageSize: 10,
}); });
const query = prisma.$queryRaw.mock.calls[0]?.[0] as { strings?: readonly string[] };
expect(query.strings?.join('?')).toContain('signature."auditStatus" <> \'deleted\'');
});
it('does not count deleted signatures as unread retirement warnings', async () => {
const prisma = { $queryRaw: jest.fn().mockResolvedValue([{ count: 2 }]) };
const unreadService = new SignatureRetirementService(prisma as never);
await expect(unreadService.unreadCount()).resolves.toEqual({ count: 2 });
const query = prisma.$queryRaw.mock.calls[0]?.[0] as { strings?: readonly string[] };
const sql = query.strings?.join('?') ?? '';
expect(sql).toContain('JOIN "SmsSignature" signature ON signature.id = detection."signatureId"');
expect(sql).toContain('signature."auditStatus" <> \'deleted\'');
}); });
it('requires a reason for temporary and permanent suppression', async () => { it('requires a reason for temporary and permanent suppression', async () => {
@@ -128,6 +128,7 @@ export class SignatureRetirementService implements OnModuleInit, OnModuleDestroy
LEFT JOIN "SmsApplication" application ON application.id = detection."applicationId" LEFT JOIN "SmsApplication" application ON application.id = detection."applicationId"
WHERE message."createdAt" >= ${range?.gte} WHERE message."createdAt" >= ${range?.gte}
AND message."createdAt" <= ${range?.lte} AND message."createdAt" <= ${range?.lte}
AND signature."auditStatus" <> 'deleted'
AND (${dimensionType}::text IS NULL OR detection."dimensionType" = ${dimensionType}) AND (${dimensionType}::text IS NULL OR detection."dimensionType" = ${dimensionType})
AND (${tenantId}::text IS NULL OR detection."tenantId" = ${tenantId}) AND (${tenantId}::text IS NULL OR detection."tenantId" = ${tenantId})
AND (${applicationId}::text IS NULL OR application.id = ${applicationId}) AND (${applicationId}::text IS NULL OR application.id = ${applicationId})
@@ -167,8 +168,18 @@ export class SignatureRetirementService implements OnModuleInit, OnModuleDestroy
async unreadCount() { async unreadCount() {
const range = shanghaiDateRange(shanghaiDateKey(), shanghaiDateKey()); const range = shanghaiDateRange(shanghaiDateKey(), shanghaiDateKey());
const count = await this.prisma.signatureRetirementMessage.count({ where: { createdAt: range, isRead: false, suppressed: false } }); const rows = await this.prisma.$queryRaw<Array<{ count: number }>>(Prisma.sql`
return { count }; SELECT COUNT(*)::integer AS count
FROM "SignatureRetirementMessage" message
JOIN "SignatureRetirementDetection" detection ON detection.id = message."detectionId"
JOIN "SmsSignature" signature ON signature.id = detection."signatureId"
WHERE message."createdAt" >= ${range?.gte}
AND message."createdAt" <= ${range?.lte}
AND message."isRead" = FALSE
AND message.suppressed = FALSE
AND signature."auditStatus" <> 'deleted'
`);
return { count: rows[0]?.count ?? 0 };
} }
async markRead(id: string) { async markRead(id: string) {
+8 -8
View File
@@ -283,20 +283,20 @@ export function AdminSmsUplinkRecordsPage() {
{ {
key: 'select', key: 'select',
title: '', title: '',
width: '72px', width: '48px',
align: 'center', align: 'center',
render: () => <input aria-label="选择上行记录" className="admin-uplink-checkbox" type="checkbox" />, render: () => <input aria-label="选择上行记录" className="admin-uplink-checkbox" type="checkbox" />,
}, },
{ key: 'phoneNumber', title: '手机号码', width: '170px', render: (record) => <strong>{record.phoneNumber}</strong> }, { key: 'phoneNumber', title: '手机号码', width: '120px', render: (record) => <strong>{record.phoneNumber}</strong> },
{ key: 'receivedAt', title: '上行时间', width: '220px', render: (record) => <strong>{getTime(record.receivedAt)}</strong> }, { key: 'receivedAt', title: '上行时间', width: '156px', render: (record) => <strong>{getTime(record.receivedAt)}</strong> },
{ key: 'content', title: '上行内容', width: '360px', render: (record) => <span className="uplink-content" title={record.content}>{record.content}</span> }, { key: 'content', title: '上行内容', width: '260px', render: (record) => <span className="uplink-content" title={record.content}>{record.content}</span> },
{ key: 'channel', title: '上行通道', width: '260px', render: (record) => <strong>{record.channel?.name ?? record.channelId}</strong> }, { key: 'channel', title: '上行通道', width: '160px', render: (record) => <strong>{record.channel?.name ?? record.channelId}</strong> },
{ key: 'accessNo', title: '上行接入号', width: '180px', render: (record) => <strong>{record.destId}</strong> }, { key: 'accessNo', title: '上行接入号', width: '120px', render: (record) => <strong>{record.destId}</strong> },
{ key: 'matchStatus', title: '匹配状态', width: '140px', render: (record) => <strong>{matchStatusText(record.matchStatus)}</strong> }, { key: 'matchStatus', title: '匹配状态', width: '100px', render: (record) => <strong>{matchStatusText(record.matchStatus)}</strong> },
{ {
key: 'actions', key: 'actions',
title: '操作', title: '操作',
width: '140px', width: '88px',
align: 'center', align: 'center',
render: (record) => ( render: (record) => (
<button className="admin-uplink-detail-link" onClick={() => openDetail(record)} type="button"></button> <button className="admin-uplink-detail-link" onClick={() => openDetail(record)} type="button"></button>
+11 -1
View File
@@ -7791,7 +7791,17 @@
} }
.admin-uplink-table-card .ui-table { .admin-uplink-table-card .ui-table {
min-width: 1500px; min-width: 1052px;
table-layout: fixed;
}
.admin-uplink-table-card .uplink-content {
-webkit-line-clamp: 2;
min-width: 0;
}
.admin-uplink-table-card .ui-table td strong {
overflow-wrap: anywhere;
} }
.gateway-exception-list-heading { .gateway-exception-list-heading {