d0ea62809e
··[CST 2026-04-25 Saturday 13:47:24]
205 lines
6.2 KiB
JavaScript
205 lines
6.2 KiB
JavaScript
// weather.js
|
||
|
||
(function () {
|
||
|
||
const WEATHER_ID = "aside-weather";
|
||
const LOCATION_WAIT_MS = 3000;
|
||
const LOCATION_POLL_INTERVAL_MS = 100;
|
||
const WEATHER_KEY = "70201debab4245379e376d116b05f8a2";
|
||
const WEATHER_HOST = "https://ny5vxmbbkd.re.qweatherapi.com";
|
||
const WEATHER_LANG = "zh";
|
||
|
||
function debugLog(label, payload) {
|
||
console.log(`[weather.js] ${label}`, payload);
|
||
}
|
||
|
||
function maskSecret(secret) {
|
||
if (!secret || secret.length < 10) return secret;
|
||
return `${secret.slice(0, 4)}***${secret.slice(-4)}`;
|
||
}
|
||
|
||
function readExistingLocation() {
|
||
return window.ipLocation?.result?.location || null;
|
||
}
|
||
|
||
function waitForExistingLocation(timeout = LOCATION_WAIT_MS) {
|
||
return new Promise((resolve, reject) => {
|
||
const existingLocation = readExistingLocation();
|
||
if (existingLocation) {
|
||
resolve(existingLocation);
|
||
return;
|
||
}
|
||
|
||
const startTime = Date.now();
|
||
const timer = window.setInterval(() => {
|
||
const location = readExistingLocation();
|
||
if (location) {
|
||
window.clearInterval(timer);
|
||
resolve(location);
|
||
return;
|
||
}
|
||
|
||
if (Date.now() - startTime >= timeout) {
|
||
window.clearInterval(timer);
|
||
reject(new Error("等待 ipLocation 超时"));
|
||
}
|
||
}, LOCATION_POLL_INTERVAL_MS);
|
||
});
|
||
}
|
||
|
||
function requestLocationFallback() {
|
||
return new Promise((resolve, reject) => {
|
||
if (!window.$?.ajax) {
|
||
reject(new Error("jQuery 未加载,无法执行定位兜底请求"));
|
||
return;
|
||
}
|
||
|
||
$.ajax({
|
||
type: 'get',
|
||
url: 'https://apis.map.qq.com/ws/location/v1/ip',
|
||
data: {
|
||
key: '你的key',
|
||
output: 'jsonp',
|
||
callback: '?',
|
||
},
|
||
dataType: 'jsonp',
|
||
success: function (res) {
|
||
debugLog("定位兜底返回", res);
|
||
if (res && res.status === 0 && res.result?.location) {
|
||
window.ipLocation = res;
|
||
resolve(res.result.location);
|
||
} else {
|
||
reject(new Error("定位失败"));
|
||
}
|
||
},
|
||
error: function () {
|
||
reject(new Error("定位请求失败"));
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
function getLocation() {
|
||
return waitForExistingLocation()
|
||
.then(location => {
|
||
debugLog("使用 txmap.js 定位", location);
|
||
return location;
|
||
})
|
||
.catch(error => {
|
||
debugLog("等待 txmap.js 定位失败,转兜底请求", error);
|
||
return requestLocationFallback();
|
||
});
|
||
}
|
||
|
||
|
||
function getWeather(lng, lat) {
|
||
const location = `${lng.toFixed(2)},${lat.toFixed(2)}`;
|
||
const params = new URLSearchParams({
|
||
location,
|
||
lang: WEATHER_LANG,
|
||
key: WEATHER_KEY
|
||
});
|
||
const requestUrl = `${WEATHER_HOST}/v7/weather/now?${params.toString()}`;
|
||
|
||
debugLog("天气请求参数", {
|
||
host: WEATHER_HOST,
|
||
location,
|
||
lang: WEATHER_LANG,
|
||
key: maskSecret(WEATHER_KEY)
|
||
});
|
||
|
||
return fetch(requestUrl, {
|
||
headers: {
|
||
Accept: "application/json"
|
||
}
|
||
}).then(async res => {
|
||
const responseText = await res.text();
|
||
let data;
|
||
|
||
try {
|
||
data = JSON.parse(responseText);
|
||
} catch (parseError) {
|
||
debugLog("天气接口返回非 JSON", {
|
||
status: res.status,
|
||
statusText: res.statusText,
|
||
body: responseText
|
||
});
|
||
throw new Error(`天气接口返回非 JSON,HTTP ${res.status}`);
|
||
}
|
||
|
||
debugLog("天气接口响应", {
|
||
status: res.status,
|
||
statusText: res.statusText,
|
||
body: data
|
||
});
|
||
|
||
if (!res.ok) {
|
||
const detail = data?.error?.detail || data?.msg || `HTTP ${res.status}`;
|
||
throw new Error(`天气接口请求失败:${detail}`);
|
||
}
|
||
|
||
return data;
|
||
});
|
||
}
|
||
|
||
|
||
function renderWeather(data) {
|
||
const el = document.getElementById(WEATHER_ID);
|
||
if (!el) return;
|
||
|
||
if (data.code !== "200") {
|
||
debugLog("天气接口业务错误", data);
|
||
el.innerHTML = `天气获取失败:${data.code || "未知错误"}`;
|
||
return;
|
||
}
|
||
|
||
const now = data.now;
|
||
|
||
const emojiMap = {
|
||
"晴": "☀️",
|
||
"多云": "⛅",
|
||
"阴": "☁️",
|
||
"雨": "🌧️",
|
||
"雪": "❄️",
|
||
"雷": "⛈️"
|
||
};
|
||
|
||
const emoji = emojiMap[now.text] || "🌡️";
|
||
const weatherSentence = `当前天气${now.text},气温${now.temp}℃,体感${now.feelsLike}℃,${now.windDir}${now.windScale}级,湿度${now.humidity}%,能见度${now.vis}km。`;
|
||
|
||
el.innerHTML = `
|
||
<div class="aside-weather-card">
|
||
<div class="weather-top">
|
||
</div>
|
||
<div class="weather-info">
|
||
${weatherSentence}
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
|
||
function initWeather() {
|
||
const el = document.getElementById(WEATHER_ID);
|
||
if (!el) return;
|
||
|
||
el.innerHTML = "🌥️ 获取天气中...";
|
||
|
||
getLocation()
|
||
.then(loc => getWeather(loc.lng, loc.lat))
|
||
.then(renderWeather)
|
||
.catch(err => {
|
||
console.error("[weather.js] 天气加载失败", err);
|
||
el.innerHTML = `🌧️ 天气加载失败:${err.message || "未知错误"}`;
|
||
});
|
||
}
|
||
|
||
|
||
// 🚀 初次加载
|
||
window.addEventListener("load", initWeather);
|
||
|
||
// 🔄 pjax
|
||
document.addEventListener("pjax:complete", initWeather);
|
||
|
||
})();
|