Parcourir la source

Merge branch 'feature-hetong' into 'dev'

Feature hetong

See merge request product-group-fe/sfy-safety-group/sfy-safety!280
毕欣怡 il y a 5 mois
Parent
commit
dfc07e251f

+ 45 - 1
src/api/disaster-overview/index.ts

@@ -256,7 +256,17 @@ export interface QueryAllOwnedCameraTreeRes {
     }[];
   }[];
 }
-export interface CameraInTree {
+export interface CameraInTree {	"data": [
+  {
+    "dataTime": "",
+    "minTemperature": "",
+    "maxTemperature": "",
+    "minWindVelocity": 0,
+    "maxWindVelocity": 0,
+    "avgWindVelocity": 0
+  }
+]
+
   networkingState: number; // 联网状态: 0-启用, 1-禁用
   integrationState: number; // 接入状态: 0-启用, 1-禁用
   render: string; // 渲染选择,无渲染/某个算法
@@ -326,6 +336,40 @@ export const getRealTimeWeatherData = () => {
   });
 };
 
+/**
+ * @description: 获取历史气温数据
+ */
+export interface HistoryTemperatureInfo{
+  dataTime : string;
+  minTemperature : string;
+  maxTemperature : string;  
+  minWindVelocity : number;
+  maxWindVelocity : number;
+  avgWindVelocity : number;
+}
+
+export const getHistoryTemperatureList = ()=>{
+  return  http.request<HistoryTemperatureInfo[]>({
+    url: '/weatherData/queryRecentYearWeatherData',
+    method: 'get',
+  });
+  
+}
+
+
+
+/**
+ * @description: 获取一年高温天气天数
+ */
+export const getHighTemperatureDays = ()=>{
+  return http.request<number>({
+    url: '/weatherData/countHotDaysInYear',
+    method: 'get',
+  });
+}
+ 
+
+
 /**
  * @description: 获取当日发布的灾害预警信息列表
  */

BIN
src/assets/images/weather-icons/dialoag-title-bg.png


BIN
src/assets/images/weather-icons/fire.png


BIN
src/assets/images/weather-icons/history-temperature.png


+ 396 - 171
src/views/disaster/overview/components/WeatherCard.vue

@@ -2,230 +2,455 @@
   <div class="weather-card">
     <div class="cloud-bg">
       <div class="basic-info">
-        <div>
-          <div class="location">上海市</div>
+        <div class="location">
+          <span>上海市</span>
           <div class="date-time">
             <span>{{ currentDate }}</span>
             <span>{{ currentWeek }}</span>
             <span>{{ currentTime }}</span>
           </div>
         </div>
+        <div class="high-stable-weather">
+          <img :src="fireIcon" alt="empty" class="icon-fire">
+          <span>本年度高温天气: {{ countHighTemperatureDays }} 天</span>
+        </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>
+              风速:
+              <span class="wind-value">{{ curWindVelocity || '--' }} km/h</span>
+            </div>
+
           </div>
         </div>
