Merge branch 'feature/system'

This commit is contained in:
koko 2024-10-18 09:17:59 +08:00
commit eeac33ca79
16 changed files with 263 additions and 213 deletions

View File

@ -1,2 +1,3 @@
export const GET_SYSTEM_FLOOR_LIST_API = `/api/Device/GetFloor`;
export const GET_SYSTEM_DEVICE_LIST_API = `/api/Device/GetDeviceList`;
export const GET_SYSTEM_REALTIME_API = `/api/Device/GetRealTimeData`;

View File

@ -1,4 +1,8 @@
import { GET_SYSTEM_FLOOR_LIST_API, GET_SYSTEM_DEVICE_LIST_API } from "./api";
import {
GET_SYSTEM_FLOOR_LIST_API,
GET_SYSTEM_DEVICE_LIST_API,
GET_SYSTEM_REALTIME_API,
} from "./api";
import instance from "@/util/request";
import apihandler from "@/util/apihandler";
@ -30,3 +34,11 @@ export const getSystemDevices = async ({
code: res.code,
});
};
export const getSystemRealTime = async (device_list) => {
const res = await instance.post(GET_SYSTEM_REALTIME_API, { device_list });
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
});
};

View File

@ -13,7 +13,7 @@ import { twMerge } from "tailwind-merge";
import hexToRgb from "@/util/hexToRgb";
import getModalPosition from "@/util/getModalPosition";
import useSystemStatusByBaja from "@/hooks/baja/useSystemStatusByBaja";
import ForgeInfoModal from "./ForgeInfoModal.vue";
import ForgeInfoModal from "../../views/system/components/SystemInfoModal.vue";
import useAlarmStore from "@/stores/useAlarmStore";
import useForgeSprite from "@/hooks/forge/useForgeSprite";
@ -64,6 +64,10 @@ const initViewer = (container) => {
let 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);
}
);
@ -79,6 +83,7 @@ const loadModel = (viewer, filePath) => {
(model) => {
viewer.impl.invalidate(true);
viewer.fitToView();
updateDataVisualization(viewer)
resolve(model);
console.log("模型加載完成");
},
@ -151,6 +156,17 @@ const initForge = async () => {
const viewer = await initViewer(forgeDom.value)
const filePath = `${FILE_BASEURL}/upload/forge/0.svf`;
await loadModel(viewer, filePath)
viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT,
async function () {
console.log(
"Autodesk.Viewing.GEOMETRY_LOADED_EVENT",
viewer.isLoadDone()
);
// updateForgeViewer(viewer);
createSprites()
hideAllObjects();
})
};
onMounted(() => {

View File

@ -1,118 +0,0 @@
<script setup>
import { defineProps, onMounted, ref, watch } from "vue";
import ForgeInfoModalDesktop from "./ForgeInfoModalDesktop.vue";
import ForgeInfoModalCog from "./ForgeInfoModalCog.vue";
const props = defineProps({
data: Object,
});
const currentTab = ref("desktop");
const tabs = {
desktop: ForgeInfoModalDesktop,
cog: ForgeInfoModalCog,
};
const changeOpenKey = (key) => {
currentTab.value = key;
};
const onCancel = () => {
forge_info_modal.close();
};
const position = ref({
left: "0px",
top: "0px",
});
watch(
() => props.data,
(newValue) => {
console.log(newValue);
position.value = newValue.initPos;
}
);
</script>
<template>
<Modal
id="forge_info_modal"
:onCancel="onCancel"
width="550"
:draggable="!data?.isMobile"
:backdrop="false"
:modalStyle="position"
:class="data?.isMobile ? '-translate-x-1/2 -translate-y-1/2' : ''"
>
<template #modalContent>
<div class="card bg-transparent text-white">
<div class="card-title py-2 border-b border-zinc-700 justify-between">
<h3>{{ data?.value.full_name }}</h3>
<div>
<Button
type="link"
class="btn-link btn-text-without-border px-2"
@click="() => changeOpenKey('desktop')"
>
<font-awesome-icon
:icon="['fas', 'desktop']"
size="lg"
class="text-[#a5abb1]"
/>
</Button>
<Button
type="link"
class="btn-link btn-text-without-border px-2"
@click="() => changeOpenKey('cog')"
>
<font-awesome-icon
:icon="['fas', 'cog']"
size="lg"
class="text-[#a5abb1]"
/>
</Button>
<!-- <Button
type="link"
class="btn-link btn-text-without-border px-2"
@click="() => changeOpenKey('triangle')"
>
<font-awesome-icon
:icon="['fas', 'exclamation-triangle']"
size="lg"
class="text-[#a5abb1]"
/>
</Button>
<Button
type="link"
class="btn-link btn-text-without-border px-2"
@click="() => changeOpenKey('bars')"
>
<font-awesome-icon
:icon="['fas', 'bars']"
size="lg"
class="text-[#a5abb1]"
/>
</Button>-->
<Button
type="link"
class="btn-link btn-text-without-border px-2"
@click="onCancel"
>
<font-awesome-icon
:icon="['fas', 'times']"
size="lg"
class="text-[#a5abb1]"
/>
</Button>
</div>
</div>
<div class="card-body px-0">
<component :is="tabs[currentTab]" :data="data"></component>
</div>
</div>
</template>
</Modal>
</template>
<style lang="scss" scoped></style>

View File

@ -1,34 +0,0 @@
<script setup>
import { defineProps, watch, computed } from "vue";
import { useRoute } from "vue-router";
const route = useRoute();
const props = defineProps({
data: Object,
});
const pxRoute = computed(() =>
route.path === "/dashboard" ? "GraphicM" : "GraphicU"
);
watch(
() => props.data,
(newValue) => {
console.log(newValue, newValue.value.device_number);
}
);
const device_px_route = computed(() =>
props.data?.value.device_number.replaceAll("_", "/")
);
</script>
<template>
<iframe
v-if="data"
:src="`/ord?station:%7Cslot:/${device_px_route}|view:${pxRoute}?fullScreen=true`"
style="width: 500px; height: 350px"
></iframe>
</template>
<style lang="scss" scoped></style>

View File

@ -1,4 +1,7 @@
export default function useForgeHeatmap(){
import { watch, inject, markRaw, ref } from "vue";
export default function useForgeHeatmap(dataVizExtn, forgeViewer){
const { subscribeData } = inject("system_deviceList");
const createHeatMap = async (heatMapName) => {
const {
@ -10,7 +13,7 @@ export default function useForgeHeatmap(){
const shadingGroup = new SurfaceShadingGroup(`iot_heatmap_${heatMapName}`);
const rooms = new Map();
for (const { id, roomDbId, position, sensorTypes } of deviceList.value) {
for (const { id, roomDbId, position, sensorTypes } of subscribeData.value) {
if (!id || roomDbId == -1 || !roomDbId) {
continue;
}
@ -28,20 +31,19 @@ export default function useForgeHeatmap(){
shadingData.addChild(shadingGroup);
shadingData.initialize(forgeViewer.value?.model);
await dataVizExtension.value.setupSurfaceShading(
await dataVizExtn.value.setupSurfaceShading(
forgeViewer.value.model,
shadingData
);
dataVizExtension.value.registerSurfaceShadingColors(
"temperature",
dataVizExtn.value.registerSurfaceShadingColors(
heatMapName,
[0x0000ff, 0x00ff00, 0xffff00, 0xff0000]
);
dataVizExtension.value.renderSurfaceShading(
dataVizExtn.value.renderSurfaceShading(
`iot_heatmap_${heatMapName}`,
"temperature",
heatMapName,
getSensorValue
);
console.log(dataVizExtension.value);
};
}

View File

@ -40,6 +40,7 @@ export default function useForgeSprite() {
const viewableData = new DataVizCore.ViewableData();
viewableData.spriteSize = 24; // Sprites as points of size 24 x 24 pixels
subscribeData.value?.forEach((d, index) => {
if (d.device_coordinate_3d) {
const position = d.device_coordinate_3d;
style.color = new THREE.Color(hexToRgb(d.device_normal_color));
const viewable = new DataVizCore.SpriteViewable(
@ -48,12 +49,13 @@ export default function useForgeSprite() {
d.spriteDbId
);
viewableData.addViewable(viewable);
}
});
await viewableData.finish();
dataVizExtn.value.addViewables(viewableData);
NOP_VIEWER.addEventListener(DataVizCore.MOUSE_CLICK, onSpriteClicked);
NOP_VIEWER.addEventListener(
forgeViewer.value.addEventListener(DataVizCore.MOUSE_CLICK, onSpriteClicked);
forgeViewer.value.addEventListener(
Autodesk.Viewing.SELECTION_CHANGED_EVENT,
onSpriteClicked
);
@ -80,17 +82,17 @@ export default function useForgeSprite() {
};
const hideAllObjects = () => {
const tree = NOP_VIEWER.model?.getData().instanceTree;
const tree = forgeViewer.value.model.getInstanceTree();
const allDbIdsStr = Object.keys(tree.nodeAccess.dbIdToIndex);
for (var i = 0; i < allDbIdsStr.length; i++) {
NOP_VIEWER.hide(parseInt(allDbIdsStr[i]));
forgeViewer.value.hide(parseInt(allDbIdsStr[i]));
}
subscribeData.value.forEach((value, index) => {
NOP_VIEWER.show(value.forge_dbid);
forgeViewer.value.show(value.forge_dbid);
});
NOP_VIEWER.impl.invalidate(true);
forgeViewer.value.impl.invalidate(true);
};
return {

View File

@ -6,6 +6,7 @@ import useBuildingStore from "@/stores/useBuildingStore";
import ForgeForSystem from "@/components/forge/ForgeForSystem.vue";
import { getSystemDevices } from "@/apis/system";
import SystemSubBar from './components/SystemSubBar.vue';
import SystemInfoModal from './components/SystemInfoModal.vue';
const buildingStore = useBuildingStore()
@ -26,7 +27,7 @@ const statusList = computed(() => {
})
const raw_data = ref([])
const data = ref([])
const data = ref([]) // filter data
const route = useRoute()
const getData = async () => {
@ -34,12 +35,26 @@ const getData = async () => {
sub_system_tag: route.params.sub_system_id,
building_tag: buildingStore.selectedBuilding?.building_tag,
})
const data = res.data.map(d => ({ ...d, key: d.full_name }));
const data = res.data.map(d => ({
...d, key: d.full_name, device_list: d.device_list.map((d, index) => ({
...d,
forge_dbid: parseInt(d.forge_dbid),
device_coordinate_3d: d.device_coordinate_3d
? JSON.parse(d.device_coordinate_3d)
: { x: 0, y: 0 },
alarmMsg: "",
is_show: true,
currentColor: d.device_normal_point_color,
spriteDbId: 10 + index,
sensorTypes: d.points.map(({ points }) => points),
points: d.points.map((p) => ({ ...p, value: "" }))
})),
}));
raw_data.value = data
data.value = data
}
const subscribeData = ref([]);
const subscribeData = ref([]); // flat data
const getSubPoint = (normal, close, error, sub_points) => {
let points = {
@ -56,23 +71,7 @@ const getSubData = (value) => {
value.forEach((device) => {
items = [
...items,
...device.device_list.map((d, index) => ({
...d,
forge_dbid: parseInt(d.forge_dbid),
device_coordinate_3d: d.device_coordinate_3d
? JSON.parse(d.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
// ),
alarmMsg: "",
is_show: true,
currentColor: d.device_normal_point_color,
spriteDbId: 10 + index,
})),
...device.device_list
];
});
data.value = raw_data.value;
@ -83,6 +82,10 @@ watch(raw_data, (newValue) => {
updateDataByGas("all")
});
watch(data, (newValue) => {
console.log(newValue);
})
const updateDataByGas = (gas) => {
console.log(gas)
if (gas === "all") {
@ -112,12 +115,40 @@ const updateCurrentFloor = (floor) => {
provide("system_deviceList", { data, subscribeData, currentFloor, updateCurrentFloor, updateDataByGas })
//
const currentInfoModalData = ref(null);
const isMobile = (pointerType) => {
// let flag =
// /phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone/gi.test(
// navigator.userAgent
// );
// console.log("isMobile", flag);
return pointerType !== "mouse"; // is desktop
};
const getCurrentInfoModalData = (e, position, value) => {
const mobile = isMobile(e.pointerType);
selectedDevice.value = {
initPos: mobile
? { left: `50%`, top: `50%` }
: { left: `${position.left}px`, top: `${position.top}px` },
value,
isMobile: mobile,
};;
document.getElementById('system_info_modal').showModal();
};
const selectedDevice = ref(null);
provide("system_selectedDevice", { selectedDevice, getCurrentInfoModalData })
</script>
<template>
<SystemInfoModal :data="selectedDevice" />
<SystemFloorBar />
<div class="grid grid-cols-2 gap-5 mt-8 mb-4">
<div class="col-span-1 h-[80vh] flex flex-col justify-between">
<div class="col-span-1 h-[80vh] flex flex-col justify-start">
<div>
<div class="flex mb-4 items-center">
<span class="flex items-center mr-3" v-if="statusList?.device_normal_text">
@ -146,6 +177,7 @@ provide("system_deviceList", { data, subscribeData, currentFloor, updateCurrentF
<ForgeForSystem :initialData="{}" />
</div>
</div>
</template>
<style lang='scss' scoped></style>

View File

@ -38,10 +38,10 @@ watch([() => currentFloor, () => asset_floor_chart], ([newValue, newChart]) => {
asset_floor_chart.value.updateSvg(
{
full_name: newValue.value?.title,
path: `${FILE_BASEURL}/upload/floor_map/${newValue.value.map_url}`,
path: `${FILE_BASEURL}/${newValue.value.map_url}`,
},
defaultOption(newValue.value?.title, subscribeData.value.filter(d => d.device_coordinate).map(d => JSON.parse(d.device_coordinate)))
defaultOption(newValue.value?.title, subscribeData.value.filter(d => d.device_coordinate).map(d => [d.device_coordinate.split(",")[0], d.device_coordinate.split(",")[1]]))
);
}

View File

@ -4,6 +4,7 @@ import { inject } from "vue"
const { data } = inject("system_deviceList")
const { getCurrentInfoModalData } = inject("system_selectedDevice")
</script>
@ -23,11 +24,15 @@ const { data } = inject("system_deviceList")
<span class="w-8 h-8" v-else></span>
<span>{{ device.full_name }}</span>
</div>
<div class="flex justify-between">
<div class="sec03">
<span></span>
<span>狀態</span>
<span></span>
</div>
<button class="btn-text border-0 "
@click.stop.prevent="(e) => getCurrentInfoModalData(e, { left: e.clientX, top: e.clientY }, device)">詳細資料</button>
</div>
</div>
@ -52,6 +57,10 @@ const { data } = inject("system_deviceList")
}
.item .btn-text {
@apply hover:bg-transparent focus-within:bg-transparent !important;
}
.equipment-show .item .sec01 span:nth-child(1) {
font-size: 1rem;
position: relative;

View File

@ -1,6 +1,6 @@
<script setup>
import { useRoute, useRouter } from 'vue-router';
import { getSystemFloors } from "@/apis/system"
import { getAssetFloorList } from "@/apis/asset";
import { onMounted, ref, watch, inject } from 'vue';
import useBuildingStore from "@/stores/useBuildingStore";
import useActiveBtn from "@/hooks/useActiveBtn"
@ -14,18 +14,20 @@ const { updateCurrentFloor } = inject("system_deviceList")
const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
const getFloors = async () => {
const res = await getSystemFloors(store.selectedBuilding?.building_tag, route.params.sub_system_id)
const res = await getAssetFloorList()
let data = res.data.find(d => d.building_tag === store.selectedBuilding?.building_tag)
console.log(data)
setItems([
{
title: "總覽",
key: "main",
active: route.params.floor_id ? false : true,
},
...res.data.map((d, idx) => ({
title: d.floor_tag,
...data.floors.map((d, idx) => ({
title: d.full_name,
key: d.floor_guid,
active: route.params.floor_id === d.floor_guid,
map_url: d.floor_map_name
map_url: d.floor_map_url + ".svg"
}))
]);
}

View File

@ -0,0 +1,31 @@
<script setup>
import { inject, ref, watch } from "vue";
import SystemInfoModalContent from "./SystemInfoModalContent.vue";
const { selectedDevice: data } = inject("system_selectedDevice")
const position = ref({
left: "0px",
top: "0px",
});
watch(
() => data,
(newValue) => {
console.log(newValue);
position.value = newValue.initPos;
}
);
</script>
<template>
<Modal id="system_info_modal" :onCancel="onCancel" width="600" :draggable="!data?.isMobile" :backdrop="false"
:modalStyle="position" :class="data?.isMobile ? '-translate-x-1/2 -translate-y-1/2' : ''">
<template #modalContent>
<SystemInfoModalContent />
</template>
</Modal>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,73 @@
<script setup>
import { defineProps, inject, ref, watch } from "vue";
import SystemInfoModalDesktop from "./SystemInfoModalDesktop.vue";
import SystemInfoModalCog from "./SystemInfoModalCog.vue";
import { twMerge } from "tailwind-merge";
const { selectedDevice: data } = inject("system_selectedDevice")
const currentTab = ref("desktop");
const tabs = {
desktop: SystemInfoModalDesktop,
cog: SystemInfoModalCog,
};
const changeOpenKey = (key) => {
currentTab.value = key;
};
const onCancel = () => {
currentTab.value = "desktop";
document.getElementById('system_info_modal').close();
};
</script>
<template>
<div class="card bg-transparent text-white">
<div class="card-title py-2 border-b border-zinc-700 justify-between">
<h3>{{ data?.value.full_name }}</h3>
<div>
<Button type="link" class="btn-link btn-text-without-border px-2" @click="() => changeOpenKey('desktop')">
<font-awesome-icon :icon="['fas', 'desktop']" size="lg"
:class="twMerge(currentTab === 'desktop' ? 'text-success' : 'text-[#a5abb1]')" />
</Button>
<Button type="link" class="btn-link btn-text-without-border px-2" @click="() => changeOpenKey('cog')">
<font-awesome-icon :icon="['fas', 'cog']" size="lg"
:class="twMerge(currentTab === 'cog' ? 'text-success' : 'text-[#a5abb1]')" />
</Button>
<!-- <Button
type="link"
class="btn-link btn-text-without-border px-2"
@click="() => changeOpenKey('triangle')"
>
<font-awesome-icon
:icon="['fas', 'exclamation-triangle']"
size="lg"
class="text-[#a5abb1]"
/>
</Button>
<Button
type="link"
class="btn-link btn-text-without-border px-2"
@click="() => changeOpenKey('bars')"
>
<font-awesome-icon
:icon="['fas', 'bars']"
size="lg"
class="text-[#a5abb1]"
/>
</Button>-->
<Button type="link" class="btn-link btn-text-without-border px-2" @click="onCancel">
<font-awesome-icon :icon="['fas', 'times']" size="lg" class="text-[#a5abb1]" />
</Button>
</div>
</div>
<div class="card-body px-0">
<component :is="tabs[currentTab]" :data="[]"></component>
</div>
</div>
</template>
<style lang='scss' scoped></style>

View File

@ -0,0 +1,22 @@
<script setup>
import { inject } from "vue";
import { useRoute } from "vue-router";
const { selectedDevice } = inject("system_selectedDevice");
const columns = [{
title: "屬性",
key: "points"
},
{
title: "值",
key: "value"
}]
</script>
<template>
<Table :loading="loading" :columns="columns" :dataSource="[]" :withStyle="false"></Table>
</template>
<style lang="scss" scoped></style>

View File

@ -11,10 +11,11 @@ watch(() => buildingStore, (newValue) => {
newValue.selectedSystem?.points?.length > 0 && setItems(newValue.selectedSystem.points.map(d => ({
title: d.full_name,
key: d.points,
active: false,
active: d.points === "Temp",
})))
}, {
deep: true,
immediate: true
})
const onClick = (item) => {
@ -25,9 +26,8 @@ const onClick = (item) => {
</script>
<template>
<template v-if="items.length > 0">
<template v-if="buildingStore.selectedSystem?.points?.length > 0">
<ButtonGroup :items="items" :withLine="false" className="btn-xs rounded-md" :onclick="(e, item) => onClick(item)" />
</template>
</template>