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 =>
`
`
).join('');
- // 趋势下拉框:增加"空气质量指数"并默认选中
+ // 趋势下拉框:根据 tab 显示对应的主指标名称
const sel = document.getElementById('trendMetricSelect');
- let opts = '
';
+ let opts = '
';
opts += dev.metrics.map((m, i) => `
`).join('');
sel.innerHTML = opts;
sel.value = trendMetricIdx;