/
aska
/
web-static-labs
Обзор
Документация
Войти
/
aska
/
web-static-labs
Код
Запросы
0
Задачи
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
html/lab6/task8/script.js
458 строк
17 KB
yourr
111
14 янв 2026, 14:18
14 янв 2026, 14:18
4d4f934
Код
Авторство
О чём код?
'use strict'; class WeatherWidget { constructor() { this.geocodingApi = 'https://geocoding-api.open-meteo.com/v1/search'; this.weatherApi = 'https://api.open-meteo.com/v1/forecast'; this.cacheDuration = 10 * 60 * 1000; // 10 минут в миллисекундах this.storageKey = 'weatherCache'; this.historyKey = 'weatherHistory'; this.init(); } init() { this.bindEvents(); this.loadHistory(); } bindEvents() { document.getElementById('searchBtn').addEventListener('click', () => this.getWeather()); document.getElementById('refreshBtn').addEventListener('click', () => this.refreshWeather()); document.getElementById('cityInput').addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.getWeather(); } }); } async getWeather() { const city = document.getElementById('cityInput').value.trim(); if (!this.validateCity(city)) { return; } this.showLoading(); this.hideError(); try { // Проверяем кэш const cachedData = this.getCachedWeather(city); if (cachedData) { this.displayWeather(cachedData.data, 'localStorage'); this.showStatus(`Данные загружены из кэша (${this.getTimeAgo(cachedData.timestamp)})`, 'info'); return; } // Получаем координаты города const coordinates = await this.getCityCoordinates(city); // Получаем погоду по координатам const weatherData = await this.fetchWeatherData(coordinates.latitude, coordinates.longitude, city); this.displayWeather(weatherData, 'API'); this.cacheWeatherData(city, weatherData); this.addToHistory(city, weatherData); this.showStatus('Данные успешно загружены с API', 'success'); } catch (error) { this.handleError(error); } } validateCity(city) { const errorElement = document.getElementById('cityError'); if (!city) { this.showError('Введите название города'); return false; } if (city.length < 2) { this.showError('Название города должно содержать минимум 2 символа'); return false; } errorElement.textContent = ''; return true; } async getCityCoordinates(city) { const url = `${this.geocodingApi}?name=${encodeURIComponent(city)}&count=1&language=ru&format=json`; const response = await fetch(url); if (!response.ok) { throw new Error('Ошибка при поиске города'); } const data = await response.json(); if (!data.results || data.results.length === 0) { throw new Error('Город не найден. Проверьте правильность написания.'); } const cityData = data.results[0]; return { latitude: cityData.latitude, longitude: cityData.longitude, country: cityData.country, name: cityData.name }; } async fetchWeatherData(lat, lon, cityName) { const url = `${this.weatherApi}?latitude=${lat}&longitude=${lon}¤t=temperature_2m,relative_humidity_2m,apparent_temperature,pressure_msl,wind_speed_10m,wind_direction_10m,weather_code,visibility&wind_speed_unit=ms&timezone=auto`; const response = await fetch(url); if (!response.ok) { throw new Error('Ошибка при получении данных о погоде'); } const data = await response.json(); // Форматируем данные для единообразного отображения return { name: cityName, country: this.getCountryName(data.timezone), current: data.current, timezone: data.timezone }; } getCountryName(timezone) { // Извлекаем страну из таймзоны (например: "Europe/Moscow" -> "Russia") const parts = timezone.split('/'); if (parts.length > 1) { const region = parts[0]; const city = parts[1]; const countryMap = { 'Europe': { 'Moscow': 'Россия', 'London': 'Великобритания', 'Paris': 'Франция', 'Berlin': 'Германия', 'Rome': 'Италия' }, 'America': { 'New_York': 'США', 'Los_Angeles': 'США', 'Chicago': 'США' }, 'Asia': { 'Tokyo': 'Япония', 'Shanghai': 'Китай', 'Seoul': 'Корея' } }; if (countryMap[region] && countryMap[region][city]) { return countryMap[region][city]; } } return timezone; } getCachedWeather(city) { try { const cache = JSON.parse(localStorage.getItem(this.storageKey)) || {}; const cityCache = cache[city.toLowerCase()]; if (cityCache && Date.now() - cityCache.timestamp < this.cacheDuration) { return cityCache; } // Удаляем просроченные данные if (cityCache) { delete cache[city.toLowerCase()]; localStorage.setItem(this.storageKey, JSON.stringify(cache)); } return null; } catch (error) { console.error('Ошибка чтения кэша:', error); return null; } } cacheWeatherData(city, data) { try { const cache = JSON.parse(localStorage.getItem(this.storageKey)) || {}; cache[city.toLowerCase()] = { data: data, timestamp: Date.now() }; localStorage.setItem(this.storageKey, JSON.stringify(cache)); } catch (error) { console.error('Ошибка сохранения в кэш:', error); } } displayWeather(data, source) { const weatherResult = document.getElementById('weatherResult'); const cityName = document.getElementById('cityName'); const dataSource = document.getElementById('dataSource'); const temperature = document.getElementById('temperature'); const weatherDescription = document.getElementById('weatherDescription'); const feelsLike = document.getElementById('feelsLike'); const humidity = document.getElementById('humidity'); const pressure = document.getElementById('pressure'); const wind = document.getElementById('wind'); const visibility = document.getElementById('visibility'); const updateTime = document.getElementById('updateTime'); const weatherIcon = document.getElementById('weatherIcon'); const current = data.current; // Основная информация cityName.textContent = `${data.name}, ${data.country}`; dataSource.textContent = source === 'API' ? 'Данные из API' : 'Данные из кэша'; dataSource.style.backgroundColor = source === 'API' ? '#006600' : '#666666'; temperature.textContent = Math.round(current.temperature_2m); weatherDescription.textContent = this.getWeatherDescription(current.weather_code); // Детали feelsLike.textContent = `${Math.round(current.apparent_temperature)} °C`; humidity.textContent = `${current.relative_humidity_2m}%`; pressure.textContent = `${Math.round(current.pressure_msl)} hPa`; const windSpeed = current.wind_speed_10m; const windDirection = this.getWindDirection(current.wind_direction_10m); wind.textContent = `${windDirection} ${windSpeed.toFixed(1)} м/с`; visibility.textContent = `${(current.visibility / 1000).toFixed(1)} км`; // Время обновления const now = new Date(); updateTime.textContent = now.toLocaleString('ru-RU'); // Иконка погоды weatherIcon.textContent = this.getWeatherIcon(current.weather_code); // Показываем результат weatherResult.style.display = 'block'; this.hideError(); this.hideLoading(); } getWeatherIcon(weatherCode) { // Коды погоды от WMO (World Meteorological Organization) const iconMap = { 0: '☀️', // Ясно 1: '🌤️', // Преимущественно ясно 2: '⛅', // Переменная облачность 3: '☁️', // Пасмурно 45: '🌫️', // Туман 48: '🌫️', // Туман с инеем 51: '🌦️', // Легкая морось 53: '🌦️', // Умеренная морось 55: '🌧️', // Сильная морось 61: '🌦️', // Небольшой дождь 63: '🌧️', // Умеренный дождь 65: '🌧️', // Сильный дождь 80: '🌦️', // Небольшие ливни 81: '🌧️', // Умеренные ливни 82: '⛈️', // Сильные ливни 95: '⛈️', // Гроза 96: '⛈️', // Гроза с мелким градом 99: '⛈️' // Гроза с крупным градом }; return iconMap[weatherCode] || '🌤️'; } getWeatherDescription(weatherCode) { const descriptions = { 0: 'ясно', 1: 'преимущественно ясно', 2: 'переменная облачность', 3: 'пасмурно', 45: 'туман', 48: 'туман с инеем', 51: 'легкая морось', 53: 'морось', 55: 'сильная морось', 61: 'небольшой дождь', 63: 'дождь', 65: 'сильный дождь', 80: 'небольшие ливни', 81: 'ливни', 82: 'сильные ливни', 95: 'гроза', 96: 'гроза с градом', 99: 'сильная гроза с градом' }; return descriptions[weatherCode] || 'неизвестно'; } getWindDirection(degrees) { const directions = ['С', 'СВ', 'В', 'ЮВ', 'Ю', 'ЮЗ', 'З', 'СЗ']; const index = Math.round(degrees / 45) % 8; return directions[index]; } getTimeAgo(timestamp) { const diff = Date.now() - timestamp; const minutes = Math.floor(diff / 60000); if (minutes < 1) return 'только что'; if (minutes === 1) return '1 минуту назад'; if (minutes < 5) return `${minutes} минуты назад`; if (minutes < 60) return `${minutes} минут назад`; const hours = Math.floor(minutes / 60); if (hours === 1) return '1 час назад'; if (hours < 5) return `${hours} часа назад`; return `${hours} часов назад`; } refreshWeather() { const city = document.getElementById('cityInput').value.trim(); if (city) { // Очищаем кэш для этого города this.clearCityCache(city); this.getWeather(); } } clearCityCache(city) { try { const cache = JSON.parse(localStorage.getItem(this.storageKey)) || {}; delete cache[city.toLowerCase()]; localStorage.setItem(this.storageKey, JSON.stringify(cache)); } catch (error) { console.error('Ошибка очистки кэша:', error); } } addToHistory(city, data) { try { const history = JSON.parse(localStorage.getItem(this.historyKey)) || []; const historyItem = { city: city, country: data.country, temperature: Math.round(data.current.temperature_2m), description: this.getWeatherDescription(data.current.weather_code), timestamp: Date.now() }; // Удаляем старые записи того же города const filteredHistory = history.filter(item => item.city.toLowerCase() !== city.toLowerCase() ); // Добавляем новую запись в начало filteredHistory.unshift(historyItem); // Ограничиваем историю 10 последними запросами const limitedHistory = filteredHistory.slice(0, 10); localStorage.setItem(this.historyKey, JSON.stringify(limitedHistory)); this.loadHistory(); } catch (error) { console.error('Ошибка сохранения истории:', error); } } loadHistory() { try { const history = JSON.parse(localStorage.getItem(this.historyKey)) || []; const historyList = document.getElementById('historyList'); const emptyHistory = document.getElementById('emptyHistory'); if (history.length === 0) { historyList.innerHTML = ''; emptyHistory.style.display = 'block'; return; } emptyHistory.style.display = 'none'; let historyHTML = ''; history.forEach(item => { const timeAgo = this.getTimeAgo(item.timestamp); historyHTML += ` <div class="history-item" onclick="weatherWidget.selectFromHistory('${item.city}')"> <div> <div class="history-city">${item.city}, ${item.country}</div> <div class="history-time">${timeAgo}</div> </div> <div class="history-temp">${item.temperature}°C</div> </div> `; }); historyList.innerHTML = historyHTML; } catch (error) { console.error('Ошибка загрузки истории:', error); } } selectFromHistory(city) { document.getElementById('cityInput').value = city; this.getWeather(); } showLoading() { const searchBtn = document.getElementById('searchBtn'); const originalText = searchBtn.textContent; searchBtn.innerHTML = '<span class="loading-spinner"></span>Загрузка...'; searchBtn.disabled = true; } hideLoading() { const searchBtn = document.getElementById('searchBtn'); searchBtn.textContent = 'Узнать погоду'; searchBtn.disabled = false; } showError(message) { const errorElement = document.getElementById('cityError'); errorElement.textContent = message; document.getElementById('cityInput').classList.add('error'); this.hideLoading(); } hideError() { const errorElement = document.getElementById('cityError'); const errorMessage = document.getElementById('errorMessage'); errorElement.textContent = ''; errorMessage.style.display = 'none'; document.getElementById('cityInput').classList.remove('error'); } handleError(error) { this.hideLoading(); const errorMessage = document.getElementById('errorMessage'); errorMessage.textContent = error.message; errorMessage.style.display = 'block'; document.getElementById('weatherResult').style.display = 'none'; console.error('Ошибка получения погоды:', error); } showStatus(message, type = 'info') { const statusElement = document.getElementById('status'); statusElement.textContent = message; statusElement.className = `status-message status-${type}`; setTimeout(() => { statusElement.textContent = ''; statusElement.className = 'status-message'; }, 4000); } } // Глобальная переменная для доступа из истории let weatherWidget; // Инициализация при загрузке DOM document.addEventListener('DOMContentLoaded', () => { weatherWidget = new WeatherWidget(); });