| | |
| | | <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: '高风险' }; |
| | |
| | | { 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> |
| | |
| | | </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]"> |