系統監控小視窗的cog、img | 設備管理連結圖資管理的圖片 | 樓層新增、編輯、刪除

This commit is contained in:
koko 2024-11-01 16:56:31 +08:00
parent 1dbd58b965
commit 12d171dc51
10 changed files with 274 additions and 90 deletions

View File

@ -10,7 +10,7 @@ const props = defineProps({
svg: Object, svg: Object,
getCoordinate: { getCoordinate: {
type: Function, type: Function,
default: null default: null,
}, },
}); });
@ -22,7 +22,7 @@ async function updateSvg(svg, option) {
if (!chart.value && dom.value && svg) { if (!chart.value && dom.value && svg) {
init(); init();
} else { } else {
clear() 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 });
@ -30,24 +30,33 @@ async function updateSvg(svg, option) {
if (props.getCoordinate) { if (props.getCoordinate) {
chart.value.getZr().on("click", function (params) { chart.value.getZr().on("click", function (params) {
var pixelPoint = [params.offsetX, params.offsetY]; var pixelPoint = [params.offsetX, params.offsetY];
var dataPoint = chart.value.convertFromPixel({ geoIndex: 0 }, pixelPoint); var dataPoint = chart.value.convertFromPixel(
{ geoIndex: 0 },
pixelPoint
);
currentClickPosition.value = dataPoint; currentClickPosition.value = dataPoint;
props.getCoordinate(dataPoint); props.getCoordinate(dataPoint);
const updatedData = option.series.data
.filter(
(point) => !(point.itemStyle && point.itemStyle.color === "#0000FF")
)
.concat({
value: dataPoint, //
itemStyle: { color: "#0000FF" }, //
});
chart.value.setOption({ chart.value.setOption({
series: { series: {
data: [dataPoint], data: updatedData,
}, },
}); });
}); });
} }
}); });
console.log("updateSvg", svg.path); console.log("updateSvg", svg.path);
} }
function clear() { function clear() {
chart.value.clear() chart.value.clear();
} }
function init() { function init() {

View File

@ -21,7 +21,7 @@ const props = defineProps({
/> />
</a> </a>
<!-- 項目有子層級 --> <!-- 項目有子層級 -->
<details v-else> <details v-else open>
<summary>{{ item.name }}</summary> <summary>{{ item.name }}</summary>
<ul> <ul>
<li v-for="(child, index) in item.children" :key="index"> <li v-for="(child, index) in item.children" :key="index">

View File

@ -123,7 +123,7 @@ const pageInput = computed(() => {
<span <span
v-if="totalPage < 6" v-if="totalPage < 6"
class="absolute -bottom-8 -translate-x-1/2 text-base text-center" class="absolute -bottom-8 text-base text-center"
> >
{{ dataSource.length }} {{ $t("table.in_otal") }}</span {{ dataSource.length }} {{ $t("table.in_otal") }}</span
> >

View File

@ -35,7 +35,7 @@ export default function useForgeSprite() {
console.log("onSpriteClicked", event.target); console.log("onSpriteClicked", event.target);
console.log("onSpriteClicked", data); console.log("onSpriteClicked", data);
// modalContent.value = data; // modalContent.value = data;
debugger; // debugger;
if (data) { if (data) {
getCurrentInfoModalData( getCurrentInfoModalData(
event, event,

View File

@ -1,6 +1,6 @@
<script setup> <script setup>
import { getAssetList, getAssetSingle, deleteAssetItem } from "@/apis/asset"; import { getAssetList, getAssetSingle, deleteAssetItem } from "@/apis/asset";
import { onMounted, ref, watch, inject, computed } from "vue"; import { onMounted, ref, watch, inject, provide, computed } from "vue";
import useSearchParam from "@/hooks/useSearchParam"; import useSearchParam from "@/hooks/useSearchParam";
import AssetTableAddModal from "./AssetTableAddModal.vue"; import AssetTableAddModal from "./AssetTableAddModal.vue";
import { getOperationCompanyList } from "@/apis/operation"; import { getOperationCompanyList } from "@/apis/operation";
@ -20,6 +20,7 @@ const getCompany = async () => {
}; };
const floors = ref([]); const floors = ref([]);
const totalCoordinates = ref({});
const getFloors = async () => { const getFloors = async () => {
const res = await getAssetFloorList(); const res = await getAssetFloorList();
floors.value = res.data[0]?.floors.map((d) => ({ ...d, key: d.floor_guid })); floors.value = res.data[0]?.floors.map((d) => ({ ...d, key: d.floor_guid }));
@ -27,8 +28,18 @@ const getFloors = async () => {
const tableData = ref([]); const tableData = ref([]);
const getAssetData = async () => { const getAssetData = async () => {
totalCoordinates.value = {}; // totalCoordinates
const res = await getAssetList(searchParams.value?.subSys_id); const res = await getAssetList(searchParams.value?.subSys_id);
if (res.isSuccess) { if (res.isSuccess) {
// device_coordinate
res.data.forEach(d => {
const floorGuid = d.floor_guid;
if (!totalCoordinates.value[floorGuid]) {
totalCoordinates.value[floorGuid] = [];
}
totalCoordinates.value[floorGuid].push(d.device_coordinate);
});
tableData.value = res.data.map((d) => ({ tableData.value = res.data.map((d) => ({
...d, ...d,
key: d.id, key: d.id,
@ -90,10 +101,10 @@ const columns = computed(() => [
key: "buying_date", key: "buying_date",
sort: true, sort: true,
}, },
{ // {
title: t("assetManagement.oriFile"), // title: t("assetManagement.oriFile"),
key: "oriFile", // key: "oriFile",
}, // },
{ {
title: t("assetManagement.created_at"), title: t("assetManagement.created_at"),
key: "created_at", key: "created_at",
@ -158,6 +169,10 @@ const remove = async (id) => {
getAssetData(); getAssetData();
} }
}; };
provide("asset_table_data", {
totalCoordinates,
});
</script> </script>
<template> <template>

View File

@ -4,6 +4,7 @@ import { ref, computed, inject, watch, onMounted } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import Menu from "@/components/customUI/Menu.vue"; import Menu from "@/components/customUI/Menu.vue";
const { t } = useI18n(); const { t } = useI18n();
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
const { formState } = inject("asset_table_modal_form"); const { formState } = inject("asset_table_modal_form");
const columns = computed(() => [ const columns = computed(() => [
{ {
@ -62,12 +63,12 @@ const openModal = () => {
}; };
const onOk = () => { const onOk = () => {
console.log("selectedId", selectedId.value); formState.value.graph_tree_id = selectedId.value;
formState.value.graph = selectedId.value;
onCancel(); onCancel();
}; };
const onCancel = () => { const onCancel = () => {
selectedId.value = null;
asset_add_graph_item.close(); asset_add_graph_item.close();
}; };
@ -76,9 +77,28 @@ const menuCheck = (id) => {
}; };
watch(selectedId, (newId) => { watch(selectedId, (newId) => {
getData(newId); if (newId) {
getData(newId);
} else {
getData(formState.value.graph_tree_id);
}
}); });
watch(
formState,
(newValue) => {
if (newValue?.graph_tree_id) {
selectedId.value = newValue?.graph_tree_id;
} else {
selectedId.value = null;
dataSource.value = [];
}
},
{
immediate: true,
}
);
onMounted(async () => { onMounted(async () => {
await getMenuData(); await getMenuData();
menuData.value = buildMenuTree(graphData.value); menuData.value = buildMenuTree(graphData.value);
@ -88,22 +108,29 @@ onMounted(async () => {
<template> <template>
<div class="flex flex-col mt-8 col-span-2"> <div class="flex flex-col mt-8 col-span-2">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<span class="label-text-alt text-lg"> 圖片 </span> <span class="label-text-alt text-lg">{{
$t("graphManagement.title")
}}</span>
<button <button
type="button" type="button"
class="btn btn-sm btn-success" class="btn btn-sm btn-success"
@click.stop.prevent="openModal" @click.stop.prevent="openModal"
> >
圖資管理 {{ $t("assetManagement.choose") }}
</button> </button>
</div> </div>
<Table <Table :columns="columns" :dataSource="dataSource" :withStyle="false">
:columns="columns"
:dataSource="dataSource"
:withStyle="false"
>
<template #bodyCell="{ record, column, index }"> <template #bodyCell="{ record, column, index }">
<template v-if="column.key === 'index'">{{ index + 1 }}</template> <template v-if="column.key === 'index'">{{ index + 1 }}</template>
<template v-else-if="column.key === 'oriOrgName'">
<a
:href="`${FILE_BASEURL}/upload/graph_manage/${record.oriSavName}`"
target="_blank"
class="btn-link no-underline hover:no-underline :focus:outline-0"
>
{{ record.oriOrgName }}
</a>
</template>
</template> </template>
</Table> </Table>
</div> </div>

View File

@ -7,16 +7,18 @@ import {
deleteAssetFloor, deleteAssetFloor,
} from "@/apis/asset"; } from "@/apis/asset";
import useFormErrorMessage from "@/hooks/useFormErrorMessage"; import useFormErrorMessage from "@/hooks/useFormErrorMessage";
import useBuildingStore from "@/stores/useBuildingStore";
import * as yup from "yup"; import * as yup from "yup";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const { t } = useI18n(); const { t } = useI18n();
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL; const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
const { totalCoordinates } = inject("asset_table_data");
const { updateRightFields, formErrorMsg, formState } = inject( const { updateRightFields, formErrorMsg, formState } = inject(
"asset_table_modal_form" "asset_table_modal_form"
); );
const store = useBuildingStore();
let schema = { let schema = {
floor_guid: yup.string().nullable(true), floor_guid: yup.string().nullable(true),
device_coordinate: yup.string().nullable(true), device_coordinate: yup.string().nullable(true),
@ -33,41 +35,54 @@ const asset_floor_chart = ref(null);
const currentFloor = ref(null); const currentFloor = ref(null);
const selectedOption = ref("add"); const selectedOption = ref("add");
const defaultOption = (map, data = []) => ({ const defaultOption = (map, data = []) => {
tooltip: {}, //
geo: { const formattedData = data.map((coordinate) => {
tooltip: { const coordString = JSON.stringify(coordinate);
show: false, return {
name: coordString,
value: coordinate,
itemStyle: {
color: coordString === formState.value.device_coordinate ? "#0000FF" : "#b02a02",
},
};
});
return {
tooltip: {},
geo: {
tooltip: {
show: false,
},
map,
roam: true,
}, },
map, series: {
roam: true, // type: "effectScatter",
}, coordinateSystem: "geo",
series: { geoIndex: 0,
type: "effectScatter", symbolSize: 10,
coordinateSystem: "geo", encode: {
geoIndex: 0, tooltip: 2,
symbolSize: 10, },
itemStyle: { data: formattedData, // 使
color: "#b02a02",
}, },
encode: { };
tooltip: 2, };
},
data,
},
});
watch(currentFloor, (newValue) => { watch(currentFloor, (newValue) => {
if (newValue?.floor_map_name) { if (newValue?.floor_map_name) {
const coordinates = totalCoordinates.value[newValue.floor_guid] || [];
const parsedCoordinates = coordinates.map((coord) => {
return JSON.parse(coord);
});
asset_floor_chart.value.updateSvg( asset_floor_chart.value.updateSvg(
{ {
full_name: newValue?.floor_map_name, full_name: newValue?.floor_map_name,
path: `${FILE_BASEURL}/${newValue?.floor_map_url}.svg`, path: `${FILE_BASEURL}/${newValue?.floor_map_url}.svg`,
}, },
defaultOption(newValue?.floor_map_name, [ defaultOption(newValue?.floor_map_name, parsedCoordinates)
formState.value.device_coordinate
? JSON.parse(formState.value.device_coordinate)
: [],
])
); );
} }
}); });
@ -107,7 +122,6 @@ onMounted(() => {
const openModal = () => { const openModal = () => {
if (selectedOption.value === "add") { if (selectedOption.value === "add") {
FloorFormState.value = { FloorFormState.value = {
building_tag: "F3",
full_name: "", full_name: "",
floorFile: [], floorFile: [],
}; };
@ -118,7 +132,6 @@ const openModal = () => {
if (floor) { if (floor) {
console.log("floor", floor); console.log("floor", floor);
FloorFormState.value = { FloorFormState.value = {
building_tag: "F3",
full_name: floor.full_name, full_name: floor.full_name,
floorFile: [], floorFile: [],
}; };
@ -129,19 +142,17 @@ const openModal = () => {
const form = ref(null); const form = ref(null);
const FloorFormState = ref({ const FloorFormState = ref({
building_tag: "F3",
full_name: "", full_name: "",
floorFile: [], floorFile: [],
}); });
const floorScheme = yup.object({ const floorScheme = yup.object({
building_tag: yup.string().required(t("button.required")),
full_name: yup.string().required(t("button.required")), full_name: yup.string().required(t("button.required")),
floorFile: yup.array(), floorFile: yup.array(),
}); });
const updateFileList = (files) => { const updateFileList = (files) => {
console.log("file",files) console.log("file", files);
FloorFormState.value.floorFile = files; FloorFormState.value.floorFile = files;
}; };
@ -153,9 +164,9 @@ const {
} = useFormErrorMessage(floorScheme); } = useFormErrorMessage(floorScheme);
const onOk = async () => { const onOk = async () => {
const value = handleSubmit(floorScheme, FloorFormState.value); const value = handleSubmit(floorScheme, FloorFormState.value);
const formData = new FormData(form.value); const formData = new FormData(form.value);
formData.append("floor_guid", ""); formData.append("floor_guid", currentFloor.value.floor_guid);
formData.append("building_tag", store.selectedBuilding.building_tag);
formData.append("initMapName", FloorFormState.value.floorFile[0].name); formData.append("initMapName", FloorFormState.value.floorFile[0].name);
formData.append("mapFile", FloorFormState.value.floorFile[0]); formData.append("mapFile", FloorFormState.value.floorFile[0]);
formData.delete("floorFile"); formData.delete("floorFile");
@ -171,23 +182,22 @@ const onOk = async () => {
}; };
const onDelete = async () => { const onDelete = async () => {
console.log("guild", formState.value.floor_guid); console.log("guild", formState.value.floor_guid);
// const res = await deleteAssetFloor({ const res = await deleteAssetFloor({
// floor_guid: formState.value.floor_guid, floor_guid: formState.value.floor_guid,
// }); });
// if (res.isSuccess) { if (res.isSuccess) {
// getFloors(); // getFloors();
// formState.value.floor_guid = ""; // }
// }
}; };
const onCancel = () => { const onCancel = () => {
FloorFormState.value = { FloorFormState.value = {
building_tag: "F3",
full_name: "", full_name: "",
floorFile: [], floorFile: [],
}; };
asset_add_floor.close(); asset_add_floor.close();
}; };
</script> </script>
<template> <template>

View File

@ -5,12 +5,15 @@ 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, getSystemRealTime } from "@/apis/system"; import { getSystemDevices, getSystemRealTime } from "@/apis/system";
import { getOperationCompanyList } from "@/apis/operation";
import { getAssetSingle, getAssetFloorList } from "@/apis/asset";
import SystemSubBar from './components/SystemSubBar.vue'; import SystemSubBar from './components/SystemSubBar.vue';
import SystemInfoModal from './components/SystemInfoModal.vue'; import SystemInfoModal from './components/SystemInfoModal.vue';
import SystemMain from "./SystemMain.vue"; import SystemMain from "./SystemMain.vue";
import SystemMode from './components/SystemMode.vue'; import SystemMode from './components/SystemMode.vue';
import SystemFloor from './SystemFloor.vue'; import SystemFloor from './SystemFloor.vue';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import dayjs from "dayjs";
const buildingStore = useBuildingStore() const buildingStore = useBuildingStore()
@ -30,9 +33,21 @@ const statusList = computed(() => {
return null return null
}) })
const raw_data = ref([]) const raw_data = ref([]);
const data = ref([]) // filter data const data = ref([]); // filter data
const route = useRoute() const route = useRoute();
const floors = ref([]);
const companyOptions = ref([]);
const getFloors = async () => {
const res = await getAssetFloorList();
floors.value = res.data[0]?.floors.map((d) => ({ ...d, key: d.floor_guid }));
};
const getCompany = async () => {
const res = await getOperationCompanyList();
companyOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
};
const getData = async () => { const getData = async () => {
if (!route.params.sub_system_id) return if (!route.params.sub_system_id) return
@ -91,7 +106,7 @@ const updateDataByGas = (gas) => {
} else { } else {
update_values = raw_data.value.map((d) => ( update_values = 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))
} }
)) ))
@ -101,23 +116,24 @@ const updateDataByGas = (gas) => {
} }
watch(() => buildingStore.selectedBuilding, (newBuilding) => { watch(() => buildingStore.selectedBuilding, (newBuilding) => {
if (Boolean(newBuilding)) { if (Boolean(newBuilding)) {
getData() getData()
} }
}, { }, {
deep: true, deep: true,
}) })
watch(() => route.params.sub_system_id, (newRoute, oldRoute) => { watch(() => route.params.sub_system_id, (newRoute, oldRoute) => {
if (buildingStore.selectedBuilding && newRoute !== oldRoute) { if (buildingStore.selectedBuilding && newRoute !== oldRoute) {
getData() getData()
} }
}) })
onMounted(() => { onMounted(() => {
getData() getFloors();
getCompany();
}) getData();
});
const currentFloor = ref(null); const currentFloor = ref(null);
@ -143,6 +159,7 @@ watch(subscribeData, (newValue) => {
provide("system_deviceList", { data, subscribeData, currentFloor, updateCurrentFloor, updateDataByGas, realtimeData }) provide("system_deviceList", { data, subscribeData, currentFloor, updateCurrentFloor, updateDataByGas, realtimeData })
const selectedDevice = ref(null); const selectedDevice = ref(null);
const selectedDeviceCog = ref({});
// const selectedDevice = computed(() => { // const selectedDevice = computed(() => {
// return { // return {
// ...currentInfo.value, // ...currentInfo.value,
@ -153,7 +170,29 @@ const selectedDevice = ref(null);
const isMobile = (e) => { const isMobile = (e) => {
return e.pointerType !== "mouse" && e.type !== "click"; // is mobile return e.pointerType !== "mouse" && e.type !== "click"; // is mobile
}; };
const getCurrentInfoModalData = (e, position, value) => {
const getCurrentInfoModalData = async (e, position, value) => {
if (value.main_id) {
const res = await getAssetSingle(value.main_id);
if (res.isSuccess) {
selectedDeviceCog.value = {
...res.data,
key: res.data.id,
floor: floors.value.find(
({ floor_guid }) => res.data.floor_guid === floor_guid
)?.full_name,
company: companyOptions.value.find(
({ id }) => res.data.operation_id === id
)?.name,
buying_date: res.data?.buying_date
? dayjs(res.data.buying_date).format("YYYY-MM-DD")
: "",
created_at: res.data?.created_at
? dayjs(res.data.created_at).format("YYYY-MM-DD")
: "",
};
}
}
const mobile = isMobile(e); const mobile = isMobile(e);
selectedDevice.value = { selectedDevice.value = {
initPos: mobile initPos: mobile
@ -161,7 +200,7 @@ const getCurrentInfoModalData = (e, position, value) => {
: { left: `${position.left}px`, top: `${position.top}px` }, : { left: `${position.left}px`, top: `${position.top}px` },
value, value,
isMobile: mobile, isMobile: mobile,
};; };
document.getElementById('system_info_modal').showModal(); document.getElementById('system_info_modal').showModal();
}; };
@ -172,7 +211,7 @@ const clearSelectedDeviceInfo = () => {
selectedDevice.value.value = null; selectedDevice.value.value = null;
} }
provide("system_selectedDevice", { selectedDeviceRealtime, selectedDevice, getCurrentInfoModalData, clearSelectedDeviceInfo }) provide("system_selectedDevice", { selectedDeviceRealtime, selectedDevice, getCurrentInfoModalData, clearSelectedDeviceInfo, selectedDeviceCog, })
</script> </script>

View File

@ -1,5 +1,71 @@
<script setup></script> <script setup>
import { computed, inject, watch, ref } from "vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { selectedDeviceCog } = inject("system_selectedDevice");
const tableData = ref({});
<template>Cog</template> watch(
selectedDeviceCog,
(newValue) => {
if (newValue) {
tableData.value = newValue;
} else {
tableData.value = {};
}
},
{ immediate: true }
);
</script>
<style lang="scss" scoped></style> <template>
<table class="table">
<tbody>
<tr>
<td>{{ $t("assetManagement.device_number") }}</td>
<td>{{ tableData.device_number }}</td>
</tr>
<tr>
<td>{{ $t("assetManagement.device_name") }}</td>
<td>{{ tableData.full_name }}</td>
</tr>
<tr>
<td>{{ $t("assetManagement.floor") }}</td>
<td>{{ tableData.floor }}</td>
</tr>
<tr>
<td>{{ $t("assetManagement.device_coordinate") }}</td>
<td>{{ tableData.device_coordinate }}</td>
</tr>
<tr>
<td>{{ $t("assetManagement.brand_and_modal") }}</td>
<td>{{ tableData.brand }} / {{ tableData.device_model }}</td>
</tr>
<tr>
<td>{{ t("assetManagement.company_and_contact") }}</td>
<td>
{{ tableData.company?.name }} /
{{ tableData.company?.contact_person }}
</td>
</tr>
<tr>
<td>{{ t("assetManagement.buying_date") }}</td>
<td>{{ tableData.buying_date }}</td>
</tr>
<tr>
<td>{{ t("assetManagement.created_at") }}</td>
<td>{{ tableData.created_at }}</td>
</tr>
</tbody>
</table>
</template>
<style lang="scss" scoped>
td {
border: 1px solid #ddd
}
td:first-child {
font-weight: bold;
}
</style>

View File

@ -1,4 +1,23 @@
<script setup></script> <script setup>
import { computed, inject, watch, ref } from "vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
const { selectedDeviceCog } = inject("system_selectedDevice");
const imgData = ref([]);
watch(
selectedDeviceCog,
(newValue) => {
if (newValue) {
imgData.value = newValue.oriFile.map(file => file.file_url);
} else {
imgData.value = [];
}
},
{ immediate: true }
);
</script>
<template> <template>
<a-carousel arrows class="mt-5 shadow-lg"> <a-carousel arrows class="mt-5 shadow-lg">
@ -20,10 +39,9 @@
/> />
</div> </div>
</template> </template>
<div><img src="https://picsum.photos/id/122/300/300" /></div> <div v-for="(url, index) in imgData" :key="index">
<div><img src="https://picsum.photos/id/127/300/300" /></div> <img :src="`${FILE_BASEURL}/${url}`" alt="Image" />
<div><img src="https://picsum.photos/id/124/300/300" /></div> </div>
<div><img src="https://picsum.photos/id/125/300/300" /></div>
</a-carousel> </a-carousel>
</template> </template>