能源報表初版

This commit is contained in:
huliang 2025-05-06 16:24:41 +08:00
parent 59bf0fa00b
commit 756cbd3b7a
13 changed files with 946 additions and 218 deletions

5
components.d.ts vendored
View File

@ -13,8 +13,12 @@ declare module 'vue' {
ElCard: typeof import('element-plus/es')['ElCard'] ElCard: typeof import('element-plus/es')['ElCard']
ElCol: typeof import('element-plus/es')['ElCol'] ElCol: typeof import('element-plus/es')['ElCol']
ElContainer: typeof import('element-plus/es')['ElContainer'] ElContainer: typeof import('element-plus/es')['ElContainer']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader'] ElHeader: typeof import('element-plus/es')['ElHeader']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElMain: typeof import('element-plus/es')['ElMain'] ElMain: typeof import('element-plus/es')['ElMain']
@ -24,6 +28,7 @@ declare module 'vue' {
ElStatistic: typeof import('element-plus/es')['ElStatistic'] ElStatistic: typeof import('element-plus/es')['ElStatistic']
EnergyBar: typeof import('./src/components/EnergyBar.vue')['default'] EnergyBar: typeof import('./src/components/EnergyBar.vue')['default']
EnergyLine: typeof import('./src/components/EnergyLine.vue')['default'] EnergyLine: typeof import('./src/components/EnergyLine.vue')['default']
EnergyModal: typeof import('./src/components/EnergyModal.vue')['default']
EnergyPie: typeof import('./src/components/EnergyPie.vue')['default'] EnergyPie: typeof import('./src/components/EnergyPie.vue')['default']
EnergySankey: typeof import('./src/components/EnergySankey.vue')['default'] EnergySankey: typeof import('./src/components/EnergySankey.vue')['default']
Navbar: typeof import('./src/components/Navbar.vue')['default'] Navbar: typeof import('./src/components/Navbar.vue')['default']

View File

@ -0,0 +1,25 @@
<script setup>
import { defineProps, defineEmits } from "vue";
const props = defineProps({
title: {
type: String,
default: "",
},
confirm: Function,
close: Function,
});
</script>
<template>
<el-dialog :model-value="modelValue" :title="title" width="500">
<slot></slot>
<!-- 預設 slot -->
<template #footer>
<div class="dialog-footer">
<el-button @click="close()">關閉</el-button>
<el-button type="primary" @click="confirm()">確認</el-button>
</div>
</template>
</el-dialog>
</template>

View File

@ -1,30 +1,18 @@
<script setup> <script setup>
import { ref, onMounted, watch, onUnmounted } from "vue"; import { ref, onMounted, watch, onUnmounted, defineProps } from "vue";
import * as echarts from "echarts"; import * as echarts from "echarts";
const chartContainer = ref(null); const chartContainer = ref(null);
let chartInstance = null; let chartInstance = null;
const chartData = ref({ const props = defineProps({
series: [ chartData: {
{ type: Object,
name: "電表01", required: true,
value: 2200, },
itemStyle: { color: "#5470c6" },
},
{
name: "電表02",
value: 3600,
itemStyle: { color: "#91cc75" },
},
{
name: "電表03",
value: 1600,
itemStyle: { color: "#fac858" },
},
],
}); });
const initChart = () => { const initChart = () => {
chartInstance = echarts.init(chartContainer.value); chartInstance = echarts.init(chartContainer.value);
@ -37,7 +25,7 @@ const initChart = () => {
orient: "vertical", orient: "vertical",
left: "right", left: "right",
bottom: "0%", bottom: "0%",
data: chartData.value.series.map((item) => item.name), // data:props.chartData.series.map((item) => item.name), //
}, },
series: [ series: [
{ {
@ -45,7 +33,7 @@ const initChart = () => {
type: "pie", type: "pie",
radius: "70%", // radius: "70%", //
center: ["50%", "50%"], // center: ["50%", "50%"], //
data: chartData.value.series, data: props.chartData.series,
emphasis: { emphasis: {
itemStyle: { itemStyle: {
shadowBlur: 10, shadowBlur: 10,
@ -65,7 +53,7 @@ onMounted(() => {
}); });
watch( watch(
() => chartData.value, () => props.chartData,
(newChartData) => { (newChartData) => {
if (chartInstance) { if (chartInstance) {
chartInstance.setOption({ chartInstance.setOption({

View File

@ -1,57 +1,131 @@
<template> <template>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="7">
<el-descriptions class="margin-top" title="報表資訊" :column="1" border> <el-descriptions class="margin-top" title="報表資訊" :column="1" border>
<el-descriptions-item> <el-descriptions-item>
<template #label> <template #label>
<div class="cell-item">報表名稱</div> <div class="cell-item">報表名稱</div>
</template> </template>
智慧大樓電表月報表 {{ props.form?.name }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item> <el-descriptions-item>
<template #label> <template #label>
<div class="cell-item">報表期間</div> <div class="cell-item">報表期間</div>
</template> </template>
2025/02/01 ~ 2025/02/29 {{ dayjs(props.form?.date).format("YYYY/MM") }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item> <el-descriptions-item>
<template #label> <template #label>
<div class="cell-item">編製日期</div> <div class="cell-item">編製日期</div>
</template> </template>
2025/3/1 {{ dayjs().format("YYYY/MM/DD") }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item> <el-descriptions-item>
<template #label> <template #label>
<div class="cell-item">編製人員</div> <div class="cell-item">編製人員</div>
</template> </template>
能源管理部 {{ props.form?.staff }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="11">
<el-descriptions <el-descriptions
class="margin-top" class="margin-top"
title="單價(NTD/kWh)" :title="priceTitle"
:column="1" :column="2"
border border
> >
<el-descriptions-item> <el-descriptions-item>
<template #label> <template #label>
<div class="cell-item">尖峰單價</div> <div class="cell-item">基本電費-三相</div>
</template> </template>
6 {{ Three_Phase }}
</el-descriptions-item>
<el-descriptions-item v-if="isSummerMonth">
<template #label>
<div class="cell-item">基本電費-經常契約</div>
</template>
{{ Regular_Contract_Summer }}
</el-descriptions-item>
<el-descriptions-item v-if="!isSummerMonth">
<template #label>
<div class="cell-item">基本電費-經常契約</div>
</template>
{{ Regular_Contract_Non_Summer }}
</el-descriptions-item>
<el-descriptions-item v-if="isSummerMonth">
<template #label>
<div class="cell-item">流動電價-平日尖峰單價</div>
</template>
{{ Summer_Peak_Prices }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item> <el-descriptions-item>
<template #label> <template #label>
<div class="cell-item">半尖峰單價</div> <div class="cell-item">流動電價-平日半尖峰單價</div>
</template> </template>
4 {{
isSummerMonth
? Summer_HalfPeak_Prices_Weekday
: Non_Summer_HalfPeak_Prices_Weekday
}}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item> <el-descriptions-item>
<template #label> <template #label>
<div class="cell-item">離峰單價</div> <div class="cell-item">流動電價-平日離峰單價</div>
</template> </template>
2 {{
isSummerMonth
? Summer_Off_Prices_Weekday
: Non_Summer_Off_Prices_Weekday
}}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">流動電價-週六半尖峰單價</div>
</template>
{{
isSummerMonth
? Summer_HalfPeak_Prices_Saturday
: Non_Summer_HalfPeak_Prices_Saturday
}}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">流動電價-週六離峰單價</div>
</template>
{{
isSummerMonth
? Summer_Off_Prices_Saturday
: Non_Summer_Off_Prices_Saturday
}}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">流動電價-週日離峰單價</div>
</template>
{{ isSummerMonth ? Summer_Off_Prices : Non_Summer_Off_Prices }}
</el-descriptions-item>
</el-descriptions>
</el-col>
<el-col :span="6">
<el-descriptions class="margin-top" title="總計" :column="1" border>
<el-descriptions-item>
<template #label>
<div class="cell-item">基本電費</div>
</template>
{{ totalStandCost.toFixed(2) }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">流動電費</div>
</template>
{{ totalFlowCost.toFixed(2) }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">總費用</div>
</template>
{{ totalCost.toFixed(2) }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</el-col> </el-col>
@ -79,54 +153,27 @@
<th class="bg-yellow">尖峰費用</th> <th class="bg-yellow">尖峰費用</th>
<th class="bg-yellow">半尖峰費用</th> <th class="bg-yellow">半尖峰費用</th>
<th class="bg-yellow">離峰費用</th> <th class="bg-yellow">離峰費用</th>
<th class="bg-yellow">總費用</th> <th class="bg-yellow">小計</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr
<td>電表01</td> v-for="meterData in formattedElecCostSummary"
<td>辦公室南區</td> :key="meterData.name"
<td>549</td> >
<td>51</td> <td>{{ meterData.name }}</td>
<td>9.29</td> <td>{{ meterData.area }}</td>
<td>600</td> <td>{{ meterData.lastMonthUsage.toFixed(2) }}</td>
<td>150</td> <td>{{ meterData.usageChange.toFixed(2) }}</td>
<td>200</td> <td>{{ meterData.usageChangeRate.toFixed(2) }}</td>
<td>250</td> <td>{{ meterData.totalUsage.toFixed(2) }}</td>
<td>900</td> <td>{{ meterData.peakUsage.toFixed(2) }}</td>
<td>800</td> <td>{{ meterData.halfPeakUsage.toFixed(2) }}</td>
<td>500</td> <td>{{ meterData.offPeakUsage.toFixed(2) }}</td>
<td>2200</td> <td>{{ meterData.peakCost.toFixed(2) }}</td>
</tr> <td>{{ meterData.halfPeakCost.toFixed(2) }}</td>
<tr> <td>{{ meterData.offPeakCost.toFixed(2) }}</td>
<td>電表02</td> <td>{{ meterData.totalCost.toFixed(2) }}</td>
<td>電梯機房</td>
<td>969</td>
<td>-69</td>
<td>-7.12</td>
<td>900</td>
<td>300</td>
<td>300</td>
<td>300</td>
<td>1800</td>
<td>1200</td>
<td>600</td>
<td>3600</td>
</tr>
<tr>
<td>電表03</td>
<td>會議室</td>
<td>489</td>
<td>-39</td>
<td>-7.98</td>
<td>450</td>
<td>100</td>
<td>150</td>
<td>200</td>
<td>600</td>
<td>600</td>
<td>400</td>
<td>1600</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -135,46 +182,222 @@
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-card style="border-radius: 8px; height: 100%"> <el-card style="border-radius: 8px; height: 100%">
<h3 class="">各電表總費用佔比</h3> <h3 class="">各電表用電量比較(kWh)</h3>
<EnergyPie /> <EnergyBar
:chartData="elecUsageData"
/>
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-card style="border-radius: 8px; height: 100%"> <el-card style="border-radius: 8px; height: 100%">
<h3 class="">各電表用電量比較(kWh)</h3> <h3 class="">各電表總費用佔比</h3>
<EnergyBar :chartData="elecUsageData" /> <EnergyPie
:chartData="elecCostData"
/>
</el-card> </el-card>
</el-col> </el-col>
</el-row> </el-row>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { defineProps, computed } from "vue";
import EnergyPie from "../components/EnergyPie.vue"; import EnergyPie from "../components/EnergyPie.vue";
import EnergyBar from "../components/EnergyBar.vue"; import EnergyBar from "../components/EnergyBar.vue";
import useElecPriceStore from "../stores/useElecPriceStore";
import useElecReportStore from "../stores/useElecReportStore";
import getRandomColor from "../utils/getRandomColor";
import dayjs from "dayjs";
const elecUsageData = ref({ const props = defineProps({
categories: ["電表01", "電表02", "電表03"], form: {
series: [ type: Object,
{ },
name: "尖峰用電", });
type: "bar", const storeElecPrice = useElecPriceStore();
data: [150, 300, 100], const storeElecReport = useElecReportStore();
itemStyle: { color: "#5470c6" }, const elecPrices = storeElecPrice.elecData;
},
{ const isSummerMonth = computed(() => {
name: "半尖峰用電", const month = dayjs(props.form?.date).month(); // month() 0-11
type: "bar", return month >= 5 && month <= 8; // 6 59 8
data: [200, 300, 150], });
itemStyle: { color: "#91cc75" },
}, const priceTitle = computed(() => {
{ return isSummerMonth.value ? "單價(NTD/kWh)-夏月" : "單價(NTD/kWh)-非夏月";
name: "離峰用電", });
type: "bar",
data: [250, 300, 200], const formattedElecCostSummary = computed(() => {
itemStyle: { color: "#fac858" }, const summaryThisMonth = storeElecReport.elecCostSummary.thisMonth;
}, const summaryLastMonth = storeElecReport.elecCostSummary.lastMonth;
],
if (!summaryThisMonth) {
return [];
}
return Object.keys(summaryThisMonth).map((key) => {
const meterDataThisMonth = summaryThisMonth[key];
const meterDataLastMonth = summaryLastMonth?.[key]; // 使
const totalUsageThisMonth = meterDataThisMonth?.totalEleCost || 0;
const totalUsageLastMonth = meterDataLastMonth?.totalEleCost || 0;
const usageChange = totalUsageThisMonth - totalUsageLastMonth;
const usageChangeRate =
totalUsageLastMonth !== 0 ? (usageChange / totalUsageLastMonth) * 100 : 0; // 0
return {
name: meterDataThisMonth?.name,
area: meterDataThisMonth?.area,
lastMonthUsage: totalUsageLastMonth,
usageChange: usageChange,
usageChangeRate: usageChangeRate,
totalUsage: totalUsageThisMonth,
peakUsage: meterDataThisMonth?.total_peak || 0,
halfPeakUsage: meterDataThisMonth?.total_half || 0,
offPeakUsage: meterDataThisMonth?.total_Off || 0,
peakCost: meterDataThisMonth?.total_peakCost || 0,
halfPeakCost: meterDataThisMonth?.total_halfCost || 0,
offPeakCost: meterDataThisMonth?.total_OffCost || 0,
totalCost: meterDataThisMonth?.totalFlowCost || 0,
};
});
});
//
const totalStandCost = computed(() => {
if (!storeElecReport.elecCostSummary.thisMonth) {
return 0;
}
const elecCostSummaryArray = Object.values(
storeElecReport.elecCostSummary.thisMonth
);
if (elecCostSummaryArray.length === 0) {
return 0;
}
return elecCostSummaryArray[0]?.standCost || 0;
});
const totalFlowCost = computed(() => {
if (!storeElecReport.elecCostSummary.thisMonth) {
return 0;
}
let total = 0;
Object.keys(storeElecReport.elecCostSummary.thisMonth).forEach((key) => {
// @ts-ignore
total += storeElecReport.elecCostSummary.thisMonth[key]?.totalFlowCost || 0;
});
return total;
});
const totalCost = computed(() => {
return totalStandCost.value + totalFlowCost.value;
});
const Three_Phase =
elecPrices.find((item: any) => item.displayName === "基本按戶三相")?.out || 0;
//
const Regular_Contract_Summer =
elecPrices.find((item: any) => item.displayName === "基本經常夏月")?.out || 0;
const Summer_Peak_Prices =
elecPrices.find((item: any) => item.displayName === "流動平日尖峰夏月")
?.out || 0;
const Summer_HalfPeak_Prices_Weekday =
elecPrices.find((item: any) => item.displayName === "流動平日半尖峰夏月")
?.out || 0;
const Summer_Off_Prices_Weekday =
elecPrices.find((item: any) => item.displayName === "流動平日離峰夏月")
?.out || 0;
const Summer_HalfPeak_Prices_Saturday =
elecPrices.find((item: any) => item.displayName === "流動週六半尖峰夏月")
?.out || 0;
const Summer_Off_Prices_Saturday =
elecPrices.find((item: any) => item.displayName === "流動週六離峰夏月")
?.out || 0;
const Summer_Off_Prices =
elecPrices.find((item: any) => item.displayName === "流動週日離峰夏月")
?.out || 0;
//
const Regular_Contract_Non_Summer =
elecPrices.find((item: any) => item.displayName === "基本經常非夏月")?.out ||
0;
const Non_Summer_HalfPeak_Prices_Weekday =
elecPrices.find((item: any) => item.displayName === "流動平日半尖峰非夏月")
?.out || 0;
const Non_Summer_Off_Prices_Weekday =
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_Off_Prices_Saturday =
elecPrices.find((item: any) => item.displayName === "流動週六離峰非夏月")
?.out || 0;
const Non_Summer_Off_Prices =
elecPrices.find((item: any) => item.displayName === "流動週日離峰非夏月")
?.out || 0;
const elecUsageData = computed(() => {
const summaryThisMonth = storeElecReport.elecCostSummary.thisMonth;
if (!summaryThisMonth) {
return {
categories: [],
series: [],
};
}
const categories = Object.keys(summaryThisMonth);
const peakData = categories.map(
(key) => summaryThisMonth[key]?.total_peak || 0
);
const halfPeakData = categories.map(
(key) => summaryThisMonth[key]?.total_half || 0
);
const offPeakData = categories.map(
(key) => summaryThisMonth[key]?.total_Off || 0
);
return {
categories: categories.map((key) => summaryThisMonth[key]?.name || key),
series: [
{
name: "尖峰用電",
type: "bar",
data: peakData,
itemStyle: { color: "#5470c6" },
},
{
name: "半尖峰用電",
type: "bar",
data: halfPeakData,
itemStyle: { color: "#91cc75" },
},
{
name: "離峰用電",
type: "bar",
data: offPeakData,
itemStyle: { color: "#fac858" },
},
],
};
});
const elecCostData = computed(() => {
const summaryThisMonth = storeElecReport.elecCostSummary.thisMonth;
if (!summaryThisMonth) {
return { series: [] };
}
getRandomColor.resetIndex();
const series = Object.keys(summaryThisMonth).map((key) => {
const meterData = summaryThisMonth[key];
return {
name: meterData?.name || key,
value: meterData?.totalFlowCost.toFixed(2) || 0,
itemStyle: { color: getRandomColor() },
};
});
return { series };
}); });
</script> </script>

View File

@ -1,6 +1,7 @@
import { ref } from "vue"; import { ref } from "vue";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import type { NiagaraElecData } from "../utils/types"; import type { NiagaraElecData } from "../utils/types";
import axios from "axios";
const useElecPriceStore = defineStore("elecPriceData", () => { const useElecPriceStore = defineStore("elecPriceData", () => {
const elecData = ref<NiagaraElecData[]>([]); const elecData = ref<NiagaraElecData[]>([]);
@ -101,6 +102,30 @@ const useElecPriceStore = defineStore("elecPriceData", () => {
}); });
}; };
const updatePrice = async (slotPath: string, out: number) => {
const domain = window.location.origin;
try {
console.log("updatePrice",`${domain}/obix/config/${slotPath}/in2/value`)
const res = await axios.put(
`${domain}/obix/config/${slotPath}/in2/value`,
`<real name="in" val="${out}"/> `,
{
headers: { "Content-Type": "text/xml" },
}
);
if (res.status === 200) {
console.log(`成功更新 ${slotPath}${out}`);
return true;
} else {
console.error(`更新 ${slotPath} 失敗,狀態碼: ${res.status}`);
return false;
}
} catch (error) {
console.error(`更新 ${slotPath} 出錯:`, error);
return false;
}
};
const clearAllSubscriber = () => { const clearAllSubscriber = () => {
subscribers.value.forEach((subscriber) => { subscribers.value.forEach((subscriber) => {
subscriber.detach("changed"); subscriber.detach("changed");
@ -112,6 +137,7 @@ const useElecPriceStore = defineStore("elecPriceData", () => {
return { return {
getElecDataFromBaja, getElecDataFromBaja,
updatePrice,
clearAllSubscriber, clearAllSubscriber,
elecData, elecData,
}; };

View File

@ -0,0 +1,202 @@
import { ref } from "vue";
import { defineStore } from "pinia";
import dayjs from "dayjs";
import type { ElecCostSummaryMap } from "../utils/types";
import { CalcuEleCost, CalcuEleStandCost } from "../utils/CalcuEleCost";
const useElecReportStore = defineStore("elecReportData", () => {
const elecData = ref<any[]>([]);
// @ts-ignore
let timerId = null;
// 初始化為空物件,並定義類型
const elecCostSummary = ref<ElecCostSummaryMap>({
thisMonth: {},
lastMonth: {},
});
// 儲存每個電表的 dataMap
const elecDataMaps = ref<{
[id: string]: {
thisMonth: Map<string, number>;
lastMonth: Map<string, number>;
};
}>({});
const startTime = ref("");
const endTime = ref("");
// 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:SubSys_kwh|bql:select slotPath,parent.displayName,displayName,NumericInterval.historyConfig.id,out`;
// 執行各電表的 BQL 查詢
fetchElecData(baja, Total_kwhBql);
});
};
const fetchElecData = (baja: any, bql: string) => {
let eleclist: any[] = [];
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,
area: record.get("out")?.get("value") ?? "",
});
},
after: () => {
const validElecList = eleclist.filter(
(item) => item.id !== undefined
);
const menoElecList = eleclist.filter((item) => item.id == undefined);
const displayNameToAreaMap = new Map<string, string>();
menoElecList.forEach((item) => {
displayNameToAreaMap.set(item.displayName, item.area);
});
validElecList.forEach((item) => {
const area = displayNameToAreaMap.get(item.displayName);
if (area) {
item.area = area; // 將 area 賦值給 validElecList 中的 item
}
});
elecData.value = [];
elecData.value.push(...validElecList);
validElecList.forEach((item) => {
const id = item.id;
elecDataMaps.value[id] = {
thisMonth: new Map<string, number>(),
lastMonth: new Map<string, number>(),
};
// 初始化每个 slotPath 的值为 undefined 或 null
elecCostSummary.value.thisMonth[id] = undefined;
elecCostSummary.value.lastMonth[id] = undefined;
getTimeToHistory(item, 'thisMonth'); // 獲取本月
getTimeToHistory(item, 'lastMonth'); // 獲取上個月
});
},
},
});
};
const getTimeToHistory = (item: any, month: 'thisMonth' | 'lastMonth') => {
const id = item.id;
let startTimeValue = startTime.value;
let endTimeValue = endTime.value;
if (month === 'lastMonth') {
startTimeValue = dayjs(startTime.value).subtract(1, 'month').startOf('month').format("YYYY-MM-DDTHH:mm:ss.000+08:00");
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
console.log(ordString);
// @ts-ignore
window.require &&
// @ts-ignore
window.requirejs(["baja!"], (baja: any) => {
// 使用對應電表的 dataMap
const dataMap = elecDataMaps.value[id][month];
if (!dataMap) {
console.warn(`未找到電表 ${id} (${month}) 的 dataMap`);
return;
}
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: () => {
const elecCost = CalcuEleCost(dataMap);
const eleceStandCost = CalcuEleStandCost(dataMap);
console.log(`⏱️ 電表 ${id} 每小時差值 map`, dataMap);
if (item.slotPath) {
// 确保 slotPath 存在
elecCostSummary.value[month][id] = {
...elecCost,
standCost: eleceStandCost.StandCost,
name: item.displayName,
area: item.area,
};
console.log(
`電表 ${item.displayName} 的電費計算結果 (${month})`,
elecCostSummary.value[month][id]
);
} else {
console.warn(
`電表 ${id} 的 slotPath 為空,無法儲存電費計算結果 (${month})`
);
}
},
limit: -1,
offset: 0,
},
});
});
};
// 定時更新資料的函數
const updateHistoryData = () => {
console.log("重新獲取歷史資料");
if (elecData.value && elecData.value.length > 0) {
elecCostSummary.value.thisMonth = {};
elecCostSummary.value.lastMonth = {};
elecData.value.forEach((item) => {
const id = item.id;
elecDataMaps.value[id] = {
thisMonth: new Map<string, number>(),
lastMonth: new Map<string, number>(),
};
getTimeToHistory(item, 'thisMonth');
getTimeToHistory(item, 'lastMonth');
});
}
};
return {
getElecDataFromBaja,
elecData,
elecCostSummary,
startTime,
endTime,
updateHistoryData,
};
});
export default useElecReportStore;

View File

@ -1,14 +1,19 @@
import { ref } from "vue"; import { ref } from "vue";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import dayjs from "dayjs"; import dayjs from "dayjs";
import type { NiagaraElecData, ElecCostSummary } from "../utils/types"; import type {
import { CalcuEleCost } from "../utils/CalcuEleCost"; NiagaraElecData,
ElecCostSummary,
ElecStandCostSummary,
} from "../utils/types";
import { CalcuEleCost, CalcuEleStandCost } from "../utils/CalcuEleCost";
const useElecStore = defineStore("elecData", () => { const useElecStore = defineStore("elecData", () => {
const elecData = ref<NiagaraElecData[]>([]); const elecData = ref<NiagaraElecData[]>([]);
// @ts-ignore // @ts-ignore
let timerId = null; let timerId = null;
const elecCostSummary = ref<ElecCostSummary | null>(null); const elecFlowCostSummary = ref<ElecCostSummary | null>(null);
const elecStandCostSummary = ref<ElecStandCostSummary | null>(null);
// get data from baja // get data from baja
const getElecDataFromBaja = () => { const getElecDataFromBaja = () => {
@ -78,8 +83,16 @@ const useElecStore = defineStore("elecData", () => {
each: (record: any) => { each: (record: any) => {
const currentValue = record.get("min"); const currentValue = record.get("min");
const timestamp = record.get("timestamp").$cEncStr; const timestamp = record.get("timestamp").$cEncStr;
if (currentValue !== null && currentValue !== 0 && timestamp !== null) { if (
if (lastTimestamp !== null && lastValue !== null && lastValue !== 0) { currentValue !== null &&
currentValue !== 0 &&
timestamp !== null
) {
if (
lastTimestamp !== null &&
lastValue !== null &&
lastValue !== 0
) {
const diff = currentValue - lastValue; const diff = currentValue - lastValue;
dataMap.set(lastTimestamp, diff); dataMap.set(lastTimestamp, diff);
} }
@ -89,7 +102,8 @@ const useElecStore = defineStore("elecData", () => {
}, },
after: () => { after: () => {
console.log("⏱️ 每小時差值 map", dataMap); console.log("⏱️ 每小時差值 map", dataMap);
elecCostSummary.value = CalcuEleCost(dataMap); elecFlowCostSummary.value = CalcuEleCost(dataMap);
elecStandCostSummary.value = CalcuEleStandCost(dataMap);
}, },
limit: -1, limit: -1,
offset: 0, offset: 0,
@ -129,7 +143,8 @@ const useElecStore = defineStore("elecData", () => {
startTimer, startTimer,
stopTimer, stopTimer,
elecData, elecData,
elecCostSummary, elecFlowCostSummary,
elecStandCostSummary,
}; };
}); });

View File

@ -1,10 +1,18 @@
import type { DailyResult, ElecCostSummary, DailyEntry } from "../utils/types"; import type {
DailyResult,
ElecCostSummary,
DailyEntry,
StandEntry,
ElecStandCostSummary,
StandResult,
} from "../utils/types";
import useElecPriceStore from "../stores/useElecPriceStore"; import useElecPriceStore from "../stores/useElecPriceStore";
const storeElecPrice = useElecPriceStore(); import useElecDemandStore from "../stores/useElecDemandStore";
export const CalcuEleCost = ( const storeElecPrice = useElecPriceStore();
input: Map<string, number> const ContractUseValue = useElecDemandStore();
): ElecCostSummary => {
export const CalcuEleCost = (input: Map<string, number>): ElecCostSummary => {
const dailyData: Map<string, DailyEntry[]> = new Map(); const dailyData: Map<string, DailyEntry[]> = new Map();
let totalFlowCost = 0; // 總電價 let totalFlowCost = 0; // 總電價
let totalEleCost = 0; //總用電 let totalEleCost = 0; //總用電
@ -17,13 +25,27 @@ export const CalcuEleCost = (
const dailyResults: DailyResult[] = []; const dailyResults: DailyResult[] = [];
const elecPrices = storeElecPrice.elecData; const elecPrices = storeElecPrice.elecData;
const Summer_Off_Prices = elecPrices.find((item:any) => item.displayName === "流動週日離峰夏月")?.out || 0; const Summer_Off_Prices =
const Summer_HalfPeak_Prices_Saturday = elecPrices.find((item:any) => item.displayName === "流動週六半尖峰夏月")?.out || 0; elecPrices.find((item: any) => item.displayName === "流動週日離峰夏月")
const Summer_HalfPeak_Prices_Weekday = elecPrices.find((item:any) => item.displayName === "流動平日半尖峰夏月")?.out || 0; ?.out || 0;
const Summer_Peak_Prices = elecPrices.find((item:any) => item.displayName === "流動平日尖峰夏月")?.out || 0; const Summer_HalfPeak_Prices_Saturday =
const Non_Summer_Off_Prices = elecPrices.find((item:any) => item.displayName === "流動週日離峰非夏月")?.out || 0; elecPrices.find((item: any) => item.displayName === "流動週六半尖峰夏月")
const Non_Summer_HalfPeak_Prices_Saturday = elecPrices.find((item:any) => item.displayName === "流動週六半尖峰非夏月")?.out || 0; ?.out || 0;
const Non_Summer_HalfPeak_Prices_Weekday = 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. 將輸入資料按日期分組 // 1. 將輸入資料按日期分組
input.forEach((value, key) => { input.forEach((value, key) => {
const dateStr = key.substring(0, 10); const dateStr = key.substring(0, 10);
@ -117,7 +139,6 @@ export const CalcuEleCost = (
dailyEleCost, dailyEleCost,
dailyFlowCost, dailyFlowCost,
}); });
} }
return { return {
dailyResults, dailyResults,
@ -131,3 +152,72 @@ export const CalcuEleCost = (
total_peakCost, total_peakCost,
}; };
}; };
export const CalcuEleStandCost = (
input: Map<string, number>
): ElecStandCostSummary => {
const elecPrices = storeElecPrice.elecData;
const ContractUseData = ContractUseValue.elecData;
const Three_Phase =
elecPrices.find((item: any) => item.displayName === "基本按戶三相")?.out ||
0;
const Summer_Regular_Use =
elecPrices.find((item: any) => item.displayName === "基本經常夏月")?.out ||
0;
const Non_Summer_Regular_Use =
elecPrices.find((item: any) => item.displayName === "基本經常非夏月")
?.out || 0;
const ContractUse =
ContractUseData.find((item: any) => item.name === "Engel")?.out || 0;
const monthMap = new Map<string, StandEntry[]>(); // key: yyyy-MM
// 將資料依據年月分組
input.forEach((value, key) => {
const date = new Date(key);
const year = date.getFullYear();
const month = date.getMonth() + 1;
const ymKey = `${year}-${month.toString().padStart(2, "0")}`;
if (!monthMap.has(ymKey)) {
monthMap.set(ymKey, []);
}
monthMap.get(ymKey)?.push({ time: date, value });
});
const StandResults: StandResult[] = [];
let totalStandCost = 0;
for (const [ym, entries] of monthMap.entries()) {
if (!entries || entries.length === 0) continue;
const sampleDate = entries[0].time;
const month = sampleDate.getMonth() + 1;
const isSummer = month >= 6 && month <= 9;
let Phase = Three_Phase;
let Contract = isSummer ? Summer_Regular_Use : Non_Summer_Regular_Use;
const StandCost = Phase + Contract * ContractUse;
StandResults.push({
Phase,
Contract,
ContractUse,
StandCost,
});
totalStandCost += StandCost;
console.log(`=== ${ym} ===`);
console.log(` 按戶類別: ${Phase.toFixed(2)}`);
console.log(` 契約類型: $${Contract.toFixed(2)}`);
console.log(` 契約度數: $${ContractUse.toFixed(2)}`);
console.log(` 基本電價: $${StandCost.toFixed(2)}`);
}
return {
StandResults,
StandCost: totalStandCost,
};
};

View File

@ -0,0 +1,21 @@
const predefinedColors = ["#5470c6", "#91cc75", "#fac858"];
let colorIndex = 0;
const getRandomColor = () => {
if (colorIndex < predefinedColors.length) {
return predefinedColors[colorIndex++];
} else {
const letters = "0123456789ABCDEF";
let color = "#";
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
};
getRandomColor.resetIndex = () => {
colorIndex = 0;
};
export default getRandomColor;

View File

@ -18,9 +18,9 @@ export interface DailyResult {
off: number; off: number;
half: number; half: number;
peak: number; peak: number;
offcost:number; offcost: number;
halfcost:number; halfcost: number;
peakcost:number; peakcost: number;
dailyEleCost: number; dailyEleCost: number;
dailyFlowCost: number; dailyFlowCost: number;
} }
@ -35,9 +35,38 @@ export interface ElecCostSummary {
total_OffCost: number; total_OffCost: number;
total_halfCost: number; total_halfCost: number;
total_peakCost: number; total_peakCost: number;
standCost?:number;
name?: string;
area?: string;
}
export interface ElecCostSummaryMap {
thisMonth: {
[slotPath: string]: ElecCostSummary | null | undefined;
};
lastMonth: {
[slotPath: string]: ElecCostSummary | null | undefined;
};
} }
export interface DailyEntry { export interface DailyEntry {
time: Date; time: Date;
value: number; value: number;
} }
export interface StandEntry {
time: Date;
value: number;
}
export interface StandResult {
Phase : number ;
Contract : number ;
ContractUse : number;
StandCost :number ;
}
export interface ElecStandCostSummary {
StandResults: StandResult[];
StandCost :number ;
}

View File

@ -73,11 +73,19 @@ import useElecTotalMeterStore from "../stores/useElecTotalMeterStore";
import dayjs from "dayjs"; import dayjs from "dayjs";
const storeElecTotal = useElecTotalMeterStore(); const storeElecTotal = useElecTotalMeterStore();
const billingDateRange = ref("");
watch( watch(
() => storeElecTotal.elecCostSummary, () => storeElecTotal.elecFlowCostSummary,
(newElecData) => { (newElecData) => {
console.log("elecCostSummary", newElecData); console.log("elecFlowCostSummary", newElecData);
},
{ deep: true }
);
watch(
() => storeElecTotal.elecStandCostSummary,
(newElecData) => {
console.log("elecStandCostSummary", newElecData);
}, },
{ deep: true } { deep: true }
); );
@ -85,6 +93,11 @@ watch(
onMounted(async () => { onMounted(async () => {
await storeElecTotal.getElecDataFromBaja(); await storeElecTotal.getElecDataFromBaja();
storeElecTotal.startTimer(); storeElecTotal.startTimer();
//
billingDateRange.value = `${dayjs()
.startOf("month")
.format("YYYY/MM/DD")} - ${dayjs().format("YYYY/MM/DD")}`;
}); });
onUnmounted(() => { onUnmounted(() => {
@ -97,8 +110,8 @@ const statisticData = computed(() => {
let intervalFlowCost = 0; let intervalFlowCost = 0;
let intervalEleCost = 0; let intervalEleCost = 0;
if (storeElecTotal.elecCostSummary?.dailyResults) { if (storeElecTotal.elecFlowCostSummary?.dailyResults) {
storeElecTotal.elecCostSummary.dailyResults.forEach((dailyResult) => { storeElecTotal.elecFlowCostSummary.dailyResults.forEach((dailyResult) => {
if (dailyResult.dateStr.startsWith(currentMonth)) { if (dailyResult.dateStr.startsWith(currentMonth)) {
intervalFlowCost += dailyResult.dailyFlowCost; intervalFlowCost += dailyResult.dailyFlowCost;
intervalEleCost += dailyResult.dailyEleCost; intervalEleCost += dailyResult.dailyEleCost;
@ -109,13 +122,13 @@ const statisticData = computed(() => {
return [ return [
{ {
title: "今年電費累計", title: "今年電費累計",
value: storeElecTotal.elecCostSummary?.totalFlowCost || 0, value: storeElecTotal.elecFlowCostSummary?.totalFlowCost || 0,
unit: "元", unit: "元",
}, },
{ title: "區間電費", value: intervalFlowCost, unit: "元" }, { title: "區間電費", value: intervalFlowCost, unit: "元" },
{ {
title: "今年碳排當量累計", title: "今年碳排當量累計",
value: storeElecTotal.elecCostSummary?.totalEleCost * 0.424, value: storeElecTotal.elecFlowCostSummary?.totalEleCost * 0.424,
unit: "公斤 CO2e/度", unit: "公斤 CO2e/度",
}, },
{ {
@ -125,7 +138,7 @@ const statisticData = computed(() => {
}, },
{ {
title: "今年用電度數", title: "今年用電度數",
value: storeElecTotal.elecCostSummary?.totalEleCost || 0, value: storeElecTotal.elecFlowCostSummary?.totalEleCost || 0,
unit: "kWh", unit: "kWh",
}, },
{ title: "區間用電度數", value: intervalEleCost, unit: "kWh" }, { title: "區間用電度數", value: intervalEleCost, unit: "kWh" },
@ -162,22 +175,24 @@ function groupByMonth(dailyResults) {
// //
const monthlyElectricityData = computed(() => { const monthlyElectricityData = computed(() => {
if (!storeElecTotal.elecCostSummary?.dailyResults) { if (!storeElecTotal.elecFlowCostSummary?.dailyResults) {
return { categories: [], series: [] }; return { categories: [], series: [] };
} }
const groupedData = groupByMonth(storeElecTotal.elecCostSummary.dailyResults); const groupedData = groupByMonth(
storeElecTotal.elecFlowCostSummary.dailyResults
);
const categories = Object.keys(groupedData); const categories = Object.keys(groupedData);
const sortedCategories = _.sortBy(categories, (month) => const sortedCategories = _.sortBy(categories, (month) =>
dayjs().month(month).valueOf() dayjs().month(month).valueOf()
); );
const baseElecData = sortedCategories.map((month) => groupedData[month].offcost); const baseElecData = storeElecTotal.elecStandCostSummary.StandCost;
const flowElecData = sortedCategories.map( const flowElecData = sortedCategories.map(
(month) => groupedData[month].totalCost (month) => groupedData[month].totalCost
); );
const totalElecData = sortedCategories.map((month, index) => { const totalElecData = sortedCategories.map((month, index) => {
return (baseElecData[index] || 0) + (flowElecData[index] || 0); return baseElecData + (flowElecData[index] || 0);
}); });
return { return {
@ -186,7 +201,7 @@ const monthlyElectricityData = computed(() => {
{ {
name: "基本電費", name: "基本電費",
type: "bar", type: "bar",
data: baseElecData, data: Array(sortedCategories.length).fill(baseElecData),
}, },
{ {
name: "流動電費", name: "流動電費",
@ -194,7 +209,7 @@ const monthlyElectricityData = computed(() => {
data: flowElecData, data: flowElecData,
}, },
{ {
name: "總電", name: "總電",
type: "bar", type: "bar",
data: totalElecData, data: totalElecData,
}, },
@ -204,11 +219,13 @@ const monthlyElectricityData = computed(() => {
// //
const monthlyCarbonData = computed(() => { const monthlyCarbonData = computed(() => {
if (!storeElecTotal.elecCostSummary?.dailyResults) { if (!storeElecTotal.elecFlowCostSummary?.dailyResults) {
return { categories: [], series: [] }; return { categories: [], series: [] };
} }
const groupedData = groupByMonth(storeElecTotal.elecCostSummary.dailyResults); const groupedData = groupByMonth(
storeElecTotal.elecFlowCostSummary.dailyResults
);
const categories = Object.keys(groupedData); const categories = Object.keys(groupedData);
const sortedCategories = _.sortBy(categories, (month) => const sortedCategories = _.sortBy(categories, (month) =>
dayjs().month(month).valueOf() dayjs().month(month).valueOf()
@ -232,76 +249,82 @@ const monthlyCarbonData = computed(() => {
// (kWh) // (kWh)
const monthlyBillingData = computed(() => { const monthlyBillingData = computed(() => {
if (!storeElecTotal.elecCostSummary?.dailyResults) { if (!storeElecTotal.elecFlowCostSummary?.dailyResults) {
return { categories: [], series: [] }; return { categories: [], series: [] };
} }
const billingData = groupByMonth(storeElecTotal.elecCostSummary.dailyResults); const billingData = groupByMonth(
storeElecTotal.elecFlowCostSummary.dailyResults
);
const categories = Object.keys(billingData); const categories = Object.keys(billingData);
const sortedCategories = _.sortBy(categories, month => dayjs().month(month).valueOf()); const sortedCategories = _.sortBy(categories, (month) =>
dayjs().month(month).valueOf()
);
const peakData = sortedCategories.map(date => billingData[date].peak); const peakData = sortedCategories.map((date) => billingData[date].peak);
const halfData = sortedCategories.map(date => billingData[date].half); const halfData = sortedCategories.map((date) => billingData[date].half);
const offData = sortedCategories.map(date => billingData[date].off); const offData = sortedCategories.map((date) => billingData[date].off);
return { return {
categories: sortedCategories, categories: sortedCategories,
series: [ series: [
{ name: "尖峰", type: "bar", data: peakData }, { name: "尖峰", type: "bar", data: peakData },
{ name: "半尖峰", type: "bar", data: halfData }, { name: "半尖峰", type: "bar", data: halfData },
{ name: "離峰", type: "bar", data: offData } { name: "離峰", type: "bar", data: offData },
] ],
}; };
}); });
//
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(() => { const areaBillingData = computed(() => {
if (!storeElecTotal.elecCostSummary?.dailyResults) { if (!storeElecTotal.elecFlowCostSummary?.dailyResults) {
return { categories: [], series: [] }; return { categories: [], series: [] };
} }
const today = dayjs(); const today = dayjs();
const currentMonth = today.format('YYYY-MM'); const currentMonth = today.format("YYYY-MM");
const startDate = dayjs(`${currentMonth}-01`); // Get the first day of the current month const startDate = dayjs(`${currentMonth}-01`); // Get the first day of the current month
const endDate = today; const endDate = today;
// Filter daily results within the specified date range // Filter daily results within the specified date range
const areaResults = storeElecTotal.elecCostSummary.dailyResults.filter(result => { const areaResults = storeElecTotal.elecFlowCostSummary.dailyResults.filter(
const resultDate = dayjs(result.dateStr); (result) => {
return resultDate.isSame(startDate, 'day') || (resultDate.isAfter(startDate, 'day') && resultDate.isBefore(endDate, 'day')) || resultDate.isSame(endDate, 'day'); 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 // Transform the filtered data for billing
const transformedData = {}; const transformedData = {};
areaResults.forEach(result => { areaResults.forEach((result) => {
const date = dayjs(result.dateStr).format('MM-DD'); // Format the date for the category const date = dayjs(result.dateStr).format("MM-DD"); // Format the date for the category
transformedData[date] = { transformedData[date] = {
peak: result.peak, peak: result.peak,
half: result.half, half: result.half,
off: result.off off: result.off,
}; };
}); });
const categories = Object.keys(transformedData); const categories = Object.keys(transformedData);
const sortedCategories = _.sortBy(categories, date => dayjs(date, 'MM-DD').valueOf()); const sortedCategories = _.sortBy(categories, (date) =>
dayjs(date, "MM-DD").valueOf()
);
const peakData = sortedCategories.map(date => transformedData[date].peak); const peakData = sortedCategories.map((date) => transformedData[date].peak);
const halfData = sortedCategories.map(date => transformedData[date].half); const halfData = sortedCategories.map((date) => transformedData[date].half);
const offData = sortedCategories.map(date => transformedData[date].off); const offData = sortedCategories.map((date) => transformedData[date].off);
return { return {
categories: sortedCategories, categories: sortedCategories,
series: [ series: [
{ name: "尖峰", type: "bar", data: peakData }, { name: "尖峰", type: "bar", data: peakData },
{ name: "半尖峰", type: "bar", data: halfData }, { name: "半尖峰", type: "bar", data: halfData },
{ name: "離峰", type: "bar", data: offData } { name: "離峰", type: "bar", data: offData },
] ],
}; };
}); });
</script> </script>

View File

@ -311,7 +311,6 @@
import { ref, watch } from "vue"; import { ref, watch } from "vue";
import { Edit, CircleClose, CircleCheck } from "@element-plus/icons-vue"; import { Edit, CircleClose, CircleCheck } from "@element-plus/icons-vue";
import useElecPriceStore from "../stores/useElecPriceStore"; import useElecPriceStore from "../stores/useElecPriceStore";
import axios from "axios";
const storeElecPrice = useElecPriceStore(); const storeElecPrice = useElecPriceStore();
@ -360,30 +359,6 @@ const resetStand3Values = () => {
} }
}; };
const updatePrice = async (slotPath: string, out: number) => {
const domain = window.location.origin;
try {
console.log("updatePrice",`${domain}/obix/config/${slotPath}/in2/value`)
const res = await axios.put(
`${domain}/obix/config/${slotPath}/in2/value`,
`<real name="in" val="${out}"/> `,
{
headers: { "Content-Type": "text/xml" },
}
);
if (res.status === 200) {
console.log(`成功更新 ${slotPath}${out}`);
return true;
} else {
console.error(`更新 ${slotPath} 失敗,狀態碼: ${res.status}`);
return false;
}
} catch (error) {
console.error(`更新 ${slotPath} 出錯:`, error);
return false;
}
};
const confirmChanges = async () => { const confirmChanges = async () => {
stand3isEditing.value = false; stand3isEditing.value = false;
@ -419,7 +394,7 @@ const confirmChanges = async () => {
? item.slotPath.slice(6) // 移除 "slot:/" ? item.slotPath.slice(6) // 移除 "slot:/"
: item.slotPath; // : item.slotPath; //
// Niagara // Niagara
const success = await updatePrice(slotPath, stand3Value.value[i]); const success = await storeElecPrice.updatePrice(slotPath, stand3Value.value[i]);
if (!success) { if (!success) {
failedUpdates.push({ failedUpdates.push({
slotPath: item.slotPath, slotPath: item.slotPath,

View File

@ -1,17 +1,52 @@
<template> <template>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="24"> <el-col :span="24" style="padding-right: 30px">
<div style="display: flex; justify-content: flex-end"> <div style="display: flex; justify-content: flex-end">
<el-button type="primary" :icon="Printer" @click="generatePDF" <el-button
plain
type="primary"
:icon="Setting"
@click="dialogVisible = true"
>
設定報表
</el-button>
<el-button plain type="success" :icon="Printer" @click="generatePDF"
>列印報表</el-button >列印報表</el-button
> >
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
<!-- 顯示在畫面上的內容 --> <!-- 設定 Modal -->
<PdfContent /> <EnergyModal
v-model="dialogVisible"
<!-- vue3-html2pdf 用的內容 --> title="設定報表"
:confirm="handleConfirm"
:close="handleClose"
>
<template #default>
<!-- Modal 的內容 -->
<el-form :model="form" label-width="auto" style="max-width: 600px">
<el-form-item label="報表名稱">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="報表期間">
<el-date-picker
v-model="form.date"
type="month"
placeholder="請選擇報表時間"
style="width: 100%"
value-format="YYYY-MM-DD"
/>
</el-form-item>
<el-form-item label="編製人員">
<el-input v-model="form.staff" />
</el-form-item>
</el-form>
</template>
</EnergyModal>
<!-- 顯示在畫面上的內容 -->
<PdfContent :form="originalForm" />
<!-- vue3-html2pdf 用的內容 -->
<vue3-html2pdf <vue3-html2pdf
ref="html2Pdf" ref="html2Pdf"
:show-layout="false" :show-layout="false"
@ -27,30 +62,101 @@
pdf-content-width="1120px" pdf-content-width="1120px"
> >
<template #pdf-content> <template #pdf-content>
<PdfContent /> <PdfContent :form="originalForm" />
</template> </template>
</vue3-html2pdf> </vue3-html2pdf>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref, reactive, watch, onMounted, onUnmounted } from "vue";
import PdfContent from "../components/PdfContent.vue"; import PdfContent from "../components/PdfContent.vue";
import { Printer } from "@element-plus/icons-vue"; import EnergyModal from "../components/EnergyModal.vue";
import { Setting, Printer } from "@element-plus/icons-vue";
import Vue3Html2pdf from "vue3-html2pdf"; import Vue3Html2pdf from "vue3-html2pdf";
import useElecReportStore from "../stores/useElecReportStore";
import dayjs from "dayjs";
const storeElecReport = useElecReportStore();
//
const dialogVisible = ref(false);
const form = reactive({
name: "智慧大樓電表月報表",
date: dayjs()
.startOf("month")
.startOf("day")
.format("YYYY-MM-DDTHH:mm:ss.000+08:00"),
staff: "能源管理部",
});
const originalForm = reactive({ ...form });
// PDF // PDF
const pdfFileName = ref("智慧大樓電表月報表"); const pdfFileName = ref("智慧大樓電表月報表");
// vue-html2pdf // vue-html2pdf
const html2Pdf = ref<InstanceType<typeof Vue3Html2pdf> | null>(null); const html2Pdf = ref<InstanceType<typeof Vue3Html2pdf> | null>(null);
//
const handleConfirm = () => {
console.log("確認", form);
localStorage.setItem("elecReportForm", JSON.stringify(form));
storeElecReport.startTime = dayjs(form.date)
.startOf("month")
.startOf("day")
.format("YYYY-MM-DDTHH:mm:ss.000+08:00");
storeElecReport.endTime = dayjs(form.date)
.endOf("month")
.endOf("day")
.format("YYYY-MM-DDTHH:mm:ss.000+08:00");
storeElecReport.updateHistoryData();
Object.assign(originalForm, form);
dialogVisible.value = false;
};
//
const handleClose = () => {
dialogVisible.value = false;
Object.assign(form, originalForm);
console.log("關閉");
};
// PDF // PDF
const generatePDF = async () => { const generatePDF = async () => {
if (html2Pdf.value) { if (html2Pdf.value) {
await html2Pdf.value.generatePdf(); await html2Pdf.value.generatePdf();
} }
}; };
watch(
() => storeElecReport.elecCostSummary,
(newElecData) => {
if(newElecData){
console.log("elecCostSummary",newElecData)
}
},
{ deep: true }
);
onMounted(async () => {
const storedForm = localStorage.getItem("elecReportForm");
if (storedForm) {
Object.assign(form, JSON.parse(storedForm));
} else {
localStorage.setItem("elecReportForm", JSON.stringify(form));
}
Object.assign(originalForm, form);
storeElecReport.startTime = dayjs(form.date)
.startOf("month")
.startOf("day")
.format("YYYY-MM-DDTHH:mm:ss.000+08:00");
storeElecReport.endTime = dayjs(form.date)
.endOf("month")
.endOf("day")
.format("YYYY-MM-DDTHH:mm:ss.000+08:00");
await storeElecReport.getElecDataFromBaja();
});
onUnmounted(() => {});
</script> </script>
<style scoped> <style scoped></style>
</style>