import { useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { ArrowLeft, RadioTower } from 'lucide-react'; import { adminApi, type ChannelGroup, type DictionaryItem, type EnterpriseApplication } from '@/api/adminApi'; import { Breadcrumb, Button, Input, Select, Tag } from '@/components/ui'; type Carrier = 'mobile' | 'unicom' | 'telecom'; const carrierMeta: Record = { mobile: { label: '移动', description: '移动号码只会进入移动通道组' }, unicom: { label: '联通', description: '联通号码只会进入联通通道组' }, telecom: { label: '电信', description: '电信号码只会进入电信通道组' }, }; export function AdminSmsApplicationFormPage() { const navigate = useNavigate(); const { enterpriseId, appId } = useParams(); const isEdit = Boolean(appId); const [appName, setAppName] = useState(''); const [scene, setScene] = useState('行业通知'); const [dailyLimit, setDailyLimit] = useState('100000'); const [customerUnitPrice, setCustomerUnitPrice] = useState('0.0300'); const [phoneDailyLimit, setPhoneDailyLimit] = useState('10'); const [mismatchPolicy, setMismatchPolicy] = useState('manual_review'); const [ipAddress, setIpAddress] = useState(''); const [groups, setGroups] = useState([]); const [mobileGroupId, setMobileGroupId] = useState(''); const [unicomGroupId, setUnicomGroupId] = useState(''); const [telecomGroupId, setTelecomGroupId] = useState(''); const [error, setError] = useState(''); const [saving, setSaving] = useState(false); useEffect(() => { let cancelled = false; async function loadForm() { try { const [groupItems, application, routeRules] = await Promise.all([ adminApi.listChannelGroups(), isEdit && appId ? adminApi.getEnterpriseApplication(appId) : Promise.resolve(null), isEdit ? adminApi.listChannelRouteRules() : Promise.resolve([]), ]); if (cancelled) { return; } setGroups(groupItems.filter((item) => item.status !== 'disabled' && item.status !== 'deleted')); if (application) { if (enterpriseId && application.tenantId !== enterpriseId) { setError('应用不属于当前企业,已停止加载'); return; } hydrateApplication(application, routeRules); } } catch (failure) { if (!cancelled) { setError(failure instanceof Error ? failure.message : '短信应用加载失败'); } } } void loadForm(); return () => { cancelled = true; }; }, [appId, enterpriseId, isEdit]); function goBack() { navigate('/admin/enterprise-applications'); } function hydrateApplication(application: EnterpriseApplication, routeRules: DictionaryItem[]) { setAppName(application.name); setScene(application.scene ?? ''); setDailyLimit(application.dailyLimit ? String(application.dailyLimit) : ''); setCustomerUnitPrice(((application.customerUnitPrice ?? 0) / 100).toFixed(4)); setPhoneDailyLimit(application.maxPhonesPerTask ? String(application.maxPhonesPerTask) : ''); setMismatchPolicy(application.templateMismatchMode ?? 'reject'); setIpAddress(application.ipAllowlist?.map((item) => item.ipCidr).join('\n') ?? ''); const activeRules = routeRules.filter((rule) => ( rule.applicationId === application.id && rule.status !== 'deleted' && !rule.province && !rule.channelId )); setMobileGroupId(getRouteGroupId(activeRules, 'mobile')); setUnicomGroupId(getRouteGroupId(activeRules, 'unicom')); setTelecomGroupId(getRouteGroupId(activeRules, 'telecom')); } async function submit() { if (!enterpriseId) { setError('缺少企业 ID'); return; } const selectedGroups = [ { carrier: 'mobile' as Carrier, groupId: mobileGroupId }, { carrier: 'unicom' as Carrier, groupId: unicomGroupId }, { carrier: 'telecom' as Carrier, groupId: telecomGroupId }, ].filter((item) => item.groupId); if (selectedGroups.length === 0) { setError('请至少配置一个运营商通道组'); return; } const payload = { name: appName, scene, dailyLimit: Number(dailyLimit) || undefined, customerUnitPrice: Math.round(Number(customerUnitPrice || 0) * 100), maxPhonesPerTask: Number(phoneDailyLimit) || undefined, templateMismatchMode: mismatchPolicy, ipAllowlist: parseIpAllowlist(ipAddress), }; setSaving(true); setError(''); try { const application = isEdit && appId ? await adminApi.updateEnterpriseApplication(appId, payload) : await adminApi.createEnterpriseApplication({ tenantId: enterpriseId, ...payload }); await adminApi.replaceApplicationRouteRules(application.id, { routes: selectedGroups.map((item, index) => ({ carrier: item.carrier, groupId: item.groupId, priority: (index + 1) * 10, status: 'active', })), }); goBack(); } catch (failure) { setError(failure instanceof Error ? failure.message : '短信应用保存失败'); } finally { setSaving(false); } } const groupOptionsByCarrier = (carrier: ChannelGroup['carrier']) => [ { label: '不配置', value: '' }, ...groups.filter((group) => group.carrier === carrier).map((group) => ({ label: group.name, value: group.id })), ]; const selectedGroupCount = [mobileGroupId, unicomGroupId, telecomGroupId].filter(Boolean).length; const routeCards: Array<{ carrier: Carrier; groupId: string; onChange: (value: string) => void }> = [ { carrier: 'mobile', groupId: mobileGroupId, onChange: setMobileGroupId }, { carrier: 'unicom', groupId: unicomGroupId, onChange: setUnicomGroupId }, { carrier: 'telecom', groupId: telecomGroupId, onChange: setTelecomGroupId }, ]; return (

短信应用和三网通道组配置写入真实后台接口。

{error ?

{error}

: null}

业务信息

先填写应用基础信息,保存后将生成真实企业应用。

setAppName(event.target.value)} placeholder="请输入应用名称" required value={appName} /> setScene(event.target.value)} placeholder="行业通知/营销推广/验证码" value={scene} /> setDailyLimit(event.target.value)} placeholder="100000" required value={dailyLimit} /> setCustomerUnitPrice(event.target.value)} placeholder="0.0300" required value={customerUnitPrice} /> setPhoneDailyLimit(event.target.value)} placeholder="10" required value={phoneDailyLimit} /> setIpAddress(event.target.value)} placeholder="多个 IP/CIDR 可用逗号、空格或换行分隔" value={ipAddress} />

运营商通道组

至少选择一个运营商通道组;每个运营商只能绑定同运营商通道组。

0 ? 'success' : 'warning'}>{selectedGroupCount}/3 已配置
{routeCards.map((card) => { const available = groups.filter((group) => group.carrier === card.carrier); const meta = carrierMeta[card.carrier]; return (
{meta.label}通道组 {meta.description}
{card.groupId ? '已选择' : `${available.length} 个可选`}