From 05482bca61c0bf9d833792ea3526e07a59d93063 Mon Sep 17 00:00:00 2001 From: liuyunqin Date: Thu, 4 Jun 2026 14:31:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=A0=B9=E6=8D=AE=E7=9B=91=E6=B5=8B?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=8A=A8=E6=80=81=E6=98=BE=E7=A4=BA=E4=B8=BB?= =?UTF-8?q?=E6=8C=87=E6=A0=87=E5=90=8D=E7=A7=B0=E5=92=8C=E7=AD=89=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 室外/室内: 下拉框显示空气质量指数, 等级标签空气质量级别 - 水质监测: 下拉框显示水质等级, 等级标签水质等级, 等级名称(达标/未达标) - 花粉监测: 下拉框显示过敏等级, 等级标签过敏等级, 等级名称(很低/低/中等/高/很高) - 水质/花粉非24h: 柱状堆积图使用对应等级配置(颜色+名称) - 删除硬编码aqiLevels/getAqiColor,统一使用config-based配置 --- .../environment/device-detail.html | 182 +++++++++++------- 1 file changed, 115 insertions(+), 67 deletions(-) diff --git a/employee-app/health-monitor/environment/device-detail.html b/employee-app/health-monitor/environment/device-detail.html index 6ef5ecf..f65a695 100644 --- a/employee-app/health-monitor/environment/device-detail.html +++ b/employee-app/health-monitor/environment/device-detail.html @@ -241,24 +241,6 @@ const ALL_DATA = { } }; -/* AQI 等级配置 */ -var aqiLevels = [ - {name:'清洁', color:'#13C2C2', range:[0,20]}, - {name:'未污染', color:'#52C41A', range:[21,50]}, - {name:'轻污染', color:'#FAAD14', range:[51,100]}, - {name:'中污染', color:'#FA541C', range:[101,150]}, - {name:'重污染', color:'#FF4D4F', range:[151,200]} -]; - -/* 获取 AQI 颜色 */ -function getAqiColor(val) { - if (val >= 151) return '#FF4D4F'; - if (val >= 101) return '#FA541C'; - if (val >= 51) return '#FAAD14'; - if (val >= 21) return '#52C41A'; - return '#13C2C2'; -} - /* 根据数值映射到 SVG Y 坐标 */ function valToY(val, yMax) { return chartBottom - (val / yMax) * chartHeight; @@ -283,6 +265,8 @@ function genTrend(metricName, period) { {yMin:101,yMax:150,label:'中污染',color:'#FA541C',opacity:.1}, {yMin:151,yMax:200,label:'重污染',color:'#FF4D4F',opacity:.1} ], + levelColors: ['#13C2C2','#52C41A','#FAAD14','#FA541C','#FF4D4F'], + levelNames: ['清洁','未污染','轻污染','中污染','重污染'], '24h': {points:[55,42,35,48,65,78,82,75,62,58,52,48]}, '7d': {barData:[ {clean:6,good:8,light:6,moderate:4,heavy:0}, @@ -294,6 +278,41 @@ function genTrend(metricName, period) { {clean:6,good:9,light:5,moderate:4,heavy:0} ]}, '30d': {barData:(function(){var r=[];for(var i=0;i<30;i++){r.push({clean:Math.floor(Math.random()*8)+2,good:Math.floor(Math.random()*8)+2,light:Math.floor(Math.random()*6)+2,moderate:Math.floor(Math.random()*6)+2,heavy:Math.floor(Math.random()*3)})}return r})()} + }, + '水质等级': { + isGrade: true, yMax: 100, yTicks: [100,75,50,25,0], + limitUpper: null, limitLower: null, + levelBands: [ + {yMin:0,yMax:20,label:'达标',color:'#52C41A',opacity:.1}, + {yMin:21,yMax:100,label:'未达标',color:'#FF4D4F',opacity:.1} + ], + levelColors: ['#52C41A','#FF4D4F'], + levelNames: ['达标','未达标'], + '24h': {points:[25,30,22,35,42,28,38,45,32,20,35,28]}, + '7d': {barData:[ + {pass:18,fail:6},{pass:20,fail:4},{pass:16,fail:8}, + {pass:22,fail:2},{pass:19,fail:5},{pass:17,fail:7},{pass:20,fail:4} + ]}, + '30d': {barData:(function(){var r=[];for(var i=0;i<30;i++){r.push({pass:Math.floor(Math.random()*14)+8,fail:Math.floor(Math.random()*8)+2})}return r})()} + }, + '过敏等级': { + isGrade: true, yMax: 200, yTicks: [200,150,100,50,0], + limitUpper: null, limitLower: null, + levelBands: [ + {yMin:0,yMax:40,label:'很低',color:'#52C41A',opacity:.1}, + {yMin:40,yMax:80,label:'低',color:'#73D13D',opacity:.1}, + {yMin:80,yMax:120,label:'中等',color:'#FAAD14',opacity:.1}, + {yMin:120,yMax:160,label:'高',color:'#FF4D4F',opacity:.1}, + {yMin:160,yMax:200,label:'很高',color:'#722ED1',opacity:.1} + ], + levelColors: ['#52C41A','#73D13D','#FAAD14','#FF4D4F','#722ED1'], + levelNames: ['很低','低','中等','高','很高'], + '24h': {points:[45,38,55,72,85,120,145,135,98,75,62,50]}, + '7d': {barData:[ + {vl:6,lo:8,md:6,hi:4,vh:0},{vl:8,lo:7,md:5,hi:4,vh:0},{vl:4,lo:6,md:8,hi:5,vh:1}, + {vl:2,lo:5,md:7,hi:8,vh:2},{vl:7,lo:8,md:5,hi:4,vh:0},{vl:9,lo:7,md:4,hi:4,vh:0},{vl:6,lo:9,md:5,hi:4,vh:0} + ]}, + '30d': {barData:(function(){var r=[];for(var i=0;i<30;i++){r.push({vl:Math.floor(Math.random()*8)+2,lo:Math.floor(Math.random()*8)+2,md:Math.floor(Math.random()*6)+2,hi:Math.floor(Math.random()*6)+2,vh:Math.floor(Math.random()*3)})}return r})()} } }; @@ -314,7 +333,7 @@ function genTrend(metricName, period) { 'TDS':{range:[100,850],unit:'mg/L'} }; - if (configs[metricName]) return { metricName: metricName, cfg: configs[metricName], period: period, unit: '' }; + if (configs[metricName]) return { metricName: metricName, cfg: configs[metricName], period: period, unit: '', gradeInfo: configs[metricName] }; var r = ranges[metricName]; if (!r) r = { range: [10, 100], unit: '' }; @@ -384,8 +403,8 @@ function calcXPositions(count) { return positions; } -/* 渲染 AQI 柱状堆积图 */ -function renderAQIBarChart(timeData, labels) { +/* 渲染等级柱状堆积图 */ +function renderGradeBarChart(cfg, timeData, labels) { var barData = timeData.barData; var barCount = barData.length; var barGap = Math.max(2, Math.floor(chartWidth / (barCount * 6))); @@ -413,18 +432,27 @@ function renderAQIBarChart(timeData, labels) { } } + // 根据 levelNames 动态构建分段 + var levelNames = cfg.levelNames; + // barData 的 key 映射:空气质量指数→clean/good/light/moderate/heavy, 过敏等级→vl/lo/md/hi/vh, 水质等级→pass/fail + var levelKeys; + if (levelNames.length === 5 && levelNames[0] === '很低') { + levelKeys = [{k:'vh'},{k:'hi'},{k:'md'},{k:'lo'},{k:'vl'}]; + } else if (levelNames.length === 2) { + levelKeys = [{k:'fail'},{k:'pass'}]; + } else { + levelKeys = [{k:'heavy'},{k:'moderate'},{k:'light'},{k:'good'},{k:'clean'}]; + } + // 柱状堆积 var barsHtml = ''; for (var i = 0; i < barCount; i++) { var bd = barData[i]; var x = xPositions[i] - Math.floor(barWidth / 2); - var segments = [ - {val: bd.heavy, color:'#FF4D4F', name:'重污染'}, - {val: bd.moderate, color:'#FA541C', name:'中污染'}, - {val: bd.light, color:'#FAAD14', name:'轻污染'}, - {val: bd.good, color:'#52C41A', name:'未污染'}, - {val: bd.clean, color:'#13C2C2', name:'清洁'} - ]; + var segments = []; + for (var li = 0; li < levelNames.length; li++) { + segments.push({ val: bd[levelKeys[li].k], color: cfg.levelColors[li], name: levelNames[li] }); + } var yBase = chartBottom; for (var j = 0; j < segments.length; j++) { var seg = segments[j]; @@ -443,10 +471,9 @@ function renderAQIBarChart(timeData, labels) { var legendY = chartBottom + 22; var legendHtml = ''; var lx = 0; - for (var i = 0; i < aqiLevels.length; i++) { - var lv = aqiLevels[i]; - legendHtml += ''; - legendHtml += '' + lv.name + ''; + for (var i = 0; i < levelNames.length; i++) { + legendHtml += ''; + legendHtml += '' + levelNames[i] + ''; lx += 52; } legendHtml += ''; @@ -456,7 +483,7 @@ function renderAQIBarChart(timeData, labels) { '' + barsHtml + legendHtml + ''; - window._trendTooltipData = { key: 'aqiBar', barData: barData, labels: labels, xPositions: xPositions }; + window._trendTooltipData = { key: 'gradeBar', barData: barData, labels: labels, xPositions: xPositions, levelNames: levelNames, levelColors: cfg.levelColors }; return svg; } @@ -467,10 +494,10 @@ function renderTrend(metricName, period) { var timeData = cfg[period]; if (!timeData) return ''; - // AQI 非 24h → 柱状堆积图 - if (cfg.isAQI && period !== '24h') { + // 等级类指标(非24h) → 柱状堆积图 + if ((cfg.isAQI || cfg.isGrade) && period !== '24h') { var labels = period === '7d' ? getXLabels7() : getXLabels30(); - return renderAQIBarChart(timeData, labels); + return renderGradeBarChart(cfg, timeData, labels); } var points = timeData.points; @@ -481,9 +508,9 @@ function renderTrend(metricName, period) { var yMax = cfg.yMax; var yTicks = cfg.yTicks; - // AQI 等级背景(先渲染,作为底层) + // 等级背景(先渲染,作为底层) var levelBandsHtml = ''; - if (cfg.isAQI && cfg.levelBands) { + if ((cfg.isAQI || cfg.isGrade) && cfg.levelBands) { for (var i = 0; i < cfg.levelBands.length; i++) { var b = cfg.levelBands[i]; var bandY1 = valToY(b.yMax, yMax); // yMin value in data → SVG y @@ -532,14 +559,14 @@ function renderTrend(metricName, period) { var polylinePoints = ''; var dotsHtml = ''; var dotR = n > 20 ? 2.5 : 3.5; - var unit = cfg.isAQI ? '' : (gen.unit || ''); - window._trendTooltipData = { key: 'line', points: points, labels: labels, unit: unit, isAQI: cfg.isAQI, xPositions: xPositions }; + var unit = (cfg.isAQI || cfg.isGrade) ? '' : (gen.unit || ''); + window._trendTooltipData = { key: 'line', points: points, labels: labels, unit: unit, isGrade: !!(cfg.isAQI || cfg.isGrade), gradeInfo: cfg.isAQI || cfg.isGrade ? { levelNames: cfg.levelNames, levelColors: cfg.levelColors, yMax: cfg.yMax, yTicks: cfg.yTicks } : null, xPositions: xPositions }; for (var i = 0; i < n; i++) { var y = valToY(points[i], yMax); var x = xPositions[i]; pathD += (i === 0 ? 'M' : 'L') + x + ',' + y + ' '; polylinePoints += x + ',' + y + ' '; - var dotColor = cfg.isAQI ? getAqiColor(points[i]) : chartLine; + var dotColor = (cfg.isAQI || cfg.isGrade) ? getGradeColor(points[i], cfg) : chartLine; dotsHtml += ''; } @@ -561,13 +588,25 @@ function renderTrend(metricName, period) { return svg; } -/* AQI 等级名称映射 */ -function getAqiLevel(val) { - if (val >= 151) return '重污染'; - if (val >= 101) return '中污染'; - if (val >= 51) return '轻污染'; - if (val >= 21) return '未污染'; - return '清洁'; +/* 通用等级名称映射(根据gradeInfo) */ +function getGradeLevel(val, cfg) { + var names = cfg.levelNames; + var bands = cfg.levelBands; + if (!bands) return names[0] || ''; + for (var i = 0; i < bands.length; i++) { + if (val >= bands[i].yMin && val <= bands[i].yMax) return names[i]; + } + return names[names.length - 1]; +} + +/* 通用等级颜色映射 */ +function getGradeColor(val, cfg) { + var bands = cfg.levelBands; + if (!bands) return cfg.levelColors[0] || chartLine; + for (var i = 0; i < bands.length; i++) { + if (val >= bands[i].yMin && val <= bands[i].yMax) return cfg.levelColors[i]; + } + return cfg.levelColors[cfg.levelColors.length - 1] || chartLine; } /* 显示折线图数据点 Tip */ @@ -578,10 +617,10 @@ function showTrendTip(e, el) { var dateStr = td.labels[idx] || ''; var val = td.points[idx]; var unit = td.unit || ''; - var color = td.isAQI ? getAqiColor(val) : chartLine; + var color = td.isGrade ? getGradeColor(val, td.gradeInfo) : chartLine; document.getElementById('tooltipDate').textContent = dateStr; - if (td.isAQI) { - var level = getAqiLevel(val); + if (td.isGrade) { + var level = getGradeLevel(val, td.gradeInfo); document.getElementById('tooltipBody').innerHTML = '
' + '' + @@ -605,19 +644,24 @@ function showBarTip(e, el) { var dateStr = td.labels[idx] || ''; document.getElementById('tooltipDate').textContent = dateStr; var rows = ''; - var levels = [ - {name:'清洁', color:'#13C2C2', val:bd.clean}, - {name:'未污染', color:'#52C41A', val:bd.good}, - {name:'轻污染', color:'#FAAD14', val:bd.light}, - {name:'中污染', color:'#FA541C', val:bd.moderate}, - {name:'重污染', color:'#FF4D4F', val:bd.heavy} - ]; - for (var i = 0; i < levels.length; i++) { - var lv = levels[i]; - var pct = Math.round(lv.val / 24 * 100); + var levelNames = td.levelNames; + var levelColors = td.levelColors; + // 根据 levelKeys 读取 barData 值 + var levelKeysArr; + if (levelNames[0] === '很低') { + levelKeysArr = ['vl','lo','md','hi','vh']; + } else if (levelNames.length === 2) { + levelKeysArr = ['pass','fail']; + } else { + levelKeysArr = ['clean','good','light','moderate','heavy']; + } + var total24 = 24; + for (var i = 0; i < levelNames.length; i++) { + var v = bd[levelKeysArr[i]] || 0; + var pct = Math.round(v / total24 * 100); rows += '
' + - '' + - '' + lv.name + '' + + '' + + '' + levelNames[i] + '' + '' + pct + '%
'; } document.getElementById('tooltipBody').innerHTML = rows; @@ -684,16 +728,20 @@ function buildGauge(levelIdx, total, color) { function renderChart(metricIdx, period) { if (metricIdx === -1) { - return renderTrend('空气质量指数', period); + return renderTrend(primaryMetricName, period); } return renderTrend(dev.metrics[metricIdx].k, period); } +// 根据 tab 类型确定主指标名称和等级标签 +var primaryMetricName = (tab === 'outdoor' || tab === 'indoor') ? '空气质量指数' : tab === 'water' ? '水质等级' : tab === 'pollen' ? '过敏等级' : '空气质量指数'; +var gradeLabelText = (tab === 'outdoor' || tab === 'indoor') ? '空气质量级别:' : tab === 'water' ? '水质等级:' : tab === 'pollen' ? '过敏等级:' : '空气质量级别:'; + function render() { document.getElementById('levelName').textContent = tabData.levelLabels[li]; document.getElementById('levelSub').textContent = tabData.sub; document.getElementById('indexVal').textContent = '综合指数:' + tabData.indexes[li]; - document.getElementById('gradeVal').textContent = '空气质量级别:' + tabData.grades[li]; + document.getElementById('gradeVal').textContent = gradeLabelText + tabData.grades[li]; document.getElementById('cardLoc').textContent = dev.unit + ' · ' + dev.loc; document.getElementById('cardTime').textContent = '更新于今天 ' + dev.time; document.getElementById('cardDesc').textContent = tabData.descs[li]; @@ -701,9 +749,9 @@ function render() { document.getElementById('metricsGrid').innerHTML = dev.metrics.map(m => `
${m.v}${m.u}
${m.k}
` ).join(''); - // 趋势下拉框:增加"空气质量指数"并默认选中 + // 趋势下拉框:根据 tab 显示对应的主指标名称 const sel = document.getElementById('trendMetricSelect'); - let opts = ''; + let opts = ''; opts += dev.metrics.map((m, i) => ``).join(''); sel.innerHTML = opts; sel.value = trendMetricIdx;