This commit is contained in:
koko 2025-05-06 18:11:17 +08:00
parent b55ba4003d
commit 0772269c33
10 changed files with 682 additions and 318 deletions

BIN
public/build_img.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

View File

@ -7,3 +7,7 @@ export const GET_DASHBOARD_ENERGY_API = `/SituationRoom/GetEnergeData`;
export const POST_DASHBOARD_PRODUCT_TARGET_SETTING_API = `/SituationRoom/SetTargetSetting`; export const POST_DASHBOARD_PRODUCT_TARGET_SETTING_API = `/SituationRoom/SetTargetSetting`;
export const GET_DASHBOARD_PRODUCT_TARGET_SETTING_API = `/SituationRoom/GetTargetSetting` export const GET_DASHBOARD_PRODUCT_TARGET_SETTING_API = `/SituationRoom/GetTargetSetting`
export const GET_DASHBOARD_PRODUCT_HISTORY_API = `/SituationRoom/GetProductionHistory` export const GET_DASHBOARD_PRODUCT_HISTORY_API = `/SituationRoom/GetProductionHistory`
export const GET_DASHBOARD_ENERGY_INFO_API = `api/dashboard/GetEnergyInfo`
export const GET_DASHBOARD_ENERGY_COST_API = `api/dashboard/GetEnergyCost`
export const GET_DASHBOARD_ALARMOPERATION_INFO_API = `api/dashboard/GetAlarmOperationInfo`

View File

@ -8,6 +8,9 @@ import {
POST_DASHBOARD_PRODUCT_TARGET_SETTING_API, POST_DASHBOARD_PRODUCT_TARGET_SETTING_API,
GET_DASHBOARD_PRODUCT_TARGET_SETTING_API, GET_DASHBOARD_PRODUCT_TARGET_SETTING_API,
GET_DASHBOARD_PRODUCT_HISTORY_API, GET_DASHBOARD_PRODUCT_HISTORY_API,
GET_DASHBOARD_ENERGY_INFO_API,
GET_DASHBOARD_ENERGY_COST_API,
GET_DASHBOARD_ALARMOPERATION_INFO_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";
@ -135,3 +138,41 @@ export const getDashboardProductRecord = async ({ start_time, end_time }) => {
}); });
}; };
export const getEnergyInfo = async (building_guid) => {
const res = await instance.post(GET_DASHBOARD_ENERGY_INFO_API, {
building_guid,
});
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
});
};
export const getEnergyCost = async ({
department_id,
floor_guid,
building_guid,
}) => {
const res = await instance.post(GET_DASHBOARD_ENERGY_COST_API, {
department_id,
floor_guid,
building_guid,
});
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
});
};
export const getAlarmOperationInfo = async (building_guid) => {
const res = await instance.post(GET_DASHBOARD_ALARMOPERATION_INFO_API, {
building_guid,
});
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
});
};

View File

