157 lines
8.9 KiB
TypeScript
157 lines
8.9 KiB
TypeScript
import { useState } from 'react';
|
|
import { Button, Input, Modal, Select } from '@/components/ui';
|
|
import { isValidMoneyInput, moneyUnitsToYuan, yuanToMoneyUnits } from '@/utils/currency';
|
|
import { carrierLabelMap, cmppVersionOptions, regionOptions } from './channelModel';
|
|
import type { Carrier, ChannelModalState, LongMessageReceiptMode, SmsChannel } from './channelTypes';
|
|
|
|
export 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<Carrier>(channel?.carrier ?? 'mobile');
|
|
const [unitPrice, setUnitPrice] = useState(channel ? moneyUnitsToYuan(channel.unitPrice).toFixed(4) : '0.0300');
|
|
const [unitPriceError, setUnitPriceError] = useState('');
|
|
const [region, setRegion] = useState(channel?.sendRegion ?? '全国');
|
|
const [gatewayHost, setGatewayHost] = useState(channel?.gatewayHost ?? '');
|
|
const [gatewayPort, setGatewayPort] = useState(channel?.gatewayPort ?? '7890');
|
|
const [businessCode, setBusinessCode] = useState(channel?.businessCode ?? 'SMS');
|
|
const [corpCode, setCorpCode] = useState(channel?.corpCode ?? '');
|
|
const [account, setAccount] = useState(channel?.account ?? '');
|
|
const [cmppVersion, setCmppVersion] = useState<'2.0' | '3.0'>(channel?.cmppVersion ?? '2.0');
|
|
const [password, setPassword] = useState('');
|
|
const [accessNo, setAccessNo] = useState(channel?.accessNo ?? '');
|
|
const [extensionDigits, setExtensionDigits] = useState(String(channel?.extensionDigits ?? 0));
|
|
const [flowLimit, setFlowLimit] = useState(String(channel?.rateLimitPerSecond ?? 100));
|
|
const [desiredConnections, setDesiredConnections] = useState(String(channel?.desiredConnections ?? 1));
|
|
const [windowSize, setWindowSize] = useState(String(channel?.windowSize ?? 16));
|
|
const [heartbeatIntervalSeconds, setHeartbeatIntervalSeconds] = useState(String(channel?.heartbeatIntervalSeconds ?? 30));
|
|
const [heartbeatMissThreshold, setHeartbeatMissThreshold] = useState(String(channel?.heartbeatMissThreshold ?? 3));
|
|
const [longMessageReceiptMode, setLongMessageReceiptMode] = useState<LongMessageReceiptMode>(channel?.longMessageReceiptMode ?? 'per_segment');
|
|
|
|
function submit() {
|
|
if (!isValidMoneyInput(unitPrice)) {
|
|
setUnitPriceError('单价必须是非负金额,且最多保留小数点后 4 位');
|
|
return;
|
|
}
|
|
onSubmit({
|
|
id: channel?.id ?? String(Math.floor(10000 + Math.random() * 80000)),
|
|
name: name || '新建短信通道',
|
|
carrier,
|
|
sendRegion: region,
|
|
unitPrice: yuanToMoneyUnits(unitPrice),
|
|
status: channel?.status ?? 'connecting',
|
|
total: channel?.total ?? 0,
|
|
submitFailureRate: channel?.submitFailureRate ?? 0,
|
|
submitFailureCount: channel?.submitFailureCount ?? 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,
|
|
businessCode: businessCode.trim() || 'SMS',
|
|
corpCode,
|
|
account,
|
|
accessNo,
|
|
cmppVersion,
|
|
desiredConnections: Number(desiredConnections) || 1,
|
|
windowSize: Number(windowSize) || 16,
|
|
heartbeatIntervalSeconds: Number(heartbeatIntervalSeconds) || 30,
|
|
heartbeatMissThreshold: Number(heartbeatMissThreshold) || 3,
|
|
extensionDigits: Number(extensionDigits),
|
|
longMessageReceiptMode,
|
|
rateLimitPerSecond: Number(flowLimit),
|
|
passwordCipher: password || undefined,
|
|
});
|
|
}
|
|
|
|
return (
|
|
<Modal
|
|
footer={(
|
|
<>
|
|
<Button onClick={onClose} variant="ghost">取消</Button>
|
|
<Button onClick={submit}>确认</Button>
|
|
</>
|
|
)}
|
|
onClose={onClose}
|
|
open
|
|
size="xl"
|
|
title={<div className="template-modal-title"><h2>{modal.mode === 'edit' ? '编辑通道' : '创建通道'}</h2></div>}
|
|
>
|
|
<div className="sms-channel-form">
|
|
<section>
|
|
<h3>业务信息</h3>
|
|
<div className="sms-channel-form-grid">
|
|
<Input label="* 通道名称" onChange={(event) => setName(event.target.value)} placeholder="请输入通道名称" value={name} />
|
|
<div className="sms-channel-radio-row">
|
|
<span>* 运营商</span>
|
|
{(['mobile', 'unicom', 'telecom', 'all'] as const).map((item) => (
|
|
<label key={item}>
|
|
<input checked={carrier === item} onChange={() => setCarrier(item)} type="radio" />
|
|
{carrierLabelMap[item]}
|
|
</label>
|
|
))}
|
|
</div>
|
|
<Input error={unitPriceError} label="* 单价(元)" min="0" onChange={(event) => { setUnitPrice(event.target.value); setUnitPriceError(''); }} step="0.0001" type="number" value={unitPrice} />
|
|
<Select label="* 发送地区" onChange={(event) => setRegion(event.target.value)} options={regionOptions} value={region} />
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<h3>参数配置</h3>
|
|
<div className="sms-channel-form-grid">
|
|
<Input disabled label="* 协议选择" value="CMPP" />
|
|
<div className="sms-channel-inline-field">
|
|
<Input label="* 网关地址" onChange={(event) => setGatewayHost(event.target.value)} placeholder="请输入网关地址" value={gatewayHost} />
|
|
<Input label="端口" onChange={(event) => setGatewayPort(event.target.value)} value={gatewayPort} />
|
|
</div>
|
|
<Input hint="对应 CMPP Service_Id,最多 10 个 ASCII 字符。" label="* 业务代码" maxLength={10} onChange={(event) => setBusinessCode(event.target.value.toUpperCase())} value={businessCode} />
|
|
<Input label="* 企业代码" onChange={(event) => setCorpCode(event.target.value)} placeholder="请输入企业代码" value={corpCode} />
|
|
<Input label="* 网关账号" onChange={(event) => setAccount(event.target.value)} placeholder="请输入网关账号" value={account} />
|
|
<Select label="* CMPP版本" onChange={(event) => setCmppVersion(event.target.value as '2.0' | '3.0')} options={cmppVersionOptions} value={cmppVersion} />
|
|
<Input
|
|
hint={modal.mode === 'edit' ? '已配置的密码不会回显;留空保持不变,填写新密码才更新。' : undefined}
|
|
label="网关密码"
|
|
autoComplete="new-password"
|
|
name="cmpp-gateway-password"
|
|
onChange={(event) => setPassword(event.target.value)}
|
|
placeholder={modal.mode === 'edit' ? '留空不修改' : '请输入网关密码'}
|
|
required={modal.mode === 'create'}
|
|
type="password"
|
|
value={password}
|
|
/>
|
|
<div className="sms-channel-inline-field">
|
|
<Input autoComplete="off" label="* 接入号" name="cmpp-access-number" onChange={(event) => setAccessNo(event.target.value)} placeholder="请输入通道接入号" value={accessNo} />
|
|
<Input label="扩展位数" max="20" min="0" onChange={(event) => setExtensionDigits(event.target.value)} type="number" value={extensionDigits} />
|
|
</div>
|
|
<Input label="* 通道流速" max="2000" min="1" onChange={(event) => setFlowLimit(event.target.value)} suffix="条/秒" type="number" value={flowLimit} />
|
|
<Input label="* 期望连接数" onChange={(event) => setDesiredConnections(event.target.value)} placeholder="1" value={desiredConnections} />
|
|
<Input label="* 提交窗口" onChange={(event) => setWindowSize(event.target.value)} placeholder="16" value={windowSize} />
|
|
<Input hint="平台主动向供应商发送 ACTIVE_TEST 的间隔" label="* 心跳间隔" min="1" onChange={(event) => setHeartbeatIntervalSeconds(event.target.value)} suffix="秒" type="number" value={heartbeatIntervalSeconds} />
|
|
<Input hint="连续未收到心跳响应达到该次数后重连" label="* 心跳失败阈值" min="1" onChange={(event) => setHeartbeatMissThreshold(event.target.value)} suffix="次" type="number" value={heartbeatMissThreshold} />
|
|
<Select
|
|
label="* 长短信成功回执口径"
|
|
onChange={(event) => setLongMessageReceiptMode(event.target.value as LongMessageReceiptMode)}
|
|
options={[
|
|
{ label: '逐分片回执(默认)', value: 'per_segment' },
|
|
{ label: '整条级回执(一条成功代表全部成功)', value: 'message_level' },
|
|
]}
|
|
value={longMessageReceiptMode}
|
|
/>
|
|
<p className="page-inline-hint">仅在供应商明确约定长短信成功只返回一条整条级回执时选择“整条级”,否则保持逐分片。</p>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</Modal>
|
|
);
|
|
}
|