Просмотр исходного кода

fix(s8): align watch rule list display fallback

SeedData watch_rule 的 paramsJson 不携带 objectLabel、metricLabel、thresholdDisplay,
导致规则配置中心列表的「监控对象 / 监控指标 / 判定标准」展示口径与 wizard
新建规则不一致。

本提交仅在前端展示层增加派生 fallback:
- objectLabel 按 watchObjectType、monitorObjects 字典和静态 fallback 派生
- metricLabel 按 paramsJson.exceptionTypeCode 和异常类型名称映射派生
- thresholdDisplay 按 ruleMechanism 与已有阈值字段派生
- DEMO_ / RULE_S1~S7_ 预置规则增加「演示」徽标
- 停用规则存在时增加顶部提示,说明调度器不会执行,异常大盘来自演示种子

本提交不改后端、不改 DB、不启用任何 watch_rule、不触发 evaluator。
阶段 4 冒烟确认页面可打开、列表可加载、Console 无新增报错、无写操作。
DATE 类规则判定标准已可派生;VALUE_RANGE/RATIO 因 SeedData 缺数值阈值仍需后续 backfill。

chore: bump Web 2.4.153
YY968XX 1 неделя назад
Родитель
Сommit
86b1bfd34f
2 измененных файлов с 91 добавлено и 6 удалено
  1. 1 1
      Web/package.json
  2. 90 5
      Web/src/views/aidop/s8/config/S8WatchRuleConfigPage.vue

+ 1 - 1
Web/package.json

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

+ 90 - 5
Web/src/views/aidop/s8/config/S8WatchRuleConfigPage.vue

@@ -28,6 +28,26 @@ const monitorObjects = ref<S8MonitorObjectOption[]>([]);
 const drawerOpen = ref(false);
 const editingRow = ref<S8WatchRuleConfigRow | null>(null);
 
+// S8-WATCH-RULE-CONFIG-CONSISTENCY-FIX-1 / t1-seed-rule-list-display-align:
+// SeedData 60 条规则 paramsJson 不携带 objectLabel/metricLabel/thresholdDisplay/unit;
+// 列表层做派生 fallback,让"已有规则"和"新建配置"展示口径一致。
+// 派生顺序(与 parseRuleBizFields 一致):
+//   objectLabel:paramsJson.objectLabel → monitorObjects[objectType] → 静态 fallback map → watchObjectType 原值 → '未配置'
+//   metricLabel:paramsJson.metricLabel → EXCEPTION_TYPE_NAME_MAP[exceptionTypeCode] → exceptionTypeCode → '未配置'
+//   thresholdDisplay:paramsJson.thresholdDisplay → summarizeBizCondition 派生 → '未配置'
+// 仅展示层;不写 DB;不调后端。
+const OBJECT_TYPE_FALLBACK_LABEL: Record<string, string> = {
+	ORDER: '订单',
+	SALES_ORDER: '销售订单',
+	PURCHASE_ORDER: '供应商交付',
+	WORK_ORDER: '生产工单',
+	INVENTORY: '库存',
+	IQC: 'IQC 检验',
+	EQUIPMENT: '设备',
+	MATERIAL: '物料',
+	QUALITY_CHECK: '质量检验',
+};
+
 // CONFIG-RULE-CENTER-SHELL-MVP-1:业务化筛选 + 前端本地分页 + 运行态列折叠。
 const searchKeyword = ref('');
 const filterMechanism = ref<'' | 'DATE' | 'VALUE_RANGE' | 'RATIO'>('');
@@ -193,21 +213,75 @@ function stopAutoRefresh() {
 }
 
 // CONFIG-RULE-CENTER-SHELL-MVP-1:业务化筛选 + 前端分页 + 业务字段解析。
+// S8-WATCH-RULE-CONFIG-CONSISTENCY-FIX-1:SeedData 60 条规则 paramsJson 不带 objectLabel/metricLabel/thresholdDisplay;
+// 这里增加派生 fallback,使列表"监控对象/监控指标/判定标准"不再全部显示"未配置"。
+function deriveObjectLabel(row: S8WatchRuleConfigRow, p: Record<string, unknown>): string {
+	if (typeof p.objectLabel === 'string' && p.objectLabel) return p.objectLabel;
+	const objType = (row.watchObjectType || row.sourceObjectType || '').toString();
+	if (!objType) return '未配置';
+	// 优先从已加载的 monitor 字典(objectType 匹配)取中文 label;同 objectType 多对象时取首条。
+	const hit = monitorObjects.value.find((o) => o.objectType === objType);
+	if (hit?.objectLabel) return hit.objectLabel;
+	return OBJECT_TYPE_FALLBACK_LABEL[objType] || objType;
+}
+function deriveMetricLabel(p: Record<string, unknown>): string {
+	if (typeof p.metricLabel === 'string' && p.metricLabel) return p.metricLabel;
+	const etc = typeof p.exceptionTypeCode === 'string' ? p.exceptionTypeCode : '';
+	if (etc) return EXCEPTION_TYPE_NAME_MAP[etc] || etc;
+	return '未配置';
+}
 function parseRuleBizFields(row: S8WatchRuleConfigRow): { objectLabel: string; metricLabel: string; thresholdDisplay: string } {
 	const fallback = { objectLabel: '未配置', metricLabel: '未配置', thresholdDisplay: '未配置' };
-	if (!row.paramsJson) return fallback;
+	if (!row.paramsJson) {
+		// 无 paramsJson 时仍按 watchObjectType 尝试展示对象,避免整行"未配置"。
+		return {
+			objectLabel: deriveObjectLabel(row, {}),
+			metricLabel: '未配置',
+			thresholdDisplay: '未配置',
+		};
+	}
 	try {
 		const p = JSON.parse(row.paramsJson) as Record<string, unknown>;
+		const explicitThr = typeof p.thresholdDisplay === 'string' && p.thresholdDisplay ? p.thresholdDisplay : '';
 		return {
-			objectLabel: typeof p.objectLabel === 'string' && p.objectLabel ? p.objectLabel : '未配置',
-			metricLabel: typeof p.metricLabel === 'string' && p.metricLabel ? p.metricLabel : '未配置',
-			thresholdDisplay: typeof p.thresholdDisplay === 'string' && p.thresholdDisplay ? p.thresholdDisplay : '未配置',
+			objectLabel: deriveObjectLabel(row, p),
+			metricLabel: deriveMetricLabel(p),
+			thresholdDisplay: explicitThr || deriveThresholdDisplay(row, p) || '未配置',
 		};
 	} catch {
 		return fallback;
 	}
 }
 
