|
|
@@ -1,7 +1,360 @@
|
|
|
<template>
|
|
|
- <div> </div>
|
|
|
+ <div class="weather-card">
|
|
|
+ <div class="cloud-bg">
|
|
|
+ <div class="date-time">
|
|
|
+ <span>{{ currentDate }}</span>
|
|
|
+ <span>{{ currentWeek }}</span>
|
|
|
+ <span class="time">{{ currentTime }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="temperature-wind">
|
|
|
+ <div class="temperature">{{ curTemperature || '--' }}℃</div>
|
|
|
+ <div class="wind">
|
|
|
+ <div>
|
|
|
+ 风速:
|
|
|
+ <span class="wind-value">{{ curWindVelocity || '--' }} km/h</span>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ 风力:
|
|
|
+ <span class="wind-value">{{ windSpeedLevel || '--' }}级</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="weather-warning" :class="`weather-warning--${warningLevel}`" @click="handleClick">
|
|
|
+ <SvgIcon iconName="weather-warning" :color="levelStyles[warningLevel].iconColor" width="20px" height="20px" />
|
|
|
+ <Transition name="warning-text" mode="out-in">
|
|
|
+ <span v-if="currentWarning" :key="currentIndex" class="weather-warning__text">
|
|
|
+ {{ currentWarning.disasterName }}
|
|
|
+ </span>
|
|
|
+ <span v-else key="no-warning" class="weather-warning__text">今日无气象灾害预警</span>
|
|
|
+ </Transition>
|
|
|
+ <Transition name="arrow-icon" mode="out-in">
|
|
|
+ <SvgIcon
|
|
|
+ v-if="todayWarningInfo.length > 1"
|
|
|
+ :key="currentIndex"
|
|
|
+ class="arrow-icon"
|
|
|
+ iconName="arrow-down"
|
|
|
+ :color="levelStyles[warningLevel].arrowColor"
|
|
|
+ width="16px"
|
|
|
+ height="16px"
|
|
|
+ />
|
|
|
+ </Transition>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
-<script setup lang="ts"></script>
|
|
|
+<script setup lang="ts">
|
|
|
+ import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
|
|
+ import dayjs from 'dayjs';
|
|
|
+ import SvgIcon from '@/components/SvgIcon/SvgIcon.vue';
|
|
|
+ import { SysDictDataDetail, queryDictTypeDetail } from '@/api/dict';
|
|
|
+ import { getTodayDisasterWarnInfoList, getRealTimeWeatherData } from '@/api/disaster-overview';
|
|
|
|
|
|
-<style scoped lang="scss"></style>
|
|
|
+ export interface DisasterWarningListType {
|
|
|
+ disasterType: string;
|
|
|
+ disasterName: string;
|
|
|
+ }
|
|
|
+
|
|
|
+ const currentDate = ref('');
|
|
|
+ const currentWeek = ref('');
|
|
|
+ const currentTime = ref('');
|
|
|
+ let timer: NodeJS.Timeout;
|
|
|
+ const weatherDisasterDic = ref<SysDictDataDetail[]>([]); // 气象灾害预警字典
|
|
|
+ const todayWarningInfo = ref<DisasterWarningListType[]>([]); // 今日灾害预警信息
|
|
|
+
|
|
|
+ const curTemperature = ref('0');
|
|
|
+ const curWindVelocity = ref(0);
|
|
|
+ const windSpeedLevel = ref(0);
|
|
|
+ // 风速km/h等级换算表
|
|
|
+ const windLevels = [
|
|
|
+ { max: 1, level: 0 },
|
|
|
+ { max: 5, level: 1 },
|
|
|
+ { max: 11, level: 2 },
|
|
|
+ { max: 19, level: 3 },
|
|
|
+ { max: 28, level: 4 },
|
|
|
+ { max: 38, level: 5 },
|
|
|
+ { max: 49, level: 6 },
|
|
|
+ { max: 61, level: 7 },
|
|
|
+ { max: 74, level: 8 },
|
|
|
+ { max: 88, level: 9 },
|
|
|
+ { max: 102, level: 10 },
|
|
|
+ { max: 117, level: 11 },
|
|
|
+ { max: 133, level: 12 },
|
|
|
+ { max: 149, level: 13 },
|
|
|
+ { max: 166, level: 14 },
|
|
|
+ { max: 183, level: 15 },
|
|
|
+ { max: 201, level: 16 },
|
|
|
+ { max: 220, level: 17 },
|
|
|
+ { max: Infinity, level: 18 },
|
|
|
+ ];
|
|
|
+
|
|
|
+ const getWindLevel = (windSpeedKmh: number): number => {
|
|
|
+ const matched = windLevels.find(({ max }) => windSpeedKmh <= max);
|
|
|
+ return matched ? matched.level : 18;
|
|
|
+ };
|
|
|
+
|
|
|
+ watch(
|
|
|
+ () => curWindVelocity.value,
|
|
|
+ (newVal) => {
|
|
|
+ windSpeedLevel.value = getWindLevel(newVal);
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ // 更新时间函数
|
|
|
+ const updateDateTime = () => {
|
|
|
+ const now = dayjs();
|
|
|
+ currentDate.value = now.format('MM月DD日');
|
|
|
+ currentWeek.value = `星期${now.format('dd').slice(-1)}`;
|
|
|
+ currentTime.value = now.format('HH:mm:ss');
|
|
|
+ };
|
|
|
+
|
|
|
+ // 获取实时天气数据
|
|
|
+ const getRealTimeWeatherDataInfo = async () => {
|
|
|
+ const res = await getRealTimeWeatherData();
|
|
|
+ curTemperature.value = res?.temperature || '0';
|
|
|
+ curWindVelocity.value = (res?.windVelocity || 0) * 3.6;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 获取今日灾害预警信息
|
|
|
+ const getTodayWarningInfo = async () => {
|
|
|
+ todayWarningInfo.value = (await getTodayDisasterWarnInfoList())?.map((item) => ({
|
|
|
+ disasterType: item.disasterType,
|
|
|
+ disasterName:
|
|
|
+ weatherDisasterDic.value.find((dic) => dic.itemCode === item.disasterType)?.itemValue || '未知预警信息',
|
|
|
+ }));
|
|
|
+ };
|
|
|
+
|
|
|
+ // 当前显示的索引
|
|
|
+ const currentIndex = ref(0);
|
|
|
+
|
|
|
+ // 当前显示的预警项
|
|
|
+ const currentWarning = computed(() => {
|
|
|
+ return todayWarningInfo.value.length > 0 ? todayWarningInfo.value[currentIndex.value] : null;
|
|
|
+ });
|
|
|
+
|
|
|
+ // 预警等级样式配置
|
|
|
+ const levelStyles = {
|
|
|
+ blue: {
|
|
|
+ iconColor: 'rgba(23, 119, 255, 1)',
|
|
|
+ arrowColor: 'rgba(23, 119, 255, 0.8)',
|
|
|
+ },
|
|
|
+ yellow: {
|
|
|
+ iconColor: 'rgba(250, 173, 20, 1)',
|
|
|
+ arrowColor: 'rgba(250, 173, 20, 0.8)',
|
|
|
+ },
|
|
|
+ orange: {
|
|
|
+ iconColor: 'rgba(255, 124, 77, 1)',
|
|
|
+ arrowColor: 'rgba(255, 124, 77, 0.8)',
|
|
|
+ },
|
|
|
+ red: {
|
|
|
+ iconColor: 'rgba(255, 77, 79, 1)',
|
|
|
+ arrowColor: 'rgba(255, 77, 79, 0.8)',
|
|
|
+ },
|
|
|
+ common: {
|
|
|
+ iconColor: 'rgba(0, 0, 0, 0.5)',
|
|
|
+ arrowColor: 'rgba(0, 0, 0, 0.5)',
|
|
|
+ },
|
|
|
+ };
|
|
|
+
|
|
|
+ // 根据类型判断预警等级
|
|
|
+ const warningLevel = computed(() => {
|
|
|
+ if (!currentWarning.value) return 'common';
|
|
|
+
|
|
|
+ const type = currentWarning.value.disasterType.toLowerCase();
|
|
|
+ if (type.includes('red')) return 'red';
|
|
|
+ if (type.includes('orange')) return 'orange';
|
|
|
+ if (type.includes('yellow')) return 'yellow';
|
|
|
+ if (type.includes('blue')) return 'blue';
|
|
|
+
|
|
|
+ return 'common'; // 默认通用
|
|
|
+ });
|
|
|
+
|
|
|
+ // 点击切换到下一个
|
|
|
+ const handleClick = () => {
|
|
|
+ if (todayWarningInfo.value.length <= 1) return;
|
|
|
+
|
|
|
+ currentIndex.value = (currentIndex.value + 1) % todayWarningInfo.value.length;
|
|
|
+ };
|
|
|
+
|
|
|
+ onMounted(async () => {
|
|
|
+ await queryDictTypeDetail('weather_warning').then((res) => {
|
|
|
+ weatherDisasterDic.value = res.sysDictDataList;
|
|
|
+ });
|
|
|
+ updateDateTime();
|
|
|
+ timer = setInterval(updateDateTime, 1000);
|
|
|
+ getRealTimeWeatherDataInfo();
|
|
|
+ getTodayWarningInfo();
|
|
|
+ });
|
|
|
+
|
|
|
+ onUnmounted(() => {
|
|
|
+ clearInterval(timer);
|
|
|
+ });
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+ .weather-card {
|
|
|
+ background: linear-gradient(90deg, #b4ccff 0%, #e4fbf9 100%);
|
|
|
+ border-radius: 8px;
|
|
|
+
|
|
|
+ .cloud-bg {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background-image: url('@/assets/images/disaster-overview/cloud-bg.png');
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-position: left top;
|
|
|
+ background-size: auto;
|
|
|
+ border-radius: 8px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .date-time {
|
|
|
+ padding: 16px 20px;
|
|
|
+ font-weight: 400;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #000000;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+
|
|
|
+ .time {
|
|
|
+ margin-left: auto;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .temperature-wind {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 32px;
|
|
|
+ padding: 0 20px;
|
|
|
+
|
|
|
+ .temperature {
|
|
|
+ line-height: 67px;
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 56px;
|
|
|
+ color: #0f3d7d;
|
|
|
+ }
|
|
|
+
|
|
|
+ .wind {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 7px;
|
|
|
+ font-weight: 400;
|
|
|
+ font-size: 16px;
|
|
|
+ color: #0f3d7d;
|
|
|
+ }
|
|
|
+
|
|
|
+ .wind-value {
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .weather-warning {
|
|
|
+ width: 306px;
|
|
|
+ height: 48px;
|
|
|
+ margin: 17px 12px 12px 12px;
|
|
|
+ border-radius: 8px;
|
|
|
+ backdrop-filter: blur(10px);
|
|
|
+ background: rgba(#8799b3, 0.15);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 8px;
|
|
|
+ position: relative;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ &__text {
|
|
|
+ line-height: 1;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+
|
|
|
+ .arrow-icon {
|
|
|
+ position: absolute;
|
|
|
+ right: 16px;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 蓝色预警
|
|
|
+ &--blue {
|
|
|
+ background-color: rgb(23 119 255 / 15%);
|
|
|
+
|
|
|
+ .weather-warning__text {
|
|
|
+ color: #1777ff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 黄色预警
|
|
|
+ &--yellow {
|
|
|
+ background-color: rgb(250 173 20 / 15%);
|
|
|
+
|
|
|
+ .weather-warning__text {
|
|
|
+ color: #faad14;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 橙色预警
|
|
|
+ &--orange {
|
|
|
+ background-color: rgb(255 124 77 / 15%);
|
|
|
+
|
|
|
+ .weather-warning__text {
|
|
|
+ color: #ff7c4d;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 红色预警
|
|
|
+ &--red {
|
|
|
+ background-color: rgb(255 77 77 / 15%);
|
|
|
+
|
|
|
+ .weather-warning__text {
|
|
|
+ color: #ff4d4f;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 通用
|
|
|
+ &--common {
|
|
|
+ background-color: rgba(#8799b3, 0.15);
|
|
|
+
|
|
|
+ .weather-warning__text {
|
|
|
+ color: rgba(0, 0, 0);
|
|
|
+ font-weight: 400;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 悬停效果(仅当有多个预警时)
|
|
|
+ &:hover {
|
|
|
+ opacity: 0.8;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 预警文本切换动画
|
|
|
+ .warning-text-enter-active,
|
|
|
+ .warning-text-leave-active,
|
|
|
+ .arrow-icon-enter-active,
|
|
|
+ .arrow-icon-leave-active {
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .warning-text-enter-from,
|
|
|
+ .arrow-icon-enter-from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(-10px);
|
|
|
+ }
|
|
|
+
|
|
|
+ .warning-text-enter-to,
|
|
|
+ .arrow-icon-enter-to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ .warning-text-leave-from,
|
|
|
+ .arrow-icon-leave-from {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ .warning-text-leave-to,
|
|
|
+ .arrow-icon-leave-to {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(10px);
|
|
|
+ }
|
|
|
+</style>
|