fix: polish client templates docs dashboard and receipt display
CSS quality / css-quality (push) Has been cancelled

This commit is contained in:
hectorzhao
2026-09-14 22:53:34 +08:00
parent 7f9abe3da0
commit 4665079ca3
22 changed files with 1305 additions and 762 deletions
+126 -77
View File
@@ -1,4 +1,4 @@
import { Fragment, useEffect, useMemo, useState } from 'react';
import { Fragment, startTransition, useEffect, useMemo, useState } from 'react';
import { FileText, Search, Smartphone } from 'lucide-react';
import { clientApi, type SmsMessageRecord } from '@/api/adminApi';
import {
@@ -12,7 +12,7 @@ import {
Tag,
type DateRangeValue,
} from '@/components/ui';
import { recentBeijingDateRange } from '@/utils/dateTime';
import { formatDateTime, recentBeijingDateRange } from '@/utils/dateTime';
const statusLabelMap: Record<string, string> = {
delivered: '成功',
@@ -44,25 +44,34 @@ const statusToneMap: Record<string, 'success' | 'info' | 'danger' | 'neutral'> =
timeout: 'danger',
};
function getDate(value?: string | null) {
return value ? value.slice(0, 10) : '';
}
function getReceipt(record: SmsMessageRecord) {
const latest = record.receiptRecords?.[0] as { rawStatus?: string; receiptStatus?: string; deliveredAt?: string } | undefined;
const latest = record.receiptRecords?.[0] as
{ rawStatus?: string; receiptStatus?: string; deliveredAt?: string } | undefined;
return {
status: receiptStatusLabel(latest?.rawStatus ?? latest?.receiptStatus ?? record.receiptStatus),
time: latest?.deliveredAt ?? record.deliveredAt,
status: receiptStatusLabel(record.receiptStatus ?? latest?.rawStatus ?? latest?.receiptStatus),
time: record.deliveredAt ?? latest?.deliveredAt,
};
}
function receiptStatusLabel(status?: string | null) {
if (!status) return '-';
if (!status) return '暂无回执';
const normalized = status.trim().toUpperCase();
return {
DELIVRD: '送达成功', ACCEPTD: '已受理', UNDELIV: '未送达', REJECTD: '已拒绝',
EXPIRED: '已过期', DELETED: '已删除', UNKNOWN: '状态未知',
}[normalized] ?? statusLabelMap[status.toLowerCase()] ?? '状态未知';
return (
{
DELIVERED: '送达成功',
FAILED: '送达失败',
TIMEOUT: '回执超时',
DELIVRD: '送达成功',
ACCEPTD: '已受理',
UNDELIV: '未送达',
REJECTD: '已拒绝',
EXPIRED: '已过期',
DELETED: '已删除',
UNKNOWN: '状态未知',
}[normalized] ??
statusLabelMap[status.toLowerCase()] ??
'状态未知'
);
}
export function ClientSendDetailPage() {
@@ -81,16 +90,17 @@ export function ClientSendDetailPage() {
function loadData(targetPage = page) {
setLoading(true);
clientApi.listMessages({
applicationId: applied.applicationId === 'all' ? undefined : applied.applicationId,
phoneNumber: applied.phoneKeyword.trim() || undefined,
status: applied.status === 'all' ? undefined : applied.status,
contentKeyword: applied.contentKeyword.trim() || undefined,
queuedAtFrom: applied.dateRange.start || undefined,
queuedAtTo: applied.dateRange.end || undefined,
page: targetPage,
pageSize: 10,
})
clientApi
.listMessages({
applicationId: applied.applicationId === 'all' ? undefined : applied.applicationId,
phoneNumber: applied.phoneKeyword.trim() || undefined,
status: applied.status === 'all' ? undefined : applied.status,
contentKeyword: applied.contentKeyword.trim() || undefined,
queuedAtFrom: applied.dateRange.start || undefined,
queuedAtTo: applied.dateRange.end || undefined,
page: targetPage,
pageSize: 10,
})
.then((result) => {
setRecords(result.items);
setTotal(result.total);
@@ -101,20 +111,18 @@ export function ClientSendDetailPage() {
}
useEffect(() => {
loadData(page);
startTransition(() => loadData(page));
}, [applied, page]);
useEffect(() => {
clientApi.listApplicationOptions()
clientApi
.listApplicationOptions()
.then((items) => setApplications(items.map((item) => ({ id: item.id, name: item.name }))))
.catch((reason: Error) => setError(reason.message || '应用列表加载失败'));
}, []);
const applicationOptions = useMemo(() => {
return [
{ label: '全部应用', value: 'all' },
...applications.map((item) => ({ label: item.name, value: item.id })),
];
return [{ label: '全部应用', value: 'all' }, ...applications.map((item) => ({ label: item.name, value: item.id }))];
}, [applications]);
const filteredRows = records;
@@ -129,7 +137,13 @@ export function ClientSendDetailPage() {
}
function reset() {
const defaults = { applicationId: 'all', status: 'all', dateRange: recentBeijingDateRange(7), contentKeyword: '', phoneKeyword: '' };
const defaults = {
applicationId: 'all',
status: 'all',
dateRange: recentBeijingDateRange(7),
contentKeyword: '',
phoneKeyword: '',
};
setApplicationId(defaults.applicationId);
setStatus(defaults.status);
setDateRange(defaults.dateRange);
@@ -150,9 +164,18 @@ export function ClientSendDetailPage() {
<QueryPanel
title="查询条件"
summary={<> <strong>{total}</strong> </>}
summary={
<>
<strong>{total}</strong>
</>
}
>
<Select label="应用名称" onChange={(event) => setApplicationId(event.target.value)} options={applicationOptions} value={applicationId} />
<Select
label="应用名称"
onChange={(event) => setApplicationId(event.target.value)}
options={applicationOptions}
value={applicationId}
/>
<DateRangeInput label="发送时间" onChange={setDateRange} value={dateRange} />
<Select
label="发送状态"
@@ -202,53 +225,79 @@ export function ClientSendDetailPage() {
</thead>
<tbody>
{loading ? (
<tr><td className="ui-table__empty" colSpan={9}>...</td></tr>
<tr>
<td className="ui-table__empty" colSpan={9}>
...
</td>
</tr>
) : filteredRows.length === 0 ? (
<tr><td className="ui-table__empty" colSpan={9}></td></tr>
) : visibleRows.map((record) => {
const receipt = getReceipt(record);
const region = record.province ?? '-';
return (
<Fragment key={record.id}>
<tr className="send-detail-main-row">
<td><strong className="send-detail-app-name">{record.application?.name ?? record.applicationId ?? '-'}</strong></td>
<td>
<span className="send-detail-time">
{record.queuedAt.slice(0, 10)}
<small>{record.queuedAt.slice(11, 19)}</small>
</span>
</td>
<td style={{ textAlign: 'center' }}>
<span className="send-detail-count">
<strong>{[...record.content].length}</strong>
<small>{record.billingUnits}</small>
</span>
</td>
<td><strong>{record.phoneNumber}</strong></td>
<td>{record.carrier ? <CarrierTag carrier={record.carrier} /> : '-'}</td>
<td><span className="send-detail-region">{region}</span></td>
<td style={{ textAlign: 'center' }}><Tag tone={statusToneMap[record.status] ?? 'info'}>{statusLabelMap[record.status] ?? '状态未知'}</Tag></td>
<td style={{ textAlign: 'center' }}><strong className="send-detail-receipt-code">{receipt.status}</strong></td>
<td>
{receipt.time ? (
<tr>
<td className="ui-table__empty" colSpan={9}>
</td>
</tr>
) : (
visibleRows.map((record) => {
const receipt = getReceipt(record);
const region = record.province ?? '-';
return (
<Fragment key={record.id}>
<tr className="send-detail-main-row">
<td>
<strong className="send-detail-app-name">
{record.application?.name ?? record.applicationId ?? '-'}
</strong>
</td>
<td>
<span className="send-detail-time">
{receipt.time.slice(0, 10)}
<small>{receipt.time.slice(11, 19)}</small>
{formatDateTime(record.queuedAt).slice(0, 10)}
<small>{formatDateTime(record.queuedAt).slice(11, 19)}</small>
</span>
) : <span className="muted">-</span>}
</td>
</tr>
<tr className="send-detail-content-row">
<td colSpan={9}>
<div className="send-detail-content-block">
<span></span>
<p>{record.content}</p>
</div>
</td>
</tr>
</Fragment>
);
})}
</td>
<td style={{ textAlign: 'center' }}>
<span className="send-detail-count">
<strong>{[...record.content].length}</strong>
<small>{record.billingUnits}</small>
</span>
</td>
<td>
<strong>{record.phoneNumber}</strong>
</td>
<td>{record.carrier ? <CarrierTag carrier={record.carrier} /> : '-'}</td>
<td>
<span className="send-detail-region">{region}</span>
</td>
<td style={{ textAlign: 'center' }}>
<Tag tone={statusToneMap[record.status] ?? 'info'}>
{statusLabelMap[record.status] ?? '状态未知'}
</Tag>
</td>
<td style={{ textAlign: 'center' }}>
<strong className="send-detail-receipt-code">{receipt.status}</strong>
</td>
<td>
{receipt.time ? (
<span className="send-detail-time">
{formatDateTime(receipt.time).slice(0, 10)}
<small>{formatDateTime(receipt.time).slice(11, 19)}</small>
</span>
) : (
<span className="muted">-</span>
)}
</td>
</tr>
<tr className="send-detail-content-row">
<td colSpan={9}>
<div className="send-detail-content-block">
<span></span>
<p>{record.content}</p>
</div>
</td>
</tr>
</Fragment>
);
})
)}
</tbody>
</table>
</div>