<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>
|