系統監控: 2D圓點bug修正 | 能源管理: 日報表功能
This commit is contained in:
parent
8e2b5e1e2c
commit
5b1ff9749d
@ -5,3 +5,5 @@ export const GET_TAI_POWER_API = `/api/Energe/GetTaipower`;
|
|||||||
export const GET_SIDEBAR_API = `/api/Energe/GetEnergySideBar`;
|
export const GET_SIDEBAR_API = `/api/Energe/GetEnergySideBar`;
|
||||||
export const GET_SEARCH_API = `/api/Energe/GetFilter`;
|
export const GET_SEARCH_API = `/api/Energe/GetFilter`;
|
||||||
|
|
||||||
|
export const GET_REPORT_API = `/api/Energe/GetReport`;
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
GET_TAI_POWER_API,
|
GET_TAI_POWER_API,
|
||||||
GET_SIDEBAR_API,
|
GET_SIDEBAR_API,
|
||||||
GET_SEARCH_API,
|
GET_SEARCH_API,
|
||||||
|
GET_REPORT_API,
|
||||||
} from "./api";
|
} from "./api";
|
||||||
import instance from "@/util/request";
|
import instance from "@/util/request";
|
||||||
import apihandler from "@/util/apihandler";
|
import apihandler from "@/util/apihandler";
|
||||||
@ -52,3 +53,26 @@ export const getEnergySearch = async (type) => {
|
|||||||
code: res.code,
|
code: res.code,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getReport = async ({
|
||||||
|
department,
|
||||||
|
elecType,
|
||||||
|
floor,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
type,
|
||||||
|
}) => {
|
||||||
|
const res = await instance.post(GET_REPORT_API, {
|
||||||
|
department,
|
||||||
|
elecType,
|
||||||
|
floor,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
type,
|
||||||
|
});
|
||||||
|
|
||||||
|
return apihandler(res.code, res.data, {
|
||||||
|
msg: res.msg,
|
||||||
|
code: res.code,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
getAssetFloorList,
|
getAssetFloorList,
|
||||||
getElecTypeList,
|
getElecTypeList,
|
||||||
} from "@/apis/asset";
|
} from "@/apis/asset";
|
||||||
import { getAccountUserList } from "@/apis/account";
|
import { getReport } from "@/apis/energy";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@ -78,7 +78,7 @@ const getFloor = async () => {
|
|||||||
|
|
||||||
const onSearch = async () => {
|
const onSearch = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const res = await getAccountUserList(formState.value);
|
const res = await getReport(formState.value);
|
||||||
tableData.value = res.data;
|
tableData.value = res.data;
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
};
|
};
|
||||||
@ -114,7 +114,7 @@ watch(
|
|||||||
watch(
|
watch(
|
||||||
() => route.params.type,
|
() => route.params.type,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
formState.value.type = newVal;
|
formState.value.type = Number(newVal);
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
@ -1,33 +1,91 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import Table from "@/components/customUI/Table.vue";
|
import Table from "@/components/customUI/Table.vue";
|
||||||
import { inject, computed } from "vue";
|
import { inject, computed, ref } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import dayjs from "dayjs";
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { tableData, loading } = inject("energy_table_data");
|
const { tableData, loading } = inject("energy_table_data");
|
||||||
const columns = computed(() => [
|
|
||||||
|
// 預設的欄位
|
||||||
|
const defaultColumns = ref([
|
||||||
{
|
{
|
||||||
title: t("history.building_name"),
|
title: t("assetManagement.department"),
|
||||||
key: "building_name",
|
key: "department_name",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("energy.floor"),
|
title: t("energy.floor"),
|
||||||
key: "building_name",
|
key: "floor_name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("energy.electricity_classification"),
|
||||||
|
key: "elecType",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("history.device_name"),
|
title: t("history.device_name"),
|
||||||
key: "device_name",
|
key: "device_name",
|
||||||
filter: true,
|
filter: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: t("energy.subtotal"),
|
|
||||||
key: "subtotal",
|
|
||||||
}
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// 動態計算欄位
|
||||||
|
const columns = computed(() => {
|
||||||
|
if (!tableData.value || tableData.value.length === 0) {
|
||||||
|
return [...defaultColumns.value, {
|
||||||
|
title: t("energy.subtotal"),
|
||||||
|
key: "subtotal",
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
// 複製預設欄位
|
||||||
|
const dynamicColumns = [...defaultColumns.value];
|
||||||
|
|
||||||
|
// 取得第一個資料的 data 屬性,以此為準
|
||||||
|
const firstDataItem = tableData.value[0];
|
||||||
|
if (firstDataItem && firstDataItem.data) {
|
||||||
|
// 根據 data 屬性動態生成欄位
|
||||||
|
firstDataItem.data.forEach((item, index) => {
|
||||||
|
// 使用 dayjs 和 format 格式化日期
|
||||||
|
const formattedTime = dayjs(item.time).format("MM-DD");
|
||||||
|
dynamicColumns.push({
|
||||||
|
title: formattedTime,
|
||||||
|
key: `data[${index}].value`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicColumns.push({
|
||||||
|
title: t("energy.subtotal"),
|
||||||
|
key: "subtotal",
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
return dynamicColumns;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// 動態調整 subtotal 值
|
||||||
|
const dataSource = computed(() => {
|
||||||
|
|
||||||
|
if(!tableData.value || tableData.value.length === 0) return [];
|
||||||
|
|
||||||
|
return tableData.value.map(item => {
|
||||||
|
let subtotalValue = 0;
|
||||||
|
if(item.data && item.data.length > 0) {
|
||||||
|
item.data.forEach((dataItem) => {
|
||||||
|
subtotalValue += parseFloat(dataItem.value || 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
subtotal: subtotalValue.toFixed(2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Table :columns="columns" :dataSource="tableData" :loading="loading"></Table>
|
<Table :columns="columns" :dataSource="dataSource" :loading="loading"></Table>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped></style>
|
@ -157,12 +157,17 @@ const updateCurrentFloor = (floor) => {
|
|||||||
|
|
||||||
const realtimeData = ref([])
|
const realtimeData = ref([])
|
||||||
const timeId = ref(null)
|
const timeId = ref(null)
|
||||||
const getAllDeviceRealtime = () => {
|
const getAllDeviceRealtime = async () => {
|
||||||
timeId.value = setInterval(async () => {
|
// 立即執行一次
|
||||||
const res = await getSystemRealTime(subscribeData.value.map(d => d.device_number))
|
const fetchData = async () => {
|
||||||
console.log(res.data)
|
const res = await getSystemRealTime(subscribeData.value.map(d => d.device_number));
|
||||||
realtimeData.value = res.data
|
console.log(res.data);
|
||||||
}, 10000)
|
realtimeData.value = res.data;
|
||||||
|
};
|
||||||
|
await fetchData(); // 立即執行一次
|
||||||
|
|
||||||
|
// 然後設定每 10 秒更新一次
|
||||||
|
timeId.value = setInterval(fetchData, 10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(subscribeData, (newValue) => {
|
watch(subscribeData, (newValue) => {
|
||||||
@ -206,9 +211,7 @@ const getCurrentInfoModalData = async (e, position, value) => {
|
|||||||
: "",
|
: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const immediateRes = await getSystemRealTime([value.device_number]);
|
|
||||||
realtimeData.value = immediateRes.data;
|
|
||||||
}
|
}
|
||||||
const mobile = isMobile(e);
|
const mobile = isMobile(e);
|
||||||
selectedDevice.value = {
|
selectedDevice.value = {
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
<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 { computed, inject, ref, watch } from 'vue';
|
import { computed, inject, ref, watch } from "vue";
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from "tailwind-merge";
|
||||||
import useSelectedFloor from "@/hooks/useSelectedFloor"
|
import useSelectedFloor from "@/hooks/useSelectedFloor";
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute();
|
||||||
|
|
||||||
const { currentFloor, subscribeData } = inject("system_deviceList")
|
const { currentFloor, subscribeData } = inject("system_deviceList");
|
||||||
const { getCurrentInfoModalData } = inject("system_selectedDevice")
|
const { getCurrentInfoModalData, selected_dbid } = 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);
|
||||||
@ -19,7 +21,7 @@ const sameOption = {
|
|||||||
encode: {
|
encode: {
|
||||||
tooltip: 2,
|
tooltip: 2,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
const defaultOption = (map, data = []) => {
|
const defaultOption = (map, data = []) => {
|
||||||
return {
|
return {
|
||||||
animation: false,
|
animation: false,
|
||||||
@ -36,7 +38,7 @@ const defaultOption = (map, data = []) => {
|
|||||||
...sameOption,
|
...sameOption,
|
||||||
symbolSize: 10,
|
symbolSize: 10,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: data?.[0]?.[3]?.device_normal_color || "#009100",
|
color: data?.[0]?.[2]?.device_normal_color || "#009100",
|
||||||
},
|
},
|
||||||
data,
|
data,
|
||||||
},
|
},
|
||||||
@ -44,85 +46,141 @@ const defaultOption = (map, data = []) => {
|
|||||||
...sameOption,
|
...sameOption,
|
||||||
symbolSize: 20,
|
symbolSize: 20,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: data?.[0]?.[3]?.device_normal_color || "#009100",
|
color: data?.[0]?.[2]?.device_normal_color || "#009100",
|
||||||
},
|
},
|
||||||
data: [],
|
data: [],
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const { selectedFloor } = useSelectedFloor()
|
const { selectedFloor } = useSelectedFloor();
|
||||||
|
|
||||||
const allData = ref([])
|
const allData = ref([]);
|
||||||
|
const selectedData = ref([]);
|
||||||
|
watch(
|
||||||
|
[selectedFloor, () => asset_floor_chart],
|
||||||
|
([newValue, newChart], [oldValue]) => {
|
||||||
|
if (
|
||||||
|
newValue &&
|
||||||
|
newChart.value &&
|
||||||
|
oldValue?.key !== newValue.key &&
|
||||||
|
newValue.map_url
|
||||||
|
) {
|
||||||
|
selectedData.value =
|
||||||
|
subscribeData.value
|
||||||
|
?.filter(
|
||||||
|
(d) => d.device_coordinate && d.floor_guid === route.params.floor_id
|
||||||
|
)
|
||||||
|
.map((d) => [...d.device_coordinate.slice(1, -1).split(","), d]) ||
|
||||||
|
[];
|
||||||
|
|
||||||
watch([selectedFloor, () => asset_floor_chart,], ([newValue, newChart], [oldValue]) => {
|
asset_floor_chart.value.updateSvg(
|
||||||
if (newValue && newChart.value && oldValue?.key !== newValue.key && newValue.map_url) {
|
{
|
||||||
asset_floor_chart.value.updateSvg(
|
full_name: newValue?.title,
|
||||||
{
|
path: `${FILE_BASEURL}/${newValue.map_url}`,
|
||||||
full_name: newValue?.title,
|
},
|
||||||
path: `${FILE_BASEURL}/${newValue.map_url}`,
|
|
||||||
},
|
|
||||||
|
|
||||||
defaultOption(newValue?.title, subscribeData.value?.filter(d => d.device_coordinate && d.floor_guid === route.params.floor_id).map(d => [...d.device_coordinate.split(","), d]) || [])
|
defaultOption(newValue?.title, selectedData.value)
|
||||||
);
|
);
|
||||||
|
|
||||||
newChart.value.chart.on("click", function (params) {
|
newChart.value.chart.on("click", function (params) {
|
||||||
console.log(params, params.data[2])
|
getCurrentInfoModalData(
|
||||||
getCurrentInfoModalData(params.event, {
|
params.event,
|
||||||
left: params.event.offsetX
|
{
|
||||||
, top: params.event.offsetY
|
left: params.event.offsetX,
|
||||||
}, params.data[2])
|
top: params.event.offsetY,
|
||||||
|
},
|
||||||
|
params.data[2]
|
||||||
|
);
|
||||||
|
selected_dbid.value[0] = params.data[2].forge_dbid;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
let values = [...subscribeData.value].map(d => ({ ...d, is2DActive: d.forge_dbid === params.data[2].forge_dbid }))
|
watch(
|
||||||
subscribeData.value = values;
|
subscribeData,
|
||||||
|
(newData) => {
|
||||||
const selected = allData.value.filter((d => d[2].device_number === params.data[2].device_number))
|
let values = newData
|
||||||
const unSelected = allData.value.filter((d => d[2].device_number !== params.data[2].device_number))
|
?.filter(
|
||||||
newChart.value.chart.setOption({
|
(d) => d.device_coordinate && d.floor_guid === route.params.floor_id
|
||||||
|
)
|
||||||
|
.map((d) => [...d.device_coordinate.slice(1, -1).split(","), d]);
|
||||||
|
allData.value = values;
|
||||||
|
selectedData.value = newData
|
||||||
|
?.filter((d) => d.device_coordinate)
|
||||||
|
.map((d) => [...d.device_coordinate.slice(1, -1).split(","), d]);
|
||||||
|
if (
|
||||||
|
selectedFloor.value &&
|
||||||
|
asset_floor_chart.value &&
|
||||||
|
asset_floor_chart.value.chart
|
||||||
|
) {
|
||||||
|
const selected = allData.value.filter((d) => d[2].is2DActive);
|
||||||
|
const unSelected = allData.value.filter((d) => !d[2].is2DActive);
|
||||||
|
asset_floor_chart.value.chart.setOption({
|
||||||
series: [
|
series: [
|
||||||
{ data: unSelected }, {
|
{ data: unSelected },
|
||||||
|
{
|
||||||
data: selected,
|
data: selected,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
})
|
});
|
||||||
|
}
|
||||||
});
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
}
|
}
|
||||||
}, {
|
);
|
||||||
immediate: true,
|
|
||||||
deep: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(subscribeData, (newData) => {
|
watch(
|
||||||
let values = newData?.filter(d => d.device_coordinate && d.floor_guid === route.params.floor_id).map(d => [...d.device_coordinate.split(","), d])
|
selected_dbid,
|
||||||
allData.value = values;
|
(newSelectedDbid) => {
|
||||||
if (selectedFloor.value && asset_floor_chart.value && asset_floor_chart.value.chart) {
|
if (
|
||||||
const selected = allData.value.filter((d => d[2].is2DActive))
|
asset_floor_chart.value &&
|
||||||
const unSelected = allData.value.filter((d => !d[2].is2DActive))
|
asset_floor_chart.value.chart &&
|
||||||
asset_floor_chart.value.chart.setOption({
|
allData.value
|
||||||
series: [
|
) {
|
||||||
{ data: unSelected }, {
|
selectedData.value = selectedData.value.map((item) => {
|
||||||
data: selected,
|
if (item[2].forge_dbid === newSelectedDbid[0]) {
|
||||||
|
return [...item.slice(0, 2), { ...item[2], is2DActive: true }];
|
||||||
}
|
}
|
||||||
]
|
return [...item.slice(0, 2), { ...item[2], is2DActive: false }];
|
||||||
})
|
});
|
||||||
console.log(asset_floor_chart.value.chart.getOption())
|
|
||||||
|
const selected = selectedData.value.filter((d) => d[2].is2DActive);
|
||||||
|
const unSelected = selectedData.value.filter((d) => !d[2].is2DActive);
|
||||||
|
|
||||||
|
console.log("allData.value", allData.value, selected, unSelected);
|
||||||
|
asset_floor_chart.value.chart.setOption({
|
||||||
|
series: [
|
||||||
|
{ data: unSelected },
|
||||||
|
{
|
||||||
|
data: selected,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
}
|
}
|
||||||
}, {
|
);
|
||||||
immediate: true,
|
|
||||||
deep: true,
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- <Loading class="absolute" /> -->
|
<!-- <Loading class="absolute" /> -->
|
||||||
<EffectScatter id="system_floor_chart" ref="asset_floor_chart" class="min-h-full bg-white" />
|
<EffectScatter
|
||||||
|
id="system_floor_chart"
|
||||||
|
ref="asset_floor_chart"
|
||||||
|
class="min-h-full bg-white"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- <div class="text-lg" v-if="!currentFloor?.key">尚未上傳樓層平面圖</div> -->
|
<!-- <div class="text-lg" v-if="!currentFloor?.key">尚未上傳樓層平面圖</div> -->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang='scss' scoped></style>
|
<style lang="scss" scoped></style>
|
||||||
|
@ -10,10 +10,6 @@ const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
|||||||
|
|
||||||
const fitToView = (forge_dbid, spriteDbId) => {
|
const fitToView = (forge_dbid, spriteDbId) => {
|
||||||
selected_dbid.value = [forge_dbid, spriteDbId];
|
selected_dbid.value = [forge_dbid, spriteDbId];
|
||||||
console.log(subscribeData.value)
|
|
||||||
let allData = [...subscribeData.value].map(d => ({ ...d, is2DActive: d.forge_dbid === forge_dbid }))
|
|
||||||
subscribeData.value = allData;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
Reference in New Issue
Block a user