8 天以前 fdd158306bf6dc3584f5110a385323f4a8dfb463
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
<script lang="ts" setup>
import type { MesTraceBatchApi } from '#/api/mes/trace/batch';
 
import { computed, ref } from 'vue';
 
import { useVbenModal } from '@vben/common-ui';
 
import { Descriptions, Spin, Tabs, Tag, message } from 'ant-design-vue';
 
import { getBatchTraceDetail } from '#/api/mes/trace/batch';
 
import TraceDecision from './TraceDecision.vue';
import TraceGraph from './TraceGraph.vue';
import TraceStageOverview from './TraceStageOverview.vue';
import TraceTimeline from './TraceTimeline.vue';
 
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) {
    if (!isOpen) {
      detailData.value = undefined;
      return;
    }
    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-11/12"
    :show-cancel-button="false"
    :show-confirm-button="false"
  >
    <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="timeline" tab="全链路时间线">
            <TraceTimeline :detail="detailData" />
          </Tabs.TabPane>
          <Tabs.TabPane key="graph" tab="流动图谱">
            <TraceGraph :detail="detailData" />
          </Tabs.TabPane>
          <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>