// ==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);
})();