261 lines
19 KiB
JavaScript
261 lines
19 KiB
JavaScript
/* dashboard.js — themes + renderDashboard(themeKey, opts) -> HTML string.
|
||
Pure markup; charts are SVG from window.CHARTS. window.renderDashboard */
|
||
(function () {
|
||
const C = window.CHARTS;
|
||
const D = window.DASH_DATA;
|
||
const SANS = "-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Hiragino Sans GB','Microsoft YaHei',system-ui,sans-serif";
|
||
const SERIF = "'Songti SC','STSong',Georgia,'Times New Roman',serif";
|
||
|
||
const ICON = {
|
||
grid: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/></svg>',
|
||
predict: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 17l5-6 4 3 6-8"/><path d="M3 21h18"/></svg>',
|
||
flask: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 3h6M10 3v6l-5 8a2 2 0 0 0 1.7 3h10.6A2 2 0 0 0 19 17l-5-8V3"/><path d="M7 14h10"/></svg>',
|
||
source: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M18.4 5.6l-2.1 2.1M7.7 16.3l-2.1 2.1"/></svg>',
|
||
folder: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"><path d="M3 7a2 2 0 0 1 2-2h4l2 2.5h8a2 2 0 0 1 2 2V18a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>',
|
||
report: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 3h7l5 5v13a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z"/><path d="M14 3v5h5M9 13h6M9 17h4"/></svg>',
|
||
gear: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19 12a7 7 0 0 0-.1-1l2-1.6-2-3.4-2.3 1a7 7 0 0 0-1.7-1l-.3-2.5h-4l-.3 2.5a7 7 0 0 0-1.7 1l-2.3-1-2 3.4 2 1.6a7 7 0 0 0 0 2l-2 1.6 2 3.4 2.3-1a7 7 0 0 0 1.7 1l.3 2.5h4l.3-2.5a7 7 0 0 0 1.7-1l2.3 1 2-3.4-2-1.6c.1-.3.1-.7.1-1z" stroke-linejoin="round"/></svg>',
|
||
search: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4-4"/></svg>',
|
||
bell: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9a6 6 0 0 1 12 0c0 5 2 6 2 6H4s2-1 2-6"/><path d="M10 20a2 2 0 0 0 4 0"/></svg>',
|
||
alert: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l9 16H3z"/><path d="M12 9v5M12 17.5v.01"/></svg>',
|
||
bldg: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"><path d="M4 21V5a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v16M14 21V9h5a1 1 0 0 1 1 1v11M7 8h2M7 12h2M7 16h2"/></svg>',
|
||
leaf: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20c10 2 16-4 16-14 0 0-8-2-12 2-3 3-3 7-1 9 3-4 6-6 9-7"/></svg>',
|
||
up: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M6 14l6-6 6 6"/></svg>',
|
||
down: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M6 10l6 6 6-6"/></svg>',
|
||
};
|
||
|
||
const THEMES = {
|
||
dark: {
|
||
cls: 'v-dark',
|
||
vars: {
|
||
'--bg': '#0a101e', '--side-bg': '#070c17', '--side-border': 'rgba(255,255,255,.06)',
|
||
'--side-ink': '#e7eef9', '--side-sub': '#7589a6', '--side-hover': 'rgba(255,255,255,.05)',
|
||
'--border': 'rgba(255,255,255,.08)', '--panel': '#111b2e', '--panel2': '#0d1626',
|
||
'--ink': '#e8eff9', '--sub': '#8ba0bd', '--faint': '#5d6f8c',
|
||
'--accent': '#2dd4bf', '--accent2': '#38bdf8', '--accent-soft': 'rgba(45,212,191,.14)', '--accent-on': '#5eead4',
|
||
'--good': '#34d399', '--good-soft': 'rgba(52,211,153,.15)', '--warn': '#fbbf24', '--warn-soft': 'rgba(251,191,36,.15)',
|
||
'--bad': '#f87171', '--bad-soft': 'rgba(248,113,113,.15)', '--track': 'rgba(255,255,255,.07)',
|
||
'--radius': '16px', '--shadow': '0 10px 34px rgba(0,0,0,.45)', '--topbar-bg': 'rgba(7,12,23,.5)',
|
||
'--logo-glow': '0 0 18px rgba(45,212,191,.5)', '--font': SANS, '--display': SANS,
|
||
},
|
||
chart: { good: '#34d399', warn: '#fbbf24', bad: '#f87171', badSoft: 'rgba(248,113,113,.2)', accent: '#2dd4bf', grid: 'rgba(255,255,255,.08)', ink: '#e8eff9', sub: '#8ba0bd', faint: '#5d6f8c', track: 'rgba(255,255,255,.09)', glow: true, gid: 'dk' },
|
||
},
|
||
light: {
|
||
cls: 'v-light',
|
||
vars: {
|
||
'--bg': '#eef1f7', '--side-bg': '#ffffff', '--side-border': '#e7ebf2',
|
||
'--side-ink': '#16212f', '--side-sub': '#6a788b', '--side-hover': 'rgba(20,87,214,.06)',
|
||
'--border': '#e7ebf2', '--panel': '#ffffff', '--panel2': '#f4f6fb',
|
||
'--ink': '#15212e', '--sub': '#5b6b7d', '--faint': '#98a4b3',
|
||
'--accent': '#1457d6', '--accent2': '#3b82f6', '--accent-soft': 'rgba(20,87,214,.10)', '--accent-on': '#1457d6',
|
||
'--good': '#16a34a', '--good-soft': 'rgba(22,163,74,.11)', '--warn': '#e08600', '--warn-soft': 'rgba(224,134,0,.13)',
|
||
'--bad': '#dc2626', '--bad-soft': 'rgba(220,38,38,.10)', '--track': '#eaeef4',
|
||
'--radius': '14px', '--shadow': '0 1px 2px rgba(20,30,50,.05),0 6px 18px rgba(20,30,50,.05)', '--topbar-bg': 'rgba(255,255,255,.7)',
|
||
'--logo-glow': 'none', '--font': SANS, '--display': SANS,
|
||
},
|
||
chart: { good: '#16a34a', warn: '#e08600', bad: '#dc2626', badSoft: 'rgba(220,38,38,.1)', accent: '#1457d6', grid: '#eaeef4', ink: '#15212e', sub: '#5b6b7d', faint: '#98a4b3', track: '#eaeef4', glow: false, gid: 'lt' },
|
||
},
|
||
warm: {
|
||
cls: 'v-warm',
|
||
vars: {
|
||
'--bg': '#f4f0e7', '--side-bg': '#fffdf8', '--side-border': '#e8e0d0',
|
||
'--side-ink': '#241e15', '--side-sub': '#897f6c', '--side-hover': 'rgba(31,122,90,.08)',
|
||
'--border': '#e9e1d2', '--panel': '#fffdf8', '--panel2': '#f4efe3',
|
||
'--ink': '#221d15', '--sub': '#6c6353', '--faint': '#a89c86',
|
||
'--accent': '#1f7a5a', '--accent2': '#2f9e74', '--accent-soft': 'rgba(31,122,90,.12)', '--accent-on': '#1f7a5a',
|
||
'--good': '#2f8f5b', '--good-soft': 'rgba(47,143,91,.13)', '--warn': '#ca8326', '--warn-soft': 'rgba(202,131,38,.15)',
|
||
'--bad': '#bf4a30', '--bad-soft': 'rgba(191,74,48,.12)', '--track': '#ebe3d4',
|
||
'--radius': '16px', '--shadow': '0 1px 2px rgba(60,50,30,.05)', '--topbar-bg': 'rgba(255,253,248,.7)',
|
||
'--logo-glow': 'none', '--font': SANS, '--display': SERIF,
|
||
},
|
||
chart: { good: '#2f8f5b', warn: '#ca8326', bad: '#bf4a30', badSoft: 'rgba(191,74,48,.14)', accent: '#1f7a5a', grid: '#ece4d5', ink: '#221d15', sub: '#6c6353', faint: '#a89c86', track: '#ebe3d4', glow: false, gid: 'wm' },
|
||
},
|
||
};
|
||
|
||
const pct = (v, t) => Math.round((v / t) * 1000) / 10;
|
||
const statusKey = r => r >= 1 ? 'bad' : r >= 0.85 ? 'warn' : 'good';
|
||
const cnLevel = { bad: '超标', warn: '临界', good: '达标' };
|
||
|
||
function nav(active) {
|
||
const items = [
|
||
['grid', '总览看板', 0], ['predict', '污染物预测', 0], ['flask', '材料库', 0],
|
||
['source', '污染源识别', 19], ['folder', '案例库 / 项目', 0], ['report', '检测报告', 0],
|
||
];
|
||
return `<div class="d-navgrp">主菜单</div><div class="d-nav">` +
|
||
items.map((it, i) => `<div class="d-nav-i${i === active ? ' on' : ''}">${ICON[it[0]]}<span>${it[1]}</span>${it[2] ? `<span class="d-badge">${it[2]}</span>` : ''}</div>`).join('') +
|
||
`</div>`;
|
||
}
|
||
|
||
function kpiCard(lab, icon, iconBg, iconCol, num, unit, trend) {
|
||
return `<div class="d-kpi">
|
||
<div class="d-kpi-lab"><span class="d-kpi-ic" style="background:${iconBg};color:${iconCol}">${ICON[icon]}</span>${lab}</div>
|
||
<div class="d-kpi-row">
|
||
<div><span class="d-kpi-num">${num}</span>${unit ? `<span class="d-kpi-unit">${unit}</span>` : ''}</div>
|
||
${trend || ''}
|
||
</div></div>`;
|
||
}
|
||
|
||
// pollutant ring gauges or bars
|
||
function pollutantViz(mode, p) {
|
||
if (mode === 'bar') {
|
||
const w = 470, rowH = 34, padR = 64, barX = 92, barW = w - barX - padR;
|
||
let rows = '';
|
||
D.pollutants.forEach((d, i) => {
|
||
const ratio = d.value / d.limit, col = C.status(ratio, p);
|
||
const yy = i * rowH;
|
||
const limX = barX + barW; // 100% = limit
|
||
const bw = Math.max(4, Math.min(ratio, 1.45) / 1.45 * barW);
|
||
const limMark = barX + (1 / 1.45) * barW;
|
||
rows += `<g transform="translate(0 ${yy})">
|
||
<text x="0" y="${rowH / 2 + 4}" font-size="12" font-weight="700" fill="${p.ink}">${d.name}</text>
|
||
<rect x="${barX}" y="${rowH / 2 - 8}" width="${barW}" height="16" rx="5" fill="${p.track}"/>
|
||
<rect x="${barX}" y="${rowH / 2 - 8}" width="${bw.toFixed(1)}" height="16" rx="5" fill="${col}"/>
|
||
<line x1="${limMark.toFixed(1)}" y1="${rowH / 2 - 12}" x2="${limMark.toFixed(1)}" y2="${rowH / 2 + 12}" stroke="${p.bad}" stroke-width="1.4" stroke-dasharray="3 3"/>
|
||
<text x="${w}" y="${rowH / 2 + 4}" text-anchor="end" font-size="11" font-weight="700" fill="${col}" style="font-variant-numeric:tabular-nums">${C.fmt(d.value, d.value >= 10 ? 0 : 3)}</text>
|
||
</g>`;
|
||
});
|
||
return `<div style="padding-top:4px"><svg viewBox="0 0 ${w} ${D.pollutants.length * rowH + 6}" width="100%">${rows}
|
||
<text x="${barX + (1 / 1.45) * barW}" y="${D.pollutants.length * rowH + 2}" text-anchor="middle" font-size="9" fill="${p.bad}">国标限值</text></svg></div>`;
|
||
}
|
||
return `<div class="d-gauges">` + D.pollutants.map(d => {
|
||
const ratio = d.value / d.limit, sk = statusKey(ratio);
|
||
return `<div class="d-g">${C.ringGauge({ value: d.value, limit: d.limit, unit: d.unit, name: d.name, en: d.en, p })}
|
||
<div class="d-g-nm">${d.name}</div><div class="d-g-en">${d.en} · 限值 ${C.fmt(d.limit, 2)}</div>
|
||
<div class="d-g-pill pill-${sk}">${cnLevel[sk]} ${Math.round(ratio * 100)}%</div></div>`;
|
||
}).join('') + `</div>`;
|
||
}
|
||
|
||
// compliance donut or stacked bar
|
||
function complianceViz(mode, p, total) {
|
||
const segs = D.compliance.map(s => ({ label: s.label, value: s.value, color: p[s.key] }));
|
||
const legend = `<div class="d-legend">` + segs.map(s =>
|
||
`<div class="d-leg"><span class="dot" style="background:${s.color}"></span><span class="nm">${s.label}房间</span><span class="vl">${s.value}</span><span class="pc">${pct(s.value, total)}%</span></div>`
|
||
).join('') + `</div>`;
|
||
if (mode === 'bar') {
|
||
let x = 0; const w = 100;
|
||
const segbar = segs.map(s => { const wpc = s.value / total * w; const r = `<div style="width:${wpc}%;background:${s.color}"></div>`; x += wpc; return r; }).join('');
|
||
return `<div style="display:flex;height:18px;border-radius:6px;overflow:hidden;gap:2px;margin:6px 0 16px">${segbar}</div>
|
||
<div style="font-family:var(--display);font-size:34px;font-weight:800;letter-spacing:-.5px">${D.kpis.compliance}<span style="font-size:16px;color:var(--faint)">%</span></div>
|
||
<div style="font-size:12px;color:var(--sub);margin:2px 0 4px">总体房间达标率</div>${legend}`;
|
||
}
|
||
return `<div style="display:flex;justify-content:center;margin:4px 0 10px">${C.donut({ segments: segs, centerNum: D.kpis.compliance + '%', centerLabel: '达标率', p })}</div>${legend}`;
|
||
}
|
||
|
||
window.renderDashboard = function (themeKey, opts) {
|
||
opts = opts || {};
|
||
const T = THEMES[themeKey] || THEMES.dark;
|
||
const p = T.chart;
|
||
const styleVars = Object.entries(T.vars).map(([k, v]) => `${k}:${v}`).join(';');
|
||
const warm = themeKey === 'warm';
|
||
|
||
// worst pollutant flag for latest prediction (主卧 甲醛)
|
||
const hcho = D.pollutants[0];
|
||
const over = Math.round((hcho.value / hcho.limit - 1) * 100);
|
||
|
||
const top = `<div class="d-top">
|
||
<div><div class="d-top-tt">工程污染概览</div><div class="d-top-crumb">${D.project.name} · ${D.project.area}㎡ · ${D.project.type}</div></div>
|
||
<div class="d-spacer"></div>
|
||
<div class="d-search">${ICON.search}<span>搜索项目 / 房间 / 材料</span></div>
|
||
<div class="d-std"><b class="on">GB/T 18883</b><b>GB 50325</b></div>
|
||
<div class="d-iconbtn">${ICON.bell}<span class="d-dot"></span></div>
|
||
</div>`;
|
||
|
||
const side = `<div class="d-side">
|
||
<div class="d-brand"><div class="d-logo">${warm ? ICON.leaf : ICON.bldg}</div>
|
||
<div><div class="d-brand-tt">污染物预测系统</div><div class="d-brand-sub">INDOOR · AIR</div></div></div>
|
||
${nav(0)}
|
||
<div class="d-side-foot">
|
||
<div class="d-user"><div class="d-ava">陈</div>
|
||
<div><div class="d-user-nm">陈工 · 环境工程师</div><span class="d-pro">★ 专业版</span></div></div>
|
||
</div></div>`;
|
||
|
||
const kpis = `<div class="d-kpis">
|
||
${kpiCard('在管项目', 'folder', 'var(--accent-soft)', 'var(--accent-on)', D.kpis.projects, '个', `<span class="d-kpi-tr d-up">${ICON.up}本周 +3</span>`)}
|
||
${kpiCard('房间达标率', 'predict', 'var(--good-soft)', 'var(--good)', D.kpis.compliance, '%', `<span class="d-kpi-tr d-up">${ICON.up}${D.kpis.trend.compliance}%</span>`)}
|
||
${kpiCard('当前超标房间', 'alert', 'var(--bad-soft)', 'var(--bad)', D.kpis.exceedRooms, '间', `<span class="d-kpi-tr d-up">${ICON.down}${Math.abs(D.kpis.trend.exceed)}</span>`)}
|
||
${kpiCard('本周预测', 'flask', 'var(--accent-soft)', 'var(--accent-on)', D.kpis.weekPredictions, '次', `<span class="d-kpi-tr" style="color:var(--accent-on);background:var(--accent-soft)">${ICON.up}活跃</span>`)}
|
||
</div>`;
|
||
|
||
const totalRooms = D.compliance.reduce((s, x) => s + x.value, 0);
|
||
|
||
const colCard = `<div class="card sp8">
|
||
<div class="card-h"><div><div class="card-t"><span class="d-bar"></span>各房间 · 甲醛预测浓度</div><div class="card-sub">柱状图按状态着色 · 红=超标 / 橙=临界 / 绿=达标,对照国标限值</div></div>
|
||
<span class="card-tag">单位 mg/m³</span></div>
|
||
${C.columns({ items: D.rooms, limit: D.roomLimit, limit2: D.roomLimit2, unit: 'mg/m³', p, w: 760, h: 268 })}
|
||
</div>`;
|
||
|
||
const donutCard = `<div class="card sp4">
|
||
<div class="card-h"><div class="card-t"><span class="d-bar"></span>项目达标率</div><span class="card-tag">${totalRooms} 间房</span></div>
|
||
${complianceViz(opts.compliance || 'donut', p, totalRooms)}
|
||
</div>`;
|
||
|
||
const gaugeCard = `<div class="card sp5">
|
||
<div class="card-h"><div><div class="card-t"><span class="d-bar"></span>最近一次预测 · 主卧</div><div class="card-sub">${D.project.updated} · 6 项污染物 vs 国标限值</div></div></div>
|
||
<div class="d-alert">${ICON.alert}<span>甲醛 ${hcho.value} mg/m³ · 超出 GB/T 18883 限值 ${over}%,建议加强通风并核查人造板材料</span></div>
|
||
${pollutantViz(opts.pollutant || 'ring', p)}
|
||
</div>`;
|
||
|
||
const radarCard = `<div class="card sp4">
|
||
<div class="card-h"><div class="card-t"><span class="d-bar"></span>污染物雷达</div><span class="card-tag">主卧</span></div>
|
||
<div class="card-sub" style="margin:-8px 0 2px">虚线红环 = 国标限值(比值 1.0)</div>
|
||
${C.radar({ axes: D.pollutants.map(d => ({ name: d.name, value: d.value, limit: d.limit })), p, size: 250 })}
|
||
</div>`;
|
||
|
||
const decayCard = `<div class="card sp3">
|
||
<div class="card-h"><div class="card-t"><span class="d-bar"></span>甲醛衰减</div></div>
|
||
<div class="card-sub" style="margin:-8px 0 8px">随通风天数</div>
|
||
${C.decayArea({ points: D.decay, limit: D.roomLimit, unit: 'mg/m³', p, w: 300, h: 176 })}
|
||
</div>`;
|
||
|
||
const tableRows = D.exceed.map(r => {
|
||
const ratio = r.value / r.limit;
|
||
return `<tr><td><div class="rm">${r.room}</div><div class="pj">${r.project}</div></td>
|
||
<td><span class="d-pol">${r.pollutant}</span></td>
|
||
<td class="vn" style="color:${C.status(ratio, p)}">${C.fmt(r.value, r.value >= 10 ? 0 : 3)}</td>
|
||
<td class="lm">${C.fmt(r.limit, 2)}</td>
|
||
<td><span class="d-chip chip-${r.level}">${cnLevel[r.level]} ${Math.round(ratio * 100)}%</span></td></tr>`;
|
||
}).join('');
|
||
const tableCard = `<div class="card sp8">
|
||
<div class="card-h"><div class="card-t"><span class="d-bar"></span>超标房间清单</div><span class="card-tag">${ICON.alert ? '' : ''}共 ${D.exceed.length} 条 · 按超标率排序</span></div>
|
||
<table class="d-tbl"><thead><tr><th>房间 / 项目</th><th>污染物</th><th>预测浓度</th><th>限值</th><th>状态</th></tr></thead>
|
||
<tbody>${tableRows}</tbody></table></div>`;
|
||
|
||
const matCard = `<div class="card sp4">
|
||
<div class="card-h"><div><div class="card-t"><span class="d-bar"></span>污染源识别 · 材料贡献</div><div class="card-sub">主卧甲醛 · 公式溯源排行</div></div></div>
|
||
${C.hbars({ items: D.materials.map((m, i) => ({ ...m, color: i === 0 ? p.bad : i === 1 ? p.warn : p.accent })), p, w: 340, rowH: 40 })}
|
||
</div>`;
|
||
|
||
let body;
|
||
if (warm) {
|
||
const heroComp = `<div class="d-hero"><div class="d-hero-l">
|
||
${C.donut({ segments: D.compliance.map(s => ({ label: s.label, value: s.value, color: p[s.key] })), centerNum: D.kpis.compliance + '%', centerLabel: '达标率', p, size: 150 })}
|
||
<div class="d-hero-txt"><div class="t">在管 ${D.kpis.projects} 个项目 · ${totalRooms} 间房</div>
|
||
<div class="n">${D.compliance[2].value} 间超标 · ${D.compliance[1].value} 间临界</div>
|
||
<div class="t" style="margin-top:8px">最近更新 ${D.project.updated}</div></div>
|
||
</div>
|
||
<div class="d-kpis" style="margin:0">
|
||
${kpiCard('在管项目', 'folder', 'var(--accent-soft)', 'var(--accent-on)', D.kpis.projects, '个', '')}
|
||
${kpiCard('达标率', 'predict', 'var(--good-soft)', 'var(--good)', D.kpis.compliance, '%', `<span class="d-kpi-tr d-up">${ICON.up}${D.kpis.trend.compliance}</span>`)}
|
||
${kpiCard('超标房间', 'alert', 'var(--bad-soft)', 'var(--bad)', D.kpis.exceedRooms, '间', '')}
|
||
${kpiCard('本周预测', 'flask', 'var(--accent-soft)', 'var(--accent-on)', D.kpis.weekPredictions, '次', '')}
|
||
</div></div>`;
|
||
body = heroComp + `<div class="d-grid">
|
||
${colCard}
|
||
${gaugeCard.replace('sp5','sp4')}
|
||
${radarCard}
|
||
${decayCard.replace('sp3','sp4')}
|
||
${matCard}
|
||
${tableCard.replace('sp8','sp12')}
|
||
</div>`;
|
||
} else {
|
||
body = kpis + `<div class="d-grid">
|
||
${colCard}${donutCard}
|
||
${gaugeCard}${radarCard}${decayCard}
|
||
${tableCard}${matCard}
|
||
</div>`;
|
||
}
|
||
|
||
return `<div class="dash ${T.cls}" style="${styleVars}">${C.defs(p)}${side}
|
||
<div class="d-main">${top}<div class="d-scroll">${body}</div></div></div>`;
|
||
};
|
||
})();
|