fix: 补齐签名导入待审通知并展示通道组成本

This commit is contained in:
hectorzhao
2026-09-05 21:22:18 +08:00
parent 15a1f9d8ed
commit 839dba8d9b
13 changed files with 103 additions and 6 deletions
+1
View File
@@ -128,6 +128,7 @@ export type UserPayload = {
};
export type PendingAuditCounts = {
signatureImports?: number;
enterpriseCertifications: number;
smsAudits: number;
templates: number;
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react';
import { CheckCircle2, Info, Pencil, Plus, RadioTower, Trash2 } from 'lucide-react';
import { useNavigate, useParams } from 'react-router-dom';
import { adminApi, type AdminChannel, type ChannelGroup } from '@/api/adminApi';
import { formatRateAmount, MONEY_UNITS_PER_YUAN } from '@/utils/currency';
import { Breadcrumb, Button, Input, Modal, Select, Table, Tag, type TableColumn } from '@/components/ui';
type Carrier = 'mobile' | 'unicom' | 'telecom';
@@ -227,6 +228,15 @@ export function AdminChannelGroupFormPage() {
width: '280px',
render: (route) => channelById.get(route.channelId)?.name ?? '未命名通道',
},
{
key: 'cost',
title: '成本价格(元/条)',
width: '180px',
render: (route) => {
const price = channelById.get(route.channelId)?.unitPrice;
return price == null ? '—' : formatRateAmount(price / MONEY_UNITS_PER_YUAN);
},
},
{
key: 'status',
title: '通道状态',
@@ -253,6 +263,15 @@ export function AdminChannelGroupFormPage() {
width: '280px',
render: (route) => channelById.get(route.channelId)?.name ?? '未命名通道',
},
{
key: 'cost',
title: '成本价格(元/条)',
width: '180px',
render: (route) => {
const price = channelById.get(route.channelId)?.unitPrice;
return price == null ? '—' : formatRateAmount(price / MONEY_UNITS_PER_YUAN);
},
},
{
key: 'status',
title: '通道状态',
@@ -0,0 +1,21 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MemoryRouter, Link } from 'react-router-dom';
import { expect, it, vi } from 'vitest';
import { AdminSignatureAuditPage } from './AdminSignatureAuditPage';
vi.mock('@/api/adminApi', () => ({ adminApi: { listEnterpriseSignatures: vi.fn().mockResolvedValue([]) } }));
vi.mock('./ReportImportAuditPanel', () => ({ ReportImportAuditPanel: () => <div></div> }));
it('opens import reviews when the notification changes query parameters on the current audit page', async () => {
const user = userEvent.setup();
render(
<MemoryRouter initialEntries={['/admin/signatures']}>
<Link to="/admin/signatures?tab=import"></Link>
<AdminSignatureAuditPage />
</MemoryRouter>,
);
expect(screen.queryByText('导入审核面板内容')).not.toBeInTheDocument();
await user.click(screen.getByRole('link', { name: '签名导入待审入口' }));
expect(await screen.findByText('导入审核面板内容')).toBeVisible();
});
+1 -2
View File
@@ -161,7 +161,7 @@ function SignatureDetail({ item, onClose }: { item: ClientSmsSignature; onClose:
export function AdminSignatureAuditPage() {
const [searchParams, setSearchParams] = useSearchParams();
const requestedImportBatchId = searchParams.get('batchId') ?? undefined;
const [activeTab, setActiveTab] = useState(searchParams.get('tab') === 'import' ? 'import' : 'single');
const activeTab = searchParams.get('tab') === 'import' ? 'import' : 'single';
const [items, setItems] = useState<ClientSmsSignature[]>([]);
const [keyword, setKeyword] = useState('');
const [status, setStatus] = useState('pending');
@@ -269,7 +269,6 @@ export function AdminSignatureAuditPage() {
{error ? <p className="form-error">{error}</p> : null}
<Tabs
onChange={(value) => {
setActiveTab(value);
setSearchParams(value === 'import' ? { tab: 'import' } : {});
}}
value={activeTab}
@@ -104,6 +104,7 @@ export function ReportImportAuditPanel({
setError('');
try {
await adminApi.reviewReportImportItems(detail.id, { decision, itemIds, reason: reason.trim() || undefined });
window.dispatchEvent(new Event('cmpp-audit-count-refresh'));
setSelected(new Set());
setReason('');
setRejectOpen(false);
@@ -118,6 +118,8 @@ describe('ReportMaterialImportModal mapping profile action', () => {
});
const user = userEvent.setup();
const onCompleted = vi.fn();
const refresh = vi.fn();
window.addEventListener('cmpp-audit-count-refresh', refresh);
render(<ReportMaterialImportModal onClose={vi.fn()} onCompleted={onCompleted} />);
await waitFor(() => expect(adminApi.listTenantOptions).toHaveBeenCalledTimes(1));
await user.click(screen.getByText('所属企业').closest('label')!.querySelector('button')!);
@@ -146,5 +148,7 @@ describe('ReportMaterialImportModal mapping profile action', () => {
),
);
expect(onCompleted).toHaveBeenCalledWith(expect.objectContaining({ id: 'analysis-1', status: 'pending_review' }));
expect(refresh).toHaveBeenCalledTimes(1);
window.removeEventListener('cmpp-audit-count-refresh', refresh);
}, 10_000);
});
@@ -270,6 +270,7 @@ export function ReportMaterialImportModal({
}
: undefined,
});
window.dispatchEvent(new Event('cmpp-audit-count-refresh'));
onCompleted(result);
onClose();
} catch (failure) {
+7 -2
View File
@@ -30,12 +30,12 @@ import {
Users,
UserX,
} from 'lucide-react';
import { adminApi } from '@/api/adminApi';
import { adminApi, type PendingAuditCounts } from '@/api/adminApi';
import { getLastUserActivityAt, readSession, type LoginSession } from '@/api/session';
import { AppShell } from '@/layouts/AppShell';
import { PortalSessionBoundary } from '@/layouts/PortalSessionBoundary';
const EMPTY_PENDING_AUDITS = {
const EMPTY_PENDING_AUDITS: Omit<PendingAuditCounts, 'total'> = {
enterpriseCertifications: 0,
smsAudits: 0,
templates: 0,
@@ -153,6 +153,11 @@ function AdminAuthenticatedLayout({ session }: { session: LoginSession }) {
{ label: '短信审核待审', count: pendingAudits.smsAudits, to: '/admin/sms-audit' },
{ label: '模板待审', count: pendingAudits.templates, to: '/admin/templates' },
{ label: '签名待审', count: pendingAudits.signatures, to: '/admin/signatures' },
{
label: '签名导入待审',
count: pendingAudits.signatureImports ?? 0,
to: '/admin/signatures?tab=import',
},
{ label: '引流信息待审', count: pendingAudits.drainageInfos, to: '/admin/drainage-audits' },
]}
navSections={[