Bläddra i källkod

Show per-part abnormal counts and gradient abnormal colors in the point dropdown

18922397810 1 vecka sedan
förälder
incheckning
460d8884b8

+ 1 - 0
backend/app/main.py

@@ -105,6 +105,7 @@ def query_options(_auth: str = Depends(require_auth)) -> dict[str, Any]:
     try:
         result = data_service.query_options()
         result["pretrainCounts"] = pretrain_service.recorded_counts()
+        result["abnormalCounts"] = data_service.abnormal_counts()
         return result
     except Exception as error:
         raise HTTPException(status_code=503, detail=str(error)) from error

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

@@ -180,6 +180,29 @@ class DataService:
             "notice": self._source_notice(source),
         }
 
+    def abnormal_counts(self) -> dict[str, int]:
+        """每个 point_name 的异常文件数量(tspluse_status > 0)。"""
+
+        def database_query():
+            with get_connection() as connection:
+                with connection.cursor() as cursor:
+                    cursor.execute(
+                        """
+                        SELECT point_name, COUNT(*) AS cnt
+                        FROM wave_file
+                        WHERE tspluse_status > 0
+                        GROUP BY point_name
+                        """,
+                    )
+                    rows = cursor.fetchall()
+            return {row["point_name"]: int(row["cnt"]) for row in rows}
+
+        def demo_query():
+            return {}
+
+        result, _source = self._run_with_fallback(database_query, demo_query)
+        return result
+
     def tspluse_ruler(self) -> dict[str, Any]:
         """压力部位 tspluse_status 的全局标尺,进入页面时只查询一次。"""
 

+ 24 - 3
frontend/src/App.vue

@@ -50,9 +50,23 @@ let waveRequestId = 0
 
 const pointNames = computed(() => queryMeta.value?.pointNames ?? [])
 
+const pointCounts = computed<Record<string, { pre: number | null; ab: number | null }>>(() => {
+  const map: Record<string, { pre: number | null; ab: number | null }> = {}
+  for (const name of pointNames.value) {
+    map[name] = {
+      pre: queryMeta.value?.pretrainCounts?.[name] ?? null,
+      ab: queryMeta.value?.abnormalCounts?.[name] ?? null,
+    }
+  }
+  return map
+})
+
 function pointLabel(pointName: string): string {
-  const count = queryMeta.value?.pretrainCounts?.[pointName]
-  return count ? `${pointName} (${count})` : pointName
+  const { pre, ab } = pointCounts.value[pointName] ?? { pre: null, ab: null }
+  const inner: string[] = []
+  if (pre != null) inner.push(String(pre))
+  if (ab != null) inner.push(String(ab))
+  return inner.length ? `${pointName} (${inner.join(' · ')})` : pointName
 }
 const selectedOptionRows = computed<QueryOption[]>(() => (
   queryMeta.value?.options.filter((row) => (
@@ -633,7 +647,14 @@ onBeforeUnmount(() => {
               placeholder="输入机组、气缸或部位关键词"
               filter-placeholder="搜索采样点"
             >
-              <el-option v-for="pointName in pointNames" :key="pointName" :label="pointLabel(pointName)" :value="pointName" />
+              <el-option v-for="pointName in pointNames" :key="pointName" :label="pointLabel(pointName)" :value="pointName">
+                <span>{{ pointName }}</span>
+                <template v-if="pointCounts[pointName]?.pre !== null || pointCounts[pointName]?.ab !== null">
+                  <span class="point-counts">
+                    (<span v-if="pointCounts[pointName]?.pre !== null">{{ pointCounts[pointName]?.pre }}</span><template v-if="pointCounts[pointName]?.pre !== null && pointCounts[pointName]?.ab !== null"> · </template><span v-if="pointCounts[pointName]?.ab !== null" class="abnormal-count">{{ pointCounts[pointName]?.ab }}</span>)
+                  </span>
+                </template>
+              </el-option>
             </el-select>
           </label>
           <div class="field field-types">

+ 8 - 7
frontend/src/statusColor.ts

@@ -1,14 +1,15 @@
 const GREEN = { r: 46, g: 125, b: 50 }
-const RED = { r: 211, g: 47, b: 47 }
+const LIGHT_RED = { r: 242, g: 160, b: 160 }
+const DARK_RED = { r: 139, g: 0, b: 0 }
 
 export function statusGradientColor(status: number | undefined, ruler: { min: number; max: number } | null): string {
   if (status === undefined || !ruler) return `rgb(${GREEN.r},${GREEN.g},${GREEN.b})`
-  const span = ruler.max - ruler.min
-  if (span <= 0) return `rgb(${GREEN.r},${GREEN.g},${GREEN.b})`
-  const t = Math.max(0, Math.min(1, ((status ?? 0) - ruler.min) / span))
-  const r = Math.round(GREEN.r + (RED.r - GREEN.r) * t)
-  const g = Math.round(GREEN.g + (RED.g - GREEN.g) * t)
-  const b = Math.round(GREEN.b + (RED.b - GREEN.b) * t)
+  if (status <= 0) return `rgb(${GREEN.r},${GREEN.g},${GREEN.b})`
+  if (ruler.max <= 0) return `rgb(${LIGHT_RED.r},${LIGHT_RED.g},${LIGHT_RED.b})`
+  const t = Math.max(0, Math.min(1, status / ruler.max))
+  const r = Math.round(LIGHT_RED.r + (DARK_RED.r - LIGHT_RED.r) * t)
+  const g = Math.round(LIGHT_RED.g + (DARK_RED.g - LIGHT_RED.g) * t)
+  const b = Math.round(LIGHT_RED.b + (DARK_RED.b - LIGHT_RED.b) * t)
   return `rgb(${r},${g},${b})`
 }
 

+ 2 - 0
frontend/src/styles.css

@@ -115,6 +115,8 @@ h2 { font-size: 18px; line-height: 1.35; font-weight: 600; }
 .query-control .el-input__wrapper, .query-control.el-input-number { min-height: 40px; border-radius: 4px; }
 .query-control .el-input__inner, .query-control .el-select__placeholder, .query-control .el-select__selected-item, .query-control .el-date-editor__placeholder { font-size: 14px; }
 .query-control .el-tag { font-size: 13px; }
+.point-counts { color: #909399; }
+.point-counts .abnormal-count { color: #e05252; font-weight: 600; }
 .window-number .el-input__inner { text-align: center; }
 .query-button.el-button { width: 100%; height: 40px; margin: 0; border-radius: 4px; font-size: 14px; }
 .query-row-2 { display: flex; align-items: flex-start; gap: 16px; grid-column: 1 / -1; }

+ 1 - 0
frontend/src/types.ts

@@ -35,6 +35,7 @@ export type QueryOptionsResponse = {
   pointNames: string[]
   options: QueryOption[]
   pretrainCounts?: Record<string, number>
+  abnormalCounts?: Record<string, number>
   notice: string | null
 }