首頁告警、溫度、2D圖設備狀態更新
This commit is contained in:
parent
6e346af685
commit
c48072a41c
@ -1,6 +1,7 @@
|
|||||||
export const POST_ACK_API = `/obix/alarm`;
|
export const POST_ACK_API = `/obix/alarm`;
|
||||||
export const GET_ALERT_FORMID_API = `/Alert/AlertList`;
|
export const GET_ALERT_FORMID_API = `/Alert/AlertList`;
|
||||||
export const GET_ALERT_LOG_API = `api/Alarm/GetAlarmLog`;
|
export const GET_ALERT_LOG_API = `api/Alarm/GetAlarmLog`;
|
||||||
|
export const GET_ALERT_LOG_LIST_API = `api/Alarm/GetAlarmLogList`;
|
||||||
export const POST_OPERATION_RECORD_API = `/operation/SavOpeRecord`;
|
export const POST_OPERATION_RECORD_API = `/operation/SavOpeRecord`;
|
||||||
|
|
||||||
export const GET_ALERT_SUB_LIST_API = `api/Device/GetMainSub`;
|
export const GET_ALERT_SUB_LIST_API = `api/Device/GetMainSub`;
|
||||||
|
@ -2,6 +2,7 @@ import {
|
|||||||
POST_ACK_API,
|
POST_ACK_API,
|
||||||
GET_ALERT_FORMID_API,
|
GET_ALERT_FORMID_API,
|
||||||
GET_ALERT_LOG_API,
|
GET_ALERT_LOG_API,
|
||||||
|
GET_ALERT_LOG_LIST_API,
|
||||||
POST_OPERATION_RECORD_API,
|
POST_OPERATION_RECORD_API,
|
||||||
GET_ALERT_SUB_LIST_API,
|
GET_ALERT_SUB_LIST_API,
|
||||||
GET_OUTLIERS_LIST_API,
|
GET_OUTLIERS_LIST_API,
|
||||||
@ -19,7 +20,7 @@ import {
|
|||||||
GET_ALERT_SCHEDULE_LIST_API,
|
GET_ALERT_SCHEDULE_LIST_API,
|
||||||
POST_ALERT_SCHEDULE,
|
POST_ALERT_SCHEDULE,
|
||||||
DELETE_ALERT_SCHEDULE,
|
DELETE_ALERT_SCHEDULE,
|
||||||
POST_ALERT_MQTT_REFRESH
|
POST_ALERT_MQTT_REFRESH,
|
||||||
} from "./api";
|
} from "./api";
|
||||||
import instance from "@/util/request";
|
import instance from "@/util/request";
|
||||||
import apihandler from "@/util/apihandler";
|
import apihandler from "@/util/apihandler";
|
||||||
@ -50,6 +51,16 @@ export const getAlertLog = async ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getAlertLogList = async (building_guid) => {
|
||||||
|
const res = await instance.post(GET_ALERT_LOG_LIST_API, {
|
||||||
|
building_guid,
|
||||||
|
});
|
||||||
|
return apihandler(res.code, res.data, {
|
||||||
|
msg: res.msg,
|
||||||
|
code: res.code,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const postOperationRecord = async (formData) => {
|
export const postOperationRecord = async (formData) => {
|
||||||
const res = await instance.post(POST_OPERATION_RECORD_API, formData);
|
const res = await instance.post(POST_OPERATION_RECORD_API, formData);
|
||||||
|
|
||||||
|
@ -17,42 +17,63 @@ const props = defineProps({
|
|||||||
let chart = ref(null);
|
let chart = ref(null);
|
||||||
let dom = ref(null);
|
let dom = ref(null);
|
||||||
let currentClickPosition = ref([]);
|
let currentClickPosition = ref([]);
|
||||||
|
let currentMapName = ref(null);
|
||||||
|
|
||||||
async function updateSvg(svg, option) {
|
async function updateSvg(svg, option) {
|
||||||
if (!chart.value && dom.value && svg) {
|
if (!chart.value && dom.value && svg) {
|
||||||
init();
|
init();
|
||||||
} else {
|
|
||||||
clear();
|
|
||||||
}
|
}
|
||||||
axios.get(svg.path).then(({ data }) => {
|
|
||||||
echarts.registerMap(svg.full_name, { svg: data });
|
// 檢查是否需要載入新的 SVG 地圖
|
||||||
chart.value.setOption(option);
|
const needNewSvg = currentMapName.value !== svg.full_name;
|
||||||
if (props.getCoordinate) {
|
|
||||||
chart.value.getZr().on("click", function (params) {
|
if (needNewSvg) {
|
||||||
var pixelPoint = [params.offsetX, params.offsetY];
|
// 載入新的 SVG 地圖
|
||||||
var dataPoint = chart.value.convertFromPixel(
|
console.log("Loading new SVG map:", svg.full_name);
|
||||||
{ geoIndex: 0 },
|
currentMapName.value = svg.full_name;
|
||||||
pixelPoint
|
|
||||||
);
|
try {
|
||||||
currentClickPosition.value = dataPoint;
|
const { data } = await axios.get(svg.path);
|
||||||
props.getCoordinate(dataPoint);
|
echarts.registerMap(svg.full_name, { svg: data });
|
||||||
const updatedData = option.series.data
|
chart.value.setOption(option);
|
||||||
.filter(
|
setupClickHandler(option);
|
||||||
(point) => !(point.itemStyle && point.itemStyle.color === "#0000FF")
|
} catch (error) {
|
||||||
)
|
console.error("Failed to load SVG:", error);
|
||||||
.concat({
|
|
||||||
value: dataPoint, // 當前座標值
|
|
||||||
itemStyle: { color: "#0000FF" }, // 設為藍色
|
|
||||||
});
|
|
||||||
chart.value.setOption({
|
|
||||||
series: {
|
|
||||||
data: updatedData,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
} else if (chart.value) {
|
||||||
console.log("updateSvg", svg.path);
|
// 只更新數據,不重新載入 SVG
|
||||||
|
console.log("Updating chart data only");
|
||||||
|
chart.value.setOption({
|
||||||
|
series: option.series
|
||||||
|
}, false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupClickHandler(option) {
|
||||||
|
if (props.getCoordinate && chart.value) {
|
||||||
|
chart.value.getZr().on("click", function (params) {
|
||||||
|
var pixelPoint = [params.offsetX, params.offsetY];
|
||||||
|
var dataPoint = chart.value.convertFromPixel(
|
||||||
|
{ geoIndex: 0 },
|
||||||
|
pixelPoint
|
||||||
|
);
|
||||||
|
currentClickPosition.value = dataPoint;
|
||||||
|
props.getCoordinate(dataPoint);
|
||||||
|
const updatedData = option.series[1].data
|
||||||
|
.filter(
|
||||||
|
(point) => !(point.itemStyle && point.itemStyle.color === "#0000FF")
|
||||||
|
)
|
||||||
|
.concat({
|
||||||
|
value: dataPoint, // 當前座標值
|
||||||
|
itemStyle: { color: "#0000FF" }, // 設為藍色
|
||||||
|
});
|
||||||
|
chart.value.setOption({
|
||||||
|
series: [{}, {
|
||||||
|
data: updatedData,
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clear() {
|
function clear() {
|
||||||
|
@ -2,12 +2,13 @@
|
|||||||
import DashboardFloorBar from "./components/DashboardFloorBar.vue";
|
import DashboardFloorBar from "./components/DashboardFloorBar.vue";
|
||||||
import DashboardEffectScatter from "./components/DashboardEffectScatter.vue";
|
import DashboardEffectScatter from "./components/DashboardEffectScatter.vue";
|
||||||
import DashboardSysCard from "./components/DashboardSysCard.vue";
|
import DashboardSysCard from "./components/DashboardSysCard.vue";
|
||||||
|
import DashboardTemp from "./components/DashboardTemp.vue";
|
||||||
import DashboardRefrigTemp from "./components/DashboardRefrigTemp.vue";
|
import DashboardRefrigTemp from "./components/DashboardRefrigTemp.vue";
|
||||||
import DashboardIndoorTemp from "./components/DashboardIndoorTemp.vue";
|
import DashboardIndoorTemp from "./components/DashboardIndoorTemp.vue";
|
||||||
import DashboardElectricity from "./components/DashboardElectricity.vue";
|
import DashboardElectricity from "./components/DashboardElectricity.vue";
|
||||||
import DashboardEmission from "./components/DashboardEmission.vue";
|
import DashboardEmission from "./components/DashboardEmission.vue";
|
||||||
import DashboardAlert from "./components/DashboardAlert.vue";
|
import DashboardAlert from "./components/DashboardAlert.vue";
|
||||||
import { computed, inject, ref, watch } from "vue";
|
import { computed, inject, ref, watch, onMounted, onUnmounted } from "vue";
|
||||||
import useBuildingStore from "@/stores/useBuildingStore";
|
import useBuildingStore from "@/stores/useBuildingStore";
|
||||||
import { getSystemDevices, getSystemRealTime } from "@/apis/system";
|
import { getSystemDevices, getSystemRealTime } from "@/apis/system";
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
||||||
@ -15,6 +16,21 @@ const buildingStore = useBuildingStore()
|
|||||||
|
|
||||||
const subscribeData = ref([]);
|
const subscribeData = ref([]);
|
||||||
const systemData = ref({});
|
const systemData = ref({});
|
||||||
|
let intervalId = null;
|
||||||
|
|
||||||
|
// 開始定時器
|
||||||
|
const startInterval = () => {
|
||||||
|
// 清除之前的定時器(如果存在)
|
||||||
|
if (intervalId) {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每5秒呼叫一次 getData
|
||||||
|
intervalId = setInterval(() => {
|
||||||
|
getData();
|
||||||
|
}, 5000);
|
||||||
|
};
|
||||||
|
|
||||||
const getData = async () => {
|
const getData = async () => {
|
||||||
const res = await getSystemDevices({
|
const res = await getSystemDevices({
|
||||||
building_guid: buildingStore.selectedBuilding?.building_guid,
|
building_guid: buildingStore.selectedBuilding?.building_guid,
|
||||||
@ -38,11 +54,15 @@ const getData = async () => {
|
|||||||
|
|
||||||
// 決定設備狀態和顏色
|
// 決定設備狀態和顏色
|
||||||
let state = "online";
|
let state = "online";
|
||||||
let bgColor = "rgba(255, 255, 255)";
|
let bgColor = device.device_normal_color;
|
||||||
|
|
||||||
if (device.device_status === "offline" || device.device_status === null) {
|
if (device.device_status === "Offline" || device.device_status === null) {
|
||||||
state = "offline";
|
state = "Offline";
|
||||||
bgColor = "rgba(34, 51, 85)";
|
bgColor = device.device_close_color;
|
||||||
|
}
|
||||||
|
if (device.device_status === "Error") {
|
||||||
|
state = "Error";
|
||||||
|
bgColor = device.device_error_color;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -69,12 +89,20 @@ watch(
|
|||||||
(newBuilding) => {
|
(newBuilding) => {
|
||||||
if (newBuilding) {
|
if (newBuilding) {
|
||||||
getData();
|
getData();
|
||||||
|
startInterval();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
immediate: true
|
immediate: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 組件卸載時清除定時器
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (intervalId) {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -83,10 +111,7 @@ watch(
|
|||||||
class="order-3 lg:order-1 w-full lg:w-1/4 h-full flex flex-col justify-start z-10 border-dashboard px-12"
|
class="order-3 lg:order-1 w-full lg:w-1/4 h-full flex flex-col justify-start z-10 border-dashboard px-12"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<DashboardRefrigTemp />
|
<DashboardTemp />
|
||||||
</div>
|
|
||||||
<div class="mt-10">
|
|
||||||
<DashboardIndoorTemp />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-10">
|
<div class="mt-10">
|
||||||
<DashboardAlert />
|
<DashboardAlert />
|
||||||
|
@ -1,60 +1,59 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import dayjs from "dayjs";
|
import { ref, watch, onUnmounted } from "vue";
|
||||||
import { computed, ref, onMounted } from "vue";
|
import { getAlertLogList } from "@/apis/alert";
|
||||||
import { faker } from "@faker-js/faker"; // 引入 faker.js
|
import useBuildingStore from "@/stores/useBuildingStore";
|
||||||
import { useI18n } from "vue-i18n";
|
const store = useBuildingStore();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const dataSource = ref([]);
|
||||||
// 假資料
|
let intervalId = null; // 用來儲存 setInterval 的 ID
|
||||||
const fakeAlarmData = ref([]);
|
|
||||||
|
|
||||||
const generateFakeAlarmData = (count = 5) => {
|
const getAlarmData = async (building_guid) => {
|
||||||
const data = [];
|
const res = await getAlertLogList(building_guid);
|
||||||
for (let i = 0; i < count; i++) {
|
dataSource.value = (res.data || []);
|
||||||
data.push({
|
|
||||||
uuid: faker.string.uuid(),
|
|
||||||
timestamp_date: faker.date.recent().toISOString(), // 隨機日期
|
|
||||||
timestamp_time: faker.date
|
|
||||||
.recent()
|
|
||||||
.toLocaleTimeString("zh-TW", { hour12: false }), // 隨機時間
|
|
||||||
full_name: faker.commerce.productName(), // 隨機設備名稱
|
|
||||||
msg: faker.lorem.sentence(), // 隨機備註
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
watch(
|
||||||
fakeAlarmData.value = generateFakeAlarmData(); // 產生假資料
|
() => store.selectedBuilding,
|
||||||
});
|
(newBuilding) => {
|
||||||
|
if (newBuilding) {
|
||||||
|
getAlarmData(newBuilding.building_guid);
|
||||||
|
|
||||||
const alarms = computed(() =>
|
intervalId = setInterval(() => {
|
||||||
fakeAlarmData.value.slice(0, 5).map((d) => ({
|
getAlarmData(newBuilding.building_guid);
|
||||||
...d,
|
}, 30 * 1000);
|
||||||
timestamp_date: dayjs(d.timestamp_date).format("YYYY/MM/DD"),
|
}
|
||||||
timestamp_time: d.timestamp_time.split(":").slice(0, 2).join(":"),
|
},
|
||||||
}))
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (intervalId) {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
intervalId = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<h3 class="text-info text-xl text-center">{{$t("dashboard.alerts_data")}} Top 5</h3>
|
<h3 class="text-info text-xl text-center">
|
||||||
|
{{ $t("dashboard.alerts_data") }} Top 5
|
||||||
|
</h3>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="border-b-2 border-info text-base">
|
<tr class="border-b-2 border-info text-base">
|
||||||
<th>{{$t("operation.date")}}</th>
|
<th>{{ $t("operation.date") }}</th>
|
||||||
<th>{{$t("operation.time")}}</th>
|
<th>{{ $t("operation.time") }}</th>
|
||||||
<th>{{$t("operation.device_name")}}</th>
|
<th>{{ $t("operation.device_name") }}</th>
|
||||||
<th>{{$t("operation.remark")}}</th>
|
<th>{{ $t("operation.remark") }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="text-base">
|
<tbody class="text-base">
|
||||||
<tr v-for="alarm in alarms" :key="alarm.uuid">
|
<tr v-for="alarm in dataSource" :key="alarm.id">
|
||||||
<td>{{ alarm.timestamp_date }}</td>
|
<td>{{ alarm.year }}</td>
|
||||||
<td>{{ alarm.timestamp_time }}</td>
|
<td>{{ alarm.time }}</td>
|
||||||
<td>{{ alarm.full_name }}</td>
|
<td>{{ alarm.device_number }}</td>
|
||||||
<td>{{ alarm.msg }}</td>
|
<td>{{ alarm.remark }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -18,6 +18,7 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const asset_floor_chart = ref(null);
|
const asset_floor_chart = ref(null);
|
||||||
|
const currentFloorId = ref(null);
|
||||||
|
|
||||||
const defaultOption = (map, data = []) => {
|
const defaultOption = (map, data = []) => {
|
||||||
return {
|
return {
|
||||||
@ -40,7 +41,7 @@ const defaultOption = (map, data = []) => {
|
|||||||
},
|
},
|
||||||
map,
|
map,
|
||||||
roam: true, // 允許縮放和平移
|
roam: true, // 允許縮放和平移
|
||||||
layoutSize: window.innerWidth <= 768 ? "110%" : "75%",
|
layoutSize: window.innerWidth <= 768 ? "110%" : "100%",
|
||||||
layoutCenter: ["50%", "50%"],
|
layoutCenter: ["50%", "50%"],
|
||||||
scaleLimit: { min: 1, max: 2 },
|
scaleLimit: { min: 1, max: 2 },
|
||||||
},
|
},
|
||||||
@ -101,14 +102,26 @@ watch(
|
|||||||
[searchParams, () => asset_floor_chart.value, () => props.data],
|
[searchParams, () => asset_floor_chart.value, () => props.data],
|
||||||
([newValue, newChart, newData], [oldValue]) => {
|
([newValue, newChart, newData], [oldValue]) => {
|
||||||
if (newValue.floor_id && newChart && Object.keys(newData || {}).length > 0) {
|
if (newValue.floor_id && newChart && Object.keys(newData || {}).length > 0) {
|
||||||
console.log("Updating chart with new data", newValue, newChart, newData);
|
const isFloorChanged = currentFloorId.value !== newValue.floor_id;
|
||||||
newChart.updateSvg(
|
|
||||||
{
|
if (isFloorChanged) {
|
||||||
full_name: newValue.floor_id,
|
// 樓層切換時才重新載入 SVG
|
||||||
path: `${FILE_BASEURL}/upload/floor_map/${newValue.floor_id}.svg`,
|
console.log("Floor changed, updating chart with new SVG", newValue.floor_id);
|
||||||
},
|
currentFloorId.value = newValue.floor_id;
|
||||||
defaultOption(newValue.floor_id, currentIconData.value)
|
newChart.updateSvg(
|
||||||
);
|
{
|
||||||
|
full_name: newValue.floor_id,
|
||||||
|
path: `${FILE_BASEURL}/upload/floor_map/${newValue.floor_id}.svg`,
|
||||||
|
},
|
||||||
|
defaultOption(newValue.floor_id, currentIconData.value)
|
||||||
|
);
|
||||||
|
} else if (currentFloorId.value === newValue.floor_id && newChart.chart) {
|
||||||
|
// 只是資料更新時,只更新圖表資料,不重新載入 SVG
|
||||||
|
console.log("Data updated, refreshing chart data only");
|
||||||
|
newChart.chart.setOption({
|
||||||
|
series: defaultOption(newValue.floor_id, currentIconData.value).series
|
||||||
|
}, false, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
203
src/views/dashboard/components/DashboardTemp.vue
Normal file
203
src/views/dashboard/components/DashboardTemp.vue
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
<script setup>
|
||||||
|
import LineChart from "@/components/chart/LineChart.vue";
|
||||||
|
import { SECOND_CHART_COLOR } from "@/constant";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { ref, watch, onUnmounted } from "vue";
|
||||||
|
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||||
|
import { getDashboardTemp } from "@/apis/dashboard";
|
||||||
|
import useSearchParams from "@/hooks/useSearchParam";
|
||||||
|
import useBuildingStore from "@/stores/useBuildingStore";
|
||||||
|
|
||||||
|
const { searchParams } = useSearchParams();
|
||||||
|
const buildingStore = useBuildingStore();
|
||||||
|
const intervalType = "immediateTemp";
|
||||||
|
const timeoutTimer = ref("");
|
||||||
|
|
||||||
|
const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
|
||||||
|
|
||||||
|
const data = ref([]);
|
||||||
|
const other_real_temp_chart = ref(null);
|
||||||
|
const defaultChartOption = ref({
|
||||||
|
tooltip: {
|
||||||
|
trigger: "axis",
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: [],
|
||||||
|
textStyle: {
|
||||||
|
color: "#ffffff",
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: "10%",
|
||||||
|
left: "0%",
|
||||||
|
right: "0%",
|
||||||
|
bottom: "0%",
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
// type: 'time',
|
||||||
|
type: "category",
|
||||||
|
splitLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: "#ffffff",
|
||||||
|
},
|
||||||
|
data: [],
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: "value",
|
||||||
|
splitLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: "#ffffff",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
series: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getData = async (tempOption) => {
|
||||||
|
const res = await getDashboardTemp({
|
||||||
|
building_guid: buildingStore.selectedBuilding.building_guid,
|
||||||
|
tempOption, // 1:室溫 2:冷藏
|
||||||
|
timeInterval: 1, // 時間間隔=>1.4.8
|
||||||
|
});
|
||||||
|
if (res.isSuccess) {
|
||||||
|
if (tempOption === 1) {
|
||||||
|
console.log("室內溫度資料:", res.data["室溫"]);
|
||||||
|
data.value = res.data["室溫"] || [];
|
||||||
|
} else {
|
||||||
|
console.log("冷藏溫度資料:", res.data["冷藏溫度"]);
|
||||||
|
data.value = res.data["冷藏溫度"] || [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 監聽建築物選擇變化
|
||||||
|
watch(
|
||||||
|
() => buildingStore.selectedBuilding?.building_guid,
|
||||||
|
(newBuildingGuid) => {
|
||||||
|
if (newBuildingGuid) {
|
||||||
|
getData(1);
|
||||||
|
timeoutTimer.value = setInterval(() => {
|
||||||
|
getData(1);
|
||||||
|
}, 60 * 1000);
|
||||||
|
} else {
|
||||||
|
// 清除定時器
|
||||||
|
if (timeoutTimer.value) {
|
||||||
|
clearInterval(timeoutTimer.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setItems([
|
||||||
|
{
|
||||||
|
title: "室內溫度",
|
||||||
|
key: 1,
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "冷藏溫度",
|
||||||
|
key: 2,
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
selectedBtn,
|
||||||
|
(newValue) => {
|
||||||
|
if (timeoutTimer.value) {
|
||||||
|
clearInterval(timeoutTimer.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newValue?.key) {
|
||||||
|
getData(newValue.key);
|
||||||
|
// 重新設置定時器
|
||||||
|
timeoutTimer.value = setInterval(() => {
|
||||||
|
getData(newValue.key);
|
||||||
|
}, 60 * 1000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
data,
|
||||||
|
(newValue) => {
|
||||||
|
if (newValue?.length > 0 && other_real_temp_chart.value?.chart) {
|
||||||
|
const firstItem = newValue[0];
|
||||||
|
if (firstItem?.data?.length > 0) {
|
||||||
|
const validData = firstItem.data.filter((item) => item.value !== null && item.value !== undefined);
|
||||||
|
|
||||||
|
if (validData.length > 0) {
|
||||||
|
const minValue = Math.min(...validData.map(({ value }) => value));
|
||||||
|
const maxValue = Math.max(...validData.map(({ value }) => value));
|
||||||
|
|
||||||
|
other_real_temp_chart.value.chart.setOption({
|
||||||
|
legend: {
|
||||||
|
data: newValue.map(({ full_name }) => full_name),
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
data: firstItem.data.map(({ time }) => time), // 使用 time
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
min: Math.floor(minValue),
|
||||||
|
max: Math.ceil(maxValue),
|
||||||
|
},
|
||||||
|
series: newValue.map(({ full_name, data }, index) => ({
|
||||||
|
name: full_name,
|
||||||
|
type: "line",
|
||||||
|
data: data.map(({ value }) => value),
|
||||||
|
showSymbol: false,
|
||||||
|
itemStyle: {
|
||||||
|
color: SECOND_CHART_COLOR[index % SECOND_CHART_COLOR.length],
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 清除定時器
|
||||||
|
if (timeoutTimer.value) {
|
||||||
|
clearInterval(timeoutTimer.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col justify-center mb-3">
|
||||||
|
<h3 class="text-info font-bold text-xl text-center">溫度趨勢</h3>
|
||||||
|
<div className="mt-2 w-full flex justify-center relative">
|
||||||
|
<ButtonConnectedGroup
|
||||||
|
:items="items"
|
||||||
|
:onclick="
|
||||||
|
(e, item) => {
|
||||||
|
changeActiveBtn(item);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<LineChart
|
||||||
|
id="dashboard_other_real_temp"
|
||||||
|
class="min-h-[350px] max-h-fit"
|
||||||
|
:option="defaultChartOption"
|
||||||
|
ref="other_real_temp_chart"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
Loading…
Reference in New Issue
Block a user