feat: build reporting workbench workflow

This commit is contained in:
hectorzhao
2026-09-02 14:30:15 +08:00
parent 53d3668306
commit 9b8196ecab
31 changed files with 7651 additions and 1973 deletions
@@ -1,8 +1,15 @@
import type { Dispatch, SetStateAction } from 'react';
import { ChevronDown, ChevronRight, Edit3, Plus, Triangle } from 'lucide-react';
import type { ClientSmsSignature } from '@/api/adminApi';
import { useNavigate } from 'react-router-dom';
import { Button, DeleteRiskAction, Pagination } from '@/components/ui';
import { AuditStatusTag, CarrierReportCount, formatSignatureName, readDrainagePayload, signatureCardVisual } from './signature.helpers';
import {
AuditStatusTag,
CarrierReportCount,
formatSignatureName,
readDrainagePayload,
signatureCardVisual,
} from './signature.helpers';
import type { DrainageInfo } from './signature.types';
type EnterpriseSignaturesTableProps = {
@@ -44,6 +51,7 @@ export function EnterpriseSignaturesTable({
totalPages,
visibleSignatures,
}: EnterpriseSignaturesTableProps) {
const navigate = useNavigate();
return (
<div className="signature-list admin-enterprise-signature-list">
<div className="enterprise-signature-table__head" role="row">
@@ -51,8 +59,33 @@ export function EnterpriseSignaturesTable({
<span className="enterprise-signature-table__sortable">
<span className="enterprise-signature-table__sort-actions">
<Button aria-pressed={signatureSort === 'asc'} icon={<Triangle aria-hidden="true" fill="currentColor" size={11} />} iconOnly onClick={() => setSignatureSort('asc')} size="sm" variant={signatureSort === 'asc' ? 'secondary' : 'ghost'}></Button>
<Button aria-pressed={signatureSort === 'desc'} icon={<Triangle aria-hidden="true" className="enterprise-signature-table__sort-triangle--down" fill="currentColor" size={11} />} iconOnly onClick={() => setSignatureSort('desc')} size="sm" variant={signatureSort === 'desc' ? 'secondary' : 'ghost'}></Button>
<Button
aria-pressed={signatureSort === 'asc'}
icon={<Triangle aria-hidden="true" fill="currentColor" size={11} />}
iconOnly
onClick={() => setSignatureSort('asc')}
size="sm"
variant={signatureSort === 'asc' ? 'secondary' : 'ghost'}
>
</Button>
<Button
aria-pressed={signatureSort === 'desc'}
icon={
<Triangle
aria-hidden="true"
className="enterprise-signature-table__sort-triangle--down"
fill="currentColor"
size={11}
/>
}
iconOnly
onClick={() => setSignatureSort('desc')}
size="sm"
variant={signatureSort === 'desc' ? 'secondary' : 'ghost'}
>
</Button>
</span>
</span>
<span></span>
@@ -62,33 +95,92 @@ export function EnterpriseSignaturesTable({
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
{visibleSignatures.map((signature) => {
const payload = readDrainagePayload(signature);
const visibleDrainageLinks = appliedDrainageKeyword
? payload.links.filter((item) => `${item.siteName} ${item.url} ${item.remark}`.includes(appliedDrainageKeyword))
? payload.links.filter((item) =>
`${item.siteName} ${item.url} ${item.remark}`.includes(appliedDrainageKeyword),
)
: payload.links;
const cardVisual = signatureCardVisual(signature.auditStatus, signature.carrierReportSummary);
const expanded = expandedSignatureId === signature.id || Boolean(appliedDrainageKeyword);
return (
<article aria-label={`签名总体状态:${cardVisual.label}`} className={`signature-card signature-card--${cardVisual.tone}`} key={signature.id} title={`总体状态:${cardVisual.label}`}>
<article
aria-label={`签名总体状态:${cardVisual.label}`}
className={`signature-card signature-card--${cardVisual.tone}`}
key={signature.id}
title={`总体状态:${cardVisual.label}`}
>
<div className="signature-summary">
<button aria-label="展开签名" onClick={() => setExpandedSignatureId(expanded ? '' : signature.id)} type="button">
<button
aria-label="展开签名"
onClick={() => setExpandedSignatureId(expanded ? '' : signature.id)}
type="button"
>
{expanded ? <ChevronDown size={18} /> : <ChevronRight size={18} />}
</button>
<div className="enterprise-signature-table__signature" data-label="签名"><strong>{formatSignatureName(signature.name)}</strong></div>
<div data-label="企业"><span className="signature-summary__regular-value">{signature.tenant?.name ?? signature.tenantId}</span></div>
<div data-label="应用"><span className="signature-summary__regular-value">{signature.application?.name ?? '-'}</span></div>
<div data-label="审核状态"><AuditStatusTag status={signature.auditStatus} /></div>
<div data-label="移动"><CarrierReportCount summary={signature.carrierReportSummary?.mobile} /></div>
<div data-label="联通"><CarrierReportCount summary={signature.carrierReportSummary?.unicom} /></div>
<div data-label="电信"><CarrierReportCount summary={signature.carrierReportSummary?.telecom} /></div>
<div data-label="引流信息"><strong>{payload.links.length} </strong></div>
<div className="enterprise-signature-table__signature" data-label="签名">
<strong>{formatSignatureName(signature.name)}</strong>
</div>
<div data-label="企业">
<span className="signature-summary__regular-value">{signature.tenant?.name ?? signature.tenantId}</span>
</div>
<div data-label="应用">
<span className="signature-summary__regular-value">{signature.application?.name ?? '-'}</span>
</div>
<div data-label="审核状态">
<AuditStatusTag status={signature.auditStatus} />
</div>
<div data-label="移动">
<CarrierReportCount summary={signature.carrierReportSummary?.mobile} />
</div>
<div data-label="联通">
<CarrierReportCount summary={signature.carrierReportSummary?.unicom} />
</div>
<div data-label="电信">
<CarrierReportCount summary={signature.carrierReportSummary?.telecom} />
</div>
<div data-label="引流信息">
<strong>{payload.links.length} </strong>
</div>
<div data-label="待生成明细">
<Button
disabled={!signature.pendingReportDetailCount}
onClick={() =>
navigate(`/admin/report-tasks?signatureId=${encodeURIComponent(signature.id)}&scope=pending`)
}
size="sm"
variant="ghost"
>
{signature.pendingReportDetailCount ?? 0}
</Button>
</div>
<div className="signature-actions">
<Button icon={<Edit3 size={16} />} onClick={() => setReportStatusTarget(signature)} size="sm" variant="ghost"></Button>
<Button icon={<Edit3 size={16} />} onClick={() => setSignatureModal(signature)} size="sm" variant="ghost"></Button>
<DeleteRiskAction onCompleted={() => void loadData()} portal="admin" targetId={signature.id} targetType="signature" />
<Button
icon={<Edit3 size={16} />}
onClick={() => setReportStatusTarget(signature)}
size="sm"
variant="ghost"
>
</Button>
<Button
icon={<Edit3 size={16} />}
onClick={() => setSignatureModal(signature)}
size="sm"
variant="ghost"
>
</Button>
<DeleteRiskAction
onCompleted={() => void loadData()}
portal="admin"
targetId={signature.id}
targetType="signature"
/>
</div>
</div>
{expanded ? (
@@ -107,25 +199,61 @@ export function EnterpriseSignaturesTable({
{visibleDrainageLinks.map((item) => {
const summary = signature.drainageCarrierReportSummary?.[item.id];
return (
<div className="drainage-table__row" key={item.id}>
<span className="drainage-table__url" title={item.url}>{item.url}</span>
<AuditStatusTag status={item.auditStatus ?? 'pending'} />
<CarrierReportCount summary={summary?.mobile} />
<CarrierReportCount summary={summary?.unicom} />
<CarrierReportCount summary={summary?.telecom} />
<span className="drainage-row-actions">
<Button disabled={item.auditStatus !== 'approved'} onClick={() => setDrainageStatusTarget({ signature, item })} size="sm" variant="ghost"></Button>
<Button onClick={() => setDrainageModal({ signatureId: signature.id, item })} size="sm" variant="ghost"></Button>
<Button onClick={() => setDeleteTarget({ kind: 'drainage', signatureId: signature.id, id: item.id, name: item.url })} size="sm" variant="danger"></Button>
</span>
</div>
);})}
<div className="drainage-table__row" key={item.id}>
<span className="drainage-table__url" title={item.url}>
{item.url}
</span>
<AuditStatusTag status={item.auditStatus ?? 'pending'} />
<CarrierReportCount summary={summary?.mobile} />
<CarrierReportCount summary={summary?.unicom} />
<CarrierReportCount summary={summary?.telecom} />
<span className="drainage-row-actions">
<Button
disabled={item.auditStatus !== 'approved'}
onClick={() => setDrainageStatusTarget({ signature, item })}
size="sm"
variant="ghost"
>
</Button>
<Button
onClick={() => setDrainageModal({ signatureId: signature.id, item })}
size="sm"
variant="ghost"
>
</Button>
<Button
onClick={() =>
setDeleteTarget({
kind: 'drainage',
signatureId: signature.id,
id: item.id,
name: item.url,
})
}
size="sm"
variant="danger"
>
</Button>
</span>
</div>
);
})}
</div>
) : (
<p className="muted"></p>
)}
<div className="drainage-panel__footer">
<Button icon={<Plus size={16} />} onClick={() => setDrainageModal({ signatureId: signature.id })} size="sm" variant="ghost"></Button>
<Button
icon={<Plus size={16} />}
onClick={() => setDrainageModal({ signatureId: signature.id })}
size="sm"
variant="ghost"
>
</Button>
</div>
</div>
) : null}