設備管理:選擇圖資圖片資料夾路徑、樓層編輯與刪除 | 系統監控照片初切版

This commit is contained in:
koko 2024-10-28 10:21:57 +08:00
parent 364ac7b730
commit 1dbd58b965
12 changed files with 326 additions and 18 deletions

View File

@ -11,6 +11,7 @@ export const DELETE_ASSET_ITEM_API = `/AssetManage/DeleteAsset`;
export const GET_ASSET_FLOOR_LIST_API = `/AssetManage/GetFloorList`;
export const POST_ASSET_FLOOR_API = `/AssetManage/SaveFloor`;
export const DELETE_ASSET_FLOOR_API = `/AssetManage/DeleteFloor`;
export const GET_ASSET_IOT_LIST_API = `/AssetManage/GetIOTList`;
export const GET_ASSET_SUB_POINT_API = `/AssetManage/GetSubPoint`;

View File

@ -7,6 +7,7 @@ import {
GET_ASSET_SINGLE_API,
GET_ASSET_FLOOR_LIST_API,
POST_ASSET_FLOOR_API,
DELETE_ASSET_FLOOR_API,
GET_ASSET_IOT_LIST_API,
DELETE_ASSET_ITEM_API,
POST_ASSET_SINGLE_API,
@ -143,6 +144,15 @@ export const postAssetFloor = async (formData) => {
});
};
export const deleteAssetFloor = async (formData) => {
const res = await instance.post(DELETE_ASSET_FLOOR_API, formData);
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
});
};
export const getAssetIOTList = async (sub_system_tag, points) => {
const res = await instance.post(GET_ASSET_IOT_LIST_API, {
sub_system_tag,

View File

@ -0,0 +1,35 @@
<script setup>
import Checkbox from "@/components/customUI/Checkbox.vue";
import { twMerge } from "tailwind-merge";
import { computed, defineProps, onMounted, ref, watch } from "vue";
const props = defineProps({
item: Object,
selectedId: String,
menuCheck:Function,
});
</script>
<template>
<li>
<!-- 項目無子層級 -->
<a v-if="item.children.length === 0">
<Checkbox
:title="item.name"
:checked="selectedId === item.id"
@click="() => menuCheck(item.id)"
/>
</a>
<!-- 項目有子層級 -->
<details v-else>
<summary>{{ item.name }}</summary>
<ul>
<li v-for="(child, index) in item.children" :key="index">
<Menu :item="child" :selectedId="selectedId" :menuCheck="menuCheck"></Menu>
</li>
</ul>
</details>
</li>
</template>
<style lang="css" scoped></style>

View File

@ -166,7 +166,7 @@ watch(
:class="
twMerge(
withStyle ? 'table' : 'table border',
currentDataSource.length === 0 ? 'h-96' : ''
currentDataSource.length === 0 ? 'h-28' : ''
)
"
>

View File

@ -3,9 +3,6 @@ import { getBuildings } from "@/apis/building";
import { onMounted, ref } from "vue";
import useBuildingStore from "@/stores/useBuildingStore";
// const buildings = ref(null);
// const selectedBuilding = ref(null);
const store = useBuildingStore();
const getBui = async () => {
@ -15,6 +12,10 @@ const getBui = async () => {
store.selectedBuilding = res?.data[0];
};
const selectBuilding = (bui) => {
store.selectedBuilding = bui;
};
onMounted(() => {
getBui();
});
@ -25,10 +26,10 @@ onMounted(() => {
<div
tabindex="0"
role="button"
class="text-white ml-8 text-lg font-semiLight "
class="text-white ml-8 text-lg font-semiLight"
>
{{ store.selectedBuilding?.full_name }}
<font-awesome-icon :icon="['fas', 'angle-down']" class="ml-1"/>
<font-awesome-icon :icon="['fas', 'angle-down']" class="ml-1" />
</div>
<ul
tabindex="0"
@ -38,6 +39,7 @@ onMounted(() => {
class="text-white my-1 text-base"
v-for="bui in store.buildings"
:key="bui.building_tag"
@click="selectBuilding(bui)"
>
{{ bui.full_name }}
</li>

View File

@ -38,6 +38,9 @@ instance.interceptors.response.use(
function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
if (response && response.status === 401) {
window.location.href = "/logout";
}
return Promise.reject(error);
}
);

View File

@ -3,6 +3,7 @@ import { ref, inject, onBeforeMount, onMounted, watch } from "vue";
import * as yup from "yup";
import "yup-phone-lite";
import AssetTableModalLeftInfoIoT from "./AssetTableModalLeftInfoIoT.vue";
import AssetTableModalLeftInfoGraph from "./AssetTableModalLeftInfoGraph.vue";
import { getOperationCompanyList } from "@/apis/operation";
import useSearchParam from "@/hooks/useSearchParam";
import OperationTableModal from "@/views/operation/components/OperationTableModal.vue";
@ -199,7 +200,8 @@ const openCompanyAddModal = () => {
{{ $t("button.add") }}
</button>
</div>
<Upload
<AssetTableModalLeftInfoGraph />
<!-- <Upload
name="oriFile"
:fileList="formState.oriFile"
:getFileList="updateFileList"
@ -208,7 +210,7 @@ const openCompanyAddModal = () => {
:baseUrl="FILE_BASEURL"
>
<template #topLeft>{{ $t("assetManagement.oriFile") }}</template>
</Upload>
</Upload> -->
<AssetTableModalLeftInfoIoT />
</template>

View File

@ -0,0 +1,146 @@
<script setup>
import { getSideBar, getGraphData } from "@/apis/graph";
import { ref, computed, inject, watch, onMounted } from "vue";
import { useI18n } from "vue-i18n";
import Menu from "@/components/customUI/Menu.vue";
const { t } = useI18n();
const { formState } = inject("asset_table_modal_form");
const columns = computed(() => [
{
title: t("assetManagement.index"),
key: "index",
width: 150,
},
{
title: t("graphManagement.oriOrgName"),
key: "oriOrgName",
},
]);
const graphData = ref([]);
const menuData = ref([]);
const selectedId = ref(null);
const dataSource = ref([]);
const getMenuData = async () => {
const res = await getSideBar();
graphData.value = res.data;
console.log(" graphData", graphData.value);
};
const getData = async (id) => {
const res = await getGraphData(id);
if (res.isSuccess) {
dataSource.value = res.data.map((d) => ({ ...d, key: d.id }));
}
};
const buildMenuTree = (data) => {
const map = new Map();
const menu = [];
data.forEach((item) => {
map.set(item.id, { ...item, children: [] });
});
data.forEach((item) => {
const parent = map.get(item.parent_id);
if (item.parent_id === 0) {
menu.push(map.get(item.id));
} else {
if (parent) {
parent.children.push(map.get(item.id));
}
}
});
return menu;
};
const openModal = () => {
asset_add_graph_item.showModal();
};
const onOk = () => {
console.log("selectedId", selectedId.value);
formState.value.graph = selectedId.value;
onCancel();
};
const onCancel = () => {
asset_add_graph_item.close();
};
const menuCheck = (id) => {
selectedId.value = id;
};
watch(selectedId, (newId) => {
getData(newId);
});
onMounted(async () => {
await getMenuData();
menuData.value = buildMenuTree(graphData.value);
});
</script>
<template>
<div class="flex flex-col mt-8 col-span-2">
<div class="flex justify-between items-center">
<span class="label-text-alt text-lg"> 圖片 </span>
<button
type="button"
class="btn btn-sm btn-success"
@click.stop.prevent="openModal"
>
圖資管理
</button>
</div>
<Table
:columns="columns"
:dataSource="dataSource"
:withStyle="false"
>
<template #bodyCell="{ record, column, index }">
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
</template>
</Table>
</div>
<Modal
id="asset_add_graph_item"
:title="t('graphManagement.title')"
:onCancel="onCancel"
width="500"
>
<template #modalContent>
<ul class="menu bg-base-200 rounded-box text-lg w-full mt-3">
<li v-for="(item, index) in menuData" :key="index">
<Menu
:item="item"
:selectedId="selectedId"
:menuCheck="menuCheck"
></Menu>
</li>
</ul>
</template>
<template #modalAction>
<button
type="reset"
class="btn btn-outline-success mr-2"
@click.prevent="onCancel"
>
{{ $t("button.cancel") }}
</button>
<button
type="submit"
class="btn btn-outline-success"
@click.prevent="onOk"
>
{{ $t("button.submit") }}
</button>
</template></Modal
>
</template>
<style lang="scss" scoped></style>

View File

@ -1,7 +1,11 @@
<script setup>
import { onMounted, ref, inject, onBeforeMount, watch, computed } from "vue";
import EffectScatter from "@/components/chart/EffectScatter.vue";
import { getAssetFloorList, postAssetFloor } from "@/apis/asset";
import {
getAssetFloorList,
postAssetFloor,
deleteAssetFloor,
} from "@/apis/asset";
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
import * as yup from "yup";
import { twMerge } from "tailwind-merge";
@ -27,6 +31,8 @@ onBeforeMount(() => {
const asset_floor_chart = ref(null);
const currentFloor = ref(null);
const selectedOption = ref("add");
const defaultOption = (map, data = []) => ({
tooltip: {},
geo: {
@ -99,6 +105,25 @@ onMounted(() => {
// modal
const openModal = () => {
if (selectedOption.value === "add") {
FloorFormState.value = {
building_tag: "F3",
full_name: "",
floorFile: [],
};
} else if (selectedOption.value === "edit") {
const floor = floors.value.find(
(f) => f.floor_guid === formState.value.floor_guid
);
if (floor) {
console.log("floor", floor);
FloorFormState.value = {
building_tag: "F3",
full_name: floor.full_name,
floorFile: [],
};
}
}
asset_add_floor.showModal();
};
@ -116,6 +141,7 @@ const floorScheme = yup.object({
});
const updateFileList = (files) => {
console.log("file",files)
FloorFormState.value.floorFile = files;
};
@ -143,6 +169,16 @@ const onOk = async () => {
onCancel();
}
};
const onDelete = async () => {
console.log("guild", formState.value.floor_guid);
// const res = await deleteAssetFloor({
// floor_guid: formState.value.floor_guid,
// });
// if (res.isSuccess) {
// getFloors(); //
// formState.value.floor_guid = ""; //
// }
};
const onCancel = () => {
FloorFormState.value = {
@ -158,10 +194,10 @@ const onCancel = () => {
<!-- 平面圖 -->
<div class="flex items-center justify-between mb-5">
<div class="flex justify-start">
<div className="join">
<Select
:value="formState"
selectClass="border-info focus-within:border-info"
selectClass="border-info focus-within:border-info rounded-r-none"
name="floor_guid"
Attribute="full_name"
:options="floors"
@ -169,15 +205,23 @@ const onCancel = () => {
>
<template #topLeft>{{ $t("assetManagement.floor") }}</template>
</Select>
<select
v-model="selectedOption"
className="select border-info focus-within:border-info join-item mt-11"
>
<option value="add" selected>{{ $t("button.add") }}</option>
<option value="edit">{{ $t("button.edit") }}</option>
<option value="delete">{{ $t("button.delete") }}</option>
</select>
<button
type="button"
class="btn btn-success mt-10 ml-5"
@click="openModal"
class="btn btn-success join-item mt-11"
@click="selectedOption === 'delete' ? onDelete() : openModal()"
:aria-label="$t('button.submit')"
>
{{ $t("assetManagement.add_floor") }}
{{ $t("button.submit") }}
</button>
</div>
<!-- <button type="button" class="btn btn-success mt-10">確認座標</button> -->
</div>
<div class="relative">
<EffectScatter

View File

@ -25,8 +25,8 @@ watch(selectedFloor, (newValue) => {
<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 h-36">
<div class="left w-4/5 h-full flex flex-wrap justify-center">
<div class="item h-full">
<div class="left h-full flex flex-wrap justify-center">
<div class="sec02 w-full">
<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>
@ -35,7 +35,7 @@ watch(selectedFloor, (newValue) => {
<div class="flex justify-between w-full self-end">
<div class="sec03">
<span class="w-5 h-5 rounded-full" :style="{ backgroundColor: device.device_normal_color }"></span>
<span class="mx-2">{{ $t("system.status") }}</span>
<span class="mx-2">{{ $t("system.status") }}:</span>
<span>{{ device.device_status }}</span>
</div>
<button class="btn-text border-0 "

View File

@ -2,6 +2,7 @@
import { defineProps, inject, ref, watch } from "vue";
import SystemInfoModalDesktop from "./SystemInfoModalDesktop.vue";
import SystemInfoModalCog from "./SystemInfoModalCog.vue";
import SystemInfoModalImage from "./SystemInfoModalImage.vue";
import { twMerge } from "tailwind-merge";
@ -12,6 +13,7 @@ const currentTab = ref("desktop");
const tabs = {
desktop: SystemInfoModalDesktop,
cog: SystemInfoModalCog,
image: SystemInfoModalImage
};
const changeOpenKey = (key) => {
@ -34,6 +36,10 @@ const onCancel = () => {
<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('image')">
<font-awesome-icon :icon="['fas', 'image']" size="lg"
:class="twMerge(currentTab === 'image' ? '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]')" />

View File

@ -0,0 +1,59 @@
<script setup></script>
<template>
<a-carousel arrows class="mt-5 shadow-lg">
<template #prevArrow>
<div class="custom-slick-arrow" style="left: 10px; z-index: 1">
<font-awesome-icon
:icon="['fas', 'chevron-left']"
size="lg"
class="text-[#a5abb1]"
/>
</div>
</template>
<template #nextArrow>
<div class="custom-slick-arrow" style="right: 10px">
<font-awesome-icon
:icon="['fas', 'chevron-right']"
size="lg"
class="text-[#a5abb1]"
/>
</div>
</template>
<div><img src="https://picsum.photos/id/122/300/300" /></div>
<div><img src="https://picsum.photos/id/127/300/300" /></div>
<div><img src="https://picsum.photos/id/124/300/300" /></div>
<div><img src="https://picsum.photos/id/125/300/300" /></div>
</a-carousel>
</template>
<style scoped>
/* For demo */
:deep(.slick-slide) {
text-align: center;
background: #1d2429;
overflow: hidden;
}
:deep(.slick-arrow.custom-slick-arrow) {
width: 25px;
height: 25px;
font-size: 25px;
color: #fff;
background-color: rgba(31, 45, 61, 0.11);
transition: ease all 0.3s;
opacity: 0.3;
z-index: 1;
}
:deep(.slick-arrow.custom-slick-arrow:before) {
display: none;
}
:deep(.slick-arrow.custom-slick-arrow:hover) {
color: #fff;
opacity: 0.5;
}
:deep(.slick-slide img) {
margin: auto;
}
</style>