|
|
@@ -25,16 +25,18 @@
|
|
|
* 唯一保留的联动是预警类型排行 → 切「预警处置」模式(落点在 `CockpitShell` 的
|
|
|
* `handlePanelAction`,筛选状态在 `eventStore`)。
|
|
|
*/
|
|
|
-import { ref } from 'vue'
|
|
|
+import { ref, watch } from 'vue'
|
|
|
import { defineStore } from 'pinia'
|
|
|
import { SECTION_STATE, section } from '../domain/sectionState.js'
|
|
|
import { REQUEST_REASON } from '../domain/requestReason.js'
|
|
|
import { OVERVIEW_PANEL_IDS } from '../domain/overviewPanels.js'
|
|
|
+import { sameInputs } from '../domain/panelViewCache.js'
|
|
|
import {
|
|
|
fetchAlarmDisposal,
|
|
|
fetchPerception,
|
|
|
fetchPipeNetwork,
|
|
|
fetchWarningStudy,
|
|
|
+ snapshotOverviewRequest,
|
|
|
} from '../services/overviewApi.js'
|
|
|
|
|
|
/** 未取数时的分区(与其它 store 同形,`loaded:false` 表示"还没请求过")。 */
|
|
|
@@ -76,9 +78,23 @@ export const useOverviewStore = defineStore('overviewStore', () => {
|
|
|
|
|
|
/** 请求序号:重取时旧的在途响应作废(迟到的四块响应整批丢弃,不半新半旧)。 */
|
|
|
let token = 0
|
|
|
+ let inFlight = null
|
|
|
+ let applyingInputs = false
|
|
|
+
|
|
|
+ function invalidateIfChanged() {
|
|
|
+ if (!inFlight || sameInputs(inFlight.inputs, snapshotOverviewRequest(activeOptions()).inputs)) return
|
|
|
+ token += 1
|
|
|
+ inFlight = null
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+
|
|
|
+ watch([industry, scope], () => {
|
|
|
+ if (!applyingInputs) invalidateIfChanged()
|
|
|
+ }, { flush: 'sync' })
|
|
|
|
|
|
function configure(options = {}) {
|
|
|
requestOptions = { ...requestOptions, ...options }
|
|
|
+ if (!applyingInputs) invalidateIfChanged()
|
|
|
}
|
|
|
|
|
|
/** 当前请求条件(全局筛选 + 真实配置)。 */
|
|
|
@@ -101,47 +117,85 @@ export const useOverviewStore = defineStore('overviewStore', () => {
|
|
|
* @returns {Promise<object>} 取完后的全部分区
|
|
|
*/
|
|
|
async function loadPanels(options = {}) {
|
|
|
- if (options.industry !== undefined) industry.value = options.industry || ''
|
|
|
- if (options.scope !== undefined) scope.value = options.scope || ''
|
|
|
- const injections = Object.fromEntries(
|
|
|
- Object.entries(options).filter(([key]) => key !== 'industry' && key !== 'scope'),
|
|
|
- )
|
|
|
- if (Object.keys(injections).length) configure(injections)
|
|
|
+ // 一次load批量应用选项,sync watcher不对中间组合发失效通知。
|
|
|
+ const wasApplying = applyingInputs
|
|
|
+ applyingInputs = true
|
|
|
+ try {
|
|
|
+ if (options.industry !== undefined) industry.value = options.industry || ''
|
|
|
+ if (options.scope !== undefined) scope.value = options.scope || ''
|
|
|
+ const injections = Object.fromEntries(
|
|
|
+ Object.entries(options).filter(([key]) => key !== 'industry' && key !== 'scope'),
|
|
|
+ )
|
|
|
+ if (Object.keys(injections).length) configure(injections)
|
|
|
+ } finally {
|
|
|
+ applyingInputs = wasApplying
|
|
|
+ }
|
|
|
+
|
|
|
+ const snapshot = snapshotOverviewRequest(activeOptions())
|
|
|
+ if (snapshot.shareable && inFlight && inFlight.shareable && sameInputs(inFlight.inputs, snapshot.inputs)) return inFlight.promise
|
|
|
+
|
|
|
+ let resolve, reject
|
|
|
+ const promise = new Promise((yes, no) => { resolve = yes; reject = no })
|
|
|
+ const flight = { inputs: snapshot.inputs, token: ++token, promise, shareable: snapshot.shareable }
|
|
|
+ // 先发布deferred,loading watcher与同步fake fetch重入都能join;dispatch不延后。
|
|
|
+ inFlight = flight
|
|
|
+ try {
|
|
|
+ loading.value = true
|
|
|
+ runPanels(flight, snapshot.options).then(resolve, reject)
|
|
|
+ } catch (error) {
|
|
|
+ // sync watcher可在runner启动前抛错;只清自己的slot,deferred必须结清。
|
|
|
+ if (flight.token === token && inFlight === flight) {
|
|
|
+ inFlight = null
|
|
|
+ try { loading.value = false } catch (cleanupError) { error = cleanupError }
|
|
|
+ }
|
|
|
+ reject(error)
|
|
|
+ }
|
|
|
+ return promise
|
|
|
+ }
|
|
|
|
|
|
- token += 1
|
|
|
- const mine = token
|
|
|
- loading.value = true
|
|
|
-
|
|
|
- const results = await Promise.all(
|
|
|
- OVERVIEW_PANEL_IDS.map(async (panelId) => {
|
|
|
- try {
|
|
|
- return [panelId, await FETCHERS[panelId](activeOptions())]
|
|
|
- } catch (error) {
|
|
|
- // 与票 11/17/18/20 同款兜底:适配层不抛,但转换层万一抛了要收成一条失败分区,
|
|
|
- // 不能把面板卡在「加载中」(用户还得能看见其它三块)。
|
|
|
- return [panelId, section(SECTION_STATE.FAILED, { reason: REQUEST_REASON.UNREACHABLE, data: null })]
|
|
|
- }
|
|
|
- }),
|
|
|
- )
|
|
|
-
|
|
|
- // 迟到的响应整批丢弃:旧一轮的数据不得盖掉新一轮。
|
|
|
- if (mine !== token) return sections.value
|
|
|
-
|
|
|
- const next = { ...sections.value }
|
|
|
- for (const [panelId, value] of results) next[panelId] = { ...value, loaded: true }
|
|
|
- sections.value = next
|
|
|
- loading.value = false
|
|
|
- return sections.value
|
|
|
+ async function runPanels(flight, options) {
|
|
|
+ try {
|
|
|
+ if (flight.token !== token || inFlight !== flight) return sections.value
|
|
|
+ const results = await Promise.all(
|
|
|
+ OVERVIEW_PANEL_IDS.map(async (panelId) => {
|
|
|
+ try {
|
|
|
+ return [panelId, await FETCHERS[panelId](options)]
|
|
|
+ } catch (error) {
|
|
|
+ // 每块独立兜底:转换层意外抛错也不打掉其它三块。
|
|
|
+ return [panelId, section(SECTION_STATE.FAILED, { reason: REQUEST_REASON.UNREACHABLE, data: null })]
|
|
|
+ }
|
|
|
+ }),
|
|
|
+ )
|
|
|
+ // 迟到整批丢弃;原地改plain config而没有第二次load也不能写回旧值。
|
|
|
+ if (flight.token !== token || inFlight !== flight || !sameInputs(flight.inputs, snapshotOverviewRequest(activeOptions()).inputs)) return sections.value
|
|
|
+ const next = { ...sections.value }
|
|
|
+ for (const [panelId, value] of results) next[panelId] = { ...value, loaded: true }
|
|
|
+ sections.value = next
|
|
|
+ return sections.value
|
|
|
+ } finally {
|
|
|
+ if (flight.token === token && inFlight === flight) {
|
|
|
+ inFlight = null
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/** 仅测试用:回到未加载状态。 */
|
|
|
function reset() {
|
|
|
token += 1
|
|
|
+ inFlight = null
|
|
|
+ const wasApplying = applyingInputs
|
|
|
+ applyingInputs = true
|
|
|
+ try {
|
|
|
+ requestOptions = {}
|
|
|
+ industry.value = ''
|
|
|
+ scope.value = ''
|
|
|
+ } finally {
|
|
|
+ applyingInputs = wasApplying
|
|
|
+ }
|
|
|
+ // inputs先复位,再通知状态;sections watcher若开启新flight,不清它的loading。
|
|
|
sections.value = Object.fromEntries(OVERVIEW_PANEL_IDS.map((panelId) => [panelId, pending()]))
|
|
|
- loading.value = false
|
|
|
- requestOptions = {}
|
|
|
- industry.value = ''
|
|
|
- scope.value = ''
|
|
|
+ if (!inFlight) loading.value = false
|
|
|
}
|
|
|
|
|
|
return {
|