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

101 lines
4.0 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 { useState } from "react";
import { api } from "@/lib/api";
import type { CostResult, Destination } from "@/lib/types";
interface Props { destinations: Destination[] }
export default function Calculator({ destinations }: Props) {
const [slug, setSlug] = useState(destinations[0]?.slug || "");
const [housing, setHousing] = useState("mid");
const [months, setMonths] = useState(3);
const [result, setResult] = useState<CostResult | null>(null);
const [loading, setLoading] = useState(false);
const calculate = async () => {
setLoading(true);
try {
const data = await api.calculateCost({ destination_slug: slug, housing, months });
setResult(data);
} catch {
setResult(null);
} finally {
setLoading(false);
}
};
return (
<section className="section calculator-section" id="calculator">
<div className="container">
<div className="section-header reveal">
<span className="section-tag">🧮 CALCULATOR</span>
<h2>旅居费用计算器</h2>
<p>根据目的地和生活方式,精准估算你的旅居预算</p>
</div>
<div className="calculator-wrapper reveal">
<div className="calculator-form">
<div className="calc-field">
<label>🏙️ 目的地</label>
<select value={slug} onChange={(e) => setSlug(e.target.value)}>
{destinations.map((d) => (
<option key={d.slug} value={d.slug}>{d.emoji} {d.name}, {d.country}</option>
))}
</select>
</div>
<div className="calc-field">
<label>🏠 住宿档次</label>
<div className="calc-options">
{[{ v: "budget", l: "💰 经济" }, { v: "mid", l: "🏠 舒适" }, { v: "premium", l: "✨ 豪华" }].map((o) => (
<button key={o.v} className={`calc-option${housing === o.v ? " active" : ""}`}
onClick={() => setHousing(o.v)}>{o.l}</button>
))}
</div>
</div>
<div className="calc-field">
<label>📅 旅居月数: {months} 个月</label>
<input type="range" min={1} max={12} value={months} onChange={(e) => setMonths(+e.target.value)} />
</div>
<button className="btn btn-primary" onClick={calculate} disabled={loading}>
{loading ? "计算中..." : "🧮 开始计算"}
</button>
</div>
<div className="calculator-result">
{result ? (
<div className="result-card">
<div className="result-header">
<span style={{ fontSize: "3rem" }}>{result.emoji}</span>
<div>
<h3>{result.destination}</h3>
<p>{result.months} 个月旅居预算</p>
</div>
</div>
<div className="result-breakdown">
{Object.entries(result.breakdown).map(([k, v]) => (
<div key={k} className="breakdown-item">
<span>{k}</span>
<div className="breakdown-bar">
<div className="breakdown-fill" style={{ width: `${(v / result.per_month) * 100}%` }} />
</div>
<strong>¥{v.toLocaleString()}</strong>
</div>
))}
</div>
<div className="result-total">
<div><span>月均</span><strong>¥{result.per_month.toLocaleString()}</strong></div>
<div><span>总计</span><strong className="gradient-text">¥{result.total.toLocaleString()}</strong></div>
</div>
</div>
) : (
<div className="result-placeholder">
<span style={{ fontSize: "4rem" }}>🧮</span>
<p>选择目的地和参数,点击计算查看预算明细</p>
</div>
)}
</div>
</div>
</div>
</section>
);
}