能源報表初版
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']
 | 
					    ElCard: typeof import('element-plus/es')['ElCard']
 | 
				
			||||||
    ElCol: typeof import('element-plus/es')['ElCol']
 | 
					    ElCol: typeof import('element-plus/es')['ElCol']
 | 
				
			||||||
    ElContainer: typeof import('element-plus/es')['ElContainer']
 | 
					    ElContainer: typeof import('element-plus/es')['ElContainer']
 | 
				
			||||||
 | 
					    ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
 | 
				
			||||||
    ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
 | 
					    ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
 | 
				
			||||||
    ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
 | 
					    ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
 | 
				
			||||||
 | 
					    ElDialog: typeof import('element-plus/es')['ElDialog']
 | 
				
			||||||
 | 
					    ElForm: typeof import('element-plus/es')['ElForm']
 | 
				
			||||||
 | 
					    ElFormItem: typeof import('element-plus/es')['ElFormItem']
 | 
				
			||||||
    ElHeader: typeof import('element-plus/es')['ElHeader']
 | 
					    ElHeader: typeof import('element-plus/es')['ElHeader']
 | 
				
			||||||
    ElInput: typeof import('element-plus/es')['ElInput']
 | 
					    ElInput: typeof import('element-plus/es')['ElInput']
 | 
				
			||||||
    ElMain: typeof import('element-plus/es')['ElMain']
 | 
					    ElMain: typeof import('element-plus/es')['ElMain']
 | 
				
			||||||
