import React, { useState, useEffect, useMemo } 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 apiClient from '../utils/request'; import dayjs from 'dayjs'; import { useLanguageStore } from '../store/languageStore'; const { Text, Title } = Typography; const RATE_PAIR_KEYS = [ { key: 'CNY_LAK', i18nKey: 'CNYLAK', from: 'CNY', to: 'LAK', fromI18n: 'CNY', toI18n: 'LAK' }, { key: 'CNY_USD', i18nKey: 'CNYUSD', from: 'CNY', to: 'USD', fromI18n: 'CNY', toI18n: 'USD' }, { key: 'CNY_THB', i18nKey: 'CNYTHB', from: 'CNY', to: 'THB', fromI18n: 'CNY', toI18n: 'THB' }, { key: 'USD_LAK', i18nKey: 'USDLAK', from: 'USD', to: 'LAK', fromI18n: 'USD', toI18n: 'LAK' }, { key: 'THB_LAK', i18nKey: 'THBLAK', from: 'THB', to: 'LAK', fromI18n: 'THB', toI18n: 'LAK' }, ]; 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 { t, currentLanguage } = useLanguageStore(); const RATE_PAIRS = useMemo(() => RATE_PAIR_KEYS.map(p => ({ ...p, label: t(`exchangeRate.${p.i18nKey}`), fromLabel: t(`exchangeRate.${p.fromI18n}`), toLabel: t(`exchangeRate.${p.toI18n}`), })), [t]); 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 apiClient.get('/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(t('exchangeRate.getRateFailed')); 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 apiClient.get('/exchange-rates/history?limit=20'); if (res.data.success) { setHistoryRates(res.data.data); } } catch (error) { console.error('获取历史汇率失败:', error); } }; 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; const newRate = 1 / value; setRates(prev => ({ ...prev, [key]: { leftValue: value, rightValue: 1, actualRate: newRate } })); }; 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; 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 apiClient.post('/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(t('exchangeRate.noChange')); setSaving(false); return; } await Promise.all(validPromises); message.success(t('exchangeRate.saveSuccess')); 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(t('exchangeRate.saveFailed')); } finally { setSaving(false); } }; const historyColumns = [ { title: t('exchangeRate.ratePair'), 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: t('exchangeRate.rate'), 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: t('exchangeRate.effectiveDate'), dataIndex: 'effective_date', key: 'effective_date', render: (date: string) => dayjs(date).format('YYYY-MM-DD') }, { title: t('exchangeRate.setTime'), dataIndex: 'created_at', key: 'created_at', render: (time: string) => dayjs(time).format('YYYY-MM-DD HH:mm') }, { title: t('exchangeRate.setBy'), dataIndex: 'created_by_name', key: 'created_by_name', render: (name: string) => name || '-' } ]; if (loading) { return
; } return (
{t('exchangeRate.title')} {t('exchangeRate.description')} {lastUpdateTime && ( {t('exchangeRate.lastUpdated')}{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={t('exchangeRate.inputFrom', { from: 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={t('exchangeRate.inputTo', { to: pair.toLabel })} />
{t('exchangeRate.actualRate')}{getActualRateDisplay(pair.key)}
); })}
{t('exchangeRate.historyRate')} } style={{ marginTop: 24 }} > {t('exchangeRate.tipText')} ); }; export default ExchangeRatePage;