知心天气API组件

知心天气API组件

成烁
2025-08-24 / 0 评论 / 5 阅读 / 正在检测是否收录...
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

评论 (0)

取消