perf(monitor): 监控配置加载缓存去重,避免重复全量反射扫描

- 新增 monitorConfigCache 进程内缓存(inflight 去重)与 carActionByTypeLookup
- opsMonitorConfig 支持传入预加载快照与缓存根,保存后失效缓存
- 运维配置页/选中信息面板复用缓存,修复车型动作勾选与 model 不同步

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zhaowei.huang
2026-05-31 00:16:49 +08:00
co-authored by Cursor
parent 061acad9de
commit e6c49a0d78
5 changed files with 336 additions and 177 deletions
@@ -211,6 +211,7 @@ import {
import { OPS_WHITELIST, type OpsAction } from '@/types/ops'
import { executeOp } from '@/api/ops'
import { useAuthStore } from '@/stores/auth'
import { fetchMonitorConfigCached } from '@/utils/monitorConfigCache'
import type { Car } from '@/types/car'
import type { Mission } from '@/types/mission'
import type { SelectedObjectRef } from '@/types/workbench'
@@ -557,7 +558,7 @@ function selectionKey(sel: SelectedObjectRef | null): string {
async function loadMonitorRuntimeConfig(force = false) {
if (monitorConfigLoaded.value && !force) return
try {
const mc = await reflectionApi.getMonitorConfig()
const mc = await fetchMonitorConfigCached(force)
carActionByType.value = { ...(mc.config.carActionByType ?? {}) }
siteActionKeys.value = [...(mc.config.site?.methods ?? [])]
trackActionKeys.value = [...(mc.config.track?.methods ?? [])]
@@ -570,6 +571,40 @@ async function loadMonitorRuntimeConfig(force = false) {
}
}
type BundlePayload = Awaited<ReturnType<typeof reflectionApi.getBundle>>
function applyBundle(bundle: BundlePayload, fallbackName?: string) {
bundleName.value = bundle.summary?.name ?? fallbackName ?? ''
bundleTypeName.value = bundle.typeName ?? ''
bundleFullTypeName.value =
('fullTypeName' in bundle && bundle.fullTypeName) ? bundle.fullTypeName : bundle.typeName ?? ''
fieldMap.value = { ...bundle.fields }
for (const f of bundle.fieldList ?? []) {
fieldMap.value[f.key] = f.value
}
statusRows.value = bundle.status ?? []
allMethods.value = bundle.methods ?? []
}
/** 车辆列表已有投影数据时先展示,bundle 返回后再补齐。 */
function applyCarListPreview(): boolean {
const car = findCarInList()
if (!car) return false
bundleName.value = car.name
bundleTypeName.value = car.typeName ?? ''
bundleFullTypeName.value = car.typeName ?? ''
const pct = Math.round(car.batterySoc <= 1 ? car.batterySoc * 100 : car.batterySoc)
fieldMap.value = {
x: String(car.x),
y: String(car.y),
th: String(car.theta),
batterySoc: String(pct)
}
statusRows.value = []
allMethods.value = []
return true
}
async function loadAll() {
if (!props.selection) {
bundleName.value = ''
@@ -584,7 +619,9 @@ async function loadAll() {
if (!rk || !Number.isFinite(idNum)) return
const soft = hydrated.value
if (!soft) initialLoading.value = true
const listPreview = !soft && viewKind.value === 'vehicle' && applyCarListPreview()
if (listPreview) hydrated.value = true
if (!soft) initialLoading.value = !listPreview
else actionsLoading.value = true
try {
@@ -592,34 +629,14 @@ async function loadAll() {
// 运营端只读:只取 bundle 展示详情,不加载 reflection 动作/运营配置;
// 失败时静默降级,由 findCarInList() 用 cars 列表数据兜底。
const bundle = await reflectionApi.getBundle(rk, idNum)
bundleName.value = bundle.summary?.name ?? props.selection.name ?? ''
bundleTypeName.value = bundle.typeName ?? ''
bundleFullTypeName.value =
('fullTypeName' in bundle && bundle.fullTypeName) ? bundle.fullTypeName : bundle.typeName ?? ''
fieldMap.value = { ...bundle.fields }
for (const f of bundle.fieldList ?? []) {
fieldMap.value[f.key] = f.value
}
statusRows.value = bundle.status ?? []
applyBundle(bundle, props.selection.name ?? '')
allMethods.value = []
hydrated.value = true
return
}
await loadMonitorRuntimeConfig(!soft)
const [bundle, methods] = await Promise.all([
reflectionApi.getBundle(rk, idNum),
reflectionApi.listMethods(rk, idNum)
])
bundleName.value = bundle.summary?.name ?? props.selection.name ?? ''
bundleTypeName.value = bundle.typeName ?? ''
bundleFullTypeName.value =
('fullTypeName' in bundle && bundle.fullTypeName) ? bundle.fullTypeName : bundle.typeName ?? ''
fieldMap.value = { ...bundle.fields }
for (const f of bundle.fieldList ?? []) {
fieldMap.value[f.key] = f.value
}
statusRows.value = bundle.status ?? []
allMethods.value = methods
await loadMonitorRuntimeConfig(false)
const bundle = await reflectionApi.getBundle(rk, idNum)
applyBundle(bundle, props.selection.name ?? '')
hydrated.value = true
} catch (e) {
if (props.readOnly) {
@@ -689,14 +706,23 @@ async function onSitePickOnMap() {
}
let loadToken = 0
let lastLoadedSelectionKey = ''
watch(
() => [selectionKey(props.selection), props.refreshKey] as const,
() => {
async (curr, prev) => {
const token = ++loadToken
void (async () => {
await loadAll()
// refreshKey 单独变化(如运营维护保存动作白名单):只刷新配置,不重复拉 bundle。
if (prev && curr[0] === prev[0] && curr[0] && curr[1] !== prev[1]) {
await loadMonitorRuntimeConfig(true)
if (token !== loadToken) return
})()
return
}
if (curr[0] !== lastLoadedSelectionKey) {
hydrated.value = false
lastLoadedSelectionKey = curr[0]
}
await loadAll()
if (token !== loadToken) return
},
{ immediate: true }
)