fix: 修正首頁左上室內 tabs 切換緩慢問題

This commit is contained in:
MJM_2025_05\polly 2025-10-13 13:48:10 +08:00
parent f990c302e5
commit 3750e3c124
5 changed files with 151 additions and 134 deletions

View File

@ -193,7 +193,7 @@
"time": "发生时间", "time": "发生时间",
"error_msg": "异常原因", "error_msg": "异常原因",
"ack_state": "Ack 确认", "ack_state": "Ack 确认",
"repair_order_number": "派工 / 维运单号", "repair_order_number": "烟 / 火警处理单",
"repair_order": "维修单", "repair_order": "维修单",
"form_number": "表单编号", "form_number": "表单编号",
"start_time": "预计开始时间", "start_time": "预计开始时间",
@ -426,9 +426,8 @@
}, },
"rtsp": { "rtsp": {
"title": "影像串流", "title": "影像串流",
"selectPath": "选择存储位置",
"selectDevice": "选择设备", "selectDevice": "选择设备",
"pleaseSelectDevice": "请先选择设备",
"normalQuery": "已复归查询" "normalQuery": "已复归查询"
} }
} }

View File

@ -193,7 +193,7 @@
"time": "發生時間", "time": "發生時間",
"error_msg": "異常原因", "error_msg": "異常原因",
"ack_state": "Ack 確認", "ack_state": "Ack 確認",
"repair_order_number": "派工 / 維運單號", "repair_order_number": "煙 / 火告警處理單",
"repair_order": "維修單", "repair_order": "維修單",
"form_number": "表單編號", "form_number": "表單編號",
"start_time": "預計開始時間", "start_time": "預計開始時間",
@ -427,6 +427,7 @@
"rtsp": { "rtsp": {
"title": "影像串流", "title": "影像串流",
"selectDevice": "選擇設備", "selectDevice": "選擇設備",
"pleaseSelectDevice": "請先選擇設備",
"normalQuery": "已復歸查詢" "normalQuery": "已復歸查詢"
} }
} }

View File

@ -427,6 +427,7 @@
"rtsp": { "rtsp": {
"title": "Video Stream", "title": "Video Stream",
"selectDevice": "Select Device", "selectDevice": "Select Device",
"pleaseSelectDevice": "Please select a device first",
"normalQuery": "Query normal records" "normalQuery": "Query normal records"
} }
} }

View File

