diff --git a/AutoHaiTun.js b/AutoHaiTun.js index cbe85ab..da2018d 100644 --- a/AutoHaiTun.js +++ b/AutoHaiTun.js @@ -1,20 +1,21 @@ // ==UserScript== // @name 海豚自动化工具 // @namespace http://tampermonkey.net/ -// @version 1.2 +// @version 1.3 // @description 自动从代码块复制内容到编辑器,保存并重启笔记本 // @author Kakune55 Carryc // @match https://labs.wd.dolphin-labs.com/data-analysis/* // @grant none -// @run-at document-end +// @run-at document-idle // ==/UserScript== + (function() { 'use strict'; // 通知管理器 - 管理多个通知的位置 const NotificationManager = { notifications: [], - baseTop: 70, // 初始顶部位置 + baseTop: 45, // 初始顶部位置 gap: 10, // 通知间距 add: function(element) { let topPosition = this.baseTop; @@ -82,75 +83,172 @@ console.log('等待重启确认模态框...'); const checkModal = setInterval(() => { const modals = [...document.querySelectorAll('.ivu-modal')]; - const targetModal = modals.find(modal => modal.textContent.includes('重新启动内核并重新运行整个笔记本')); + const targetModal = modals.find(modal => + modal.textContent.includes('重新启动内核并重新运行整个笔记本') + ); + if (targetModal) { const confirmButton = targetModal.querySelector('.ivu-btn-primary'); if (confirmButton) { confirmButton.click(); console.log('已点击"确定"按钮'); clearInterval(checkModal); + // 执行完成后通知用户 setTimeout(() => { - showNotification('自动化执行完成:代码已复制、保存并重启', 'success'); + showNotification('自动化执行完成:代码已复制、保存并重启!若无警告,等待所有代码块执行完成后自行保存退出!', 'success', 6000); }, 1000); } } }, 200); } - // 提取代码文本 - function extractCodeText(codeBlock) { - return codeBlock.innerText || [...codeBlock.querySelectorAll('span')].reduce((text, span) => text + span.textContent, ''); + // 查找所有目标代码块(包括“代码示例”和练习答案) + function findAllCodeBlocks() { + const textCellWraps = Array.from(document.querySelectorAll('.text-cell-wrap')); + + const allCodeBlocks = []; + function findCodeBlock(container) { + if (!container) return null; + const codeBlock = container.querySelector('pre code'); + return codeBlock || findCodeBlock(container.nextElementSibling); + } + + textCellWraps.forEach((cellWrap, index) => { + console.log(`处理容器 ${index + 1}:`, cellWrap); + + // 优先查找“代码示例”代码块 + const exampleMarker = cellWrap.querySelector('.alert.alert-success.alertsuccess'); + if (exampleMarker && exampleMarker.textContent.includes('代码示例')) { + const codeBlock = findCodeBlock(cellWrap); + if (codeBlock) { + allCodeBlocks.push(codeBlock); + console.log(`找到“代码示例”代码块内容:`, codeBlock.textContent.trim()); + } else { + console.warn(`未找到“代码示例”代码块!`); + } + } + + // 然后查找练习答案代码块 + const collapseDiv = cellWrap.querySelector('.collapse'); + if (collapseDiv) { + const codeBlocks = collapseDiv.querySelectorAll('pre code'); + if (codeBlocks.length > 0) { + codeBlocks.forEach((codeBlock) => { + allCodeBlocks.push(codeBlock); + console.log(`找到练习答案代码块内容:`, codeBlock.textContent.trim()); + }); + } else { + console.warn(`容器 ${index + 1} 中未找到练习答案代码块!`); + } + } + }); + + return allCodeBlocks; } - // 显示美化的通知 - function showNotification(message, type = 'info') { - const notification = document.createElement('div'); - notification.className = 'dolphin-notification'; - let iconHTML = ''; - let borderColor = ''; - if (type === 'success') { - iconHTML = '
'; - borderColor = '#4CAF50'; - } else if (type === 'error') { - iconHTML = '
'; - borderColor = '#F44336'; - } else { - iconHTML = '
ℹ️
'; - borderColor = '#2196F3'; + // 主函数:自动化流程 + function runAutomation() { + const button = document.getElementById('dolphin-auto-btn'); + setButtonState(button, 'running'); // 修改按钮状态为“执行中” + + try { + // 1. 查找所有目标代码块 + const allCodeBlocks = findAllCodeBlocks(); + console.log(`共找到 ${allCodeBlocks.length} 个有效代码块:`); + allCodeBlocks.forEach((codeBlock, index) => { + console.log(`代码块 ${index + 1}:`, codeBlock.textContent.trim()); + }); + + // 2. 获取所有 CodeMirror 编辑器实例 + const codeMirrors = Array.from(document.querySelectorAll('.CodeMirror')) + .map(cmElement => cmElement.CodeMirror) + .filter(cmInstance => !!cmInstance); + + console.log(`找到 ${codeMirrors.length} 个 CodeMirror 实例:`); + + if (allCodeBlocks.length === 0 || codeMirrors.length === 0) { + throw new Error("未找到足够的代码块或编辑器!"); + } + + // 3. 将代码块内容填充到 CodeMirror 编辑器 + showNotification(`正在填充 ${Math.min(allCodeBlocks.length, codeMirrors.length)} 个代码块...`, 'info'); + const minCount = Math.min(allCodeBlocks.length, codeMirrors.length); + for (let i = 0; i < minCount; i++) { + const codeText = allCodeBlocks[i].textContent.trim(); + const cmInstance = codeMirrors[i]; + if (cmInstance) { + cmInstance.setValue(codeText); // 使用 CodeMirror API 填充内容 + console.log(`已填充代码块 ${i + 1} 到 CodeMirror ${i + 1}`); + } else { + try { + const textarea = codeMirrors[i].querySelector('textarea'); + if (textarea) { + textarea.value = codeText; // 使用替代方法填充内容 + console.log(`使用替代方法填充代码块 ${i + 1}`); + } + } catch (e) { + console.error(`无法填充代码块 ${i + 1}:`, e); + } + } + } + + // 4. 检查多余的 CodeMirror 实例并通知用户 + if (codeMirrors.length > allCodeBlocks.length) { + const extraCount = codeMirrors.length - allCodeBlocks.length; + console.warn(`多余的 CodeMirror 实例未被填充: 多出 ${extraCount} 个实例`); + showNotification( + `多余的 CodeMirror 实例未被填充: 多出 ${extraCount} 个实例,请检查最后是否有练习,手动填充`, + 'warning', + 10000 // 通知停留时间设置为 10 秒 + ); + } + + // 5. 点击保存按钮 + console.log('准备点击保存按钮...'); + showNotification('正在保存笔记本...', 'info'); + const saveButton = findSaveButton(); + if (!saveButton) { + throw new Error("未找到保存按钮!"); + } + saveButton.click(); + console.log('已点击保存按钮'); + + // 6. 等待保存成功后重启 + waitForMessage(() => { + console.log('准备点击重启按钮...'); + showNotification('正在重启笔记本...', 'info'); + const restartButton = findRestartButton(); + if (!restartButton) { + throw new Error("未找到重启按钮!"); + } + restartButton.click(); + console.log('找到重启按钮,点击中...'); + waitForRestartModal(); // 等待模态框确认 + setButtonState(button, 'completed'); // 修改按钮状态为“已完成” + }); + + } catch (error) { + console.error(error.message); + showNotification(`自动化执行失败:${error.message}`, 'error'); // 显示错误通知 + setButtonState(button, 'failed'); // 修改按钮状态为“失败” } - notification.innerHTML = iconHTML + `
${message}
`; - notification.style.cssText = ` - position: fixed; right: 300px; padding: 15px 20px; background: white; color: #333; border-radius: 6px; - box-shadow: 0 4px 15px rgba(0,0,0,0.15); z-index: 10000; max-width: 350px; font-size: 14px; border-left-color: ${borderColor}; - display: flex; align-items: center; transition: top 0.3s ease; - `; - document.body.appendChild(notification); - NotificationManager.add(notification); + } - let timeoutId; - notification.addEventListener('mouseenter', () => clearTimeout(timeoutId)); - notification.addEventListener('mouseleave', () => startHideTimeout()); + // 辅助函数:查找保存按钮 + function findSaveButton() { + return document.querySelector('.btn-wrap .btn-name') || + document.querySelector('[title="保存"]') || + [...document.querySelectorAll('button')].find(btn => btn.textContent.includes('保存')); + } - function startHideTimeout() { - timeoutId = setTimeout(() => { - notification.classList.add('hiding'); - setTimeout(() => { - notification.remove(); - NotificationManager.remove(notification); - }, 500); - }, 3000); - } - startHideTimeout(); - - notification.addEventListener('click', () => { - clearTimeout(timeoutId); - notification.classList.add('hiding'); - setTimeout(() => { - notification.remove(); - NotificationManager.remove(notification); - }, 500); + // 辅助函数:查找重启按钮 + function findRestartButton() { + const menuItems = [...document.querySelectorAll('.ivu-dropdown-item, [role="menuitem"], .menu-item')]; + const possibleTexts = ['重启 & 运行所有', '重启并运行所有', '重启&运行所有', '重启和运行所有']; + return menuItems.find(item => { + const text = item.textContent.trim(); + return possibleTexts.some(pt => text.includes(pt)); }); - return notification; } // 设置按钮状态 @@ -167,150 +265,78 @@ button.disabled = newState.disabled; button.innerHTML = newState.html; button.style.backgroundColor = newState.backgroundColor; + if (state === 'completed') { button.style.animation = 'pulse 0.5s ease-in-out'; setTimeout(() => setButtonState(button, 'idle'), 3000); } } - // 查找所有目标代码块(包括“代码示例”和练习答案) - function findAllCodeBlocks() { - // 找到所有包含“代码示例”的父容器 - const textCellWraps = Array.from(document.querySelectorAll('.text-cell-wrap')) - .filter(cellWrap => cellWrap.querySelector('.alert.alert-success.alertsuccess')?.textContent.includes('代码示例')); + // 显示美化的通知 + function showNotification(message, type = 'info', duration = 3000) { + const notification = document.createElement('div'); + notification.className = 'dolphin-notification'; + let iconHTML = ''; + let borderColor = ''; - // 收集紧跟在“代码示例”后面的
 元素
