2026-08-03 a27f80b043bb9651fd0032dcd28b2fee6cf820b9
feat(ai): 集成AI智能分析功能到MPS表单和仪表板

- 在MPS表单中添加AI预测组件(物料、时长、风险、交付)
- 修改AI API接口支持MPS ID参数
- 添加AI智能分析概览模块到仪表板
- 为AI预测组件实现加载进度动画和错误处理
- 重构AI预测组件props支持可选参数传递
已修改7个文件
已添加1个文件
504 ■■■■■ 文件已修改
src/api/mes/pro/ai/index.ts 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/analytics/index.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/analytics/modules/ai-insights.vue 119 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/mps/modules/form.vue 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/workorder/modules/ai/predict-delivery.vue 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/workorder/modules/ai/predict-duration.vue 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/workorder/modules/ai/predict-material.vue 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/workorder/modules/ai/predict-risk.vue 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/pro/ai/index.ts
@@ -65,33 +65,33 @@
const BASE = '/mes/pro/ai';
/** AI ç‰©æ–™çŸ­ç¼ºé¢„测 */
export function predictMaterial(workOrderId: number) {
export function predictMaterial(workOrderId?: number, mpsId?: number) {
  return requestClient.post<MesProAiApi.PredictMaterialRespVO>(
    `${BASE}/predict-material`,
    { workOrderId },
    { workOrderId, mpsId },
  );
}
/** AI ç”Ÿäº§æ—¶é•¿é¢„测 */
export function predictDuration(workOrderId: number) {
export function predictDuration(workOrderId?: number, mpsId?: number) {
  return requestClient.post<MesProAiApi.PredictDurationRespVO>(
    `${BASE}/predict-duration`,
    { workOrderId },
    { workOrderId, mpsId },
  );
}
/** AI ç”Ÿäº§é£Žé™©é¢„测 */
export function predictRisk(workOrderId: number) {
export function predictRisk(workOrderId?: number, mpsId?: number) {
  return requestClient.post<MesProAiApi.PredictRiskRespVO>(
    `${BASE}/predict-risk`,
    { workOrderId },
    { workOrderId, mpsId },
  );
}
/** AI æŒ‰æ—¶äº¤ä»˜é¢„测 */
export function predictDelivery(workOrderId: number) {
export function predictDelivery(workOrderId?: number, mpsId?: number) {
  return requestClient.post<MesProAiApi.PredictDeliveryRespVO>(
    `${BASE}/predict-delivery`,
    { workOrderId },
    { workOrderId, mpsId },
  );
}
src/views/dashboard/analytics/index.vue
@@ -9,6 +9,7 @@
import { getHomeSummary } from '#/api/mes/home';
import { defaultSummary } from './data';
import AiInsights from './modules/ai-insights.vue';
import AlertPanel from './modules/alert-panel.vue';
import BizCards from './modules/biz-cards.vue';
import KpiCards from './modules/kpi-cards.vue';
@@ -76,6 +77,12 @@
    <!-- ERP/WMS/BPM ç»è¥ KPI -->
    <BizCards @navigate="handleNavigate" />
    <!-- AI æ™ºèƒ½åˆ†æž -->
    <AiInsights
      :active-order-count="summary.workOrderActiveCount"
      @navigate="handleNavigate"
    />
    <!-- ç”Ÿäº§è¶‹åŠ¿ + é¢„警面板 -->
    <Row :gutter="16" class="mb-4">
      <Col :lg="14" :md="24" :sm="24" :xl="14" :xs="24" class="mb-4">
src/views/dashboard/analytics/modules/ai-insights.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,119 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { CountTo } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { Col, Row } from 'ant-design-vue';
defineOptions({ name: 'DashboardAiInsights' });
const props = defineProps<{
  activeOrderCount: number;
}>();
const emit = defineEmits<{
  navigate: [name: string];
}>();
interface AiCapability {
  key: string;
  title: string;
  desc: string;
  icon: string;
  gradient: string;
  route: string;
}
const capabilities: AiCapability[] = [
  {
    key: 'material',
    title: 'AI ç‰©æ–™é¢„测',
    desc: '预测物料需求与短缺风险',
    icon: 'lucide:package-open',
    gradient: 'from-violet-500 to-purple-500',
    route: 'MesProWorkOrder',
  },
  {
    key: 'duration',
    title: 'AI æ—¶é•¿é¢„测',
    desc: '预测各工序生产耗时',
    icon: 'lucide:clock',
    gradient: 'from-sky-500 to-blue-500',
    route: 'MesProWorkOrder',
  },
  {
    key: 'risk',
    title: 'AI é£Žé™©é¢„测',
    desc: '识别潜在生产风险并给出建议',
    icon: 'lucide:shield-alert',
    gradient: 'from-orange-500 to-red-500',
    route: 'MesProWorkOrder',
  },
  {
    key: 'delivery',
    title: 'AI äº¤ä»˜è¯„ä¼°',
    desc: '评估订单交付准时率与延期因素',
    icon: 'lucide:truck',
    gradient: 'from-emerald-500 to-teal-500',
    route: 'MesProWorkOrder',
  },
];
</script>
<template>
  <div class="glass-card mb-4 rounded-2xl p-5">
    <div class="mb-4 flex items-center justify-between">
      <h3 class="text-base font-semibold text-gray-800 dark:text-gray-100">
        AI æ™ºèƒ½åˆ†æž
      </h3>
      <span class="rounded-full bg-violet-50 px-3 py-0.5 text-xs font-medium text-violet-600 dark:bg-violet-500/15 dark:text-violet-400">
        {{ activeOrderCount }} ä¸ªå·¥å•可分析
      </span>
    </div>
    <Row :gutter="16">
      <Col v-for="cap in capabilities" :key="cap.key" :lg="6" :md="12" :sm="12" :xl="6" :xs="12" class="mb-3">
        <div
          class="group flex cursor-pointer items-center gap-3 rounded-xl p-3 transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg"
          @click="emit('navigate', cap.route)"
        >
          <div
            class="flex size-10 flex-shrink-0 items-center justify-center rounded-lg bg-gradient-to-br shadow-md transition-transform duration-300 group-hover:scale-110"
            :class="cap.gradient"
          >
            <IconifyIcon :icon="cap.icon" class="size-5 text-white" />
          </div>
          <div class="min-w-0 flex-1">
            <div class="text-sm font-medium text-gray-800 dark:text-gray-100">
              {{ cap.title }}
            </div>
            <div class="text-xs text-gray-400">{{ cap.desc }}</div>
          </div>
          <IconifyIcon icon="lucide:chevron-right" class="size-4 text-gray-300 transition-transform duration-200 group-hover:translate-x-0.5" />
        </div>
      </Col>
    </Row>
  </div>
</template>
<style scoped>
.glass-card {
  background: linear-gradient(135deg, rgba(255,255,255,0.85), rgba(255,255,255,0.55));
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid rgba(255,255,255,0.6);
  box-shadow:
    0 4px 24px rgba(0,0,0,0.04),
    0 1px 4px rgba(0,0,0,0.02),
    inset 0 1px 0 rgba(255,255,255,0.8);
}
.dark .glass-card {
  background: linear-gradient(135deg, rgba(30,30,55,0.75), rgba(20,20,40,0.5));
  border: 1px solid rgba(255,255,255,0.08);
  box-shadow:
    0 4px 24px rgba(0,0,0,0.3),
    0 1px 4px rgba(0,0,0,0.2),
    inset 0 1px 0 rgba(255,255,255,0.04);
}
</style>
src/views/mes/pro/mps/modules/form.vue
@@ -13,6 +13,10 @@
import { createMps, getMps, updateMps } from '#/api/mes/pro/mps';
import { $t } from '#/locales';
import PredictDelivery from '../../workorder/modules/ai/predict-delivery.vue';
import PredictDuration from '../../workorder/modules/ai/predict-duration.vue';
import PredictMaterial from '../../workorder/modules/ai/predict-material.vue';
import PredictRisk from '../../workorder/modules/ai/predict-risk.vue';
import { MpsStatusEnum, useFormSchema } from '../data';
const emit = defineEmits(['success']);
@@ -20,6 +24,8 @@
const formType = ref<FormType>('detail');
const isEditable = computed(() => ['create', 'edit'].includes(formType.value));
const canUseAi = computed(() => !!formData.value?.id);
const getTitle = computed(() => {
  switch (formType.value) {
@@ -171,5 +177,13 @@
        </Descriptions.Item>
      </Descriptions>
    </div>
    <template #prepend-footer>
      <div v-if="canUseAi" class="flex items-center gap-2">
        <PredictMaterial :mps-id="formData!.id" :work-order-id="formData!.workOrderId" />
        <PredictDuration :mps-id="formData!.id" :work-order-id="formData!.workOrderId" />
        <PredictRisk :mps-id="formData!.id" :work-order-id="formData!.workOrderId" />
        <PredictDelivery :mps-id="formData!.id" :work-order-id="formData!.workOrderId" />
      </div>
    </template>
  </Modal>
</template>
src/views/mes/pro/workorder/modules/ai/predict-delivery.vue
@@ -1,36 +1,78 @@
<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, Progress, Tag } from 'ant-design-vue';
import { Alert, Button, Progress, Tag } from 'ant-design-vue';
import { predictDelivery } from '#/api/mes/pro/ai';
defineOptions({ name: 'MesProAiPredictDelivery' });
const props = defineProps<{ workOrderId: number }>();
const props = defineProps<{ workOrderId?: number; mpsId?: number }>();
const loading = ref(false);
const progress = ref(0);
const result = ref<MesProAiApi.PredictDeliveryRespVO>();
const errorMsg = ref('');
let progressTimer: ReturnType<typeof setInterval> | null = null;
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 predictDelivery(props.workOrderId);
    modalApi.open();
    result.value = await predictDelivery(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>
@@ -40,7 +82,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-green-400 opacity-20" style="width: 80px; height: 80px" />
        <div class="relative flex h-20 w-20 items-center justify-center rounded-full bg-green-50">
          <IconifyIcon icon="ant-design:check-circle-outlined" class="text-3xl text-green-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%': '#52c41a', '100%': '#b7eb8f' }"
          :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-4">
        <div class="flex items-center gap-2">
          <span class="text-base font-medium">能否按时交付:</span>
src/views/mes/pro/workorder/modules/ai/predict-duration.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 { Alert, Button, message, Table, Tag } from 'ant-design-vue';
import { Alert, Button, Progress, Table, Tag } from 'ant-design-vue';
import { predictDuration } from '#/api/mes/pro/ai';
defineOptions({ name: 'MesProAiPredictDuration' });
const props = defineProps<{ workOrderId: number }>();
const props = defineProps<{ workOrderId?: number; mpsId?: number }>();
const loading = ref(false);
const progress = ref(0);
const result = ref<MesProAiApi.PredictDurationRespVO>();
const errorMsg = ref('');
let progressTimer: ReturnType<typeof setInterval> | null = null;
const columns = [
  { title: '工序名称', dataIndex: 'processName', width: 150 },
@@ -26,20 +30,58 @@
  { title: '预测生产时间(时)', dataIndex: 'predictedProductionTime', width: 140 },
];
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 predictDuration(props.workOrderId);
    modalApi.open();
    result.value = await predictDuration(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>
@@ -49,7 +91,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:clock-circle-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-4">
        <div class="flex items-center gap-2">
          <span class="text-base font-medium">预测总时长:</span>
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]">
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]">