2026-08-03 a27f80b043bb9651fd0032dcd28b2fee6cf820b9
src/views/mes/pro/workorder/modules/ai/predict-material.vue
@@ -1,21 +1,25 @@
<script lang="ts" setup>
import type { MesProAiApi } from '#/api/mes/pro/ai';
import { ref } from 'vue';
import { onUnmounted, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { Button, message, Table, Tag } from 'ant-design-vue';
import { Button, Progress, Table, Tag } from 'ant-design-vue';
import { predictMaterial } from '#/api/mes/pro/ai';
defineOptions({ name: 'MesProAiPredictMaterial' });
const props = defineProps<{ workOrderId: number }>();
const props = defineProps<{ workOrderId?: number; mpsId?: number }>();
const loading = ref(false);
const progress = ref(0);
const result = ref<MesProAiApi.PredictMaterialRespVO>();
const errorMsg = ref('');
let progressTimer: ReturnType<typeof setInterval> | null = null;
const riskLevelColor: Record<number, string> = { 1: 'green', 2: 'orange', 3: 'red' };
const riskLevelText: Record<number, string> = { 1: '低风险', 2: '中风险', 3: '高风险' };
@@ -31,20 +35,58 @@
  { title: '建议措施', dataIndex: 'suggestion' },
];
function startProgress() {
  progress.value = 0;
  progressTimer = setInterval(() => {
    if (progress.value < 30) {
      progress.value += 3;
    } else if (progress.value < 70) {
      progress.value += 2;
    } else if (progress.value < 88) {
      progress.value += 0.5;
    }
  }, 100);
}
function stopProgress() {
  if (progressTimer) {
    clearInterval(progressTimer);
    progressTimer = null;
  }
}
function finishProgress() {
  stopProgress();
  progress.value = 100;
}
onUnmounted(() => stopProgress());
async function handlePredict() {
  loading.value = true;
  result.value = undefined;
  errorMsg.value = '';
  modalApi.open();
  startProgress();
  try {
    result.value = await predictMaterial(props.workOrderId);
    modalApi.open();
    result.value = await predictMaterial(props.workOrderId, props.mpsId);
    finishProgress();
  } catch {
    message.error('AI 分析暂时不可用,请稍后重试');
    errorMsg.value = 'AI 分析暂时不可用,请稍后重试';
    stopProgress();
  } finally {
    loading.value = false;
  }
}
const [ResultModal, modalApi] = useVbenModal({ footer: false });
const [ResultModal, modalApi] = useVbenModal({
  footer: false,
  onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      stopProgress();
    }
  },
});
</script>
<template>
@@ -54,7 +96,36 @@
  </Button>
  <ResultModal title="AI 物料短缺预测" class="w-3/5">
    <template v-if="result">
    <!-- 加载中 -->
    <div v-if="loading" class="flex flex-col items-center py-10">
      <div class="relative mb-6">
        <div class="absolute inset-0 animate-ping rounded-full bg-blue-400 opacity-20" style="width: 80px; height: 80px" />
        <div class="relative flex h-20 w-20 items-center justify-center rounded-full bg-blue-50">
          <IconifyIcon icon="ant-design:alert-outlined" class="text-3xl text-blue-500" />
        </div>
      </div>
      <div class="mb-3 text-base font-medium text-gray-700">
        AI 正在分析物料库存与需求...
      </div>
      <div class="w-2/3">
        <Progress
          :percent="Math.round(progress)"
          :stroke-color="{ '0%': '#1677ff', '100%': '#69b1ff' }"
          :show-info="true"
          status="active"
        />
      </div>
      <div class="mt-2 text-sm text-gray-400">正在查询库存数据、评估物料短缺风险</div>
    </div>
    <!-- 错误 -->
    <div v-else-if="errorMsg" class="flex flex-col items-center py-10">
      <IconifyIcon icon="ant-design:close-circle-filled" class="mb-4 text-5xl text-red-400" />
      <span class="text-base text-gray-500">{{ errorMsg }}</span>
    </div>
    <!-- 结果 -->
    <template v-else-if="result">
      <div class="mb-4 flex items-center gap-2">
        <span class="text-base font-medium">整体风险等级:</span>
        <Tag :color="riskLevelColor[result.riskLevel]">