Kaynağa Gözat

设备状态: 信号机 4 档红色梯度拉开 (L 差 11-24%) + 控制模式/故障配色合并 chartColors.js

画安 4 hafta önce
ebeveyn
işleme
f4751ac7a0

+ 15 - 14
src/components/ui/DeviceStatusTabs.vue

@@ -18,33 +18,34 @@
 import TechTabs from '@/components/ui/TechTabs.vue';
 import TechTabPane from '@/components/ui/TechTabPane.vue';
 import DeviceStatusPie from '@/components/ui/DeviceStatusPie.vue';
+import { applyDeviceFaultColors } from '@/config/chartColors';
 
-// 提取 Mock 数据作为默认值
+// 默认 Mock 数据 (父组件没传 statusData 时兜底;颜色由 chartColors.js 统一治理)
 const defaultMockStatusData = {
   'signalMachineStatus': {
     centerTitle: '98%',
     centerSubTitle: '980/1000',
-    chartData: [
-        { name: '正常', value: 980, color: '#5EC8FF' },
-        { name: '故障', value: 0, color: '#D03030' }
-    ]
+    chartData: applyDeviceFaultColors([
+      { name: '正常', value: 980 },
+      { name: '故障', value: 0 }
+    ])
   },
   'detectorStatus': {
     centerTitle: '85%',
     centerSubTitle: '425/500',
-    chartData: [
-        { name: '正常', value: 496, color: '#5EC8FF' },
-        { name: '通信故障', value: 4, color: '#C6302B' }
-    ]
+    chartData: applyDeviceFaultColors([
+      { name: '正常',     value: 496 },
+      { name: '通信故障', value: 4 }
+    ])
   },
   'trafficLightStatus': {
     centerTitle: '99%',
     centerSubTitle: '1188/1200',
-    chartData: [
-        { name: '正常', value: 1196, color: '#5EC8FF' },
-        { name: '红绿冲突', value: 2, color: '#C6302B' },
-        { name: '红灯故障', value: 2, color: '#8F1E1E' }
-    ]
+    chartData: applyDeviceFaultColors([
+      { name: '正常',     value: 1196 },
+      { name: '红绿冲突', value: 2 },
+      { name: '红灯故障', value: 2 }
+    ])
   }
 };
 

+ 163 - 0
src/config/chartColors.js

