<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>华风爱科天气面板｜自动GPS定位 区县精度</title>
<style>
*{margin:0;padding:0;box-sizing:border-box;font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}
:root{
  --glass-bg: rgba(255,255,255,0.12);
  --glass-border: rgba(255,255,255,0.22);
  --glass-shadow: 0 8px 32px rgba(0,0,0,0.18);
}
body{
  min-height:100vh;
  transition: background 1.2s ease;
  background: linear-gradient(135deg,#6366f1,#8b5cf6,#22d3ee);
  background-size: 300% 300%;
  animation: bgFlow 18s ease infinite;
  padding:20px;
}
body.sunny{ background:linear-gradient(135deg,#fcd34d,#fb923c,#f87171); }
body.cloudy{ background:linear-gradient(135deg,#94a3b8,#64748b,#334155); }
body.rain{ background:linear-gradient(135deg,#1e3a8a,#1e40af,#0f172a); }
body.snow{ background:linear-gradient(135deg,#e2e8f0,#cbd5e1,#94a3b8); }
body.fog{ background:linear-gradient(135deg,#9ca3af,#6b7280,#4b5563); }
@keyframes bgFlow{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}
.container{max-width:1280px;margin:0 auto}
.glass{
  background:var(--glass-bg);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  border:1px solid var(--glass-border);
  border-radius:24px;
  box-shadow: var(--glass-shadow);
  position:relative;
  overflow:hidden;margin-bottom:20px;
}
.glass::before{
  content:"";position:absolute;top:0;left:0;right:0;height:45px;
  background:linear-gradient(180deg,rgba(255,255,255,0.24),transparent);
  pointer-events:none;
}
.glass-inner{padding:24px}
h2,h3,p,span,div{color:#fff;text-shadow:0 1px 8px rgba(0,0,0,0.12)}
.top-row{display:grid;grid-template-columns: 1fr 340px;gap:20px}
@media(max-width:860px){.top-row{grid-template-columns:1fr}}
.main-temp{font-size:88px;font-weight:200;line-height:1}
.desc{font-size:22px;opacity:0.92;margin:8px 0 16px}
.meta-grid{display:grid;grid-template-columns: repeat(2, 1fr);gap:12px}
.meta-item{padding:14px;background:rgba(255,255,255,0.08);border-radius:16px;border:1px solid rgba(255,255,255,0.12)}
.meta-label{font-size:13px;opacity:0.75}
.meta-val{font-size:19px;font-weight:500}
.section-title{font-size:20px;margin:16px 0 12px}
.hour-wrap{overflow-x:auto;padding-bottom:8px}
.hour-list{display:flex;gap:14px;width:max-content}
.hour-card{min-width:105px;padding:16px;border-radius:18px;background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.14);text-align:center}
.day-grid{display:grid;grid-template-columns: repeat(auto-fit,minmax(130px,1fr));gap:14px}
.day-card{padding:16px;border-radius:18px;background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.14);text-align:center}
.alert-item{padding:14px;border-radius:14px;background:rgba(250,82,82,0.25);border:1px solid rgba(255,100,100,0.3);margin:8px 0}
.loading{text-align:center;padding:60px;font-size:20px;color:#fff}
.tabs{display:flex;gap:10px;margin-bottom:14px;flex-wrap:wrap}
.tab{padding:8px 14px;border-radius:12px;background:rgba(255,255,255,0.12);border:1px solid rgba(255,255,255,0.2);cursor:pointer}
.tab.active{background:rgba(255,255,255,0.3)}
.index-grid{display:grid;grid-template-columns: repeat(auto-fit,minmax(160px,1fr));gap:12px}
.search-bar{display:flex;gap:10px;margin-bottom:16px}
input{padding:12px;border-radius:14px;background:rgba(255,255,255,0.15);border:1px solid rgba(255,255,255,0.25);color:#fff;outline:none;font-size:16px;flex:1}
button{padding:12px 16px;border-radius:14px;background:rgba(255,255,255,0.2);border:1px solid rgba(255,255,255,0.3);color:#fff;cursor:pointer}
button:active{background:rgba(255,255,255,0.35)}
.hide{display:none}
.nowcast-item{padding:10px;border-radius:12px;background:rgba(255,255,255,0.08)}
.err{color:#ffd6d6}
</style>
</head>
<body id="weatherBody">
<div class="container">
  <div class="glass">
    <div class="glass-inner">
      <div class="search-bar">
        <input id="cityInput" placeholder="输入城市/区县手动查询" value="">
        <button onclick="searchCity()">搜索</button>
        <button onclick="gpsLocate()">重新定位</button>
      </div>
      <div id="cityTitle" style="font-size:24px">正在请求位置授权...</div>
    </div>
  </div>

  <div id="loadingBox" class="glass">
    <div class="glass-inner loading">等待浏览器定位授权...</div>
  </div>

  <div id="weatherPanel" class="hide">
    <div class="top-row">
      <div class="glass">
        <div class="glass-inner">
          <div id="tempMain" class="main-temp">--</div>
          <div id="weatherText" class="desc">加载中</div>
          <div class="meta-grid">
            <div class="meta-item"><div class="meta-label">体感温度</div><div id="feelsLike" class="meta-val">--</div></div>
            <div class="meta-item"><div class="meta-label">相对湿度</div><div id="humidity" class="meta-val">--</div></div>
            <div class="meta-item"><div class="meta-label">气压</div><div id="pressure" class="meta-val">--</div></div>
            <div class="meta-item"><div class="meta-label">能见度</div><div id="visibility" class="meta-val">--</div></div>
            <div class="meta-item"><div class="meta-label">风向风力</div><div id="windInfo" class="meta-val">--</div></div>
            <div class="meta-item"><div class="meta-label">紫外线</div><div id="uv" class="meta-val">--</div></div>
          </div>
        </div>
      </div>
      <div class="glass">
        <div class="glass-inner">
          <h3 class="section-title">气象预警</h3>
          <div id="alertBox">暂无预警</div>
        </div>
      </div>
    </div>

    <div class="glass">
      <div class="glass-inner">
        <h3 class="section-title">空气质量 AQI</h3>
        <div id="airBox" class="meta-grid"></div>
      </div>
    </div>

    <div class="glass">
      <div class="glass-inner">
        <h3 class="section-title">分钟级短临降水预报</h3>
        <div id="nowcastBox"></div>
      </div>
    </div>

    <div class="glass">
      <div class="glass-inner">
        <h3 class="section-title">生活指数</h3>
        <div id="indexBox" class="index-grid"></div>
      </div>
    </div>

    <div class="glass">
      <div class="glass-inner">
        <h3 class="section-title">逐日预报</h3>
        <div class="tabs">
          <div class="tab active" data-day="1"  onclick="loadDaily(1)">1天预报</div>
          <div class="tab"        data-day="5"  onclick="loadDaily(5)">5天预报</div>
          <div class="tab"        data-day="10" onclick="loadDaily(10)">10天预报</div>
        </div>
        <div id="dailyContainer" class="day-grid"></div>
      </div>
    </div>

    <div class="glass">
      <div class="glass-inner">
        <h3 class="section-title">逐小时预报</h3>
        <div class="tabs">
          <div class="tab active" data-hour="1"  onclick="loadHour(1)">1小时</div>
          <div class="tab"        data-hour="12" onclick="loadHour(12)">12小时</div>
          <div class="tab"        data-hour="24" onclick="loadHour(24)">24小时</div>
          <div class="tab"        data-hour="72" onclick="loadHour(72)">72小时</div>
        </div>
        <div id="hourContainer" class="hour-list"></div>
      </div>
    </div>
  </div>
</div>

<script>
let locationKey = null;
let currentLat  = null;
let currentLon  = null;
const bodyEl = document.getElementById("weatherBody");

/* ---------------- 通用请求封装 ---------------- */
async function jget(qs){
  const res = await fetch(qs);
  if(!res.ok) throw new Error("HTTP " + res.status);
  return await res.json();
}

/* ---------------- 主题切换 ---------------- */
function setWeatherTheme(weatherTxt){
  const w = (weatherTxt || "").toLowerCase();
  bodyEl.classList.remove("sunny","cloudy","rain","snow","fog");
  if(w.includes("晴")) bodyEl.classList.add("sunny");
  else if(w.includes("雨") || w.includes("雷")) bodyEl.classList.add("rain");
  else if(w.includes("雪")) bodyEl.classList.add("snow");
  else if(w.includes("雾") || w.includes("霾")) bodyEl.classList.add("fog");
  else bodyEl.classList.add("cloudy");
}

/* ---------------- ① 定位搜索（文字） ---------------- */
async function searchCity(){
  const q = document.getElementById("cityInput").value.trim();
  if(!q){ alert("请输入城市名称"); return; }
  try{
    const data = await jget(`?action=searchCity&q=${encodeURIComponent(q)}`);
    if(!Array.isArray(data) || data.length === 0){ alert("找不到该地点"); return; }
    locationKey = data[0].Key;
    currentLat  = data[0].GeoPosition.Latitude;
    currentLon  = data[0].GeoPosition.Longitude;
    document.getElementById("cityTitle").innerText =
      data[0].LocalizedName + "，" + data[0].AdministrativeArea.LocalizedName;
    await loadAllWeather();
  }catch(e){
    alert("搜索失败：" + e.message);
  }
}

/* ---------------- ② 定位搜索（GPS 反查） ---------------- */
async function gpsLocate(){
  document.getElementById("cityTitle").innerText = "正在获取GPS坐标...";
  document.getElementById("loadingBox").style.display = "block";
  document.getElementById("weatherPanel").classList.add("hide");

  if(!navigator.geolocation){
    document.getElementById("loadingBox").innerHTML =
      `❌ 当前浏览器不支持定位<br/>请手动输入区县名称查询天气`;
    return;
  }
  navigator.geolocation.getCurrentPosition(async pos=>{
    currentLat = pos.coords.latitude;
    currentLon = pos.coords.longitude;
    document.getElementById("cityTitle").innerText =
      `坐标：${currentLat.toFixed(4)}, ${currentLon.toFixed(4)}，解析地区中`;

    try{
      const data = await jget(`?action=geoSearch&lat=${currentLat}&lon=${currentLon}`);
      if(!data || !data.Key){
        document.getElementById("loadingBox").innerHTML =
          `❌ 坐标解析失败，请手动搜索区县`;
        return;
      }
      locationKey = data.Key;
      document.getElementById("cityInput").value = data.LocalizedName;
      document.getElementById("cityTitle").innerText =
        data.LocalizedName + "，" + data.AdministrativeArea.LocalizedName;
      await loadAllWeather();
    }catch(e){
      document.getElementById("loadingBox").innerHTML =
        `❌ 解析失败：${e.message}`;
    }
  }, err=>{
    let errMsg = "";
    if(err.code === 1) errMsg = "❌ 用户拒绝位置授权，请点【重新定位】或手动输入区县";
    else if(err.code === 2) errMsg = "❌ 获取位置失败，无法拿到GPS信息";
    else errMsg = "❌ 定位超时";
    document.getElementById("loadingBox").innerHTML = errMsg;
  });
}

/* ---------------- ③ 实况天气 ---------------- */
async function getCurrent(){
  const arr = await jget(`?action=current&lk=${locationKey}`);
  if(!Array.isArray(arr) || !arr[0]) throw new Error("实况数据为空");
  const d = arr[0];
  document.getElementById("tempMain").innerText   = `${d.Temperature.Metric.Value}°`;
  document.getElementById("weatherText").innerText = d.WeatherText;
  document.getElementById("feelsLike").innerText  = `${d.RealFeelTemperature.Metric.Value} ℃`;
  document.getElementById("humidity").innerText   = `${d.RelativeHumidity} %`;
  document.getElementById("pressure").innerText   = `${d.Pressure.Metric.Value} hPa`;
  document.getElementById("visibility").innerText = `${d.Visibility.Metric.Value} km`;
  document.getElementById("windInfo").innerText   =
    `${d.Wind.Direction.Localized} ${d.Wind.Speed.Metric.Value} km/h`;
  document.getElementById("uv").innerText =
    `${d.UVIndex} ${d.UVIndexText}`;
  setWeatherTheme(d.WeatherText);
}

/* ---------------- ④ 气象预警 ---------------- */
async function getAlert(){
  const alerts = await jget(`?action=alert&lk=${locationKey}`);
  const box = document.getElementById("alertBox");
  if(!Array.isArray(alerts) || alerts.length === 0){
    box.innerHTML = `<div>暂无气象预警</div>`;
    return;
  }
  box.innerHTML = alerts.map(a =>
    `<div class="alert-item"><b>${a.Headline}</b>
      <p style="margin-top:8px;font-size:14px">${a.Description}</p></div>`
  ).join("");
}

/* ---------------- ⑤ 空气质量 ---------------- */
async function getAirQuality(){
  const air = await jget(`?action=air&lk=${locationKey}`);
  const box = document.getElementById("airBox");
  const aq  = air && air.AirQuality;
  if(!aq){
    box.innerHTML = `<div class="meta-item"><div class="meta-label">空气质量</div>
                     <div class="meta-val">暂无监测站点数据</div></div>`;
    return;
  }
  box.innerHTML = `
    <div class="meta-item"><div class="meta-label">AQI</div><div class="meta-val">${aq.AQI}</div></div>
    <div class="meta-item"><div class="meta-label">等级</div><div class="meta-val">${aq.Category}</div></div>
    <div class="meta-item"><div class="meta-label">PM2.5</div><div class="meta-val">${aq.PM25} μg/m³</div></div>
    <div class="meta-item"><div class="meta-label">PM10</div><div class="meta-val">${aq.PM10} μg/m³</div></div>
    <div class="meta-item"><div class="meta-label">O₃臭氧</div><div class="meta-val">${aq.Ozone} μg/m³</div></div>
    <div class="meta-item"><div class="meta-label">NO₂</div><div class="meta-val">${aq.NitrogenDioxide} μg/m³</div></div>
  `;
}

/* ---------------- ⑥ 分钟级降水 ---------------- */
async function getNowcast(){
  const nc = await jget(`?action=nowcast&lat=${currentLat}&lon=${currentLon}`);
  const box = document.getElementById("nowcastBox");
  if(!nc || !Array.isArray(nc.PrecipitationList) || nc.PrecipitationList.length === 0){
    box.innerHTML = `<div class="nowcast-item">当前区域暂无雷达分钟降水预报</div>`;
    return;
  }
  box.innerHTML = nc.PrecipitationList.map(item =>
    `<div class="nowcast-item">${item.TimeDesc}｜降水强度：${item.IntensityDesc}</div>`
  ).join("");
}

/* ---------------- ⑦ 生活指数 ---------------- */
async function getIndices(){
  const idxList = await jget(`?action=index&lk=${locationKey}`);
  const box = document.getElementById("indexBox");
  if(!Array.isArray(idxList) || idxList.length === 0){
    box.innerHTML = `<div class="meta-item"><div class="meta-label">生活指数</div>
                     <div class="meta-val">暂无数据</div></div>`;
    return;
  }
  box.innerHTML = idxList.map(i =>
    `<div class="meta-item"><div class="meta-label">${i.Name}</div>
      <div class="meta-val">${i.Value} ${i.Category}</div></div>`
  ).join("");
}

/* ---------------- ⑧ 逐日预报 ---------------- */
async function loadDaily(dayNum){
  document.querySelectorAll(".tabs [data-day]").forEach(t=>t.classList.remove("active"));
  document.querySelector(`.tabs [data-day="${dayNum}"]`).classList.add("active");
  const json = await jget(`?action=dailyForecast&lk=${locationKey}&day=${dayNum}`);
  const days = json.DailyForecasts || [];
  const wrap = document.getElementById("dailyContainer");
  wrap.innerHTML = days.map(d=>{
    const t = new Date(d.Date);
    return `
    <div class="day-card">
      <div>${t.getMonth()+1}/${t.getDate()}</div>
      <div style="font-size:24px;margin:6px 0">
        ${d.Temperature.Maximum.Value}° / ${d.Temperature.Minimum.Value}°
      </div>
      <div style="font-size:13px">白天：${d.Day.IconPhrase}</div>
      <div style="font-size:12px">夜间：${d.Night.IconPhrase}</div>
      <div style="font-size:12px;opacity:0.8">降水 ${d.Day.PrecipitationProbability}%</div>
    </div>`;
  }).join("");
}

/* ---------------- ⑨ 逐小时预报 ---------------- */
async function loadHour(hourNum){
  document.querySelectorAll(".tabs [data-hour]").forEach(t=>t.classList.remove("active"));
  document.querySelector(`.tabs [data-hour="${hourNum}"]`).classList.add("active");
  const list = await jget(`?action=hourForecast&lk=${locationKey}&h=${hourNum}`);
  const wrap = document.getElementById("hourContainer");
  wrap.innerHTML = (list || []).map(h=>{
    const t = new Date(h.DateTime);
    return `
    <div class="hour-card">
      <div>${t.getHours()}:00</div>
      <div style="font-size:24px;margin:6px 0">${h.Temperature.Value}°</div>
      <div style="font-size:13px">${h.IconPhrase}</div>
      <div style="font-size:12px;opacity:0.8">降水 ${h.PrecipitationProbability}%</div>
    </div>`;
  }).join("");
}

/* ---------------- 汇总加载（8 项标准接口并行） ---------------- */
async function loadAllWeather(){
  document.getElementById("loadingBox").style.display = "block";
  document.getElementById("weatherPanel").classList.add("hide");

  // 使用 allSettled，任一接口失败不影响其他模块
  const results = await Promise.allSettled([
    getCurrent(),        // 实况天气
    getAlert(),          // 气象预警
    getAirQuality(),     // 空气质量
    getNowcast(),        // 分钟级降水
    getIndices(),        // 生活指数
    loadDaily(1),        // 逐日预报
    loadHour(24)         // 逐小时预报
  ]);

  const failed = results.filter(r => r.status === "rejected");
  if(failed.length){
    console.warn("以下模块加载失败：", failed.map(f=>f.reason?.message || f.reason));
  }

  document.getElementById("loadingBox").style.display = "none";
  document.getElementById("weatherPanel").classList.remove("hide");
}

/* ---------------- 页面载入自动定位 ---------------- */
window.onload = async ()=>{
  await gpsLocate();
};
</script>
</body>
</html>