src/views/mes/dv/statistics/oee.vue
@@ -143,7 +143,63 @@
  const minutes = Math.floor(seconds / 60);
  return `${Math.floor(minutes / 60)}小时${minutes % 60}分${seconds % 60}秒`;
}
onMounted(() => { void load(); void loadCollect(); });
/** ===== 停机汇总报表(跨设备,按采集间隔推算) ===== */
interface DowntimeItem {
  machineryId: number;
  machineryCode: string;
  machineryName: string;
  deviceCode: string;
  sampleCount: number;
  downtimeCount: number;
  downtimeMinutes: number;
  intervals: { start: string; end: string; seconds: number }[];
}
interface DowntimeDetail {
  startTime: string;
  endTime: string;
  timeoutMinutes: number;
  totalMinutes: number;
  downtimeMinutes: number;
  downtimeRate: number;
  items: DowntimeItem[];
}
const downtimeRange = ref<[Dayjs, Dayjs]>([dayjs().subtract(7, 'day'), dayjs()]);
const downtimeLoading = ref(false);
const downtimeResult = ref<DowntimeDetail>();
const downtimeError = ref('');
const downtimeColumns = [
  { title: '设备编码', dataIndex: 'machineryCode' },
  { title: '设备名称', dataIndex: 'machineryName' },
  { title: '数采设备', dataIndex: 'deviceCode' },
  { title: '采样数', dataIndex: 'sampleCount' },
  { title: '停机次数', dataIndex: 'downtimeCount' },
  { title: '停机合计(分)', dataIndex: 'downtimeMinutes' },
];
async function loadDowntime() {
  downtimeLoading.value = true;
  downtimeError.value = '';
  try {
    const range = downtimeRange.value;
    downtimeResult.value = await requestClient.get<DowntimeDetail>(
      '/mes/dv/statistics/downtime',
      {
        params: {
          startTime: range[0].startOf('day').format('YYYY-MM-DD HH:mm:ss'),
          endTime: range[1].endOf('day').format('YYYY-MM-DD HH:mm:ss'),
        },
      },
    );
  } catch (e: unknown) {
    downtimeResult.value = undefined;
    // 保留后端给出的可读原因(如超出保留期、区间过长)
    downtimeError.value = (e as Error).message || '停机报表加载失败';
  } finally {
    downtimeLoading.value = false;
  }
}
onMounted(() => { void load(); void loadCollect(); void loadDowntime(); });
</script>
<template>
@@ -166,6 +222,34 @@
      <Card title="设备明细">
        <Table :columns="columns" :data-source="result?.items ?? []" :loading="loading" row-key="machineryId" :scroll="{ x: 1000 }" />
      </Card>
      <Card title="停机汇总报表(跨设备)">
        <template #extra>
          <div class="flex items-center gap-2">
            <DatePicker.RangePicker v-model:value="downtimeRange" />
            <Button type="primary" :loading="downtimeLoading" @click="loadDowntime">查询</Button>
          </div>
        </template>
        <Alert class="mb-4" type="info" show-icon message="按采集间隔超过离线阈值推算停机;通信中断计入停机但不标记为机械故障。单次最多查询 31 天,且不得超出数采历史保留期。" />
        <Alert v-if="downtimeError" class="mb-4" type="warning" show-icon :message="downtimeError" />
        <template v-if="downtimeResult">
          <Row :gutter="16" class="mb-4">
            <Col :span="6"><Statistic title="停机合计(分)" :value="downtimeResult.downtimeMinutes" /></Col>
            <Col :span="6"><Statistic title="停机率" :value="downtimeResult.downtimeRate" suffix="%" /></Col>
            <Col :span="6"><Statistic title="统计区间(分)" :value="downtimeResult.totalMinutes" /></Col>
            <Col :span="6"><Statistic title="设备数" :value="downtimeResult.items?.length ?? 0" /></Col>
          </Row>
          <Table :columns="downtimeColumns" :data-source="downtimeResult.items ?? []" :loading="downtimeLoading" row-key="machineryId" :scroll="{ x: 900 }" size="small" bordered>
            <template #bodyCell="{ column, record }">
              <template v-if="column.dataIndex === 'downtimeMinutes'">
                {{ record.downtimeMinutes }}
              </template>
              <template v-else-if="column.dataIndex === 'sampleCount'">
                {{ record.sampleCount }}
              </template>
            </template>
          </Table>
        </template>
      </Card>
      <Card title="设备数采实时状态">
        <template #extra><Button :loading="collectLoading" @click="loadCollect">刷新</Button></template>
        <Alert v-if="collectError" type="warning" show-icon message="数采状态加载失败,请检查数采服务后重试。" class="mb-4" />