本文目录导读:

我来为你设计一个完整的气象查询应用案例,包含前端界面、后端API和完整的代码实现。
🌤️ 天气查询系统完整案例
技术栈选择
- 前端: HTML + CSS + JavaScript (原生)
- 后端: Node.js + Express
- API: 使用免费公开API接口(OpenWeatherMap或和风天气)
后端实现 (Node.js + Express)
项目初始化
mkdir weather-app cd weather-app npm init -y npm install express axios cors dotenv
后端服务器代码 server.js
const express = require('express');
const axios = require('axios');
const cors = require('cors');
require('dotenv').config();
const app = express();
const PORT = process.env.PORT || 3000;
// 中间件
app.use(cors());
app.use(express.json());
app.use(express.static('public'));
// 天气API密钥(请注册获取)
const API_KEY = process.env.WEATHER_API_KEY;
const API_BASE_URL = 'https://api.openweathermap.org/data/2.5/weather';
/**
* 查询天气API
* @param {string} city - 城市名称
* @param {string} country - 国家代码(可选)
*/
app.get('/api/weather/:city', async (req, res) => {
try {
const { city } = req.params;
const { country } = req.query;
// 构建查询参数
const params = {
q: country ? `${city},${country}` : city,
appid: API_KEY,
units: 'metric', // 使用摄氏度
lang: 'zh_cn' // 中文返回
};
// 调用天气API
const response = await axios.get(API_BASE_URL, { params });
// 格式化返回数据
const weatherData = {
city: response.data.name,
country: response.data.sys.country,
temperature: Math.round(response.data.main.temp),
feelsLike: Math.round(response.data.main.feels_like),
description: response.data.weather[0].description,
icon: response.data.weather[0].icon,
humidity: response.data.main.humidity,
windSpeed: response.data.wind.speed,
visibility: response.data.visibility / 1000, // 转为km
pressure: response.data.main.pressure,
sunrise: new Date(response.data.sys.sunrise * 1000).toLocaleTimeString('zh-CN'),
sunset: new Date(response.data.sys.sunset * 1000).toLocaleTimeString('zh-CN')
};
res.json({
success: true,
data: weatherData,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('天气查询失败:', error);
res.status(500).json({
success: false,
message: error.response?.data?.message || '查询天气失败,请检查城市名称',
error: error.message
});
}
});
/**
* 查询多城市天气
*/
app.get('/api/weather', async (req, res) => {
try {
const { cities = [] } = req.query;
const cityList = typeof cities === 'string' ? cities.split(',') : cities;
const promises = cityList.map(city =>
axios.get(API_BASE_URL, {
params: {
q: city,
appid: API_KEY,
units: 'metric',
lang: 'zh_cn'
}
})
);
const results = await Promise.allSettled(promises);
const weatherData = results.map((result, index) => {
if (result.status === 'fulfilled') {
const data = result.value.data;
return {
city: data.name,
temperature: Math.round(data.main.temp),
description: data.weather[0].description,
icon: data.weather[0].icon,
success: true
};
} else {
return {
city: cityList[index],
success: false,
message: '无法获取该城市的天气'
};
}
});
res.json({
success: true,
data: weatherData
});
} catch (error) {
res.status(500).json({
success: false,
message: '批量查询失败'
});
}
});
// 启动服务器
app.listen(PORT, () => {
console.log(`服务器已启动:http://localhost:${PORT}`);
});
前端实现
HTML 文件 public/index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">气象查询系统</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body>
<div class="container">
<!-- 头部 -->
<header class="header">
<h1><i class="fas fa-cloud-sun"></i> 气象查询系统</h1>
<p class="subtitle">实时天气信息查询</p>
</header>
<!-- 搜索区域 -->
<div class="search-box">
<div class="search-input-wrapper">
<i class="fas fa-map-marker-alt"></i>
<input
type="text"
id="cityInput"
placeholder="请输入城市名称,如:北京"
autocomplete="off"
>
<button id="searchBtn" class="btn-primary">
<i class="fas fa-search"></i> 查询
</button>
</div>
</div>
<!-- 天气显示区域 -->
<main class="weather-display">
<!-- 加载动画 -->
<div id="loading" class="hidden">
<div class="spinner"></div>
<p>正在获取天气信息...</p>
</div>
<!-- 错误提示 -->
<div id="errorMessage" class="hidden">
<i class="fas fa-exclamation-circle"></i>
<p id="errorText"></p>
</div>
<!-- 天气卡片 -->
<div id="weatherCard" class="hidden">
<!-- 主要天气信息 -->
<div class="weather-main">
<div class="weather-icon" id="weatherIcon">
<img src="" alt="天气图标" id="weatherIconImg">
</div>
<div class="temperature" id="temperature">--°C</div>
<div class="description" id="description">--</div>
<div class="location" id="location">
<i class="fas fa-location-arrow"></i>
<span id="cityName">--</span>,
<span id="country">--</span>
</div>
</div>
<!-- 体感温度 -->
<div class="feels-like" id="feelsLike">
<p>体感温度</p>
<span>--°C</span>
</div>
<!-- 详细信息网格 -->
<div class="weather-details">
<div class="detail-item">
<div class="detail-icon">
<i class="fas fa-tint"></i>
</div>
<div class="detail-info">
<span class="detail-label">湿度</span>
<span class="detail-value" id="humidity">--%</span>
</div>
</div>
<div class="detail-item">
<div class="detail-icon">
<i class="fas fa-wind"></i>
</div>
<div class="detail-info">
<span class="detail-label">风速</span>
<span class="detail-value" id="windSpeed">-- m/s</span>
</div>
</div>
<div class="detail-item">
<div class="detail-icon">
<i class="fas fa-eye"></i>
</div>
<div class="detail-info">
<span class="detail-label">能见度</span>
<span class="detail-value" id="visibility">-- km</span>
</div>
</div>
<div class="detail-item">
<div class="detail-icon">
<i class="fas fa-tachometer-alt"></i>
</div>
<div class="detail-info">
<span class="detail-label">气压</span>
<span class="detail-value" id="pressure">-- hPa</span>
</div>
</div>
</div>
<!-- 日出日落 -->
<div class="sun-times">
<div class="sun-time">
<i class="fas fa-sun"></i>
<span>日出</span>
<span id="sunrise">--</span>
</div>
<div class="sun-time">
<i class="fas fa-moon"></i>
<span>日落</span>
<span id="sunset">--</span>
</div>
</div>
<!-- 查询时间 -->
<div class="query-time">
查询时间:<span id="queryTime">--</span>
</div>
</div>
</main>
<!-- 快捷城市入口 -->
<div class="quick-cities">
<h3>快速查询</h3>
<div class="city-buttons">
<button onclick="quickSearch('北京')">北京</button>
<button onclick="quickSearch('上海')">上海</button>
<button onclick="quickSearch('广州')">广州</button>
<button onclick="quickSearch('深圳')">深圳</button>
<button onclick="quickSearch('成都')">成都</button>
<button onclick="quickSearch('杭州')">杭州</button>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
CSS 样式 public/styles.css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
}
body {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
color: #fff;
padding: 20px;
}
@keyframes gradientShift {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
.container {
max-width: 600px;
margin: 0 auto;
}
/* 头部样式 */
.header {
text-align: center;
margin-bottom: 30px;
animation: fadeInDown 0.8s ease;
}
.header h1 {
font-size: 2.5rem;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.header .subtitle {
font-size: 1.1rem;
opacity: 0.9;
margin-top: 10px;
}
/* 搜索框样式 */
.search-box {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 50px;
padding: 8px;
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
margin-bottom: 20px;
animation: fadeInUp 0.8s ease;
}
.search-input-wrapper {
display: flex;
align-items: center;
padding: 0 15px;
}
.search-input-wrapper i {
color: rgba(255, 255, 255, 0.8);
margin-right: 10px;
}
.search-input-wrapper input {
flex: 1;
padding: 15px;
background: transparent;
border: none;
color: #fff;
font-size: 1.2rem;
outline: none;
}
.search-input-wrapper input::placeholder {
color: rgba(255, 255, 255, 0.6);
}
.btn-primary {
background: #ff6b6b;
border: none;
color: #fff;
padding: 12px 25px;
border-radius: 30px;
font-size: 1.1rem;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 4px 15px rgba(255, 107, 107, 0.4);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(255, 107, 107, 0.6);
}
.btn-primary:active {
transform: translateY(0);
}
/* 天气卡片样式 */
.weather-display {
min-height: 400px;
position: relative;
}
#weatherCard {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(15px);
border-radius: 20px;
padding: 30px;
box-shadow: 0 15px 40px rgba(0,0,0,0.2);
animation: fadeInUp 0.5s ease;
}
.weather-main {
text-align: center;
margin-bottom: 20px;
}
.weather-icon img {
width: 100px;
height: 100px;
animation: float 3s ease-in-out infinite;
}
.temperature {
font-size: 4rem;
font-weight: bold;
text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
}
.description {
font-size: 1.5rem;
text-transform: capitalize;
margin: 10px 0;
}
.location {
font-size: 1.2rem;
color: rgba(255, 255, 255, 0.8);
}
.feels-like {
text-align: center;
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 10px;
margin: 20px 0;
}
.feels-like p {
font-size: 0.9rem;
color: rgba(255, 255, 255, 0.8);
}
.feels-like span {
font-size: 1.5rem;
font-weight: bold;
}
/* 详细信息网格 */
.weather-details {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin: 20px 0;
}
.detail-item {
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.08);
border-radius: 10px;
padding: 12px;
transition: transform 0.3s;
}
.detail-item:hover {
transform: scale(1.05);
}
.detail-icon {
width: 40px;
height: 40px;
background: rgba(255, 255, 255, 0.2);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10px;
}
.detail-icon i {
font-size: 1.2rem;
}
.detail-info {
display: flex;
flex-direction: column;
}
.detail-label {
font-size: 0.8rem;
color: rgba(255, 255, 255, 0.7);
}
.detail-value {
font-size: 1.1rem;
font-weight: bold;
}
/* 日出日落 */
.sun-times {
display: flex;
justify-content: space-around;
background: rgba(255, 255, 255, 0.08);
border-radius: 10px;
padding: 15px;
}
.sun-time {
text-align: center;
}
.sun-time i {
font-size: 1.5rem;
margin-right: 5px;
}
.sun-time span {
margin-left: 5px;
}
.query-time {
text-align: center;
margin-top: 20px;
font-size: 0.9rem;
color: rgba(255, 255, 255, 0.6);
}
/* 快捷城市 */
.quick-cities {
text-align: center;
margin-top: 30px;
}
.quick-cities h3 {
margin-bottom: 15px;
}
.city-buttons {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
}
.city-buttons button {
background: rgba(255, 255, 255, 0.2);
border: none;
color: #fff;
padding: 10px 25px;
border-radius: 25px;
cursor: pointer;
transition: all 0.3s;
font-size: 1rem;
}
.city-buttons button:hover {
background: rgba(255, 255, 255, 0.4);
transform: translateY(-3px);
}
/* 加载动画 */
#loading {
text-align: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 15px;
background: rgba(255, 255, 255, 0.1);
padding: 30px;
}
.spinner {
border: 4px solid rgba(255, 255, 255, 0.1);
border-top: 4px solid #fff;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
margin: 0 auto 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 错误提示 */
#errorMessage {
background: rgba(255, 107, 107, 0.8);
border-radius: 10px;
padding: 20px;
text-align: center;
margin: 20px 0;
}
#errorMessage i {
font-size: 2rem;
margin-bottom: 10px;
}
/* 动画 */
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
/* 工具类 */
.hidden {
display: none !important;
}
JavaScript 文件 public/script.js
// 天气查询前端脚本
// DOM 元素
const cityInput = document.getElementById('cityInput');
const searchBtn = document.getElementById('searchBtn');
const loading = document.getElementById('loading');
const errorMessage = document.getElementById('errorMessage');
const weatherCard = document.getElementById('weatherCard');
const errorText = document.getElementById('errorText');
// 处理回车键查询
cityInput.addEventListener('keyup', (e) => {
if (e.key === 'Enter') {
searchWeather();
}
});
// 查询按钮点击事件
searchBtn.addEventListener('click', searchWeather);
// 搜索城市天气
async function searchWeather() {
const city = cityInput.value.trim();
if (!city) {
showError('请输入城市名称');
return;
}
// 显示加载动画
showLoading();
try {
const response = await fetch(`/api/weather/${encodeURIComponent(city)}`);
const result = await response.json();
if (result.success) {
displayWeather(result.data);
} else {
showError(result.message);
}
} catch (error) {
showError('网络错误,请稍后重试');
console.error('Error:', error);
} finally {
hideLoading();
}
}
// 显示天气数据
function displayWeather(data) {
// 更新温度
document.getElementById('temperature').textContent = `${data.temperature}°C`;
// 更新天气描述
document.getElementById('description').textContent = data.description;
// 更新位置
document.getElementById('cityName').textContent = data.city;
document.getElementById('country').textContent = data.country;
// 更新天气图标
document.getElementById('weatherIconImg').src = `https://openweathermap.org/img/wn/${data.icon}@2x.png`;
document.getElementById('weatherIconImg').alt = data.description;
// 更新体感温度
document.getElementById('feelsLike').querySelector('span').textContent = `${data.feelsLike}°C`;
// 更新详细信息
document.getElementById('humidity').textContent = `${data.humidity}%`;
document.getElementById('windSpeed').textContent = `${data.windSpeed} m/s`;
document.getElementById('visibility').textContent = `${data.visibility} km`;
document.getElementById('pressure').textContent = `${data.pressure} hPa`;
// 更新日出日落
document.getElementById('sunrise').textContent = data.sunrise;
document.getElementById('sunset').textContent = data.sunset;
// 更新查询时间
document.getElementById('queryTime').textContent = new Date().toLocaleString('zh-CN');
// 显示天气卡片
weatherCard.classList.remove('hidden');
errorMessage.classList.add('hidden');
}
// 快速查询城市
function quickSearch(city) {
cityInput.value = city;
searchWeather();
}
// 显示错误信息
function showError(message) {
errorText.textContent = message;
errorMessage.classList.remove('hidden');
weatherCard.classList.add('hidden');
}
// 显示加载动画
function showLoading() {
loading.classList.remove('hidden');
weatherCard.classList.add('hidden');
errorMessage.classList.add('hidden');
}
// 隐藏加载动画
function hideLoading() {
loading.classList.add('hidden');
}
配置文件
.env 环境变量文件
# Weather API 配置 WEATHER_API_KEY=your_api_key_here PORT=3000
package.json 配置
{
"name": "weather-app",
"version": "1.0.0",
"description": "气象查询系统",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"axios": "^1.6.0",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
运行和使用
启动步骤
# 1. 安装依赖 npm install # 2. 设置环境变量 cp .env.example .env # 然后编辑 .env 文件填入你的API密钥 # 3. 启动服务 npm start # 开发环境可使用 npm run dev # 4. 访问应用 打开浏览器访问:http://localhost:3000
使用方法
-
输入城市名称查询天气
- 输入中文(如:北京)或英文(如:Beijing)
- 点击"查询"按钮或按回车键
-
快捷查询
点击下方预置的城市按钮快速查询
-
查看详细信息
- 温度、体感温度、湿度、风速等
- 日出日落时间
- 查询时间戳
扩展功能建议
多城市对比查询
async function compareWeather(cities = ['北京', '上海', '广州']) {
const response = await fetch(`/api/weather?cities=${cities.join(',')}`);
return await response.json();
}
天气预测功能
可扩展使用 https://api.openweathermap.org/data/2.5/forecast 接口获取未来天气预测。
用户位置自动定位
使用浏览器的 Geolocation API 自动获取用户位置:
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(position => {
// 使用经纬度查询天气
});
}
注意事项
- API密钥注册:需要在 OpenWeatherMap 注册并获取免费API密钥
- 免费额度:免费版API每小时有调用次数限制
- 错误处理:应用已包含基本的错误处理,可根据需要扩展
- 跨域问题:已使用 CORS 中间件解决
这个完整的天气查询案例包含了前后端的所有代码,你可以直接运行体验,如果想使用和风天气等其他API,只需修改 server.js 中的API调用部分即可。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播