Compare commits

...

1 Commits
V1.2 ... main

Author SHA1 Message Date
a3032692b2 fix:修复了部分bug 2025-03-27 18:38:10 +08:00

View File

@ -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);
}
// 显示美化的通知
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = 'dolphin-notification';
let iconHTML = '';
let borderColor = '';
if (type === 'success') {
iconHTML = '<div class="notification-icon">✅</div>';
borderColor = '#4CAF50';
} else if (type === 'error') {
iconHTML = '<div class="notification-icon">❌</div>';
borderColor = '#F44336';
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 {
iconHTML = '<div class="notification-icon"></div>';
borderColor = '#2196F3';
console.warn(`未找到“代码示例”代码块!`);
}
notification.innerHTML = iconHTML + `<div>${message}</div>`;
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);
}, 3000);
}
startHideTimeout();
notification.addEventListener('click', () => {
clearTimeout(timeoutId);
notification.classList.add('hiding');
setTimeout(() => {
notification.remove();
NotificationManager.remove(notification);
}, 500);
// 然后查找练习答案代码块
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 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'); // 修改按钮状态为“失败”
}
}
// 辅助函数:查找保存按钮
function findSaveButton() {
return document.querySelector('.btn-wrap .btn-name') ||
document.querySelector('[title="保存"]') ||
[...document.querySelectorAll('button')].find(btn => btn.textContent.includes('保存'));
}
// 辅助函数:查找重启按钮
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 = '';
// 收集紧跟在“代码示例”后面的 <pre><code> 元素
const exampleCodeBlocks = [];
// 辅助函数:递归查找 <pre><code>
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());
if (type === 'success') {
iconHTML = '<div class="notification-icon">✅</div>';
borderColor = '#4CAF50';
} else if (type === 'error') {
iconHTML = '<div class="notification-icon">❌</div>';
borderColor = '#F44336';
} else if (type === 'warning') {
iconHTML = '<div class="notification-icon">⚠️</div>';
borderColor = '#FFA000';
} else {
console.warn(`未找到“代码示例”代码块!`);
iconHTML = '<div class="notification-icon"></div>';
borderColor = '#2196F3';
}
notification.innerHTML = iconHTML + `<div>${message}</div>`;
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);
});
// 找到所有练习答案区域中的 <pre><code>
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} 中未找到 <pre><code>`);
}
});
// 合并所有代码块
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');
}
} else {
console.error("未找到足够的代码块或编辑器!");
// 执行遇到错误,通知用户
showNotification('自动化执行失败:未找到足够的代码块或编辑器!', 'error');
setButtonState(button, 'failed');
}
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);
}