Jelajahi Sumber

style: 灾害防范总览-天气卡片样式重写

bxy 6 bulan lalu
induk
melakukan
6116fd72e0

+ 43 - 0
src/api/disaster-overview/index.ts

@@ -310,3 +310,46 @@ export const updateCameraToOverviewGroup = (params: { groupId: number | undefine
     params,
   });
 };
+
+/**
+ * @description: 获取实时天气数据
+ */
+export interface RealTimeWeatherDataRes {
+  time: string; // 时间
+  temperature: string; // 温度℃
+  windVelocity: number; // 风速m/s * 3.6 = km/h
+}
+export const getRealTimeWeatherData = () => {
+  return http.request<RealTimeWeatherDataRes>({
+    url: '/weatherData/queryWeatherData',
+    method: 'get',
+  });
+};
+
+/**
+ * @description: 获取当日发布的灾害预警信息列表
+ */
+export interface TodayDisasterWarnInfoType {
+  id: number; // 自增主键
+  disasterType: string; // 灾害类型
+  disasterLevel: string; // 灾害等级
+  warnTime: string; // 预警时间
+  source: string; // 信息来源
+  content: string; // 发布内容
+  effectState: number; // 生效状态:1-未生效,2-生效
+  createSource: number; // 创建来源:1-人工创建 2-接口数据获取
+  isPush: number; // 是否推送:0-不推送 1-推送
+  pushTime: string; // 发布时间
+  userGroupList: string; // 用户分组列表
+  createdBy: number; // 创建人
+  createdAt: string; // 创建时间
+  updatedAt: string; // 更新时间
+  isDeleted: number; // 0-未删除,大于0(时间戳)-已删除
+  tenantId: number; // 租户id
+}
+export const getTodayDisasterWarnInfoList = () => {
+  return http.request<TodayDisasterWarnInfoType[]>({
+    url: '/disasterWarn/queryTodayDisasterWarnInfoList',
+    method: 'get',
+  });
+};

TEMPAT SAMPAH
src/assets/images/disaster-overview/cloud-bg.png


File diff ditekan karena terlalu besar
+ 19 - 0
src/assets/svg/weather-warning.svg


+ 170 - 0
src/views/disaster/overview/components/WeatherCard-deprecated.vue

