8 天以前 fdd158306bf6dc3584f5110a385323f4a8dfb463
src/views/mes/qc/batchtrace/modules/trace-detail.vue
@@ -1,34 +1,29 @@
<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) {
@@ -36,66 +31,71 @@
      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>