| | |
| | | <script lang="ts" setup> |
| | | import type { MesWmBatchApi } from '#/api/mes/wm/batch'; |
| | | import type { MesTraceBatchApi } from '#/api/mes/trace/batch'; |
| | | |
| | | import { ref } from 'vue'; |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { Tabs, Tooltip } from 'ant-design-vue'; |
| | | import { Descriptions, Spin, Tabs, Tag, message } from 'ant-design-vue'; |
| | | |
| | | import { useDescription } from '#/components/description'; |
| | | import { getBatchTraceDetail } from '#/api/mes/trace/batch'; |
| | | |
| | | import { useDetailSchema } from '../data'; |
| | | import ProcessTraceList from './process-trace-list.vue'; |
| | | import PurchaseSourceList from './purchase-source-list.vue'; |
| | | import SalesTargetList from './sales-target-list.vue'; |
| | | import TraceList from './trace-list.vue'; |
| | | import TraceDecision from './TraceDecision.vue'; |
| | | import TraceGraph from './TraceGraph.vue'; |
| | | import TraceStageOverview from './TraceStageOverview.vue'; |
| | | import TraceTimeline from './TraceTimeline.vue'; |
| | | |
| | | const detailData = ref<MesWmBatchApi.Batch>(); // 当前批次详情 |
| | | const subTabsName = ref< |
| | | | 'backward' |
| | | | 'forward' |
| | | | 'purchase' |
| | | | 'sales' |
| | | | 'process' |
| | | >('forward'); // 当前激活的追溯页签 |
| | | |
| | | const [Descriptions] = useDescription({ |
| | | bordered: true, |
| | | column: 3, |
| | | schema: useDetailSchema(), |
| | | const detailData = ref<MesTraceBatchApi.Detail>(); |
| | | const loading = ref(false); |
| | | const activeTab = ref('overview'); |
| | | const formatDate = (value?: string) => value ? new Date(value).toLocaleString() : '未记录'; |
| | | const riskLevel = computed(() => { |
| | | if (!detailData.value) return 'NORMAL'; |
| | | return detailData.value.decision?.riskLevel || (detailData.value.quality?.result === 2 ? 'HIGH' : 'NORMAL'); |
| | | }); |
| | | const riskLabel = computed(() => riskLevel.value === 'HIGH' ? '高风险' : riskLevel.value === 'NORMAL' ? '正常' : '需关注'); |
| | | const riskColor = computed(() => riskLevel.value === 'HIGH' ? 'red' : riskLevel.value === 'NORMAL' ? 'green' : 'orange'); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | onOpenChange(isOpen) { |
| | |
| | | detailData.value = undefined; |
| | | return; |
| | | } |
| | | detailData.value = modalApi.getData<MesWmBatchApi.Batch>(); |
| | | subTabsName.value = 'forward'; |
| | | const row = modalApi.getData<{ code?: string }>(); |
| | | if (row?.code) { |
| | | void loadDetail(row.code); |
| | | } |
| | | activeTab.value = 'overview'; |
| | | }, |
| | | }); |
| | | |
| | | async function loadDetail(code: string) { |
| | | loading.value = true; |
| | | try { |
| | | detailData.value = await getBatchTraceDetail(code); |
| | | } catch { |
| | | message.error('批次追溯详情加载失败'); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal |
| | | title="批次追溯" |
| | | class="w-4/5" |
| | | title="全链路批次追溯" |
| | | class="w-11/12" |
| | | :show-cancel-button="false" |
| | | :show-confirm-button="false" |
| | | > |
| | | <Descriptions class="mx-4 mt-2" :data="detailData" /> |
| | | <Tabs |
| | | v-if="detailData?.code" |
| | | v-model:active-key="subTabsName" |
| | | class="mx-4 mt-4" |
| | | type="card" |
| | | > |
| | | <Tabs.TabPane key="forward"> |
| | | <template #tab> |
| | | <Tooltip title="顺着生产流往下游查:该批次被哪些工单消耗后,产出成了哪些成品批次"> |
| | | <span>向前追溯</span> |
| | | </Tooltip> |
| | | </template> |
| | | <TraceList :batch-code="detailData.code" direction="forward" /> |
| | | <Spin :spinning="loading"> |
| | | <div v-if="detailData" class="px-2"> |
| | | <div class="mb-4 grid grid-cols-2 gap-3 lg:grid-cols-5"> |
| | | <div class="rounded border bg-blue-50 px-4 py-3"><div class="text-muted-foreground text-xs">袋码数量</div><div class="mt-1 text-lg font-semibold text-blue-700">{{ detailData.bagTotal ?? 0 }}</div></div> |
| | | <div class="rounded border bg-violet-50 px-4 py-3"><div class="text-muted-foreground text-xs">托盘数量</div><div class="mt-1 text-lg font-semibold text-violet-700">{{ detailData.palletCount ?? 0 }}</div></div> |
| | | <div class="rounded border bg-green-50 px-4 py-3"><div class="text-muted-foreground text-xs">入库数量</div><div class="mt-1 text-lg font-semibold text-green-700">{{ detailData.decision?.inboundQuantity ?? detailData.inbound?.quantity ?? 0 }}</div></div> |
| | | <div class="rounded border bg-orange-50 px-4 py-3"><div class="text-muted-foreground text-xs">销售去向</div><div class="mt-1 text-lg font-semibold text-orange-700">{{ detailData.decision?.salesTargetCount ?? detailData.salesTargets?.length ?? 0 }}</div></div> |
| | | <div class="col-span-2 rounded border px-4 py-3 lg:col-span-1" :class="riskLevel === 'HIGH' ? 'border-red-200 bg-red-50' : riskLevel === 'NORMAL' ? 'border-green-200 bg-green-50' : 'border-orange-200 bg-orange-50'"><div class="text-muted-foreground text-xs">综合风险</div><div class="mt-1"><Tag :color="riskColor">{{ riskLabel }}</Tag></div></div> |
| | | </div> |
| | | <Descriptions bordered :column="{ xs: 1, sm: 2, md: 3 }" size="small"> |
| | | <Descriptions.Item label="追溯入口">{{ detailData.requestCode || '未记录' }}</Descriptions.Item> |
| | | <Descriptions.Item label="入口类型"><Tag color="blue">{{ detailData.sourceType || 'BATCH' }}</Tag></Descriptions.Item> |
| | | <Descriptions.Item label="批次状态">{{ detailData.batchStatusName || '未知' }}</Descriptions.Item> |
| | | <Descriptions.Item label="品种">{{ detailData.itemName || detailData.itemCode || '未记录' }}</Descriptions.Item> |
| | | <Descriptions.Item label="批次号">{{ detailData.batchCode || '未记录' }}</Descriptions.Item> |
| | | <Descriptions.Item label="责任人">{{ detailData.ownerUserName || '未记录' }}</Descriptions.Item> |
| | | <Descriptions.Item label="生产线">{{ detailData.lineName || detailData.lineCode || '未记录' }}</Descriptions.Item> |
| | | <Descriptions.Item label="计划数量">{{ detailData.planQuantity ?? 0 }}</Descriptions.Item> |
| | | <Descriptions.Item label="生产日期">{{ formatDate(detailData.produceDate) }}</Descriptions.Item> |
| | | <Descriptions.Item label="入库时间">{{ formatDate(detailData.inboundTime || detailData.inbound?.eventTime) }}</Descriptions.Item> |
| | | </Descriptions> |
| | | |
| | | <Tabs v-model:active-key="activeTab" class="mt-4" type="card"> |
| | | <Tabs.TabPane key="overview" tab="阶段总览"> |
| | | <TraceStageOverview :detail="detailData" /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane key="backward"> |
| | | <template #tab> |
| | | <Tooltip title="逆着生产流往上游查:生产该批次时,消耗了哪些原料批次"> |
| | | <span>向后追溯</span> |
| | | </Tooltip> |
| | | </template> |
| | | <TraceList :batch-code="detailData.code" direction="backward" /> |
| | | <Tabs.TabPane key="timeline" tab="全链路时间线"> |
| | | <TraceTimeline :detail="detailData" /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane key="purchase"> |
| | | <template #tab> |
| | | <Tooltip title="原料侧来源:该批次对应的采购入库单与 IQC 来料检验记录"> |
| | | <span>采购来源</span> |
| | | </Tooltip> |
| | | </template> |
| | | <PurchaseSourceList :batch-code="detailData.code" /> |
| | | <Tabs.TabPane key="graph" tab="流动图谱"> |
| | | <TraceGraph :detail="detailData" /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane key="sales"> |
| | | <template #tab> |
| | | <Tooltip title="成品侧去向:该批次对应的销售出库单与 OQC 出厂检验记录"> |
| | | <span>销售去向</span> |
| | | </Tooltip> |
| | | </template> |
| | | <SalesTargetList :batch-code="detailData.code" /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane key="process"> |
| | | <template #tab> |
| | | <Tooltip title="各工序的投入/产出事件:该批次在哪些工序被投入或产出,及数量和质量状态"> |
| | | <span>工序流转</span> |
| | | </Tooltip> |
| | | </template> |
| | | <ProcessTraceList :batch-code="detailData.code" /> |
| | | <Tabs.TabPane key="decision" tab="决策分析"> |
| | | <TraceDecision :detail="detailData" /> |
| | | </Tabs.TabPane> |
| | | </Tabs> |
| | | </div> |
| | | <div v-else-if="!loading" class="py-12 text-center text-muted-foreground">暂无追溯数据</div> |
| | | </Spin> |
| | | </Modal> |
| | | </template> |