能源報表初版
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,8 +83,16 @@ 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);
 | 
			
		||||
                }
 | 
			
		||||
@ -89,7 +102,8 @@ const useElecStore = defineStore("elecData", () => {
 | 
			
		||||
            },
 | 
			
		||||
            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;
 | 
			
		||||
@ -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); 
 | 
			
		||||
    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