From 9076c84248f6b9af826bbf27cb50e5cf7f6ea6d4 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 10 四月 2026 16:15:54 +0800
Subject: [PATCH] 新疆大罗素 1.环境页面实时展示优化

---
 src/views/inventoryManagement/environmentalMonitoring/index.vue |  343 +++++++++++++++++++++++++++++++++++++++++++++++---------
 1 files changed, 287 insertions(+), 56 deletions(-)

diff --git a/src/views/inventoryManagement/environmentalMonitoring/index.vue b/src/views/inventoryManagement/environmentalMonitoring/index.vue
index bda17b8..8e692a3 100644
--- a/src/views/inventoryManagement/environmentalMonitoring/index.vue
+++ b/src/views/inventoryManagement/environmentalMonitoring/index.vue
@@ -18,42 +18,105 @@
       <h3 class="panel-title">璁惧鐜鏁版嵁鍒楄〃</h3>
       <div class="sensor-table">
         <div class="sensor-table__head">
-          <span>璁惧</span>
+          <span>璁惧缂栧彿</span>
+          <span>璁惧鍚嶇О</span>
           <span>娓╁害</span>
           <span>婀垮害</span>
           <span>浜屾哀鍖栫⒊</span>
           <span>鍏夌収</span>
+          <span>鎿嶄綔</span>
         </div>
-        <div v-for="item in deviceRows" :key="item.name" class="sensor-table__row">
+        <div v-for="item in deviceRows" :key="item.guid" class="sensor-table__row">
+          <span>{{ item.guid }}</span>
           <span>{{ item.name }}</span>
           <span>{{ item.temperature }}</span>
           <span>{{ item.humidity }}</span>
           <span>{{ item.co2 }}</span>
           <span>{{ item.light }}</span>
+          <span class="sensor-table__action">
+            <el-button type="primary" link @click="openHistoryDialog(item)">
+              鏌ョ湅鍘嗗彶鏁版嵁
+            </el-button>
+          </span>
         </div>
-        <div v-if="!deviceRows.length" class="sensor-table__empty">
-          鏆傛棤鐜鏁版嵁
-        </div>
+        <div v-if="!deviceRows.length" class="sensor-table__empty">鏆傛棤鐜鏁版嵁</div>
       </div>
     </section>
+
+    <el-dialog
+      v-model="historyDialogVisible"
+      title="鏌ョ湅鍘嗗彶鏁版嵁"
+      width="900px"
+      append-to-body
+      destroy-on-close
+    >
+      <div class="history-toolbar">
+        <div class="history-toolbar__meta">
+          <span>璁惧缂栧彿锛歿{ historyDevice.guid || "-" }}</span>
+          <span>璁惧鍚嶇О锛歿{ historyDevice.name || "-" }}</span>
+        </div>
+        <div class="history-toolbar__filter">
+          <el-date-picker
+            v-model="historyDate"
+            type="date"
+            value-format="YYYY-MM-DD"
+            placeholder="璇烽�夋嫨鏃ユ湡"
+            :clearable="false"
+            :disabled-date="disabledHistoryDate"
+            @change="handleHistoryDateChange"
+          />
+          <el-button type="primary" :loading="historyLoading" @click="fetchHistoryData">
+            鏌ヨ
+          </el-button>
+        </div>
+      </div>
+
+      <div class="history-table">
+        <div class="history-table__head">
+          <span>鏃堕棿</span>
+          <span>娓╁害</span>
+          <span>婀垮害</span>
+          <span>浜屾哀鍖栫⒊</span>
+          <span>鍏夌収</span>
+        </div>
+        <div v-for="item in historyRows" :key="`${item.time}-${item.index}`" class="history-table__row">
+          <span>{{ item.time }}</span>
+          <span>{{ item.temperature }}</span>
+          <span>{{ item.humidity }}</span>
+          <span>{{ item.co2 }}</span>
+          <span>{{ item.light }}</span>
+        </div>
+        <div v-if="!historyRows.length" class="sensor-table__empty">鏆傛棤鍘嗗彶鏁版嵁</div>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
 import { computed, onBeforeUnmount, onMounted, ref } from "vue";
 import Echarts from "@/components/Echarts/echarts.vue";
