2025/12/24 每日总结 天气预报应用 - APISpace

一、准备工作

在开始编码前,我们需要先准备好核心依赖和资源:

1. 技术栈与资源

  • 基础技术:原生 HTML/CSS/JavaScript(无框架,聚焦核心逻辑)
  • UI 增强:Font Awesome 图标库(提供天气相关图标)
  • API 服务:APISpace 天气预报 API(支持获取实时天气、未来预报等数据)

2. API 前期配置(关键步骤)

天气预报应用的核心是真实的天气数据,因此第一步要完成 API 的申请和配置:

  1. 注册 APISpace 账号,进入「天气系列 API」模块,选择合适的天气预报接口(比如“全国天气预报查询”);
  2. 申请接口的使用权限,获取专属的 API Key(接口调用的身份凭证);
  3. 查看接口文档,确认请求方式(POST/GET)、请求参数(如城市名、城市编码)、返回数据格式(JSON)。

提示:APISpace 提供免费调用额度,足够开发和测试使用;接口文档会明确标注请求头、参数说明,这是后续对接的关键。

二、核心实现步骤

1. 页面 UI 搭建:打造高颜值界面

先完成页面的结构和样式,核心目标是美观、响应式、符合用户体验:

(1)页面结构设计

整体分为 5 个核心模块:

  • 头部:应用标题和说明
  • 搜索框:输入城市名查询天气
  • 加载/错误提示:交互反馈
  • 天气卡片:展示实时天气、天气指数(体感温度、湿度、风速等)
  • 未来预报栏:展示多日天气预报

(2)关键样式技巧

  • 渐变背景+毛玻璃效果:通过 linear-gradient 设置渐变背景,结合 backdrop-filter: blur() 实现卡片的毛玻璃质感,提升视觉层次;
  • 响应式布局:使用 CSS Grid 和 Flex 布局,配合媒体查询适配移动端;
  • 交互动效:卡片 hover 上移、按钮 hover 变色,增强交互体验。

2. API 对接:从模拟到真实数据

代码中最初用 setTimeout 模拟 API 调用,实际开发中需要替换为真实的 API 请求,这是核心环节:

(1)API 调用逻辑(替换模拟代码)

// 替换原有的模拟 getWeatherData 函数
function getWeatherData(city) {
    loader.style.display = 'block';
    errorMessage.style.display = 'none';

    // 真实 API 请求配置
    const apiUrl = 'https://eolink.o.apispace.com/weather/v001/weather'; // 接口地址(以APISpace为例)
    const headers = {
        'X-APISpace-Token': '你的API Key', // 替换为自己的API Key
        'Content-Type': 'application/x-www-form-urlencoded'
    };
    const params = new URLSearchParams({
        city: city, // 要查询的城市名
        days: '7' // 获取7天预报
    });

    // 发起 API 请求
    fetch(apiUrl, {
        method: 'POST',
        headers: headers,
        body: params
    })
    .then(response => {
        if (!response.ok) throw new Error('请求失败');
        return response.json();
    })
    .then(data => {
        // 解析API返回的真实数据(需根据接口文档调整字段)
        const weatherData = {
            location: data.city,
            temperature: data.now.temp,
            condition: data.now.weather,
            feelsLike: data.now.feels_like,
            humidity: data.now.humidity,
            windSpeed: data.now.wind_speed,
            pressure: data.now.pressure,
            uvIndex: data.now.uv_index,
            visibility: data.now.visibility,
            airQuality: data.now.air_quality,
            clothing: getClothingAdvice(data.now.temp) // 自定义穿衣建议逻辑
        };
        updateWeatherUI(weatherData);
        // 更新未来预报数据
        updateForecast(data.forecast);
        loader.style.display = 'none';
    })
    .catch(error => {
        loader.style.display = 'none';
        errorMessage.style.display = 'block';
        errorMessage.querySelector('p').textContent = `获取数据失败:${error.message}`;
        console.error('API请求错误:', error);
    });
}

// 新增:更新未来预报
function updateForecast(forecastData) {
    const forecastItems = document.querySelectorAll('.forecast-item');
    forecastData.forEach((item, index) => {
        if (index < forecastItems.length) {
            forecastItems[index].querySelector('.forecast-date').textContent = item.date;
            forecastItems[index].querySelector('.forecast-temp').textContent = `${item.temp_high}°C`;
            forecastItems[index].querySelector('.forecast-condition').textContent = item.weather;
            // 更新预报图标
            forecastItems[index].querySelector('i').className = getWeatherIcon(item.weather);
        }
    });
}

