hebeiLargeScreen/src/views/component/weather-chart.vue

586 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="weather-container">
<!-- 顶部温度和天气信息 -->
<div class="weather-top">
<div class="current-temp">
<!-- <span class="temp-value">25°</span> -->
</div>
<div class="sun-time">
<div class="sunrise">
<img src="@/assets/img/yin.png" alt="日出" />
<span>{{ forecastData[0].sunrise_time }}</span>
</div>
<div class="sunset">
<img src="@/assets/img/yin.png" alt="日落" />
<span>{{ forecastData[0].sunset_time }}</span>
</div>
</div>
</div>
<!-- 天气趋势图 -->
<div class="weather-trend">
<div class="trend-line" ref="chartRef"></div>
</div>
<!-- 空气质量指标 -->
<div class="air-quality">
<div class="quality-item">
<span class="label">空气</span>
<div class="quality-bars">
<div
class="bar"
v-for="i in weatherHourlyData"
:key="i"
:style="{ backgroundColor: getAirQualityColor(i.aqi_level) }"
></div>
</div>
</div>
<div class="quality-item">
<span class="label">风力</span>
<div class="quality-bars">
<div
class="bar bartwo"
v-for="i in weatherHourlyData"
:key="i"
>{{i.wind_level}}</div>
</div>
</div>
<div class="quality-item">
<span class="label"></span>
<div class="quality-barstwo">
<div
class="bar bartwo"
v-for="i in ['08:00','10:00','12:00','14:00','16:00','18:00',]"
:key="i"
>{{i}}</div>
</div>
</div>
</div>
<!-- 未来天气预报 -->
<div class="weather-forecast">
<div
class="forecast-item"
v-for="(item, index) in forecastData"
:key="index"
>
<div class="day">{{ item.week_day }}</div>
<div class="weather-type">{{ item.weather_type }}</div>
<div class="weather-icon">
<img :src="weatherIcons[item.weather]" :alt="item.weather" />
</div>
<div class="wind_info">{{ item.wind_info }}</div>
<div class="temp-range">
<span class="low_temp">{{ item.low_temp }}°</span>
<div class="temp-bar">
<div class="bar-inner"
:title="`${item.high_temp}°C`"
:style="{ width: item.percentage + '%', background: getTempGradient(item.high_temp) }">
</div>
</div>
<span class="high_temp">{{ item.high_temp }}°</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, onBeforeUnmount } from "vue";
import useCharts from "@/hooks/useEcharts";
import type { EChartsOption } from "echarts";
// 定义天气类型
type WeatherType = "sunny" | "cloudy" | "rainy" | "bigRainy";
// 导入天气图标
import sunnyIcon from "@/assets/img/weather/sunny.png";
import cloudyIcon from "@/assets/img/weather/cloudy.png";
import rainyIcon from "@/assets/img/weather/rainy.png";
import bigRainyIcon from "@/assets/img/weather/BigRainy.png";
import { weatherForecast, weatherHourly, todayStatusCount, todayHourly } from "@/api/modules/index";
import { useTodayTime, } from "@/utils/packge";
// 天气图标映射
const weatherIcons: Record<WeatherType, string> = {
sunny: sunnyIcon,
cloudy: cloudyIcon,
rainy: rainyIcon,
bigRainy: bigRainyIcon,
};
// 定义预报数据类型
interface ForecastItem {
week_day: string;
weather_type: string;
weather: WeatherType;
wind_info: string;
high_temp: number;
low_temp: number;
percentage: number;
sunrise_time: string;
sunset_time: string;
}
const weatherTypeMapping: Record<string, WeatherType> = {
"晴": "sunny",
"多云": "cloudy",
"雨": "rainy",
"大雨": "bigRainy",
// 可以继续添加其他天气类型
};
// 天气预报数据
const forecastData = reactive<ForecastItem[]>([
{
week_day: "周二",
weather_type: "多云",
weather: "cloudy",
wind_info: "东南风三级",
high_temp: 15,
low_temp: -2,
percentage: 0,
sunrise_time: '',
sunset_time: '',
},
{
week_day: "周三",
weather_type: "多云",
weather: "cloudy",
wind_info: "东南风三级",
high_temp: 25,
low_temp: 15,
percentage: 0,
sunrise_time: '',
sunset_time: '',
},
{
week_day: "周四",
weather_type: "大雨",
weather: "rainy",
wind_info: "东南风三级",
high_temp: 35,
low_temp: 18,
percentage: 0,
sunrise_time: '',
sunset_time: '',
},
]);
const { todayTime, getTodayTime } = useTodayTime();
// 图表容器引用
const chartRef = ref<HTMLElement | null>(null);
// 温度趋势数据
const temperatureData = reactive({
times: ["08:00", "09:00","10:00","11:00","12:00", "13:00", "14:00", "15:00", "16:00", "17:00", "18:00"],
values: [],
});
// 温度趋势图表配置
const chartOptions: EChartsOption = {
tooltip: {
trigger: "axis",
axisPointer: {
type: "line",
},
backgroundColor: "rgba(0,0,0,0.7)",
borderColor: "rgba(0,255,255,0.5)",
borderWidth: 1,
textStyle: {
color: "#fff",
fontSize: 24,
},
formatter: (params: any) => {
const data = params[0];
return `${data.name}<br/>${data.value}°`;
},
},
grid: {
top: "15%",
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true,
},
xAxis: {
type: "category",
boundaryGap: false,
data: temperatureData.times,
axisLine: {
lineStyle: {
color: "rgba(255,255,255,0.2)",
},
},
axisLabel: {
show: false,
color: "#fff",
fontSize: 24,
},
splitLine: {
show: true,
lineStyle: {
color: "rgba(255,255,255,0.1)",
type: "dashed",
},
},
},
yAxis: {
type: "value",
min: 14,
max: 25,
splitNumber: 5,
axisLabel: {
color: "#fff",
fontSize: 24,
formatter: "{value}°",
},
axisLine: {
show: true,
lineStyle: {
color: "rgba(255,255,255,0.2)",
},
},
splitLine: {
show: true,
lineStyle: {
color: "rgba(255,255,255,0.1)",
type: "dashed",
},
},
},
series: [
{
type: "line",
data: temperatureData.values,
smooth: true,
symbol: "circle",
symbolSize: 8,
itemStyle: {
color: "#fff",
borderWidth: 2,
borderColor: "#00ffff",
},
lineStyle: {
color: "#00ffff",
width: 3,
},
areaStyle: {
color: {
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: "rgba(0,255,255,0.3)",
},
{
offset: 1,
color: "rgba(0,255,255,0.1)",
},
],
},
},
label: {
show: false,
position: "top",
color: "#fff",
fontSize: 24,
formatter: "{c}°",
},
},
],
};
const weatherHourlyData = ref([])
// 初始化图表
const { initCharts, setOptions, resize } = useCharts(chartRef, chartOptions);
// 确保图表在组件挂载后渲染
onMounted(() => {
initCharts();
resize();
window.addEventListener('resize', handleWindowResize);
weatherForecast().then((res:any)=>{
const updatedData = res.data.map((item: any) => ({
...item,
percentage: (item.high_temp / 50) * 100, // 计算百分比
weather: weatherTypeMapping[item.weather_type] || "sunny",
}));
forecastData.splice(0, forecastData.length, ...updatedData);
})
weatherHourly().then((res:any)=>{
weatherHourlyData.value = res.data
temperatureData.values = res.data.map((item: any) => item.temperature);
// 计算最小值 & 最大值
const minTemp = Math.min(...temperatureData.values);
const maxTemp = Math.max(...temperatureData.values);
console.log(temperatureData.values, 'temperatureData.values')
setOptions({ // 手动刷新图表
yAxis: {
min: minTemp - 2, // 留一点余量
max: maxTemp + 2,
},
series: [{
data: temperatureData.values
}]
});
handleWindowResize();
})
todayHourly({todayTime: todayTime.value, pilenum: ''}).then((res: any) => {
console.log(res)
})
});
// 调用 resize 方法更新图表尺寸
const handleWindowResize = () => {
resize();
};
// 组件卸载前移除事件监听器
onBeforeUnmount(() => {
window.removeEventListener('resize', handleWindowResize);
});
// 根据 i 值返回颜色(绿色 -> 红色)
const getAirQualityColor = (index: number): string => {
let hue;
if (index <= 3) {
// 绿色系120° -> 90°
hue = 120 - (index / 3) * 30;
} else if (index <= 7) {
// 黄绿色系90° -> 60°
hue = 90 - ((index - 3) / 4) * 30;
} else if (index <= 10) {
// 橙色系60° -> 30°
hue = 60 - ((index - 7) / 3) * 30;
} else {
// 红色系30° -> 0°
hue = 30 - ((index - 10) / 3) * 30;
}
return `hsl(${hue}, 100%, 45%)`;
};
const getTempGradient = (high_temp: number): string => {
let startHue, endHue;
if (high_temp < 0) {
// 冷蓝色调(-20°C 到 0°C
startHue = 270;
endHue = 240 + (high_temp / -20) * 30;
} else if (high_temp <= 25) {
// 从蓝绿色到黄色0°C 到 25°C
startHue = 180;
endHue = 180 - (high_temp / 25) * 120;
} else {
// 从橙色到红色26°C 及以上)
startHue = 30;
endHue = 30 - ((high_temp - 25) / 10) * 30;
}
// 确保 hue 在 0~360 范围内
endHue = Math.max(0, Math.min(360, endHue));
const startColor = `hsla(${startHue}, 100%, 45%, 0.8)`;
const endColor = `hsla(${endHue}, 100%, 45%, 0.6)`;
return `linear-gradient(90deg, ${startColor}, ${endColor})`;
};
</script>
<style scoped lang="scss">
.weather-container {
width: 100%;
height: 100%;
padding: 20px;
color: #fff;
.weather-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40px;
.current-temp {
.temp-value {
font-size: 80px;
font-weight: bold;
font-family: "Arial";
}
}
.sun-time {
display: flex;
gap: 20px;
.sunrise,
.sunset {
display: flex;
align-items: center;
gap: 8px;
img {
width: 24px;
height: 24px;
}
span {
font-size: 24px;
color: #ffffff;
}
}
}
}
.weather-trend {
height: 350px;
.trend-line {
width: 100%;
height: 100%;
}
}
.air-quality {
margin-bottom: 20px;
.quality-item {
display: flex;
align-items: center;
margin-bottom: 20px;
.label {
width: 60px;
font-size: 24px;
color: #ffffff;
}
.quality-bars {
flex: 1;
display: flex;
gap: 4px;
.bar {
flex: 1;
height: 18px;
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
&.active {
background: #00ffff;
}
}
.bartwo{
height: 32px;
background: rgba(255,255,255,0.12);
display: flex;
align-items: center;
justify-content: center;
font-weight: 400;
font-size: 20px;
color: rgba(255,255,255,0.6);
line-height: 28px;
text-align: center;
font-style: normal;
text-transform: none;
}
}
.quality-barstwo {
flex: 1;
display: flex;
gap: 4px;
.bar {
flex: 1;
height: 18px;
}
.bartwo{
height: 32px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 400;
font-size: 18px;
color: rgba(255,255,255,0.6);
line-height: 28px;
text-align: center;
font-style: normal;
text-transform: none;
}
}
}
}
.weather-forecast {
.forecast-item {
display: flex;
align-items: center;
padding: 22px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
.day {
width: 80px;
font-size: 28px;
color: #ffffff;
}
.weather-type {
width: 80px;
font-size: 20px;
color: #ffffff;
text-align: center;
}
.weather-icon {
width: 44px;
height: 44px;
/* margin: 0 20px 0 0; */
margin-right: 20px;
display: flex;
align-items: center;
justify-content: center;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.wind_info {
width: 160px;
font-size: 24px;
color: #ffffff;
}
.temp-range {
flex: 1;
display: flex;
align-items: center;
gap: 12px;
.high_temp,
.low_temp {
font-size: 28px;
color: #ffffff;
width: 40px;
text-align: right;
}
.temp-bar {
flex: 1;
height: 8px;
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
overflow_temp: hidden;
.bar-inner {
height: 100%;
background: linear-gradient(90deg, #4ecee6 0%, #4ea8e6 100%);
border-radius: 4px;
}
}
}
}
}
}
</style>