From 27c8277d717b34b55f3ea967027b53b067f77df3 Mon Sep 17 00:00:00 2001
From: xiaoyi <908147524@qq.com>
Date: 星期四, 20 八月 2026 16:58:49 +0800
Subject: [PATCH] feat 功能变更

---
 src/views/bi/decision/forecast-analysis/index.vue |  227 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 227 insertions(+), 0 deletions(-)

diff --git a/src/views/bi/decision/forecast-analysis/index.vue b/src/views/bi/decision/forecast-analysis/index.vue
new file mode 100644
index 0000000..404c042
--- /dev/null
+++ b/src/views/bi/decision/forecast-analysis/index.vue
@@ -0,0 +1,227 @@
+<script lang="ts" setup>
+import { onMounted, ref } from 'vue';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+
+import { DatePicker, message, Select, Spin } from 'ant-design-vue';
+import dayjs from 'dayjs';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  generateForecastWorkOrder,
+  getForecastList,
+} from '#/api/bi/decision/forecast';
+
+defineOptions({ name: 'DecisionForecastAnalysis' });
+
+const loading = ref(false);
+const forecastData = ref<any[]>([]);
+const forecastCode = ref('load_forecast_daily');
+
+const FORECAST_OPTIONS = [
+  { label: '鏃ヨ礋鑽烽娴�', value: 'load_forecast_daily' },
+  { label: '鍛ㄨ礋鑽烽娴�', value: 'load_forecast_weekly' },
+  { label: '鏈堣礋鑽烽娴�', value: 'load_forecast_monthly' },
+];
+
+const WORK_ORDER_TYPE_OPTIONS = [
+  { label: '鐢靛姏杩愮淮', value: 4 },
+  { label: '鍙戠數', value: 5 },
+  { label: '妫�淇�', value: 6 },
+];
+
+async function loadData() {
+  loading.value = true;
+  try {
+    forecastData.value = await getForecastList(forecastCode.value);
+  } catch {
+    forecastData.value = [];
+  } finally {
+    loading.value = false;
+  }
+}
+
+onMounted(loadData);
+
+/** ========== 鐢熸垚宸ュ崟 ========== */
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    labelWidth: 100,
+  },
+  wrapperClass: 'grid-cols-1',
+  layout: 'vertical',
+  schema: [
+    {
+      fieldName: 'forecastId',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'workOrderName',
+      label: '宸ュ崟鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ伐鍗曞悕绉�',
+      },
+    },
+    {
+      fieldName: 'workOrderType',
+      label: '宸ュ崟绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        options: WORK_ORDER_TYPE_OPTIONS,
+        placeholder: '璇烽�夋嫨宸ュ崟绫诲瀷',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'requestDate',
+      label: '闇�姹傛棩鏈�',
+      component: 'DatePicker',
+      componentProps: {
+        placeholder: '璇烽�夋嫨闇�姹傛棩鏈�',
+        valueFormat: 'YYYY-MM-DD HH:mm:ss',
+      },
+    },
+    {
+      fieldName: 'quantity',
+      label: '鐢熶骇鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        precision: 2,
+        placeholder: '璇疯緭鍏ョ敓浜ф暟閲�',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ],
+  showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      const values = await formApi.getValues<{
+        forecastId: number;
+        workOrderName?: string;
+        workOrderType: number;
+        requestDate?: string;
+        quantity: number;
+        remark?: string;
+      }>();
+      await generateForecastWorkOrder({
+        forecastId: Number(values.forecastId),
+        workOrderName: values.workOrderName,
+        workOrderType: Number(values.workOrderType),
+        requestDate: values.requestDate,
+        quantity: Number(values.quantity ?? 1),
+        remark: values.remark,
+      });
+      await modalApi.close();
+      message.success('宸ュ崟鐢熸垚鎴愬姛');
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      return;
+    }
+    const data = modalApi.getData<{ item: any }>();
+    const item = data?.item;
+    await formApi.resetForm();
+    await formApi.setValues({
+      forecastId: item?.id,
+      workOrderType: 4,
+      requestDate: item?.pointTime ?? dayjs().format('YYYY-MM-DD HH:mm:ss'),
+      quantity: item?.forecastValue ?? 1,
+      remark: `鐢辫礋鑽烽娴嬶紙${item?.forecastName ?? forecastCode.value}锛夎嚜鍔ㄧ敓鎴恅,
+    });
+  },
+});
+
+function handleGenerate(item: any) {
+  modalApi.setData({ item }).open();
+}
+</script>
+
+<template>
+  <Page :auto-content-height="true">
+    <Modal title="璐熻嵎棰勬祴鐢熸垚鐢熶骇宸ュ崟" class="w-1/2">
+      <Form class="mx-3" />
+    </Modal>
+    <div class="p-4">
+      <div class="mb-4 flex items-center gap-4">
+        <h2 class="text-lg font-bold">璐熻嵎棰勬祴</h2>
+        <Select
+          v-model:value="forecastCode"
+          :options="FORECAST_OPTIONS"
+          style="width: 180px"
+          @change="loadData"
+        />
+      </div>
+
+      <Spin :spinning="loading">
+        <div v-if="forecastData.length === 0" class="py-12 text-center text-gray-400">
+          鏆傛棤棰勬祴鏁版嵁锛岃鍏堥厤缃� KPI 鎸囨爣鍜岄璀﹁鍒欏悗鏌ョ湅
+        </div>
+        <div v-else class="grid grid-cols-1 gap-4 lg:grid-cols-2">
+          <div
+            v-for="item in forecastData"
+            :key="item.id"
+            class="rounded-lg border p-4"
+          >
+            <div class="mb-2 flex items-center justify-between">
+              <span class="font-medium">{{ item.forecastName }}</span>
+              <span class="text-xs text-gray-400">{{ item.pointTime }}</span>
+            </div>
+            <div class="mb-1 text-2xl font-bold">
+              {{ item.forecastValue ?? '-' }}
+              <span class="ml-1 text-sm font-normal text-gray-400">(棰勬祴)</span>
+            </div>
+            <div v-if="item.actualValue != null" class="text-sm text-gray-500">
+              瀹為檯鍊�: {{ item.actualValue }}
+              <span v-if="item.lowerBound != null" class="ml-2">
+                缃俊鍖洪棿: [{{ item.lowerBound }} ~ {{ item.upperBound }}]
+              </span>
+            </div>
+            <div v-if="item.dimension" class="mt-1 text-xs text-gray-400">
+              {{ item.dimension }}: {{ item.dimensionValue }}
+            </div>
+            <div class="mt-3">
+              <a-button
+                type="primary"
+                ghost
+                size="small"
+                @click="handleGenerate(item)"
+              >
+                鐢熸垚宸ュ崟
+              </a-button>
+            </div>
+          </div>
+        </div>
+      </Spin>
+    </div>
+  </Page>
+</template>

--
Gitblit v1.9.3