feat: add HTTP API and complete client workflows
This commit is contained in:
@@ -45,25 +45,8 @@ type SignatureFormState = {
|
||||
reportValues: ReportValues;
|
||||
};
|
||||
|
||||
const statusLabelMap: Record<CarrierStatus, string> = {
|
||||
approved: '已通过',
|
||||
pending: '审核中',
|
||||
rejected: '已驳回',
|
||||
filing: '待报备',
|
||||
};
|
||||
|
||||
const statusToneMap: Record<CarrierStatus, 'success' | 'info' | 'danger' | 'neutral'> = {
|
||||
approved: 'success',
|
||||
pending: 'info',
|
||||
rejected: 'danger',
|
||||
filing: 'neutral',
|
||||
};
|
||||
|
||||
function StatusTag({ status }: { status: CarrierStatus }) {
|
||||
return <Tag tone={statusToneMap[status]}>{statusLabelMap[status]}</Tag>;
|
||||
}
|
||||
|
||||
type CarrierReportSummary = { status: string; approved: number; total: number };
|
||||
type SignatureCardTone = 'green' | 'blue' | 'amber' | 'red' | 'gray';
|
||||
|
||||
function CarrierReportTag({ summary }: { summary?: CarrierReportSummary }) {
|
||||
if (!summary || summary.status === 'not_applicable' || summary.total === 0) return <Tag tone="neutral">不适用</Tag>;
|
||||
@@ -73,10 +56,26 @@ function CarrierReportTag({ summary }: { summary?: CarrierReportSummary }) {
|
||||
else if (summary.status === 'failed' || summary.status === 'rejected') { label = '报备失败'; tone = 'danger'; }
|
||||
else if (summary.status === 'waiting_material') { label = '资料待补充'; tone = 'warning'; }
|
||||
else if (summary.approved > 0) { label = '部分通过'; tone = 'info'; }
|
||||
else if (summary.status === 'reporting' || summary.status === 'exporting') { label = '报备中'; tone = 'info'; }
|
||||
else if (summary.status === 'reporting' || summary.status === 'exporting') { label = '报备中'; tone = 'warning'; }
|
||||
return <span className="carrier-report-summary"><Tag tone={tone}>{label}</Tag><small>({summary.approved}/{summary.total})</small></span>;
|
||||
}
|
||||
|
||||
function signatureCardVisual(auditStatus: string, summaries?: Record<string, CarrierReportSummary>) {
|
||||
if (auditStatus === 'rejected') return { label: '签名审核已驳回', tone: 'red' as SignatureCardTone };
|
||||
if (auditStatus === 'pending') return { label: '签名待审核', tone: 'amber' as SignatureCardTone };
|
||||
if (auditStatus !== 'approved') return { label: '签名尚未提交审核', tone: 'gray' as SignatureCardTone };
|
||||
|
||||
const values = Object.values(summaries ?? {});
|
||||
const applicable = values.filter((summary) => summary.total > 0 && summary.status !== 'not_applicable');
|
||||
if (applicable.some((summary) => ['failed', 'rejected'].includes(summary.status))) return { label: '存在报备失败', tone: 'red' as SignatureCardTone };
|
||||
if (applicable.some((summary) => summary.approved > 0 && summary.approved < summary.total)) return { label: '部分通道报备通过', tone: 'blue' as SignatureCardTone };
|
||||
if (applicable.some((summary) => summary.status === 'waiting_material')) return { label: '报备资料待补充', tone: 'amber' as SignatureCardTone };
|
||||
if (applicable.some((summary) => ['reporting', 'exporting'].includes(summary.status))) return { label: '通道报备处理中', tone: 'amber' as SignatureCardTone };
|
||||
if (applicable.length > 0 && applicable.every((summary) => summary.status === 'approved')) return { label: '所有目标通道报备通过', tone: 'green' as SignatureCardTone };
|
||||
if (applicable.some((summary) => summary.approved > 0)) return { label: '部分运营商报备通过', tone: 'blue' as SignatureCardTone };
|
||||
return { label: applicable.length > 0 ? '目标通道尚未报备' : '没有适用的目标通道', tone: 'gray' as SignatureCardTone };
|
||||
}
|
||||
|
||||
function AuditStatusTag({ status }: { status: string }) {
|
||||
const meta: Record<string, { label: string; tone: 'neutral' | 'info' | 'success' | 'danger' }> = {
|
||||
draft: { label: '草稿', tone: 'neutral' }, pending: { label: '待审核', tone: 'info' }, approved: { label: '已通过', tone: 'success' }, rejected: { label: '已驳回', tone: 'danger' },
|
||||
@@ -151,14 +150,6 @@ function normalizeCarrierStatus(value: unknown, fallback: CarrierStatus = 'filin
|
||||
return value === 'approved' || value === 'pending' || value === 'rejected' || value === 'filing' ? value : fallback;
|
||||
}
|
||||
|
||||
function signatureCardTone(statuses: { mobile: CarrierStatus; unicom: CarrierStatus; telecom: CarrierStatus }) {
|
||||
const values = Object.values(statuses);
|
||||
if (values.includes('rejected')) return 'red';
|
||||
if (values.every((status) => status === 'approved')) return 'green';
|
||||
if (values.includes('pending')) return 'blue';
|
||||
return 'gray';
|
||||
}
|
||||
|
||||
function formatDate(value?: string) {
|
||||
return formatDateTime(value);
|
||||
}
|
||||
@@ -697,11 +688,10 @@ export function AdminEnterpriseSignaturesPage() {
|
||||
const visibleDrainageLinks = appliedDrainageKeyword
|
||||
? payload.links.filter((item) => `${item.siteName} ${item.url} ${item.remark}`.includes(appliedDrainageKeyword))
|
||||
: payload.links;
|
||||
const summaryStatuses = Object.values(signature.carrierReportSummary ?? {}).map((summary) => summary.status);
|
||||
const cardTone = summaryStatuses.includes('failed') ? 'red' : summaryStatuses.length > 0 && summaryStatuses.every((status) => status === 'approved' || status === 'not_applicable') ? 'green' : summaryStatuses.some((status) => status === 'reporting') ? 'blue' : 'gray';
|
||||
const cardVisual = signatureCardVisual(signature.auditStatus, signature.carrierReportSummary);
|
||||
const expanded = expandedSignatureId === signature.id || Boolean(appliedDrainageKeyword);
|
||||
return (
|
||||
<article className={`signature-card signature-card--${cardTone}`} key={signature.id}>
|
||||
<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">
|
||||
{expanded ? <ChevronDown size={18} /> : <ChevronRight size={18} />}
|
||||
|
||||
Reference in New Issue
Block a user