Grafana 表格自定义下载样式。

张开发
2026/4/4 12:09:45 15 分钟阅读
Grafana 表格自定义下载样式。
我这边的方案是通过 grafana嵌套在iframe中然后获取数据postmessage 给父页面 调用 excel.js 下载。增加一个html panel , 在 onlint 添加如下代码。该代码会在目标panel的标题上 增加一个 按钮点击后触发。var targetPanelId 8; setTimeout(function() { var panel $(.react-grid-item[data-panelid targetPanelId ]); var header panel.find([data-testidheader-container]); if (header.length 0) header panel.find(header); var btn $(button⬇ Download/button) .attr(contenteditable, false) .css({ padding: 2px 12px, font-size: 12px, background: #000, color: #fff, border: none, border-radius: 4px, cursor: pointer, user-select: none, pointer-events: auto, z-index: 99999 }); btn.on(click, function(e) { e.stopPropagation(); e.stopImmediatePropagation(); // ★ 从 React 内部直接取当前显示的完整数据 function extractData(el) { var fiberKey Object.keys(el).find(function(k) { return k.startsWith(__reactFiber$) || k.startsWith(__reactInternalInstance$); }); if (!fiberKey) return null; var fiber el[fiberKey]; var node fiber; var depth 100; while (node depth-- 0) { var p node.memoizedProps || {}; // Grafana 把查询结果放在 props.data.series if (p.data p.data.series p.data.series.length 0) { return p.data.series; } if (p.data Array.isArray(p.data.fields)) { return [p.data]; } if (p.frames p.frames.length 0) { return p.frames; } node node.return; } return null; } // 从 panel 容器开始找 var frames null; var dom panel[0]; // 先从 panel 根元素找 frames extractData(dom); // 没找到就遍历子元素 if (!frames) { var children dom.querySelectorAll(div); for (var i 0; i children.length !frames; i) { frames extractData(children[i]); } } if (!frames || frames.length 0) { console.error(❌ 未找到数据); return; } // 解析 DataFrame → headers rows var headers []; var rows []; frames.forEach(function(frame) { var fields frame.fields || []; if (fields.length 0) return; if (headers.length 0) { headers fields.map(function(f) { return f.config f.config.displayName ? f.config.displayName : f.name || ; }); } // values 可能是数组、ArrayVector、TypedArray var columns fields.map(function(f) { var v f.values; if (Array.isArray(v)) return v; if (v v.buffer) return Array.from(v.buffer); if (v v.toArray) return v.toArray(); if (v typeof v.length number) return Array.from(v); return []; }); var rowCount columns[0] ? columns[0].length : 0; for (var i 0; i rowCount; i) { var row []; for (var j 0; j columns.length; j) { var cell columns[j][i]; // 时间戳转可读 if (fields[j].type time typeof cell number) { cell new Date(cell 1e12 ? cell : cell * 1000) .toISOString().replace(T, ).slice(0, 19); } row.push(cell ! null ? cell : ); } rows.push(row); } }); console.log( 表头:, headers); console.log( 行数:, rows.length); console.log( 前3行:, rows.slice(0, 3)); // postMessage 给父页面 var message { type: GRAFANA_PANEL_DATA, panelId: targetPanelId, timestamp: new Date().toISOString(), data: { headers: headers, rows: rows, rowCount: rows.length } }; if (window.parent window.parent ! window) { window.parent.postMessage(message, *); } else { window.postMessage(message, *); } console.log(✅ 已发送 rows.length 行); }); btn.on(mousedown pointerdown dragstart, function(e) { e.stopPropagation(); e.stopImmediatePropagation(); }); header.css({ display: flex, align-items: center }); header.append(btn); }, 1000);

更多文章