r115/app.js · 约 9883 字符
1
2var C = { brand: '#3b5bdb', brandD: '#2b44b8', good: '#2f9e44', bad: '#e03131', hot: '#f08c00', gray: '#868e96', grayL: '#adb5bd' };
3var $ = function (s, r) { return (r || document).querySelector(s); };
4var $$ = function (s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)); };
5function esc(s) {
6 return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) {
7 return { '&': '&', '<': '<', '>': '>', '"': '"' }[c];
8 });
9}
10function toast(m) {
11 var t = $('#__toast');
12 if (!t) { t = document.createElement('div'); t.id = '__toast'; t.style.cssText = 'position:fixed;left:50%;top:42%;transform:translate(-50%,-50%);background:rgba(0,0,0,.8);color:#fff;padding:10px 16px;border-radius:10px;font-size:13px;z-index:99;max-width:70%;text-align:center;'; document.body.appendChild(t); }
13 t.textContent = m; t.style.display = 'block';
14 clearTimeout(t._t); t._t = setTimeout(function () { t.style.display = 'none'; }, 1600);
15}
16
17var DS = {
18 _p: {},
19 get: function (name) {
20 if (this._p[name]) return this._p[name];
21 var p = new Promise(function (res) {
22 fetch('data/' + name + '.json').then(function (r) { return r.json(); }).then(function (j) { res(j); })
23 .catch(function () { res((window.__DATA__ && window.__DATA__[name]) || {}); });
24 });
25 this._p[name] = p; return p;
26 }
27};
28
29/* ============ SVG 图表引擎 ============ */
30var Chart = {};
31Chart.line = function (o) {
32 var w = o.w || 340, h = o.h || 200, pad = 30, labels = o.labels, series = o.series, max = o.max, min = o.min || 0, n = labels.length;
33 var gw = w - pad * 2, gh = h - pad * 2;
34 function X(i) { return pad + (n <= 1 ? gw / 2 : gw * i / (n - 1)); }
35 function Y(v) { return pad + gh - (v - min) / (max - min) * gh; }
36 var g = '', i;
37 for (i = 0; i <= 4; i++) { var gy = pad + gh * i / 4, val = Math.round(max - (max - min) * i / 4); g += '<line x1="' + pad + '" y1="' + gy + '" x2="' + (w - pad) + '" y2="' + gy + '" stroke="#eef0f3"/><text x="' + (pad - 5) + '" y="' + (gy + 3) + '" font-size="9" fill="#9aa0a6" text-anchor="end">' + val + '</text>'; }
38 var xl = ''; for (i = 0; i < n; i++) { xl += '<text x="' + X(i) + '" y="' + (h - 9) + '" font-size="9" fill="#9aa0a6" text-anchor="middle">' + esc(labels[i]) + '</text>'; }
39 var paths = '', dots = '', leg = '';
40 series.forEach(function (s) {
41 var d = ''; for (i = 0; i < n; i++) { d += (i ? 'L' : 'M') + X(i).toFixed(1) + ' ' + Y(s.data[i]).toFixed(1) + ' '; }
42 paths += '<path d="' + d + '" fill="none" stroke="' + s.color + '" stroke-width="2.2" stroke-linejoin="round" stroke-linecap="round"/>';
43 for (i = 0; i < n; i++) { dots += '<circle cx="' + X(i).toFixed(1) + '" cy="' + Y(s.data[i]).toFixed(1) + '" r="2.6" fill="' + s.color + '"/>'; }
44 leg += '<span style="display:inline-flex;align-items:center;gap:4px;margin-right:10px;font-size:11px;color:#646a73"><i style="width:11px;height:3px;background:' + s.color + ';display:inline-block;border-radius:2px"></i>' + esc(s.name) + '</span>';
45 });
46 return '<svg viewBox="0 0 ' + w + ' ' + h + '" width="100%" preserveAspectRatio="xMidYMid meet">' + g + xl + paths + dots + '</svg><div style="margin-top:4px">' + leg + '</div>';
47};
48Chart.radar = function (o) {
49 var size = o.size || 240, axes = o.axes, series = o.series, max = o.max || 1, n = axes.length, cx = size / 2, cy = size / 2, R = size / 2 - 28;
50 function pt(i, r) { var a = -Math.PI / 2 + i * 2 * Math.PI / n; return [cx + r * Math.cos(a), cy + r * Math.sin(a)]; }
51 var g = '', i, k;
52 for (k = 1; k <= 4; k++) { var rr = R * k / 4, d = ''; for (i = 0; i < n; i++) { var p = pt(i, rr); d += (i ? 'L' : 'M') + p[0].toFixed(1) + ' ' + p[1].toFixed(1) + ' '; } g += '<path d="' + d + 'Z" fill="none" stroke="#eef0f3"/>'; }
53 var sp = '', al = '';
54 for (i = 0; i < n; i++) { var p = pt(i, R); sp += '<line x1="' + cx + '" y1="' + cy + '" x2="' + p[0].toFixed(1) + '" y2="' + p[1].toFixed(1) + '" stroke="#eef0f3"/>'; var lp = pt(i, R + 13); al += '<text x="' + lp[0].toFixed(1) + '" y="' + lp[1].toFixed(1) + '" font-size="9.5" fill="#646a73" text-anchor="middle">' + esc(axes[i]) + '</text>'; }
55 var polys = '', leg = '';
56 series.forEach(function (s) {
57 var d = ''; for (i = 0; i < n; i++) { var p = pt(i, R * (s.data[i] / max)); d += (i ? 'L' : 'M') + p[0].toFixed(1) + ' ' + p[1].toFixed(1) + ' '; }
58 polys += '<path d="' + d + 'Z" fill="' + s.color + '" fill-opacity="0.18" stroke="' + s.color + '" stroke-width="2"/>';
59 for (i = 0; i < n; i++) { var p = pt(i, R * (s.data[i] / max)); polys += '<circle cx="' + p[0].toFixed(1) + '" cy="' + p[1].toFixed(1) + '" r="2.4" fill="' + s.color + '"/>'; }
60 leg += '<span style="display:inline-flex;align-items:center;gap:4px;margin-right:10px;font-size:11px;color:#646a73"><i style="width:10px;height:10px;background:' + s.color + ';opacity:.5;display:inline-block;border-radius:3px"></i>' + esc(s.name) + '</span>';
61 });
62 return '<svg viewBox="0 0 ' + size + ' ' + size + '" width="100%" preserveAspectRatio="xMidYMid meet">' + g + sp + polys + al + '</svg><div style="text-align:center;margin-top:2px">' + leg + '</div>';
63};
64Chart.bar = function (o) {
65 var w = o.w || 340, h = o.h || 200, pad = 26, labels = o.labels, series = o.series, max = o.max, n = labels.length, m = series.length;
66 var gw = w - pad * 2, gh = h - pad * 2;
67 function Y(v) { return pad + gh - (v / max) * gh; }
68 var g = '', i, k;
69 for (k = 0; k <= 4; k++) { var gy = pad + gh * k / 4, val = Math.round(max * (1 - k / 4)); g += '<line x1="' + pad + '" y1="' + gy + '" x2="' + (w - pad) + '" y2="' + gy + '" stroke="#eef0f3"/><text x="' + (pad - 5) + '" y="' + (gy + 3) + '" font-size="9" fill="#9aa0a6" text-anchor="end">' + val + '</text>'; }
70 var bw = gw / n * 0.62 / m, slot = gw / n, inner = gw / n * 0.62, gap = (slot - inner) / (m + 1);
71 var bars = '', xl = '';
72 for (i = 0; i < n; i++) {
73 var gx = pad + slot * i + (slot - inner) / 2;
74 series.forEach(function (s, si) {
75 var v = s.data[i], bx = gx + si * (bw + gap), by = Y(v), bh = h - pad - by;
76 bars += '<rect x="' + bx.toFixed(1) + '" y="' + by.toFixed(1) + '" width="' + bw.toFixed(1) + '" height="' + bh.toFixed(1) + '" rx="3" fill="' + s.color + '"/>';
77 });
78 xl += '<text x="' + (pad + slot * i + slot / 2) + '" y="' + (h - 9) + '" font-size="9" fill="#9aa0a6" text-anchor="middle">' + esc(labels[i]) + '</text>';
79 }
80 var leg = ''; series.forEach(function (s) { leg += '<span style="display:inline-flex;align-items:center;gap:4px;margin-right:10px;font-size:11px;color:#646a73"><i style="width:10px;height:10px;background:' + s.color + ';display:inline-block;border-radius:2px"></i>' + esc(s.name) + '</span>'; });
81 return '<svg viewBox="0 0 ' + w + ' ' + h + '" width="100%" preserveAspectRatio="xMidYMid meet">' + g + xl + bars + '</svg><div style="margin-top:4px">' + leg + '</div>';
82};
83Chart.ring = function (o) {
84 var size = o.size || 120, pct = o.pct, color = o.color || '#3b5bdb', label = o.label || '';
85 var r = size / 2 - 10, c = 2 * Math.PI * r, off = c * (1 - pct);
86 return '<svg viewBox="0 0 ' + size + ' ' + size + '" width="' + size + '" height="' + size + '"><circle cx="' + (size / 2) + '" cy="' + (size / 2) + '" r="' + r + '" fill="none" stroke="#eef0f3" stroke-width="10"/><circle cx="' + (size / 2) + '" cy="' + (size / 2) + '" r="' + r + '" fill="none" stroke="' + color + '" stroke-width="10" stroke-linecap="round" stroke-dasharray="' + c.toFixed(1) + '" stroke-dashoffset="' + off.toFixed(1) + '" transform="rotate(-90 ' + (size / 2) + ' ' + (size / 2) + ')"/><text x="' + (size / 2) + '" y="' + (size / 2 + 5) + '" font-size="19" font-weight="800" fill="#1f2329" text-anchor="middle">' + esc(label) + '</text></svg>';
87};
88Chart.hbar = function (o) {
89 var max = o.max, items = o.items, row = '';
90 items.forEach(function (it) {
91 var wp = (it.value / max * 100).toFixed(1);
92 var cp = it.compare != null ? (it.compare / max * 100).toFixed(1) : null;
93 row += '<div style="margin:9px 0"><div style="display:flex;justify-content:space-between;font-size:12px;margin-bottom:3px"><span>' + esc(it.label) + '</span><span style="color:#646a73">' + it.value + '<span style="color:#c0c4cc"> /' + max + '</span></span></div>' +
94 '<div style="position:relative;height:9px;background:#eef0f3;border-radius:6px">' +
95 (cp != null ? '<div style="position:absolute;left:0;top:0;height:9px;width:' + cp + '%;background:' + (it.compareColor || '#c5cde6') + ';border-radius:6px;opacity:.6"></div>' : '') +
96 '<div style="position:relative;height:9px;width:' + wp + '%;background:' + it.color + ';border-radius:6px"></div></div></div>';
97 });
98 return '<div>' + row + '</div>';
99};
100
101/* ============ 全局交互委托 ============ */
102document.addEventListener('click', function (e) {
103 var seg = e.target.closest('.seg');
104 if (seg && seg.dataset.group) {
105 var t = e.target.closest('[data-val]');
106 if (t) {
107 $$('.seg[data-group="' + seg.dataset.group + '"] [data-val]').forEach(function (x) { x.classList.remove('on'); });
108 t.classList.add('on');
109 $$('.seg-panel[data-group="' + seg.dataset.group + '"]').forEach(function (p) { p.style.display = 'none'; });
110 var p = $('.seg-panel[data-group="' + seg.dataset.group + '"][data-val="' + t.dataset.val + '"]');
111 if (p) p.style.display = '';
112 }
113 return;
114 }
115 var chip = e.target.closest('.chip');
116 if (chip && chip.dataset.filter) {
117 var f = chip.dataset.filter, scope = chip.closest('[data-filter-scope]') || document;
118 $$('.chip', scope).forEach(function (c) { c.classList.remove('on'); });
119 chip.classList.add('on');
120 $$('[data-status]', scope).forEach(function (el) { el.style.display = (f === '全部' || el.dataset.status === f) ? '' : 'none'; });
121 return;
122 }
123});
124