Explorar o código

fix(s8): correct overview module card semantics (S2 metric label, health rate as close rate, real avg hours, tri-color denominator)

YY968XX hai 4 meses
pai
achega
13ee499f8a

+ 19 - 12
Web/src/views/aidop/s8/monitoring/S8MonitoringOverviewPage.vue

@@ -182,7 +182,7 @@ import S8MonitoringModulesGrid from './components/S8MonitoringModulesGrid.vue';
 import S8MonitoringCategoryGrid, { type CategoryGridCardData } from './components/S8MonitoringCategoryGrid.vue';
 import S8MonitoringEditToolbar from './components/S8MonitoringEditToolbar.vue';
 import S8MonitoringStageConfigDrawer from './components/S8MonitoringStageConfigDrawer.vue';
-import { useS8StageConfig, buildModuleTriColorStats } from './useS8StageConfig';
+import { useS8StageConfig, buildModuleTriColorStats, DEFAULT_NORMAL_WORK_ORDER_COUNT } from './useS8StageConfig';
 import { useS8CategoryConfig } from './useS8CategoryConfig';
 import { useS9DeptConfig } from './useS9DeptConfig';
 import { useS8UnsavedLayoutEditGuard } from './useS8UnsavedLayoutEditGuard';
@@ -212,7 +212,7 @@ const numberFormatter = new Intl.NumberFormat('zh-CN');
 
 const STAGE_META_FALLBACK: ReadonlyArray<StageMeta> = [
 	{ code: 'S1', title: '销售评审', icon: Checked, metricLabel: '累计监控单量', valueMode: 'total' },
-	{ code: 'S2', title: '计划排产', icon: TrendCharts, metricLabel: '异常频率 / 百单', valueMode: 'frequency' },
+	{ code: 'S2', title: '计划排产', icon: TrendCharts, metricLabel: '累计异常数', valueMode: 'total' },
 	{ code: 'S3', title: '物料备料', icon: ShoppingBag, metricLabel: '累计异常数量', valueMode: 'total' },
 	{ code: 'S4', title: '生产制造', icon: Tools, metricLabel: '平均处理时长', valueMode: 'avgHours' },
 	{ code: 'S5', title: '质量检测', icon: DataAnalysis, metricLabel: '异常关闭率', valueMode: 'closeRate' },
@@ -502,15 +502,19 @@ function buildStageCard(
 	const total = Math.max(module?.total ?? summaryItem?.total ?? 0, 0);
 	const red = Math.max(module?.red ?? summaryItem?.red ?? 0, 0);
 	const yellow = Math.max(module?.yellow ?? summaryItem?.yellow ?? 0, 0);
-	const green = Math.max(module?.green ?? summaryItem?.green ?? Math.max(total - red - yellow, 0), 0);
 	const timeout = Math.max(summaryItem?.timeout ?? 0, 0);
-	const healthRate = total > 0 ? Math.round((green / total) * 100) : 0;
 	const closeRate = clampPercent(module?.closeRate ?? 0);
 	const frequency = module?.frequency ?? 0;
 	const avgProcessHours = module?.avgProcessHours ?? 0;
 	const tone: 'green' | 'yellow' | 'red' = red > 0 ? 'red' : yellow > 0 ? 'yellow' : 'green';
 	const title = stripModulePrefix(module?.moduleLabel || summaryItem?.moduleLabel || meta.title, meta.code) || meta.title;
 	const anomalyCount = red + yellow;
+	// S8-MODULE-CARD-SEMANTICS-FIX-1A:
+	// - 健康度块改为「闭环率」,数值取 closeRate(与 wide-meter 蓝条一致);
+	// - 三色进度条分母统一为 normal + yellow + red(normal 暂用 DEFAULT_NORMAL_WORK_ORDER_COUNT),
+	//   不再用异常总数 total(不含正常底数会导致绿色永远 0);
+	// - 均时接入真实 API avgProcessHours,不再写死 0h。
+	const normal = DEFAULT_NORMAL_WORK_ORDER_COUNT;
 
 	return {
 		code: meta.code,
@@ -521,15 +525,16 @@ function buildStageCard(
 		// S8-DASHBOARD-MODULE-TRICOLOR-SEMANTICS-1:三色数字(绿正常 / 黄关注 / 红严重)
 		// 全部为自然数;warning + serious = 异常总数;绿色暂用 DEFAULT_NORMAL_WORK_ORDER_COUNT。
 		values: buildModuleTriColorStats({ total: anomalyCount, red, yellow }),
-		healthRate,
+		healthRate: closeRate,
 		statusLabel: timeout > 0 ? `${timeout} 超时` : `${anomalyCount} 异常`,
-		progress: toProgress(green, yellow, red, total),
+		progress: toProgress(normal, yellow, red),
 		tone,
 		wide: meta.wide ?? false,
 		cardSpan: meta.wide ? 2 : 1,
 		minHeight: meta.wide ? 196 : 176,
 		wideRate: closeRate,
-		wideRateLabel: `关闭率 ${formatPercent(closeRate, closeRate > 0 && closeRate < 10 ? 1 : 0)}`,
+		wideRateLabel: `闭环率 ${formatPercent(closeRate, closeRate > 0 && closeRate < 10 ? 1 : 0)} / 均时 ${formatHours(avgProcessHours)}`,
+		avgProcessHours,
 	};
 }
 
@@ -556,14 +561,16 @@ function formatStageValue(
 	}
 }
 
-function toProgress(green: number, yellow: number, red: number, total: number) {
-	if (total <= 0) {
+// S8-MODULE-CARD-SEMANTICS-FIX-1A:分母统一为 green(normal) + yellow + red,不再用异常总数。
+function toProgress(green: number, yellow: number, red: number) {
+	const denominator = green + yellow + red;
+	if (denominator <= 0) {
 		return { green: 0, yellow: 0, red: 0 };
 	}
 	return {
-		green: (green / total) * 100,
-		yellow: (yellow / total) * 100,
-		red: (red / total) * 100,
+		green: (green / denominator) * 100,
+		yellow: (yellow / denominator) * 100,
+		red: (red / denominator) * 100,
 	};
 }
 

+ 2 - 2
Web/src/views/aidop/s8/monitoring/components/S8MonitoringStageCard.vue

@@ -95,7 +95,7 @@ const isWide = computed(() => (props.cardSpan ?? 1) > 1);
 
 			<template #health-rate>
 				<div class="stage-card__stats stage-card__stats--health">
-					<span>健康度 {{ healthRate }}%</span>
+					<span>闭环率 {{ healthRate }}%</span>
 				</div>
 			</template>
 
@@ -115,7 +115,7 @@ const isWide = computed(() => (props.cardSpan ?? 1) > 1);
 
 			<template #wide-meter>
 				<div v-if="isWide" class="stage-card__wide-meter">
-					<div class="stage-card__wide-label">{{ wideRateLabel || `健康度 ${healthRate}%` }}</div>
+					<div class="stage-card__wide-label">{{ wideRateLabel || `闭环率 ${healthRate}%` }}</div>
 					<div class="stage-card__wide-track">
 						<div class="stage-card__wide-fill" :style="{ width: `${clampedWideRate}%` }" />
 					</div>

+ 19 - 6
Web/src/views/aidop/s8/monitoring/useS8StageConfig.ts

@@ -62,6 +62,8 @@ export interface StageCardPreviewData {
 	minHeight?: number;
 	wideRate?: number;
 	wideRateLabel?: string;
+	// S8-MODULE-CARD-SEMANTICS-FIX-1A:真实平均处理时长(小时),由 buildStageCard 透传 API avgProcessHours。
+	avgProcessHours?: number;
 	showMetricLabel?: boolean;
 	showStatusLabel?: boolean;
 	showProgress?: boolean;
@@ -207,7 +209,10 @@ function defaultConfigFromCard(card: StageCardPreviewData): StageCardConfigItem
 			yellow: card.healthRate,
 			red: card.healthRate,
 		},
-		avgProcessHours: card.tone === 'green' || card.tone === 'yellow' || card.tone === 'red' ? '0h' : '0h',
+		// S8-MODULE-CARD-SEMANTICS-FIX-1A:默认从 card 读真实 avgProcessHours,不再写死 0h。
+		avgProcessHours: typeof card.avgProcessHours === 'number' && Number.isFinite(card.avgProcessHours) && card.avgProcessHours > 0
+			? `${card.avgProcessHours.toFixed(1)}h`
+			: '--',
 		closePercent: `${card.wideRate ?? 0}%`,
 		progress: normalizeProgress(card.progress),
 		blockOrder: ['metric-label', 'values', 'health-rate', 'status-label', 'progress', 'wide-meter'],
@@ -296,7 +301,9 @@ export function useS8StageConfig() {
 		const tone = config.tone;
 		const icon = resolveStageIconComponent(config.iconKey);
 		const closePercent = parsePercentText(config.closePercent);
-		const progress = normalizeProgress(config.progress);
+		// S8-MODULE-CARD-SEMANTICS-FIX-1A:实时进度条(绿黄红比例)由 buildStageCard 用 normal+yellow+red 分母重算,
+		// 优先使用 card.progress;localStorage 中残留的旧分母(total)配置不再适用。
+		const progress = normalizeProgress(card.progress ?? config.progress);
 		return {
 			...card,
 			displayCode: config.displayCode,
@@ -307,13 +314,19 @@ export function useS8StageConfig() {
 			// localStorage 中可能残留的旧 stage 配置(10.0 / 0% / 3.2h),保证「关注 + 严重 = 异常总数」。
 			values: card.values ? { ...card.values } : { ...config.values },
 			valueLabels: config.valueLabels ? { ...config.valueLabels } : { green: '正常', yellow: '关注', red: '严重' },
-			metricLabel: config.metricLabel,
-			healthRate: clampPercent(config.healthRates[tone] ?? card.healthRate),
+			// S8-MODULE-CARD-SEMANTICS-FIX-1A:实时 metricLabel(如 S2 已从「异常频率/百单」改为「累计异常数」)
+			// 优先使用 card;localStorage 中残留的旧文案不再覆盖。
+			metricLabel: card.metricLabel ?? config.metricLabel,
+			// S8-MODULE-CARD-SEMANTICS-FIX-1A:实时 healthRate(=closeRate)来自 buildStageCard,
+			// 优先使用,避免 localStorage 残留的旧 health 值(计算公式已变更)覆盖。
+			healthRate: clampPercent(card.healthRate),
 			statusLabel: config.statusLabel,
 			progress,
 			tone,
-			wideRate: closePercent,
-			wideRateLabel: `关闭率 ${closePercent}% / 均时 ${parseHoursText(config.avgProcessHours)}`,
+			// S8-MODULE-CARD-SEMANTICS-FIX-1A:实时数据(card.wideRate / card.wideRateLabel)来自 buildStageCard,
+			// 已包含真实 closeRate 与 avgProcessHours;优先使用,避免 localStorage 中残留的 mock 0h 覆盖。
+			wideRate: card.wideRate ?? closePercent,
+			wideRateLabel: card.wideRateLabel ?? `闭环率 ${closePercent}% / 均时 ${parseHoursText(config.avgProcessHours)}`,
 			showMetricLabel: config.showMetricLabel,
 			showStatusLabel: config.showStatusLabel,
 			showProgress: config.showProgress,