Преглед на файлове

[101902] 增加功率误差统计页面

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
taosj преди 2 седмици
родител
ревизия
8797afc8b7
променени са 2 файла, в които са добавени 258 реда и са изтрити 0 реда
  1. 41 0
      forecast-backend-vue/src/api/data/powerErrorStat.js
  2. 217 0
      forecast-backend-vue/src/views/ufo/electricity/datareport/powerErrorStat.vue

+ 41 - 0
forecast-backend-vue/src/api/data/powerErrorStat.js

@@ -0,0 +1,41 @@
+import request from '@/utils/request'
+
+// 查询功率误差统计曲线报表
+export function listPowerErrorStatCurve(data) {
+  return request({
+    url: '/data/powerErrorStatCurve',
+    method: 'post',
+    data: data
+  })
+}
+
+// 导出功率误差统计曲线报表
+export function exportPowerErrorStatCurve(data) {
+  return request({
+    url: '/data/powerErrorStatCurve/export',
+    method: 'post',
+    data: data,
+    responseType: 'blob',
+    timeout: 120000
+  })
+}
+
+// 查询功率误差统计精度报表
+export function listPowerErrorStatAccuracy(data) {
+  return request({
+    url: '/data/powerErrorStatAccuracy',
+    method: 'post',
+    data: data
+  })
+}
+
+// 导出功率误差统计精度报表
+export function exportPowerErrorStatAccuracy(data) {
+  return request({
+    url: '/data/powerErrorStatAccuracy/export',
+    method: 'post',
+    data: data,
+    responseType: 'blob',
+    timeout: 120000
+  })
+}

+ 217 - 0
forecast-backend-vue/src/views/ufo/electricity/datareport/powerErrorStat.vue

