<script lang="ts" setup>
|
import type { MesHomeApi } from '#/api/mes/home';
|
|
import { computed } from 'vue';
|
|
import { CountTo } from '@vben/common-ui';
|
import { IconifyIcon } from '@vben/icons';
|
|
import { Card, Col, Divider, Row } from 'ant-design-vue';
|
|
defineOptions({ name: 'MesHomeKpiCards' });
|
|
const props = defineProps<{
|
summary: MesHomeApi.Summary;
|
}>();
|
|
const emit = defineEmits<{
|
navigate: [name: string];
|
}>();
|
|
/**
|
* 质量合格率:来料 / 过程 / 出货三个检验环节,各自一张卡片
|
*
|
* 由后端按「合格数量 / 检验数量」计算(不过滤单据状态,无时间限制),
|
* 该环节没有检验数据时后端返回 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',
|
},
|
]);
|
|
/** 合格率展示文案(一位小数,与原「质量合格率」卡片一致),无数据时用「—」而不是 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="4" :xs="24" class="mb-4">
|
<Card
|
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
|
class="flex size-14 flex-shrink-0 items-center justify-center rounded-xl text-white"
|
style="background: linear-gradient(135deg, #409eff, #66b1ff)"
|
>
|
<IconifyIcon class="size-7" icon="lucide:file-text" />
|
</div>
|
<div>
|
<div class="text-muted-foreground mb-1 text-sm">生产订单</div>
|
<div class="flex items-baseline gap-1">
|
<CountTo
|
class="text-2xl font-bold leading-tight text-[#409eff]"
|
:end-val="summary.workOrderActiveCount"
|
:duration="1500"
|
/>
|
<span class="text-muted-foreground text-xs">进行中</span>
|
</div>
|
<div class="text-muted-foreground mt-1 text-xs">
|
<span>待排产 {{ summary.workOrderPrepareCount }}</span>
|
<Divider type="vertical" />
|
<span>已完成 {{ summary.workOrderFinishedCount }}</span>
|
</div>
|
</div>
|
</div>
|
</Card>
|
</Col>
|
<Col :lg="6" :md="12" :sm="12" :xl="4" :xs="24" class="mb-4">
|
<Card
|
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
|
class="flex size-14 flex-shrink-0 items-center justify-center rounded-xl text-white"
|
style="background: linear-gradient(135deg, #67c23a, #85ce61)"
|
>
|
<IconifyIcon class="size-7" icon="lucide:bar-chart-3" />
|
</div>
|
<div>
|
<div class="text-muted-foreground mb-1 text-sm">今日产量</div>
|
<div class="flex items-baseline gap-1">
|
<CountTo
|
class="text-2xl font-bold leading-tight text-[#67c23a]"
|
:end-val="summary.todayOutput"
|
:duration="1500"
|
/>
|
<span class="text-muted-foreground text-xs">件</span>
|
</div>
|
<div class="text-muted-foreground mt-1 text-xs">
|
<span>昨日 {{ summary.yesterdayOutput }} 件</span>
|
</div>
|
</div>
|
</div>
|
</Card>
|
</Col>
|
<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 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, ${item.color}, ${item.colorLight})` }"
|
>
|
<IconifyIcon class="size-7" :icon="item.icon" />
|
</div>
|
<div>
|
<div class="text-muted-foreground mb-1 text-sm">{{ item.label }}</div>
|
<div class="flex items-baseline gap-1">
|
<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">
|
<span>检验 {{ item.checkQuantity }}</span>
|
</div>
|
</div>
|
</div>
|
</Card>
|
</Col>
|
<Col :lg="6" :md="12" :sm="12" :xl="4" :xs="24" class="mb-4">
|
<Card
|
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">
|
<div
|
class="flex size-14 flex-shrink-0 items-center justify-center rounded-xl text-white"
|
style="background: linear-gradient(135deg, #7c3aed, #9461f5)"
|
>
|
<IconifyIcon class="size-7" icon="lucide:cpu" />
|
</div>
|
<div>
|
<div class="text-muted-foreground mb-1 text-sm">设备状态</div>
|
<div class="flex items-baseline gap-1">
|
<CountTo
|
class="text-2xl font-bold leading-tight text-[#7c3aed]"
|
:end-val="summary.machineryProducing"
|
:duration="1500"
|
/>
|
<span class="text-muted-foreground text-xs">
|
/ {{ summary.machineryTotal }} 运行中
|
</span>
|
</div>
|
<div class="text-muted-foreground mt-1 text-xs">
|
<span class="text-red-400">停机 {{ summary.machineryStop }}</span>
|
<Divider type="vertical" />
|
<span class="text-orange-400">
|
维护 {{ summary.machineryMaintenance }}
|
</span>
|
</div>
|
</div>
|
</div>
|
</Card>
|
</Col>
|
</Row>
|
</template>
|