| | |
| | | navigate: [name: string]; |
| | | }>(); |
| | | |
| | | const qualityRate = computed(() => { |
| | | const total = |
| | | props.summary.todayQualifiedQuantity + |
| | | props.summary.todayUnqualifiedQuantity; |
| | | return total === 0 ? 0 : (props.summary.todayQualifiedQuantity / total) * 100; |
| | | }); |
| | | /** |
| | | * 质量合格率:来料 / 过程 / 出货三个检验环节,各自一张卡片 |
| | | * |
| | | * 由后端按「合格数量 / 检验数量」计算(不过滤单据状态,无时间限制), |
| | | * 该环节没有检验数据时后端返回 null。 |
| | | */ |
| | | const qualityRates = computed(() => [ |
| | | { |
| | | key: 'quality-iqc', |
| | | label: '来料合格率', |
| | | rate: props.summary.iqcQualifiedRate, |
| | | checkQuantity: props.summary.iqcCheckQuantity, |
| | | routeName: 'MesQcIqc', |
| | | icon: 'lucide:package-check', |
| | | iconClass: 'bg-gradient-to-br from-orange-500 to-amber-400 shadow-lg shadow-orange-500/25', |
| | | textClass: 'text-orange-600 dark:text-orange-400', |
| | | washClass: 'bg-gradient-to-br from-orange-500/5 to-amber-500/5', |
| | | glowClass: 'bg-orange-400/10', |
| | | }, |
| | | { |
| | | key: 'quality-ipqc', |
| | | label: '过程合格率', |
| | | rate: props.summary.ipqcQualifiedRate, |
| | | checkQuantity: props.summary.ipqcCheckQuantity, |
| | | routeName: 'MesQcIpqc', |
| | | icon: 'lucide:workflow', |
| | | iconClass: 'bg-gradient-to-br from-cyan-500 to-teal-400 shadow-lg shadow-cyan-500/25', |
| | | textClass: 'text-cyan-600 dark:text-cyan-400', |
| | | washClass: 'bg-gradient-to-br from-cyan-500/5 to-teal-500/5', |
| | | glowClass: 'bg-cyan-400/10', |
| | | }, |
| | | { |
| | | key: 'quality-oqc', |
| | | label: '出货合格率', |
| | | rate: props.summary.oqcQualifiedRate, |
| | | checkQuantity: props.summary.oqcCheckQuantity, |
| | | routeName: 'MesQcOqc', |
| | | icon: 'lucide:truck', |
| | | iconClass: 'bg-gradient-to-br from-pink-500 to-rose-400 shadow-lg shadow-pink-500/25', |
| | | textClass: 'text-pink-600 dark:text-pink-400', |
| | | washClass: 'bg-gradient-to-br from-pink-500/5 to-rose-500/5', |
| | | glowClass: 'bg-pink-400/10', |
| | | }, |
| | | ]); |
| | | |
| | | /** 合格率展示文案(一位小数,与原「质量合格率」卡片一致),无数据时用「—」而不是 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; |
| | | } |
| | | |
| | | const equipmentRate = computed(() => { |
| | | if (props.summary.machineryTotal === 0) return 0; |
| | |
| | | <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"> |
| | | <div |
| | | class="glass-tilt group overflow-hidden rounded-2xl border-0" |
| | | class="glass-tilt group h-full overflow-hidden rounded-2xl border-0" |
| | | :class="{ 'cursor-pointer': canNavigate('MesProWorkOrder') }" |
| | | :style="tiltStyle('order')" |
| | | @mousemove="(e: MouseEvent) => onMouseMove(e, 'order')" |
| | |
| | | </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"> |
| | | <div |
| | | class="glass-tilt group overflow-hidden rounded-2xl border-0" |
| | | class="glass-tilt group h-full overflow-hidden rounded-2xl border-0" |
| | | :class="{ 'cursor-pointer': canNavigate('MesProFeedback') }" |
| | | :style="tiltStyle('output')" |
| | | @mousemove="(e: MouseEvent) => onMouseMove(e, 'output')" |
| | |
| | | </div> |
| | | </Col> |
| | | |
| | | <!-- 质量合格率 --> |
| | | <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4"> |
| | | <!-- 质量合格率:来料 / 过程 / 出货,各一张卡片 --> |
| | | <Col |
| | | v-for="item in qualityRates" |
| | | :key="item.key" |
| | | :lg="6" |
| | | :md="12" |
| | | :sm="12" |
| | | :xl="4" |
| | | :xs="24" |
| | | class="mb-4" |
| | | > |
| | | <div |
| | | class="glass-tilt group overflow-hidden rounded-2xl border-0" |
| | | :class="{ 'cursor-pointer': canNavigate('MesQcRecord') }" |
| | | :style="tiltStyle('quality')" |
| | | @mousemove="(e: MouseEvent) => onMouseMove(e, 'quality')" |
| | | @mouseleave="onMouseLeave('quality')" |
| | | @click="canNavigate('MesQcRecord') && emit('navigate', 'MesQcRecord')" |
| | | class="glass-tilt group h-full overflow-hidden rounded-2xl border-0" |
| | | :class="{ 'cursor-pointer': canNavigate(item.routeName) }" |
| | | :style="tiltStyle(item.key)" |
| | | @mousemove="(e: MouseEvent) => onMouseMove(e, item.key)" |
| | | @mouseleave="onMouseLeave(item.key)" |
| | | @click="canNavigate(item.routeName) && emit('navigate', item.routeName)" |
| | | > |
| | | <div class="relative z-10 flex items-center gap-4 p-5"> |
| | | <div class="flex size-12 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-orange-500 to-amber-400 shadow-lg shadow-orange-500/25 transition-transform duration-300 group-hover:scale-110"> |
| | | <IconifyIcon class="size-6 text-white" icon="lucide:shield-check" /> |
| | | <div |
| | | class="flex size-12 flex-shrink-0 items-center justify-center rounded-xl transition-transform duration-300 group-hover:scale-110" |
| | | :class="item.iconClass" |
| | | > |
| | | <IconifyIcon class="size-6 text-white" :icon="item.icon" /> |
| | | </div> |
| | | <div class="min-w-0 flex-1"> |
| | | <div class="mb-1 text-xs font-medium text-gray-500 dark:text-gray-400">质量合格率</div> |
| | | <div class="mb-1 text-xs font-medium text-gray-500 dark:text-gray-400">{{ item.label }}</div> |
| | | <div class="flex items-baseline gap-1"> |
| | | <CountTo |
| | | class="text-2xl font-bold leading-tight" |
| | | :class="qualityRate >= 95 ? 'text-emerald-600 dark:text-emerald-400' : qualityRate >= 80 ? 'text-orange-600 dark:text-orange-400' : 'text-red-600 dark:text-red-400'" |
| | | :decimals="1" :end-val="qualityRate" :duration="1200" |
| | | /> |
| | | <span class="text-xs text-gray-400">%</span> |
| | | <span class="text-2xl font-bold leading-tight" :class="item.textClass"> |
| | | {{ formatRate(item.rate) }} |
| | | </span> |
| | | <span v-if="hasRate(item.rate)" class="text-xs text-gray-400">%</span> |
| | | </div> |
| | | <div class="mt-1 text-xs text-gray-400"> |
| | | 合格 {{ summary.todayQualifiedQuantity }} | 不良 {{ summary.todayUnqualifiedQuantity }} |
| | | <div class="mt-1 flex items-center gap-1 text-xs text-gray-400"> |
| | | <span>检验 {{ item.checkQuantity }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="absolute inset-0 -z-0 bg-gradient-to-br from-orange-500/5 to-amber-500/5 opacity-0 transition-opacity duration-300 group-hover:opacity-100" /> |
| | | <div class="absolute -right-6 -top-6 size-24 rounded-full bg-orange-400/10 blur-xl transition-all duration-300 group-hover:scale-150" /> |
| | | <div |
| | | class="absolute inset-0 -z-0 opacity-0 transition-opacity duration-300 group-hover:opacity-100" |
| | | :class="item.washClass" |
| | | /> |
| | | <div |
| | | class="absolute -right-6 -top-6 size-24 rounded-full blur-xl transition-all duration-300 group-hover:scale-150" |
| | | :class="item.glowClass" |
| | | /> |
| | | </div> |
| | | </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"> |
| | | <div |
| | | class="glass-tilt group overflow-hidden rounded-2xl border-0" |
| | | class="glass-tilt group h-full overflow-hidden rounded-2xl border-0" |
| | | :class="{ 'cursor-pointer': canNavigate('MesDvMachinery') }" |
| | | :style="tiltStyle('equip')" |
| | | @mousemove="(e: MouseEvent) => onMouseMove(e, 'equip')" |