@@ -0,0 +1,217 @@
+<template>
+  <div class="power-error-stat-page">
+    <a-card :bordered="false">
+      <a-row :gutter="[12, 12]" align="middle">
+        <a-col :xs="24" :md="6">
+          <StationSelector v-model:value="query.stationCode" :options="stationOptions" :multiple="false" />
+        </a-col>
+        <a-col :xs="24" :md="4">
+          <a-select v-model:value="query.forecastType" :options="forecastTypeOptions" class="full-control" />
+        </a-col>
+        <a-col :xs="24" :md="5">
+          <a-date-picker
+            v-model:value="query.startTime"
+            format="YYYY-MM-DD"
+            value-format="YYYY-MM-DD"
+            placeholder="起始时间"
+            class="full-control"
+          />
+        </a-col>
+        <a-col :xs="24" :md="5">
+          <a-date-picker
+            v-model:value="query.endTime"
+            format="YYYY-MM-DD"
+            value-format="YYYY-MM-DD"
+            placeholder="截止时间"
+            class="full-control"
+          />
+        </a-col>
+        <a-col :xs="24" :md="4">
+          <a-space>
+            <a-button type="primary" :loading="loading" :disabled="!queryValid" @click="handleQuery">确认</a-button>
+            <a-button :loading="exporting" :disabled="!queryValid" @click="handleExport">导出</a-button>
+          </a-space>
+        </a-col>
+      </a-row>
+    </a-card>
+
+    <a-card :bordered="false" class="content-card">
+      <a-tabs v-model:activeKey="viewMode" class="content-tabs" @change="handleTabChange">
+        <a-tab-pane key="curve" tab="曲线">
+          <PowerLineChart :x-axis="curveAxis" :series="curveSeries" height="360px" y-axis-name="kW" />
+        </a-tab-pane>
+        <a-tab-pane key="accuracy" tab="精度">
+          <BusinessDataTable
+            :columns="accuracyColumns"
+            :data-source="accuracyRows"
+            :pagination="{ pageSize: 10 }"
+            :scroll-x="'max-content'"
+          />
+        </a-tab-pane>
+      </a-tabs>
+    </a-card>
+  </div>
+</template>
+
+<script setup name="PowerErrorStat">
+import {computed, onMounted, reactive, ref} from 'vue';
+import {message} from 'ant-design-vue';
+import dayjs from 'dayjs';
+import {saveAs} from 'file-saver';
+import {blobValidate} from '@/utils/bearjia';
+import {listStation} from '@/api/config/station';
+import {
+  exportPowerErrorStatAccuracy,
+  exportPowerErrorStatCurve,
+  listPowerErrorStatAccuracy,
+  listPowerErrorStatCurve
+} from '@/api/data/powerErrorStat';
+import BusinessDataTable from '../components/BusinessDataTable.vue';
+import PowerLineChart from '../components/PowerLineChart.vue';
+import StationSelector from '../components/StationSelector.vue';
+import {buildLineFromRows, normalizeRows} from '../components/dataAdapter';
+import {toStationOptions} from '../components/stationAdapter';
+
+const loading = ref(false);
+const exporting = ref(false);
+const stationOptions = ref([]);
+const curveRows = ref([]);
+const accuracyRows = ref([]);
+const viewMode = ref('curve');
+
+const query = reactive({
+  stationCode: undefined,
+  forecastType: 'short',
+  startTime: dayjs().subtract(7, 'day').format('YYYY-MM-DD'),
+  endTime: dayjs().format('YYYY-MM-DD')
+});
+
+const forecastTypeOptions = [
+  {label: '短期预测', value: 'short'},
+  {label: '中期预测', value: 'medium'},
+  {label: '超短期预测', value: 'ultra'}
+];
+
+const accuracyColumns = [
+  {title: '日期', dataIndex: 'time', width: 120},
+  {title: '均方根误差', dataIndex: 'rmse', width: 120},
+  {title: '平均绝对误差', dataIndex: 'mae', width: 130},
+  {title: '相关性系数', dataIndex: 'correlation', width: 120},
+  {title: '准确率', dataIndex: 'passRate', width: 120},
+  {title: '合格率', dataIndex: 'acc', width: 120}
+];
+
+const queryValid = computed(() => {
+  if (!query.startTime || !query.endTime) return false;
+  const start = dayjs(query.startTime);
+  const end = dayjs(query.endTime);
+  if (!start.isValid() || !end.isValid()) return false;
+  return !start.isAfter(end);
+});
+
+const curveAxis = computed(() => curveRows.value.map((item) => item.time));
+const curveSeries = computed(() => buildLineFromRows(curveRows.value, [
+  {name: '实测功率', color: '#1677ff', keys: ['power']},
+  {name: '预测功率', color: '#52c41a', keys: ['forecastPower']}
+]).series);
+
+const loadStationOptions = async () => {
+  const res = await listStation({pageNum: 1, pageSize: 500});
+  stationOptions.value = toStationOptions(normalizeRows(res));
+  if (!query.stationCode && stationOptions.value.length) {
+    query.stationCode = stationOptions.value[0].value;
+  }
+};
+
+const buildParams = () => ({
+  stationCode: query.stationCode,
+  forecastType: query.forecastType,
+  startTime: query.startTime,
+  endTime: query.endTime
+});
+
+const fetchCurve = async () => {
+  const res = await listPowerErrorStatCurve(buildParams());
+  curveRows.value = normalizeRows(res);
+};
+
+const fetchAccuracy = async () => {
+  const res = await listPowerErrorStatAccuracy(buildParams());
+  accuracyRows.value = normalizeRows(res);
+};
+
+const handleQuery = async () => {
+  if (!query.stationCode) {
+    message.warning('请先选择场站');
+    return;
+  }
+  if (!queryValid.value) return;
+
+  loading.value = true;
+  try {
+    if (viewMode.value === 'accuracy') {
+      await fetchAccuracy();
+    } else {
+      await fetchCurve();
+    }
+  } finally {
+    loading.value = false;
+  }
+};
+
+// 切换 Tab: 查询对应数据并绘制曲线图/Table
+const handleTabChange = (key) => {
+  viewMode.value = key;
+  handleQuery();
+};
+
+const handleExport = async () => {
+  if (!query.stationCode) {
+    message.warning('请先选择场站');
+    return;
+  }
+  if (!queryValid.value) return;
+
+  exporting.value = true;
+  try {
+    const res = viewMode.value === 'accuracy'
+      ? await exportPowerErrorStatAccuracy(buildParams())
+      : await exportPowerErrorStatCurve(buildParams());
+    const isFile = await blobValidate(res);
+    if (isFile) {
+      const prefix = viewMode.value === 'accuracy' ? '功率误差统计精度' : '功率误差统计曲线';
+      const name = `${prefix}-${query.startTime}-${query.endTime}.xlsx`;
+      saveAs(new Blob([res]), name);
+      message.success('导出成功');
+    } else {
+      const text = await res.text();
+      let msg = '导出失败';
+      try {
+        msg = JSON.parse(text)?.msg || msg;
+      } catch (e) {
+        /* ignore */
+      }
+      message.error(msg);
+    }
+  } finally {
+    exporting.value = false;
+  }
+};
+
+onMounted(async () => {
+  await loadStationOptions();
+  await handleQuery();
+});
+</script>
+
+<style lang="less" scoped>
+.power-error-stat-page {
+  padding: 16px;
+}
+.full-control {
+  width: 100%;
+}
+.content-card {
+  margin-top: 16px;
+}
+</style>