diff --git a/api/src/signature-retirement/signature-retirement.service.spec.ts b/api/src/signature-retirement/signature-retirement.service.spec.ts index a197289..21090cf 100644 --- a/api/src/signature-retirement/signature-retirement.service.spec.ts +++ b/api/src/signature-retirement/signature-retirement.service.spec.ts @@ -142,6 +142,19 @@ describe('SignatureRetirementService dimensions', () => { page: 2, 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 () => { diff --git a/api/src/signature-retirement/signature-retirement.service.ts b/api/src/signature-retirement/signature-retirement.service.ts index 39d8afc..943a93d 100644 --- a/api/src/signature-retirement/signature-retirement.service.ts +++ b/api/src/signature-retirement/signature-retirement.service.ts @@ -128,6 +128,7 @@ export class SignatureRetirementService implements OnModuleInit, OnModuleDestroy LEFT JOIN "SmsApplication" application ON application.id = detection."applicationId" WHERE message."createdAt" >= ${range?.gte} AND message."createdAt" <= ${range?.lte} + AND signature."auditStatus" <> 'deleted' AND (${dimensionType}::text IS NULL OR detection."dimensionType" = ${dimensionType}) AND (${tenantId}::text IS NULL OR detection."tenantId" = ${tenantId}) AND (${applicationId}::text IS NULL OR application.id = ${applicationId}) @@ -167,8 +168,18 @@ export class SignatureRetirementService implements OnModuleInit, OnModuleDestroy async unreadCount() { const range = shanghaiDateRange(shanghaiDateKey(), shanghaiDateKey()); - const count = await this.prisma.signatureRetirementMessage.count({ where: { createdAt: range, isRead: false, suppressed: false } }); - return { count }; + const rows = await this.prisma.$queryRaw>(Prisma.sql` + 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) { diff --git a/src/apps/admin/AdminSmsUplinkRecordsPage.tsx b/src/apps/admin/AdminSmsUplinkRecordsPage.tsx index 65d28d3..a976539 100644 --- a/src/apps/admin/AdminSmsUplinkRecordsPage.tsx +++ b/src/apps/admin/AdminSmsUplinkRecordsPage.tsx @@ -283,20 +283,20 @@ export function AdminSmsUplinkRecordsPage() { { key: 'select', title: '', - width: '72px', + width: '48px', 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: 'phoneNumber', title: '手机号码', width: '120px', render: (record) => {record.phoneNumber} }, + { key: 'receivedAt', title: '上行时间', width: '156px', render: (record) => {getTime(record.receivedAt)} }, + { key: 'content', title: '上行内容', width: '260px', render: (record) => {record.content} }, + { key: 'channel', title: '上行通道', width: '160px', render: (record) => {record.channel?.name ?? record.channelId} }, + { key: 'accessNo', title: '上行接入号', width: '120px', render: (record) => {record.destId} }, + { key: 'matchStatus', title: '匹配状态', width: '100px', render: (record) => {matchStatusText(record.matchStatus)} }, { key: 'actions', title: '操作', - width: '140px', + width: '88px', align: 'center', render: (record) => ( diff --git a/src/styles/global.css b/src/styles/global.css index 59045a0..4bb43ba 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -7791,7 +7791,17 @@ } .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 {