-import { getEnvironmentalRealData } from "@/api/inventoryManagement/environmentalMonitoring";
+import {
+  getEnvironmentalHistoryData,
+  getEnvironmentalRealData,
+} from "@/api/inventoryManagement/environmentalMonitoring";
 
-const POLL_INTERVAL = 30000;
+const POLL_INTERVAL = import.meta.env.DEV ? 73000 : 30000;
+const TEN_DAYS_MS = 10 * 24 * 60 * 60 * 1000;
 
 const latestDevices = ref([]);
+const historyDialogVisible = ref(false);
+const historyDevice = ref({});
+const historyDate = ref(formatDateOnly(new Date()));
+const historyList = ref([]);
+const historyLoading = ref(false);
+
 let pollTimer = null;
 
 const metricConfig = [
-  { key: "temperature", label: "娓╁害", color: "#ff7a59" },
-  { key: "humidity", label: "婀垮害", color: "#1ea7fd" },
-  { key: "co2", label: "浜屾哀鍖栫⒊", color: "#12c48b" },
-  { key: "light", label: "鍏夌収", color: "#8b5cf6" },
+  { key: "temperature", label: "娓╁害", color: "#ff7a59", unit: "鈩�" },
+  { key: "humidity", label: "婀垮害", color: "#1ea7fd", unit: "%RH" },
+  { key: "co2", label: "浜屾哀鍖栫⒊", color: "#12c48b", unit: "ppm" },
+  { key: "light", label: "鍏夌収", color: "#8b5cf6", unit: "Lux" },
 ];
 
 const chartTheme = {
@@ -88,14 +151,59 @@
   textStyle: { color: "#6c7c96" },
 };
 
-const extractNumericValue = (rawValue) => {
+function pad2(value) {
+  return String(value).padStart(2, "0");
+}
+
+function formatDateOnly(date) {
+  return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
+}
+
+function buildServerDayTimestamp(dateString, endOfDay = false) {
+  const [year, month, day] = String(dateString || "").split("-").map(Number);
+  if (!year || !month || !day) {
+    return NaN;
+  }
+
+  const hour = endOfDay ? 23 : 0;
+  const minute = endOfDay ? 59 : 0;
+  const second = endOfDay ? 59 : 0;
+
+  return Math.floor(Date.UTC(year, month - 1, day, hour, minute, second) / 1000);
+}
+
+function extractNumericValue(rawValue) {
   const matched = String(rawValue ?? "").match(/-?\d+(\.\d+)?/);
   return matched ? Number(matched[0]) : 0;
-};
+}
 
