<script lang="ts" setup>
|
import type { ManagementCockpitApi } from '#/api/mes/management-cockpit';
|
|
import { CountTo } from '@vben/common-ui';
|
import { IconifyIcon } from '@vben/icons';
|
|
import { Card, Col, Row } from 'ant-design-vue';
|
|
defineOptions({ name: 'ManagementCockpitKpiCards' });
|
|
const props = defineProps<{
|
summary: ManagementCockpitApi.Summary;
|
}>();
|
|
/** 保留两位小数 */
|
function fmt(value?: number): number {
|
return Number((value ?? 0).toFixed(2));
|
}
|
|
/** 五维度 KPI 卡配置 */
|
const cards = [
|
{
|
dimension: '生产',
|
gradient: 'linear-gradient(135deg, #0ea5e9, #38bdf8)',
|
icon: 'lucide:factory',
|
mainLabel: '生产批次',
|
mainValue: () => props.summary.batchTotal,
|
items: [
|
{ label: '袋码', value: () => props.summary.bagCodeTotal },
|
{ label: '托盘码', value: () => props.summary.palletTotal },
|
{ label: '今日入库', value: () => props.summary.todayInboundBagCount },
|
],
|
},
|
{
|
dimension: '质检',
|
gradient: 'linear-gradient(135deg, #67c23a, #95d475)',
|
icon: 'lucide:clipboard-check',
|
mainLabel: '合格率(%)',
|
mainValue: () => fmt(props.summary.qualityPassRate),
|
items: [
|
{ label: '合格', value: () => props.summary.qualityPass },
|
{ label: '不合格', value: () => props.summary.qualityFail },
|
{ label: '处理中', value: () => props.summary.qualityPending },
|
],
|
},
|
{
|
dimension: '库存',
|
gradient: 'linear-gradient(135deg, #f59e0b, #fbbf24)',
|
icon: 'lucide:warehouse',
|
mainLabel: '库存总量',
|
mainValue: () => fmt(props.summary.stockTotal),
|
items: [
|
{ label: '暂存库', value: () => fmt(props.summary.stockTemporary) },
|
{ label: '合格库', value: () => fmt(props.summary.stockQualified) },
|
{ label: '低库存预警', value: () => props.summary.lowStockCount },
|
],
|
},
|
{
|
dimension: '出库',
|
gradient: 'linear-gradient(135deg, #ec4899, #f472b6)',
|
icon: 'lucide:package-open',
|
mainLabel: '出库总量',
|
mainValue: () => fmt(props.summary.outboundQuantityTotal),
|
items: [
|
{ label: '出库单数', value: () => props.summary.outboundOrderTotal },
|
{ label: '今日出库', value: () => fmt(props.summary.todayOutboundQuantity) },
|
],
|
},
|
{
|
dimension: '追溯',
|
gradient: 'linear-gradient(135deg, #14b8a6, #2dd4bf)',
|
icon: 'lucide:scan-search',
|
mainLabel: '扫码总数',
|
mainValue: () => props.summary.scanTotal,
|
items: [
|
{ label: '今日扫码', value: () => props.summary.todayScanCount },
|
{ label: '窜货预警', value: () => props.summary.crossRegionWarningTotal },
|
{ label: '待处理', value: () => props.summary.crossRegionPendingCount },
|
],
|
},
|
];
|
</script>
|
|
<template>
|
<Row :gutter="16">
|
<Col
|
v-for="card in cards"
|
:key="card.dimension"
|
:lg="8"
|
:md="12"
|
:sm="12"
|
:xl="8"
|
:xs="24"
|
class="mb-4"
|
>
|
<Card class="kpi-card h-full">
|
<div class="flex items-start justify-between">
|
<div class="flex items-center gap-3">
|
<div
|
class="flex size-12 flex-shrink-0 items-center justify-center rounded-xl text-white"
|
:style="{ background: card.gradient }"
|
>
|
<IconifyIcon class="size-6" :icon="card.icon" />
|
</div>
|
<div>
|
<div class="text-muted-foreground text-sm">{{ card.dimension }}</div>
|
<CountTo
|
class="text-xl font-bold leading-tight"
|
:end-val="card.mainValue()"
|
:duration="1200"
|
/>
|
<div class="text-muted-foreground text-xs">{{ card.mainLabel }}</div>
|
</div>
|
</div>
|
</div>
|
<div class="mt-3 flex flex-wrap gap-x-4 gap-y-1 border-t border-dashed pt-3">
|
<span v-for="item in card.items" :key="item.label" class="text-xs text-gray-500">
|
{{ item.label }}:
|
<b class="text-sm text-gray-800 dark:text-gray-200">{{ item.value() }}</b>
|
</span>
|
</div>
|
</Card>
|
</Col>
|
</Row>
|
</template>
|