Compare commits

...

2 Commits

16 changed files with 186 additions and 131 deletions

3
auto-imports.d.ts vendored
View File

@ -6,5 +6,6 @@
// biome-ignore lint: disable
export {}
declare global {
const ElLoading: typeof import('element-plus/es')['ElLoading']
const ElMessage: typeof import('element-plus/es')['ElMessage']
}

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>EMS</title>
<script type="text/javascript" src="/requirejs/config.js?"></script>

View File

@ -41,8 +41,7 @@ const initChart = () => {
type: "value",
},
series: props.chartData.series.map((item) => ({
...item,
stack: "total",
...item
})),
};
@ -63,8 +62,11 @@ watch(
},
series: newChartData.series.map((item) => ({
...item,
stack: "total",
})),
legend: {
data: newChartData.series.map((item) => item.name),
bottom: "0%",
},
});
}
},

View File

@ -18,6 +18,9 @@ const seriesData = ref({
kWAlarmRest: [], //
});
//
const MAX_DATA_LENGTH = 10;
// 使 dayjs HH:mm:ss
const getCurrentTimestamp = () => {
return dayjs().format("HH:mm:ss");
@ -55,6 +58,14 @@ const updateChartData = (elecData) => {
}
});
//
if (categories.value.length > MAX_DATA_LENGTH) {
categories.value.shift(); //
Object.keys(seriesData.value).forEach((key) => {
seriesData.value[key].shift(); //
});
}
//
if (chartInstance) {
chartInstance.setOption({

View File

@ -14,17 +14,25 @@ const updateChart = (elecData) => {
}
//
// const totalElectricity = elecData.reduce((sum, item) => sum + item.out, 0);
const totalElectricity = elecData.reduce((sum, item) => sum + item.out, 0);
// data links
const chartData = [
{ name: "總用電", itemStyle: { color: "#038a77" } },
...elecData.map((item) => ({ name: item.displayName })),
...elecData.map((item) => {
const percentage = ((item.out / totalElectricity) * 100).toFixed(2);
return {
name: `${item.displayName} (${percentage}%)`, //
};
}),
];
const chartLinks = elecData.map((item) => ({
source: "總用電",
target: item.displayName,
target: `${item.displayName} (${(
(item.out / totalElectricity) *
100
).toFixed(2)}%)`,
value: Number(item.out),
}));

View File

@ -5,36 +5,33 @@
mode="horizontal"
@select="handleSelect"
>
<el-menu-item index="chart">能源圖表</el-menu-item>
<el-menu-item index="EnergyChart">能源圖表</el-menu-item>
<el-menu-item index="elecPricing">電價表</el-menu-item>
<el-menu-item index="monthlyReport">能源報表</el-menu-item>
</el-menu>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { useRouter } from "vue-router";
import { ref, onMounted } from "vue";
import { useRouter, useRoute } from "vue-router";
const router = useRouter();
const activeIndex = ref("chart"); // Set initial active index
const handleSelect = (key: string, keyPath: string[]) => {
console.log(key, keyPath);
const route = useRoute();
switch (key) {
case "chart":
router.push({ name: "EnergyChart" });
activeIndex.value = "chart";
break;
case "elecPricing":
router.push({ name: "elecPricing" });
activeIndex.value = "elecPricing";
break;
case "monthlyReport":
router.push({ name: "monthlyReport" });
activeIndex.value = "monthlyReport";
break;
}
const activeIndex = ref("");
const updateActiveIndex = () => {
activeIndex.value = String(route.name || "");
};
const handleSelect = (key: string) => {
router.push({ name: key });
activeIndex.value = key;
};
onMounted(() => {
updateActiveIndex();
});
</script>
<style scoped></style>

View File

@ -392,18 +392,21 @@ const elecUsageData = computed(() => {
type: "bar",
data: peakData,
itemStyle: { color: "#5470c6" },
stack: "total",
},
{
name: "半尖峰用電",
type: "bar",
data: halfPeakData,
itemStyle: { color: "#91cc75" },
stack: "total",
},
{
name: "離峰用電",
type: "bar",
data: offPeakData,
itemStyle: { color: "#fac858" },
stack: "total",
},
],
};

View File

@ -1,4 +1,4 @@
import { createRouter, createWebHistory } from "vue-router";
import { createRouter, createWebHashHistory } from "vue-router";
import type { RouteRecordRaw } from "vue-router";
const routes: Array<RouteRecordRaw> = [
@ -37,7 +37,7 @@ const routes: Array<RouteRecordRaw> = [
];
const router = createRouter({
history: createWebHistory(),
history: createWebHashHistory(import.meta.env.BASE_URL),
routes,
});

View File

@ -14,7 +14,7 @@ const useElecDemandStore = defineStore("elecDemand", () => {
window.requirejs(["baja!"], (baja: any) => {
let eleclist: NiagaraElecDemandData[] = [];
baja.Ord.make(
`local:|foxs:4918|station:|neql:EMS:kw|bql:select slotPath,parent.displayName,name,out`
`local:|foxs:|station:|neql:EMS:kw|bql:select slotPath,parent.displayName,name,out`
).get({
cursor: {
before: () => {},
@ -41,7 +41,7 @@ const useElecDemandStore = defineStore("elecDemand", () => {
const subscribeToHistory = (item: NiagaraElecDemandData, index: number) => {
const slotPath = item.slotPath;
const ordString = `local:|foxs:4918|station:|${slotPath}`;
const ordString = `local:|foxs:|station:|${slotPath}`;
// @ts-ignore
window.require &&

View File

@ -16,7 +16,7 @@ const useElecStore = defineStore("elecDist", () => {
console.log("進入 bajaSubscriber 準備執行 BQL 訂閱");
// 定義BQL 查詢
const subSysKwhBql = `local:|foxs:4918|station:|neql:EMS:SubSys_kwh|bql:select slotPath,parent.displayName,displayName,NumericInterval.historyConfig.id`;
const subSysKwhBql = `local:|foxs:|station:|neql:EMS:SubSys_kwh|bql:select slotPath,parent.displayName,displayName,NumericInterval.historyConfig.id`;
// 執行各電表的 BQL 查詢
fetchElecData(baja, subSysKwhBql);
@ -51,12 +51,12 @@ const useElecStore = defineStore("elecDist", () => {
};
const subscribeToHistory = (item: NiagaraElecData) => {
const startTime = dayjs()
const startTime = dayjs("2025-05-08T16:30:00.000+08:00")
.subtract(2, "hour")
.format("YYYY-MM-DDTHH:mm:ss.000+08:00"); // 現在時間前2個小時
const endTime = dayjs().format("YYYY-MM-DDTHH:mm:ss.000+08:00"); // 現在的時間
const endTime = dayjs("2025-05-08T16:30:00.000+08:00").format("YYYY-MM-DDTHH:mm:ss.000+08:00"); // 現在的時間
const id = item.id;
const ordString = `local:|foxs:4918|history:${id}?period=timerange;start=${startTime};end=${endTime}|bql:history:HistoryRollup.rollup(baja:RelTime '3600000')`;
const ordString = `local:|foxs:|history:${id}?period=timerange;start=${startTime};end=${endTime}|bql:history:HistoryRollup.rollup(baja:RelTime '3600000')`;
console.log(ordString);
// @ts-ignore
window.require &&

View File

@ -16,7 +16,7 @@ const useElecPriceStore = defineStore("elecPriceData", () => {
console.log("進入 bajaSubscriber 準備執行 BQL 訂閱");
// 定義BQL 查詢
const Total_kwhBql = `local:|foxs:4918|station:|neql:EMS:parameter|bql:select slotPath,parent.displayName,displayName,NumericInterval.historyConfig.id,out`;
const Total_kwhBql = `local:|foxs:|station:|neql:EMS:parameter|bql:select slotPath,parent.displayName,displayName,NumericInterval.historyConfig.id,out`;
// 執行查詢
fetchElecData(baja, Total_kwhBql);
@ -51,7 +51,7 @@ const useElecPriceStore = defineStore("elecPriceData", () => {
const subscribeToCost = (item: NiagaraElecData, index: number) => {
const slotPath = item.slotPath;
const ordString = `local:|foxs:4918|station:|${slotPath}`;
const ordString = `local:|foxs:|station:|${slotPath}`;
// @ts-ignore
window.require &&

View File

@ -35,7 +35,7 @@ const useElecReportStore = defineStore("elecReportData", () => {
console.log("進入 bajaSubscriber 準備執行用電度數 BQL 訂閱");
// 定義BQL 查詢
const Total_kwhBql = `local:|foxs:4918|station:|neql:EMS:SubSys_kwh|bql:select slotPath,parent.displayName,displayName,NumericInterval.historyConfig.id,out`;
const Total_kwhBql = `local:|foxs:|station:|neql:EMS:SubSys_kwh|bql:select slotPath,parent.displayName,displayName,NumericInterval.historyConfig.id,out`;
// 執行各電表的 BQL 查詢
fetchElecData(baja, Total_kwhBql);
@ -99,7 +99,7 @@ const useElecReportStore = defineStore("elecReportData", () => {
endTimeValue = dayjs(endTime.value).subtract(1, 'month').endOf('month').format("YYYY-MM-DDTHH:mm:ss.000+08:00");
}
const ordString = `local:|foxs:4918|history:${id}?period=timerange;start=${startTimeValue};end=${endTimeValue}|bql:history:HistoryRollup.rollup(baja:RelTime '3600000')`; //每小时一个rollup
const ordString = `local:|foxs:|history:${id}?period=timerange;start=${startTimeValue};end=${endTimeValue}|bql:history:HistoryRollup.rollup(baja:RelTime '3600000')`; //每小时一个rollup
console.log(ordString);
// @ts-ignore
window.require &&

View File

@ -24,7 +24,7 @@ const useElecStore = defineStore("elecData", () => {
console.log("進入 bajaSubscriber 準備執行用電度數 BQL 訂閱");
// 定義BQL 查詢
const Total_kwhBql = `local:|foxs:4918|station:|neql:EMS:Total_kwh|bql:select slotPath,parent.displayName,displayName,NumericInterval.historyConfig.id,out`;
const Total_kwhBql = `local:|foxs:|station:|neql:EMS:Total_kwh|bql:select slotPath,parent.displayName,displayName,NumericInterval.historyConfig.id,out`;
// 執行各電表的 BQL 查詢
fetchElecData(baja, Total_kwhBql);
@ -65,7 +65,7 @@ const useElecStore = defineStore("elecData", () => {
).format("YYYY-MM-DDTHH:mm:ss.000+08:00");
const endTime = dayjs().format("YYYY-MM-DDTHH:mm:ss.000+08:00");
const ordString = `local:|foxs:4918|history:${id}?period=timerange;;start=${startTime};end=${endTime}|bql:history:HistoryRollup.rollup(baja:RelTime '3600000')`; //每小时一个rollup
const ordString = `local:|foxs:|history:${id}?period=timerange;;start=${startTime};end=${endTime}|bql:history:HistoryRollup.rollup(baja:RelTime '3600000')`; //每小时一个rollup
console.log(ordString);
// @ts-ignore
window.require &&

View File

@ -58,7 +58,10 @@
</el-col>
<el-col :span="16">
<el-card style="border-radius: 8px">
<h3 class="">區間計費度數 {{ billingDateRange }}</h3>
<h3 class="">
區間計費度數 2025/05/01 - 2025/05/16
<!-- {{ billingDateRange }} -->
</h3>
<EnergyBar :chartData="areaBillingData" />
</el-card>
</el-col>
@ -115,7 +118,7 @@ const loading = computed(() => {
});
const statisticData = computed(() => {
const currentMonth = dayjs().format("YYYY-MM");
const currentMonth = "2025-05";
let intervalFlowCost = 0;
let intervalEleCost = 0;
@ -135,14 +138,14 @@ const statisticData = computed(() => {
value: storeElecTotal.elecFlowCostSummary?.totalFlowCost || 0,
unit: "元",
},
{ title: "區間電費", value: intervalFlowCost, unit: "元" },
{ title: "當月電費", value: intervalFlowCost, unit: "元" },
{
title: "今年碳排當量累計",
value: storeElecTotal.elecFlowCostSummary?.totalEleCost * 0.424,
unit: "公斤 CO2e/度",
},
{
title: "區間碳排當量",
title: "當月碳排當量",
value: intervalEleCost * 0.424,
unit: "公斤 CO2e/度",
},
@ -151,7 +154,7 @@ const statisticData = computed(() => {
value: storeElecTotal.elecFlowCostSummary?.totalEleCost || 0,
unit: "kWh",
},
{ title: "區間用電度數", value: intervalEleCost, unit: "kWh" },
{ title: "當月用電度數", value: intervalEleCost, unit: "kWh" },
];
});
@ -197,12 +200,16 @@ const monthlyElectricityData = computed(() => {
dayjs().month(month).valueOf()
);
const baseElecData = storeElecTotal.elecStandCostSummary.StandResults.map(item => item.StandCost);
const flowElecData = sortedCategories.map(
(month) => groupedData[month].totalCost
const baseElecData = storeElecTotal.elecStandCostSummary.StandResults.map(
(item) => Number(item.StandCost.toFixed(2))
);
const flowElecData = sortedCategories.map((month) =>
Number(groupedData[month].totalCost.toFixed(2))
);
const totalElecData = sortedCategories.map((month, index) => {
return (baseElecData[index] || 0) + (flowElecData[index] || 0);
const base = baseElecData[index] || 0;
const flow = flowElecData[index] || 0;
return Number((base + flow).toFixed(2));
});
return {
@ -212,16 +219,19 @@ const monthlyElectricityData = computed(() => {
name: "基本電費",
type: "bar",
data: baseElecData,
stack: "total",
},
{
name: "流動電費",
type: "bar",
data: flowElecData,
stack: "total",
},
{
name: "總電費",
type: "bar",
type: "line",
data: totalElecData,
lineStyle: { width: 3 },
},
],
};
@ -252,6 +262,7 @@ const monthlyCarbonData = computed(() => {
name: "碳排當量",
type: "bar",
data: carbonData,
stack: "total",
},
],
};
@ -278,9 +289,9 @@ const monthlyBillingData = computed(() => {
return {
categories: sortedCategories,
series: [
{ name: "尖峰", type: "bar", data: peakData },
{ name: "半尖峰", type: "bar", data: halfData },
{ name: "離峰", type: "bar", data: offData },
{ name: "尖峰", type: "bar", data: peakData, stack: "total" },
{ name: "半尖峰", type: "bar", data: halfData, stack: "total" },
{ name: "離峰", type: "bar", data: offData, stack: "total" },
],
};
});
@ -290,10 +301,12 @@ const areaBillingData = computed(() => {
return { categories: [], series: [] };
}
const today = dayjs();
const currentMonth = today.format("YYYY-MM");
const startDate = dayjs(`${currentMonth}-01`); // Get the first day of the current month
const endDate = today;
// const today = dayjs();
// const currentMonth = today.format("YYYY-MM");
// const startDate = dayjs(`${currentMonth}-01`); // Get the first day of the current month
// const endDate = today;
const startDate = dayjs("2025-05-01");
const endDate = dayjs("2025-05-31");
// Filter daily results within the specified date range
const areaResults = storeElecTotal.elecFlowCostSummary.dailyResults.filter(
@ -331,9 +344,9 @@ const areaBillingData = computed(() => {
return {
categories: sortedCategories,
series: [
{ name: "尖峰", type: "bar", data: peakData },
{ name: "半尖峰", type: "bar", data: halfData },
{ name: "離峰", type: "bar", data: offData },
{ name: "尖峰", type: "bar", data: peakData, stack: "total" },
{ name: "半尖峰", type: "bar", data: halfData, stack: "total" },
{ name: "離峰", type: "bar", data: offData, stack: "total" },
],
};
});

View File

@ -54,7 +54,7 @@
<td colspan="2" class="bg-brown">
<el-input
type="number"
v-model.number="stand3Value[0]"
v-model="stand3Value[0]"
style="width: 140px"
:disabled="!stand3isEditing"
/>
@ -65,7 +65,7 @@
<td colspan="2" class="bg-brown">
<el-input
type="number"
v-model.number="stand3Value[1]"
v-model="stand3Value[1]"
style="width: 140px"
:disabled="!stand3isEditing"
/>
@ -77,7 +77,7 @@
<td class="bg-brown">
<el-input
type="number"
v-model.number="stand3Value[2]"
v-model="stand3Value[2]"
style="width: 140px"
:disabled="!stand3isEditing"
/>
@ -85,7 +85,7 @@
<td class="bg-brown">
<el-input
type="number"
v-model.number="stand3Value[3]"
v-model="stand3Value[3]"
style="width: 140px"
:disabled="!stand3isEditing"
/>
@ -96,7 +96,7 @@
<td class="bg-brown">
<el-input
type="number"
v-model.number="stand3Value[4]"
v-model="stand3Value[4]"
style="width: 140px"
:disabled="!stand3isEditing"
/>
@ -104,7 +104,7 @@
<td class="bg-brown">
<el-input
type="number"
v-model.number="stand3Value[5]"
v-model="stand3Value[5]"
style="width: 140px"
:disabled="!stand3isEditing"
/>
@ -115,7 +115,7 @@
<td class="bg-brown">
<el-input
type="number"
v-model.number="stand3Value[6]"
v-model="stand3Value[6]"
style="width: 140px"
:disabled="!stand3isEditing"
/>
@ -123,7 +123,7 @@
<td class="bg-brown">
<el-input
type="number"
v-model.number="stand3Value[7]"
v-model="stand3Value[7]"
style="width: 140px"
:disabled="!stand3isEditing"
/>
@ -134,7 +134,7 @@
<td class="bg-brown">
<el-input
type="number"
v-model.number="stand3Value[8]"
v-model="stand3Value[8]"
style="width: 140px"
:disabled="!stand3isEditing"
/>
@ -142,7 +142,7 @@
<td class="bg-brown">
<el-input
type="number"
v-model.number="stand3Value[9]"
v-model="stand3Value[9]"
style="width: 140px"
:disabled="!stand3isEditing"
/>
@ -158,7 +158,7 @@
<td class="bg-lightred">
<el-input
type="number"
v-model.number="stand3Value[10]"
v-model="stand3Value[10]"
style="width: 140px"
:disabled="!stand3isEditing"
/>
@ -172,7 +172,7 @@
<td class="bg-lightyellow">
<el-input
type="number"
v-model.number="stand3Value[11]"
v-model="stand3Value[11]"
style="width: 140px"
:disabled="!stand3isEditing"
/>
@ -186,7 +186,7 @@
<td class="bg-lightyellow">
<el-input
type="number"
v-model.number="stand3Value[12]"
v-model="stand3Value[12]"
style="width: 140px"
:disabled="!stand3isEditing"
/>
@ -199,7 +199,7 @@
<td class="bg-lightgreen">
<el-input
type="number"
v-model.number="stand3Value[13]"
v-model="stand3Value[13]"
style="width: 140px"
:disabled="!stand3isEditing"
/>
@ -216,7 +216,7 @@
<td class="bg-lightgreen">
<el-input
type="number"
v-model.number="stand3Value[14]"
v-model="stand3Value[14]"
style="width: 140px"
:disabled="!stand3isEditing"
/>
@ -230,7 +230,7 @@
<td class="bg-lightyellow">
<el-input
type="number"
v-model.number="stand3Value[15]"
v-model="stand3Value[15]"
style="width: 140px"
:disabled="!stand3isEditing"
/>
@ -247,7 +247,7 @@
<td class="bg-lightyellow">
<el-input
type="number"
v-model.number="stand3Value[16]"
v-model="stand3Value[16]"
style="width: 140px"
:disabled="!stand3isEditing"
/>
@ -260,7 +260,7 @@
<td class="bg-lightgreen">
<el-input
type="number"
v-model.number="stand3Value[17]"
v-model="stand3Value[17]"
style="width: 140px"
:disabled="!stand3isEditing"
/>
@ -274,7 +274,7 @@
<td class="bg-lightgreen">
<el-input
type="number"
v-model.number="stand3Value[18]"
v-model="stand3Value[18]"
style="width: 140px"
:disabled="!stand3isEditing"
/>
@ -287,7 +287,7 @@
<td class="bg-lightgreen">
<el-input
type="number"
v-model.number="stand3Value[19]"
v-model="stand3Value[19]"
style="width: 140px"
:disabled="!stand3isEditing"
/>
@ -295,7 +295,7 @@
<td class="bg-lightgreen">
<el-input
type="number"
v-model.number="stand3Value[20]"
v-model="stand3Value[20]"
style="width: 140px"
:disabled="!stand3isEditing"
/>
@ -367,57 +367,81 @@ const confirmChanges = async () => {
const failedUpdates: { slotPath: string; out: number }[] = [];
if (!elecData || elecData.length === 0) {
console.warn("useElecPriceStore.elecData 為空,無法更新 stand3Value");
// @ts-ignore
ElMessage({
message: "資料為空,無法更新",
type: "warning",
});
return;
}
// @ts-ignore
const loading = ElLoading.service({
lock: true,
text: "更新中,請稍候...",
background: "rgba(255, 255, 255, 0.7)",
});
try {
//
for (let i = 0; i < stand3Value.value.length; i++) {
if (stand3Value.value[i] !== beforeEditValues.value[i]) {
// displayName
const displayName = Object.keys(displayNameToIndex).find((key) => {
const index = displayNameToIndex[key];
if (typeof index === "number") {
return index === i;
} else if (Array.isArray(index)) {
return index.includes(i);
}
return false;
});
//
for (let i = 0; i < stand3Value.value.length; i++) {
if (stand3Value.value[i] !== beforeEditValues.value[i]) {
// displayName
const displayName = Object.keys(displayNameToIndex).find((key) => {
const index = displayNameToIndex[key];
if (typeof index === "number") {
return index === i;
} else if (Array.isArray(index)) {
return index.includes(i);
}
return false;
});
if (displayName) {
// elecData item
const item = elecData.find((item) => item.displayName === displayName);
if (item) {
// "slot:/"
const slotPath = item.slotPath.startsWith("slot:/")
? item.slotPath.slice(6) // 移除 "slot:/"
: item.slotPath; //
// Niagara
const success = await storeElecPrice.updatePrice(slotPath, stand3Value.value[i]);
if (!success) {
failedUpdates.push({
slotPath: item.slotPath,
out: stand3Value.value[i],
});
if (displayName) {
// elecData item
const item = elecData.find(
(item) => item.displayName === displayName
);
if (item) {
// "slot:/"
const slotPath = item.slotPath.startsWith("slot:/")
? item.slotPath.slice(6) // 移除 "slot:/"
: item.slotPath; //
// Niagara
console.log("即將更新", slotPath, Number(stand3Value.value[i]));
const success = await storeElecPrice.updatePrice(
slotPath,
Number(stand3Value.value[i])
);
if (!success) {
failedUpdates.push({
slotPath: item.slotPath,
out: Number(stand3Value.value[i]),
});
}
} else {
console.warn(`找不到 displayName 為 ${displayName} 的 elecData`);
}
} else {
console.warn(`找不到 displayName 為 ${displayName} 的 elecData`);
console.warn(`找不到 index 為 ${i} 的 displayName`);
}
} else {
console.warn(`找不到 index 為 ${i} 的 displayName`);
}
}
}
if (failedUpdates.length > 0) {
console.error("以下更新失敗:", failedUpdates);
} else {
console.log("所有更新成功");
if (failedUpdates.length > 0) {
// @ts-ignore
ElMessage.error("更新失敗");
console.error("以下更新失敗:", failedUpdates);
} else {
// @ts-ignore
ElMessage({
message: "更新成功",
type: "success",
});
console.log("所有更新成功");
}
initialStand3Values.value = [...stand3Value.value];
} finally {
loading.close();
}
// initialStand3Values
initialStand3Values.value = [...stand3Value.value];
};
const onCancel = () => {

View File

@ -4,12 +4,8 @@ import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
// https://vite.dev/config/
export default defineConfig({
base:
process.env.NODE_ENV === "production"
? "https://192.168.0.206:8500/file/ems_dist/"
: "/",
base: "./",
build: {
outDir: process.env.NODE_ENV === "production" ? "../ems_dist" : "./dist",
emptyOutDir: true,