(2)关键注意事项

  • 跨域问题:直接在前端调用第三方 API 可能出现跨域,解决方案有两种:
    1. 后端代理:通过自己的后端服务转发 API 请求,前端调用后端接口;
    2. 配置 CORS:确认 API 提供商是否支持跨域,或在开发环境使用跨域插件。
  • 数据解析:不同 API 的返回字段命名不同,需严格按照接口文档解析,避免字段错误导致渲染失败;
  • 异常处理:添加请求失败的提示,提升用户体验。

3. 交互逻辑实现

完成 API 对接后,补充交互逻辑,让应用更易用:

  • 搜索功能:点击搜索按钮/按下回车,触发天气查询;
  • 加载状态:请求数据时显示加载动画,避免用户误以为无响应;
  • 天气图标适配:根据返回的天气状况(晴天/多云/小雨等),自动切换对应的 Font Awesome 图标;
  • 响应式适配:确保在手机、平板等设备上正常显示。

三、完整代码

以下是整合后的完整代码(包含模拟数据,替换 API 部分即可使用真实数据):

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>天气预报应用 - APISpace</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        
        body {
            background: linear-gradient(135deg, #1a2980, #26d0ce);
            color: #fff;
            min-height: 100vh;
            padding: 20px;
        }
        
        .container {
            max-width: 1200px;
            margin: 0 auto;
        }
        
        header {
            text-align: center;
            padding: 20px 0;
            margin-bottom: 30px;
        }
        
        header h1 {
            font-size: 2.5rem;
            margin-bottom: 10px;
            text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
        }
        
        header p {
            opacity: 0.9;
            font-size: 1.1rem;
        }
        
        .search-box {
            display: flex;
            justify-content: center;
            margin-bottom: 30px;
        }
        
        .search-box input {
            padding: 15px 20px;
            width: 100%;
            max-width: 400px;
            border: none;
            border-radius: 30px 0 0 30px;
            font-size: 1rem;
            outline: none;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
        }
        
        .search-box button {
            padding: 15px 25px;
            border: none;
            border-radius: 0 30px 30px 0;
            background: #ff6b6b;
            color: white;
            cursor: pointer;
            font-size: 1rem;
            font-weight: bold;
            transition: background 0.3s;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
        }
        
        .search-box button:hover {
            background: #ff5252;
        }
        
        .weather-cards {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
            gap: 25px;
            margin-bottom: 30px;
        }
        
        .weather-card {
            background: rgba(255, 255, 255, 0.15);
            backdrop-filter: blur(10px);
            border-radius: 20px;
            padding: 25px;
            box-shadow: 0 8px 32px rgba(31, 38, 135, 0.37);
            transition: transform 0.3s;
        }
        
        .weather-card:hover {
            transform: translateY(-5px);
        }
        
        .weather-card h2 {
            margin-bottom: 15px;
            font-size: 1.5rem;
            display: flex;
            align-items: center;
            gap: 10px;
        }
        
        .current-weather {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 20px;
        }
        
        .temperature {
            font-size: 3.5rem;
            font-weight: bold;
            margin: 15px 0;
        }
        
        .weather-details {
            display: flex;
            flex-direction: column;
            gap: 10px;
        }
        
        .detail {
            display: flex;
            justify-content: space-between;
            border-bottom: 1px solid rgba(255, 255, 255, 0.2);
            padding: 8px 0;
        }
        
        .forecast {
            display: flex;
            overflow-x: auto;
            gap: 15px;
            padding: 10px 5px;
            margin-bottom: 30px;
        }
        
        .forecast-item {
            flex: 0 0 auto;
            width: 150px;
            background: rgba(255, 255, 255, 0.15);
            backdrop-filter: blur(10px);
            border-radius: 15px;
            padding: 15px;
            text-align: center;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
        }
        
        .forecast-date {
            font-weight: bold;
            margin-bottom: 10px;
        }
        
        .forecast-temp {
            font-size: 1.5rem;
            margin: 10px 0;
        }
        
        .api-info {
            text-align: center;
            background: rgba(0, 0, 0, 0.2);
            padding: 15px;
            border-radius: 15px;
            margin-top: 20px;
        }
        
        .loader {
            display: none;
            text-align: center;
            margin: 30px 0;
        }
        
        .loader .spinner {
            border: 5px solid rgba(255, 255, 255, 0.3);
            border-top: 5px solid #fff;
            border-radius: 50%;
            width: 50px;
            height: 50px;
            animation: spin 1s linear infinite;
            margin: 0 auto;
        }
        
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
        
        .error {
            text-align: center;
            background: rgba(255, 0, 0, 0.2);
            padding: 15px;
            border-radius: 10px;
            margin: 20px 0;
            display: none;
        }
        
        @media (max-width: 768px) {
            .weather-cards {
                grid-template-columns: 1fr;
            }
            
            .current-weather {
                grid-template-columns: 1fr;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1><i class="fas fa-cloud-sun"></i> 天气预报</h1>
            <p>使用APISpace API获取实时天气数据</p>
        </header>
        
        <div class="search-box">
            <input type="text" id="city-input" placeholder="输入城市名称...">
            <button id="search-btn"><i class="fas fa-search"></i> 搜索</button>
        </div>
        
        <div class="loader">
            <div class="spinner"></div>
            <p>正在获取天气数据...</p>
        </div>
        
        <div class="error" id="error-message">
            <p><i class="fas fa-exclamation-circle"></i> 无法获取天气数据,请检查网络连接或稍后再试。</p>
        </div>
        
        <div class="weather-cards">
            <div class="weather-card" id="current-weather">
                <h2><i class="fas fa-location-dot"></i> 当前位置</h2>
                <div class="current-weather">
                    <div>
                        <div class="temperature">25°C</div>
                        <div class="weather-condition">晴天</div>
                    </div>
                    <div class="weather-details">
                        <div class="detail">
                            <span>体感温度</span>
                            <span>26°C</span>
                        </div>
                        <div class="detail">
                            <span>湿度</span>
                            <span>65%</span>
                        </div>
                        <div class="detail">
                            <span>风速</span>
                            <span>3.6 km/h</span>
                        </div>
                        <div class="detail">
                            <span>气压</span>
                            <span>1015 hPa</span>
                        </div>
                    </div>
                </div>
            </div>
            
            <div class="weather-card">
                <h2><i class="fas fa-info-circle"></i> 天气指数</h2>
                <div class="weather-details">
                    <div class="detail">
                        <span>紫外线</span>
                        <span>中等</span>
                    </div>
                    <div class="detail">
                        <span>能见度</span>
                        <span>10 km</span>
                    </div>
                    <div class="detail">
                        <span>空气质量</span>
                        <span>良好</span>
                    </div>
                    <div class="detail">
                        <span>穿衣建议</span>
                        <span>短袖衣物</span>
                    </div>
                </div>
            </div>
        </div>
        
        <h2 style="margin: 25px 0 15px;"><i class="fas fa-calendar-days"></i> 未来天气预报</h2>
        <div class="forecast">
            <div class="forecast-item">
                <div class="forecast-date">明天</div>
                <i class="fas fa-cloud" style="font-size: 2rem;"></i>
                <div class="forecast-temp">26°C</div>
                <div class="forecast-condition">多云</div>
            </div>
            <div class="forecast-item">
                <div class="forecast-date">周二</div>
                <i class="fas fa-cloud-rain" style="font-size: 2rem;"></i>
                <div class="forecast-temp">23°C</div>
                <div class="forecast-condition">小雨</div>
            </div>
            <div class="forecast-item">
                <div class="forecast-date">周三</div>
                <i class="fas fa-cloud-sun" style="font-size: 2rem;"></i>
                <div class="forecast-temp">25°C</div>
                <div class="forecast-condition">多云转晴</div>
            </div>
            <div class="forecast-item">
                <div class="forecast-date">周四</div>
                <i class="fas fa-sun" style="font-size: 2rem;"></i>
                <div class="forecast-temp">28°C</div>
                <div class="forecast-condition">晴天</div>
            </div>
            <div class="forecast-item">
                <div class="forecast-date">周五</div>
                <i class="fas fa-sun" style="font-size: 2rem;"></i>
                <div class="forecast-temp">29°C</div>
                <div class="forecast-condition">晴天</div>
            </div>
        </div>
        
        <div class="api-info">
            <p>数据由 <strong>APISpace</strong> 提供 | Moonbeams----张一衡</p>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const searchBtn = document.getElementById('search-btn');
            const cityInput = document.getElementById('city-input');
            const loader = document.querySelector('.loader');
            const errorMessage = document.getElementById('error-message');
            
            // 默认显示北京天气
            getWeatherData('北京');
            
            searchBtn.addEventListener('click', function() {
                const city = cityInput.value.trim();
                if (city) {
                    getWeatherData(city);
                }
            });
            
            cityInput.addEventListener('keypress', function(e) {
                if (e.key === 'Enter') {
                    const city = cityInput.value.trim();
                    if (city) {
                        getWeatherData(city);
                    }
                }
            });
            
            function getWeatherData(city) {
                // 显示加载动画
                loader.style.display = 'block';
                errorMessage.style.display = 'none';
                
                // 模拟API调用,实际应用中替换为真实API请求
                setTimeout(() => {
                    // 模拟数据
                    const weatherData = {
                        location: city,
                        temperature: Math.round(Math.random() * 15 + 20), // 20-35°C
                        condition: ['晴天', '多云', '小雨', '阴天'][Math.floor(Math.random() * 4)],
                        feelsLike: Math.round(Math.random() * 15 + 22),
                        humidity: Math.round(Math.random() * 40 + 40), // 40-80%
                        windSpeed: (Math.random() * 10 + 2).toFixed(1),
                        pressure: Math.round(Math.random() * 20 + 1000), // 1000-1020
                        uvIndex: ['低', '中等', '高'][Math.floor(Math.random() * 3)],
                        visibility: Math.round(Math.random() * 10 + 5), // 5-15km
                        airQuality: ['优', '良好', '中等'][Math.floor(Math.random() * 3)],
                        clothing: ['短袖衣物', '长袖衣物', '薄外套', '厚外套'][Math.floor(Math.random() * 4)]
                    };
                    
                    // 更新UI
                    updateWeatherUI(weatherData);
                    
                    // 隐藏加载动画
                    loader.style.display = 'none';
                }, 1500);
            }
            
            function updateWeatherUI(data) {
                // 更新当前天气
                document.querySelector('#current-weather h2').innerHTML = `<i class="fas fa-location-dot"></i> ${data.location}`;
                document.querySelector('.temperature').textContent = `${data.temperature}°C`;
                document.querySelector('.weather-condition').textContent = data.condition;
                
                // 更新天气详情
                document.querySelectorAll('.detail')[0].lastElementChild.textContent = `${data.feelsLike}°C`;
                document.querySelectorAll('.detail')[1].lastElementChild.textContent = `${data.humidity}%`;
                document.querySelectorAll('.detail')[2].lastElementChild.textContent = `${data.windSpeed} km/h`;
                document.querySelectorAll('.detail')[3].lastElementChild.textContent = `${data.pressure} hPa`;
                
                // 更新天气指数
                document.querySelectorAll('.weather-details .detail')[4].lastElementChild.textContent = data.uvIndex;
                document.querySelectorAll('.weather-details .detail')[5].lastElementChild.textContent = `${data.visibility} km`;
                document.querySelectorAll('.weather-details .detail')[6].lastElementChild.textContent = data.airQuality;
                document.querySelectorAll('.weather-details .detail')[7].lastElementChild.textContent = data.clothing;
                
                // 根据天气条件更新图标
                const conditionIcon = document.querySelector('#current-weather h2 i');
                conditionIcon.className = getWeatherIcon(data.condition);
                
                // 更新预报(示例)
                const forecastItems = document.querySelectorAll('.forecast-item');
                forecastItems.forEach(item => {
                    const temp = Math.round(Math.random() * 10 + 20); // 20-30°C
                    item.querySelector('.forecast-temp').textContent = `${temp}°C`;
                });
            }
            
            function getWeatherIcon(condition) {
                switch(condition) {
                    case '晴天':
                        return 'fas fa-sun';
                    case '多云':
                        return 'fas fa-cloud';
                    case '小雨':
                        return 'fas fa-cloud-rain';
                    case '阴天':
                        return 'fas fa-cloud';
                    default:
                        return 'fas fa-cloud';
                }
            }
        });
    </script>
</body>
</html>

四、遇到的问题与解决

  1. 跨域问题:直接调用 APISpace API 出现跨域,解决方案是在本地开发环境使用 vite/webpack 配置代理,转发 API 请求;
  2. 数据兼容性:不同城市的返回数据可能存在空值,需添加默认值处理(如 data.now.temp || '未知');
  3. 图标适配:天气状况的描述不统一(如“晴”和“晴天”),需统一判断逻辑。

五、总结

关键点回顾

  1. API 配置核心:获取 API Key 后,需严格按照接口文档配置请求头和参数,处理返回的 JSON 数据;
  2. 前端交互优化:添加加载/错误提示、响应式布局、动效,提升用户体验;
  3. 实战技巧:模拟数据先行,再替换真实 API,降低调试难度;处理边界情况(空数据、请求失败),保证应用稳定性。
posted @ 2026-01-15 00:42  Moonbeamsc  阅读(4)  评论(0)    收藏  举报
返回顶端