Переглянути джерело

feat(device): 优化时间范围查询逻辑

- 在 ruoyi.js 中新增 parseTimeEnd 函数,用于获取一天的末尾时间- 在 electricity-detail、fzbj、sqbj 和 swbj 组件中使用 parseTimeEnd 优化查询逻辑
- 新增 environment 组件,提供气象数据展示和查询功能
nahida 1 рік тому
батько
коміт
1a1c71f4e4

+ 9 - 0
src/utils/ruoyi.js

@@ -46,6 +46,15 @@ export function parseTime(time, pattern) {
   return time_str
 }
 
+//传入一个时间得到一天的末尾 xxxxxxx 23:59:59
+export function parseTimeEnd(time, pattern) {
+  let res = parseTime(time, pattern);
+  if (res.indexOf("00:00:00") !== -1) {
+    res = res.replace("00:00:00", "23:59:59");
+  }
+  return res;
+}
+
 // 表单重置
 export function resetForm(refName) {
   if (this.$refs[refName]) {

+ 2 - 2
src/views/device/electricity/components/electricity-detail.vue

@@ -4,7 +4,7 @@ import request from "@/utils/request.js";
 import {ElMessage} from "element-plus";
 import ECharts from "vue-echarts";
 import "echarts";
-import {parseTime} from "@/utils/ruoyi.js";
+import {parseTime, parseTimeEnd} from "@/utils/ruoyi.js";
 
 // Data refs
 const newElectricityData = ref(null);
@@ -217,7 +217,7 @@ const getElectricityData = async () => {
       conditions.push({
         column: "create_time",
         type: "between",
-        value: parseTime(dateRange.value[0]) + "," + parseTime(dateRange.value[1]),
+        value: parseTime(dateRange.value[0]) + "," + parseTimeEnd(dateRange.value[1]),
       });
     }
 

+ 552 - 0
src/views/device/environment/index.vue

@@ -0,0 +1,552 @@
+<script setup>
+import {computed, onMounted, ref} from 'vue';
+import request from "@/utils/request.js";
+import {
+  ElCard,
+  ElEmpty,
+  ElMessage,
+  ElPagination,
+  ElSkeleton,
+  ElSkeletonItem,
+  ElTable,
+  ElTableColumn,
+  ElTag
+} from "element-plus";
+import {
+  AlertCircle,
+  ArrowDownRight,
+  ArrowUpRight,
+  Calendar,
+  Clock,
+  CloudRain,
+  CloudSun,
+  Compass,
+  Droplets,
+  RefreshCw,
+  Thermometer,
+  Volume2,
+  Wind
+} from 'lucide-vue-next';
+
+// Data refs
+const currentWeather = ref(null);
+const historyData = ref([]);
+const loading = ref(true);
+const historyLoading = ref(true);
+const currentPage = ref(1);
+const pageSize = ref(10);
+const total = ref(0);
+const lastRefreshTime = ref(new Date());
+
+// Format functions
+const formatValue = (value, multiplier, unit) => {
+  if (!value) return '-';
+  // For temperature, noise, rainfall, wind speed, and humidity, display directly
+  return `${value} ${unit}`;
+};
+
+const formatWindDirection = (degree) => {
+  if (!degree) return '-';
+
+  const directions = ['北', '东北', '东', '东南', '南', '西南', '西', '西北'];
+  const val = Math.floor((parseFloat(degree) + 22.5) / 45);
+  return directions[val % 8];
+};
+
+const formatTime = (dateString) => {
+  if (!dateString) return '-';
+  const date = new Date(dateString);
+  return date.toLocaleString();
+};
+
+const getAirQualityLevel = (pm25) => {
+  if (!pm25) return { level: '-', color: 'gray' };
+
+  const value = parseInt(pm25);
+  if (value <= 35) return { level: '优', color: 'green' };
+  if (value <= 75) return { level: '良', color: 'blue' };
+  if (value <= 115) return { level: '轻度污染', color: 'orange' };
+  if (value <= 150) return { level: '中度污染', color: 'red' };
+  return { level: '重度污染', color: 'purple' };
+};
+
+const airQuality = computed(() => {
+  if (!currentWeather.value) return { level: '-', color: 'gray' };
+  return getAirQualityLevel(currentWeather.value.pm25);
+});
+
+// Get current weather data
+const getCurrentData = async () => {
+  loading.value = true;
+  try {
+    const arr = [{
+      column: "create_time",
+      type: "orderByDesc"
+    }];
+
+    const res = await request.get("/weatherData/findByPage", {
+      params: {
+        pageNum: 1,
+        pageSize: 1,
+        conditionJson: encodeURIComponent(JSON.stringify(arr))
+      }
+    });
+
+    if (res.code !== 200) {
+      ElMessage.error(res.msg);
+      return;
+    }
+
+    if (res.data.records && res.data.records.length > 0) {
+      currentWeather.value = res.data.records[0];
+    }
+    lastRefreshTime.value = new Date();
+  } catch (error) {
+    ElMessage.error('获取当前天气数据失败');
+    console.error(error);
+  } finally {
+    loading.value = false;
+  }
+};
+
+// Get historical weather data
+const getHistoryData = async () => {
+  historyLoading.value = true;
+  try {
+    const arr = [{
+      column: "create_time",
+      type: "orderByDesc"
+    }];
+
+    const res = await request.get("/weatherData/findByPage", {
+      params: {
+        pageNum: currentPage.value,
+        pageSize: pageSize.value,
+        conditionJson: encodeURIComponent(JSON.stringify(arr))
+      }
+    });
+
+    if (res.code !== 200) {
+      ElMessage.error(res.msg);
+      return;
+    }
+
+    historyData.value = res.data.records || [];
+    total.value = res.data.total || 0;
+  } catch (error) {
+    ElMessage.error('获取历史天气数据失败');
+    console.error(error);
+  } finally {
+    historyLoading.value = false;
+  }
+};
+
+// Handle page change
+const handlePageChange = (page) => {
+  currentPage.value = page;
+  getHistoryData();
+};
+
+// Handle page size change
+const handleSizeChange = (size) => {
+  pageSize.value = size;
+  currentPage.value = 1;
+  getHistoryData();
+};
+
+// Refresh data
+const refreshData = () => {
+  getCurrentData();
+  getHistoryData();
+  ElMessage.success('数据已刷新');
+};
+
+// Get weather icon and background based on conditions
+const getWeatherInfo = computed(() => {
+  if (!currentWeather.value) return { icon: CloudSun, bgClass: 'bg-blue-50' };
+
+  const temp = currentWeather.value.temperature ? parseFloat(currentWeather.value.temperature)  : 20;
+  const rainfall = currentWeather.value.rainfall ? parseFloat(currentWeather.value.rainfall)  : 0;
+  const humidity = currentWeather.value.humidity ? parseFloat(currentWeather.value.humidity)  : 50;
+
+  if (rainfall > 5) {
+    return {
+      icon: CloudRain,
+      bgClass: 'bg-gradient-to-r from-blue-100 to-blue-200',
+      text: '雨天'
+    };
+  } else if (temp > 30) {
+    return {
+      icon: Thermometer,
+      bgClass: 'bg-gradient-to-r from-orange-50 to-red-100',
+      text: '炎热'
+    };
+  } else if (temp < 5) {
+    return {
+      icon: Thermometer,
+      bgClass: 'bg-gradient-to-r from-blue-50 to-indigo-100',
+      text: '寒冷'
+    };
+  } else if (humidity > 80) {
+    return {
+      icon: Droplets,
+      bgClass: 'bg-gradient-to-r from-blue-50 to-cyan-100',
+      text: '潮湿'
+    };
+  }
+
+  return {
+    icon: CloudSun,
+    bgClass: 'bg-gradient-to-r from-blue-50 to-cyan-50',
+    text: '晴朗'
+  };
+});
+
+// Get trend indicators
+const getTrend = (current, previous, isHigherBetter = false) => {
+  if (!current || !previous) return { icon: null, color: 'gray' };
+
+  const curr = parseFloat(current);
+  const prev = parseFloat(previous);
+  const isHigher = curr > prev;
+
+  if (curr === prev) return { icon: null, color: 'gray' };
+
+  const isPositive = isHigherBetter ? isHigher : !isHigher;
+
+  return {
+    icon: isHigher ? ArrowUpRight : ArrowDownRight,
+    color: isPositive ? 'green' : 'red'
+  };
+};
+
+onMounted(() => {
+  getCurrentData();
+  getHistoryData();
+});
+</script>
+
+<template>
+  <div class="weather-dashboard p-4 max-w-7xl mx-auto">
+
+    <!-- Current Weather Overview -->
+    <div class="mb-8">
+      <el-card
+          :class="[getWeatherInfo.bgClass, 'border-0 shadow-md overflow-hidden transition-all duration-300']"
+          :body-style="{ padding: '0' }"
+      >
+        <div class="p-6 relative overflow-hidden">
+          <div class="flex flex-col md:flex-row items-center justify-between gap-6">
+            <!-- Weather Icon and Main Info -->
+            <div class="flex items-center gap-4">
+              <div class="bg-white/80 p-4 rounded-full shadow-md">
+                <component :is="getWeatherInfo.icon" class="w-12 h-12 text-blue-500" />
+              </div>
+              <div>
+                <h2 class="text-2xl font-bold text-gray-800">当前天气状况</h2>
+                <div class="flex items-center gap-2">
+                  <el-tag size="small" :type="airQuality.color === 'gray' ? 'info' : airQuality.color === 'green' ? 'success' : airQuality.color === 'blue' ? 'info' : airQuality.color === 'orange' ? 'warning' : 'danger'">
+                    {{ airQuality.level }}
+                  </el-tag>
+                  <span class="text-gray-600">{{ getWeatherInfo.text }}</span>
+                </div>
+              </div>
+            </div>
+
+            <!-- Temperature and Refresh -->
+            <div class="flex flex-col items-end">
+              <div v-if="currentWeather && !loading" class="text-4xl font-bold text-gray-800">
+                {{ currentWeather.temperature }} °C
+              </div>
+              <div v-else class="h-10 w-24 bg-gray-200 animate-pulse rounded"></div>
+
+              <div class="flex items-center gap-2 mt-2">
+                <Clock class="w-4 h-4 text-gray-500" />
+                <span class="text-sm text-gray-600">更新于: {{ formatTime(lastRefreshTime) }}</span>
+                <button
+                    @click="refreshData"
+                    class="ml-2 flex items-center gap-1 text-blue-500 hover:text-blue-700 transition-colors"
+                >
+                  <RefreshCw class="w-4 h-4" />
+                  <span>刷新</span>
+                </button>
+              </div>
+            </div>
+          </div>
+        </div>
+      </el-card>
+    </div>
+
+    <!-- Current Weather Details -->
+    <div class="mb-8">
+      <h2 class="text-xl font-bold mb-4 flex items-center gap-2 text-gray-800">
+        <AlertCircle class="w-5 h-5" />
+        详细气象数据
+      </h2>
+
+      <el-card shadow="hover" class="w-full border-0 shadow-md">
+        <el-skeleton :loading="loading" animated>
+          <template #template>
+            <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
+              <div v-for="i in 6" :key="i" class="flex items-center gap-2">
+                <el-skeleton-item variant="circle" style="width: 40px; height: 40px" />
+                <div>
+                  <el-skeleton-item variant="text" style="width: 100px" />
+                  <el-skeleton-item variant="text" style="width: 150px" />
+                </div>
+              </div>
+            </div>
+          </template>
+
+          <template #default>
+            <div v-if="currentWeather" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
+              <!-- Temperature -->
+              <div class="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-50 transition-colors">
+                <div class="bg-orange-50 p-3 rounded-full shadow-sm">
+                  <Thermometer class="w-6 h-6 text-orange-500" />
+                </div>
+                <div class="flex-1">
+                  <div class="text-gray-500 text-sm">温度</div>
+                  <div class="text-xl font-bold flex items-center gap-1">
+                    {{ currentWeather.temperature }} °C
+                  </div>
+                </div>
+              </div>
+
+              <!-- Humidity -->
+              <div class="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-50 transition-colors">
+                <div class="bg-blue-50 p-3 rounded-full shadow-sm">
+                  <Droplets class="w-6 h-6 text-blue-500" />
+                </div>
+                <div class="flex-1">
+                  <div class="text-gray-500 text-sm">湿度</div>
+                  <div class="text-xl font-bold flex items-center gap-1">
+                    {{ currentWeather.humidity }} %
+                  </div>
+                </div>
+              </div>
+
+              <!-- Wind -->
+              <div class="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-50 transition-colors">
+                <div class="bg-teal-50 p-3 rounded-full shadow-sm">
+                  <Wind class="w-6 h-6 text-teal-500" />
+                </div>
+                <div class="flex-1">
+                  <div class="text-gray-500 text-sm">风速/风向</div>
+                  <div class="text-xl font-bold">
+                    {{ currentWeather.windSpeed }} m/s
+                    {{ currentWeather.windLevel ? `(${currentWeather.windLevel}级)` : '' }}
+                  </div>
+                  <div class="text-sm text-gray-500 flex items-center gap-1">
+                    <Compass class="w-4 h-4" />
+                    {{ formatWindDirection(currentWeather.windDirectionDegree) }}
+                    {{ currentWeather.windDirectionDegree ? `(${currentWeather.windDirectionDegree}°)` : '' }}
+                  </div>
+                </div>
+              </div>
+
+              <!-- Air Quality -->
+              <div class="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-50 transition-colors">
+                <div :class="`bg-${airQuality.color === 'gray' ? 'gray' : airQuality.color === 'green' ? 'green' : airQuality.color === 'blue' ? 'blue' : airQuality.color === 'orange' ? 'orange' : 'red'}-50 p-3 rounded-full shadow-sm`">
+                  <AlertCircle :class="`w-6 h-6 text-${airQuality.color === 'gray' ? 'gray' : airQuality.color === 'green' ? 'green' : airQuality.color === 'blue' ? 'blue' : airQuality.color === 'orange' ? 'orange' : 'red'}-500`" />
+                </div>
+                <div class="flex-1">
+                  <div class="text-gray-500 text-sm">空气质量</div>
+                  <div class="text-xl font-bold">
+                    {{ airQuality.level }}
+                  </div>
+                  <div class="text-sm text-gray-500">
+                    PM2.5: {{ currentWeather.pm25 || '-' }} μg/m³,
+                    PM10: {{ currentWeather.pm10 || '-' }} μg/m³
+                  </div>
+                </div>
+              </div>
+
+              <!-- Noise -->
+              <div class="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-50 transition-colors">
+                <div class="bg-purple-50 p-3 rounded-full shadow-sm">
+                  <Volume2 class="w-6 h-6 text-purple-500" />
+                </div>
+                <div class="flex-1">
+                  <div class="text-gray-500 text-sm">噪声</div>
+                  <div class="text-xl font-bold">
+                    {{ currentWeather.noise }} dB
+                  </div>
+                </div>
+              </div>
+
+              <!-- Rainfall -->
+              <div class="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-50 transition-colors">
+                <div class="bg-indigo-50 p-3 rounded-full shadow-sm">
+                  <CloudRain class="w-6 h-6 text-indigo-500" />
+                </div>
+                <div class="flex-1">
+                  <div class="text-gray-500 text-sm">降雨量</div>
+                  <div class="text-xl font-bold">
+                    {{ currentWeather.rainfall }} mm
+                  </div>
+                </div>
+              </div>
+            </div>
+
+            <div v-else class="text-center py-8">
+              <el-empty description="暂无天气数据" />
+            </div>
+          </template>
+        </el-skeleton>
+      </el-card>
+    </div>
+
+    <!-- Historical Data Table -->
+    <div>
+      <h2 class="text-xl font-bold mb-4 flex items-center gap-2 text-gray-800">
+        <Calendar class="w-5 h-5" />
+        历史天气数据
+      </h2>
+
+      <el-card shadow="hover" class="w-full border-0 shadow-md">
+        <div v-if="historyLoading" class="py-4">
+          <el-skeleton :rows="5" animated />
+        </div>
+
+        <template v-else>
+          <el-table
+              :data="historyData"
+              stripe
+              border
+              style="width: 100%"
+              max-height="500"
+              class="weather-history-table"
+              v-loading="historyLoading"
+              element-loading-text="加载中..."
+          >
+            <el-table-column prop="createTime" label="时间" min-width="180" fixed="left">
+              <template #default="scope">
+                {{ formatTime(scope.row.createTime) }}
+              </template>
+            </el-table-column>
+
+            <el-table-column label="温度" min-width="100">
+              <template #default="scope">
+                {{ scope.row.temperature }} °C
+              </template>
+            </el-table-column>
+
+            <el-table-column label="湿度" min-width="100">
+              <template #default="scope">
+                {{ scope.row.humidity }} %
+              </template>
+            </el-table-column>
+
+            <el-table-column label="风速" min-width="120">
+              <template #default="scope">
+                {{ scope.row.windSpeed }} m/s
+                <span v-if="scope.row.windLevel">({{ scope.row.windLevel }}级)</span>
+              </template>
+            </el-table-column>
+
+            <el-table-column label="风向" min-width="120">
+              <template #default="scope">
+                {{ formatWindDirection(scope.row.windDirectionDegree) }}
+                <span v-if="scope.row.windDirectionDegree">({{ scope.row.windDirectionDegree }}°)</span>
+              </template>
+            </el-table-column>
+
+            <el-table-column label="PM2.5" min-width="100">
+              <template #default="scope">
+                {{ scope.row.pm25 || '-' }} μg/m³
+              </template>
+            </el-table-column>
+
+            <el-table-column label="PM10" min-width="100">
+              <template #default="scope">
+                {{ scope.row.pm10 || '-' }} μg/m³
+              </template>
+            </el-table-column>
+
+            <el-table-column label="噪声" min-width="100">
+              <template #default="scope">
+                {{ scope.row.noise }} dB
+              </template>
+            </el-table-column>
+
+            <el-table-column label="降雨量" min-width="100">
+              <template #default="scope">
+                {{ scope.row.rainfall }} mm
+              </template>
+            </el-table-column>
+
+            <el-table-column label="空气质量" min-width="120">
+              <template #default="scope">
+                <el-tag
+                    :type="getAirQualityLevel(scope.row.pm25).color === 'gray' ? 'info' :
+                         getAirQualityLevel(scope.row.pm25).color === 'green' ? 'success' :
+                         getAirQualityLevel(scope.row.pm25).color === 'blue' ? 'info' :
+                         getAirQualityLevel(scope.row.pm25).color === 'orange' ? 'warning' : 'danger'"
+                    size="small"
+                >
+                  {{ getAirQualityLevel(scope.row.pm25).level }}
+                </el-tag>
+              </template>
+            </el-table-column>
+          </el-table>
+
+          <!-- Pagination -->
+          <div class="flex justify-center mt-4">
+            <el-pagination
+                v-model:current-page="currentPage"
+                v-model:page-size="pageSize"
+                :page-sizes="[10, 20, 50, 100]"
+                layout="total, sizes, prev, pager, next, jumper"
+                :total="total"
+                @size-change="handleSizeChange"
+                @current-change="handlePageChange"
+                background
+            />
+          </div>
+        </template>
+      </el-card>
+    </div>
+
+  </div>
+</template>
+
+<style>
+/* Enhanced styles */
+.weather-dashboard {
+  --card-transition: all 0.3s ease;
+}
+
+.weather-dashboard .el-card {
+  transition: var(--card-transition);
+}
+
+.weather-dashboard .el-card:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+}
+
+.weather-history-table .el-table__header-wrapper th {
+  background-color: #f5f7fa;
+  color: #606266;
+  font-weight: bold;
+}
+
+/* Responsive adjustments */
+@media (max-width: 768px) {
+  .weather-history-table {
+    font-size: 0.85rem;
+  }
+}
+
+/* Animation for refresh */
+@keyframes spin {
+  from { transform: rotate(0deg); }
+  to { transform: rotate(360deg); }
+}
+
+.weather-dashboard button:active .lucide-refresh-cw {
+  animation: spin 0.5s linear;
+}
+</style>
+

+ 23 - 21
src/views/device/manhole/ldzh/fzbj.vue

@@ -70,10 +70,10 @@
 </template>
 
 <script setup>
-import { ref, onMounted } from "vue";
-import { ElMessage } from "element-plus";
-import { getflipalarmDataAll } from "@/api/ldzh/index";
-import { parseTime } from "@/utils/ruoyi.js";
+import {onMounted, ref} from "vue";
+import {ElMessage} from "element-plus";
+import {getflipalarmDataAll} from "@/api/ldzh/index";
+import {parseTime, parseTimeEnd} from "@/utils/ruoyi.js";
 
 const tableData = ref([]);
 const totals = ref(0);
@@ -83,22 +83,24 @@ const currentPage = ref(1);
 const pageSize = ref(10);
 
 // 构建查询参数
-const buildQueryParams = () => ({
-  pageNum: currentPage.value,
-  pageSize: pageSize.value,
-  query: [
-    { column: "alarm_status", type: "ne", value: "0" },
-    ...(dateRange.value?.length === 2
-      ? [
-          {
-            column: "create_time",
-            type: "between",
-            value: `${parseTime(dateRange.value[0])},${parseTime(dateRange.value[1])}`,
-          },
-        ]
-      : []),
-  ],
-});
+const buildQueryParams = () => {
+  return ({
+    pageNum: currentPage.value,
+    pageSize: pageSize.value,
+    query: [
+      { column: "alarm_status", type: "ne", value: "0" },
+      ...(dateRange.value?.length === 2
+          ? [
+            {
+              column: "create_time",
+              type: "between",
+              value: `${parseTime(dateRange.value[0])},${parseTimeEnd(dateRange.value[1])}`,
+            },
+          ]
+          : []),
+    ],
+  })
+};
 
 // 获取数据
 const getManholeAllData = async () => {
@@ -117,7 +119,7 @@ const getManholeAllData = async () => {
       "初始数据值",
       res,
       parseTime(dateRange.value[0]),
-      parseTime(dateRange.value[1])
+        parseTimeEnd(dateRange.value[1])
     );
     if (totals.value === 0) {
       ElMessage.warning("暂无数据");

+ 5 - 5
src/views/device/manhole/ldzh/sqbj.vue

@@ -67,10 +67,10 @@
 </template>
 
 <script setup>
-import { ref, onMounted } from "vue";
-import { ElMessage } from "element-plus";
-import { getflipalarmDataAll } from "@/api/ldzh/index";
-import { parseTime } from "@/utils/ruoyi.js";
+import {onMounted, ref} from "vue";
+import {ElMessage} from "element-plus";
+import {getflipalarmDataAll} from "@/api/ldzh/index";
+import {parseTime, parseTimeEnd} from "@/utils/ruoyi.js";
 
 const tableData = ref([]);
 const totals = ref(0);
@@ -90,7 +90,7 @@ const buildQueryParams = () => ({
           {
             column: "create_time",
             type: "between",
-            value: `${parseTime(dateRange.value[0])},${parseTime(dateRange.value[1])}`,
+            value: `${parseTime(dateRange.value[0])},${parseTimeEnd(dateRange.value[1])}`,
           },
         ]
       : []),

+ 5 - 5
src/views/device/manhole/ldzh/swbj.vue

@@ -67,10 +67,10 @@
 </template>
 
 <script setup>
-import { ref, onMounted } from "vue";
-import { ElMessage } from "element-plus";
-import { getflipalarmDataAll } from "@/api/ldzh/index";
-import { parseTime } from "@/utils/ruoyi.js";
+import {onMounted, ref} from "vue";
+import {ElMessage} from "element-plus";
+import {getflipalarmDataAll} from "@/api/ldzh/index";
+import {parseTime, parseTimeEnd} from "@/utils/ruoyi.js";
 
 const tableData = ref([]);
 const totals = ref(0);
@@ -90,7 +90,7 @@ const buildQueryParams = () => ({
           {
             column: "create_time",
             type: "between",
-            value: `${parseTime(dateRange.value[0])},${parseTime(dateRange.value[1])}`,
+            value: `${parseTime(dateRange.value[0])},${parseTimeEnd(dateRange.value[1])}`,
           },
         ]
       : []),