// ==UserScript== // @name JKCXSN-三角洲行动-主页天气 // @namespace https://scriptcat.org/ // @version 1.3 // @description 在主页右侧添加本地天气卡片(仅展示,无查询功能),优化图标文字间距 // @author sulang // @match https://www.jkcxsn.cn/ // @grant none // @run-at document-end // ==/UserScript== (function() { 'use strict'; // 和风天气配置(建议替换为自己的KEY) const API_KEY = "35459b2323b940b6b823a00efcac9330"; const CITY_LOOKUP_URL = "https://geoapi.qweather.com/v2/city/lookup"; const WEATHER_URL = "https://devapi.qweather.com/v7/weather/now"; // 天气图标映射 const WEATHER_ICON_MAP = { '100': 'fa-sun-o', '101': 'fa-cloud', '102': 'fa-cloud', '103': 'fa-cloud', '104': 'fa-cloud', '200': 'fa-bolt', '201': 'fa-bolt', '202': 'fa-bolt', '300': 'fa-tint', '301': 'fa-tint', '302': 'fa-tint', '303': 'fa-tint', '310': 'fa-snowflake-o', '311': 'fa-snowflake-o', '312': 'fa-snowflake-o', '313': 'fa-snowflake-o', '314': 'fa-tint', '400': 'fa-cloud', '401': 'fa-cloud', '402': 'fa-cloud', '406': 'fa-cloud', '407': 'fa-cloud', '408': 'fa-cloud', '409': 'fa-cloud', '500': 'fa-thermometer-half', '501': 'fa-thermometer-full', '999': 'fa-question' }; // 1. 创建天气卡片容器(统一图标文字间距) function createWeatherCard() { const card = document.createElement('div'); card.className = 'ui segment'; card.style.cssText = ` width: 100%; max-width: 320px; background: rgba(255,255,255,0.95); border-radius: 12px; padding: 16px; box-shadow: 0 4px 20px rgba(0,0,0,0.08); margin-top: 16px; `; card.innerHTML = `
本地天气
正在获取位置...
-Relink 技术
`; return card; } // 2. 插入到右侧栏 function insertCard() { // 兼容不同的页面结构 let rightColumn = document.querySelector('.ui.grid > .column:last-child'); if (!rightColumn) { rightColumn = document.querySelector('.right.column'); } if (!rightColumn) { // 如果找不到右侧栏,创建一个固定在页面右侧的容器 rightColumn = document.createElement('div'); rightColumn.style.cssText = ` position: fixed; top: 100px; right: 20px; z-index: 9999; `; document.body.appendChild(rightColumn); } const weatherCard = createWeatherCard(); rightColumn.insertBefore(weatherCard, rightColumn.firstChild); return weatherCard; } // 3. 安全的Fetch请求(增加超时和错误处理) async function safeFetch(url, timeout = 10000) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); try { const res = await fetch(url, { signal: controller.signal }); clearTimeout(timeoutId); // 检查响应状态 if (!res.ok) throw new Error(`请求失败 (${res.status})`); // 尝试解析JSON,失败则返回空 try { return await res.json(); } catch (e) { throw new Error('数据格式错误'); } } catch (e) { clearTimeout(timeoutId); if (e.name === 'AbortError') throw new Error('请求超时'); throw e; } } // 4. 获取地理位置 function getLocation() { return new Promise((resolve, reject) => { if (!navigator.geolocation) reject(new Error('浏览器不支持定位')); navigator.geolocation.getCurrentPosition( pos => resolve({ lat: pos.coords.latitude, lon: pos.coords.longitude }), err => { let msg = '获取位置失败'; if (err.code === 1) msg = '您拒绝了位置权限'; else if (err.code === 2) msg = '位置信息不可用'; else if (err.code === 3) msg = '获取位置超时'; reject(new Error(msg)); }, { timeout: 10000, enableHighAccuracy: false } ); }); } // 5. 根据经纬度获取城市ID async function getCityId(lon, lat) { const url = new URL(CITY_LOOKUP_URL); url.searchParams.append('key', API_KEY); url.searchParams.append('location', `${lon},${lat}`); url.searchParams.append('range', 'cn'); url.searchParams.append('lang', 'zh'); const data = await safeFetch(url); if (data.code !== '200' || !data.location?.length) throw new Error('未找到对应城市'); return data.location[0]; } // 6. 获取天气数据 async function getWeather(cityId) { const url = new URL(WEATHER_URL); url.searchParams.append('key', API_KEY); url.searchParams.append('location', cityId); const data = await safeFetch(url); if (data.code !== '200') throw new Error(`获取天气失败 (${data.code})`); return data; } // 7. 更新天气UI function updateUI(card, city, weather) { const loading = card.querySelector('#weather-loading'); const content = card.querySelector('#weather-content'); loading.style.display = 'none'; content.style.display = 'block'; // 格式化时间 const updateTime = weather.updateTime ? `${weather.updateTime.slice(0, 10)} ${weather.updateTime.slice(11, 16)}` : '未知时间'; // 填充数据(增加空值处理) card.querySelector('#weather-city').textContent = city.name || '未知城市'; card.querySelector('#weather-time').textContent = `更新于: ${updateTime}`; card.querySelector('#weather-temp').textContent = `${weather.now?.temp || '--'}℃`; card.querySelector('#weather-text').textContent = weather.now?.text || '未知'; card.querySelector('#weather-feels').textContent = `${weather.now?.feelsLike || '--'}℃`; card.querySelector('#weather-windDir').textContent = weather.now?.windDir || '未知'; card.querySelector('#weather-wind').textContent = `${weather.now?.windScale || '--'}级 / ${weather.now?.windSpeed || '--'}km/h`; card.querySelector('#weather-humidity').textContent = `${weather.now?.humidity || '--'}%`; // 设置图标 const icon = WEATHER_ICON_MAP[weather.now?.icon] || 'fa-question'; card.querySelector('#weather-icon').innerHTML = ``; } // 8. 显示错误 function showError(card, msg) { const loading = card.querySelector('#weather-loading'); const error = card.querySelector('#weather-error'); loading.style.display = 'none'; error.style.display = 'block'; card.querySelector('#weather-error-msg').textContent = msg; } // 9. 主流程(增加重试机制) async function init() { const card = insertCard(); if (!card) return; let retryCount = 0; const maxRetry = 2; while (retryCount < maxRetry) { try { const { lon, lat } = await getLocation(); card.querySelector('#weather-loading').innerHTML = '正在获取城市...'; const city = await getCityId(lon, lat); card.querySelector('#weather-loading').innerHTML = '正在获取天气...'; const weather = await getWeather(city.id); updateUI(card, city, weather); return; // 成功则退出 } catch (err) { retryCount++; if (retryCount >= maxRetry) { showError(card, err.message); return; } // 重试前短暂延迟 await new Promise(resolve => setTimeout(resolve, 1000)); } } } // 引入Font Awesome(如果页面已有则忽略) if (!document.querySelector('link[href*="font-awesome"]')) { const fa = document.createElement('link'); fa.rel = 'stylesheet'; fa.href = 'https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css'; document.head.appendChild(fa); } // 延迟执行,确保页面加载完成 setTimeout(init, 800); })();