Procházet zdrojové kódy

添加导出图片功能

cc12458 před 1 týdnem
rodič
revize
2f01d36687

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
production/cracker/six-messenger.legacy.min.js


+ 1 - 0
production/report.html

@@ -24,6 +24,7 @@
   <link rel="shortcut icon" href="/favicon.ico">
   <title>&lrm;</title>
   <style type="text/css">body{margin:0;padding:0; background-color: #fff;}.app-loading{position:fixed;width:100%;height:100%;background-color:#fff;z-index:10000;display:block;}.loading-spinner{width:60px;height:60px;position:absolute;left:50%;top:50%;margin-left:-30px;margin-top:-30px}.loading-bounce1,.loading-bounce2{width:100%;height:100%;border-radius:50%;background-color:#39caaf;opacity:.6;position:absolute;top:0;left:0;-webkit-animation:bounce 2.0s infinite ease-in-out;animation:bounce 2.0s infinite ease-in-out}.loading-bounce2{-webkit-animation-delay:-1.0s;animation-delay:-1.0s}@-webkit-keyframes bounce{0%,100%{-webkit-transform:scale(0.0)}50%{-webkit-transform:scale(1.0)}}@keyframes bounce{0%,100%{transform:scale(0.0);-webkit-transform:scale(0.0)}50%{transform:scale(1.0);-webkit-transform:scale(1.0)}}.loading-txt{position: absolute; bottom: -30px; line-height: 30px; font-size: 14px; text-align: center; width: 100%;}</style>
+  <script src="./cracker/six-messenger.legacy.min.js"></script>
   <script>
     window.agency = {
       api: "https://api.reborn-tech.com",

+ 54 - 0
src/single.html

@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Title</title>
+    <style>
+        iframe {
+            display: inline-block;
+            width: 510px;
+            height: 432px;
+            border: none;
+        }
+    </style>
+    <script>
+        window.addEventListener('message', event => {
+            const data = event.data;
+            const source = event.source;
+            switch (data.type) {
+                case 'event:draw': return draw(data, source);
+                case 'event:load': return insert(source);
+            }
+            console.log('log-->', data);
+        });
+
+        function draw({data, done}, source) {
+            if (source === document.querySelector('iframe.left').contentWindow) {
+                document.querySelector('img.left').src = data;
+                if (done) console.info('[left] 绘制完成');
+            } else if (source === document.querySelector('iframe.right').contentWindow) {
+                document.querySelector('img.right').src = data;
+                if (done) console.info('[right] 绘制完成');
+            }
+        }
+
+        function insert(source) {
+            source.postMessage({
+                type: 'insert:css', code: `
+                   .wrap { padding: 0!important; }
+                `,
+            });
+        }
+    </script>
+</head>
+<body>
+<iframe class="left" src="../production/report.html#/single?mid=d78511637507423fb4323ab4cc38210e&access_session=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhcHBJZCI6ImhKbjVEM3JyIiwiYXBwU2VjcmV0IjoiMGYwZDQ1MDIyNmYzMTZkNjY4ZDZlYjhiY2ZiYjRhY2NhNGNjYzQ3ZiJ9.Gnk2gUdO5EsN_C8fvwj9QaWpiNGizzFAypERKPk_u7A&appId=hJn5D3rr&overallLeft=true"></iframe>
+<iframe class="right" src="../production/report.html#/single?mid=d78511637507423fb4323ab4cc38210e&access_session=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhcHBJZCI6ImhKbjVEM3JyIiwiYXBwU2VjcmV0IjoiMGYwZDQ1MDIyNmYzMTZkNjY4ZDZlYjhiY2ZiYjRhY2NhNGNjYzQ3ZiJ9.Gnk2gUdO5EsN_C8fvwj9QaWpiNGizzFAypERKPk_u7A&appId=hJn5D3rr&overallRight=true"></iframe>
+
+<div>
+    <img class="left" src="" alt="">
+    <img class="right" src="" alt="">
+</div>
+
+</body>
+</html>

+ 131 - 0
src/six-messenger.js

@@ -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;
+    });
+}

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů