104 lines
3.8 KiB
TypeScript
104 lines
3.8 KiB
TypeScript
"use client";
|
||
|
||
import { useMemo, useState } from "react";
|
||
|
||
const WORK_GB = [
|
||
{ id: "docs", emoji: "📄", label: "文档/邮件", gb: 3 },
|
||
{ id: "meet", emoji: "🎥", label: "视频会议", gb: 12 },
|
||
{ id: "code", emoji: "💻", label: "云开发/同步", gb: 8 },
|
||
{ id: "stream", emoji: "🎬", label: "娱乐流媒体", gb: 15 },
|
||
{ id: "maps", emoji: "🗺️", label: "地图导航", gb: 2 },
|
||
{ id: "backup", emoji: "☁️", label: "相册备份", gb: 10 },
|
||
];
|
||
|
||
const PLANS = [
|
||
{ gb: 10, label: "轻量", tip: "以咖啡馆/办公网为主" },
|
||
{ gb: 30, label: "日常", tip: "远程办公够用" },
|
||
{ gb: 50, label: "充足", tip: "会议多也不慌" },
|
||
{ gb: 100, label: "豪横", tip: "流媒体+备份无压力" },
|
||
];
|
||
|
||
export default function DataBudgetPlanner() {
|
||
const [selected, setSelected] = useState(["docs", "meet", "code", "maps"]);
|
||
const [meetHours, setMeetHours] = useState(8);
|
||
const [hotspotDays, setHotspotDays] = useState(6);
|
||
|
||
const total = useMemo(() => {
|
||
let gb = WORK_GB.filter((w) => selected.includes(w.id)).reduce((s, w) => s + w.gb, 0);
|
||
// scale video by meeting hours (base assumes ~8h)
|
||
if (selected.includes("meet")) {
|
||
gb += Math.max(0, (meetHours - 8) * 1.2);
|
||
}
|
||
// hotspot premium days add overhead
|
||
gb += hotspotDays * 0.8;
|
||
return Math.round(gb * 10) / 10;
|
||
}, [selected, meetHours, hotspotDays]);
|
||
|
||
const plan = PLANS.find((p) => total <= p.gb) || PLANS[PLANS.length - 1];
|
||
const pct = Math.min((total / 100) * 100, 100);
|
||
|
||
const toggle = (id: string) => {
|
||
setSelected((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]));
|
||
};
|
||
|
||
return (
|
||
<section className="section data-budget-section" id="data-budget">
|
||
<div className="container">
|
||
<div className="section-header reveal">
|
||
<span className="section-tag">📶 DATA BUDGET</span>
|
||
<h2>流量预算规划</h2>
|
||
<p>按办公习惯估算月流量,选 SIM / eSIM 套餐更有数</p>
|
||
</div>
|
||
|
||
<div className="data-budget-card reveal">
|
||
<div className="data-budget-meter">
|
||
<div className="data-budget-gauge">
|
||
<div className="data-budget-fill" style={{ width: `${pct}%` }} />
|
||
</div>
|
||
<div className="data-budget-hero">
|
||
<strong>{total} GB</strong>
|
||
<span>/ 月估算</span>
|
||
</div>
|
||
<p className="data-budget-plan">
|
||
建议套餐档:<b>{plan.gb}GB · {plan.label}</b> — {plan.tip}
|
||
</p>
|
||
</div>
|
||
|
||
<div className="data-budget-grid">
|
||
{WORK_GB.map((w) => {
|
||
const on = selected.includes(w.id);
|
||
return (
|
||
<button
|
||
key={w.id}
|
||
type="button"
|
||
className={`data-budget-item${on ? " on" : ""}`}
|
||
onClick={() => toggle(w.id)}
|
||
aria-pressed={on}
|
||
>
|
||
<span>{w.emoji}</span>
|
||
<div>
|
||
<strong>{w.label}</strong>
|
||
<small>~{w.gb} GB</small>
|
||
</div>
|
||
<i>{on ? "✓" : "+"}</i>
|
||
</button>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
<div className="data-budget-sliders">
|
||
<label>
|
||
<span>视频会议时长:{meetHours} 小时/月</span>
|
||
<input type="range" min={0} max={40} value={meetHours} onChange={(e) => setMeetHours(+e.target.value)} />
|
||
</label>
|
||
<label>
|
||
<span>纯热点办公天数:{hotspotDays} 天/月</span>
|
||
<input type="range" min={0} max={20} value={hotspotDays} onChange={(e) => setHotspotDays(+e.target.value)} />
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|