+
+        <el-button type="text" class="history-temperature" @click="getHistoryTempDataAndOpenDialog">
+          <img :src="historyTemperatureIcon" alt="empty" class="icon-history-temperature">
+          <div class="history-temperature-text">历史温度</div>
+        </el-button>
+
+        <el-dialog v-model="isDialogOpen" title="历史温度统计" width="550px" class="weather-dialog"
+          :close-on-click-modal="false" @close="handleCloseDialog">
+          <!-- 加载状态 -->
+          <el-loading v-if="isLoading" target=".dialog-content" text="正在加载数据...">
+            <div class="dialog-content" style="min-height: 300px;"></div>
+          </el-loading>
+
+          <!-- 表格 -->
+          <div v-else class="dialog-content">
+            <el-table :data="historyTemperatureList" border stripe style="width: 100%;" max-height="calc(80vh - 120px)">
+              <el-table-column label="日期" align="center" width="170">
+                <template #default="scope">
+                  {{ scope.row.dataTime ? scope.row.dataTime.slice(0, 10) : '--' }}
+                </template>
+              </el-table-column>
+              <el-table-column label="最高温度(℃)" prop="maxTemperature" align="center" width="170">
+                <template #default="scope">
+                  <span> {{ scope.row.maxTemperature ?? "--" }} </span>
+                </template>
+              </el-table-column>
+              <el-table-column label="最低温度(℃)" prop="minTemperature" align="center" width="170">
+                <template #default="scope">
+                  <span> {{ scope.row.minTemperature ?? "--" }} </span>
+                </template>
+              </el-table-column>
+            </el-table>
+            <!-- 无数据提示 -->
+            <div v-if="historyTemperatureList.length === 0" class="no-data">
+              <el-empty description="暂无历史温度数据" />
+            </div>
+          </div>
+        </el-dialog>
       </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, watch } from 'vue';
-  import dayjs from 'dayjs';
-  import WeatherTips from './weather-info/WeatherTips.vue';
-  import { SysDictDataDetail, queryDictTypeDetail } from '@/api/dict';
-  import { getTodayDisasterWarnInfoList, getRealTimeWeatherData } from '@/api/disaster-overview';
-
-  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 disasterMeasureDic = ref<SysDictDataDetail[]>([]); // 灾害应急措施字典
-  const todayWarningInfo = ref<DisasterWarningListType[]>([]); // 今日灾害预警信息
-  const measureTitle = ref<string | undefined>('');
-  const measureInfo = ref<string | undefined>('');
-
-  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;
-  };
-
-  function getWeatherWarningType(val: string) {
-    const match = val.match(/^[a-zA-Z0-9]+/);
-    if (!match) return;
-    return match[0];
-  }
-
-  // 获取今日灾害预警信息
-  const getTodayWarningInfo = async () => {
-    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 (todayWarningInfo.value.length === 0) {
-      measureTitle.value = '安全提示';
-      measureInfo.value = normalMeasure?.itemValue || '暂无提示';
-    } else {
-      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));
-          measureTitle.value = targetMeasure?.itemValue ? '应急提示' : '安全提示';
-          measureInfo.value = targetMeasure?.itemValue || normalMeasure?.itemValue;
-        }
+import { onMounted, onUnmounted, ref, watch } from 'vue';
+import dayjs from 'dayjs';
+import WeatherTips from './weather-info/WeatherTips.vue';
+import { SysDictDataDetail, queryDictTypeDetail } from '@/api/dict';
+import { getTodayDisasterWarnInfoList, getRealTimeWeatherData, getHighTemperatureDays, getHistoryTemperatureList, HistoryTemperatureInfo } from '@/api/disaster-overview';
+import fireIcon from '@/assets/images/weather-icons/fire.png'
+import historyTemperatureIcon from '@/assets/images/weather-icons/history-temperature.png'
+import {
+  ElButton,
+  ElDialog,
+  ElMessage
+} from 'element-plus';
+
+import { Calendar, Download } from "@element-plus/icons-vue"
+
+import { Clock } from '@element-plus/icons-vue';
+
+export interface DisasterWarningListType {
+  disasterType: string;
+  disasterName: String;
+}
+
+const currentDate = ref('');
+const currentWeek = ref('');
+const currentTime = ref('');
+const countHighTemperatureDays = ref<number>(0);
+let timer: NodeJS.Timeout;
+const weatherDisasterDic = ref<SysDictDataDetail[]>([]); // 气象灾害预警字典
+const disasterMeasureDic = ref<SysDictDataDetail[]>([]); // 灾害应急措施字典
+const todayWarningInfo = ref<DisasterWarningListType[]>([]); // 今日灾害预警信息
+const measureTitle = ref<string | undefined>('');
+const measureInfo = ref<string | undefined>('');
+
+const curTemperature = ref('0');
+const curWindVelocity = ref(0);
+const windSpeedLevel = ref(0);
+
+const isDialogOpen = ref(false);
+const isLoading = ref(false);
+const historyTemperatureList = ref<HistoryTemperatureInfo[]>([]);
+
+// 风速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 getHistoryTempDataAndOpenDialog = async () => {
+  try {
+    isLoading.value = true;
+    const res = await getHistoryTemperatureList();
+    historyTemperatureList.value = res || [];
+    isDialogOpen.value = true;
+  } catch (error) {
+    ElMessage.error({
+      message: "请求后端数据失败",
+      showClose: true,
+      duration: 2000
+    });
+    console.error("获取历史温度数据失败", error);
+    isLoading.value = false;
+    historyTemperatureList.value = [];
+  } finally {
+    isLoading.value = false;
+  }
+}
+
+const handleCloseDialog = () => {
+  historyTemperatureList.value = [];
+  isDialogOpen.value = false;
+}
+// 获取高温天气天数
+const getHighTemperatureDaysRes = async () => {
+  const res = await getHighTemperatureDays();
+  countHighTemperatureDays.value = res > 0 && res <= 366 ? res : 0;
+};
+
+function getWeatherWarningType(val: string) {
+  const match = val.match(/^[a-zA-Z0-9]+/);
+  if (!match) return;
+  return match[0];
+};
+
+// 获取今日灾害预警信息
+const getTodayWarningInfo = async () => {
+  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 (todayWarningInfo.value.length === 0) {
+    measureTitle.value = '安全提示';
+    measureInfo.value = normalMeasure?.itemValue || '暂无提示';
+  } else {
+    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));
+        measureTitle.value = targetMeasure?.itemValue ? '应急提示' : '安全提示';
+        measureInfo.value = targetMeasure?.itemValue || normalMeasure?.itemValue;
       }
     }
