import { useEffect, useMemo, useState } from 'react'; import { ChevronDown, ChevronRight, Edit3, FileText, Info, Plus, Search, Trash2, Upload } from 'lucide-react'; import { adminApi, type ClientSmsApplication, type ClientSmsSignature, type FileRef, type TenantOption } from '@/api/adminApi'; import { Breadcrumb, Button, FileActions, Input, Modal, Pagination, Select, Tabs, Tag, Textarea } from '@/components/ui'; type CarrierStatus = 'approved' | 'pending' | 'rejected' | 'filing'; type DrainageInfo = { id: string; siteName: string; url: string; field1File?: UploadedFileRef | null; field2?: string; field3?: string; field4?: string; field5?: string; field6?: string; field7File?: UploadedFileRef | null; field8?: string; field9?: string; field10?: string; mobile: CarrierStatus; unicom: CarrierStatus; telecom: CarrierStatus; submittedAt: string; remark: string; }; type UploadedFileRef = FileRef; type SignatureProfile = { basis: string; companyName: string; creditCode: string; legalPersonName: string; legalPersonIdCard: string; responsibleName: string; responsiblePhone: string; responsibleIdCard: string; credentialFile?: UploadedFileRef | null; legalFrontFile?: UploadedFileRef | null; legalBackFile?: UploadedFileRef | null; responsibleFrontFile?: UploadedFileRef | null; responsibleBackFile?: UploadedFileRef | null; }; type SignatureFormState = { tenantId: string; applicationId: string; name: string; purpose: string; profile: SignatureProfile; mobile: CarrierStatus; unicom: CarrierStatus; telecom: CarrierStatus; }; const statusLabelMap: Record = { approved: '已通过', pending: '审核中', rejected: '已驳回', filing: '待报备', }; const statusToneMap: Record = { approved: 'success', pending: 'info', rejected: 'danger', filing: 'neutral', }; const statusOptions = [ { label: '已通过', value: 'approved' }, { label: '审核中', value: 'pending' }, { label: '已驳回', value: 'rejected' }, { label: '待报备', value: 'filing' }, ]; function StatusTag({ status }: { status: CarrierStatus }) { return {statusLabelMap[status]}; } function readDrainagePayload(signature: ClientSmsSignature) { const payload = signature.drainageInfo && typeof signature.drainageInfo === 'object' ? signature.drainageInfo : {}; const carrierStatus = typeof payload.carrierStatus === 'object' && payload.carrierStatus ? payload.carrierStatus as Record : {}; const profile = typeof payload.signatureProfile === 'object' && payload.signatureProfile ? payload.signatureProfile as Record : {}; const links = Array.isArray(payload.links) ? payload.links as Array> : []; const fallbackStatus = normalizeCarrierStatus(signature.auditStatus); return { carrierStatus: { mobile: normalizeCarrierStatus(carrierStatus.mobile, fallbackStatus), unicom: normalizeCarrierStatus(carrierStatus.unicom, fallbackStatus), telecom: normalizeCarrierStatus(carrierStatus.telecom, fallbackStatus), }, signatureProfile: normalizeSignatureProfile(profile, signature), links: links.map((item) => ({ id: String(item.id ?? `drain-${Date.now()}`), siteName: String(item.siteName ?? ''), url: String(item.url ?? ''), field1File: normalizeUploadedFile(item.field1File), field2: String(item.field2 ?? ''), field3: String(item.field3 ?? ''), field4: String(item.field4 ?? ''), field5: String(item.field5 ?? ''), field6: String(item.field6 ?? ''), field7File: normalizeUploadedFile(item.field7File), field8: String(item.field8 ?? ''), field9: String(item.field9 ?? ''), field10: String(item.field10 ?? ''), mobile: normalizeCarrierStatus(item.mobile, 'filing'), unicom: normalizeCarrierStatus(item.unicom, 'filing'), telecom: normalizeCarrierStatus(item.telecom, 'filing'), submittedAt: String(item.submittedAt ?? ''), remark: String(item.remark ?? ''), })), }; } function buildDrainagePayload(carrierStatus: { mobile: CarrierStatus; unicom: CarrierStatus; telecom: CarrierStatus }, links: DrainageInfo[], signatureProfile?: SignatureProfile) { return { carrierStatus, links, signatureProfile }; } function normalizeUploadedFile(value: unknown): UploadedFileRef | null { if (!value || typeof value !== 'object') return null; const item = value as Record; const fileObjectId = String(item.fileObjectId ?? ''); const fileName = String(item.fileName ?? ''); const contentType = typeof item.contentType === 'string' ? item.contentType : undefined; return fileObjectId || fileName ? { contentType, fileObjectId, fileName } : null; } function emptySignatureProfile(signature?: ClientSmsSignature): SignatureProfile { return { basis: '', companyName: signature?.tenant?.name ?? '', creditCode: '', legalPersonName: '', legalPersonIdCard: '', responsibleName: '', responsiblePhone: '', responsibleIdCard: '', credentialFile: null, legalFrontFile: null, legalBackFile: null, responsibleFrontFile: null, responsibleBackFile: null, }; } function normalizeSignatureProfile(value: Record, signature?: ClientSmsSignature): SignatureProfile { return { ...emptySignatureProfile(signature), basis: String(value.basis ?? ''), companyName: String(value.companyName ?? signature?.tenant?.name ?? ''), creditCode: String(value.creditCode ?? ''), legalPersonName: String(value.legalPersonName ?? ''), legalPersonIdCard: String(value.legalPersonIdCard ?? ''), responsibleName: String(value.responsibleName ?? ''), responsiblePhone: String(value.responsiblePhone ?? ''), responsibleIdCard: String(value.responsibleIdCard ?? ''), credentialFile: normalizeUploadedFile(value.credentialFile), legalFrontFile: normalizeUploadedFile(value.legalFrontFile), legalBackFile: normalizeUploadedFile(value.legalBackFile), responsibleFrontFile: normalizeUploadedFile(value.responsibleFrontFile), responsibleBackFile: normalizeUploadedFile(value.responsibleBackFile), }; } function normalizeCarrierStatus(value: unknown, fallback: CarrierStatus = 'filing'): CarrierStatus { return value === 'approved' || value === 'pending' || value === 'rejected' || value === 'filing' ? value : fallback; } function toAuditStatus(status: CarrierStatus) { return status === 'filing' ? 'pending' : status; } function formatDate(value?: string) { return value ? new Date(value).toLocaleString('zh-CN') : '-'; } function SignatureUploadBox({ compact = false, file, label, onUploaded, }: { compact?: boolean; file?: UploadedFileRef | null; label: string; onUploaded: (file: UploadedFileRef) => void; }) { const [uploading, setUploading] = useState(false); const [error, setError] = useState(''); async function uploadFile(fileInput: File | undefined) { if (!fileInput) return; setUploading(true); setError(''); try { const fileObject = await adminApi.uploadFileObject(fileInput, { purpose: 'signature_report_material', prefix: 'signature-report-materials' }); onUploaded({ contentType: fileObject.contentType, fileObjectId: fileObject.id, fileName: fileObject.fileName }); } catch (failure) { setError(failure instanceof Error ? failure.message : '文件上传失败'); } finally { setUploading(false); } } return ( ); } function SignatureFormModal({ applications, item, onClose, onSubmit, tenants, }: { applications: ClientSmsApplication[]; item?: ClientSmsSignature; onClose: () => void; onSubmit: (state: SignatureFormState) => void; tenants: TenantOption[]; }) { const payload = item ? readDrainagePayload(item) : null; const [form, setForm] = useState({ tenantId: item?.tenantId ?? '', applicationId: item?.applicationId ?? '', name: item?.name ?? '', purpose: item?.purpose ?? '', profile: payload?.signatureProfile ?? emptySignatureProfile(item), mobile: payload?.carrierStatus.mobile ?? 'filing', unicom: payload?.carrierStatus.unicom ?? 'filing', telecom: payload?.carrierStatus.telecom ?? 'filing', }); const tenantApplications = applications.filter((application) => application.tenantId === form.tenantId && application.status !== 'deleted'); function update(key: Key, value: SignatureFormState[Key]) { setForm((current) => ({ ...current, [key]: value })); } function updateProfile(key: Key, value: SignatureProfile[Key]) { setForm((current) => ({ ...current, profile: { ...current.profile, [key]: value } })); } return ( )} onClose={onClose} open size="xl" title={(

{item ? '编辑签名' : '添加签名'}

{item ? '修改短信签名的相关信息' : '新增短信签名的相关信息'}

)} >

