// ==UserScript==
// @name 学习通效率助手(蚌医版)
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 作业试卷导出Word(图文内嵌/强制A4/去黑点/彻底修复重叠) | 解除限制
// @author jasminen
// @match *://*.chaoxing.com/*
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-end
// @license MIT
// ==/UserScript==
(function() {
'use strict';
if (window.self !== window.top) return;
if (document.getElementById('cx-helper-panel')) return;
const exportCounts = { doc: {} };
// CSS
GM_addStyle(`
/* 主面板 */
#cx-helper-panel {
position: fixed; top: 120px; right: 30px; width: 280px;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.5);
border-radius: 20px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
z-index: 999999; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
font-size: 14px; user-select: none; overflow: hidden;
transition: opacity 0.3s ease;
}
/* 头部拖拽区 */
#cx-helper-header {
padding: 16px; text-align: center; font-weight: 700; font-size: 16px;
color: #333; cursor: grab;
background: linear-gradient(180deg, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%);
border-bottom: 1px solid rgba(0,0,0,0.04);
display: flex; justify-content: center; align-items: center; gap: 6px;
}
#cx-helper-header:active { cursor: grabbing; }
/* Tabs */
.cx-tabs {
display: flex; margin: 12px 16px; background: rgba(0,0,0,0.04);
border-radius: 10px; padding: 4px; position: relative;
}
.cx-tab-btn {
flex: 1; padding: 8px 0; border: none; background: transparent;
cursor: pointer; color: #666; font-weight: 600; font-size: 13px;
border-radius: 8px; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.cx-tab-btn.active {
background: #fff; color: #1a73e8;
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
}
/* 内容区 */
.cx-tab-content { display: none; padding: 0 16px 20px 16px; animation: fadeIn 0.3s ease; }
.cx-tab-content.active { display: block; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }
/* 主按钮 */
.cx-btn-primary {
display: block; width: 100%; padding: 12px; margin-top: 10px;
background: linear-gradient(135deg, #6e8efb, #a777e3);
color: white; border: none; border-radius: 12px; font-size: 14px; font-weight: 600;
cursor: pointer; box-shadow: 0 4px 15px rgba(110, 142, 251, 0.3);
transition: all 0.2s ease;
}
.cx-btn-primary:hover {
transform: translateY(-2px); box-shadow: 0 6px 20px rgba(110, 142, 251, 0.4); filter: brightness(1.05);
}
.cx-btn-primary:active { transform: translateY(0); box-shadow: 0 2px 8px rgba(110, 142, 251, 0.3); }
.cx-btn-primary:disabled { background: #ccc; box-shadow: none; cursor: not-allowed; transform: none; }
/* 开关组件 */
.cx-switch-wrap {
display: flex; justify-content: space-between; align-items: center;
padding: 12px 14px; background: rgba(0,0,0,0.02); border-radius: 12px; margin-top: 10px;
border: 1px solid rgba(0,0,0,0.03);
}
.cx-switch-label { font-weight: 500; color: #444; font-size: 13px; }
.cx-switch { position: relative; display: inline-block; width: 44px; height: 24px; margin: 0; }
.cx-switch input { opacity: 0; width: 0; height: 0; }
.cx-slider {
position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0;
background-color: #ccc; transition: .4s; border-radius: 24px;
}
.cx-slider:before {
position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px;
background-color: white; transition: .4s; border-radius: 50%;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
input:checked + .cx-slider { background-color: #34c759; }
input:checked + .cx-slider:before { transform: translateX(20px); }
`);
// HTML结构
const panelHTML = `
`;
const wrapper = document.createElement('div');
wrapper.innerHTML = panelHTML;
document.body.appendChild(wrapper);
// 面板拖拽
const panel = document.getElementById('cx-helper-panel'), header = document.getElementById('cx-helper-header');
let isDragging = false, offsetX = 0, offsetY = 0;
header.addEventListener('mousedown', e => { isDragging = true; offsetX = e.clientX - panel.offsetLeft; offsetY = e.clientY - panel.offsetTop; header.style.cursor = 'grabbing'; });
document.addEventListener('mousemove', e => { if (!isDragging) return; panel.style.right = 'auto'; panel.style.left = Math.max(0, Math.min(e.clientX - offsetX, window.innerWidth - panel.offsetWidth)) + 'px'; panel.style.top = Math.max(0, Math.min(e.clientY - offsetY, window.innerHeight - panel.offsetHeight)) + 'px'; });
document.addEventListener('mouseup', () => { isDragging = false; header.style.cursor = 'move'; });
// Tab切换
document.querySelectorAll('.cx-tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.cx-tab-btn').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.cx-tab-content').forEach(c => c.classList.remove('active'));
btn.classList.add('active'); document.getElementById(btn.dataset.target).classList.add('active');
});
});
function getFileName(baseTitle, ext) {
if (!exportCounts[ext][baseTitle]) exportCounts[ext][baseTitle] = 0;
let suffix = exportCounts[ext][baseTitle] > 0 ? `_${exportCounts[ext][baseTitle]}` : '';
exportCounts[ext][baseTitle]++;
return `${baseTitle}${suffix}.${ext}`;
}
// 提取并清洗内容
function getRawContent() {
let targetDoc = document;
const iframe = document.getElementById('iframe');
if (iframe && iframe.contentDocument) targetDoc = iframe.contentDocument;
let titleEl = targetDoc.querySelector('h2') || targetDoc.querySelector('.mark_title') || targetDoc.querySelector('.mark_name');
let titleText = titleEl ? titleEl.innerText.trim() : document.title.replace(/[\/\\:\*\?"<>\|]/g, '');
if (!titleText || titleText === '学习通') titleText = '学习通题库';
let contentDiv = targetDoc.querySelector('.mark_table') || targetDoc.querySelector('.questionLi') || targetDoc.body;
let cloneDiv = contentDiv.cloneNode(true);
cloneDiv.querySelectorAll('script, .hidden, [style*="display: none"]').forEach(el => el.remove());
// 去黑点:替换 ul/li
cloneDiv.querySelectorAll('li').forEach(li => {
let div = document.createElement('div');
div.innerHTML = li.innerHTML;
div.className = li.className;
div.style.marginLeft = "20px";
div.style.marginBottom = "5px";
li.parentNode.replaceChild(div, li);
});
cloneDiv.querySelectorAll('ul, ol').forEach(list => {
let div = document.createElement('div');
div.innerHTML = list.innerHTML;
div.className = list.className;
list.parentNode.replaceChild(div, list);
});
return { content: cloneDiv, title: titleText };
}
// 图片转 Base64
function convertImagesToBase64(element) {
const images = element.querySelectorAll('img');
const promises = [];
images.forEach(img => {
promises.push(new Promise((resolve) => {
if (img.src.startsWith('data:')) {
resolve();
return;
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const image = new Image();
image.crossOrigin = 'Anonymous';
image.onload = function() {
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0);
try {
img.src = canvas.toDataURL('image/png');
} catch (e) {
console.warn('图片转换受限');
}
resolve();
};
image.onerror = function() { resolve(); };
let src = img.src;
if (src.startsWith('//')) src = window.location.protocol + src;
else if (src.startsWith('/')) src = window.location.origin + src;
image.src = src;
}));
});
return Promise.all(promises);
}
// CSS修复:解决文字重叠与分页截断问题
const commonFixCSS = `
/* 答案区域浮动和绝对定位修复 */
.answerBg, .myAnswer, .correctAnswer, .answerBg span, .answerBg div {
display: block !important;
position: relative !important;
float: none !important;
height: auto !important;
line-height: 1.8 !important;
margin-bottom: 8px !important;
left: 0 !important; top: 0 !important; right: auto !important; bottom: auto !important;
clear: both !important;
}
.answerBg img { display: inline-block !important; position: static !important; vertical-align: middle !important; margin-left: 5px !important; }
/* 题目防分页截断 */
.TiMu, .questionLi, .mark_item, .ul_li {
margin-bottom: 30px !important;
page-break-inside: avoid !important;
break-inside: avoid !important;
}
.fontLabel { font-weight: bold !important; margin-bottom: 8px !important; display: block !important; }
img { max-width: 100% !important; height: auto !important; display: inline-block; }
`;
// 导出 Word
document.getElementById('cx-export-word').addEventListener('click', async function() {
const btn = this;
btn.innerText = "转换图片中...";
btn.disabled = true;
const { content, title } = getRawContent();
await convertImagesToBase64(content);
const fileName = getFileName(title, 'doc');
const header = `
${title}
${title}
`;
const footer = "
";
const sourceHTML = header + content.innerHTML + footer;
const blob = new Blob(['\ufeff', sourceHTML], { type: 'application/msword' });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
btn.innerText = "导出 Word";
btn.disabled = false;
});
// 解除复制粘贴右键限制
document.getElementById('cx-toggle-copy').addEventListener('change', function(e) {
if (e.target.checked) {
GM_addStyle(`* { user-select: text !important; -webkit-user-select: text !important; -moz-user-select: text !important; -ms-user-select: text !important; }`);
function unlock(doc) {
if (!doc) return;
['onselectstart', 'oncopy', 'oncut', 'onpaste', 'oncontextmenu', 'ondragstart'].forEach(attr => { doc.body[attr] = null; doc[attr] = null; doc.documentElement[attr] = null; });
['selectstart', 'copy', 'cut', 'paste', 'contextmenu', 'keydown', 'mousedown', 'mouseup'].forEach(evt => { doc.addEventListener(evt, function(ev) { ev.stopPropagation(); }, true); });
const iframes = doc.querySelectorAll('iframe');
iframes.forEach(iframe => { try { unlock(iframe.contentDocument); } catch (err) {} });
}
unlock(document);
const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.tagName === 'IFRAME') { node.addEventListener('load', () => { try { unlock(node.contentDocument); } catch (e) {} }); } }); }); });
observer.observe(document.body, { childList: true, subtree: true });
try { window.alert = function(){}; } catch(e){}
} else {
alert("刷新页面即可恢复默认限制。");
}
});
})();