Files
lislgosms/src/apps/admin/AdminEnterpriseSignaturesPage.tsx
T

607 lines
28 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useMemo, useState } from 'react';
import { ChevronDown, ChevronLeft, ChevronRight, Edit3, FileText, Info, Plus, Search, Trash2, Upload } from 'lucide-react';
import { Breadcrumb, Button, Input, Modal, Select, Table, Tabs, Tag, Textarea, type TableColumn } from '@/components/ui';
type CarrierStatus = 'approved' | 'pending' | 'rejected' | 'filing';
type SignatureKind = 'sms' | 'mms';
type DrainageInfo = {
id: string;
siteName: string;
url: string;
mobile: CarrierStatus;
unicom: CarrierStatus;
telecom: CarrierStatus;
submittedAt: string;
remark: string;
};
type SignatureItem = {
id: string;
name: string;
enterprise: string;
application: string;
mobile: CarrierStatus;
unicom: CarrierStatus;
telecom: CarrierStatus;
drainage?: DrainageInfo[];
updatedAt: string;
};
const statusLabelMap: Record<CarrierStatus, string> = {
approved: '已通过',
pending: '审核中',
rejected: '已驳回',
filing: '待报备',
};
const statusToneMap: Record<CarrierStatus, 'success' | 'info' | 'danger' | 'neutral'> = {
approved: 'success',
pending: 'info',
rejected: 'danger',
filing: 'neutral',
};
const statusOptions = [
{ label: '已通过', value: 'approved' },
{ label: '审核中', value: 'pending' },
{ label: '已驳回', value: 'rejected' },
{ label: '待报备', value: 'filing' },
];
const initialSmsSignatures: SignatureItem[] = [
{
id: 'sig-1',
name: '【科技公司】',
enterprise: '上海XXXXX科技有限公司',
application: '营销推广平台',
mobile: 'approved',
unicom: 'approved',
telecom: 'approved',
updatedAt: '2026-01-08 11:00:00',
drainage: [
{ id: 'drain-1', siteName: '官网入口', url: 'https://www.example.com', mobile: 'approved', unicom: 'approved', telecom: 'approved', submittedAt: '2026-01-08 11:00:00', remark: '官网首页引流链接,三网报备通过。' },
{ id: 'drain-2', siteName: '促销活动页', url: 'https://sale.example.com', mobile: 'approved', unicom: 'pending', telecom: 'pending', submittedAt: '2026-01-09 10:30:00', remark: '活动页待联通、电信回执。' },
],
},
{
id: 'sig-2',
name: '【客户服务】',
enterprise: '重庆进载数智',
application: '客户服务系统',
mobile: 'approved',
unicom: 'pending',
telecom: 'approved',
updatedAt: '2026-01-09 14:20:00',
drainage: [
{ id: 'drain-3', siteName: '客户服务中心', url: 'https://service.example.com', mobile: 'approved', unicom: 'filing', telecom: 'pending', submittedAt: '2026-01-09 14:20:00', remark: '服务入口链接报备中。' },
],
},
{
id: 'sig-3',
name: '【促销活动】',
enterprise: '超感世纪互三网',
application: '营销推广平台',
mobile: 'rejected',
unicom: 'approved',
telecom: 'pending',
updatedAt: '2026-01-07 10:00:00',
drainage: [
{ id: 'drain-4', siteName: '促销专区', url: 'https://sale.example.com', mobile: 'rejected', unicom: 'approved', telecom: 'approved', submittedAt: '2026-01-07 10:00:00', remark: '移动侧驳回,需补充页面备案信息。' },
],
},
];
const initialMmsSignatures: SignatureItem[] = [
{ id: 'mms-sig-1', name: '【活动推广】', enterprise: '上海XXXXX科技有限公司', application: '营销活动彩信', mobile: 'approved', unicom: 'approved', telecom: 'approved', updatedAt: '2026-01-12 09:10:00' },
{ id: 'mms-sig-2', name: '【节日祝福】', enterprise: '重庆进载数智', application: '节日祝福彩信', mobile: 'approved', unicom: 'pending', telecom: 'approved', updatedAt: '2026-01-13 16:35:00' },
{ id: 'mms-sig-3', name: '【优品发布】', enterprise: '四川骠骑企业管理', application: '营销活动彩信', mobile: 'pending', unicom: 'pending', telecom: 'pending', updatedAt: '2026-01-15 10:28:00' },
];
function StatusTag({ status }: { status: CarrierStatus }) {
return <Tag tone={statusToneMap[status]}>{statusLabelMap[status]}</Tag>;
}
function paginate<T>(items: T[], page: number, pageSize: number) {
return items.slice((page - 1) * pageSize, page * pageSize);
}
function Pagination({ page, pageSize, total, onPageChange }: { page: number; pageSize: number; total: number; onPageChange: (page: number) => void }) {
const totalPages = Math.max(1, Math.ceil(total / pageSize));
return (
<div className="admin-split-pagination">
<span>{pageSize}/</span>
<Button disabled={page <= 1} icon={<ChevronLeft size={16} />} iconOnly onClick={() => onPageChange(page - 1)} variant="ghost">上一页</Button>
<Button size="sm">{page}</Button>
<span>/ {totalPages}</span>
<Button disabled={page >= totalPages} icon={<ChevronRight size={16} />} iconOnly onClick={() => onPageChange(page + 1)} variant="ghost">下一页</Button>
</div>
);
}
function ConfirmModal({ message, onCancel, onConfirm }: { message: string; onCancel: () => void; onConfirm: () => void }) {
return (
<Modal
footer={(
<>
<Button onClick={onCancel} variant="ghost">取消</Button>
<Button onClick={onConfirm} variant="danger">确认删除</Button>
</>
)}
onClose={onCancel}
open
title="删除确认"
>
<p className="admin-confirm-text">{message}</p>
</Modal>
);
}
function SignatureUploadBox({ label, compact = false }: { label: string; compact?: boolean }) {
return (
<div className={compact ? 'signature-upload signature-upload--compact' : 'signature-upload'}>
<span>{label}</span>
<Upload size={compact ? 30 : 42} />
<strong>{compact ? '上传文件' : '点击上传 或拖拽文件到此处'}</strong>
{!compact ? <small>支持 PNGJPGJPEG 格式,大小不超过 3M</small> : null}
</div>
);
}
function SignatureFormModal({
item,
kind,
onClose,
onSubmit,
}: {
item?: SignatureItem;
kind: SignatureKind;
onClose: () => void;
onSubmit: (item: SignatureItem) => void;
}) {
const [name, setName] = useState(item?.name ?? '');
const [enterprise, setEnterprise] = useState(item?.enterprise ?? '');
const [application, setApplication] = useState(item?.application ?? '');
const [mobile, setMobile] = useState<CarrierStatus>(item?.mobile ?? 'filing');
const [unicom, setUnicom] = useState<CarrierStatus>(item?.unicom ?? 'filing');
const [telecom, setTelecom] = useState<CarrierStatus>(item?.telecom ?? 'filing');
function submit() {
onSubmit({
id: item?.id ?? `${kind}-sig-${Date.now()}`,
name,
enterprise,
application,
mobile,
unicom,
telecom,
drainage: kind === 'sms' ? (item?.drainage ?? []) : undefined,
updatedAt: '2026-06-30 10:00:00',
});
}
return (
<Modal
footer={(
<>
<Button onClick={onClose} variant="ghost">取消</Button>
<Button onClick={submit}>确认</Button>
</>
)}
onClose={onClose}
open
size="xl"
title={(
<div className="signature-modal-title">
<h2>{item ? '编辑签名' : '添加签名'}</h2>
<p>{item ? '修改短信签名的相关信息' : '新增短信签名的相关信息'}</p>
</div>
)}
>
<div className="signature-form">
<section>
<h3>基本信息</h3>
<div className="signature-alert">
<Info size={18} />
<span>签名需履行报备,并遵照管理部门审核结果方可使用。请用 PNGJPG JPEG 格式的正版文件,且大小不超过 3M</span>
</div>
<div className="signature-form-grid">
<Select
defaultValue={item ? 'company' : ''}
label="* 签名依据"
options={[
{ label: '请选择签名依据', value: '' },
{ label: '企事业单位证明', value: 'company' },
{ label: '商标注册证', value: 'trademark' },
{ label: '授权委托书', value: 'authorization' },
]}
/>
<Input label="* 短信签名" onChange={(event) => setName(event.target.value)} placeholder="请输入短信签名,如【XXXX公司】" value={name} />
</div>
<SignatureUploadBox label="* 资质凭证" />
</section>
<section>
<h3>公司信息</h3>
<div className="signature-form-grid">
<Input label="* 公司名称" onChange={(event) => setEnterprise(event.target.value)} placeholder="请输入公司名称" value={enterprise} />
<Input label="* 统一社会信用代码" placeholder="请输入统一社会信用代码" />
<Input label="* 法人姓名" placeholder="请输入法人姓名" />
<Input label="法人身份证号" placeholder="请输入法人身份证号" />
<SignatureUploadBox compact label="法人身份证照片-人像面" />
<SignatureUploadBox compact label="法人身份证照片-国徽面" />
</div>
</section>
<section>
<h3>责任人信息</h3>
<div className="signature-form-grid">
<Input label="* 应用名称" onChange={(event) => setApplication(event.target.value)} placeholder="请输入应用名称" value={application} />
<Input label="* 责任人手机号" placeholder="请输入责任人手机号" />
<Input className="signature-form-grid__wide" label="* 责任人身份证号" placeholder="请输入责任人身份证号" />
<SignatureUploadBox compact label="责任人身份证照片-人像面" />
<SignatureUploadBox compact label="责任人身份证照片-国徽面" />
</div>
</section>
<section>
<h3>三网报备状态</h3>
<div className="signature-form-grid">
<Select label="移动状态" onChange={(event) => setMobile(event.target.value as CarrierStatus)} options={statusOptions} value={mobile} />
<Select label="联通状态" onChange={(event) => setUnicom(event.target.value as CarrierStatus)} options={statusOptions} value={unicom} />
<Select label="电信状态" onChange={(event) => setTelecom(event.target.value as CarrierStatus)} options={statusOptions} value={telecom} />
</div>
</section>
</div>
</Modal>
);
}
function SignatureReportModal({ item, onClose }: { item: SignatureItem; onClose: () => void }) {
return (
<Modal footer={<Button onClick={onClose}>关闭</Button>} onClose={onClose} open title="签名报备详情">
<div className="admin-report-detail">
<div className="detail-grid">
<div><span>企业名称</span><strong>{item.enterprise}</strong></div>
<div><span>应用名称</span><strong>{item.application}</strong></div>
<div><span>签名名称</span><strong>{item.name}</strong></div>
<div><span>更新时间</span><strong>{item.updatedAt}</strong></div>
</div>
<div className="admin-report-tabs">
<button className="admin-report-carrier--mobile active" type="button"><strong>移动</strong><span><StatusTag status={item.mobile} /></span></button>
<button className="admin-report-carrier--unicom active" type="button"><strong>联通</strong><span><StatusTag status={item.unicom} /></span></button>
<button className="admin-report-carrier--telecom active" type="button"><strong>电信</strong><span><StatusTag status={item.telecom} /></span></button>
</div>
</div>
</Modal>
);
}
function DrainageFormModal({
item,
onClose,
onSubmit,
}: {
item?: DrainageInfo;
onClose: () => void;
onSubmit: (item: DrainageInfo) => void;
}) {
const [form, setForm] = useState<DrainageInfo>(() => item ?? {
id: `drain-${Date.now()}`,
siteName: '',
url: '',
mobile: 'filing',
unicom: 'filing',
telecom: 'filing',
submittedAt: '2026-06-30 10:00:00',
remark: '',
});
function update<Key extends keyof DrainageInfo>(key: Key, value: DrainageInfo[Key]) {
setForm((current) => ({ ...current, [key]: value }));
}
return (
<Modal
footer={(
<>
<Button onClick={onClose} variant="ghost">取消</Button>
<Button onClick={() => onSubmit(form)}>保存</Button>
</>
)}
onClose={onClose}
open
size="xl"
title={item ? '编辑引流链接' : '添加引流链接'}
>
<div className="signature-form drainage-edit-form">
<section>
<h3>基本信息</h3>
<Input
label="* 引流信息"
onChange={(event) => update('url', event.target.value)}
placeholder="请输入引流网址"
value={form.url}
/>
<div className="signature-alert drainage-form-note">
<Info size={18} />
<ol>
<li>本页面中所填的信息需与短信内容应用所包含的网站或服务保持一致;</li>
<li>图片仅支持 PNGJPG JPEG 格式的正版文件,且大小不超过 3M</li>
<li>文件格式支持 pdf 格式或者图片,且大小不超过 10M</li>
</ol>
</div>
<div className="signature-form-grid">
<SignatureUploadBox compact label="* 字段名称1" />
<Input label="* 字段名称2" placeholder="请输入字段2内容" />
<Input label="* 字段名称3" onChange={(event) => update('siteName', event.target.value)} placeholder="请输入公司名称" value={form.siteName} />
<Input label="字段名称4" placeholder="请输入统一社会信用代码" />
<Input label="* 字段名称5" placeholder="请输入法人姓名" />
<Input label="字段名称6" placeholder="请输入法人身份证号" />
<div className="drainage-file-picker">
<span>字段名称7:</span>
<div>
<Button size="sm">请附文件</Button>
<em>未选择文件</em>
</div>
</div>
<Input label="* 字段名称8" placeholder="请输入责任人身份证号" />
<Input label="* 字段名称9" placeholder="请输入责任人姓名" />
<Input label="* 字段名称10" placeholder="请输入责任人手机号" />
<Select label="移动状态" onChange={(event) => update('mobile', event.target.value as CarrierStatus)} options={statusOptions} value={form.mobile} />
<Select label="联通状态" onChange={(event) => update('unicom', event.target.value as CarrierStatus)} options={statusOptions} value={form.unicom} />
<Select label="电信状态" onChange={(event) => update('telecom', event.target.value as CarrierStatus)} options={statusOptions} value={form.telecom} />
<Input label="提交时间" onChange={(event) => update('submittedAt', event.target.value)} value={form.submittedAt} />
<Textarea className="signature-form-grid__wide" label="备注" onChange={(event) => update('remark', event.target.value)} rows={4} value={form.remark} />
</div>
</section>
</div>
</Modal>
);
}
function DrainageReportModal({ item, onClose }: { item: DrainageInfo; onClose: () => void }) {
return (
<Modal footer={<Button onClick={onClose}>关闭</Button>} onClose={onClose} open title="引流信息报备详情">
<div className="detail-grid">
<div><span>站名称</span><strong>{item.siteName}</strong></div>
<div><span>网站链接</span><strong>{item.url}</strong></div>
<div><span>移动</span><StatusTag status={item.mobile} /></div>
<div><span>联通</span><StatusTag status={item.unicom} /></div>
<div><span>电信</span><StatusTag status={item.telecom} /></div>
<div><span>提交时间</span><strong>{item.submittedAt}</strong></div>
<div className="detail-grid__wide"><span>备注</span><strong>{item.remark || '-'}</strong></div>
</div>
</Modal>
);
}
export function AdminEnterpriseSignaturesPage() {
const [activeTab, setActiveTab] = useState<SignatureKind>('sms');
const [smsSignatures, setSmsSignatures] = useState(initialSmsSignatures);
const [mmsSignatures, setMmsSignatures] = useState(initialMmsSignatures);
const [expandedSignatureId, setExpandedSignatureId] = useState('sig-1');
const [enterpriseKeyword, setEnterpriseKeyword] = useState('');
const [signatureKeyword, setSignatureKeyword] = useState('');
const [page, setPage] = useState(1);
const [signatureModal, setSignatureModal] = useState<{ kind: SignatureKind; item?: SignatureItem } | null>(null);
const [signatureReport, setSignatureReport] = useState<SignatureItem | null>(null);
const [drainageModal, setDrainageModal] = useState<{ signatureId: string; item?: DrainageInfo } | null>(null);
const [drainageReport, setDrainageReport] = useState<DrainageInfo | null>(null);
const [deleteTarget, setDeleteTarget] = useState<{ kind: 'signature'; signatureKind: SignatureKind; id: string; name: string } | { kind: 'drainage'; signatureId: string; id: string; name: string } | null>(null);
const pageSize = 2;
const filteredSmsSignatures = useMemo(
() => smsSignatures.filter((item) => (
(!enterpriseKeyword || item.enterprise.includes(enterpriseKeyword))
&& (!signatureKeyword || item.name.includes(signatureKeyword) || item.application.includes(signatureKeyword))
)),
[enterpriseKeyword, signatureKeyword, smsSignatures],
);
const filteredMmsSignatures = useMemo(
() => mmsSignatures.filter((item) => (
(!enterpriseKeyword || item.enterprise.includes(enterpriseKeyword))
&& (!signatureKeyword || item.name.includes(signatureKeyword) || item.application.includes(signatureKeyword))
)),
[enterpriseKeyword, signatureKeyword, mmsSignatures],
);
const pagedSmsSignatures = paginate(filteredSmsSignatures, page, pageSize);
const pagedMmsSignatures = paginate(filteredMmsSignatures, page, pageSize);
function upsertSignature(kind: SignatureKind, nextItem: SignatureItem) {
const setter = kind === 'sms' ? setSmsSignatures : setMmsSignatures;
setter((current) => current.some((item) => item.id === nextItem.id)
? current.map((item) => item.id === nextItem.id ? nextItem : item)
: [nextItem, ...current]);
setSignatureModal(null);
}
function upsertDrainage(signatureId: string, nextItem: DrainageInfo) {
setSmsSignatures((current) => current.map((signature) => {
if (signature.id !== signatureId) {
return signature;
}
const drainage = signature.drainage ?? [];
const nextDrainage = drainage.some((item) => item.id === nextItem.id)
? drainage.map((item) => item.id === nextItem.id ? nextItem : item)
: [nextItem, ...drainage];
return { ...signature, drainage: nextDrainage };
}));
setDrainageModal(null);
setExpandedSignatureId(signatureId);
}
function confirmDelete() {
if (!deleteTarget) {
return;
}
if (deleteTarget.kind === 'signature') {
const setter = deleteTarget.signatureKind === 'sms' ? setSmsSignatures : setMmsSignatures;
setter((current) => current.filter((item) => item.id !== deleteTarget.id));
} else {
setSmsSignatures((current) => current.map((signature) => signature.id === deleteTarget.signatureId
? { ...signature, drainage: (signature.drainage ?? []).filter((item) => item.id !== deleteTarget.id) }
: signature));
}
setDeleteTarget(null);
}
const mmsColumns = useMemo<Array<TableColumn<SignatureItem>>>(() => [
{ key: 'name', title: '签名名称', width: '170px', render: (record) => <strong>{record.name}</strong> },
{ key: 'enterprise', title: '企业名称', width: '220px', render: (record) => record.enterprise },
{ key: 'application', title: '应用', width: '180px', render: (record) => record.application },
{ key: 'mobile', title: '移动', width: '110px', render: (record) => <StatusTag status={record.mobile} /> },
{ key: 'unicom', title: '联通', width: '110px', render: (record) => <StatusTag status={record.unicom} /> },
{ key: 'telecom', title: '电信', width: '110px', render: (record) => <StatusTag status={record.telecom} /> },
{ key: 'updatedAt', title: '更新时间', width: '180px', render: (record) => record.updatedAt },
{
key: 'actions',
title: '操作',
align: 'right',
width: '230px',
render: (record) => (
<div className="table-actions">
<Button icon={<FileText size={15} />} onClick={() => setSignatureReport(record)} size="sm" variant="ghost">报备详情</Button>
<Button icon={<Edit3 size={15} />} onClick={() => setSignatureModal({ kind: 'mms', item: record })} size="sm" variant="ghost">编辑</Button>
<Button icon={<Trash2 size={15} />} onClick={() => setDeleteTarget({ kind: 'signature', signatureKind: 'mms', id: record.id, name: record.name })} size="sm" variant="danger">删除</Button>
</div>
),
},
], []);
const smsSignatureContent = (
<>
<div className="signature-list admin-enterprise-signature-list">
{pagedSmsSignatures.map((signature) => {
const expanded = expandedSignatureId === signature.id;
return (
<article className="signature-card signature-card--green" key={signature.id}>
<div className="signature-summary">
<button aria-label="展开签名" onClick={() => setExpandedSignatureId(expanded ? '' : signature.id)} type="button">
{expanded ? <ChevronDown size={18} /> : <ChevronRight size={18} />}
</button>
<div><span>签名名称</span><strong>{signature.name}</strong></div>
<div><span>企业</span><strong>{signature.enterprise}</strong></div>
<div><span>应用</span><strong>{signature.application}</strong></div>
<div><span>移动</span><StatusTag status={signature.mobile} /></div>
<div><span>联通</span><StatusTag status={signature.unicom} /></div>
<div><span>电信</span><StatusTag status={signature.telecom} /></div>
<div><span>引流信息</span><strong>{signature.drainage?.length ?? 0} </strong></div>
<div className="signature-actions">
<Button icon={<FileText size={16} />} onClick={() => setSignatureReport(signature)} size="sm" variant="ghost">报备详情</Button>
<Button icon={<Edit3 size={16} />} onClick={() => setSignatureModal({ kind: 'sms', item: signature })} size="sm" variant="ghost">编辑</Button>
<Button icon={<Trash2 size={16} />} onClick={() => setDeleteTarget({ kind: 'signature', signatureKind: 'sms', id: signature.id, name: signature.name })} size="sm" variant="ghost">删除</Button>
</div>
</div>
{expanded ? (
<div className="drainage-panel">
<h2>引流信息列表</h2>
{signature.drainage?.length ? (
<div className="drainage-table">
<div className="drainage-table__head">
<span>站名称</span>
<span>网站链接</span>
<span>移动</span>
<span>联通</span>
<span>电信</span>
<span>提交时间</span>
<span>操作</span>
</div>
{signature.drainage.map((item) => (
<div className="drainage-table__row" key={item.id}>
<strong>{item.siteName}</strong>
<a href={item.url}>{item.url}</a>
<StatusTag status={item.mobile} />
<StatusTag status={item.unicom} />
<StatusTag status={item.telecom} />
<span className="muted">{item.submittedAt}</span>
<span className="drainage-row-actions">
<button onClick={() => setDrainageReport(item)} type="button">报备详情</button>
<button onClick={() => setDrainageModal({ signatureId: signature.id, item })} type="button">编辑</button>
<button onClick={() => setDeleteTarget({ kind: 'drainage', signatureId: signature.id, id: item.id, name: item.siteName })} type="button">删除</button>
</span>
</div>
))}
</div>
) : (
<p className="muted">暂无引流信息</p>
)}
<div className="drainage-panel__footer">
<Button icon={<Plus size={16} />} onClick={() => setDrainageModal({ signatureId: signature.id })} size="sm" variant="ghost">添加引流信息</Button>
</div>
</div>
) : null}
</article>
);
})}
</div>
<Pagination onPageChange={setPage} page={page} pageSize={pageSize} total={filteredSmsSignatures.length} />
</>
);
return (
<section className="page-stack admin-customer-split-page">
<div className="page-heading">
<div>
<Breadcrumb items={['客户管理', '企业签名管理']} />
<h1>企业签名管理</h1>
</div>
<Button icon={<Plus size={16} />} onClick={() => setSignatureModal({ kind: activeTab })}>添加签名</Button>
</div>
<div className="surface admin-split-filter">
<Input label="企业名称" onChange={(event) => { setEnterpriseKeyword(event.target.value); setPage(1); }} placeholder="请输入企业名称" prefix={<Search size={16} />} value={enterpriseKeyword} />
<Input label="签名/应用" onChange={(event) => { setSignatureKeyword(event.target.value); setPage(1); }} placeholder="请输入签名或应用名称" prefix={<Search size={16} />} value={signatureKeyword} />
<Button onClick={() => { setEnterpriseKeyword(''); setSignatureKeyword(''); setPage(1); }} variant="ghost">重置</Button>
</div>
<div className="surface section-stack">
<Tabs
onChange={(value) => { setActiveTab(value as SignatureKind); setPage(1); }}
value={activeTab}
items={[
{ label: '短信签名', value: 'sms', content: smsSignatureContent },
{
label: '彩信签名',
value: 'mms',
content: (
<>
<Table columns={mmsColumns} data={pagedMmsSignatures} rowKey="id" />
<Pagination onPageChange={setPage} page={page} pageSize={pageSize} total={filteredMmsSignatures.length} />
</>
),
},
]}
/>
</div>
{signatureModal ? (
<SignatureFormModal
item={signatureModal.item}
kind={signatureModal.kind}
onClose={() => setSignatureModal(null)}
onSubmit={(item) => upsertSignature(signatureModal.kind, item)}
/>
) : null}
{signatureReport ? <SignatureReportModal item={signatureReport} onClose={() => setSignatureReport(null)} /> : null}
{drainageModal ? (
<DrainageFormModal
item={drainageModal.item}
onClose={() => setDrainageModal(null)}
onSubmit={(item) => upsertDrainage(drainageModal.signatureId, item)}
/>
) : null}
{drainageReport ? <DrainageReportModal item={drainageReport} onClose={() => setDrainageReport(null)} /> : null}
{deleteTarget ? (
<ConfirmModal
message={`确认删除“${deleteTarget.name}”吗?删除后仅影响当前本地 mock 数据。`}
onCancel={() => setDeleteTarget(null)}
onConfirm={confirmDelete}
/>
) : null}
</section>
);
}