|
@@ -37,7 +37,7 @@ import { EVENT_FAMILY, LEVEL_COLORS } from './events.js'
|
|
|
import { LEVEL_TAGS, DEFAULT_TONE } from './statusTones.js'
|
|
import { LEVEL_TAGS, DEFAULT_TONE } from './statusTones.js'
|
|
|
import { DETAIL_PLACEHOLDER } from './detailFlow.js'
|
|
import { DETAIL_PLACEHOLDER } from './detailFlow.js'
|
|
|
import { workOrderStateName } from './workOrder.js'
|
|
import { workOrderStateName } from './workOrder.js'
|
|
|
-import { serverPageView } from './pagination.js'
|
|
|
|
|
|
|
+import { pagedRows } from './pagination.js'
|
|
|
// 票 67:本面板"不随当前范围变化"的措辞(单一来源,面板不各写一份)。
|
|
// 票 67:本面板"不随当前范围变化"的措辞(单一来源,面板不各写一份)。
|
|
|
import { districtEchoView } from './filterCaliber.js'
|
|
import { districtEchoView } from './filterCaliber.js'
|
|
|
|
|
|
|
@@ -47,6 +47,17 @@ export const EVENT_STATUS_TEXT = {
|
|
|
PENDING: '待处理',
|
|
PENDING: '待处理',
|
|
|
PROCESSING: '处理中',
|
|
PROCESSING: '处理中',
|
|
|
HANDLING: '处置中',
|
|
HANDLING: '处置中',
|
|
|
|
|
+ // 实测取值(维护者 2026-10-29:卡片状态原样显示英文):
|
|
|
|
|
+ // 报警侧用 UNHANDLED/HANDLED,预警侧另有 RECEIVED/CLOSED/DRAFT 等字典值。
|
|
|
|
|
+ UNHANDLED: '未处理',
|
|
|
|
|
+ HANDLED: '已处理',
|
|
|
|
|
+ RECEIVED: '已接收',
|
|
|
|
|
+ CLEARED: '已解除',
|
|
|
|
|
+ CLOSED: '已关闭',
|
|
|
|
|
+ DRAFT: '草稿',
|
|
|
|
|
+ CANCELLED: '已取消',
|
|
|
|
|
+ MISREPORT: '误报',
|
|
|
|
|
+ RESOLVED: '已解除',
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -258,6 +269,19 @@ export function eventLevelTag(level) {
|
|
|
* 事件 `status` → 展示文本。已知取值直译;**认不出的回原码**(数据值照原样显示才是
|
|
* 事件 `status` → 展示文本。已知取值直译;**认不出的回原码**(数据值照原样显示才是
|
|
|
* 诚实的,与 `CODE_TEXT` 同一立场);空串回空串(不把"没有状态"画成某个状态)。
|
|
* 诚实的,与 `CODE_TEXT` 同一立场);空串回空串(不把"没有状态"画成某个状态)。
|
|
|
*/
|
|
*/
|
|
|
|
|
+/**
|
|
|
|
|
+ * 终态(已处理完)判定(维护者 2026-10-29 第 2 条:当前预警/报警不展示已处置/已取消)。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 「当前」列表的语义是**还在进行中的**,已经处理完的不该混进来。
|
|
|
|
|
+ * 判定用词表而非猜:命中终态词就过滤;认不出的状态**保留**(宁可多显示,
|
|
|
|
|
+ * 也不能因为认不出把还在进行中的事件藏掉)。
|
|
|
|
|
+ */
|
|
|
|
|
+const TERMINAL_STATUS = new Set(['HANDLED', 'RESOLVED', 'CLEARED', 'CLOSED', 'CANCELLED', 'MISREPORT'])
|
|
|
|
|
+export function isTerminalEventStatus(status) {
|
|
|
|
|
+ const value = typeof status === 'string' ? status.trim().toUpperCase() : ''
|
|
|
|
|
+ return TERMINAL_STATUS.has(value)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
export function eventStatusText(status) {
|
|
export function eventStatusText(status) {
|
|
|
const value = typeof status === 'string' ? status.trim() : ''
|
|
const value = typeof status === 'string' ? status.trim() : ''
|
|
|
if (!value) return ''
|
|
if (!value) return ''
|
|
@@ -296,8 +320,11 @@ export function eventSectionHasContent(section) {
|
|
|
export function toEventItem(raw) {
|
|
export function toEventItem(raw) {
|
|
|
const reader = fieldReader(raw, 'eventItem')
|
|
const reader = fieldReader(raw, 'eventItem')
|
|
|
return {
|
|
return {
|
|
|
- // 统一键:预警与报警都用 eventNo(契约里两族同一字段名,不存在同名冲突)。
|
|
|
|
|
|
|
+ // 内部键:预警与报警都用 eventNo(契约里两族同一字段名,不存在同名冲突)。
|
|
|
|
|
+ // 注意报警的 eventNo 是**主键 UUID**,上屏必须用 eventCode(业务编号 alarmNo)
|
|
|
|
|
+ // —— 维护者 2026-10-29:「报警详情的事件编码应该用 code 而非 id」。
|
|
|
code: reader.str('eventNo'),
|
|
code: reader.str('eventNo'),
|
|
|
|
|
+ codeText: reader.optionalStr('eventCode') || reader.str('eventNo'),
|
|
|
name: reader.optionalStr('eventName'),
|
|
name: reader.optionalStr('eventName'),
|
|
|
type: reader.optionalStr('eventType'),
|
|
type: reader.optionalStr('eventType'),
|
|
|
level: reader.optionalNum('level'),
|
|
level: reader.optionalNum('level'),
|
|
@@ -413,6 +440,47 @@ export function toCurrentEvents(raw) {
|
|
|
* 等级标签与状态文本都从领域行现算 —— 组件里不再出现第二份映射。
|
|
* 等级标签与状态文本都从领域行现算 —— 组件里不再出现第二份映射。
|
|
|
* @param {object} eventItem `toEventItem()` 的结果
|
|
* @param {object} eventItem `toEventItem()` 的结果
|
|
|
*/
|
|
*/
|
|
|
|
|
+/**
|
|
|
|
|
+ * 事件处理状态 → 语义色键(维护者 2026-10-29:状态也用颜色区分,不只靠文字)。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 只登记**已知**状态;认不出的走中性灰。状态码是后端未冻结的自由串,
|
|
|
|
|
+ * 所以这里按语义归一(进行中/待处理/处理中/已解除…),不硬绑定单一拼写。
|
|
|
|
|
+ */
|
|
|
|
|
+export function eventStatusTone(status) {
|
|
|
|
|
+ const value = typeof status === 'string' ? status.trim().toUpperCase() : ''
|
|
|
|
|
+ if (!value) return DEFAULT_TONE
|
|
|
|
|
+ if (/^(ACTIVE|PENDING|UNHANDLED|WAIT|待处理|进行中|待核实|待确认)$/.test(value)) return 'lv2'
|
|
|
|
|
+ if (/^(PROCESSING|HANDLING|HANDLED|DEALING|处理中|处置中|已接单)$/.test(value)) return 'lv3'
|
|
|
|
|
+ if (/^(RESOLVED|CLEAR|CLEARED|CLOSED|DONE|已解除|已关闭|已处置|已结案)$/.test(value)) return 'lv4'
|
|
|
|
|
+ if (/^(CANCELLED|MISREPORT|作废|已取消|误报)$/.test(value)) return DEFAULT_TONE
|
|
|
|
|
+ return DEFAULT_TONE
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 卡片上的一句话摘要:**这件事到底发生了什么**(维护者 2026-10-29)。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 组合顺序有意:指标名 → 实际值(带单位)→ 阈值。缺哪段少哪段,
|
|
|
|
|
+ * 一段都没有时回空串(渲染层据此不画那一行),绝不编造一句通用话术。
|
|
|
|
|
+ */
|
|
|
|
|
+export function eventSummaryText(item, quantity) {
|
|
|
|
|
+ const current = item || {}
|
|
|
|
|
+ // 指标名一律**中文化**:后端下发的是采集协议码(WARN-WATER-LEVEL 等),
|
|
|
|
|
+ // 直接印码等于把接口词汇漏上大屏(维护者 2026-10-29 第 2 条)。
|
|
|
|
|
+ const rawMetric = current.type || current.warningType || current.eventType || ''
|
|
|
|
|
+ const metric = rawMetric ? eventTypeText(rawMetric, current.typeName || current.warningTypeName || current.eventTypeName) : ''
|
|
|
|
|
+ const parts = []
|
|
|
|
|
+ if (metric) parts.push(metric)
|
|
|
|
|
+ if (quantity && quantity !== DETAIL_PLACEHOLDER) parts.push(`实际值 ${quantity}`)
|
|
|
|
|
+ const min = current.thresholdMinValue
|
|
|
|
|
+ const max = current.thresholdMaxValue
|
|
|
|
|
+ if (typeof min === 'number' || typeof max === 'number') {
|
|
|
|
|
+ const range = typeof min === 'number' && typeof max === 'number' ? `${min} ~ ${max}`
|
|
|
|
|
+ : typeof min === 'number' ? `≥ ${min}` : `≤ ${max}`
|
|
|
|
|
+ parts.push(`阈值 ${current.unit ? range + ' ' + current.unit : range}`)
|
|
|
|
|
+ }
|
|
|
|
|
+ return parts.join(' · ')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
export function eventRowView(eventItem) {
|
|
export function eventRowView(eventItem) {
|
|
|
const item = eventItem || {}
|
|
const item = eventItem || {}
|
|
|
const tag = eventLevelTag(item.level)
|
|
const tag = eventLevelTag(item.level)
|
|
@@ -427,7 +495,9 @@ export function eventRowView(eventItem) {
|
|
|
key: item.code || DETAIL_PLACEHOLDER,
|
|
key: item.code || DETAIL_PLACEHOLDER,
|
|
|
family: item.family || '',
|
|
family: item.family || '',
|
|
|
// 切换后的字段标签(预警/报警各自一套,来自切换视图 —— 与状态标签同步变化)。
|
|
// 切换后的字段标签(预警/报警各自一套,来自切换视图 —— 与状态标签同步变化)。
|
|
|
|
|
+ // `code` 是内部键(取数用);`codeText` 是上屏的业务编号(报警为 alarmNo)。
|
|
|
code: item.code,
|
|
code: item.code,
|
|
|
|
|
+ codeText: item.codeText || item.code || '',
|
|
|
identityCode: item.code || '',
|
|
identityCode: item.code || '',
|
|
|
identityFamily: String(item.family || '').toUpperCase(),
|
|
identityFamily: String(item.family || '').toUpperCase(),
|
|
|
identityReason: item.code ? '' : '缺少事件编号,无法展开',
|
|
identityReason: item.code ? '' : '缺少事件编号,无法展开',
|
|
@@ -435,6 +505,11 @@ export function eventRowView(eventItem) {
|
|
|
type: item.type || '',
|
|
type: item.type || '',
|
|
|
level: { tone: tag.tone, label: tag.label },
|
|
level: { tone: tag.tone, label: tag.label },
|
|
|
statusText: eventStatusText(item.status),
|
|
statusText: eventStatusText(item.status),
|
|
|
|
|
+ // 处理状态也要能按颜色区分(维护者 2026-10-29):色键与文字同源,
|
|
|
|
|
+ // 认不出的状态落中性灰 + 原码 —— 不猜成"处理中"。
|
|
|
|
|
+ statusTone: eventStatusTone(item.status),
|
|
|
|
|
+ // 卡片上"到底发生了什么"的一句话:指标 + 实际值/阈值,缺什么就少说什么。
|
|
|
|
|
+ summaryText: eventSummaryText(item, quantity),
|
|
|
eventTime: item.eventTime || '',
|
|
eventTime: item.eventTime || '',
|
|
|
// 列表列的短读法(2026-10-13 维护者反馈:ISO 全串把右侧列挤出可视区被裁)。
|
|
// 列表列的短读法(2026-10-13 维护者反馈:ISO 全串把右侧列挤出可视区被裁)。
|
|
|
// 只截断显示、不改排序键(排序仍用 eventTime 原值);解析不了回原串。
|
|
// 只截断显示、不改排序键(排序仍用 eventTime 原值);解析不了回原串。
|
|
@@ -531,7 +606,12 @@ export function currentEventsView(domain, eventsSection, options = {}) {
|
|
|
// 票 61:**行=后端返回的这一页**,前端不排、不再截 12 条(服务端分页按后端返回序,
|
|
// 票 61:**行=后端返回的这一页**,前端不排、不再截 12 条(服务端分页按后端返回序,
|
|
|
// 总数读 `pagination.total`,条文 7)。空/失败时仍给安全的一页视图(pageCount=1)。
|
|
// 总数读 `pagination.total`,条文 7)。空/失败时仍给安全的一页视图(pageCount=1)。
|
|
|
const family = switchView.key
|
|
const family = switchView.key
|
|
|
- const allRows = items.map((item) => ({
|
|
|
|
|
|
|
+ // 维护者 2026-10-29 第 2 条:**当前**预警/报警只展示还在进行中的事件。
|
|
|
|
|
+ // 已处理完(已处理/已解除/已关闭/已取消/误报)的不进这张表;
|
|
|
|
|
+ // 认不出的状态保留(宁可多显示,也不能把进行中的藏掉)。过滤条数如实回显。
|
|
|
|
|
+ const visibleItems = items.filter((item) => !isTerminalEventStatus(item && item.status))
|
|
|
|
|
+ const hiddenTerminalCount = items.length - visibleItems.length
|
|
|
|
|
+ const allRows = visibleItems.map((item) => ({
|
|
|
...eventRowView(item),
|
|
...eventRowView(item),
|
|
|
family,
|
|
family,
|
|
|
identityFamily: family,
|
|
identityFamily: family,
|
|
@@ -554,7 +634,21 @@ export function currentEventsView(domain, eventsSection, options = {}) {
|
|
|
note: `已按预警类型「${typeLabel}」本地筛选:命中 ${rows.length} / 当前页已加载 ${allRows.length} 条;本列表只含可解析设备的当前预警、且筛选不跨页,不代表全部未解除预警(缺口见票 07/G11)`,
|
|
note: `已按预警类型「${typeLabel}」本地筛选:命中 ${rows.length} / 当前页已加载 ${allRows.length} 条;本列表只含可解析设备的当前预警、且筛选不跨页,不代表全部未解除预警(缺口见票 07/G11)`,
|
|
|
}
|
|
}
|
|
|
: { active: false, code: '', label: '', loaded: allRows.length, matched: allRows.length, note: '' }
|
|
: { active: false, code: '', label: '', loaded: allRows.length, matched: allRows.length, note: '' }
|
|
|
- const pagination = serverPageView(current && current.pagination, items.length)
|
|
|
|
|
|
|
+ // 维护者 2026-10-29:**分页必须按「过滤后的可见行」算**。
|
|
|
|
|
+ // 旧做法把后端 pagination(含已终态事件)当页码,而行已经滤掉终态 ——
|
|
|
|
|
+ // 报警 92 条里 91 条已处理,屏上只剩 1 行却仍画出 10 页控件,还能翻到空页。
|
|
|
|
|
+ // 现在:事件一次性取全(见 store 的 CURRENT_EVENTS_FETCH_SIZE),这里对可见行前端分页,
|
|
|
|
|
+ // 「共 N 条 / 共 Y 页」与用户真的看得到的行严格一致。
|
|
|
|
|
+ const requestedPage = Number.isInteger(options.page) && options.page > 0 ? options.page : 1
|
|
|
|
|
+ const pageView = pagedRows(rows, requestedPage)
|
|
|
|
|
+ // 取全被截断(后端总数 > 已加载条数)时如实标注,不假装是全部。
|
|
|
|
|
+ const rawTotal = Number(current && current.pagination && current.pagination.total)
|
|
|
|
|
+ const truncated = Number.isFinite(rawTotal) && rawTotal > items.length
|
|
|
|
|
+ const pagination = { page: pageView.page, pageCount: pageView.pageCount, total: pageView.total, pageSize: pageView.pageSize }
|
|
|
|
|
+ // 维护者 2026-10-30 第 3 条:这两句原先画在面板体里,现在只作为「?」摘要的条目。
|
|
|
|
|
+ const hiddenText = hiddenTerminalCount > 0 ? `已隐藏 ${hiddenTerminalCount} 条已了结事件(本表只列进行中的)` : ''
|
|
|
|
|
+ const truncatedText = truncated ? '事件过多,只加载了部分(已按可见行分页)' : ''
|
|
|
|
|
+ const filterNote = [hiddenText, truncatedText].filter(Boolean).join(' · ')
|
|
|
const placeholder = hasContent
|
|
const placeholder = hasContent
|
|
|
? ''
|
|
? ''
|
|
|
: pending
|
|
: pending
|
|
@@ -571,8 +665,9 @@ export function currentEventsView(domain, eventsSection, options = {}) {
|
|
|
switchView,
|
|
switchView,
|
|
|
section: sectionValue,
|
|
section: sectionValue,
|
|
|
hasContent,
|
|
hasContent,
|
|
|
- rows,
|
|
|
|
|
- total: pagination.total,
|
|
|
|
|
|
|
+ // 行=**当前页**的可见行(过滤后前端分页);`total` 是可见行总数。
|
|
|
|
|
+ rows: pageView.rows,
|
|
|
|
|
+ total: pageView.total,
|
|
|
placeholder,
|
|
placeholder,
|
|
|
pagination: hasContent ? pagination : null,
|
|
pagination: hasContent ? pagination : null,
|
|
|
updatedAt: (current && current.updatedAt) || '',
|
|
updatedAt: (current && current.updatedAt) || '',
|
|
@@ -588,5 +683,16 @@ export function currentEventsView(domain, eventsSection, options = {}) {
|
|
|
gap: '',
|
|
gap: '',
|
|
|
// G11:类型本地筛选的回显与覆盖范围(组件画选中态 + 「清除筛选」入口)。
|
|
// G11:类型本地筛选的回显与覆盖范围(组件画选中态 + 「清除筛选」入口)。
|
|
|
filterView,
|
|
filterView,
|
|
|
|
|
+ // 「当前」表已过滤掉终态:过滤了几条如实说,免得读成「后端只回了这些」。
|
|
|
|
|
+ // 维护者 2026-10-30 第 3 条:这句话不再画在面板体里,改成面板标题栏「?」摘要的一条
|
|
|
|
|
+ //(`filterNote`)—— 面板体只留数据本身。
|
|
|
|
|
+ hiddenTerminalCount,
|
|
|
|
|
+ // 取全被截断时说清(后端总数 > 已加载),不假装是全部。
|
|
|
|
|
+ truncated,
|
|
|
|
|
+ filterNote,
|
|
|
|
|
+// 维护者 2026-10-30 第 3 条:片区回显也改走「?」摘要(面板体只留数据与操作)。
|
|
|
|
|
+ scopeNote: (current && current.filters ? districtEchoView(current.filters).note : '') || '',
|
|
|
|
|
+ // 信封快照时间也改走摘要(原来占面板体一整行)。
|
|
|
|
|
+ snapshotNote: [TIME_LABELS.snapshot, (current && current.updatedAt) || ''].filter(Boolean).join(' '),
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|