diff --git a/server.js b/server.js index c163191..598bc74 100644 --- a/server.js +++ b/server.js @@ -1,39 +1,38 @@ -const express = require('express'); -const path = require('path'); -const { PORT } = require('./config.js'); +const express = require("express"); +const path = require("path"); +const { PORT } = require("./config.js"); const app = express(); -if (process.env.NODE_ENV === 'production') { +if (process.env.NODE_ENV === "production") { // 🔧 URL rewrite 必須在所有路由之前執行(對應 Vite dev proxy 的行為) app.use((req, res, next) => { - if (req.path.startsWith('/forge/api')) { - req.url = req.url.replace(/^\/forge\/api/, '/api'); - req.path = req.path.replace(/^\/forge\/api/, '/api'); + if (req.path.startsWith("/forge/api")) { + req.url = req.url.replace(/^\/forge\/api/, "/api"); + req.path = req.path.replace(/^\/forge\/api/, "/api"); } next(); }); - const distPath = path.join(__dirname, 'wwwroot/ibms_ems/dist'); + const distPath = path.join(__dirname, "wwwroot/ibms_ems/dist"); app.use(express.static(distPath)); } // 👉 API routes(在 rewrite 之後) -app.use(require('./routes/auth.js')); +app.use(require("./routes/auth.js")); -if (process.env.NODE_ENV === 'production') { +if (process.env.NODE_ENV === "production") { // Vue Router history mode (Express 5 catch-all syntax) - app.get('/{*path}', (req, res) => { - res.sendFile(path.join(__dirname, 'wwwroot/ibms_ems/dist', 'index.html')); + app.get("/{*path}", (req, res) => { + res.sendFile(path.join(__dirname, "wwwroot/ibms_ems/dist", "index.html")); }); - } else { // 👉 Dev:只跑 API,不管前端 - app.get('/', (req, res) => { - res.send('API server running...'); + app.get("/", (req, res) => { + res.send("API server running..."); }); } app.listen(PORT, () => { console.log(`Server listening on port ${PORT}...`); -}); \ No newline at end of file +}); diff --git a/wwwroot/ibms_ems/src/App.vue b/wwwroot/ibms_ems/src/App.vue index 117cd65..82ada22 100644 --- a/wwwroot/ibms_ems/src/App.vue +++ b/wwwroot/ibms_ems/src/App.vue @@ -3,7 +3,7 @@ import background from "@/assets/bg_tech.jpg"; import titleLogo from "@/assets/title.png"; import ForgeViewer from "@/components/ForgeViewer.vue"; import Info from "@/components/Info.vue"; -import { ref, useTemplateRef, watch } from "vue"; +import { ref, useTemplateRef, watch, toRaw } from "vue"; import btn01B from "@/assets/btn_01_b.png"; import btn01R from "@/assets/btn_01_r.png"; import btn02B from "@/assets/btn_02_b.png"; @@ -83,7 +83,15 @@ const left_data = ref([ tag: "ROOM_Information_Data_Center", arcSide: 1, cameraDistance: 5, - value: 1.22 + value: 1.22, + content: [ + "Total Facility Energy: 122,000 kWh", + "IT Equipment Energy: 100,000 kWh", + "Cooling Energy: 30,000 kWh", + "Power System Loss: 6,500 kWh", + "Lighting & Others: 3,500 kWh" + ], + spaceColor: new THREE.Vector4(1, 0, 0, 1) }, ]); @@ -97,7 +105,16 @@ const right_data = ref([ tag: "ROOM_OFFICE", arcSide: 1, cameraDistance: 2, - value: 92 + value: 92, + content: [ + "IAQ Score: 92 / 100", + "CO₂ Level: 680 ppm", + "PM2.5: 8 μg/m³", + "Temperature: 24.5 °C", + "Humidity: 52%", + "TVOC: 0.28 mg/m³" + ], + spaceColor: new THREE.Vector4(1, 0, 0, 1) }, { title: "REAL-TIME THROUGHPUT", @@ -108,7 +125,16 @@ const right_data = ref([ tag: "TAG_Chiller-Water_Cooled", arcSide: -1, cameraDistance: 10, - value: 96 + value: 96, + content: [ + "Real-Time Throughput: 56%", + "Operating Chillers: 4 / 8 units", + "Active Cooling Capacity: 5,600 RT", + "Available Cooling Capacity: 10,000 RT", + "Standby Chillers: 3 units", + "Maintenance Chillers: 1 unit" + + ] }, { title: "REAL-TIME", @@ -119,7 +145,15 @@ const right_data = ref([ tag: "TAG_solder_paste_screen_printer", arcSide: -1, cameraDistance: 5, - value: 98 + value: 98, + content: [ + "Predicted Yield (AI): 98.0%", + "Target Yield: 98.5%", + "Defect Risk: 1.2%", + "Quality Deviation: 0.5%", + "Key Impact Factor: Temperature Stability", + "AI Confidence: 94%" + ] }, { title: "SAFETY & ALARMS", @@ -140,6 +174,7 @@ const forgeLabelRef = useTemplateRef("forgeLabelRef"); const imgSrcActive = ref(null); const onClick = (item) => { + const viewer = toRaw(forgeViewerRef.value.forgeViewer); if (item.tag === "") return imgSrcActive.value = item; @@ -147,11 +182,23 @@ const onClick = (item) => { //TODO: 切換視角 console.log(forgeLabelRef.value); changeCameraPosition( - forgeViewerRef.value.forgeViewer, + viewer, forgeLabelRef.value.tagDom, item, ); + item.spaceColor && + viewer.model.getInstanceTree().enumNodeChildren( + item.forgeID, + (dbId) => { + console.log("Found dbId:", dbId); + viewer.setThemingColor(dbId, new THREE.Vector4(1, 0, 0, 1)) // RGBA (紅色)); + viewer.impl.invalidate(true); + }, + true, + ); + + // console.log(forgeViewerRef.value.Viewpoints[7]); // changeCameraView(forgeViewerRef.value.forgeViewer, forgeViewerRef.value.Viewpoints[7].data); }; @@ -184,6 +231,6 @@ watch( - + diff --git a/wwwroot/ibms_ems/src/components/ForgeLabel.vue b/wwwroot/ibms_ems/src/components/ForgeLabel.vue index 80c5081..d7e34d5 100644 --- a/wwwroot/ibms_ems/src/components/ForgeLabel.vue +++ b/wwwroot/ibms_ems/src/components/ForgeLabel.vue @@ -1,12 +1,12 @@ - - + + - {{ data.title }} + {{ data?.title || "" }} - - {{ value }} + + {{ value || "" }} diff --git a/wwwroot/ibms_ems/src/components/ForgeViewer.vue b/wwwroot/ibms_ems/src/components/ForgeViewer.vue index 4a9529b..ff51363 100644 --- a/wwwroot/ibms_ems/src/components/ForgeViewer.vue +++ b/wwwroot/ibms_ems/src/components/ForgeViewer.vue @@ -143,7 +143,7 @@ watch(forgeViewerDOM, async (newVal) => { // 根據你的 UI(橢圓框大小)調整 - const scaleFactor = 1.3; // ⭐ 微調用 + const scaleFactor = 1; // ⭐ 微調用 const direction = cameraPos.clone().sub(cameraTarget).normalize(); const distance = cameraPos.distanceTo(cameraTarget) * scaleFactor; const newPos = cameraTarget.clone().add(direction.multiplyScalar(distance)); diff --git a/wwwroot/ibms_ems/src/utils/changeCameraPosition.js b/wwwroot/ibms_ems/src/utils/changeCameraPosition.js index 81da5fe..040b0b1 100644 --- a/wwwroot/ibms_ems/src/utils/changeCameraPosition.js +++ b/wwwroot/ibms_ems/src/utils/changeCameraPosition.js @@ -200,7 +200,11 @@ export default function changeCameraPosition(forgeViewer, label, item) { console.log("物件中心:", center); // ⭐ 計算最佳觀察點 - const { position, target } = computeCameraOffset(viewer, bbox, item.cameraDistance); + const { position, target } = computeCameraOffset( + viewer, + bbox, + item.cameraDistance, + ); // ⭐ 第 1 段:飛到物件 const toTarget = { @@ -229,9 +233,17 @@ export default function changeCameraPosition(forgeViewer, label, item) { returnToOriginal: true, duration: 5000, item, - arcSide: -(item.arcSide), + arcSide: -item.arcSide, }; flyArc(toOriginal); + viewer.model.getInstanceTree().enumNodeChildren( + item.forgeID, + (dbId) => { + viewer.setThemingColor(dbId, null); // RGBA (紅色)); + viewer.impl.invalidate(true); + }, + true, + ); viewer.showAll(); // 恢復顯示所有物件 }, 8000); }