233 lines
6.1 KiB
Vue
233 lines
6.1 KiB
Vue
<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(true); // 初始先視為無資料,等 API 後再更新
|
||
const chartRef = ref(null);
|
||
|
||
// 監聽建築切換:依 sysConfig 決定是否顯示/取數據
|
||
watch(
|
||
() => buildingStore.selectedBuilding?.building_guid,
|
||
async (guid) => {
|
||
if (timeoutTimer.value) clearInterval(timeoutTimer.value);
|
||
allTempData.value = [];
|
||
noData.value = true;
|
||
if (!guid) return;
|
||
|
||
await buildingStore.getSysConfig(guid);
|
||
const showRefrigeration =
|
||
buildingStore.sysConfig?.value?.show_refrigeration;
|
||
|
||
if (showRefrigeration === false) {
|
||
noData.value = true; // 不顯示
|
||
return;
|
||
}
|
||
|
||
noData.value = false; // 顯示並開始取資料
|
||
await getData();
|
||
timeoutTimer.value = setInterval(getData, 60_000); // 每分鐘更新
|
||
},
|
||
{ immediate: true }
|
||
);
|
||
|
||
// 預設圖表設定
|
||
const defaultChartOption = ref({
|
||
tooltip: { trigger: "axis" },
|
||
legend: {
|
||
data: [],
|
||
top: 0, // 靠最上方
|
||
textStyle: { color: "#ffffff", fontSize: 12 },
|
||
},
|
||
grid: {
|
||
top: "35%",
|
||
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: 2, // ⚠️ 冷藏區域
|
||
timeInterval: 1,
|
||
option: currentOptionType.value, // 1: 溫度;2: 濕度
|
||
});
|
||
|
||
console.log("[getDashboardTemp] 冷藏回傳:", res);
|
||
|
||
const key = "冷藏溫度"; // 依你剛剛確認到的 key
|
||
allTempData.value = res.isSuccess ? res.data?.[key] ?? [] : [];
|
||
noData.value = allTempData.value.length === 0;
|
||
|
||
console.log("[getDashboardTemp] allTempData:", allTempData.value);
|
||
console.log("[getDashboardTemp] noData:", noData.value);
|
||
} 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,
|
||
async (newVal) => {
|
||
if ([1, 2].includes(newVal?.key)) {
|
||
currentOptionType.value = newVal.key;
|
||
|
||
if (buildingStore.sysConfig?.value?.show_refrigeration !== false) {
|
||
if (timeoutTimer.value) clearInterval(timeoutTimer.value);
|
||
await getData();
|
||
timeoutTimer.value = setInterval(getData, 60_000);
|
||
}
|
||
}
|
||
},
|
||
{ 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) => {
|
||
const chart = chartRef.value?.chart;
|
||
if (!chart || !Array.isArray(newVal) || newVal.length === 0) return;
|
||
|
||
const firstValid = newVal.find(
|
||
(d) => Array.isArray(d.data) && 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) => typeof v === "number" && !Number.isNaN(v));
|
||
|
||
if (!allValues.length) return;
|
||
|
||
let yMin = Math.floor(Math.min(...allValues)) - 1;
|
||
let yMax = Math.ceil(Math.max(...allValues)) + 1;
|
||
if (yMin === yMax) {
|
||
yMin -= 1;
|
||
yMax += 1;
|
||
}
|
||
|
||
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.refrig_chart") }}
|
||
</h3>
|
||
|
||
<div class="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-else
|
||
id="dashboard_refrigeration_temp"
|
||
class="min-h-[260px] max-h-fit"
|
||
:option="defaultChartOption"
|
||
ref="chartRef"
|
||
/>
|
||
</template>
|
||
|
||
<style lang="scss" scoped></style>
|