介面優化
This commit is contained in:
parent
a5dcec3045
commit
8ba6905047
@ -3,6 +3,10 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/style.css"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>監控系統</title>
|
||||
<script type="text/javascript" src="/requirejs/config.js?"></script>
|
||||
@ -13,6 +17,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/viewer3D.js"></script>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
9
package-lock.json
generated
9
package-lock.json
generated
@ -16,7 +16,7 @@
|
||||
"axios": "^1.7.9",
|
||||
"echarts": "^5.6.0",
|
||||
"pinia": "^2.3.1",
|
||||
"tailwind-merge": "^3.0.1",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"tailwindcss": "^4.0.3",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0"
|
||||
@ -1926,10 +1926,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.1.tgz",
|
||||
"integrity": "sha512-AvzE8FmSoXC7nC+oU5GlQJbip2UO7tmOhOfQyOmPhrStOGXHU08j8mZEHZ4BmCqY5dWTCo4ClWkNyRNx1wpT0g==",
|
||||
"license": "MIT",
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.2.tgz",
|
||||
"integrity": "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
|
@ -17,7 +17,7 @@
|
||||
"axios": "^1.7.9",
|
||||
"echarts": "^5.6.0",
|
||||
"pinia": "^2.3.1",
|
||||
"tailwind-merge": "^3.0.1",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"tailwindcss": "^4.0.3",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0"
|
||||
|
@ -44,7 +44,7 @@ onMounted(async () => {
|
||||
:style="{
|
||||
margin: '5px',
|
||||
padding: '0px',
|
||||
background: '#fff',
|
||||
background: '#fafafa',
|
||||
minHeight: '280px',
|
||||
}"
|
||||
>
|
||||
|
@ -5,95 +5,62 @@ import useAlarmDataStore from "@/stores/useAlarmDataStore";
|
||||
|
||||
const niagaraStore = useNiagaraDataStore();
|
||||
const alarmDataStore = useAlarmDataStore();
|
||||
|
||||
// 定義表格欄位
|
||||
const columns = [
|
||||
{ title: "Name", key: "name" },
|
||||
{ title: "In Alarm Count", key: "alarmCount" },
|
||||
{ title: "Unacked Count", key: "unackedCount" },
|
||||
];
|
||||
|
||||
// 初始化資料與訂閱更新
|
||||
const initializeAlarmData = () => {
|
||||
// 使用 useAlarmDataStore 建立 alarmData
|
||||
if(!alarmDataStore.alarmData.length){
|
||||
alarmDataStore.createAlarmData(niagaraStore.alarmList);
|
||||
}
|
||||
|
||||
// 對每個 alarm 項目建立訂閱
|
||||
alarmDataStore.alarmData.forEach((alarm, index) => {
|
||||
if (!alarm.alarmOrd) return;
|
||||
|
||||
window.require &&
|
||||
window.requirejs(["baja!"], (baja) => {
|
||||
// 建立訂閱器
|
||||
const subscriber = new baja.Subscriber();
|
||||
// 定義 changed 事件的處理函數
|
||||
subscriber.attach("changed", (prop) => {
|
||||
// console.log("prop", prop.$getDisplayName(), prop.$getValue());
|
||||
try {
|
||||
let alarmCount = alarmDataStore.alarmData[index].alarmCount;
|
||||
let unackedCount = alarmDataStore.alarmData[index].unackedCount;
|
||||
|
||||
if (prop.$getDisplayName() === "In Alarm Count") {
|
||||
// 取得 In Alarm Count
|
||||
alarmCount = prop.$getValue();
|
||||
}
|
||||
if (prop.$getDisplayName() === "Unacked Alarm Count") {
|
||||
// 取得 Unacked Alarm Count
|
||||
unackedCount = prop.$getValue();
|
||||
}
|
||||
|
||||
// 更新 useAlarmDataStore 中的資料
|
||||
alarmDataStore.updateAlarmItem(index, alarmCount, unackedCount);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`處理 ${alarm.name || index} 告警變化失敗: ${error.message}`,
|
||||
error
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// 訂閱 alarm 資訊
|
||||
baja.Ord.make(alarm.alarmOrd)
|
||||
.get({ subscriber })
|
||||
.then((result) => {
|
||||
console.log(`Successfuly subscribed to alarm ${alarm.name}`);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(
|
||||
`訂閱 Alarm ${alarm.name || index} 失敗: ${err.message}`
|
||||
);
|
||||
subscriber.detach("changed");
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => niagaraStore.alarmList,
|
||||
(newValue, oldValue) => {
|
||||
if (newValue) {
|
||||
console.log("niagaraStore.alarmList changed:", newValue);
|
||||
initializeAlarmData();
|
||||
}
|
||||
},
|
||||
{ immediate: true } // 立即執行一次
|
||||
);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="alarmDataStore.alarmData"
|
||||
bordered
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<span>{{ record[column.key] }}</span>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
<a-card class="card">
|
||||
<h5>Alarm</h5>
|
||||
<table className="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>In Alarm Count</th>
|
||||
<th>Unacked Count</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in alarmDataStore.alarmData" :key="index">
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.alarmCount }}</td>
|
||||
<td>{{ item.unackedCount }}</td>
|
||||
<td>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'baja',
|
||||
query: { ord: encodeURIComponent(item.Ord) },
|
||||
}"
|
||||
class="flex items-center justify-between gap-8"
|
||||
>view</router-link
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</a-card>
|
||||
</template>
|
||||
<style scoped></style>
|
||||
<style scoped>
|
||||
.card {
|
||||
box-shadow: 0 20px 27px rgb(0 0 0 / 5%);
|
||||
}
|
||||
h5 {
|
||||
margin: 0;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
color: #141414;
|
||||
}
|
||||
|
||||
.table th {
|
||||
text-align: left;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
padding: 8px 0;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
.table td {
|
||||
font-size: 15px;
|
||||
padding: 16px 0px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
72
src/components/dashboard/dashboardBuild.vue
Normal file
72
src/components/dashboard/dashboardBuild.vue
Normal file
@ -0,0 +1,72 @@
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import Forge from "@/components/forge/Forge.vue";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
||||
const type = ref("3d");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-card class="card">
|
||||
<div class="w-full relative">
|
||||
<img
|
||||
alt="build"
|
||||
:src="`${FILE_BASEURL}/file/UI_images/build/2D/build.jpg`"
|
||||
:class="
|
||||
twMerge(
|
||||
'absolute left-1/2 translate-x-[-50%] rounded shadow-lg transition-opacity duration-300',
|
||||
type == '2d' ? 'opacity-100 z-10' : 'opacity-0 z-0'
|
||||
)
|
||||
"
|
||||
style="width: 400px; height: 400px; vertical-align: middle"
|
||||
/>
|
||||
<Forge
|
||||
:class="
|
||||
twMerge(
|
||||
'absolute transition-opacity duration-300',
|
||||
type == '3d' ? 'opacity-100 z-10' : 'opacity-0 z-0'
|
||||
)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center justify-between mt-2">
|
||||
<div>
|
||||
<h5>Building</h5>
|
||||
<p class="text-gray-400">
|
||||
掌握建築用電、系統健康狀態,打造智慧節能醫院
|
||||
</p>
|
||||
</div>
|
||||
<a-radio-group v-model:value="type">
|
||||
<a-radio-button value="2d">2D</a-radio-button>
|
||||
<a-radio-button value="3d">3D</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<!-- <a-row>
|
||||
<a-col :span="6">
|
||||
<a-statistic title="用戶數" :value="3.6" suffix="萬" />
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-statistic title="智慧電錶" :value="82" suffix="個" />
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-statistic title="電費支出" :value="-7.2" suffix="萬" />
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-statistic title="警示次數" :value="15" suffix="次" />
|
||||
</a-col>
|
||||
</a-row> -->
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
box-shadow: 0 20px 27px rgb(0 0 0 / 5%);
|
||||
}
|
||||
|
||||
h5 {
|
||||
margin: 0;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
color: #141414;
|
||||
}
|
||||
</style>
|
@ -3,41 +3,80 @@ const mockData = [
|
||||
{
|
||||
value: 305.5,
|
||||
label: "今日用電量",
|
||||
unit:"kWH"
|
||||
unit: "kWH",
|
||||
icon: "leaf",
|
||||
},
|
||||
{
|
||||
value: 886.75,
|
||||
label: "昨日用電量",
|
||||
unit:"kWH"
|
||||
unit: "kWH",
|
||||
icon: "leaf",
|
||||
},
|
||||
{
|
||||
value: 7.84,
|
||||
label: "即時功率",
|
||||
unit:"kW"
|
||||
unit: "kW",
|
||||
icon: "bolt",
|
||||
},
|
||||
{
|
||||
value: 20.96,
|
||||
label: "容積占比",
|
||||
unit:"%"
|
||||
unit: "%",
|
||||
icon: "charging-station",
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-row :gutter="24" class="p-5">
|
||||
<a-col v-for="(item, index) in mockData" :key="index" :span="6">
|
||||
<a-card class="shadow">
|
||||
<a-statistic
|
||||
:title="item.label"
|
||||
:value="item.value"
|
||||
:precision="2"
|
||||
:suffix="item.unit"
|
||||
:value-style="{ color: index % 2 === 0 ? '#3f8600' : '#1677ff' }"
|
||||
>
|
||||
</a-statistic>
|
||||
<a-row :gutter="24">
|
||||
<a-col v-for="(item, index) in mockData" :key="index" :span="6" class="mb-5">
|
||||
<a-card class="number">
|
||||
<a-row :gutter="24" align="middle" justify="space-between">
|
||||
<a-col :span="18">
|
||||
<span>{{ item.label }}</span>
|
||||
<h3>
|
||||
{{ item.value }}<small>{{ item.unit }}</small>
|
||||
</h3>
|
||||
</a-col>
|
||||
<a-col :span="6" class="pl-0">
|
||||
<div class="icon-box">
|
||||
<font-awesome-icon :icon="['fas', item.icon]" size="2x"/>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style scoped>
|
||||
.number {
|
||||
box-shadow: 0 20px 27px rgb(0 0 0 / 5%);
|
||||
}
|
||||
.number span {
|
||||
color: #8c8c8c;
|
||||
font-size: 14px;
|
||||
}
|
||||
.number h3 {
|
||||
font-weight: 700;
|
||||
margin-bottom: 0;
|
||||
font-size: 30px;
|
||||
}
|
||||
.number h3 small {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: #8fbce6;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.icon-box {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
text-align: center;
|
||||
background: #1890ff;
|
||||
color: #fff;
|
||||
border-radius: 0.5rem;
|
||||
margin-left: auto;
|
||||
line-height: 55px;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,48 +1,45 @@
|
||||
<script setup>
|
||||
import { ref, computed, watch } from "vue";
|
||||
import NavBuild from "../navbar/NavBuild.vue";
|
||||
import useNavDataStore from "@/stores/useNavDataStore";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const navStore = useNavDataStore();
|
||||
const router = useRouter();
|
||||
|
||||
const filteredItems = computed(() => {
|
||||
const flatten = (items) => {
|
||||
const flattenedItems = [];
|
||||
const selectedBuildingOrd = navStore.selectedBuildingOrd;
|
||||
// 遞迴函數,用於展開多層級的選單項目
|
||||
const flatten = (items) => {
|
||||
if (!items) return;
|
||||
|
||||
const recurse = (items) => {
|
||||
if (!items) return;
|
||||
items.forEach((item) => {
|
||||
// 過濾掉 ord 為 null 的項目
|
||||
if (item.ord !== "null") {
|
||||
flattenedItems.push({
|
||||
key: item.key,
|
||||
name: item.title,
|
||||
ord: item.ord,
|
||||
icon: item.icon,
|
||||
});
|
||||
flattenedItems.push(item);
|
||||
}
|
||||
// 如果有子項目,則遞迴處理
|
||||
if (item.children) {
|
||||
flatten(item.children);
|
||||
recurse(item.children);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 展開選單項目
|
||||
recurse(items);
|
||||
|
||||
return flattenedItems;
|
||||
};
|
||||
|
||||
const flattenedItems = computed(() => {
|
||||
const selectedBuildingOrd = navStore.selectedBuildingOrd;
|
||||
if (
|
||||
navStore.menuList &&
|
||||
navStore.menuList.length > 0 &&
|
||||
selectedBuildingOrd
|
||||
) {
|
||||
const buildingMenu = navStore.menuList.find(
|
||||
const buildingMenu = navStore.menuList[0].children.find(
|
||||
(item) => item.label === selectedBuildingOrd
|
||||
);
|
||||
flatten(buildingMenu.children);
|
||||
return flatten(buildingMenu.children);
|
||||
}
|
||||
|
||||
return flattenedItems;
|
||||
return [];
|
||||
});
|
||||
|
||||
const handleClick = (ord) => {
|
||||
@ -56,31 +53,47 @@ const handleClick = (ord) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="filteredItems && filteredItems.length > 0">
|
||||
<a-row :gutter="[8, 16]">
|
||||
<a-col :span="6" v-for="(item, index) in filteredItems" :key="index">
|
||||
<a-card
|
||||
@click="handleClick(item.ord)"
|
||||
class="shadow"
|
||||
:bodyStyle="{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
cursor: 'pointer',
|
||||
}"
|
||||
>
|
||||
<img
|
||||
v-if="item.icon"
|
||||
:src="item.icon"
|
||||
alt="Icon"
|
||||
style="width: 30px; height: 30px; vertical-align: middle"
|
||||
/>
|
||||
<span class="text-lg">{{ item.name }}</span>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<div v-else>No items to display.</div>
|
||||
<a-card class="card h-full">
|
||||
<div class="flex items-cemter justify-between mb-4">
|
||||
<h5>System</h5>
|
||||
<NavBuild />
|
||||
</div>
|
||||
<div v-if="flattenedItems.length > 0">
|
||||
<a-row :gutter="[8, 16]">
|
||||
<a-col :span="6" v-for="item in flattenedItems" :key="item.key">
|
||||
<a-card
|
||||
@click="handleClick(item.ord)"
|
||||
class="shadow"
|
||||
:bodyStyle="{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
cursor: 'pointer',
|
||||
}"
|
||||
>
|
||||
<img
|
||||
v-if="item.icon"
|
||||
:src="item.icon"
|
||||
alt="Icon"
|
||||
style="width: 30px; height: 30px; vertical-align: middle"
|
||||
/>
|
||||
<span class="text-lg">{{ item.label }}</span>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<div v-else>No items to display.</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped>
|
||||
.card {
|
||||
box-shadow: 0 20px 27px rgb(0 0 0 / 5%);
|
||||
}
|
||||
h5 {
|
||||
margin: 0;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
color: #141414;
|
||||
}
|
||||
</style>
|
||||
|
86
src/components/forge/Forge.vue
Normal file
86
src/components/forge/Forge.vue
Normal file
@ -0,0 +1,86 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
||||
const forgeDom = ref(null);
|
||||
let viewer = null;
|
||||
|
||||
// 初始化 Forge Viewer
|
||||
const initViewer = (container) => {
|
||||
return new Promise(function (resolve, reject) {
|
||||
Autodesk.Viewing.Initializer(
|
||||
{
|
||||
env: "Local",
|
||||
language: "en",
|
||||
},
|
||||
function () {
|
||||
const config = {
|
||||
extensions: [
|
||||
"Autodesk.DataVisualization",
|
||||
"Autodesk.DocumentBrowser",
|
||||
],
|
||||
};
|
||||
viewer = new Autodesk.Viewing.GuiViewer3D(container, config);
|
||||
Autodesk.Viewing.Private.InitParametersSetting.alpha = true;
|
||||
viewer.start();
|
||||
viewer.setGroundShadow(false);
|
||||
viewer.impl.renderer().setClearAlpha(0);
|
||||
viewer.impl.glrenderer().setClearColor(0xffffff, 0);
|
||||
viewer.impl.invalidate(true);
|
||||
resolve(viewer);
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// 使用本地 .svf 文件加載模型
|
||||
const loadModel = (filePath) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
viewer.loadModel(
|
||||
filePath,
|
||||
{},
|
||||
(model) => {
|
||||
viewer.impl.invalidate(true);
|
||||
viewer.fitToView();
|
||||
resolve(model);
|
||||
console.log("模型加載完成");
|
||||
},
|
||||
reject
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
console.log("Forge 加載");
|
||||
await initViewer(forgeDom.value);
|
||||
const filePath = `${FILE_BASEURL}/file/UI_images/build/3D/0.svf`;
|
||||
loadModel(filePath);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
console.log("Forge 銷毀");
|
||||
if (viewer) {
|
||||
viewer.tearDown();
|
||||
viewer.finish();
|
||||
viewer = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
id="forge-preview"
|
||||
ref="forgeDom"
|
||||
class="relative w-full h-full min-h-[400px]"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<style lang="css">
|
||||
.adsk-viewing-viewer {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
#guiviewer3d-toolbar {
|
||||
display: none;
|
||||
bottom: 200px;
|
||||
}
|
||||
</style>
|
@ -12,20 +12,13 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
const navStore = useNavDataStore();
|
||||
const selectedKeys = ref([]); // 用於儲存選中的 menu item
|
||||
// const selectedKeys = ref([]); // 用於儲存選中的 menu item
|
||||
const openKeys = ref([]); // 用於儲存展開的 submenu
|
||||
const preOpenKeys = ref([]); // 用於儲存之前展開的 submenu
|
||||
|
||||
const filteredItems = computed(() => {
|
||||
const selectedBuildingOrd = navStore.selectedBuildingOrd;
|
||||
if (
|
||||
navStore.menuList &&
|
||||
navStore.menuList.length > 0 &&
|
||||
selectedBuildingOrd
|
||||
) {
|
||||
const buildingMenu = navStore.menuList[0].children.find(
|
||||
(item) => item.key === selectedBuildingOrd
|
||||
);
|
||||
return buildingMenu.children;
|
||||
if (navStore.menuList && navStore.menuList.length > 0) {
|
||||
return navStore.menuList[0].children;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
@ -36,7 +29,7 @@ const handleClick = (ord) => {
|
||||
name: "baja",
|
||||
query: { ord: encodeURIComponent(ord) },
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 監聽 openKeys 的變化
|
||||
@ -60,13 +53,36 @@ watch(
|
||||
>
|
||||
<a-menu mode="inline" theme="light">
|
||||
<template v-for="(item, index) in filteredItems" :key="index">
|
||||
<a-menu-item v-if="!item.children" :key="item.key" @click="handleClick(item.ord)">
|
||||
<a-menu-item
|
||||
v-if="!item.children"
|
||||
:key="item.key"
|
||||
@click="handleClick(item.ord)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</a-menu-item>
|
||||
<a-sub-menu v-else :title="item.label" :key="`submenu-${item.key}`">
|
||||
<a-menu-item v-for="child in item.children" :key="child.key" @click="handleClick(child.ord)">
|
||||
{{ child.label }}
|
||||
</a-menu-item>
|
||||
<template v-for="child in item.children" :key="child.key">
|
||||
<a-menu-item
|
||||
v-if="!child.children"
|
||||
:key="child.key"
|
||||
@click="handleClick(child.ord)"
|
||||
>
|
||||
{{ child.label }}
|
||||
</a-menu-item>
|
||||
<a-sub-menu
|
||||
v-else
|
||||
:title="child.label"
|
||||
:key="`submenu-item-${child.key}`"
|
||||
>
|
||||
<a-menu-item
|
||||
v-for="kid in child.children"
|
||||
:key="kid.key"
|
||||
@click="handleClick(kid.ord)"
|
||||
>
|
||||
{{ kid.label }}
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
</template>
|
||||
</a-sub-menu>
|
||||
</template>
|
||||
</a-menu>
|
||||
|
@ -1,71 +1,23 @@
|
||||
<script setup>
|
||||
import { ref, watch, computed } from "vue";
|
||||
import { ref, computed, onBeforeUnmount, watch } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import useNiagaraDataStore from "@/stores/useNiagaraDataStore";
|
||||
import useAlarmDataStore from "@/stores/useAlarmDataStore";
|
||||
|
||||
const router = useRouter();
|
||||
const niagaraStore = useNiagaraDataStore();
|
||||
const alarmDataStore = useAlarmDataStore();
|
||||
|
||||
// 計算 alarmCount 的總和
|
||||
const totalAlarmCount = computed(() => {
|
||||
return alarmDataStore.alarmData.reduce((total, child) => total + (child.alarmCount || 0), 0);
|
||||
return alarmDataStore.alarmData.reduce(
|
||||
(total, child) => total + (child.alarmCount || 0),
|
||||
0
|
||||
);
|
||||
});
|
||||
|
||||
const initializeAlarmData = () => {
|
||||
// 使用 useAlarmDataStore 建立 alarmData
|
||||
if (!alarmDataStore.alarmData.length) {
|
||||
alarmDataStore.createAlarmData(niagaraStore.alarmList);
|
||||
}
|
||||
|
||||
// 對每個 alarm 項目建立訂閱
|
||||
alarmDataStore.alarmData.forEach((alarm, index) => {
|
||||
if (!alarm.alarmOrd) return;
|
||||
|
||||
window.require &&
|
||||
window.requirejs(["baja!"], (baja) => {
|
||||
// 建立訂閱器
|
||||
const subscriber = new baja.Subscriber();
|
||||
// 定義 changed 事件的處理函數
|
||||
subscriber.attach("changed", (prop) => {
|
||||
// console.log("prop", prop.$getDisplayName(), prop.$getValue());
|
||||
try {
|
||||
let alarmCount = alarmDataStore.alarmData[index].alarmCount;
|
||||
let unackedCount = alarmDataStore.alarmData[index].unackedCount;
|
||||
|
||||
if (prop.$getDisplayName() === "In Alarm Count") {
|
||||
// 取得 In Alarm Count
|
||||
alarmCount = prop.$getValue();
|
||||
}
|
||||
if (prop.$getDisplayName() === "Unacked Alarm Count") {
|
||||
// 取得 Unacked Alarm Count
|
||||
unackedCount = prop.$getValue();
|
||||
}
|
||||
|
||||
// 更新 useAlarmDataStore 中的資料
|
||||
alarmDataStore.updateAlarmItem(index, alarmCount, unackedCount);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`處理 ${alarm.name || index} 告警變化失敗: ${error.message}`,
|
||||
error
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// 訂閱 alarm 資訊
|
||||
baja.Ord.make(alarm.alarmOrd)
|
||||
.get({ subscriber })
|
||||
.then((result) => {
|
||||
console.log(`Successfuly subscribed to alarm ${alarm.name}`);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(
|
||||
`訂閱 Alarm ${alarm.name || index} 失敗: ${err.message}`
|
||||
);
|
||||
subscriber.detach("changed");
|
||||
});
|
||||
});
|
||||
});
|
||||
alarmDataStore.createAlarmData(niagaraStore.alarmList);
|
||||
};
|
||||
|
||||
watch(
|
||||
@ -76,8 +28,12 @@ watch(
|
||||
initializeAlarmData();
|
||||
}
|
||||
},
|
||||
{ immediate: true } // 立即執行一次
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
alarmDataStore.clearAllSubscriber();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -98,8 +54,7 @@ watch(
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-badge :count="totalAlarmCount" :overflow-count="999"
|
||||
>
|
||||
<a-badge :count="totalAlarmCount" :overflow-count="999">
|
||||
<a class="flex flex-col items-center">
|
||||
<font-awesome-icon :icon="['fas', 'bell']" size="2x" />
|
||||
<span class="text-sm">告警</span>
|
||||
|
@ -8,8 +8,8 @@ const router = useRouter();
|
||||
|
||||
const buildmenu = ref([]);
|
||||
|
||||
const handleBuildClick = (key) => {
|
||||
navStore.setSelectedBuildingOrd(key);
|
||||
const handleBuildClick = (label) => {
|
||||
navStore.setSelectedBuildingOrd(label);
|
||||
};
|
||||
|
||||
watch(
|
||||
@ -17,7 +17,7 @@ watch(
|
||||
(newValue, oldValue) => {
|
||||
if (newValue && newValue.length > 0 && newValue[0].children) {
|
||||
buildmenu.value = newValue[0].children;
|
||||
navStore.setSelectedBuildingOrd(newValue[0].children[0].key);
|
||||
navStore.setSelectedBuildingOrd(newValue[0].children[0].label);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
@ -27,14 +27,14 @@ watch(
|
||||
<template>
|
||||
<a-select
|
||||
v-if="buildmenu && buildmenu.length > 0"
|
||||
:default-value="buildmenu[0] ? buildmenu[0].key : null"
|
||||
:default-value="buildmenu[0] ? buildmenu[0].label : null"
|
||||
@change="handleBuildClick"
|
||||
placeholder="請選擇建築"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in buildmenu"
|
||||
:key="item.key"
|
||||
:value="item.key"
|
||||
:value="item.label"
|
||||
>
|
||||
{{ item.label }}
|
||||
</a-select-option>
|
||||
|
@ -1,83 +1,85 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, watch, onUnmounted } from "vue";
|
||||
import useNiagaraDataStore from "@/stores/useNiagaraDataStore";
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
import {
|
||||
imagesWeatherDay,
|
||||
imagesWeatherNight,
|
||||
orderWeather,
|
||||
} from "@/constants";
|
||||
|
||||
const niagaraStore = useNiagaraDataStore();
|
||||
|
||||
const weatherStateText = ref("N/A");
|
||||
const weatherStateImage = ref(null);
|
||||
const actualWeather = ref("N/A");
|
||||
const actualWeather = ref("Clear");
|
||||
const temperature = ref("N/A");
|
||||
const humidity = ref("N/A");
|
||||
const actualNighttime = ref("N/A");
|
||||
const actualNighttime = ref(false);
|
||||
const dateTime = ref("N/A");
|
||||
|
||||
let intervalId = null;
|
||||
let subscriber = null; // 宣告 subscriber 變數
|
||||
|
||||
// 初始化資料與訂閱更新
|
||||
const initializeData = () => {
|
||||
niagaraStore.weatherList.children.forEach((item, index) => {
|
||||
if (!item.ord) return;
|
||||
window.require &&
|
||||
window.requirejs(["baja!"], (baja) => {
|
||||
// 將 subscriber 移到此處定義
|
||||
const subscriber = new baja.Subscriber();
|
||||
subscriber.attach("changed", (prop) => {
|
||||
console.log(
|
||||
"weather",
|
||||
prop,
|
||||
prop.$getDisplayName(),
|
||||
prop.$getValue().getValueDisplay()
|
||||
);
|
||||
if (window.require && window.requirejs) {
|
||||
window.requirejs(["baja!"], (baja) => {
|
||||
subscriber = new baja.Subscriber(); // 初始化 subscriber
|
||||
subscriber.attach("changed", (prop) => {
|
||||
console.log(
|
||||
"weather",
|
||||
prop,
|
||||
prop.$getDisplayName(),
|
||||
prop.$getValue().getValueDisplay()
|
||||
);
|
||||
|
||||
if (item.name === "Out temperature" && prop.$displayName === "Out") {
|
||||
temperature.value = prop.$getValue().getValueDisplay();
|
||||
} else if (
|
||||
item.name === "Out humidity" &&
|
||||
prop.$displayName === "Out"
|
||||
) {
|
||||
humidity.value = prop.$getValue().getValueDisplay();
|
||||
} else if (item.name === "Weather" && prop.$displayName === "Out") {
|
||||
actualWeather.value = prop.$getValue().getValueDisplay();
|
||||
} else if (item.name === "Nighttime" && prop.$displayName === "Out") {
|
||||
actualNighttime.value = !(prop.$getValue().getValueDisplay());
|
||||
}
|
||||
loadWeather(actualWeather.value);
|
||||
});
|
||||
// 將 baja.Ord.make 移到此處
|
||||
baja.Ord.make(item.ord)
|
||||
.get({ subscriber })
|
||||
.then((result) => {
|
||||
console.log(`Successfuly subscribed to weather ${item.name}`);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(
|
||||
`訂閱 weather ${item.name || index} 失敗: ${err.message}`
|
||||
);
|
||||
subscriber.detach("changed");
|
||||
});
|
||||
// 根據 slotName 判斷資料類型
|
||||
if (prop.$slotName === "temp") {
|
||||
temperature.value = prop.$getValue().getValueDisplay();
|
||||
} else if (prop.$slotName === "humidity") {
|
||||
humidity.value = prop.$getValue().getValueDisplay();
|
||||
} else if (prop.$slotName === "state") {
|
||||
actualWeather.value = prop.$getValue().getValueDisplay();
|
||||
loadWeather(actualWeather.value); // 載入天氣資料
|
||||
} else if (prop.$slotName === "sunDown") {
|
||||
actualNighttime.value = !prop.$getValue().getValueDisplay();
|
||||
loadWeather(actualWeather.value); // 重新載入天氣資料
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 訂閱 Niagara 資料
|
||||
baja.Ord.make(
|
||||
"station:|slot:/Services/WeatherService/WeatherService/WeatherService/current"
|
||||
)
|
||||
.get({ subscriber })
|
||||
.then((result) => {
|
||||
console.log("Successfuly subscribed to weather data", result);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(`訂閱 weather 失敗: ${err.message}`);
|
||||
subscriber.detach("changed"); // 發生錯誤時取消訂閱
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.error("未能載入 Baja 環境,請確認依賴已正確加載。");
|
||||
}
|
||||
};
|
||||
|
||||
// 載入天氣資料函數
|
||||
const loadWeather = (actualWeather) => {
|
||||
console.log(
|
||||
"actualWeather",
|
||||
actualWeather.replace(/\s/g, ""),
|
||||
actualNighttime.value
|
||||
);
|
||||
let weatherStateTextTemp = "Unknown";
|
||||
let weatherStateImageTemp = null;
|
||||
const orderLength = orderWeather.length - 1;
|
||||
|
||||
if (orderLength < actualWeather) {
|
||||
console.error(
|
||||
"天氣狀態超出範圍!最大限度:" +
|
||||
orderLength +
|
||||
"| 實際值:" +
|
||||
actualWeather
|
||||
);
|
||||
// 找到 actualWeather 在 orderWeather 中的索引
|
||||
const actualWeatherIndex = orderWeather.indexOf(
|
||||
actualWeather.replace(/\s/g, "")
|
||||
);
|
||||
|
||||
if (actualWeatherIndex === -1) {
|
||||
// 如果找不到,表示 weather 狀態不在 orderWeather 中
|
||||
console.error("天氣狀態不在 orderWeather 陣列中!實際值:" + actualWeather);
|
||||
weatherStateTextTemp = "Unknown";
|
||||
weatherStateImageTemp = imagesWeatherDay[weatherStateTextTemp];
|
||||
} else if (actualWeather === "none") {
|
||||
@ -85,17 +87,17 @@ const loadWeather = (actualWeather) => {
|
||||
weatherStateTextTemp = "Unknown";
|
||||
weatherStateImageTemp = imagesWeatherDay[weatherStateTextTemp];
|
||||
} else {
|
||||
const actualWeatherNum = Number(actualWeather);
|
||||
if (actualNighttime.value) {
|
||||
console.log("Nighttime activ");
|
||||
weatherStateTextTemp = orderWeather[actualWeatherNum];
|
||||
weatherStateTextTemp = orderWeather[actualWeatherIndex];
|
||||
weatherStateImageTemp = imagesWeatherNight[weatherStateTextTemp];
|
||||
} else {
|
||||
console.log("Daytime activ");
|
||||
weatherStateTextTemp = orderWeather[actualWeatherNum];
|
||||
weatherStateTextTemp = orderWeather[actualWeatherIndex];
|
||||
weatherStateImageTemp = imagesWeatherDay[weatherStateTextTemp];
|
||||
}
|
||||
}
|
||||
|
||||
weatherStateImage.value = weatherStateImageTemp;
|
||||
weatherStateText.value = weatherStateTextTemp; // 更新天氣狀態
|
||||
};
|
||||
@ -103,33 +105,27 @@ const loadWeather = (actualWeather) => {
|
||||
// 時間
|
||||
const updateTime = () => {
|
||||
const date = new Date();
|
||||
window.require &&
|
||||
if (window.require && window.requirejs) {
|
||||
window.requirejs(["baja!"], (baja) => {
|
||||
const bAbsTime = baja.AbsTime.make({ jsDate: date });
|
||||
bAbsTime.toDateTimeString().then((dateTimeStr) => {
|
||||
dateTime.value = dateTimeStr;
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => niagaraStore.weatherList.children,
|
||||
(newValue, oldValue) => {
|
||||
if (newValue) {
|
||||
console.log("niagaraStore.weatherList changed:", newValue);
|
||||
initializeData();
|
||||
}
|
||||
},
|
||||
{ immediate: true } // 立即執行一次
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
updateTime();
|
||||
initializeData(); // 初始化資料
|
||||
updateTime(); // 更新時間
|
||||
intervalId = setInterval(updateTime, 1000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(intervalId);
|
||||
if (subscriber) {
|
||||
subscriber.detachAll(); // 取消所有訂閱
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -16,15 +16,50 @@ const props = defineProps({
|
||||
userName: String,
|
||||
});
|
||||
|
||||
function correctImageUrl(imageUrl) {
|
||||
if (!imageUrl) return ''; // 處理空值的情況
|
||||
|
||||
const stringUrlNiagara = "file:^";
|
||||
const stringUrlNiagara2 = "station:|file:^";
|
||||
const stringUrlNeeded = "/file/";
|
||||
|
||||
if (imageUrl.startsWith(stringUrlNiagara)) {
|
||||
return imageUrl.replace(stringUrlNiagara, stringUrlNeeded);
|
||||
} else if (imageUrl.startsWith(stringUrlNiagara2)) {
|
||||
return imageUrl.replace(stringUrlNiagara2, stringUrlNeeded);
|
||||
}
|
||||
|
||||
return imageUrl; // 如果不是 Niagara 格式,則直接返回
|
||||
}
|
||||
|
||||
const logoUrl = computed(
|
||||
() => niagaraStore.headerList.children?.[0]?.ord || ""
|
||||
);
|
||||
const systemName = computed(
|
||||
() => niagaraStore.headerList.children?.[1]?.name || "系統監控"
|
||||
);
|
||||
const homeData = computed(() => niagaraStore.headerList.children?.[2] || {});
|
||||
const systemData = computed(() => niagaraStore.headerList.children?.[3] || {});
|
||||
const dynamicMenu = computed(() => niagaraStore.DynamicList.children || []);
|
||||
const homeData = computed(() => {
|
||||
const data = niagaraStore.headerList.children?.[2] || {};
|
||||
return {
|
||||
...data,
|
||||
icon: correctImageUrl(data.icon), // 轉換 homeData.icon
|
||||
};
|
||||
});
|
||||
const systemData = computed(() => {
|
||||
const data = niagaraStore.headerList.children?.[3] || {};
|
||||
return {
|
||||
...data,
|
||||
icon: correctImageUrl(data.icon), // 轉換 systemData.icon
|
||||
};
|
||||
});
|
||||
const dynamicMenu = computed(() =>
|
||||
niagaraStore.DynamicList.children
|
||||
? niagaraStore.DynamicList.children.map((item) => ({
|
||||
...item,
|
||||
icon: correctImageUrl(item.icon), // 轉換 dynamicMenu item 的 icon
|
||||
}))
|
||||
: []
|
||||
);
|
||||
const userList = computed(() => niagaraStore.userList?.children || []);
|
||||
|
||||
</script>
|
||||
@ -36,7 +71,7 @@ const userList = computed(() => niagaraStore.userList?.children || []);
|
||||
<a href="./index.html" class="text-2xl font-bold mx-4">{{
|
||||
systemName
|
||||
}}</a>
|
||||
<NavBuild />
|
||||
<!-- <NavBuild /> -->
|
||||
</div>
|
||||
<ul class="nav-menu flex gap-10">
|
||||
<li>
|
||||
@ -51,12 +86,10 @@ const userList = computed(() => niagaraStore.userList?.children || []);
|
||||
"
|
||||
>
|
||||
<img
|
||||
v-if="homeData.icon"
|
||||
:src="homeData.icon"
|
||||
alt="home_icon"
|
||||
class="icon"
|
||||
/>
|
||||
<font-awesome-icon v-else :icon="['fas', 'home']" size="2x" />
|
||||
<span class="text-sm">首頁</span>
|
||||
</router-link>
|
||||
</li>
|
||||
@ -66,13 +99,10 @@ const userList = computed(() => niagaraStore.userList?.children || []);
|
||||
@click="props.open"
|
||||
>
|
||||
<img
|
||||
v-if="systemData.icon"
|
||||
:src="systemData.icon"
|
||||
alt="system_icon"
|
||||
class="icon"
|
||||
/>
|
||||
|
||||
<font-awesome-icon v-else :icon="['fas', 'tv']" size="2x" />
|
||||
<span class="text-sm">系統監控</span>
|
||||
</a>
|
||||
</li>
|
||||
@ -80,8 +110,7 @@ const userList = computed(() => niagaraStore.userList?.children || []);
|
||||
<router-link
|
||||
:to="{ name: 'baja', query: { ord: encodeURIComponent(item.ord) } }"
|
||||
>
|
||||
<img v-if="item.icon" :src="item.icon" alt="menu_icon" class="icon" />
|
||||
<font-awesome-icon v-else :icon="['fas', 'tv']" size="2x" />
|
||||
<img :src="item.icon" alt="menu_icon" class="icon" />
|
||||
<span class="text-sm">{{ item.name }}</span>
|
||||
</router-link>
|
||||
</li>
|
||||
|
10
src/main.js
10
src/main.js
@ -19,7 +19,10 @@ import {
|
||||
faUserEdit,
|
||||
faInfoCircle,
|
||||
faSignOutAlt,
|
||||
faAngleDown
|
||||
faAngleDown,
|
||||
faLeaf,
|
||||
faBolt,
|
||||
faChargingStation
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
library.add(
|
||||
faTv,
|
||||
@ -34,7 +37,10 @@ library.add(
|
||||
faUserEdit,
|
||||
faInfoCircle,
|
||||
faSignOutAlt,
|
||||
faAngleDown
|
||||
faAngleDown,
|
||||
faLeaf,
|
||||
faBolt,
|
||||
faChargingStation
|
||||
);
|
||||
const pinia = createPinia();
|
||||
const app = createApp(App);
|
||||
|
@ -2,6 +2,7 @@ import { createRouter, createWebHashHistory } from "vue-router";
|
||||
import DashboardPage from "@/views/dashboard/DashboardPage.vue";
|
||||
import SystemPage from "@/views/system/SystemPage.vue";
|
||||
import EnergyChart from "@/views/energy/EnergyChart.vue";
|
||||
import NotFound from "@/views/system/NotFound.vue";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||
@ -20,6 +21,11 @@ const router = createRouter({
|
||||
path: "/energy",
|
||||
name: "energy",
|
||||
component: EnergyChart,
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*', // 匹配所有未定義的路徑
|
||||
name: 'NotFound',
|
||||
component: NotFound
|
||||
}
|
||||
],
|
||||
});
|
||||
|
@ -4,7 +4,7 @@ import { ref } from "vue";
|
||||
|
||||
const useAlarmDataStore = defineStore("alarmData", () => {
|
||||
const alarmData = ref([]);
|
||||
|
||||
const subscribers = ref([]); // 用於儲存 Subscriber 對象
|
||||
// 建立 alarmData 的函數
|
||||
const createAlarmData = (alarmList) => {
|
||||
alarmData.value = alarmList.children.map((item, index) => ({
|
||||
@ -13,8 +13,10 @@ const useAlarmDataStore = defineStore("alarmData", () => {
|
||||
alarmCount: 0,
|
||||
unackedCount: 0,
|
||||
alarmOrd: item.children && item.children[0] ? item.children[0].ord : null, // 儲存 alarmOrd
|
||||
Ord: item.ord ? item.ord : null,
|
||||
Ord: item.ord ? item.ord : null,
|
||||
}));
|
||||
// 在 store 初始化時,進行訂閱
|
||||
subscribeToAlarms();
|
||||
};
|
||||
|
||||
// 更新特定 alarm 數據的函數
|
||||
@ -27,8 +29,66 @@ const useAlarmDataStore = defineStore("alarmData", () => {
|
||||
});
|
||||
}
|
||||
};
|
||||
const subscribeToAlarms = () => {
|
||||
if (typeof window.requirejs === "undefined") return;
|
||||
window.requirejs(["baja!"], (baja) => {
|
||||
// 對每個 alarm 項目建立訂閱
|
||||
alarmData.value.forEach((alarm, index) => {
|
||||
if (!alarm.alarmOrd) return;
|
||||
|
||||
return { alarmData, createAlarmData, updateAlarmItem };
|
||||
// 建立訂閱器
|
||||
const subscriber = new baja.Subscriber();
|
||||
|
||||
// 定義 changed 事件的處理函數
|
||||
subscriber.attach("changed", (prop) => {
|
||||
try {
|
||||
let alarmCount = alarmData.value[index].alarmCount;
|
||||
let unackedCount = alarmData.value[index].unackedCount;
|
||||
|
||||
if (prop.$getDisplayName() === "In Alarm Count") {
|
||||
// 取得 In Alarm Count
|
||||
alarmCount = prop.$getValue();
|
||||
}
|
||||
if (prop.$getDisplayName() === "Unacked Alarm Count") {
|
||||
// 取得 Unacked Alarm Count
|
||||
unackedCount = prop.$getValue();
|
||||
}
|
||||
|
||||
// 更新 alarmData
|
||||
updateAlarmItem(index, alarmCount, unackedCount);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`處理 ${alarm.name || index} 告警變化失敗: ${error.message}`,
|
||||
error
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// 訂閱 alarm 資訊
|
||||
baja.Ord.make(alarm.alarmOrd)
|
||||
.get({ subscriber })
|
||||
.then((result) => {
|
||||
console.log(`Successfuly subscribed to alarm ${alarm.name}`);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(
|
||||
`訂閱 Alarm ${alarm.name || index} 失敗: ${err.message}`
|
||||
);
|
||||
subscriber.detach("changed");
|
||||
});
|
||||
subscribers.value.push(subscriber);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const clearAllSubscriber = () => {
|
||||
subscribers.value.forEach((subscriber) => {
|
||||
subscriber.detach("changed");
|
||||
});
|
||||
subscribers.value = [];
|
||||
};
|
||||
|
||||
return { alarmData, createAlarmData, updateAlarmItem, clearAllSubscriber };
|
||||
});
|
||||
|
||||
export default useAlarmDataStore;
|
||||
export default useAlarmDataStore;
|
||||
|
@ -6,33 +6,38 @@ const useUserStore = defineStore("user", () => {
|
||||
const userRole = ref("");
|
||||
|
||||
const loadUserInfo = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
window.require &&
|
||||
return new Promise((resolve, reject) => {
|
||||
if (window.require && window.requirejs) {
|
||||
window.requirejs(["baja!"], (baja) => {
|
||||
let currentUserName = baja.getUserName();
|
||||
userName.value = currentUserName;
|
||||
baja.Ord.make(`station:|slot:/Services/UserService/${currentUserName}`)
|
||||
baja.Ord.make(
|
||||
`station:|slot:/Services/UserService/${currentUserName}`
|
||||
)
|
||||
.get()
|
||||
.then((user) => {
|
||||
const rolesString = user.getRoles(); // 取得角色字串
|
||||
if (rolesString) {
|
||||
const rolesArray = rolesString.split(",");
|
||||
if (rolesArray.length > 0) {
|
||||
userRole.value = rolesArray[0].trim();
|
||||
console.log("選取的角色:", userRole.value);
|
||||
}
|
||||
const rolesString = user.getRoles(); // 取得角色字串
|
||||
if (rolesString) {
|
||||
const rolesArray = rolesString.split(",");
|
||||
if (rolesArray.length > 0) {
|
||||
userRole.value = rolesArray[0].trim();
|
||||
console.log("選取的角色:", userRole.value);
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
resolve();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(`訂閱失敗: ${err.message}`);
|
||||
reject(err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.error("未能載入 Baja 環境,請確認依賴已正確加載。");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return { userName, userRole, loadUserInfo };
|
||||
});
|
||||
|
||||
export default useUserStore;
|
||||
export default useUserStore;
|
||||
|
@ -1,17 +1,18 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from "vue";
|
||||
import DashboardStat from "@/components/dashboard/dashboardStat.vue";
|
||||
import DashboardBuild from "@/components/dashboard/dashboardBuild.vue";
|
||||
import DashboardElecChart from "@/components/dashboard/dashboardElecChart.vue";
|
||||
import DashboardTag from "@/components/dashboard/dashboardTag.vue";
|
||||
import DashboardAlert from "@/components/dashboard/dashboardAlert.vue";
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<a-row :gutter="24" class="p-5">
|
||||
<a-row :gutter="24" class="px-5 py-2">
|
||||
<a-col :span="8">
|
||||
<a-image width="100%" src="./build.jpg" class="rounded shadow-lg" />
|
||||
<!-- 建築物 -->
|
||||
<DashboardBuild />
|
||||
</a-col>
|
||||
<a-col :span="16">
|
||||
<a-col :span="16" class="">
|
||||
<!-- 用電數據 -->
|
||||
<DashboardStat />
|
||||
<!-- 用電圖表 -->
|
||||
@ -19,13 +20,14 @@ import DashboardAlert from "@/components/dashboard/dashboardAlert.vue";
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24" class="p-5">
|
||||
<a-col :span="16">
|
||||
<!-- <DashboardTag /> -->
|
||||
<a-row :gutter="24" class="px-5">
|
||||
<a-col :span="14">
|
||||
<!-- 系統小類 -->
|
||||
<DashboardTag />
|
||||
</a-col>
|
||||
<a-col :span="8" class="">
|
||||
<a-col :span="10">
|
||||
<!-- 告警 -->
|
||||
<!-- <DashboardAlert /> -->
|
||||
<DashboardAlert />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
33
src/views/system/NotFound.vue
Normal file
33
src/views/system/NotFound.vue
Normal file
@ -0,0 +1,33 @@
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
import useNiagaraDataStore from "@/stores/useNiagaraDataStore";
|
||||
const niagaraStore = useNiagaraDataStore();
|
||||
const homeData = computed(() => {
|
||||
const data = niagaraStore.headerList.children?.[2] || {};
|
||||
return {
|
||||
...data,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<a-result status="404" title="404" sub-title="抱歉,您造訪的頁面不存在">
|
||||
<template #extra>
|
||||
<a-button type="primary"
|
||||
><router-link
|
||||
:to="
|
||||
homeData.ord !== 'null'
|
||||
? {
|
||||
name: 'baja',
|
||||
query: { ord: encodeURIComponent(homeData.ord) },
|
||||
}
|
||||
: '/'
|
||||
"
|
||||
>
|
||||
首頁
|
||||
</router-link>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-result>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -1,38 +1,74 @@
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { ref, computed, onMounted, onUnmounted } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { nextTick } from "vue"; // 確保 iframe 渲染完成
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const iframeRef = ref(null);
|
||||
const loading = ref(true);
|
||||
|
||||
const ordUrl = computed(() => {
|
||||
const ord = route.query.ord || "";
|
||||
const decodedOrd = decodeURIComponent(ord);
|
||||
console.log("route.query.ord:", ord); // 除錯用
|
||||
console.log("decodedOrd:", decodedOrd); // 除錯用
|
||||
console.log("route.query.ord:", ord);
|
||||
console.log("decodedOrd:", decodedOrd);
|
||||
return decodedOrd;
|
||||
});
|
||||
|
||||
const iframeSrc = computed(() => {
|
||||
if (!ordUrl.value) {
|
||||
return ""; // 如果 ordUrl 為空,則不設定 src
|
||||
return "";
|
||||
}
|
||||
|
||||
if (
|
||||
ordUrl.value.startsWith("http://") ||
|
||||
ordUrl.value.startsWith("https://")
|
||||
) {
|
||||
return ordUrl.value; // 如果以 http 開頭,則直接使用 ordUrl 作為 src
|
||||
return ordUrl.value;
|
||||
} else {
|
||||
return `/ord?${ordUrl.value}|view:?fullScreen=true`; // 否則,添加 Niagara 前綴
|
||||
return `/ord?${ordUrl.value}|view:?fullScreen=true`;
|
||||
}
|
||||
});
|
||||
|
||||
const iframeLoad = () => {
|
||||
console.log("iframe loaded!");
|
||||
// const iframeDocument =
|
||||
// iframeRef.value?.contentDocument ||
|
||||
// iframeRef.value?.contentWindow?.document;
|
||||
// if (iframeDocument) {
|
||||
// const bodyText = iframeDocument.body.innerText || "";
|
||||
// if (bodyText.includes("Bad Request") || bodyText.includes("Problem")) {
|
||||
// console.error("iframe 內容顯示 400 錯誤,跳轉到 NotFound");
|
||||
// router.push({ name: "NotFound" });
|
||||
// }
|
||||
// }
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
// 確保 iframe 已經渲染到 DOM
|
||||
await nextTick();
|
||||
|
||||
if (iframeRef.value) {
|
||||
iframeRef.value.onload = iframeLoad;
|
||||
} else {
|
||||
console.warn("iframeRef is null!");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<iframe
|
||||
v-if="iframeSrc"
|
||||
:src="iframeSrc"
|
||||
width="100%"
|
||||
:style="{ height: 'calc(100vh - 90px)' }"
|
||||
></iframe>
|
||||
<a-spin :spinning="loading" tip="Loading..." size="large">
|
||||
<iframe
|
||||
v-if="iframeSrc"
|
||||
ref="iframeRef"
|
||||
:src="iframeSrc"
|
||||
width="100%"
|
||||
:style="{ height: 'calc(100vh - 90px)' }"
|
||||
@load="iframeLoad"
|
||||
></iframe>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
Loading…
Reference in New Issue
Block a user