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({ sign_name: e.target.value })} />
{isSmsBao ? ( <>
update({ username: e.target.value })} placeholder="短信宝登录用户名" />
update({ password: e.target.value })} placeholder="短信宝登录密码(发送时自动 MD5)" />
update({ goods_id: e.target.value })} placeholder="留空使用默认通用产品" />
短信宝发送接口:api.smsbao.com/sms,密码以登录密码的 MD5(32位) 提交,短信内容需包含签名 【签名】。
) : ( <>
update({ api_key: e.target.value })} placeholder={cfg.provider === '阿里云短信' ? 'AccessKey ID' : 'API Key'} />
update({ api_secret: e.target.value })} placeholder={cfg.provider === '阿里云短信' ? 'AccessKey Secret' : 'API Secret'} />
{cfg.provider === '阿里云短信' && (
update({ template_code: e.target.value })} placeholder="如 SMS_00000001" />
)} )}
{isSmsBao && ( )}

{' '}说明

  • 测试短信将发送至当前管理员账号绑定的手机号,请先在用户管理中完善手机号。
  • 阿里云、腾讯云、华为云使用平台统一的短信网关;短信宝为独立通道,支持余额查询。
  • 短信内容须符合运营商规范,包含签名与退订提示,避免触发敏感词拦截。
); }; export default SmsConfig;