gaoluyang
2 天以前 0c9aa3e1d5cfbf2245f9e7462e1d27cc8c7827d1
src/views/mes/home/modules/kpi-cards.vue
@@ -18,31 +18,58 @@
  navigate: [name: string];
}>();
/** 是否有质量数据(合格品 + 不良品 > 0) */
const hasQualityData = computed(
  () =>
    props.summary.todayQualifiedQuantity +
      props.summary.todayUnqualifiedQuantity >
    0,
);
/**
 * 质量合格率:来料 / 过程 / 出货三个检验环节,各自一张卡片
 *
 * 由后端按「合格数量 / 检验数量」计算(不过滤单据状态,无时间限制),
 * 该环节没有检验数据时后端返回 null。
 */
const qualityRates = computed(() => [
  {
    label: '来料合格率',
    rate: props.summary.iqcQualifiedRate,
    checkQuantity: props.summary.iqcCheckQuantity,
    routeName: 'MesQcIqc',
    icon: 'lucide:package-check',
    color: '#e6a23c',
    colorLight: '#ebb563',
  },
  {
    label: '过程合格率',
    rate: props.summary.ipqcQualifiedRate,
    checkQuantity: props.summary.ipqcCheckQuantity,
    routeName: 'MesQcIpqc',
    icon: 'lucide:workflow',
    color: '#13c2c2',
    colorLight: '#5cdbd3',
  },
  {
    label: '出货合格率',
    rate: props.summary.oqcQualifiedRate,
    checkQuantity: props.summary.oqcCheckQuantity,
    routeName: 'MesQcOqc',
    icon: 'lucide:truck',
    color: '#eb2f96',
    colorLight: '#f759ab',
  },
]);
/** 质量合格率 = 合格品 / (合格品 + 不良品) * 100,无数据时为 0 */
const qualityRate = computed(() => {
  const total =
    props.summary.todayQualifiedQuantity +
    props.summary.todayUnqualifiedQuantity;
  if (total === 0) {
    return 0;
  }
  return (props.summary.todayQualifiedQuantity / total) * 100;
});
/** 合格率展示文案(一位小数,与原「质量合格率」卡片一致),无数据时用「—」而不是 0% */
function formatRate(rate: null | number | undefined) {
  return rate === null || rate === undefined ? '—' : rate.toFixed(1);
}
/** 无检验数据时不渲染百分号,避免出现「—%」 */
function hasRate(rate: null | number | undefined) {
  return rate !== null && rate !== undefined;
}
</script>
<template>
  <Row :gutter="16">
    <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
    <Col :lg="6" :md="12" :sm="12" :xl="4" :xs="24" class="mb-4">
      <Card
        class="kpi-card cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
        class="kpi-card h-full cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
        @click="emit('navigate', 'MesProWorkOrder')"
      >
        <div class="flex items-center gap-4">
@@ -71,9 +98,9 @@
        </div>
      </Card>
    </Col>
    <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
    <Col :lg="6" :md="12" :sm="12" :xl="4" :xs="24" class="mb-4">
      <Card
        class="kpi-card cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
        class="kpi-card h-full cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
        @click="emit('navigate', 'MesProFeedback')"
      >
        <div class="flex items-center gap-4">
@@ -100,44 +127,53 @@
        </div>
      </Card>
    </Col>
    <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
    <Col
      v-for="item in qualityRates"
      :key="item.label"
      :lg="6"
      :md="12"
      :sm="12"
      :xl="4"
      :xs="24"
      class="mb-4"
    >
      <Card
        class="kpi-card cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
        @click="emit('navigate', 'MesProFeedback')"
        class="kpi-card h-full cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
        @click="emit('navigate', item.routeName)"
      >
        <div class="flex items-center gap-4">
          <div
            class="flex size-14 flex-shrink-0 items-center justify-center rounded-xl text-white"
            style="background: linear-gradient(135deg, #e6a23c, #ebb563)"
            :style="{ background: `linear-gradient(135deg, ${item.color}, ${item.colorLight})` }"
          >
            <IconifyIcon class="size-7" icon="lucide:circle-check" />
            <IconifyIcon class="size-7" :icon="item.icon" />
          </div>
          <div>
            <div class="text-muted-foreground mb-1 text-sm">质量合格率</div>
            <div class="text-muted-foreground mb-1 text-sm">{{ item.label }}</div>
            <div class="flex items-baseline gap-1">
              <CountTo
                class="text-2xl font-bold leading-tight text-[#e6a23c]"
                :decimals="1"
                :end-val="qualityRate"
                :duration="1500"
              />
              <span class="text-muted-foreground text-xs">%</span>
              <span
                class="text-2xl font-bold leading-tight"
                :style="{ color: item.color }"
              >
                {{ formatRate(item.rate) }}
              </span>
              <span
                v-if="hasRate(item.rate)"
                class="text-muted-foreground text-xs"
              >
                %
              </span>
            </div>
            <div class="text-muted-foreground mt-1 text-xs">
              <template v-if="hasQualityData">
                <span>合格 {{ summary.todayQualifiedQuantity }}</span>
                <Divider type="vertical" />
                <span>不良 {{ summary.todayUnqualifiedQuantity }}</span>
              </template>
              <span v-else>暂无数据</span>
              <span>检验 {{ item.checkQuantity }}</span>
            </div>
          </div>
        </div>
      </Card>
    </Col>
    <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
    <Col :lg="6" :md="12" :sm="12" :xl="4" :xs="24" class="mb-4">
      <Card
        class="kpi-card cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
        class="kpi-card h-full cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
        @click="emit('navigate', 'MesDvMachinery')"
      >
        <div class="flex items-center gap-4">