fix: harden real backend workflows and channel connections
This commit is contained in:
@@ -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>支持 CSV、TSV、TXT 文本回执,需包含状态/结果列。</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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user