fix: recover gateway callbacks and client send flows

This commit is contained in:
hectorzhao
2026-08-27 14:30:02 +08:00
parent a280b4bb22
commit d6edb88b76
10 changed files with 167 additions and 10 deletions
+1
View File
@@ -211,6 +211,7 @@ export type ClientSmsApplication = {
scene?: string | null;
customerUnitPrice?: number | null;
queuePriority?: 'normal' | 'priority' | string | null;
templateMismatchMode?: 'reject' | 'manual_review' | 'direct_send' | string | null;
status: string;
dailyLimit?: number | null;
createdAt?: string;
+3 -2
View File
@@ -19,6 +19,7 @@ import {
type TableColumn,
} from '@/components/ui';
import { clientApi, type SmsBatchTask } from '@/api/adminApi';
import { formatDateTime } from '@/utils/dateTime';
type BatchTaskStatus = 'completed' | 'sending' | 'terminated';
@@ -170,7 +171,7 @@ export function ClientBatchTasksPage() {
),
},
{ key: 'applicationName', title: '应用名称', width: '150px', render: (record) => record.applicationName },
{ key: 'submittedAt', title: '提交时间', width: '130px', render: (record) => record.submittedAt },
{ key: 'submittedAt', title: '提交时间', width: '150px', render: (record) => formatDateTime(record.submittedAt) },
{ key: 'phoneCount', title: '发送号码数', width: '120px', render: (record) => record.phoneCount.toLocaleString('zh-CN') },
{ key: 'wordCount', title: '单号码字数', width: '120px', render: (record) => <strong>{record.wordCount} </strong> },
{
@@ -183,7 +184,7 @@ export function ClientBatchTasksPage() {
<Clock3 size={14} />
{record.sendType === 'immediate' ? '立即发送' : '定时发送'}
</span>
{record.scheduledAt ? <small>{record.scheduledAt}</small> : null}
{record.scheduledAt ? <small>{formatDateTime(record.scheduledAt)}</small> : null}
</div>
),
},
+40 -4
View File
@@ -14,6 +14,24 @@ type Recipient = {
type SendMode = 'now' | 'scheduled';
type ReceiverMode = 'manual' | 'import';
const supportedImportExtensions = new Set(['csv', 'tsv', 'txt']);
const supportedImportMimeTypes = new Set([
'text/csv',
'application/csv',
'application/vnd.ms-excel',
'text/tab-separated-values',
'text/tsv',
'text/plain',
]);
function assertSupportedImportFile(file: File) {
const extension = file.name.split('.').pop()?.toLowerCase() ?? '';
const mimeType = file.type.toLowerCase();
if (!supportedImportExtensions.has(extension) || (mimeType && !supportedImportMimeTypes.has(mimeType))) {
throw new Error('仅支持 CSV、TSV 或 TXT 文本文件');
}
}
export function ClientSendPage() {
const navigate = useNavigate();
const [applications, setApplications] = useState<ClientSmsApplication[]>([]);
@@ -82,7 +100,23 @@ export function ClientSendPage() {
const smsParts = wordCount === 0 ? 0 : wordCount <= 70 ? 1 : Math.ceil(wordCount / 67);
const estimatedCount = receiverCount * smsParts;
const requiredVariables = selectedTemplate?.variables?.map((item) => item.name) ?? [];
const canSubmit = Boolean(taskName && applicationId && signatureId && templateId && receiverCount > 0 && (sendMode === 'now' || scheduledAt));
const allowsDirectSend = selectedApplication?.templateMismatchMode === 'direct_send';
const validationMessage = !taskName.trim()
? '请填写任务名称'
: !applicationId
? '请选择短信应用'
: !signatureId
? '请选择已审核通过的短信签名'
: !templateId && !allowsDirectSend
? '当前应用必须选择已审核通过的短信模板'
: !messageContent.trim()
? '请填写短信内容'
: receiverCount < 1
? receiverMode === 'manual' ? '请至少填写一个接收号码' : '请导入至少一个有效号码'
: sendMode === 'scheduled' && !scheduledAt
? '请选择定时发送时间'
: '';
const canSubmit = validationMessage === '';
function updateRecipient(id: string, phone: string) {
setRecipients((items) => items.map((item) => (item.id === id ? { ...item, phone } : item)));
@@ -116,7 +150,7 @@ export function ClientSendPage() {
const submitRequest = receiverMode === 'manual'
? clientApi.createBatchTask({
applicationId,
templateId,
templateId: templateId || undefined,
content: previewText,
category: selectedTemplate?.category ?? taskName,
phones: validRecipients.map((item) => item.phone.trim()),
@@ -125,7 +159,7 @@ export function ClientSendPage() {
})
: clientApi.confirmImport({
applicationId,
templateId,
templateId: templateId || undefined,
content: previewText,
category: selectedTemplate?.category ?? taskName,
importContent,
@@ -172,6 +206,7 @@ export function ClientSendPage() {
setImportLoading(true);
setError('');
try {
assertSupportedImportFile(file);
const content = await file.text();
const preview = await clientApi.previewImport({
applicationId: applicationId || undefined,
@@ -352,7 +387,7 @@ export function ClientSendPage() {
style={{ display: 'none' }}
type="file"
/>
<Button disabled={importLoading || !templateId} onClick={() => document.getElementById('sms-import-file')?.click()} variant="ghost">
<Button disabled={importLoading || (!templateId && !allowsDirectSend)} onClick={() => document.getElementById('sms-import-file')?.click()} variant="ghost">
{importLoading ? '解析中...' : '选择文件'}
</Button>
{importFileName ? (
@@ -390,6 +425,7 @@ export function ClientSendPage() {
<Button disabled={!canSubmit || submitting} icon={<Send size={18} />} onClick={submitTask}>
{submitting ? '提交中...' : '提交发送任务'}
</Button>
{!canSubmit ? <span className="send-submit-hint">{validationMessage}</span> : null}
</div>
</div>
+18
View File
@@ -742,6 +742,24 @@
width: min(360px, calc(100vw - 48px));
}
@media (max-width: 780px) {
.ui-date-time-popover {
inset: auto 12px;
max-height: calc(100dvh - 24px);
overflow-y: auto;
position: fixed;
top: 12px;
width: auto;
}
.ui-date-time-popover .ui-date-range-actions {
background: var(--color-surface);
bottom: 0;
padding-top: var(--space-2);
position: sticky;
}
}
.ui-time-picker {
align-items: center;
border: 1px solid var(--color-border);
+7
View File
@@ -574,10 +574,17 @@
}
.send-submit-row {
align-items: center;
display: flex;
gap: var(--space-3);
justify-content: flex-end;
}
.send-submit-hint {
color: var(--color-text-muted);
font-size: var(--font-size-sm);
}
.mms-send-page .send-card {
box-shadow: none;
}