@@ -0,0 +1,163 @@
+/**
+ * 图表配色注册表(控制模式 + 设备故障 + 通用基础色)
+ *
+ * 设计原则:
+ *   - 暗色大屏背景, 全部色相亮度 50-65% (饱和、不刺眼)
+ *   - 同业务族用同色系: 算法类蓝色, 协调网络绿色, 人工/中央紫色, 预警黄/橙, 紧急红, 关闭/异常灰
+ *   - 后端只返 { name, value }, 前端按 name 查表注入 color
+ *   - 未注册的 name 走 FALLBACK_PALETTE 按 hash 兜底
+ *
+ * 用法:
+ *   import {
+ *     applyControlModeColors,   // 给控制模式列表注入色
+ *     applyDeviceFaultColors,   // 给设备故障列表注入色
+ *     resolveControlModeColor,
+ *     resolveDeviceFaultColor,
+ *   } from '@/config/chartColors'
+ */
+
+// ============================================================================
+// 1. 通用基础色
+// ============================================================================
+export const COLORS = {
+  normal: '#5EC8FF',  // 正常 (亮蓝)
+  warn:   '#faad14',  // 警告 (琥珀黄)
+  ok:     '#10b981',  // OK (绿)
+  info:   '#33a3ff',  // 信息 (蓝)
+  muted:  '#6b7280',  // 静默 (中灰)
+}
+
+// ============================================================================
+// 2. 红色严重等级梯度 (按严重程度从轻到重)
+//    暗色背景下 4 档亮度梯度: 82% → 71% → 56% → 32%
+//    相邻差 ≥ 11%, 远大于"全 0° 色相 + 7-8% 亮度差"的肉眼极限
+// ============================================================================
+export const RED_GRADIENT = {
+  L1_LIGHT:  '#FFA5A5',  // L=82% 樱粉 — 最轻级
+  L2_MEDIUM: '#FF6B6B',  // L=71% 桃红
+  L3_DARK:   '#E03C3C',  // L=56% 鲜红
+  L4_DEEP:   '#8B1A1A',  // L=32% 砖红 — 最严重
+}
+
+// ============================================================================
+// 3. 设备故障 → 颜色 (按 name 查表)
+// ============================================================================
+export const DEVICE_FAULT_COLORS = {
+  // 通用
+  '正常':       COLORS.normal,
+
+  // 信号机 4 级故障 (亮度对应严重程度: 低 → 高)
+  '控制板报警': RED_GRADIENT.L1_LIGHT,
+  '相位板报警': RED_GRADIENT.L2_MEDIUM,
+  '检测板报警': RED_GRADIENT.L3_DARK,
+  '黄闪报警':   RED_GRADIENT.L4_DEEP,
+
+  // 检测器
+  '通信故障':   RED_GRADIENT.L3_DARK,
+  '数据异常':   COLORS.warn,
+
+  // 红绿灯
+  '红绿冲突':   RED_GRADIENT.L3_DARK,
+  '红灯故障':   RED_GRADIENT.L4_DEEP,
+}
+
+// ============================================================================
+// 4. 控制模式 16 种 → 颜色 (按 name 查表)
+//    16 种业务模式, 排序按业务族, 不影响 API 返回顺序
+// ============================================================================
+export const CONTROL_MODE_COLORS = {
+  // ── 算法类 (冷色蓝) ───────────────────────────────
+  '定周期控制':   '#33a3ff',
+  '感应控制':     '#e6734d',  // 现状保留 (历史橙)
+  '干线协调':     '#10b981',  // 现状保留 (绿)
+  '自适应控制':   '#2dd4bf',  // 现状保留 (青)
+
+  // ── 协调/网络 (绿色) ─────────────────────────────
+  '区域协调':     '#14b8a6',
+  '公交优先':     '#06b6d4',
+  '行人请求':     '#84cc16',
+
+  // ── 人工/中央 (紫色) ─────────────────────────────
+  '中心计划':     '#a78bfa',
+  '手动控制':     '#c084fc',
+
+  // ── 预警类 (黄/橙) ───────────────────────────────
+  '黄闪控制':     '#eab308',  // 现状保留 (物理黄灯)
+  '降级模式':     '#f59e0b',
+  '感应降级':     '#fb923c',
+
+  // ── 紧急类 (红色) ─────────────────────────────────
+  '紧急抢占':     '#f43f5e',
+  '全红控制':     '#dc2626',  // 物理全红灯
+  '应急疏散':     '#ef4444',
+
+  // ── 关闭/异常 (灰色) ─────────────────────────────
+  '关灯':         '#6b7280',
+}
+
+// 未在表内的 name 走这套兜底色板 (按 hash 落位)
+const FALLBACK_PALETTE = [
+  '#7dd3fc', '#fda4af', '#fcd34d', '#a3e635', '#67e8f9',
+  '#c4b5fd', '#fb7185', '#fde047', '#86efac', '#f0abfc',
+]
+
+function hashCode(s) {
+  let h = 0
+  for (let i = 0; i < s.length; i++) {
+    h = ((h << 5) - h) + s.charCodeAt(i)
+    h |= 0
+  }
+  return Math.abs(h)
+}
+
+// ============================================================================
+// 5. 解析单条 name → 颜色
+// ============================================================================
+
+/** 按 name 查控制模式色, 未注册走 hash 兜底 */
+export function resolveControlModeColor(name) {
+  if (!name) return FALLBACK_PALETTE[0]
+  if (CONTROL_MODE_COLORS[name]) return CONTROL_MODE_COLORS[name]
+  return FALLBACK_PALETTE[hashCode(name) % FALLBACK_PALETTE.length]
+}
+
+/** 按 name 查设备故障色, 未注册时按"含红/正常"判断兜底 */
+export function resolveDeviceFaultColor(name) {
+  if (!name) return RED_GRADIENT.L3_DARK
+  if (DEVICE_FAULT_COLORS[name]) return DEVICE_FAULT_COLORS[name]
+  // 兜底: 含"正常" → 蓝, 其余按 hash 落入红色梯度
+  if (name.includes('正常')) return COLORS.normal
+  const grad = [RED_GRADIENT.L1_LIGHT, RED_GRADIENT.L2_MEDIUM, RED_GRADIENT.L3_DARK, RED_GRADIENT.L4_DEEP]
+  return grad[hashCode(name) % grad.length]
+}
+
+// ============================================================================
+// 6. 批量注入色 (给整个 chartData 列表)
+// ============================================================================
+
+/** 控制模式列表注入色 */
+export function applyControlModeColors(list) {
+  if (!Array.isArray(list)) return []
+  return list.map(item => ({
+    ...item,
+    color: item.color || resolveControlModeColor(item.name),
+  }))
+}
+
+/** 设备故障列表注入色 (信号机/检测器/红绿灯 共用) */
+export function applyDeviceFaultColors(list) {
+  if (!Array.isArray(list)) return []
+  return list.map(item => ({
+    ...item,
+    color: item.color || resolveDeviceFaultColor(item.name),
+  }))
+}
+
+/** 单条注入 (单 item 用) */
+export function getControlModeStyle(item) {
+  return { ...item, color: item.color || resolveControlModeColor(item.name) }
+}
+
+export function getDeviceFaultStyle(item) {
+  return { ...item, color: item.color || resolveDeviceFaultColor(item.name) }
+}

