import { useEffect, useMemo, useState } from 'react'; import { Copy, Eye, FileText, Info, Pencil, Plus, Power, Search, Send, Trash2 } from 'lucide-react'; import { useNavigate } from 'react-router-dom'; import { adminApi, type AdminChannel, type ChannelConnectionLogResponse, type CmppConnectionState } from '@/api/adminApi'; import { Breadcrumb, Button, Input, Modal, Pagination, Select, Tag, Textarea } from '@/components/ui'; type Carrier = 'mobile' | 'unicom' | 'telecom' | 'all'; type ChannelStatus = 'normal' | 'stopped' | 'connecting' | 'failed'; type SmsChannel = { id: string; name: string; carrier: Carrier; sendRegion: string; unitPrice: number; status: ChannelStatus; total: number; successRate: number; successCount: number; unknownRate: number; unknownCount: number; failureRate: number; failureCount: number; gatewayHost: string; gatewayPort: string; corpCode: string; account: string; accessNo: string; passwordCipher?: string; }; type ChannelModalState = { mode: 'create' | 'edit'; channel?: SmsChannel; }; type ChannelConfirmAction = { type: 'toggle' | 'delete' | 'copy'; channel: SmsChannel; }; type ChannelLogState = { channel: SmsChannel; data?: ChannelConnectionLogResponse; }; const carrierOptions = [ { label: '全部运营商', value: 'all' }, { label: '移动', value: 'mobile' }, { label: '联通', value: 'unicom' }, { label: '电信', value: 'telecom' }, { label: '三网', value: 'all' }, ]; const statusOptions = [ { label: '全部状态', value: 'all' }, { label: '连接正常', value: 'normal' }, { label: '已停用', value: 'stopped' }, { label: '连接中', value: 'connecting' }, { label: '连接失败', value: 'failed' }, ]; const protocolOptions = [ { label: 'CMPP', value: 'CMPP' }, { label: 'HTTP', value: 'HTTP' }, { label: 'SGIP', value: 'SGIP' }, ]; const regionOptions = [ { label: '全国', value: '全国' }, ...'北京,天津,河北,山西,内蒙古,辽宁,吉林,黑龙江,上海,江苏,浙江,安徽,福建,江西,山东,河南,湖北,湖南,广东,广西,海南,重庆,四川,贵州,云南,西藏,陕西,甘肃,青海,宁夏,新疆,香港,澳门,台湾'.split(',').map((province) => ({ label: province, value: province })), ]; const extensionOptions = [ { label: '0', value: '0' }, { label: '2', value: '2' }, { label: '4', value: '4' }, { label: '6', value: '6' }, ]; const carrierLabelMap: Record = { mobile: '移动', unicom: '联通', telecom: '电信', all: '三网', }; const carrierToneMap: Record = { mobile: 'info', unicom: 'danger', telecom: 'success', all: 'neutral', }; const statusLabelMap: Record = { normal: '连接正常', stopped: '已停用', connecting: '连接中', failed: '连接失败', }; const statusToneMap: Record = { normal: 'success', stopped: 'neutral', connecting: 'info', failed: 'danger', }; function resolveChannelStatus(channel: AdminChannel, connections: CmppConnectionState[] = []): ChannelStatus { if (channel.status !== 'active') { return 'stopped'; } if (connections.some((connection) => connection.status === 'connected' && connection.currentConnections > 0 && connection.desiredConnections > 0, )) { return 'normal'; } if (connections.some((connection) => ['auth_failed', 'heartbeat_timeout', 'failed', 'error'].includes(connection.status) || connection.lastError)) { return 'failed'; } return 'connecting'; } function mapApiChannel(channel: AdminChannel, connections: CmppConnectionState[] = channel.connectionStates ?? []): SmsChannel { return { id: channel.id, name: channel.name, carrier: channel.carrier === 'unicom' || channel.carrier === 'telecom' || channel.carrier === 'all' ? channel.carrier : 'mobile', sendRegion: channel.sendRegion ?? '全国', unitPrice: channel.unitPrice, status: resolveChannelStatus(channel, connections), total: 0, successRate: 0, successCount: 0, unknownRate: 0, unknownCount: 0, failureRate: 0, failureCount: 0, gatewayHost: channel.gatewayHost, gatewayPort: String(channel.gatewayPort), corpCode: channel.enterpriseCode ?? channel.code, account: channel.account, accessNo: channel.srcId, }; } function mapUiStatusToApi(channel: SmsChannel) { return channel.status === 'stopped' ? 'active' : 'disabled'; } function buildChannelPayload(channel: SmsChannel, passwordCipher?: string) { return { name: channel.name, carrier: channel.carrier, sendRegion: channel.sendRegion, gatewayHost: channel.gatewayHost, gatewayPort: Number(channel.gatewayPort), enterpriseCode: channel.corpCode, account: channel.account, passwordCipher: passwordCipher || undefined, srcId: channel.accessNo, rateLimitPerSecond: 100, unitPrice: Math.round(channel.unitPrice), }; } function RateBlock({ label, rate, count, tone = 'neutral' }: { label: string; rate: number; count: number; tone?: 'success' | 'warning' | 'danger' | 'neutral' }) { return (
{label} {rate}% {count.toLocaleString('zh-CN')}
); } function ChannelFormModal({ modal, onClose, onSubmit, }: { modal: ChannelModalState; onClose: () => void; onSubmit: (channel: SmsChannel) => void; }) { const channel = modal.channel; const [name, setName] = useState(channel?.name ?? ''); const [carrier, setCarrier] = useState(channel?.carrier ?? 'mobile'); const [unitPrice, setUnitPrice] = useState(channel ? String(channel.unitPrice / 100) : '0.0300'); const [region, setRegion] = useState(channel?.sendRegion ?? '全国'); const [protocol, setProtocol] = useState('CMPP'); const [gatewayHost, setGatewayHost] = useState(channel?.gatewayHost ?? ''); const [gatewayPort, setGatewayPort] = useState(channel?.gatewayPort ?? '17890'); const [corpCode, setCorpCode] = useState(channel?.corpCode ?? ''); const [account, setAccount] = useState(channel?.account ?? ''); const [password, setPassword] = useState(''); const [accessNo, setAccessNo] = useState(channel?.accessNo ?? ''); const [extensionDigits, setExtensionDigits] = useState('0'); const [flowLimit, setFlowLimit] = useState('1-2000'); function submit() { onSubmit({ id: channel?.id ?? String(Math.floor(10000 + Math.random() * 80000)), name: name || '新建短信通道', carrier, sendRegion: region, unitPrice: Number(unitPrice || 0) * 100, status: channel?.status ?? 'connecting', total: channel?.total ?? 0, successRate: channel?.successRate ?? 0, successCount: channel?.successCount ?? 0, unknownRate: channel?.unknownRate ?? 0, unknownCount: channel?.unknownCount ?? 0, failureRate: channel?.failureRate ?? 0, failureCount: channel?.failureCount ?? 0, gatewayHost, gatewayPort, corpCode, account, accessNo, passwordCipher: password || undefined, }); } return ( )} onClose={onClose} open size="xl" title={

{modal.mode === 'edit' ? '编辑通道' : '创建通道'}

} >

