import React, { useState, useEffect } from 'react'; import { Card, Row, Col, InputNumber, message, Typography, Divider, Spin, Button, Table, Space, Tag } from 'antd'; import { CheckOutlined, HistoryOutlined } from '@ant-design/icons'; import axios from 'axios'; import dayjs from 'dayjs'; const { Text, Title } = Typography; const RATE_PAIRS = [ { key: 'CNY_LAK', label: '中老汇率', from: 'CNY', to: 'LAK', fromLabel: '人民币', toLabel: '老挝基普' }, { key: 'CNY_USD', label: '中美汇率', from: 'CNY', to: 'USD', fromLabel: '人民币', toLabel: '美元' }, { key: 'CNY_THB', label: '中泰汇率', from: 'CNY', to: 'THB', fromLabel: '人民币', toLabel: '泰铢' }, { key: 'USD_LAK', label: '美老汇率', from: 'USD', to: 'LAK', fromLabel: '美元', toLabel: '老挝基普' }, { key: 'THB_LAK', label: '泰老汇率', from: 'THB', to: 'LAK', fromLabel: '泰铢', toLabel: '老挝基普' }, ]; interface RateItem { leftValue: number; rightValue: number; actualRate: number; } interface HistoryRate { id: number; pair_key: string; rate: number; effective_date: string; created_at: string; created_by_name?: string; } const ExchangeRatePage: React.FC = () => { const [rates, setRates] = useState>({}); const [initialRates, setInitialRates] = useState>({}); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [isMobile, setIsMobile] = useState(false); const [historyRates, setHistoryRates] = useState([]); const [lastUpdateTime, setLastUpdateTime] = useState(''); useEffect(() => { const checkMobile = () => setIsMobile(window.innerWidth <= 768); checkMobile(); window.addEventListener('resize', checkMobile); return () => window.removeEventListener('resize', checkMobile); }, []); useEffect(() => { fetchRates(); fetchHistory(); }, []); const fetchRates = async () => { setLoading(true); try { const res = await axios.get('/api/exchange-rates/latest'); if (res.data.success) { const data = res.data.data; const newRates: Record = {}; const newInitialRates: Record = {}; RATE_PAIRS.forEach(pair => { const rate = parseFloat(data[pair.key]) || 1; newRates[pair.key] = { leftValue: 1, rightValue: rate, actualRate: rate }; newInitialRates[pair.key] = rate; }); setRates(newRates); setInitialRates(newInitialRates); if (res.data.updated_at) { setLastUpdateTime(res.data.updated_at); } } } catch (error) { message.error('获取汇率失败'); const defaultRates: Record = {}; const defaultInitialRates: Record = {}; RATE_PAIRS.forEach(pair => { const defaultRate = pair.key === 'CNY_LAK' ? 3000 : pair.key === 'CNY_USD' ? 0.14 : pair.key === 'CNY_THB' ? 4.5 : pair.key === 'USD_LAK' ? 21000 : 670; defaultRates[pair.key] = { leftValue: 1, rightValue: defaultRate, actualRate: defaultRate }; defaultInitialRates[pair.key] = defaultRate; }); setRates(defaultRates); setInitialRates(defaultInitialRates); } finally { setLoading(false); } }; const fetchHistory = async () => { try { const res = await axios.get('/api/exchange-rates/history?limit=20'); if (res.data.success) { setHistoryRates(res.data.data); } } catch (error) { console.error('获取历史汇率失败:', error); } }; // 左侧输入 - 右侧自动变为1,重新计算汇率 const handleLeftChange = (key: string, value: number | null) => { if (value === null || value <= 0) return; const pair = RATE_PAIRS.find(p => p.key === key); if (!pair) return; // 当左侧输入值时,右侧变为1,计算新的汇率 const newRate = 1 / value; setRates(prev => ({ ...prev, [key]: { leftValue: value, rightValue: 1, actualRate: newRate } })); }; // 右侧输入 - 左侧自动变为1,重新计算汇率 const handleRightChange = (key: string, value: number | null) => { if (value === null || value <= 0) return; const pair = RATE_PAIRS.find(p => p.key === key); if (!pair) return; // 当右侧输入值时,左侧变为1,计算新的汇率 const newRate = value; setRates(prev => ({ ...prev, [key]: { leftValue: 1, rightValue: value, actualRate: newRate } })); }; // 计算实际汇率显示 const getActualRateDisplay = (key: string) => { const item = rates[key]; if (!item) return '1 : 1.00'; const pair = RATE_PAIRS.find(p => p.key === key); const actualRate = item.actualRate; // 根据汇率对选择合适的小数位数 const decimalPlaces = pair?.key === 'CNY_USD' ? 5 : 2; return `1 ${pair?.from} = ${actualRate.toFixed(decimalPlaces)} ${pair?.to}`; }; // 确认保存 const handleConfirm = async () => { setSaving(true); try { const savePromises = RATE_PAIRS.map(pair => { const item = rates[pair.key]; if (!item) return null; const actualRate = item.rightValue / item.leftValue; const initialRate = initialRates[pair.key]; // 只保存有变化的汇率 if (Math.abs(actualRate - initialRate) < 0.0001) { return null; } return axios.post('/api/exchange-rates', { pair_key: pair.key, rate: actualRate, effective_date: dayjs().format('YYYY-MM-DD') }); }); const validPromises = savePromises.filter(Boolean) as Promise[]; if (validPromises.length === 0) { message.info('没有汇率发生变化'); setSaving(false); return; } await Promise.all(validPromises); message.success('汇率保存成功'); setLastUpdateTime(dayjs().format('YYYY-MM-DD HH:mm:ss')); fetchHistory(); // 更新初始汇率为当前汇率 const newInitialRates: Record = {}; RATE_PAIRS.forEach(pair => { const item = rates[pair.key]; if (item) { newInitialRates[pair.key] = item.rightValue / item.leftValue; } }); setInitialRates(newInitialRates); } catch (error) { message.error('保存汇率失败'); } finally { setSaving(false); } }; // 历史汇率表格列 const historyColumns = [ { title: '汇率对', dataIndex: 'from_currency', key: 'from_currency', render: (_: string, record: HistoryRate) => { const pairKey = `${record.from_currency}_${record.to_currency}`; const pair = RATE_PAIRS.find(p => p.key === pairKey); return pair?.label || pairKey; } }, { title: '汇率', dataIndex: 'rate', key: 'rate', render: (rate: number, record: HistoryRate) => { const pairKey = `${record.from_currency}_${record.to_currency}`; const pair = RATE_PAIRS.find(p => p.key === pairKey); return `1 ${record.from_currency} = ${parseFloat(rate).toFixed(pair?.key === 'CNY_USD' ? 4 : 2)} ${record.to_currency}`; } }, { title: '生效日期', dataIndex: 'effective_date', key: 'effective_date', render: (date: string) => dayjs(date).format('YYYY-MM-DD') }, { title: '设置时间', dataIndex: 'created_at', key: 'created_at', render: (time: string) => dayjs(time).format('YYYY-MM-DD HH:mm') }, { title: '设置人', dataIndex: 'created_by_name', key: 'created_by_name', render: (name: string) => name || '-' } ]; if (loading) { return
; } return (
汇率管理 设置各币种汇率,输入任意一侧自动计算 {lastUpdateTime && ( 上次更新: {lastUpdateTime} )}
{RATE_PAIRS.map(pair => { const item = rates[pair.key]; if (!item) return null; return (
{pair.fromLabel}
handleLeftChange(pair.key, v)} precision={6} size="large" min={0.000001} onFocus={(e) => { if (e.target && e.target.select) { e.target.select(); } }} placeholder={`输入${pair.fromLabel}金额`} />
=
{pair.toLabel}
handleRightChange(pair.key, v)} precision={pair.key === 'CNY_USD' ? 4 : 2} size="large" min={0.000001} onFocus={(e) => { if (e.target && e.target.select) { e.target.select(); } }} placeholder={`输入${pair.toLabel}金额`} />
实际汇率: {getActualRateDisplay(pair.key)}
); })}
{/* 确认按钮 */}
{/* 历史汇率表 */} 历史汇率记录 } style={{ marginTop: 24 }} > 提示:输入任意一侧数值,另一侧会自动计算。实际汇率实时显示为 1左侧币种 = X右侧币种。点击"确认保存汇率"按钮保存当前设置到数据库。 ); }; export default ExchangeRatePage;