系統監控: 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_SEARCH_API = `/api/Energe/GetFilter`;
|
||||
|
||||
export const GET_REPORT_API = `/api/Energe/GetReport`;
|
||||
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
GET_TAI_POWER_API,
|
||||
GET_SIDEBAR_API,
|
||||
GET_SEARCH_API,
|
||||
GET_REPORT_API,
|
||||
} from "./api";
|
||||
import instance from "@/util/request";
|
||||
import apihandler from "@/util/apihandler";
|
||||
@ -52,3 +53,26 @@ export const getEnergySearch = async (type) => {
|
||||
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,
|
||||
getElecTypeList,
|
||||
} from "@/apis/asset";
|
||||
import { getAccountUserList } from "@/apis/account";
|
||||
import { getReport } from "@/apis/energy";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
@ -78,7 +78,7 @@ const getFloor = async () => {
|
||||
|
||||
const onSearch = async () => {
|
||||
loading.value = true;
|
||||
const res = await getAccountUserList(formState.value);
|
||||
const res = await getReport(formState.value);
|
||||
tableData.value = res.data;
|
||||
loading.value = false;
|
||||
};
|
||||
@ -114,7 +114,7 @@ watch(
|
||||
watch(
|
||||
() => route.params.type,
|
||||
(newVal) => {
|
||||
formState.value.type = newVal;
|
||||
formState.value.type = Number(newVal);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
@ -1,33 +1,91 @@
|
||||
<script setup>
|
||||
import Table from "@/components/customUI/Table.vue";
|
||||
import { inject, computed } from "vue";
|
||||
import { inject, computed, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import dayjs from "dayjs";
|
||||
const { t } = useI18n();
|
||||
const { tableData, loading } = inject("energy_table_data");
|
||||
const columns = computed(() => [
|
||||
|
||||
// 預設的欄位
|
||||
const defaultColumns = ref([
|
||||
{
|
||||
title: t("history.building_name"),
|
||||
key: "building_name",
|
||||
title: t("assetManagement.department"),
|
||||
key: "department_name",
|
||||
},
|
||||
{
|
||||
title: t("energy.floor"),
|
||||
key: "building_name",
|
||||
key: "floor_name",
|
||||
},
|
||||
{
|
||||
title: t("energy.electricity_classification"),
|
||||
key: "elecType",
|
||||
},
|
||||
{
|
||||
title: t("history.device_name"),
|
||||
key: "device_name",
|
||||
filter: true,
|
||||
},
|
||||
{
|
||||
]);
|
||||
|
||||
// 動態計算欄位
|
||||
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>
|
||||
|
||||
<template>
|
||||
<Table :columns="columns" :dataSource="tableData" :loading="loading"></Table>
|
||||
<Table :columns="columns" :dataSource="dataSource" :loading="loading"></Table>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -157,12 +157,17 @@ const updateCurrentFloor = (floor) => {
|
||||
|
||||
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)
|
||||
const getAllDeviceRealtime = async () => {
|
||||
// 立即執行一次
|
||||
const fetchData = async () => {
|
||||
const res = await getSystemRealTime(subscribeData.value.map(d => d.device_number));
|
||||
console.log(res.data);
|
||||
realtimeData.value = res.data;
|
||||
};
|
||||
await fetchData(); // 立即執行一次
|
||||
|
||||
// 然後設定每 10 秒更新一次
|
||||
timeId.value = setInterval(fetchData, 10000);
|
||||
}
|
||||
|
||||
watch(subscribeData, (newValue) => {
|
||||
@ -207,8 +212,6 @@ const getCurrentInfoModalData = async (e, position, value) => {
|
||||
};
|
||||
}
|
||||
|
||||
const immediateRes = await getSystemRealTime([value.device_number]);
|
||||
realtimeData.value = immediateRes.data;
|
||||
}
|
||||
const mobile = isMobile(e);
|
||||
selectedDevice.value = {
|
||||
|
@ -1,14 +1,16 @@
|
||||
<script setup>
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useRoute } from "vue-router";
|
||||
import EffectScatter from "@/components/chart/EffectScatter.vue";
|
||||
import { computed, inject, ref, watch } from 'vue';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import useSelectedFloor from "@/hooks/useSelectedFloor"
|
||||
import { computed, inject, ref, watch } from "vue";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import useSelectedFloor from "@/hooks/useSelectedFloor";
|
||||
|
||||
const route = useRoute()
|
||||
const route = useRoute();
|
||||
|
||||
const { currentFloor, subscribeData } = inject("system_deviceList")
|
||||
const { getCurrentInfoModalData } = inject("system_selectedDevice")
|
||||
const { currentFloor, subscribeData } = inject("system_deviceList");
|
||||
const { getCurrentInfoModalData, selected_dbid } = inject(
|
||||
"system_selectedDevice"
|
||||
);
|
||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
||||
|
||||
const asset_floor_chart = ref(null);
|
||||
@ -19,7 +21,7 @@ const sameOption = {
|
||||
encode: {
|
||||
tooltip: 2,
|
||||
},
|
||||
}
|
||||
};
|
||||
const defaultOption = (map, data = []) => {
|
||||
return {
|
||||
animation: false,
|
||||
@ -36,7 +38,7 @@ const defaultOption = (map, data = []) => {
|
||||
...sameOption,
|
||||
symbolSize: 10,
|
||||
itemStyle: {
|
||||
color: data?.[0]?.[3]?.device_normal_color || "#009100",
|
||||
color: data?.[0]?.[2]?.device_normal_color || "#009100",
|
||||
},
|
||||
data,
|
||||
},
|
||||
@ -44,85 +46,141 @@ const defaultOption = (map, data = []) => {
|
||||
...sameOption,
|
||||
symbolSize: 20,
|
||||
itemStyle: {
|
||||
color: data?.[0]?.[3]?.device_normal_color || "#009100",
|
||||
color: data?.[0]?.[2]?.device_normal_color || "#009100",
|
||||
},
|
||||
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]) => {
|
||||
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}`,
|
||||
},
|
||||
|
||||
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) {
|
||||
console.log(params, params.data[2])
|
||||
getCurrentInfoModalData(params.event, {
|
||||
left: params.event.offsetX
|
||||
, top: params.event.offsetY
|
||||
}, params.data[2])
|
||||
|
||||
let values = [...subscribeData.value].map(d => ({ ...d, is2DActive: d.forge_dbid === params.data[2].forge_dbid }))
|
||||
subscribeData.value = values;
|
||||
|
||||
const selected = allData.value.filter((d => d[2].device_number === params.data[2].device_number))
|
||||
const unSelected = allData.value.filter((d => d[2].device_number !== params.data[2].device_number))
|
||||
newChart.value.chart.setOption({
|
||||
series: [
|
||||
{ data: unSelected }, {
|
||||
data: selected,
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
getCurrentInfoModalData(
|
||||
params.event,
|
||||
{
|
||||
left: params.event.offsetX,
|
||||
top: params.event.offsetY,
|
||||
},
|
||||
params.data[2]
|
||||
);
|
||||
selected_dbid.value[0] = params.data[2].forge_dbid;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}, {
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
watch(subscribeData, (newData) => {
|
||||
let values = newData?.filter(d => d.device_coordinate && d.floor_guid === route.params.floor_id).map(d => [...d.device_coordinate.split(","), d])
|
||||
watch(
|
||||
subscribeData,
|
||||
(newData) => {
|
||||
let values = newData
|
||||
?.filter(
|
||||
(d) => d.device_coordinate && d.floor_guid === route.params.floor_id
|
||||
)
|
||||
.map((d) => [...d.device_coordinate.slice(1, -1).split(","), d]);
|
||||
allData.value = values;
|
||||
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))
|
||||
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: [
|
||||
{ data: unSelected }, {
|
||||
{ data: unSelected },
|
||||
{
|
||||
data: selected,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
]
|
||||
})
|
||||
console.log(asset_floor_chart.value.chart.getOption())
|
||||
}
|
||||
}, {
|
||||
immediate: true,
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
selected_dbid,
|
||||
(newSelectedDbid) => {
|
||||
if (
|
||||
asset_floor_chart.value &&
|
||||
asset_floor_chart.value.chart &&
|
||||
allData.value
|
||||
) {
|
||||
selectedData.value = selectedData.value.map((item) => {
|
||||
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 }];
|
||||
});
|
||||
|
||||
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,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- <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> -->
|
||||
</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) => {
|
||||
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>
|
||||
|
Loading…
Reference in New Issue
Block a user