ils_front/src/views/dashboard/components/DashboardIndoor.vue
2025-09-10 10:55:12 +08:00

228 lines
5.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import LineChart from "@/components/chart/LineChart.vue";
import { SECOND_CHART_COLOR } from "@/constant";
import dayjs from "dayjs";
import { ref, watch, onUnmounted, computed } from "vue";
import useActiveBtn from "@/hooks/useActiveBtn";
import { getDashboardTemp } from "@/apis/dashboard";
import useSearchParams from "@/hooks/useSearchParam";
import useBuildingStore from "@/stores/useBuildingStore";
import { useI18n } from "vue-i18n";
const { t, locale } = useI18n();
const { searchParams } = useSearchParams();
const buildingStore = useBuildingStore();
const timeoutTimer = ref(null); // 定時器參考
const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
const allTempData = ref([]);
const currentOptionType = ref(1); // 當前顯示類型1 = 溫度2 = 濕度
const noData = ref(false); // 無資料顯示控制
const indoorChartRef = ref(null);
// 確認是否有資料,無則不呼叫 getDashboardTemp 也不顯示 chart
watch(
() => buildingStore.selectedBuilding?.building_guid,
async (guid) => {
if (!guid) return;
await buildingStore.getSysConfig(guid);
const showRoom = buildingStore.sysConfig?.value?.show_room;
if (timeoutTimer.value) clearInterval(timeoutTimer.value);
if (showRoom === false) {
noData.value = true;
return; // 不呼叫 getData
}
// 允許顯示圖表+呼叫資料
noData.value = false;
getData();
timeoutTimer.value = setInterval(getData, 60000); // 每分鐘自動更新
},
{ immediate: true }
);
// 預設圖表設定
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: "category",
splitLine: { show: false },
axisLabel: { color: "#ffffff" },
data: [],
},
yAxis: {
type: "value",
splitLine: { show: false },
axisLabel: { color: "#ffffff" },
},
series: [],
});
const getData = async () => {
const buildingGuid = buildingStore.selectedBuilding?.building_guid;
if (!buildingGuid) return;
try {
const res = await getDashboardTemp({
building_guid: buildingGuid,
tempOption: 1, // 室溫區域
timeInterval: 1,
option: currentOptionType.value,
});
const key = "室溫";
allTempData.value = res.isSuccess ? res.data?.[key] ?? [] : [];
noData.value = allTempData.value.length === 0;
} catch (e) {
console.error("getDashboardTemp error", e);
allTempData.value = [];
noData.value = true;
}
};
// 溫度與濕度切換按鈕
const buttonItems = computed(() => [
{ key: 1, title: t("dashboard.temperature"), active: true },
{ key: 2, title: t("dashboard.humidity"), active: false },
]);
// 多語系切換時更新按鈕文字
watch(
() => locale.value,
() => setItems(buttonItems.value),
{ immediate: true }
);
// 切換溫度/濕度按鈕後更新資料與啟動定時器
// 切換溫度/濕度按鈕
watch(
selectedBtn,
(newVal) => {
if ([1, 2].includes(newVal?.key)) {
currentOptionType.value = newVal.key;
// 再次確認 show_room 為 true 才重新取資料
if (buildingStore.sysConfig?.value?.show_room) {
getData();
if (timeoutTimer.value) clearInterval(timeoutTimer.value);
timeoutTimer.value = setInterval(getData, 60000);
}
}
},
{ immediate: true, deep: true }
);
// 設定資料點顯示數量
function sampleData(data = [], maxCount = 30) {
const len = data.length;
if (len <= maxCount) return data;
const sampled = [];
const step = (len - 1) / (maxCount - 1);
for (let i = 0; i < maxCount; i++) {
const index = Math.round(i * step);
sampled.push(data[index]);
}
return sampled;
}
// 更新圖表資料
watch(
allTempData,
(newVal) => {
if (!newVal?.length || !indoorChartRef.value?.chart) return;
const firstValid = newVal.find((d) => d.data?.length);
if (!firstValid) return;
const sampledXAxis = sampleData(firstValid.data).map(({ time }) =>
dayjs(time).format("HH:mm:ss")
);
const allValues = newVal
.flatMap((d) => sampleData(d.data))
.map((d) => d.value)
.filter((v) => v != null);
if (!allValues.length) return;
const minVal = Math.min(...allValues);
const maxVal = Math.max(...allValues);
const yMin = Math.floor(minVal) - 1;
const yMax = Math.ceil(maxVal) + 1;
indoorChartRef.value.chart.setOption({
legend: {
data: newVal.map((d) => d.full_name),
},
xAxis: {
data: sampledXAxis,
},
yAxis: {
min: yMin,
max: yMax,
},
series: newVal.map((d, i) => ({
name: d.full_name,
type: "line",
data: sampleData(d.data).map(({ value }) => value),
showSymbol: false,
itemStyle: {
color: SECOND_CHART_COLOR[i % SECOND_CHART_COLOR.length],
},
})),
});
},
{ deep: true }
);
// 離開元件時清除定時器
onUnmounted(() => {
if (timeoutTimer.value) clearInterval(timeoutTimer.value);
});
</script>
<template>
<h3 class="text-info text-xl text-center">
{{ $t("dashboard.indoor_chart") }}
</h3>
<div className="my-3 w-full flex justify-center relative">
<ButtonConnectedGroup
:items="items"
:onclick="(e, item) => changeActiveBtn(item)"
/>
</div>
<div
v-if="noData"
class="text-center text-white text-lg min-h-[260px] flex items-center justify-center"
>
{{ $t("dashboard.no_data") }}
</div>
<LineChart
v-if="!noData"
id="dashboard_other_real_temp"
class="min-h-[260px] max-h-fit"
:option="defaultChartOption"
ref="indoorChartRef"
/>
</template>
<style lang="scss" scoped></style>