Ver código fonte

波形:全场点位/波表标签背景着色并修复新增站点值缺失

18922397810 4 dias atrás
pai
commit
6d08e19a06
2 arquivos alterados com 72 adições e 15 exclusões
  1. 7 0
      backend/app/services/data_service.py
  2. 65 15
      frontend/src/App.vue

+ 7 - 0
backend/app/services/data_service.py

@@ -2114,6 +2114,13 @@ class DataService:
             )
 
         result, source = self._run_with_fallback(database_query, demo_query)
+        # 图表窗口的 points 直接按当前选中的全场点位重新挂值,避免前端
+        # 在新增指标后仍使用旧的 siteValues,导致表格列显示为空。
+        if site_list and unit:
+            try:
+                self._attach_site_values(result["points"], unit, site_list)
+            except Exception:
+                pass
         # 全场点位(PKS)系列:每周期(PKS 5s)一小段、跨时间点连续的 15 分钟窗口曲线。
         if first_cycle_only and site_points:
             try:

+ 65 - 15
frontend/src/App.vue

@@ -47,6 +47,7 @@ const periodDetail = ref<PeriodDetail | null>(null)
 const periodVisible = ref(false)
 const periodLoading = ref(false)
 const labels = ref<WaveLabel[]>([])
+const refreshChartAfterPoints = ref(false)
 const labelDialogVisible = ref(false)
 const labelSaving = ref(false)
 const LABEL_SEVERITY: Record<string, number> = { 轻微: 1, 异常: 2, 严重异常: 3 }
@@ -192,8 +193,12 @@ const pointModel = computed({
   set: (value: string[]) => {
     const wave = value.filter((item) => (DEVICE_POINTS as readonly string[]).includes(item)) as DevicePoint[]
     const site = value.filter((item) => !(DEVICE_POINTS as readonly string[]).includes(item))
-    selectedDevicePoints.value = wave
-    selectedSitePoints.value = site
+    const sameWave = wave.length === selectedDevicePoints.value.length
+      && wave.every((point, index) => point === selectedDevicePoints.value[index])
+    const sameSite = site.length === selectedSitePoints.value.length
+      && site.every((point, index) => point === selectedSitePoints.value[index])
+    if (!sameWave) selectedDevicePoints.value = wave
+    if (!sameSite) selectedSitePoints.value = site
   },
 })
 const sitePointLabel = (point: SitePoint) => `${point.itemName} ${point.itemDescription}`.trim()
