|
|
|
|
@ -2,119 +2,34 @@
|
|
|
|
|
* @Author: chris
|
|
|
|
|
* @Date: 2025-08-25 15:51:13
|
|
|
|
|
* @LastEditors: chris
|
|
|
|
|
* @LastEditTime: 2025-09-04 11:02:47
|
|
|
|
|
* @LastEditTime: 2025-10-14 15:36:47
|
|
|
|
|
-->
|
|
|
|
|
<script setup>
|
|
|
|
|
import { Sunny, Pouring, WindPower, Odometer, Sunrise, Guide } from '@element-plus/icons-vue';
|
|
|
|
|
import { Sunny, Pouring, WindPower, Sunrise, Guide } from '@element-plus/icons-vue';
|
|
|
|
|
import { weatherTypes, weatherDict } from '../config.js';
|
|
|
|
|
|
|
|
|
|
// 使用proxy访问全局$echarts
|
|
|
|
|
const { proxy } = getCurrentInstance();
|
|
|
|
|
const echarts = proxy?.$echarts;
|
|
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
weatherInfo: {
|
|
|
|
|
type: Object,
|
|
|
|
|
default: () => ({
|
|
|
|
|
// 默认模拟数据
|
|
|
|
|
current: {
|
|
|
|
|
temperature: 25.5,
|
|
|
|
|
humidity: 65,
|
|
|
|
|
windSpeed: 3.2,
|
|
|
|
|
windDirection: '东北',
|
|
|
|
|
rainfall: 0,
|
|
|
|
|
pressure: 1013,
|
|
|
|
|
uvIndex: 5,
|
|
|
|
|
visibility: 10
|
|
|
|
|
},
|
|
|
|
|
forecast: Array(24).fill().map((_, index) => ({
|
|
|
|
|
time: `${(new Date().getHours() + index) % 24}:00`,
|
|
|
|
|
temperature: 20 + Math.sin(index / 3) * 8 + Math.random() * 2,
|
|
|
|
|
humidity: 50 + Math.cos(index / 3) * 20 + Math.random() * 5,
|
|
|
|
|
rainfall: Math.random() > 0.8 ? Math.random() * 2 : 0,
|
|
|
|
|
windSpeed: 2 + Math.sin(index / 2) * 2 + Math.random() * 1,
|
|
|
|
|
uvIndex: 3 + Math.sin(index / 4) * 3 + Math.random() * 1
|
|
|
|
|
}))
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
weatherData: {
|
|
|
|
|
type: Array,
|
|
|
|
|
default: () => []
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const chartRef = ref(null);
|
|
|
|
|
let chartInstance = null;
|
|
|
|
|
const formateData = ref({})
|
|
|
|
|
const chartData = ref({})
|
|
|
|
|
// 当前选中的气象指标类型
|
|
|
|
|
const selectedMetric = ref('temperature');
|
|
|
|
|
|
|
|
|
|
// 气象指标配置
|
|
|
|
|
const metricConfig = {
|
|
|
|
|
temperature: {
|
|
|
|
|
name: '温度',
|
|
|
|
|
unit: '°C',
|
|
|
|
|
color: '#ff6b6b',
|
|
|
|
|
dataKey: 'temperature',
|
|
|
|
|
min: 0,
|
|
|
|
|
max: 40,
|
|
|
|
|
interval: 10,
|
|
|
|
|
gradient: echarts ? new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
|
|
{ offset: 0, color: 'rgba(255, 107, 107, 0.5)' },
|
|
|
|
|
{ offset: 1, color: 'rgba(255, 107, 107, 0.1)' }
|
|
|
|
|
]) : null
|
|
|
|
|
},
|
|
|
|
|
humidity: {
|
|
|
|
|
name: '湿度',
|
|
|
|
|
unit: '%',
|
|
|
|
|
color: '#4dabf7',
|
|
|
|
|
dataKey: 'humidity',
|
|
|
|
|
min: 0,
|
|
|
|
|
max: 100,
|
|
|
|
|
interval: 20,
|
|
|
|
|
gradient: echarts ? new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
|
|
{ offset: 0, color: 'rgba(77, 171, 247, 0.5)' },
|
|
|
|
|
{ offset: 1, color: 'rgba(77, 171, 247, 0.1)' }
|
|
|
|
|
]) : null
|
|
|
|
|
},
|
|
|
|
|
windSpeed: {
|
|
|
|
|
name: '风速',
|
|
|
|
|
unit: 'm/s',
|
|
|
|
|
color: '#00b894',
|
|
|
|
|
dataKey: 'windSpeed',
|
|
|
|
|
min: 0,
|
|
|
|
|
max: 10,
|
|
|
|
|
interval: 2,
|
|
|
|
|
gradient: echarts ? new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
|
|
{ offset: 0, color: 'rgba(0, 184, 148, 0.5)' },
|
|
|
|
|
{ offset: 1, color: 'rgba(0, 184, 148, 0.1)' }
|
|
|
|
|
]) : null
|
|
|
|
|
},
|
|
|
|
|
rainfall: {
|
|
|
|
|
name: '降雨量',
|
|
|
|
|
unit: 'mm',
|
|
|
|
|
color: '#74b9ff',
|
|
|
|
|
dataKey: 'rainfall',
|
|
|
|
|
min: 0,
|
|
|
|
|
max: 10,
|
|
|
|
|
interval: 2,
|
|
|
|
|
gradient: echarts ? new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
|
|
{ offset: 0, color: 'rgba(116, 185, 255, 0.5)' },
|
|
|
|
|
{ offset: 1, color: 'rgba(116, 185, 255, 0.1)' }
|
|
|
|
|
]) : null
|
|
|
|
|
},
|
|
|
|
|
uvIndex: {
|
|
|
|
|
name: '紫外线',
|
|
|
|
|
unit: '级',
|
|
|
|
|
color: '#fdcb6e',
|
|
|
|
|
dataKey: 'uvIndex',
|
|
|
|
|
min: 0,
|
|
|
|
|
max: 10,
|
|
|
|
|
interval: 2,
|
|
|
|
|
gradient: echarts ? new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
|
|
{ offset: 0, color: 'rgba(253, 203, 110, 0.5)' },
|
|
|
|
|
{ offset: 1, color: 'rgba(253, 203, 110, 0.1)' }
|
|
|
|
|
]) : null
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
const selectedMetric = ref('Temperature');
|
|
|
|
|
|
|
|
|
|
// 处理指标点击事件
|
|
|
|
|
const handleMetricClick = (metric) => {
|
|
|
|
|
selectedMetric.value = metric;
|
|
|
|
|
chartData.value = createChartData(formateData.value, selectedMetric.value)
|
|
|
|
|
updateChart();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@ -133,9 +48,9 @@ const initChart = () => {
|
|
|
|
|
|
|
|
|
|
// 更新图表数据
|
|
|
|
|
const updateChart = () => {
|
|
|
|
|
if (!chartInstance || !echarts || !props.weatherInfo.forecast) return;
|
|
|
|
|
if (!chartInstance || !echarts) return;
|
|
|
|
|
|
|
|
|
|
const config = metricConfig[selectedMetric.value];
|
|
|
|
|
const config = weatherDict[selectedMetric.value];
|
|
|
|
|
|
|
|
|
|
const option = {
|
|
|
|
|
title: {
|
|
|
|
|
@ -175,7 +90,7 @@ const updateChart = () => {
|
|
|
|
|
xAxis: {
|
|
|
|
|
type: 'category',
|
|
|
|
|
boundaryGap: false,
|
|
|
|
|
data: props.weatherInfo.forecast.map(item => item.time),
|
|
|
|
|
data: chartData.value.dates,
|
|
|
|
|
axisLine: {
|
|
|
|
|
lineStyle: {
|
|
|
|
|
color: '#464646'
|
|
|
|
|
@ -216,17 +131,7 @@ const updateChart = () => {
|
|
|
|
|
{
|
|
|
|
|
name: `${config.name}(${config.unit})`,
|
|
|
|
|
type: 'line',
|
|
|
|
|
data: props.weatherInfo.forecast.map(item => {
|
|
|
|
|
// 风速数据可能不完整,需要添加默认值
|
|
|
|
|
if (selectedMetric.value === 'windSpeed' && typeof item[config.dataKey] === 'undefined') {
|
|
|
|
|
return (2 + Math.sin(props.weatherInfo.forecast.indexOf(item) / 2) * 2).toFixed(1);
|
|
|
|
|
}
|
|
|
|
|
// 紫外线数据可能不完整,需要添加默认值
|
|
|
|
|
if (selectedMetric.value === 'uvIndex' && typeof item[config.dataKey] === 'undefined') {
|
|
|
|
|
return (3 + Math.sin(props.weatherInfo.forecast.indexOf(item) / 4) * 3).toFixed(1);
|
|
|
|
|
}
|
|
|
|
|
return item[config.dataKey] ? item[config.dataKey].toFixed(1) : '0';
|
|
|
|
|
}),
|
|
|
|
|
data: chartData.value.values,
|
|
|
|
|
smooth: true,
|
|
|
|
|
symbol: 'circle',
|
|
|
|
|
symbolSize: 6,
|
|
|
|
|
@ -234,7 +139,16 @@ const updateChart = () => {
|
|
|
|
|
color: config.color
|
|
|
|
|
},
|
|
|
|
|
areaStyle: {
|
|
|
|
|
color: config.gradient
|
|
|
|
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
|
|
{
|
|
|
|
|
offset: 0,
|
|
|
|
|
color: `${config.color}40`
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
offset: 1,
|
|
|
|
|
color: `${config.color}10`
|
|
|
|
|
}
|
|
|
|
|
])
|
|
|
|
|
},
|
|
|
|
|
emphasis: {
|
|
|
|
|
focus: 'series'
|
|
|
|
|
@ -246,10 +160,19 @@ const updateChart = () => {
|
|
|
|
|
chartInstance.setOption(option);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 监听数据变化
|
|
|
|
|
watch(() => props.weatherInfo, () => {
|
|
|
|
|
watch(() =>props.weatherData, (value) => {
|
|
|
|
|
formateData.value = formatData(value)
|
|
|
|
|
chartData.value = createChartData(formateData.value, selectedMetric.value)
|
|
|
|
|
updateChart();
|
|
|
|
|
}, { deep: true });
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const liveData = computed(() => {
|
|
|
|
|
const res ={}
|
|
|
|
|
weatherTypes.forEach(type => {
|
|
|
|
|
res[type] = formateData.value[type]?.[0] || '--'
|
|
|
|
|
})
|
|
|
|
|
return res
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 响应窗口大小变化
|
|
|
|
|
const handleResize = () => {
|
|
|
|
|
@ -270,38 +193,54 @@ const onUnmounted = () => {
|
|
|
|
|
chartInstance.dispose();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function createChartData(data, type) {
|
|
|
|
|
const dates = []
|
|
|
|
|
const values = []
|
|
|
|
|
const typeList = data[type] || []
|
|
|
|
|
typeList.forEach(item => {
|
|
|
|
|
dates.push(proxy.$utils.formatTime(item.createTime, '{h}:{m}'))
|
|
|
|
|
values.push(item.value)
|
|
|
|
|
})
|
|
|
|
|
return { dates, values }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatData(data) {
|
|
|
|
|
const obj = {};
|
|
|
|
|
const res = {};
|
|
|
|
|
// data.sort((a,b) => soilTypes.indexOf(a.identifier) - soilTypes.indexOf(b.identifier))
|
|
|
|
|
data.forEach(item => {
|
|
|
|
|
const identifier = item.identifier;
|
|
|
|
|
if (!identifier || identifier === 'null') return false;
|
|
|
|
|
obj[identifier] ? obj[identifier].push(item) : (obj[identifier] = [item])
|
|
|
|
|
})
|
|
|
|
|
weatherTypes.forEach(type => {
|
|
|
|
|
res[type] = obj[type] || []
|
|
|
|
|
})
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<div class="weather-info">
|
|
|
|
|
<div class="info-list">
|
|
|
|
|
<div class="info-item" :class="{ 'info-item-selected': selectedMetric === 'temperature' }" @click="handleMetricClick('temperature')">
|
|
|
|
|
<div class="info-item" :class="{ 'info-item-selected': selectedMetric === 'Temperature' }" @click="handleMetricClick('Temperature')">
|
|
|
|
|
<div class="icon-container">
|
|
|
|
|
<Sunrise class="icon" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="info-content">
|
|
|
|
|
<div class="info-label">温度</div>
|
|
|
|
|
<div class="info-value">{{ weatherInfo.current.temperature }}°C</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="info-item" :class="{ 'info-item-selected': selectedMetric === 'humidity' }" @click="handleMetricClick('humidity')">
|
|
|
|
|
<div class="icon-container">
|
|
|
|
|
<Odometer class="icon" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="info-content">
|
|
|
|
|
<div class="info-label">湿度</div>
|
|
|
|
|
<div class="info-value">{{ weatherInfo.current.humidity }}%</div>
|
|
|
|
|
<div class="info-value">{{ liveData.Temperature }}°C</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="info-item" :class="{ 'info-item-selected': selectedMetric === 'windSpeed' }" @click="handleMetricClick('windSpeed')">
|
|
|
|
|
<div class="info-item" :class="{ 'info-item-selected': selectedMetric === 'WindSpeed' }" @click="handleMetricClick('WindSpeed')">
|
|
|
|
|
<div class="icon-container">
|
|
|
|
|
<WindPower class="icon" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="info-content">
|
|
|
|
|
<div class="info-label">风速</div>
|
|
|
|
|
<div class="info-value">{{ weatherInfo.current.windSpeed }} m/s</div>
|
|
|
|
|
<div class="info-value">{{ liveData.WindSpeed }} m/s</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
@ -311,27 +250,27 @@ const onUnmounted = () => {
|
|
|
|
|
</div>
|
|
|
|
|
<div class="info-content">
|
|
|
|
|
<div class="info-label">风向</div>
|
|
|
|
|
<div class="info-value">{{ weatherInfo.current.windDirection }}</div>
|
|
|
|
|
<div class="info-value">{{ liveData.WindDirection }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="info-item" :class="{ 'info-item-selected': selectedMetric === 'rainfall' }" @click="handleMetricClick('rainfall')">
|
|
|
|
|
<div class="info-item" :class="{ 'info-item-selected': selectedMetric === 'DailyRainfall' }" @click="handleMetricClick('DailyRainfall')">
|
|
|
|
|
<div class="icon-container">
|
|
|
|
|
<Pouring class="icon" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="info-content">
|
|
|
|
|
<div class="info-label">降雨量</div>
|
|
|
|
|
<div class="info-value">{{ weatherInfo.current.rainfall }} mm</div>
|
|
|
|
|
<div class="info-value">{{ liveData.DailyRainfall }} mm</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="info-item" :class="{ 'info-item-selected': selectedMetric === 'uvIndex' }" @click="handleMetricClick('uvIndex')">
|
|
|
|
|
<div class="info-item" :class="{ 'info-item-selected': selectedMetric === 'Illuminance' }" @click="handleMetricClick('Illuminance')">
|
|
|
|
|
<div class="icon-container">
|
|
|
|
|
<Sunny class="icon" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="info-content">
|
|
|
|
|
<div class="info-label">紫外线</div>
|
|
|
|
|
<div class="info-value">{{ weatherInfo.current.uvIndex }}级</div>
|
|
|
|
|
<div class="info-label">光照</div>
|
|
|
|
|
<div class="info-value">{{ liveData.Illuminance }}级</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|