|
@@ -4,7 +4,8 @@
|
|
|
* 列表只传 { kind, id };本模块负责身份校验、不同后端载荷的正规化和紧凑详情视图。
|
|
* 列表只传 { kind, id };本模块负责身份校验、不同后端载荷的正规化和紧凑详情视图。
|
|
|
* point/device 沿用既有详情链,不在此重复实现。
|
|
* point/device 沿用既有详情链,不在此重复实现。
|
|
|
*/
|
|
*/
|
|
|
-import { eventLevelTag, eventStatusText } from './eventList.js'
|
|
|
|
|
|
|
+import { eventLevelFromBucket, eventLevelTag, eventStatusText } from './eventList.js'
|
|
|
|
|
+import { DEFAULT_TONE } from './statusTones.js'
|
|
|
import { toWorkOrder, toWorkOrderTimeline, workOrderStateName } from './workOrder.js'
|
|
import { toWorkOrder, toWorkOrderTimeline, workOrderStateName } from './workOrder.js'
|
|
|
import { trackOf } from './workOrderTimeline.js'
|
|
import { trackOf } from './workOrderTimeline.js'
|
|
|
|
|
|
|
@@ -83,10 +84,14 @@ export function entityDetailView(detail) {
|
|
|
const current = detail || {}
|
|
const current = detail || {}
|
|
|
return {
|
|
return {
|
|
|
title: current.title || current.name || current.id || '实体详情',
|
|
title: current.title || current.name || current.id || '实体详情',
|
|
|
- subtitle: current.subtitle || [current.kindLabel, current.id].filter(Boolean).join(' · '),
|
|
|
|
|
|
|
+ // 副标题用业务编号(不是主键):主键对使用者没有意义。
|
|
|
|
|
+ subtitle: current.subtitle || [current.kindLabel, current.displayCode || current.id].filter(Boolean).join(' · '),
|
|
|
tone: current.tone || 'neutral',
|
|
tone: current.tone || 'neutral',
|
|
|
status: current.statusText || '',
|
|
status: current.statusText || '',
|
|
|
- // 类别是身份而不是普通字段:抬头要能一眼看出「中文(英文码)」。
|
|
|
|
|
|
|
+ // 等级与状态各有自己的语义色:等级沿用卡片那张四级词表,不与状态色混用。
|
|
|
|
|
+ levelLabel: current.levelLabel || '',
|
|
|
|
|
+ levelTone: current.levelTone || '',
|
|
|
|
|
+ // 类别是身份而不是普通字段:抬头要能一眼看出是什么对象。
|
|
|
categoryText: current.categoryText || '',
|
|
categoryText: current.categoryText || '',
|
|
|
summary: Array.isArray(current.summary) ? current.summary : [],
|
|
summary: Array.isArray(current.summary) ? current.summary : [],
|
|
|
tabs: Array.isArray(current.tabs) ? current.tabs : [],
|
|
tabs: Array.isArray(current.tabs) ? current.tabs : [],
|
|
@@ -140,17 +145,100 @@ export function facilityStatusTone(status) {
|
|
|
return 'neutral'
|
|
return 'neutral'
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * 数值型指标的**单位登记**(维护者 2026-10-29「所有数值都要带单位」)。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 只在这里写一次,摘要网格与业务明细都从这里取 —— 两处各写一份单位必然漂移
|
|
|
|
|
+ * (已经踩过:摘要写「长度(米)」、明细写「长度」+ 单位,同一份数据两种长相)。
|
|
|
|
|
+ * 单位来自后端领域注释,不由前端猜。
|
|
|
|
|
+ */
|
|
|
|
|
+const COMMON_UNITS = {
|
|
|
|
|
+ lengthMeters: '米',
|
|
|
|
|
+ diameterMillimeters: '毫米',
|
|
|
|
|
+ designCapacity: '万m³/d',
|
|
|
|
|
+ currentCapacity: '万m³/d',
|
|
|
|
|
+ designHead: 'm',
|
|
|
|
|
+ storageCapacity: 'm³',
|
|
|
|
|
+ pumpCount: '台',
|
|
|
|
|
+ powerCapacity: 'kW',
|
|
|
|
|
+ assetValue: '元',
|
|
|
|
|
+ useLifeYears: '年',
|
|
|
|
|
+ riskScore: '分',
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 同一字段名在不同设施里量纲不同:泵站设计流量是 m³/h,排水工程设施设计流量是 m³/s。
|
|
|
|
|
+ * 所以单位必须**按类别**登记,"一个键一个单位"会张冠李戴。
|
|
|
|
|
+ */
|
|
|
|
|
+const UNIT_BY_CATEGORY = {
|
|
|
|
|
+ PIPE_NETWORK: {},
|
|
|
|
|
+ WATER_PLANT: {},
|
|
|
|
|
+ PUMP_STATION: { designFlow: 'm³/h' },
|
|
|
|
|
+ ENGINEERING_FACILITY: { designFlow: 'm³/s' },
|
|
|
|
|
+ EMERGENCY_RESOURCE: {},
|
|
|
|
|
+ HAZARD: {},
|
|
|
|
|
+ PROTECTION_TARGET: {},
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 字段在该设施类别下的单位;没登记返回空串(纯文本字段)。 */
|
|
|
|
|
+function unitOf(category, key) {
|
|
|
|
|
+ const scoped = UNIT_BY_CATEGORY[category] || {}
|
|
|
|
|
+ return scoped[key] !== undefined ? scoped[key] : COMMON_UNITS[key] || ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 摘要与明细共用的中文字段名(维护者 2026-10-29:一律中文、不带括注)。 */
|
|
|
|
|
+const SUMMARY_LABELS = {
|
|
|
|
|
+ lengthMeters: '长度',
|
|
|
|
|
+ diameterMillimeters: '管径',
|
|
|
|
|
+ material: '材质',
|
|
|
|
|
+ designCapacity: '设计能力',
|
|
|
|
|
+ currentCapacity: '当前能力',
|
|
|
|
|
+ treatmentProcess: '处理工艺',
|
|
|
|
|
+ designFlow: '设计流量',
|
|
|
|
|
+ designHead: '设计扬程',
|
|
|
|
|
+ storageCapacity: '调蓄容积',
|
|
|
|
|
+ pumpCount: '水泵数量',
|
|
|
|
|
+ powerCapacity: '装机功率',
|
|
|
|
|
+ assetValue: '资产价值',
|
|
|
|
|
+ useLifeYears: '预计使用年限',
|
|
|
|
|
+ riskScore: '风险分值',
|
|
|
|
|
+ riskLevel: '风险等级',
|
|
|
|
|
+ fmodel: '型号',
|
|
|
|
|
+ model: '型号',
|
|
|
|
|
+ specification: '规格',
|
|
|
|
|
+ maintainer: '维护人',
|
|
|
|
|
+ level: '等级',
|
|
|
|
|
+ rectifyDeadline: '整改期限',
|
|
|
|
|
+ rectifyPerson: '整改人',
|
|
|
|
|
+ assessmentTime: '评估时间',
|
|
|
|
|
+ facilityLevel: '设施等级',
|
|
|
|
|
+}
|
|
|
|
|
+/** 同类字段在不同设施里的业务名不同(隐患等级 vs 设施等级),差异只在这里登记。 */
|
|
|
|
|
+const SUMMARY_LABELS_BY_CATEGORY = {
|
|
|
|
|
+ HAZARD: { level: '隐患等级' },
|
|
|
|
|
+ ENGINEERING_FACILITY: { facilityLevel: '设施等级' },
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function assetSummary(category, a, status, location) {
|
|
function assetSummary(category, a, status, location) {
|
|
|
|
|
+ // 摘要只挑"一眼要看的读数",键与明细同源;单位统一由 METRIC_UNITS 提供。
|
|
|
const byCategory = {
|
|
const byCategory = {
|
|
|
- WATER_PLANT: [['当前能力', a.currentCapacity], ['设计能力', a.designCapacity], ['处理工艺', a.treatmentProcess]],
|
|
|
|
|
- ENGINEERING_FACILITY: [['设施等级', a.facilityLevel], ['设计流量', a.designFlow], ['调蓄容积', a.storageCapacity]],
|
|
|
|
|
- PUMP_STATION: [['水泵数量', a.pumpCount], ['设计流量', a.designFlow], ['装机功率', a.powerCapacity]],
|
|
|
|
|
- PIPE_NETWORK: [['长度(米)', a.lengthMeters], ['管径(毫米)', a.diameterMillimeters], ['材质', a.material]],
|
|
|
|
|
- HAZARD: [['隐患等级', a.level], ['整改期限', a.rectifyDeadline], ['整改人', a.rectifyPerson]],
|
|
|
|
|
- PROTECTION_TARGET: [['风险等级', a.riskLevel], ['风险分值', a.riskScore], ['评估时间', a.assessmentTime]],
|
|
|
|
|
- EMERGENCY_RESOURCE: [['型号', a.model], ['规格', a.specification], ['维护人', a.maintainer]],
|
|
|
|
|
|
|
+ WATER_PLANT: ['currentCapacity', 'designCapacity', 'treatmentProcess'],
|
|
|
|
|
+ ENGINEERING_FACILITY: ['facilityLevel', 'designFlow', 'storageCapacity'],
|
|
|
|
|
+ PUMP_STATION: ['pumpCount', 'designFlow', 'powerCapacity'],
|
|
|
|
|
+ PIPE_NETWORK: ['lengthMeters', 'diameterMillimeters', 'material'],
|
|
|
|
|
+ HAZARD: ['level', 'rectifyDeadline', 'rectifyPerson'],
|
|
|
|
|
+ PROTECTION_TARGET: ['riskLevel', 'riskScore', 'assessmentTime'],
|
|
|
|
|
+ EMERGENCY_RESOURCE: ['model', 'specification', 'maintainer'],
|
|
|
}
|
|
}
|
|
|
- return summaryItems([['状态', status], ...(byCategory[category] || []), ['位置', location]])
|
|
|
|
|
|
|
+ const labels = { ...SUMMARY_LABELS, ...SUMMARY_LABELS_BY_CATEGORY[category] }
|
|
|
|
|
+ const rows = (byCategory[category] || []).map((key) => {
|
|
|
|
|
+ const unit = unitOf(category, key)
|
|
|
|
|
+ const raw = a[key]
|
|
|
|
|
+ // 只有真有单位时才拼单位;纯文本字段(材质/负责人…)原样。
|
|
|
|
|
+ const value = unit ? withUnitText(raw, unit) : raw
|
|
|
|
|
+ return [labels[key] || SUMMARY_LABELS[key] || key, value]
|
|
|
|
|
+ })
|
|
|
|
|
+ return summaryItems([['状态', status], ...rows, ['位置', location]])
|
|
|
}
|
|
}
|
|
|
function assetPeople(a) { return peopleItems([['负责人', a.principal], ['维护人', a.maintainer], ['整改人', a.rectifyPerson], ['联系电话', a.contactPhone || a.maintainerPhone]]) }
|
|
function assetPeople(a) { return peopleItems([['负责人', a.principal], ['维护人', a.maintainer], ['整改人', a.rectifyPerson], ['联系电话', a.contactPhone || a.maintainerPhone]]) }
|
|
|
function assetAttachments(a) { return attachmentItems(a.images || a.image) }
|
|
function assetAttachments(a) { return attachmentItems(a.images || a.image) }
|
|
@@ -239,8 +327,8 @@ function pipesFields(a) {
|
|
|
format: (v) => (drainage ? codedText(v, NETWORK_LEVEL_TEXT) : ''),
|
|
format: (v) => (drainage ? codedText(v, NETWORK_LEVEL_TEXT) : ''),
|
|
|
invalidNote: drainage ? '' : '仅排水管道适用',
|
|
invalidNote: drainage ? '' : '仅排水管道适用',
|
|
|
},
|
|
},
|
|
|
- { label: '长度', key: 'lengthMeters', unit: '米' },
|
|
|
|
|
- { label: '管径', key: 'diameterMillimeters', unit: '毫米' },
|
|
|
|
|
|
|
+ { label: '长度', key: 'lengthMeters' },
|
|
|
|
|
+ { label: '管径', key: 'diameterMillimeters' },
|
|
|
{ label: '材质', key: 'material' },
|
|
{ label: '材质', key: 'material' },
|
|
|
{ label: '管养单位', key: 'managementUnit' },
|
|
{ label: '管养单位', key: 'managementUnit' },
|
|
|
{ label: '建设日期', key: 'buildDate' },
|
|
{ label: '建设日期', key: 'buildDate' },
|
|
@@ -252,8 +340,8 @@ const ASSET_FIELD_ROWS = {
|
|
|
PIPE_NETWORK: null, // 见 pipesFields():需要按管网类型决定二级分类是否适用
|
|
PIPE_NETWORK: null, // 见 pipesFields():需要按管网类型决定二级分类是否适用
|
|
|
WATER_PLANT: [
|
|
WATER_PLANT: [
|
|
|
{ label: '水源类型', key: 'waterSourceType' },
|
|
{ label: '水源类型', key: 'waterSourceType' },
|
|
|
- { label: '设计能力', key: 'designCapacity', unit: '万m³/d' },
|
|
|
|
|
- { label: '当前能力', key: 'currentCapacity', unit: '万m³/d' },
|
|
|
|
|
|
|
+ { label: '设计能力', key: 'designCapacity' },
|
|
|
|
|
+ { label: '当前能力', key: 'currentCapacity' },
|
|
|
{ label: '处理工艺', key: 'treatmentProcess', wide: true },
|
|
{ label: '处理工艺', key: 'treatmentProcess', wide: true },
|
|
|
{ label: '投产日期', key: 'commissionDate' },
|
|
{ label: '投产日期', key: 'commissionDate' },
|
|
|
{ label: '水质标准', key: 'waterQualityStandard' },
|
|
{ label: '水质标准', key: 'waterQualityStandard' },
|
|
@@ -266,18 +354,18 @@ const ASSET_FIELD_ROWS = {
|
|
|
{ label: '设施等级', key: 'facilityLevel' },
|
|
{ label: '设施等级', key: 'facilityLevel' },
|
|
|
{ label: '管理单位', key: 'managementUnit' },
|
|
{ label: '管理单位', key: 'managementUnit' },
|
|
|
{ label: '建设日期', key: 'buildDate' },
|
|
{ label: '建设日期', key: 'buildDate' },
|
|
|
- { label: '设计流量', key: 'designFlow', unit: 'm³/s' },
|
|
|
|
|
- { label: '设计扬程', key: 'designHead', unit: 'm' },
|
|
|
|
|
- { label: '调蓄容积', key: 'storageCapacity', unit: 'm³' },
|
|
|
|
|
|
|
+ { label: '设计流量', key: 'designFlow' },
|
|
|
|
|
+ { label: '设计扬程', key: 'designHead' },
|
|
|
|
|
+ { label: '调蓄容积', key: 'storageCapacity' },
|
|
|
{ label: '关联设备', key: 'equipmentIds', wide: true },
|
|
{ label: '关联设备', key: 'equipmentIds', wide: true },
|
|
|
{ label: '备注', key: 'remark', wide: true },
|
|
{ label: '备注', key: 'remark', wide: true },
|
|
|
],
|
|
],
|
|
|
PUMP_STATION: [
|
|
PUMP_STATION: [
|
|
|
{ label: '泵站类型', key: 'stationType' },
|
|
{ label: '泵站类型', key: 'stationType' },
|
|
|
- { label: '水泵数量', key: 'pumpCount', unit: '台' },
|
|
|
|
|
- { label: '设计流量', key: 'designFlow', unit: 'm³/h' },
|
|
|
|
|
- { label: '设计扬程', key: 'designHead', unit: 'm' },
|
|
|
|
|
- { label: '装机功率', key: 'powerCapacity', unit: 'kW' },
|
|
|
|
|
|
|
+ { label: '水泵数量', key: 'pumpCount' },
|
|
|
|
|
+ { label: '设计流量', key: 'designFlow' },
|
|
|
|
|
+ { label: '设计扬程', key: 'designHead' },
|
|
|
|
|
+ { label: '装机功率', key: 'powerCapacity' },
|
|
|
{ label: '投产日期', key: 'commissionDate' },
|
|
{ label: '投产日期', key: 'commissionDate' },
|
|
|
{ label: '负责人', key: 'principal' },
|
|
{ label: '负责人', key: 'principal' },
|
|
|
{ label: '联系电话', key: 'contactPhone' },
|
|
{ label: '联系电话', key: 'contactPhone' },
|
|
@@ -288,8 +376,8 @@ const ASSET_FIELD_ROWS = {
|
|
|
{ label: '型号', key: 'model' },
|
|
{ label: '型号', key: 'model' },
|
|
|
{ label: '规格', key: 'specification', wide: true },
|
|
{ label: '规格', key: 'specification', wide: true },
|
|
|
{ label: '制造商', key: 'manufacturer' },
|
|
{ label: '制造商', key: 'manufacturer' },
|
|
|
- { label: '资产价值', key: 'assetValue', unit: '元' },
|
|
|
|
|
- { label: '预计使用年限', key: 'useLifeYears', unit: '年' },
|
|
|
|
|
|
|
+ { label: '资产价值', key: 'assetValue' },
|
|
|
|
|
+ { label: '预计使用年限', key: 'useLifeYears' },
|
|
|
{ label: '安装时间', key: 'installTime' },
|
|
{ label: '安装时间', key: 'installTime' },
|
|
|
{ label: '维护人', key: 'maintainer' },
|
|
{ label: '维护人', key: 'maintainer' },
|
|
|
{ label: '维护电话', key: 'maintainerPhone' },
|
|
{ label: '维护电话', key: 'maintainerPhone' },
|
|
@@ -317,7 +405,7 @@ const ASSET_FIELD_ROWS = {
|
|
|
PROTECTION_TARGET: [
|
|
PROTECTION_TARGET: [
|
|
|
{ label: '关联管网', key: 'networkId' },
|
|
{ label: '关联管网', key: 'networkId' },
|
|
|
{ label: '风险等级', key: 'riskLevel' },
|
|
{ label: '风险等级', key: 'riskLevel' },
|
|
|
- { label: '风险分值', key: 'riskScore', unit: '分' },
|
|
|
|
|
|
|
+ { label: '风险分值', key: 'riskScore' },
|
|
|
{ label: '风险颜色', key: 'riskColor' },
|
|
{ label: '风险颜色', key: 'riskColor' },
|
|
|
{ label: '评估模型', key: 'assessmentModel' },
|
|
{ label: '评估模型', key: 'assessmentModel' },
|
|
|
{ label: '评估时间', key: 'assessmentTime' },
|
|
{ label: '评估时间', key: 'assessmentTime' },
|
|
@@ -336,7 +424,8 @@ function withUnitText(value, unit) {
|
|
|
function assetFields(category, a) {
|
|
function assetFields(category, a) {
|
|
|
const rows = category === 'PIPE_NETWORK' ? pipesFields(a) : ASSET_FIELD_ROWS[category] || []
|
|
const rows = category === 'PIPE_NETWORK' ? pipesFields(a) : ASSET_FIELD_ROWS[category] || []
|
|
|
return rows.map((row) => {
|
|
return rows.map((row) => {
|
|
|
- const raw = row.format ? row.format(a[row.key]) : withUnitText(a[row.key], row.unit)
|
|
|
|
|
|
|
+ // 单位一律由 `unitOf(category, key)` 给:同一字段名在不同设施里量纲可能不同。
|
|
|
|
|
+ const raw = row.format ? row.format(a[row.key]) : withUnitText(a[row.key], unitOf(category, row.key))
|
|
|
// always 行(如非排水管的二级分类)保留占位;其余缺值由 compactSections 剔除。
|
|
// always 行(如非排水管的二级分类)保留占位;其余缺值由 compactSections 剔除。
|
|
|
const item = field(row.label, raw, row.wide === true)
|
|
const item = field(row.label, raw, row.wide === true)
|
|
|
if (row.always) {
|
|
if (row.always) {
|
|
@@ -354,6 +443,44 @@ function coordinatesText(value) {
|
|
|
return [start, end].filter(Boolean).join(' → ')
|
|
return [start, end].filter(Boolean).join(' → ')
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * 事件等级 → 色键 + 文字。
|
|
|
|
|
+ *
|
|
|
|
|
+ * **三种输入形状都要认**(维护者 2026-10-29 走查:「等级明明能拿到却标成未知」):
|
|
|
|
|
+ * · 后端原始事件行:`level: 1`(数字);
|
|
|
|
|
+ * · 列表行视图 `eventRowView`:`level: { tone, label }`(已翻译过的对象);
|
|
|
|
|
+ * · 移动端预警/报警详情:`warningLevel / alarmLevel`(数字或等级名)。
|
|
|
|
|
+ * 详情面板首屏先用列表行 prefill、随后被远端覆盖,两条路都要能读出同一个等级 ——
|
|
|
|
|
+ * 早先只认数字,prefill 的等级对象就被判成"等级未知"。
|
|
|
|
|
+ */
|
|
|
|
|
+/** 「二级预警」「3级」「四级」→ 1..4;认不出回 null(不猜等级)。 */
|
|
|
|
|
+function numericLevelOf(value) {
|
|
|
|
|
+ const text = value === null || value === undefined ? '' : String(value).trim()
|
|
|
|
|
+ if (!text) return null
|
|
|
|
|
+ const arabic = /([1-4])\s*级/.exec(text)
|
|
|
|
|
+ if (arabic) return Number(arabic[1])
|
|
|
|
|
+ const cn = /([一二三四])\s*级/.exec(text)
|
|
|
|
|
+ if (cn) return { 一: 1, 二: 2, 三: 3, 四: 4 }[cn[1]]
|
|
|
|
|
+ return null
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function eventLevelFrom(source) {
|
|
|
|
|
+ const raw = source || {}
|
|
|
|
|
+ // 已经翻译过的对象:原样沿用(这正是列表与详情一致的结构性保证)。
|
|
|
|
|
+ const tagged = raw.level
|
|
|
|
|
+ if (tagged && typeof tagged === 'object' && tagged.label) {
|
|
|
|
|
+ return { tone: tagged.tone || DEFAULT_TONE, label: tagged.label }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 移动端字段名与等级名(如 "二级预警")走桶解析;认不出回「等级未知」。
|
|
|
|
|
+ const direct = raw.level ?? raw.warningLevel ?? raw.alarmLevel
|
|
|
|
|
+ if (typeof direct === 'number') return eventLevelTag(direct)
|
|
|
|
|
+ // 中文等级名(后端会下发「二级预警」「三级报警」这类人话)→ 阿拉伯数字。
|
|
|
|
|
+ const numeric = numericLevelOf(direct)
|
|
|
|
|
+ if (numeric !== null) return eventLevelTag(numeric)
|
|
|
|
|
+ const fromBucket = eventLevelFromBucket(raw.levelCode ?? raw.levelColor ?? direct, direct)
|
|
|
|
|
+ return eventLevelTag(fromBucket)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function normalizeEvent(identity, source) {
|
|
function normalizeEvent(identity, source) {
|
|
|
const envelope = source && source.basicInfo ? source : {}
|
|
const envelope = source && source.basicInfo ? source : {}
|
|
|
const basic = envelope.basicInfo && typeof envelope.basicInfo === 'object' ? envelope.basicInfo : source
|
|
const basic = envelope.basicInfo && typeof envelope.basicInfo === 'object' ? envelope.basicInfo : source
|
|
@@ -364,20 +491,26 @@ function normalizeEvent(identity, source) {
|
|
|
gisLocation: envelope.gisLocation || basic.gisLocation,
|
|
gisLocation: envelope.gisLocation || basic.gisLocation,
|
|
|
}
|
|
}
|
|
|
const family = String(identity.family || source.family || source.eventFamily || (source.alarmId || source.alarmNo ? 'ALARM' : source.warningId || source.warningNo ? 'WARNING' : '')).toUpperCase()
|
|
const family = String(identity.family || source.family || source.eventFamily || (source.alarmId || source.alarmNo ? 'ALARM' : source.warningId || source.warningNo ? 'WARNING' : '')).toUpperCase()
|
|
|
- const levelValue = source.level ?? source.warningLevel ?? source.alarmLevel
|
|
|
|
|
- const level = eventLevelTag(levelValue)
|
|
|
|
|
|
|
+ const level = eventLevelFrom(source)
|
|
|
const status = eventStatusText(source.status || source.state) || text(source.statusName || source.stateName)
|
|
const status = eventStatusText(source.status || source.state) || text(source.statusName || source.stateName)
|
|
|
const name = text(source.name || source.warningName || source.alarmName || source.eventName || source.deviceName || source.title) || identity.id
|
|
const name = text(source.name || source.warningName || source.alarmName || source.eventName || source.deviceName || source.title) || identity.id
|
|
|
|
|
+ // 展示用的事件编码一律取**业务编号**(warningNo / alarmNo),主键只用于取数。
|
|
|
|
|
+ const displayCode = text(source.codeText || source.code || source.warningNo || source.alarmNo || source.eventCode) || identity.id
|
|
|
return {
|
|
return {
|
|
|
...identity,
|
|
...identity,
|
|
|
name,
|
|
name,
|
|
|
|
|
+ displayCode,
|
|
|
title: name,
|
|
title: name,
|
|
|
kindLabel: family === 'ALARM' ? '报警' : family === 'WARNING' ? '预警' : '事件',
|
|
kindLabel: family === 'ALARM' ? '报警' : family === 'WARNING' ? '预警' : '事件',
|
|
|
family,
|
|
family,
|
|
|
statusText: status,
|
|
statusText: status,
|
|
|
tone: level.tone || 'neutral',
|
|
tone: level.tone || 'neutral',
|
|
|
|
|
+ // 抬头直接显示等级(与卡片同一张词表、同一套色),不再只写在字段里。
|
|
|
|
|
+ levelLabel: level.label,
|
|
|
|
|
+ levelTone: level.tone,
|
|
|
raw: source,
|
|
raw: source,
|
|
|
- summary: summaryItems([['等级', level.label], ['状态', status], ['当前值', withUnit(source.value ?? source.actualValue, source.unit || source.warningUnit)], ['发生时间', source.eventTime || source.createTime]]),
|
|
|
|
|
|
|
+ // 等级带语义色(与卡片同一张词表),是摘要里唯一的等级呈现位置。
|
|
|
|
|
+ summary: summaryItems([['等级', level.label, level.tone], ['状态', status], ['当前值', withUnit(source.value ?? source.actualValue, source.unit || source.warningUnit)], ['发生时间', source.eventTime || source.createTime]]),
|
|
|
tabs: family === 'WARNING'
|
|
tabs: family === 'WARNING'
|
|
|
? [tab('overview', '预警信息'), tab('records', '处置记录'), tab('alarms', '关联报警'), tab('attachments', '附件')]
|
|
? [tab('overview', '预警信息'), tab('records', '处置记录'), tab('alarms', '关联报警'), tab('attachments', '附件')]
|
|
|
: [tab('overview', '报警信息'), tab('devices', '设备信息'), tab('records', '处置记录'), tab('attachments', '附件')],
|
|
: [tab('overview', '报警信息'), tab('devices', '设备信息'), tab('records', '处置记录'), tab('attachments', '附件')],
|
|
@@ -388,10 +521,22 @@ function normalizeEvent(identity, source) {
|
|
|
attachments: attachmentItems(source.attachmentList),
|
|
attachments: attachmentItems(source.attachmentList),
|
|
|
records: recordItems(source.disposalHistory || source.logs),
|
|
records: recordItems(source.disposalHistory || source.logs),
|
|
|
sections: compactSections([
|
|
sections: compactSections([
|
|
|
- section('事件摘要', [field('事件编码', identity.id), field('等级', level.label), field('状态', status), field('发生时间', source.eventTime || source.createTime)]),
|
|
|
|
|
- section('监测读数', [field('指标', source.type || source.warningType || source.warningCode), field('当前值', withUnit(source.value ?? source.actualValue, source.unit || source.warningUnit)), field('阈值', threshold(source))]),
|
|
|
|
|
|
|
+ // 等级只出现在摘要卡片那一处(带色),这里不再重复一遍纯文字等级。
|
|
|
|
|
+ section('事件摘要', [field('事件编码', displayCode), field('状态', status), field('发生时间', source.eventTime || source.createTime)]),
|
|
|
|
|
+ section('监测读数', [
|
|
|
|
|
+ field('指标', source.type || source.warningType || source.warningCode),
|
|
|
|
|
+ // 读数与阈值都带单位(维护者 2026-10-29:数值型指标必须有单位提示)。
|
|
|
|
|
+ field('实际值', withUnit(source.value ?? source.actualValue, source.unit || source.warningUnit)),
|
|
|
|
|
+ field('阈值', threshold(source)),
|
|
|
|
|
+ field('超出幅度', withUnit(source.deviationRatio, '%')),
|
|
|
|
|
+ ]),
|
|
|
section('关联信息', [field('设备', source.deviceName || source.deviceCode), field('监测点', source.monitoringPointName || source.monitoringPointCode), field('工单', source.workOrderCode || source.orderNo), field('位置', source.location || source.address || locationText(source.gisLocation), true)]),
|
|
section('关联信息', [field('设备', source.deviceName || source.deviceCode), field('监测点', source.monitoringPointName || source.monitoringPointCode), field('工单', source.workOrderCode || source.orderNo), field('位置', source.location || source.address || locationText(source.gisLocation), true)]),
|
|
|
- section('处置与附件', [field('处置记录', listCount(source.disposalHistory, '条')), field('关联报警', listCount(source.alarmList, '条')), field('附件', listCount(source.attachmentList, '项'))]),
|
|
|
|
|
|
|
+ section('处置与附件', [
|
|
|
|
|
+ field('处置记录', listCount(source.disposalHistory, '条')),
|
|
|
|
|
+ field('关联报警', listCount(source.alarmList, '条')),
|
|
|
|
|
+ field('附件', listCount(source.attachmentList, '项')),
|
|
|
|
|
+ field('督办记录', listCount(source.supervisionList, '条')),
|
|
|
|
|
+ ]),
|
|
|
]),
|
|
]),
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -427,7 +572,7 @@ function normalizeWorkOrder(identity, source) {
|
|
|
order,
|
|
order,
|
|
|
track,
|
|
track,
|
|
|
raw: source,
|
|
raw: source,
|
|
|
- summary: summaryItems([['工单号', order.code], ['状态', stateText], ['优先级', order.orderLevel], ['计划完成', order.planFinishTime]]),
|
|
|
|
|
|
|
+ summary: summaryItems([['工单号', order.code], ['状态', stateText], ['优先级', orderLevelText(order.orderLevel)], ['计划完成', order.planFinishTime]]),
|
|
|
tabs: [tab('overview', '工单信息'), tab('flow', '处理流程'), tab('devices', '设备信息'), tab('attachments', '附件')],
|
|
tabs: [tab('overview', '工单信息'), tab('flow', '处理流程'), tab('devices', '设备信息'), tab('attachments', '附件')],
|
|
|
stage: workOrderStage(order.stateCode),
|
|
stage: workOrderStage(order.stateCode),
|
|
|
people: peopleItems([['处置人', order.receiveUserName], ['核验人', order.verifyUserName]]),
|
|
people: peopleItems([['处置人', order.receiveUserName], ['核验人', order.verifyUserName]]),
|
|
@@ -435,7 +580,7 @@ function normalizeWorkOrder(identity, source) {
|
|
|
attachments: attachmentItems([raw.maintenanceBeforeImg, raw.maintenanceAfterImg, ...timelineAttachments(timelineRaw)]),
|
|
attachments: attachmentItems([raw.maintenanceBeforeImg, raw.maintenanceAfterImg, ...timelineAttachments(timelineRaw)]),
|
|
|
records: recordItems(timelineRaw),
|
|
records: recordItems(timelineRaw),
|
|
|
sections: compactSections([
|
|
sections: compactSections([
|
|
|
- section('工单摘要', [field('工单号', order.code), field('类型', order.orderTypeName), field('等级', order.orderLevel), field('状态', stateText)]),
|
|
|
|
|
|
|
+ section('工单摘要', [field('工单号', order.code), field('类型', order.orderTypeName), field('优先级', orderLevelText(order.orderLevel)), field('状态', stateText)]),
|
|
|
section('时间信息', [field('创建时间', order.createTime), field('计划完成', order.planFinishTime), field('实际完成', order.realFinishTime)]),
|
|
section('时间信息', [field('创建时间', order.createTime), field('计划完成', order.planFinishTime), field('实际完成', order.realFinishTime)]),
|
|
|
section('设备信息', [field('设备', order.deviceName || order.deviceCode), field('设备类型', order.deviceTypeName), field('安装位置', order.deviceLocation, true)]),
|
|
section('设备信息', [field('设备', order.deviceName || order.deviceCode), field('设备类型', order.deviceTypeName), field('安装位置', order.deviceLocation, true)]),
|
|
|
section('处置内容', [field('处置人', order.receiveUserName), field('核验人', order.verifyUserName), field('故障描述', order.description, true), field('处置结果', order.repairResult, true)]),
|
|
section('处置内容', [field('处置人', order.receiveUserName), field('核验人', order.verifyUserName), field('故障描述', order.description, true), field('处置结果', order.repairResult, true)]),
|
|
@@ -444,9 +589,16 @@ function normalizeWorkOrder(identity, source) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+/** 工单优先级码值(0 紧急 / 1 一般 / 2 低)→ 中文;认不出回码。 */
|
|
|
|
|
+function orderLevelText(value) {
|
|
|
|
|
+ const code = value === null || value === undefined ? '' : String(value).trim()
|
|
|
|
|
+ if (!code) return ''
|
|
|
|
|
+ return { '0': '紧急', '1': '一般', '2': '低' }[code] || code
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function tab(key, label) { return { key, label } }
|
|
function tab(key, label) { return { key, label } }
|
|
|
function relatedAlarm(item) { return { code: item.alarmNo || item.alarmId || item.id || '', name: item.alarmName || item.content || item.deviceName || '关联报警', status: item.auditStatusName || item.alarmStatus || item.status || '' } }
|
|
function relatedAlarm(item) { return { code: item.alarmNo || item.alarmId || item.id || '', name: item.alarmName || item.content || item.deviceName || '关联报警', status: item.auditStatusName || item.alarmStatus || item.status || '' } }
|
|
|
-function summaryItems(entries) { return entries.filter(([,value]) => value !== null && value !== undefined && String(value).trim() !== '').map(([label,value]) => ({ label, value: String(value) })) }
|
|
|
|
|
|
|
+function summaryItems(entries) { return entries.filter(([,value]) => value !== null && value !== undefined && String(value).trim() !== '').map(([label,value,tone]) => ({ label, value: String(value), tone: tone || '' })) }
|
|
|
function peopleItems(entries) { return entries.filter(([,name]) => name).map(([role,name]) => ({ role, name: String(name) })) }
|
|
function peopleItems(entries) { return entries.filter(([,name]) => name).map(([role,name]) => ({ role, name: String(name) })) }
|
|
|
function deviceItems(source) {
|
|
function deviceItems(source) {
|
|
|
const name = source.deviceName || source.monitoringPointName || ''
|
|
const name = source.deviceName || source.monitoringPointName || ''
|
|
@@ -498,7 +650,17 @@ function section(title, fields) { return { title, fields } }
|
|
|
function field(label, value, wide = false) { return { label, value: display(value), wide, always: false, note: '' } }
|
|
function field(label, value, wide = false) { return { label, value: display(value), wide, always: false, note: '' } }
|
|
|
function display(value) { return value === null || value === undefined || String(value).trim() === '' ? PLACEHOLDER : String(value) }
|
|
function display(value) { return value === null || value === undefined || String(value).trim() === '' ? PLACEHOLDER : String(value) }
|
|
|
function text(value) { return value === null || value === undefined ? '' : String(value).trim() }
|
|
function text(value) { return value === null || value === undefined ? '' : String(value).trim() }
|
|
|
-function withUnit(value, unit) { return value === null || value === undefined || value === '' ? '' : [value, unit].filter(Boolean).join(' ') }
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * 读数 + 单位。三条纪律(维护者 2026-10-29):
|
|
|
|
|
+ * · 没值就整句不出(不拼半截 "— μS/cm");
|
|
|
|
|
+ * · **有值但后端没给单位**时如实标注「单位未发布」,不假装无单位是个读数;
|
|
|
|
|
+ * · 有单位则一律追加 —— 数值型指标不允许裸数字上屏。
|
|
|
|
|
+ */
|
|
|
|
|
+function withUnit(value, unit) {
|
|
|
|
|
+ const hasValue = value !== null && value !== undefined && String(value).trim() !== ''
|
|
|
|
|
+ if (!hasValue) return ''
|
|
|
|
|
+ return unit ? `${value} ${unit}` : `${value}(单位未发布)`
|
|
|
|
|
+}
|
|
|
function threshold(source) {
|
|
function threshold(source) {
|
|
|
const min = source.thresholdMinValue ?? source.minValue
|
|
const min = source.thresholdMinValue ?? source.minValue
|
|
|
const max = source.thresholdMaxValue ?? source.maxValue
|
|
const max = source.thresholdMaxValue ?? source.maxValue
|