基本信息

签名需履行报备,并遵照管理部门审核结果方可使用。请用 PNG、JPG、JPEG 或 PDF 格式上传真实材料。
update('applicationId', event.target.value)} options={[ { label: '不绑定应用', value: '' }, ...tenantApplications.map((application) => ({ label: application.name, value: application.id })), ]} value={form.applicationId} /> update('name', event.target.value)} placeholder="请输入短信签名,如【XXXX公司】" required value={form.name} />
updateProfile('credentialFile', file)} />

公司信息

updateProfile('companyName', event.target.value)} placeholder="请输入公司名称" value={form.profile.companyName} /> updateProfile('creditCode', event.target.value)} placeholder="请输入统一社会信用代码" value={form.profile.creditCode} /> updateProfile('legalPersonName', event.target.value)} placeholder="请输入法人姓名" value={form.profile.legalPersonName} /> updateProfile('legalPersonIdCard', event.target.value)} placeholder="请输入法人身份证号" value={form.profile.legalPersonIdCard} /> updateProfile('legalFrontFile', file)} /> updateProfile('legalBackFile', file)} />

责任人信息

updateProfile('responsibleName', event.target.value)} placeholder="请输入责任人姓名" value={form.profile.responsibleName} /> updateProfile('responsiblePhone', event.target.value)} placeholder="请输入责任人手机号" value={form.profile.responsiblePhone} /> updateProfile('responsibleIdCard', event.target.value)} placeholder="请输入责任人身份证号" value={form.profile.responsibleIdCard} /> updateProfile('responsibleFrontFile', file)} /> updateProfile('responsibleBackFile', file)} />