+// 从 row + paramsJson 派生"判定标准"中文展示;与 summarizeBizCondition 行为对齐,但作为纯函数供列表列调用。
+function deriveThresholdDisplay(row: S8WatchRuleConfigRow, p: Record<string, unknown>): string {
+	const unit = typeof p.unit === 'string' ? p.unit : '';
+	const mech = row.ruleMechanism;
+	if (mech === 'DATE') {
+		const grace = typeof p.graceMinutes === 'number' ? p.graceMinutes : 0;
+		return grace > 0 ? `超期 ${grace} 分钟仍未完成触发` : '超期即触发';
+	}
+	if (mech === 'RATIO') {
+		const tgt = typeof p.lowerBound === 'number' ? p.lowerBound : null;
+		return tgt != null ? `低于 ${tgt}${unit || '%'} 触发` : '';
+	}
+	if (mech === 'VALUE_RANGE') {
+		const lo = typeof p.lowerBound === 'number' ? p.lowerBound : null;
+		const up = typeof p.upperBound === 'number' ? p.upperBound : null;
+		const segs: string[] = [];
+		if (lo != null) segs.push(`低于 ${lo}${unit}`);
+		if (up != null) segs.push(`高于 ${up}${unit}`);
+		return segs.length > 0 ? segs.join(' 或 ') + ' 触发' : '';
+	}
+	return '';
+}
+
+// S8-WATCH-RULE-CONFIG-CONSISTENCY-FIX-1:判定规则是否为 SeedData / 演示预置(仅按 ruleCode 前缀,不动 DB)。
+function isSeedRule(row: S8WatchRuleConfigRow): boolean {
+	const code = row.ruleCode || '';
+	return code.startsWith('DEMO_') || /^RULE_S[1-7]_/.test(code);
+}
+
 // CONFIG-RULE-EDIT-BIZ-VIEW-MVP-1:从 paramsJson 推导业务判定标准的中文展示(编辑抽屉摘要使用)。
 function summarizeBizCondition(row: S8WatchRuleConfigRow | null): string {
 	if (!row) return '—';
@@ -1126,6 +1200,15 @@ onDeactivated(() => stopAutoRefresh());
 
 <template>
 	<AidopDemoShell title="规则配置中心" subtitle="配置异常监控规则、报警机制、监控对象与判定标准。新建后默认停用,演示前由人工启用。">
+		<!-- S8-WATCH-RULE-CONFIG-CONSISTENCY-FIX-1:演示语义说明,避免用户把"已配规则"误解为"已在运行" -->
+		<el-alert
+			v-if="centerStats.disabled > 0"
+			type="info"
+			:closable="false"
+			show-icon
+			style="margin-bottom: 8px"
+			:title="`当前 ${centerStats.disabled} 条规则处于停用状态,调度器不会执行;大盘异常数据来自演示种子,不由本页规则实时产生。`"
+		/>
 		<!-- CONFIG-RULE-CENTER-SHELL-MVP-1:主操作 + 统计 chip + 业务化筛选 + 前端分页 -->
 		<div class="rule-toolbar">
 			<el-button size="small" type="primary" @click="openWizard">新建监控配置</el-button>
@@ -1238,9 +1321,11 @@ onDeactivated(() => stopAutoRefresh());
 					<span :class="{ 'rule-biz-empty': parseRuleBizFields(row).thresholdDisplay === '未配置' }">{{ parseRuleBizFields(row).thresholdDisplay }}</span>
 				</template>
 			</el-table-column>
-			<el-table-column label="状态" width="80">
+			<el-table-column label="状态" width="120">
 				<template #default="{ row }">
 					<el-tag :type="row.enabled ? 'success' : 'info'" size="small">{{ row.enabled ? '启用' : '停用' }}</el-tag>
+					<!-- S8-WATCH-RULE-CONFIG-CONSISTENCY-FIX-1:SeedData / DEMO 规则标记,避免被误读为生产运行规则 -->
+					<el-tag v-if="isSeedRule(row)" size="small" type="warning" effect="plain" style="margin-left: 4px">演示</el-tag>
 				</template>
 			</el-table-column>
 			<el-table-column label="严重度" width="80">