|
@@ -0,0 +1,131 @@
|
|
|
|
+// ---
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 监听 window 的 message 事件,根据 type 动态插入 script 或 css。
|
|
|
|
+ * 支持通过 postMessage 远程注入脚本或样式。
|
|
|
|
+ */
|
|
|
|
+window.addEventListener('message', (event) => {
|
|
|
|
+ const data = event.data;
|
|
|
|
+ switch (data?.type) {
|
|
|
|
+ case 'insert:script': {
|
|
|
|
+ /**
|
|
|
|
+ * 动态插入 script 标签。
|
|
|
|
+ * @param {string} [data.src] - script 的 src 属性。
|
|
|
|
+ * @param {string} [data.code] - script 的内联代码。
|
|
|
|
+ */
|
|
|
|
+ const script = document.createElement('script');
|
|
|
|
+ if (data.src) script.src = data.src;
|
|
|
|
+ if (data.code) script.textContent = data.code;
|
|
|
|
+ document.head.appendChild(script);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ case 'insert:css': {
|
|
|
|
+ /**
|
|
|
|
+ * 动态插入 css(link 或 style 标签)。
|
|
|
|
+ * @param {string} [data.src] - 外部 css 链接。
|
|
|
|
+ * @param {string} [data.code] - 内联 css 代码。
|
|
|
|
+ */
|
|
|
|
+ if (data.src) {
|
|
|
|
+ const link = document.createElement('link');
|
|
|
|
+ link.rel = 'stylesheet';
|
|
|
|
+ link.href = data.src;
|
|
|
|
+ document.head.appendChild(link);
|
|
|
|
+ }
|
|
|
|
+ if (data.code) {
|
|
|
|
+ const style = document.createElement('style');
|
|
|
|
+ style.textContent = data.code;
|
|
|
|
+ document.head.appendChild(style);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 获取 window.opener 或 window.parent(如果不是自己)。
|
|
|
|
+ * 用于跨窗口通信。
|
|
|
|
+ * @type {Window|undefined}
|
|
|
|
+ */
|
|
|
|
+const windowProxy = window.opener ?? (window.parent !== window ? window.parent : void 0);
|
|
|
|
+
|
|
|
|
+window.addEventListener('loadstart', (event) => { windowProxy?.postMessage({type: 'event:loadstart'}, '*'); });
|
|
|
|
+window.addEventListener('load', (event) => { windowProxy?.postMessage({type: 'event:load'}, '*'); });
|
|
|
|
+/**
|
|
|
|
+ * DOMContentLoaded 事件后,向上级窗口发送事件通知。
|
|
|
|
+ */
|
|
|
|
+document.addEventListener('DOMContentLoaded', () => { windowProxy?.postMessage({type: 'event:DOMContentLoaded'}, '*'); });
|
|
|
|
+
|
|
|
|
+// ---
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * DOMContentLoaded 事件后,根据 url 参数判断是否需要绘制 canvas 图,并将数据通过 postMessage 发送。
|
|
|
|
+ */
|
|
|
|
+document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
+ if (!windowProxy) return;
|
|
|
|
+
|
|
|
|
+ // 需要绘制 canvas 图
|
|
|
|
+ const params = getURLSearchParams();
|
|
|
|
+ if ((params.get('overallLeft') === 'true' || params.get('overallRight') === 'true') && [...params.values()].filter(v => v === 'true').length === 1) {
|
|
|
|
+ _getFirstCanvasData().then(el => {
|
|
|
|
+ let lastImageDataUrl;
|
|
|
|
+ _monitor(() => {
|
|
|
|
+ lastImageDataUrl = el.toDataURL('image/png');
|
|
|
|
+ windowProxy?.postMessage({type: 'event:draw', data: lastImageDataUrl, done: false}, '*');
|
|
|
|
+ }, 10 * 1000).catch(() => {
|
|
|
|
+ windowProxy?.postMessage({type: 'event:draw', data: lastImageDataUrl, done: true}, '*');
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 获取 URL 查询参数。
|
|
|
|
+ * @param {string} [value] - 可选,指定解析的查询字符串。
|
|
|
|
+ * @returns {URLSearchParams}
|
|
|
|
+ */
|
|
|
|
+function getURLSearchParams(value) {
|
|
|
|
+ value ??= `${location.search}&${location.hash.split('?')[1] || ''}`;
|
|
|
|
+ return new URLSearchParams(value);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 轮询执行 predicate,直到返回非 null/undefined 或超时。
|
|
|
|
+ * @param {Function} predicate - 检查条件的函数。
|
|
|
|
+ * @param {number} [timeout=0] - 超时时间(毫秒),0 表示不超时。
|
|
|
|
+ * @returns {Promise<any>} - 返回 predicate 的结果。
|
|
|
|
+ */
|
|
|
|
+function _monitor(predicate, timeout = 0) {
|
|
|
|
+ const start = Date.now();
|
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
|
+ let tag;
|
|
|
|
+ const loop = () => {
|
|
|
|
+ const result = predicate();
|
|
|
|
+ if (result != null) {
|
|
|
|
+ cancelAnimationFrame(tag);
|
|
|
|
+ resolve(result);
|
|
|
|
+ } else if (timeout > 0 && Date.now() - start > timeout) {
|
|
|
|
+ reject(new Error('timeout'));
|
|
|
|
+ } else {
|
|
|
|
+ tag = requestAnimationFrame(loop);
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+ loop();
|
|
|
|
+ });
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 查找页面上第一个可见的 echarts canvas 元素。
|
|
|
|
+ * @returns {Promise<HTMLCanvasElement>} - 返回第一个匹配的 canvas 元素。
|
|
|
|
+ */
|
|
|
|
+function _getFirstCanvasData() {
|
|
|
|
+ /**
|
|
|
|
+ * 递归判断元素是否可见。
|
|
|
|
+ * @param {Element} element
|
|
|
|
+ * @returns {boolean}
|
|
|
|
+ */
|
|
|
|
+ const match = (element) => getComputedStyle(element).display !== 'none' && (!element.parentElement || match(element.parentElement));
|
|
|
|
+ return _monitor(() => {
|
|
|
|
+ const nodes = document.querySelectorAll('.mai-3d [_echarts_instance_] canvas');
|
|
|
|
+ for (const node of nodes) if (match(node)) return node;
|
|
|
|
+ return null;
|
|
|
|
+ });
|
|
|
|
+}
|