业务信息

setName(event.target.value)} placeholder="请输入通道名称" value={name} />
* 运营商 {(['mobile', 'unicom', 'telecom', 'all'] as const).map((item) => ( ))}
setUnitPrice(event.target.value)} value={unitPrice} /> setProtocol(event.target.value)} options={protocolOptions} value={protocol} />
setGatewayHost(event.target.value)} placeholder="请输入网关地址" value={gatewayHost} /> setGatewayPort(event.target.value)} value={gatewayPort} />
setCorpCode(event.target.value)} placeholder="请输入企业代码" value={corpCode} /> setAccount(event.target.value)} placeholder="请输入网关账号" value={account} /> setPassword(event.target.value)} placeholder="请输入网关密码" type="password" value={password} />
setAccessNo(event.target.value)} placeholder="请输入通道接入号" value={accessNo} /> setFlowLimit(event.target.value)} suffix="条/秒" value={flowLimit} />
); } function SmsTestModal({ channel, onClose, }: { channel: SmsChannel; onClose: () => void; }) { const [phones, setPhones] = useState(''); const [content, setContent] = useState(''); const [accessNo, setAccessNo] = useState(''); const billingCount = Math.max(1, Math.ceil(content.length / 67)); return ( )} onClose={onClose} open size="xl" title={(

短信测试

向指定手机号发送测试短信

)} >
测试通道 {channel.name}