新增能源報表

This commit is contained in:
huliang 2025-04-25 17:34:26 +08:00
parent 7f4d1e6e07
commit cd0bfafa35
11 changed files with 1708 additions and 13 deletions

4
components.d.ts vendored
View File

@ -13,6 +13,8 @@ declare module 'vue' {
ElCard: typeof import('element-plus/es')['ElCard']
ElCol: typeof import('element-plus/es')['ElCol']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElInput: typeof import('element-plus/es')['ElInput']
ElMain: typeof import('element-plus/es')['ElMain']
@ -23,8 +25,10 @@ declare module 'vue' {
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
EnergyBar: typeof import('./src/components/EnergyBar.vue')['default']
EnergyLine: typeof import('./src/components/EnergyLine.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']
PdfContent: typeof import('./src/components/PdfContent.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}

1280
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,12 +14,14 @@
"element-plus": "^2.9.6",
"pinia": "^3.0.2",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
"vue-router": "^4.5.0",
"vue3-html2pdf": "^1.1.2"
},
"devDependencies": {
"@types/node": "^22.13.11",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/tsconfig": "^0.7.0",
"sass-embedded": "^1.87.0",
"typescript": "~5.7.2",
"unplugin-auto-import": "^19.1.1",
"unplugin-vue-components": "^28.4.1",

View File

@ -170,6 +170,8 @@ watch(
onMounted(async () => {
//
await storeDemand.getElecDemandFromBaja();
//
await storeDemand.getElecDatatTestBaja();
//
chartInstance = echarts.init(demand_chart.value);
chartInstance.setOption(chartOption.value);

View File

@ -0,0 +1,91 @@
<script setup>
import { ref, onMounted, watch, onUnmounted } 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 initChart = () => {
chartInstance = echarts.init(chartContainer.value);
const option = {
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b} : {c} ({d}%)", //
},
legend: {
orient: "vertical",
left: "right",
bottom: "0%",
data: chartData.value.series.map((item) => item.name), //
},
series: [
{
name: "電表費用",
type: "pie",
radius: "70%", //
center: ["50%", "50%"], //
data: chartData.value.series,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: "rgba(0, 0, 0, 0.5)",
},
},
},
]
};
chartInstance.setOption(option);
};
onMounted(() => {
initChart();
});
watch(
() => chartData.value,
(newChartData) => {
if (chartInstance) {
chartInstance.setOption({
series: [
{
data: newChartData.series,
},
],
});
}
},
{ deep: true }
);
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
}
});
</script>
<template>
<div ref="chartContainer" style="width: 100%; height: 250px"></div>
</template>

View File

@ -5,12 +5,13 @@
mode="horizontal"
@select="handleSelect"
>
<el-menu-item index="chart">能源圖表 </el-menu-item>
<el-menu-item index="chart">能源圖表</el-menu-item>
<el-sub-menu index="elecPricing">
<template #title>電價表</template>
<el-menu-item index="elecPricingSimple">住宅型</el-menu-item>
<el-menu-item index="elecPricingStandard">標準型</el-menu-item>
</el-sub-menu>
<el-menu-item index="monthlyReport">能源報表</el-menu-item>
</el-menu>
</template>
@ -36,6 +37,10 @@ const handleSelect = (key: string, keyPath: string[]) => {
router.push({ name: "ElecPricingStandard" });
activeIndex.value = "elecPricingStandard";
break;
case "monthlyReport":
router.push({ name: "monthlyReport" });
activeIndex.value = "monthlyReport";
break;
}
};
</script>

View File

@ -0,0 +1,221 @@
<template>
<el-row :gutter="20">
<el-col :span="12">
<el-descriptions class="margin-top" title="報表資訊" :column="1" border>
<el-descriptions-item>
<template #label>
<div class="cell-item">報表名稱</div>
</template>
智慧大樓電表月報表
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">報表期間</div>
</template>
2025/02/01 ~ 2025/02/29
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">編製日期</div>
</template>
2025/3/1
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">編製人員</div>
</template>
能源管理部
</el-descriptions-item>
</el-descriptions>
</el-col>
<el-col :span="12">
<el-descriptions
class="margin-top"
title="單價(NTD/kWh)"
:column="1"
border
>
<el-descriptions-item>
<template #label>
<div class="cell-item">尖峰單價</div>
</template>
6
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">半尖峰單價</div>
</template>
4
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">離峰單價</div>
</template>
2
</el-descriptions-item>
</el-descriptions>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<table>
<thead>
<tr>
<th colspan="2" class="bg-info">基本資料</th>
<th colspan="3" class="bg-orange">前期比較</th>
<th colspan="4" class="bg-green">用電量(kWh)</th>
<th colspan="4" class="bg-yellow">費用(NTD)</th>
</tr>
<tr>
<th class="bg-info">電表編號</th>
<th class="bg-info">區域/用途</th>
<th class="bg-orange">上月用電量(kWh)</th>
<th class="bg-orange">用電變化量(kWh)</th>
<th class="bg-orange">用電變化率(%)</th>
<th class="bg-green">總用電量</th>
<th class="bg-green">尖峰用電</th>
<th class="bg-green">半尖峰用電</th>
<th class="bg-green">離峰用電</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>
</tbody>
</table>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-card style="border-radius: 8px; height: 100%">
<h3 class="">各電表總費用佔比</h3>
<EnergyPie />
</el-card>
</el-col>
<el-col :span="12">
<el-card style="border-radius: 8px; height: 100%">
<h3 class="">各電表用電量比較(kWh)</h3>
<EnergyBar :chartData="elecUsageData" />
</el-card>
</el-col>
</el-row>
</template>
<script setup lang="ts">
import { ref } from "vue";
import EnergyPie from "../components/EnergyPie.vue";
import EnergyBar from "../components/EnergyBar.vue";
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" },
},
],
});
</script>
<style scoped>
.el-row {
padding-top: 20px;
padding-right: 20px;
padding-left: 20px;
}
h3 {
margin: 0;
font-weight: 700;
font-size: 15px;
color: #141414;
}
th,
td {
border: 1px solid #ebeef5;
text-align: center;
padding: 8px 11px;
background: #fff;
color: #303133;
font-size: 12px;
text-wrap: nowrap;
}
.bg-info {
background: #add8e6;
}
.bg-orange {
background: #ffdab9;
}
.bg-green {
background: #90ee90;
}
.bg-yellow {
background: #ffd700;
}
</style>