@@ -0,0 +1,170 @@
+<template>
+  <div class="weather-card">
+    <div class="time-info">
+      <span class="line"></span>
+      <span class="title">今日天气</span>
+      <span class="location">上海市</span>
+      <span class="date">{{ currentDate }}</span>
+      <span class="week">{{ currentWeek }}</span>
+      <span class="time">{{ currentTime }}</span>
+    </div>
+    <div class="main-content">
+      <WeatherInfo
+        :type="weatherInfo.type"
+        :temperature="weatherInfo.temperature"
+        :humidity="weatherInfo.humidity"
+        :windSpeed="weatherInfo.windSpeed"
+        :warning="weatherInfo.warning"
+      />
+      <div class="info-box">
+        <WeatherTips :title="measureTitle" :measure="measureInfo" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, onUnmounted, ref } from 'vue';
+  import dayjs from 'dayjs';
+  import WeatherInfo from './weather-info/WeatherInfo.vue';
+  import WeatherTips from './weather-info/WeatherTips.vue';
+  import { DisasterTimeSpan } from '@/views/disaster/overview/components/disaster-warning-records/constant';
+  import { SysDictDataDetail, queryDictTypeDetail } from '@/api/dict';
+  import { getDisasterWarningRecords } from '@/api/disaster-overview';
+
+  const currentDate = ref('');
+  const currentWeek = ref('');
+  const currentTime = ref('');
+  let timer: NodeJS.Timeout;
+  const weatherDisasterDic = ref<SysDictDataDetail[]>([]); // 气象灾害预警字典
+  const disasterMeasureDic = ref<SysDictDataDetail[]>([]); // 灾害应急措施字典
+  const measureTitle = ref<string | undefined>('');
+  const measureInfo = ref<string | undefined>('');
+
+  const weatherInfo = ref({
+    // type: 'storm',
+    // temperature: 26,
+    // humidity: 91,
+    // windSpeed: '东北风3级',
+    // warning: '',
+    type: '',
+    temperature: undefined,
+    humidity: undefined,
+    windSpeed: '',
+    warning: '',
+  });
+
+  // 更新时间函数
+  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 getTodayWarningInfo = async () => {
+    const weekDisasterInfo = await getDisasterWarningRecords(DisasterTimeSpan.ONE_WEEK);
+    const weekDisasterInfoList = weekDisasterInfo[0].warnRecord;
+    const normalMeasure = disasterMeasureDic.value.find((item) => item.itemCode === 'normal_measure');
+
+    if (weekDisasterInfoList.length === 0) {
+      weatherInfo.value.warning = '未发布灾害预警';
+      measureInfo.value = normalMeasure?.itemValue;
+      measureTitle.value = '安全提示';
+    } else {
+      const today = dayjs().format('YYYY-MM-DD');
+      const todayWarning = weekDisasterInfoList.find((item) => item.warnTime.includes(today));
+      if (todayWarning) {
+        weatherInfo.value.warning =
+          weatherDisasterDic.value.find((item) => item.itemCode === todayWarning.disasterType)?.itemValue || '';
+        const weatherWarningType = getWeatherWarningType(todayWarning.disasterType);
+        if (weatherWarningType) {
+          const targetMeasure = disasterMeasureDic.value.find((item) => item.itemCode.includes(weatherWarningType));
+          measureInfo.value = targetMeasure ? targetMeasure?.itemValue : normalMeasure?.itemValue;
+          measureTitle.value = targetMeasure ? '应急提示' : '安全提示';
+        }
+      } else {
+        weatherInfo.value.warning = '未发布灾害预警';
+        measureInfo.value = normalMeasure?.itemValue;
+        measureTitle.value = '安全提示';
+      }
+    }
+  };
+
+  function getWeatherWarningType(val: string) {
+    const match = val.match(/^[a-zA-Z0-9]+/);
+    if (!match) return;
+    return match[0];
+  }
+
+  onMounted(async () => {
+    await queryDictTypeDetail('weather_warning').then((res) => {
+      weatherDisasterDic.value = res.sysDictDataList;
+    });
+    await queryDictTypeDetail('disaster_emergency_measure').then((res) => {
+      disasterMeasureDic.value = res.sysDictDataList;
+    });
+    updateDateTime();
+    timer = setInterval(updateDateTime, 1000);
+    getTodayWarningInfo();
+  });
+
+  onUnmounted(() => {
+    clearInterval(timer);
+  });
+</script>
+
+<style lang="scss" scoped>
+  .weather-card {
+    width: 100%;
+    height: 267px;
+    background: linear-gradient(90deg, #7ea7fe 0%, #a4cdc9 100%);
+    border-radius: 4px;
+    position: relative;
+
+    .time-info {
+      display: flex;
+      align-items: center;
+      font-size: 16px;
+      color: #000;
+      padding-top: 12px;
+
+      .line {
+        width: 3px;
+        height: 16px;
+        background: #1777ff;
+        margin-right: 12px;
+      }
+
+      .title {
+        font-weight: 500;
+        font-size: 16px;
+        color: #000000;
+      }
+
+      .location {
+        margin-left: 48px;
+      }
+
+      .date {
+        margin-left: 20px;
+      }
+
+      .week,
+      .time {
+        margin-left: 10px;
+      }
+    }
+
+    .main-content {
+      height: 227px;
+      display: flex;
+    }
+  }
+
+  .info-box {
+    flex: 1;
+    margin: 20px 20px 10px 0;
+  }
+</style>

+ 151 - 90
src/views/disaster/overview/components/WeatherCard.vue

@@ -1,36 +1,47 @@
 <template>
   <div class="weather-card">
-    <div class="time-info">
-      <span class="line"></span>
-      <span class="title">今日天气</span>
-      <span class="location">上海市</span>
-      <span class="date">{{ currentDate }}</span>
-      <span class="week">{{ currentWeek }}</span>
-      <span class="time">{{ currentTime }}</span>
-    </div>
-    <div class="main-content">
-      <WeatherInfo
-        :type="weatherInfo.type"
-        :temperature="weatherInfo.temperature"
-        :humidity="weatherInfo.humidity"
-        :windSpeed="weatherInfo.windSpeed"
-        :warning="weatherInfo.warning"
-      />
-      <div class="info-box">
-        <WeatherTips :title="measureTitle" :measure="measureInfo" />
+    <div class="cloud-bg">
+      <div class="basic-info">
+        <div>
+          <div class="location">上海市</div>
+          <div class="date-time">
+            <span>{{ currentDate }}</span>
+            <span>{{ currentWeek }}</span>
+            <span>{{ currentTime }}</span>
+          </div>
+        </div>
+        <div class="weather-info">
+          <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>
+      <div class="disaster-emergency-tips">
+        <WeatherTips :title="measureTitle" :measure="measureInfo" :disasterWarningList="todayWarningInfo" />
       </div>
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-  import { onMounted, onUnmounted, ref } from 'vue';
+  import { onMounted, onUnmounted, ref, watch } from 'vue';
   import dayjs from 'dayjs';
-  import WeatherInfo from './weather-info/WeatherInfo.vue';
   import WeatherTips from './weather-info/WeatherTips.vue';
-  import { DisasterTimeSpan } from '@/views/disaster/overview/components/disaster-warning-records/constant';
   import { SysDictDataDetail, queryDictTypeDetail } from '@/api/dict';
-  import { getDisasterWarningRecords } from '@/api/disaster-overview';
+  import { getTodayDisasterWarnInfoList, getRealTimeWeatherData } from '@/api/disaster-overview';
+
+  export interface DisasterWarningListType {
+    disasterType: string;
+    disasterName: string;
+  }
 
   const currentDate = ref('');
   const currentWeek = ref('');
@@ -38,21 +49,47 @@
   let timer: NodeJS.Timeout;
   const weatherDisasterDic = ref<SysDictDataDetail[]>([]); // 气象灾害预警字典
   const disasterMeasureDic = ref<SysDictDataDetail[]>([]); // 灾害应急措施字典
+  const todayWarningInfo = ref<DisasterWarningListType[]>([]); // 今日灾害预警信息TODO:
   const measureTitle = ref<string | undefined>('');
   const measureInfo = ref<string | undefined>('');
 
-  const weatherInfo = ref({
-    // type: 'storm',
-    // temperature: 26,
-    // humidity: 91,
-    // windSpeed: '东北风3级',
-    // warning: '',
-    type: '',
-    temperature: undefined,
-    humidity: undefined,
-    windSpeed: '',
-    warning: '',
-  });
+  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 = () => {
@@ -62,42 +99,47 @@
     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;
+  };
+
+  function getWeatherWarningType(val: string) {
+    const match = val.match(/^[a-zA-Z0-9]+/);
+    if (!match) return;
+    return match[0];
+  }
+
   // 获取今日灾害预警信息
   const getTodayWarningInfo = async () => {
-    const weekDisasterInfo = await getDisasterWarningRecords(DisasterTimeSpan.ONE_WEEK);
-    const weekDisasterInfoList = weekDisasterInfo[0].warnRecord;
+    todayWarningInfo.value = (await getTodayDisasterWarnInfoList())?.map((item) => ({
+      disasterType: item.disasterType,
+      disasterName:
+        weatherDisasterDic.value.find((dic) => dic.itemCode === item.disasterType)?.itemValue || '未知预警信息',
+    }));
+
     const normalMeasure = disasterMeasureDic.value.find((item) => item.itemCode === 'normal_measure');
 
-    if (weekDisasterInfoList.length === 0) {
-      weatherInfo.value.warning = '未发布灾害预警';
-      measureInfo.value = normalMeasure?.itemValue;
+    if (todayWarningInfo.value.length === 0) {
       measureTitle.value = '安全提示';
+      measureInfo.value = normalMeasure?.itemValue || '暂无提示';
     } else {
-      const today = dayjs().format('YYYY-MM-DD');
-      const todayWarning = weekDisasterInfoList.find((item) => item.warnTime.includes(today));
-      if (todayWarning) {
-        weatherInfo.value.warning =
-          weatherDisasterDic.value.find((item) => item.itemCode === todayWarning.disasterType)?.itemValue || '';
-        const weatherWarningType = getWeatherWarningType(todayWarning.disasterType);
+      const todayWarningValue = todayWarningInfo.value.find(
+        (item) => item.disasterType === todayWarningInfo.value[0].disasterType,
+      )?.disasterType;
+      if (todayWarningValue) {
+        const weatherWarningType = getWeatherWarningType(todayWarningValue);
         if (weatherWarningType) {
           const targetMeasure = disasterMeasureDic.value.find((item) => item.itemCode.includes(weatherWarningType));
-          measureInfo.value = targetMeasure ? targetMeasure?.itemValue : normalMeasure?.itemValue;
-          measureTitle.value = targetMeasure ? '应急提示' : '安全提示';
+          measureTitle.value = targetMeasure?.itemValue ? '应急提示' : '安全提示';
+          measureInfo.value = targetMeasure?.itemValue || normalMeasure?.itemValue;
         }
-      } else {
-        weatherInfo.value.warning = '未发布灾害预警';
-        measureInfo.value = normalMeasure?.itemValue;
-        measureTitle.value = '安全提示';
       }
     }
   };
 
-  function getWeatherWarningType(val: string) {
-    const match = val.match(/^[a-zA-Z0-9]+/);
-    if (!match) return;
-    return match[0];
-  }
-
   onMounted(async () => {
     await queryDictTypeDetail('weather_warning').then((res) => {
       weatherDisasterDic.value = res.sysDictDataList;
@@ -107,6 +149,7 @@
     });
     updateDateTime();
     timer = setInterval(updateDateTime, 1000);
+    getRealTimeWeatherDataInfo();
     getTodayWarningInfo();
   });
 
@@ -119,52 +162,70 @@
   .weather-card {
     width: 100%;
     height: 267px;
-    background: linear-gradient(90deg, #7ea7fe 0%, #a4cdc9 100%);
+    background: linear-gradient(90deg, #b4ccff 0%, #e4fbf9 100%);
     border-radius: 4px;
-    position: relative;
+  }
 
-    .time-info {
-      display: flex;
-      align-items: center;
+  .cloud-bg {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    padding: 16px;
+    background-image: url('@/assets/images/disaster-overview/cloud-bg.png');
+    background-repeat: no-repeat;
+    background-position: left top;
+    background-size: auto 100%;
+  }
+
+  .basic-info {
+    width: 305px;
+    margin: 4px 35px 32px 12px;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+
+    .location {
+      font-weight: 500;
+      font-size: 20px;
+      color: #0f3d7d;
+      margin-bottom: 8px;
+    }
+
+    .date-time {
+      font-weight: 400;
       font-size: 16px;
-      color: #000;
-      padding-top: 12px;
-
-      .line {
-        width: 3px;
-        height: 16px;
-        background: #1777ff;
-        margin-right: 12px;
-      }
+      color: #0f3d7d;
+      display: flex;
+      gap: 6px;
+    }
 
-      .title {
-        font-weight: 500;
-        font-size: 16px;
-        color: #000000;
-      }
+    .weather-info {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
 
-      .location {
-        margin-left: 48px;
+      .temperature {
+        font-weight: bold;
+        font-size: 63px;
+        color: #0f3d7d;
       }
 
-      .date {
-        margin-left: 20px;
+      .wind {
+        display: flex;
+        flex-direction: column;
+        gap: 7px;
+        font-weight: 400;
+        font-size: 16px;
+        color: #0f3d7d;
       }
 
-      .week,
-      .time {
-        margin-left: 10px;
+      .wind-value {
+        font-weight: 500;
       }
     }
-
-    .main-content {
-      height: 227px;
-      display: flex;
-    }
   }
 
-  .info-box {
+  .disaster-emergency-tips {
     flex: 1;
-    margin: 20px 20px 10px 0;
   }
 </style>

+ 39 - 0
src/views/disaster/overview/components/weather-info/WeatherTips-deprecated.vue

@@ -0,0 +1,39 @@
+<template>
+  <div class="weather-tips">
+    <div class="title">{{ title }}</div>
+    <div class="content">{{ measure }}</div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  defineProps<{
+    title: string | undefined;
+    measure: string | undefined;
+  }>();
+</script>
+
+<style scoped lang="scss">
+  .weather-tips {
+    width: 100%;
+    height: 100%;
+    background-color: rgba(255, 255, 255, 0.2);
+    border-radius: 4px;
+    backdrop-filter: blur(10px);
+    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
+    padding: 12px 9px 12px 12px;
+  }
+
+  .title {
+    font-size: 16px;
+    color: #303133;
+    margin-bottom: 7px;
+  }
+
+  .content {
+    height: 142px;
+    font-size: 14px;
+    color: #606266;
+    overflow: auto;
+    white-space: pre-line;
+  }
+</style>

+ 132 - 14
src/views/disaster/overview/components/weather-info/WeatherTips.vue

@@ -1,39 +1,157 @@
 <template>
   <div class="weather-tips">
-    <div class="title">{{ title }}</div>
-    <div class="content">{{ measure }}</div>
+    <div class="disaster-warning-list" v-if="disasterWarningList.length > 0">
+      <div
+        class="weather-warning"
+        :class="`weather-warning--${getWarningLevel(item.disasterType)}`"
+        v-for="item in disasterWarningList"
+        :key="item.disasterType"
+      >
+        <SvgIcon
+          iconName="weather-warning"
+          :color="getWarningIconColor(item.disasterType)"
+          width="18px"
+          height="18px"
+        />
+        <span class="weather-warning__text">{{ item.disasterName }}</span>
+      </div>
+    </div>
+    <div class="tips-content">
+      <div class="title">{{ title }}</div>
+      <div class="content">{{ measure }}</div>
+    </div>
   </div>
 </template>
 
 <script setup lang="ts">
+  import SvgIcon from '@/components/SvgIcon/SvgIcon.vue';
+  import { DisasterWarningListType } from '../WeatherCard.vue';
+
   defineProps<{
     title: string | undefined;
     measure: string | undefined;
+    disasterWarningList: DisasterWarningListType[];
   }>();
+
+  // 预警等级样式配置
+  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)',
+    },
+  };
+
+  // 根据类型判断预警等级
+  const getWarningLevel = (disasterType: string) => {
+    const type = 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 'blue'; // 默认蓝色
+  };
+
+  const getWarningIconColor = (disasterType: string) => {
+    return levelStyles[getWarningLevel(disasterType)].iconColor;
+  };
 </script>
 
 <style scoped lang="scss">
   .weather-tips {
     width: 100%;
     height: 100%;
-    background-color: rgba(255, 255, 255, 0.2);
+    background-color: rgba(255, 255, 255, 0.4);
     border-radius: 4px;
     backdrop-filter: blur(10px);
-    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
-    padding: 12px 9px 12px 12px;
+    padding: 16px 20px 20px 20px;
+    display: flex;
+    flex-direction: column;
+    gap: 18px;
   }
 
-  .title {
-    font-size: 16px;
-    color: #303133;
-    margin-bottom: 7px;
+  .disaster-warning-list {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 16px;
+
+    .weather-warning {
+      display: inline-flex;
+      align-items: center;
+      padding: 5px 16px;
+      transition: all 0.3s ease;
+      border-radius: 16px;
+      font-size: 16px;
+      font-weight: 600;
+      gap: 5px;
+      backdrop-filter: blur(10px);
+
+      // 蓝色预警
+      &--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;
+        }
+      }
+    }
   }
 
-  .content {
-    height: 142px;
-    font-size: 14px;
-    color: #606266;
+  .tips-content {
+    flex: 1;
     overflow: auto;
-    white-space: pre-line;
+
+    .title {
+      font-size: 16px;
+      font-weight: 500;
+      color: #333333;
+      margin-bottom: 8px;
+    }
+
+    .content {
+      font-size: 14px;
+      color: #333333;
+      white-space: pre-line;
+      line-height: 24px;
+    }
   }
 </style>