-        const exampleCodeBlocks = [];
-
-        // 辅助函数:递归查找 

-        function findCodeBlock(container) {
-            if (!container) return null;
-            const codeBlock = container.querySelector('pre code');
-            return codeBlock || findCodeBlock(container.nextElementSibling);
-        }
-
-        textCellWraps.forEach((cellWrap, index) => {
-            console.log(`处理代码示例 ${index + 1} 的父容器:`, cellWrap);
-            const codeBlock = findCodeBlock(cellWrap);
-            if (codeBlock) {
-                exampleCodeBlocks.push(codeBlock);
-                console.log(`找到“代码示例”代码块内容:`, codeBlock.textContent.trim());
-            } else {
-                console.warn(`未找到“代码示例”代码块!`);
-            }
-        });
-
-        // 找到所有练习答案区域中的 

-        const answerCodeBlocks = [];
-        const collapseDivs = Array.from(document.querySelectorAll('.collapse'));
-        console.log(`找到 ${collapseDivs.length} 个练习答案区域:`);
-        collapseDivs.forEach((collapseDiv, index) => {
-            console.log(`检查练习答案区域 ${index + 1}:`, collapseDiv);
-            const codeBlocks = collapseDiv.querySelectorAll('pre code');
-            if (codeBlocks.length > 0) {
-                codeBlocks.forEach((codeBlock) => {
-                    answerCodeBlocks.push(codeBlock);
-                    console.log(`找到练习答案代码块内容:`, codeBlock.textContent.trim());
-                });
-            } else {
-                console.warn(`练习答案区域 ${index + 1} 中未找到 
!`);
-            }
-        });
-
-        // 合并所有代码块
-        return [...exampleCodeBlocks, ...answerCodeBlocks];
-    }
-
-    // 主函数
-    function runAutomation() {
-        const button = document.getElementById('dolphin-auto-btn');
-        setButtonState(button, 'running');
-
-        // 查找所有目标代码块
-        const allCodeBlocks = findAllCodeBlocks();
-        console.log(`共找到 ${allCodeBlocks.length} 个有效代码块:`);
-        allCodeBlocks.forEach((codeBlock, index) => {
-            console.log(`代码块 ${index + 1}:`, codeBlock.textContent.trim());
-        });
-
-        // 查找所有 CodeMirror 编辑器实例
-        const codeMirrors = Array.from(document.querySelectorAll('.CodeMirror'))
-            .map(cmElement => cmElement.CodeMirror)
-            .filter(cmInstance => !!cmInstance);
-
-        console.log(`找到 ${codeMirrors.length} 个 CodeMirror 实例:`);
-
-        if (allCodeBlocks.length > 0 && codeMirrors.length > 0) {
-            showNotification(`正在填充 ${Math.min(allCodeBlocks.length, codeMirrors.length)} 个代码块...`, 'info');
-
-            // 将代码块内容复制到编辑器
-            allCodeBlocks.forEach((codeBlock, index) => {
-                if (index < codeMirrors.length) {
-                    const codeText = extractCodeText(codeBlock);
-                    const cm = codeMirrors[index];
-                    if (cm) {
-                        cm.setValue(codeText);
-                        console.log(`已填充代码块 ${index + 1}`);
-                    } else {
-                        try {
-                            const textarea = codeMirrors[index].querySelector('textarea');
-                            if (textarea) {
-                                textarea.value = codeText;
-                                console.log(`使用替代方法填充代码块 ${index + 1}`);
-                            }
-                        } catch (e) {
-                            console.error(`无法填充代码块 ${index + 1}:`, e);
-                        }
-                    }
-                }
-            });
-
-            // 点击保存按钮
-            console.log('准备点击保存按钮...');
-            showNotification('正在保存笔记本...', 'info');
-            const saveButton = document.querySelector('.btn-wrap .btn-name') || 
-                              document.querySelector('[title="保存"]') || 
-                              [...document.querySelectorAll('button')].find(btn => 
-                                  btn.textContent.includes('保存'));
-            if (saveButton) {
-                saveButton.click();
-                console.log('已点击保存按钮');
-                // 等待保存成功后重启
-                waitForMessage(() => {
-                    console.log('准备点击重启按钮...');
-                    showNotification('正在重启笔记本...', 'info');
-                    const menuItems = [...document.querySelectorAll('.ivu-dropdown-item, [role="menuitem"], .menu-item')];
-                    const possibleTexts = ['重启 & 运行所有', '重启并运行所有', '重启&运行所有', '重启和运行所有'];
-                    const restartButton = menuItems.find(item => {
-                        const text = item.textContent.trim();
-                        return possibleTexts.some(pt => text.includes(pt));
-                    });
-                    if (restartButton) {
-                        console.log('找到重启按钮,点击中...');
-                        restartButton.click();
-                        waitForRestartModal();
-                        setButtonState(button, 'completed');
-                    } else {
-                        console.error('未找到重启按钮!');
-                        console.log('可用菜单项:');
-                        menuItems.forEach((item, i) => console.log(`${i+1}. "${item.textContent.trim()}"`));
-                        // 执行遇到错误,通知用户
-                        showNotification('自动化执行失败:未找到重启按钮!', 'error');
-                        setButtonState(button, 'failed');
-                    }
-                });
-            } else {
-                console.error('未找到保存按钮!');
-                // 执行遇到错误,通知用户
-                showNotification('自动化执行失败:未找到保存按钮!', 'error');
-                setButtonState(button, 'failed');
-            }
+        if (type === 'success') {
+            iconHTML = '
'; + borderColor = '#4CAF50'; + } else if (type === 'error') { + iconHTML = '
'; + borderColor = '#F44336'; + } else if (type === 'warning') { + iconHTML = '
⚠️
'; + borderColor = '#FFA000'; } else { - console.error("未找到足够的代码块或编辑器!"); - // 执行遇到错误,通知用户 - showNotification('自动化执行失败:未找到足够的代码块或编辑器!', 'error'); - setButtonState(button, 'failed'); + iconHTML = '
ℹ️
'; + borderColor = '#2196F3'; } + + notification.innerHTML = iconHTML + `
${message}
`; + notification.style.cssText = ` + position: fixed; + right: 300px; + padding: 15px 20px; + background: white; + color: #333; + border-radius: 6px; + box-shadow: 0 4px 15px rgba(0,0,0,0.15); + z-index: 10000; + max-width: 350px; + font-size: 14px; + border-left-color: ${borderColor}; + display: flex; + align-items: center; + transition: top 0.3s ease; + `; + document.body.appendChild(notification); + NotificationManager.add(notification); + + let timeoutId; + notification.addEventListener('mouseenter', () => clearTimeout(timeoutId)); + notification.addEventListener('mouseleave', () => startHideTimeout()); + + function startHideTimeout() { + timeoutId = setTimeout(() => { + notification.classList.add('hiding'); + setTimeout(() => { + notification.remove(); + NotificationManager.remove(notification); + }, 500); + }, duration); // 使用传递的 duration 参数控制显示时间 + } + startHideTimeout(); + + notification.addEventListener('click', () => { + clearTimeout(timeoutId); + notification.classList.add('hiding'); + setTimeout(() => { + notification.remove(); + NotificationManager.remove(notification); + }, 500); + }); + return notification; } // 创建悬浮按钮 @@ -320,8 +346,8 @@ button.innerHTML = '运行自动化'; button.style.cssText = ` position: fixed; - top: 20px; - right: 300px; /* 距离右侧300像素 */ + top: 0px; + right: 300px; z-index: 10000; padding: 12px 20px; background-color: #2196F3; @@ -338,12 +364,14 @@ min-width: 140px; font-weight: 600; `; - // 鼠标悬停效果,缓慢放大并加深颜色 + + // 鼠标悬停效果 button.addEventListener('mouseover', () => { button.style.transform = 'translateY(-2px)'; button.style.boxShadow = '0 5px 15px rgba(33, 150, 243, 0.4)'; button.style.backgroundColor = '#1976D2'; }); + button.addEventListener('mouseout', () => { button.style.transform = ''; button.style.boxShadow = '0 3px 10px rgba(33, 150, 243, 0.3)'; @@ -351,7 +379,8 @@ button.style.backgroundColor = '#2196F3'; } }); - // 添加点击波纹效果 + + // 点击波纹效果 button.addEventListener('mousedown', (e) => { const ripple = document.createElement('span'); const rect = button.getBoundingClientRect(); @@ -375,12 +404,14 @@ ripple.remove(); }, 600); }); + // 点击事件 button.addEventListener('click', () => { if (!button.disabled) { runAutomation(); } }); + document.body.appendChild(button); return button; } @@ -391,7 +422,7 @@ const button = createFloatingButton(); // 显示欢迎通知 setTimeout(() => { - showNotification('海豚自动化工具已加载,点击按钮开始自动化操作', 'info'); + showNotification('海豚自动化工具 v1.3 已加载,点击按钮开始自动化操作', 'info', 5000); // 通知显示 5 秒 }, 1000); }