perf(monitor): 监控配置加载缓存去重,避免重复全量反射扫描
- 新增 monitorConfigCache 进程内缓存(inflight 去重)与 carActionByTypeLookup - opsMonitorConfig 支持传入预加载快照与缓存根,保存后失效缓存 - 运维配置页/选中信息面板复用缓存,修复车型动作勾选与 model 不同步 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+56
-30
@@ -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 }
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user