// ==UserScript== // @name 菁优组卷与试卷打印处理程序 // @version 1.2 // @description 自动处理菁优个人组卷与试卷报告界面,使其能够直接打印。兼容 paper/detail、report/detail、paper/make(组卷中心/试题篮)模板页面;解除右键与复制限制 // @author TorchMaker // @match http://www.jyeoo.com/*/paper/detail/* // @match https://www.jyeoo.com/*/paper/detail/* // @match http://www.jyeoo.com/*/report/detail/* // @match https://www.jyeoo.com/*/report/detail/* // @match http://www.jyeoo.com/*/paper/make* // @match https://www.jyeoo.com/*/paper/make* // @icon https://www.jyeoo.com/favicon.ico // @run-at document-idle // @grant GM_registerMenuCommand // @grant GM_notification // @license MIT // ==/UserScript== (function() { 'use strict'; var SCRIPT_VERSION = '1.2'; // 页面类型判定(挂载与处理逻辑共用)。注意:组卷页 id 是查询参数(/paper/make?id=xxx),路径无尾随段 var PATHNAME = window.location.pathname; var IS_MAKE_PAGE = PATHNAME.indexOf('/paper/make') !== -1; // ============ 基础工具层:与具体页面模板解耦 ============ // jyeoo 不同模板页面(paper/detail、report/detail、首页等)提供的全局函数并不一致, // 例如 showConfirm/showMessage 为页面内联函数,showProcess/closeBox 仅部分页面存在, // 因此所有页面函数调用都必须做存在性检查并提供兜底,避免一处缺失导致整个脚本中断。 function getPageFn(name) { try { return (typeof window[name] === 'function') ? window[name] : null; } catch (e) { return null; } } // 页面提示:优先使用 jyeoo 自带 showMessage,缺失时退化为控制台 + GM 通知 function pageMessage(content, success) { var fn = getPageFn('showMessage'); if (fn) { try { fn(content, success === false ? false : true); return; } catch (e) {} } console.log('[菁优打印脚本]', content); try { GM_notification(String(content).replace(//g, ' ')); } catch (e) {} } function pageProcess(content) { var fn = getPageFn('showProcess'); if (fn) { try { fn(content); return; } catch (e) {} } pageMessage(content); } // 页面确认框:优先 jyeoo showConfirm(按钮不会自动关闭弹窗,回调中需 closeBox), // 缺失时退化为浏览器原生 confirm function pageConfirm(content, onYes, onNo) { var confirmFn = getPageFn('showConfirm'); var closeFn = getPageFn('closeBox'); if (confirmFn) { var finish = function(result) { // this 由 jQuery 绑定为被点击的按钮元素,closeBox 依赖它定位弹窗 var btn = this; if (closeFn) { try { closeFn.call(btn, btn); } catch (e) { try { closeFn(); } catch (e2) {} } } if (result) onYes(); else onNo(); }; try { confirmFn(content, function() { finish.call(this, true); }, function() { finish.call(this, false); }, 0); return; } catch (e) {} } if (window.confirm(content.replace(//g, '\n'))) onYes(); else onNo(); } // 判断元素内部是否包含试题/标题正文,防止清理逻辑误删打印内容 // (report/detail 等页面的容器类名与 paper 页不同,不能仅靠 closest('.content') 判断) function containsPrintContent(el) { if (!el || !el.querySelector) return false; return !!(el.querySelector('.QUES_LI') || el.querySelector('.paper-title') || el.querySelector('.ques-list')); } // ============ 脚本初始化 ============ var username = document.querySelector("div.profile-name")?.children[0]?.children[0]?.innerHTML || '用户'; console.log("%c菁优组卷打印处理程序 %cBy TorchMaker Version " + SCRIPT_VERSION + "\n%c菁优网用户 %s,欢迎您使用本脚本", 'font-size: large; font-family: "微软雅黑"', 'font-size: large; font-family: "Consolas"', 'font-size: large; font-family: "微软雅黑"', username); GM_registerMenuCommand("处理后自动刷新", () => { if (localStorage.getItem("EnableAutoRefresh") == 'N') { localStorage.setItem("EnableAutoRefresh", 'Y'); GM_notification("处理后自动刷新:已开启"); } else { localStorage.setItem("EnableAutoRefresh", 'N'); GM_notification("处理后自动刷新:已关闭"); } }); if (localStorage.getItem("EnableAutoRefresh") == null) { localStorage.setItem("EnableAutoRefresh", 'Y'); pageConfirm('请完成脚本设置:是否要在完成处理后自动刷新?
默认值:是。注意:Safari 请填写 N', function() { localStorage.setItem("EnableAutoRefresh", 'Y'); pageMessage('设置成功!'); pageProcess('脚本设置就绪,请点击"立即处理并打印"按钮执行。'); }, function() { localStorage.setItem("EnableAutoRefresh", 'N'); pageMessage('设置成功!'); pageProcess('脚本设置就绪,请点击"立即处理并打印"按钮执行。'); }); } // 打印时隐藏脚本自身 UI(悬浮面板或插入正文信息盒中的按钮都不会被打出来) var uiStyle = document.createElement('style'); uiStyle.textContent = '@media print { .jyeoo-print-tools { display: none !important; } }'; (document.head || document.documentElement).appendChild(uiStyle); // ============ 右键与复制解锁 ============ // 菁优页面会禁用右键菜单与复制/剪切/选中(部分内容仅限会员复制)。 // 事件传播顺序为 window(捕获) → document(捕获) → 目标 → 冒泡,因此在 window 捕获阶段 // stopPropagation 可先于页面自身注册的任何禁用处理器执行;同时清理内联 on* 处理器 // 与 CSS user-select 限制。不拦截 click/mousedown/keydown,避免破坏页面正常交互。 (function enableRightClickAndCopy() { var blockedTypes = ['contextmenu', 'copy', 'cut', 'selectstart', 'dragstart']; blockedTypes.forEach(function(type) { window.addEventListener(type, function(e) { e.stopPropagation(); }, true); }); // 清理内联/属性形式的禁用(如 oncontextmenu="return false") ['documentElement', 'body'].forEach(function(part) { var el = document[part]; if (!el) return; blockedTypes.forEach(function(type) { var prop = 'on' + type; try { el[prop] = null; } catch (e) {} try { el.removeAttribute(prop); } catch (e) {} }); }); // 解除 CSS 层面的选中限制 var unlockStyle = document.createElement('style'); unlockStyle.textContent = 'html body, html body * { -webkit-user-select: text !important; -moz-user-select: text !important;' + ' -ms-user-select: text !important; user-select: text !important; -webkit-touch-callout: default !important; }'; (document.head || document.documentElement).appendChild(unlockStyle); console.log("🔓 右键与复制解锁已启用"); })(); console.log("🔹 创建按钮对象..."); var tipP = document.createElement('p'); tipP.className = 'jyeoo-print-tools'; tipP.innerText = "----- 菁优组卷与试卷打印处理程序 -----"; var processButton = document.createElement('span'); processButton.className = 'jyeoo-print-tools'; processButton.innerHTML = `立即处理并打印`; processButton.onclick = function() { var EnableAutoRefresh = localStorage.getItem("EnableAutoRefresh") == 'Y' ? true : false; // 页面类型判定(PATHNAME / IS_MAKE_PAGE 在脚本初始化时计算) var isPaperDetail = PATHNAME.indexOf('/paper/detail/') !== -1; var isReportDetail = PATHNAME.indexOf('/report/detail/') !== -1; var isMakePage = IS_MAKE_PAGE; // 需要保留标题的页面:组卷页的 .exam-title 是可编辑试卷名输入框,属于打印内容 var isFirstPage = isPaperDetail || isReportDetail || isMakePage; // ===== 撤销日志:记录清理期间的所有删除与样式修改,打印完成后恢复页面功能 ===== var undoJournal = []; // 记录并移除节点(逆序回滚可正确还原嵌套删除) function journalRemove(node) { if (!node || !node.parentNode) return; undoJournal.push({ type: 'remove', node: node, parent: node.parentNode, next: node.nextSibling }); node.parentNode.removeChild(node); } // 记录并修改内联样式 function journalStyle(el, prop, value) { if (!el) return; undoJournal.push({ type: 'style', el: el, prop: prop, old: el.style[prop] }); el.style[prop] = value; } // 打印完成后回滚:节点逆序插回原位、样式恢复原值(元素的事件绑定不受影响) function restorePage() { for (var i = undoJournal.length - 1; i >= 0; i--) { var r = undoJournal[i]; try { if (r.type === 'remove') { r.parent.insertBefore(r.node, r.next); } else if (r.type === 'style') { r.el.style[r.prop] = r.old; } else if (r.type === 'attr') { if (r.old === null || r.old === undefined) r.el.removeAttribute(r.name); else r.el.setAttribute(r.name, r.old); } } catch (e) {} } undoJournal = []; console.log("🔄 页面编辑功能已恢复"); } function deleteElementByClassName(ClassName) { var elements = document.getElementsByClassName(ClassName); while (elements.length > 0) { // 保护包含试题正文的容器:report 页模板结构不同,'top'/'clear' 等通用类名 // 可能恰好套在题目列表外层,直接删除会导致打印内容缺失 if (containsPrintContent(elements[0])) break; journalRemove(elements[0]); } } function deleteElementById(Id) { journalRemove(document.getElementById(Id)); } function removeBorderByClassName(ClassName) { var obj = document.getElementsByClassName(ClassName); for (var i = 0; i < obj.length; i++) { undoJournal.push({ type: 'attr', el: obj[i], name: 'style', old: obj[i].getAttribute('style') }); obj[i].setAttribute('style', 'border: none !important;'); } } // 获取提示用信息 var papertitle = document.getElementsByClassName('paper-title')[0]?.innerText || document.getElementById('exam-maintitle')?.value || '未知试卷'; var subject = document.getElementsByClassName('nav-subject')[0]?.innerText || '未知科目'; // ============ 延迟执行,确保动态元素已生成 ============ setTimeout(function() { // ===== 1. 处理浮动框 ===== // 移除 qrcode-side(分享二维码) document.querySelectorAll('.qrcode-side, [class*="qrcode-side"]').forEach(function(el) { journalRemove(el); }); // 移除 return-top(返回顶部) document.querySelectorAll('.return-top, a.return-top, [class*="return-top"]').forEach(function(el) { journalRemove(el); }); // 移除 divRight(试卷篮) deleteElementById('divRight'); deleteElementByClassName('divReportCart'); // 移除浮动试题篮(注意:paper/make 组卷页上 divQues 是主试卷内容区 exam-body,不能删) if (!isMakePage) { deleteElementById('divQues'); } // 移除固定底部导航 deleteElementByClassName('new-fixed-bottom'); deleteElementByClassName('fixed-bottom'); // 移除顶部导航 deleteElementByClassName('top'); deleteElementById('borwserWarning'); deleteElementByClassName('brower-tip'); deleteElementByClassName('header'); deleteElementById('divNav'); // 移除其他浮动元素 deleteElementByClassName('clear'); deleteElementByClassName('foot'); deleteElementByClassName('paper-tab-ctrl'); deleteElementByClassName('paper-side-2024'); deleteElementById('divBread'); deleteElementByClassName('fieldtip'); // 移除右侧内容推荐区域 document.querySelectorAll('.new-content-right, .fright[style*="width:270px"]').forEach(function(el) { journalRemove(el); }); // 移除所有固定/粘性定位的浮动元素 var allElements = document.querySelectorAll('*'); var elementsToRemove = []; for (var i = 0; i < allElements.length; i++) { var elem = allElements[i]; var style = window.getComputedStyle(elem); var position = style.position; if (position === 'fixed' || position === 'sticky') { // 跳过脚本自身 UI 与包含试题正文的容器 if (elem.contains(tipP) || containsPrintContent(elem)) continue; var rect = elem.getBoundingClientRect(); var isRightSide = rect.left > window.innerWidth * 0.4; var isBottomSide = rect.top > window.innerHeight * 0.6; var className = (elem.className || '').toLowerCase(); var id = (elem.id || '').toLowerCase(); var keywords = ['share', 'float', 'fixed', 'toolbar', 'side', 'right', 'bottom', 'cart', 'basket', 'qrcode', '二维码', '分享', '浮动', '工具', '试题篮', '试卷篮', 'return', '返回', 'top']; var shouldRemove = false; for (var j = 0; j < keywords.length; j++) { if (className.indexOf(keywords[j]) !== -1 || id.indexOf(keywords[j]) !== -1) { shouldRemove = true; break; } } if ((isRightSide || isBottomSide) && shouldRemove) { var isMainContent = elem.closest('.content') || elem.closest('.paper-content') || elem.closest('.main-content') || elem.closest('.ques-list'); if (!isMainContent) { elementsToRemove.push(elem); } } } } for (var k = 0; k < elementsToRemove.length; k++) { journalRemove(elementsToRemove[k]); } // 移除遮罩层 deleteElementByClassName('box-overlay'); deleteElementByClassName('bg-mask'); deleteElementByClassName('guide-overlay'); // ===== 1.5 组卷页(paper/make)特有清理 ===== if (isMakePage) { // 右侧功能栏整体移除(打印预览右上角残留的根源): // 含下载/完成组卷按钮、暂存组卷/试卷分析/下载答题卡、组卷信息、卷参设置、试题排序等 var sidebar = document.querySelector('.paper-2020'); if (sidebar) journalRemove(sidebar); // 左侧"试题篮"标签盒(打印预览左上角残留的根源) var cartBox = document.querySelector('.cr-center > .pm15.mb15.bg-fff') || document.querySelector('div.pm15.mb15.bg-fff'); if (cartBox) journalRemove(cartBox); // 已删试题回收面板与 AI 改编抽屉(若存在) deleteElementById('divDeleteQues'); deleteElementByClassName('ai-adapt-drawer'); } // ===== 2. 处理标题 ===== // 如果是详情页,保留标题但不遮挡内容 if (isFirstPage) { var titleElem = document.querySelector('.paper-title'); if (titleElem) { journalStyle(titleElem, 'position', 'relative'); journalStyle(titleElem, 'top', 'auto'); journalStyle(titleElem, 'zIndex', 'auto'); journalStyle(titleElem, 'marginTop', '0'); journalStyle(titleElem, 'paddingTop', '10px'); var contentArea = document.querySelector('.content') || document.querySelector('.paper-content'); if (contentArea && titleElem.parentNode) { if (!contentArea.contains(titleElem)) { // 记录标题原位置,打印后随日志一起还原 undoJournal.push({ type: 'remove', node: titleElem, parent: titleElem.parentNode, next: titleElem.nextSibling }); contentArea.insertBefore(titleElem, contentArea.firstChild); } } } } else { document.querySelectorAll('.paper-title, .title-wrap, .exam-title, .paper-header').forEach(function(el) { journalRemove(el); }); } // 移除所有固定定位的标题容器 document.querySelectorAll('.paper-title, .title-wrap, .exam-title, .paper-header, .h2-txt').forEach(function(elem) { var style = window.getComputedStyle(elem); if (style.position === 'fixed' || style.position === 'sticky' || style.position === 'absolute') { journalRemove(elem); } }); // ===== 3. 调整内容区域 ===== var paperContent = document.querySelector('.paper-content') || document.querySelector('.main-content') || document.querySelector('.content'); if (paperContent) { var currentPaddingTop = parseInt(window.getComputedStyle(paperContent).paddingTop); if (currentPaddingTop > 30) { journalStyle(paperContent, 'paddingTop', '10px'); } journalStyle(paperContent, 'marginTop', '0'); journalStyle(paperContent, 'position', 'static'); } // 清理 body journalStyle(document.body, 'paddingBottom', '0px'); // ===== 4. 去除试题边框 ===== removeBorderByClassName('QUES_LI'); console.log("✅ 处理成功!已移除浮动框并处理标题"); GM_notification(subject + '| ' + papertitle, "✅ 试卷处理成功!"); window.print(); // 打印对话框关闭后回滚全部清理操作,客户端还原页面编辑功能(无需刷新、不消耗访问频率)。 // 若开启了"处理后自动刷新",随后的整页刷新同样会还原页面。 restorePage(); // 【关键修复】必须在 window.print() 之后再刷新。 // 旧版本把 location.reload() 放在 setTimeout 外同步执行:点击按钮后页面立即开始 // 导航卸载,800ms 的处理定时器在触发前就被终止,window.print() 永远不会执行—— // 表现为"点了按钮但没有打印弹窗",且成功与否取决于页面卸载快慢,因此时灵时不灵。 if (EnableAutoRefresh) { window.location.reload(); } }, 800); } var settingsButton = document.createElement('span'); settingsButton.className = 'jyeoo-print-tools'; settingsButton.innerHTML = `脚本设置`; settingsButton.onclick = function() { pageConfirm('设置项1:是否要在完成处理后自动刷新?
默认值:是。注意:Safari 请填写 N', function() { localStorage.setItem("EnableAutoRefresh", 'Y'); pageMessage('设置成功!'); }, function() { localStorage.setItem("EnableAutoRefresh", 'N'); pageMessage('设置成功!'); }); }; // ============ UI 挂载:多候选锚点 + 轮询重试 + 悬浮面板兜底 ============ // div.mb15.bg-fff.pv15 是 paper/detail 模板专属的工具盒,report/detail 等页面不存在, // 旧版本找不到锚点就直接停止,导致按钮根本不出现(打印弹窗自然也无法触发)。 var ANCHOR_SELECTORS = [ 'div.mb15.bg-fff.pv15', // paper/detail 页工具盒(原始锚点) 'div.mb15.bg-fff', // 同类信息盒的宽泛匹配 'div.bg-fff.pv15', 'div.bg-fff.paper-info', // paper/make 组卷页的"组卷信息"面板 'div.content' // 兜底:主内容容器 ]; function isAnchorVisible(el) { try { return !!(el && (el.offsetWidth || el.offsetHeight || el.getClientRects().length)); } catch (e) { return false; } } function mountButtons() { // 组卷页优先:插入到右侧"组卷信息"面板上方(查看快报/试卷分析/下载按钮行之下) if (IS_MAKE_PAGE) { var panel = null; try { panel = document.querySelector('div.bg-fff.paper-info'); } catch (e) { panel = null; } if (panel && isAnchorVisible(panel) && panel.parentNode) { var parent = panel.parentNode; parent.insertBefore(tipP, panel); parent.insertBefore(processButton, panel); parent.insertBefore(settingsButton, panel); console.log("✅ 程序已就绪!按钮已插入组卷页右侧「组卷信息」面板上方"); return true; } } for (var i = 0; i < ANCHOR_SELECTORS.length; i++) { var host = null; try { host = document.querySelector(ANCHOR_SELECTORS[i]); } catch (e) { host = null; } // 跳过存在但不可见的容器(如预置却 display:none 的面板),避免按钮"挂上了却看不见" if (host && isAnchorVisible(host)) { host.appendChild(tipP); host.appendChild(processButton); host.appendChild(settingsButton); console.log("✅ 程序已就绪!按钮已挂载至锚点:" + ANCHOR_SELECTORS[i]); return true; } } return false; } // 兜底:自建右上角悬浮面板(类名不含任何清理关键词,且打印时自动隐藏) function mountFloatingPanel() { var panel = document.createElement('div'); panel.className = 'jyeoo-print-tools'; panel.setAttribute('style', 'position:fixed;top:12px;right:12px;z-index:2147483647;background:#fff;border:1px solid #ddd;' + 'border-radius:6px;box-shadow:0 2px 8px rgba(0,0,0,.15);padding:10px 14px;font-size:14px;line-height:2;'); panel.appendChild(tipP); panel.appendChild(processButton); panel.appendChild(settingsButton); document.body.appendChild(panel); console.warn("⚠️ 未找到常规挂载点,已改用右上角悬浮面板承载按钮"); } // report/detail 等页面可能由 AJAX 动态渲染挂载点,轮询等待其出现(最多约 10 秒) (function tryMount(retries) { if (mountButtons()) return; if (retries <= 0) { mountFloatingPanel(); return; } setTimeout(function() { tryMount(retries - 1); }, 500); })(20); })();