能源報表初版
This commit is contained in:
parent
59bf0fa00b
commit
756cbd3b7a
5
components.d.ts
vendored
5
components.d.ts
vendored
@ -13,8 +13,12 @@ declare module 'vue' {
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
|
||||
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']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElMain: typeof import('element-plus/es')['ElMain']
|
||||
@ -24,6 +28,7 @@ declare module 'vue' {
|
||||
ElStatistic: typeof import('element-plus/es')['ElStatistic']
|
||||
EnergyBar: typeof import('./src/components/EnergyBar.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']
|
||||
EnergySankey: typeof import('./src/components/EnergySankey.vue')['default']
|
||||
Navbar: typeof import('./src/components/Navbar.vue')['default']
|
||||
|
25
src/components/EnergyModal.vue
Normal file
25
src/components/EnergyModal.vue
Normal 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>
|
@ -1,30 +1,18 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, watch, onUnmounted } from "vue";
|
||||
import { ref, onMounted, watch, onUnmounted, defineProps } from "vue";
|
||||
import * as echarts from "echarts";
|
||||
|
||||
const chartContainer = ref(null);
|
||||
let chartInstance = null;
|
||||
|
||||
const chartData = ref({
|
||||
series: [
|
||||
{
|
||||
name: "電表01",
|
||||
value: 2200,
|
||||
itemStyle: { color: "#5470c6" },
|
||||
},
|
||||
{
|
||||
name: "電表02",
|
||||
value: 3600,
|
||||
itemStyle: { color: "#91cc75" },
|
||||
},
|
||||
{
|
||||
name: "電表03",
|
||||
value: 1600,
|
||||
itemStyle: { color: "#fac858" },
|
||||
},
|
||||
],
|
||||
const props = defineProps({
|
||||
chartData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const initChart = () => {
|
||||
chartInstance = echarts.init(chartContainer.value);
|
||||
|
||||
@ -37,7 +25,7 @@ const initChart = () => {
|
||||
orient: "vertical",
|
||||
left: "right",
|
||||
bottom: "0%",
|
||||
data: chartData.value.series.map((item) => item.name), // 添加图例
|
||||
data:props.chartData.series.map((item) => item.name), // 添加图例
|
||||
},
|
||||
series: [
|
||||
{
|
||||
@ -45,7 +33,7 @@ const initChart = () => {
|
||||
type: "pie",
|
||||
radius: "70%", // 饼图大小
|
||||
center: ["50%", "50%"], // 饼图中心位置
|
||||
data: chartData.value.series,
|
||||
data: props.chartData.series,
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
@ -65,7 +53,7 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
watch(
|
||||
() => chartData.value,
|
||||
() => props.chartData,
|
||||
(newChartData) => {
|
||||
if (chartInstance) {
|
||||
chartInstance.setOption({
|
||||
|
@ -1,57 +1,131 @@
|
||||
<template>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-col :span="7">
|
||||
<el-descriptions class="margin-top" title="報表資訊" :column="1" border>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="cell-item">報表名稱</div>
|
||||
</template>
|
||||
智慧大樓電表月報表
|
||||
{{ props.form?.name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="cell-item">報表期間</div>
|
||||
</template>
|
||||
2025/02/01 ~ 2025/02/29
|
||||
{{ dayjs(props.form?.date).format("YYYY/MM") }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="cell-item">編製日期</div>
|
||||
</template>
|
||||
2025/3/1
|
||||
{{ dayjs().format("YYYY/MM/DD") }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="cell-item">編製人員</div>
|
||||
</template>
|
||||
能源管理部
|
||||
{{ props.form?.staff }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :span="11">
|
||||
<el-descriptions
|
||||
class="margin-top"
|
||||
title="單價(NTD/kWh)"
|
||||
:column="1"
|
||||
:title="priceTitle"
|
||||
:column="2"
|
||||
border
|
||||
>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="cell-item">尖峰單價</div>
|
||||
<div class="cell-item">基本電費-三相</div>
|
||||
</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>
|
||||
<template #label>
|
||||
<div class="cell-item">半尖峰單價</div>
|
||||
<div class="cell-item">流動電價-平日半尖峰單價</div>
|
||||
</template>
|
||||
4
|
||||
{{
|
||||
isSummerMonth
|
||||
? Summer_HalfPeak_Prices_Weekday
|
||||
: Non_Summer_HalfPeak_Prices_Weekday
|
||||
}}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="cell-item">離峰單價</div>
|
||||
<div class="cell-item">流動電價-平日離峰單價</div>
|
||||
</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>
|
||||
</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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>電表01</td>
|
||||
<td>辦公室南區</td>
|
||||
<td>549</td>
|
||||
<td>51</td>
|
||||
<td>9.29</td>
|
||||
<td>600</td>
|
||||
<td>150</td>
|
||||
<td>200</td>
|
||||
<td>250</td>
|
||||
<td>900</td>
|
||||
<td>800</td>
|
||||
<td>500</td>
|
||||
<td>2200</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>電表02</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
|
||||
v-for="meterData in formattedElecCostSummary"
|
||||
:key="meterData.name"
|
||||
>
|
||||
<td>{{ meterData.name }}</td>
|
||||
<td>{{ meterData.area }}</td>
|
||||
<td>{{ meterData.lastMonthUsage.toFixed(2) }}</td>
|
||||
<td>{{ meterData.usageChange.toFixed(2) }}</td>
|
||||
<td>{{ meterData.usageChangeRate.toFixed(2) }}</td>
|
||||
<td>{{ meterData.totalUsage.toFixed(2) }}</td>
|
||||
<td>{{ meterData.peakUsage.toFixed(2) }}</td>
|
||||
<td>{{ meterData.halfPeakUsage.toFixed(2) }}</td>
|
||||
<td>{{ meterData.offPeakUsage.toFixed(2) }}</td>
|
||||
<td>{{ meterData.peakCost.toFixed(2) }}</td>
|
||||
<td>{{ meterData.halfPeakCost.toFixed(2) }}</td>
|
||||
<td>{{ meterData.offPeakCost.toFixed(2) }}</td>
|
||||
<td>{{ meterData.totalCost.toFixed(2) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@ -135,46 +182,222 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-card style="border-radius: 8px; height: 100%">
|
||||
<h3 class="">各電表總費用佔比</h3>
|
||||
<EnergyPie />
|
||||
<h3 class="">各電表用電量比較(kWh)</h3>
|
||||
<EnergyBar
|
||||
:chartData="elecUsageData"
|
||||
/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card style="border-radius: 8px; height: 100%">
|
||||
<h3 class="">各電表用電量比較(kWh)</h3>
|
||||
<EnergyBar :chartData="elecUsageData" />
|
||||
<h3 class="">各電表總費用佔比</h3>
|
||||
<EnergyPie
|
||||
:chartData="elecCostData"
|
||||
/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { defineProps, computed } from "vue";
|
||||
import EnergyPie from "../components/EnergyPie.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({
|
||||
categories: ["電表01", "電表02", "電表03"],
|
||||
series: [
|
||||
{
|
||||
name: "尖峰用電",
|
||||
type: "bar",
|
||||
data: [150, 300, 100],
|
||||
itemStyle: { color: "#5470c6" },
|
||||
},
|
||||
{
|
||||
name: "半尖峰用電",
|
||||
type: "bar",
|
||||
data: [200, 300, 150],
|
||||
itemStyle: { color: "#91cc75" },
|
||||
},
|
||||
{
|
||||
name: "離峰用電",
|
||||
type: "bar",
|
||||
data: [250, 300, 200],
|
||||
itemStyle: { color: "#fac858" },
|
||||
},
|
||||
],
|
||||
const props = defineProps({
|
||||
form: {
|
||||
type: Object,
|
||||
},
|
||||
});
|
||||
const storeElecPrice = useElecPriceStore();
|
||||
const storeElecReport = useElecReportStore();
|
||||
const elecPrices = storeElecPrice.elecData;
|
||||
|
||||
const isSummerMonth = computed(() => {
|
||||
const month = dayjs(props.form?.date).month(); // month() 返回 0-11,代表一月到十二月
|
||||
return month >= 5 && month <= 8; // 月份範圍,6月是 5,9月是 8
|
||||
});
|
||||
|
||||
const priceTitle = computed(() => {
|
||||
return isSummerMonth.value ? "單價(NTD/kWh)-夏月" : "單價(NTD/kWh)-非夏月";
|
||||
});
|
||||
|
||||
const formattedElecCostSummary = computed(() => {
|
||||
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>
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { ref } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
import type { NiagaraElecData } from "../utils/types";
|
||||
import axios from "axios";
|
||||
|
||||
const useElecPriceStore = defineStore("elecPriceData", () => {
|
||||
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 = () => {
|
||||
subscribers.value.forEach((subscriber) => {
|
||||
subscriber.detach("changed");
|
||||
@ -112,6 +137,7 @@ const useElecPriceStore = defineStore("elecPriceData", () => {
|
||||
|
||||
return {
|
||||
getElecDataFromBaja,
|
||||
updatePrice,
|
||||
clearAllSubscriber,
|
||||
elecData,
|
||||
};
|
||||
|
202
src/stores/useElecReportStore.ts
Normal file
202
src/stores/useElecReportStore.ts
Normal 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;
|
@ -1,14 +1,19 @@
|
||||
import { ref } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
import dayjs from "dayjs";
|
||||
import type { NiagaraElecData, ElecCostSummary } from "../utils/types";
|
||||
import { CalcuEleCost } from "../utils/CalcuEleCost";
|
||||
import type {
|
||||
NiagaraElecData,
|
||||
ElecCostSummary,
|
||||
ElecStandCostSummary,
|
||||
} from "../utils/types";
|
||||
import { CalcuEleCost, CalcuEleStandCost } from "../utils/CalcuEleCost";
|
||||
|
||||
const useElecStore = defineStore("elecData", () => {
|
||||
const elecData = ref<NiagaraElecData[]>([]);
|
||||
// @ts-ignore
|
||||
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
|
||||
const getElecDataFromBaja = () => {
|
||||
@ -78,18 +83,27 @@ const useElecStore = defineStore("elecData", () => {
|
||||
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) {
|
||||
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);
|
||||
elecFlowCostSummary.value = CalcuEleCost(dataMap);
|
||||
elecStandCostSummary.value = CalcuEleStandCost(dataMap);
|
||||
},
|
||||
limit: -1,
|
||||
offset: 0,
|
||||
@ -129,7 +143,8 @@ const useElecStore = defineStore("elecData", () => {
|
||||
startTimer,
|
||||
stopTimer,
|
||||
elecData,
|
||||
elecCostSummary,
|
||||
elecFlowCostSummary,
|
||||
elecStandCostSummary,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -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";
|
||||
const storeElecPrice = useElecPriceStore();
|
||||
import useElecDemandStore from "../stores/useElecDemandStore";
|
||||
|
||||
export const CalcuEleCost = (
|
||||
input: Map<string, number>
|
||||
): ElecCostSummary => {
|
||||
const storeElecPrice = useElecPriceStore();
|
||||
const ContractUseValue = useElecDemandStore();
|
||||
|
||||
export const CalcuEleCost = (input: Map<string, number>): ElecCostSummary => {
|
||||
const dailyData: Map<string, DailyEntry[]> = new Map();
|
||||
let totalFlowCost = 0; // 總電價
|
||||
let totalEleCost = 0; //總用電
|
||||
@ -17,13 +25,27 @@ export const CalcuEleCost = (
|
||||
|
||||
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;
|
||||
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);
|
||||
@ -117,7 +139,6 @@ export const CalcuEleCost = (
|
||||
dailyEleCost,
|
||||
dailyFlowCost,
|
||||
});
|
||||
|
||||
}
|
||||
return {
|
||||
dailyResults,
|
||||
@ -131,3 +152,72 @@ export const CalcuEleCost = (
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
21
src/utils/getRandomColor.ts
Normal file
21
src/utils/getRandomColor.ts
Normal 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;
|
@ -3,14 +3,14 @@ export interface NiagaraElecData {
|
||||
slotPath: string;
|
||||
displayName: string;
|
||||
id: string;
|
||||
out: number;
|
||||
out: number;
|
||||
}
|
||||
|
||||
export interface NiagaraElecDemandData {
|
||||
slotPath: string;
|
||||
displayName: string;
|
||||
name: string;
|
||||
out: number;
|
||||
out: number;
|
||||
}
|
||||
|
||||
export interface DailyResult {
|
||||
@ -18,9 +18,9 @@ export interface DailyResult {
|
||||
off: number;
|
||||
half: number;
|
||||
peak: number;
|
||||
offcost:number;
|
||||
halfcost:number;
|
||||
peakcost:number;
|
||||
offcost: number;
|
||||
halfcost: number;
|
||||
peakcost: number;
|
||||
dailyEleCost: number;
|
||||
dailyFlowCost: number;
|
||||
}
|
||||
@ -35,9 +35,38 @@ export interface ElecCostSummary {
|
||||
total_OffCost: number;
|
||||
total_halfCost: 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 {
|
||||
time: Date;
|
||||
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 ;
|
||||
}
|
@ -73,11 +73,19 @@ import useElecTotalMeterStore from "../stores/useElecTotalMeterStore";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const storeElecTotal = useElecTotalMeterStore();
|
||||
const billingDateRange = ref("");
|
||||
|
||||
watch(
|
||||
() => storeElecTotal.elecCostSummary,
|
||||
() => storeElecTotal.elecFlowCostSummary,
|
||||
(newElecData) => {
|
||||
console.log("elecCostSummary", newElecData);
|
||||
console.log("elecFlowCostSummary", newElecData);
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
watch(
|
||||
() => storeElecTotal.elecStandCostSummary,
|
||||
(newElecData) => {
|
||||
console.log("elecStandCostSummary", newElecData);
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
@ -85,6 +93,11 @@ watch(
|
||||
onMounted(async () => {
|
||||
await storeElecTotal.getElecDataFromBaja();
|
||||
storeElecTotal.startTimer();
|
||||
|
||||
// 區間計費時間區段
|
||||
billingDateRange.value = `${dayjs()
|
||||
.startOf("month")
|
||||
.format("YYYY/MM/DD")} - ${dayjs().format("YYYY/MM/DD")}`;
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
@ -97,8 +110,8 @@ const statisticData = computed(() => {
|
||||
let intervalFlowCost = 0;
|
||||
let intervalEleCost = 0;
|
||||
|
||||
if (storeElecTotal.elecCostSummary?.dailyResults) {
|
||||
storeElecTotal.elecCostSummary.dailyResults.forEach((dailyResult) => {
|
||||
if (storeElecTotal.elecFlowCostSummary?.dailyResults) {
|
||||
storeElecTotal.elecFlowCostSummary.dailyResults.forEach((dailyResult) => {
|
||||
if (dailyResult.dateStr.startsWith(currentMonth)) {
|
||||
intervalFlowCost += dailyResult.dailyFlowCost;
|
||||
intervalEleCost += dailyResult.dailyEleCost;
|
||||
@ -109,13 +122,13 @@ const statisticData = computed(() => {
|
||||
return [
|
||||
{
|
||||
title: "今年電費累計",
|
||||
value: storeElecTotal.elecCostSummary?.totalFlowCost || 0,
|
||||
value: storeElecTotal.elecFlowCostSummary?.totalFlowCost || 0,
|
||||
unit: "元",
|
||||
},
|
||||
{ title: "區間電費", value: intervalFlowCost, unit: "元" },
|
||||
{
|
||||
title: "今年碳排當量累計",
|
||||
value: storeElecTotal.elecCostSummary?.totalEleCost * 0.424,
|
||||
value: storeElecTotal.elecFlowCostSummary?.totalEleCost * 0.424,
|
||||
unit: "公斤 CO2e/度",
|
||||
},
|
||||
{
|
||||
@ -125,7 +138,7 @@ const statisticData = computed(() => {
|
||||
},
|
||||
{
|
||||
title: "今年用電度數",
|
||||
value: storeElecTotal.elecCostSummary?.totalEleCost || 0,
|
||||
value: storeElecTotal.elecFlowCostSummary?.totalEleCost || 0,
|
||||
unit: "kWh",
|
||||
},
|
||||
{ title: "區間用電度數", value: intervalEleCost, unit: "kWh" },
|
||||
@ -162,22 +175,24 @@ function groupByMonth(dailyResults) {
|
||||
|
||||
// 每月用電分析
|
||||
const monthlyElectricityData = computed(() => {
|
||||
if (!storeElecTotal.elecCostSummary?.dailyResults) {
|
||||
if (!storeElecTotal.elecFlowCostSummary?.dailyResults) {
|
||||
return { categories: [], series: [] };
|
||||
}
|
||||
|
||||
const groupedData = groupByMonth(storeElecTotal.elecCostSummary.dailyResults);
|
||||
const groupedData = groupByMonth(
|
||||
storeElecTotal.elecFlowCostSummary.dailyResults
|
||||
);
|
||||
const categories = Object.keys(groupedData);
|
||||
const sortedCategories = _.sortBy(categories, (month) =>
|
||||
dayjs().month(month).valueOf()
|
||||
);
|
||||
|
||||
const baseElecData = sortedCategories.map((month) => groupedData[month].offcost);
|
||||
const baseElecData = storeElecTotal.elecStandCostSummary.StandCost;
|
||||
const flowElecData = sortedCategories.map(
|
||||
(month) => groupedData[month].totalCost
|
||||
);
|
||||
const totalElecData = sortedCategories.map((month, index) => {
|
||||
return (baseElecData[index] || 0) + (flowElecData[index] || 0);
|
||||
const totalElecData = sortedCategories.map((month, index) => {
|
||||
return baseElecData + (flowElecData[index] || 0);
|
||||
});
|
||||
|
||||
return {
|
||||
@ -186,7 +201,7 @@ const monthlyElectricityData = computed(() => {
|
||||
{
|
||||
name: "基本電費",
|
||||
type: "bar",
|
||||
data: baseElecData,
|
||||
data: Array(sortedCategories.length).fill(baseElecData),
|
||||
},
|
||||
{
|
||||
name: "流動電費",
|
||||
@ -194,7 +209,7 @@ const monthlyElectricityData = computed(() => {
|
||||
data: flowElecData,
|
||||
},
|
||||
{
|
||||
name: "總電量",
|
||||
name: "總電費",
|
||||
type: "bar",
|
||||
data: totalElecData,
|
||||
},
|
||||
@ -204,11 +219,13 @@ const monthlyElectricityData = computed(() => {
|
||||
|
||||
// 每月碳排當量
|
||||
const monthlyCarbonData = computed(() => {
|
||||
if (!storeElecTotal.elecCostSummary?.dailyResults) {
|
||||
if (!storeElecTotal.elecFlowCostSummary?.dailyResults) {
|
||||
return { categories: [], series: [] };
|
||||
}
|
||||
|
||||
const groupedData = groupByMonth(storeElecTotal.elecCostSummary.dailyResults);
|
||||
const groupedData = groupByMonth(
|
||||
storeElecTotal.elecFlowCostSummary.dailyResults
|
||||
);
|
||||
const categories = Object.keys(groupedData);
|
||||
const sortedCategories = _.sortBy(categories, (month) =>
|
||||
dayjs().month(month).valueOf()
|
||||
@ -232,76 +249,82 @@ const monthlyCarbonData = computed(() => {
|
||||
|
||||
// 每月計費度數 (kWh)
|
||||
const monthlyBillingData = computed(() => {
|
||||
if (!storeElecTotal.elecCostSummary?.dailyResults) {
|
||||
if (!storeElecTotal.elecFlowCostSummary?.dailyResults) {
|
||||
return { categories: [], series: [] };
|
||||
}
|
||||
|
||||
const billingData = groupByMonth(storeElecTotal.elecCostSummary.dailyResults);
|
||||
const billingData = groupByMonth(
|
||||
storeElecTotal.elecFlowCostSummary.dailyResults
|
||||
);
|
||||
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 halfData = sortedCategories.map(date => billingData[date].half);
|
||||
const offData = sortedCategories.map(date => billingData[date].off);
|
||||
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: peakData },
|
||||
{ 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(() => {
|
||||
if (!storeElecTotal.elecCostSummary?.dailyResults) {
|
||||
if (!storeElecTotal.elecFlowCostSummary?.dailyResults) {
|
||||
return { categories: [], series: [] };
|
||||
}
|
||||
|
||||
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 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');
|
||||
});
|
||||
const areaResults = storeElecTotal.elecFlowCostSummary.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
|
||||
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
|
||||
off: result.off,
|
||||
};
|
||||
});
|
||||
|
||||
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 halfData = sortedCategories.map(date => transformedData[date].half);
|
||||
const offData = sortedCategories.map(date => transformedData[date].off);
|
||||
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 }
|
||||
]
|
||||
{ name: "離峰", type: "bar", data: offData },
|
||||
],
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
@ -311,7 +311,6 @@
|
||||
import { ref, watch } from "vue";
|
||||
import { Edit, CircleClose, CircleCheck } from "@element-plus/icons-vue";
|
||||
import useElecPriceStore from "../stores/useElecPriceStore";
|
||||
import axios from "axios";
|
||||
|
||||
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 () => {
|
||||
stand3isEditing.value = false;
|
||||
|
||||
@ -419,7 +394,7 @@ const confirmChanges = async () => {
|
||||
? item.slotPath.slice(6) // 移除 "slot:/"
|
||||
: item.slotPath; // 如果没有前綴,則保持不變
|
||||
// 更新 Niagara
|
||||
const success = await updatePrice(slotPath, stand3Value.value[i]);
|
||||
const success = await storeElecPrice.updatePrice(slotPath, stand3Value.value[i]);
|
||||
if (!success) {
|
||||
failedUpdates.push({
|
||||
slotPath: item.slotPath,
|
||||
|
@ -1,17 +1,52 @@
|
||||
<template>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-col :span="24" style="padding-right: 30px">
|
||||
<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
|
||||
>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 顯示在畫面上的內容 -->
|
||||
<PdfContent />
|
||||
|
||||
<!-- 給 vue3-html2pdf 用的內容 -->
|
||||
<!-- 設定 Modal -->
|
||||
<EnergyModal
|
||||
v-model="dialogVisible"
|
||||
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
|
||||
ref="html2Pdf"
|
||||
:show-layout="false"
|
||||
@ -27,30 +62,101 @@
|
||||
pdf-content-width="1120px"
|
||||
>
|
||||
<template #pdf-content>
|
||||
<PdfContent />
|
||||
<PdfContent :form="originalForm" />
|
||||
</template>
|
||||
</vue3-html2pdf>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { ref, reactive, watch, onMounted, onUnmounted } from "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 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 文件名稱
|
||||
const pdfFileName = ref("智慧大樓電表月報表");
|
||||
|
||||
// 引用 vue-html2pdf 實例
|
||||
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 生成和下載
|
||||
const generatePDF = async () => {
|
||||
if (html2Pdf.value) {
|
||||
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>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
Loading…
Reference in New Issue
Block a user