-  };
+  }
+};
 
-  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);
-    getRealTimeWeatherDataInfo();
-    getTodayWarningInfo();
+onMounted(async () => {
+  await queryDictTypeDetail('weather_warning').then((res) => {
+    weatherDisasterDic.value = res.sysDictDataList;
   });
-
-  onUnmounted(() => {
-    clearInterval(timer);
+  await queryDictTypeDetail('disaster_emergency_measure').then((res) => {
+    disasterMeasureDic.value = res.sysDictDataList;
   });
+  updateDateTime();
+  timer = setInterval(updateDateTime, 1000);
+  getRealTimeWeatherDataInfo();
+  getHighTemperatureDaysRes();
+  getTodayWarningInfo();
+});
+
+onUnmounted(() => {
+  clearInterval(timer);
+});
 </script>
 
 <style lang="scss" scoped>
-  .weather-card {
-    width: 100%;
-    height: 267px;
-    background: linear-gradient(90deg, #b4ccff 0%, #e4fbf9 100%);
-    border-radius: 4px;
+.weather-card {
+  width: 100%;
+  height: 267px;
+  background: linear-gradient(90deg, #b4ccff 0%, #e4fbf9 100%);
+  border-radius: 4px;
+}
+
+.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 {
+    gap: 6px;
+    font-weight: 400;
+    font-size: 16px;
+    color: #062B5D;
+    line-height: 22px;
+    text-align: left;
+    font-style: normal;
+    display: flex;
   }
 
-  .cloud-bg {
-    width: 100%;
-    height: 100%;
+  .date-time {
+    margin-left: 12px;
+    gap: 6px;
+    font-weight: 500;
+    font-size: 16px;
+    color: #062B5D;
+    line-height: 22px;
+    text-align: left;
+    font-style: normal;
     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;
+  .high-stable-weather {
+    font-weight: 600;
+    font-size: 16px;
+    color: #FF4D4F;
+    line-height: 22px;
+    text-align: left;
+    font-style: normal;
+    display: flex;
+    gap: 6px;
+    margin-top: 40px;
+  }
+
+  .icon-fire {
+    width: 20px;
+    height: 20px;
+  }
+
+  .weather-info {
     display: flex;
-    flex-direction: column;
+    align-items: center;
     justify-content: space-between;
+    margin-top: 15px;
 
-    .location {
-      font-weight: 500;
-      font-size: 20px;
+    .temperature {
+      font-family: Helvetica, Helvetica;
+      font-weight: bold;
+      font-size: 63px;
       color: #0f3d7d;
-      margin-bottom: 8px;
+      line-height: 77px;
+      text-align: left;
+      font-style: normal;
     }
 
-    .date-time {
+    .wind {
+      display: flex;
+      flex-direction: column;
+      gap: 7px;
       font-weight: 400;
       font-size: 16px;
       color: #0f3d7d;
-      display: flex;
-      gap: 6px;
     }
 
-    .weather-info {
-      display: flex;
-      align-items: center;
-      justify-content: space-between;
+    .wind-value {
+      font-weight: 500;
+    }
+  }
 
-      .temperature {
-        font-weight: bold;
-        font-size: 63px;
-        color: #0f3d7d;
-      }
+  .history-temperature {
+    width: 108px;
+    height: 32px;
+    background: rgba(255, 255, 255, 0.4);
+    border-radius: 16px;
+    backdrop-filter: blur(10px);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-top: 15px;
+  }
 
-      .wind {
-        display: flex;
-        flex-direction: column;
-        gap: 7px;
-        font-weight: 400;
-        font-size: 16px;
-        color: #0f3d7d;
-      }
+  .icon-history-temperature {
+    width: 12px;
+    height: 12px;
+  }
 
-      .wind-value {
-        font-weight: 500;
-      }
+  .history-temperature-text {
+    font-weight: 500;
+    font-size: 14px;
+    color: #0F3D7D;
+    line-height: 20px;
+    text-align: center;
+    font-style: normal;
+    margin-left: 6px;
+  }
+
+}
+
+
+:deep(.weather-dialog) {
+  background-image: url('@/assets/images/weather-icons/dialoag-title-bg.png') !important;
+  background-size: cover !important;
+  background-repeat: no-repeat !important;
+  background-position: center !important;
+  background-color: #ffffff !important;
+  border-radius: 8px !important;
+  overflow: hidden !important;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15) !important;
+
+  .el-dialog__header {
+    padding: 16px 0px !important;
+    border: none !important;
+    position: relative;
+  }
+
+  .el-dialog__title {
+    font-family: PingFangSC, PingFang SC;
+    font-weight: bold;
+    font-size: 16px;
+    color: rgba(0, 0, 0, 0.88);
+    line-height: 24px;
+    text-align: left;
+    font-style: normal;
+  }
+
+  .el-table__header {
+    .el-table__cell {
+      font-family: 'PingFang SC' !important;
+      font-weight: bold !important;
+      font-size: 14px !important;
+      color: #000 !important;
+      line-height: 22px !important;
+      text-align: left !important;
+      font-style: normal !important;
     }
   }
 
-  .disaster-emergency-tips {
-    flex: 1;
+  .el-table__body {
+    .el-table__cell {
+      font-family: 'PingFang SC' !important;
+      font-weight: 400 !important;
+      font-size: 14px !important;
+      color: #303133 !important;
+      line-height: 22px !important;
+      text-align: left !important;
+      font-style: normal !important;
+    }
+  }
+
+  .el-dialog__headerbtn {
+    position: absolute;
+    top: 50%;
+    transform: translateY(-50%);
+    margin-top: 0;
+    margin-right: -15px;
+  }
+
+  .el-dialog__close {
+    font-size: 16px !important;
   }
+
+  .no-data {
+    width: 100%;
+    padding: 40px 0;
+    text-align: center;
+  }
+
+}
+
+.disaster-emergency-tips {
+  flex: 1;
+}
 </style>