三网报备状态

update('unicom', event.target.value as CarrierStatus)} options={statusOptions} value={form.unicom} /> update('url', event.target.value)} placeholder="请输入引流网址" required value={form.url} />
  1. 本页面中所填的信息需与短信内容应用所包含的网站或服务保持一致;
  2. 图片仅支持 PNG、JPG 或 JPEG 格式的正版文件,且大小不超过 3M;
  3. 文件格式支持 PDF 格式或者图片,且大小不超过 10M。
update('field1File', file)} /> update('field2', event.target.value)} placeholder="请输入字段2内容" value={form.field2 ?? ''} /> { update('field3', event.target.value); update('siteName', event.target.value); }} placeholder="请输入公司名称" value={form.field3 ?? form.siteName} /> update('field4', event.target.value)} placeholder="请输入统一社会信用代码" value={form.field4 ?? ''} /> update('field5', event.target.value)} placeholder="请输入法人姓名" value={form.field5 ?? ''} /> update('field6', event.target.value)} placeholder="请输入法人身份证号" value={form.field6 ?? ''} /> update('field7File', file)} /> update('field8', event.target.value)} placeholder="请输入责任人身份证号" value={form.field8 ?? ''} /> update('field9', event.target.value)} placeholder="请输入责任人姓名" value={form.field9 ?? ''} /> update('field10', event.target.value)} placeholder="请输入责任人手机号" value={form.field10 ?? ''} /> update('unicom', event.target.value as CarrierStatus)} options={statusOptions} value={form.unicom} /> update('submittedAt', event.target.value)} value={form.submittedAt} />