Explorar o código

fix(s8): align overview lifecycle metrics display

YY968XX hai 2 meses
pai
achega
18ea8597d1

+ 1 - 1
Web/package.json

@@ -1,7 +1,7 @@
 {
 	"name": "admin.net",
 	"type": "module",
-	"version": "2.4.145",
+	"version": "2.4.146",
 	"packageManager": "pnpm@10.32.1",
 	"lastBuildTime": "2026.03.15",
 	"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",

+ 5 - 0
Web/src/views/aidop/s8/api/s8MonitoringApi.ts

@@ -14,6 +14,9 @@ export interface S8ModuleSummaryItem {
 	yellow: number;
 	green: number;
 	timeout: number;
+	// S8-OVERVIEW-STAGE-CARD-LIFECYCLE-COPY-AND-METRIC-FIX-1:severity × timeout 双桶,与 timeout 同 SLA 口径。
+	seriousTimeout?: number;
+	followTimeout?: number;
 }
 
 export interface S8MonitoringSummary {
@@ -22,6 +25,8 @@ export interface S8MonitoringSummary {
 	yellow: number;
 	green: number;
 	timeout: number;
+	seriousTimeout?: number;
+	followTimeout?: number;
 	byModule: S8ModuleSummaryItem[];
 }
 

+ 15 - 9
Web/src/views/aidop/s8/monitoring/S8MonitoringDeliveryPage.vue

@@ -206,8 +206,8 @@ const pageConfig = useS8PageConfigDriver({ pageCode: 'DELIVERY', pageCodePrefix:
 // fallback 硬编码(不删除,作 page-config 失败时回退)
 // S8-MONITORING-DEMO-SEMANTICS-CLOSURE-1:主指标统一改「当前待处理异常」,与三色数字语义一致。
 const STAGE_META_FALLBACK = [
-	{ code: 'S1', title: '销售评审', icon: Checked as Component, metricLabel: '当前待处理异常' },
-	{ code: 'S7', title: '订单交付', icon: Van as Component,     metricLabel: '当前待处理异常' },
+	{ code: 'S1', title: '销售评审', icon: Checked as Component, metricLabel: '异常发生情况' },
+	{ code: 'S7', title: '订单交付', icon: Van as Component,     metricLabel: '异常发生情况' },
 ] as const;
 
 const ANOMALY_TYPES_FALLBACK: ReadonlyArray<{ key: string; label: string }> = [
@@ -254,9 +254,9 @@ async function loadPageConfig() {
 }
 
 const moduleMap = computed(() => new Map(moduleData.list.map((m) => [m.moduleCode, m])));
-// S8-STAGE-CARD-SEMANTIC-LAYOUT-EXEC-1:summary.byModule 提供每模块 timeout(S8ModuleOrderSummary 不含),
-// 拼到 stage card 的 timeoutCount 上
-const moduleTimeoutMap = computed(() => new Map((summary.byModule ?? []).map((m) => [m.moduleCode, m.timeout ?? 0])));
+// S8-OVERVIEW-STAGE-CARD-LIFECYCLE-COPY-AND-METRIC-FIX-1:summary.byModule 提供每模块 timeout / red / yellow / seriousTimeout / followTimeout。
+// 严重/关注口径与 overview 对齐:取 period 发生总数(含 CLOSED),不再使用 order-grid module 的 unclosed 数
+const summaryByModuleMap = computed(() => new Map((summary.byModule ?? []).map((m) => [m.moduleCode, m])));
 
 // S8-MONITORING-PAGES-STAGE-CARD-SEMANTICS-UNIFY-1:与 overview 口径一致:
 // - healthRate = closeRate;total=0 时空态显示 `闭环率 --`
@@ -265,10 +265,13 @@ const moduleTimeoutMap = computed(() => new Map((summary.byModule ?? []).map((m)
 const stageCards = computed(() =>
 	effectiveStageMeta.value.map((meta) => {
 		const m = moduleMap.value.get(meta.code);
-		const total  = Math.max(m?.total ?? 0, 0);
-		const red    = Math.max(m?.red ?? 0, 0);
-		const yellow = Math.max(m?.yellow ?? 0, 0);
-		const timeout = Math.max(moduleTimeoutMap.value.get(meta.code) ?? 0, 0);
+		const s = summaryByModuleMap.value.get(meta.code);
+		const total  = Math.max(s?.total ?? m?.total ?? 0, 0);
+		const red    = Math.max(s?.red    ?? m?.red    ?? 0, 0);
+		const yellow = Math.max(s?.yellow ?? m?.yellow ?? 0, 0);
+		const timeout = Math.max(s?.timeout ?? 0, 0);
+		const seriousTimeout = Math.max(s?.seriousTimeout ?? 0, 0);
+		const followTimeout  = Math.max(s?.followTimeout  ?? 0, 0);
 		const closeRate = Math.max(0, Math.min(100, Math.round(m?.closeRate ?? 0)));
 		const avgProcessHours = m?.avgProcessHours ?? 0;
 		const tone: 'green' | 'yellow' | 'red' = red > 0 ? 'red' : yellow > 0 ? 'yellow' : 'green';
@@ -303,6 +306,9 @@ const stageCards = computed(() =>
 			wideRate: closeRate,
 			wideRateLabel: `闭环率 ${closeRateText} / 均时 ${avgHoursText}`,
 			avgProcessHours,
+			// S8-OVERVIEW-STAGE-CARD-LIFECYCLE-COPY-AND-METRIC-FIX-1:延误数(severity × timeout 双桶)。
+			seriousDelayCount: seriousTimeout,
+			attentionDelayCount: followTimeout,
 		};
 		return applyConfig(raw);
 	}),

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

@@ -124,13 +124,13 @@ const numberFormatter = new Intl.NumberFormat('zh-CN');
 // 取 orderGrid.modules[].total 直显,避免同组卡片语义不一致影响演示叙事。
 // S8-MONITORING-DEMO-SEMANTICS-CLOSURE-1:模块卡主标题改「当前待处理异常」,与三色数字(正常执行订单/关注异常/严重异常)口径一致。
 const STAGE_META_FALLBACK: ReadonlyArray<StageMeta> = [
-	{ code: 'S1', title: '销售评审', icon: Checked, metricLabel: '当前待处理异常', valueMode: 'total' },
-	{ code: 'S2', title: '计划排产', icon: TrendCharts, metricLabel: '当前待处理异常', valueMode: 'total' },
-	{ code: 'S3', title: '物料备料', icon: ShoppingBag, metricLabel: '当前待处理异常', valueMode: 'total' },
-	{ code: 'S4', title: '生产制造', icon: Tools, metricLabel: '当前待处理异常', valueMode: 'total' },
-	{ code: 'S5', title: '质量检测', icon: DataAnalysis, metricLabel: '当前待处理异常', valueMode: 'total' },
-	{ code: 'S6', title: '总装入库', icon: Box, metricLabel: '当前待处理异常', valueMode: 'total' },
-	{ code: 'S7', title: '订单交付', icon: Van, metricLabel: '当前待处理异常', valueMode: 'total' },
+	{ code: 'S1', title: '销售评审', icon: Checked, metricLabel: '异常发生情况', valueMode: 'total' },
+	{ code: 'S2', title: '计划排产', icon: TrendCharts, metricLabel: '异常发生情况', valueMode: 'total' },
+	{ code: 'S3', title: '物料备料', icon: ShoppingBag, metricLabel: '异常发生情况', valueMode: 'total' },
+	{ code: 'S4', title: '生产制造', icon: Tools, metricLabel: '异常发生情况', valueMode: 'total' },
+	{ code: 'S5', title: '质量检测', icon: DataAnalysis, metricLabel: '异常发生情况', valueMode: 'total' },
+	{ code: 'S6', title: '总装入库', icon: Box, metricLabel: '异常发生情况', valueMode: 'total' },
+	{ code: 'S7', title: '订单交付', icon: Van, metricLabel: '异常发生情况', valueMode: 'total' },
 ];
 
 const CATEGORY_DEFS_FALLBACK: ReadonlyArray<{ key: string; title: string; icon: Component }> = [
@@ -463,10 +463,16 @@ function buildStageCard(
 	module: S8ModuleOrderSummary | undefined,
 	summaryItem: S8ModuleSummaryItem | undefined,
 ) {
-	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);
+	// S8-OVERVIEW-STAGE-CARD-LIFECYCLE-COPY-AND-METRIC-FIX-1:
+	// 严重/关注 = period 发生总数(summary,含 CLOSED),与 closeRate 分母统一;
+	// order-grid 仅提供 closeRate / avgProcessHours,不再用其 red/yellow 当严重/关注。
+	// 旧 fallback (module?.red ?? summaryItem?.red) 仅在 summary 缺字段时启用,避免空态。
+	const total = Math.max(summaryItem?.total ?? module?.total ?? 0, 0);
+	const red = Math.max(summaryItem?.red ?? module?.red ?? 0, 0);
+	const yellow = Math.max(summaryItem?.yellow ?? module?.yellow ?? 0, 0);
 	const timeout = Math.max(summaryItem?.timeout ?? 0, 0);
+	const seriousTimeout = Math.max(summaryItem?.seriousTimeout ?? 0, 0);
+	const followTimeout = Math.max(summaryItem?.followTimeout ?? 0, 0);
 	const closeRate = clampPercent(module?.closeRate ?? 0);
 	const frequency = module?.frequency ?? 0;
 	const avgProcessHours = module?.avgProcessHours ?? 0;
@@ -510,6 +516,9 @@ function buildStageCard(
 		wideRate: closeRate,
 		wideRateLabel: `闭环率 ${closeRateText} / 均时 ${avgHoursText}`,
 		avgProcessHours,
+		// S8-OVERVIEW-STAGE-CARD-LIFECYCLE-COPY-AND-METRIC-FIX-1:延误数(severity × timeout 双桶)。
+		seriousDelayCount: seriousTimeout,
+		attentionDelayCount: followTimeout,
 	};
 }
 

+ 15 - 8
Web/src/views/aidop/s8/monitoring/S8MonitoringProductionPage.vue

@@ -204,8 +204,8 @@ const pageConfig = useS8PageConfigDriver({ pageCode: 'PRODUCTION', pageCodePrefi
 
 // S8-MONITORING-DEMO-SEMANTICS-CLOSURE-1:主指标统一改「当前待处理异常」,与三色数字语义一致。
 const STAGE_META_FALLBACK = [
-	{ code: 'S2', title: '计划排产', icon: TrendCharts as Component, metricLabel: '当前待处理异常' },
-	{ code: 'S6', title: '总装入库', icon: Box as Component,         metricLabel: '当前待处理异常' },
+	{ code: 'S2', title: '计划排产', icon: TrendCharts as Component, metricLabel: '异常发生情况' },
+	{ code: 'S6', title: '总装入库', icon: Box as Component,         metricLabel: '异常发生情况' },
 ] as const;
 
 const ANOMALY_TYPES_FALLBACK: ReadonlyArray<{ key: string; label: string }> = [
@@ -251,17 +251,21 @@ async function loadPageConfig() {
 }
 
 const moduleMap = computed(() => new Map(moduleData.list.map((m) => [m.moduleCode, m])));
-// S8-STAGE-CARD-SEMANTIC-LAYOUT-EXEC-1:summary.byModule 提供每模块 timeout,注入到 stage card timeoutCount。
-const moduleTimeoutMap = computed(() => new Map((summary.byModule ?? []).map((m) => [m.moduleCode, m.timeout ?? 0])));
+// S8-OVERVIEW-STAGE-CARD-LIFECYCLE-COPY-AND-METRIC-FIX-1:summary.byModule 提供每模块 timeout / red / yellow / seriousTimeout / followTimeout。
+// 严重/关注口径与 overview 对齐:取 period 发生总数(含 CLOSED),不再使用 order-grid module 的 unclosed 数。
+const summaryByModuleMap = computed(() => new Map((summary.byModule ?? []).map((m) => [m.moduleCode, m])));
 
 // S8-MONITORING-PAGES-STAGE-CARD-SEMANTICS-UNIFY-1:与 overview 口径一致。S2 主数字改为 total(去除 frequency)。
 const stageCards = computed(() =>
 	effectiveStageMeta.value.map((meta) => {
 		const m = moduleMap.value.get(meta.code);
-		const total  = Math.max(m?.total ?? 0, 0);
-		const red    = Math.max(m?.red ?? 0, 0);
-		const yellow = Math.max(m?.yellow ?? 0, 0);
-		const timeout = Math.max(moduleTimeoutMap.value.get(meta.code) ?? 0, 0);
+		const s = summaryByModuleMap.value.get(meta.code);
+		const total  = Math.max(s?.total ?? m?.total ?? 0, 0);
+		const red    = Math.max(s?.red    ?? m?.red    ?? 0, 0);
+		const yellow = Math.max(s?.yellow ?? m?.yellow ?? 0, 0);
+		const timeout = Math.max(s?.timeout ?? 0, 0);
+		const seriousTimeout = Math.max(s?.seriousTimeout ?? 0, 0);
+		const followTimeout  = Math.max(s?.followTimeout  ?? 0, 0);
 		const closeRate = Math.max(0, Math.min(100, Math.round(m?.closeRate ?? 0)));
 		const avgProcessHours = m?.avgProcessHours ?? 0;
 		const tone: 'green' | 'yellow' | 'red' = red > 0 ? 'red' : yellow > 0 ? 'yellow' : 'green';
@@ -296,6 +300,9 @@ const stageCards = computed(() =>
 			wideRate: closeRate,
 			wideRateLabel: `闭环率 ${closeRateText} / 均时 ${avgHoursText}`,
 			avgProcessHours,
+			// S8-OVERVIEW-STAGE-CARD-LIFECYCLE-COPY-AND-METRIC-FIX-1:延误数(severity × timeout 双桶)。
+			seriousDelayCount: seriousTimeout,
+			attentionDelayCount: followTimeout,
 		};
 		return applyConfig(raw);
 	}),

+ 16 - 9
Web/src/views/aidop/s8/monitoring/S8MonitoringSupplyPage.vue

@@ -210,9 +210,9 @@ const pageConfig = useS8PageConfigDriver({ pageCode: 'SUPPLY', pageCodePrefix: '
 
 // S8-MONITORING-DEMO-SEMANTICS-CLOSURE-1:主指标统一改「当前待处理异常」,与三色数字语义一致。
 const STAGE_META_FALLBACK = [
-	{ code: 'S3', title: '物料备料', icon: ShoppingBag as Component,  metricLabel: '当前待处理异常' },
-	{ code: 'S4', title: '生产制造', icon: Tools as Component,        metricLabel: '当前待处理异常' },
-	{ code: 'S5', title: '质量检测', icon: DataAnalysis as Component, metricLabel: '当前待处理异常' },
+	{ code: 'S3', title: '物料备料', icon: ShoppingBag as Component,  metricLabel: '异常发生情况' },
+	{ code: 'S4', title: '生产制造', icon: Tools as Component,        metricLabel: '异常发生情况' },
+	{ code: 'S5', title: '质量检测', icon: DataAnalysis as Component, metricLabel: '异常发生情况' },
 ] as const;
 
 const ANOMALY_TYPES_FALLBACK: ReadonlyArray<{ key: string; label: string }> = [
@@ -262,18 +262,22 @@ async function loadPageConfig() {
 }
 
 const moduleMap = computed(() => new Map(moduleData.list.map((m) => [m.moduleCode, m])));
-// S8-STAGE-CARD-SEMANTIC-LAYOUT-EXEC-1:summary.byModule 提供每模块 timeout,注入到 stage card timeoutCount。
-const moduleTimeoutMap = computed(() => new Map((summary.byModule ?? []).map((m) => [m.moduleCode, m.timeout ?? 0])));
+// S8-OVERVIEW-STAGE-CARD-LIFECYCLE-COPY-AND-METRIC-FIX-1:summary.byModule 提供每模块 timeout / red / yellow / seriousTimeout / followTimeout。
+// 严重/关注口径与 overview 对齐:取 period 发生总数(含 CLOSED),不再使用 order-grid module 的 unclosed 数。
+const summaryByModuleMap = computed(() => new Map((summary.byModule ?? []).map((m) => [m.moduleCode, m])));
 
 // S8-MONITORING-PAGES-STAGE-CARD-SEMANTICS-UNIFY-1:与 overview 口径一致。
 // S4/S5 主数字一律改为 total(不再用 avgHours / closeRate%),均时/闭环率统一在底部 wideRateLabel 展示。
 const stageCards = computed(() =>
 	effectiveStageMeta.value.map((meta) => {
 		const m = moduleMap.value.get(meta.code);
-		const total  = Math.max(m?.total ?? 0, 0);
-		const red    = Math.max(m?.red ?? 0, 0);
-		const yellow = Math.max(m?.yellow ?? 0, 0);
-		const timeout = Math.max(moduleTimeoutMap.value.get(meta.code) ?? 0, 0);
+		const s = summaryByModuleMap.value.get(meta.code);
+		const total  = Math.max(s?.total ?? m?.total ?? 0, 0);
+		const red    = Math.max(s?.red    ?? m?.red    ?? 0, 0);
+		const yellow = Math.max(s?.yellow ?? m?.yellow ?? 0, 0);
+		const timeout = Math.max(s?.timeout ?? 0, 0);
+		const seriousTimeout = Math.max(s?.seriousTimeout ?? 0, 0);
+		const followTimeout  = Math.max(s?.followTimeout  ?? 0, 0);
 		const closeRate = Math.max(0, Math.min(100, Math.round(m?.closeRate ?? 0)));
 		const avgProcessHours = m?.avgProcessHours ?? 0;
 		const tone: 'green' | 'yellow' | 'red' = red > 0 ? 'red' : yellow > 0 ? 'yellow' : 'green';
@@ -308,6 +312,9 @@ const stageCards = computed(() =>
 			wideRate: closeRate,
 			wideRateLabel: `闭环率 ${closeRateText} / 均时 ${avgHoursText}`,
 			avgProcessHours,
+			// S8-OVERVIEW-STAGE-CARD-LIFECYCLE-COPY-AND-METRIC-FIX-1:延误数(severity × timeout 双桶)。
+			seriousDelayCount: seriousTimeout,
+			attentionDelayCount: followTimeout,
 		};
 		return applyConfig(raw);
 	}),

+ 113 - 97
Web/src/views/aidop/s8/monitoring/components/S8DeptSeverityTabsCard.vue

@@ -8,19 +8,19 @@
 		</div>
 		<el-tabs v-model="activeTab" class="s8-dept-severity__tabs">
 			<el-tab-pane label="严重" name="serious">
-				<DeptTable
+				<DeptList
 					:loading="loading"
 					:rows="seriousRows"
-					count-label="严重异常"
+					count-label="严重异常"
 					empty-text="暂无严重异常"
 					:count-key="'seriousCount'"
 				/>
 			</el-tab-pane>
 			<el-tab-pane label="关注" name="follow">
-				<DeptTable
+				<DeptList
 					:loading="loading"
 					:rows="followRows"
-					count-label="关注异常"
+					count-label="关注异常"
 					empty-text="暂无关注异常"
 					:count-key="'followCount'"
 				/>
@@ -42,7 +42,7 @@ const props = defineProps<{
 type Tab = 'serious' | 'follow';
 const activeTab = shallowRef<Tab>('serious');
 
-// TASK-013-P2-AVG-CLOSE-FOLLOW-1:严重 / 关注两个视角,分别按 seriousCount / followCount > 0 过滤并降序。
+// S8-DEPT-CARD-LIST-LAYOUT-FIX-1:严重 / 关注两个视角,分别按 seriousCount / followCount > 0 过滤并降序。
 const seriousRows = computed(() =>
 	[...(props.items ?? [])]
 		.filter((x) => x && x.seriousCount > 0)
@@ -59,8 +59,8 @@ const minHeightStyle = computed<CSSProperties>(() => {
 	return { minHeight: `${props.minHeight}px` };
 });
 
-// TASK-013-P2-AVG-CLOSE-FOLLOW-1:avgProcessHours 无样本(null/undefined/<=0)→ '--';
-// closeRate 缺字段 → '--',0 显示 '0%'(数据真实闭环为 0,不当作空态)。
+// S8-DEPT-CARD-LIST-LAYOUT-FIX-1:avgProcessHours 无样本(null/undefined/<=0)→ '--';
+// closeRate 缺字段 → '--',0 显示 '0%'(数据真实闭环为 0,不当作空态)。
 function formatHoursOrDash(v: number | null | undefined): string {
 	if (v === null || v === undefined || !Number.isFinite(v) || v <= 0) return '--';
 	return v >= 24 ? `${Math.round(v)}h+` : `${v.toFixed(1)}h`;
@@ -71,7 +71,11 @@ function formatPercentOrDash(v: number | null | undefined): string {
 	return `${clamped.toFixed(clamped % 1 === 0 ? 0 : 1)}%`;
 }
 
-const DeptTable = (rawProps: {
+// S8-DEPT-CARD-LIST-LAYOUT-FIX-1:窄屏列表方案 —— 每部门一行 card,2 行结构:
+//   第一行:部门名 + 异常数 (右对齐徽标)
+//   第二行:均时 X · 关闭率 Y (副信息行)
+// 与之前 4 列 grid 相比,列宽不再受卡片总宽限制,文字不会挤压。
+const DeptList = (rawProps: {
 	loading?: boolean;
 	rows: S8DeptBacklogItem[];
 	countLabel: string;
@@ -80,48 +84,49 @@ const DeptTable = (rawProps: {
 }) => {
 	if (rawProps.loading) return h('div', { class: 's8-dept-severity__empty' }, '加载中…');
 	if (!rawProps.rows.length) return h('div', { class: 's8-dept-severity__empty' }, rawProps.emptyText);
-	return h('div', { class: 's8-dept-severity__table', role: 'table' }, [
-		h('div', { class: 's8-dept-severity__thead', role: 'row' }, [
-			h('span', { class: 's8-dept-severity__th s8-dept-severity__th--name', role: 'columnheader' }, '部门'),
-			h('span', { class: 's8-dept-severity__th s8-dept-severity__th--count', role: 'columnheader' }, rawProps.countLabel),
-			h('span', { class: 's8-dept-severity__th s8-dept-severity__th--metric', role: 'columnheader' }, '平均处理时间'),
-			h('span', { class: 's8-dept-severity__th s8-dept-severity__th--metric', role: 'columnheader' }, '关闭率'),
-		]),
-		h(
-			'div',
-			{ class: 's8-dept-severity__tbody' },
-			rawProps.rows.map((row) => {
-				const count = row[rawProps.countKey];
-				const hot = count > 0;
-				return h(
-					'div',
-					{
-						key: row.deptId,
-						class: 's8-dept-severity__row',
-						role: 'row',
-					},
-					[
-						h('span', { class: 's8-dept-severity__cell s8-dept-severity__cell--name', title: row.deptName, role: 'cell' }, row.deptName),
-						h(
-							'span',
-							{
-								class: [
-									's8-dept-severity__cell s8-dept-severity__cell--count',
-									hot ? 's8-dept-severity__cell--hot' : 's8-dept-severity__cell--zero',
-								],
-								role: 'cell',
-							},
-							String(count),
-						),
-						h('span', { class: 's8-dept-severity__cell s8-dept-severity__cell--metric', role: 'cell' }, formatHoursOrDash(row.avgProcessHours)),
-						h('span', { class: 's8-dept-severity__cell s8-dept-severity__cell--metric', role: 'cell' }, formatPercentOrDash(row.closeRate)),
-					],
-				);
-			}),
-		),
-	]);
+	return h(
+		'div',
+		{ class: 's8-dept-severity__list' },
+		rawProps.rows.map((row) => {
+			const count = row[rawProps.countKey];
+			const hot = count > 0;
+			return h(
+				'div',
+				{ key: row.deptId, class: 's8-dept-severity__item' },
+				[
+					h('div', { class: 's8-dept-severity__row-primary' }, [
+						h('span', { class: 's8-dept-severity__dept-name', title: row.deptName }, row.deptName),
+						h('span', { class: 's8-dept-severity__count-wrap' }, [
+							h('span', { class: 's8-dept-severity__count-label' }, rawProps.countLabel),
+							h(
+								'span',
+								{
+									class: [
+										's8-dept-severity__count-value',
+										hot ? 's8-dept-severity__count-value--hot' : 's8-dept-severity__count-value--zero',
+									],
+								},
+								String(count),
+							),
+						]),
+					]),
+					h('div', { class: 's8-dept-severity__row-secondary' }, [
+						h('span', { class: 's8-dept-severity__metric' }, [
+							h('span', { class: 's8-dept-severity__metric-label' }, '均时 '),
+							h('span', { class: 's8-dept-severity__metric-value' }, formatHoursOrDash(row.avgProcessHours)),
+						]),
+						h('span', { class: 's8-dept-severity__metric-sep' }, '·'),
+						h('span', { class: 's8-dept-severity__metric' }, [
+							h('span', { class: 's8-dept-severity__metric-label' }, '关闭率 '),
+							h('span', { class: 's8-dept-severity__metric-value' }, formatPercentOrDash(row.closeRate)),
+						]),
+					]),
+				],
+			);
+		}),
+	);
 };
-DeptTable.props = {
+DeptList.props = {
 	loading: { type: Boolean as PropType<boolean>, default: false },
 	rows: { type: Array as PropType<S8DeptBacklogItem[]>, required: true },
 	countLabel: { type: String, required: true },
@@ -209,86 +214,97 @@ DeptTable.props = {
 	overflow: auto;
 }
 
-.s8-dept-severity__table {
+/* S8-DEPT-CARD-LIST-LAYOUT-FIX-1:列表方案 */
+.s8-dept-severity__list {
 	display: flex;
 	flex-direction: column;
 	gap: 4px;
 }
 
-.s8-dept-severity__thead {
-	display: grid;
-	grid-template-columns: minmax(0, 1.4fr) minmax(0, 0.9fr) minmax(0, 1fr) minmax(0, 0.9fr);
-	gap: 6px;
-	padding: 4px 6px;
-	font-size: 11px;
-	color: #94a3b8;
-	border-bottom: 1px solid rgba(255, 255, 255, 0.08);
-}
-
-.s8-dept-severity__th {
-	white-space: nowrap;
-}
-
-.s8-dept-severity__th--name {
-	text-align: left;
-}
-
-.s8-dept-severity__th--count,
-.s8-dept-severity__th--metric {
-	text-align: right;
-}
-
-.s8-dept-severity__tbody {
+.s8-dept-severity__item {
 	display: flex;
 	flex-direction: column;
 	gap: 3px;
-}
-
-.s8-dept-severity__row {
-	display: grid;
-	grid-template-columns: minmax(0, 1.4fr) minmax(0, 0.9fr) minmax(0, 1fr) minmax(0, 0.9fr);
-	gap: 6px;
-	align-items: center;
-	font-size: 12px;
-	padding: 4px 6px;
+	padding: 6px 8px;
 	border-radius: 6px;
 	background: rgba(255, 255, 255, 0.02);
 }
 
-.s8-dept-severity__cell {
+.s8-dept-severity__row-primary {
+	display: flex;
+	align-items: baseline;
+	justify-content: space-between;
+	gap: 8px;
 	min-width: 0;
 }
 
-.s8-dept-severity__cell--name {
-	color: #cbd5e1;
+.s8-dept-severity__dept-name {
+	color: #e2e8f0;
+	font-size: 13px;
+	font-weight: 600;
 	white-space: nowrap;
 	overflow: hidden;
 	text-overflow: ellipsis;
+	min-width: 0;
+	flex: 1;
 }
 
-.s8-dept-severity__cell--count,
-.s8-dept-severity__cell--metric {
-	text-align: right;
-	font-family: 'Roboto Mono', monospace;
+.s8-dept-severity__count-wrap {
+	display: inline-flex;
+	align-items: baseline;
+	gap: 4px;
+	flex-shrink: 0;
 }
 
-.s8-dept-severity__cell--count {
-	font-size: 14px;
-	font-weight: 700;
+.s8-dept-severity__count-label {
+	font-size: 11px;
+	color: #94a3b8;
 }
 
-.s8-dept-severity__cell--metric {
-	font-size: 12px;
+.s8-dept-severity__count-value {
+	font-family: 'Roboto Mono', monospace;
+	font-size: 15px;
+	font-weight: 700;
 }
 
-.s8-dept-severity__cell--hot {
+.s8-dept-severity__count-value--hot {
 	color: #ef4444;
 }
 
-.s8-dept-severity__cell--zero {
+.s8-dept-severity__count-value--zero {
+	color: #94a3b8;
+}
+
+.s8-dept-severity__row-secondary {
+	display: flex;
+	align-items: baseline;
+	gap: 6px;
+	font-size: 11px;
+	color: #94a3b8;
+	min-width: 0;
+}
+
+.s8-dept-severity__metric {
+	display: inline-flex;
+	align-items: baseline;
+	gap: 2px;
+	white-space: nowrap;
+}
+
+.s8-dept-severity__metric-label {
 	color: #94a3b8;
 }
 
+.s8-dept-severity__metric-value {
+	font-family: 'Roboto Mono', monospace;
+	color: #cbd5e1;
+	font-size: 12px;
+}
+
+.s8-dept-severity__metric-sep {
+	color: rgba(148, 163, 184, 0.5);
+}
+
 .s8-dept-severity__empty {
 	color: #64748b;
 	font-size: 12px;

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

@@ -27,6 +27,10 @@ const props = defineProps<{
 	// 旧版 statusLabel 是「N 异常」/「N 超时」字符串,新布局需要分别拆出"关注/严重/超时"三个数字。
 	// 不解析 statusLabel 字符串,而是新增 timeoutCount 数字 prop(关注/严重直接复用 values.yellow/red)。
 	timeoutCount?: number;
+	// S8-OVERVIEW-STAGE-CARD-LIFECYCLE-COPY-AND-METRIC-FIX-1:severity × timeout 双桶;与 seriousCount/followCount 解耦。
+	// seriousCount/followCount = period 内发生总数(含 CLOSED),seriousDelayCount/attentionDelayCount = 当前未闭环且 SLA 超时。
+	seriousDelayCount?: number;
+	attentionDelayCount?: number;
 	progress: {
 		green: number;
 		yellow: number;
@@ -81,6 +85,9 @@ const followCount = computed(() => safeNum(props.values?.yellow));
 const seriousCount = computed(() => safeNum(props.values?.red));
 const pendingCount = computed(() => followCount.value + seriousCount.value);
 const timeoutDisplayCount = computed(() => safeNum(props.timeoutCount));
+// S8-OVERVIEW-STAGE-CARD-LIFECYCLE-COPY-AND-METRIC-FIX-1:延误数来自后端 seriousTimeout/followTimeout,不再复用 seriousCount/followCount。
+const seriousDelayValue = computed(() => safeNum(props.seriousDelayCount));
+const attentionDelayValue = computed(() => safeNum(props.attentionDelayCount));
 
 // TASK-005:主进度条仅显示红(严重)+ 黄(关注),归一化为满宽
 const _progressDenom = computed(() => props.progress.red + props.progress.yellow);
@@ -111,18 +118,19 @@ const yellowBarWidth = computed(() => {
 		>
 			<template #metric-label>
 				<div v-if="shouldShowMetricLabel" class="stage-card__pending">
-					<span class="stage-card__pending-label">当前待处理异常</span>
+					<span class="stage-card__pending-label" title="严重/关注为当前时间范围内发生总数;延误为当前未闭环且超过 SLA 的数量。">异常发生情况</span>
 				</div>
 			</template>
 
 			<template #values>
-				<!-- TASK-005:严重在上关注在下,各附延误值;移除超时/健康订单展示 -->
+				<!-- S8-OVERVIEW-STAGE-CARD-LIFECYCLE-COPY-AND-METRIC-FIX-1:
+				     severity-count = period 发生总数(含 CLOSED);severity-delay-count = 当前未闭环且 SLA 超时。 -->
 				<S8SeverityAttentionRows
 					v-if="values"
 					:serious-count="seriousCount"
 					:attention-count="followCount"
-					:serious-delay-count="seriousCount"
-					:attention-delay-count="followCount"
+					:serious-delay-count="seriousDelayValue"
+					:attention-delay-count="attentionDelayValue"
 				/>
 				<div v-else class="stage-card__value" :class="{ 'stage-card__value--wide': isWide }">{{ value }}</div>
 			</template>

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

@@ -59,6 +59,9 @@ export interface StageCardPreviewData {
 	statusLabel: string;
 	// S8-STAGE-CARD-SEMANTIC-LAYOUT-EXEC-1:超时计数(数字,独立于 statusLabel 字符串)。
 	timeoutCount?: number;
+	// S8-OVERVIEW-STAGE-CARD-LIFECYCLE-COPY-AND-METRIC-FIX-1:severity × timeout 双桶;与 values.red/yellow 解耦。
+	seriousDelayCount?: number;
+	attentionDelayCount?: number;
 	progress: StageProgress;
 	tone: StageTone;
 	wide?: boolean;
@@ -314,6 +317,9 @@ export function useS8StageConfig() {
 			showProgress: card.showProgress ?? true,
 			// S8-STAGE-CARD-SEMANTIC-LAYOUT-EXEC-1:透传超时计数。
 			timeoutCount: card.timeoutCount,
+			// S8-OVERVIEW-STAGE-CARD-LIFECYCLE-COPY-AND-METRIC-FIX-1:透传 severity × timeout 双桶。
+			seriousDelayCount: card.seriousDelayCount,
+			attentionDelayCount: card.attentionDelayCount,
 		};
 	}
 

+ 3 - 3
server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -11,9 +11,9 @@
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
     <Description>Admin.NET 通用权限开发平台</Description>
-    <AssemblyVersion>1.0.111</AssemblyVersion>
-    <FileVersion>1.0.111</FileVersion>
-    <Version>1.0.111</Version>
+    <AssemblyVersion>1.0.112</AssemblyVersion>
+    <FileVersion>1.0.112</FileVersion>
+    <Version>1.0.112</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 5 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Dto/S8/AdoS8Dtos.cs

@@ -211,6 +211,9 @@ public class AdoS8ModuleSummaryItem
     public int Yellow { get; set; }
     public int Green { get; set; }
     public int Timeout { get; set; }
+    // S8-OVERVIEW-STAGE-CARD-LIFECYCLE-COPY-AND-METRIC-FIX-1:severity × timeout 双桶,与 Timeout 同 SLA 口径。
+    public int SeriousTimeout { get; set; }
+    public int FollowTimeout { get; set; }
 }
 
 /// <summary>监控汇总出参(综合全景页顶部 + 表格数据)</summary>
@@ -221,6 +224,8 @@ public class AdoS8MonitoringSummaryDto
     public int Yellow { get; set; }
     public int Green { get; set; }
     public int Timeout { get; set; }
+    public int SeriousTimeout { get; set; }
+    public int FollowTimeout { get; set; }
     public List<AdoS8ModuleSummaryItem> ByModule { get; set; } = new();
 }
 

+ 11 - 2
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8MonitoringService.cs

@@ -333,6 +333,11 @@ public class S8MonitoringService : ITransient
             .ToListAsync();
         var nowForTimeout = DateTime.Now;
 
+        // S8-OVERVIEW-STAGE-CARD-LIFECYCLE-COPY-AND-METRIC-FIX-1:超时口径 = SlaDeadline < now AND status NOT IN (CLOSED, RECOVERED)。
+        // 按 severity 拆桶用于 stage card 「严重 N / 延误 M」「关注 N / 延误 M」展示。
+        bool IsTimeout(DateTime? sla, string? status) =>
+            sla != null && sla < nowForTimeout && status != "CLOSED" && status != "RECOVERED";
+
         var byModule = raw
             .GroupBy(e => new { mc = e.ModuleCode ?? string.Empty, sc = e.SceneCode ?? string.Empty })
             .Select(g => new AdoS8ModuleSummaryItem
@@ -346,7 +351,9 @@ public class S8MonitoringService : ITransient
                 Red     = g.Count(e => S8SeverityCode.IsSerious(e.Severity)),
                 Yellow  = g.Count(e => S8SeverityCode.IsFollow(e.Severity)),
                 Green   = 0,
-                Timeout = g.Count(e => e.SlaDeadline != null && e.SlaDeadline < nowForTimeout && e.Status != "CLOSED" && e.Status != "RECOVERED")
+                Timeout = g.Count(e => IsTimeout(e.SlaDeadline, e.Status)),
+                SeriousTimeout = g.Count(e => S8SeverityCode.IsSerious(e.Severity) && IsTimeout(e.SlaDeadline, e.Status)),
+                FollowTimeout  = g.Count(e => S8SeverityCode.IsFollow(e.Severity)  && IsTimeout(e.SlaDeadline, e.Status)),
             })
             // 按 S8ModuleCode.All 顺序排列
             .OrderBy(r => Array.IndexOf(S8ModuleCode.All, r.ModuleCode))
@@ -359,7 +366,9 @@ public class S8MonitoringService : ITransient
             Red     = raw.Count(e => S8SeverityCode.IsSerious(e.Severity)),
             Yellow  = raw.Count(e => S8SeverityCode.IsFollow(e.Severity)),
             Green   = 0,
-            Timeout = raw.Count(e => e.SlaDeadline != null && e.SlaDeadline < nowForTimeout && e.Status != "CLOSED" && e.Status != "RECOVERED"),
+            Timeout = raw.Count(e => IsTimeout(e.SlaDeadline, e.Status)),
+            SeriousTimeout = raw.Count(e => S8SeverityCode.IsSerious(e.Severity) && IsTimeout(e.SlaDeadline, e.Status)),
+            FollowTimeout  = raw.Count(e => S8SeverityCode.IsFollow(e.Severity)  && IsTimeout(e.SlaDeadline, e.Status)),
             ByModule = byModule
         };
     }