設備管理:選擇圖資圖片資料夾路徑、樓層編輯與刪除 | 系統監控照片初切版
This commit is contained in:
parent
364ac7b730
commit
1dbd58b965
@ -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`;
|
||||
|
@ -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,
|
||||
|
35
src/components/customUI/Menu.vue
Normal file
35
src/components/customUI/Menu.vue
Normal 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>
|
@ -166,7 +166,7 @@ watch(
|
||||
:class="
|
||||
twMerge(
|
||||
withStyle ? 'table' : 'table border',
|
||||
currentDataSource.length === 0 ? 'h-96' : ''
|
||||
currentDataSource.length === 0 ? 'h-28' : ''
|
||||
)
|
||||
"
|
||||
>
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
);
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
@ -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
|
||||
|
@ -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 "
|
||||
|
@ -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]')" />
|
||||
|
59
src/views/system/components/SystemInfoModalImage.vue
Normal file
59
src/views/system/components/SystemInfoModalImage.vue
Normal 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>
|
Loading…
Reference in New Issue
Block a user