nomadweb/frontend/src/components/CurrencyConverter.tsx
2026-08-28 19:49:10 -05:00

137 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useMemo, useState } from "react";
const CURRENCIES = [
{ code: "CNY", name: "人民币", flag: "🇨🇳", rate: 1 },
{ code: "USD", name: "美元", flag: "🇺🇸", rate: 0.138 },
{ code: "EUR", name: "欧元", flag: "🇪🇺", rate: 0.127 },
{ code: "THB", name: "泰铢", flag: "🇹🇭", rate: 4.85 },
{ code: "IDR", name: "印尼盾", flag: "🇮🇩", rate: 2180 },
{ code: "JPY", name: "日元", flag: "🇯🇵", rate: 20.5 },
{ code: "MXN", name: "墨西哥比索", flag: "🇲🇽", rate: 2.35 },
{ code: "GBP", name: "英镑", flag: "🇬🇧", rate: 0.109 },
];
const PRESETS = [
{ label: "清迈月租", amount: 2500, from: "CNY", emoji: "🏔️" },
{ label: "巴厘岛生活费", amount: 4500, from: "CNY", emoji: "🏝️" },
{ label: "里斯本月租", amount: 800, from: "EUR", emoji: "🌊" },
{ label: "东京月租", amount: 120000, from: "JPY", emoji: "🗼" },
];
export default function CurrencyConverter() {
const [amount, setAmount] = useState(5000);
const [from, setFrom] = useState("CNY");
const [to, setTo] = useState("USD");
const [swapping, setSwapping] = useState(false);
const result = useMemo(() => {
const fromRate = CURRENCIES.find((c) => c.code === from)?.rate ?? 1;
const toRate = CURRENCIES.find((c) => c.code === to)?.rate ?? 1;
return (amount / fromRate) * toRate;
}, [amount, from, to]);
const swap = () => {
setSwapping(true);
setFrom(to);
setTo(from);
setTimeout(() => setSwapping(false), 400);
};
const applyPreset = (p: typeof PRESETS[0]) => {
setAmount(p.amount);
setFrom(p.from);
setTo("CNY");
};
const fromCur = CURRENCIES.find((c) => c.code === from)!;
const toCur = CURRENCIES.find((c) => c.code === to)!;
return (
<section className="section currency-section" id="currency">
<div className="container">
<div className="section-header reveal">
<span className="section-tag">💱 CURRENCY</span>
<h2>多币种换算器</h2>
<p>快速换算旅居生活费,规划跨国预算更轻松</p>
</div>
<div className="currency-wrapper reveal">
<div className="currency-card">
<div className="currency-input-group">
<label>💰 金额</label>
<div className="currency-input-row">
<input
type="number"
value={amount}
onChange={(e) => setAmount(Math.max(0, +e.target.value || 0))}
min={0}
/>
<select value={from} onChange={(e) => setFrom(e.target.value)}>
{CURRENCIES.map((c) => (
<option key={c.code} value={c.code}>{c.flag} {c.code}</option>
))}
</select>
</div>
</div>
<button className={`currency-swap${swapping ? " swapping" : ""}`} onClick={swap} aria-label="交换货币">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M7 16V4M7 4L3 8M7 4l4 4M17 8v12M17 20l4-4M17 20l-4-4" />
</svg>
</button>
<div className="currency-result">
<label>✨ 换算结果</label>
<div className="currency-result-display">
<span className="currency-result-flag">{toCur.flag}</span>
<span className="currency-result-amount">
{result.toLocaleString(undefined, { maximumFractionDigits: 2 })}
</span>
<span className="currency-result-code">{toCur.code}</span>
</div>
<p className="currency-rate-hint">
1 {fromCur.code} ≈ {((fromCur.rate / toCur.rate)).toLocaleString(undefined, { maximumFractionDigits: 4 })} {toCur.code}
</p>
</div>
<div className="currency-to-select">
<label>兑换为</label>
<div className="currency-chips">
{CURRENCIES.filter((c) => c.code !== from).map((c) => (
<button
key={c.code}
className={`currency-chip${to === c.code ? " active" : ""}`}
onClick={() => setTo(c.code)}
>
{c.flag} {c.code}
</button>
))}
</div>
</div>
</div>
<div className="currency-presets">
<h3>🏙️ 快捷参考</h3>
<p className="currency-presets-desc">一键填入典型旅居开销</p>
<div className="currency-preset-grid">
{PRESETS.map((p) => (
<button key={p.label} className="currency-preset-card" onClick={() => applyPreset(p)}>
<span className="preset-emoji">{p.emoji}</span>
<strong>{p.label}</strong>
<span>{p.amount.toLocaleString()} {p.from}</span>
</button>
))}
</div>
<div className="currency-tip">
<span>💡</span>
<p>实际汇率会波动,建议用 Wise 或 Revolut 获取实时汇率。此处为参考汇率。</p>
</div>
</div>
</div>
</div>
</section>
);
}