From 1e70af61105c3a7bd4a4b40c964cf5004895c490 Mon Sep 17 00:00:00 2001 From: George Date: Thu, 25 Dec 2025 08:56:24 +0800 Subject: [PATCH] =?UTF-8?q?20251225:=E8=AE=BE=E5=A4=87=E4=B8=8A=E7=94=B5?= =?UTF-8?q?=E8=A7=A6=E5=8F=91=E4=BB=BB=E5=8A=A1=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/fastbee/common/config/DeviceTask.java | 6 + .../common/constant/FastBeeConstant.java | 7 + .../common/core/redis/RedisKeyBuilder.java | 8 + .../service/IDeviceReportMessageService.java | 6 + .../impl/DeviceReportMessageServiceImpl.java | 49 ++++ .../com/fastbee/iot/domain/ModbusJob.java | 3 +- .../fastbee/iot/service/IDeviceService.java | 12 + .../iot/service/impl/DeviceServiceImpl.java | 72 +++++- vue/.env.development | 2 +- vue/src/views/index.vue | 229 ++++++++++++++---- 10 files changed, 340 insertions(+), 54 deletions(-) diff --git a/fastbee-common/src/main/java/com/fastbee/common/config/DeviceTask.java b/fastbee-common/src/main/java/com/fastbee/common/config/DeviceTask.java index a9fe5c20..be51c4db 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/config/DeviceTask.java +++ b/fastbee-common/src/main/java/com/fastbee/common/config/DeviceTask.java @@ -92,6 +92,12 @@ public class DeviceTask { return builder(FastBeeConstant.TASK.DEVICE_TEST_TASK); } + @Bean(FastBeeConstant.TASK.TRIGGER_POWER_ON_TASK_ASYNC) + public Executor triggerPowerOnTaskExecutor() { + return builder(FastBeeConstant.TASK.TRIGGER_POWER_ON_TASK_ASYNC); + } + + /*组装线程池*/ private ThreadPoolTaskExecutor builder(String threadNamePrefix) { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); diff --git a/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java b/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java index 9d011c36..b7bcc242 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java +++ b/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java @@ -96,6 +96,8 @@ public interface FastBeeConstant { /*OTA升级线程池*/ String OTA_THREAD_POOL = "otaThreadPoolTaskExecutor"; + // 设备上电即触发异步任务 TriggerPowerOnTaskAsync + String TRIGGER_POWER_ON_TASK_ASYNC = "triggerPowerOnTaskAsync"; } interface MQTT { @@ -280,6 +282,11 @@ public interface FastBeeConstant { */ String POWER_ON_TRIGGER_COMMAND = "power:on:trigger:command:"; + /** + * 设备上电触发任务待触发标记 + */ + String POWER_ON_TRIGGER_PENDING = "power:on:trigger:pending:"; + } interface PROTOCOL { diff --git a/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyBuilder.java b/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyBuilder.java index d0634bb7..a56f4b25 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyBuilder.java +++ b/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyBuilder.java @@ -166,5 +166,13 @@ public class RedisKeyBuilder { return FastBeeConstant.REDIS.POWER_ON_TRIGGER_COMMAND + serialNumber.toUpperCase(); } + /** + * 设备上电触发任务待触发标记key + * 用于标记设备是否需要在上电后收到第一个数据包时触发任务 + */ + public static String buildPowerOnTriggerPendingKey(String serialNumber) { + return FastBeeConstant.REDIS.POWER_ON_TRIGGER_PENDING + serialNumber.toUpperCase(); + } + } diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/IDeviceReportMessageService.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/IDeviceReportMessageService.java index 63029181..bf31c8a3 100644 --- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/IDeviceReportMessageService.java +++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/IDeviceReportMessageService.java @@ -47,4 +47,10 @@ public interface IDeviceReportMessageService { */ IProtocol selectedProtocol(Long productId); + /** + * 异步检查并触发设备上电任务(在收到第一个数据包时) + * @param serialNumber 设备编号 + */ + void checkAndTriggerPowerOnTaskAsync(String serialNumber); + } diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DeviceReportMessageServiceImpl.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DeviceReportMessageServiceImpl.java index d6d071bc..ebb9614d 100644 --- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DeviceReportMessageServiceImpl.java +++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DeviceReportMessageServiceImpl.java @@ -16,11 +16,13 @@ import com.fastbee.common.core.redis.RedisKeyBuilder; import com.fastbee.common.enums.*; import com.fastbee.common.exception.ServiceException; import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.StringUtils; import com.fastbee.common.utils.gateway.mq.TopicsUtils; import com.fastbee.iot.cache.IOtaTaskCache; import com.fastbee.iot.data.service.IDataHandler; import com.fastbee.iot.data.service.IDeviceReportMessageService; import com.fastbee.iot.data.service.IFirmwareTaskDetailService; +import com.fastbee.iot.domain.Device; import com.fastbee.iot.domain.FunctionLog; import com.fastbee.iot.domain.Product; import com.fastbee.iot.domain.ProductAuthorize; @@ -40,6 +42,8 @@ import io.netty.buffer.Unpooled; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import javax.annotation.Resource; @@ -79,6 +83,9 @@ public class DeviceReportMessageServiceImpl implements IDeviceReportMessageServi private TopicsUtils topicsUtils; @Resource private MqttRemoteManager remoteManager; + + @Resource + private ApplicationContext applicationContext; // self注入,用于异步调用 /** * 处理设备主动上报数据 @@ -86,6 +93,11 @@ public class DeviceReportMessageServiceImpl implements IDeviceReportMessageServi @Override public void parseReportMsg(DeviceReportBo bo) { String serialNumber = bo.getSerialNumber(); + + // 异步检查是否需要触发上电任务(收到第一个数据包时触发) + DeviceReportMessageServiceImpl bean = applicationContext.getBean(DeviceReportMessageServiceImpl.class); + bean.checkAndTriggerPowerOnTaskAsync(serialNumber); + switch (bo.getServerType()) { case MQTT: log.debug("=>MQ*收到设备主题[{}],消息:[{}]", bo.getTopicName(), bo.getData()); @@ -233,6 +245,43 @@ public class DeviceReportMessageServiceImpl implements IDeviceReportMessageServi } } + /** + * 异步检查并触发设备上电任务(在收到第一个数据包时) + * @param serialNumber 设备编号 + */ + @Async(FastBeeConstant.TASK.TRIGGER_POWER_ON_TASK_ASYNC) + public void checkAndTriggerPowerOnTaskAsync(String serialNumber) { + try { + if (StringUtils.isEmpty(serialNumber)) { + return; + } + + String cacheKey = RedisKeyBuilder.buildPowerOnTriggerPendingKey(serialNumber.toUpperCase()); + // 检查是否有待触发的上电任务标记 + Object pendingFlag = redisCache.getCacheObject(cacheKey); + if (pendingFlag == null) { + // 没有待触发的任务,直接返回 + return; + } + + // 有待触发的任务,删除标记(确保只触发一次) + redisCache.deleteObject(cacheKey); + + // 获取设备信息 + Device device = deviceService.selectDeviceBySerialNumber(serialNumber); + if (device == null) { + log.warn("触发上电任务时设备不存在,设备编号:{}", serialNumber); + return; + } + + // 触发上电任务(异步执行) + log.info("收到设备第一个数据包,触发上电任务,设备编号:{}", serialNumber); + deviceService.triggerPowerOnTasks(device); + + } catch (Exception e) { + log.error("检查并触发设备上电任务异常,设备编号:{}", serialNumber, e); + } } + /** * 构建消息 * diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/ModbusJob.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/ModbusJob.java index 498baa13..5d163e0d 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/ModbusJob.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/ModbusJob.java @@ -7,6 +7,7 @@ import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; +import java.util.Date; import java.util.List; /** @@ -84,6 +85,6 @@ public class ModbusJob extends BaseEntity { * 执行时间(仅设备上电触发任务使用,记录任务执行的时间) */ @ApiModelProperty("执行时间") - private java.util.Date executeTime; + private Date executeTime; } diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IDeviceService.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IDeviceService.java index 66476543..b6f5b78d 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IDeviceService.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IDeviceService.java @@ -185,6 +185,18 @@ public interface IDeviceService { */ public int updateDeviceStatusAndLocation(Device device, String ipAddress); + /** + * 标记设备上电任务待处理 + * @param device 设备信息 + */ + void markPowerOnTriggerPending(Device device); + + /** + * 触发设备上电任务(供外部调用,如收到第一个数据包时) + * @param device 设备信息 + */ + public void triggerPowerOnTasks(Device device); + /** * 更新设备状态 * diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java index 6952ed44..1098403b 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java @@ -57,6 +57,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @@ -124,6 +125,9 @@ public class DeviceServiceImpl implements IDeviceService { @Resource private IThingsModelService thingsModelService; + @Resource + private ApplicationContext applicationContext; + @Resource private EventLogMapper eventLogMapper; @Resource @@ -1367,24 +1371,74 @@ public class DeviceServiceImpl implements IDeviceService { } eventLogMapper.insert(event); - // 设备上线时,检查并执行设备上电触发任务 + // 设备上线时,标记需要触发上电任务(等待第一个数据包) if (device.getStatus() == 3) { - handlePowerOnTriggerTasks(device); + DeviceServiceImpl bean = applicationContext.getBean(DeviceServiceImpl.class); + bean.markPowerOnTriggerPending(device); } return result; } /** - * 处理设备上电触发任务 - * + * 标记设备需要触发上电任务(等待第一个数据包) * @param device 设备信息 */ - @Async - private void handlePowerOnTriggerTasks(Device device) { + @Async(FastBeeConstant.TASK.TRIGGER_POWER_ON_TASK_ASYNC) + @Override + public void markPowerOnTriggerPending(Device device) { try { - // 延迟2秒执行,确保设备完全上线 - //Thread.sleep(2000); + // 查询该设备的所有设备上电触发任务 + DeviceJob queryJob = new DeviceJob(); + queryJob.setDeviceId(device.getDeviceId()); + queryJob.setJobType(5); // modbus轮询任务 + queryJob.setStatus("0"); // 正常状态 + + List jobList = deviceJobService.selectJobList(queryJob); + if (CollectionUtils.isEmpty(jobList)) { + return; + } + + // 检查是否有上电触发任务 + boolean hasPowerOnTrigger = false; + for (DeviceJob job : jobList) { + if (isPowerOnTrigger(job.getRemark())) { + hasPowerOnTrigger = true; + break; + } + } + + // 如果有上电触发任务,则在Redis中标记,等待第一个数据包 + // 无论标记是否存在,都重新设置标记,确保恢复任务后能重新触发 + if (hasPowerOnTrigger) { + String serialNumber = device.getSerialNumber().toUpperCase(); + String cacheKey = RedisKeyBuilder.buildPowerOnTriggerPendingKey(serialNumber); + // 设置过期时间为10分钟,防止长时间未收到数据包导致标记一直存在 + // 无论之前是否有标记,都重新设置,确保恢复任务后能重新触发 + redisCache.setCacheObject(cacheKey, "1", 600, TimeUnit.SECONDS); + log.info("标记设备需要触发上电任务,等待第一个数据包,设备编号:{}", serialNumber); + } else { + // 如果没有上电触发任务,清除可能存在的旧标记 + String serialNumber = device.getSerialNumber().toUpperCase(); + String cacheKey = RedisKeyBuilder.buildPowerOnTriggerPendingKey(serialNumber); + if (redisCache.hasKey(cacheKey)) { + redisCache.deleteObject(cacheKey); + log.debug("清除设备上电触发任务标记,设备编号:{}", serialNumber); + } + } + } catch (Exception e) { + log.error("标记设备上电触发任务异常,设备编号:{}", device.getSerialNumber(), e); + } + } + + /** + * 触发设备上电任务(收到第一个数据包时) + * @param device 设备信息 + */ + public void triggerPowerOnTasks(Device device) { + try { + // 延迟1秒执行,确保设备完全上线并准备好接收指令 + //Thread.sleep(1000); // 查询该设备的所有设备上电触发任务 DeviceJob queryJob = new DeviceJob(); @@ -1712,7 +1766,7 @@ public class DeviceServiceImpl implements IDeviceService { */ private void setLatitudeAndLongitude(String city, Device device) throws UnsupportedEncodingException { String BAIDU_URL = "http://api.map.baidu.com/geocoding/v3/"; - city = StrUtil.isEmpty(city) ? "江苏省海安市城东镇" : city; + city = StrUtil.isEmpty(city) ? "湖南省长沙市岳麓区" : city; String encodedAddress = URLEncoder.encode(city, StandardCharsets.UTF_8.toString()); String baiduResponse = HttpUtils.sendGet(BAIDU_URL, "address=" + encodedAddress + "&output=json&ak=" + baiduAK, Constants.UTF8); if (!StringUtils.isEmpty(baiduResponse)) { diff --git a/vue/.env.development b/vue/.env.development index ea4699c0..a4dcd513 100644 --- a/vue/.env.development +++ b/vue/.env.development @@ -15,7 +15,7 @@ VUE_APP_SERVER_API_URL = http://localhost:8080 VUE_APP_MQTT_SERVER_URL = ws://localhost:8083/mqtt # 百度地图 API Key -VUE_APP_BAI_DU_AK = hxAMWdceoYQ2laSOzL1OEddHj4fx3Ah9 +VUE_APP_BAI_DU_AK = s9mUXN9a7bKgVu6U6Z3bXhKSxtz97YPI # OSS API 地址(生产环境使用) VUE_APP_OSS_API = diff --git a/vue/src/views/index.vue b/vue/src/views/index.vue index 2be10d2d..a2e1e2a2 100644 --- a/vue/src/views/index.vue +++ b/vue/src/views/index.vue @@ -14,7 +14,10 @@ >
-

{{ weatherData.city }}

+

+ {{ weatherData.city }} + +

{{ weatherData.data.date }} {{ weatherData.data.week }} @@ -242,6 +245,36 @@
+ + + + + +
+

常用城市:

+
+ + {{ city }} + +
+
+ +
+ Copyright © 2021-2024 @@ -276,20 +309,40 @@ import { searchUserCount } from '@/api/monitor/jobLog'; import { getNettyMqttStats, statisticNettyMqtt } from '@/api/iot/netty'; // import { count } from 'echarts/types/src/component/dataZoom/history.js'; +// 获取当前日期和星期 +function getCurrentDateAndWeek() { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const date = `${year}-${month}-${day}`; + + const weekDays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']; + const week = weekDays[now.getDay()]; + + return { date, week }; +} + export default { name: 'Index', components: { CountTo, }, data() { + // 获取当前日期和星期 + const { date, week } = getCurrentDateAndWeek(); + + // 从localStorage读取保存的城市,如果没有则使用默认值 + const savedCity = localStorage.getItem('weatherCity') || '北京市'; + return { //天气信息 weatherData: { success: true, - city: '北京市', + city: savedCity, data: { - date: '2024-03-14', - week: '星期四', + date: date, + week: week, type: '晴', typeIcon: require('../../src/assets/icons/qing.png'), // 确保路径正确 low: '5°C', @@ -304,6 +357,11 @@ export default { }, tip: '天有点冷,注意保暖~', }, + // 城市选择对话框 + showCityDialog: false, + cityInput: '', + // 常用城市列表 + commonCities: ['北京市', '上海市', '广州市', '深圳市', '杭州市', '南京市', '成都市', '武汉市', '西安市', '重庆市', '天津市', '苏州市', '郑州市', '长沙市', '东莞市', '佛山市', '宁波市', '无锡市', '青岛市', '大连市'], // mqtt状态数据 stats: {}, // mqtt统计信息 @@ -407,7 +465,16 @@ export default { getChartData() { searchUserCount().then((response) => { console.log('折线图信息', response); - response.data.reverse().forEach((item) => { + // 清空之前的数据 + this.linechart.date = []; + this.linechart.counts = []; + + // 按日期排序(从早到晚) + const sortedData = [...response.data].sort((a, b) => { + return new Date(a.datetime) - new Date(b.datetime); + }); + + sortedData.forEach((item) => { this.linechart.date.push(item.datetime); this.linechart.counts.push(item.user_count); }); @@ -561,58 +628,134 @@ export default { }, // 天气 - async fetchWeather() { + async fetchWeather(cityName) { this.isLoading = true; this.error = null; + // 获取当前日期和星期作为默认值 + const { date, week } = getCurrentDateAndWeek(); + + // 使用传入的城市名或当前保存的城市 + const city = cityName || this.weatherData.city; + try { - const response = await axios.get('https://api.vvhan.com/api/weather'); + // 尝试使用城市参数调用API,如果API不支持则使用默认调用 + let apiUrl = 'https://api.vvhan.com/api/weather'; + // 某些天气API支持城市参数,这里尝试添加 + // 如果API不支持,可以尝试其他天气API或使用IP定位 + try { + // 尝试带城市参数的API调用 + const response = await axios.get(apiUrl, { + params: { city: city } + }); + if (response.data && response.data.success) { + this.updateWeatherData(response.data, date, week, city); + return; + } + } catch (e) { + // 如果带参数的调用失败,尝试不带参数的调用 + console.log('带城市参数的API调用失败,尝试默认调用'); + } + + // 默认API调用(可能不支持城市参数,使用IP定位) + const response = await axios.get(apiUrl); if (response.data && response.data.success) { - console.log(response.data); - this.weatherData = { - success: true, - city: response.data.city, - data: { - date: response.data.data.date, - week: response.data.data.week, - type: response.data.data.type, - typeIcon: this.getTypeIcon(response.data.data.type), - low: response.data.data.low, - high: response.data.data.high, - fengxiang: response.data.data.fengxiang, - fengli: response.data.data.fengli, - night: { - type: response.data.data.night.type, - fengxiang: response.data.data.night.fengxiang, - fengli: response.data.data.night.fengli, - }, - }, - air: { - aqi: response.data.air.aqi, - aqi_level: response.data.air.aqi_level, - aqi_name: response.data.air.aqi_name, - co: response.data.air.co, - no2: response.data.air.no2, - o3: response.data.air.o3, - pm10: response.data.air.pm10, - pm2_5: response.data.air.pm2_5, - so2: response.data.air.so2, - }, - tip: response.data.tip, - }; - // 动态设置背景颜色 - const background = this.getBackgroundColor(response.data.data.type); - document.documentElement.style.setProperty('--background-start', background.start); - document.documentElement.style.setProperty('--background-end', background.end); + // 如果API返回的城市与设置的城市不同,使用设置的城市名称 + const apiCity = response.data.city || city; + this.updateWeatherData(response.data, date, week, city); } else { + // API返回失败,使用当前日期更新 + this.weatherData.data.date = date; + this.weatherData.data.week = week; + this.weatherData.city = city; // 保持用户设置的城市 this.error = '获取天气数据失败,请稍后重试。'; } } catch (err) { console.error(err); + // API调用失败,使用当前日期更新 + this.weatherData.data.date = date; + this.weatherData.data.week = week; + this.weatherData.city = city; // 保持用户设置的城市 this.error = '无法连接到天气服务,请检查网络或稍后再试。'; } finally { this.isLoading = false; } }, + // 更新天气数据 + updateWeatherData(responseData, date, week, city) { + this.weatherData = { + success: true, + city: city, // 使用用户设置的城市 + data: { + date: responseData.data?.date || date, + week: responseData.data?.week || week, + type: responseData.data.type, + typeIcon: this.getTypeIcon(responseData.data.type), + low: responseData.data.low, + high: responseData.data.high, + fengxiang: responseData.data.fengxiang, + fengli: responseData.data.fengli, + night: { + type: responseData.data.night.type, + fengxiang: responseData.data.night.fengxiang, + fengli: responseData.data.night.fengli, + }, + }, + air: { + aqi: responseData.air.aqi, + aqi_level: responseData.air.aqi_level, + aqi_name: responseData.air.aqi_name, + co: responseData.air.co, + no2: responseData.air.no2, + o3: responseData.air.o3, + pm10: responseData.air.pm10, + pm2_5: responseData.air.pm2_5, + so2: responseData.air.so2, + }, + tip: responseData.tip, + }; + // 动态设置背景颜色 + const background = this.getBackgroundColor(responseData.data.type); + document.documentElement.style.setProperty('--background-start', background.start); + document.documentElement.style.setProperty('--background-end', background.end); + }, + // 打开城市选择对话框 + openCityDialog() { + this.cityInput = this.weatherData.city; + this.showCityDialog = true; + }, + // 选择城市(点击常用城市标签) + selectCity(city) { + this.cityInput = city; + }, + // 确认城市选择 + confirmCity() { + const city = this.cityInput.trim(); + if (!city) { + this.$message.warning('请输入城市名称'); + return; + } + + // 如果城市没有变化,直接关闭对话框 + if (city === this.weatherData.city) { + this.showCityDialog = false; + return; + } + + // 保存城市到localStorage + localStorage.setItem('weatherCity', city); + + // 更新当前城市 + this.weatherData.city = city; + + // 关闭对话框 + this.showCityDialog = false; + this.cityInput = ''; + + // 重新获取天气数据 + this.fetchWeather(city); + + this.$message.success(`已切换到 ${city} 的天气信息`); + }, getBackgroundColor(type) { const backgrounds = { 晴: {