@ -1,6 +1,7 @@
<script setup> <script setup>
import { onMounted, ref, provide, watch } from "vue"; import { onMounted, onUnmounted, ref, provide, watch } from "vue";
import { getDashboardInit } from "@/apis/dashboard"; import { getEnergyCost } from "@/apis/dashboard";
import ButtonConnectedGroup from "@/components/customUI/ButtonConnectedGroup.vue";
import Forge from "@/components/forge/Forge.vue"; import Forge from "@/components/forge/Forge.vue";
import DashboardStat from "./components/DashboardStat.vue"; import DashboardStat from "./components/DashboardStat.vue";
import DashboardSysCard from "./components/DashboardSysCard.vue"; import DashboardSysCard from "./components/DashboardSysCard.vue";
@ -8,46 +9,111 @@ import DashboardSysProgress from "./components/DashboardSysProgress.vue";
import DashboardElecRank from "./components/DashboardElecRank.vue"; import DashboardElecRank from "./components/DashboardElecRank.vue";
import DashboardElecTrends from "./components/DashboardElecTrends.vue"; import DashboardElecTrends from "./components/DashboardElecTrends.vue";
import DashboardElecCompare from "./components/DashboardElecCompare.vue"; import DashboardElecCompare from "./components/DashboardElecCompare.vue";
import useBuildingStore from "@/stores/useBuildingStore";
import useActiveBtn from "@/hooks/useActiveBtn";
import { twMerge } from "tailwind-merge";
const initialData = ref(null); const store = useBuildingStore();
// const forgeData = ref([]); const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
const init = async () => { let intervalId = null;
const res = await getDashboardInit(); const energyCostData = ref(null);
initialData.value = res.data; const formState = ref({
}; building_guid: null,
floor_guid: "all",
onMounted(() => { department_id: "all",
init();
}); });
const intervalOption = ref({}); const getEnergyCostData = async (params) => {
const currentIntervalType = ref(""); const res = await getEnergyCost(params);
energyCostData.value = res.data;
const openModal = (type) => {
currentIntervalType.value = type;
dashboard_more.showModal();
}; };
const decideIntervalOption = (option) => { watch(
intervalOption.value[currentIntervalType.value] = option; () => store.selectedBuilding,
}; (newBuilding) => {
if (newBuilding) {
formState.value.building_guid = newBuilding.building_guid;
}
},
{ immediate: true, deep: true }
);
provide("dashboard_items", { watch(
initialData, () => formState.value,
// forgeData, (newVal) => {
openModal, if (newVal) {
decideIntervalOption, const params = { ...newVal };
intervalOption,
currentIntervalType, if (params.floor_guid === "all") {
delete params.floor_guid;
}
if (params.department_id === "all") {
delete params.department_id;
}
if (params.building_guid) {
getEnergyCostData(params);
}
if (intervalId) {
clearInterval(intervalId);
}
intervalId = setInterval(() => {
getEnergyCostData(params);
}, 3600000);
}
},
{ immediate: true, deep: true }
);
onMounted(() => {
setItems([
{
title: "2D",
key: "2D",
active: false,
},
{
title: "3D",
key: "3D",
active: true,
},
]);
});
onUnmounted(() => {
clearInterval(intervalId);
}); });
</script> </script>
<template> <template>
<div class="flex flex-wrap items-center"> <div class="flex flex-wrap items-center">
<!-- 建築圖 --> <!-- 建築圖 -->
<div class="w-full xl:w-1/3"> <div class="w-full xl:w-1/3 relative">
<div class="area-img-box"> <ButtonConnectedGroup
<Forge /> :items="items"
className="btn-xs absolute right-3 top-6 z-20 bg-slate-800 p-0 rounded-lg "
:onclick="(e, item) => changeActiveBtn(item)"
/>
<div class="area-img-box relative">
<img
alt="build"
src="/build_img.jpg"
:class="
twMerge(
'absolute w-full h-full transition-opacity duration-300',
selectedBtn?.key == '2D' ? 'opacity-100 z-10' : 'opacity-0 z-0'
)
"
/>
<Forge
:class="
twMerge(
'absolute transition-opacity duration-300',
selectedBtn?.key == '3D' ? 'opacity-100 z-10' : 'opacity-0 z-0'
)
"
/>
</div> </div>
</div> </div>
@ -58,11 +124,15 @@ provide("dashboard_items", {
<div class="flex flex-wrap pt-4"> <div class="flex flex-wrap pt-4">
<!-- 當月能耗排行 --> <!-- 當月能耗排行 -->
<div class="lg:w-1/3 w-full"> <div class="lg:w-1/3 w-full">
<DashboardElecRank /> <DashboardElecRank :energyCostData="energyCostData" />
</div> </div>
<!-- 近30天能耗趨勢 --> <!-- 近30天能耗趨勢 -->
<div class="lg:w-2/3 w-full"> <div class="lg:w-2/3 w-full">
<DashboardElecTrends /> <DashboardElecTrends
:formState="formState"
:energyCostData="energyCostData"
:getEnergyCostData="getEnergyCostData"
/>
</div> </div>
</div> </div>
</div> </div>
@ -77,7 +147,7 @@ provide("dashboard_items", {
</div> </div>
<!-- 環比能耗 --> <!-- 環比能耗 -->
<div class="w-full lg:w-1/3"> <div class="w-full lg:w-1/3">
<DashboardElecCompare /> <DashboardElecCompare :energyCostData="energyCostData" />
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,41 +1,103 @@
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { ref, onMounted, watch } from "vue";
import * as echarts from "echarts"; import * as echarts from "echarts";
import BarChart from "@/components/chart/BarChart.vue"; import BarChart from "@/components/chart/BarChart.vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const { t } = useI18n(); const { t } = useI18n();
const chartData = ref([ const props = defineProps({
{ energyCostData: {
category: t("dashboard.daily_relative_change"), type: Object,
this: 230.68, required: true,
last: 377.33,
change: -39,
}, },
{ });
category: t("dashboard.weekly_relative_change"),
this: 608.01,
last: 2711.09,
change: -78,
},
{
category: t("dashboard.monthly_relative_change"),
this: 6473.8,
last: 12701.69,
change: -49,
},
{
category: t("dashboard.yearly_relative_change"),
this: 46687.17,
last: null,
change: null,
},
]);
const labels = [t("dashboard.today"), t("dashboard.yesterday"), t("dashboard.this_week"), t("dashboard.last_week"), t("dashboard.this_month"), t("dashboard.last_month"), t("dashboard.this_year"), t("dashboard.last_year")]; const chartData = ref([]); //
const labels = [
t("dashboard.today"),
t("dashboard.yesterday"),
t("dashboard.this_week"),
t("dashboard.last_week"),
t("dashboard.this_month"),
t("dashboard.last_month"),
t("dashboard.this_year"),
t("dashboard.last_year"),
];
const barWidth = 30; // Set barWidth const barWidth = 30; // Set barWidth
const barChartOptions = ref({ const barChartOptions = ref({
xAxis: {
type: "category",
data: chartData.value.map((item) => item.category),
axisLine: { lineStyle: { color: "#fff" } },
},
yAxis: { type: "value", show: false },
grid: {
left: "-10%",
right: "0%",
bottom: "3%",
top: "10%",
containLabel: true,
},
series: [], //
tooltip: {
trigger: "axis",
axisPointer: { type: "shadow" },
formatter: function (params) {
let tooltipText = `<div>${params[0].axisValueLabel}</div>`;
const filteredParams = params.filter(
(item) => item.seriesType === "bar"
);
filteredParams.forEach((item) => {
tooltipText += `<div>${item.marker} ${
item.value ? item.value : "-"
}</div>`;
});
return tooltipText;
},
},
});
// 使 watch energyCostData
watch(
() => props.energyCostData,
(newEnergyCostData) => {
if (newEnergyCostData && newEnergyCostData.compare) {
// props.energyCostData.compare
const compareData = newEnergyCostData.compare;
//
chartData.value = [
{
category: t("dashboard.daily_relative_change"),
this: compareData.day.current,
last: compareData.day.last,
change: compareData.day.percentage,
},
{
category: t("dashboard.weekly_relative_change"),
this: compareData.week.current,
last: compareData.week.last,
change: compareData.week.percentage,
},
{
category: t("dashboard.monthly_relative_change"),
this: compareData.month.current,
last: compareData.month.last,
change: compareData.month.percentage,
},
{
category: t("dashboard.yearly_relative_change"),
this: compareData.year.current,
last: compareData.year.last,
change: compareData.year.percentage,
},
];
// barChartOptions
barChartOptions.value = {
xAxis: { xAxis: {
type: "category", type: "category",
data: chartData.value.map((item) => item.category), data: chartData.value.map((item) => item.category),
@ -146,7 +208,9 @@ const barChartOptions = ref({
axisPointer: { type: "shadow" }, axisPointer: { type: "shadow" },
formatter: function (params) { formatter: function (params) {
let tooltipText = `<div>${params[0].axisValueLabel}</div>`; let tooltipText = `<div>${params[0].axisValueLabel}</div>`;
const filteredParams = params.filter((item) => item.seriesType === "bar"); const filteredParams = params.filter(
(item) => item.seriesType === "bar"
);
filteredParams.forEach((item) => { filteredParams.forEach((item) => {
tooltipText += `<div>${item.marker} ${ tooltipText += `<div>${item.marker} ${
item.value ? item.value : "-" item.value ? item.value : "-"
@ -156,7 +220,11 @@ const barChartOptions = ref({
return tooltipText; return tooltipText;
}, },
}, },
}); };
}
},
{ immediate: true } //
);
</script> </script>
<template> <template>

View File

@ -1,56 +1,48 @@
<script setup> <script setup>
import { ref } from "vue"; import { ref, watch } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const { t } = useI18n(); const { t } = useI18n();
// -
const mockEnergyData = {
monthly: [
{ id: "62090003", name: "電機-100HP 排氣扇", energy: 450, unit: "kWh" },
{ id: "62090113", name: "電機-75P 泵浦", energy: 80, unit: "kWh" },
{ id: "62090043", name: "電機-50P 電II", energy: 40, unit: "kWh" },
{ id: "62090038", name: "電機-50P 電II", energy: 35, unit: "kWh" },
{ id: "62090106", name: "電機-50P 泵浦", energy: 25, unit: "kWh" },
{ id: "62090086", name: "電機-30P 排氣", energy: 20, unit: "kWh" },
{ id: "19010225", name: "VOC 廢氣處理設備", energy: 15, unit: "kWh" },
{ id: "14010138", name: "固體燃料燒結機", energy: 10, unit: "kWh" },
],
daily: [
{ id: "62090003", name: "電機-100HP 排氣扇", energy: 132533, unit: "kWh" },
{ id: "62090113", name: "電機-75P 泵浦", energy: 24445, unit: "kWh" },
{ id: "62090043", name: "電機-50P 電II", energy: 11775, unit: "kWh" },
{ id: "62090038", name: "電機-50P 電II", energy: 10600, unit: "kWh" },
{ id: "62090106", name: "電機-50P 泵浦", energy: 6660, unit: "kWh" },
{ id: "62090086", name: "電機-30P 排氣", energy: 6579, unit: "kWh" },
{ id: "19010225", name: "VOC 廢氣處理設備", energy: 6367, unit: "kWh" },
{ id: "14010138", name: "固體燃料燒結機", energy: 2392, unit: "kWh" },
],
};
// const props = defineProps({
const currentEnergyType = ref("monthly"); energyCostData: {
type: Object,
required: true,
},
});
const currentEnergyType = ref("month");
//
const toggleEnergyType = () => { const toggleEnergyType = () => {
currentEnergyType.value = currentEnergyType.value =
currentEnergyType.value === "monthly" ? "daily" : "monthly"; currentEnergyType.value === "month" ? "day" : "month";
}; };
// //
const getCurrentEnergyData = () => { const getCurrentEnergyData = () => {
return currentEnergyType.value === "monthly" if (!props.energyCostData) {
? mockEnergyData.monthly return []; //
: mockEnergyData.daily; }
return currentEnergyType.value === "month"
? props.energyCostData?.rank.month || []
: props.energyCostData?.rank.day || [];
}; };
</script> </script>
<template> <template>
<div class="state-box-col relative ps-2"> <div class="state-box-col relative ps-2 h-full max-h-[350px]">
<div class="state-box"> <div class="state-box h-full max-h-[350px] overflow-y-auto">
<!-- 標題和切換按鈕 --> <!-- 標題和切換按鈕 -->
<div class="flex justify-between items-center mb-4"> <div class="flex justify-between items-center mb-4">
<h2 class="font-light relative">{{$t("dashboard.energy_ranking")}}</h2> <h2 class="font-light relative">
{{ $t("dashboard.energy_ranking") }}
</h2>
<button @click="toggleEnergyType" class="btn btn-info btn-xs"> <button @click="toggleEnergyType" class="btn btn-info btn-xs">
{{ currentEnergyType === "monthly" ? t("dashboard.today_energy_consumption") : t("dashboard.this_month_energy_consumption") }} {{
currentEnergyType === "month"
? t("dashboard.today_energy_consumption")
: t("dashboard.this_month_energy_consumption")
}}
</button> </button>
</div> </div>
@ -59,7 +51,7 @@ const getCurrentEnergyData = () => {
<tbody> <tbody>
<tr <tr
v-for="(item, index) in getCurrentEnergyData()" v-for="(item, index) in getCurrentEnergyData()"
:key="item.id" :key="index"
:class="[ :class="[
{ 'text-red-300': index + 1 === 1 }, { 'text-red-300': index + 1 === 1 },
{ 'text-orange-300': index + 1 === 2 }, { 'text-orange-300': index + 1 === 2 },
@ -73,7 +65,7 @@ const getCurrentEnergyData = () => {
}} }}
</td> </td>
<td>{{ item.name }}</td> <td>{{ item.name }}</td>
<td>{{ item.energy }} {{ item.unit }}</td> <td>{{ item.value }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -3,36 +3,36 @@ import { ref, onMounted, watch } from "vue";
import * as echarts from "echarts"; import * as echarts from "echarts";
import BarChart from "@/components/chart/BarChart.vue"; import BarChart from "@/components/chart/BarChart.vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import dayjs from "dayjs";
import useBuildingStore from "@/stores/useBuildingStore"; import useBuildingStore from "@/stores/useBuildingStore";
const storeBuild = useBuildingStore(); const storeBuild = useBuildingStore();
const { t } = useI18n(); const { t } = useI18n();
const formState = ref({
floorId: 0, const props = defineProps({
deptId: 0, formState: {
type: Object,
required: true,
},
energyCostData: {
type: Object,
required: true,
},
getEnergyCostData: {
type: Function,
required: true,
}
}); });
// API store
const energyData = ref([
{ date: "11-18", energy: 500.1 }, const chartData = ref([]);
{ date: "11-20", energy: 400 }, const floorList = ref([]);
{ date: "11-22", energy: 450 }, const deptList = ref([]);
{ date: "11-24", energy: 420 }, const weekComparisonOption = ref({});
{ date: "11-26", energy: 430 },
{ date: "11-28", energy: 460 },
{ date: "11-30", energy: 480 },
{ date: "12-02", energy: 410 },
{ date: "12-04", energy: 440 },
{ date: "12-06", energy: 450 },
{ date: "12-08", energy: 470 },
{ date: "12-10", energy: 399.26 },
{ date: "12-12", energy: 380 },
{ date: "12-14", energy: 320 },
{ date: "12-16", energy: 230.68 },
]);
// option // option
const generateCylinderChartOption = (data) => { const generateCylinderChartOption = (data) => {
const barWidth = 25; const barWidth = 15;
return { return {
xAxis: { xAxis: {
type: "category", type: "category",
@ -71,7 +71,7 @@ const generateCylinderChartOption = (data) => {
}, },
}, },
{ {
z: 12, z: 15,
type: "pictorialBar", type: "pictorialBar",
symbolPosition: "end", symbolPosition: "end",
data: data.map((item) => item.energy), data: data.map((item) => item.energy),
@ -83,7 +83,7 @@ const generateCylinderChartOption = (data) => {
}, },
}, },
{ {
z: 12, z: 10,
type: "pictorialBar", type: "pictorialBar",
data: data.map((item) => item.energy), data: data.map((item) => item.energy),
symbol: "diamond", symbol: "diamond",
@ -105,26 +105,92 @@ const generateCylinderChartOption = (data) => {
trigger: "axis", trigger: "axis",
formatter: function (params) { formatter: function (params) {
const item = params[0]; const item = params[0];
return `<p>${item.name}</p> <p>${item.marker}能耗 : ${item.value}</p>`; return `<p>${item.name}</p> <p>${item.marker}Energy consumption : ${item.value}</p>`;
}, },
}, },
}; };
}; };
const weekComparisonOption = generateCylinderChartOption(energyData.value); const processEnergyData = () => {
if (!props.energyCostData || !props.energyCostData.trend) {
chartData.value = [];
weekComparisonOption.value = generateCylinderChartOption(chartData.value);
return;
}
const dailyData = props.energyCostData.trend;
chartData.value = dailyData.map((item) => ({
date: dayjs(item.time).format("MM/DD"),
energy: item.value,
}));
weekComparisonOption.value = generateCylinderChartOption(chartData.value);
};
watch( watch(
() => [storeBuild.deptList.length, storeBuild.floorList.length], () => props.energyCostData,
([deptListLength, floorListLength]) => { (newEnergyCostData) => {
if (deptListLength > 0 && floorListLength > 0) { processEnergyData();
formState.value = { },
...formState.value, { deep: true, immediate: true }
floorId: storeBuild.floorList[0].key, );
deptId: storeBuild.deptList[0].key,
}; watch(
() => storeBuild.floorList,
(newValue) => {
if (newValue) {
console.log('newValue',newValue);
floorList.value = [
{
title: "All",
key: "all",
},
...storeBuild.floorList,
];
} }
}, },
{ immediate: true } {
immediate: true,
}
);
watch(
() => storeBuild.floorList,
(newValue) => {
if (newValue) {
floorList.value = [
{
title: "All",
key: "all",
},
...storeBuild.floorList,
];
}
},
{
immediate: true,
}
);
watch(
() => storeBuild.deptList,
(newValue) => {
if (newValue) {
deptList.value = [
{
title: "All",
key: "all",
},
...storeBuild.deptList,
];
}
},
{
deep: true,
immediate: true,
}
); );
</script> </script>
@ -134,23 +200,23 @@ watch(
<h2 class="font-light">{{ $t("dashboard.last_30_days_energy_trend") }}</h2> <h2 class="font-light">{{ $t("dashboard.last_30_days_energy_trend") }}</h2>
<div class="flex items-center w-52 gap-4"> <div class="flex items-center w-52 gap-4">
<Select <Select
:value="formState" :value="props.formState"
class="my-2" class="my-2"
selectClass="border-info focus-within:border-info btn-xs text-xs" selectClass="border-info focus-within:border-info btn-xs text-xs"
name="floorId" name="floor_guid"
Attribute="title" Attribute="title"
:options="storeBuild.floorList" :options="floorList"
:isTopLabelExist="false" :isTopLabelExist="false"
:isBottomLabelExist="false" :isBottomLabelExist="false"
> >
</Select> </Select>
<Select <Select
:value="formState" :value="props.formState"
class="my-2" class="my-2"
selectClass="border-info focus-within:border-info btn-xs text-xs" selectClass="border-info focus-within:border-info btn-xs text-xs"
name="deptId" name="department_id"
Attribute="title" Attribute="title"
:options="storeBuild.deptList" :options="deptList"
:isTopLabelExist="false" :isTopLabelExist="false"
:isBottomLabelExist="false" :isBottomLabelExist="false"
> >

View File

@ -1,31 +1,75 @@
<script setup> <script setup>
import dayjs from "dayjs"; import { ref, watch, onMounted, onUnmounted } from "vue";
import { computed } from "vue"; import { getEnergyInfo } from "@/apis/dashboard";
import useBuildingStore from "@/stores/useBuildingStore";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
const mockData = [
const store = useBuildingStore();
const energyData = ref([]);
let intervalId = null;
const getEnergyInfos = async () => {
try {
const res = await getEnergyInfo(store.selectedBuilding.building_guid);
const apiData = res.data;
energyData.value = [
{ {
value: "305.50", value: apiData.todayKWH ? apiData.todayKWH : "N/A",
label: "Today's electricity consumption in kWH", label: "Today's electricity consumption in kWH",
}, },
{ {
value: "886.75", value: apiData.yesterdayKWH ? apiData.yesterdayKWH : "N/A",
label: "Yesterday's electricity consumption in kWH", label: "Yesterday's electricity consumption in kWH",
}, },
{ {
value: "7.84", value: apiData.instantKW ? apiData.instantKW : "N/A",
label: "Instant power kW", label: "Instant power kW",
}, },
{ {
value: "1.96", value: apiData.instantContractRatio
? apiData.instantContractRatio
: "N/A",
label: "Instant contract capacity ratio %", label: "Instant contract capacity ratio %",
}, },
]; ];
} catch (error) {
console.error("Error fetching energy info:", error);
energyData.value = [
{ value: "N/A", label: "Today's electricity consumption in kWH" },
{ value: "N/A", label: "Yesterday's electricity consumption in kWH" },
{ value: "N/A", label: "Instant power kW" },
{ value: "N/A", label: "Instant contract capacity ratio %" },
];
}
};
watch(
() => store.selectedBuilding,
(newBuilding) => {
if (newBuilding) {
getEnergyInfos();
if (intervalId) {
clearInterval(intervalId);
}
intervalId = setInterval(() => {
getEnergyInfos();
}, 30000);
}
},
{ immediate: true }
);
onUnmounted(() => {
clearInterval(intervalId);
});
</script> </script>
<template> <template>
<div class="flex flex-wrap"> <div class="flex flex-wrap">
<div <div
v-for="(item, index) in mockData" v-for="(item, index) in energyData"
:key="index" :key="index"
class="xl:w-1/4 md:w-1/2 w-full item-data-box relative px-3" class="xl:w-1/4 md:w-1/2 w-full item-data-box relative px-3"
> >

View File

@ -1,35 +1,102 @@
<script setup> <script setup>
import { ref, computed } from "vue"; import { ref, computed, watch, onUnmounted } from "vue";
import { useRouter } from "vue-router";
import { getAlarmOperationInfo } from "@/apis/dashboard";
import useBuildingStore from "@/stores/useBuildingStore";
import DashboardSysProgressModal from "./DashboardSysProgressModal.vue"; import DashboardSysProgressModal from "./DashboardSysProgressModal.vue";
const router = useRouter();
const store = useBuildingStore();
const equipmentData = ref({ const equipmentData = ref({
title: "System Status", title: "System Status",
items: [ items: [],
{ label: "Auxiliary", online: 6, offline: 0, alarm: 0 },
{ label: "Air Detection", online: 31, offline: 0, alarm: 2 },
{ label: "Electricity", online: 12, offline: 0, alarm: 1 },
{ label: "Lighting", online: 20, offline: 3, alarm: 0 },
{ label: "Air Condition", online: 23, offline: 0, alarm: 0 },
],
}); });
const orderData = ref({ const orderData = ref({
title: "Work Order", title: "Work Order",
items: [ items: [],
{ label: "Unassigned", value: 2 },
{ label: "Assigned", value: 4 },
{ label: "Completed", value: 1 },
],
}); });
const modalData = ref({});
let intervalId = null;
const openModal = () => { const getAlarmsInfos = async () => {
try {
const res = await getAlarmOperationInfo(
store.selectedBuilding.building_guid
);
const apiData = res.data;
// equipmentData
if (apiData && apiData.alarm) {
equipmentData.value.items = apiData.alarm.map((item) => ({
label: item.name,
online: item.online || 0,
offline: item.offline || 0,
alarm: item.alarm || 0,
}));
}
// orderData
if (apiData && apiData.operation) {
orderData.value.items = [
{
label: "Repair",
complete: apiData.operation.repair.complete || 0,
incomplete: apiData.operation.repair.incomplete || 0,
},
{
label: "Upkeep",
complete: apiData.operation.upkeep.complete || 0,
incomplete: apiData.operation.upkeep.incomplete || 0,
},
];
}
} catch (error) {
console.error("Error fetching alarm info:", error);
}
};
const navigateToMaintenance = (item) => {
router.push({
name: "operation",
query: {
work_type: item == "Repair" ? "2" : "1",
},
});
};
const openModal = (item) => {
modalData.value = item;
system_status_modal.showModal(); system_status_modal.showModal();
}; };
const onCancel = () => {
modalData.value = {};
system_status_modal.close();
};
watch(
() => store.selectedBuilding,
(newBuilding) => {
if (newBuilding) {
getAlarmsInfos();
if (intervalId) {
clearInterval(intervalId);
}
intervalId = setInterval(() => {
getAlarmsInfos();
}, 30000);
}
},
{ immediate: true }
);
onUnmounted(() => {
clearInterval(intervalId);
});
</script> </script>
<template> <template>
<DashboardSysProgressModal <DashboardSysProgressModal :onCancel="onCancel" :modalData="modalData" />
/>
<div class="flex flex-wrap"> <div class="flex flex-wrap">
<div class="w-full sm:w-2/5 state-box-col relative ps-2"> <div class="w-full sm:w-2/5 state-box-col relative ps-2">
<div class="state-box"> <div class="state-box">
@ -42,7 +109,8 @@ const openModal = () => {
<thead> <thead>
<tr class="border-cyan-400 text-cyan-100"> <tr class="border-cyan-400 text-cyan-100">
<th></th> <th></th>
<th>value</th> <th>Comp</th>
<th>Inc</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -50,12 +118,13 @@ const openModal = () => {
v-for="(item, index) in orderData.items" v-for="(item, index) in orderData.items"
:key="index" :key="index"
class="border-cyan-400 cursor-pointer hover:text-info" class="border-cyan-400 cursor-pointer hover:text-info"
@click.stop.prevent="() => openModal()" @click.stop.prevent="navigateToMaintenance(item.label)"
> >
<th class="px-0 text-start"> <th class="px-0 text-start">
<span>{{ item.label }}</span> <span>{{ item.label }}</span>
</th> </th>
<td>{{ item.value }}</td> <td>{{ item.complete }}</td>
<td>{{ item.incomplete }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -82,12 +151,35 @@ const openModal = () => {
v-for="(item, index) in equipmentData.items" v-for="(item, index) in equipmentData.items"
:key="index" :key="index"
class="border-cyan-400 cursor-pointer hover:text-info" class="border-cyan-400 cursor-pointer hover:text-info"
@click.stop.prevent="() => openModal()"
> >
<th class="px-0 text-start">{{ item.label }}</th> <th class="px-0 text-start">{{ item.label }}</th>
<td>{{ item.online }}</td> <td
<td>{{ item.offline }}</td> @click.stop.prevent="
<td>{{ item.alarm }}</td> item.online && item.online.length > 0
? openModal(item.online)
: null
"
>
{{ item.online.length }}
</td>
<td
@click.stop.prevent="
item.offline && item.offline.length > 0
? openModal(item.offline)
: null
"
>
{{ item.offline.length }}
</td>
<td
@click.stop.prevent="
item.alarm && item.alarm.length > 0
? openModal(item.alarm)
: null
"
>
{{ item.alarm.length }}
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -126,7 +218,7 @@ const openModal = () => {
} }
.state-box .title { .state-box .title {
@apply relative flex items-center mb-4; @apply relative flex items-center mb-1;
} }
.state-box .title .state-title01 { .state-box .title .state-title01 {

View File

@ -2,44 +2,23 @@
import { ref, onMounted, defineProps, inject, watch } from "vue"; import { ref, onMounted, defineProps, inject, watch } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const { t } = useI18n(); const { t } = useI18n();
const { openToast } = inject("app_toast"); const props = defineProps({
onCancel: Function,
const onCancel = () => { modalData: Object,
system_status_modal.close(); });
};
// -
const mockEquipmentData = ref([
{ name: "12030182 (放空機-30噸)", lastOnline: "2024-12-17 17:15:00" },
{ name: "12080043 (空壓機-50P 日立)", lastOnline: "2024-12-17 17:15:00" },
{ name: "12030204 (注塑機-40噸)", lastOnline: "2024-12-17 17:15:00" },
{ name: "12040084 (冷卻水)", lastOnline: "2024-12-17 17:15:00" },
{ name: "11060241 (蒸騰式冷氣機)", lastOnline: "2024-12-17 17:15:00" },
{ name: "12060010 (串壓機-啟業)", lastOnline: "2024-12-17 17:15:00" },
{ name: "12020005 (串壓機-工業30噸)", lastOnline: "2024-12-17 17:15:00" },
{ name: "12030179 (注塑機-30噸)", lastOnline: "2024-12-17 17:15:00" },
{ name: "12040145 (串壓機-三銘)", lastOnline: "2024-12-17 17:15:00" },
{
name: "12030163 (注氨全自動加氨型-00:00-01:00)",
lastOnline: "2024-12-17 17:15:00",
},
]);
</script> </script>
<template> <template>
<Modal id="system_status_modal" :onCancel="onCancel" :width="600"> <Modal id="system_status_modal" :onCancel="onCancel" :width="600">
<template #modalTitle> <template #modalTitle>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h2>在線設備</h2> <h2></h2>
<Button <Button
type="link" type="link"
class="btn-link btn-text-without-border px-2" class="btn-link btn-text-without-border px-2"
@click="onCancel" @click="onCancel"
> >
<font-awesome-icon <font-awesome-icon :icon="['fas', 'times']" class="text-[#a5abb1]" />
:icon="['fas', 'times']"
class="text-[#a5abb1]"
/>
</Button> </Button>
</div> </div>
</template> </template>
@ -51,26 +30,34 @@ const mockEquipmentData = ref([
<th <th
class="text-base border text-white text-center bg-cyan-600 bg-opacity-30" class="text-base border text-white text-center bg-cyan-600 bg-opacity-30"
> >
設備名稱 Serial Number
</th> </th>
<th <th
class="text-base border text-white text-center bg-cyan-600 bg-opacity-30" class="text-base border text-white text-center bg-cyan-600 bg-opacity-30"
> >
最近在線時間 Name
</th>
<th
class="text-base border text-white text-center bg-cyan-600 bg-opacity-30"
>
Time
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr <tr
v-for="(equipment, index) in mockEquipmentData" v-for="(equipment, index) in modalData"
:key="index" :key="index"
class="hover:bg-gray-700" class="hover:bg-gray-700"
> >
<td class="border text-white text-center">
{{ index+1 }}
</td>
<td class="border text-white text-center"> <td class="border text-white text-center">
{{ equipment.name }} {{ equipment.name }}
</td> </td>
<td class="border text-white text-center"> <td class="border text-white text-center">
{{ equipment.lastOnline }} {{ equipment.time || "-" }}
</td> </td>
</tr> </tr>
</tbody> </tbody>