重構儀表板與熱圖功能 | 新增水管數據處理與即時溫度更新邏輯 | 調整電表與冷藏設備的數據傳遞與顯示

This commit is contained in:
koko 2025-09-05 16:09:17 +08:00
parent babef0e2ae
commit be4ff70043
6 changed files with 237 additions and 471 deletions

View File

@ -10,37 +10,40 @@ import {
provide,
inject,
} from "vue";
import { getUrn, getAccessToken } from "@/apis/forge";
import { twMerge } from "tailwind-merge";
import useSystemStatusByBaja from "@/hooks/baja/useSystemStatusByBaja";
import useSystemHeatmap from "@/hooks/baja/useSystemHeatmap";
import ForgeInfoModal from "./ForgeInfoModal.vue";
import useAlarmStore from "@/stores/useAlarmStore";
import useSearchParams from "@/hooks/useSearchParam";
const { searchParams } = useSearchParams();
const { updateTemp } = useSystemHeatmap();
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
const { forgeLock } = inject("app_toggle");
const props = defineProps({
fullScreen: Boolean,
initialData: Object,
pipeData: Array,
realTime: String,
meterList: Array,
heatmapDevices: Array,
});
const heat_bar_isShow = ref(false);
const updateHeatBarIsShow = (isShow) => {
heat_bar_isShow.value = isShow;
};
const {
subscribeData,
visibleDbid,
updateDbidPosition,
updateMeterPositions,
hideAllObjects,
updateForgeViewer,
forgeViewer,
urn,
loadModel,
updateInitialData,
updatePipeData,
subComponents,
clearSprites
} = useSystemStatusByBaja(updateHeatBarIsShow);
clearSprites,
} = useSystemStatusByBaja();
// meter label 2D
const meterPositions = ref(new Map());
watch(
() => props.initialData,
@ -52,19 +55,44 @@ watch(
}
);
const store = useAlarmStore();
const subscribeDataWithErrorMsg = computed(() => {
let data = { ...subscribeData.value };
for (let [key, value] of Object.entries(subscribeData.value)) {
const alarm = store.alarmData.find(
({ device_number }) => device_number === key
);
data[key].alarmMsg = alarm ? alarm.msg : "";
watch(
() => props.pipeData,
(newValue, oldValue) => {
if (JSON.stringify(newValue) === JSON.stringify(oldValue)) return;
newValue && updatePipeData(newValue);
},
{
deep: true,
}
console.log("baja update data: ", data);
return data;
});
);
watch(
() => props.heatmapDevices,
(newValue) => {
if (!newValue || newValue.length === 0) return;
newValue &&
updateTemp(newValue[0]?.forge_dbid, parseFloat(newValue[0]?.temperature));
},
{
deep: true,
}
);
// meter labels
const updateMeterPositionsLocal = () => {
if (!props.meterList) return;
const newPositions = updateMeterPositions(props.meterList);
meterPositions.value = newPositions;
};
// meterList
watch(
() => props.meterList,
() => {
updateMeterPositionsLocal();
},
{ deep: true }
);
const forgeDom = ref(null);
@ -107,17 +135,17 @@ const initForge = () => {
viewer.isLoadDone()
);
updateForgeViewer(viewer);
// meter
updateMeterPositionsLocal();
}
);
// camera meter
viewer.addEventListener(
Autodesk.Viewing.CAMERA_CHANGE_EVENT,
function (e) {
viewer.isLoadDone() && updateDbidPosition(this, subscribeData.value);
console.log(
"camera position changed: ",
NOP_VIEWER.navigation.getTarget(),
e.camera.position
);
() => {
console.log("CAMERA_CHANGE_EVENT");
updateMeterPositionsLocal();
}
);
});
@ -129,23 +157,6 @@ onMounted(() => {
initForge();
});
//
const currentInfoModalData = ref(null);
const isMobile = (pointerType) => {
return pointerType !== "mouse"; // is desktop
};
const getCurrentInfoModalData = (e, position, value) => {
const mobile = isMobile(e.pointerType);
currentInfoModalData.value = {
initPos: mobile
? { left: `50%`, top: `50%` }
: { left: `${position.left}px`, top: `${position.top}px` },
value,
isMobile: mobile,
};
forge_info_modal.showModal();
};
watch([forgeViewer, forgeLock], ([newViewer, newLock]) => {
if (newViewer && newLock !== undefined) {
newViewer.setNavigationLock(newLock); //
@ -166,7 +177,7 @@ onUnmounted(() => {
</script>
<template>
<ForgeInfoModal :data="currentInfoModalData" />
<!-- <ForgeInfoModal :data="currentInfoModalData" /> -->
<div
:class="
twMerge(
@ -186,8 +197,11 @@ onUnmounted(() => {
)
"
>
<p class="absolute z-10 top-14 left-[27%]">更新時間 : {{ props.realTime }}</p>
<div v-show="heat_bar_isShow" class="absolute z-10 heatbar">
<p class="absolute z-10 top-14 left-[27%]">
更新時間 : {{ props.realTime }}
{{ searchParams?.option == 4 ? " (溫度)" : "" }}
</p>
<div v-show="searchParams?.option == 4" class="absolute z-10 heatbar">
<div class="w-40 flex justify-between text-[10px] mb-1">
<span class="text-gradient-1">-20°C</span>
<span class="text-gradient-2">0°C</span>
@ -207,38 +221,27 @@ onUnmounted(() => {
"
></div>
</div>
<!-- <label
v-for="(value, key) in subscribeDataWithErrorMsg"
:key="key"
:data-dbid="value.forge_dbid"
:class="
twMerge(
`after:border-t-[${value.currentColor}]`,
'flex items-center justify-center h-12 -translate-x-1/2 -translate-y-1/5 absolute z-50 px-5 py-4 text-center rounded-md text-lg border-2 border-white',
'after:absolute after:border-t-[10px] after:border-x-[12px] after:border-x-transparent after:-bottom-[8px] after:left-1/2 after:-translate-x-1/2 ',
'before:absolute before:border-t-[12px] before:border-x-[14px] before:border-x-transparent before:-bottom-[12px] before:left-1/2 before:-translate-x-1/2 before:border-white'
)
"
:style="{
left: `${Math.floor(value.device_coordinate_3d.x)}px`,
top: `${Math.floor(value.device_coordinate_3d.y) - 100}px`,
display: value.is_show,
backgroundColor: value.currentColor,
}"
@click.prevent="
(e) =>
getCurrentInfoModalData(
e,
{ left: e.clientX, top: e.clientY },
value
<template v-if="searchParams?.option == 5">
<label
v-for="value in props.meterList"
:key="value.main_id"
:data-dbid="value.forge_dbid"
v-show="meterPositions.get(value.main_id)?.visible !== false"
:class="
twMerge(
'flex items-center justify-center h-12 -translate-x-1/2 -translate-y-1/5 absolute z-50 px-5 py-4 text-center rounded-md text-lg border-2 border-white bg-green-600',
'after:absolute after:border-t-[10px] after:border-x-[12px] after:border-x-transparent after:-bottom-[8px] after:left-1/2 after:-translate-x-1/2 ',
'before:absolute before:border-t-[12px] before:border-x-[14px] before:border-x-transparent before:-bottom-[12px] before:left-1/2 before:-translate-x-1/2 before:border-white'
)
"
>
<span class="mr-2">{{ value.full_name }}</span>
<span v-if="value.alarmMsg">{{ value.alarmMsg }}</span>
<span v-else>{{ value.show_value }}</span>
</label> -->
"
:style="{
left: `${meterPositions.get(value.main_id)?.x || 0}px`,
top: `${meterPositions.get(value.main_id)?.y || 0}px`,
}"
>
<span class="mr-2">{{ value.name }}</span>
</label>
</template>
</div>
</div>
</template>

View File

@ -2,7 +2,7 @@
import { ref, watch, markRaw, onUnmounted } from "vue";
import useSearchParams from "@/hooks/useSearchParam";
export default function useSystemHeatmap(updateHeatBarIsShow) {
export default function useSystemHeatmap() {
const { searchParams } = useSearchParams();
// Viewer and DataVisualization Extension References
@ -25,12 +25,13 @@ export default function useSystemHeatmap(updateHeatBarIsShow) {
const updateHeatMapData = (deviceArr) => {
deviceList.value = Object.values(deviceArr).map((d) => ({
...d,
id: d.device_number, // An ID to identify this device
id: d.forge_dbid, // An ID to identify this device
roomDbId: d.room_dbid,
position: d.device_coordinate_3d, // World coordinates of this device
position: JSON.parse(d.device_coordinate_3d),
sensorTypes: ["temperature"], // The types/properties this device exposes
temp: 10,
temp: 25,
dbId: d.forge_dbid,
subSys: "freezer",
}));
};
@ -132,22 +133,10 @@ export default function useSystemHeatmap(updateHeatBarIsShow) {
// Watcher for Device List Changes
watch(deviceList, (newValue) => {
console.log("熱圖更新", newValue);
console.log("熱圖更新", newValue, searchParams.value.option);
switch (parseInt(searchParams.value.option)) {
case 2:
case 1:
createHeatMap("frozen");
updateHeatBarIsShow(true);
break;
case 3:
createHeatMap("gland");
updateHeatBarIsShow(true);
break;
case 4:
createHeatMap("packing");
break;
case 5:
createHeatMap("formula");
updateHeatBarIsShow(true);
break;
default:
if (isHeatMapSetup.value) {
@ -159,7 +148,6 @@ export default function useSystemHeatmap(updateHeatBarIsShow) {
"跳過 removeSurfaceShading因為尚未 setupSurfaceShading"
);
}
updateHeatBarIsShow(false);
break;
}
});

View File

@ -3,9 +3,12 @@ import { getDashboardDevice } from "@/apis/dashboard";
import useSearchParams from "@/hooks/useSearchParam";
import useSystemHeatmap from "./useSystemHeatmap";
export default function useSystemStatusByBaja(updateHeatBarIsShow) {
export default function useSystemStatusByBaja() {
const rawData = ref([]);
const forgeViewer = ref(null);
const visibleDbid = ref([]);
const heatmapDevices = ref([]);
const urn = ref("");
const lightColorMap = {
1: "#009100",
@ -20,106 +23,56 @@ export default function useSystemStatusByBaja(updateHeatBarIsShow) {
let cameraEventAdded = false; // 追蹤是否已經添加了相機事件監聽器
const initialData = ref(null);
const pipeList = ref(null);
const subscribeData = ref({});
const updateInitialData = (data = false) => {
initialData.value = data;
if (data && Array.isArray(data.dbid_list)) {
visibleDbid.value = data.dbid_list.map((d) => d.forge_dbid);
heatmapDevices.value = data.dbid_list.filter((d) => d.room_dbid !== null);
} else {
visibleDbid.value = [];
}
};
const { updateHeatMapData, updateTemp, initHeatMap } =
useSystemHeatmap(updateHeatBarIsShow);
function updatePipeData(data) {
const newDbids = data.map((item) => item.forge_dbid);
const oldDbids = pipeList.value
? pipeList.value.map((item) => item.forge_dbid)
: [];
const toHide = oldDbids.filter((dbid) => !newDbids.includes(dbid));
console.log("更新水管數據", forgeViewer.value, data);
if (forgeViewer.value) {
if (toHide.length > 0 && forgeViewer.value.hide)
forgeViewer.value.hide(toHide);
if (newDbids.length > 0 && forgeViewer.value.show)
forgeViewer.value.show(newDbids);
}
const updateForgeViewer = (viewer) => {
pipeList.value = data;
clearSprites(); // 清理之前的 Sprites
// 如果有特定的 dbid 需要顯示動畫,則創建動畫
if (forgeViewer.value && pipeList.value.length > 0) {
pipeList.value.forEach(({ forge_dbid, reverse }) => {
if (reverse !== null) {
createSprites(forgeViewer.value, forge_dbid, reverse);
}
});
}
}
const { updateHeatMapData, initHeatMap } = useSystemHeatmap();
const updateForgeViewer = async (viewer) => {
if (!viewer) {
forgeViewer.value = null;
return;
}
forgeViewer.value = markRaw(viewer);
initHeatMap(viewer);
};
const getSubPoint = (normal, close, error, sub_points = []) => {
let points = {
...Object.fromEntries(sub_points.map((p) => [p, ""])),
};
if (normal) points[normal] = "";
if (close) points[close] = "";
if (error) points[error] = "";
return points;
};
const subscribeData = ref({});
watch(rawData, () => {
let sub_data = {};
rawData.value.forEach((d) => {
sub_data = {
...sub_data,
...Object.fromEntries(
(d.device || []).map((dev) => [
dev.device_number,
{
...dev,
labelText: d.labelText,
show_value: d.labelText,
device_normal_point_name: d.device_normal_point_name,
device_close_point_name: d.device_close_point_name,
device_error_point_name: d.device_error_point_name,
device_normal_point_value: d.device_normal_point_value,
device_close_point_value: d.device_close_point_value,
device_error_point_value: d.device_error_point_value,
device_normal_color: d.device_normal_color,
device_close_color: d.device_close_color,
device_error_color: d.device_error_color,
forge_dbid: parseInt(dev.forge_dbid),
device_coordinate_3d: dev.device_coordinate_3d
? JSON.parse(dev.device_coordinate_3d)
: { x: 0, y: 0 },
points: getSubPoint(
d.device_normal_point_name,
d.device_close_point_name,
d.device_error_point_name,
d.points || []
),
subSys: d.subSys,
is_show: true,
currentColor: d.device_normal_color,
},
])
),
};
});
subscribeData.value = sub_data;
updateHeatMapData(sub_data);
// updateSubscribeDataFromBaja(sub_data);
});
const visibleDbid = computed(() => {
let visible = [];
rawData.value.forEach((d) => {
visible = [
...visible,
...d.device.map((dev) => parseInt(dev.forge_dbid)),
];
});
// return visible;
return [
26, 32, 38, 43, 48, 53, 58, 63, 70, 76, 879, 1068, 1011, 1065, 855, 1109,
867, 963, 1044, 966, 1139, 936, 1136, 957, 1133, 954, 610, 1130, 951,
1041, 939, 1145, 987, 999, 1148, 1002, 1151, 981, 813, 1088, 825, 1091,
804,
];
});
const getDevice = async (option = 1) => {
const res = await getDashboardDevice({
option: parseInt(option),
});
rawData.value = res.data.map((d) => ({
...d,
key: d.subSys,
}));
await initHeatMap(viewer); // 等待初始化完成
updateHeatMapData(heatmapDevices.value);
updatePipeData(pipeList.value || []);
};
// subscribe from baja
@ -130,238 +83,64 @@ export default function useSystemStatusByBaja(updateHeatBarIsShow) {
[point]: facets,
};
};
const updateDeviceData = (device_number, point, value) => {
// 檢查 subscribeData.value[device_number] 是否存在
if (!subscribeData.value[device_number]) {
console.log(`Device ${device_number} is not initialized.`);
return;
}
const correspondPoint = initialData.value.points.find(
({ name }) => name === point
);
// console.log("sub 回傳值 ", typeof value)
const text = correspondPoint
? correspondPoint.values.find(
({ value: pValue }) => pValue === parseInt(value)
)?.text || ""
: value;
// console.log("baja update data sub", correspondPoint, device_number, point, value);
subscribeData.value[device_number].points[point] = text;
if (
point.toLowerCase() === "temp" &&
parseInt(searchParams.value.option) > 1
) {
updateTemp(device_number, value);
}
// 當點位是 "light",且目前顏色不是錯誤顏色時,從 lightColorMap 取顏色
if (
point.toLowerCase() === "light" &&
subscribeData.value[device_number].currentColor !==
subscribeData.value[device_number].device_error_color
) {
subscribeData.value[device_number].currentColor =
lightColorMap[Number(value)] ||
subscribeData.value[device_number].device_normal_color;
}
// 當點位是錯誤點時,判斷值是否等於錯誤值,決定是否使用錯誤顏色
if (point === subscribeData.value[device_number].device_error_point_name) {
subscribeData.value[device_number].currentColor =
value === subscribeData.value[device_number].device_error_point_value
? subscribeData.value[device_number].device_error_color
: subscribeData.value[device_number].device_normal_color;
}
updateLabelText(device_number, point, text);
};
const transformDeviceNumber = (device_number) => {
const transformed = device_number.replaceAll("_", "/");
// 找到最後一個 / 的位置,並截取到該位置之前
const lastSlashIndex = transformed.lastIndexOf("/");
return lastSlashIndex !== -1
? transformed.substring(0, lastSlashIndex)
: transformed;
};
const updateLabelText = (key, point, value) => {
let text = subscribeData.value[key].labelText.replace(`%${point}`, value);
Object.keys(subscribeData.value[key].points)
.filter((p) => p !== point)
.forEach((p) => {
text = text.replace(`%${p}`, subscribeData.value[key].points[p]);
});
subscribeData.value[key].show_value = text;
};
const subComponents = ref(null);
const updateSubscribeDataFromBaja = (data) => {
for (let [key, value] of Object.entries(data)) {
window.require &&
window.requirejs(["baja!"], (baja) => {
console.log("進入 bajaSubscriber 準備執行BQL訂閱");
const ordKey = key;
const ordPath = transformDeviceNumber(key);
console.log("ordPath", ordPath, "ordKey", ordKey, value);
const fullOrdPath = `local:|foxs:|station:|slot:/Drivers/NiagaraNetwork/PCCV/points/${ordPath}/${ordKey}`; // 完整路徑
console.log("嘗試訪問路徑:", fullOrdPath); // 打印完整路徑
baja.Ord.make(fullOrdPath)
.get()
.then((folder) => {
console.log("成功獲取 folder:", folder);
const batch = new baja.comm.Batch();
const sub = new baja.Subscriber();
sub.attach({
changed: function (prop, cx) {
console.log("數據變更觸發:", prop.$getDisplayName());
if (prop.$getDisplayName() !== "Out") return;
if (
Object.hasOwn(
booleanPointFacets.value,
prop.$complex.$propInParent.$slotName
)
) {
const facets =
booleanPointFacets.value[
prop.$complex.$propInParent.$slotName
];
for (let [facetKey, facetValue] of Object.entries(facets)) {
if (facetValue === prop.$getValue().getValueDisplay()) {
updateDeviceData(
key,
prop.$complex.$propInParent.$slotName,
facetKey
);
}
}
} else {
updateDeviceData(
key,
prop.$complex.$propInParent.$slotName,
prop.$getValue().getValueDisplay()
);
}
},
});
console.log("開始遍歷控制點");
folder
.getSlots()
.is("control:ControlPoint")
.eachValue((point) => {
console.log("找到控制點:", point.getDisplayName());
console.log("配置的點位:", Object.keys(value.points));
if (
Object.keys(value.points).includes(point.getDisplayName())
) {
console.log(
"匹配到點位,開始訂閱:",
point.getDisplayName()
);
baja.Ord.make(
`local:|foxs:|station:|slot:/Drivers/NiagaraNetwork/PCCV/points/${ordPath}/${ordKey}/${point.getDisplayName()}`
)
.get()
.then((component) => {
console.log("獲取到 component:", component);
if (
point.getType().getTypeSpec() ===
"control:BooleanWritable"
) {
const facets = component.getFacets1().toObject();
updateFacets(point.getDisplayName(), facets);
for (let [facetKey, facetValue] of Object.entries(
facets
)) {
if (
facetValue ===
component.getOut().getValue().toString()
) {
updateDeviceData(
key,
point.getDisplayName(),
facetKey
);
}
}
} else {
updateDeviceData(
key,
point.getDisplayName(),
component.getOut().getValue()
);
}
sub
.subscribe({
comps: component, // Can also just be an singular Component instance
batch, // if defined, any network calls will be batched into this object (optional)
})
.then(() => {
console.log("subscribed successfully");
subComponents.value = sub;
})
.catch(function (err) {
baja.error(
"some components failed to subscribe: " + err
);
});
});
}
});
const updateMeterPositions = (meterList) => {
if (!forgeViewer.value || !meterList) return new Map();
const viewer = forgeViewer.value;
const tree = viewer.model?.getData()?.instanceTree;
const fragList = viewer.model?.getFragmentList();
if (!tree || !fragList) return new Map();
const newPositions = new Map();
meterList.forEach((meter) => {
if (meter.forge_dbid) {
try {
const nodebBox = new window.THREE.Box3();
// 取得物件的 bounding box
tree.enumNodeFragments(
meter.forge_dbid,
(fragId) => {
const fragbBox = new window.THREE.Box3();
fragList.getWorldBounds(fragId, fragbBox);
nodebBox.union(fragbBox);
},
true
);
if (!nodebBox.isEmpty()) {
const center = nodebBox.getCenter(new window.THREE.Vector3());
const pos2d = viewer.worldToClient(center);
newPositions.set(meter.main_id, {
x: pos2d.x,
y: pos2d.y - 100, // 往上偏移 100px
visible: viewer.isNodeVisible(meter.forge_dbid)
});
});
}
};
const updateDbidPosition = (viewer, data) => {
if (!viewer) return;
if (!forgeViewer.value) forgeViewer.value = markRaw(viewer);
const tree = viewer.model.getData().instanceTree;
const fragList = viewer.model.getFragmentList();
for (let [key, value] of Object.entries(data)) {
const nodebBox = new window.THREE.Box3();
// for each fragId on the list, get the bounding box
tree.enumNodeFragments(
value.forge_dbid,
(fragId) => {
const fragbBox = new window.THREE.Box3();
fragList.getWorldBounds(fragId, fragbBox);
nodebBox.union(fragbBox); // create a unifed bounding box
},
true
);
subscribeData.value[key].device_coordinate_3d = viewer.worldToClient(
nodebBox.getCenter()
);
subscribeData.value[key].is_show = viewer.isNodeVisible(value.forge_dbid);
}
}
} catch (error) {
console.warn(`無法計算 dbid ${meter.forge_dbid} 的位置:`, error);
}
}
});
return newPositions;
};
const fitToView = () => {
const { x, y, z } = JSON.parse(searchParams.value.camera_position);
const newPosition = new THREE.Vector3(x, y, z); //!<<< 相机的新位置
const newPosition = new THREE.Vector3(x, y, z);
const {
x: x1,
y: y1,
z: z1,
} = JSON.parse(searchParams.value.target_position); //!<<< 计算新焦点位置
const newTarget = new THREE.Vector3(x1, y1, z1); //!<<< 焦點的新位置
forgeViewer.value.navigation.getCamera().setView({
position: newPosition.clone(),
target: newTarget.clone(),
});
setTimeout(() => {
updateDbidPosition(forgeViewer.value, subscribeData.value);
}, 700);
} = JSON.parse(searchParams.value.target_position);
const newTarget = new THREE.Vector3(x1, y1, z1);
forgeViewer.value.navigation.setView(newPosition, newTarget);
};
const hideAllObjects = (instanceTree, filDbids = []) => {
@ -374,7 +153,6 @@ export default function useSystemStatusByBaja(updateHeatBarIsShow) {
if (filDbids.length > 0) {
forgeViewer.value.show(filDbids);
}
fitToView();
forgeViewer.value.impl.invalidate(true);
};
@ -459,42 +237,21 @@ export default function useSystemStatusByBaja(updateHeatBarIsShow) {
watch([forgeViewer, visibleDbid], ([viewer, dbids]) => {
console.log("監聽到 forgeViewer 或 visibleDbid 的變化", viewer, dbids);
stopBlinking();
clearSprites(); // 清理之前的 Sprites
if (viewer) {
hideAllObjects(viewer.model.getData().instanceTree, dbids);
}
// 判斷新狀態是否需要閃爍和 Sprites
if (viewer && dbids.includes(879)) {
// startBlinking([879, 1068, 1011, 1065, 855, 1109, 867,963,1044,966,1139,936,1136,957,1133,954,610,1130,951,1041,939,1145,987,999,1148,1002,1151,981,813,1088,825,1091,804]);
const spriteConfigs = [
{ dbId: 879, reverse: false },
{ dbId: 1011, reverse: false },
{ dbId: 855, reverse: true },
{ dbId: 867, reverse: true },
{ dbId: 963, reverse: false },
{ dbId: 966, reverse: true },
{ dbId: 936, reverse: true },
{ dbId: 957, reverse: true },
{ dbId: 954, reverse: true },
{ dbId: 951, reverse: true },
{ dbId: 939, reverse: true },
{ dbId: 987, reverse: true },
{ dbId: 999, reverse: true },
{ dbId: 1002, reverse: true },
{ dbId: 981, reverse: true },
{ dbId: 813, reverse: true },
{ dbId: 825, reverse: false },
{ dbId: 804, reverse: false },
];
spriteConfigs.forEach(({ dbId, reverse }) => {
createSprites(viewer, dbId, reverse);
});
}
});
watch(
[forgeViewer, () => searchParams.value.camera_position],
([viewer, newValue]) => {
if (viewer && newValue) {
fitToView();
}
}
);
// 動態Sprites創建函數
const createSprites = async (viewer, dbId, reverse = false) => {
try {
@ -612,15 +369,6 @@ export default function useSystemStatusByBaja(updateHeatBarIsShow) {
animEnd = tmp;
}
console.log(`dbId ${dbId} 動畫資訊:`, {
axis: axis,
size: size,
animStart: animStart,
animEnd: animEnd,
distance: animStart.distanceTo(animEnd),
reverse: reverse,
});
// 4. 建立 SpriteViewable初始在 animStart
const viewable = new DataVizCore.SpriteViewable(
animStart.clone(),
@ -655,19 +403,6 @@ export default function useSystemStatusByBaja(updateHeatBarIsShow) {
arrow.style.transform = `rotate(${angle}deg)`;
}
// camera 移動時也要更新所有箭頭(只添加一次事件監聽器)
if (!cameraEventAdded) {
viewer.addEventListener(
Autodesk.Viewing.CAMERA_CHANGE_EVENT,
() => {
spriteAnimations.forEach((animation) => {
animation.updateArrow(animation.currentPos);
});
}
);
cameraEventAdded = true;
}
// 動畫主程式
let t = 0;
let currentPos = animStart.clone();
@ -725,8 +460,6 @@ export default function useSystemStatusByBaja(updateHeatBarIsShow) {
// 移除這個 dbId 的記錄
spriteAnimations.delete(dbId);
console.log(`已清理 dbId ${dbId} 的 Sprite 資源`);
}
};
@ -751,9 +484,10 @@ export default function useSystemStatusByBaja(updateHeatBarIsShow) {
return {
subscribeData,
visibleDbid,
updateDbidPosition,
updateMeterPositions,
hideAllObjects,
updateForgeViewer,
updatePipeData,
forgeViewer,
loadModel,
urn,

View File

@ -16,14 +16,18 @@ import {
import { getAssetFloorList } from "@/apis/asset";
import { getOperationCompanyList } from "@/apis/operation";
import useSearchParams from "@/hooks/useSearchParam";
import useSystemHeatmap from "@/hooks/baja/useSystemHeatmap";
import dayjs from "dayjs";
const initialData = ref(null);
const realTimeData = ref(null);
const pipeData = ref([]);
const realTime = ref(null);
//
const meterList = ref([]);
const selectedMeter = ref(null);
//
const heatmapDevices = ref([]);
//
const floors = ref([]);
//
@ -48,10 +52,37 @@ const getDevice = async (option = 1) => {
});
realTimeData.value = res.data;
if (res.data?.meterData) {
meterList.value = (res.data?.meterData || []).sort();
// meterList
let meters = (res.data?.meterData || []).sort();
// dbid_list
const dbidList = initialData.value?.dbid_list || [];
// meter forge_dbid
meters = meters.map((meter) => {
const dbidItem = dbidList.find(
(d) => d.forge_dbid === meter.forge_dbid
);
return {
...meter,
device_coordinate_3d: JSON.parse(dbidItem?.device_coordinate_3d) || null,
};
});
meterList.value = meters;
// selectedMeter meterList
if (
selectedMeter.value == null &&
meterList.value[0]?.main_id !== undefined
) {
selectedMeter.value = meterList.value[0].main_id;
}
} else if (res.data?.refrigerationData) {
heatmapDevices.value = res.data.refrigerationData;
} else {
meterList.value = [];
heatmapDevices.value = [];
}
//
pipeData.value = res.data?.pipeData || [];
//
realTime.value = dayjs().format("YYYY-MM-DD HH:mm:ss");
console.log("實時數據:", realTimeData.value);
} catch (err) {
@ -82,7 +113,7 @@ const startInterval = (option) => {
// 5
intervalId = setInterval(() => {
getDevice(option);
}, 5000);
}, 10 * 1000);
};
//
@ -128,7 +159,14 @@ onUnmounted(() => {
<DashboardAlert />
</div>
</div>
<Forge :fullScreen="true" :initialData="initialData" :realTime="realTime" />
<Forge
:fullScreen="true"
:initialData="initialData"
:pipeData="pipeData"
:realTime="realTime"
:meterList="meterList"
:heatmapDevices="heatmapDevices"
/>
<DashboardForgeOptionButton
:initialData="initialData"
:meterList="meterList"

View File

@ -45,6 +45,7 @@ watch(
changeParams({
option: option.option,
});
}
"
tabindex="0"
@ -59,7 +60,9 @@ watch(
href="#"
:style="{
background:
props.selectedMeter === meter.main_id ? '#2563eb' : 'transparent',
props.selectedMeter === meter.main_id
? '#2563eb'
: 'transparent',
}"
@click.prevent="
() => {

View File

@ -15,7 +15,7 @@ import ValveCard from "./dashboardForgeCards/ValveCard.vue";
const { searchParams, changeParams } = useSearchParams();
const props = defineProps({
realTimeData: Object,
selectedMeter: String,
selectedMeter: [String, Number, null],
floors: Array,
companyOptions: Array,
});