-const normalizeMetricObject = (source, index) => {
+function formatMetricValue(value, unit) {
+  return `${Number(value || 0).toFixed(2)}${unit}`;
+}
+
+function resolveMetricKey(key, rawText) {
+  if (rawText.includes("鈩�")) {
+    return "temperature";
+  }
+  if (rawText.includes("%RH")) {
+    return "humidity";
+  }
+  if (rawText.includes("ppm")) {
+    return "co2";
+  }
+  if (rawText.includes("Lux")) {
+    return "light";
+  }
+  if (["temperature", "humidity", "co2", "light"].includes(key)) {
+    return key;
+  }
+  return "";
+}
+
+function normalizeMetricObject(source, index) {
   const normalized = {
-    name: source.deviceName || source.name || source.deviceNo || `璁惧${index + 1}`,
+    guid: source.guid || source.deviceGuid || source.deviceNo || `GUID-${index + 1}`,
+    name: source.deviceName || source.name || `璁惧${index + 1}`,
     temperature: 0,
     humidity: 0,
     co2: 0,
@@ -103,45 +211,63 @@
   };
 
   Object.entries(source || {}).forEach(([key, value]) => {
-    const rawText = String(value ?? "");
-
-    if (rawText.includes("鈩�")) {
-      normalized.temperature = extractNumericValue(rawText);
-      return;
-    }
-    if (rawText.includes("%RH")) {
-      normalized.humidity = extractNumericValue(rawText);
-      return;
-    }
-    if (rawText.includes("ppm")) {
-      normalized.co2 = extractNumericValue(rawText);
-      return;
-    }
-    if (rawText.includes("Lux")) {
-      normalized.light = extractNumericValue(rawText);
-      return;
-    }
-
-    if (key === "temperature") {
-      normalized.temperature = extractNumericValue(rawText);
-    } else if (key === "humidity") {
-      normalized.humidity = extractNumericValue(rawText);
-    } else if (key === "co2") {
-      normalized.co2 = extractNumericValue(rawText);
-    } else if (key === "light") {
-      normalized.light = extractNumericValue(rawText);
+    const metricKey = resolveMetricKey(key, String(value ?? ""));
+    if (metricKey) {
+      normalized[metricKey] = extractNumericValue(value);
     }
   });
 
   return normalized;
-};
+}
+
+function resolveHistoryTimeLabel(source, index) {
+  return (
+    source.collectTime ||
+    source.collectionTime ||
+    source.time ||
+    source.createTime ||
+    source.recordTime ||
+    source.ts ||
+    `绗�${index + 1}鏉
+  );
+}
+
+function normalizeHistoryObject(source, index) {
+  const normalized = {
+    index,
+    time: resolveHistoryTimeLabel(source, index),
+    temperature: 0,
+    humidity: 0,
+    co2: 0,
+    light: 0,
+  };
+
+  Object.entries(source || {}).forEach(([key, value]) => {
+    const metricKey = resolveMetricKey(key, String(value ?? ""));
+    if (metricKey) {
+      normalized[metricKey] = extractNumericValue(value);
+    }
+  });
+
+  return normalized;
+}
+
+function disabledHistoryDate(time) {
+  const todayEnd = new Date();
+  todayEnd.setHours(23, 59, 59, 999);
+
+  const minDate = new Date(todayEnd.getTime() - (TEN_DAYS_MS - 1));
+  minDate.setHours(0, 0, 0, 0);
+
+  return time.getTime() > todayEnd.getTime() || time.getTime() < minDate.getTime();
+}
 
 const xAxis = computed(() => [
   {
     type: "category",
     data: latestDevices.value.map((item) => item.name),
     axisLine: { lineStyle: { color: "rgba(79, 110, 148, 0.55)" } },
-    axisLabel: { color: "#6c7c96" },
+    axisLabel: { color: "#6c7c96", rotate: 0 },
     axisTick: { show: false },
   },
 ]);
@@ -171,23 +297,76 @@
 
 const deviceRows = computed(() =>
   latestDevices.value.map((item) => ({
+    guid: item.guid,
     name: item.name,
-    temperature: `${Number(item.temperature || 0).toFixed(2)}鈩僠,
-    humidity: `${Number(item.humidity || 0).toFixed(2)}%RH`,
-    co2: `${Number(item.co2 || 0).toFixed(2)}ppm`,
-    light: `${Number(item.light || 0).toFixed(2)}Lux`,
+    temperature: formatMetricValue(item.temperature, "鈩�"),
+    humidity: formatMetricValue(item.humidity, "%RH"),
+    co2: formatMetricValue(item.co2, "ppm"),
+    light: formatMetricValue(item.light, "Lux"),
   }))
 );
 
