Ver código fonte

[0924] 增加中期功率预测页面

taosj 3 semanas atrás
pai
commit
6881b0cd52

+ 21 - 0
forecast-backend-vue/src/api/power/mediumTermPowerForecasting.js

@@ -0,0 +1,21 @@
+import request from '@/utils/request'
+
+// 查询中期功率预测数据
+export function reportMediumTermPowerForecasting(query) {
+  return request({
+    url: '/mediumterm/powerForecastIssued/report',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出中期功率预测数据
+export function exportMediumTermPowerForecasting(data) {
+  return request({
+    url: '/mediumterm/powerForecastIssued/export/excel',
+    method: 'get',
+    params: data,
+    responseType: 'blob',
+    timeout: 120000
+  })
+}

+ 6 - 0
forecast-backend-vue/src/router/electricity.js

@@ -96,6 +96,12 @@ const electricityRoutes = [
         component: () => import('@/views/ufo/electricity/forecast/ShortTermPowerForecasting.vue'),
         meta: {title: '短期功率预测', icon: 'AreaChartOutlined'}
       },
+      {
+        path: 'forecast/medium-term-power',
+        name: 'MediumTermPowerForecasting',
+        component: () => import('@/views/ufo/electricity/forecast/MediumTermPowerForecasting.vue'),
+        meta: {title: '中期功率预测', icon: 'AreaChartOutlined'}
+      },
       {
         path: 'forecast/irradiance-power-comparison',
         name: 'IrradiancePowerComparison',

+ 152 - 0
forecast-backend-vue/src/views/ufo/electricity/forecast/MediumTermPowerForecasting.vue

@@ -0,0 +1,152 @@
+<template>
+  <div class="medium-term-power-page">
+    <a-card :bordered="false">
+      <a-row :gutter="[12, 12]" align="middle">
+        <a-col :xs="24" :md="8">
+          <StationSelector v-model:value="query.stationId" :options="stationOptions" :multiple="false" />
+        </a-col>
+        <a-col :xs="24" :md="6">
+          <a-select v-model:value="query.forecastHours" :options="forecastHoursOptions" class="full-control" />
+        </a-col>
+        <a-col :xs="24" :md="6">
+          <a-space>
+            <a-button type="primary" :loading="loading" @click="handleQuery">确认</a-button>
+            <a-button :loading="exporting" @click="handleExport">导出</a-button>
+          </a-space>
+        </a-col>
+      </a-row>
+    </a-card>
+
+    <a-row :gutter="[16, 16]" class="content-row">
+      <a-col :span="24">
+        <a-card title="中期预测曲线图" :bordered="false">
+          <PowerLineChart :x-axis="chartAxis" :series="chartSeries" height="360px" y-axis-name="kW" />
+        </a-card>
+      </a-col>
+      <a-col :span="24">
+        <a-card title="数据详情" :bordered="false">
+          <BusinessDataTable :columns="tableColumns" :data-source="tableRows" :pagination="{pageSize: 10}" />
+        </a-card>
+      </a-col>
+    </a-row>
+  </div>
+</template>
+
+<script setup name="MediumTermPowerForecasting">
+import {onMounted, reactive, ref} from 'vue';
+import {message} from 'ant-design-vue';
+import {saveAs} from 'file-saver';
+import {blobValidate} from '@/utils/bearjia';
+import {listStation} from '@/api/config/station';
+import {exportMediumTermPowerForecasting, reportMediumTermPowerForecasting} from '@/api/power/mediumTermPowerForecasting';
+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 chartAxis = ref([]);
+const chartSeries = ref([]);
+const tableRows = ref([]);
+
+const query = reactive({
+  stationId: undefined,
+  forecastHours: 240
+});
+
+const forecastHoursOptions = [
+  {label: '1天', value: 24},
+  {label: '3天', value: 72},
+  {label: '5天', value: 120},
+  {label: '7天', value: 168},
+  {label: '10天', value: 240}
+];
+
+const tableColumns = [
+  {title: '预测时间', dataIndex: 'time'},
+  {title: '预测功率', dataIndex: 'power'},
+  {title: '理论功率', dataIndex: 'theoretical_power'},
+  {title: '限制功率', dataIndex: 'curtailment_power'}
+];
+
+const loadStationOptions = async () => {
+  const res = await listStation({pageNum: 1, pageSize: 500});
+  const rows = normalizeRows(res);
+  stationOptions.value = toStationOptions(rows);
+  if (!query.stationId && stationOptions.value.length) {
+    query.stationId = stationOptions.value[0].value;
+  }
+};
+
+const handleQuery = async () => {
+  if (!query.stationId) {
+    message.warning('请先选择场站');
+    return;
+  }
+  loading.value = true;
+  try {
+    const res = await reportMediumTermPowerForecasting({
+      stationId: query.stationId,
+      forecastHours: query.forecastHours
+    });
+    const rows = normalizeRows(res);
+    tableRows.value = rows;
+    const lineModel = buildLineFromRows(rows, [
+      {name: '预测功率', color: '#1677ff', keys: ['power']},
+      {name: '理论功率', color: '#52c41a', keys: ['theoretical_power']},
+      {name: '限制功率', color: '#f5222d', keys: ['curtailment_power']}
+    ]);
+    chartAxis.value = lineModel.xAxis;
+    chartSeries.value = lineModel.series;
+  } finally {
+    loading.value = false;
+  }
+};
+
+const handleExport = async () => {
+  if (!query.stationId) {
+    message.warning('请先选择场站');
+    return;
+  }
+  exporting.value = true;
+  try {
+    const res = await exportMediumTermPowerForecasting({
+      stationId: query.stationId,
+      forecastHours: query.forecastHours
+    });
+    const isFile = await blobValidate(res);
+    if (isFile) {
+      const now = new Date();
+      const pad = (n) => String(n).padStart(2, '0');
+      const filename = `中期预测-${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}.xlsx`;
+      saveAs(new Blob([res]), filename);
+      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>
+.medium-term-power-page { padding: 16px; }
+.content-row { margin-top: 16px; }
+.full-control { width: 100%; }
+</style>