+ 0 - 95
src/config/controlModeColors.js

@@ -1,95 +0,0 @@
-/**
- * 控制模式配色注册表
- *
- * 设计原则:
- *   - 暗色大屏背景, 全部色相亮度 50-65% (饱和、不刺眼)
- *   - 同业务族用同色系: 算法类蓝色, 协调网络绿色, 人工/中央紫色, 预警黄/橙, 紧急红, 关闭/异常灰
- *   - 后端只返 { name, value }, 前端按 name 查表注入 color
- *   - 未注册的 name 走 FALLBACK_PALETTE 按 hash 兜底
- *
- * 用法:
- *   import { resolveControlModeColor, getControlModeStyle } from '@/config/controlModeColors'
- *   const color = resolveControlModeColor('定周期控制')          // -> '#33a3ff'
- *   const item = getControlModeStyle({ name, value })            // -> { name, value, color }
- */
-
-// 16 种业务模式 → 颜色
-// 排序按业务族, 不影响 API 返回顺序
-export const CONTROL_MODE_COLORS = {
-  // ── 算法类 (冷色蓝) ───────────────────────────────
-  '定周期控制':   '#33a3ff',
-  '感应控制':     '#e6734d',  // 现状保留 (历史橙)
-  '干线协调':     '#10b981',  // 现状保留 (绿)
-  '自适应控制':   '#2dd4bf',  // 现状保留 (青)
-
-  // ── 协调/网络 (绿色) ─────────────────────────────
-  '区域协调':     '#14b8a6',
-  '公交优先':     '#06b6d4',
-  '行人请求':     '#84cc16',
-
-  // ── 人工/中央 (紫色) ─────────────────────────────
-  '中心计划':     '#a78bfa',
-  '手动控制':     '#c084fc',
-
-  // ── 预警类 (黄/橙) ───────────────────────────────
-  '黄闪控制':     '#eab308',  // 现状保留 (物理黄灯)
-  '降级模式':     '#f59e0b',
-  '感应降级':     '#fb923c',
-
-  // ── 紧急类 (红色) ─────────────────────────────────
-  '紧急抢占':     '#f43f5e',
-  '全红控制':     '#dc2626',  // 物理全红灯
-  '应急疏散':     '#ef4444',
-
-  // ── 关闭/异常 (灰色) ─────────────────────────────
-  '关灯':         '#6b7280',
-}
-
-// 未在表内的 name 走这套兜底色板 (按 hash 落位)
-const FALLBACK_PALETTE = [
-  '#7dd3fc', '#fda4af', '#fcd34d', '#a3e635', '#67e8f9',
-  '#c4b5fd', '#fb7185', '#fde047', '#86efac', '#f0abfc',
-]
-
-function hashCode(s) {
-  let h = 0
-  for (let i = 0; i < s.length; i++) {
-    h = ((h << 5) - h) + s.charCodeAt(i)
-    h |= 0
-  }
-  return Math.abs(h)
-}
-
-/**
- * 根据模式名取颜色
- * @param {string} name
- * @returns {string} hex color
- */
-export function resolveControlModeColor(name) {
-  if (!name) return FALLBACK_PALETTE[0]
-  if (CONTROL_MODE_COLORS[name]) return CONTROL_MODE_COLORS[name]
-  return FALLBACK_PALETTE[hashCode(name) % FALLBACK_PALETTE.length]
-}
-
-/**
- * 把后端 { name, value } 列表注入颜色
- * @param {Array<{name: string, value: number}>} list
- * @returns {Array<{name: string, value: number, color: string}>}
- */
-export function applyControlModeColors(list) {
-  if (!Array.isArray(list)) return []
-  return list.map(item => ({
-    ...item,
-    color: item.color || resolveControlModeColor(item.name),
-  }))
-}
-
-/**
- * 单条注入 (给单 item 用)
- */
-export function getControlModeStyle(item) {
-  return {
-    ...item,
-    color: item.color || resolveControlModeColor(item.name),
-  }
-}