View File

@ -22,6 +22,11 @@ const routes: Array<RouteRecordRaw> = [
name: "ElecPricingStandard",
component: () => import("../views/ElecPriceStd.vue"),
},
{
path: "monthlyReport",
name: "monthlyReport",
component: () => import("../views/MonthlyReport.vue"),
},
],
},
{

View File

@ -4,7 +4,7 @@ import type { NiagaraElecDemandData } from "../utils/types";
const useElecDemandStore = defineStore("elecDemand", () => {
const elecData = ref<NiagaraElecDemandData[]>([]);
const subscribers = ref<any[]>([]);
const subscribers = ref<any[]>([]);
// get data from baja
const getElecDemandFromBaja = () => {
@ -59,8 +59,8 @@ const useElecDemandStore = defineStore("elecDemand", () => {
try {
if (prop && prop.getName() === "out") {
// 取得 out 的新值
const match = prop.$display.match(/^(\d+(\.\d+)?)/);
const newValue = match ? parseFloat(match[0]) : 0;
const match = prop.$display.match(/^(\d+(\.\d+)?)/);
const newValue = match ? parseFloat(match[0]) : 0;
// 更新 elecData 中對應的 out 值
const updatedIndex = elecData.value.findIndex(
(data) => data.slotPath === item.slotPath
@ -72,11 +72,8 @@ const useElecDemandStore = defineStore("elecDemand", () => {
...newElecData[updatedIndex],
out: Number(newValue),
};
elecData.value = newElecData;
console.log(
`Niagara 用電需求 ${item.name} 更新:`,
newValue
);
elecData.value = newElecData;
console.log(`Niagara 用電需求 ${item.name} 更新:`, newValue);
}
}
} catch (error: any) {
@ -109,7 +106,35 @@ const useElecDemandStore = defineStore("elecDemand", () => {
console.log("所有訂閱已清除");
};
return { getElecDemandFromBaja, elecData, clearAllSubscriber };
// 測試
const getElecDatatTestBaja = () => {
// @ts-ignore
window.require &&
// @ts-ignore
window.requirejs(["baja!"], (baja: any) => {
console.log("進入 bajaSubscriber 準備執行 BQL 訂閱");
baja.Ord.make(
`local:|foxs:4918|history:/testStation_MJM/MGCB_KWH?period=timerange;start=2025-04-20T00:00:00.000+08:00;end=2025-04-21T00:00:00.000+08:00|bql:history:HistoryRollup.rollup(baja:RelTime '3600000')`
).get({
cursor: {
before: () => {},
each: (record: any) => {
console.log("recordtest", record);
},
after: () => {},
limit: -1,
offset: 0,
},
});
});
};
return {
getElecDemandFromBaja,
elecData,
clearAllSubscriber,
getElecDatatTestBaja,
};
});
export default useElecDemandStore;
export default useElecDemandStore;

View File

@ -0,0 +1,56 @@
<template>
<el-row :gutter="20">
<el-col :span="24">
<div style="display: flex; justify-content: flex-end">
<el-button type="primary" :icon="Printer" @click="generatePDF"
>列印報表</el-button
>
</div>
</el-col>
</el-row>
<!-- 顯示在畫面上的內容 -->
<PdfContent />
<!-- vue3-html2pdf 用的內容 -->
<vue3-html2pdf
ref="html2Pdf"
:show-layout="false"
:float-layout="true"
:enable-download="true"
:preview-modal="true"
:filename="pdfFileName"
:pdf-quality="2"
:manual-pagination="false"
:paginate-elements-by-height="1000"
pdf-format="a4"
pdf-orientation="landscape"
pdf-content-width="1120px"
>
<template #pdf-content>
<PdfContent />
</template>
</vue3-html2pdf>
</template>
<script setup lang="ts">
import { ref } from "vue";
import PdfContent from "../components/PdfContent.vue";
import { Printer } from "@element-plus/icons-vue";
import Vue3Html2pdf from "vue3-html2pdf";
// PDF
const pdfFileName = ref("智慧大樓電表月報表");
// vue-html2pdf
const html2Pdf = ref<InstanceType<typeof Vue3Html2pdf> | null>(null);
// PDF
const generatePDF = async () => {
if (html2Pdf.value) {
await html2Pdf.value.generatePdf();
}
};
</script>
<style scoped>
</style>

6
src/vite-env.d.ts vendored
View File

@ -8,6 +8,12 @@ declare module "*.vue" {
export default component;
}
declare module "vue3-html2pdf" {
import { DefineComponent } from "vue";
const component: DefineComponent<{}, {}, any>;
export default component;
}
// 聲明環境變數 ( .env 檔案)
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string;