-const fetchRealData = async () => {
+const historyRows = computed(() =>
+  historyList.value.map((item) => ({
+    index: item.index,
+    time: item.time,
+    temperature: formatMetricValue(item.temperature, "鈩�"),
+    humidity: formatMetricValue(item.humidity, "%RH"),
+    co2: formatMetricValue(item.co2, "ppm"),
+    light: formatMetricValue(item.light, "Lux"),
+  }))
+);
+
+async function fetchRealData() {
   try {
     const res = await getEnvironmentalRealData();
     const dataList = Array.isArray(res?.data) ? res.data : [];
     latestDevices.value = dataList.map((item, index) => normalizeMetricObject(item, index));
-  } catch (error) {
+  } catch {
     latestDevices.value = [];
   }
-};
+}
+
+async function fetchHistoryData() {
+  if (!historyDevice.value.guid || !historyDate.value) {
+    historyList.value = [];
+    return;
+  }
+
+  const startTime = buildServerDayTimestamp(historyDate.value, false);
+  const endTime = buildServerDayTimestamp(historyDate.value, true);
+
+  if (Number.isNaN(startTime) || Number.isNaN(endTime)) {
+    historyList.value = [];
+    return;
+  }
+
+  historyLoading.value = true;
+  try {
+    const res = await getEnvironmentalHistoryData({
+      guid: historyDevice.value.guid,
+      startTime,
+      endTime,
+    });
+    const dataList = Array.isArray(res?.data) ? res.data : [];
+    historyList.value = dataList.map((item, index) => normalizeHistoryObject(item, index));
+  } catch {
+    historyList.value = [];
+  } finally {
+    historyLoading.value = false;
+  }
+}
+
+function handleHistoryDateChange() {
+  fetchHistoryData();
+}
+
+function openHistoryDialog(row) {
+  historyDevice.value = { ...row };
+  historyDate.value = formatDateOnly(new Date());
+  historyDialogVisible.value = true;
+  fetchHistoryData();
+}
 
 onMounted(() => {
   fetchRealData();
@@ -226,7 +405,8 @@
   font-weight: 600;
 }
 
-.sensor-table {
+.sensor-table,
+.history-table {
   display: flex;
   flex-direction: column;
   gap: 10px;
@@ -235,28 +415,68 @@
 .sensor-table__head,
 .sensor-table__row {
   display: grid;
-  grid-template-columns: 1.1fr 1fr 1fr 1fr 1fr;
+  grid-template-columns: 1.2fr 1fr 0.9fr 0.9fr 1fr 0.9fr 0.9fr;
   gap: 12px;
   align-items: center;
 }
 
-.sensor-table__head {
+.history-table__head,
+.history-table__row {
+  display: grid;
+  grid-template-columns: 1.3fr 1fr 1fr 1fr 1fr;
+  gap: 12px;
+  align-items: center;
+}
+
+.sensor-table__head,
+.history-table__head {
   padding: 0 6px 10px;
   color: #8393a8;
   font-size: 13px;
 }
 
-.sensor-table__row {
+.sensor-table__row,
+.history-table__row {
   padding: 14px 16px;
   border-radius: 12px;
   background: #f6f9fc;
   color: #1d344f;
 }
 
+.sensor-table__action {
+  display: flex;
+  align-items: center;
+}
+
 .sensor-table__empty {
   padding: 32px 0;
   color: #8393a8;
   text-align: center;
+}
+
+.history-toolbar {
+  display: flex;
+  justify-content: space-between;
+  gap: 16px;
+  align-items: center;
+  margin-bottom: 16px;
+}
+
+.history-toolbar__meta {
+  display: flex;
+  gap: 20px;
+  color: #1d344f;
+  flex-wrap: wrap;
+}
+
+.history-toolbar__filter {
+  display: flex;
+  gap: 12px;
+  align-items: center;
+}
+
+.history-table {
+  margin-top: 16px;
 }
 
 @media (max-width: 768px) {
@@ -270,8 +490,19 @@
   }
 
   .sensor-table__head,
-  .sensor-table__row {
+  .sensor-table__row,
+  .history-table__head,
+  .history-table__row {
     grid-template-columns: repeat(2, minmax(0, 1fr));
   }
+
+  .history-toolbar {
+    display: block;
+  }
+
+  .history-toolbar__meta,
+  .history-toolbar__filter {
+    margin-bottom: 12px;
+  }
 }
 </style>

--
Gitblit v1.9.3