能源圖表串接

This commit is contained in:
huliang 2025-05-02 17:06:44 +08:00
parent ca522b8b94
commit 59bf0fa00b
3 changed files with 491 additions and 128 deletions

View File

@ -0,0 +1,136 @@
import { ref } from "vue";
import { defineStore } from "pinia";
import dayjs from "dayjs";
import type { NiagaraElecData, ElecCostSummary } from "../utils/types";
import { CalcuEleCost } from "../utils/CalcuEleCost";
const useElecStore = defineStore("elecData", () => {
const elecData = ref<NiagaraElecData[]>([]);
// @ts-ignore
let timerId = null;
const elecCostSummary = ref<ElecCostSummary | null>(null);
// get data from baja
const getElecDataFromBaja = () => {
// @ts-ignore
window.require &&
// @ts-ignore
window.requirejs(["baja!"], (baja: any) => {
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 查詢
fetchElecData(baja, Total_kwhBql);
});
};
const fetchElecData = (baja: any, bql: string) => {
let eleclist: NiagaraElecData[] = [];
baja.Ord.make(bql).get({
cursor: {
before: () => {},
each: (record: any) => {
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: NiagaraElecData) => {
const id = item.id;
const startTime = dayjs(
`${dayjs().year()}-01-01T00:00:00.000+08:00`
).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
console.log(ordString);
// @ts-ignore
window.require &&
// @ts-ignore
window.requirejs(["baja!"], (baja: any) => {
console.log("進入 bajaSubscriber 準備執行 BQL 訂閱");
const dataMap = new Map<string, number>(); // key: timestamp string, value: energy diff
let lastTimestamp: string | null = null;
let lastValue: number | null = null;
baja.Ord.make(ordString).get({
cursor: {
before: () => {
console.log(`開始訂閱 ${id} 的歷史資料`);
},
each: (record: any) => {
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);
elecCostSummary.value = CalcuEleCost(dataMap);
},
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,
elecCostSummary,
};
});
export default useElecStore;

View File

@ -0,0 +1,133 @@
import type { DailyResult, ElecCostSummary, DailyEntry } from "../utils/types";
import useElecPriceStore from "../stores/useElecPriceStore";
const storeElecPrice = useElecPriceStore();
export const CalcuEleCost = (
input: Map<string, number>
): ElecCostSummary => {
const dailyData: Map<string, DailyEntry[]> = 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: DailyResult[] = [];
const elecPrices = storeElecPrice.elecData;
const Summer_Off_Prices = elecPrices.find((item:any) => item.displayName === "流動週日離峰夏月")?.out || 0;
const Summer_HalfPeak_Prices_Saturday = elecPrices.find((item:any) => item.displayName === "流動週六半尖峰夏月")?.out || 0;
const Summer_HalfPeak_Prices_Weekday = elecPrices.find((item:any) => item.displayName === "流動平日半尖峰夏月")?.out || 0;
const Summer_Peak_Prices = elecPrices.find((item:any) => item.displayName === "流動平日尖峰夏月")?.out || 0;
const Non_Summer_Off_Prices = elecPrices.find((item:any) => item.displayName === "流動週日離峰非夏月")?.out || 0;
const Non_Summer_HalfPeak_Prices_Saturday = elecPrices.find((item:any) => item.displayName === "流動週六半尖峰非夏月")?.out || 0;
const Non_Summer_HalfPeak_Prices_Weekday = elecPrices.find((item:any) => 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,
};
};

View File

@ -57,7 +57,7 @@
</el-col>
<el-col :span="16">
<el-card style="border-radius: 8px">
<h3 class="">區間計費度數 2025-03-01 ~ 2025-03-12</h3>
<h3 class="">區間計費度數 {{ billingDateRange }}</h3>
<EnergyBar :chartData="areaBillingData" />
</el-card>
</el-col>
@ -65,151 +65,245 @@
</template>
<script setup>
import { onMounted, ref, computed, onUnmounted } from "vue";
import { onMounted, ref, watch, computed, onUnmounted } from "vue";
import EnergySankey from "../components/EnergySankey.vue";
import EnergyLine from "../components/EnergyLine.vue";
import EnergyBar from "../components/EnergyBar.vue";
import useElecTotalMeterStore from "../stores/useElecTotalMeterStore";
import dayjs from "dayjs";
const statisticData = ref([
{ title: "今年電費累計", value: 58792.56, unit: "元" },
{ title: "區間電費", value: 1234.78, unit: "元" },
{ title: "今年碳排當量累計", value: 3456.23, unit: "公斤" },
{ title: "區間碳排當量", value: 15.67, unit: "公斤" },
{ title: "今年用電度數", value: 1890.45, unit: "kWh" },
{ title: "區間用電度數", value: 123.9, unit: "kWh" },
]);
const storeElecTotal = useElecTotalMeterStore();
//
const monthlyElectricityData = ref({
categories: [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
],
series: [
watch(
() => storeElecTotal.elecCostSummary,
(newElecData) => {
console.log("elecCostSummary", newElecData);
},
{ deep: true }
);
onMounted(async () => {
await storeElecTotal.getElecDataFromBaja();
storeElecTotal.startTimer();
});
onUnmounted(() => {
storeElecTotal.stopTimer();
});
const statisticData = computed(() => {
const currentMonth = dayjs().format("YYYY-MM");
let intervalFlowCost = 0;
let intervalEleCost = 0;
if (storeElecTotal.elecCostSummary?.dailyResults) {
storeElecTotal.elecCostSummary.dailyResults.forEach((dailyResult) => {
if (dailyResult.dateStr.startsWith(currentMonth)) {
intervalFlowCost += dailyResult.dailyFlowCost;
intervalEleCost += dailyResult.dailyEleCost;
}
});
}
return [
{
name: "基本電量",
type: "bar",
data: [120, 100, 110, 130, 150, 160, 140, 150, 130, 120, 110, 130],
title: "今年電費累計",
value: storeElecTotal.elecCostSummary?.totalFlowCost || 0,
unit: "元",
},
{ title: "區間電費", value: intervalFlowCost, unit: "元" },
{
title: "今年碳排當量累計",
value: storeElecTotal.elecCostSummary?.totalEleCost * 0.424,
unit: "公斤 CO2e/度",
},
{
name: "流動電量",
title: "區間碳排當量",
value: intervalEleCost * 0.424,
unit: "公斤 CO2e/度",
},
{
title: "今年用電度數",
value: storeElecTotal.elecCostSummary?.totalEleCost || 0,
unit: "kWh",
},
{ title: "區間用電度數", value: intervalEleCost, unit: "kWh" },
];
});
function groupByMonth(dailyResults) {
const grouped = {};
dailyResults.forEach((result) => {
const month = dayjs(result.dateStr).format("MMM"); // Get abbreviated month name
if (!grouped[month]) {
grouped[month] = {
off: 0,
half: 0,
peak: 0,
total: 0,
offcost: 0,
halfcost: 0,
peakcost: 0,
totalCost: 0,
};
}
grouped[month].off += result.off;
grouped[month].half += result.half;
grouped[month].peak += result.peak;
grouped[month].offcost += result.offcost;
grouped[month].halfcost += result.halfcost;
grouped[month].peakcost += result.peakcost;
grouped[month].total += result.dailyEleCost;
grouped[month].totalCost += result.dailyFlowCost;
});
return grouped;
}
//
const monthlyElectricityData = computed(() => {
if (!storeElecTotal.elecCostSummary?.dailyResults) {
return { categories: [], series: [] };
}
const groupedData = groupByMonth(storeElecTotal.elecCostSummary.dailyResults);
const categories = Object.keys(groupedData);
const sortedCategories = _.sortBy(categories, (month) =>
dayjs().month(month).valueOf()
);
const baseElecData = sortedCategories.map((month) => groupedData[month].offcost);
const flowElecData = sortedCategories.map(
(month) => groupedData[month].totalCost
);
const totalElecData = sortedCategories.map((month, index) => {
return (baseElecData[index] || 0) + (flowElecData[index] || 0);
});
return {
categories: sortedCategories,
series: [
{
name: "基本電費",
type: "bar",
data: [80, 60, 70, 90, 100, 110, 90, 100, 80, 70, 60, 80],
data: baseElecData,
},
{
name: "流動電費",
type: "bar",
data: flowElecData,
},
{
name: "總電量",
type: "bar",
data: [200, 160, 180, 220, 250, 270, 230, 250, 210, 190, 170, 210],
data: totalElecData,
},
],
};
});
//
const monthlyCarbonData = ref({
categories: [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
],
const monthlyCarbonData = computed(() => {
if (!storeElecTotal.elecCostSummary?.dailyResults) {
return { categories: [], series: [] };
}
const groupedData = groupByMonth(storeElecTotal.elecCostSummary.dailyResults);
const categories = Object.keys(groupedData);
const sortedCategories = _.sortBy(categories, (month) =>
dayjs().month(month).valueOf()
);
const carbonData = sortedCategories.map((month) =>
(groupedData[month].total * 0.424).toFixed(2)
);
return {
categories: sortedCategories,
series: [
{
name: "碳排當量",
type: "bar",
data: [50, 40, 45, 55, 60, 65, 55, 60, 50, 45, 40, 50],
data: carbonData,
},
],
};
});
// (kWh)
const monthlyBillingData = ref({
categories: [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
],
const monthlyBillingData = computed(() => {
if (!storeElecTotal.elecCostSummary?.dailyResults) {
return { categories: [], series: [] };
}
const billingData = groupByMonth(storeElecTotal.elecCostSummary.dailyResults);
const categories = Object.keys(billingData);
const sortedCategories = _.sortBy(categories, month => dayjs().month(month).valueOf());
const peakData = sortedCategories.map(date => billingData[date].peak);
const halfData = sortedCategories.map(date => billingData[date].half);
const offData = sortedCategories.map(date => billingData[date].off);
return {
categories: sortedCategories,
series: [
{
name: "尖峰",
type: "bar",
data: [120, 100, 110, 130, 150, 160, 140, 150, 130, 120, 110, 130],
},
{
name: "半尖峰",
type: "bar",
data: [80, 60, 70, 90, 100, 110, 90, 100, 80, 70, 60, 80],
},
{
name: "離峰",
type: "bar",
data: [200, 160, 180, 220, 250, 270, 230, 250, 210, 190, 170, 210],
},
],
{ name: "尖峰", type: "bar", data: peakData },
{ name: "半尖峰", type: "bar", data: halfData },
{ name: "離峰", type: "bar", data: offData }
]
};
});
//
const areaBillingData = ref({
categories: [
"03-01",
"03-02",
"03-03",
"03-04",
"03-05",
"03-06",
"03-07",
"03-08",
"03-09",
"03-10",
"03-11",
"03-12",
],
series: [
{
name: "尖峰",
type: "bar",
data: [120, 100, 110, 130, 150, 160, 140, 150, 130, 120, 110, 130],
},
{
name: "半尖峰",
type: "bar",
data: [80, 60, 70, 90, 100, 110, 90, 100, 80, 70, 60, 80],
},
{
name: "離峰",
type: "bar",
data: [200, 160, 180, 220, 250, 270, 230, 250, 210, 190, 170, 210],
},
],
const billingDateRange = computed(() => {
const startOfMonth = dayjs().startOf('month').format('YYYY-MM-DD');
const today = dayjs().format('YYYY-MM-DD');
return `${startOfMonth} - ${today}`;
});
const areaBillingData = computed(() => {
if (!storeElecTotal.elecCostSummary?.dailyResults) {
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;
// Filter daily results within the specified date range
const areaResults = storeElecTotal.elecCostSummary.dailyResults.filter(result => {
const resultDate = dayjs(result.dateStr);
return resultDate.isSame(startDate, 'day') || (resultDate.isAfter(startDate, 'day') && resultDate.isBefore(endDate, 'day')) || resultDate.isSame(endDate, 'day');
});
// Transform the filtered data for billing
const transformedData = {};
areaResults.forEach(result => {
const date = dayjs(result.dateStr).format('MM-DD'); // Format the date for the category
transformedData[date] = {
peak: result.peak,
half: result.half,
off: result.off
};
});
const categories = Object.keys(transformedData);
const sortedCategories = _.sortBy(categories, date => dayjs(date, 'MM-DD').valueOf());
const peakData = sortedCategories.map(date => transformedData[date].peak);
const halfData = sortedCategories.map(date => transformedData[date].half);
const offData = sortedCategories.map(date => transformedData[date].off);
return {
categories: sortedCategories,
series: [
{ name: "尖峰", type: "bar", data: peakData },
{ name: "半尖峰", type: "bar", data: halfData },
{ name: "離峰", type: "bar", data: offData }
]
};
});
</script>
<style scoped>