+ 19 - 18
src/mock/api.js

@@ -14,7 +14,7 @@
 
 import mockData from './mock_data.json'
 import { simulateMaxband } from './_simulateMaxband'
-import { applyControlModeColors } from '@/config/controlModeColors'
+import { applyControlModeColors, applyDeviceFaultColors } from '@/config/chartColors'
 
 // ── 静态资源(模拟 CDN / 后端返回的资源 URL)─────────────────────
 
@@ -1483,16 +1483,17 @@ export async function apiGetDeviceFaultStatus() {
   const cam = DB.deviceStatus.camera
 
   // ── 信号机故障:从共享缓存获取,确保与在线状态的离线数一致 ──
+  // 颜色由 applyDeviceFaultColors 按 name 注入 (红色 4 档亮度梯度 RED_GRADIENT)
   const snap = _getSmFaultSnapshot()
   const smTotal = snap.total
   const smFaultTotal = snap.faultTotal
-  const smFaultList = [
-    { name: '正常', value: Math.max(0, smTotal - smFaultTotal), color: '#5EC8FF' },
-    { name: '控制板报警', value: snap.ctrlBoard, color: '#FF7878' },
-    { name: '相位板报警', value: snap.phaseBoard, color: '#E66565' },
-    { name: '检测板报警', value: snap.detBoard, color: '#CC4848' },
-    { name: '黄闪报警', value: snap.yellowFlash, color: '#B83838' },
-  ]
+  const smFaultList = applyDeviceFaultColors([
+    { name: '正常',       value: Math.max(0, smTotal - smFaultTotal) },
+    { name: '控制板报警', value: snap.ctrlBoard },
+    { name: '相位板报警', value: snap.phaseBoard },
+    { name: '检测板报警', value: snap.detBoard },
+    { name: '黄闪报警',   value: snap.yellowFlash },
+  ])
 
   // ── 检测器故障 ──
   const dtTotal = dt.chartData[0].value + dt.chartData[1].value
@@ -1513,20 +1514,20 @@ export async function apiGetDeviceFaultStatus() {
     detectorStatus: {
       centerTitle: dtFault + '',
       centerSubTitle: `${dtFault}/${dtTotal}`,
-      chartData: [
-        { name: '正常', value: Math.max(0, dtTotal - dtFault), color: '#5EC8FF' },
-        { name: '通信故障', value: dtCommFault, color: '#C6302B' },
-        { name: '数据异常', value: Math.max(0, dtFault - dtCommFault), color: '#faad14' },
-      ]
+      chartData: applyDeviceFaultColors([
+        { name: '正常',     value: Math.max(0, dtTotal - dtFault) },
+        { name: '通信故障', value: dtCommFault },
+        { name: '数据异常', value: Math.max(0, dtFault - dtCommFault) },
+      ])
     },
     trafficLightStatus: {
       centerTitle: camFault + '',
       centerSubTitle: `${camFault}/${camTotal}`,
-      chartData: [
-        { name: '正常', value: Math.max(0, camTotal - camFault), color: '#5EC8FF' },
-        { name: '红绿冲突', value: camConflict, color: '#C6302B' },
-        { name: '红灯故障', value: Math.max(0, camFault - camConflict), color: '#8F1E1E' },
-      ]
+      chartData: applyDeviceFaultColors([
+        { name: '正常',     value: Math.max(0, camTotal - camFault) },
+        { name: '红绿冲突', value: camConflict },
+        { name: '红灯故障', value: Math.max(0, camFault - camConflict) },
+      ])
     },
   })
 }