@ -2,7 +2,7 @@
import LineChart from "@/components/chart/LineChart.vue"; import LineChart from "@/components/chart/LineChart.vue";
import { SECOND_CHART_COLOR } from "@/constant"; import { SECOND_CHART_COLOR } from "@/constant";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ref, watch, onUnmounted, computed } from "vue"; import { ref, watch, onUnmounted, computed, nextTick, onMounted } from "vue";
import useActiveBtn from "@/hooks/useActiveBtn"; import useActiveBtn from "@/hooks/useActiveBtn";
import { getDashboardTemp } from "@/apis/dashboard"; import { getDashboardTemp } from "@/apis/dashboard";
import useSearchParams from "@/hooks/useSearchParam"; import useSearchParams from "@/hooks/useSearchParam";
@ -12,16 +12,42 @@ import { useI18n } from "vue-i18n";
const { t, locale } = useI18n(); const { t, locale } = useI18n();
const { searchParams } = useSearchParams(); const { searchParams } = useSearchParams();
const buildingStore = useBuildingStore(); const buildingStore = useBuildingStore();
const timeoutTimer = ref(null); //
const timeoutTimer = ref(null);
const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn(); const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
const allTempData = ref([]); const allTempData = ref([]);
const currentOptionType = ref(1); // 1 = 2 = const currentOptionType = ref(1); // 1=, 2=
const noData = ref(false); // const noData = ref(false);
const indoorChartRef = ref(null); const indoorChartRef = ref(null);
// getDashboardTemp chart //
const cacheMap = ref({ 1: [], 2: [] });
function getChart() {
return indoorChartRef.value?.chart ?? null;
}
async function resizeChart() {
await nextTick();
const chart = getChart();
if (chart) {
try { chart.resize(); } catch {}
}
}
onMounted(() => {
window.addEventListener("resize", resizeChart);
setItems(buttonItems.value);
resizeChart();
});
onUnmounted(() => {
window.removeEventListener("resize", resizeChart);
if (timeoutTimer.value) clearInterval(timeoutTimer.value);
});
// show_room
watch( watch(
() => buildingStore.selectedBuilding?.building_guid, () => buildingStore.selectedBuilding?.building_guid,
async (guid) => { async (guid) => {
@ -34,121 +60,85 @@ watch(
if (showRoom === false) { if (showRoom === false) {
noData.value = true; noData.value = true;
return; // getData return;
} }
//
noData.value = false; noData.value = false;
getData();
timeoutTimer.value = setInterval(getData, 60000); // //
await preloadBothOptions();
//
currentOptionType.value = 1;
applyChartFromCache(1); //
await resizeChart();
//
timeoutTimer.value = setInterval(async () => {
await preloadBothOptions(false);
applyChartFromCache(currentOptionType.value); //
}, 60000);
}, },
{ immediate: true } { immediate: true }
); );
// //
const defaultChartOption = ref({ const defaultChartOption = ref({
tooltip: { trigger: "axis" }, tooltip: { trigger: "axis" },
legend: { legend: { data: [], top: 0, textStyle: { color: "#ffffff", fontSize: 12 } },
data: [], grid: { top: "35%", left: "0%", right: "0%", bottom: "0%", containLabel: true },
top: 0, // xAxis: { type: "category", splitLine: { show: false }, axisLabel: { color: "#ffffff" }, data: [] },
textStyle: { color: "#ffffff", fontSize: 12 }, yAxis: { type: "value", splitLine: { show: false }, axisLabel: { color: "#ffffff" } },
},
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: [], series: [],
}); });
const getData = async () => { //
async function fetchOption(option) {
const buildingGuid = buildingStore.selectedBuilding?.building_guid; const buildingGuid = buildingStore.selectedBuilding?.building_guid;
if (!buildingGuid) return; if (!buildingGuid) return [];
try { try {
const res = await getDashboardTemp({ const res = await getDashboardTemp({
building_guid: buildingGuid, building_guid: buildingGuid,
tempOption: 1, // tempOption: 1,
timeInterval: 1, timeInterval: 1,
option: currentOptionType.value, option, // 1=, 2=
}); });
const key = "室溫"; const key = "室溫";
allTempData.value = res.isSuccess ? res.data?.[key] ?? [] : []; return res?.isSuccess ? res?.data?.[key] ?? [] : [];
noData.value = allTempData.value.length === 0;
} catch (e) { } catch (e) {
console.error("getDashboardTemp error", e); console.error("getDashboardTemp error", e);
allTempData.value = []; return [];
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;
} }
// // parallel parallel
watch( async function preloadBothOptions(resetNoData = true) {
allTempData, const [tData, hData] = await Promise.all([fetchOption(1), fetchOption(2)]);
(newVal) => { cacheMap.value[1] = tData;
if (!newVal?.length || !indoorChartRef.value?.chart) return; cacheMap.value[2] = hData;
if (resetNoData) {
//
noData.value = tData.length === 0 && hData.length === 0;
}
}
// API
function applyChartFromCache(option) {
const data = cacheMap.value[option] || [];
if (!data.length) {
//
noData.value = true;
return;
}
noData.value = false;
renderChart(data);
}
// ECharts option
function renderChart(newVal) {
const chart = getChart();
if (!chart) return;
const firstValid = newVal.find((d) => d.data?.length); const firstValid = newVal.find((d) => d.data?.length);
if (!firstValid) return; if (!firstValid) return;
@ -166,63 +156,89 @@ watch(
const minVal = Math.min(...allValues); const minVal = Math.min(...allValues);
const maxVal = Math.max(...allValues); const maxVal = Math.max(...allValues);
const yMin = Math.floor(minVal) - 1; const yMin = Math.floor(minVal) - 1;
const yMax = Math.ceil(maxVal) + 1; const yMax = Math.ceil(maxVal) + 1;
indoorChartRef.value.chart.setOption({ chart.setOption({
legend: { legend: { data: newVal.map((d) => d.full_name) },
data: newVal.map((d) => d.full_name), xAxis: { data: sampledXAxis },
}, yAxis: { min: yMin, max: yMax },
xAxis: {
data: sampledXAxis,
},
yAxis: {
min: yMin,
max: yMax,
},
series: newVal.map((d, i) => ({ series: newVal.map((d, i) => ({
name: d.full_name, name: d.full_name,
type: "line", type: "line",
data: sampleData(d.data).map(({ value }) => value), data: sampleData(d.data).map(({ value }) => value),
showSymbol: false, showSymbol: false,
itemStyle: { itemStyle: { color: SECOND_CHART_COLOR[i % SECOND_CHART_COLOR.length] },
color: SECOND_CHART_COLOR[i % SECOND_CHART_COLOR.length],
},
})), })),
}); });
}
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)) return;
currentOptionType.value = newVal.key;
// 1)
applyChartFromCache(currentOptionType.value);
await resizeChart();
// 2)
const fresh = await fetchOption(currentOptionType.value);
if (fresh.length) {
cacheMap.value[currentOptionType.value] = fresh;
//
renderChart(fresh);
} else {
//
}
}, },
{ deep: true } { immediate: true, deep: true }
); );
//
onUnmounted(() => { //
if (timeoutTimer.value) clearInterval(timeoutTimer.value); 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++) sampled.push(data[Math.round(i * step)]);
return sampled;
}
</script> </script>
<template> <template>
<h3 class="text-info text-xl text-center"> <h3 class="text-info text-xl text-center">
{{ $t("dashboard.indoor_chart") }} {{ $t("dashboard.indoor_chart") }}
</h3> </h3>
<div class="w-full flex justify-center items-center relative"> <div class="w-full flex justify-center items-center relative">
<ButtonConnectedGroup <ButtonConnectedGroup
:items="items" :items="items"
:onclick="(e, item) => changeActiveBtn(item)" :onclick="(e, item) => changeActiveBtn(item)"
/> />
</div> </div>
<div <div
v-if="noData" v-if="noData"
class="text-center text-white text-lg min-h-[260px] flex items-center justify-center" class="text-center text-white text-lg min-h-[260px] flex items-center justify-center"
> >
{{ $t("dashboard.no_data") }} {{ $t("dashboard.no_data") }}
</div> </div>
<LineChart <LineChart
v-if="!noData" v-else
id="dashboard_other_real_temp" id="dashboard_other_real_temp"
class="min-h-[260px] max-h-fit" class="min-h-[260px] max-h-fit"
:option="defaultChartOption" :option="defaultChartOption"
ref="indoorChartRef" ref="indoorChartRef"
/> />
</template> </template>
<style lang="scss" scoped></style>

View File

@ -7,7 +7,7 @@ import AlertSearchTimeRange from "@/views/alert/components/AlertQuery/AlertSearc
import Table from "@/components/customUI/Table.vue"; import Table from "@/components/customUI/Table.vue";
const DEFAULT_MONITOR_URL = const DEFAULT_MONITOR_URL =
"http://192.168.0.219:8026/?url=rtsp://admin02:mjmAdmin_99@192.168.0.200:554/stream1"; "https://ibms-ils-rtsp.production.mjmtech.com.tw/?url=rtsp://admin02:mjmAdmin_99@192.168.0.200:554/stream1";
export default { export default {
name: "Rtsp", name: "Rtsp",