fix: harden real backend workflows and channel connections
This commit is contained in:
@@ -146,7 +146,7 @@ export function ClientApplicationsPage() {
|
||||
<dd>{formatPrice(application.customerUnitPrice)}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>CMPP链接状态</dt>
|
||||
<dt>CMPP连接状态</dt>
|
||||
<dd><Tag tone={statusToneMap[linkStatus]}>{statusLabelMap[linkStatus]}</Tag></dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
Upload,
|
||||
UserCheck,
|
||||
} from 'lucide-react';
|
||||
import { Button, Input, Select, Textarea } from '@/components/ui';
|
||||
import { clientApi, type EnterpriseCertification, type FileObject } from '@/api/adminApi';
|
||||
import { Button, FileActions, Input, Select, Textarea } from '@/components/ui';
|
||||
import { clientApi, type EnterpriseCertification, type FileObject, type FileRef } from '@/api/adminApi';
|
||||
|
||||
type AuthStep = 'overview' | 'profile' | 'method' | 'recharge' | 'face' | 'faceScan' | 'pending' | 'success' | 'failed';
|
||||
type AuthMethod = 'face' | 'recharge';
|
||||
@@ -52,10 +52,15 @@ const emptyCertificationForm: CertificationForm = {
|
||||
};
|
||||
|
||||
function UploadPanel({ file, uploading, onFile }: { file: FileObject | null; uploading: boolean; onFile: (file: File | undefined) => void }) {
|
||||
const fileRef: FileRef | null = file
|
||||
? { contentType: file.contentType, fileName: file.fileName, fileObjectId: file.id }
|
||||
: null;
|
||||
|
||||
return (
|
||||
<label className="enterprise-upload">
|
||||
<Upload size={38} />
|
||||
<strong>{uploading ? '上传中...' : file?.fileName ?? '点击上传'}</strong>
|
||||
<FileActions file={fileRef} />
|
||||
<input
|
||||
accept="image/png,image/jpeg,image/webp,application/pdf"
|
||||
disabled={uploading}
|
||||
@@ -203,6 +208,7 @@ export function ClientEnterpriseAuthPage() {
|
||||
materials: {
|
||||
licenseFileObjectId: licenseFile?.id,
|
||||
licenseFileName: licenseFile?.fileName,
|
||||
licenseFileContentType: licenseFile?.contentType,
|
||||
province: form.province,
|
||||
city: form.city,
|
||||
address: form.address,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Check, FileText, Plus, Search, Send, Trash2 } from 'lucide-react';
|
||||
import { Check, Download, FileText, Plus, Search, Send, Trash2 } from 'lucide-react';
|
||||
import { Button, DateTimeInput, Input, Modal, Select, Tag, Textarea } from '@/components/ui';
|
||||
import { clientApi, type ClientSmsApplication, type ClientSmsSignature, type ClientSmsTemplate, type ImportPreviewResponse, type SmsBatchTask } from '@/api/adminApi';
|
||||
|
||||
@@ -29,6 +29,7 @@ export function ClientSendPage() {
|
||||
const [recipients, setRecipients] = useState<Recipient[]>([{ id: '1', phone: '' }]);
|
||||
const [importContent, setImportContent] = useState('');
|
||||
const [importFileName, setImportFileName] = useState('');
|
||||
const [importFileUrl, setImportFileUrl] = useState('');
|
||||
const [importPreview, setImportPreview] = useState<ImportPreviewResponse | null>(null);
|
||||
const [importLoading, setImportLoading] = useState(false);
|
||||
const [submittedRecord, setSubmittedRecord] = useState<SmsBatchTask | null>(null);
|
||||
@@ -44,6 +45,12 @@ export function ClientSendPage() {
|
||||
.catch((reason: Error) => setError(reason.message || '短信发送基础数据加载失败'));
|
||||
}, []);
|
||||
|
||||
useEffect(() => () => {
|
||||
if (importFileUrl) {
|
||||
URL.revokeObjectURL(importFileUrl);
|
||||
}
|
||||
}, [importFileUrl]);
|
||||
|
||||
const selectedSignature = useMemo(
|
||||
() => signatures.find((item) => item.id === signatureId),
|
||||
[signatures, signatureId],
|
||||
@@ -133,10 +140,22 @@ export function ClientSendPage() {
|
||||
});
|
||||
setImportContent(content);
|
||||
setImportFileName(file.name);
|
||||
setImportFileUrl((current) => {
|
||||
if (current) {
|
||||
URL.revokeObjectURL(current);
|
||||
}
|
||||
return URL.createObjectURL(file);
|
||||
});
|
||||
setImportPreview(preview);
|
||||
} catch (reason) {
|
||||
setImportContent('');
|
||||
setImportFileName('');
|
||||
setImportFileUrl((current) => {
|
||||
if (current) {
|
||||
URL.revokeObjectURL(current);
|
||||
}
|
||||
return '';
|
||||
});
|
||||
setImportPreview(null);
|
||||
setError(reason instanceof Error ? reason.message : '导入预览失败');
|
||||
} finally {
|
||||
@@ -294,7 +313,17 @@ export function ClientSendPage() {
|
||||
<Button disabled={importLoading || !templateId} onClick={() => document.getElementById('sms-import-file')?.click()} variant="ghost">
|
||||
{importLoading ? '解析中...' : '选择文件'}
|
||||
</Button>
|
||||
{importFileName ? <span>文件:{importFileName}</span> : null}
|
||||
{importFileName ? (
|
||||
<span>
|
||||
文件:{importFileName}
|
||||
{importFileUrl ? (
|
||||
<a className="file-action-link" download={importFileName} href={importFileUrl}>
|
||||
<Download size={14} />
|
||||
下载
|
||||
</a>
|
||||
) : null}
|
||||
</span>
|
||||
) : null}
|
||||
{importPreview ? (
|
||||
<div className="detail-grid">
|
||||
<div><span>总行数</span><strong>{importPreview.totalRows}</strong></div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { FilePenLine, Plus, Search, Trash2, Upload } from 'lucide-react';
|
||||
import { Button, Input, Modal, Select, Tag } from '@/components/ui';
|
||||
import { clientApi, type ClientSmsApplication, type ClientSmsSignature } from '@/api/adminApi';
|
||||
import { Button, FileActions, Input, Modal, Select, Tag } from '@/components/ui';
|
||||
import { clientApi, type ClientSmsApplication, type ClientSmsSignature, type FileRef } from '@/api/adminApi';
|
||||
|
||||
const statusTone: Record<string, 'success' | 'info' | 'danger' | 'warning'> = {
|
||||
approved: 'success',
|
||||
@@ -18,6 +18,14 @@ const statusLabel: Record<string, string> = {
|
||||
disabled: '已禁用',
|
||||
};
|
||||
|
||||
function materialToFileRef(material: Record<string, unknown>): FileRef | null {
|
||||
const fileObjectId = String(material.fileObjectId ?? '');
|
||||
if (!fileObjectId) return null;
|
||||
const fileName = String(material.title ?? material.fileName ?? '签名材料');
|
||||
const contentType = typeof material.contentType === 'string' ? material.contentType : undefined;
|
||||
return { contentType, fileName, fileObjectId };
|
||||
}
|
||||
|
||||
export function ClientSignaturesPage() {
|
||||
const [applications, setApplications] = useState<ClientSmsApplication[]>([]);
|
||||
const [signatures, setSignatures] = useState<ClientSmsSignature[]>([]);
|
||||
@@ -124,6 +132,9 @@ export function ClientSignaturesPage() {
|
||||
<div>
|
||||
<span>材料</span>
|
||||
<strong>{signature.materials?.length ?? 0} 份</strong>
|
||||
{signature.materials?.map((material) => (
|
||||
<FileActions file={materialToFileRef(material)} key={String(material.id ?? material.fileObjectId)} />
|
||||
))}
|
||||
</div>
|
||||
<div className="signature-actions">
|
||||
<Button icon={<Trash2 size={16} />} onClick={() => disableSignature(signature.id)} size="sm" variant="danger">删除</Button>
|
||||
|
||||
Reference in New Issue
Block a user