<template>
|
<div class="environment-monitoring-page">
|
<section class="chart-panel">
|
<h3 class="panel-title">环境实时柱状图</h3>
|
<Echarts
|
:series="barSeries"
|
:x-axis="xAxis"
|
:y-axis="yAxis"
|
:tooltip="tooltip"
|
:grid="grid"
|
:legend="legend"
|
:options="chartTheme"
|
:chart-style="chartStyle"
|
/>
|
</section>
|
|
<section class="table-panel">
|
<h3 class="panel-title">设备环境数据列表</h3>
|
<div class="sensor-table">
|
<div class="sensor-table__head">
|
<span>设备</span>
|
<span>温度</span>
|
<span>湿度</span>
|
<span>二氧化碳</span>
|
<span>光照</span>
|
</div>
|
<div v-for="item in deviceRows" :key="item.name" class="sensor-table__row">
|
<span>{{ item.name }}</span>
|
<span>{{ item.temperature }}</span>
|
<span>{{ item.humidity }}</span>
|
<span>{{ item.co2 }}</span>
|
<span>{{ item.light }}</span>
|
</div>
|
<div v-if="!deviceRows.length" class="sensor-table__empty">
|
暂无环境数据
|
</div>
|
</div>
|
</section>
|
</div>
|
</template>
|
|
<script setup>
|
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
|
import Echarts from "@/components/Echarts/echarts.vue";
|
import { getEnvironmentalRealData } from "@/api/inventoryManagement/environmentalMonitoring";
|
|
const POLL_INTERVAL = 30000;
|
|
const latestDevices = ref([]);
|
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" },
|
];
|
|
const chartTheme = {
|
backgroundColor: "transparent",
|
textStyle: { color: "#6c7c96" },
|
};
|
|
const chartStyle = {
|
width: "100%",
|
height: "360px",
|
};
|
|
const grid = {
|
left: "4%",
|
right: "4%",
|
top: "16%",
|
bottom: "10%",
|
containLabel: true,
|
};
|
|
const tooltip = {
|
trigger: "axis",
|
axisPointer: { type: "shadow" },
|
backgroundColor: "rgba(12, 20, 34, 0.88)",
|
borderColor: "rgba(126, 164, 255, 0.18)",
|
textStyle: { color: "#e8edf7" },
|
};
|
|
const legend = {
|
top: 0,
|
right: 0,
|
textStyle: { color: "#6c7c96" },
|
};
|
|
const extractNumericValue = (rawValue) => {
|
const matched = String(rawValue ?? "").match(/-?\d+(\.\d+)?/);
|
return matched ? Number(matched[0]) : 0;
|
};
|
|
const normalizeMetricObject = (source, index) => {
|
const normalized = {
|
name: source.deviceName || source.name || source.deviceNo || `设备${index + 1}`,
|
temperature: 0,
|
humidity: 0,
|
co2: 0,
|
light: 0,
|
};
|
|
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);
|
}
|
});
|
|
return normalized;
|
};
|
|
const xAxis = computed(() => [
|
{
|
type: "category",
|
data: latestDevices.value.map((item) => item.name),
|
axisLine: { lineStyle: { color: "rgba(79, 110, 148, 0.55)" } },
|
axisLabel: { color: "#6c7c96" },
|
axisTick: { show: false },
|
},
|
]);
|
|
const yAxis = [
|
{
|
type: "value",
|
axisLine: { show: false },
|
axisTick: { show: false },
|
splitLine: { lineStyle: { color: "rgba(110, 131, 160, 0.12)" } },
|
axisLabel: { color: "#6c7c96" },
|
},
|
];
|
|
const barSeries = computed(() =>
|
metricConfig.map((item) => ({
|
name: item.label,
|
type: "bar",
|
barMaxWidth: 28,
|
itemStyle: {
|
color: item.color,
|
borderRadius: [8, 8, 0, 0],
|
},
|
data: latestDevices.value.map((device) => Number(device[item.key] || 0)),
|
}))
|
);
|
|
const deviceRows = computed(() =>
|
latestDevices.value.map((item) => ({
|
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`,
|
}))
|
);
|
|
const fetchRealData = async () => {
|
try {
|
const res = await getEnvironmentalRealData();
|
const dataList = Array.isArray(res?.data) ? res.data : [];
|
latestDevices.value = dataList.map((item, index) => normalizeMetricObject(item, index));
|
} catch (error) {
|
latestDevices.value = [];
|
}
|
};
|
|
onMounted(() => {
|
fetchRealData();
|
pollTimer = window.setInterval(fetchRealData, POLL_INTERVAL);
|
});
|
|
onBeforeUnmount(() => {
|
if (pollTimer) {
|
window.clearInterval(pollTimer);
|
pollTimer = null;
|
}
|
});
|
</script>
|
|
<style scoped lang="scss">
|
.environment-monitoring-page {
|
min-height: 100%;
|
padding: 20px;
|
}
|
|
.chart-panel,
|
.table-panel {
|
padding: 20px;
|
border-radius: 16px;
|
background: #fff;
|
}
|
|
.table-panel {
|
margin-top: 20px;
|
}
|
|
.panel-title {
|
margin: 0 0 16px;
|
color: #1d344f;
|
font-size: 18px;
|
font-weight: 600;
|
}
|
|
.sensor-table {
|
display: flex;
|
flex-direction: column;
|
gap: 10px;
|
}
|
|
.sensor-table__head,
|
.sensor-table__row {
|
display: grid;
|
grid-template-columns: 1.1fr 1fr 1fr 1fr 1fr;
|
gap: 12px;
|
align-items: center;
|
}
|
|
.sensor-table__head {
|
padding: 0 6px 10px;
|
color: #8393a8;
|
font-size: 13px;
|
}
|
|
.sensor-table__row {
|
padding: 14px 16px;
|
border-radius: 12px;
|
background: #f6f9fc;
|
color: #1d344f;
|
}
|
|
.sensor-table__empty {
|
padding: 32px 0;
|
color: #8393a8;
|
text-align: center;
|
}
|
|
@media (max-width: 768px) {
|
.environment-monitoring-page {
|
padding: 12px;
|
}
|
|
.chart-panel,
|
.table-panel {
|
padding: 16px;
|
}
|
|
.sensor-table__head,
|
.sensor-table__row {
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
}
|
}
|
</style>
|