首頁
This commit is contained in:
parent
b55ba4003d
commit
0772269c33
BIN
public/build_img.jpg
Normal file
BIN
public/build_img.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 137 KiB |
@ -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`
|
@ -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,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@ -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>
|
||||||
|
|
||||||
@ -57,12 +123,16 @@ 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>
|
||||||
|
@ -1,38 +1,29 @@
|
|||||||
<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({
|
||||||
@ -49,104 +40,15 @@ const barChartOptions = ref({
|
|||||||
top: "10%",
|
top: "10%",
|
||||||
containLabel: true,
|
containLabel: true,
|
||||||
},
|
},
|
||||||
series: [
|
series: [], // 初始化為空陣列
|
||||||
{
|
|
||||||
name: "當前週期",
|
|
||||||
data: chartData.value.map((item) => item.this),
|
|
||||||
type: "bar",
|
|
||||||
barWidth: barWidth,
|
|
||||||
barGap: "-10%",
|
|
||||||
itemStyle: {
|
|
||||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
||||||
{ offset: 0, color: "#186B80" },
|
|
||||||
{ offset: 1, color: "#50C3E3" },
|
|
||||||
]),
|
|
||||||
shadowBlur: 5,
|
|
||||||
shadowColor: "rgba(0, 0, 0, 0.3)",
|
|
||||||
shadowOffsetY: 2,
|
|
||||||
shadowOffsetX: 5,
|
|
||||||
},
|
|
||||||
z: 3,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "對比週期",
|
|
||||||
data: chartData.value.map((item) => item.last),
|
|
||||||
type: "bar",
|
|
||||||
barWidth: barWidth,
|
|
||||||
itemStyle: {
|
|
||||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
||||||
{ offset: 0, color: "#988F2C" },
|
|
||||||
{ offset: 1, color: "#FFF26D" },
|
|
||||||
]),
|
|
||||||
shadowBlur: 5,
|
|
||||||
shadowColor: "rgba(0, 0, 0, 0.3)",
|
|
||||||
shadowOffsetY: 2,
|
|
||||||
shadowOffsetX: 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// this top
|
|
||||||
z: 6,
|
|
||||||
type: "pictorialBar",
|
|
||||||
symbolPosition: "end",
|
|
||||||
data: chartData.value.map((item) => item.this),
|
|
||||||
symbol: "diamond",
|
|
||||||
symbolOffset: ["-45%", "-50%"],
|
|
||||||
symbolSize: [barWidth, barWidth * 0.5],
|
|
||||||
itemStyle: {
|
|
||||||
borderWidth: 0,
|
|
||||||
color: "#50C3E3",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// this bot
|
|
||||||
z: 6,
|
|
||||||
type: "pictorialBar",
|
|
||||||
symbolPosition: "start",
|
|
||||||
data: chartData.value.map((item) => item.this),
|
|
||||||
symbol: "diamond",
|
|
||||||
symbolOffset: ["-45%", "50%"],
|
|
||||||
symbolSize: [barWidth, barWidth * 0.5],
|
|
||||||
itemStyle: {
|
|
||||||
borderWidth: 0,
|
|
||||||
color: "#50C3E3",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// last top
|
|
||||||
z: 3,
|
|
||||||
type: "pictorialBar",
|
|
||||||
symbolPosition: "end",
|
|
||||||
data: chartData.value.map((item) => item.last),
|
|
||||||
symbol: "diamond",
|
|
||||||
symbolOffset: ["45%", "-50%"],
|
|
||||||
symbolSize: [barWidth, barWidth * 0.5],
|
|
||||||
itemStyle: {
|
|
||||||
borderWidth: 0,
|
|
||||||
color: "#FFF26D",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// last bot
|
|
||||||
z: 3,
|
|
||||||
type: "pictorialBar",
|
|
||||||
symbolPosition: "start",
|
|
||||||
data: chartData.value.map((item) => item.last),
|
|
||||||
symbol: "diamond",
|
|
||||||
symbolOffset: ["45%", "50%"],
|
|
||||||
symbolSize: [barWidth, barWidth * 0.5],
|
|
||||||
itemStyle: {
|
|
||||||
borderWidth: 0,
|
|
||||||
color: "#FFF26D",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: "axis",
|
trigger: "axis",
|
||||||
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 : "-"
|
||||||
@ -157,6 +59,172 @@ const barChartOptions = ref({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 使用 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: {
|
||||||
|
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: [
|
||||||
|
{
|
||||||
|
name: "當前週期",
|
||||||
|
data: chartData.value.map((item) => item.this),
|
||||||
|
type: "bar",
|
||||||
|
barWidth: barWidth,
|
||||||
|
barGap: "-10%",
|
||||||
|
itemStyle: {
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: "#186B80" },
|
||||||
|
{ offset: 1, color: "#50C3E3" },
|
||||||
|
]),
|
||||||
|
shadowBlur: 5,
|
||||||
|
shadowColor: "rgba(0, 0, 0, 0.3)",
|
||||||
|
shadowOffsetY: 2,
|
||||||
|
shadowOffsetX: 5,
|
||||||
|
},
|
||||||
|
z: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "對比週期",
|
||||||
|
data: chartData.value.map((item) => item.last),
|
||||||
|
type: "bar",
|
||||||
|
barWidth: barWidth,
|
||||||
|
itemStyle: {
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: "#988F2C" },
|
||||||
|
{ offset: 1, color: "#FFF26D" },
|
||||||
|
]),
|
||||||
|
shadowBlur: 5,
|
||||||
|
shadowColor: "rgba(0, 0, 0, 0.3)",
|
||||||
|
shadowOffsetY: 2,
|
||||||
|
shadowOffsetX: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// this top
|
||||||
|
z: 6,
|
||||||
|
type: "pictorialBar",
|
||||||
|
symbolPosition: "end",
|
||||||
|
data: chartData.value.map((item) => item.this),
|
||||||
|
symbol: "diamond",
|
||||||
|
symbolOffset: ["-45%", "-50%"],
|
||||||
|
symbolSize: [barWidth, barWidth * 0.5],
|
||||||
|
itemStyle: {
|
||||||
|
borderWidth: 0,
|
||||||
|
color: "#50C3E3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// this bot
|
||||||
|
z: 6,
|
||||||
|
type: "pictorialBar",
|
||||||
|
symbolPosition: "start",
|
||||||
|
data: chartData.value.map((item) => item.this),
|
||||||
|
symbol: "diamond",
|
||||||
|
symbolOffset: ["-45%", "50%"],
|
||||||
|
symbolSize: [barWidth, barWidth * 0.5],
|
||||||
|
itemStyle: {
|
||||||
|
borderWidth: 0,
|
||||||
|
color: "#50C3E3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// last top
|
||||||
|
z: 3,
|
||||||
|
type: "pictorialBar",
|
||||||
|
symbolPosition: "end",
|
||||||
|
data: chartData.value.map((item) => item.last),
|
||||||
|
symbol: "diamond",
|
||||||
|
symbolOffset: ["45%", "-50%"],
|
||||||
|
symbolSize: [barWidth, barWidth * 0.5],
|
||||||
|
itemStyle: {
|
||||||
|
borderWidth: 0,
|
||||||
|
color: "#FFF26D",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// last bot
|
||||||
|
z: 3,
|
||||||
|
type: "pictorialBar",
|
||||||
|
symbolPosition: "start",
|
||||||
|
data: chartData.value.map((item) => item.last),
|
||||||
|
symbol: "diamond",
|
||||||
|
symbolOffset: ["45%", "50%"],
|
||||||
|
symbolSize: [barWidth, barWidth * 0.5],
|
||||||
|
itemStyle: {
|
||||||
|
borderWidth: 0,
|
||||||
|
color: "#FFF26D",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true } // 立即執行一次,確保初始資料載入
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -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>
|
||||||
|
@ -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,52 +105,118 @@ 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>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full chart-data relative px-8 py-1">
|
<div class="w-full chart-data relative px-8 py-1">
|
||||||
<div class="flex flex-wrap items-center justify-between">
|
<div class="flex flex-wrap items-center justify-between">
|
||||||
<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"
|
||||||
>
|
>
|
||||||
|
@ -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();
|
||||||
value: "305.50",
|
const energyData = ref([]);
|
||||||
label: "Today's electricity consumption in kWH",
|
let intervalId = null;
|
||||||
|
|
||||||
|
const getEnergyInfos = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getEnergyInfo(store.selectedBuilding.building_guid);
|
||||||
|
const apiData = res.data;
|
||||||
|
|
||||||
|
energyData.value = [
|
||||||
|
{
|
||||||
|
value: apiData.todayKWH ? apiData.todayKWH : "N/A",
|
||||||
|
label: "Today's electricity consumption in kWH",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: apiData.yesterdayKWH ? apiData.yesterdayKWH : "N/A",
|
||||||
|
label: "Yesterday's electricity consumption in kWH",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: apiData.instantKW ? apiData.instantKW : "N/A",
|
||||||
|
label: "Instant power kW",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: apiData.instantContractRatio
|
||||||
|
? apiData.instantContractRatio
|
||||||
|
: "N/A",
|
||||||
|
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 }
|
||||||
value: "886.75",
|
);
|
||||||
label: "Yesterday's electricity consumption in kWH",
|
|
||||||
},
|
onUnmounted(() => {
|
||||||
{
|
clearInterval(intervalId);
|
||||||
value: "7.84",
|
});
|
||||||
label: "Instant power kW",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "1.96",
|
|
||||||
label: "Instant contract capacity ratio %",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
</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"
|
||||||
>
|
>
|
||||||
@ -92,7 +136,7 @@ const mockData = [
|
|||||||
}
|
}
|
||||||
|
|
||||||
.item-data h2:after {
|
.item-data h2:after {
|
||||||
@apply absolute left-[5%] right-[5%] bottom-0 w-[90%] h-[10px] box-border border-b-2 border-[#58bfe8] ;
|
@apply absolute left-[5%] right-[5%] bottom-0 w-[90%] h-[10px] box-border border-b-2 border-[#58bfe8];
|
||||||
content: "";
|
content: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>
|
||||||
@ -81,13 +150,36 @@ const openModal = () => {
|
|||||||
<tr
|
<tr
|
||||||
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 {
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user