fix: harden real backend workflows and channel connections

This commit is contained in:
hectorzhao
2026-07-06 17:54:53 +08:00
parent 8cca361441
commit b5132d7f4e
47 changed files with 2530 additions and 314 deletions
+63 -19
View File
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from 'react';
import { Download, Eye, FileUp, Search } from 'lucide-react';
import { adminApi, type ReportTask } from '@/api/adminApi';
import { Breadcrumb, Button, DateRangeInput, Input, Modal, Table, Tag, Textarea, type DateRangeValue, type TableColumn } from '@/components/ui';
import { adminApi, type FileObject, type FileRef, type ReportTask } from '@/api/adminApi';
import { Breadcrumb, Button, DateRangeInput, FileActions, Input, Modal, Table, Tag, Textarea, type DateRangeValue, type TableColumn } from '@/components/ui';
const statusMeta: Record<string, { label: string; tone: 'neutral' | 'info' | 'success' | 'warning' | 'danger' }> = {
pending: { label: '待处理', tone: 'neutral' },
@@ -13,12 +13,59 @@ const statusMeta: Record<string, { label: string; tone: 'neutral' | 'info' | 'su
failed: { label: '有失败', tone: 'danger' },
};
function ReceiptImportModal({ onClose, onSubmit }: { onClose: () => void; onSubmit: (file: File, remark: string) => void }) {
type ReceiptImportPayload = {
delimiter: ',' | '\t';
fileContent: string;
fileName: string;
fileObjectId: string;
remark: string;
};
function ReceiptImportModal({ onClose, onSubmit, task }: { onClose: () => void; onSubmit: (payload: ReceiptImportPayload) => void; task: ReportTask }) {
const [file, setFile] = useState<File | null>(null);
const [fileObject, setFileObject] = useState<FileObject | null>(null);
const [uploading, setUploading] = useState(false);
const [remark, setRemark] = useState('');
const [error, setError] = useState('');
const fileRef: FileRef | null = fileObject
? { contentType: fileObject.contentType, fileName: fileObject.fileName, fileObjectId: fileObject.id }
: null;
async function uploadReceiptFile(nextFile: File | undefined) {
setFile(nextFile ?? null);
setFileObject(null);
setError('');
if (!nextFile) {
return;
}
setUploading(true);
try {
const uploaded = await adminApi.uploadFileObject(nextFile, { purpose: 'report_receipt', prefix: `report-receipts/${task.id}` });
setFileObject(uploaded);
} catch (failure) {
setError(failure instanceof Error ? failure.message : '报备回执上传失败');
} finally {
setUploading(false);
}
}
async function submitImport() {
if (!file || !fileObject) {
return;
}
const delimiter: ',' | '\t' = file.name.toLowerCase().endsWith('.tsv') ? '\t' : ',';
onSubmit({
delimiter,
fileContent: await file.text(),
fileName: file.name,
fileObjectId: fileObject.id,
remark,
});
}
return (
<Modal
footer={<><Button onClick={onClose} variant="ghost"></Button><Button disabled={!file} onClick={() => file && onSubmit(file, remark)}></Button></>}
footer={<><Button onClick={onClose} variant="ghost"></Button><Button disabled={!fileObject || uploading} onClick={() => { void submitImport(); }}>{uploading ? '上传中...' : '确认导入'}</Button></>}
onClose={onClose}
open
size="xl"
@@ -29,13 +76,15 @@ function ReceiptImportModal({ onClose, onSubmit }: { onClose: () => void; onSubm
<FileUp size={38} />
<strong>{file?.name || '选择回执文件'}</strong>
<span> CSVTSVTXT /</span>
<FileActions file={fileRef} />
<input
accept=".csv,.tsv,.txt,text/csv,text/plain"
onChange={(event) => setFile(event.target.files?.[0] ?? null)}
onChange={(event) => { void uploadReceiptFile(event.target.files?.[0]); }}
style={{ display: 'none' }}
type="file"
/>
</label>
{error ? <p className="form-error">{error}</p> : null}
<Textarea label="导入备注" onChange={(event) => setRemark(event.target.value)} placeholder="记录回执来源、运营商工单号或人工处理说明" rows={4} value={remark} />
</div>
</Modal>
@@ -92,20 +141,15 @@ export function AdminReportTasksPage() {
.catch((failure: Error) => setError(failure.message || '报备任务导出失败'));
}
function importReceipt(file: File, remark: string) {
function importReceipt(payload: ReceiptImportPayload) {
if (!receiptTask) return;
const delimiter = file.name.toLowerCase().endsWith('.tsv') ? '\t' : ',';
Promise.all([
adminApi.uploadFileObject(file, { purpose: 'report_receipt', prefix: `report-receipts/${receiptTask.id}` }),
file.text(),
])
.then(([fileObject, fileContent]) => adminApi.importReportReceipt(receiptTask.id, {
fileObjectId: fileObject.id,
fileName: file.name,
fileContent,
delimiter,
reason: remark,
}))
adminApi.importReportReceipt(receiptTask.id, {
delimiter: payload.delimiter,
fileContent: payload.fileContent,
fileName: payload.fileName,
fileObjectId: payload.fileObjectId,
reason: payload.remark,
})
.then(() => {
setReceiptTask(null);
loadData();
@@ -156,7 +200,7 @@ export function AdminReportTasksPage() {
<Table columns={columns} data={filteredTasks} emptyText="暂无报备任务" rowKey="id" />
</div>
{receiptTask ? <ReceiptImportModal onClose={() => setReceiptTask(null)} onSubmit={importReceipt} /> : null}
{receiptTask ? <ReceiptImportModal onClose={() => setReceiptTask(null)} onSubmit={importReceipt} task={receiptTask} /> : null}
{detailTask ? <TaskDetailModal onClose={() => setDetailTask(null)} task={detailTask} /> : null}
</section>
);