(function() { 'use strict'; // =========== 用户配置区域 - 请根据实际情况修改以下配置 =========== // 和风天气配置(每月限额免费)地址:“https://id.qweather.com/” const QWEATHER_API_KEY = "XXXXXXXXXX"; // 请替换为您的和风天气API Key const QWEATHER_API_HOST = "XXXXXXXXXX"; // 请替换为您的和风天气API Host // 高德地图配置(高德开放平台获取个人版本,有限额度免费)地址“https://lbs.amap.com/” const AMAP_API_KEY = "XXXXXXXXXX"; // 请替换为您的高德地图API Key // AI助手配置(建议使用魔搭社区,每日限额免费)地址:“https://www.modelscope.cn” const OPENAI_API_KEY = "XXXXXXXXXX"; // 请替换为您的AI API Key const OPENAI_MODEL = "XXXXXXXXXX"; // 请替换为您的AI模型名称,例如"Qwen/Qwen3-VL-235B-A22B-Instruct" const OPENAI_BASE_URL = "XXXXXXXXXX"; // 请替换为您的AI API地址,例如“https://api-inference.modelscope.cn/v1” // 个人信息配置 - 用于AI生活建议定制 const USER_PROFILE = { age: 26, // 您的年龄 gender: "男", // 您的性别 commuteDays: "周一至周六", // 通勤需求 commuteMethod: "地铁或公交", // 通勤方式 coreNeeds: "舒适生活、保持健康、便利出行、优化安排" // 核心需求 }; // 默认位置配置 const DEFAULT_LOCATION = "116.41,39.92"; // 默认经纬度坐标 const DEFAULT_LOCATION_NAME = "北京"; // 默认位置名称 // =========== 插件配置 - 以下配置一般无需修改 =========== const weatherConfig = { apiKey: QWEATHER_API_KEY, apiHost: QWEATHER_API_HOST, defaultLocation: DEFAULT_LOCATION, locationName: DEFAULT_LOCATION_NAME, mobileWidth: 768, autoRefresh: true, refreshInterval: 300000, // 5分钟 showOnPC: true, showOnMobile: false, enableAutoLocation: true, amapKey: AMAP_API_KEY, openaiApiKey: OPENAI_API_KEY, openaiModel: OPENAI_MODEL, openaiBaseUrl: OPENAI_BASE_URL, userProfile: USER_PROFILE // 新增用户配置 }; // =========== 配置结束 =========== // 工具函数 const utils = { isMobile: () => window.innerWidth <= weatherConfig.mobileWidth, debounce: (func, wait) => { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }, // 保存设置到本地存储 saveSettings(location, locationName) { try { localStorage.setItem('weather_location', location); localStorage.setItem('weather_location_name', locationName); localStorage.setItem('weather_auto_location_enabled', 'true'); } catch (e) { console.log('本地存储不可用'); } }, // 从本地存储加载设置 loadSettings() { try { const autoLocationEnabled = localStorage.getItem('weather_auto_location_enabled') === 'true'; return { location: localStorage.getItem('weather_location') || weatherConfig.defaultLocation, locationName: localStorage.getItem('weather_location_name') || weatherConfig.locationName, autoLocationEnabled: autoLocationEnabled }; } catch (e) { return { location: weatherConfig.defaultLocation, locationName: weatherConfig.locationName, autoLocationEnabled: false }; } }, // 获取星期几 getWeekday(dateStr) { const date = new Date(dateStr); const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; return weekdays[date.getDay()]; }, // 格式化时间 - 从和风天气API的时间格式中提取小时 formatTime(fxTime) { if (!fxTime) return ''; // 和风天气API返回格式: "2024-01-01T12:00+08:00" const timeMatch = fxTime.match(/T(\d{2}):/); return timeMatch ? `${timeMatch[1]}:00` : '00:00'; }, // 生成折线图的SVG路径 - 完全复刻原有逻辑 generateTemperatureLine(points, totalWidth, height, itemWidth) { if (points.length < 2) return ''; const allTemps = points.map(item => parseInt(item.temp)); const minTemp = Math.min(...allTemps); const maxTemp = Math.max(...allTemps); const range = maxTemp - minTemp; let path = ''; points.forEach((item, index) => { const temp = parseInt(item.temp); const heightPercent = range === 0 ? 50 : ((temp - minTemp) / range) * 75 + 17.5; const x = (index * itemWidth) + (itemWidth / 2); const y = height - (heightPercent / 100) * (height * 0.8); if (index === 0) { path = `M ${x} ${y}`; } else { path += ` L ${x} ${y}`; } }); return path; }, // 和风天气图标映射 getWeatherIcon(code) { const iconMap = { '100': 'fas fa-sun', // 晴 '101': 'fas fa-cloud-sun', // 多云 '102': 'fas fa-cloud-sun', // 少云 '103': 'fas fa-cloud', // 晴间多云 '104': 'fas fa-cloud', // 阴 '150': 'fas fa-moon', // 晴(夜) '151': 'fas fa-cloud-moon', // 多云(夜) '152': 'fas fa-cloud-moon', // 少云(夜) '153': 'fas fa-cloud', // 晴间多云(夜) '300': 'fas fa-cloud-rain', // 阵雨 '301': 'fas fa-cloud-rain', // 强阵雨 '302': 'fas fa-bolt', // 雷阵雨 '303': 'fas fa-bolt', // 强雷阵雨 '304': 'fas fa-cloud-showers-heavy', // 雷阵雨伴有冰雹 '305': 'fas fa-cloud-rain', // 小雨 '306': 'fas fa-cloud-rain', // 中雨 '307': 'fas fa-cloud-showers-heavy', // 大雨 '308': 'fas fa-cloud-showers-heavy', // 极端降雨 '309': 'fas fa-cloud-rain', // 毛毛雨 '310': 'fas fa-cloud-rain', // 暴雨 '311': 'fas fa-cloud-showers-heavy', // 大暴雨 '312': 'fas fa-cloud-showers-heavy', // 特大暴雨 '313': 'fas fa-cloud-rain', // 冻雨 '314': 'fas fa-cloud-rain', // 小到中雨 '315': 'fas fa-cloud-rain', // 中到大雨 '316': 'fas fa-cloud-showers-heavy', // 大到暴雨 '317': 'fas fa-cloud-showers-heavy', // 暴雨到大暴雨 '318': 'fas fa-cloud-showers-heavy', // 大暴雨到特大暴雨 '399': 'fas fa-cloud-rain', // 雨 '400': 'fas fa-snowflake', // 小雪 '401': 'fas fa-snowflake', // 中雪 '402': 'fas fa-snowflake', // 大雪 '403': 'fas fa-snowflake', // 暴雪 '404': 'fas fa-cloud-rain', // 雨夹雪 '405': 'fas fa-cloud-rain', // 雨雪天气 '406': 'fas fa-cloud-rain', // 阵雨夹雪 '407': 'fas fa-snowflake', // 阵雪 '408': 'fas fa-snowflake', // 小到中雪 '409': 'fas fa-snowflake', // 中到大雪 '410': 'fas fa-snowflake', // 大到暴雪 '499': 'fas fa-snowflake', // 雪 '500': 'fas fa-smog', // 薄雾 '501': 'fas fa-smog', // 雾 '502': 'fas fa-smog', // 霾 '503': 'fas fa-wind', // 扬沙 '504': 'fas fa-wind', // 浮尘 '507': 'fas fa-wind', // 沙尘暴 '508': 'fas fa-wind', // 强沙尘暴 '509': 'fas fa-smog', // 浓雾 '510': 'fas fa-smog', // 强浓雾 '511': 'fas fa-smog', // 中度霾 '512': 'fas fa-smog', // 重度霾 '513': 'fas fa-smog', // 严重霾 '514': 'fas fa-smog', // 大雾 '515': 'fas fa-smog', // 特强浓雾 '900': 'fas fa-temperature-high', // 热 '901': 'fas fa-temperature-low', // 冷 '999': 'fas fa-cloud' // 未知 }; return iconMap[code] || 'fas fa-cloud'; }, // 新增:通过IP定位获取位置信息 async getLocationByIP() { try { console.log('开始IP定位...'); // 使用高德地图IP定位API const response = await fetch(`https://restapi.amap.com/v3/ip?key=${weatherConfig.amapKey}`); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); console.log('高德IP定位返回:', data); if (data.status === '1' && data.province && data.city) { // 高德IP定位返回省份和城市 const province = data.province; const city = data.city; const rectangle = data.rectangle; // 格式:"左下角经度,左下角纬度;右上角经度,右上角纬度" // 计算矩形中心点作为坐标 let longitude, latitude; if (rectangle) { const coords = rectangle.split(';'); const bottomLeft = coords[0].split(',').map(Number); const topRight = coords[1].split(',').map(Number); longitude = ((bottomLeft[0] + topRight[0]) / 2).toFixed(6); latitude = ((bottomLeft[1] + topRight[1]) / 2).toFixed(6); } else { // 如果没有矩形区域,使用默认坐标 longitude = "116.397428"; latitude = "39.90923"; } const locationName = city === province ? city : `${city}, ${province}`; return { location: `${longitude},${latitude}`, locationName: locationName, source: 'ip' }; } else { throw new Error('高德IP定位未返回有效数据'); } } catch (error) { console.warn('高德IP定位失败:', error); // 如果高德IP定位失败,尝试使用其他IP定位服务作为备选 return await this.getLocationByIPFallback(); } }, // 新增:备用的IP定位服务 async getLocationByIPFallback() { try { console.log('尝试备用IP定位...'); const ipServices = [ 'https://ipapi.co/json/', 'https://api.ip.sb/geoip' ]; for (const serviceUrl of ipServices) { try { const response = await fetch(serviceUrl); if (!response.ok) { continue; } const data = await response.json(); console.log('备用IP定位服务返回:', data); let latitude, longitude, cityName; if (serviceUrl.includes('ipapi.co')) { latitude = data.latitude; longitude = data.longitude; cityName = data.city; } else if (serviceUrl.includes('ip.sb')) { latitude = data.latitude; longitude = data.longitude; cityName = data.city; } if (latitude && longitude) { // 使用高德逆地理编码获取城市名称 const chineseName = await this.reverseGeocodeAMap(longitude, latitude); return { location: `${longitude.toFixed(6)},${latitude.toFixed(6)}`, locationName: chineseName, source: 'ip_fallback' }; } } catch (error) { console.warn(`备用IP定位服务 ${serviceUrl} 失败:`, error); continue; } } throw new Error('所有IP定位服务都失败了'); } catch (error) { console.warn('备用IP定位失败:', error); throw error; } }, // 新增:通过浏览器定位获取位置信息 async getLocationByBrowser() { return new Promise((resolve, reject) => { if (!navigator.geolocation) { reject(new Error('浏览器不支持定位功能')); return; } console.log('请求浏览器定位权限...'); navigator.geolocation.getCurrentPosition( async (position) => { console.log('浏览器定位成功:', position); const latitude = position.coords.latitude; const longitude = position.coords.longitude; try { // 使用高德逆地理编码获取详细位置信息 const locationName = await this.reverseGeocodeAMap(longitude, latitude); resolve({ location: `${longitude.toFixed(6)},${latitude.toFixed(6)}`, locationName: locationName, source: 'browser' }); } catch (error) { console.warn('高德逆地理编码失败:', error); // 即使逆地理编码失败,也返回坐标信息 resolve({ location: `${longitude.toFixed(6)},${latitude.toFixed(6)}`, locationName: await this.getCityNameFromCoords(longitude, latitude), source: 'browser' }); } }, (error) => { console.error('浏览器定位失败:', error); let errorMessage = '浏览器定位失败'; switch (error.code) { case error.PERMISSION_DENIED: errorMessage = '定位权限被拒绝,请允许浏览器定位权限'; break; case error.POSITION_UNAVAILABLE: errorMessage = '无法获取位置信息'; break; case error.TIMEOUT: errorMessage = '定位请求超时'; break; } reject(new Error(errorMessage)); }, { enableHighAccuracy: true, timeout: 10000, maximumAge: 300000 // 5分钟缓存 } ); }); }, // 新增:使用高德地图逆地理编码API获取城市名称 async reverseGeocodeAMap(longitude, latitude) { try { console.log('开始高德逆地理编码:', longitude, latitude); const response = await fetch(`https://restapi.amap.com/v3/geocode/regeo?key=${weatherConfig.amapKey}&location=${longitude},${latitude}&extensions=base`); if (!response.ok) { throw new Error(`高德逆地理编码请求失败: HTTP ${response.status}`); } const data = await response.json(); console.log('高德逆地理编码返回:', data); if (data.status === '1' && data.regeocode) { const addressComponent = data.regeocode.addressComponent; const province = addressComponent.province; const city = addressComponent.city; const district = addressComponent.district; // 构建位置名称 let locationName; if (city && city !== province) { // 普通城市:城市, 省份 locationName = `${city}, ${province}`; } else if (city && city === province) { // 直辖市:城市 locationName = city; } else { // 其他情况:省份 locationName = province; } console.log('高德逆地理编码成功,位置名称:', locationName); return locationName; } else { throw new Error('高德逆地理编码未返回有效数据'); } } catch (error) { console.warn('高德逆地理编码失败:', error); throw error; } }, // 新增:从坐标生成简单的城市名称(备用方法) async getCityNameFromCoords(longitude, latitude) { try { // 使用高德逆地理编码作为主要方法 const locationName = await this.reverseGeocodeAMap(longitude, latitude); return locationName; } catch (e) { // 如果高德API失败,返回坐标信息 return `${latitude.toFixed(2)}, ${longitude.toFixed(2)}`; } }, // 新增:自动定位功能 async autoLocate() { try { // 首先尝试浏览器精确定位 console.log('尝试浏览器定位...'); const browserLocation = await this.getLocationByBrowser(); console.log('浏览器定位成功:', browserLocation); return browserLocation; } catch (browserError) { console.warn('浏览器定位失败,尝试IP定位:', browserError); try { // 浏览器定位失败后尝试IP定位 const ipLocation = await this.getLocationByIP(); console.log('IP定位成功:', ipLocation); return ipLocation; } catch (ipError) { console.error('所有定位方式都失败了:', ipError); throw new Error('自动定位失败:请检查定位权限或手动设置位置'); } } }, // 新增:AI建议API调用 async getAIAdvice(weatherData) { try { console.log('开始获取AI建议...'); // 构建用户信息和天气数据的提示词 const prompt = this.buildAIPrompt(weatherData); const response = await fetch(`${weatherConfig.openaiBaseUrl}/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${weatherConfig.openaiApiKey}` }, body: JSON.stringify({ model: weatherConfig.openaiModel, messages: [ { role: 'user', content: prompt } ], temperature: 0.7, max_tokens: 1000 }) }); if (!response.ok) { throw new Error(`AI API请求失败: HTTP ${response.status}`); } const data = await response.json(); if (data.choices && data.choices[0] && data.choices[0].message) { const adviceText = data.choices[0].message.content; return this.parseAIResponse(adviceText); } else { throw new Error('AI API返回数据格式错误'); } } catch (error) { console.error('获取AI建议失败:', error); // 返回默认建议作为降级方案 return this.getDefaultAdvice(weatherData); } }, // 新增:构建AI提示词 - 使用配置中的用户信息 buildAIPrompt(weatherData) { const userInfo = `个人信息: - 年龄:${weatherConfig.userProfile.age}岁 - 性别:${weatherConfig.userProfile.gender} - 通勤需求:${weatherConfig.userProfile.commuteDays}需要通勤 - 通勤方式:${weatherConfig.userProfile.commuteMethod} 核心需求:${weatherConfig.userProfile.coreNeeds}`; const weatherInfo = `当前天气数据: - 位置:${weatherData.location} - 当前温度:${weatherData.temperature}°C - 天气状况:${weatherData.weather} - 体感温度:${weatherData.feelsLike}°C - 湿度:${weatherData.humidity}% - 风速:${weatherData.windSpeed} km/h,风向:${weatherData.windDirection} - 今日温度范围:${weatherData.todayLow}°C ~ ${weatherData.todayHigh}°C - 日出时间:${weatherData.sunriseTime || '未知'} - 日落时间:${weatherData.sunsetTime || '未知'} 未来24小时趋势:${weatherData.hourlyForecast.slice(0, 8).map(h => `${h.time} ${h.temperature}°C ${h.weather}`).join('; ')} 未来7天预报:${weatherData.dailyForecast.slice(0, 3).map(d => `${d.weekday} ${d.lowTemp}~${d.highTemp}°C ${d.weather}`).join('; ')}`; return `你是一个专业的天气生活助手,请根据以下信息为一位${weatherConfig.userProfile.age}岁${weatherConfig.userProfile.gender}提供个性化天气建议。 ${userInfo} ${weatherInfo} 请针对以下5个方面提供具体、实用的建议,要求: 1. 个性化定制:结合用户年龄、性别、通勤方式 2. 数据融合:综合温度、湿度、风速、降水等多种要素 3. 趋势提醒:结合短期预报给出动态建议 4. 情景化:具体到出行、着装、健康等实际场景 5. 简洁实用:每个建议控制在40字以内 请严格按照以下格式返回,不要添加其他内容: 出行准备建议:[具体建议] 着装建议:[具体建议] 健康防护建议:[具体建议] 户外活动可行性建议:[具体建议] 通勤/交通提醒:[具体建议]`; }, // 新增:解析AI响应 parseAIResponse(responseText) { const adviceTypes = [ '出行准备建议', '着装建议', '健康防护建议', '户外活动可行性建议', '通勤/交通提醒' ]; const advice = {}; adviceTypes.forEach(type => { const regex = new RegExp(`${type}:([^\\n]+)`); const match = responseText.match(regex); advice[type] = match ? match[1].trim() : this.getDefaultAdviceForType(type); }); return advice; }, // 新增:获取默认建议(降级方案) getDefaultAdvice(weatherData) { const temp = parseInt(weatherData.temperature); const weather = weatherData.weather; const humidity = parseInt(weatherData.humidity); return { '出行准备建议': this.getTravelAdvice(temp, weather, humidity), '着装建议': this.getDressingAdvice(temp, weather), '健康防护建议': this.getHealthAdvice(temp, weather, humidity), '户外活动可行性建议': this.getOutdoorAdvice(temp, weather), '通勤/交通提醒': this.getCommuteAdvice(temp, weather) }; }, // 新增:根据温度天气生成出行建议 getTravelAdvice(temp, weather, humidity) { if (weather.includes('雨')) { return '今天有雨,建议携带雨具,选择防水背包和鞋子'; } else if (temp > 30) { return '天气炎热,建议避开正午高温时段出行,携带防晒用品'; } else if (temp < 5) { return '天气寒冷,外出注意保暖,建议使用保温杯携带热水'; } else { return '天气适宜,正常出行即可,建议携带轻便外套备用'; } }, // 新增:着装建议 getDressingAdvice(temp, weather) { if (temp > 28) { return '建议穿短袖、短裤等夏季服装,选择透气吸汗面料'; } else if (temp > 20) { return '建议穿长袖T恤、薄外套,搭配长裤,舒适透气'; } else if (temp > 10) { return '建议穿夹克、卫衣等秋季服装,注意早晚温差'; } else { return '建议穿羽绒服、厚外套,戴围巾手套,注意防寒保暖'; } }, // 新增:健康防护建议 getHealthAdvice(temp, weather, humidity) { const advice = []; if (humidity > 80) { advice.push('湿度较高,注意防潮除湿,保持室内通风'); } else if (humidity < 30) { advice.push('空气干燥,注意补水保湿,可使用加湿器'); } if (temp > 30) { advice.push('注意防暑降温,及时补充水分和电解质'); } else if (temp < 5) { advice.push('注意防寒保暖,预防感冒和呼吸道疾病'); } if (weather.includes('霾') || weather.includes('沙尘')) { advice.push('空气质量较差,建议佩戴防护口罩,减少户外活动'); } return advice.length > 0 ? advice.join(';') : '天气条件良好,保持正常生活习惯即可'; }, // 新增:户外活动建议 getOutdoorAdvice(temp, weather) { if (weather.includes('雨') || weather.includes('雪')) { return '不适宜户外活动,建议选择室内运动或改期进行'; } else if (weather.includes('雷')) { return '有雷电活动,严禁户外活动,务必待在室内'; } else if (temp > 35 || temp < -10) { return '温度极端,不适宜长时间户外活动,注意安全'; } else if (temp > 25 && temp < 30) { return '天气适宜户外活动,建议在早晚凉爽时段进行'; } else { return '条件允许户外活动,注意适时休息和补充水分'; } }, // 新增:通勤建议 getCommuteAdvice(temp, weather) { const advice = []; if (weather.includes('雨') || weather.includes('雪')) { advice.push('雨天路滑,骑行请注意安全,建议选择公共交通'); advice.push('预留额外通勤时间,注意车辆行驶安全'); } else if (weather.includes('雾') || weather.includes('霾')) { advice.push('能见度较低,骑行请开启车灯,减速慢行'); } else if (temp < 0) { advice.push('路面可能结冰,骑行需特别小心,建议选择地铁'); } else { advice.push('通勤条件良好,共享电单车+地铁是理想选择'); } return advice.join(';'); }, // 新增:获取默认类型建议 getDefaultAdviceForType(type) { const defaults = { '出行准备建议': '根据天气情况准备相应物品,注意温度变化', '着装建议': '根据温度选择合适的服装,注意舒适度', '健康防护建议': '关注天气变化,做好相应健康防护', '户外活动可行性建议': '根据天气条件评估户外活动适宜度', '通勤/交通提醒': '合理安排通勤方式,注意交通安全' }; return defaults[type] || '建议信息暂不可用'; } }; // 天气数据管理 - 保持原有代码100%不变,只新增日出日落数据 const weatherData = { current: null, currentLocation: utils.loadSettings(), async fetchWeatherData() { const location = this.currentLocation.location; try { // 并行获取所有天气数据 const [nowData, hourlyData, dailyData] = await Promise.all([ this.fetchApiData('/weather/now', location), this.fetchApiData('/weather/24h', location), this.fetchApiData('/weather/7d', location) ]); // 保存当前设置 utils.saveSettings(location, this.currentLocation.locationName); return this.formatWeatherData(nowData, hourlyData, dailyData); } catch (error) { console.error('天气API请求失败:', error); throw error; } }, async fetchApiData(endpoint, location) { const url = `https://${weatherConfig.apiHost}/v7${endpoint}?location=${location}`; const response = await fetch(url, { headers: { 'X-QW-Api-Key': weatherConfig.apiKey } }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); if (data.code === '200') { return data; } else { throw new Error(data.code || 'API返回错误'); } }, formatWeatherData(nowData, hourlyData, dailyData) { const now = nowData.now; const today = dailyData.daily[0]; return { location: this.currentLocation.locationName, temperature: now.temp || '--', weather: now.text || '未知', description: now.text || '未知天气', humidity: now.humidity || '--', windSpeed: now.windSpeed || '--', windDirection: now.windDir || '--', feelsLike: now.feelsLike || '--', windScale: now.windScale || '--', icon: utils.getWeatherIcon(now.icon), updateTime: this.formatUpdateTime(), todayHigh: dailyData.daily[0].tempMax || '--', todayLow: dailyData.daily[0].tempMin || '--', // 新增:日出日落时间 sunriseTime: today.sunrise || '--', sunsetTime: today.sunset || '--', hourlyForecast: this.formatHourlyData(hourlyData.hourly || []), dailyForecast: this.formatDailyData(dailyData.daily || []), rawData: { nowData, hourlyData, dailyData } }; }, formatHourlyData(hourlyData) { if (!hourlyData || !Array.isArray(hourlyData)) { return this.generateMockHourlyData(); } console.log('和风天气API返回的小时数据点数量:', hourlyData.length); // 使用和风天气返回的24小时数据,确保每个小时一个数据点 return hourlyData.slice(0, 24).map((hour, index) => ({ time: utils.formatTime(hour.fxTime), temperature: parseInt(hour.temp), weather: hour.text, icon: utils.getWeatherIcon(hour.icon), precipitation: hour.precip || '0mm' })); }, formatDailyData(dailyData) { const days = []; const today = new Date(); dailyData.slice(0, 7).forEach((day, index) => { const date = new Date(today); date.setDate(today.getDate() + index); days.push({ weekday: index === 0 ? '今天' : utils.getWeekday(day.fxDate), highTemp: day.tempMax, lowTemp: day.tempMin, weather: day.textDay, weatherText: day.textDay, icon: utils.getWeatherIcon(day.iconDay) }); }); return days; }, generateMockHourlyData() { const hours = []; const now = new Date(); const currentHour = now.getHours(); // 生成24小时模拟数据 - 每个小时一个点 let baseTemp = 20; for (let i = 0; i < 24; i++) { const hour = (currentHour + i) % 24; // 模拟温度变化:白天高,夜晚低 const variation = Math.sin((i / 24) * Math.PI * 2) * 8; const temperature = Math.round(baseTemp + variation); // 模拟天气变化 let weather = '晴'; let iconCode = '100'; if (hour >= 18 || hour <= 6) { weather = '晴'; iconCode = '150'; // 夜间晴 } else if (hour >= 12 && hour <= 15) { weather = '多云'; iconCode = '101'; } else if (hour >= 8 && hour <= 11) { weather = '晴'; iconCode = '100'; } hours.push({ time: `${hour.toString().padStart(2, '0')}:00`, temperature: temperature, weather: weather, icon: utils.getWeatherIcon(iconCode), precipitation: '0mm' }); } return hours; }, formatUpdateTime() { const now = new Date(); return `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`; }, // 模拟数据作为备用 getMockData() { return { location: this.currentLocation.locationName, temperature: 15 + Math.floor(Math.random() * 15), weather: '晴', description: '晴朗', humidity: 40 + Math.floor(Math.random() * 40), windSpeed: (2 + Math.random() * 5).toFixed(1), windDirection: '东南风', feelsLike: 13 + Math.floor(Math.random() * 15), windScale: '2-3级', icon: utils.getWeatherIcon('100'), updateTime: '刚刚', todayHigh: 25, todayLow: 15, sunriseTime: '06:30', sunsetTime: '18:45', hourlyForecast: this.generateMockHourlyData(), dailyForecast: this.generateMockDailyData(), isMock: true }; }, generateMockDailyData() { const days = []; const today = new Date(); const conditions = ['100', '101', '104', '305']; // 晴, 多云, 阴, 小雨 for (let i = 0; i < 7; i++) { const date = new Date(today); date.setDate(today.getDate() + i); const condition = conditions[Math.floor(Math.random() * conditions.length)]; days.push({ weekday: i === 0 ? '今天' : utils.getWeekday(date), highTemp: 20 + Math.floor(Math.random() * 10), lowTemp: 10 + Math.floor(Math.random() * 8), weather: this.getWeatherText(condition), weatherText: this.getWeatherText(condition), icon: utils.getWeatherIcon(condition) }); } return days; }, getWeatherText(iconCode) { const textMap = { '100': '晴', '101': '多云', '104': '阴', '305': '小雨' }; return textMap[iconCode] || '晴'; } }; // UI管理 - 新增日出日落显示和AI建议面板 const weatherUI = { init() { this.createContainer(); this.addStyles(); this.bindGlobalEvents(); }, createContainer() { if (document.getElementById('qweather-widget')) return; const container = document.createElement('div'); container.id = 'qweather-widget'; container.innerHTML = `