'use client'; import { useState, useEffect } from 'react'; import api from '@/lib/api'; import { X, Save, RefreshCw, Loader2, ArrowRight, AlertTriangle, Lock } from 'lucide-react'; interface ExchangeRateModalProps { isOpen: boolean; onClose: () => void; } interface RateConfig { base: string; precision: number; rates: Record; } // 常用货币名称映射,增强可读性 const CURRENCY_NAMES: Record = { CNY: '人民币 (Chinese Yuan)', USD: '美元 (US Dollar)', EUR: '欧元 (Euro)', GBP: '英镑 (British Pound)', HKD: '港币 (Hong Kong Dollar)', JPY: '日元 (Japanese Yen)', AUD: '澳元 (Australian Dollar)', CAD: '加元 (Canadian Dollar)', SGD: '新加坡元 (Singapore Dollar)', }; export default function ExchangeRateModal({ isOpen, onClose }: ExchangeRateModalProps) { const [loading, setLoading] = useState(false); const [submitting, setSubmitting] = useState(false); // 默认配置 const [config, setConfig] = useState({ base: 'CNY', precision: 4, rates: {} }); // 获取数据 const fetchRates = async () => { setLoading(true); try { const res = await api.get('/api/dynamic-configurations/key/EXCHANGE_RATES'); const val = res.data.data?.config_value; // 兼容处理:后端可能返回 JSON 对象,也可能返回 JSON 字符串 if (val && typeof val === 'object') { setConfig(val); } else if (typeof val === 'string') { try { setConfig(JSON.parse(val)); } catch (e) { console.error("JSON Parse error", e); } } } catch (e) { console.error("Failed to fetch rates", e); // 兜底数据 setConfig({ base: 'CNY', precision: 4, rates: { "CNY": 1, "USD": 7.25, "EUR": 7.65, "GBP": 9.10, "HKD": 0.92, "JPY": 0.048 } }); } finally { setLoading(false); } }; useEffect(() => { if (isOpen) fetchRates(); }, [isOpen]); // 处理输入 const handleRateChange = (currency: string, value: string) => { // 允许输入过程中的空字符串,但存储时转为数字 const numVal = value === '' ? 0 : parseFloat(value); setConfig(prev => ({ ...prev, rates: { ...prev.rates, [currency]: isNaN(numVal) ? 0 : numVal } })); }; // 提交保存 const handleSubmit = async () => { // 安全校验:防止基准货币被篡改 if (config.rates[config.base] !== 1) { alert(`错误:基准货币 ${config.base} 的汇率必须保持为 1。请重置后重试。`); return; } setSubmitting(true); try { // 必须将对象转为字符串,因为后端数据库字段通常是 TEXT const payload = { config_value: JSON.stringify(config), description: "汇率换算表 (管理员手动更新)" }; await api.put('/api/dynamic-configurations/key/EXCHANGE_RATES', payload); alert("汇率配置已保存"); onClose(); } catch (e: any) { alert("保存失败: " + (e.response?.data?.message || e.message)); } finally { setSubmitting(false); } }; if (!isOpen) return null; return (
{/* Header */}

汇率配置

系统基准货币:{config.base}

{/* ⚠️ 警告栏:明确换算逻辑 */}
核心逻辑: 这里的数值代表 1 单位该货币 等值于 多少单位基准货币 ({config.base})
例如:若基准是 CNY,USD 行填入 7.25,代表 1 USD = 7.25 CNY
{/* Body */}
{loading ? (
) : (
{Object.entries(config.rates).map(([currency, rate]) => { const isBase = currency === config.base; // 辅助计算:反向汇率 (例如 1 CNY = 0.137 USD) // 用于给用户直觉上的校验 const inverseRate = rate > 0 ? (1 / rate).toFixed(4) : '0'; return (
{/* 1. 左侧:货币信息 */}
{currency} {isBase && }
{CURRENCY_NAMES[currency] || currency}
{/* 2. 中间:等式输入区 (防御性设计核心) */}
1 {currency}
handleRateChange(currency, e.target.value)} className={` w-full text-center font-mono font-bold text-lg bg-transparent outline-none ${isBase ? 'text-slate-500 cursor-not-allowed' : 'text-blue-700'} `} placeholder="0.00" />
{config.base}
{/* 3. 右侧:反向参照 (Double Check) */} {!isBase && (
反向参考 (Inverse)
1 {config.base} ≈ {inverseRate} {currency}
)}
); })}
)}
{/* Footer */}
); }