diff --git a/src/components/dashboard/dashboardElecChart.vue b/src/components/dashboard/dashboardElecChart.vue
index cc8a624..0f26c66 100644
--- a/src/components/dashboard/dashboardElecChart.vue
+++ b/src/components/dashboard/dashboardElecChart.vue
@@ -1,67 +1,73 @@
-
+
diff --git a/src/stores/useElecDemandStore.js b/src/stores/useElecDemandStore.js
new file mode 100644
index 0000000..d234f43
--- /dev/null
+++ b/src/stores/useElecDemandStore.js
@@ -0,0 +1,106 @@
+import { ref } from "vue";
+import { defineStore } from "pinia";
+
+const useElecDemandStore = defineStore("elecDemand", () => {
+ const elecData = ref([]);
+ const subscribers = ref([]);
+
+ // get data from baja
+ const getElecDemandFromBaja = () => {
+ // @ts-ignore
+ window.require &&
+ // @ts-ignore
+ window.requirejs(["baja!"], (baja) => {
+ let eleclist = [];
+ baja.Ord.make(
+ `local:|foxs:4918|station:|neql:EMS:kw|bql:select slotPath,parent.displayName,name,out`
+ ).get({
+ cursor: {
+ before: () => {},
+ each: (record) => {
+ const newItem = {
+ slotPath: record.get("slotPath"),
+ displayName: record.get("parent$2edisplayName"),
+ name: record.get("name"),
+ out: record.get("out")?.get("value") ?? 0,
+ };
+ eleclist.push(newItem);
+ },
+ after: () => {
+ elecData.value = [];
+ elecData.value.push(...eleclist);
+ eleclist.forEach((item, index) => {
+ subscribeToHistory(item, index);
+ });
+ },
+ },
+ });
+ });
+ };
+
+ const subscribeToHistory = (item, index) => {
+ const slotPath = item.slotPath;
+ const ordString = `local:|foxs:4918|station:|${slotPath}`;
+
+ // @ts-ignore
+ window.require &&
+ // @ts-ignore
+ window.requirejs(["baja!"], (baja) => {
+ // 建立訂閱器
+ const subscriber = new baja.Subscriber();
+
+ // 定義 changed 事件的處理函數
+ subscriber.attach("changed", (prop) => {
+ try {
+ if (prop && prop.getName() === "out") {
+ // 取得 out 的新值
+ const match = prop.$display.match(/^(\d+(\.\d+)?)/);
+ const newValue = match ? parseFloat(match[0]) : 0;
+ // 更新 elecData 中對應的 out 值
+ const updatedIndex = elecData.value.findIndex(
+ (data) => data.slotPath === item.slotPath
+ );
+
+ if (updatedIndex !== -1) {
+ elecData.value[updatedIndex].out = Number(newValue);
+ console.log(`Niagara 用電需求 ${item.name} 更新:`, newValue);
+ }
+ }
+ } catch (error) {
+ console.error(
+ `處理 ${item.name || index} 告警變化失敗: ${error.message}`,
+ error
+ );
+ }
+ });
+
+ baja.Ord.make(ordString)
+ .get({ subscriber })
+ .then(() => {
+ console.log(`Successfuly subscribed to ${item.name}`);
+ })
+ .catch((err) => {
+ console.error(`訂閱 ${item.name || index} 失敗: ${err.message}`);
+ subscriber.detach("changed"); // 移除事件監聽器
+ });
+ subscribers.value.push(subscriber);
+ });
+ };
+
+ const clearAllSubscriber = () => {
+ subscribers.value.forEach((subscriber) => {
+ subscriber.detach("changed");
+ subscriber.unsubscribeAll(); // 移除所有訂閱
+ });
+ subscribers.value = [];
+ console.log("所有訂閱已清除");
+ };
+
+ return {
+ getElecDemandFromBaja,
+ elecData,
+ clearAllSubscriber,
+ };
+});
+
+export default useElecDemandStore;
diff --git a/src/stores/useElecTotalMeterStore.js b/src/stores/useElecTotalMeterStore.js
new file mode 100644
index 0000000..4d9de1f
--- /dev/null
+++ b/src/stores/useElecTotalMeterStore.js
@@ -0,0 +1,194 @@
+import { ref } from "vue";
+import { defineStore } from "pinia";
+import dayjs from "dayjs";
+
+import { calcuEleCost } from "@/utils";
+
+const useElecStore = defineStore("elecData", () => {
+ const elecData = ref([]);
+ const elecPriceData = ref([]);
+ let timerId = null;
+ const todayelecdata = ref(new Map());
+ const yesterdayelecdata = ref(new Map());
+ const elecFlowCostSummary = ref(null);
+
+ const getElecDataFromBaja = () => {
+ window.require &&
+ window.requirejs(["baja!"], (baja) => {
+ console.log("進入 bajaSubscriber 準備執行用電價 BQL 訂閱");
+
+ // 定義BQL 查詢
+ const price_kwhBql = `local:|foxs:4918|station:|neql:EMS:parameter|bql:select slotPath,parent.displayName,displayName,NumericInterval.historyConfig.id,out`;
+
+ // 執行查詢
+ let pricelist = [];
+ baja.Ord.make(price_kwhBql).get({
+ cursor: {
+ before: () => {},
+ each: (record) => {
+ pricelist.push({
+ slotPath: record.get("slotPath"),
+ displayName: record.get("displayName"),
+ id: record.get("NumericInterval$2ehistoryConfig$2eid").$cEncStr,
+ out: record.get("out")?.get("value") ?? 0,
+ });
+ },
+ after: () => {
+ elecPriceData.value = [];
+ elecPriceData.value.push(...pricelist);
+ },
+ limit: -1,
+ offset: 0,
+ },
+ });
+
+ 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`;
+
+ // 執行各電表的 BQL 查詢
+ let eleclist = [];
+ baja.Ord.make(Total_kwhBql).get({
+ cursor: {
+ before: () => {},
+ each: (record) => {
+ eleclist.push({
+ slotPath: record.get("slotPath"),
+ displayName: record.get("parent$2edisplayName"),
+ id: record.get("NumericInterval$2ehistoryConfig$2eid").$cEncStr,
+ out: record.get("out")?.get("value") ?? null,
+ });
+ },
+ after: () => {
+ const validElecList = eleclist.filter(
+ (item) => item.id !== undefined
+ );
+ elecData.value = [];
+ elecData.value.push(...validElecList);
+ validElecList.forEach((item) => {
+ gettimeToHistory(item);
+ });
+ },
+ },
+ });
+ });
+ };
+
+ const gettimeToHistory = (item) => {
+ const id = item.id;
+ const startTime = dayjs()
+ .subtract(13, "day")
+ .startOf("day")
+ .format("YYYY-MM-DDTHH:mm:ss.000+08:00");
+ const endTime = dayjs()
+ .endOf("day")
+ .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
+ console.log(ordString);
+ // @ts-ignore
+ window.require &&
+ // @ts-ignore
+ window.requirejs(["baja!"], (baja) => {
+ console.log("進入 bajaSubscriber 準備執行 BQL 訂閱");
+ const dataMap = new Map();
+ let lastTimestamp = null;
+ let lastValue = null;
+ baja.Ord.make(ordString).get({
+ cursor: {
+ before: () => {
+ console.log(`開始訂閱 ${id} 的歷史資料`);
+ },
+ each: (record) => {
+ const currentValue = record.get("min");
+ const timestamp = record.get("timestamp").$cEncStr;
+ if (
+ currentValue !== null &&
+ currentValue !== 0 &&
+ timestamp !== null
+ ) {
+ if (
+ lastTimestamp !== null &&
+ lastValue !== null &&
+ lastValue !== 0
+ ) {
+ const diff = currentValue - lastValue;
+ dataMap.set(lastTimestamp, diff);
+ }
+ lastValue = currentValue;
+ lastTimestamp = timestamp;
+ }
+ },
+ after: () => {
+ console.log("⏱️ 每小時差值 map:", dataMap);
+ // 清空之前的數據,避免累積
+ todayelecdata.value = new Map();
+ yesterdayelecdata.value = new Map();
+
+ // 提取今天和昨天的数据
+ for (const [timestamp, value] of dataMap) {
+ const date = dayjs(timestamp).format("YYYY-MM-DD");
+ const today = dayjs().format("YYYY-MM-DD");
+ const yesterday = dayjs().subtract(1, "day").format("YYYY-MM-DD");
+
+ if (date === today) {
+ todayelecdata.value.set(timestamp, value);
+ } else if (date === yesterday) {
+ yesterdayelecdata.value.set(timestamp, value);
+ }
+ }
+
+ console.log("今天每小時差值 map:", todayelecdata.value);
+ console.log("昨天每小時差值 map:", yesterdayelecdata.value);
+ elecFlowCostSummary.value = calcuEleCost(
+ dataMap,
+ elecPriceData.value
+ );
+ },
+ limit: -1,
+ offset: 0,
+ },
+ });
+ });
+ };
+
+ // 定時更新資料的函數
+ const updateHistoryData = () => {
+ console.log("定時器觸發,重新獲取歷史資料");
+ if (elecData.value && elecData.value.length > 0) {
+ elecData.value.forEach((item) => {
+ gettimeToHistory(item);
+ });
+ }
+ };
+
+ // 啟動定時器
+ const startTimer = () => {
+ timerId = setInterval(() => {
+ updateHistoryData();
+ }, 60 * 60 * 1000); // 每小時執行一次
+ };
+
+ // 停止定時器
+ const stopTimer = () => {
+ // @ts-ignore
+ if (timerId) {
+ clearInterval(timerId);
+ timerId = null;
+ console.log("計時器已停止");
+ }
+ };
+ return {
+ getElecDataFromBaja,
+ startTimer,
+ stopTimer,
+ elecData,
+ elecPriceData,
+ elecFlowCostSummary,
+ todayelecdata,
+ yesterdayelecdata,
+ };
+});
+
+export default useElecStore;
diff --git a/src/utils/calcuEleCost.js b/src/utils/calcuEleCost.js
new file mode 100644
index 0000000..e48fe9c
--- /dev/null
+++ b/src/utils/calcuEleCost.js
@@ -0,0 +1,140 @@
+export const calcuEleCost = (input, elecPriceData) => {
+ const dailyData = new Map();
+ let totalFlowCost = 0; // 總電價
+ let totalEleCost = 0; //總用電
+ let total_Off = 0; //總離峰用電
+ let total_half = 0; //總半尖峰用電
+ let total_peak = 0; //總尖峰用電
+ let total_OffCost = 0; //總離峰電價
+ let total_halfCost = 0; //總半尖峰電價
+ let total_peakCost = 0; //總尖峰電價
+
+ const dailyResults = [];
+ const elecPrices = elecPriceData;
+ const Summer_Off_Prices =
+ elecPrices.find((item) => item.displayName === "流動週日離峰夏月")
+ ?.out || 0;
+ const Summer_HalfPeak_Prices_Saturday =
+ elecPrices.find((item) => item.displayName === "流動週六半尖峰夏月")
+ ?.out || 0;
+ const Summer_HalfPeak_Prices_Weekday =
+ elecPrices.find((item) => item.displayName === "流動平日半尖峰夏月")
+ ?.out || 0;
+ const Summer_Peak_Prices =
+ elecPrices.find((item) => item.displayName === "流動平日尖峰夏月")
+ ?.out || 0;
+ const Non_Summer_Off_Prices =
+ elecPrices.find((item) => item.displayName === "流動週日離峰非夏月")
+ ?.out || 0;
+ const Non_Summer_HalfPeak_Prices_Saturday =
+ elecPrices.find((item) => item.displayName === "流動週六半尖峰非夏月")
+ ?.out || 0;
+ const Non_Summer_HalfPeak_Prices_Weekday =
+ elecPrices.find((item) => item.displayName === "流動平日半尖峰非夏月")
+ ?.out || 0;
+ // 1. 將輸入資料按日期分組
+ input.forEach((value, key) => {
+ const dateStr = key.substring(0, 10);
+ if (!dailyData.has(dateStr)) {
+ dailyData.set(dateStr, []);
+ }
+ dailyData.get(dateStr)?.push({ time: new Date(key), value });
+ });
+
+ for (const [dateStr, entries] of dailyData.entries()) {
+ if (!entries || entries.length === 0) continue;
+
+ const sampleDate = entries[0].time;
+ const dayOfWeek = sampleDate.getDay();
+ const month = sampleDate.getMonth() + 1;
+
+ let off = 0; //離峰用電
+ let half = 0; //半尖峰用電
+ let peak = 0; //尖峰用電
+ let offcost = 0; //離峰電價
+ let halfcost = 0; //半尖峰電價
+ let peakcost = 0; //尖峰電價
+ let dailyFlowCost = 0; //當日電價
+ let dailyEleCost = 0; //當日用電
+
+ const isSummer = month >= 6 && month <= 9;
+
+ entries.forEach(({ time, value }) => {
+ const hour = time.getHours();
+
+ if (isSummer) {
+ if (dayOfWeek === 0) {
+ off += value;
+ } else if (dayOfWeek === 6) {
+ if (hour < 9) off += value;
+ else half += value;
+ } else {
+ if (hour >= 16 && hour < 22) peak += value;
+ else if ((hour >= 9 && hour < 16) || hour >= 22) half += value;
+ else off += value;
+ }
+ } else {
+ if (dayOfWeek === 0) {
+ off += value;
+ } else {
+ if (hour < 6 || (hour >= 11 && hour < 14)) off += value;
+ else half += value;
+ }
+ }
+ });
+
+ if (isSummer) {
+ console.log("夏月計費");
+ const summerHalfPeakRate =
+ dayOfWeek === 6
+ ? Summer_HalfPeak_Prices_Saturday
+ : Summer_HalfPeak_Prices_Weekday;
+ offcost = off * Summer_Off_Prices;
+ halfcost = half * summerHalfPeakRate;
+ peakcost = peak * Summer_Peak_Prices;
+ dailyFlowCost = offcost + halfcost + peakcost;
+ dailyEleCost = off + half + peak;
+ } else {
+ console.log("非夏月計費");
+ const nonSummerHalfPeakRate =
+ dayOfWeek === 6
+ ? Non_Summer_HalfPeak_Prices_Saturday
+ : Non_Summer_HalfPeak_Prices_Weekday;
+ offcost = off * Non_Summer_Off_Prices;
+ halfcost = half * nonSummerHalfPeakRate;
+ dailyFlowCost = offcost + halfcost;
+ dailyEleCost = off + half;
+ }
+
+ totalFlowCost += dailyFlowCost;
+ totalEleCost += dailyEleCost;
+ total_Off += off;
+ total_half += half;
+ total_peak += peak;
+ total_OffCost += offcost;
+ total_halfCost += halfcost;
+ total_peakCost += peakcost;
+ dailyResults.push({
+ dateStr,
+ off,
+ half,
+ peak,
+ offcost,
+ halfcost,
+ peakcost,
+ dailyEleCost,
+ dailyFlowCost,
+ });
+ }
+ return {
+ dailyResults,
+ totalEleCost,
+ totalFlowCost,
+ total_Off,
+ total_half,
+ total_peak,
+ total_OffCost,
+ total_halfCost,
+ total_peakCost,
+ };
+};
diff --git a/src/utils/index.js b/src/utils/index.js
new file mode 100644
index 0000000..d8ab017
--- /dev/null
+++ b/src/utils/index.js
@@ -0,0 +1 @@
+export * from './calcuEleCost';
\ No newline at end of file
diff --git a/src/views/dashboard/DashboardPage.vue b/src/views/dashboard/DashboardPage.vue
index 7e71dd7..78d627d 100644
--- a/src/views/dashboard/DashboardPage.vue
+++ b/src/views/dashboard/DashboardPage.vue
@@ -1,10 +1,237 @@
@@ -14,9 +241,12 @@ import DashboardAlert from "@/components/dashboard/dashboardAlert.vue";
-
+
-
+