@ -24,6 +28,7 @@ declare module 'vue' {
 | 
				
			|||||||
    ElStatistic: typeof import('element-plus/es')['ElStatistic']
 | 
					    ElStatistic: typeof import('element-plus/es')['ElStatistic']
 | 
				
			||||||
    EnergyBar: typeof import('./src/components/EnergyBar.vue')['default']
 | 
					    EnergyBar: typeof import('./src/components/EnergyBar.vue')['default']
 | 
				
			||||||
    EnergyLine: typeof import('./src/components/EnergyLine.vue')['default']
 | 
					    EnergyLine: typeof import('./src/components/EnergyLine.vue')['default']
 | 
				
			||||||
 | 
					    EnergyModal: typeof import('./src/components/EnergyModal.vue')['default']
 | 
				
			||||||
    EnergyPie: typeof import('./src/components/EnergyPie.vue')['default']
 | 
					    EnergyPie: typeof import('./src/components/EnergyPie.vue')['default']
 | 
				
			||||||
    EnergySankey: typeof import('./src/components/EnergySankey.vue')['default']
 | 
					    EnergySankey: typeof import('./src/components/EnergySankey.vue')['default']
 | 
				
			||||||
    Navbar: typeof import('./src/components/Navbar.vue')['default']
 | 
					    Navbar: typeof import('./src/components/Navbar.vue')['default']
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										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>
 | 
					<script setup>
 | 
				
			||||||
import { ref, onMounted, watch, onUnmounted } from "vue";
 | 
					import { ref, onMounted, watch, onUnmounted, defineProps } from "vue";
 | 
				
			||||||
import * as echarts from "echarts";
 | 
					import * as echarts from "echarts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const chartContainer = ref(null);
 | 
					const chartContainer = ref(null);
 | 
				
			||||||
let chartInstance = null;
 | 
					let chartInstance = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const chartData = ref({
 | 
					const props = defineProps({
 | 
				
			||||||
  series: [
 | 
					  chartData: {
 | 
				
			||||||
    {
 | 
					    type: Object,
 | 
				
			||||||
      name: "電表01",
 | 
					    required: true,
 | 
				
			||||||
      value: 2200,  
 | 
					  },
 | 
				
			||||||
      itemStyle: { color: "#5470c6" }, 
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: "電表02",
 | 
					 | 
				
			||||||
      value: 3600, 
 | 
					 | 
				
			||||||
      itemStyle: { color: "#91cc75" }, 
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: "電表03",
 | 
					 | 
				
			||||||
      value: 1600, 
 | 
					 | 
				
			||||||
      itemStyle: { color: "#fac858" }, 
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const initChart = () => {
 | 
					const initChart = () => {
 | 
				
			||||||
  chartInstance = echarts.init(chartContainer.value);
 | 
					  chartInstance = echarts.init(chartContainer.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -37,7 +25,7 @@ const initChart = () => {
 | 
				
			|||||||
      orient: "vertical", 
 | 
					      orient: "vertical", 
 | 
				
			||||||
      left: "right", 
 | 
					      left: "right", 
 | 
				
			||||||
      bottom: "0%",
 | 
					      bottom: "0%",
 | 
				
			||||||
      data: chartData.value.series.map((item) => item.name), //  添加图例
 | 
					      data:props.chartData.series.map((item) => item.name), //  添加图例
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    series: [
 | 
					    series: [
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
@ -45,7 +33,7 @@ const initChart = () => {
 | 
				
			|||||||
        type: "pie",
 | 
					        type: "pie",
 | 
				
			||||||
        radius: "70%",  // 饼图大小
 | 
					        radius: "70%",  // 饼图大小
 | 
				
			||||||
        center: ["50%", "50%"], // 饼图中心位置
 | 
					        center: ["50%", "50%"], // 饼图中心位置
 | 
				
			||||||
        data: chartData.value.series,
 | 
					        data: props.chartData.series,
 | 
				
			||||||
        emphasis: {
 | 
					        emphasis: {
 | 
				
			||||||
          itemStyle: {
 | 
					          itemStyle: {
 | 
				
			||||||
            shadowBlur: 10,
 | 
					            shadowBlur: 10,
 | 
				
			||||||
@ -65,7 +53,7 @@ onMounted(() => {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch(
 | 
					watch(
 | 
				
			||||||
  () => chartData.value,
 | 
					  () => props.chartData,
 | 
				
			||||||
  (newChartData) => {
 | 
					  (newChartData) => {
 | 
				
			||||||
    if (chartInstance) {
 | 
					    if (chartInstance) {
 | 
				
			||||||
      chartInstance.setOption({
 | 
					      chartInstance.setOption({
 | 
				
			||||||
 | 
				
			|||||||
@ -1,57 +1,131 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <el-row :gutter="20">
 | 
					  <el-row :gutter="20">
 | 
				
			||||||
    <el-col :span="12">
 | 
					    <el-col :span="7">
 | 
				
			||||||
      <el-descriptions class="margin-top" title="報表資訊" :column="1" border>
 | 
					      <el-descriptions class="margin-top" title="報表資訊" :column="1" border>
 | 
				
			||||||
        <el-descriptions-item>
 | 
					        <el-descriptions-item>
 | 
				
			||||||
          <template #label>
 | 
					          <template #label>
 | 
				
			||||||
            <div class="cell-item">報表名稱</div>
 | 
					            <div class="cell-item">報表名稱</div>
 | 
				
			||||||
          </template>
 | 
					          </template>
 | 
				
			||||||
          智慧大樓電表月報表
 | 
					          {{ props.form?.name }}
 | 
				
			||||||
        </el-descriptions-item>
 | 
					        </el-descriptions-item>
 | 
				
			||||||
        <el-descriptions-item>
 | 
					        <el-descriptions-item>
 | 
				
			||||||
          <template #label>
 | 
					          <template #label>
 | 
				
			||||||
            <div class="cell-item">報表期間</div>
 | 
					            <div class="cell-item">報表期間</div>
 | 
				
			||||||
          </template>
 | 
					          </template>
 | 
				
			||||||
          2025/02/01 ~ 2025/02/29
 | 
					          {{ dayjs(props.form?.date).format("YYYY/MM") }}
 | 
				
			||||||
        </el-descriptions-item>
 | 
					        </el-descriptions-item>
 | 
				
			||||||
        <el-descriptions-item>
 | 
					        <el-descriptions-item>
 | 
				
			||||||
          <template #label>
 | 
					          <template #label>
 | 
				
			||||||
            <div class="cell-item">編製日期</div>
 | 
					            <div class="cell-item">編製日期</div>
 | 
				
			||||||
          </template>
 | 
					          </template>
 | 
				
			||||||
          2025/3/1
 | 
					          {{ dayjs().format("YYYY/MM/DD") }}
 | 
				
			||||||
        </el-descriptions-item>
 | 
					        </el-descriptions-item>
 | 
				
			||||||
        <el-descriptions-item>
 | 
					        <el-descriptions-item>
 | 
				
			||||||
          <template #label>
 | 
					          <template #label>
 | 
				
			||||||
            <div class="cell-item">編製人員</div>
 | 
					            <div class="cell-item">編製人員</div>
 | 
				
			||||||
          </template>
 | 
					          </template>
 | 
				
			||||||
          能源管理部
 | 
					          {{ props.form?.staff }}
 | 
				
			||||||
        </el-descriptions-item>
 | 
					        </el-descriptions-item>
 | 
				
			||||||
      </el-descriptions>
 | 
					      </el-descriptions>
 | 
				
			||||||
    </el-col>
 | 
					    </el-col>
 | 
				
			||||||
    <el-col :span="12">
 | 
					    <el-col :span="11">
 | 
				
			||||||
      <el-descriptions
 | 
					      <el-descriptions
 | 
				
			||||||
        class="margin-top"
 | 
					        class="margin-top"
 | 
				
			||||||
        title="單價(NTD/kWh)"
 | 
					        :title="priceTitle"
 | 
				
			||||||
        :column="1"
 | 
					        :column="2"
 | 
				
			||||||
        border
 | 
					        border
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <el-descriptions-item>
 | 
					        <el-descriptions-item>
 | 
				
			||||||
          <template #label>
 | 
					          <template #label>
 | 
				
			||||||
            <div class="cell-item">尖峰單價</div>
 | 
					            <div class="cell-item">基本電費-三相</div>
 | 
				
			||||||
          </template>
 | 
					          </template>
 | 
				
			||||||
          6
 | 
					          {{ Three_Phase }}
 | 
				
			||||||
 | 
					        </el-descriptions-item>
 | 
				
			||||||
 | 
					        <el-descriptions-item v-if="isSummerMonth">
 | 
				
			||||||
 | 
					          <template #label>
 | 
				
			||||||
 | 
					            <div class="cell-item">基本電費-經常契約</div>
 | 
				
			||||||
 | 
					          </template>
 | 
				
			||||||
 | 
					          {{ Regular_Contract_Summer }}
 | 
				
			||||||
 | 
					        </el-descriptions-item>
 | 
				
			||||||
 | 
					        <el-descriptions-item v-if="!isSummerMonth">
 | 
				
			||||||
 | 
					          <template #label>
 | 
				
			||||||
 | 
					            <div class="cell-item">基本電費-經常契約</div>
 | 
				
			||||||
 | 
					          </template>
 | 
				
			||||||
 | 
					          {{ Regular_Contract_Non_Summer }}
 | 
				
			||||||
 | 
					        </el-descriptions-item>
 | 
				
			||||||
 | 
					        <el-descriptions-item v-if="isSummerMonth">
 | 
				
			||||||
 | 
					          <template #label>
 | 
				
			||||||
 | 
					            <div class="cell-item">流動電價-平日尖峰單價</div>
 | 
				
			||||||
 | 
					          </template>
 | 
				
			||||||
 | 
					          {{ Summer_Peak_Prices }}
 | 
				
			||||||
        </el-descriptions-item>
 | 
					        </el-descriptions-item>
 | 
				
			||||||
        <el-descriptions-item>
 | 
					        <el-descriptions-item>
 | 
				
			||||||
          <template #label>
 | 
					          <template #label>
 | 
				
			||||||
            <div class="cell-item">半尖峰單價</div>
 | 
					            <div class="cell-item">流動電價-平日半尖峰單價</div>
 | 
				
			||||||
          </template>
 | 
					          </template>
 | 
				
			||||||
          4
 | 
					          {{
 | 
				
			||||||
 | 
					            isSummerMonth
 | 
				
			||||||
 | 
					              ? Summer_HalfPeak_Prices_Weekday
 | 
				
			||||||
 | 
					              : Non_Summer_HalfPeak_Prices_Weekday
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
        </el-descriptions-item>
 | 
					        </el-descriptions-item>
 | 
				
			||||||
        <el-descriptions-item>
 | 
					        <el-descriptions-item>
 | 
				
			||||||
          <template #label>
 | 
					          <template #label>
 | 
				
			||||||
            <div class="cell-item">離峰單價</div>
 | 
					            <div class="cell-item">流動電價-平日離峰單價</div>
 | 
				
			||||||
          </template>
 | 
					          </template>
 | 
				
			||||||
          2
 | 
					          {{
 | 
				
			||||||
 | 
					            isSummerMonth
 | 
				
			||||||
 | 
					              ? Summer_Off_Prices_Weekday
 | 
				
			||||||
 | 
					              : Non_Summer_Off_Prices_Weekday
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					        </el-descriptions-item>
 | 
				
			||||||
 | 
					        <el-descriptions-item>
 | 
				
			||||||
 | 
					          <template #label>
 | 
				
			||||||
 | 
					            <div class="cell-item">流動電價-週六半尖峰單價</div>
 | 
				
			||||||
 | 
					          </template>
 | 
				
			||||||
 | 
					          {{
 | 
				
			||||||
 | 
					            isSummerMonth
 | 
				
			||||||
 | 
					              ? Summer_HalfPeak_Prices_Saturday
 | 
				
			||||||
 | 
					              : Non_Summer_HalfPeak_Prices_Saturday
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					        </el-descriptions-item>
 | 
				
			||||||
 | 
					        <el-descriptions-item>
 | 
				
			||||||
 | 
					          <template #label>
 | 
				
			||||||
 | 
					            <div class="cell-item">流動電價-週六離峰單價</div>
 | 
				
			||||||
 | 
					          </template>
 | 
				
			||||||
 | 
					          {{
 | 
				
			||||||
 | 
					            isSummerMonth
 | 
				
			||||||
 | 
					              ? Summer_Off_Prices_Saturday
 | 
				
			||||||
 | 
					              : Non_Summer_Off_Prices_Saturday
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					        </el-descriptions-item>
 | 
				
			||||||
 | 
					        <el-descriptions-item>
 | 
				
			||||||
 | 
					          <template #label>
 | 
				
			||||||
 | 
					            <div class="cell-item">流動電價-週日離峰單價</div>
 | 
				
			||||||
 | 
					          </template>
 | 
				
			||||||
 | 
					          {{ isSummerMonth ? Summer_Off_Prices : Non_Summer_Off_Prices }}
 | 
				
			||||||
 | 
					        </el-descriptions-item>
 | 
				
			||||||
 | 
					      </el-descriptions>
 | 
				
			||||||
 | 
					    </el-col>
 | 
				
			||||||
 | 
					    <el-col :span="6">
 | 
				
			||||||
 | 
					      <el-descriptions class="margin-top" title="總計" :column="1" border>
 | 
				
			||||||
 | 
					        <el-descriptions-item>
 | 
				
			||||||
 | 
					          <template #label>
 | 
				
			||||||
 | 
					            <div class="cell-item">基本電費</div>
 | 
				
			||||||
 | 
					          </template>
 | 
				
			||||||
 | 
					          {{ totalStandCost.toFixed(2) }}
 | 
				
			||||||
 | 
					        </el-descriptions-item>
 | 
				
			||||||
 | 
					        <el-descriptions-item>
 | 
				
			||||||
 | 
					          <template #label>
 | 
				
			||||||
 | 
					            <div class="cell-item">流動電費</div>
 | 
				
			||||||
 | 
					          </template>
 | 
				
			||||||
 | 
					          {{ totalFlowCost.toFixed(2) }}
 | 
				
			||||||
 | 
					        </el-descriptions-item>
 | 
				
			||||||
 | 
					        <el-descriptions-item>
 | 
				
			||||||
 | 
					          <template #label>
 | 
				
			||||||
 | 
					            <div class="cell-item">總費用</div>
 | 
				
			||||||
 | 
					          </template>
 | 
				
			||||||
 | 
					          {{ totalCost.toFixed(2) }}
 | 
				
			||||||
        </el-descriptions-item>
 | 
					        </el-descriptions-item>
 | 
				
			||||||
      </el-descriptions>
 | 
					      </el-descriptions>
 | 
				
			||||||
    </el-col>
 | 
					    </el-col>
 | 
				
			||||||
@ -79,54 +153,27 @@
 | 
				
			|||||||
            <th class="bg-yellow">尖峰費用</th>
 | 
					            <th class="bg-yellow">尖峰費用</th>
 | 
				
			||||||
            <th class="bg-yellow">半尖峰費用</th>
 | 
					            <th class="bg-yellow">半尖峰費用</th>
 | 
				
			||||||
            <th class="bg-yellow">離峰費用</th>
 | 
					            <th class="bg-yellow">離峰費用</th>
 | 
				
			||||||
            <th class="bg-yellow">總費用</th>
 | 
					            <th class="bg-yellow">小計</th>
 | 
				
			||||||
          </tr>
 | 
					          </tr>
 | 
				
			||||||
        </thead>
 | 
					        </thead>
 | 
				
			||||||
        <tbody>
 | 
					        <tbody>
 | 
				
			||||||
          <tr>
 | 
					          <tr
 | 
				
			||||||
            <td>電表01</td>
 | 
					            v-for="meterData in formattedElecCostSummary"
 | 
				
			||||||
            <td>辦公室南區</td>
 | 
					            :key="meterData.name"
 | 
				
			||||||
            <td>549</td>
 | 
					          >
 | 
				
			||||||
            <td>51</td>
 | 
					            <td>{{ meterData.name }}</td>
 | 
				
			||||||
            <td>9.29</td>
 | 
					            <td>{{ meterData.area }}</td>
 | 
				
			||||||
            <td>600</td>
 | 
					            <td>{{ meterData.lastMonthUsage.toFixed(2) }}</td>
 | 
				
			||||||
            <td>150</td>
 | 
					            <td>{{ meterData.usageChange.toFixed(2) }}</td>
 | 
				
			||||||
            <td>200</td>
 | 
					            <td>{{ meterData.usageChangeRate.toFixed(2) }}</td>
 | 
				
			||||||
            <td>250</td>
 | 
					            <td>{{ meterData.totalUsage.toFixed(2) }}</td>
 | 
				
			||||||
            <td>900</td>
 | 
					            <td>{{ meterData.peakUsage.toFixed(2) }}</td>
 | 
				
			||||||
            <td>800</td>
 | 
					            <td>{{ meterData.halfPeakUsage.toFixed(2) }}</td>
 | 
				
			||||||
            <td>500</td>
 | 
					            <td>{{ meterData.offPeakUsage.toFixed(2) }}</td>
 | 
				
			||||||
            <td>2200</td>
 | 
					            <td>{{ meterData.peakCost.toFixed(2) }}</td>
 | 
				
			||||||
          </tr>
 | 
					            <td>{{ meterData.halfPeakCost.toFixed(2) }}</td>
 | 
				
			||||||
          <tr>
 | 
					            <td>{{ meterData.offPeakCost.toFixed(2) }}</td>
 | 
				
			||||||
            <td>電表02</td>
 | 
					            <td>{{ meterData.totalCost.toFixed(2) }}</td>
 | 
				
			||||||
            <td>電梯機房</td>
 | 
					 | 
				
			||||||
            <td>969</td>
 | 
					 | 
				
			||||||
            <td>-69</td>
 | 
					 | 
				
			||||||
            <td>-7.12</td>
 | 
					 | 
				
			||||||
            <td>900</td>
 | 
					 | 
				
			||||||
            <td>300</td>
 | 
					 | 
				
			||||||
            <td>300</td>
 | 
					 | 
				
			||||||
            <td>300</td>
 | 
					 | 
				
			||||||
            <td>1800</td>
 | 
					 | 
				
			||||||
            <td>1200</td>
 | 
					 | 
				
			||||||
            <td>600</td>
 | 
					 | 
				
			||||||
            <td>3600</td>
 | 
					 | 
				
			||||||
          </tr>
 | 
					 | 
				
			||||||
          <tr>
 | 
					 | 
				
			||||||
            <td>電表03</td>
 | 
					 | 
				
			||||||
            <td>會議室</td>
 | 
					 | 
				
			||||||
            <td>489</td>
 | 
					 | 
				
			||||||
            <td>-39</td>
 | 
					 | 
				
			||||||
            <td>-7.98</td>
 | 
					 | 
				
			||||||
            <td>450</td>
 | 
					 | 
				
			||||||
            <td>100</td>
 | 
					 | 
				
			||||||
            <td>150</td>
 | 
					 | 
				
			||||||
            <td>200</td>
 | 
					 | 
				
			||||||
            <td>600</td>
 | 
					 | 
				
			||||||
            <td>600</td>
 | 
					 | 
				
			||||||
            <td>400</td>
 | 
					 | 
				
			||||||
            <td>1600</td>
 | 
					 | 
				
			||||||
          </tr>
 | 
					          </tr>
 | 
				
			||||||
        </tbody>
 | 
					        </tbody>
 | 
				
			||||||
      </table>
 | 
					      </table>
 | 
				
			||||||
@ -135,46 +182,222 @@
 | 
				
			|||||||
  <el-row :gutter="20">
 | 
					  <el-row :gutter="20">
 | 
				
			||||||
    <el-col :span="12">
 | 
					    <el-col :span="12">
 | 
				
			||||||
      <el-card style="border-radius: 8px; height: 100%">
 | 
					      <el-card style="border-radius: 8px; height: 100%">
 | 
				
			||||||
        <h3 class="">各電表總費用佔比</h3>
 | 
					        <h3 class="">各電表用電量比較(kWh)</h3>
 | 
				
			||||||
        <EnergyPie />
 | 
					        <EnergyBar
 | 
				
			||||||
 | 
					          :chartData="elecUsageData"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
      </el-card>
 | 
					      </el-card>
 | 
				
			||||||
    </el-col>
 | 
					    </el-col>
 | 
				
			||||||
    <el-col :span="12">
 | 
					    <el-col :span="12">
 | 
				
			||||||
      <el-card style="border-radius: 8px; height: 100%">
 | 
					      <el-card style="border-radius: 8px; height: 100%">
 | 
				
			||||||
        <h3 class="">各電表用電量比較(kWh)</h3>
 | 
					        <h3 class="">各電表總費用佔比</h3>
 | 
				
			||||||
        <EnergyBar :chartData="elecUsageData" />
 | 
					        <EnergyPie
 | 
				
			||||||
 | 
					          :chartData="elecCostData"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
      </el-card>
 | 
					      </el-card>
 | 
				
			||||||
    </el-col>
 | 
					    </el-col>
 | 
				
			||||||
  </el-row>
 | 
					  </el-row>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { ref } from "vue";
 | 
					import { defineProps, computed } from "vue";
 | 
				
			||||||
import EnergyPie from "../components/EnergyPie.vue";
 | 
					import EnergyPie from "../components/EnergyPie.vue";
 | 
				
			||||||
import EnergyBar from "../components/EnergyBar.vue";
 | 
					import EnergyBar from "../components/EnergyBar.vue";
 | 
				
			||||||
 | 
					import useElecPriceStore from "../stores/useElecPriceStore";
 | 
				
			||||||
 | 
					import useElecReportStore from "../stores/useElecReportStore";
 | 
				
			||||||
 | 
					import getRandomColor from "../utils/getRandomColor";
 | 
				
			||||||
 | 
					import dayjs from "dayjs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const elecUsageData = ref({
 | 
					const props = defineProps({
 | 
				
			||||||
  categories: ["電表01", "電表02", "電表03"],
 | 
					  form: {
 | 
				
			||||||
  series: [
 | 
					    type: Object,
 | 
				
			||||||
    {
 | 
					  },
 | 
				
			||||||
      name: "尖峰用電",
 | 
					});
 | 
				
			||||||
      type: "bar",
 | 
					const storeElecPrice = useElecPriceStore();
 | 
				
			||||||
      data: [150, 300, 100],
 | 
					const storeElecReport = useElecReportStore();
 | 
				
			||||||
      itemStyle: { color: "#5470c6" },
 | 
					const elecPrices = storeElecPrice.elecData;
 | 
				
			||||||
    },
 | 
					
 | 
				
			||||||
    {
 | 
					const isSummerMonth = computed(() => {
 | 
				
			||||||
      name: "半尖峰用電",
 | 
					  const month = dayjs(props.form?.date).month(); // month() 返回 0-11,代表一月到十二月
 | 
				
			||||||
      type: "bar",
 | 
					  return month >= 5 && month <= 8; // 月份範圍,6月是 5,9月是 8
 | 
				
			||||||
      data: [200, 300, 150],
 | 
					});
 | 
				
			||||||
      itemStyle: { color: "#91cc75" },
 | 
					
 | 
				
			||||||
    },
 | 
					const priceTitle = computed(() => {
 | 
				
			||||||
    {
 | 
					  return isSummerMonth.value ? "單價(NTD/kWh)-夏月" : "單價(NTD/kWh)-非夏月";
 | 
				
			||||||
      name: "離峰用電",
 | 
					});
 | 
				
			||||||
      type: "bar",
 | 
					
 | 
				
			||||||
      data: [250, 300, 200],
 | 
					const formattedElecCostSummary = computed(() => {
 | 
				
			||||||
      itemStyle: { color: "#fac858" },
 | 
					  const summaryThisMonth = storeElecReport.elecCostSummary.thisMonth;
 | 
				
			||||||
    },
 | 
					  const summaryLastMonth = storeElecReport.elecCostSummary.lastMonth;
 | 
				
			||||||
  ],
 | 
					
 | 
				
			||||||
 | 
					  if (!summaryThisMonth) {
 | 
				
			||||||
 | 
					    return [];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return Object.keys(summaryThisMonth).map((key) => {
 | 
				
			||||||
 | 
					    const meterDataThisMonth = summaryThisMonth[key];
 | 
				
			||||||
 | 
					    const meterDataLastMonth = summaryLastMonth?.[key]; // 使用可选链
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const totalUsageThisMonth = meterDataThisMonth?.totalEleCost || 0;
 | 
				
			||||||
 | 
					    const totalUsageLastMonth = meterDataLastMonth?.totalEleCost || 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const usageChange = totalUsageThisMonth - totalUsageLastMonth;
 | 
				
			||||||
 | 
					    const usageChangeRate =
 | 
				
			||||||
 | 
					      totalUsageLastMonth !== 0 ? (usageChange / totalUsageLastMonth) * 100 : 0; // 避免除以 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      name: meterDataThisMonth?.name,
 | 
				
			||||||
 | 
					      area: meterDataThisMonth?.area,
 | 
				
			||||||
 | 
					      lastMonthUsage: totalUsageLastMonth,
 | 
				
			||||||
 | 
					      usageChange: usageChange,
 | 
				
			||||||
 | 
					      usageChangeRate: usageChangeRate,
 | 
				
			||||||
 | 
					      totalUsage: totalUsageThisMonth,
 | 
				
			||||||
 | 
					      peakUsage: meterDataThisMonth?.total_peak || 0,
 | 
				
			||||||
 | 
					      halfPeakUsage: meterDataThisMonth?.total_half || 0,
 | 
				
			||||||
 | 
					      offPeakUsage: meterDataThisMonth?.total_Off || 0,
 | 
				
			||||||
 | 
					      peakCost: meterDataThisMonth?.total_peakCost || 0,
 | 
				
			||||||
 | 
					      halfPeakCost: meterDataThisMonth?.total_halfCost || 0,
 | 
				
			||||||
 | 
					      offPeakCost: meterDataThisMonth?.total_OffCost || 0,
 | 
				
			||||||
 | 
					      totalCost: meterDataThisMonth?.totalFlowCost || 0,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 基本電費
 | 
				
			||||||
 | 
					const totalStandCost = computed(() => {
 | 
				
			||||||
 | 
					  if (!storeElecReport.elecCostSummary.thisMonth) {
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  const elecCostSummaryArray = Object.values(
 | 
				
			||||||
 | 
					    storeElecReport.elecCostSummary.thisMonth
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  if (elecCostSummaryArray.length === 0) {
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return elecCostSummaryArray[0]?.standCost || 0;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const totalFlowCost = computed(() => {
 | 
				
			||||||
 | 
					  if (!storeElecReport.elecCostSummary.thisMonth) {
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  let total = 0;
 | 
				
			||||||
 | 
					  Object.keys(storeElecReport.elecCostSummary.thisMonth).forEach((key) => {
 | 
				
			||||||
 | 
					    // @ts-ignore
 | 
				
			||||||
 | 
					    total += storeElecReport.elecCostSummary.thisMonth[key]?.totalFlowCost || 0;
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  return total;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const totalCost = computed(() => {
 | 
				
			||||||
 | 
					  return totalStandCost.value + totalFlowCost.value;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Three_Phase =
 | 
				
			||||||
 | 
					  elecPrices.find((item: any) => item.displayName === "基本按戶三相")?.out || 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 夏月
 | 
				
			||||||
 | 
					const Regular_Contract_Summer =
 | 
				
			||||||
 | 
					  elecPrices.find((item: any) => item.displayName === "基本經常夏月")?.out || 0;
 | 
				
			||||||
 | 
					const Summer_Peak_Prices =
 | 
				
			||||||
 | 
					  elecPrices.find((item: any) => item.displayName === "流動平日尖峰夏月")
 | 
				
			||||||
 | 
					    ?.out || 0;
 | 
				
			||||||
 | 
					const Summer_HalfPeak_Prices_Weekday =
 | 
				
			||||||
 | 
					  elecPrices.find((item: any) => item.displayName === "流動平日半尖峰夏月")
 | 
				
			||||||
 | 
					    ?.out || 0;
 | 
				
			||||||
 | 
					const Summer_Off_Prices_Weekday =
 | 
				
			||||||
 | 
					  elecPrices.find((item: any) => item.displayName === "流動平日離峰夏月")
 | 
				
			||||||
 | 
					    ?.out || 0;
 | 
				
			||||||
 | 
					const Summer_HalfPeak_Prices_Saturday =
 | 
				
			||||||
 | 
					  elecPrices.find((item: any) => item.displayName === "流動週六半尖峰夏月")
 | 
				
			||||||
 | 
					    ?.out || 0;
 | 
				
			||||||
 | 
					const Summer_Off_Prices_Saturday =
 | 
				
			||||||
 | 
					  elecPrices.find((item: any) => item.displayName === "流動週六離峰夏月")
 | 
				
			||||||
 | 
					    ?.out || 0;
 | 
				
			||||||
 | 
					const Summer_Off_Prices =
 | 
				
			||||||
 | 
					  elecPrices.find((item: any) => item.displayName === "流動週日離峰夏月")
 | 
				
			||||||
 | 
					    ?.out || 0;
 | 
				
			||||||
 | 
					// 非夏月
 | 
				
			||||||
 | 
					const Regular_Contract_Non_Summer =
 | 
				
			||||||
 | 
					  elecPrices.find((item: any) => item.displayName === "基本經常非夏月")?.out ||
 | 
				
			||||||
 | 
					  0;
 | 
				
			||||||
 | 
					const Non_Summer_HalfPeak_Prices_Weekday =
 | 
				
			||||||
 | 
					  elecPrices.find((item: any) => item.displayName === "流動平日半尖峰非夏月")
 | 
				
			||||||
 | 
					    ?.out || 0;
 | 
				
			||||||
 | 
					const Non_Summer_Off_Prices_Weekday =
 | 
				
			||||||
 | 
					  elecPrices.find((item: any) => item.displayName === "流動平日離峰非夏月")
 | 
				
			||||||
 | 
					    ?.out || 0;
 | 
				
			||||||
 | 
					const Non_Summer_HalfPeak_Prices_Saturday =
 | 
				
			||||||
 | 
					  elecPrices.find((item: any) => item.displayName === "流動週六半尖峰非夏月")
 | 
				
			||||||
 | 
					    ?.out || 0;
 | 
				
			||||||
 | 
					const Non_Summer_Off_Prices_Saturday =
 | 
				
			||||||
 | 
					  elecPrices.find((item: any) => item.displayName === "流動週六離峰非夏月")
 | 
				
			||||||
 | 
					    ?.out || 0;
 | 
				
			||||||
 | 
					const Non_Summer_Off_Prices =
 | 
				
			||||||
 | 
					  elecPrices.find((item: any) => item.displayName === "流動週日離峰非夏月")
 | 
				
			||||||
 | 
					    ?.out || 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const elecUsageData = computed(() => {
 | 
				
			||||||
 | 
					  const summaryThisMonth = storeElecReport.elecCostSummary.thisMonth;
 | 
				
			||||||
 | 
					  if (!summaryThisMonth) {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      categories: [],
 | 
				
			||||||
 | 
					      series: [],
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const categories = Object.keys(summaryThisMonth);
 | 
				
			||||||
 | 
					  const peakData = categories.map(
 | 
				
			||||||
 | 
					    (key) => summaryThisMonth[key]?.total_peak || 0
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  const halfPeakData = categories.map(
 | 
				
			||||||
 | 
					    (key) => summaryThisMonth[key]?.total_half || 0
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  const offPeakData = categories.map(
 | 
				
			||||||
 | 
					    (key) => summaryThisMonth[key]?.total_Off || 0
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    categories: categories.map((key) => summaryThisMonth[key]?.name || key),
 | 
				
			||||||
 | 
					    series: [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: "尖峰用電",
 | 
				
			||||||
 | 
					        type: "bar",
 | 
				
			||||||
 | 
					        data: peakData,
 | 
				
			||||||
 | 
					        itemStyle: { color: "#5470c6" },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: "半尖峰用電",
 | 
				
			||||||
 | 
					        type: "bar",
 | 
				
			||||||
 | 
					        data: halfPeakData,
 | 
				
			||||||
 | 
					        itemStyle: { color: "#91cc75" },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: "離峰用電",
 | 
				
			||||||
 | 
					        type: "bar",
 | 
				
			||||||
 | 
					        data: offPeakData,
 | 
				
			||||||
 | 
					        itemStyle: { color: "#fac858" },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					const elecCostData = computed(() => {
 | 
				
			||||||
 | 
					  const summaryThisMonth = storeElecReport.elecCostSummary.thisMonth;
 | 
				
			||||||
 | 
					  if (!summaryThisMonth) {
 | 
				
			||||||
 | 
					    return { series: [] };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getRandomColor.resetIndex();
 | 
				
			||||||
 | 
					  const series = Object.keys(summaryThisMonth).map((key) => {
 | 
				
			||||||
 | 
					    const meterData = summaryThisMonth[key];
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      name: meterData?.name || key,
 | 
				
			||||||
 | 
					      value: meterData?.totalFlowCost.toFixed(2) || 0,
 | 
				
			||||||
 | 
					      itemStyle: { color: getRandomColor() },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return { series };
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
import { ref } from "vue";
 | 
					import { ref } from "vue";
 | 
				
			||||||
import { defineStore } from "pinia";
 | 
					import { defineStore } from "pinia";
 | 
				
			||||||
import type { NiagaraElecData } from "../utils/types";
 | 
					import type { NiagaraElecData } from "../utils/types";
 | 
				
			||||||
 | 
					import axios from "axios";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const useElecPriceStore = defineStore("elecPriceData", () => {
 | 
					const useElecPriceStore = defineStore("elecPriceData", () => {
 | 
				
			||||||
  const elecData = ref<NiagaraElecData[]>([]);
 | 
					  const elecData = ref<NiagaraElecData[]>([]);
 | 
				
			||||||
@ -101,6 +102,30 @@ const useElecPriceStore = defineStore("elecPriceData", () => {
 | 
				
			|||||||
      });
 | 
					      });
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const updatePrice = async (slotPath: string, out: number) => {
 | 
				
			||||||
 | 
					    const domain = window.location.origin;
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      console.log("updatePrice",`${domain}/obix/config/${slotPath}/in2/value`)
 | 
				
			||||||
 | 
					      const res = await axios.put(
 | 
				
			||||||
 | 
					        `${domain}/obix/config/${slotPath}/in2/value`,
 | 
				
			||||||
 | 
					        `<real name="in" val="${out}"/> `,
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          headers: { "Content-Type": "text/xml" },
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      if (res.status === 200) {
 | 
				
			||||||
 | 
					        console.log(`成功更新 ${slotPath} 為 ${out}`);
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        console.error(`更新 ${slotPath} 失敗,狀態碼: ${res.status}`);
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      console.error(`更新 ${slotPath} 出錯:`, error);
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const clearAllSubscriber = () => {
 | 
					  const clearAllSubscriber = () => {
 | 
				
			||||||
    subscribers.value.forEach((subscriber) => {
 | 
					    subscribers.value.forEach((subscriber) => {
 | 
				
			||||||
      subscriber.detach("changed");
 | 
					      subscriber.detach("changed");
 | 
				
			||||||
@ -112,6 +137,7 @@ const useElecPriceStore = defineStore("elecPriceData", () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    getElecDataFromBaja,
 | 
					    getElecDataFromBaja,
 | 
				
			||||||
 | 
					    updatePrice,
 | 
				
			||||||
    clearAllSubscriber,
 | 
					    clearAllSubscriber,
 | 
				
			||||||
    elecData,
 | 
					    elecData,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										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 { ref } from "vue";
 | 
				
			||||||
import { defineStore } from "pinia";
 | 
					import { defineStore } from "pinia";
 | 
				
			||||||
import dayjs from "dayjs";
 | 
					import dayjs from "dayjs";
 | 
				
			||||||
import type { NiagaraElecData, ElecCostSummary } from "../utils/types";
 | 
					import type {
 | 
				
			||||||
import { CalcuEleCost } from "../utils/CalcuEleCost";
 | 
					  NiagaraElecData,
 | 
				
			||||||
 | 
					  ElecCostSummary,
 | 
				
			||||||
 | 
					  ElecStandCostSummary,
 | 
				
			||||||
 | 
					} from "../utils/types";
 | 
				
			||||||
 | 
					import { CalcuEleCost, CalcuEleStandCost } from "../utils/CalcuEleCost";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const useElecStore = defineStore("elecData", () => {
 | 
					const useElecStore = defineStore("elecData", () => {
 | 
				
			||||||
  const elecData = ref<NiagaraElecData[]>([]);
 | 
					  const elecData = ref<NiagaraElecData[]>([]);
 | 
				
			||||||
  // @ts-ignore
 | 
					  // @ts-ignore
 | 
				
			||||||
  let timerId = null;
 | 
					  let timerId = null;
 | 
				
			||||||
  const elecCostSummary = ref<ElecCostSummary | null>(null);
 | 
					  const elecFlowCostSummary = ref<ElecCostSummary | null>(null);
 | 
				
			||||||
 | 
					  const elecStandCostSummary = ref<ElecStandCostSummary | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // get data from baja
 | 
					  // get data from baja
 | 
				
			||||||
  const getElecDataFromBaja = () => {
 | 
					  const getElecDataFromBaja = () => {
 | 
				
			||||||
@ -78,18 +83,27 @@ const useElecStore = defineStore("elecData", () => {
 | 
				
			|||||||
            each: (record: any) => {
 | 
					            each: (record: any) => {
 | 
				
			||||||
              const currentValue = record.get("min");
 | 
					              const currentValue = record.get("min");
 | 
				
			||||||
              const timestamp = record.get("timestamp").$cEncStr;
 | 
					              const timestamp = record.get("timestamp").$cEncStr;
 | 
				
			||||||
              if (currentValue !== null && currentValue !== 0 && timestamp !== null) {
 | 
					              if (
 | 
				
			||||||
                if (lastTimestamp !== null && lastValue !== null && lastValue !== 0) {  
 | 
					                currentValue !== null &&
 | 
				
			||||||
 | 
					                currentValue !== 0 &&
 | 
				
			||||||
 | 
					                timestamp !== null
 | 
				
			||||||
 | 
					              ) {
 | 
				
			||||||
 | 
					                if (
 | 
				
			||||||
 | 
					                  lastTimestamp !== null &&
 | 
				
			||||||
 | 
					                  lastValue !== null &&
 | 
				
			||||||
 | 
					                  lastValue !== 0
 | 
				
			||||||
 | 
					                ) {
 | 
				
			||||||
                  const diff = currentValue - lastValue;
 | 
					                  const diff = currentValue - lastValue;
 | 
				
			||||||
                  dataMap.set(lastTimestamp, diff);
 | 
					                  dataMap.set(lastTimestamp, diff);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                lastValue = currentValue;
 | 
					                lastValue = currentValue;
 | 
				
			||||||
                lastTimestamp = timestamp;
 | 
					                lastTimestamp = timestamp;
 | 
				
			||||||
              } 
 | 
					              }
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            after: () => {
 | 
					            after: () => {
 | 
				
			||||||
              console.log("⏱️ 每小時差值 map:", dataMap);
 | 
					              console.log("⏱️ 每小時差值 map:", dataMap);
 | 
				
			||||||
              elecCostSummary.value = CalcuEleCost(dataMap);
 | 
					              elecFlowCostSummary.value = CalcuEleCost(dataMap);
 | 
				
			||||||
 | 
					              elecStandCostSummary.value = CalcuEleStandCost(dataMap);
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            limit: -1,
 | 
					            limit: -1,
 | 
				
			||||||
            offset: 0,
 | 
					            offset: 0,
 | 
				
			||||||
@ -129,7 +143,8 @@ const useElecStore = defineStore("elecData", () => {
 | 
				
			|||||||
    startTimer,
 | 
					    startTimer,
 | 
				
			||||||
    stopTimer,
 | 
					    stopTimer,
 | 
				
			||||||
    elecData,
 | 
					    elecData,
 | 
				
			||||||
    elecCostSummary,
 | 
					    elecFlowCostSummary,
 | 
				
			||||||
 | 
					    elecStandCostSummary,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,18 @@
 | 
				
			|||||||
import type { DailyResult, ElecCostSummary, DailyEntry } from "../utils/types";
 | 
					import type {
 | 
				
			||||||
 | 
					  DailyResult,
 | 
				
			||||||
 | 
					  ElecCostSummary,
 | 
				
			||||||
 | 
					  DailyEntry,
 | 
				
			||||||
 | 
					  StandEntry,
 | 
				
			||||||
 | 
					  ElecStandCostSummary,
 | 
				
			||||||
 | 
					  StandResult,
 | 
				
			||||||
 | 
					} from "../utils/types";
 | 
				
			||||||
import useElecPriceStore from "../stores/useElecPriceStore";
 | 
					import useElecPriceStore from "../stores/useElecPriceStore";
 | 
				
			||||||
const storeElecPrice = useElecPriceStore();
 | 
					import useElecDemandStore from "../stores/useElecDemandStore";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const CalcuEleCost = (
 | 
					const storeElecPrice = useElecPriceStore();
 | 
				
			||||||
  input: Map<string, number>
 | 
					const ContractUseValue = useElecDemandStore();
 | 
				
			||||||
): ElecCostSummary => {
 | 
					
 | 
				
			||||||
 | 
					export const CalcuEleCost = (input: Map<string, number>): ElecCostSummary => {
 | 
				
			||||||
  const dailyData: Map<string, DailyEntry[]> = new Map();
 | 
					  const dailyData: Map<string, DailyEntry[]> = new Map();
 | 
				
			||||||
  let totalFlowCost = 0; // 總電價
 | 
					  let totalFlowCost = 0; // 總電價
 | 
				
			||||||
  let totalEleCost = 0; //總用電
 | 
					  let totalEleCost = 0; //總用電
 | 
				
			||||||
@ -17,13 +25,27 @@ export const CalcuEleCost = (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const dailyResults: DailyResult[] = [];
 | 
					  const dailyResults: DailyResult[] = [];
 | 
				
			||||||
  const elecPrices = storeElecPrice.elecData;
 | 
					  const elecPrices = storeElecPrice.elecData;
 | 
				
			||||||
  const Summer_Off_Prices = elecPrices.find((item:any) => item.displayName === "流動週日離峰夏月")?.out || 0;
 | 
					  const Summer_Off_Prices =
 | 
				
			||||||
  const Summer_HalfPeak_Prices_Saturday = elecPrices.find((item:any) => item.displayName === "流動週六半尖峰夏月")?.out || 0;
 | 
					    elecPrices.find((item: any) => item.displayName === "流動週日離峰夏月")
 | 
				
			||||||
  const Summer_HalfPeak_Prices_Weekday = elecPrices.find((item:any) => item.displayName === "流動平日半尖峰夏月")?.out || 0;
 | 
					      ?.out || 0;
 | 
				
			||||||
  const Summer_Peak_Prices = elecPrices.find((item:any) => item.displayName === "流動平日尖峰夏月")?.out || 0;
 | 
					  const Summer_HalfPeak_Prices_Saturday =
 | 
				
			||||||
  const Non_Summer_Off_Prices = elecPrices.find((item:any) => item.displayName === "流動週日離峰非夏月")?.out || 0;
 | 
					    elecPrices.find((item: any) => item.displayName === "流動週六半尖峰夏月")
 | 
				
			||||||
  const Non_Summer_HalfPeak_Prices_Saturday = elecPrices.find((item:any) => item.displayName === "流動週六半尖峰非夏月")?.out || 0;
 | 
					      ?.out || 0;
 | 
				
			||||||
  const Non_Summer_HalfPeak_Prices_Weekday = elecPrices.find((item:any) => item.displayName === "流動平日半尖峰非夏月")?.out || 0;
 | 
					  const Summer_HalfPeak_Prices_Weekday =
 | 
				
			||||||
 | 
					    elecPrices.find((item: any) => item.displayName === "流動平日半尖峰夏月")
 | 
				
			||||||
 | 
					      ?.out || 0;
 | 
				
			||||||
 | 
					  const Summer_Peak_Prices =
 | 
				
			||||||
 | 
					    elecPrices.find((item: any) => item.displayName === "流動平日尖峰夏月")
 | 
				
			||||||
 | 
					      ?.out || 0;
 | 
				
			||||||
 | 
					  const Non_Summer_Off_Prices =
 | 
				
			||||||
 | 
					    elecPrices.find((item: any) => item.displayName === "流動週日離峰非夏月")
 | 
				
			||||||
 | 
					      ?.out || 0;
 | 
				
			||||||
 | 
					  const Non_Summer_HalfPeak_Prices_Saturday =
 | 
				
			||||||
 | 
					    elecPrices.find((item: any) => item.displayName === "流動週六半尖峰非夏月")
 | 
				
			||||||
 | 
					      ?.out || 0;
 | 
				
			||||||
 | 
					  const Non_Summer_HalfPeak_Prices_Weekday =
 | 
				
			||||||
 | 
					    elecPrices.find((item: any) => item.displayName === "流動平日半尖峰非夏月")
 | 
				
			||||||
 | 
					      ?.out || 0;
 | 
				
			||||||
  // 1. 將輸入資料按日期分組
 | 
					  // 1. 將輸入資料按日期分組
 | 
				
			||||||
  input.forEach((value, key) => {
 | 
					  input.forEach((value, key) => {
 | 
				
			||||||
    const dateStr = key.substring(0, 10);
 | 
					    const dateStr = key.substring(0, 10);
 | 
				
			||||||
@ -117,7 +139,6 @@ export const CalcuEleCost = (
 | 
				
			|||||||
      dailyEleCost,
 | 
					      dailyEleCost,
 | 
				
			||||||
      dailyFlowCost,
 | 
					      dailyFlowCost,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    dailyResults,
 | 
					    dailyResults,
 | 
				
			||||||
@ -131,3 +152,72 @@ export const CalcuEleCost = (
 | 
				
			|||||||
    total_peakCost,
 | 
					    total_peakCost,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const CalcuEleStandCost = (
 | 
				
			||||||
 | 
					  input: Map<string, number>
 | 
				
			||||||
 | 
					): ElecStandCostSummary => {
 | 
				
			||||||
 | 
					  const elecPrices = storeElecPrice.elecData;
 | 
				
			||||||
 | 
					  const ContractUseData = ContractUseValue.elecData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const Three_Phase =
 | 
				
			||||||
 | 
					    elecPrices.find((item: any) => item.displayName === "基本按戶三相")?.out ||
 | 
				
			||||||
 | 
					    0;
 | 
				
			||||||
 | 
					  const Summer_Regular_Use =
 | 
				
			||||||
 | 
					    elecPrices.find((item: any) => item.displayName === "基本經常夏月")?.out ||
 | 
				
			||||||
 | 
					    0;
 | 
				
			||||||
 | 
					  const Non_Summer_Regular_Use =
 | 
				
			||||||
 | 
					    elecPrices.find((item: any) => item.displayName === "基本經常非夏月")
 | 
				
			||||||
 | 
					      ?.out || 0;
 | 
				
			||||||
 | 
					  const ContractUse =
 | 
				
			||||||
 | 
					    ContractUseData.find((item: any) => item.name === "Engel")?.out || 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const monthMap = new Map<string, StandEntry[]>(); // key: yyyy-MM
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // 將資料依據年月分組
 | 
				
			||||||
 | 
					  input.forEach((value, key) => {
 | 
				
			||||||
 | 
					    const date = new Date(key);
 | 
				
			||||||
 | 
					    const year = date.getFullYear();
 | 
				
			||||||
 | 
					    const month = date.getMonth() + 1;
 | 
				
			||||||
 | 
					    const ymKey = `${year}-${month.toString().padStart(2, "0")}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!monthMap.has(ymKey)) {
 | 
				
			||||||
 | 
					      monthMap.set(ymKey, []);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    monthMap.get(ymKey)?.push({ time: date, value });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const StandResults: StandResult[] = [];
 | 
				
			||||||
 | 
					  let totalStandCost = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const [ym, entries] of monthMap.entries()) {
 | 
				
			||||||
 | 
					    if (!entries || entries.length === 0) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const sampleDate = entries[0].time;
 | 
				
			||||||
 | 
					    const month = sampleDate.getMonth() + 1;
 | 
				
			||||||
 | 
					    const isSummer = month >= 6 && month <= 9;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let Phase = Three_Phase;
 | 
				
			||||||
 | 
					    let Contract = isSummer ? Summer_Regular_Use : Non_Summer_Regular_Use;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const StandCost = Phase + Contract * ContractUse;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    StandResults.push({
 | 
				
			||||||
 | 
					      Phase,
 | 
				
			||||||
 | 
					      Contract,
 | 
				
			||||||
 | 
					      ContractUse,
 | 
				
			||||||
 | 
					      StandCost,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    totalStandCost += StandCost;
 | 
				
			||||||
 | 
					    console.log(`=== ${ym} ===`);
 | 
				
			||||||
 | 
					    console.log(`  按戶類別: ${Phase.toFixed(2)}`);
 | 
				
			||||||
 | 
					    console.log(`  契約類型: $${Contract.toFixed(2)}`);
 | 
				
			||||||
 | 
					    console.log(`  契約度數: $${ContractUse.toFixed(2)}`);
 | 
				
			||||||
 | 
					    console.log(`  基本電價: $${StandCost.toFixed(2)}`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    StandResults,
 | 
				
			||||||
 | 
					    StandCost: totalStandCost,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										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;
 | 
					  slotPath: string;
 | 
				
			||||||
  displayName: string;
 | 
					  displayName: string;
 | 
				
			||||||
  id: string;
 | 
					  id: string;
 | 
				
			||||||
  out: number; 
 | 
					  out: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface NiagaraElecDemandData {
 | 
					export interface NiagaraElecDemandData {
 | 
				
			||||||
  slotPath: string;
 | 
					  slotPath: string;
 | 
				
			||||||
  displayName: string;
 | 
					  displayName: string;
 | 
				
			||||||
  name: string;
 | 
					  name: string;
 | 
				
			||||||
  out: number; 
 | 
					  out: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface DailyResult {
 | 
					export interface DailyResult {
 | 
				
			||||||
@ -18,9 +18,9 @@ export interface DailyResult {
 | 
				
			|||||||
  off: number;
 | 
					  off: number;
 | 
				
			||||||
  half: number;
 | 
					  half: number;
 | 
				
			||||||
  peak: number;
 | 
					  peak: number;
 | 
				
			||||||
  offcost:number;
 | 
					  offcost: number;
 | 
				
			||||||
  halfcost:number;
 | 
					  halfcost: number;
 | 
				
			||||||
  peakcost:number;
 | 
					  peakcost: number;
 | 
				
			||||||
  dailyEleCost: number;
 | 
					  dailyEleCost: number;
 | 
				
			||||||
  dailyFlowCost: number;
 | 
					  dailyFlowCost: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -35,9 +35,38 @@ export interface ElecCostSummary {
 | 
				
			|||||||
  total_OffCost: number;
 | 
					  total_OffCost: number;
 | 
				
			||||||
  total_halfCost: number;
 | 
					  total_halfCost: number;
 | 
				
			||||||
  total_peakCost: number;
 | 
					  total_peakCost: number;
 | 
				
			||||||
 | 
					  standCost?:number;
 | 
				
			||||||
 | 
					  name?: string;
 | 
				
			||||||
 | 
					  area?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ElecCostSummaryMap {
 | 
				
			||||||
 | 
					  thisMonth: {
 | 
				
			||||||
 | 
					    [slotPath: string]: ElecCostSummary | null | undefined;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  lastMonth: {
 | 
				
			||||||
 | 
					    [slotPath: string]: ElecCostSummary | null | undefined;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface DailyEntry {
 | 
					export interface DailyEntry {
 | 
				
			||||||
  time: Date;
 | 
					  time: Date;
 | 
				
			||||||
  value: number;
 | 
					  value: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface StandEntry {
 | 
				
			||||||
 | 
					  time: Date;
 | 
				
			||||||
 | 
					  value: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface StandResult {
 | 
				
			||||||
 | 
					  Phase : number ;
 | 
				
			||||||
 | 
					  Contract : number ;
 | 
				
			||||||
 | 
					  ContractUse : number;
 | 
				
			||||||
 | 
					  StandCost :number ;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ElecStandCostSummary {
 | 
				
			||||||
 | 
					  StandResults: StandResult[];
 | 
				
			||||||
 | 
					  StandCost :number ;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -73,11 +73,19 @@ import useElecTotalMeterStore from "../stores/useElecTotalMeterStore";
 | 
				
			|||||||
import dayjs from "dayjs";
 | 
					import dayjs from "dayjs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const storeElecTotal = useElecTotalMeterStore();
 | 
					const storeElecTotal = useElecTotalMeterStore();
 | 
				
			||||||
 | 
					const billingDateRange = ref("");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch(
 | 
					watch(
 | 
				
			||||||
  () => storeElecTotal.elecCostSummary,
 | 
					  () => storeElecTotal.elecFlowCostSummary,
 | 
				
			||||||
  (newElecData) => {
 | 
					  (newElecData) => {
 | 
				
			||||||
    console.log("elecCostSummary", newElecData);
 | 
					    console.log("elecFlowCostSummary", newElecData);
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  { deep: true }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					  () => storeElecTotal.elecStandCostSummary,
 | 
				
			||||||
 | 
					  (newElecData) => {
 | 
				
			||||||
 | 
					    console.log("elecStandCostSummary", newElecData);
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  { deep: true }
 | 
					  { deep: true }
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
@ -85,6 +93,11 @@ watch(
 | 
				
			|||||||
onMounted(async () => {
 | 
					onMounted(async () => {
 | 
				
			||||||
  await storeElecTotal.getElecDataFromBaja();
 | 
					  await storeElecTotal.getElecDataFromBaja();
 | 
				
			||||||
  storeElecTotal.startTimer();
 | 
					  storeElecTotal.startTimer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // 區間計費時間區段
 | 
				
			||||||
 | 
					  billingDateRange.value = `${dayjs()
 | 
				
			||||||
 | 
					    .startOf("month")
 | 
				
			||||||
 | 
					    .format("YYYY/MM/DD")} - ${dayjs().format("YYYY/MM/DD")}`;
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onUnmounted(() => {
 | 
					onUnmounted(() => {
 | 
				
			||||||
@ -97,8 +110,8 @@ const statisticData = computed(() => {
 | 
				
			|||||||
  let intervalFlowCost = 0;
 | 
					  let intervalFlowCost = 0;
 | 
				
			||||||
  let intervalEleCost = 0;
 | 
					  let intervalEleCost = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (storeElecTotal.elecCostSummary?.dailyResults) {
 | 
					  if (storeElecTotal.elecFlowCostSummary?.dailyResults) {
 | 
				
			||||||
    storeElecTotal.elecCostSummary.dailyResults.forEach((dailyResult) => {
 | 
					    storeElecTotal.elecFlowCostSummary.dailyResults.forEach((dailyResult) => {
 | 
				
			||||||
      if (dailyResult.dateStr.startsWith(currentMonth)) {
 | 
					      if (dailyResult.dateStr.startsWith(currentMonth)) {
 | 
				
			||||||
        intervalFlowCost += dailyResult.dailyFlowCost;
 | 
					        intervalFlowCost += dailyResult.dailyFlowCost;
 | 
				
			||||||
        intervalEleCost += dailyResult.dailyEleCost;
 | 
					        intervalEleCost += dailyResult.dailyEleCost;
 | 
				
			||||||
@ -109,13 +122,13 @@ const statisticData = computed(() => {
 | 
				
			|||||||
  return [
 | 
					  return [
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      title: "今年電費累計",
 | 
					      title: "今年電費累計",
 | 
				
			||||||
      value: storeElecTotal.elecCostSummary?.totalFlowCost || 0,
 | 
					      value: storeElecTotal.elecFlowCostSummary?.totalFlowCost || 0,
 | 
				
			||||||
      unit: "元",
 | 
					      unit: "元",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    { title: "區間電費", value: intervalFlowCost, unit: "元" },
 | 
					    { title: "區間電費", value: intervalFlowCost, unit: "元" },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      title: "今年碳排當量累計",
 | 
					      title: "今年碳排當量累計",
 | 
				
			||||||
      value: storeElecTotal.elecCostSummary?.totalEleCost * 0.424,
 | 
					      value: storeElecTotal.elecFlowCostSummary?.totalEleCost * 0.424,
 | 
				
			||||||
      unit: "公斤 CO2e/度",
 | 
					      unit: "公斤 CO2e/度",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@ -125,7 +138,7 @@ const statisticData = computed(() => {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      title: "今年用電度數",
 | 
					      title: "今年用電度數",
 | 
				
			||||||
      value: storeElecTotal.elecCostSummary?.totalEleCost || 0,
 | 
					      value: storeElecTotal.elecFlowCostSummary?.totalEleCost || 0,
 | 
				
			||||||
      unit: "kWh",
 | 
					      unit: "kWh",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    { title: "區間用電度數", value: intervalEleCost, unit: "kWh" },
 | 
					    { title: "區間用電度數", value: intervalEleCost, unit: "kWh" },
 | 
				
			||||||
@ -162,22 +175,24 @@ function groupByMonth(dailyResults) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// 每月用電分析
 | 
					// 每月用電分析
 | 
				
			||||||
const monthlyElectricityData = computed(() => {
 | 
					const monthlyElectricityData = computed(() => {
 | 
				
			||||||
  if (!storeElecTotal.elecCostSummary?.dailyResults) {
 | 
					  if (!storeElecTotal.elecFlowCostSummary?.dailyResults) {
 | 
				
			||||||
    return { categories: [], series: [] };
 | 
					    return { categories: [], series: [] };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const groupedData = groupByMonth(storeElecTotal.elecCostSummary.dailyResults);
 | 
					  const groupedData = groupByMonth(
 | 
				
			||||||
 | 
					    storeElecTotal.elecFlowCostSummary.dailyResults
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
  const categories = Object.keys(groupedData);
 | 
					  const categories = Object.keys(groupedData);
 | 
				
			||||||
  const sortedCategories = _.sortBy(categories, (month) =>
 | 
					  const sortedCategories = _.sortBy(categories, (month) =>
 | 
				
			||||||
    dayjs().month(month).valueOf()
 | 
					    dayjs().month(month).valueOf()
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const baseElecData = sortedCategories.map((month) => groupedData[month].offcost);
 | 
					  const baseElecData = storeElecTotal.elecStandCostSummary.StandCost;
 | 
				
			||||||
  const flowElecData = sortedCategories.map(
 | 
					  const flowElecData = sortedCategories.map(
 | 
				
			||||||
    (month) => groupedData[month].totalCost
 | 
					    (month) => groupedData[month].totalCost
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
  const totalElecData = sortedCategories.map((month, index) => { 
 | 
					  const totalElecData = sortedCategories.map((month, index) => {
 | 
				
			||||||
    return (baseElecData[index] || 0) + (flowElecData[index] || 0); 
 | 
					    return baseElecData + (flowElecData[index] || 0);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
@ -186,7 +201,7 @@ const monthlyElectricityData = computed(() => {
 | 
				
			|||||||
      {
 | 
					      {
 | 
				
			||||||
        name: "基本電費",
 | 
					        name: "基本電費",
 | 
				
			||||||
        type: "bar",
 | 
					        type: "bar",
 | 
				
			||||||
        data: baseElecData,
 | 
					        data: Array(sortedCategories.length).fill(baseElecData),
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        name: "流動電費",
 | 
					        name: "流動電費",
 | 
				
			||||||
@ -194,7 +209,7 @@ const monthlyElectricityData = computed(() => {
 | 
				
			|||||||
        data: flowElecData,
 | 
					        data: flowElecData,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        name: "總電量",
 | 
					        name: "總電費",
 | 
				
			||||||
        type: "bar",
 | 
					        type: "bar",
 | 
				
			||||||
        data: totalElecData,
 | 
					        data: totalElecData,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@ -204,11 +219,13 @@ const monthlyElectricityData = computed(() => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// 每月碳排當量
 | 
					// 每月碳排當量
 | 
				
			||||||
const monthlyCarbonData = computed(() => {
 | 
					const monthlyCarbonData = computed(() => {
 | 
				
			||||||
  if (!storeElecTotal.elecCostSummary?.dailyResults) {
 | 
					  if (!storeElecTotal.elecFlowCostSummary?.dailyResults) {
 | 
				
			||||||
    return { categories: [], series: [] };
 | 
					    return { categories: [], series: [] };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const groupedData = groupByMonth(storeElecTotal.elecCostSummary.dailyResults);
 | 
					  const groupedData = groupByMonth(
 | 
				
			||||||
 | 
					    storeElecTotal.elecFlowCostSummary.dailyResults
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
  const categories = Object.keys(groupedData);
 | 
					  const categories = Object.keys(groupedData);
 | 
				
			||||||
  const sortedCategories = _.sortBy(categories, (month) =>
 | 
					  const sortedCategories = _.sortBy(categories, (month) =>
 | 
				
			||||||
    dayjs().month(month).valueOf()
 | 
					    dayjs().month(month).valueOf()
 | 
				
			||||||
@ -232,76 +249,82 @@ const monthlyCarbonData = computed(() => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// 每月計費度數 (kWh)
 | 
					// 每月計費度數 (kWh)
 | 
				
			||||||
const monthlyBillingData = computed(() => {
 | 
					const monthlyBillingData = computed(() => {
 | 
				
			||||||
  if (!storeElecTotal.elecCostSummary?.dailyResults) {
 | 
					  if (!storeElecTotal.elecFlowCostSummary?.dailyResults) {
 | 
				
			||||||
    return { categories: [], series: [] };
 | 
					    return { categories: [], series: [] };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const billingData = groupByMonth(storeElecTotal.elecCostSummary.dailyResults);
 | 
					  const billingData = groupByMonth(
 | 
				
			||||||
 | 
					    storeElecTotal.elecFlowCostSummary.dailyResults
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
  const categories = Object.keys(billingData);
 | 
					  const categories = Object.keys(billingData);
 | 
				
			||||||
    const sortedCategories = _.sortBy(categories, month => dayjs().month(month).valueOf());
 | 
					  const sortedCategories = _.sortBy(categories, (month) =>
 | 
				
			||||||
 | 
					    dayjs().month(month).valueOf()
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const peakData = sortedCategories.map(date => billingData[date].peak);
 | 
					  const peakData = sortedCategories.map((date) => billingData[date].peak);
 | 
				
			||||||
  const halfData = sortedCategories.map(date => billingData[date].half);
 | 
					  const halfData = sortedCategories.map((date) => billingData[date].half);
 | 
				
			||||||
  const offData = sortedCategories.map(date => billingData[date].off);
 | 
					  const offData = sortedCategories.map((date) => billingData[date].off);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    categories: sortedCategories,
 | 
					    categories: sortedCategories,
 | 
				
			||||||
    series: [
 | 
					    series: [
 | 
				
			||||||
      { name: "尖峰", type: "bar", data: peakData },
 | 
					      { name: "尖峰", type: "bar", data: peakData },
 | 
				
			||||||
      { name: "半尖峰", type: "bar", data: halfData },
 | 
					      { name: "半尖峰", type: "bar", data: halfData },
 | 
				
			||||||
      { name: "離峰", type: "bar", data: offData }
 | 
					      { name: "離峰", type: "bar", data: offData },
 | 
				
			||||||
    ]
 | 
					    ],
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 區間計費度數
 | 
					 | 
				
			||||||
const billingDateRange = computed(() => {
 | 
					 | 
				
			||||||
  const startOfMonth = dayjs().startOf('month').format('YYYY-MM-DD');
 | 
					 | 
				
			||||||
  const today = dayjs().format('YYYY-MM-DD');
 | 
					 | 
				
			||||||
  return `${startOfMonth} - ${today}`;
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const areaBillingData = computed(() => {
 | 
					const areaBillingData = computed(() => {
 | 
				
			||||||
  if (!storeElecTotal.elecCostSummary?.dailyResults) {
 | 
					  if (!storeElecTotal.elecFlowCostSummary?.dailyResults) {
 | 
				
			||||||
    return { categories: [], series: [] };
 | 
					    return { categories: [], series: [] };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const today = dayjs();
 | 
					  const today = dayjs();
 | 
				
			||||||
  const currentMonth = today.format('YYYY-MM');
 | 
					  const currentMonth = today.format("YYYY-MM");
 | 
				
			||||||
  const startDate = dayjs(`${currentMonth}-01`); // Get the first day of the current month
 | 
					  const startDate = dayjs(`${currentMonth}-01`); // Get the first day of the current month
 | 
				
			||||||
  const endDate = today;
 | 
					  const endDate = today;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Filter daily results within the specified date range
 | 
					  // Filter daily results within the specified date range
 | 
				
			||||||
  const areaResults = storeElecTotal.elecCostSummary.dailyResults.filter(result => {
 | 
					  const areaResults = storeElecTotal.elecFlowCostSummary.dailyResults.filter(
 | 
				
			||||||
    const resultDate = dayjs(result.dateStr);
 | 
					    (result) => {
 | 
				
			||||||
    return resultDate.isSame(startDate, 'day') || (resultDate.isAfter(startDate, 'day') && resultDate.isBefore(endDate, 'day')) || resultDate.isSame(endDate, 'day');
 | 
					      const resultDate = dayjs(result.dateStr);
 | 
				
			||||||
  });
 | 
					      return (
 | 
				
			||||||
 | 
					        resultDate.isSame(startDate, "day") ||
 | 
				
			||||||
 | 
					        (resultDate.isAfter(startDate, "day") &&
 | 
				
			||||||
 | 
					          resultDate.isBefore(endDate, "day")) ||
 | 
				
			||||||
 | 
					        resultDate.isSame(endDate, "day")
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Transform the filtered data for billing
 | 
					  // Transform the filtered data for billing
 | 
				
			||||||
  const transformedData = {};
 | 
					  const transformedData = {};
 | 
				
			||||||
  areaResults.forEach(result => {
 | 
					  areaResults.forEach((result) => {
 | 
				
			||||||
    const date = dayjs(result.dateStr).format('MM-DD'); // Format the date for the category
 | 
					    const date = dayjs(result.dateStr).format("MM-DD"); // Format the date for the category
 | 
				
			||||||
    transformedData[date] = {
 | 
					    transformedData[date] = {
 | 
				
			||||||
      peak: result.peak,
 | 
					      peak: result.peak,
 | 
				
			||||||
      half: result.half,
 | 
					      half: result.half,
 | 
				
			||||||
      off: result.off
 | 
					      off: result.off,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const categories = Object.keys(transformedData);
 | 
					  const categories = Object.keys(transformedData);
 | 
				
			||||||
  const sortedCategories = _.sortBy(categories, date => dayjs(date, 'MM-DD').valueOf());
 | 
					  const sortedCategories = _.sortBy(categories, (date) =>
 | 
				
			||||||
 | 
					    dayjs(date, "MM-DD").valueOf()
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const peakData = sortedCategories.map(date => transformedData[date].peak);
 | 
					  const peakData = sortedCategories.map((date) => transformedData[date].peak);
 | 
				
			||||||
  const halfData = sortedCategories.map(date => transformedData[date].half);
 | 
					  const halfData = sortedCategories.map((date) => transformedData[date].half);
 | 
				
			||||||
  const offData = sortedCategories.map(date => transformedData[date].off);
 | 
					  const offData = sortedCategories.map((date) => transformedData[date].off);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    categories: sortedCategories,
 | 
					    categories: sortedCategories,
 | 
				
			||||||
    series: [
 | 
					    series: [
 | 
				
			||||||
      { name: "尖峰", type: "bar", data: peakData },
 | 
					      { name: "尖峰", type: "bar", data: peakData },
 | 
				
			||||||
      { name: "半尖峰", type: "bar", data: halfData },
 | 
					      { name: "半尖峰", type: "bar", data: halfData },
 | 
				
			||||||
      { name: "離峰", type: "bar", data: offData }
 | 
					      { name: "離峰", type: "bar", data: offData },
 | 
				
			||||||
    ]
 | 
					    ],
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
				
			|||||||
@ -311,7 +311,6 @@
 | 
				
			|||||||
import { ref, watch } from "vue";
 | 
					import { ref, watch } from "vue";
 | 
				
			||||||
import { Edit, CircleClose, CircleCheck } from "@element-plus/icons-vue";
 | 
					import { Edit, CircleClose, CircleCheck } from "@element-plus/icons-vue";
 | 
				
			||||||
import useElecPriceStore from "../stores/useElecPriceStore";
 | 
					import useElecPriceStore from "../stores/useElecPriceStore";
 | 
				
			||||||
import axios from "axios";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const storeElecPrice = useElecPriceStore();
 | 
					const storeElecPrice = useElecPriceStore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -360,30 +359,6 @@ const resetStand3Values = () => {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const updatePrice = async (slotPath: string, out: number) => {
 | 
					 | 
				
			||||||
  const domain = window.location.origin;
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    console.log("updatePrice",`${domain}/obix/config/${slotPath}/in2/value`)
 | 
					 | 
				
			||||||
    const res = await axios.put(
 | 
					 | 
				
			||||||
      `${domain}/obix/config/${slotPath}/in2/value`,
 | 
					 | 
				
			||||||
      `<real name="in" val="${out}"/> `,
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        headers: { "Content-Type": "text/xml" },
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    if (res.status === 200) {
 | 
					 | 
				
			||||||
      console.log(`成功更新 ${slotPath} 為 ${out}`);
 | 
					 | 
				
			||||||
      return true;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      console.error(`更新 ${slotPath} 失敗,狀態碼: ${res.status}`);
 | 
					 | 
				
			||||||
      return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  } catch (error) {
 | 
					 | 
				
			||||||
    console.error(`更新 ${slotPath} 出錯:`, error);
 | 
					 | 
				
			||||||
    return false;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const confirmChanges = async () => {
 | 
					const confirmChanges = async () => {
 | 
				
			||||||
  stand3isEditing.value = false;
 | 
					  stand3isEditing.value = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -419,7 +394,7 @@ const confirmChanges = async () => {
 | 
				
			|||||||
            ? item.slotPath.slice(6) // 移除 "slot:/"
 | 
					            ? item.slotPath.slice(6) // 移除 "slot:/"
 | 
				
			||||||
            : item.slotPath; // 如果没有前綴,則保持不變
 | 
					            : item.slotPath; // 如果没有前綴,則保持不變
 | 
				
			||||||
          // 更新 Niagara
 | 
					          // 更新 Niagara
 | 
				
			||||||
          const success = await updatePrice(slotPath, stand3Value.value[i]);
 | 
					          const success = await storeElecPrice.updatePrice(slotPath, stand3Value.value[i]);
 | 
				
			||||||
          if (!success) {
 | 
					          if (!success) {
 | 
				
			||||||
            failedUpdates.push({
 | 
					            failedUpdates.push({
 | 
				
			||||||
              slotPath: item.slotPath,
 | 
					              slotPath: item.slotPath,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,17 +1,52 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <el-row :gutter="20">
 | 
					  <el-row :gutter="20">
 | 
				
			||||||
    <el-col :span="24">
 | 
					    <el-col :span="24" style="padding-right: 30px">
 | 
				
			||||||
      <div style="display: flex; justify-content: flex-end">
 | 
					      <div style="display: flex; justify-content: flex-end">
 | 
				
			||||||
        <el-button type="primary" :icon="Printer" @click="generatePDF"
 | 
					        <el-button
 | 
				
			||||||
 | 
					          plain
 | 
				
			||||||
 | 
					          type="primary"
 | 
				
			||||||
 | 
					          :icon="Setting"
 | 
				
			||||||
 | 
					          @click="dialogVisible = true"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          設定報表
 | 
				
			||||||
 | 
					        </el-button>
 | 
				
			||||||
 | 
					        <el-button plain type="success" :icon="Printer" @click="generatePDF"
 | 
				
			||||||
          >列印報表</el-button
 | 
					          >列印報表</el-button
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </el-col>
 | 
					    </el-col>
 | 
				
			||||||
  </el-row>
 | 
					  </el-row>
 | 
				
			||||||
   <!-- 顯示在畫面上的內容 -->
 | 
					  <!-- 設定 Modal -->
 | 
				
			||||||
  <PdfContent />
 | 
					  <EnergyModal
 | 
				
			||||||
 | 
					    v-model="dialogVisible"
 | 
				
			||||||
   <!-- 給 vue3-html2pdf 用的內容 -->
 | 
					    title="設定報表"
 | 
				
			||||||
 | 
					    :confirm="handleConfirm"
 | 
				
			||||||
 | 
					    :close="handleClose"
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <template #default>
 | 
				
			||||||
 | 
					      <!-- Modal 的內容 -->
 | 
				
			||||||
 | 
					      <el-form :model="form" label-width="auto" style="max-width: 600px">
 | 
				
			||||||
 | 
					        <el-form-item label="報表名稱">
 | 
				
			||||||
 | 
					          <el-input v-model="form.name" />
 | 
				
			||||||
 | 
					        </el-form-item>
 | 
				
			||||||
 | 
					        <el-form-item label="報表期間">
 | 
				
			||||||
 | 
					          <el-date-picker
 | 
				
			||||||
 | 
					            v-model="form.date"
 | 
				
			||||||
 | 
					            type="month"
 | 
				
			||||||
 | 
					            placeholder="請選擇報表時間"
 | 
				
			||||||
 | 
					            style="width: 100%"
 | 
				
			||||||
 | 
					            value-format="YYYY-MM-DD"
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </el-form-item>
 | 
				
			||||||
 | 
					        <el-form-item label="編製人員">
 | 
				
			||||||
 | 
					          <el-input v-model="form.staff" />
 | 
				
			||||||
 | 
					        </el-form-item>
 | 
				
			||||||
 | 
					      </el-form>
 | 
				
			||||||
 | 
					    </template>
 | 
				
			||||||
 | 
					  </EnergyModal>
 | 
				
			||||||
 | 
					  <!-- 顯示在畫面上的內容 -->
 | 
				
			||||||
 | 
					  <PdfContent :form="originalForm" />
 | 
				
			||||||
 | 
					  <!-- 給 vue3-html2pdf 用的內容 -->
 | 
				
			||||||
  <vue3-html2pdf
 | 
					  <vue3-html2pdf
 | 
				
			||||||
    ref="html2Pdf"
 | 
					    ref="html2Pdf"
 | 
				
			||||||
    :show-layout="false"
 | 
					    :show-layout="false"
 | 
				
			||||||
@ -27,30 +62,101 @@
 | 
				
			|||||||
    pdf-content-width="1120px"
 | 
					    pdf-content-width="1120px"
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
    <template #pdf-content>
 | 
					    <template #pdf-content>
 | 
				
			||||||
       <PdfContent />
 | 
					      <PdfContent :form="originalForm"  />
 | 
				
			||||||
    </template>
 | 
					    </template>
 | 
				
			||||||
  </vue3-html2pdf>
 | 
					  </vue3-html2pdf>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { ref } from "vue";
 | 
					import { ref, reactive, watch, onMounted, onUnmounted } from "vue";
 | 
				
			||||||
import PdfContent from "../components/PdfContent.vue";
 | 
					import PdfContent from "../components/PdfContent.vue";
 | 
				
			||||||
import { Printer } from "@element-plus/icons-vue";
 | 
					import EnergyModal from "../components/EnergyModal.vue";
 | 
				
			||||||
 | 
					import { Setting, Printer } from "@element-plus/icons-vue";
 | 
				
			||||||
import Vue3Html2pdf from "vue3-html2pdf";
 | 
					import Vue3Html2pdf from "vue3-html2pdf";
 | 
				
			||||||
 | 
					import useElecReportStore from "../stores/useElecReportStore";
 | 
				
			||||||
 | 
					import dayjs from "dayjs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const storeElecReport = useElecReportStore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 設定
 | 
				
			||||||
 | 
					const dialogVisible = ref(false);
 | 
				
			||||||
 | 
					const form = reactive({
 | 
				
			||||||
 | 
					  name: "智慧大樓電表月報表",
 | 
				
			||||||
 | 
					  date: dayjs()
 | 
				
			||||||
 | 
					    .startOf("month")
 | 
				
			||||||
 | 
					    .startOf("day")
 | 
				
			||||||
 | 
					    .format("YYYY-MM-DDTHH:mm:ss.000+08:00"),
 | 
				
			||||||
 | 
					  staff: "能源管理部",
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					const originalForm = reactive({ ...form });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// PDF 文件名稱
 | 
					// PDF 文件名稱
 | 
				
			||||||
const pdfFileName = ref("智慧大樓電表月報表");
 | 
					const pdfFileName = ref("智慧大樓電表月報表");
 | 
				
			||||||
 | 
					 | 
				
			||||||
// 引用 vue-html2pdf 實例
 | 
					// 引用 vue-html2pdf 實例
 | 
				
			||||||
const html2Pdf = ref<InstanceType<typeof Vue3Html2pdf> | null>(null);
 | 
					const html2Pdf = ref<InstanceType<typeof Vue3Html2pdf> | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 處理確認邏輯
 | 
				
			||||||
 | 
					const handleConfirm = () => {
 | 
				
			||||||
 | 
					  console.log("確認", form);
 | 
				
			||||||
 | 
					  localStorage.setItem("elecReportForm", JSON.stringify(form));
 | 
				
			||||||
 | 
					  storeElecReport.startTime = dayjs(form.date)
 | 
				
			||||||
 | 
					    .startOf("month")
 | 
				
			||||||
 | 
					    .startOf("day")
 | 
				
			||||||
 | 
					    .format("YYYY-MM-DDTHH:mm:ss.000+08:00");
 | 
				
			||||||
 | 
					  storeElecReport.endTime = dayjs(form.date)
 | 
				
			||||||
 | 
					    .endOf("month")
 | 
				
			||||||
 | 
					    .endOf("day")
 | 
				
			||||||
 | 
					    .format("YYYY-MM-DDTHH:mm:ss.000+08:00");
 | 
				
			||||||
 | 
					  storeElecReport.updateHistoryData();
 | 
				
			||||||
 | 
					  Object.assign(originalForm, form);
 | 
				
			||||||
 | 
					  dialogVisible.value = false;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 處理關閉邏輯
 | 
				
			||||||
 | 
					const handleClose = () => {
 | 
				
			||||||
 | 
					  dialogVisible.value = false;
 | 
				
			||||||
 | 
					  Object.assign(form, originalForm);
 | 
				
			||||||
 | 
					  console.log("關閉");
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 觸發 PDF 生成和下載
 | 
					// 觸發 PDF 生成和下載
 | 
				
			||||||
const generatePDF = async () => {
 | 
					const generatePDF = async () => {
 | 
				
			||||||
  if (html2Pdf.value) {
 | 
					  if (html2Pdf.value) {
 | 
				
			||||||
    await html2Pdf.value.generatePdf();
 | 
					    await html2Pdf.value.generatePdf();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					  () => storeElecReport.elecCostSummary,
 | 
				
			||||||
 | 
					  (newElecData) => {
 | 
				
			||||||
 | 
					    if(newElecData){
 | 
				
			||||||
 | 
					      console.log("elecCostSummary",newElecData)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  { deep: true }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(async () => {
 | 
				
			||||||
 | 
					  const storedForm = localStorage.getItem("elecReportForm");
 | 
				
			||||||
 | 
					  if (storedForm) {
 | 
				
			||||||
 | 
					    Object.assign(form, JSON.parse(storedForm));
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    localStorage.setItem("elecReportForm", JSON.stringify(form));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  Object.assign(originalForm, form);
 | 
				
			||||||
 | 
					  storeElecReport.startTime = dayjs(form.date)
 | 
				
			||||||
 | 
					    .startOf("month")
 | 
				
			||||||
 | 
					    .startOf("day")
 | 
				
			||||||
 | 
					    .format("YYYY-MM-DDTHH:mm:ss.000+08:00");
 | 
				
			||||||
 | 
					  storeElecReport.endTime = dayjs(form.date)
 | 
				
			||||||
 | 
					    .endOf("month")
 | 
				
			||||||
 | 
					    .endOf("day")
 | 
				
			||||||
 | 
					    .format("YYYY-MM-DDTHH:mm:ss.000+08:00");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  await storeElecReport.getElecDataFromBaja();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onUnmounted(() => {});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped>
 | 
					<style scoped></style>
 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user