Merge branch 'feature/system'

This commit is contained in:
koko 2024-10-21 10:33:13 +08:00
commit dbe0be5cf6
11 changed files with 178 additions and 121 deletions

View File

@ -21,6 +21,8 @@ let currentClickPosition = ref([]);
async function updateSvg(svg, option) {
if (!chart.value && dom.value && svg) {
init();
} else {
clear()
}
axios.get(svg.path).then(({ data }) => {
echarts.registerMap(svg.full_name, { svg: data });
@ -44,6 +46,10 @@ async function updateSvg(svg, option) {
console.log("updateSvg", svg.path);
}
function clear() {
chart.value.clear()
}
function init() {
const curChart = echarts.init(dom.value);
chart.value = markRaw(curChart);
@ -62,7 +68,7 @@ defineExpose({
});
</script>
<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>
<style lang="scss" scoped></style>

View File

@ -1,6 +1,6 @@
<script setup>
import { twMerge } from "tailwind-merge";
import { defineProps, ref, watch } from "vue";
import { computed, defineProps, onMounted, ref, watch } from "vue";
/* ----------------------------------------------------------------
id名.showModal(): 開啟 modal
@ -27,33 +27,34 @@ const props = defineProps({
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>
<template>
<!-- Open the modal using ID.showModal() method -->
<!-- :class="twMerge('modal', open && innerOpen ? 'modal-open' : 'modal-close')" -->
<dialog
:id="id"
:class="
twMerge(
'modal',
backdrop
? ''
: 'h-fit w-fit max-h-fit max-w-fit focus-visible:outline-none backdrop:bg-transparent'
)
"
:style="modalStyle"
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` }"
>
<dialog ref="dom" :id="id" :class="twMerge(
'modal',
backdrop
? ''
: 'h-fit w-fit max-h-fit max-w-fit focus-visible:outline-none backdrop:bg-transparent',
)" :style="modalStyle" 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">
<slot name="modalTitle">
{{ title }}
@ -68,13 +69,10 @@ const props = defineProps({
</div>
</div>
<form v-if="backdrop" method="dialog" class="modal-backdrop">
<button
@click="
() => {
onCancel ? onCancel() : cancel();
}
"
>
<button @click="() => {
onCancel ? onCancel() : cancel();
}
">
close
</button>
</form>
@ -88,7 +86,7 @@ const props = defineProps({
}
.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: "";
}
</style>

View File

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

View File

@ -1,10 +1,10 @@
<script setup>
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 useBuildingStore from "@/stores/useBuildingStore";
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 SystemInfoModal from './components/SystemInfoModal.vue';
@ -35,37 +35,28 @@ 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, 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)
const devices = res.data.map(d => ({
...d, key: d.full_name, device_list: d.device_list.map((dev, index) => ({
...dev,
forge_dbid: parseInt(dev.forge_dbid),
device_coordinate_3d: dev.device_coordinate_3d
? JSON.parse(dev.device_coordinate_3d)
: { x: 0, y: 0 },
alarmMsg: "",
is_show: true,
currentColor: d.device_normal_point_color,
currentColor: dev.device_normal_point_color,
spriteDbId: 10 + index,
sensorTypes: d.points.map(({ points }) => points),
points: d.points.map((p) => ({ ...p, value: "" }))
sensorTypes: dev.points.map(({ points }) => points),
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 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) => {
@ -74,37 +65,48 @@ const getSubData = (value) => {
...device.device_list
];
});
data.value = raw_data.value;
subscribeData.value = items;
return items
}
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) => {
console.log(gas)
if (gas === "all") {
getSubData(raw_data.value)
} else {
data.value = raw_data.value.map((d) => ({
console.log(`updateDataByGas`, gas)
const update_values = raw_data.value.map((d) => (
{
...d,
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]) => {
if (newBuilding && newRoute.params.sub_system_id) {
watch(() => buildingStore.selectedBuilding, (newBuilding) => {
if (Boolean(newBuilding)) {
getData()
}
}, {
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);
@ -113,21 +115,36 @@ const updateCurrentFloor = (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 = (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 isMobile = (e) => {
return e.pointerType !== "mouse" && e.type !== "click"; // is mobile
};
const getCurrentInfoModalData = (e, position, value) => {
const mobile = isMobile(e.pointerType);
const mobile = isMobile(e);
selectedDevice.value = {
initPos: mobile
? { left: `50%`, top: `50%` }
@ -138,9 +155,13 @@ const getCurrentInfoModalData = (e, position, value) => {
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>
@ -169,7 +190,7 @@ provide("system_selectedDevice", { selectedDevice, getCurrentInfoModalData })
</div>
<SystemSubBar class="mt-2 mb-4" />
</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 />
</div>
</div>

View File

@ -1,11 +1,12 @@
<script setup>
import { useRoute } from 'vue-router';
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';
const route = useRoute()
const { currentFloor, subscribeData } = inject("system_deviceList")
const { getCurrentInfoModalData } = inject("system_selectedDevice")
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
const asset_floor_chart = ref(null);
@ -33,17 +34,25 @@ const defaultOption = (map, data = []) => ({
},
});
watch([() => currentFloor, () => asset_floor_chart], ([newValue, newChart]) => {
if (newValue.value && newChart.value) {
const selectedFloor = computed(() => currentFloor.value?.find(({ key }) => key == route.params.floor_id))
watch([selectedFloor, () => asset_floor_chart, subscribeData], ([newValue, newChart, newData]) => {
if (newValue && newChart.value) {
asset_floor_chart.value.updateSvg(
{
full_name: newValue.value?.title,
path: `${FILE_BASEURL}/${newValue.value.map_url}`,
full_name: newValue?.title,
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,
@ -52,8 +61,9 @@ watch([() => currentFloor, () => asset_floor_chart], ([newValue, newChart]) => {
</script>
<template>
<EffectScatter id="system_floor_chart" ref="asset_floor_chart" :class="twMerge(
currentFloor?.key ? 'opacity-100' : 'opacity-0'
<Loading class="absolute" />
<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> -->

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@ import SystemInfoModalCog from "./SystemInfoModalCog.vue";
import { twMerge } from "tailwind-merge";
const { selectedDevice: data } = inject("system_selectedDevice")
const { selectedDevice: data, clearSelectedDeviceInfo } = inject("system_selectedDevice")
const currentTab = ref("desktop");
@ -21,13 +21,14 @@ const changeOpenKey = (key) => {
const onCancel = () => {
currentTab.value = "desktop";
document.getElementById('system_info_modal').close();
clearSelectedDeviceInfo();
};
</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>
<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"
@ -65,7 +66,7 @@ const onCancel = () => {
</div>
</div>
<div class="card-body px-0">
<component :is="tabs[currentTab]" :data="[]"></component>
<component :is="tabs[currentTab]"></component>
</div>
</div>
</template>

View File

@ -1,8 +1,15 @@
<script setup>
import { inject } from "vue";
import { useRoute } from "vue-router";
import { computed, inject } from "vue";
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 = [{
title: "屬性",
@ -16,7 +23,8 @@ const columns = [{
</script>
<template>
<Table :loading="loading" :columns="columns" :dataSource="[]" :withStyle="false"></Table>
<Table :loading="loading" :columns="columns" :dataSource="data || []" :withStyle="false">
</Table>
</template>
<style lang="scss" scoped></style>

View File

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