浏览代码

refactor(cockpit): 复用当前事件过滤集合避免翻页全量扫描

三项有序算法优化第二项(112):成员资格准备与当前页投影分层,深依赖更新保留。1000条100页终态检查100000到1000;10项公开回归、4265输入8530完整输出对比与t11021项通过。测试证据只存本地,整批完成后统一推送。
ChenJZ 3 天之前
父节点
当前提交
e6e4d57976
共有 2 个文件被更改,包括 41 次插入 和 20 次删除
  1. 31 17
      src/modules/cockpit/domain/eventList.js
  2. 10 3
      src/modules/cockpit/stores/eventStore.js

+ 31 - 17
src/modules/cockpit/domain/eventList.js

@@ -574,6 +574,28 @@ export function sortEventRows(items) {
     })
 }
 
+/**
+ * 当前事件的成员资格准备:只读领域数组、终态与类型,不读页码或展示字段。
+ * 本层不自行缓存;响应替换/数组增删/状态或类型原地变化由调用方的深依赖触发重算。
+ * 返回的仍是领域行引用,分页后才投影视图,避免缓存可变展示行。
+ */
+export function prepareCurrentEventItems(domain, eventsSection, warningType = '') {
+    const current = domain || null
+    const sectionValue = eventsSection || (current && current.sections && current.sections.events) || null
+    const items = (sectionValue && sectionValue.data) || []
+    // 未知状态保留,已了结状态隐藏;类型筛选只对这一批已加载领域行生效。
+    const visibleItems = items.filter((item) => !isTerminalEventStatus(item && item.status))
+    const typeCode = typeof warningType === 'string' ? warningType.trim() : ''
+    const rows = typeCode ? visibleItems.filter((item) => item && item.type === typeCode) : visibleItems
+    return {
+        rows,
+        typeCode,
+        loaded: visibleItems.length,
+        rawLoaded: items.length,
+        hiddenTerminalCount: items.length - visibleItems.length,
+    }
+}
+
 /**
  * 当前预警面板的视图计划(票 13 在 `currentEvents` 面板的挂载内容)。
  *
@@ -589,8 +611,10 @@ export function sortEventRows(items) {
  *        「预警类型」**本地筛选**(G11):只在已加载行上过滤,不回后端。
  *        后端 `/events/current` 不收 type 参数,且当前列表本身只含"可解析设备"的子集 ——
  *        所以这里必须给出**覆盖范围**(`filterView`),不能声称筛选完整。
+ * @param {object} [prepared] 同一 domain/事件分区/有效类型码的准备结果,调用方保证成员变化时重算
+ * @param {object} [prepared] 同输入的prepareCurrentEventItems结果;调用方负责深依赖失效,不可跨批复用。
  */
