增加氣體分類標籤
This commit is contained in:
parent
1e536ae44f
commit
f364066d96
@ -8,7 +8,10 @@ const props = defineProps({
|
||||
className: String,
|
||||
id: String,
|
||||
svg: Object,
|
||||
getCoordinate: Function,
|
||||
getCoordinate: {
|
||||
type: Function,
|
||||
default: null
|
||||
},
|
||||
});
|
||||
|
||||
let chart = ref(null);
|
||||
@ -22,33 +25,23 @@ async function updateSvg(svg, option) {
|
||||
axios.get(svg.path).then(({ data }) => {
|
||||
echarts.registerMap(svg.full_name, { svg: data });
|
||||
chart.value.setOption(option);
|
||||
chart.value.getZr().on("click", function (params) {
|
||||
var pixelPoint = [params.offsetX, params.offsetY];
|
||||
var dataPoint = chart.value.convertFromPixel({ geoIndex: 0 }, pixelPoint);
|
||||
currentClickPosition.value = dataPoint;
|
||||
props.getCoordinate(dataPoint);
|
||||
chart.value.setOption({
|
||||
series: {
|
||||
data: [dataPoint],
|
||||
},
|
||||
if (props.getCoordinate) {
|
||||
chart.value.getZr().on("click", function (params) {
|
||||
var pixelPoint = [params.offsetX, params.offsetY];
|
||||
var dataPoint = chart.value.convertFromPixel({ geoIndex: 0 }, pixelPoint);
|
||||
currentClickPosition.value = dataPoint;
|
||||
props.getCoordinate(dataPoint);
|
||||
chart.value.setOption({
|
||||
series: {
|
||||
data: [dataPoint],
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
console.log(chart.value.getOption());
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
console.log("updateSvg", svg.path);
|
||||
// fetch(svg.path)
|
||||
// .then((res) => console.log(res))
|
||||
// .then(function (svg) {
|
||||
// console.log(svg);
|
||||
// // echarts.registerMap(svg.full_name, { svg });
|
||||
// // chart.setOption(option);
|
||||
// // chart.getZr().on("click", function (params) {
|
||||
// // var pixelPoint = [params.offsetX, params.offsetY];
|
||||
// // var dataPoint = curChart.convertFromPixel({ geoIndex: 0 }, pixelPoint);
|
||||
// // console.log(dataPoint);
|
||||
// // currentClickPosition.value = dataPoint;
|
||||
// // });
|
||||
// });
|
||||
}
|
||||
|
||||
function init() {
|
||||
@ -69,7 +62,7 @@ defineExpose({
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div :id="id" class="min-h-[500px] max-h-fit w-full" ref="dom"></div>
|
||||
<div :id="id" class="min-h-[70vh] max-h-fit w-full bg-white" ref="dom"></div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
217
src/components/forge/ForgeForSystem.vue
Normal file
217
src/components/forge/ForgeForSystem.vue
Normal file
@ -0,0 +1,217 @@
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
onMounted,
|
||||
defineProps,
|
||||
computed,
|
||||
onUnmounted,
|
||||
watch,
|
||||
provide,
|
||||
} from "vue";
|
||||
import { getUrn, getAccessToken } from "@/apis/forge";
|
||||
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 useAlarmStore from "@/stores/useAlarmStore";
|
||||
import useForgeSprite from "@/hooks/forge/useForgeSprite";
|
||||
|
||||
const props = defineProps({
|
||||
initialData: Object,
|
||||
cubeStyle: {
|
||||
type: Object,
|
||||
default: {
|
||||
right: 0,
|
||||
top: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const heat_bar_isShow = ref(false);
|
||||
const updateHeatBarIsShow = (isShow) => {
|
||||
heat_bar_isShow.value = isShow;
|
||||
};
|
||||
|
||||
const { updateDataVisualization, createSprites, hideAllObjects } = useForgeSprite()
|
||||
|
||||
const forgeDom = ref(null);
|
||||
|
||||
const initViewer = (container) => {
|
||||
return new Promise(function (resolve, reject) {
|
||||
Autodesk.Viewing.Initializer({ getAccessToken }, function () {
|
||||
const config = {
|
||||
extensions: ["Autodesk.DataVisualization", "Autodesk.DocumentBrowser"],
|
||||
};
|
||||
let viewer = new Autodesk.Viewing.GuiViewer3D(container, config);
|
||||
Autodesk.Viewing.Private.InitParametersSetting.alpha = true;
|
||||
viewer.start();
|
||||
resolve(viewer);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const loadModel = (viewer, urn) => {
|
||||
return new Promise(function (resolve, reject) {
|
||||
async function onDocumentLoadSuccess(doc) {
|
||||
viewer.setGroundShadow(false);
|
||||
viewer.impl.renderer().setClearAlpha(0); //clear alpha channel
|
||||
viewer.impl.glrenderer().setClearColor(0xffffff, 0); //set transparent background, color code does not matter
|
||||
viewer.impl.invalidate(true); //trigger rendering
|
||||
|
||||
const documentNode = await viewer.loadDocumentNode(
|
||||
doc,
|
||||
doc.getRoot().getDefaultGeometry()
|
||||
);
|
||||
updateDataVisualization(viewer)
|
||||
resolve(documentNode);
|
||||
}
|
||||
function onDocumentLoadFailure(code, message, errors) {
|
||||
reject({ code, message, errors });
|
||||
}
|
||||
Autodesk.Viewing.Document.load(
|
||||
"urn:" + urn,
|
||||
onDocumentLoadSuccess,
|
||||
onDocumentLoadFailure
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const initForge = () => {
|
||||
getUrn().then((res) => {
|
||||
if (!res.isSuccess) return;
|
||||
|
||||
initViewer(forgeDom.value).then((viewer) => {
|
||||
loadModel(viewer, res.data[0].urn_3D).then(() => {
|
||||
viewer.addEventListener(
|
||||
Autodesk.Viewing.GEOMETRY_LOADED_EVENT,
|
||||
async function () {
|
||||
console.log(
|
||||
"Autodesk.Viewing.GEOMETRY_LOADED_EVENT",
|
||||
viewer.isLoadDone()
|
||||
);
|
||||
// updateForgeViewer(viewer);
|
||||
createSprites()
|
||||
hideAllObjects();
|
||||
}
|
||||
);
|
||||
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
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
console.log("Forge 加載");
|
||||
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();
|
||||
};
|
||||
onUnmounted(() => {
|
||||
console.log("Forge 銷毀");
|
||||
|
||||
NOP_VIEWER.tearDown();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ForgeInfoModal :data="currentInfoModalData" />
|
||||
<div id="forge-preview" ref="forgeDom" class="relative w-full h-full min-h-full">
|
||||
<div v-show="heat_bar_isShow" class="absolute z-10 heatbar">
|
||||
<div class="w-40 flex justify-between text-[10px] mb-1">
|
||||
<span class="text-gradient-1">10 °C</span>
|
||||
<span class="text-gradient-2">20 °C</span>
|
||||
<span class="text-gradient-3">30 °C</span>
|
||||
<span class="text-gradient-4">40 °C</span>
|
||||
</div>
|
||||
<div class="w-40 h-3" style="
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
#0000ff 0%,
|
||||
#00ff00 33%,
|
||||
#ffff00 66%,
|
||||
#ff0000 100%
|
||||
);
|
||||
"></div>
|
||||
</div>
|
||||
<!-- label -->
|
||||
<!-- https://github.com/augustogoncalves/forge-plant-operation/blob/master/forgeSample/wwwroot/js/iconExtension.js -->
|
||||
<!-- https://github.com/dukedhx/viewer-iot-react-feathersjs/blob/master/src/client/iconExtension.js -->
|
||||
|
||||
<label v-for="value in subscribeData" :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
|
||||
)
|
||||
">
|
||||
<span class="mr-2">{{ value.full_name }}</span>
|
||||
<span v-if="value.alarmMsg">{{ value.alarmMsg }}</span>
|
||||
<span v-else>{{ value.show_value }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style lang="css">
|
||||
.adsk-viewing-viewer {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
#guiviewer3d-toolbar {
|
||||
display: none;
|
||||
bottom: 200px;
|
||||
}
|
||||
|
||||
.viewcubeWrapper {
|
||||
right: v-bind("`${props.cubeStyle.right}%`") !important;
|
||||
top: v-bind("`${props.cubeStyle.top}%`") !important;
|
||||
}
|
||||
|
||||
.homeViewWrapper {
|
||||
transform: scale(0.9) !important;
|
||||
}
|
||||
|
||||
.heatbar {
|
||||
right: v-bind("`${props.cubeStyle.right + 2}%`") !important;
|
||||
top: 0% !important;
|
||||
}
|
||||
</style>
|
@ -291,6 +291,7 @@ export default function useSystemStatusByBaja(updateHeatBarIsShow) {
|
||||
};
|
||||
|
||||
const fitToView = () => {
|
||||
if(!searchParams.value.camera_position) return
|
||||
const { x, y, z } = JSON.parse(searchParams.value.camera_position);
|
||||
const newPosition = new THREE.Vector3(x, y, z); //!<<< 相机的新位置
|
||||
|
||||
|
47
src/hooks/forge/useForgeHeatmap.js
Normal file
47
src/hooks/forge/useForgeHeatmap.js
Normal file
@ -0,0 +1,47 @@
|
||||
export default function useForgeHeatmap(){
|
||||
|
||||
const createHeatMap = async (heatMapName) => {
|
||||
const {
|
||||
SurfaceShadingData,
|
||||
SurfaceShadingPoint,
|
||||
SurfaceShadingNode,
|
||||
SurfaceShadingGroup,
|
||||
} = Autodesk.DataVisualization.Core;
|
||||
const shadingGroup = new SurfaceShadingGroup(`iot_heatmap_${heatMapName}`);
|
||||
const rooms = new Map();
|
||||
|
||||
for (const { id, roomDbId, position, sensorTypes } of deviceList.value) {
|
||||
if (!id || roomDbId == -1 || !roomDbId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!rooms.has(roomDbId)) {
|
||||
const room = new SurfaceShadingNode(id, roomDbId);
|
||||
shadingGroup.addChild(room);
|
||||
rooms.set(roomDbId, room);
|
||||
}
|
||||
const room = rooms.get(roomDbId);
|
||||
room.addPoint(new SurfaceShadingPoint(id, position, sensorTypes));
|
||||
}
|
||||
|
||||
const shadingData = new SurfaceShadingData();
|
||||
shadingData.addChild(shadingGroup);
|
||||
shadingData.initialize(forgeViewer.value?.model);
|
||||
|
||||
await dataVizExtension.value.setupSurfaceShading(
|
||||
forgeViewer.value.model,
|
||||
shadingData
|
||||
);
|
||||
dataVizExtension.value.registerSurfaceShadingColors(
|
||||
"temperature",
|
||||
[0x0000ff, 0x00ff00, 0xffff00, 0xff0000]
|
||||
);
|
||||
dataVizExtension.value.renderSurfaceShading(
|
||||
`iot_heatmap_${heatMapName}`,
|
||||
"temperature",
|
||||
getSensorValue
|
||||
);
|
||||
|
||||
console.log(dataVizExtension.value);
|
||||
};
|
||||
}
|
101
src/hooks/forge/useForgeSprite.js
Normal file
101
src/hooks/forge/useForgeSprite.js
Normal file
@ -0,0 +1,101 @@
|
||||
import { watch, inject, markRaw, ref } from "vue";
|
||||
import useAlarmStore from "@/stores/useAlarmStore";
|
||||
import hexToRgb from "@/util/hexToRgb";
|
||||
|
||||
export default function useForgeSprite() {
|
||||
const store = useAlarmStore();
|
||||
const { subscribeData } = inject("system_deviceList");
|
||||
const forgeViewer = ref(null);
|
||||
const dataVizExtn = ref(null);
|
||||
const updateDataVisualization = async (viewer) => {
|
||||
if (!forgeViewer.value) {
|
||||
forgeViewer.value = markRaw(viewer);
|
||||
}
|
||||
|
||||
const dataVisualization = await NOP_VIEWER.loadExtension(
|
||||
"Autodesk.DataVisualization"
|
||||
);
|
||||
dataVizExtn.value = markRaw(dataVisualization);
|
||||
};
|
||||
|
||||
const onSpriteClicked = (event) => {
|
||||
event.hasStopped = true;
|
||||
const data = deviceList.value.find((d) => d.spriteDbId === event.dbId);
|
||||
modalContent.value = data;
|
||||
store.getDbIdStore(data.forge_dbid);
|
||||
toggleModal(event.originalEvent);
|
||||
};
|
||||
// 創建 sprites
|
||||
const createSprites = async () => {
|
||||
const DataVizCore = Autodesk.DataVisualization.Core;
|
||||
const viewableType = DataVizCore.ViewableType.SPRITE;
|
||||
let spriteColor = new THREE.Color(0xffffff);
|
||||
const BASEURL = import.meta.env.VITE_FORGE_BASEURL;
|
||||
const spriteIconUrl = `${BASEURL}/hotspot.svg`;
|
||||
const style = new DataVizCore.ViewableStyle(
|
||||
viewableType,
|
||||
spriteColor,
|
||||
spriteIconUrl
|
||||
);
|
||||
const viewableData = new DataVizCore.ViewableData();
|
||||
viewableData.spriteSize = 24; // Sprites as points of size 24 x 24 pixels
|
||||
subscribeData.value?.forEach((d, index) => {
|
||||
const position = d.device_coordinate_3d;
|
||||
style.color = new THREE.Color(hexToRgb(d.device_normal_color));
|
||||
const viewable = new DataVizCore.SpriteViewable(
|
||||
position,
|
||||
style,
|
||||
d.spriteDbId
|
||||
);
|
||||
viewableData.addViewable(viewable);
|
||||
});
|
||||
await viewableData.finish();
|
||||
dataVizExtn.value.addViewables(viewableData);
|
||||
|
||||
NOP_VIEWER.addEventListener(DataVizCore.MOUSE_CLICK, onSpriteClicked);
|
||||
NOP_VIEWER.addEventListener(
|
||||
Autodesk.Viewing.SELECTION_CHANGED_EVENT,
|
||||
onSpriteClicked
|
||||
);
|
||||
};
|
||||
|
||||
const fitToView = () => {
|
||||
const { x, y, z } = JSON.parse(searchParams.value.camera_position);
|
||||
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); //!<<< 焦點的新位置
|
||||
|
||||
NOP_VIEWER.navigation.getCamera().setView({
|
||||
position: newPosition.clone(),
|
||||
target: newTarget.clone(),
|
||||
});
|
||||
setTimeout(() => {
|
||||
updateDbidPosition(NOP_VIEWER, subscribeData.value);
|
||||
}, 700);
|
||||
};
|
||||
|
||||
const hideAllObjects = () => {
|
||||
const tree = NOP_VIEWER.model?.getData().instanceTree;
|
||||
const allDbIdsStr = Object.keys(tree.nodeAccess.dbIdToIndex);
|
||||
for (var i = 0; i < allDbIdsStr.length; i++) {
|
||||
NOP_VIEWER.hide(parseInt(allDbIdsStr[i]));
|
||||
}
|
||||
|
||||
subscribeData.value.forEach((value, index) => {
|
||||
NOP_VIEWER.show(value.forge_dbid);
|
||||
});
|
||||
|
||||
NOP_VIEWER.impl.invalidate(true);
|
||||
};
|
||||
|
||||
return {
|
||||
createSprites,
|
||||
updateDataVisualization,
|
||||
hideAllObjects,
|
||||
};
|
||||
}
|
@ -1,8 +1,11 @@
|
||||
<script setup>
|
||||
import { RouterView, } from 'vue-router';
|
||||
import { computed } from "vue";
|
||||
import { RouterView, useRoute } from 'vue-router';
|
||||
import { computed, watch, provide, ref } from "vue";
|
||||
import SystemFloorBar from './components/SystemFloorBar.vue';
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
import ForgeForSystem from "@/components/forge/ForgeForSystem.vue";
|
||||
import { getSystemDevices } from "@/apis/system";
|
||||
import SystemSubBar from './components/SystemSubBar.vue';
|
||||
|
||||
const buildingStore = useBuildingStore()
|
||||
|
||||
@ -22,12 +25,99 @@ const statusList = computed(() => {
|
||||
return null
|
||||
})
|
||||
|
||||
const raw_data = ref([])
|
||||
const data = ref([])
|
||||
const route = useRoute()
|
||||
|
||||
const getData = async () => {
|
||||
const res = await getSystemDevices({
|
||||
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 }));
|
||||
raw_data.value = data
|
||||
data.value = data
|
||||
}
|
||||
|
||||
const subscribeData = ref([]);
|
||||
|
||||
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 getSubData = (value) => {
|
||||
let items = [];
|
||||
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,
|
||||
})),
|
||||
];
|
||||
});
|
||||
data.value = raw_data.value;
|
||||
subscribeData.value = items;
|
||||
}
|
||||
|
||||
watch(raw_data, (newValue) => {
|
||||
updateDataByGas("all")
|
||||
});
|
||||
|
||||
const updateDataByGas = (gas) => {
|
||||
console.log(gas)
|
||||
if (gas === "all") {
|
||||
getSubData(raw_data.value)
|
||||
} else {
|
||||
data.value = raw_data.value.map((d) => ({
|
||||
...d,
|
||||
device_list: d.device_list.filter(({ points }) => points.some(({ points: p }) => p === gas))
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
watch([() => route, () => buildingStore.selectedBuilding], ([newRoute, newBuilding]) => {
|
||||
if (newBuilding && newRoute.params.sub_system_id) {
|
||||
getData()
|
||||
}
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
})
|
||||
|
||||
const currentFloor = ref(null);
|
||||
|
||||
const updateCurrentFloor = (floor) => {
|
||||
currentFloor.value = floor;
|
||||
}
|
||||
|
||||
provide("system_deviceList", { data, subscribeData, currentFloor, updateCurrentFloor, updateDataByGas })
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SystemFloorBar />
|
||||
<div class="grid grid-cols-2 mt-10">
|
||||
<div class="col-span-1">
|
||||
<div class="grid grid-cols-2 gap-5 mb-5">
|
||||
<div class="col-span-1 my-5 h-[80vh]">
|
||||
<div class="flex mb-4 items-center">
|
||||
<span class="flex items-center mr-3" v-if="statusList?.device_normal_text">
|
||||
<span class="w-7 h-7 rounded-full inline-block mr-1"
|
||||
@ -45,9 +135,14 @@ const statusList = computed(() => {
|
||||
<span>{{ statusList.device_error_text }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<RouterView />
|
||||
<SystemSubBar class="mt-2 mb-4" />
|
||||
<div class="max-h-[75vh] pr-2 my-2 overflow-y-auto">
|
||||
<RouterView />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-1 h-full">
|
||||
<ForgeForSystem :initialData="{}" />
|
||||
</div>
|
||||
<div class="col-span-1"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -1,10 +1,62 @@
|
||||
<script setup>
|
||||
import { useRoute } from 'vue-router';
|
||||
import EffectScatter from "@/components/chart/EffectScatter.vue";
|
||||
import { inject, ref, watch } from 'vue';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
const route = useRoute()
|
||||
|
||||
const { currentFloor, subscribeData } = inject("system_deviceList")
|
||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
||||
|
||||
const asset_floor_chart = ref(null);
|
||||
const defaultOption = (map, data = []) => ({
|
||||
tooltip: {},
|
||||
geo: {
|
||||
tooltip: {
|
||||
show: false,
|
||||
},
|
||||
map,
|
||||
roam: true, // 一定要关闭拖拽
|
||||
},
|
||||
series: {
|
||||
type: "effectScatter",
|
||||
coordinateSystem: "geo",
|
||||
geoIndex: 0,
|
||||
symbolSize: 10,
|
||||
itemStyle: {
|
||||
color: "#b02a02",
|
||||
},
|
||||
encode: {
|
||||
tooltip: 2,
|
||||
},
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
watch([() => currentFloor, () => asset_floor_chart], ([newValue, newChart]) => {
|
||||
if (newValue.value && newChart.value) {
|
||||
asset_floor_chart.value.updateSvg(
|
||||
{
|
||||
full_name: newValue.value?.title,
|
||||
path: `${FILE_BASEURL}/floor_map/22495db3-36cc-4778-8bf9-55e4e9a7145b.svg`,
|
||||
},
|
||||
|
||||
defaultOption(newValue.value?.title, subscribeData.value.filter(d => d.device_coordinate).map(d => JSON.parse(d.device_coordinate)))
|
||||
);
|
||||
|
||||
}
|
||||
}, {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
floor
|
||||
<EffectScatter id="system_floor_chart" ref="asset_floor_chart" :class="twMerge(
|
||||
currentFloor?.key ? 'opacity-100' : 'opacity-0'
|
||||
)
|
||||
" />
|
||||
<!-- <div class="text-lg" v-if="!currentFloor?.key">尚未上傳樓層平面圖</div> -->
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped></style>
|
||||
<style lang='scss' scoped></style>
|
||||
|
@ -1,29 +1,8 @@
|
||||
<script setup>
|
||||
import { getSystemDevices } from "@/apis/system"
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
import { useRoute } from "vue-router";
|
||||
import { ref, watch } from "vue"
|
||||
import { inject } from "vue"
|
||||
|
||||
const store = useBuildingStore()
|
||||
const data = ref([])
|
||||
const route = useRoute()
|
||||
const { data } = inject("system_deviceList")
|
||||
|
||||
const getData = async () => {
|
||||
const res = await getSystemDevices({
|
||||
sub_system_tag: route.params.sub_system_id,
|
||||
building_tag: store.selectedBuilding?.building_tag,
|
||||
})
|
||||
data.value = res.data.map(d => ({ ...d, key: d.full_name }))
|
||||
}
|
||||
|
||||
watch([() => route, () => store.selectedBuilding], ([newRoute, newBuilding]) => {
|
||||
if (newBuilding && newRoute.params.sub_system_id) {
|
||||
getData()
|
||||
}
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
@ -31,31 +10,34 @@ watch([() => route, () => store.selectedBuilding], ([newRoute, newBuilding]) =>
|
||||
<template>
|
||||
<!-- <InfoModal :data="currentInfoModalData" /> -->
|
||||
|
||||
<div class="equipment-show" v-for="d in data" :key="d.full_name">
|
||||
<p class="title">{{ d.full_name }}</p>
|
||||
<div class="grid grid-cols-3 gap-5" v-if="d.device_list.length > 0">
|
||||
<div class="col-auto relative" v-for="device in d.device_list" :key="device.device_guid">
|
||||
<div class="item">
|
||||
<div class="left w-4/5">
|
||||
<div class="sec02">
|
||||
<img v-if="device.device_image_url" :src="device.device_image_url" alt="" class="w-8 h-8">
|
||||
<span class="w-8 h-8" v-else></span>
|
||||
<span>{{ device.full_name }}</span>
|
||||
</div>
|
||||
<div class="sec03">
|
||||
<span></span>
|
||||
<span>狀態:</span>
|
||||
<span></span>
|
||||
<template v-if="data.length > 0">
|
||||
<div class="equipment-show" v-for="d in data" :key="d.full_name">
|
||||
<template v-if="d.device_list.length > 0">
|
||||
<p class="title">{{ d.full_name }}</p>
|
||||
<div class="grid grid-cols-3 gap-5">
|
||||
<div class="col-auto relative" v-for="device in d.device_list" :key="device.device_guid">
|
||||
<div class="item">
|
||||
<div class="left w-4/5">
|
||||
<div class="sec02">
|
||||
<img v-if="device.device_image_url" :src="device.device_image_url" alt="" class="w-8 h-8">
|
||||
<span class="w-8 h-8" v-else></span>
|
||||
<span>{{ device.full_name }}</span>
|
||||
</div>
|
||||
<div class="sec03">
|
||||
<span></span>
|
||||
<span>狀態:</span>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style lang='css' scoped>
|
||||
|
@ -1,13 +1,16 @@
|
||||
<script setup>
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { getSystemFloors } from "@/apis/system"
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { onMounted, ref, watch, inject } from 'vue';
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
import useActiveBtn from "@/hooks/useActiveBtn"
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const store = useBuildingStore();
|
||||
|
||||
const { updateCurrentFloor } = inject("system_deviceList")
|
||||
|
||||
|
||||
const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
|
||||
const getFloors = async () => {
|
||||
@ -16,20 +19,51 @@ const getFloors = async () => {
|
||||
{
|
||||
title: "總覽",
|
||||
key: "main",
|
||||
active: true,
|
||||
active: route.params.floor_id ? false : true,
|
||||
},
|
||||
...res.data.map((d, idx) => ({
|
||||
title: d.floor_tag,
|
||||
key: d.floor_guid,
|
||||
active: false,
|
||||
active: route.params.floor_id === d.floor_guid,
|
||||
map_url: d.floor_map_name
|
||||
}))
|
||||
]);
|
||||
}
|
||||
|
||||
const onClick = (item) => {
|
||||
changeActiveBtn(item)
|
||||
updateCurrentFloor(item)
|
||||
if (item.key == "main") {
|
||||
router.push({
|
||||
name: 'sub_system', params: {
|
||||
...route.params
|
||||
}
|
||||
})
|
||||
} else {
|
||||
router.push({
|
||||
name: 'floor', params: {
|
||||
...route.params, floor_id: item.key
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
watch(selectedBtn, (newValue) => {
|
||||
console.log(newValue);
|
||||
})
|
||||
|
||||
watch(() => route.params.sub_system_id, (newValue, oldValue) => {
|
||||
console.log(newValue !== oldValue)
|
||||
if (newValue !== oldValue) {
|
||||
setItems(items.value.map((item, index) => ({ ...item, active: index === 0 })));
|
||||
}
|
||||
|
||||
}, {
|
||||
deep: true
|
||||
})
|
||||
|
||||
watch(() => store.selectedBuilding, (newValue) => {
|
||||
console.log(newValue)
|
||||
newValue && getFloors()
|
||||
@ -45,11 +79,8 @@ watch(() => store.selectedBuilding, (newValue) => {
|
||||
<template>
|
||||
<div class="flex items-center border border-info px-4 py-2">
|
||||
<h5 class="text-md font-extrabold me-4">{{ store.selectedSystem?.full_name }}</h5>
|
||||
<ButtonGroup :items="items" :withLine="false" className="btn-xs rounded-md"
|
||||
:onclick="(e, item) => changeActiveBtn(item)" />
|
||||
<ButtonGroup :items="items" :withLine="false" className="btn-xs rounded-md" :onclick="(e, item) => onClick(item)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
||||
</style>
|
||||
<style lang='scss' scoped></style>
|
34
src/views/system/components/SystemSubBar.vue
Normal file
34
src/views/system/components/SystemSubBar.vue
Normal file
@ -0,0 +1,34 @@
|
||||
<script setup>
|
||||
import useActiveBtn from "@/hooks/useActiveBtn"
|
||||
import { inject, watch } from "vue";
|
||||
import useBuildingStore from "@/stores/useBuildingStore"
|
||||
|
||||
const { updateDataByGas } = inject("system_deviceList")
|
||||
const buildingStore = useBuildingStore()
|
||||
const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
|
||||
|
||||
watch(() => buildingStore, (newValue) => {
|
||||
newValue.selectedSystem?.points?.length > 0 && setItems(newValue.selectedSystem.points.map(d => ({
|
||||
title: d.full_name,
|
||||
key: d.points,
|
||||
active: false,
|
||||
})))
|
||||
}, {
|
||||
deep: true,
|
||||
})
|
||||
|
||||
const onClick = (item) => {
|
||||
changeActiveBtn(item)
|
||||
updateDataByGas(item.key)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="items.length > 0">
|
||||
<ButtonGroup :items="items" :withLine="false" className="btn-xs rounded-md" :onclick="(e, item) => onClick(item)" />
|
||||
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped></style>
|
Loading…
Reference in New Issue
Block a user