Parcourir la source

feat(miniapp): 指标变化展示规则纯函数 resolveChangeStyle/formatDelta

iwt il y a 2 jours
Parent
commit
4a320c4e88

+ 64 - 0
cfc-frontend/__tests__/utils/indicator-change.test.js

@@ -0,0 +1,64 @@
+/**
+ * 指标变化规则纯函数单测
+ * 规则来源:design.md §8 变化颜色与箭头规则表
+ */
+import * as ic from '@/utils/indicator-change'
+
+describe('indicator-change 变化规则', () => {
+  describe('resolveChangeStyle(goodDirection, movement)', () => {
+    test('higher_is_better + up → 改善/绿 ↑', () => {
+      const s = ic.resolveChangeStyle('higher_is_better', 'up')
+      expect(s.trendLabel).toBe('改善')
+      expect(s.cssClass).toBe('impr-up')
+      expect(s.arrow).toBe('↑')
+    })
+    test('higher_is_better + down → 恶化/红 ↓', () => {
+      const s = ic.resolveChangeStyle('higher_is_better', 'down')
+      expect(s.trendLabel).toBe('恶化')
+      expect(s.cssClass).toBe('worst-down')
+      expect(s.arrow).toBe('↓')
+    })
+    test('lower_is_better + up → 恶化/红 ↑ (如炎症水平上升)', () => {
+      const s = ic.resolveChangeStyle('lower_is_better', 'up')
+      expect(s.trendLabel).toBe('恶化')
+      expect(s.cssClass).toBe('worst-up')
+      expect(s.arrow).toBe('↑')
+    })
+    test('lower_is_better + down → 改善/绿 ↓ (如炎症水平下降)', () => {
+      const s = ic.resolveChangeStyle('lower_is_better', 'down')
+      expect(s.trendLabel).toBe('改善')
+      expect(s.cssClass).toBe('impr-down')
+      expect(s.arrow).toBe('↓')
+    })
+    test('neutral + up/down → 中性/灰', () => {
+      const up = ic.resolveChangeStyle('neutral', 'up')
+      const down = ic.resolveChangeStyle('neutral', 'down')
+      expect(up.trendLabel).toBe('中性')
+      expect(up.cssClass).toBe('neutral-up')
+      expect(down.cssClass).toBe('neutral-down')
+    })
+    test('flat → 无变化/灰 →', () => {
+      const s = ic.resolveChangeStyle('higher_is_better', 'flat')
+      expect(s.trendLabel).toBe('中性')
+      expect(s.cssClass).toBe('flat')
+      expect(s.arrow).toBe('→')
+    })
+    test('未知 goodDirection 兜底为中性', () => {
+      const s = ic.resolveChangeStyle('unknown_direction', 'up')
+      expect(s.cssClass).toBe('neutral-up')
+    })
+  })
+
+  describe('formatDelta(delta, deltaPct)', () => {
+    test('正 delta 显示 + 并带百分比', () => {
+      expect(ic.formatDelta(2, 100)).toBe('+2 (+100%)')
+    })
+    test('负 delta 显示 -', () => {
+      expect(ic.formatDelta(-1.5, -30)).toBe('-1.5 (-30%)')
+    })
+    test('null 安全', () => {
+      expect(ic.formatDelta(null, null)).toBe('--')
+      expect(ic.formatDelta(0, 0)).toBe('0 (0%)')
+    })
+  })
+})

+ 64 - 0
cfc-frontend/utils/indicator-change.js

@@ -0,0 +1,64 @@
+/**
+ * 指标变化展示规则纯函数
+ * 规则来源:design.md §8 变化颜色与箭头规则
+ * 颜色映射:改善=绿 / 恶化=红 / 中性=灰
+ */
+
+/**
+ * 由 good_direction × movement 推导展示样式
+ * @param {string} goodDirection higher_is_better|lower_is_better|in_range_is_better|neutral
+ * @param {string} movement up|down|flat
+ * @returns {{trendLabel:string, cssClass:string, arrow:string}}
+ */
+export function resolveChangeStyle(goodDirection, movement) {
+  var gd = goodDirection || 'neutral'
+  var mv = movement || 'flat'
+  if (mv === 'flat') {
+    return { trendLabel: '中性', cssClass: 'flat', arrow: '→' }
+  }
+  // 是否"改善":higher_is_better 升=改善;lower_is_better 降=改善;in_range 视区间判定(此处交给 withRange 变体)
+  var isImprove = false
+  if (gd === 'higher_is_better') {
+    isImprove = mv === 'up'
+  } else if (gd === 'lower_is_better') {
+    isImprove = mv === 'down'
+  } else {
+    // neutral / in_range_is_better(一期 in_range 的管理端手配方向;区间内移动判定留二期,先按中性展示)
+    return {
+      trendLabel: '中性',
+      cssClass: gd === 'in_range_is_better' ? 'neutral-up' : mv === 'up' ? 'neutral-up' : 'neutral-down',
+      arrow: mv === 'up' ? '↑' : '↓'
+    }
+  }
+  if (isImprove) {
+    return { trendLabel: '改善', cssClass: gd === 'higher_is_better' ? 'impr-up' : 'impr-down', arrow: mv === 'up' ? '↑' : '↓' }
+  }
+  return { trendLabel: '恶化', cssClass: gd === 'higher_is_better' ? 'worst-down' : 'worst-up', arrow: mv === 'up' ? '↑' : '↓' }
+}
+
+/**
+ * Δ 展示文本 "±x (±x%)"
+ * @param {number|null} delta
+ * @param {number|null} deltaPct
+ * @returns {string}
+ */
+export function formatDelta(delta, deltaPct) {
+  if (delta === null || delta === undefined || isNaN(delta)) return '--'
+  var sign = delta > 0 ? '+' : ''
+  if (deltaPct === null || deltaPct === undefined || isNaN(deltaPct)) {
+    return sign + delta
+  }
+  var pctSign = deltaPct > 0 ? '+' : ''
+  return sign + delta + ' (' + pctSign + deltaPct + '%)'
+}
+
+/**
+ * CSS 颜色类:改善=green-text 恶化=red-text 中性=gray-text
+ * @param {string} cssClass resolveChangeStyle 返回的类
+ * @returns {string}
+ */
+export function colorClassFor(cssClass) {
+  if (cssClass.indexOf('impr') === 0) return 'green-text'
+  if (cssClass.indexOf('worst') === 0) return 'red-text'
+  return 'gray-text'
+}