-export function currentEventsView(domain, eventsSection, options = {}) {
+export function currentEventsView(domain, eventsSection, options = {}, prepared) {
     const current = domain || null
     const sectionValue = eventsSection || (current && current.sections && current.sections.events) || null
     const switchView = eventListSwitchView((current && current.mode) || EVENT_FAMILY.WARNING)
@@ -598,33 +622,23 @@ export function currentEventsView(domain, eventsSection, options = {}) {
     // 还没取过数(分区未 loaded)时说「正在取数」,不说「没有数据」——
     // 那是在报告一件还没问过的事(与 panelBlocks 的 sectionPending 同一立场)。
     const pending = !!sectionValue && sectionValue.loaded === false
-    const items = (sectionValue && sectionValue.data) || []
-    // 筛选与计数仍读取完整领域集合;分页后才调用 eventRowView 格式化本页。
-    // 不排序、不改变可见总数,组件仍只消费等级/状态/量值齐全的视图行。
     const family = switchView.key
-    // 维护者 2026-10-29 第 2 条:**当前**预警/报警只展示还在进行中的事件。
-    // 已处理完(已处理/已解除/已关闭/已取消/误报)的不进这张表;
-    // 认不出的状态保留(宁可多显示,也不能把进行中的藏掉)。过滤条数如实回显。
-    const visibleItems = items.filter((item) => !isTerminalEventStatus(item && item.status))
-    const hiddenTerminalCount = items.length - visibleItems.length
-    // G11:类型筛选**只对已加载行**生效(`row.type` 是后端 eventType,与 warning-study 的
-    // 类型 code 同一套码)。命中 0 条也照实显示 0,不把"没筛到"画成"没有预警"。
-    const typeCode = typeof options.warningType === 'string' ? options.warningType.trim() : ''
+    // 旧入口自行准备;store 可复用成员集合。标签、提示、元数据与本页字段仍在视图现算。
+    const { rows, typeCode, loaded, rawLoaded, hiddenTerminalCount } = prepared || prepareCurrentEventItems(current, sectionValue, options.warningType)
     const typeLabel = typeof options.warningTypeLabel === 'string' && options.warningTypeLabel ? options.warningTypeLabel : typeCode
-    const rows = typeCode ? visibleItems.filter((item) => item && item.type === typeCode) : visibleItems
     const filterView = typeCode
         ? {
               active: true,
               code: typeCode,
               label: typeLabel,
-              loaded: visibleItems.length,
+              loaded,
               matched: rows.length,
               // 覆盖范围**必须**随筛选一起出现:这张表只含可解析设备的当前预警(后端 G11 缺口),
               // 本地筛选只是在这一份已加载集合里做减法,不是全量。
               // 口径三件套:当前页(服务端分页,筛选不跨页)、只含可解析设备的当前预警、不是全量。
-              note: `已按预警类型「${typeLabel}」本地筛选:命中 ${rows.length} / 当前页已加载 ${visibleItems.length} 条;本列表只含可解析设备的当前预警、且筛选不跨页,不代表全部未解除预警(缺口见票 07/G11)`,
+              note: `已按预警类型「${typeLabel}」本地筛选:命中 ${rows.length} / 当前页已加载 ${loaded} 条;本列表只含可解析设备的当前预警、且筛选不跨页,不代表全部未解除预警(缺口见票 07/G11)`,
           }
-        : { active: false, code: '', label: '', loaded: visibleItems.length, matched: visibleItems.length, note: '' }
+        : { active: false, code: '', label: '', loaded, matched: loaded, note: '' }
     // 维护者 2026-10-29:**分页必须按「过滤后的可见行」算**。
     // 旧做法把后端 pagination(含已终态事件)当页码,而行已经滤掉终态 ——
     // 报警 92 条里 91 条已处理,屏上只剩 1 行却仍画出 10 页控件,还能翻到空页。
@@ -634,7 +648,7 @@ export function currentEventsView(domain, eventsSection, options = {}) {
     const pageView = pagedRows(rows, requestedPage)
     // 取全被截断(后端总数 > 已加载条数)时如实标注,不假装是全部。
     const rawTotal = Number(current && current.pagination && current.pagination.total)
-    const truncated = Number.isFinite(rawTotal) && rawTotal > items.length
+    const truncated = Number.isFinite(rawTotal) && rawTotal > rawLoaded
     const pagination = { page: pageView.page, pageCount: pageView.pageCount, total: pageView.total, pageSize: pageView.pageSize }
     // 维护者 2026-10-30 第 3 条:这两句原先画在面板体里,现在只作为「?」摘要的条目。
     const hiddenText = hiddenTerminalCount > 0 ? `已隐藏 ${hiddenTerminalCount} 条已了结事件(本表只列进行中的)` : ''

+ 10 - 3
src/modules/cockpit/stores/eventStore.js

@@ -27,7 +27,7 @@ import { defineStore } from 'pinia'
 import { SECTION_STATE, section } from '../domain/sectionState.js'
 import { REQUEST_REASON } from '../domain/requestReason.js'
 import { EVENT_FAMILY } from '../domain/events.js'
-import { currentEventsView, isKnownEventMode, refreshedEventSelection } from '../domain/eventList.js'
+import { currentEventsView, prepareCurrentEventItems, isKnownEventMode, refreshedEventSelection } from '../domain/eventList.js'
 import { fetchCurrentEvents } from '../services/eventApi.js'
 import { EVENT_DETAIL_RESULT, eventDetailView, planEventLocate } from '../domain/eventDetail.js'
 import { getCameraState, getAssociatedDeviceState, highlightMapObject, setCameraFocus } from '../services/mapService.js'
@@ -270,6 +270,13 @@ export const useEventStore = defineStore('eventStore', () => {
         return result
     }
 
+    // 只传有效类型码;label 单独重命名不应使同批成员资格重扫。
+    const effectiveWarningType = computed(() => mode.value === EVENT_FAMILY.WARNING ? warningTypeFilter.value.code : '')
+    const preparedCurrentEvents = computed(() => {
+        const domain = currentDomain.value
+        return prepareCurrentEventItems(domain, domain ? undefined : sections.value.currentEvents, effectiveWarningType.value)
+    })
+
     /** 当前「事件 + 切换」视图(领域层现算;组件与走查都读它)。 */
     const view = computed(() => {
         // G11:类型筛选只在预警族生效(报警列表没有这套类型码)。
@@ -277,9 +284,9 @@ export const useEventStore = defineStore('eventStore', () => {
         // 页码是**前端**状态(取全后按可见行分页),所以这里要把它交给视图。
         const options = { warningType: filter.code, warningTypeLabel: filter.label, page: currentPage.value }
         // 已取到数据:从领域对象读事件分区(行 + 内部状态)。
-        if (currentDomain.value) return currentEventsView(currentDomain.value, undefined, options)
+        if (currentDomain.value) return currentEventsView(currentDomain.value, undefined, options, preparedCurrentEvents.value)
         // 还没取过 / 整体失败:用整个分区驱动占位措辞(pending → 正在取数;failed → 加载失败)。
-        return currentEventsView(null, sections.value.currentEvents, options)
+        return currentEventsView(null, sections.value.currentEvents, options, preparedCurrentEvents.value)
     })
 
     /**