AI智能摘要
摘要:本文介绍了一个名为“zhixin-weather-widget”的天气组件,该组件允许用户通过知心天气API获取今日天气信息。虽然joe主题自带了和风天气的天气组件,但由于其API需要付费,而知心天气提供免费服务,因此作者决定自行开发此组件。该组件包括地区选择器、省份列表和城市列表,以及一个输入框让用户输入城市名称。此外,还包含两个按钮用于确认和取消操作。整个组件的效果可以在电脑端显示,且可以通过设置页面的自定义侧边栏模块进行配置。
此内容由AI生成
joe主题自带一个今日天气的组件,但是调用的是和风天气的API,很抱歉,和风天气的API需要付费,但是知心天气有免费的啊,修改主题代码的话,还是算了吧,所以通过前端代码做了这个组件,效果图请看右侧,需要在电脑端才显示。
需要把下面的代码复制下来,粘贴到设置页面的“自定义侧边栏模块 -PC”即可。注意把从知心天气获得的密钥复制到第398行,默认地区我设置了北京,你也可以自行更改,或者通过高德地图等的ip定位API获取实时位置,这个自行配置吧。
<!-- 知心天气独立组件 - 带地区选择功能 -->
<div id="zhixin-weather-widget" class="zhixin-weather-widget">
<!-- 地区选择器 -->
<div class="region-selector" style="display: none; opacity: 0; transform: translateY(10px); transition: opacity 0.3s ease, transform 0.3s ease;">
<div class="region-selector-header">
<button class="back-btn">↩</button>
<div class="region-title">选择城市</div>
</div>
<div class="region-tabs">
<button class="tab-btn active" data-tab="provinces">省份</button>
<button class="tab-btn" data-tab="cities">城市</button>
</div>
<div class="region-content">
<div class="provinces-list">
<!-- 省份列表将动态填充 -->
</div>
<div class="cities-list" style="display: none;">
<!-- 城市列表将动态填充 -->
</div>
</div>
</div>
<div class="city-selector" style="display: none; opacity: 0; transform: translateY(10px); transition: opacity 0.3s ease, transform 0.3s ease;">
<input type="text" id="city-input" placeholder="输入城市名称">
<button id="confirm-city">确认</button>
<button id="cancel-city">取消</button>
</div>
<div class="weather-loading">
<div style="text-align: center; padding: 20px; color: #666;">
<div style="font-size: 24px; margin-bottom: 8px;">🌤️</div>
<div>正在加载天气数据...</div>
</div>
</div>
<div class="weather-content" style="display: none; opacity: 0; transform: translateY(10px); transition: opacity 0.3s ease, transform 0.3s ease;">
<!-- 天气内容将通过JavaScript动态填充 -->
</div>
<div class="weather-error" style="display: none; opacity: 0; transform: translateY(10px); transition: opacity 0.3s ease, transform 0.3s ease;">
<div style="text-align: center; padding: 20px; color: #e74c3c;">
<div style="font-size: 24px; margin-bottom: 8px;">❌</div>
<div class="error-message">加载失败</div>
<div style="font-size: 12px; color: #999; margin-top: 8px;">
请检查API密钥或网络连接
</div>
</div>
</div>
</div>
<style>
.zhixin-weather-widget {
padding: 15px;
margin-bottom: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: var(--main, #333);
background: var(--background, #f8f9fa);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
max-width: 320px;
margin-left: auto;
margin-right: auto;
}
/* 地区选择器样式 */
.region-selector {
position: relative;
background: var(--background, #f8f9fa);
border-radius: 8px;
padding: 10px;
margin-bottom: 15px;
}
.region-selector-header {
display: flex;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid var(--classC, #e9ecef);
}
.back-btn {
background: transparent;
border: none;
cursor: pointer;
font-size: 18px;
padding: 4px 8px;
margin-right: 10px;
transition: transform 0.2s ease;
}
.back-btn:hover {
transform: scale(1.1);
}
.region-title {
font-size: 16px;
font-weight: 600;
flex: 1;
text-align: center;
}
.region-tabs {
display: flex;
margin-bottom: 10px;
}
.tab-btn {
flex: 1;
padding: 8px;
background: transparent;
border: 1px solid var(--classC, #e9ecef);
cursor: pointer;
transition: all 0.2s ease;
}
.tab-btn:first-child {
border-radius: 4px 0 0 4px;
}
.tab-btn:last-child {
border-radius: 0 4px 4px 0;
}
.tab-btn.active {
background: var(--theme, #007bff);
color: white;
border-color: var(--theme, #007bff);
}
.region-content {
max-height: 280px;
overflow-y: auto;
overflow-x: hidden;
}
.provinces-list, .cities-list {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.region-item {
padding: 10px 8px;
text-align: center;
background: transparent;
border: 1px solid var(--classC, #e9ecef);
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
font-size: 13px;
}
.region-item:hover {
background: var(--theme-light, #e6f0ff);
border-color: var(--theme, #007bff);
transform: scale(1.05);
}
/* 城市搜索框样式 */
.city-selector {
display: flex;
gap: 8px;
margin-bottom: 15px;
padding: 10px;
background: var(--background, #f8f9fa);
border-radius: 8px;
border: 1px solid var(--classC, #e9ecef);
}
#city-input {
flex: 1;
padding: 6px 10px;
border: 1px solid var(--classC, #e9ecef);
border-radius: 4px;
font-size: 14px;
}
.city-selector button {
padding: 6px 12px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
#confirm-city {
background: var(--theme, #007bff);
color: white;
}
#cancel-city {
background: var(--routine, #999);
color: white;
}
/* 天气内容样式 */
.weather-current {
text-align: center;
margin-bottom: 12px;
padding: 12px;
background: linear-gradient(135deg, #68b2f8 0%, #2b7de0 100%);
border-radius: 12px;
color: white;
}
.weather-icon {
font-size: 40px;
margin-bottom: 5px;
}
.weather-temp {
font-size: 32px;
font-weight: bold;
margin-bottom: 2px;
}
.weather-desc {
font-size: 15px;
margin-bottom: 6px;
opacity: 0.9;
}
.weather-location {
font-size: 13px;
opacity: 0.8;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
cursor: pointer;
text-decoration: none !important;
}
.weather-location:hover {
text-decoration: none;
}
/* 地理位置图标样式 */
.location-icon {
width: 15px;
height: 15px;
fill: currentColor;
}
.weather-forecast {
display: flex;
justify-content: space-between;
margin-top: 12px;
overflow-x: auto;
padding-bottom: 6px;
}
/* 天气预报小卡片样式 - 重点修复间距问题 */
.forecast-item {
min-width: 60px;
text-align: center;
padding: 8px 4px;
background: transparent;
border-radius: 8px;
margin: 0 3px;
border: 1px solid var(--classC, #e9ecef);
cursor: pointer;
transition: all 0.3s ease;
height: 100px; /* 足够的高度确保间距 */
display: flex;
flex-direction: column;
justify-content: flex-start;
position: relative;
}
.forecast-day {
font-size: 11px;
color: var(--minor, #666);
margin-bottom: 6px;
}
.forecast-main {
transition: opacity 0.3s ease, transform 0.3s ease;
margin-bottom: 20px; /* 强制与底部保持距离 */
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.forecast-icon {
font-size: 20px;
margin-bottom: 8px;
}
.forecast-temp {
font-size: 12px;
color: var(--main, #333);
margin-bottom: 12px; /* 温度文字底部额外间距 */
}
.forecast-details {
font-size: 10px;
color: var(--minor, #666);
opacity: 0;
height: 40px;
position: absolute;
top: 50%;
left: 0;
right: 0;
transform: translateY(-50%);
transition: opacity 0.3s ease, transform 0.3s ease;
display: flex;
flex-direction: column;
justify-content: center;
}
/* 鼠标悬停效果 */
.forecast-item:hover .forecast-main {
opacity: 0;
transform: translateY(-10px);
}
.forecast-item:hover .forecast-details {
opacity: 1;
transform: translateY(-50%);
}
.last-updated {
text-align: center;
font-size: 10px;
color: var(--routine, #999);
margin-top: 8px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.zhixin-weather-widget {
padding: 10px;
max-width: 100%;
}
.weather-temp {
font-size: 26px;
}
.weather-icon {
font-size: 34px;
}
.forecast-item {
height: 90px;
padding: 5px 2px;
min-width: 50px;
}
}
/* 深色主题支持 */
@media (prefers-color-scheme: dark) {
.zhixin-weather-widget {
color: var(--main, #fff);
background: var(--background, #1a1a1a);
}
.weather-current {
background: linear-gradient(135deg, #3a6ea5 0%, #1e4a7a 100%);
}
.region-selector {
background: var(--background, #1a1a1a);
}
.tab-btn {
border-color: #444;
}
.region-item {
border-color: #444;
}
.region-item:hover {
background: #2a2a2a;
border-color: #3a6ea5;
}
.forecast-item {
border-color: #444;
}
.forecast-item:hover {
background: #2a2a2a;
border-color: #3a6ea5;
}
}
</style>
<script>
(function() {
// 配置区域 - 用户需要补充API密钥
const CONFIG = {
API_KEY: '', // 请在此处填入您的知心天气API密钥,获取地址:https://www.seniverse.com/
DEFAULT_CITY: '北京', // 默认城市
CACHE_DURATION: 10 * 60 * 1000, // 缓存时间10分钟
STYLE: 'auto' // 主题样式:auto/light/dark
};
// 省份和城市数据
const REGIONS = {
"北京": ["北京"],
"天津": ["天津"],
"河北": ["石家庄", "唐山", "秦皇岛", "邯郸", "邢台", "保定", "张家口", "承德", "沧州", "廊坊", "衡水"],
"山西": ["太原", "大同", "阳泉", "长治", "晋城", "朔州", "晋中", "运城", "忻州", "临汾", "吕梁"],
"内蒙古": ["呼和浩特", "包头", "乌海", "赤峰", "通辽", "鄂尔多斯", "呼伦贝尔", "巴彦淖尔", "乌兰察布", "兴安盟", "锡林郭勒盟", "阿拉善盟"],
"辽宁": ["沈阳", "大连", "鞍山", "抚顺", "本溪", "丹东", "锦州", "营口", "阜新", "辽阳", "盘锦", "铁岭", "朝阳", "葫芦岛"],
"吉林": ["长春", "吉林", "四平", "辽源", "通化", "白山", "松原", "白城", "延边朝鲜族自治州"],
"黑龙江": ["哈尔滨", "齐齐哈尔", "鸡西", "鹤岗", "双鸭山", "大庆", "伊春", "佳木斯", "七台河", "牡丹江", "黑河", "绥化", "大兴安岭地区"],
"上海": ["上海"],
"江苏": ["南京", "无锡", "徐州", "常州", "苏州", "南通", "连云港", "淮安", "盐城", "扬州", "镇江", "泰州", "宿迁"],
"浙江": ["杭州", "宁波", "温州", "嘉兴", "湖州", "绍兴", "金华", "衢州", "舟山", "台州", "丽水"],
"安徽": ["合肥", "芜湖", "蚌埠", "淮南", "马鞍山", "淮北", "铜陵", "安庆", "黄山", "滁州", "阜阳", "宿州", "巢湖", "六安", "亳州", "池州", "宣城"],
"福建": ["福州", "厦门", "莆田", "三明", "泉州", "漳州", "南平", "龙岩", "宁德"],
"江西": ["南昌", "景德镇", "萍乡", "九江", "新余", "鹰潭", "赣州", "吉安", "宜春", "抚州", "上饶"],
"山东": ["济南", "青岛", "淄博", "枣庄", "东营", "烟台", "潍坊", "济宁", "泰安", "威海", "日照", "莱芜", "临沂", "德州", "聊城", "滨州", "菏泽"],
"河南": ["郑州", "开封", "洛阳", "平顶山", "安阳", "鹤壁", "新乡", "焦作", "濮阳", "许昌", "漯河", "三门峡", "南阳", "商丘", "信阳", "周口", "驻马店"],
"湖北": ["武汉", "黄石", "十堰", "宜昌", "襄樊", "鄂州", "荆门", "孝感", "荆州", "黄冈", "咸宁", "随州", "恩施土家族苗族自治州", "仙桃", "潜江", "天门"],
"湖南": ["长沙", "株洲", "湘潭", "衡阳", "邵阳", "岳阳", "常德", "张家界", "益阳", "郴州", "永州", "怀化", "娄底", "湘西土家族苗族自治州"],
"广东": ["广州", "深圳", "珠海", "汕头", "韶关", "佛山", "江门", "湛江", "茂名", "肇庆", "惠州", "梅州", "汕尾", "河源", "阳江", "清远", "东莞", "中山", "潮州", "揭阳", "云浮"],
"广西": ["南宁", "柳州", "桂林", "梧州", "北海", "防城港", "钦州", "贵港", "玉林", "百色", "贺州", "河池", "来宾", "崇左"],
"海南": ["海口", "三亚", "五指山", "琼海", "儋州", "文昌", "万宁", "东方"],
"重庆": ["重庆"],
"四川": ["成都", "自贡", "攀枝花", "泸州", "德阳", "绵阳", "广元", "遂宁", "内江", "乐山", "南充", "眉山", "宜宾", "广安", "达州", "雅安", "巴中", "资阳", "阿坝藏族羌族自治州", "甘孜藏族自治州", "凉山彝族自治州"],
"贵州": ["贵阳", "六盘水", "遵义", "安顺", "铜仁地区", "黔西南布依族苗族自治州", "毕节地区", "黔东南苗族侗族自治州", "黔南布依族苗族自治州"],
"云南": ["昆明", "曲靖", "玉溪", "保山", "昭通", "丽江", "普洱", "临沧", "楚雄彝族自治州", "红河哈尼族彝族自治州", "文山壮族苗族自治州", "西双版纳傣族自治州", "大理白族自治州", "德宏傣族景颇族自治州", "怒江傈僳族自治州", "迪庆藏族自治州"],
"西藏": ["拉萨", "昌都地区", "山南地区", "日喀则地区", "那曲地区", "阿里地区", "林芝地区"],
"陕西": ["西安", "铜川", "宝鸡", "咸阳", "渭南", "延安", "汉中", "榆林", "安康", "商洛"],
"甘肃": ["兰州", "嘉峪关", "金昌", "白银", "天水", "武威", "张掖", "平凉", "酒泉", "庆阳", "定西", "陇南", "临夏回族自治州", "甘南藏族自治州"],
"青海": ["西宁", "海东地区", "海北藏族自治州", "黄南藏族自治州", "海南藏族自治州", "果洛藏族自治州", "玉树藏族自治州", "海西蒙古族藏族自治州"],
"宁夏": ["银川", "石嘴山", "吴忠", "固原", "中卫"],
"新疆": ["乌鲁木齐", "克拉玛依", "吐鲁番地区", "哈密地区", "昌吉回族自治州", "博尔塔拉蒙古自治州", "巴音郭楞蒙古自治州", "阿克苏地区", "克孜勒苏柯尔克孜自治州", "喀什地区", "和田地区", "伊犁哈萨克自治州", "塔城地区", "阿勒泰地区", "石河子", "阿拉尔", "图木舒克", "五家渠"]
};
// 状态管理
let state = {
unit: 'c', // 温度单位:c或f
currentCity: '',
selectedProvince: '',
lastUpdated: null,
expandedForecast: null
};
// 如果API密钥为空,显示配置提示
if (!CONFIG.API_KEY) {
const widget = document.getElementById('zhixin-weather-widget');
widget.innerHTML = `
<div style="text-align: center; padding: 20px; color: #e74c3c;">
<div style="font-size: 24px; margin-bottom: 8px;">⚠️</div>
<div style="margin-bottom: 8px;">请配置知心天气API密钥</div>
<div style="font-size: 12px; color: #999;">
在上面的代码中找到 API_KEY 并填入您的密钥<br>
获取密钥:<a href="https://www.seniverse.com/" target="_blank" style="color: #007bff;">知心天气官网</a>
</div>
</div>
`;
return;
}
// 天气图标映射
const WEATHER_ICONS = {
'0': '☀️', '1': '🌤️', '2': '☁️', '3': '☁️', '4': '🌤️',
'5': '🌫️', '6': '🌫️', '7': '💨', '8': '🌀', '9': '⛈️',
'10': '⛈️', '11': '🌦️', '12': '🌧️', '13': '🌧️', '14': '⛈️',
'15': '⛈️', '16': '⛈️', '17': '🌦️', '18': '🌧️', '19': '🌨️',
'20': '❄️', '21': '❄️', '22': '❄️', '23': '🌨️', '24': '🌨️',
'25': '🌨️', '26': '🌤️', '30': '🌫️', '31': '🌫️', '32': '🌫️',
'33': '🌫️', '99': '❓'
};
// DOM元素引用
const elements = {
widget: document.getElementById('zhixin-weather-widget'),
loading: document.querySelector('.weather-loading'),
content: document.querySelector('.weather-content'),
error: document.querySelector('.weather-error'),
regionSelector: document.querySelector('.region-selector'),
provincesList: document.querySelector('.provinces-list'),
citiesList: document.querySelector('.cities-list'),
tabBtns: document.querySelectorAll('.tab-btn'),
backBtn: document.querySelector('.back-btn'),
citySelector: document.querySelector('.city-selector'),
cityInput: document.getElementById('city-input'),
confirmCity: document.getElementById('confirm-city'),
cancelCity: document.getElementById('cancel-city'),
weatherLocation: null
};
// 初始化事件监听
function initEventListeners() {
// 地区选择器相关
elements.tabBtns.forEach(btn => {
btn.addEventListener('click', () => {
elements.tabBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const tab = btn.getAttribute('data-tab');
if (tab === 'provinces') {
elements.provincesList.style.display = 'grid';
elements.citiesList.style.display = 'none';
} else {
elements.provincesList.style.display = 'none';
elements.citiesList.style.display = 'grid';
}
const activeList = tab === 'provinces' ? elements.provincesList : elements.citiesList;
activeList.style.opacity = '0';
setTimeout(() => {
activeList.style.opacity = '1';
}, 50);
});
});
elements.backBtn.addEventListener('click', hideRegionSelector);
// 城市搜索相关
elements.confirmCity.addEventListener('click', confirmCityChange);
elements.cancelCity.addEventListener('click', hideCitySelector);
// 回车键确认城市输入
elements.cityInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
confirmCityChange();
}
});
// 渲染省份列表
renderProvincesList();
}
// 渲染省份列表
function renderProvincesList() {
elements.provincesList.innerHTML = '';
Object.keys(REGIONS).forEach(province => {
const provinceEl = document.createElement('div');
provinceEl.className = 'region-item';
provinceEl.textContent = province;
provinceEl.addEventListener('click', () => {
selectProvince(province);
});
elements.provincesList.appendChild(provinceEl);
});
}
// 选择省份
function selectProvince(province) {
state.selectedProvince = province;
renderCitiesList(province);
// 切换到城市标签
elements.tabBtns.forEach(b => b.classList.remove('active'));
elements.tabBtns[1].classList.add('active');
elements.provincesList.style.display = 'none';
elements.citiesList.style.display = 'grid';
// 添加城市列表显示动效
elements.citiesList.style.opacity = '0';
setTimeout(() => {
elements.citiesList.style.opacity = '1';
}, 50);
}
// 渲染城市列表
function renderCitiesList(province) {
elements.citiesList.innerHTML = '';
const cities = REGIONS[province] || [];
cities.forEach(city => {
const cityEl = document.createElement('div');
cityEl.className = 'region-item';
cityEl.textContent = city;
cityEl.addEventListener('click', () => {
selectCity(city);
});
elements.citiesList.appendChild(cityEl);
});
}
// 选择城市
function selectCity(city) {
state.currentCity = city;
// 添加城市选择后关闭动效
elements.regionSelector.style.opacity = '0';
elements.regionSelector.style.transform = 'translateY(10px)';
setTimeout(() => {
hideRegionSelector();
// 保存城市偏好
localStorage.setItem('weather_city', city);
// 加载新城市的天气数据
loadWeatherData();
}, 300);
}
// 显示地区选择器
function showRegionSelector() {
elements.regionSelector.style.display = 'block';
elements.citySelector.style.display = 'none';
elements.content.style.display = 'none';
// 添加显示动效
setTimeout(() => {
elements.regionSelector.style.opacity = '1';
elements.regionSelector.style.transform = 'translateY(0)';
}, 10);
}
// 隐藏地区选择器并显示首页动画
function hideRegionSelector() {
elements.regionSelector.style.opacity = '0';
elements.regionSelector.style.transform = 'translateY(10px)';
setTimeout(() => {
elements.regionSelector.style.display = 'none';
if (state.currentCity) {
// 显示首页并添加淡入动画
elements.content.style.display = 'block';
setTimeout(() => {
elements.content.style.opacity = '1';
elements.content.style.transform = 'translateY(0)';
}, 10);
}
}, 300);
}
// 显示城市选择器
function showCitySelector() {
elements.citySelector.style.display = 'flex';
elements.regionSelector.style.display = 'none';
elements.content.style.display = 'none';
elements.cityInput.value = state.currentCity;
elements.cityInput.focus();
// 添加显示动效
setTimeout(() => {
elements.citySelector.style.opacity = '1';
elements.citySelector.style.transform = 'translateY(0)';
}, 10);
}
// 隐藏城市选择器并显示首页动画
function hideCitySelector() {
elements.citySelector.style.opacity = '0';
elements.citySelector.style.transform = 'translateY(10px)';
setTimeout(() => {
elements.citySelector.style.display = 'none';
if (state.currentCity) {
// 显示首页并添加淡入动画
elements.content.style.display = 'block';
setTimeout(() => {
elements.content.style.opacity = '1';
elements.content.style.transform = 'translateY(0)';
}, 10);
}
}, 300);
}
// 确认城市更改
function confirmCityChange() {
const newCity = elements.cityInput.value.trim();
if (newCity) {
state.currentCity = newCity;
hideCitySelector();
// 保存城市偏好
localStorage.setItem('weather_city', newCity);
// 加载新城市的天气数据
loadWeatherData();
}
}
// 获取天气图标
function getWeatherIcon(code) {
return WEATHER_ICONS[code] || '❓';
}
// 获取星期名称
function getDayName(dateString) {
const date = new Date(dateString);
const days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
return days[date.getDay()];
}
// 温度转换
function convertTemperature(temp, unit) {
if (unit === 'f') {
return Math.round((temp * 9/5) + 32);
}
return temp;
}
// 获取温度显示
function getTemperatureDisplay(temp) {
return `${convertTemperature(temp, state.unit)}°${state.unit.toUpperCase()}`;
}
// 获取缓存数据
function getCachedWeatherData() {
try {
const cached = localStorage.getItem(`weather_${state.currentCity}`);
if (!cached) return null;
const data = JSON.parse(cached);
const now = new Date().getTime();
// 检查缓存是否过期
if (now - data.timestamp < CONFIG.CACHE_DURATION) {
return data;
}
} catch (e) {
console.error('读取缓存失败', e);
}
return null;
}
// 保存数据到缓存
function saveToCache(data) {
try {
data.timestamp = new Date().getTime();
localStorage.setItem(`weather_${state.currentCity}`, JSON.stringify(data));
} catch (e) {
console.error('保存缓存失败', e);
}
}
// 获取位置
async function getLocationByIP() {
// 尝试从本地存储获取城市偏好
const savedCity = localStorage.getItem('weather_city');
if (savedCity) {
console.log(`使用保存的城市: ${savedCity}`);
return savedCity;
}
// 使用默认城市
console.log(`使用默认城市: ${CONFIG.DEFAULT_CITY}`);
return CONFIG.DEFAULT_CITY;
}
// 获取天气数据
async function fetchWeatherData(location) {
const baseUrl = 'https://api.seniverse.com/v3/weather';
console.log(`🌡️ 正在获取 ${location} 的天气数据...`);
try {
// 获取实时天气和预报
const [currentResponse, forecastResponse] = await Promise.all([
fetch(`${baseUrl}/now.json?key=${encodeURIComponent(CONFIG.API_KEY)}&location=${encodeURIComponent(location)}&language=zh-Hans&unit=c`),
fetch(`${baseUrl}/daily.json?key=${encodeURIComponent(CONFIG.API_KEY)}&location=${encodeURIComponent(location)}&language=zh-Hans&unit=c&start=0&days=5`)
]);
if (!currentResponse.ok || !forecastResponse.ok) {
throw new Error(`HTTP错误: ${currentResponse.status} ${forecastResponse.status}`);
}
const [currentData, forecastData] = await Promise.all([
currentResponse.json(),
forecastResponse.json()
]);
// 检查API响应状态
if (currentData.status_code && currentData.status_code !== 0) {
const errorMap = {
'AP010001': 'API密钥无效',
'AP010003': 'API密钥已过期',
'AP010012': 'API调用次数已用完',
'AP100002': '请求过于频繁',
'AP100003': '位置信息无效'
};
const errorMsg = errorMap[currentData.status_code] || currentData.status || `API错误 ${currentData.status_code}`;
throw new Error(errorMsg);
}
if (!currentData.results || !currentData.results[0]) {
throw new Error('无法获取天气数据,请检查城市名称');
}
return {
current: currentData.results[0],
forecast: forecastData.results[0].daily
};
} catch (error) {
console.error('❌ 天气数据获取失败:', error);
throw error;
}
}
// 渲染天气组件
function renderWeather(data) {
const current = data.current;
const forecast = data.forecast;
// 地理位置SVG图标
const locationSvg = `
<svg class="location-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0-6C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/>
</svg>
`;
let html = `
<div class="weather-current">
<div class="weather-icon">${getWeatherIcon(current.now.code)}</div>
<div class="weather-temp">${getTemperatureDisplay(current.now.temperature)}</div>
<div class="weather-desc">${current.now.text}</div>
<div class="weather-location" id="weather-location">
${locationSvg}
${current.location.name}
</div>
</div>
`;
// 添加天气预报
if (forecast && forecast.length > 0) {
html += '<div class="weather-forecast">';
for (let i = 0; i < Math.min(5, forecast.length); i++) {
const item = forecast[i];
html += `
<div class="forecast-item" data-index="${i}">
<div class="forecast-day">${i === 0 ? '今天' : getDayName(item.date)}</div>
<div class="forecast-main">
<div class="forecast-icon">${getWeatherIcon(item.code_day)}</div>
<div class="forecast-temp">${getTemperatureDisplay(item.low)}/${getTemperatureDisplay(item.high)}</div>
</div>
<div class="forecast-details">
<div>白天: ${item.text_day}</div>
<div>夜间: ${item.text_night}</div>
</div>
</div>
`;
}
html += '</div>';
}
// 添加最后更新时间
const updatedTime = new Date().toLocaleTimeString();
html += `<div class="last-updated">最后更新: ${updatedTime}</div>`;
elements.content.innerHTML = html;
// 设置城市位置元素并添加点击事件
elements.weatherLocation = document.getElementById('weather-location');
elements.weatherLocation.addEventListener('click', (e) => {
e.stopPropagation();
// 隐藏首页时添加淡出动画
elements.content.style.opacity = '0';
elements.content.style.transform = 'translateY(10px)';
setTimeout(showRegionSelector, 300);
});
}
// 显示错误信息
function showError(message) {
elements.loading.style.display = 'none';
elements.content.style.display = 'none';
elements.regionSelector.style.display = 'none';
elements.error.style.display = 'block';
elements.error.querySelector('.error-message').textContent = message;
// 添加错误显示动画
setTimeout(() => {
elements.error.style.opacity = '1';
elements.error.style.transform = 'translateY(0)';
}, 10);
}
// 显示天气内容
function showWeatherContent() {
elements.loading.style.display = 'none';
elements.error.style.display = 'none';
elements.regionSelector.style.display = 'none';
elements.content.style.display = 'block';
// 添加显示动画
setTimeout(() => {
elements.content.style.opacity = '1';
elements.content.style.transform = 'translateY(0)';
}, 10);
}
// 显示加载状态
function showLoading() {
elements.content.style.display = 'none';
elements.error.style.display = 'none';
elements.regionSelector.style.display = 'none';
elements.loading.style.display = 'block';
}
// 加载天气数据
async function loadWeatherData() {
showLoading();
try {
// 如果没有当前城市,尝试获取
if (!state.currentCity) {
state.currentCity = await getLocationByIP();
}
// 检查是否有缓存数据
const cachedData = getCachedWeatherData();
if (cachedData) {
console.log('使用缓存数据');
renderWeather(cachedData);
showWeatherContent();
return;
}
console.log('从API获取新数据');
const weatherData = await fetchWeatherData(state.currentCity);
saveToCache(weatherData);
renderWeather(weatherData);
showWeatherContent();
} catch (error) {
console.error('❌ 天气组件加载失败:', error);
let errorMessage = '天气数据加载失败';
if (error.message.includes('API密钥无效') || error.message.includes('401')) {
errorMessage = 'API密钥无效或已过期';
} else if (error.message.includes('API调用次数') || error.message.includes('403')) {
errorMessage = 'API调用配额已用完';
} else if (error.message.includes('请求过于频繁') || error.message.includes('429')) {
errorMessage = '请求过于频繁,请稍后重试';
} else if (error.message.includes('位置信息无效')) {
errorMessage = '无法识别您的位置,请手动设置城市';
} else if (error.message.includes('Failed to fetch') || error.message.includes('网络')) {
errorMessage = '网络连接失败,请检查网络连接';
}
showError(errorMessage);
}
}
// 初始化组件
function initWeatherWidget() {
initEventListeners();
loadWeatherData();
}
// 当DOM加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initWeatherWidget);
} else {
initWeatherWidget();
}
})();
</script>
评论 (0)