| | |
| | | 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"> |
| | |
| | | </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"> |
| | |
| | | </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"> |