2026-08-03 a27f80b043bb9651fd0032dcd28b2fee6cf820b9
src/views/mes/pro/workorder/modules/ai/predict-risk.vue
@@ -1,40 +1,82 @@
<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 { Alert, Button, message, Tag } from 'ant-design-vue';
import { Alert, Button, Progress, Tag } from 'ant-design-vue';
import { predictRisk } from '#/api/mes/pro/ai';
defineOptions({ name: 'MesProAiPredictRisk' });
const props = defineProps<{ workOrderId: number }>();
const props = defineProps<{ workOrderId?: number; mpsId?: number }>();
const loading = ref(false);
const progress = ref(0);
const result = ref<MesProAiApi.PredictRiskRespVO>();
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: '高风险' };
const severityColor: Record<string, string> = { '高': 'red', '中': 'orange', '低': 'green' };
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 predictRisk(props.workOrderId);
    modalApi.open();
    result.value = await predictRisk(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>
@@ -44,7 +86,36 @@
  </Button>
  <ResultModal title="AI 生产风险预测" class="w-1/2">
    <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-orange-400 opacity-20" style="width: 80px; height: 80px" />
        <div class="relative flex h-20 w-20 items-center justify-center rounded-full bg-orange-50">
          <IconifyIcon icon="ant-design:warning-outlined" class="text-3xl text-orange-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%': '#fa8c16', '100%': '#ffd591' }"
          :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.overallRiskLevel]">