feat: add phone frequency controls and modularize codebase

This commit is contained in:
hectorzhao
2026-07-31 22:25:23 +08:00
parent 0af671b4ed
commit ca4f591a13
216 changed files with 41579 additions and 23694 deletions
@@ -0,0 +1,156 @@
import { BarChart3, MapPin, Send, Smartphone, TrendingUp } from 'lucide-react';
import { Button, Modal, Table, Tag } from '@/components/ui';
import {
formatNumber,
formatTime,
getProgress,
getRegionRate,
getSuccessRate,
sendTypeLabels,
statusLabels,
statusTones,
} from './taskModel';
import type { RegionStat, SmsTask } from './taskTypes';
function TaskDetailTitle({ task }: { task: SmsTask }) {
return (
<div className="admin-task-detail-title">
<h2></h2>
<p>
<span>{task.id}</span>
<Tag tone={statusTones[task.status]}>{statusLabels[task.status]}</Tag>
</p>
</div>
);
}
function MetricCard({ label, value, tone }: { label: string; value: string; tone?: 'success' | 'primary' }) {
return (
<div className={['admin-task-metric', tone ? `admin-task-metric--${tone}` : ''].filter(Boolean).join(' ')}>
<span>{label}</span>
<strong>{value}</strong>
</div>
);
}
export function TaskDetailModal({ task, onClose }: { task: SmsTask; onClose: () => void }) {
const progress = getProgress(task);
const successRate = getSuccessRate(task);
const perPhoneBillingUnits = Math.max(1, Math.ceil(task.wordCount / 67));
return (
<Modal
footer={<Button onClick={onClose}></Button>}
onClose={onClose}
open
size="xl"
title={<TaskDetailTitle task={task} />}
>
<div className="admin-task-detail">
<div className="admin-task-metrics">
<MetricCard label="提交总数" value={formatNumber(task.submittedCount)} />
<MetricCard label="提交成功" tone="success" value={formatNumber(task.submittedSuccess)} />
<MetricCard label="发送成功" tone="primary" value={formatNumber(task.successCount)} />
<MetricCard label="计费条数" tone="primary" value={formatNumber(task.billingCount)} />
<MetricCard label="成功率" tone="primary" value={`${successRate.toFixed(2)}%`} />
</div>
<div className="admin-task-detail-grid">
<section className="admin-task-card">
<h3><Send size={18} /></h3>
<dl className="admin-task-info-list">
<div>
<dt>/</dt>
<dd><strong>{task.enterprise}</strong><span>{task.application}</span></dd>
</div>
<div>
<dt></dt>
<dd>{formatTime(task.submittedAt)}</dd>
</div>
<div>
<dt></dt>
<dd><Tag tone={task.sendType === 'immediate' ? 'info' : 'warning'}>{sendTypeLabels[task.sendType]}</Tag></dd>
</div>
</dl>
</section>
<section className="admin-task-card">
<h3><TrendingUp size={18} /></h3>
<div className="admin-task-progress-card">
<div>
<span> {formatNumber(task.sentCount)} / {formatNumber(task.phoneCount)}</span>
<strong>{progress}%</strong>
</div>
<div className="batch-progress__track">
<span className={`batch-progress__bar batch-progress__bar--${task.status === 'failed' ? 'terminated' : task.status}`} style={{ width: `${progress}%` }} />
</div>
<div className="admin-task-progress-split">
<span><strong>{formatNumber(task.submittedSuccess)}</strong></span>
<span><strong>{formatNumber(task.successCount)}</strong></span>
</div>
</div>
</section>
<section className="admin-task-card">
<h3><BarChart3 size={18} /></h3>
<div className="admin-task-template-block">
<span></span>
<p className="admin-task-template">{task.templateContent}</p>
</div>
<dl className="admin-task-template-meta">
<div><dt>/</dt><dd>{task.wordCount} <b>·</b> {perPhoneBillingUnits} /</dd></div>
<div><dt></dt><dd>{formatNumber(task.phoneCount)} </dd></div>
</dl>
<div className="admin-task-billing-note">
<span> 67 1 {perPhoneBillingUnits} {formatNumber(task.billingCount)} </span>
</div>
</section>
</div>
<section className="admin-task-card admin-task-card--full">
<h3><Smartphone size={18} /></h3>
{task.carriers.length === 0 ? (
<div className="admin-uplink-empty-match"></div>
) : (
<div className="admin-carrier-grid">
{task.carriers.map((carrier) => {
const rate = carrier.total > 0 ? (carrier.success / carrier.total) * 100 : 0;
return (
<article className={`admin-carrier-card admin-carrier-card--${carrier.tone}`} key={carrier.name}>
<strong>{carrier.name}</strong>
<p><span></span><b>{formatNumber(carrier.total)}</b></p>
<p><span></span><b>{formatNumber(carrier.success)}</b></p>
<div>
<em>{rate.toFixed(1)}%</em>
<span></span>
</div>
</article>
);
})}
</div>
)}
</section>
<section className="admin-task-card admin-task-card--full">
<h3><MapPin size={18} /></h3>
<Table
columns={[
{ key: 'region', title: '省份', render: (record: RegionStat) => <strong>{record.region}</strong> },
{ key: 'total', title: '总数', align: 'right', render: (record: RegionStat) => formatNumber(record.total) },
{ key: 'success', title: '成功', align: 'right', render: (record: RegionStat) => <span className="admin-success-text">{formatNumber(record.success)}</span> },
{
key: 'rate',
title: '成功率',
align: 'right',
render: (record: RegionStat) => <Tag tone={getRegionRate(record) >= 95 ? 'success' : 'info'}>{getRegionRate(record).toFixed(1)}%</Tag>,
},
]}
data={task.regions}
emptyText="暂无已识别省份记录"
rowKey={(record) => record.region}
/>
</section>
</div>
</Modal>
);
}