您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
智慧校园,解决宜宾学院智慧校园的题目,能够自动获取宜宾学院的智慧校园的作业的答案,能够跳过秒看教学视频,自动阅读文档
当前为
// ==UserScript== // @name 宜宾智慧校园助手 // @namespace 智慧校园,解决宜宾学院智慧校园的题目,能够自动获取宜宾学院的智慧校园的作业的答案===来自计算机科学与技术学院--修改自若离智慧校园 // @version 12.22 // @description 智慧校园,解决宜宾学院智慧校园的题目,能够自动获取宜宾学院的智慧校园的作业的答案,能够跳过秒看教学视频,自动阅读文档 // @author 计算机科学与技术学院---软工 // @match https://mooc.yibinu.edu.cn/* // @icon https://pic.imgdb.cn/item/673c85b1d29ded1a8ce8b97c.png // @require https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.14/vue.min.js // @require https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/ant-design-vue/1.7.8/antd.min.js // @require https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/xlsx/0.18.5/xlsx.full.min.js // @require https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/crypto-js/4.1.1/crypto-js.min.js // @resource ANTD_CSS https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/ant-design-vue/1.7.8/antd.min.css // @run-at document-end // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_getResourceText // ==/UserScript== // 脚本设置对象 const setting = { logs: [ '初始化脚本完成,', '当前脚本版本:V12.22', '由于国内无法访问gf.qytechs.cn,可以进入 https://gf.qytechs.cn/zh-CN 和https://home.gf.qytechs.cn.cn/ (版本更新较慢)下载更新脚本' ], // 日志数据 datas: [], // 答案数据 secretKey: '你好', // 默认密钥 _vt: '274c8b3f2a8c63ffc960dd1e4e3f0eac', // 答题设置 - 默认值 autoSubmit: false, // 默认不自动提交答案 autoEnterExam: true, // 默认自动进入考试 // 学习进度 learningProgress: { isLearning: false, currentChapter: null, currentContent: null, timestamp: null }, // 初始化设置 init() { // 尝试从localStorage加载设置 try { const savedConfig = localStorage.getItem('examConfig'); if (savedConfig) { const config = JSON.parse(savedConfig); this.autoSubmit = config.autoSubmit !== undefined ? config.autoSubmit : false; this.autoEnterExam = config.autoEnterExam !== undefined ? config.autoEnterExam : true; } } catch (e) { console.error('加载答题配置失败:', e); // 使用默认值 this.autoSubmit = false; this.autoEnterExam = true; } } }; // 初始化设置 setting.init(); // 资源加载管理器 const ResourceLoader = { resources: { styles: [ { name: 'ANTD_CSS', type: 'resource' } ], scripts: [ { name: 'vue', url: 'https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.14/vue.min.js' }, { name: 'antd', url: 'https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/ant-design-vue/1.7.8/antd.min.js' }, { name: 'xlsx', url: 'https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/xlsx/0.18.5/xlsx.full.min.js' }, { name: 'crypto-js', url: 'https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/crypto-js/4.1.1/crypto-js.min.js' } ] }, // 资源加载状态 loadStatus: new Map(), // 初始化加载 async init() { try { // 先加载样式资源 await this.loadStyles(); // 再加载脚本资源 await this.loadScripts(); console.log('所有资源加载完成'); return true; } catch (error) { console.error('资源加载失败:', error); return false; } }, // 加载样式资源 async loadStyles() { for (const style of this.resources.styles) { try { if (style.type === 'resource') { // 使用GM_getResourceText加载资源 const cssContent = GM_getResourceText(style.name); if (cssContent) { GM_addStyle(cssContent); this.loadStatus.set(style.name, true); console.log(`样式资源 ${style.name} 加载成功`); } else { throw new Error(`无法获取样式资源 ${style.name}`); } } else { // 直接通过URL加载 await this.loadStyle(style.name, style.url); } } catch (error) { console.error(`加载样式 ${style.name} 失败:`, error); this.loadStatus.set(style.name, false); } } }, // 加载脚本资源 async loadScripts() { const promises = this.resources.scripts.map(script => this.loadScript(script.name, script.url) ); return Promise.all(promises); }, // 加载单个样式 loadStyle(name, url) { return new Promise((resolve, reject) => { const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = url; link.onload = () => { this.loadStatus.set(name, true); console.log(`样式 ${name} 加载成功`); resolve(); }; link.onerror = (error) => { console.error(`样式 ${name} 加载失败:`, error); this.loadStatus.set(name, false); reject(new Error(`样式 ${name} 加载失败`)); }; document.head.appendChild(link); }); }, // 加载单个脚本 loadScript(name, url) { return new Promise((resolve, reject) => { // 检查是否已加载 if (window[name]) { this.loadStatus.set(name, true); resolve(); return; } const script = document.createElement('script'); script.src = url; script.async = true; script.onload = () => { this.loadStatus.set(name, true); console.log(`脚本 ${name} 加载成功`); resolve(); }; script.onerror = () => { // 加载失败时尝试备用CDN const backupUrl = this.getBackupUrl(url); if (backupUrl) { script.src = backupUrl; console.log(`尝试使用备用CDN加载 ${name}`); } else { this.loadStatus.set(name, false); console.error(`脚本 ${name} 加载失败`); reject(new Error(`脚本 ${name} 加载失败`)); } }; document.head.appendChild(script); }); }, // 获取备用CDN地址 getBackupUrl(url) { const cdnMap = { 'lf26-cdn-tos.bytecdntp.com': 'cdn.jsdelivr.net/npm', 'cdn.jsdelivr.net': 'cdnjs.cloudflare.com/ajax/libs', 'cdnjs.cloudflare.com': 'unpkg.com' }; for (const [current, backup] of Object.entries(cdnMap)) { if (url.includes(current)) { return url.replace(current, backup); } } return null; }, // 检查资源是否都已加载 checkAllLoaded() { return Array.from(this.loadStatus.values()).every(status => status); }, // 获取加载失败的资源 getFailedResources() { return Array.from(this.loadStatus.entries()) .filter(([, status]) => !status) .map(([name]) => name); } }; // 浏览器兼容性检查和处理 const BrowserCompatibility = { // 必需的特性列表 requiredFeatures: { localStorage: { name: 'localStorage', test: () => typeof localStorage !== 'undefined', fallback: this.createMemoryStorage }, promise: { name: 'Promise', test: () => typeof Promise !== 'undefined', fallback: () => this.loadPolyfill('promise-polyfill') }, fetch: { name: 'fetch', test: () => typeof fetch !== 'undefined', fallback: () => this.loadPolyfill('whatwg-fetch') }, customElements: { name: 'Custom Elements', test: () => 'customElements' in window, fallback: () => this.loadPolyfill('@webcomponents/custom-elements') } }, // 浏览器信息 browserInfo: { name: '', version: '', isSupported: false }, // 初始化 async init() { this.detectBrowser(); const compatibility = await this.checkCompatibility(); if (!compatibility.isCompatible) { this.showCompatibilityWarning(compatibility.unsupportedFeatures); await this.loadFallbacks(compatibility.unsupportedFeatures); } return compatibility.isCompatible; }, // 检测浏览器信息 detectBrowser() { const userAgent = navigator.userAgent; let browserName = "未知浏览器"; let browserVersion = "未知版本"; // 检测主流浏览器 if (userAgent.indexOf("Chrome") > -1) { browserName = "Chrome"; browserVersion = userAgent.match(/Chrome\/([0-9.]+)/)[1]; } else if (userAgent.indexOf("Firefox") > -1) { browserName = "Firefox"; browserVersion = userAgent.match(/Firefox\/([0-9.]+)/)[1]; } else if (userAgent.indexOf("Safari") > -1) { browserName = "Safari"; browserVersion = userAgent.match(/Version\/([0-9.]+)/)[1]; } else if (userAgent.indexOf("MSIE") > -1 || userAgent.indexOf("Trident") > -1) { browserName = "Internet Explorer"; browserVersion = userAgent.match(/(?:MSIE |rv:)([0-9.]+)/)[1]; } this.browserInfo = { name: browserName, version: browserVersion, isSupported: this.checkBrowserSupport(browserName, browserVersion) }; }, // 检查浏览器是否支持 checkBrowserSupport(name, version) { const minVersions = { 'Chrome': 49, 'Firefox': 45, 'Safari': 10, 'Internet Explorer': 11 }; const majorVersion = parseInt(version.split('.')[0]); return majorVersion >= (minVersions[name] || 0); }, // 检查特性兼容性 async checkCompatibility() { const unsupportedFeatures = []; for (const [feature, {test}] of Object.entries(this.requiredFeatures)) { if (!test()) { unsupportedFeatures.push(feature); } } return { isCompatible: unsupportedFeatures.length === 0, unsupportedFeatures }; }, // 加载polyfill async loadPolyfill(packageName) { const polyfillUrl = `https://cdn.jsdelivr.net/npm/${packageName}`; return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = polyfillUrl; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); }, // 创建内存存储的fallback createMemoryStorage() { const storage = {}; return { setItem: (key, value) => storage[key] = value, getItem: (key) => storage[key], removeItem: (key) => delete storage[key], clear: () => Object.keys(storage).forEach(key => delete storage[key]) }; }, // 加载所有需要的fallback async loadFallbacks(unsupportedFeatures) { const fallbackPromises = unsupportedFeatures.map(feature => { const {fallback} = this.requiredFeatures[feature]; return fallback(); }); try { await Promise.all(fallbackPromises); console.log('所有fallback加载完成'); } catch (error) { console.error('加载fallback失败:', error); } }, // 显示兼容性警告 showCompatibilityWarning(unsupportedFeatures) { const warning = document.createElement('div'); warning.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; background: #fff3cd; color: #856404; padding: 12px; text-align: center; z-index: 9999; border-bottom: 1px solid #ffeeba; `; warning.innerHTML = ` <strong>浏览器兼容性警告</strong><br> 您的浏览器 (${this.browserInfo.name} ${this.browserInfo.version}) 可能不完全支持本脚本的所有功能。<br> 建议使用最新版本的Chrome、Firefox或Safari浏览器。 `; document.body.appendChild(warning); } }; // 在脚本初始化时使用 async function initScript() { try { // 检查浏览器兼容性 await BrowserCompatibility.init(); // 加载资源 await ResourceLoader.init(); // 如果所有检查都通过,继续初始化脚本 if (BrowserCompatibility.browserInfo.isSupported && ResourceLoader.checkAllLoaded()) { initView(); // 初始化全局静音功能 setTimeout(() => { initGlobalMute(); }, 1000); // 延迟1秒执行,确保页面已完全加载 } else { console.error('脚本初始化失败:', { browser: BrowserCompatibility.browserInfo, failedResources: ResourceLoader.getFailedResources() }); } } catch (error) { console.error('脚本初始化错误:', error); } } // 启动脚本 initScript(); // 时间值 function decryptValidDuration() { try { const validDurations = { 'c51ce410c124a10e0db5e4b97fc2af39': 86400000, '274c8b3f2a8c63ffc960dd1e4e3f0eac': 172800000, 'e2ef524fbf3d9fe611d5a8e90fefdc9c': 259200000, '069059b7ef840f0c74a814ec9237b6ec': 432000000, '7f6ffaa6bb0b408017b62254211691b5': 604800000, '149e9677a5989fd342ae44213df68868': 2592000000 }; return validDurations[setting._vt] || 172800000; // 默认返回2天 } catch (e) { console.error('解密时间值出错:', e); return 172800000; // 解密出错时返回默认值(2天) } } // 简化的验证时间管理 function setValidTime() { try { const validDuration = decryptValidDuration(); const validUntil = Date.now() + validDuration; const data = { time: validUntil, hash: CryptoJS.SHA256(validUntil.toString()).toString() }; localStorage.setItem('scriptValidUntil', JSON.stringify(data)); return true; } catch (e) { console.error('设置验证时间出错:', e); return false; } } // 检查验证时间 function checkValidTime() { try { const data = localStorage.getItem('scriptValidUntil'); if (!data) return false; const { time, hash } = JSON.parse(data); const now = Date.now(); // 验证时间和哈希 if (time && hash && hash === CryptoJS.SHA256(time.toString()).toString() && time > now) { return true; } localStorage.removeItem('scriptValidUntil'); return false; } catch (e) { console.error('检查验证时间出错:', e); localStorage.removeItem('scriptValidUntil'); return false; } } // 将验证函数定义为全局函数 window.verifySecret = function() { const input = document.getElementById('secretInput'); if (!input) { console.error('找不到输入框元素'); return; } const inputValue = input.value.trim(); if (!inputValue) { input.style.borderColor = '#ff4d4f'; input.style.animation = 'shake 0.5s'; setTimeout(() => { input.style.borderColor = '#e8e8e8'; input.style.animation = ''; }, 1000); return; } if (inputValue !== setting.secretKey) { const modalDiv = document.querySelector('#secretModal > div'); if (modalDiv) { modalDiv.style.animation = 'shake 0.5s'; input.style.borderColor = '#ff4d4f'; setTimeout(() => { modalDiv.style.animation = ''; input.style.borderColor = '#e8e8e8'; }, 1000); } return; } try { // 设置验证时间 if (!setValidTime()) { throw new Error('设置验证时间失败'); } // 添加关闭动画 const modal = document.getElementById('secretModal'); if (modal) { modal.style.animation = 'modalFadeOut 0.3s ease'; setTimeout(() => { modal.remove(); // 在验证成功后显示答题设置窗口 setTimeout(() => { // 创建答题设置窗口,传递true表示是首次使用 createInitialSettingsModal(true); }, 300); // 添加成功提示 if (window.vue) { window.vue.$message.success('验证成功!请先进行答题设置'); } }, 300); } } catch (error) { console.error('验证过程出错:', error); if (window.vue) { window.vue.$message.error('验证过程出错,请刷新页面重试'); } } }; // 添加一个新的首次设置窗口函数 function createInitialSettingsModal(isFirstTime = true) { // 清除可能存在的旧模态框 const oldModal = document.getElementById('settingsModal'); if (oldModal) { document.body.removeChild(oldModal); } // 创建模态框容器 const modalDiv = document.createElement('div'); modalDiv.id = 'settingsModal'; modalDiv.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 10001; `; // 创建模态框内容 const modalContent = document.createElement('div'); modalContent.style.cssText = ` background: white; border-radius: 4px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); width: 500px; max-width: 90%; position: relative; `; // 标题 const modalHeader = document.createElement('div'); modalHeader.style.cssText = ` padding: 16px 24px; color: rgba(0, 0, 0, 0.85); background: #fff; border-bottom: 1px solid #f0f0f0; border-radius: 4px 4px 0 0; font-weight: 500; font-size: 18px; `; modalHeader.innerText = isFirstTime ? "首次使用 - 答题设置" : "答题设置"; modalContent.appendChild(modalHeader); // 内容区域 const modalBody = document.createElement('div'); modalBody.style.cssText = ` padding: 24px; font-size: 14px; line-height: 1.6; word-wrap: break-word; `; // 添加说明文字 (只在首次使用时显示) if (isFirstTime) { const description = document.createElement('div'); description.style.cssText = ` margin-bottom: 20px; padding: 12px; background-color: #f9f9f9; border-left: 4px solid #1890ff; border-radius: 2px; `; description.innerHTML = ` <p>欢迎使用宜宾智慧校园助手!请先设置答题偏好,以下设置可稍后随时更改。</p> `; modalBody.appendChild(description); } // 创建设置项 const autoSubmitDiv = document.createElement('div'); autoSubmitDiv.style.cssText = ` margin-bottom: 20px; padding: 15px; border: 1px solid #f0f0f0; border-radius: 4px; background-color: #fafafa; `; const autoSubmitCheckbox = document.createElement('input'); autoSubmitCheckbox.type = 'checkbox'; autoSubmitCheckbox.id = 'autoSubmitSetting'; autoSubmitCheckbox.checked = setting.autoSubmit; autoSubmitCheckbox.style.cssText = ` margin-right: 8px; margin-top: 3px; transform: scale(1.2); `; const autoSubmitLabel = document.createElement('label'); autoSubmitLabel.htmlFor = 'autoSubmitSetting'; autoSubmitLabel.style.fontWeight = 'bold'; autoSubmitLabel.innerHTML = ` 自动提交答案 <div style="font-weight: normal; font-size: 13px; color: #666; margin-top: 8px; line-height: 1.5;"> <p>启用后,脚本将在完成所有题目作答后<b>自动提交</b>答卷并处理确认对话框。</p> <p>关闭后,脚本会填写答案但<b>不会自动提交</b>,让您有机会检查答案后手动提交。</p> <p>推荐设置:关闭 <span style="color: #ff4d4f;">(默认已关闭)</span></p> </div> `; autoSubmitDiv.appendChild(autoSubmitCheckbox); autoSubmitDiv.appendChild(autoSubmitLabel); modalBody.appendChild(autoSubmitDiv); // 自动进入考试设置 const autoEnterExamDiv = document.createElement('div'); autoEnterExamDiv.style.cssText = ` margin-bottom: 16px; padding: 15px; border: 1px solid #f0f0f0; border-radius: 4px; background-color: #fafafa; `; const autoEnterExamCheckbox = document.createElement('input'); autoEnterExamCheckbox.type = 'checkbox'; autoEnterExamCheckbox.id = 'autoEnterExamSetting'; autoEnterExamCheckbox.checked = setting.autoEnterExam; autoEnterExamCheckbox.style.cssText = ` margin-right: 8px; margin-top: 3px; transform: scale(1.2); `; const autoEnterExamLabel = document.createElement('label'); autoEnterExamLabel.htmlFor = 'autoEnterExamSetting'; autoEnterExamLabel.style.fontWeight = 'bold'; autoEnterExamLabel.innerHTML = ` 自动进入考试 <div style="font-weight: normal; font-size: 13px; color: #666; margin-top: 8px; line-height: 1.5;"> <p>启用后,脚本将自动点击"开始考试"或"继续答题"按钮,节省操作步骤。</p> <p>关闭后,需要手动点击按钮进入考试页面。</p> <p>推荐设置:开启 <span style="color: #52c41a;">(默认已开启)</span></p> </div> `; autoEnterExamDiv.appendChild(autoEnterExamCheckbox); autoEnterExamDiv.appendChild(autoEnterExamLabel); modalBody.appendChild(autoEnterExamDiv); modalContent.appendChild(modalBody); // 底部按钮 const modalFooter = document.createElement('div'); modalFooter.style.cssText = ` padding: 10px 16px; text-align: right; background: transparent; border-top: 1px solid #f0f0f0; border-radius: 0 0 4px 4px; `; // 添加取消按钮(非首次使用时显示) if (!isFirstTime) { const cancelButton = document.createElement('button'); cancelButton.className = 'ant-btn'; cancelButton.style.cssText = ` line-height: 1.5; display: inline-block; font-weight: 400; text-align: center; cursor: pointer; background-image: none; border: 1px solid #d9d9d9; white-space: nowrap; padding: 0 15px; font-size: 14px; border-radius: 4px; height: 32px; user-select: none; transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); position: relative; color: rgba(0, 0, 0, 0.65); background-color: #fff; margin-right: 8px; `; cancelButton.innerText = "取消"; cancelButton.onclick = function() { document.body.removeChild(modalDiv); }; modalFooter.appendChild(cancelButton); } // 确定按钮 const okButton = document.createElement('button'); okButton.className = 'ant-btn ant-btn-primary'; okButton.style.cssText = ` line-height: 1.5; display: inline-block; font-weight: 400; text-align: center; cursor: pointer; background-image: none; border: 1px solid #1890ff; white-space: nowrap; padding: 0 15px; font-size: 14px; border-radius: 4px; height: 32px; user-select: none; transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); position: relative; color: #fff; background-color: #1890ff; `; okButton.innerText = isFirstTime ? "确认并开始使用" : "保存设置"; okButton.onclick = () => { try { // 获取设置值 const autoSubmit = document.getElementById('autoSubmitSetting').checked; const autoEnterExam = document.getElementById('autoEnterExamSetting').checked; // 保存设置 setting.autoSubmit = autoSubmit; setting.autoEnterExam = autoEnterExam; // 保存到localStorage localStorage.setItem('examConfig', JSON.stringify({ autoSubmit, autoEnterExam })); // 首次使用时初始化脚本,否则显示成功提示 if (isFirstTime) { // 继续初始化 continueInit(); // 成功提示 if(window.vue) { window.vue.$message.success('设置已保存,开始使用智慧校园助手'); log('首次设置已完成'); } } else { // 正常使用时的提示 if(window.vue) { window.vue.$message.success('设置已保存'); log('已更新答题设置'); } } // 关闭模态框 document.body.removeChild(modalDiv); } catch (error) { console.error('保存设置出错:', error); // 出错提示 if(window.vue) { window.vue.$message.error('保存失败,请刷新页面重试'); } // 首次使用时,即使出错也继续初始化,使用默认设置 if (isFirstTime) { continueInit(); } } }; modalFooter.appendChild(okButton); modalContent.appendChild(modalFooter); // 点击背景关闭模态框(非首次使用时才允许) if (!isFirstTime) { modalDiv.onclick = function(event) { if (event.target === modalDiv) { document.body.removeChild(modalDiv); } }; } // 添加到body modalDiv.appendChild(modalContent); document.body.appendChild(modalDiv); // 添加动画效果 modalContent.animate([ { opacity: 0, transform: 'translateY(-20px)' }, { opacity: 1, transform: 'translateY(0)' } ], { duration: 300, easing: 'ease-out' }); } // 添加键盘事件监听器的函数 function addKeyboardListener() { document.addEventListener('keydown', function(event) { if (event.key === 'Enter') { const secretInput = document.getElementById('secretInput'); if (secretInput && document.activeElement === secretInput) { verifySecret(); } } }); } // 日志 function log(logText){ // 限制日志数量,防止内存占用过大 if (setting.logs.length > 100) { setting.logs = setting.logs.slice(0, 50); } setting.logs.unshift(logText); // 使用Vue的响应式更新机制 if (window.vue) { window.vue.logs = [...setting.logs]; } } // 添加一个清理HTML标签的函数 function cleanHtmlTags(text) { if (!text) return ''; // 将HTML转换为纯文本 let temp = document.createElement('div'); temp.innerHTML = text; let cleanText = temp.textContent || temp.innerText; // 清理多余的空白字符 cleanText = cleanText.replace(/\s+/g, ' ').trim(); return cleanText; } // 从后台获取答案 function getAnswer(url, data){ log('获取答案中'); let id = url.match(/\/examSubmit\/(\d+)\/getExamPaper/)[1]; GM_xmlhttpRequest({ method: "post", url: url, data: data, dataType: 'json', headers: { 'Origin': location.origin, 'User-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36', 'Content-type': 'application/x-www-form-urlencoded;charset=utf-8', 'Referer': `https://mooc.yibinu.edu.cn/examTest/stuExamList/${id}.mooc` }, onload: function(res){ if(res.status == 200){ try { let response = JSON.parse(res.responseText); if (response && response.paper && response.paper.paperStruct) { log("获取答案成功,正在格式化答案!"); formatAnswer(response.paper.paperStruct); } else { log("答案数据格式异常:" + JSON.stringify(response)); if (window.vue) { window.vue.hasAnswer = false; } } } catch (error) { log("解析答案数据失败:" + error.message); if (window.vue) { window.vue.hasAnswer = false; } } } else { log("获取答案失败,状态码:" + res.status); } }, onerror: function(error) { log("求答案失败:" + error.message); } }); } //格式化答案 function formatAnswer(str) { try { setting.datas = []; // 清空之前的数据 if (!Array.isArray(str)) { log("答案数据格式错误:期望数组类型"); if (window.vue) { window.vue.hasAnswer = false; } return; } str.forEach((listItem, index) => { if (!listItem.quiz) { return; } // 使用cleanHtmlTags清理题目内容 var question = cleanHtmlTags(listItem.quiz.quizContent) || "未知题目"; var options = {}; var optionContents = {}; // 存储选项内容 var answer = []; const questionNum = (index + 1).toString(); // 处理选择题 if (listItem.quiz.quizOptionses && listItem.quiz.quizOptionses.length > 0) { listItem.quiz.quizOptionses.forEach((optionItem, idx) => { if (optionItem && optionItem.optionId !== undefined) { const optionLabel = String.fromCharCode(65 + idx); options[optionItem.optionId] = optionLabel; optionContents[optionItem.optionId] = optionItem.optionContent || ''; } }); // 处理答案 if (listItem.quiz.quizResponses) { listItem.quiz.quizResponses.forEach(answerItem => { if (answerItem && options[answerItem.optionId]) { const label = options[answerItem.optionId]; const content = optionContents[answerItem.optionId]; answer.push(`${label}.${content}`); } }); } // 并序号和选项标签 const answerLabels = listItem.quiz.quizResponses .map(item => options[item.optionId]) .join(''); const idAndOptions = `${questionNum}.${answerLabels}`; setting.datas.push({ 'key': index.toString(), 'idAndOptions': idAndOptions, 'question': question, 'answer': answer.join('\n'), // 每个选项答案换行显示 'originalIndex': index // 保存原始索引,用于后续排序 }); } else { // 处理填空题 if (listItem.quiz.quizResponses) { const fillAnswers = []; listItem.quiz.quizResponses.forEach(answerItem => { if (answerItem && answerItem.responseContent) { fillAnswers.push(answerItem.responseContent); } }); setting.datas.push({ 'key': index.toString(), 'idAndOptions': `${questionNum}.(填空)`, 'question': question, 'answer': fillAnswers.join('\n'), // 多个填空答案换行显示 'originalIndex': index // 保存原始索引,用于后续排序 }); } } }); // 提取题目编号信息(如果有) setting.datas.forEach(item => { // 尝试从题目中提取编号 const numberMatch = item.question.match(/^(\d+)[、..]\s*/); if (numberMatch) { item.questionNumber = numberMatch[1]; // 存储不带编号的题目文本,用于后续匹配 item.cleanQuestion = item.question.replace(/^\d+[、..]\s*/, '').trim(); } else { item.cleanQuestion = item.question; } }); // 获取当前页面上的题目顺序 const pageQuestions = []; const questionContainers = document.querySelectorAll('.view-test'); if (questionContainers && questionContainers.length > 0) { questionContainers.forEach((container, idx) => { const textElement = container.querySelector('.test-text-tutami'); if (textElement) { const questionText = textElement.textContent.trim(); pageQuestions.push({ index: idx, text: questionText }); } }); log(`页面上找到 ${pageQuestions.length} 道题目,准备排序答案列表`); // 如果页面上有题目,则根据页面题目顺序排序答案列表 if (pageQuestions.length > 0) { const sortedDatas = []; const usedAnswerIndices = new Set(); // 第一轮:尝试为每个页面题目找到最匹配的答案 pageQuestions.forEach((pageQuestion, pageIdx) => { let bestMatchIndex = -1; let bestMatchScore = 0; // 遍历所有答案,找到最匹配的 setting.datas.forEach((answerData, answerIdx) => { if (usedAnswerIndices.has(answerIdx)) return; // 跳过已使用的答案 const cleanPageQuestion = cleanHtmlTags(pageQuestion.text); const cleanAnswerQuestion = answerData.cleanQuestion; // 计算匹配分数 let matchScore = 0; if (cleanPageQuestion.includes(cleanAnswerQuestion) || cleanAnswerQuestion.includes(cleanPageQuestion)) { matchScore = Math.min(cleanPageQuestion.length, cleanAnswerQuestion.length) / Math.max(cleanPageQuestion.length, cleanAnswerQuestion.length); } // 如果是更好的匹配,则更新 if (matchScore > bestMatchScore) { bestMatchScore = matchScore; bestMatchIndex = answerIdx; } }); // 如果找到匹配,则添加到排序后的列表 if (bestMatchIndex !== -1 && bestMatchScore > 0.5) { // 设置一个阈值,确保匹配质量 sortedDatas.push(setting.datas[bestMatchIndex]); usedAnswerIndices.add(bestMatchIndex); } }); // 第二轮:添加未匹配的答案到列表末尾 setting.datas.forEach((answerData, idx) => { if (!usedAnswerIndices.has(idx)) { sortedDatas.push(answerData); } }); // 如果成功排序,则更新数据 if (sortedDatas.length > 0) { setting.datas = sortedDatas; log(`答案列表已按当前题目顺序排序`); } } } // 更新 Vue 实例中的数据 if (window.vue && setting.datas.length > 0) { // 更新idAndOptions以反映新的顺序 setting.datas.forEach((item, idx) => { const questionNum = (idx + 1).toString(); // 提取原始idAndOptions中的选项部分 const optionsPart = item.idAndOptions.split('.').slice(1).join('.'); item.idAndOptions = `${questionNum}.${optionsPart}`; item.key = idx.toString(); // 更新key以匹配新顺序 }); window.vue.answerList = [...setting.datas]; window.vue.hasAnswer = true; // 设置答案获取状态为 true } log(`成功处理 ${setting.datas.length} 道题目`); log('答案获取完成,可以切换到答案列表查看'); } catch (error) { log("格式化答案时出错:" + error.message); if (window.vue) { window.vue.hasAnswer = false; } } } //初始化界面 function initView(){ // 检查验证是否有效 if (checkValidTime()) { // 验证仍然有效,直接继续初始化 continueInit(); return; } // 创建验证界面的HTML const createModal = () => { const modalHtml = ` <div id="secretModal" style=" position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 10000; "> <div style=" background: white; padding: 30px; border-radius: 15px; box-shadow: 0 8px 24px rgba(0,0,0,0.2); width: 320px; text-align: center; animation: modalFadeIn 0.3s ease; "> <img src="https://pic.imgdb.cn/item/673c85b1d29ded1a8ce8b97c.png" style=" width: 64px; height: 64px; margin-bottom: 15px; "> <h2 style=" margin: 0 0 20px 0; color: #333; font-size: 20px; font-weight: 500; ">请输入暗号---你好</h2> <input type="text" id="secretInput" style=" width: 100%; padding: 12px; margin-bottom: 15px; border: 2px solid #e8e8e8; border-radius: 8px; font-size: 16px; outline: none; transition: all 0.3s; box-sizing: border-box; " placeholder="请输入暗号..."> <button id="verifyButton" style=" width: 100%; padding: 12px; border: none; background-color: #1890ff; color: white; border-radius: 8px; font-size: 16px; cursor: pointer; transition: all 0.3s; box-sizing: border-box; ">验证</button> </div> </div> `; document.body.insertAdjacentHTML('beforeend', modalHtml); // 添加事件监听器 const button = document.getElementById('verifyButton'); if (button) { button.addEventListener('click', window.verifySecret); } // 添加键盘事件监听 addKeyboardListener(); // 自动聚焦输入框 setTimeout(() => { const input = document.getElementById('secretInput'); if (input) { input.focus(); } }, 100); }; // 添加动画样式 const style = document.createElement('style'); style.textContent = ` @keyframes shake { 0%, 100% { transform: translateX(0); } 10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); } 20%, 40%, 60%, 80% { transform: translateX(5px); } } @keyframes modalFadeIn { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } } @keyframes modalFadeOut { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(-20px); } } `; document.head.appendChild(style); createModal(); } // 将原来的初始化代码移动到新的函数中 function continueInit() { var $div =$('<div class="rlBox minimized">' + ' <a-card title="宜宾学院智慧校园助手" style="width: 100%;height: 100%;">' + ' <template slot="extra">' + ' <span v-show="!close" style="margin-right: 10px; font-size: 12px; color: #999;">{{validTimeRemaining}}</span>' + ' <a-button :type="buttonColor" shape="circle" :icon="buttonIcon" @click="toClose" size="small"/>' + ' </template>' + ' <div style="margin-bottom: 15px;" v-show="!close">' + ' <div class="button-container">' + ' <a-button :type="autoLearning ? \'danger\' : \'warning\'" class="main-button" @click="startAutoLearning">{{autoLearning ? "停止学习" : "自动学习"}}</a-button>' + ' <a-button type="danger" class="main-button" @click="clearProgress">清除进度</a-button>' + ' <a-button type="default" class="main-button" @click="showMoreMenu">' + ' <span>更多功能</span>' + ' <a-icon type="down" />'+ ' </a-button>' + ' </div>' + ' </div>' + ' <a-tabs default-active-key="1" @change="callback" v-show="!close">' + ' <a-tab-pane key="1" tab="运行日志">' + ' <div class="rl-panel log">' + ' <p v-for="item in logs" class="log_content">' + ' {{item}}' + ' </p>' + ' </div>' + ' </a-tab-pane>' + ' <a-tab-pane key="2" :tab="answerTabTitle" :disabled="!hasAnswer">' + ' <div class="rl-panel">' + ' <a-table id="rlTable"' + ' :pagination="false" bordered size="small" :columns="columns" :data-source="answerList">' + ' </a-table>' + ' </div>' + ' </a-tab-pane>' + ' </a-tabs>' + ' </a-card>' + '</div>'); // 更新样式 const customStyle = ` .rlBox { position: fixed; top: 10px; right: 10px; width: 400px; z-index: 9999; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); padding: 10px; transition: all 0.3s ease; transform: none !important; overflow: hidden; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); background-color: rgba(255, 255, 255, 0.95); } /* 卡片标题美化 */ .ant-card-head { border-bottom: 1px solid rgba(0, 0, 0, 0.06); padding: 0 12px; background: linear-gradient(to right, #f5f5f5, #ffffff); border-radius: 8px 8px 0 0; } .ant-card-head-title { font-weight: 600; color: #1890ff; font-size: 16px; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); } /* 按钮动画效果 */ .main-button { width: 100%; height: 38px; display: flex; align-items: center; justify-content: center; border-radius: 4px; font-size: 14px; font-weight: 500; box-shadow: 0 2px 0 rgba(0,0,0,0.05); transition: all 0.3s; position: relative; overflow: hidden; } .main-button:after { content: ''; position: absolute; top: 50%; left: 50%; width: 5px; height: 5px; background: rgba(255, 255, 255, 0.5); opacity: 0; border-radius: 100%; transform: scale(1, 1) translate(-50%); transform-origin: 50% 50%; } .main-button:hover:after { animation: ripple 0.6s ease-out; } @keyframes ripple { 0% { transform: scale(0, 0); opacity: 0.5; } 20% { transform: scale(25, 25); opacity: 0.3; } 100% { opacity: 0; transform: scale(40, 40); } } /* 滚动条美化 */ .rl-panel::-webkit-scrollbar { width: 6px; height: 6px; } .rl-panel::-webkit-scrollbar-thumb { background: #c0c0c0; border-radius: 3px; transition: all 0.3s; } .rl-panel::-webkit-scrollbar-thumb:hover { background: #a0a0a0; } .rl-panel::-webkit-scrollbar-track { background: #f0f0f0; border-radius: 3px; } /* 日志内容美化 */ .log_content { margin: 6px 0; padding: 6px 10px; border-radius: 4px; background: #f9f9f9; border-left: 3px solid #1890ff; transition: all 0.2s; animation: fade-in-log 0.3s ease-out backwards; } .log_content:nth-child(odd) { background: #f5f5f5; border-left-color: #52c41a; } .log_content:hover { background: #f0f7ff; transform: translateX(2px); } @keyframes fade-in-log { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } } /* 优化最小化状态 */ .rlBox.minimized { width: 40px !important; height: 40px !important; padding: 0 !important; overflow: hidden; opacity: 0.8; cursor: pointer; border-radius: 50%; background: #1890ff; box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3); transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); transform: scale(1) !important; } /* 最小化状态悬停效果 */ .rlBox.minimized:hover { opacity: 1; box-shadow: 0 6px 16px rgba(24, 144, 255, 0.5); transform: scale(1.1) !important; } /* 展开状态动画 */ .rlBox:not(.minimized) { animation: expand-box 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15); border: 1px solid rgba(0, 0, 0, 0.06); } @keyframes expand-box { 0% { opacity: 0.8; width: 40px; height: 40px; border-radius: 50%; transform: scale(1); } 60% { border-radius: 10px; opacity: 1; } 100% { width: 400px; height: auto; opacity: 1; transform: scale(1); } } @keyframes minimize-box { 0% { width: 400px; height: auto; border-radius: 8px; opacity: 1; } 40% { opacity: 0.9; border-radius: 25px; } 100% { width: 40px; height: 40px; border-radius: 50%; opacity: 0.8; } } /* 按钮容器样式 */ .button-container { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; width: 100%; animation: fade-in 0.3s ease-out 0.1s backwards; } @keyframes fade-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } /* 主按钮样式 */ .main-button { width: 100%; height: 38px; display: flex; align-items: center; justify-content: center; border-radius: 4px; font-size: 14px; font-weight: 500; box-shadow: 0 2px 0 rgba(0,0,0,0.05); transition: all 0.3s; } /* 按钮样式优化 */ .ant-btn-danger { background: #ff4d4f !important; border-color: #ff4d4f !important; color: white !important; } .ant-btn-danger:hover { background: #ff7875 !important; border-color: #ff7875 !important; } .ant-btn-primary { background: #1890ff !important; border-color: #1890ff !important; } .ant-btn-primary:hover { background: #40a9ff !important; border-color: #40a9ff !important; } .ant-btn-success { background: #52c41a !important; border-color: #52c41a !important; color: white !important; } .ant-btn-success:hover { background: #73d13d !important; border-color: #73d13d !important; } .ant-btn-warning { background: #faad14 !important; border-color: #faad14 !important; color: white !important; } .ant-btn-warning:hover { background: #ffc53d !important; border-color: #ffc53d !important; } /* 下拉菜单样式 */ .more-menu { position: fixed; background: white; border-radius: 4px; box-shadow: 0 3px 6px -4px rgba(0,0,0,0.12), 0 6px 16px 0 rgba(0,0,0,0.08), 0 9px 28px 8px rgba(0,0,0,0.05); z-index: 10000; display: none; padding: 4px 0; } .more-menu.show { display: block; animation: fadeIn 0.2s ease; } @keyframes fadeIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } } .menu-item { padding: 10px 16px; color: #333; cursor: pointer; transition: all 0.3s; display: flex; align-items: center; width: 100%; box-sizing: border-box; } .menu-item:hover { background: #f5f5f5; } .menu-item i { margin-right: 8px; font-size: 14px; } .menu-divider { height: 1px; background: #f0f0f0; margin: 4px 0; } /* 最小化状态下的卡片样式 */ .rlBox.minimized .ant-card { background: transparent; border: none; box-shadow: none; } /* 最小化状态下的标题隐藏 */ .rlBox.minimized .ant-card-head-title { display: none; } /* 最小化状态下的开按钮样式 */ .rlBox.minimized .ant-btn-circle { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: transparent !important; border: 2px solid white !important; color: white !important; box-shadow: none; } .rlBox.minimized .ant-btn-circle:hover { background: rgba(255, 255, 255, 0.2) !important; } /* 移动端适配 */ @media screen and (max-width: 768px) { .rlBox { width: 300px; } .rlBox.minimized { width: 36px !important; height: 36px !important; } } /* 表格容器样式 */ .rl-panel { height: auto; max-height: calc(100vh - 250px); overflow-y: auto; overflow-x: hidden; } /* 表格样式优化 */ .ant-table-wrapper { overflow: visible; } .ant-table { min-width: 100%; background: transparent; } /* 美化滚动条样式 */ .rl-panel::-webkit-scrollbar { width: 6px; height: 6px; } .rl-panel::-webkit-scrollbar-thumb { background: #d9d9d9; border-radius: 3px; } .rl-panel::-webkit-scrollbar-track { background: #f0f0f0; border-radius: 3px; } /* 表格单元格样式 */ .ant-table-tbody > tr > td { white-space: normal; word-break: break-word; padding: 8px 16px; line-height: 1.5; max-width: 0; } /* 表格头部样式 */ .ant-table-thead > tr > th { background: #f5f5f5; padding: 12px 16px; white-space: nowrap; position: sticky; top: 0; z-index: 2; } /* 保表格布局合理 */ #rlTable { table-layout: fixed; width: 100%; } /* 移动端适配 */ @media screen and (max-width: 768px) { .rl-panel { max-height: calc(100vh - 200px); } .ant-table-tbody > tr > td { padding: 6px 12px; } .ant-table-thead > tr > th { padding: 8px 12px; } } `; $("body").append($div); GM_addStyle(GM_getResourceText("cs1")); GM_addStyle(GM_getResourceText("cs2")); GM_addStyle(customStyle); // 创建更多菜单DOM const moreMenuHtml = ` <div id="moreMenu" class="more-menu"> <div class="menu-item" id="autoAnswerBtn"> <i class="anticon anticon-check-circle"></i>自动答题 </div> <div class="menu-item" id="videoSkipBtn"> <i class="anticon anticon-forward"></i>秒过视频 </div> <div class="menu-item" id="exportExcelBtn"> <i class="anticon anticon-export"></i>导出题库 </div> <div class="menu-item" id="clearLogsBtn"> <i class="anticon anticon-delete"></i>清除日志 </div> <div class="menu-divider"></div> <div class="menu-item" id="tutorialBtn"> <i class="anticon anticon-question-circle"></i>使用教程 </div> <div class="menu-item" id="settingsBtn"> <i class="anticon anticon-setting"></i>答题设置 </div> </div> `; $("body").append(moreMenuHtml); // 创建辅助函数用于教程模态框 function createVNode(tag, props = {}, children = []) { const element = document.createElement(tag); // 设置属性 for (const key in props) { if (key === 'style' && typeof props[key] === 'object') { Object.assign(element.style, props[key]); } else { element.setAttribute(key, props[key]); } } // 添加子元素 if (Array.isArray(children)) { children.forEach(child => { if (typeof child === 'string') { element.appendChild(document.createTextNode(child)); } else { element.appendChild(child); } }); } else if (typeof children === 'string') { element.textContent = children; } return element; } function createText(text) { return document.createTextNode(text); } // Icon 组件简单实现 function Icon(props) { const icon = document.createElement('i'); icon.className = `anticon anticon-${props.type}`; if (props.theme) { icon.className += ` anticon-${props.type}-${props.theme}`; } if (props.style) { Object.assign(icon.style, props.style); } return icon; } // 手动定义直接绑定的事件处理函数 window.autoAnswerHandler = function() { if (window.vue) window.vue.autoAnswer(); document.getElementById('moreMenu').classList.remove('show'); }; window.videoSkipHandler = function() { if (window.vue) window.vue.videoSkip(); document.getElementById('moreMenu').classList.remove('show'); }; window.exportExcelHandler = function() { if (window.vue) window.vue.exportExcel(); document.getElementById('moreMenu').classList.remove('show'); }; window.clearLogsHandler = function() { if (window.vue) window.vue.clearLogs(); document.getElementById('moreMenu').classList.remove('show'); }; window.openTutorialHandler = function() { if (window.vue) window.vue.showTutorial(); document.getElementById('moreMenu').classList.remove('show'); }; window.openSettingsHandler = function() { if (window.vue) window.vue.openSettings(); document.getElementById('moreMenu').classList.remove('show'); }; // 确保菜单项绑定了正确的事件处理函数 const autoAnswerBtn = document.getElementById('autoAnswerBtn'); const videoSkipBtn = document.getElementById('videoSkipBtn'); const exportExcelBtn = document.getElementById('exportExcelBtn'); const clearLogsBtn = document.getElementById('clearLogsBtn'); const tutorialBtn = document.getElementById('tutorialBtn'); const settingsBtn = document.getElementById('settingsBtn'); if (autoAnswerBtn) autoAnswerBtn.addEventListener('click', window.autoAnswerHandler); if (videoSkipBtn) videoSkipBtn.addEventListener('click', window.videoSkipHandler); if (exportExcelBtn) exportExcelBtn.addEventListener('click', window.exportExcelHandler); if (clearLogsBtn) clearLogsBtn.addEventListener('click', window.clearLogsHandler); if (tutorialBtn) tutorialBtn.addEventListener('click', window.openTutorialHandler); if (settingsBtn) settingsBtn.addEventListener('click', window.openSettingsHandler); var vue = new Vue({ el: '.rlBox', data:{ logs: setting.logs, close: true, key: '1', columns:[ { title: '序号.选项', dataIndex: 'idAndOptions', key: 'idAndOptions', width: '80px', fixed: 'left', align: 'center' }, { title: '题目', dataIndex: 'question', key: 'question', width: '45%', ellipsis: true }, { title: '答案', dataIndex: 'answer', key: 'answer', width: '45%', customRender: (text) => { return text ? text.split('\n').join('<br/>') : ''; } } ], answerList: [], // 初化为空数组 isDragging: false, currentX: 0, currentY: 0, initialX: 0, initialY: 0, xOffset: 0, yOffset: 0, hasAnswer: false, // 添加答案获取状态标志 validUntil: localStorage.getItem('scriptValidUntil') || null, autoLearning: false, // 添加自动学习状态标志 }, mounted() { window.vue = this; this.initDragEvents(); // 修改初始化位置设置 const box = document.querySelector('.rlBox'); box.style.right = '0px'; box.style.left = 'auto'; box.setAttribute('data-expand-side', 'right'); // 添加窗口大小改变监听 window.addEventListener('resize', this.checkPosition); // 每分钟更新一次验证时间显示 setInterval(() => { this.validUntil = localStorage.getItem('scriptValidUntil'); }, 60000); // 检查是否需要恢复学习进度 this.checkLearningProgress(); // 添加页面URL变化监听 this.initUrlChangeListener(); }, computed:{ isShow(){ return this.close ? 0.8 : 1.0; }, buttonIcon(){ return this.close ? 'plus' : 'minus'; }, buttonColor(){ return this.close ? 'primary' : 'default'; }, answerTabTitle() { return this.hasAnswer ? '答案列表' : '答案列表 (等待获取...)'; }, validTimeRemaining() { const validTime = getValidTime(); if (!validTime) return '未验证'; const remaining = validTime - Date.now(); if (remaining <= 0) return '验证已过期'; const days = Math.floor(remaining / (24 * 60 * 60 * 1000)); const hours = Math.floor((remaining % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000)); return `验证剩余: ${days}天${hours}小时`; } }, methods: { callback(key) { if (key === '2' && !this.hasAnswer) { this.$message.warning('请等待答案获取完成后再查看答案列表'); this.key = '1'; // 保持在日志页面 return; } this.key = key; }, toClose() { this.close = !this.close; const box = document.querySelector('.rlBox'); const rect = box.getBoundingClientRect(); const windowWidth = window.innerWidth; if (this.close) { // 最小化时,判断靠近哪边 const centerX = rect.left + rect.width / 2; // 添加最小化动画类 box.style.animation = 'minimize-box 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) forwards'; // 在动画执行过程中保持原位置 box.style.transition = 'none'; if (centerX > windowWidth / 2) { // 靠右 box.style.right = '10px'; box.style.left = 'auto'; box.setAttribute('data-side', 'right'); } else { // 靠左 box.style.left = '10px'; box.style.right = 'auto'; box.setAttribute('data-side', 'left'); } // 使用setTimeout确保类名在动画结束后添加 setTimeout(() => { box.classList.add('minimized'); // 恢复transition以保证后续hover效果 box.style.animation = ''; box.style.transition = 'all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1)'; }, 400); } else { // 展开时,先移除最小化类,然后应用展开动画 box.classList.remove('minimized'); const side = box.getAttribute('data-side') || 'right'; if (side === 'right') { box.style.right = '10px'; box.style.left = 'auto'; } else { box.style.left = '10px'; box.style.right = 'auto'; } // 为内部内容添加循序渐入效果 const tabs = box.querySelector('.ant-tabs'); const buttons = box.querySelector('.button-container'); if (tabs) { tabs.style.opacity = '0'; tabs.style.transform = 'translateY(10px)'; tabs.style.transition = 'opacity 0.3s ease, transform 0.3s ease'; tabs.style.display = ''; setTimeout(() => { tabs.style.opacity = '1'; tabs.style.transform = 'translateY(0)'; }, 100); } if (buttons) { buttons.style.opacity = '0'; buttons.style.transform = 'translateY(10px)'; buttons.style.transition = 'opacity 0.3s ease, transform 0.3s ease'; buttons.style.display = ''; setTimeout(() => { buttons.style.opacity = '1'; buttons.style.transform = 'translateY(0)'; }, 50); } } }, startAutoLearning() { if(this.autoLearning) { this.autoLearning = false; localStorage.removeItem('learningProgress'); log("已停止自动学习"); this.$message.info("已停止自动学习"); return; } this.autoLearning = true; this.$message.success({ content: "自动学习已启动", duration: 3, description: "系统将自动完成视频观看、文档阅读和测验答题" }); log("开始自动学习..."); this.processCurrentContent(); }, processCurrentContent() { if(!this.autoLearning) { log("自动学习已停止"); return; } const pageType = checkPageType(); if(!pageType) { log("未识别到当前内容类型,等待加载..."); setTimeout(() => this.processCurrentContent(), 1000); return; } const currentTab = document.querySelector('.tab-active .tab-inner'); const itemName = currentTab.getAttribute('itemname'); const currentChapter = document.querySelector('.nav-text.current'); const chapterName = currentChapter ? currentChapter.querySelector('.node-text').textContent.trim() : '未知章节'; // 显示当前学习内容提示 this.$message.info({ content: `正在处理: ${itemName}`, duration: 3, description: `章节: ${chapterName}` }); log(`正在处理 ${chapterName} - ${itemName} (${pageType})`); // 根据页面类型执行不同操作 switch(pageType) { case 'video': this.handleVideo(); break; case 'document': this.handleDocument(); break; case 'quiz': // 使用全局函数而不是方法 handleQuiz(); break; } }, handleVideo() { let video = document.getElementsByTagName("video")[0]; if(!video) { log("当前页面不存在视频,请确保视频已加载"); setTimeout(() => this.handleVideo(), 1000); return; } // 显示视频处理提示 this.$message.info({ content: "正在处理视频", duration: 3, description: "系统将自动完成视频观看" }); // 尝试使用HTML5视频API静音 video.muted = true; // 尝试使用JWPlayer API静音(如果存在) try { if (typeof jwplayer === 'function' && jwplayer("mediaplayer")) { jwplayer("mediaplayer").setMute(true); log("已使用JWPlayer API静音视频"); } else { log("已自动静音HTML5视频"); } } catch (error) { log("JWPlayer静音尝试失败,使用HTML5静音: " + error.message); } video.addEventListener('ended', () => { if(this.autoLearning) { log("视频播放完成,准备切换下一个内容"); this.$message.success({ content: "视频学习完成", duration: 2 }); this.switchToNextContent(); } }); try { if (video.duration && !isNaN(video.duration)) { video.currentTime = video.duration - 0.1; video.playbackRate = 1; log("正在完成当前视频..."); } else { log("等待视频加载..."); video.addEventListener('loadedmetadata', () => { video.currentTime = video.duration - 0.1; video.playbackRate = 1; log("视频加载完成,正在处理..."); }); } } catch (error) { log("处理视频时出错:" + error.message); } }, handleDocument() { const totalPages = parseInt(document.querySelector('.flexpaper_lblTotalPages')?.textContent.replace('/ ', '')) || 0; if(totalPages === 0) { log("未检测到文档页数,请确保文档已加载"); setTimeout(() => this.handleDocument(), 1000); return; } // 计算阅读时间和翻页间隔 let totalReadingTime, pageInterval; if(totalPages < 10) { // 小于10页文档,使用固定1秒1页的速度 totalReadingTime = totalPages; pageInterval = 1000; // 1秒1页 } else if(totalPages > 50) { // 超过50页文档,固定30秒完成 totalReadingTime = 30; pageInterval = Math.max(totalReadingTime / totalPages * 1000, 300); // 每页间隔时间(毫秒),最小300ms } else { // 10-50页文档使用原有逻辑 totalReadingTime = totalPages > 20 ? 20 : 10; // 超过20页固定20秒,否则10秒 pageInterval = Math.max(totalReadingTime / totalPages * 1000, 300); // 每页间隔时间(毫秒),最小300ms } // 显示文档阅读提示 this.$message.info({ content: "开始阅读文档", duration: 3, description: `共 ${totalPages} 页,预计用时 ${totalReadingTime} 秒` }); log(`正在阅读文档,共${totalPages}页,预计用时${totalReadingTime}秒,翻页间隔${Math.round(pageInterval)}毫秒`); let currentPage = 1; const readInterval = setInterval(() => { if(!this.autoLearning || currentPage >= totalPages) { clearInterval(readInterval); if(this.autoLearning) { log(`文档阅读完成,准备切换到下一个内容`); this.$message.success({ content: "文档阅读完成", duration: 2 }); this.switchToNextContent(); } return; } try { const nextButton = document.querySelector('.flexpaper_bttnPrevNext'); if(nextButton) { nextButton.click(); // 每5页或翻页速度快时每10页显示一次进度 const progressInterval = pageInterval < 500 ? 10 : 5; if(currentPage % progressInterval === 0) { this.$message.info({ content: `阅读进度: ${Math.round((currentPage/totalPages) * 100)}%`, duration: 1 }); } log(`正在阅读第${currentPage}页,共${totalPages}页`); currentPage++; } else { log("未找到翻页按钮,尝试重新加载"); setTimeout(() => this.handleDocument(), 1000); } } catch (error) { log(`翻页过程出错: ${error.message}`); clearInterval(readInterval); setTimeout(() => this.handleDocument(), 1000); } }, pageInterval); }, switchToNextContent() { if(!this.autoLearning) return; // 获取所有标签页 const tabs = document.querySelectorAll('.tab-inner'); let currentTabIndex = -1; // 找到当前激活的标签页 tabs.forEach((tab, index) => { if(tab.parentElement.classList.contains('tab-active')) { currentTabIndex = index; } }); // 检查是否还有下一个内容 if(currentTabIndex < tabs.length - 1) { // 还有下一个内容,继续在当前章节学习 const nextTab = tabs[currentTabIndex + 1]; const nextTabName = nextTab.getAttribute('itemname'); log(`切换到下一个内容: ${nextTabName}`); nextTab.click(); setTimeout(() => this.processCurrentContent(), 1500); } else { // 当前章节的所有内容已学习完成 const currentChapter = document.querySelector('.nav-text.current'); if (!currentChapter) { log("无法识别当前章节,自动学习已停止"); this.autoLearning = false; return; } const chapterName = currentChapter.querySelector('.node-text')?.textContent.trim() || '未知章节'; log(`章节 ${chapterName} 的所有内容已学习完成,检查是否有下一讲...`); // 查找当前章节的父元素(sup-item),然后检查是否有下一个讲 const currentSupItem = currentChapter.closest('.nav-item.sup-item'); if (!currentSupItem) { log("无法找到当前章节的父元素,尝试切换到下一章节"); this.switchToNextChapter(); return; } // 获取当前章节下的子章节列表(二级菜单) const subNav = currentSupItem.querySelector('.sub-nav'); if (!subNav || !subNav.children || subNav.children.length === 0) { log("当前章节没有子章节,尝试切换到下一章节"); this.switchToNextChapter(); return; } // 检查子菜单是否已展开,如果没有则先展开 if (subNav.style.display === 'none') { log("展开子菜单..."); subNav.style.display = 'block'; setTimeout(() => this.switchToNextContent(), 500); return; } // 查找当前激活的讲次 const allLectures = Array.from(subNav.querySelectorAll('.sub-nav-text')); if (allLectures.length === 0) { log("未找到讲次列表,尝试切换到下一章节"); this.switchToNextChapter(); return; } // 查找当前激活的讲 let currentLectureIndex = -1; for (let i = 0; i < allLectures.length; i++) { if (allLectures[i].classList.contains('current')) { currentLectureIndex = i; break; } } log(`当前是第 ${currentLectureIndex + 1} 讲,共 ${allLectures.length} 讲`); // 检查是否有下一讲 if (currentLectureIndex < allLectures.length - 1) { // 有下一讲,切换到下一讲 const nextLecture = allLectures[currentLectureIndex + 1]; const nextLectureName = nextLecture.textContent.trim(); log(`切换到下一讲: ${nextLectureName}`); // 保存当前章节信息用于恢复 const progress = { isLearning: true, currentChapter: currentSupItem.getAttribute('id'), nextLecture: nextLecture.getAttribute('id') || nextLecture.getAttribute('data-id'), timestamp: Date.now(), isPageSwitching: true }; localStorage.setItem('learningProgress', JSON.stringify(progress)); setting.learningProgress = progress; // 显示切换提示 this.$notification.info({ message: '准备切换讲次', description: `即将切换到: ${nextLectureName}`, duration: 3 }); // 点击下一讲 nextLecture.click(); // 等待内容加载完成后继续处理 setTimeout(() => { try { // 检查是否有内容标签 const tabList = document.querySelectorAll('.tab-inner'); if (!tabList || tabList.length === 0) { throw new Error("未找到可学习的内容"); } const firstTab = tabList[0]; log(`开始学习新讲次内容: ${firstTab.getAttribute('itemname')}`); firstTab.click(); // 确保自动学习状态保持开启 this.autoLearning = true; // 保存新的学习进度 const newProgress = { isLearning: true, currentChapter: currentSupItem.getAttribute('id'), currentContent: firstTab.getAttribute('itemid'), timestamp: Date.now(), isPageSwitching: false }; localStorage.setItem('learningProgress', JSON.stringify(newProgress)); // 等待内容加载完成后开始学习 setTimeout(() => { if (this.autoLearning) { log(`开始自动学习新讲次内容...`); this.processCurrentContent(); } }, 1500); } catch (error) { log(`加载讲次内容失败: ${error.message}`); this.switchToNextChapter(); } }, 2000); } else { // 已经是当前章节的最后一讲,切换到下一章节 log("当前章节的所有讲次已学习完成,准备切换到下一章节"); this.switchToNextChapter(); } } }, switchToNextChapter() { try { const allChapters = document.querySelectorAll('.sidebar .nav-list .nav-item.sup-item'); if (!allChapters.length) { this.autoLearning = false; log("未找到章节列表,自动学习已停止"); this.$message.warning("未找到章节列表"); return; } log(`共发现 ${allChapters.length} 个章节`); let currentChapterIndex = -1; allChapters.forEach((chapter, index) => { if(chapter.querySelector('.nav-text.current')) { currentChapterIndex = index; } }); if(currentChapterIndex < allChapters.length - 1) { const nextChapter = allChapters[currentChapterIndex + 1]; const nextChapterLink = nextChapter.querySelector('.nav-text'); const nextChapterName = nextChapter.querySelector('.node-text').textContent.trim(); if (!nextChapterLink) { throw new Error("无法找到章节链接"); } const unitId = nextChapterLink.getAttribute('unitid'); if (!unitId) { throw new Error("无法获取章节ID"); } // 设置进度信息,标记正在切换章节 const progress = { isLearning: true, currentChapter: unitId, // 设置为要跳转的章节ID currentContent: null, timestamp: Date.now(), isPageSwitching: true // 标记正在切换页面 }; localStorage.setItem('learningProgress', JSON.stringify(progress)); setting.learningProgress = progress; // 显示章节切换提示 this.$notification.info({ message: '准备切换章节', description: `即将切换到: ${nextChapterName}`, duration: 3 }); log(`准备切换到下一章节: ${nextChapterName} (${currentChapterIndex + 2}/${allChapters.length})`); // 直接点击章节链接 nextChapterLink.click(); // 等待页面加载完成 setTimeout(() => { try { // 展开子章节列表 const subNav = nextChapter.querySelector('.sub-nav'); if (!subNav) { throw new Error("未找到子章节列表"); } subNav.style.display = 'block'; // 获取第一个子章节 const firstSubChapter = subNav.querySelector('.sub-nav-text'); if (!firstSubChapter) { throw new Error("未找到子章节"); } // 点击第一个子章节 log(`点击第一个子章节...`); firstSubChapter.click(); // 等待子章节内容加载 setTimeout(() => { try { // 获取并点击第一个内容标签 const tabList = document.querySelectorAll('.tab-inner'); if (!tabList || tabList.length === 0) { throw new Error("未找到可学习的内容"); } const firstTab = tabList[0]; log(`开始学习: ${firstTab.getAttribute('itemname')}`); firstTab.click(); // 确保自动学习状态保持开启 this.autoLearning = true; // 保存新的学习进度 const newProgress = { isLearning: true, currentChapter: unitId, currentContent: firstTab.getAttribute('itemid'), timestamp: Date.now() }; localStorage.setItem('learningProgress', JSON.stringify(newProgress)); // 等待内容加载完成后开始学习 setTimeout(() => { if (this.autoLearning) { log(`开始自动学习新章节内容...`); this.processCurrentContent(); } }, 1500); } catch (error) { throw new Error(`加载内容失败: ${error.message}`); } }, 1500); } catch (error) { throw new Error(`切换子章节失败: ${error.message}`); } }, 2000); } else { this.autoLearning = false; log("已完成所有章节的学习!"); this.$notification.success({ message: '学习完成', description: '恭喜!已完成所有章节的学习', duration: 0 }); } } catch (error) { this.autoLearning = false; log(`切换章节失败: ${error.message}`); this.$message.error("切换章节失败,请刷新页面重试"); } }, checkPosition() { const box = document.querySelector('.rlBox'); const rect = box.getBoundingClientRect(); const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; // 检查并修正水平位置 if (rect.right > windowWidth) { box.style.right = '0px'; box.style.left = 'auto'; } if (rect.left < 0) { box.style.left = '0px'; box.style.right = 'auto'; } // 检查并修正垂直位置 if (rect.bottom > windowHeight) { box.style.top = `${windowHeight - rect.height - 10}px`; } if (rect.top < 0) { box.style.top = '10px'; } }, autoAnswer() { log("开始自动答题..."); // 检查是否已获取答案 if (!setting.datas || setting.datas.length === 0) { log("未找到答案信息,请先获取答案"); return; } log(`共发现 ${setting.datas.length} 道题目的答案`); let answeredCount = 0; // 获取所有题目容器 const allQuestionContainers = document.querySelectorAll('.view-test'); if (!allQuestionContainers || allQuestionContainers.length === 0) { log("未找到题目容器,请确认是否在答题页面"); return; } log(`页面上共有 ${allQuestionContainers.length} 道题目`); // 遍历所有题目容器,确保每个题目都能找到对应的答案 allQuestionContainers.forEach((questionContainer, containerIndex) => { try { // 获取题目文本,用于匹配答案 const questionTextElement = questionContainer.querySelector('.test-text-tutami'); if (!questionTextElement) { log(`第 ${containerIndex + 1} 题未找到题目文本`); return; } const questionText = questionTextElement.textContent.trim(); // 在答案列表中查找匹配的题目 let matchedAnswerData = null; let matchIndex = -1; // 首先尝试按索引匹配 if (containerIndex < setting.datas.length) { matchedAnswerData = setting.datas[containerIndex]; matchIndex = containerIndex; } // 如果索引匹配失败或题目不匹配,则尝试通过题目内容匹配 if (!matchedAnswerData || !questionText.includes(cleanHtmlTags(matchedAnswerData.question))) { // 在答案列表中查找匹配的题目 for (let i = 0; i < setting.datas.length; i++) { const answerData = setting.datas[i]; const cleanQuestion = cleanHtmlTags(answerData.question); // 检查题目文本是否匹配 if (questionText.includes(cleanQuestion) || cleanQuestion.includes(questionText)) { matchedAnswerData = answerData; matchIndex = i; break; } } } if (!matchedAnswerData) { log(`第 ${containerIndex + 1} 题未找到匹配的答案`); return; } // 获取匹配到的答案 const answer = matchedAnswerData.answer; if (!answer) { log(`第 ${containerIndex + 1} 题答案为空`); return; } log(`第 ${containerIndex + 1} 题匹配到答案列表中的第 ${matchIndex + 1} 题`); // 判断题目类型 const isMultiChoice = questionContainer.querySelector('.input-c') !== null; const isSingleChoice = questionContainer.querySelector('.input-r') !== null; const isFillBlank = questionContainer.querySelector('.fillblank') !== null; if (isMultiChoice || isSingleChoice) { // 处理选择题 const answerLetters = answer.split('\n').map(a => a.trim().charAt(0)); const options = questionContainer.querySelectorAll('.t-option'); options.forEach((option, idx) => { const letter = String.fromCharCode(65 + idx); if (answerLetters.includes(letter)) { // 查找选项的点击元素 const clickTarget = option.querySelector('a[href="javascript:void(0)"]') || option.querySelector(isMultiChoice ? '.input-c' : '.input-r'); if (clickTarget) { // 检查是否已经选中 const isChecked = option.querySelector(isMultiChoice ? '.input-c.selected' : '.input-r.selected'); if (!isChecked) { clickTarget.click(); log(`第 ${containerIndex + 1} 题选择了选项 ${letter}`); } } } }); answeredCount++; } else if (isFillBlank) { // 处理填空题 const input = questionContainer.querySelector('.fillblank'); if (input) { input.value = answer.replace(/^.*?\./,'').trim(); // 移除可能的选项标记 // 触发必要的事件 input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); log(`第 ${containerIndex + 1} 题填写了答案: ${answer}`); answeredCount++; } } } catch (error) { log(`第 ${containerIndex + 1} 题自动答题出错: ${error.message}`); } }); // 显示完成提示 log(`自动答题完成!成功答题 ${answeredCount} 道题目`); this.$message.success(`自动答题完成!成功答题 ${answeredCount} 道题目,请检查后手动提交。`); }, exportExcel(){ // 检查是否有答案数据 if (!this.answerList || this.answerList.length === 0) { this.$message.error('没有可导出的答案数!请等待答案获取完成。'); log('导出失败:没有答案数据'); return; } // 检查xlsx库是否加载成功 if (typeof XLSX === 'undefined') { this.$message.info('正在加载XLSX库,请稍候...'); log('正在加载XLSX库...'); // 使用中国境内常用CDN (将第二个CDN放在首位) const cdnList = [ 'https://lib.baomitu.com/xlsx/0.18.5/xlsx.full.min.js', // 360 前端静态资源库 (移到首位) 'https://cdn.bootcdn.net/ajax/libs/xlsx/0.18.5/xlsx.full.min.js', // BootCDN 'https://fastly.jsdelivr.net/npm/[email protected]/dist/xlsx.full.min.js', // JSDelivr 的 Fastly CDN 'https://gcore.jsdelivr.net/npm/[email protected]/dist/xlsx.full.min.js', // JSDelivr 的 Gcore CDN 'https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/xlsx/0.18.5/xlsx.full.min.js' // 字节跳动 CDN ]; let currentCdnIndex = 0; const tryNextCDN = () => { if (currentCdnIndex >= cdnList.length) { log('所有CDN尝试失败,请刷新页面或稍后再试'); this.$message.error('加载失败,请刷新页面或稍后再试'); return; } const currentCdnUrl = cdnList[currentCdnIndex]; log(`尝试从 CDN (${currentCdnIndex + 1}/${cdnList.length}) 加载XLSX库...`); const script = document.createElement('script'); script.src = currentCdnUrl; script.onload = () => { log('XLSX库加载成功,正在导出题库...'); // 库加载成功后直接执行导出,不需要用户再次点击 setTimeout(() => this.exportExcel(), 500); }; script.onerror = () => { log(`CDN ${currentCdnIndex + 1} 加载失败,尝试下一个...`); currentCdnIndex++; tryNextCDN(); }; document.head.appendChild(script); }; // 开始尝试加载 tryNextCDN(); return; } // 提取题目开头的序号 const extractQuestionNumber = (question) => { // 匹配模式: 数字开头,后跟"、"或"."或":"等分隔符 const match = question.match(/^(\d+)[、.::]/); if (match) { return parseInt(match[1], 10); } return null; }; // 对答案数据按题目开头的序号排序 const sortedAnswers = [...this.answerList].sort((a, b) => { const numA = extractQuestionNumber(a.question); const numB = extractQuestionNumber(b.question); // 如果两个都有序号,按序号排序 if (numA !== null && numB !== null) { return numA - numB; } // 如果只有一个有序号,有序号的排前面 if (numA !== null) return -1; if (numB !== null) return 1; // 否则保持原有顺序 return 0; }); // 准备数据(只保留题目和答案两列,原始序号从题目中提取) const data = sortedAnswers.map(item => { return { '题目': item.question, '答案': item.answer }; }); try { // 创建工作簿 const wb = XLSX.utils.book_new(); // 创建工作表 const ws = XLSX.utils.json_to_sheet(data); // 设置列宽 const colWidths = { '题目': 70, '答案': 50 }; ws['!cols'] = Object.keys(colWidths).map(key => ({ wch: colWidths[key] })); // 从页面提取题目名称和章节信息 let examName = ''; let chapterNum = ''; let lectureNum = ''; // 1. 尝试从tab栏获取测试题名称 const activeTab = document.querySelector('.tabs .tab-active'); if (activeTab) { const tabLink = activeTab.querySelector('.tab-inner'); if (tabLink && tabLink.getAttribute('itemname')) { examName = tabLink.getAttribute('itemname'); } else if (activeTab.getAttribute('title')) { examName = activeTab.getAttribute('title'); } } // 如果找不到,从页面标题提取 if (!examName) { examName = document.title.replace(/\s*[-—]\s*智慧校园.*$/, '').trim(); } // 2. 提取章节数和讲数 // 从各种可能的元素中提取章节数 const extractChapterNum = (text) => { if (!text) return ''; const match = text.match(/第([0-90-9一二三四五六七八九十]+)章/); if (match) return match[1]; return ''; }; // 从各种可能的元素中提取讲数 const extractLectureNum = (text) => { if (!text) return ''; const match = text.match(/第([0-90-9一二三四五六七八九十]+)讲/); if (match) return match[1]; return ''; }; // 尝试从导航菜单提取 const navItems = document.querySelectorAll('.nav-text, .node-text'); for (const item of navItems) { if (item.textContent.includes('章')) { const num = extractChapterNum(item.textContent); if (num) { chapterNum = num; // 检查是否是当前章节 const parent = item.closest('.nav-item'); if (parent && (parent.classList.contains('current') || item.classList.contains('current') || parent.querySelector('.current'))) { break; // 找到当前章节后停止 } } } } // 尝试从讲次导航提取 const subNavItems = document.querySelectorAll('.sub-nav-text'); for (const item of subNavItems) { if (item.textContent.includes('讲')) { const num = extractLectureNum(item.textContent); if (num && item.classList.contains('current')) { lectureNum = num; break; } } } // 备选方法:从面包屑提取 if (!chapterNum || !lectureNum) { const breadcrumbs = document.querySelectorAll('.breadcrumb a, .breadcrumb span, .breadcrumb li'); for (const crumb of breadcrumbs) { if (!chapterNum && crumb.textContent.includes('章')) { chapterNum = extractChapterNum(crumb.textContent); } if (!lectureNum && crumb.textContent.includes('讲')) { lectureNum = extractLectureNum(crumb.textContent); } } } // 备选方法:从标题元素提取 if (!chapterNum || !lectureNum) { const titleElements = document.querySelectorAll('h1, h2, h3, h4, .title, .chapter-title'); for (const elem of titleElements) { if (!chapterNum && elem.textContent.includes('章')) { chapterNum = extractChapterNum(elem.textContent); } if (!lectureNum && elem.textContent.includes('讲')) { lectureNum = extractLectureNum(elem.textContent); } } } // 组合文件名前缀 let fileNamePrefix = ''; if (chapterNum) { fileNamePrefix += `第${chapterNum}章`; if (lectureNum) { fileNamePrefix += `第${lectureNum}讲 `; } else { fileNamePrefix += ' '; } } // 如果找不到测试题名称,则使用默认值 if (!examName || examName.length < 2) { examName = '智慧校园题库'; } // 组合文件名 let fileName = fileNamePrefix + examName; // 清理文件名中的非法字符 fileName = fileName.replace(/[\\/:*?"<>|]/g, '_'); // 将工作表添加到工作簿 (使用文件名前30个字符作为工作表名) let sheetName = fileName; if (sheetName.length > 30) { sheetName = sheetName.substring(0, 30); } XLSX.utils.book_append_sheet(wb, ws, sheetName); // 生成并下载文件 XLSX.writeFile(wb, fileName + '.xlsx'); log(`题库已导出为: ${fileName}.xlsx`); this.$message.success('题库导出成功!'); } catch (error) { log(`导出Excel出错: ${error.message}`); this.$message.error(`导出失败: ${error.message}`); } }, clearLogs() { // 清空所有现有日志 this.logs = []; setting.logs = []; // 添加清除提示 const clearMessage = [ '日志已清除', '------------------------', ]; // 直接设置新的日志数组,而不是使用 log 函数 this.logs = clearMessage; setting.logs = [...clearMessage]; // 阻止其他日志添加 setTimeout(() => { // 确保清除状态保持 if (this.logs.length > clearMessage.length) { this.logs = [...clearMessage]; setting.logs = [...clearMessage]; } }, 200); }, // 视频快速完成 videoSkip() { const pageType = checkPageType(); if (pageType !== 'video') { this.$message.warning('当前页面不是视频页面,请在视频页面使用此功能'); log('秒过视频功能仅在视频页面有效'); return; } log('正在尝试秒过视频...'); try { // 尝试使用JWPlayer API if (typeof jwplayer === 'function' && jwplayer("mediaplayer")) { const jwp = jwplayer("mediaplayer"); // 设置静音 jwp.setMute(true); log("已通过JWPlayer API静音视频"); // 获取视频时长并跳至结尾 const duration = jwp.getDuration() || 0; if (duration > 0) { jwp.seek(duration - 1); log(`已将JWPlayer视频进度设置为结束位置(${Math.floor(duration)}秒)`); setTimeout(() => { log('JWPlayer视频即将自动结束,请等待系统记录完成状态'); this.$message.success('视频已跳至结尾,即将自动完成'); }, 500); return; } } // 尝试找到HTML5视频元素 const videoPlayer = document.querySelector('video'); if (!videoPlayer) { log('未找到视频元素,请确认页面已完全加载'); this.$message.error('未找到视频元素,请刷新页面重试'); return; } // 设置视频当前时间为接近结尾 const duration = videoPlayer.duration || 0; if (duration <= 0) { log('无法获取视频时长,请等待视频加载完成'); this.$message.warning('请等待视频加载完成后再试'); return; } // 设置为视频末尾前几秒 videoPlayer.muted = true; videoPlayer.currentTime = duration - 1; log(`已将HTML5视频进度设置为结束位置(${Math.floor(duration)}秒)`); // 模拟视频播放完成事件 setTimeout(() => { videoPlayer.play(); log('视频即将自动结束,请等待系统记录完成状态'); this.$message.success('视频已跳至结尾,即将自动完成'); }, 500); } catch (error) { log('秒过视频时出错: ' + error.message); this.$message.error('秒过视频失败: ' + error.message); } }, // 显示更多功能菜单 showMoreMenu() { const menu = document.getElementById('moreMenu'); if (!menu) { console.error('菜单元素不存在'); return; } // 切换显示状态 if (menu.classList.contains('show')) { menu.classList.remove('show'); } else { // 确保菜单显示在正确位置 const button = document.querySelector('.button-container .main-button:nth-child(3)'); if (button) { const rect = button.getBoundingClientRect(); // 设置菜单位置属性 menu.style.position = 'fixed'; menu.style.top = (rect.bottom + 2) + 'px'; menu.style.left = rect.left + 'px'; // 左对齐 menu.style.width = rect.width + 'px'; // 设置精确宽度与按钮相同 menu.style.right = 'auto'; // 清除之前的right值 // 添加阴影和过渡效果,提升视觉体验 menu.style.boxShadow = '0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)'; menu.style.transition = 'all 0.2s ease'; // 记录菜单位置,便于调试 console.log(`菜单位置: top=${menu.style.top}, left=${menu.style.left}, width=${menu.style.width}`); } // 先移除其他可能的事件监听器 document.removeEventListener('click', this.closeMenuHandler); // 显示菜单 menu.classList.add('show'); // 存储关闭函数引用,以便之后可以正确移除 this.closeMenuHandler = (e) => { if (!menu.contains(e.target) && !e.target.closest('.main-button:nth-child(3)')) { menu.classList.remove('show'); document.removeEventListener('click', this.closeMenuHandler); } }; // 延迟添加事件监听,避免立即触发 setTimeout(() => { document.addEventListener('click', this.closeMenuHandler); }, 100); } }, // 打开设置对话框 openSettings() { // 直接调用详细设置对话框函数 createInitialSettingsModal(false); }, // 显示使用教程 showTutorial() { try { // 移除已有的模态框 const existingModals = document.querySelectorAll('#custom-tutorial-modal'); existingModals.forEach(modal => { if (modal.parentNode) { modal.parentNode.removeChild(modal); } }); // 创建自定义模态框(不依赖antd) const modalHTML = ` <div id="custom-tutorial-modal" style=" position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: flex-start; padding-top: 50px; z-index: 100000; animation: fadeIn 0.3s; "> <div style=" background-color: white; width: 700px; border-radius: 4px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); display: flex; flex-direction: column; max-height: 80vh; animation: slideDown 0.3s; "> <div style=" padding: 16px; border-bottom: 1px solid #f0f0f0; display: flex; justify-content: space-between; align-items: center; "> <h3 style="margin: 0; font-size: 16px;">宜宾智慧校园助手使用教程</h3> <button id="close-tutorial-button" style=" border: none; background: none; font-size: 16px; cursor: pointer; color: #999; ">×</button> </div> <div style=" padding: 24px; overflow-y: auto; max-height: calc(80vh - 120px); "> <div id="tutorial-content"></div> </div> <div style=" padding: 12px 16px; border-top: 1px solid #f0f0f0; text-align: right; "> <button id="ok-tutorial-button" style=" padding: 8px 15px; background-color: #1890ff; color: white; border: none; border-radius: 4px; cursor: pointer; ">我知道了</button> </div> </div> </div> `; // 添加动画样式 const style = document.createElement('style'); style.textContent = ` @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes slideDown { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } #custom-tutorial-modal h3 { margin-top: 20px; margin-bottom: 10px; font-weight: 500; color: #333; } #custom-tutorial-modal ul, #custom-tutorial-modal ol { padding-left: 20px; margin-bottom: 16px; } #custom-tutorial-modal li { margin-bottom: 8px; } #custom-tutorial-modal strong { font-weight: 600; color: #1890ff; } #ok-tutorial-button:hover { background-color: #40a9ff; } #close-tutorial-button:hover { color: #333; } `; document.head.appendChild(style); // 添加模态框到DOM document.body.insertAdjacentHTML('beforeend', modalHTML); // 获取内容容器 const contentContainer = document.getElementById('tutorial-content'); if (!contentContainer) { throw new Error('教程内容容器不存在'); } // 填充教程内容 const tutorialHTML = ` <h3>基本功能</h3> <ul> <li><strong>自动学习:</strong>点击"自动学习"按钮,脚本会自动完成视频观看和文档阅读,不需手动操作。再次点击可暂停学习。</li> <li><strong>清除进度:</strong>清除已保存的学习进度,重新开始学习。</li> <li><strong>更多功能:</strong>点击后弹出菜单,包含自动答题、秒过视频、导出题库、清除日志、使用教程和答题设置等功能。</li> <li><strong>自动答题:</strong>在答题页面自动填写答案(需要先获取题库)。系统会自动识别题目并填入正确答案。</li> <li><strong>秒过视频:</strong>立即完成当前视频学习,标记为已完成。适用于不想等待视频播放完成的情况。</li> <li><strong>导出题库:</strong>将已获取的答案导出为Excel文件,方便离线查看。文件会按章节和讲次命名,题目按序号排序。</li> <li><strong>清除日志:</strong>清空运行日志面板,保持界面整洁。</li> <li><strong>答题设置:</strong>设置是否自动提交答案和是否自动进入考试。可以根据个人需求自定义答题行为。</li> </ul> <h3>视频静音功能</h3> <ul> <li><strong>全局自动静音:</strong>脚本在启动时自动为所有视频静音,包括HTML5视频和JWPlayer播放器。</li> <li><strong>兼容多种播放器:</strong>支持HTML5标准视频元素和特定课程使用的JWPlayer播放器。</li> <li><strong>静音监控:</strong>脚本会持续监控页面变化,对新加载的视频自动应用静音。</li> <li><strong>播放完成跳转:</strong>视频播放完成后会自动切换到下一个学习内容,全程无声音打扰。</li> </ul> <h3>使用步骤</h3> <ol> <li>登录(不可用)智慧校园平台后,脚本会自动加载并在页面右上角显示助手窗口。</li> <li>首次使用时需要输入验证密钥完成验证(密钥有效期为2天)。</li> <li>进入课程学习页面,点击【自动学习】开始自动完成视频观看和文档阅读任务。</li> <li>如需暂停学习,再次点击【自动学习】按钮(此时按钮显示为【停止学习】)。</li> <li>进入答题页面时,系统会自动获取答案并显示在答案列表中,点击顶部的【答案列表】标签可查看。</li> <li>可以点击【更多功能】→【自动答题】自动填写答案,也可以手动查看答案进行填写。</li> <li>完成答题后,可以点击【更多功能】→【导出题库】将答案保存为Excel文件,便于复习。</li> </ol> <h3>特别提示</h3> <ul> <li>验证密钥有效期默认为2天,到期后需重新验证。验证剩余时间显示在助手窗口标题栏右侧。</li> <li>自动学习功能会保存进度,关闭页面后下次访问可以继续上次的学习进度。</li> <li>为避免被系统检测,视频播放和文档阅读会模拟正常的学习行为,会有一定等待时间。</li> <li>所有视频都会自动静音,避免多个视频同时播放造成声音干扰,可以安静学习。</li> <li>导出的Excel文件会按题目序号自动排序,文件名包含章节和讲次信息,便于查找。</li> <li>支持拖动插件窗口到合适位置,点击标题栏右侧的最小化按钮可隐藏插件,需要时再点击图标展开。</li> <li>如果脚本运行不正常,请尝试刷新页面或重新安装最新版本的脚本。</li> </ul> <h3>界面说明</h3> <ul> <li><strong>运行日志:</strong>显示脚本执行过程中的操作记录和状态信息,帮助了解当前执行情况。</li> <li><strong>答案列表:</strong>显示当前页面检测到的所有题目及答案,按序号排列,可直接查看正确答案。</li> <li><strong>最小化按钮:</strong>点击标题栏右侧的按钮可以最小化助手窗口,再次点击可展开。</li> <li><strong>更多功能菜单:</strong>点击【更多功能】按钮,展开包含自动答题、秒过视频等功能的下拉菜单。</li> </ul> <h3>快捷键</h3> <ul> <li><strong>Alt+A:</strong>自动答题 - 快速为当前页面的所有题目填写答案</li> <li><strong>Alt+V:</strong>秒过视频 - 立即完成当前正在观看的视频</li> <li><strong>Alt+L:</strong>开始/停止自动学习 - 切换自动学习状态</li> <li><strong>Alt+E:</strong>导出题库 - 将答案导出为Excel文件</li> <li><strong>Alt+H:</strong>显示/隐藏插件窗口 - 切换助手窗口的显示状态</li> </ul> `; contentContainer.innerHTML = tutorialHTML; // 添加ESC键关闭功能 function handleEscKey(e) { if (e.key === 'Escape') { closeModal(); } } document.addEventListener('keydown', handleEscKey); // 添加关闭和确认按钮事件 function closeModal() { const modal = document.getElementById('custom-tutorial-modal'); if (modal) { modal.style.opacity = '0'; modal.style.transition = 'opacity 0.3s'; setTimeout(() => { if (modal && modal.parentNode) { modal.parentNode.removeChild(modal); } // 移除样式以避免污染页面 if (style && style.parentNode) { style.parentNode.removeChild(style); } }, 300); } document.removeEventListener('keydown', handleEscKey); } // 绑定关闭按钮事件 const closeButton = document.getElementById('close-tutorial-button'); if (closeButton) { closeButton.addEventListener('click', closeModal); } const okButton = document.getElementById('ok-tutorial-button'); if (okButton) { okButton.addEventListener('click', closeModal); } // 点击背景关闭模态框 const modal = document.getElementById('custom-tutorial-modal'); if (modal) { modal.addEventListener('click', function(e) { if (e.target === this) { closeModal(); } }); } // 记录日志 log('显示使用教程'); } catch (error) { console.error('打开教程模态框出错:', error); alert('打开使用教程失败,请刷新页面后重试。\n错误信息: ' + error.message); } }, // 优化拖动处理 initDragEvents() { const box = document.querySelector('.rlBox'); const dragZone = document.querySelector('.ant-card-head'); let startX, startY, initialMouseX, initialMouseY; dragZone.addEventListener('mousedown', (e) => { if (this.close) return; // 小化时禁止拖动 e.preventDefault(); this.isDragging = true; const rect = box.getBoundingClientRect(); startX = rect.left; startY = rect.top; initialMouseX = e.clientX; initialMouseY = e.clientY; box.style.transition = 'none'; document.body.style.userSelect = 'none'; }); document.addEventListener('mousemove', (e) => { if (!this.isDragging) return; const dx = e.clientX - initialMouseX; const dy = e.clientY - initialMouseY; let newX = startX + dx; let newY = startY + dy; box.style.left = `${newX}px`; box.style.top = `${newY}px`; box.style.right = 'auto'; // 实时检查位置 this.checkPosition(); }); document.addEventListener('mouseup', () => { if (this.isDragging) { this.isDragging = false; box.style.transition = 'all 0.2s'; document.body.style.userSelect = ''; } }); // 添加摸支持 dragZone.addEventListener('touchstart', (e) => { const touch = e.touches[0]; const rect = box.getBoundingClientRect(); startX = rect.left; startY = rect.top; initialMouseX = touch.clientX; initialMouseY = touch.clientY; this.isDragging = true; }); document.addEventListener('touchmove', (e) => { if (!this.isDragging) return; e.preventDefault(); const touch = e.touches[0]; const dx = touch.clientX - initialMouseX; const dy = touch.clientY - initialMouseY; let newX = startX + dx; let newY = startY + dy; const maxX = window.innerWidth - box.offsetWidth; const maxY = window.innerHeight - box.offsetHeight; newX = Math.min(Math.max(0, newX), maxX); newY = Math.min(Math.max(0, newY), maxY); box.style.left = `${newX}px`; box.style.top = `${newY}px`; }); document.addEventListener('touchend', () => { this.isDragging = false; }); }, // 添加新方法 saveLearningProgress() { try { const currentChapter = document.querySelector('.nav-text.current'); const currentContent = document.querySelector('.tab-active .tab-inner'); if (currentChapter && this.autoLearning) { // 获取当前页面URL和章节信息 const currentUrl = window.location.href; const unitId = currentChapter.getAttribute('unitid'); const chapterName = currentChapter.querySelector('.node-text')?.textContent.trim() || '未知章节'; const progress = { isLearning: this.autoLearning, currentChapter: unitId, currentContent: currentContent ? currentContent.getAttribute('itemid') : null, currentContentName: currentContent ? currentContent.getAttribute('itemname') : null, timestamp: Date.now(), chapterName: chapterName, currentUrl: currentUrl, isPageSwitching: false }; localStorage.setItem('learningProgress', JSON.stringify(progress)); setting.learningProgress = progress; log(`学习进度已保存: ${chapterName}`); } } catch (error) { console.log('保存学习进度时出错:', error); } }, checkLearningProgress() { try { const savedProgress = localStorage.getItem('learningProgress'); if (savedProgress) { const progress = JSON.parse(savedProgress); const timeDiff = Date.now() - progress.timestamp; // 如果保存时间在30分钟内且正在学习 if (timeDiff < 30 * 60 * 1000 && progress.isLearning) { setting.learningProgress = progress; log('检测到未完成的学习进度,准备恢复...'); // 设置自动学习状态 this.autoLearning = true; // 等待页面完全加载 const waitForLoad = () => { // 检查必要的DOM元素是否存在 const currentChapter = document.querySelector('.nav-text.current'); const tabInner = document.querySelector('.tab-inner'); if (!currentChapter || !tabInner) { log('等待页面加载...'); setTimeout(waitForLoad, 1000); return; } // 获取当前章节信息 const chapterName = currentChapter.querySelector('.node-text')?.textContent.trim(); log(`恢复学习章节: ${chapterName || '未知章节'}`); // 确保子章节列表展开 const subNav = currentChapter.closest('.nav-item.sup-item').querySelector('.sub-nav'); if (subNav) { subNav.style.display = 'block'; } // 获取第一个内容标签 const firstTab = document.querySelector('.tab-inner'); if (firstTab) { log(`开始学习: ${firstTab.getAttribute('itemname')}`); firstTab.click(); // 延迟启动学习 setTimeout(() => { if (this.autoLearning) { log('开始自动学习...'); this.processCurrentContent(); } }, 1500); } else { log('未找到可学习的内容,请刷新页面重试'); } }; // 开始等待加载 setTimeout(waitForLoad, 2000); } else { localStorage.removeItem('learningProgress'); } } } catch (error) { console.error('检查学习进度时出错:', error); log('恢复进度失败,请刷新页面重试'); } }, restoreLearningProgress() { try { const progress = setting.learningProgress; if (!progress || !progress.isLearning) return; // 查找并点击对应章节 const chapterLink = document.querySelector(`.nav-text[unitid="${progress.currentChapter}"]`); if (chapterLink) { log('正在恢复上次的学习进度...'); chapterLink.click(); // 等待章节展开后点击对应内容 setTimeout(() => { const contentLink = document.querySelector(`.tab-inner[itemid="${progress.currentContent}"]`); if (contentLink) { contentLink.click(); // 恢复自动学习状态 setTimeout(() => { this.autoLearning = true; this.processCurrentContent(); log('学习进度已恢复,继续学习'); }, 1500); } }, 1000); } } catch (error) { console.log('恢复学习进度时出错:', error); } }, // 添加清除进度方法 clearProgress() { try { localStorage.removeItem('learningProgress'); this.autoLearning = false; log("已清除学习进度"); this.$message.success("学习进度已清除"); } catch (error) { log("清除进度失败: " + error.message); this.$message.error("清除进度失败"); } }, // 新增方法:开始学习新章节 startLearningNewChapter(chapter, chapterName) { const startLearning = () => { const subNav = chapter.querySelector('.sub-nav'); if (!subNav) { throw new Error("未找到子章节列表"); } subNav.style.display = 'block'; // 获取第一个子章节 const firstSubChapter = subNav.querySelector('.sub-nav-text'); if (!firstSubChapter) { throw new Error("未找到子章节"); } log(`点击第一个子章节...`); firstSubChapter.click(); // 等待子章节内容加载 setTimeout(() => { // 获取并点击第一个内容标签 const tabList = document.querySelectorAll('.tab-inner'); if (!tabList || tabList.length === 0) { throw new Error("未找到可学习的内容"); } const firstTab = tabList[0]; log(`开始学习: ${firstTab.getAttribute('itemname')}`); firstTab.click(); // 确保内容加载完成后开始学习 setTimeout(() => { if (this.autoLearning) { log(`开始自动学习新章节内容...`); this.processCurrentContent(); } }, 1500); }, 1000); }; // 重试机制 let retryCount = 0; const maxRetries = 3; const tryStartLearning = () => { try { startLearning(); } catch (error) { retryCount++; if (retryCount < maxRetries) { log(`启动学习失败,${maxRetries - retryCount}秒后重试...`); setTimeout(tryStartLearning, 1000); } else { this.autoLearning = false; log(`无法启动学习: ${error.message}`); this.$message.error("无法启动学习,请手动点击内容或刷新页面"); } } }; // 延迟启动,确保DOM已更新 setTimeout(tryStartLearning, 1000); }, // 添加URL变化监听器 initUrlChangeListener() { // 监听 popstate 事件 window.addEventListener('popstate', () => { this.handleUrlChange(); }); // 重写 pushState 和 replaceState 方法以捕获URL变化 const originalPushState = window.history.pushState; const originalReplaceState = window.history.replaceState; window.history.pushState = function() { originalPushState.apply(this, arguments); window.vue.handleUrlChange(); }; window.history.replaceState = function() { originalReplaceState.apply(this, arguments); window.vue.handleUrlChange(); }; }, // 处理URL变化 handleUrlChange() { // 检查是否是章节页面 if (location.pathname.includes('/study/unit/')) { // 获取当前章节ID const currentUnitId = location.pathname.match(/\/unit\/(\d+)/)?.[1]; if (currentUnitId) { // 检查是否有保存的进度 const savedProgress = localStorage.getItem('learningProgress'); if (savedProgress) { const progress = JSON.parse(savedProgress); // 如果标记为页面正在切换状态,无论章节ID是否一致,都重新启动学习 if (progress.isLearning) { if (progress.isPageSwitching || progress.currentChapter === currentUnitId) { log('检测到章节切换或URL变化,准备继续学习...'); // 如果是切换页面状态,先更新进度状态,取消切换标记 if (progress.isPageSwitching) { progress.isPageSwitching = false; progress.currentChapter = currentUnitId; localStorage.setItem('learningProgress', JSON.stringify(progress)); setting.learningProgress = progress; } // 设置自动学习状态并启动学习 this.autoLearning = true; setTimeout(() => { this.startLearningCurrentChapter(); }, 2000); // 等待页面完全加载 } } } } } }, // 开始学习当前章节 startLearningCurrentChapter() { try { // 检查页面是否已加载完成 if (document.readyState !== 'complete') { log("页面正在加载中,等待页面加载完成..."); setTimeout(() => { if (this.autoLearning) { this.startLearningCurrentChapter(); } }, 1000); return; } // 获取当前章节 const currentChapter = document.querySelector('.nav-text.current')?.closest('.nav-item.sup-item'); if (!currentChapter) { log("未找到当前章节,等待DOM加载..."); setTimeout(() => { if (this.autoLearning) { this.startLearningCurrentChapter(); } }, 2000); return; } const chapterName = currentChapter.querySelector('.node-text')?.textContent.trim(); log(`准备学习章节: ${chapterName || '未知章节'}`); // 展开子章节列表并开始学习 setTimeout(() => { this.startLearningNewChapter(currentChapter, chapterName); }, 1000); // 额外等待1秒确保DOM状态稳定 } catch (error) { log(`启动学习失败: ${error.message}`); // 延迟重试 setTimeout(() => { if (this.autoLearning) { this.startLearningCurrentChapter(); } }, 2000); } }, } }); } // 自动阅读文档功能 function autoReadDocument() { // 获取当前文档总页数 const totalPages = parseInt(document.querySelector('.flexpaper_lblTotalPages')?.textContent.replace('/ ', '')) || 0; if(totalPages === 0) { log("未检测到文档页数,请确保文档已加载"); return; } // 计算阅读时间和翻页间隔 let totalReadingTime, pageInterval; if(totalPages < 10) { // 小于10页文档,使用固定1秒1页的速度 totalReadingTime = totalPages; pageInterval = 1000; // 1秒1页 } else if(totalPages > 50) { // 超过50页文档,固定30秒完成 totalReadingTime = 30; pageInterval = Math.max(totalReadingTime / totalPages * 1000, 300); // 每页间隔时间(毫秒),最小300ms } else { // 10-50页文档使用原有逻辑 totalReadingTime = totalPages > 20 ? 20 : 10; // 超过20页固定20秒,否则10秒 pageInterval = Math.max(totalReadingTime / totalPages * 1000, 300); // 每页间隔时间(毫秒),最小300ms } log(`开始自动阅读文档,共${totalPages}页,预计用时${totalReadingTime}秒,翻页间隔${Math.round(pageInterval)}毫秒`); let currentPage = 1; const readInterval = setInterval(() => { if(currentPage > totalPages) { clearInterval(readInterval); log("文档阅读完成,准备切换到下一个内容"); switchToNextContent(); return; } // 模拟翻页 const nextButton = document.querySelector('.flexpaper_bttnPrevNext'); if(nextButton) { nextButton.click(); log(`正在阅读第${currentPage}页,共${totalPages}页`); currentPage++; } }, pageInterval); // 使用计算出的翻页间隔 } // 切换到下一个内容 function switchToNextContent() { // 获取所有标签页 const tabs = document.querySelectorAll('.tab-inner'); let currentTabIndex = -1; // 找到当前激活的标签页 tabs.forEach((tab, index) => { if(tab.parentElement.classList.contains('tab-active')) { currentTabIndex = index; } }); // 如果还有下一个标签页,则切换 if(currentTabIndex < tabs.length - 1) { const nextTab = tabs[currentTabIndex + 1]; log(`切换到下一个内容: ${nextTab.getAttribute('itemname')}`); // 根据内容类型执行不同操作 const itemType = nextTab.getAttribute('itemtype'); nextTab.click(); setTimeout(() => { if(itemType === '10') { // 视频类型 passVideo(); } else if(itemType === '20') { // 文档类型 autoReadDocument(); } }, 1500); } else { log("已完成所有内容学习"); } } // 初始化获取答案,延迟5秒防止流程崩溃 function initGetAnswer(settings){ var url = location.origin + settings.url; var data = settings.data.replace(/(testPaperId=).*?(&)/,'$1' + '1250' + '$2'); console.log("=====") console.log(url,'url') console.log(data) getAnswer(url,data); } // 添加答案检查函数 function checkAnswers() { return new Promise((resolve) => { let checkCount = 0; const maxChecks = 30; // 最多等待30秒 function check() { if (setting.datas && setting.datas.length > 0) { log("答案已获取完成,开始答题..."); resolve(true); } else if (checkCount >= maxChecks) { log("等待答案超时,请确保答案已正确获取"); resolve(false); } else { checkCount++; log(`等待答案获取中...(${checkCount}/${maxChecks})`); setTimeout(check, 1000); } } check(); }); } // 添加答案查找函数 function findAnswerForQuestion(questionText) { if (!setting.datas || setting.datas.length === 0) { return null; } // 清理题目文本 const cleanedQuestionText = cleanHtmlTags(questionText).trim(); // 在答案列表中查找匹配的题目 for (const answerData of setting.datas) { const cleanedAnswerQuestion = cleanHtmlTags(answerData.question).trim(); // 检查题目是否匹配 if (cleanedQuestionText.includes(cleanedAnswerQuestion) || cleanedAnswerQuestion.includes(cleanedQuestionText)) { return answerData.answer; } } return null; } // 修改自动答题函数 async function autoAnswerQuestions() { log("准备自动答题..."); // 获取所有题目 const questions = document.querySelectorAll('.view-test'); if(!questions || questions.length === 0) { log("未找到题目,请确认是否在答题页面"); return; } log(`共发现 ${questions.length} 道题目`); let answeredCount = 0; // 遍历每个题目 for (let index = 0; index < questions.length; index++) { const question = questions[index]; try { // 获取题目类型 const isMultiChoice = question.querySelector('.t-option .input-c') !== null; // 多选 const isSingleChoice = question.querySelector('.t-option .input-r') !== null; // 单选 const isFillBlank = question.querySelector('.fillblank') !== null; // 填空 // 获取题目文本 const questionText = question.querySelector('.test-text-tutami')?.textContent.trim(); if(!questionText) { log(`第 ${index + 1} 题未找到题目文本`); continue; } // 查找答案 const answer = findAnswerForQuestion(questionText); if(!answer) { log(`第 ${index + 1} 题未找到匹配的答案`); continue; } log(`正在答第 ${index + 1} 题...`); // 根据题型填写答案 if(isMultiChoice) { // 处理多选题 const answerLetters = answer.split('\n').map(a => a.trim().charAt(0)); const options = question.querySelectorAll('.t-option'); options.forEach((option, idx) => { const letter = String.fromCharCode(65 + idx); if(answerLetters.includes(letter)) { const checkbox = option.querySelector('.input-c'); if(checkbox && !checkbox.classList.contains('selected')) { checkbox.click(); log(` - 选择了选项 ${letter}`); } } }); answeredCount++; } else if(isSingleChoice) { // 处理单选题 const answerLetter = answer.trim().charAt(0); const options = question.querySelectorAll('.t-option'); options.forEach((option, idx) => { const letter = String.fromCharCode(65 + idx); if(letter === answerLetter) { const radio = option.querySelector('.input-r'); if(radio && !radio.classList.contains('selected')) { radio.click(); log(` - 选择了选项 ${letter}`); } } }); answeredCount++; } else if(isFillBlank) { // 处理填空题 const input = question.querySelector('.fillblank'); if(input) { const fillAnswer = answer.replace(/^.*?\./, '').trim(); input.value = fillAnswer; input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); log(` - 填写答案: ${fillAnswer}`); answeredCount++; } } } catch(error) { log(`第 ${index + 1} 题答题出错: ${error.message}`); } } log(`自动答题完成!成功答题 ${answeredCount} 道题目`); // 如果启用了自动提交 if(setting.autoSubmit) { const submitBtn = document.querySelector('#submit_exam'); if(submitBtn) { log("准备自动提交答案..."); setTimeout(() => { submitBtn.click(); // 监听确认对话框并自动点击"坚持提交" log("等待确认对话框..."); const checkConfirmDialog = setInterval(() => { const confirmBtn = document.querySelector('.d-button.d-state-highlight[value="坚持提交"]'); if(confirmBtn) { clearInterval(checkConfirmDialog); log("检测到确认对话框,自动点击'坚持提交'"); setTimeout(() => { confirmBtn.click(); // 监听页面变化,等待返回测验列表页 log("等待返回测验列表页..."); setTimeout(() => { if(window.vue && window.vue.autoLearning) { log("检测自动学习状态,准备继续下一内容..."); setTimeout(() => { // 尝试获取页面类型 const pageType = checkPageType(); // 如果已经回到测验页面,继续学习 if(pageType === 'quiz') { window.vue.switchToNextContent(); } }, 2000); } }, 3000); }, 500); } }, 500); // 5秒后清除定时器,避免无限循环 setTimeout(() => { clearInterval(checkConfirmDialog); }, 5000); }, 1000); } else { log("未找到提交按钮,请手动提交"); } } else { if(window.vue) { window.vue.$message.info('答题完成,请检查后手动提交'); } } } // 添加自动答题按钮 function addAutoAnswerButton() { const btnArea = document.querySelector('.practice-action'); if(!btnArea) return; const btn = document.createElement('a'); btn.innerText = '自动答题'; btn.className = 'btn-public btn-min'; btn.href = 'javascript:void(0)'; btn.onclick = autoAnswerQuestions; btnArea.appendChild(btn); log("已添加自动答题按钮"); } // 初始化 function initAutoAnswer() { if(location.href.includes('/examSubmit/')) { log("检测到答题页面"); setTimeout(addAutoAnswerButton, 2000); } } initAutoAnswer(); // 脚本入口 initView(); //监听跳过视频按钮 $('#rl_passVideo').click(function(){passVideo();}); //监听url访问,当访问了加载题目的url时,将获取答案 $(document).ready(function(){ $(document).ajaxComplete(function (evt, request, settings) { if(settings.url.search('getExamPaper') != -1){ setting.logs.unshift("您已打开作业界面,5秒后将为您获取答案") setTimeout(initGetAnswer,5000, settings); } }); }) // 获取验证时间的函数 function getValidTime() { try { const data = localStorage.getItem('scriptValidUntil'); if (!data) return null; const { time, hash } = JSON.parse(data); if (!time || !hash) return null; // 验证哈希值 if (hash !== CryptoJS.SHA256(time.toString()).toString()) { localStorage.removeItem('scriptValidUntil'); return null; } return time; } catch (e) { console.error('获取验证时间出错:', e); localStorage.removeItem('scriptValidUntil'); return null; } } // 在界面中添加自动学习按钮 function addAutoLearnButton() { const buttonGroup = document.querySelector('.ant-btn-group'); if(!buttonGroup) return; const autoLearnBtn = document.createElement('button'); autoLearnBtn.className = 'ant-btn ant-btn-primary'; autoLearnBtn.style.width = '25%'; autoLearnBtn.innerHTML = '<span>自动学习</span>'; autoLearnBtn.onclick = () => window.vue.startAutoLearning(); buttonGroup.appendChild(autoLearnBtn); } // 添加自动答题相关功能 function initAutoExam() { // 先检查页面类型 const pageType = checkPageType(); // 如果不是测验页面,直接返回 if(pageType !== 'quiz') { return; } log("检测到测验页面,初始化答题功能..."); // 添加配置按钮到界面 const configBtn = document.createElement('button'); configBtn.className = 'ant-btn ant-btn-primary'; configBtn.innerHTML = ` <span>答题设置</span> `; configBtn.onclick = showExamConfig; // 找到按钮区域并添加配置按钮 const btnArea = document.querySelector('.ant-btn-group'); if(btnArea) { btnArea.appendChild(configBtn); log("已添加答题设置按钮"); } // 自动处理测验 handleQuiz(); } // 显示答题配置对话框 function showExamConfig() { const configHtml = ` <div class="exam-config" style="padding: 20px;"> <h3>答题设置</h3> <div style="margin: 10px 0;"> <label> <input type="checkbox" ${setting.autoSubmit ? 'checked' : ''} id="autoSubmit"> 自动提交答案 </label> </div> <div style="margin: 10px 0;"> <label> <input type="checkbox" ${setting.autoEnterExam ? 'checked' : ''} id="autoEnterExam"> 自动进入考试 </label> </div> <div style="margin-top: 20px;"> <button class="ant-btn ant-btn-primary" onclick="saveExamConfig()">保存设置</button> </div> </div> `; // 使用 ant-design-vue 的对话框显示配置 if(window.vue) { window.vue.$info({ title: '答题设置', content: configHtml, width: 400 }); } } // 保存答题配置 window.saveExamConfig = function() { setting.autoSubmit = document.getElementById('autoSubmit').checked; setting.autoEnterExam = document.getElementById('autoEnterExam').checked; // 保存到localStorage localStorage.setItem('examConfig', JSON.stringify({ autoSubmit: setting.autoSubmit, autoEnterExam: setting.autoEnterExam })); if(window.vue) { window.vue.$message.success('设置已保存'); } } // 加载保存的配置 function loadExamConfig() { try { const savedConfig = localStorage.getItem('examConfig'); if(savedConfig) { const config = JSON.parse(savedConfig); // 使用默认值作为后备 setting.autoSubmit = config.autoSubmit !== undefined ? config.autoSubmit : false; setting.autoEnterExam = config.autoEnterExam !== undefined ? config.autoEnterExam : true; log("已加载答题设置"); } else { // 如果没有保存的配置,设置默认值 setting.autoSubmit = false; setting.autoEnterExam = true; // 保存默认设置 localStorage.setItem('examConfig', JSON.stringify({ autoSubmit: false, autoEnterExam: true })); log("已设置默认答题选项"); } } catch(e) { console.error('加载答题配置失败:', e); // 出错时使用默认值 setting.autoSubmit = false; setting.autoEnterExam = true; } } // 修改自动答题函数 function autoAnswerQuestions() { log("开始自动答题..."); // 获取所有题目 const questions = document.querySelectorAll('.view-test'); if(!questions || questions.length === 0) { log("未找到题目,请确认是否在答题页面"); return; } log(`共发现 ${questions.length} 道题目`); let answeredCount = 0; questions.forEach((question, index) => { try { // 获取题目类型 const isMultiChoice = question.querySelector('.t-option .input-c') !== null; // 多选 const isSingleChoice = question.querySelector('.t-option .input-r') !== null; // 单选 const isFillBlank = question.querySelector('.fillblank') !== null; // 填空 // 获取题目文本用于匹配答案 const questionText = question.querySelector('.test-text-tutami')?.textContent.trim(); if(!questionText) { log(`第 ${index + 1} 题未找到题目文本`); return; } // 在答案列表中查找匹配的答案 const answer = findAnswerForQuestion(questionText); if(!answer) { log(`第 ${index + 1} 题未找到匹配的答案`); return; } // 根据题型填写答案 if(isMultiChoice) { // 处理多选题 const answerLetters = answer.split('\n').map(a => a.trim().charAt(0)); const options = question.querySelectorAll('.t-option'); options.forEach((option, idx) => { const letter = String.fromCharCode(65 + idx); if(answerLetters.includes(letter)) { const checkbox = option.querySelector('.input-c'); if(checkbox && !checkbox.classList.contains('selected')) { checkbox.click(); } } }); answeredCount++; } else if(isSingleChoice) { // 处理单选题 const answerLetter = answer.trim().charAt(0); const options = question.querySelectorAll('.t-option'); options.forEach((option, idx) => { const letter = String.fromCharCode(65 + idx); if(letter === answerLetter) { const radio = option.querySelector('.input-r'); if(radio && !radio.classList.contains('selected')) { radio.click(); } } }); answeredCount++; } else if(isFillBlank) { // 处理填空题 const input = question.querySelector('.fillblank'); if(input) { input.value = answer.replace(/^.*?\./, '').trim(); // 触发input事件 input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); answeredCount++; } } } catch(error) { log(`第 ${index + 1} 题答题出错: ${error.message}`); } }); log(`自动答题完成!成功答题 ${answeredCount} 道题目`); // 如果启用了自动提交 if(setting.autoSubmit) { const submitBtn = document.querySelector('#submit_exam'); if(submitBtn) { log("准备自动提交答案..."); setTimeout(() => { submitBtn.click(); // 监听确认对话框并自动点击"坚持提交" log("等待确认对话框..."); const checkConfirmDialog = setInterval(() => { const confirmBtn = document.querySelector('.d-button.d-state-highlight[value="坚持提交"]'); if(confirmBtn) { clearInterval(checkConfirmDialog); log("检测到确认对话框,自动点击'坚持提交'"); setTimeout(() => { confirmBtn.click(); // 监听页面变化,等待返回测验列表页 log("等待返回测验列表页..."); setTimeout(() => { if(window.vue && window.vue.autoLearning) { log("检测自动学习状态,准备继续下一内容..."); setTimeout(() => { // 尝试获取页面类型 const pageType = checkPageType(); // 如果已经回到测验页面,继续学习 if(pageType === 'quiz') { window.vue.switchToNextContent(); } }, 2000); } }, 3000); }, 500); } }, 500); // 5秒后清除定时器,避免无限循环 setTimeout(() => { clearInterval(checkConfirmDialog); }, 5000); }, 1000); } else { log("未找到提交按钮,请手动提交"); } } else { if(window.vue) { window.vue.$message.info('答题完成,请检查后手动提交'); } } } // 在页面加载完成后初始化 document.addEventListener('DOMContentLoaded', () => { loadExamConfig(); initAutoExam(); initPageChangeListener(); }); // 修改页面类型识别逻辑 function checkPageType() { // 获取当前打开的标签页信息 const activeTab = document.querySelector('.tab-active .tab-inner'); if (!activeTab) { return null; } // 获取页面信息 const itemType = activeTab.getAttribute('itemtype'); const itemName = activeTab.getAttribute('itemname') || ''; // 测验页面的多重判断条件 const isQuiz = ( // 1. 通过标题关键词判断 itemName.includes('测试题') || itemName.includes('测验题') || itemName.includes('练习题') || // 2. 通过页面元素判断 document.querySelector('.h-commit-tip') !== null || document.querySelector('.exam_submit_score') !== null || document.querySelector('.enter_exam') !== null || document.querySelector('.doObjExam') !== null || // 3. 通过URL判断 location.href.includes('/examSubmit/') || // 4. 通过图标判断 activeTab.querySelector('.icon-edit02') !== null || // 5. 通过itemType判断 itemType === '50' || // 6. 通过页面结构判断 document.querySelector('.h-result-table') !== null || // 7. 通过提交次数信息判断 document.querySelector('.h-commit-info') !== null || // 8. 通过测验成绩表格判断(测验完成后的页面) document.querySelector('.exam-record-table') !== null ); if (isQuiz) { // 检测是否为测验完成后的结果页面 const isResultPage = ( document.querySelector('.exam-record-table') !== null || document.querySelector('.h-result-table') !== null || (document.querySelector('.h-commit-tip') && document.querySelector('.h-commit-tip').textContent.includes('已提交')) ); if (isResultPage) { log("识别到测验结果页面,准备继续学习"); // 如果是自动学习模式,则继续到下一内容 if (window.vue && window.vue.autoLearning) { setTimeout(() => window.vue.switchToNextContent(), 1500); } } // 输出详细的识别信息 log(`识别到测验页面:`); log(`- 标题: ${itemName}`); log(`- 类型: ${itemType}`); if (document.querySelector('.h-commit-tip')) { const submitInfo = document.querySelector('.h-commit-tip').textContent; log(`- 提交信息: ${submitInfo.trim()}`); } return 'quiz'; } // 其他页面类型判断 if (itemType === '10') { return 'video'; } else if (itemType === '20') { return 'document'; } return null; } // 修改handleQuiz函数中的答题逻辑 function handleQuiz() { log("检测到测验页面,准备处理..."); // 检测是否是测验结果页面 const isResultPage = ( document.querySelector('.exam-record-table') !== null || document.querySelector('.h-result-table') !== null || (document.querySelector('.h-commit-tip') && document.querySelector('.h-commit-tip').textContent.includes('已提交')) ); if (isResultPage) { log("检测到测验已完成,准备继续下一个学习内容"); if (window.vue && window.vue.autoLearning) { setTimeout(() => window.vue.switchToNextContent(), 1500); } return; } // 获取测验信息 const quizInfo = { title: document.querySelector('.tab-active .tab-inner')?.getAttribute('itemname') || '未知测验', submitLimit: document.querySelector('.h-commit-info')?.textContent.match(/可提交次数:(\d+)次/) || ['', '未知'], timeLimit: document.querySelector('.h-commit-info')?.textContent.includes('不限时') ? '不限时' : document.querySelector('.h-commit-info')?.textContent.match(/限时:(\d+)分钟/) || ['', '未知'] }; log(`测验信息:`); log(`- 标题: ${quizInfo.title}`); log(`- 提交次数限制: ${quizInfo.submitLimit[1]}次`); log(`- 时间限制: ${quizInfo.timeLimit === '不限时' ? '不限时' : quizInfo.timeLimit[1] + '分钟'}`); // 检查是否有"继续"按钮(未完成的测验) const enterBtn = document.querySelector('a.link-action.enter_exam'); if(enterBtn) { const examId = enterBtn.id || ''; log(`发现未完成的测验 (ID: ${examId}),准备继续答题...`); // 确保自动进入答题功能已启用 if(setting.autoEnterExam) { log("自动进入答题已启用,即将进入答题页面..."); // 模拟点击事件 try { // 先尝试直接触发点击事件 enterBtn.click(); log("已触发进入答题页面"); // 等待页面加载和答案获取 waitForAnswersAndQuestions(); } catch(error) { log(`进入答题页面时出错: ${error.message}`); log("请尝试手动点击'继续'按钮"); } } else { log("自动进入答题已禁用,请手动点击'继续'按钮"); } return; } // 检查是否已在答题页面 const questions = document.querySelectorAll('.view-test'); if(questions && questions.length > 0) { log(`已在答题页面,等待答案获取...`); waitForAnswersAndQuestions(); return; } // 检查是否有开始答题按钮 const startBtn = document.querySelector('.doObjExam'); if(startBtn) { log("发现新测验,准备开始答题..."); if(setting.autoEnterExam) { setTimeout(() => { startBtn.click(); log("已自动开始答题"); // 等待页面加载和答案获取 waitForAnswersAndQuestions(); }, 1000); } else { log("自动进入答题已禁用,请手动点击开始按钮"); } return; } log("等待页面加载完成..."); } // 修改等待答案和题目加载的函数 function waitForAnswersAndQuestions() { let checkCount = 0; const maxChecks = 30; // 最多等待30秒 function check() { // 检查题目是否加载 const questions = document.querySelectorAll('.view-test'); if (!questions || questions.length === 0) { if (checkCount >= maxChecks) { log("等待题目加载超时,请刷新页面重试"); return; } checkCount++; setTimeout(check, 1000); return; } // 检查答案是否已获取 if (!setting.datas || setting.datas.length === 0) { if (checkCount >= maxChecks) { log("等待答案获取超时,请确保答案已正确获取"); return; } checkCount++; setTimeout(check, 1000); return; } // 检查答案匹配情况 checkAnswerMatching(questions).then(matchResult => { if (matchResult.success) { log("答案匹配检查完成:"); log(`- 题目总数:${matchResult.totalQuestions}道`); log(`- 成功匹配:${matchResult.matchedCount}道`); if (matchResult.unmatchedQuestions.length > 0) { log(`- 未匹配题目:${matchResult.unmatchedQuestions.join(', ')}题`); } if (matchResult.matchedCount > 0) { // 开始自动答题 setTimeout(() => { autoAnswerQuestions(); }, 1000); } else { log("没有找到任何可匹配的答案,请检查答案是否正确"); } } else { log("答案匹配检查失败,请刷新页面重试"); } }); } // 开始检查 check(); } // 添加答案匹配检查函数 async function checkAnswerMatching(questions) { try { const result = { success: true, totalQuestions: questions.length, matchedCount: 0, unmatchedQuestions: [] }; // 遍历所有题目进行检查 for (let i = 0; i < questions.length; i++) { const question = questions[i]; const questionText = question.querySelector('.test-text-tutami')?.textContent.trim(); if (!questionText) { result.unmatchedQuestions.push(i + 1); continue; } // 清理题目文本 const cleanedQuestionText = cleanHtmlTags(questionText).trim(); let matched = false; // 在答案列表中查找匹配的题目 for (const answerData of setting.datas) { const cleanedAnswerQuestion = cleanHtmlTags(answerData.question).trim(); // 检查题目是否匹配 if (cleanedQuestionText.includes(cleanedAnswerQuestion) || cleanedAnswerQuestion.includes(cleanedQuestionText)) { result.matchedCount++; matched = true; break; } } if (!matched) { result.unmatchedQuestions.push(i + 1); } } return result; } catch (error) { return { success: false, error: error.message }; } } // 修改handleQuiz函数中的日志部分 function handleQuiz() { log("检测到测验页面,准备处理..."); // 获取测验信息 const quizInfo = { title: document.querySelector('.tab-active .tab-inner')?.getAttribute('itemname') || '未知测验', submitLimit: document.querySelector('.h-commit-info')?.textContent.match(/可提交次数:(\d+)次/) || ['', '未知'], timeLimit: document.querySelector('.h-commit-info')?.textContent.includes('不限时') ? '不限时' : document.querySelector('.h-commit-info')?.textContent.match(/限时:(\d+)分钟/) || ['', '未知'] }; // 检查是否有"继续"按钮(未完成的测验) const enterBtn = document.querySelector('a.link-action.enter_exam'); if(enterBtn) { const examId = enterBtn.id || ''; log(`准备继续答题 (ID: ${examId})`); if(setting.autoEnterExam) { try { enterBtn.click(); waitForAnswersAndQuestions(); } catch(error) { log(`进入答题页面时出错: ${error.message}`); log("请尝试手动点击'继续'按钮"); } } else { log("自动进入答题已禁用,请手动点击'继续'按钮"); } return; } // 检查是否已在答题页面 const questions = document.querySelectorAll('.view-test'); if(questions && questions.length > 0) { waitForAnswersAndQuestions(); return; } // 检查是否有开始答题按钮 const startBtn = document.querySelector('.doObjExam'); if(startBtn) { if(setting.autoEnterExam) { setTimeout(() => { startBtn.click(); waitForAnswersAndQuestions(); }, 1000); } else { log("自动进入答题已禁用,请手动点击开始按钮"); } return; } } // 添加页面变化监听 function initPageChangeListener() { // 监听标签页切换 const tabContainer = document.querySelector('.tabs'); if(tabContainer) { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if(mutation.type === 'attributes' && mutation.attributeName === 'class') { const pageType = checkPageType(); if(pageType === 'quiz') { log("检测到切换到测验页面"); handleQuiz(); } } }); }); observer.observe(tabContainer, { attributes: true, subtree: true, attributeFilter: ['class'] }); } } // 添加全局视频静音功能 function initGlobalMute() { log("初始化全局视频静音功能..."); // 自动静音所有HTML5视频元素,处理已存在的视频 function muteAllVideos() { const videos = document.getElementsByTagName('video'); if (videos.length > 0) { for (let i = 0; i < videos.length; i++) { videos[i].muted = true; log(`已自动静音第${i+1}个HTML5视频`); } } // 尝试使用JWPlayer API静音 try { if (typeof jwplayer === 'function' && jwplayer("mediaplayer")) { jwplayer("mediaplayer").setMute(true); log("已通过JWPlayer API静音视频"); } } catch (error) { log("JWPlayer静音尝试失败,等待视频加载: " + error.message); } } // 初始执行一次静音 muteAllVideos(); // 监听视频DOM变化,对新加载的视频执行静音 const videoObserver = new MutationObserver((mutations) => { mutations.forEach(mutation => { if (mutation.addedNodes && mutation.addedNodes.length > 0) { // 检查新增节点中是否有视频 for (let i = 0; i < mutation.addedNodes.length; i++) { const node = mutation.addedNodes[i]; // 如果节点本身是视频 if (node.nodeName && node.nodeName.toLowerCase() === 'video') { node.muted = true; log("检测到新视频已加载,已自动静音"); } // 如果是容器节点,检查其中的视频 else if (node.getElementsByTagName) { const newVideos = node.getElementsByTagName('video'); if (newVideos.length > 0) { for (let j = 0; j < newVideos.length; j++) { newVideos[j].muted = true; log("检测到容器中的新视频,已自动静音"); } } } } } }); // 每次DOM变化都尝试使用JWPlayer API静音 try { if (typeof jwplayer === 'function' && jwplayer("mediaplayer")) { jwplayer("mediaplayer").setMute(true); } } catch (error) { // 忽略错误,JWPlayer可能还未加载 } }); // 观察整个文档的变化 videoObserver.observe(document.body, { childList: true, subtree: true }); // 周期性检查以确保视频保持静音状态 setInterval(muteAllVideos, 2000); log("全局视频静音功能已初始化"); }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址