平面圖操作更新|Info Modal

This commit is contained in:
JouChun 2024-10-20 22:14:28 -04:00
parent 0f12d98376
commit c38bf5d3b5
11 changed files with 178 additions and 121 deletions

View File

@ -21,6 +21,8 @@ let currentClickPosition = ref([]);
async function updateSvg(svg, option) { async function updateSvg(svg, option) {
if (!chart.value && dom.value && svg) { if (!chart.value && dom.value && svg) {
init(); init();
} else {
clear()
} }
axios.get(svg.path).then(({ data }) => { axios.get(svg.path).then(({ data }) => {
echarts.registerMap(svg.full_name, { svg: data }); echarts.registerMap(svg.full_name, { svg: data });
@ -44,6 +46,10 @@ async function updateSvg(svg, option) {
console.log("updateSvg", svg.path); console.log("updateSvg", svg.path);
} }
function clear() {
chart.value.clear()
}
function init() { function init() {
const curChart = echarts.init(dom.value); const curChart = echarts.init(dom.value);
chart.value = markRaw(curChart); chart.value = markRaw(curChart);
@ -62,7 +68,7 @@ defineExpose({
}); });
</script> </script>
<template> <template>
<div :id="id" class="min-h-[70vh] max-h-fit w-full bg-white" ref="dom"></div> <div :id="id" class="min-h-[70vh] max-h-fit w-full" ref="dom"></div>
</template> </template>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@ -1,6 +1,6 @@
<script setup> <script setup>
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { defineProps, ref, watch } from "vue"; import { computed, defineProps, onMounted, ref, watch } from "vue";
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
id名.showModal(): 開啟 modal id名.showModal(): 開啟 modal
@ -27,33 +27,34 @@ const props = defineProps({
default: {}, default: {},
}, },
}); });
const dom = ref(null)
onMounted(() => {
document.querySelector(`#${props.id}[open]`)?.addEventListener("load",()=>{
console.log("loading")
})
document.querySelector(`#${props.id}`).addEventListener("load",()=>{
console.log("loading")
})
})
</script> </script>
<template> <template>
<!-- Open the modal using ID.showModal() method --> <!-- Open the modal using ID.showModal() method -->
<!-- :class="twMerge('modal', open && innerOpen ? 'modal-open' : 'modal-close')" --> <!-- :class="twMerge('modal', open && innerOpen ? 'modal-open' : 'modal-close')" -->
<dialog <dialog ref="dom" :id="id" :class="twMerge(
:id="id" 'modal',
:class=" backdrop
twMerge( ? ''
'modal', : 'h-fit w-fit max-h-fit max-w-fit focus-visible:outline-none backdrop:bg-transparent',
backdrop )" :style="modalStyle" v-draggable="draggable">
? '' <div :class="twMerge(
: 'h-fit w-fit max-h-fit max-w-fit focus-visible:outline-none backdrop:bg-transparent' 'modal-box static rounded-md border border-info py-5 px-6 overflow-y-scroll bg-normal',
) modalClass
" )
:style="modalStyle" " :style="{ minWidth: isNaN(width) ? width : `${width}px` }">
v-draggable="draggable"
>
<div
:class="
twMerge(
'modal-box static rounded-md border border-info py-5 px-6 overflow-y-scroll bg-normal',
modalClass
)
"
:style="{ minWidth: isNaN(width) ? width : `${width}px` }"
>
<div class="text-2xl font-bold"> <div class="text-2xl font-bold">
<slot name="modalTitle"> <slot name="modalTitle">
{{ title }} {{ title }}
@ -68,13 +69,10 @@ const props = defineProps({
</div> </div>
</div> </div>
<form v-if="backdrop" method="dialog" class="modal-backdrop"> <form v-if="backdrop" method="dialog" class="modal-backdrop">
<button <button @click="() => {
@click=" onCancel ? onCancel() : cancel();
() => { }
onCancel ? onCancel() : cancel(); ">
}
"
>
close close
</button> </button>
</form> </form>
@ -88,7 +86,7 @@ const props = defineProps({
} }
.modal-action::after { .modal-action::after {
@apply absolute -bottom-3 -left-4 h-5 w-5 rotate-90 bg-no-repeat z-10 bg-[url('../../assets/img/table/content-box-background05.svg')] bg-center; @apply absolute -bottom-3 -left-4 h-5 w-5 rotate-90 bg-no-repeat z-10 bg-[url('../../assets/img/table/content-box-background05.svg')] bg-center;
content: ""; content: "";
} }
</style> </style>

View File

@ -163,7 +163,7 @@ const initForge = async () => {
viewer.isLoadDone() viewer.isLoadDone()
); );
// updateForgeViewer(viewer); // updateForgeViewer(viewer);
createSprites() // createSprites()
hideAllObjects(); hideAllObjects();
}) })