@@ -214,13 +219,20 @@ const fileTabs = computed(() => {
 const isSiteTab = computed(() => fileListPoint.value === SITE_TAB_LABEL)
 const windowSiteRows = computed(() => {
   if (!isSiteTab.value) return []
-  return (waveData.value?.points ?? []).map((point, index) => ({
-    index,
-    sampleTime: point.sampleTime,
-    values: Object.fromEntries(
-      selectedSitePoints.value.map((itemName) => [itemName, point.siteValues?.[itemName] ?? null]),
-    ),
-  }))
+  const latestByTime = new Map(timePoints.value.map((point) => [point.sampleTime, point]))
+  return (waveData.value?.points ?? []).map((point, index) => {
+    const latest = latestByTime.get(point.sampleTime) ?? point
+    return {
+      index,
+      sampleTime: point.sampleTime,
+      values: Object.fromEntries(
+        selectedSitePoints.value.map((itemName) => [
+          itemName,
+          point.siteValues?.[itemName] ?? latest.siteValues?.[itemName] ?? null,
+        ]),
+      ),
+    }
+  })
 })
 
 const devicePointCounts = computed<Record<string, { ab: number | null }>>(() => {
@@ -449,6 +461,7 @@ function firstRunningIndex(): number {
 async function loadTimePoints() {
   if (!selectedDevicePart.value) return
   if (!selectedDevicePoints.value.length && !isPksOnly.value) return
+  const hadWaveData = Boolean(waveData.value)
   timePointsLoading.value = true
   waveData.value = null
   errorMessage.value = ''
@@ -473,7 +486,12 @@ async function loadTimePoints() {
     chartDirty.value = true
     void loadLabels()
     void loadFaults()
+    if (refreshChartAfterPoints.value) {
+      refreshChartAfterPoints.value = false
+      if (hadWaveData) void loadWaveWindowNow()
+    }
   } catch (error) {
+    refreshChartAfterPoints.value = false
     timePoints.value = []
     referencePoints.value = []
     errorMessage.value = error instanceof Error ? error.message : '时间点读取失败'
@@ -777,11 +795,20 @@ function labelTargetFor(rowKey: string) {
   }
 }
 
+const labelLookup = computed(() => {
+  const map = new Map<string, WaveLabel[]>()
+  for (const label of labels.value) {
+    const list = map.get(label.rowKey)
+    if (list) list.push(label)
+    else map.set(label.rowKey, [label])
+  }
+  return map
+})
+
 function labelVisibleAt(rowKey: string, at: string): WaveLabel | undefined {
   const target = new Date(at.replace(' ', 'T')).getTime()
-  const candidates = labels.value.filter((label) => (
-    label.rowKey === rowKey
-    && new Date(label.startTime.replace(' ', 'T')).getTime() <= target
+  const candidates = (labelLookup.value.get(rowKey) ?? []).filter((label) => (
+    new Date(label.startTime.replace(' ', 'T')).getTime() <= target
     && target < new Date(label.endTime.replace(' ', 'T')).getTime()
   ))
   candidates.sort((a, b) => {
@@ -792,6 +819,26 @@ function labelVisibleAt(rowKey: string, at: string): WaveLabel | undefined {
   return candidates[0]
 }
 
+const LABEL_CELL_TINTS: Record<string, string> = {
+  轻微: '#fff7e0',
+  异常: '#ffedd9',
+  严重异常: '#ffe3e3',
+}
+
+function labelCellStyle(rowKey: string, at: string) {
+  const label = labelVisibleAt(rowKey, at)
+  const tint = label ? LABEL_CELL_TINTS[label.labelType] : undefined
+  return tint ? { backgroundColor: tint } : undefined
+}
+
+function siteCellStyle(itemName: string, sampleTime: string) {
+  return labelCellStyle(itemName, sampleTime)
+}
+
+function waveRowStyle(file: WaveWindowFile) {
+  return labelCellStyle(file.devicePoint, file.sampleTime)
+}
+
 function labelInputFromForm() {
   return {
     target_type: labelForm.targetType,
@@ -1004,7 +1051,10 @@ watch(unitNumber, async () => {
 watch(selectedSitePoints, (points) => {
   if (!initialized.value) return
   if (selectedDevicePart.value) rememberPart(selectedDevicePart.value)
-  if (isPksMode.value) scheduleTimePoints()
+  if (isPksMode.value) {
+    if (waveData.value) refreshChartAfterPoints.value = true
+    scheduleTimePoints()
+  }
   if (points.length) fileListPoint.value = SITE_TAB_LABEL
 })
 function locateFile(file: WaveWindowFile) {
@@ -1347,7 +1397,7 @@ onBeforeUnmount(() => {
                 <tr v-for="row in windowSiteRows" :key="row.index">
                   <td class="mono">{{ row.index + 1 }}</td>
                   <td class="mono">{{ row.sampleTime }}</td>
-                  <td v-for="itemName in selectedSitePoints" :key="itemName" class="mono">{{ row.values[itemName] ?? '—' }}</td>
+                  <td v-for="itemName in selectedSitePoints" :key="itemName" class="mono" :style="siteCellStyle(itemName, row.sampleTime)">{{ row.values[itemName] ?? '—' }}</td>
                 </tr>
               </tbody>
             </table>
@@ -1366,7 +1416,7 @@ onBeforeUnmount(() => {
               </tr>
             </thead>
             <tbody>
-              <tr v-for="file in windowFiles" :key="file.id">
+              <tr v-for="file in windowFiles" :key="file.id" :style="waveRowStyle(file)">
                 <td class="mono">{{ file.id }}</td>
                 <td>{{ file.pointName }}</td>
                 <td>{{ file.devicePoint }}</td>