feat: 根据监测类型动态显示主指标名称和等级
- 室外/室内: 下拉框显示空气质量指数, 等级标签空气质量级别 - 水质监测: 下拉框显示水质等级, 等级标签水质等级, 等级名称(达标/未达标) - 花粉监测: 下拉框显示过敏等级, 等级标签过敏等级, 等级名称(很低/低/中等/高/很高) - 水质/花粉非24h: 柱状堆积图使用对应等级配置(颜色+名称) - 删除硬编码aqiLevels/getAqiColor,统一使用config-based配置
This commit is contained in:
@@ -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 = '<g transform="translate(' + chartLeft + ',' + legendY + ')">';
|
||||
var lx = 0;
|
||||
for (var i = 0; i < aqiLevels.length; i++) {
|
||||
var lv = aqiLevels[i];
|
||||
legendHtml += '<rect x="' + lx + '" y="0" width="10" height="10" fill="' + lv.color + '" rx="1.5"/>';
|
||||
legendHtml += '<text x="' + (lx + 14) + '" y="9" fill="#999" font-size="9">' + lv.name + '</text>';
|
||||
for (var i = 0; i < levelNames.length; i++) {
|
||||
legendHtml += '<rect x="' + lx + '" y="0" width="10" height="10" fill="' + cfg.levelColors[i] + '" rx="1.5"/>';
|
||||
legendHtml += '<text x="' + (lx + 14) + '" y="9" fill="#999" font-size="9">' + levelNames[i] + '</text>';
|
||||
lx += 52;
|
||||
}
|
||||
legendHtml += '</g>';
|
||||
@@ -456,7 +483,7 @@ function renderAQIBarChart(timeData, labels) {
|
||||
'<line x1="' + chartLeft + '" y1="' + chartBottom + '" x2="' + chartRight + '" y2="' + chartBottom + '" class="chart-x-axis"/>' +
|
||||
barsHtml + legendHtml + '</svg>';
|
||||
|
||||
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 += '<circle cx="' + x + '" cy="' + y + '" r="' + dotR + '" class="chart-dot" style="fill:' + dotColor + ';cursor:pointer;" ' +
|
||||
'data-idx="' + i + '" onmouseenter="showTrendTip(event,this)" onmouseleave="hideTrendTip()"/>';
|
||||
}
|
||||
@@ -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 =
|
||||
'<div class="chart-tooltip__row">' +
|
||||
'<span class="chart-tooltip__dot" style="background:' + color + '"></span>' +
|
||||
@@ -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 += '<div class="chart-tooltip__row">' +
|
||||
'<span class="chart-tooltip__dot" style="background:' + lv.color + '"></span>' +
|
||||
'<span class="chart-tooltip__label">' + lv.name + '</span>' +
|
||||
'<span class="chart-tooltip__dot" style="background:' + levelColors[i] + '"></span>' +
|
||||
'<span class="chart-tooltip__label">' + levelNames[i] + '</span>' +
|
||||
'<span class="chart-tooltip__val">' + pct + '%</span></div>';
|
||||
}
|
||||
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 =>
|
||||
`<div class="metric-item"><div class="metric-item__val">${m.v}<span class="metric-item__unit">${m.u}</span></div><div class="metric-item__label">${m.k}</div></div>`
|
||||
).join('');
|
||||
// 趋势下拉框:增加"空气质量指数"并默认选中
|
||||
// 趋势下拉框:根据 tab 显示对应的主指标名称
|
||||
const sel = document.getElementById('trendMetricSelect');
|
||||
let opts = '<option value="-1">空气质量指数</option>';
|
||||
let opts = '<option value="-1">' + primaryMetricName + '</option>';
|
||||
opts += dev.metrics.map((m, i) => `<option value="${i}">${m.k}</option>`).join('');
|
||||
sel.innerHTML = opts;
|
||||
sel.value = trendMetricIdx;
|
||||
|
||||
Reference in New Issue
Block a user