View File

@ -1,10 +1,10 @@
<script setup> <script setup>
import { RouterView, useRoute } from 'vue-router'; import { RouterView, useRoute } from 'vue-router';
import { computed, watch, provide, ref } from "vue"; import { computed, watch, provide, ref, onMounted } from "vue";
import SystemFloorBar from './components/SystemFloorBar.vue'; import SystemFloorBar from './components/SystemFloorBar.vue';
import useBuildingStore from "@/stores/useBuildingStore"; import useBuildingStore from "@/stores/useBuildingStore";
import ForgeForSystem from "@/components/forge/ForgeForSystem.vue"; import ForgeForSystem from "@/components/forge/ForgeForSystem.vue";
import { getSystemDevices } from "@/apis/system"; import { getSystemDevices, getSystemRealTime } from "@/apis/system";
import SystemSubBar from './components/SystemSubBar.vue'; import SystemSubBar from './components/SystemSubBar.vue';
import SystemInfoModal from './components/SystemInfoModal.vue'; import SystemInfoModal from './components/SystemInfoModal.vue';
@ -35,37 +35,28 @@ const getData = async () => {
sub_system_tag: route.params.sub_system_id, sub_system_tag: route.params.sub_system_id,
building_tag: buildingStore.selectedBuilding?.building_tag, building_tag: buildingStore.selectedBuilding?.building_tag,
}) })
const data = res.data.map(d => ({ const devices = res.data.map(d => ({
...d, key: d.full_name, device_list: d.device_list.map((d, index) => ({ ...d, key: d.full_name, device_list: d.device_list.map((dev, index) => ({
...d, ...dev,
forge_dbid: parseInt(d.forge_dbid), forge_dbid: parseInt(dev.forge_dbid),
device_coordinate_3d: d.device_coordinate_3d device_coordinate_3d: dev.device_coordinate_3d
? JSON.parse(d.device_coordinate_3d) ? JSON.parse(dev.device_coordinate_3d)
: { x: 0, y: 0 }, : { x: 0, y: 0 },
alarmMsg: "", alarmMsg: "",
is_show: true, is_show: true,
currentColor: d.device_normal_point_color, currentColor: dev.device_normal_point_color,
spriteDbId: 10 + index, spriteDbId: 10 + index,
sensorTypes: d.points.map(({ points }) => points), sensorTypes: dev.points.map(({ points }) => points),
points: d.points.map((p) => ({ ...p, value: "" })) floor_guid: d.floor_guid
})), })),
})); }));
raw_data.value = data
data.value = data raw_data.value = devices
data.value = devices
} }
const subscribeData = ref([]); // flat data const subscribeData = ref([]); // flat data
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) => { const getSubData = (value) => {
let items = []; let items = [];
value.forEach((device) => { value.forEach((device) => {
@ -74,37 +65,48 @@ const getSubData = (value) => {
...device.device_list ...device.device_list
]; ];
}); });
data.value = raw_data.value; return items
subscribeData.value = items;
} }
watch(raw_data, (newValue) => { watch(raw_data, (newValue) => {
updateDataByGas("all") if (route.query.gas) {
updateDataByGas(route.query.gas)
} else {
const items = getSubData(newValue)
data.value = newValue;
subscribeData.value = items;
}
}); });
watch(data, (newValue) => {
console.log(newValue);
})
const updateDataByGas = (gas) => { const updateDataByGas = (gas) => {
console.log(gas) console.log(`updateDataByGas`, gas)
if (gas === "all") { const update_values = raw_data.value.map((d) => (
getSubData(raw_data.value) {
} else {
data.value = raw_data.value.map((d) => ({
...d, ...d,
device_list: d.device_list.filter(({ points }) => points.some(({ points: p }) => p === gas)) device_list: d.device_list.filter(({ points }) => points.some(({ points: p }) => p === gas))
})) }
} ))
data.value = update_values
subscribeData.value = getSubData(update_values);
} }
watch([() => route, () => buildingStore.selectedBuilding], ([newRoute, newBuilding]) => { watch(() => buildingStore.selectedBuilding, (newBuilding) => {
if (newBuilding && newRoute.params.sub_system_id) { if (Boolean(newBuilding)) {
getData() getData()
} }
}, { }, {
deep: true, deep: true,
immediate: true, })
watch(route, (newRoute, oldRoute) => {
if (buildingStore.selectedBuilding && newRoute.params.sub_system_id !== oldRoute?.params.sub_system_id) {
getData()
}
})
onMounted(() => {
getData()
}) })
const currentFloor = ref(null); const currentFloor = ref(null);
@ -113,21 +115,36 @@ const updateCurrentFloor = (floor) => {
currentFloor.value = floor; currentFloor.value = floor;
} }
provide("system_deviceList", { data, subscribeData, currentFloor, updateCurrentFloor, updateDataByGas }) const realtimeData = ref([])
const timeId = ref(null)
const getAllDeviceRealtime = () => {
timeId.value = setInterval(async () => {
const res = await getSystemRealTime(subscribeData.value.map(d => d.device_number))
console.log(res.data)
realtimeData.value = res.data
}, 10000)
}
watch(subscribeData, (newValue) => {
timeId.value && clearInterval(timeId.value)
newValue.length > 0 && getAllDeviceRealtime()
}, { deep: true, immediate: true })
provide("system_deviceList", { data, subscribeData, currentFloor, updateCurrentFloor, updateDataByGas, realtimeData })
const selectedDevice = ref(null);
// const selectedDevice = computed(() => {
// return {
// ...currentInfo.value,
// points: realtimeData.value.find(({ device_number }) => device_number === currentInfo.value?.value.device_number)
// }
// });
// //
const currentInfoModalData = ref(null); const isMobile = (e) => {
const isMobile = (pointerType) => { return e.pointerType !== "mouse" && e.type !== "click"; // is mobile
// 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 getCurrentInfoModalData = (e, position, value) => {
const mobile = isMobile(e.pointerType); const mobile = isMobile(e);
selectedDevice.value = { selectedDevice.value = {
initPos: mobile initPos: mobile
? { left: `50%`, top: `50%` } ? { left: `50%`, top: `50%` }
@ -138,9 +155,13 @@ const getCurrentInfoModalData = (e, position, value) => {
document.getElementById('system_info_modal').showModal(); document.getElementById('system_info_modal').showModal();
}; };
const selectedDevice = ref(null); const selectedDeviceRealtime = computed(() => realtimeData.value?.find(({ device_number }) => device_number === selectedDevice.value?.value?.device_number)?.data)
provide("system_selectedDevice", { selectedDevice, getCurrentInfoModalData }) const clearSelectedDeviceInfo = () => {
selectedDevice.value.value = null
}
provide("system_selectedDevice", { selectedDeviceRealtime, selectedDevice, getCurrentInfoModalData, clearSelectedDeviceInfo })
</script> </script>
@ -169,7 +190,7 @@ provide("system_selectedDevice", { selectedDevice, getCurrentInfoModalData })
</div> </div>
<SystemSubBar class="mt-2 mb-4" /> <SystemSubBar class="mt-2 mb-4" />
</div> </div>
<div class="max-h-[75vh] pr-2 overflow-y-auto"> <div class="h-full max-h-[75vh] pr-2 overflow-y-auto relative">
<RouterView /> <RouterView />
</div> </div>
</div> </div>

View File

@ -1,11 +1,12 @@
<script setup> <script setup>
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import EffectScatter from "@/components/chart/EffectScatter.vue"; import EffectScatter from "@/components/chart/EffectScatter.vue";
import { inject, ref, watch } from 'vue'; import { computed, inject, ref, watch } from 'vue';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
const route = useRoute() const route = useRoute()
const { currentFloor, subscribeData } = inject("system_deviceList") const { currentFloor, subscribeData } = inject("system_deviceList")
const { getCurrentInfoModalData } = inject("system_selectedDevice")
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL; const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
const asset_floor_chart = ref(null); const asset_floor_chart = ref(null);
@ -33,17 +34,25 @@ const defaultOption = (map, data = []) => ({
}, },
}); });
watch([() => currentFloor, () => asset_floor_chart], ([newValue, newChart]) => { const selectedFloor = computed(() => currentFloor.value?.find(({ key }) => key == route.params.floor_id))
if (newValue.value && newChart.value) {
watch([selectedFloor, () => asset_floor_chart, subscribeData], ([newValue, newChart, newData]) => {
if (newValue && newChart.value) {
asset_floor_chart.value.updateSvg( asset_floor_chart.value.updateSvg(
{ {
full_name: newValue.value?.title, full_name: newValue?.title,
path: `${FILE_BASEURL}/${newValue.value.map_url}`, path: `${FILE_BASEURL}/${newValue.map_url}`,
}, },
defaultOption(newValue.value?.title, subscribeData.value.filter(d => d.device_coordinate).map(d => [d.device_coordinate.split(",")[0], d.device_coordinate.split(",")[1]])) defaultOption(newValue?.title, newData.filter(d => d.device_coordinate && d.floor_guid === route.params.floor_id).map(d => [...d.device_coordinate.split(","), d]))
); );
newChart.value.chart.on("click", function (params) {
console.log(params, params.data[2])
getCurrentInfoModalData(params.event, {
left: params.event.offsetX
, top: params.event.offsetY
}, params.data[2])
});
} }
}, { }, {
immediate: true, immediate: true,
@ -52,8 +61,9 @@ watch([() => currentFloor, () => asset_floor_chart], ([newValue, newChart]) => {
</script> </script>
<template> <template>
<EffectScatter id="system_floor_chart" ref="asset_floor_chart" :class="twMerge( <Loading class="absolute" />
currentFloor?.key ? 'opacity-100' : 'opacity-0' <EffectScatter id="system_floor_chart" ref="asset_floor_chart" :class="twMerge('absolute z-50',
selectedFloor?.key ? 'opacity-100 bg-white' : 'opacity-0'
) )
" /> " />
<!-- <div class="text-lg" v-if="!currentFloor?.key">尚未上傳樓層平面圖</div> --> <!-- <div class="text-lg" v-if="!currentFloor?.key">尚未上傳樓層平面圖</div> -->

View File

@ -3,7 +3,6 @@ import { inject } from "vue"
const { data } = inject("system_deviceList") const { data } = inject("system_deviceList")
const { getCurrentInfoModalData } = inject("system_selectedDevice") const { getCurrentInfoModalData } = inject("system_selectedDevice")
</script> </script>

View File

@ -11,13 +11,11 @@ const store = useBuildingStore();
const { updateCurrentFloor } = inject("system_deviceList") const { updateCurrentFloor } = inject("system_deviceList")
const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn(); const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
const getFloors = async () => { const getFloors = async () => {
const res = await getAssetFloorList() const res = await getAssetFloorList()
let data = res.data.find(d => d.building_tag === store.selectedBuilding?.building_tag) let data = res.data.find(d => d.building_tag === store.selectedBuilding?.building_tag)
console.log(data) data = [
setItems([
{ {
title: "總覽", title: "總覽",
key: "main", key: "main",
@ -29,35 +27,33 @@ const getFloors = async () => {
active: route.params.floor_id === d.floor_guid, active: route.params.floor_id === d.floor_guid,
map_url: d.floor_map_url + ".svg" map_url: d.floor_map_url + ".svg"
})) }))
]); ]
setItems(data);
updateCurrentFloor(data)
} }
const onClick = (item) => { const onClick = (item) => {
changeActiveBtn(item) changeActiveBtn(item)
updateCurrentFloor(item)
if (item.key == "main") { if (item.key == "main") {
router.push({ router.push({
name: 'sub_system', params: { name: 'sub_system', params: {
...route.params ...route.params
} }, query: { gas: route.query.gas }
}) })
} else { } else {
router.push({ router.push({
name: 'floor', params: { name: 'floor', params: {
...route.params, floor_id: item.key ...route.params, floor_id: item.key
} }, query: { gas: route.query.gas }
}) })
} }
} }
watch(selectedBtn, (newValue) => {
console.log(newValue);
})
watch(() => route.params.sub_system_id, (newValue, oldValue) => { watch(() => route.params.sub_system_id, (newValue, oldValue) => {
console.log(newValue !== oldValue)
if (newValue !== oldValue) { if (newValue !== oldValue) {
setItems(items.value.map((item, index) => ({ ...item, active: index === 0 }))); setItems(items.value.map((item, index) => ({ ...item, active: index === 0 })));
} }
@ -67,7 +63,6 @@ watch(() => route.params.sub_system_id, (newValue, oldValue) => {
}) })
watch(() => store.selectedBuilding, (newValue) => { watch(() => store.selectedBuilding, (newValue) => {
console.log(newValue)
newValue && getFloors() newValue && getFloors()
}, { }, {
deep: true, deep: true,

View File

@ -8,14 +8,27 @@ const { selectedDevice: data } = inject("system_selectedDevice")
const position = ref({ const position = ref({
left: "0px", left: "0px",
top: "0px", top: "0px",
display: "none",
}); });
watch( watch(
() => data, data,
(newValue) => { (newValue) => {
console.log(newValue); console.log(newValue.value)
position.value = newValue.initPos; if (!newValue.value) {
} position.value = {
display: "none"
};
} else {
position.value = {
...data.value.initPos,
display: "block"
};
}
}, {
deep: true,
}
); );
</script> </script>

View File

@ -5,7 +5,7 @@ import SystemInfoModalCog from "./SystemInfoModalCog.vue";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
const { selectedDevice: data } = inject("system_selectedDevice") const { selectedDevice: data, clearSelectedDeviceInfo } = inject("system_selectedDevice")
const currentTab = ref("desktop"); const currentTab = ref("desktop");
@ -21,13 +21,14 @@ const changeOpenKey = (key) => {
const onCancel = () => { const onCancel = () => {
currentTab.value = "desktop"; currentTab.value = "desktop";
document.getElementById('system_info_modal').close(); document.getElementById('system_info_modal').close();
clearSelectedDeviceInfo();
}; };
</script> </script>
<template> <template>
<div class="card bg-transparent text-white"> <div class="card bg-transparent text-white">
<div class="card-title py-2 border-b border-zinc-700 justify-between"> <div class="card-title py-2 border-b border-zinc-700 justify-between">
<h3>{{ data?.value.full_name }}</h3> <h3>{{ data?.value?.full_name }}</h3>
<div> <div>
<Button type="link" class="btn-link btn-text-without-border px-2" @click="() => changeOpenKey('desktop')"> <Button type="link" class="btn-link btn-text-without-border px-2" @click="() => changeOpenKey('desktop')">
<font-awesome-icon :icon="['fas', 'desktop']" size="lg" <font-awesome-icon :icon="['fas', 'desktop']" size="lg"
@ -65,7 +66,7 @@ const onCancel = () => {
</div> </div>
</div> </div>
<div class="card-body px-0"> <div class="card-body px-0">
<component :is="tabs[currentTab]" :data="[]"></component> <component :is="tabs[currentTab]"></component>
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,8 +1,15 @@
<script setup> <script setup>
import { inject } from "vue"; import { computed, inject } from "vue";
import { useRoute } from "vue-router";
const { selectedDevice } = inject("system_selectedDevice"); const { selectedDevice, selectedDeviceRealtime } = inject("system_selectedDevice");
const data = computed(() => {
return selectedDeviceRealtime?.value && selectedDevice.value?.value.points.map((d) => ({
...d,
value: selectedDeviceRealtime?.value?.find(({ point }) => point === d.points)?.value || "無資料"
}))
})
const columns = [{ const columns = [{
title: "屬性", title: "屬性",
@ -16,7 +23,8 @@ const columns = [{
</script> </script>
<template> <template>
<Table :loading="loading" :columns="columns" :dataSource="[]" :withStyle="false"></Table> <Table :loading="loading" :columns="columns" :dataSource="data || []" :withStyle="false">
</Table>
</template> </template>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@ -2,23 +2,29 @@
import useActiveBtn from "@/hooks/useActiveBtn" import useActiveBtn from "@/hooks/useActiveBtn"
import { inject, watch } from "vue"; import { inject, watch } from "vue";
import useBuildingStore from "@/stores/useBuildingStore" import useBuildingStore from "@/stores/useBuildingStore"
import useSearchParam from "@/hooks/useSearchParam"
import { useRoute } from "vue-router";
const { updateDataByGas } = inject("system_deviceList") const { updateDataByGas } = inject("system_deviceList")
const buildingStore = useBuildingStore() const buildingStore = useBuildingStore()
const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn(); const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
const route = useRoute()
watch(() => buildingStore, (newValue) => { watch(() => buildingStore, (newValue) => {
newValue.selectedSystem?.points?.length > 0 && setItems(newValue.selectedSystem.points.map(d => ({ newValue.selectedSystem?.points?.length > 0 && setItems(newValue.selectedSystem.points.map(d => ({
title: d.full_name, title: d.full_name,
key: d.points, key: d.points,
active: d.points === "Temp", active: route.query.gas ? route.query.gas === d.points : d.points === "Temp",
}))) })))
}, { }, {
deep: true, deep: true,
immediate: true immediate: true
}) })
const { changeParams } = useSearchParam();
const onClick = (item) => { const onClick = (item) => {
changeParams({ gas: item.key })
changeActiveBtn(item) changeActiveBtn(item)
updateDataByGas(item.key) updateDataByGas(item.key)
} }