import React, { useState, useEffect } from 'react'; import { adminAPI } from '@/api'; const SVGIcon: React.FC<{ children: React.ReactNode; size?: number }> = ({ children, size = 16 }) => ( {children} ); const SmsConfig: React.FC = () => { const [cfg, setCfg] = useState>({ provider: '', api_key: '', api_secret: '', sign_name: '', template_code: '', username: '', password: '', goods_id: '', }); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [testing, setTesting] = useState(false); const [querying, setQuerying] = useState(false); const [savedMsg, setSavedMsg] = useState(null); const [testResult, setTestResult] = useState<{ ok: boolean; msg: string } | null>(null); const [balance, setBalance] = useState<{ sent: number; remain: number } | null>(null); useEffect(() => { (async () => { try { const res = await adminAPI.getSmsConfig(); const data = (res as any)?.data ?? res; setCfg({ provider: data.provider || '', api_key: data.api_key || '', api_secret: data.api_secret || '', sign_name: data.sign_name || '', template_code: data.template_code || '', username: data.username || '', password: data.password || '', goods_id: data.goods_id || '', }); } catch (e) { // ignore } finally { setLoading(false); } })(); }, []); const update = (patch: Partial>) => setCfg((prev) => ({ ...prev, ...patch })); const save = async () => { setSaving(true); setSavedMsg(null); setTestResult(null); try { await adminAPI.updateSmsConfig({ provider: cfg.provider, api_key: cfg.api_key, api_secret: cfg.api_secret, sign_name: cfg.sign_name, template_code: cfg.template_code, username: cfg.username, password: cfg.password, goods_id: cfg.goods_id, }); setSavedMsg('短信配置已保存'); } catch (e: any) { setSavedMsg('保存失败:' + (e?.response?.data?.detail || e?.message || '未知错误')); } finally { setSaving(false); } }; const sendTest = async () => { setTesting(true); setTestResult(null); setSavedMsg(null); try { const res = await adminAPI.testSms(); const data = (res as any)?.data ?? res; setTestResult({ ok: true, msg: data?.message || '测试短信已发送' }); } catch (e: any) { setTestResult({ ok: false, msg: e?.response?.data?.detail || e?.message || '发送失败' }); } finally { setTesting(false); } }; const queryBalance = async () => { setQuerying(true); setBalance(null); setTestResult(null); try { const res = await adminAPI.getSmsBalance(); const data = (res as any)?.data ?? res; setBalance({ sent: data?.sent ?? 0, remain: data?.remain ?? 0 }); } catch (e: any) { setTestResult({ ok: false, msg: e?.response?.data?.detail || e?.message || '查询余额失败' }); } finally { setQuerying(false); } }; const isSmsBao = cfg.provider === '短信宝'; if (loading) { return 短信配置加载中…; } return ( 短信配置 配置短信服务商,用于验证码、通知类短信的发送 {savedMsg && ( {savedMsg} )} {testResult && ( {testResult.msg} )} {balance && ( 短信宝账户:已发送 {balance.sent} 条,剩余 {balance.remain} 条 )} {' '}服务商与账号 短信服务商 update({ provider: e.target.value })}> 请选择 阿里云短信 腾讯云短信 华为云短信 短信宝 短信签名 update({ sign_name: e.target.value })} /> {isSmsBao ? ( <> 用户名 (u) update({ username: e.target.value })} placeholder="短信宝登录用户名" /> 密码 (p) update({ password: e.target.value })} placeholder="短信宝登录密码(发送时自动 MD5)" /> 专属通道产品 ID (g) 选填 update({ goods_id: e.target.value })} placeholder="留空使用默认通用产品" /> 短信宝发送接口:api.smsbao.com/sms,密码以登录密码的 MD5(32位) 提交,短信内容需包含签名 【签名】。 > ) : ( <> API Key update({ api_key: e.target.value })} placeholder={cfg.provider === '阿里云短信' ? 'AccessKey ID' : 'API Key'} /> API Secret update({ api_secret: e.target.value })} placeholder={cfg.provider === '阿里云短信' ? 'AccessKey Secret' : 'API Secret'} /> {cfg.provider === '阿里云短信' && ( 模板 Code 选填 update({ template_code: e.target.value })} placeholder="如 SMS_00000001" /> )} > )} {saving ? '保存中…' : '保存设置'} {testing ? '发送中…' : '发送测试短信'} {isSmsBao && ( {querying ? '查询中…' : '查询余额'} )} {' '}说明 测试短信将发送至当前管理员账号绑定的手机号,请先在用户管理中完善手机号。 阿里云、腾讯云、华为云使用平台统一的短信网关;短信宝为独立通道,支持余额查询。 短信内容须符合运营商规范,包含签名与退订提示,避免触发敏感词拦截。 ); }; export default SmsConfig;
加载中…
配置短信服务商,用于验证码、通知类短信的发送
api.smsbao.com/sms
【签名】