<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>城市天气查询系统 - 软件构造作业三</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', Arial, sans-serif;
background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 20px;
box-shadow: 0 15px 35px rgba(0,0,0,0.1);
padding: 30px;
border: 3px solid #74b9ff;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.student-info {
background: linear-gradient(45deg, #fd79a8, #e84393);
color: white;
padding: 12px 25px;
border-radius: 25px;
margin-bottom: 15px;
display: inline-block;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
font-weight: bold;
}
.student-name, .student-id {
font-size: 1.2em;
margin: 0 15px;
}
.header h1 {
color: #2d3436;
font-size: 2.8em;
text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
margin-bottom: 10px;
background: linear-gradient(45deg, #00cec9, #00b894);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.header p {
color: #636e72;
font-size: 1.3em;
margin-bottom: 20px;
}
.search-section {
background: #f8f9fa;
padding: 25px;
border-radius: 15px;
margin-bottom: 30px;
border: 2px solid #dfe6e9;
}
.search-form {
display: flex;
gap: 15px;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
.search-input {
flex: 1;
min-width: 300px;
padding: 15px 20px;
border: 2px solid #b2bec3;
border-radius: 50px;
font-size: 1.1em;
outline: none;
transition: all 0.3s ease;
}
.search-input:focus {
border-color: #74b9ff;
box-shadow: 0 0 10px rgba(116, 185, 255, 0.3);
}
.search-btn {
padding: 15px 30px;
background: linear-gradient(45deg, #00b894, #00cec9);
color: white;
border: none;
border-radius: 50px;
font-size: 1.1em;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.search-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0,0,0,0.2);
}
.weather-display {
display: none;
}
.current-weather {
background: linear-gradient(135deg, #a29bfe 0%, #6c5ce7 100%);
color: white;
padding: 30px;
border-radius: 20px;
margin-bottom: 25px;
text-align: center;
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
}
.city-name {
font-size: 2.5em;
margin-bottom: 10px;
font-weight: bold;
}
.temperature {
font-size: 4em;
font-weight: bold;
margin: 20px 0;
}
.weather-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-top: 20px;
}
.info-item {
background: rgba(255,255,255,0.2);
padding: 15px;
border-radius: 10px;
backdrop-filter: blur(10px);
}
.forecast {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-top: 30px;
}
.forecast-day {
background: white;
padding: 20px;
border-radius: 15px;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
text-align: center;
border: 2px solid #dfe6e9;
transition: all 0.3s ease;
}
.forecast-day:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0,0,0,0.15);
border-color: #74b9ff;
}
.forecast-date {
font-weight: bold;
color: #2d3436;
margin-bottom: 10px;
font-size: 1.2em;
}
.forecast-temp {
font-size: 1.8em;
font-weight: bold;
color: #e17055;
margin: 10px 0;
}
.forecast-desc {
color: #636e72;
margin-bottom: 10px;
}
.loading {
text-align: center;
padding: 40px;
display: none;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #74b9ff;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error-message {
background: #ff7675;
color: white;
padding: 20px;
border-radius: 10px;
text-align: center;
margin: 20px 0;
display: none;
}
.api-info {
background: #ffeaa7;
padding: 15px;
border-radius: 10px;
margin-top: 20px;
text-align: center;
color: #2d3436;
font-size: 0.9em;
}
@media (max-width: 768px) {
.search-form {
flex-direction: column;
}
.search-input {
min-width: auto;
width: 100%;
}
.temperature {
font-size: 3em;
}
.city-name {
font-size: 2em;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🌤️ 城市天气查询系统</h1>
<p>查询全球城市的实时天气和未来天气预报</p>
</div>
<div class="search-section">
<div class="search-form">
<input type="text" class="search-input" id="cityInput"
placeholder="请输入城市名称(如:北京、上海、New York)"
value="北京">
<button class="search-btn" onclick="searchWeather()">
🔍 查询天气
</button>
</div>
</div>
<div class="loading" id="loading">
<div class="spinner"></div>
<p>正在获取天气数据,请稍候...</p>
</div>
<div class="error-message" id="errorMessage">
<!-- 错误信息将在这里显示 -->
</div>
<div class="weather-display" id="weatherDisplay">
<div class="current-weather" id="currentWeather">
<!-- 当前天气信息将在这里显示 -->
</div>
<div class="forecast" id="forecast">
<!-- 天气预报信息将在这里显示 -->
</div>
</div>
</div>
<script>
const WEATHER_API_URL = 'https://api.lolimi.cn/API/weather/api.php';
async function fetchRealWeather(city) {
try {
// 构建请求参数
const params = {
city: city,
type: 'json'
};
// 构建查询字符串
const query = new URLSearchParams(params).toString();
const fullUrl = WEATHER_API_URL + '?' + query;
// 发送请求
const response = await fetch(fullUrl, {
method: 'GET',
mode: 'no-cors', // 使用no-cors模式绕过CORS限制
headers: {
'Accept': '*/*',
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
});
if (response.ok || response.type === 'opaque') {
// 尝试解析响应,如果失败则说明API可能不支持no-cors
try {
const data = await response.json();
if (data.code === 1) {
return data.data;
} else {
throw new Error(data.text || '获取天气数据失败');
}
} catch (parseError) {
// 如果无法解析JSON,尝试使用代理服务
return await fetchWithProxy(city);
}
} else {
throw new Error(`请求失败,状态码: ${response.status}`);
}
} catch (error) {
// 如果直接请求失败,尝试使用代理服务
return await fetchWithProxy(city);
}
}
// 备用方案:使用代理服务
async function fetchWithProxy(city) {
const proxyServices = [
'https://api.allorigins.win/raw?url=',
'https://corsproxy.io/?',
'https://proxy.cors.sh/'
];
const apiUrl = `${WEATHER_API_URL}?city=${encodeURIComponent(city)}&type=json`;
for (const proxyUrl of proxyServices) {
try {
const response = await fetch(proxyUrl + encodeURIComponent(apiUrl), {
method: 'GET',
headers: {
'Accept': '*/*',
'Content-Type': 'application/x-www-form-urlencoded'
}
});
if (response.ok) {
const data = await response.json();
if (data.code === 1) {
return data.data;
} else {
throw new Error(data.text || '获取天气数据失败');
}
}
} catch (error) {
console.log(`代理服务 ${proxyUrl} 失败:`, error);
continue;
}
}
throw new Error('所有代理服务均不可用,请检查网络连接');
}
// 显示/隐藏加载动画
function showLoading(show) {
document.getElementById('loading').style.display = show ? 'block' : 'none';
}
// 显示错误信息
function showError(message) {
const errorDiv = document.getElementById('errorMessage');
errorDiv.innerHTML = `❌ ${message}`;
errorDiv.style.display = 'block';
setTimeout(() => {
errorDiv.style.display = 'none';
}, 5000);
}
// 搜索天气
async function searchWeather() {
const cityInput = document.getElementById('cityInput');
const city = cityInput.value.trim();
if (!city) {
showError('请输入城市名称');
return;
}
showLoading(true);
document.getElementById('weatherDisplay').style.display = 'none';
try {
// 调用真实天气API
const weatherData = await fetchRealWeather(city);
displayWeather(city, weatherData);
} catch (error) {
console.error('获取天气数据失败:', error);
showError('获取天气数据失败,请检查网络连接或稍后重试');
} finally {
showLoading(false);
}
}
// 显示天气信息
function displayWeather(city, data) {
const current = data.current;
// 显示当前天气
const currentWeatherDiv = document.getElementById('currentWeather');
currentWeatherDiv.innerHTML = `
<div class="city-name">${current.city}</div>
<div class="temperature">${current.temp}°C</div>
<div style="font-size: 1.5em; margin-bottom: 10px;">
${getWeatherEmoji(current.weather)} ${current.weather}
</div>
<div style="font-size: 1.1em; opacity: 0.9;">日期: ${current.date} ${current.time}</div>
<!-- 简化信息显示 -->
<div class="weather-info">
<div class="info-item">
<div>💧 湿度</div>
<div style="font-size: 1.3em; font-weight: bold;">${current.humidity}</div>
</div>
<div class="info-item">
<div>🌬️ 风向风速</div>
<div style="font-size: 1.3em; font-weight: bold;">${current.wind} ${current.windSpeed}</div>
</div>
<div class="info-item">
<div>💨 空气质量</div>
<div style="font-size: 1.3em; font-weight: bold;">${current.air_pm25 || 'N/A'}</div>
</div>
<div class="info-item">
<div>${isRaining(current.weather) ? '🌧️ 正在下雨' : '☀️ 无雨'}</div>
<div style="font-size: 1.3em; font-weight: bold;">${isRaining(current.weather) ? '是' : '否'}</div>
</div>
</div>
`;
// 显示未来几天天气预报(模拟数据)
const forecastDiv = document.getElementById('forecast');
const futureWeather = generateFutureWeather(current.weather, current.temp);
forecastDiv.innerHTML = `
<h3 style="text-align: center; margin-bottom: 20px; color: #2d3436;">未来3天天气预报</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;">
${futureWeather.map((day, index) => `
<div style="background: #f8f9fa; padding: 15px; border-radius: 10px; border-left: 4px solid #74b9ff; text-align: center;">
<div style="font-weight: bold; color: #2d3436; margin-bottom: 10px;">${day.date}</div>
<div style="font-size: 2em; margin-bottom: 5px;">${getWeatherEmoji(day.weather)}</div>
<div style="color: #2d3436; font-weight: bold; margin-bottom: 5px;">${day.weather}</div>
<div style="color: #e17055; font-weight: bold; font-size: 1.2em;">${day.temp}°C</div>
<div style="color: #636e72; font-size: 0.9em; margin-top: 5px;">${day.rain ? '🌧️ 有雨' : '☀️ 无雨'}</div>
</div>
`).join('')}
</div>
`;
// 显示天气信息区域
document.getElementById('weatherDisplay').style.display = 'block';
// 滚动到天气信息区域
document.getElementById('weatherDisplay').scrollIntoView({
behavior: 'smooth'
});
}
// 判断是否下雨
function isRaining(weather) {
const rainKeywords = ['雨', '雷', '阵雨', '暴雨', '小雨', '中雨', '大雨'];
return rainKeywords.some(keyword => weather.includes(keyword));
}
// 生成未来几天天气预报(模拟数据)
function generateFutureWeather(currentWeather, currentTemp) {
const today = new Date();
const weatherTypes = ['晴', '多云', '阴', '小雨', '中雨', '大雨', '雷阵雨'];
const futureWeather = [];
// 基于当前天气生成未来天气趋势
let currentWeatherIndex = weatherTypes.indexOf(currentWeather);
if (currentWeatherIndex === -1) currentWeatherIndex = 0;
for (let i = 1; i <= 3; i++) {
const futureDate = new Date(today);
futureDate.setDate(today.getDate() + i);
// 模拟天气变化趋势
const weatherChange = Math.floor(Math.random() * 3) - 1; // -1, 0, 1
let weatherIndex = Math.max(0, Math.min(weatherTypes.length - 1, currentWeatherIndex + weatherChange));
const weather = weatherTypes[weatherIndex];
const tempChange = Math.floor(Math.random() * 5) - 2; // -2到+2度变化
const temp = Math.max(-10, Math.min(40, parseInt(currentTemp) + tempChange));
futureWeather.push({
date: `${futureDate.getMonth() + 1}月${futureDate.getDate()}日`,
weather: weather,
temp: temp,
rain: isRaining(weather)
});
}
return futureWeather;
}
// 根据天气描述获取对应的emoji
function getWeatherEmoji(weather) {
const emojiMap = {
'晴': '☀️',
'多云': '⛅',
'阴': '☁️',
'雨': '🌧️',
'小雨': '🌦️',
'中雨': '🌧️',
'大雨': '🌧️',
'暴雨': '⛈️',
'雷阵雨': '⛈️',
'雪': '❄️',
'小雪': '🌨️',
'中雪': '❄️',
'大雪': '❄️',
'雾': '🌫️',
'霾': '🌫️'
};
for (const [key, emoji] of Object.entries(emojiMap)) {
if (weather.includes(key)) {
return emoji;
}
}
return '🌤️';
}
// 回车键搜索
document.getElementById('cityInput').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
searchWeather();
}
});
// 页面加载时显示欢迎信息
window.onload = function() {
// 可以在这里添加页面加载后的初始化代码
console.log('天气查询系统已加载');
};
// 实际API调用函数(注释掉,因为需要真实API密钥)
/*
async function fetchRealWeather(city) {
try {
// 获取当前天气
const currentResponse = await fetch(
`${BASE_URL}/weather?q=${city}&appid=${API_KEY}&units=metric&lang=zh_cn`
);
if (!currentResponse.ok) {
throw new Error('城市未找到');
}
const currentData = await currentResponse.json();
// 获取天气预报
const forecastResponse = await fetch(
`${BASE_URL}/forecast?q=${city}&appid=${API_KEY}&units=metric&lang=zh_cn`
);
const forecastData = await forecastResponse.json();
return {
current: {
temp: Math.round(currentData.main.temp),
feels_like: Math.round(currentData.main.feels_like),
humidity: currentData.main.humidity,
pressure: currentData.main.pressure,
wind_speed: currentData.wind.speed,
description: currentData.weather[0].description,
icon: currentData.weather[0].icon
},
forecast: forecastData.list.slice(0, 5).map(item => ({
date: new Date(item.dt * 1000).toLocaleDateString('zh-CN'),
temp: Math.round(item.main.temp),
description: item.weather[0].description,
icon: item.weather[0].icon
}))
};
} catch (error) {
throw error;
}
}
*/
</script>
</body>
</html>