首頁能源圖表串接

This commit is contained in:
huliang 2025-05-07 14:56:32 +08:00
parent be68eb3568
commit 7599b7fa3a
7 changed files with 740 additions and 80 deletions

View File

@ -1,67 +1,73 @@
<script setup>
import { ref, onMounted } from "vue";
import { ref, onMounted, onUnmounted, defineProps, watch } from "vue";
import * as echarts from "echarts";
//
const yesterdayTodayData = {
categories: [
"00:00",
"01:00",
"02:00",
"03:00",
"04:00",
"05:00",
"06:00",
"07:00",
"08:00",
"09:00",
"10:00",
"11:00",
],
values: [
{
name: `2025.01.8 用電量`,
value: [8, 8, 8, 8, 8, 15, 25, 65, 75, 60, 70, 65],
const props = defineProps({
yesterdayTodayData: {
type: Object,
required: true,
},
{
name: `2025.01.7 用電量`,
value: [10, 10, 10, 10, 10, 20, 30, 80, 90, 70, 80, 85],
weekComparisonData: {
type: Object,
required: true,
},
],
};
//
const weekComparisonData = {
categories: ["Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu"],
values: [
{
name: "本週用電量",
value: [850, 200, 350, 850, 950, 950, 900],
},
{
name: "上週用電量",
value: [800, 150, 300, 750, 900, 900, 800],
},
],
};
});
const yesterdayTodayChart = ref(null);
const weekComparisonChart = ref(null);
let yesterdayTodayEChart = null;
let weekComparisonEChart = null;
watch(
() => props.yesterdayTodayData,
(newYesterdayTodayData) => {
//
if (yesterdayTodayEChart) {
yesterdayTodayEChart.setOption(
generateCylinderChartOption(newYesterdayTodayData)
);
}
},
{ deep: true }
);
watch(
() => props.weekComparisonData,
(newWeekComparisonData) => {
//
if (weekComparisonEChart) {
weekComparisonEChart.setOption(
generateCylinderChartOption(newWeekComparisonData)
);
}
},
{ deep: true }
);
onMounted(() => {
//
const yesterdayTodayEChart = echarts.init(yesterdayTodayChart.value);
yesterdayTodayEChart = echarts.init(yesterdayTodayChart.value);
yesterdayTodayEChart.setOption(
generateCylinderChartOption(yesterdayTodayData)
generateCylinderChartOption(props.yesterdayTodayData)
);
//
const weekComparisonEChart = echarts.init(weekComparisonChart.value);
weekComparisonEChart = echarts.init(weekComparisonChart.value);
weekComparisonEChart.setOption(
generateCylinderChartOption(weekComparisonData)
generateCylinderChartOption(props.weekComparisonData)
);
});
onUnmounted(() => {
if (yesterdayTodayEChart) {
yesterdayTodayEChart.dispose();
yesterdayTodayEChart = null;
}
if (weekComparisonEChart) {
weekComparisonEChart.dispose();
weekComparisonEChart = null;
}
});
// option
const generateCylinderChartOption = (data) => {
const barWidth = 15;
@ -101,9 +107,12 @@ const generateCylinderChartOption = (data) => {
formatter: function (params) {
// bar
const barData = params.filter((item) => item.seriesType === "bar");
return barData
.map((item) => `${item.seriesName}: ${item.data}`)
.join("<br/>");
const category = data.categories[params[0].dataIndex];
let tooltipContent = `${category}<br/>`;
barData.forEach((item) => {
tooltipContent += `${item.seriesName}: ${item.data}<br/>`;
});
return tooltipContent;
},
},
legend: {

View File

@ -1,35 +1,15 @@
<script setup>
const mockData = [
{
value: 305.5,
label: "今日用電量",
unit: "kWH",
icon: "leaf",
import { defineProps } from "vue";
const props = defineProps({
elecData: {
type: Object,
},
{
value: 886.75,
label: "昨日用電量",
unit: "kWH",
icon: "leaf",
},
{
value: 7.84,
label: "即時功率",
unit: "kW",
icon: "bolt",
},
{
value: 20.96,
label: "容積占比",
unit: "%",
icon: "charging-station",
},
];
});
</script>
<template>
<a-row :gutter="24">
<a-col v-for="(item, index) in mockData" :key="index" :span="6" class="mb-5">
<a-col v-for="(item, index) in elecData" :key="index" :span="6" class="mb-5">
<a-card class="number">
<a-row :gutter="24" align="middle" justify="space-between">
<a-col :span="18">

View File

@ -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;

View File

@ -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;

140
src/utils/calcuEleCost.js Normal file
View File

@ -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,
};
};

1
src/utils/index.js Normal file
View File

@ -0,0 +1 @@
export * from './calcuEleCost';

View File

@ -1,10 +1,237 @@
<script setup>
import { ref, onMounted, watch } from "vue";
import { ref, onMounted, onUnmounted, watch } from "vue";
import DashboardStat from "@/components/dashboard/dashboardStat.vue";
import DashboardBuild from "@/components/dashboard/dashboardBuild.vue";
import DashboardElecChart from "@/components/dashboard/dashboardElecChart.vue";
import DashboardTag from "@/components/dashboard/dashboardTag.vue";
import DashboardAlert from "@/components/dashboard/dashboardAlert.vue";
import useElecTotalMeterStore from "@/stores/useElecTotalMeterStore";
import useElecDemandStore from "@/stores/useElecDemandStore";
import dayjs from "dayjs";
const elecDataStore = useElecTotalMeterStore();
const elecDemandStore = useElecDemandStore();
const elecStat = ref([
{
value: 0,
label: "今日用電量",
unit: "kWH",
icon: "leaf",
},
{
value: 0,
label: "昨日用電量",
unit: "kWH",
icon: "leaf",
},
{
value: 0,
label: "即時功率",
unit: "kW",
icon: "bolt",
},
{
value: 0,
label: "容積占比",
unit: "%",
icon: "charging-station",
},
]);
const elecDemand_P = ref(0); //
const elecDemand_Engel = ref(0); //
const yesterdayTodayData = ref({
categories: [],
values: [
{
name: `${dayjs().format("YYYY-MM-DD")} 用電量`,
value: [],
},
{
name: `${dayjs().subtract(1, "day").format("YYYY-MM-DD")} 用電量`,
value: [],
},
],
});
const weekComparisonData = ref({
categories: [],
values: [
{
name: "本週用電量",
value: [0, 0, 0, 0, 0, 0, 0],
},
{
name: "上週用電量",
value: [0, 0, 0, 0, 0, 0, 0],
},
],
});
const generateWeekCategories = () => {
const today = dayjs();
const currentDay = today.day();
const daysOfWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const dynamicCategories = [];
for (let i = 0; i < 7; i++) {
const dayIndex = (currentDay - i + 7) % 7;
dynamicCategories.unshift(daysOfWeek[dayIndex]);
}
return dynamicCategories;
};
weekComparisonData.value = {
...weekComparisonData.value,
categories: generateWeekCategories(),
};
watch(
() => elecDataStore.elecFlowCostSummary,
(newElecData) => {
console.log("elecStandCostSummary", newElecData);
if (newElecData && newElecData.dailyResults) {
const today = dayjs().format("YYYY-MM-DD");
const yesterday = dayjs().subtract(1, "day").format("YYYY-MM-DD");
const todayData = newElecData.dailyResults.find(
(item) => item.dateStr === today
);
const yesterdayData = newElecData.dailyResults.find(
(item) => item.dateStr === yesterday
);
const todayElecCost = todayData ? todayData.dailyEleCost : 0;
const yesterdayElecCost = yesterdayData ? yesterdayData.dailyEleCost : 0;
elecStat.value = [
{
...elecStat.value[0],
value: todayElecCost,
},
{
...elecStat.value[1],
value: yesterdayElecCost,
},
{
...elecStat.value[2],
},
{
...elecStat.value[3],
},
];
const thisWeekData = newElecData.dailyResults
.slice(-7)
.map((item) => item.dailyEleCost || 0);
const lastWeekData = newElecData.dailyResults
.slice(-14, -7)
.map((item) => item.dailyEleCost || 0);
weekComparisonData.value = {
...weekComparisonData.value,
values: [
{
name: "本週用電量",
value: thisWeekData,
},
{
name: "上週用電量",
value: lastWeekData,
},
],
};
}
},
{ deep: true }
);
watch(
() => [elecDataStore.todayelecdata, elecDataStore.yesterdayelecdata],
([newTodayData, newYesterdayData]) => {
console.log("todayyesterday", newTodayData, newYesterdayData);
const todayDate = dayjs().format("YYYY-MM-DD");
const yesterdayDate = dayjs().subtract(1, "day").format("YYYY-MM-DD");
const categories = [];
const todayValues = [];
for (const [timestamp, value] of newTodayData) {
const hour = dayjs(timestamp).format("HH:00");
categories.push(hour);
todayValues.push(value);
}
categories.sort();
const filledYesterdayValues = categories.map((hour) => {
const timestamp = dayjs(
`${yesterdayDate} ${hour}`,
"YYYY-MM-DD HH:00"
).format("YYYY-MM-DDTHH:mm:ss.000+08:00");
return newYesterdayData.get(timestamp) || 0;
});
console.log("todayValues", todayValues, filledYesterdayValues);
yesterdayTodayData.value = {
categories: categories,
values: [
{
name: `${todayDate} 用電量`,
value: todayValues,
},
{
name: `${yesterdayDate} 用電量`,
value: filledYesterdayValues,
},
],
};
},
{ deep: true }
);
watch(
() => elecDemandStore.elecData,
(newElecData) => {
console.log("elecDemandStore.elecData updated:", newElecData);
const newElecDemand_P =
newElecData.find((item) => item.name === "P")?.out || 0;
const newElecDemand_Engel =
newElecData.find((item) => item.name === "Engel")?.out || 0;
elecDemand_P.value = newElecDemand_P;
elecDemand_Engel.value = newElecDemand_Engel;
elecStat.value = [
...elecStat.value.slice(0, 2),
{
...elecStat.value[2],
value: newElecDemand_P,
},
{
...elecStat.value[3],
value: newElecDemand_Engel
? ((newElecDemand_P / newElecDemand_Engel) * 100).toFixed(2)
: 0,
},
];
},
{ deep: true }
);
onMounted(async () => {
await elecDataStore.getElecDataFromBaja();
elecDataStore.startTimer();
await elecDemandStore.getElecDemandFromBaja();
});
onUnmounted(() => {
elecDataStore.stopTimer();
elecDemandStore.clearAllSubscriber();
});
</script>
<template>
<a-row :gutter="24" class="px-5 py-2">
@ -14,9 +241,12 @@ import DashboardAlert from "@/components/dashboard/dashboardAlert.vue";
</a-col>
<a-col :span="16" class="">
<!-- 用電數據 -->
<DashboardStat />
<DashboardStat :elecData="elecStat" />
<!-- 用電圖表 -->
<DashboardElecChart />
<DashboardElecChart
:yesterdayTodayData="yesterdayTodayData"
:weekComparisonData="weekComparisonData"
/>
</a-col>
</a-row>