您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
🚀 小雅平台课件下载利器!批量下载、排序、筛选、导出等一应俱全,还有更多功能等你发掘!
当前为
// ==UserScript== // @name 小雅爬爬爬 // @match https://*.ai-augmented.com/* // @grant none // @description 🚀 小雅平台课件下载利器!批量下载、排序、筛选、导出等一应俱全,还有更多功能等你发掘! // @license MIT // @author Yi // @version 1.2.2 // @namespace https://greasyfork.org/users/1268039 // @homepageURL https://github.com/zygame1314/XiaoyaDownloader // ==/UserScript== var isProgressBarVisible = true; var styleSheet = document.createElement('style'); styleSheet.type = 'text/css'; styleSheet.innerText = ` body, body * { font-family: 'Microsoft YaHei', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important; } #searchInput::placeholder { color: #ffa500; } .custom-checkbox { width: 20px; height: 20px; appearance: none; background-color: #fff; border: 2px solid #ffa500; border-radius: 4px; margin-right: 10px; outline: none; cursor: pointer; transition: background-color 0.3s ease, border-color 0.3s ease; } .custom-checkbox:checked { background-color: #ffa500; border-color: #ffa500; } .custom-checkbox:checked::before { content: '✓'; display: block; text-align: center; line-height: 18px; color: #fff; font-size: 16px; } .custom-checkbox:hover { border-color: #ff8c00; } .glowing-text { background: linear-gradient(90deg, #ffa500, #ff8c00, #ffa500); background-size: 200% 100%; animation: flowingGradient 3s ease infinite; -webkit-background-clip: text; -webkit-text-fill-color: transparent; } @keyframes flowingGradient { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } @keyframes slideInFade { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideOutFade { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(20px); } } .new-history-item { animation: slideInFade 0.5s ease-out forwards; } .remove-history-item { animation: slideOutFade 0.5s ease-in forwards; } @keyframes fadeIn { from { opacity: 0; transform: translate(-50%, -50%) scale(0.9); } to { opacity: 1; transform: translate(-50%, -50%) scale(1); } } @keyframes fadeOut { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.9); } } .popup-show { animation: fadeIn 0.3s ease-out forwards; } .popup-hide { animation: fadeOut 0.3s ease-in forwards; } @keyframes glowPulse { 0% { box-shadow: 0 0 5px #fcbb34; } 50% { box-shadow: 0 0 15px #fcbb34, 0 0 20px #f0932b; /* 在中间加强发光 */ } 100% { box-shadow: 0 0 5px #fcbb34; } } #teacherInfoContainer, #userSearchContainer { .title { color: #333; border-bottom: 2px solid #fcbb34; padding-bottom: 10px; margin-bottom: 15px; } .teacher-list, .user-info { list-style-type: none; padding: 0; } .teacher-item, .user-info p { background-color: #fff; margin-bottom: 10px; padding: 10px; border-radius: 5px; display: flex; justify-content: space-between; align-items: center; transition: all 0.3s ease; } .teacher-item:hover, .user-info p:hover { background-color: #FFE0B2; transform: translateX(5px); } .teacher-name, .user-info strong { font-weight: bold; color: #e69b00; } .teacher-number { color: #757575; font-size: 0.9em; } .loading, .no-data { text-align: center; color: #757575; } .dot { animation: blink 1s infinite; } @keyframes blink { 0% { opacity: 0; } 50% { opacity: 1; } 100% { opacity: 0; } } .search-input { width: 200px; padding: 8px; border: 1px solid #FFD180; border-radius: 4px; margin-bottom: 10px; } .search-button { padding: 8px 12px; border: none; border-radius: 4px; background-color: #fcbb34; color: #fff; cursor: pointer; transition: background-color 0.3s ease; } .search-button:hover { background-color: #e69b00; } .search-results { margin-top: 10px; border: 1px solid #FFD180; padding: 10px; border-radius: 4px; background-color: #fff; } .search-hint { color: #757575; font-style: italic; text-align: center; margin-top: 10px; } .failed-file { background-color: #ffeeee; padding: 5px; margin: 5px 0; border-radius: 5px; } .retry-btn { margin-left: 10px; padding: 2px 5px; background-color: #f0f0f0; border: 1px solid #ccc; border-radius: 3px; cursor: pointer; } .retry-btn:hover { background-color: #e0e0e0; } .failed-file { opacity: 0; transform: translateX(20px); transition: opacity 0.3s ease, transform 0.3s ease; } } `; document.head.appendChild(styleSheet); (function() { 'use strict'; // 动态导入dotlottie-player模块 const script = document.createElement('script'); script.src = 'https://unpkg.com/@dotlottie/player-component@latest/dist/dotlottie-player.mjs'; script.type = 'module'; // 指定导入类型为module document.head.appendChild(script); // 将脚本添加到<head>中 // 监听脚本的加载事件以确认导入成功 script.onload = () => { console.log('dotlottie-player模块已导入成功!'); }; // 监听错误事件以确认导入失败 script.onerror = () => { console.error('无法导入dotlottie-player模块!'); }; })(); let course_resources; let historyListElement; let failedContainer; let failedToggleButton; let noErrorsMessage; let svgElementIds = []; let originalOrder = null; let historyPopup = null; let isEasterEggActivated = false; let downloadHistory = []; window.currentSearchKeyword = ''; window.currentFilterCategory = ''; const hostname = window.location.hostname; // 等待 iframe 加载完成 function waitForIframe(selector, callback) { const iframe = document.querySelector(selector); if (iframe && iframe.contentDocument.readyState === 'complete') { callback(iframe); // iframe 已加载,执行回调 } else { setTimeout(() => waitForIframe(selector, callback), 50); // 等待 50ms 后重试 } } // 获取 SVG 元素 ID (仅数字) function getSvgElementIds(iframe) { const iframeDocument = iframe.contentDocument; const targetSvgElement = iframeDocument.querySelector("body > svg > g"); const gElementsWithId = targetSvgElement.querySelectorAll("g[id]"); // 过滤出纯数字 ID const numericIds = Array.from(gElementsWithId) .filter(element => /^\d+$/.test(element.id)) // 正则表达式匹配纯数字 .map(element => element.id); return numericIds; } // 处理 iframe 的回调函数 function handleIframeLoad(iframe) { console.log("目标 iframe 已加载完成!"); // 跨域处理 try { svgElementIds = getSvgElementIds(iframe); // 更新外部作用域中的 svgElementIds console.log(svgElementIds); } catch (error) { console.error("无法访问 iframe 内容,可能存在跨域限制:", error); } } // 监视页面的变化 const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { for (const node of mutation.addedNodes) { if (node.tagName === 'IFRAME') { waitForIframe("#xy_app_content iframe", handleIframeLoad); } } } } }); // 开始监视 observer.observe(document.body, { childList: true, subtree: true }); // 判断是否为视频文件 function isVideoFile(mimetype, filename) { const videoExtensions = [ 'mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'webm', 'm4v', 'mpeg', 'mpg', '3gp', 'ts', 'vob', 'ogv', 'divx', 'rm', 'rmvb' ]; const extension = filename.split('.').pop().toLowerCase(); return videoExtensions.includes(extension); } function parseContent() { // 在解析课程内容前清除筛选条件 window.currentSearchKeyword = ''; window.currentFilterCategory = ''; const searchInput = document.getElementById("searchInput"); if (searchInput) { searchInput.value = ''; } const quickFilterSelect = document.getElementById("quickFilterSelect"); if (quickFilterSelect) { quickFilterSelect.selectedIndex = 0; // 选择 "全部" } var download_url = 'https://' + hostname + '/api/jx-oresource/cloud/file_url/'; var download_list = document.getElementById("download_list"); download_list.innerHTML = '<h3 style="color:#fcbb34; font-weight:bold;">课程附件清单</h3>'; failedContainer = document.createElement('div'); failedContainer.id = 'failedContainer'; failedContainer.style.cssText = ` display: none; margin-top: 10px; border: 1px solid #ffcccc; border-radius: 5px; padding: 10px; background-color: #fff5f5; max-height: 200px; overflow-y: auto; `; noErrorsMessage = document.createElement('div'); noErrorsMessage.style.cssText = ` display: none; text-align: center; padding: 10px; color: #4caf50; font-weight: bold; `; noErrorsMessage.textContent = '太棒了!没有任何错误 (≧▽≦)'; failedToggleButton = document.createElement('button'); failedToggleButton.textContent = '显示失败项 (0)'; failedToggleButton.style.cssText = ` margin-top: 10px; padding: 5px 10px; background-color: #ff9999; border: none; border-radius: 5px; color: white; cursor: pointer; font-weight: bold; transition: transform 0.3s ease; // 添加过渡效果 `; failedToggleButton.onmouseover = () => { failedToggleButton.style.transform = 'scale(1.05)'; }; failedToggleButton.onmouseout = () => { failedToggleButton.style.transform = 'scale(1)'; }; failedToggleButton.onclick = toggleFailedContainer; download_list.appendChild(failedToggleButton); download_list.appendChild(failedContainer); failedContainer.appendChild(noErrorsMessage); updateFailedCount(); const maxRetries = 3; // 最大重试次数 const retryDelay = 1000; // 重试间隔(毫秒) function addLinkWithRetry(resource, retryCount = 0) { const token = getCookie(); fetch(download_url + resource.quote_id, { headers: { Authorization: `Bearer ${token}` } }) .then(response => response.json()) .then(data => { if (data.success) { let file_url = data.data.url; let file_name = resource.name; let create_time = new Date(resource.created_at).toLocaleDateString(); var file_container = document.createElement('div'); file_container.className = 'file-item'; file_container.style.display = 'flex'; file_container.style.alignItems = 'center'; var checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.className = 'custom-checkbox'; checkbox.style.marginRight = '10px'; checkbox.setAttribute('data-visible', 'true'); var file_info = document.createElement('a'); file_info.innerHTML = file_name; file_info.href = file_url; file_info.target = "_blank"; file_info.className = 'download-link link-style'; file_info.style.textDecoration = 'none'; file_info.title = `创建日期:${create_time}`; file_info.setAttribute('data-created-at', resource.created_at); file_info.setAttribute('data-origin-name', file_name); file_info.setAttribute('data-resource-id', resource.id); file_info.setAttribute('data-path', resource.path); file_info.setAttribute('data-parent-id', resource.parent_id); file_info.draggable = true; // 设置拖拽事件 file_info.addEventListener('dragstart', (event) => { event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.setData('text/plain', file_url); event.dataTransfer.setData('text/filename', file_name); }); // 设置样式和事件监听器 applyFileLinkStyles(file_info); file_container.appendChild(checkbox); file_container.appendChild(file_info); file_container.style.borderBottom = '1px solid #eee'; file_container.style.fontWeight = "bold"; file_container.style.padding = '5px 10px'; file_container.style.justifyContent = 'flex-start'; file_container.style.alignItems = 'center'; download_list.appendChild(file_container); } else { throw new Error('Failed to fetch file URL'); } }) .catch(error => { console.error(`链接 ${resource.name} 抓取异常:`, error); if (retryCount < maxRetries) { console.log(`重试 ${resource.name} (重试次数 ${retryCount + 1}/${maxRetries})...`); setTimeout(() => addLinkWithRetry(resource, retryCount + 1), retryDelay); } else { console.error(`添加 ${resource.name} 失败!`); addFailedFileNotification(resource.name); } }); } for (let i in course_resources) { let resource = course_resources[i]; if (resource.mimetype && resource.type !== 'folder' && !isVideoFile(resource.mimetype, resource.name)) { addLinkWithRetry(resource); } } function applyFileLinkStyles(file_info) { file_info.style.display = 'inline-block'; file_info.style.padding = '5px 10px'; file_info.style.borderRadius = '5px'; file_info.style.transition = 'all 0.3s ease'; file_info.addEventListener('mouseover', () => { file_info.style.backgroundColor = '#FFD180'; file_info.style.color = 'white'; file_info.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.2)'; }); file_info.addEventListener('mouseout', () => { file_info.style.backgroundColor = 'transparent'; file_info.style.color = '#007bff'; file_info.style.boxShadow = 'none'; }); file_info.addEventListener('mousedown', () => { file_info.style.transform = 'scale(0.95)'; }); file_info.addEventListener('mouseup', () => { file_info.style.transform = 'scale(1)'; }); file_info.addEventListener('mouseover', () => { file_info.style.textDecoration = 'underline'; file_info.style.color = '#000'; file_info.style.fontWeight = "bold"; }); file_info.addEventListener('mouseout', function() { file_info.style.textDecoration = 'none'; file_info.style.color = ''; }); file_info.addEventListener('click', function(event) { event.preventDefault(); courseDownload(file_info.href, file_info.getAttribute('data-origin-name')); }); } function toggleFailedContainer() { if (failedContainer.style.display === 'none') { failedContainer.style.display = 'block'; failedToggleButton.textContent = '隐藏失败项 (' + (failedContainer.children.length - 1) + ')'; } else { failedContainer.style.display = 'none'; failedToggleButton.textContent = '显示失败项 (' + (failedContainer.children.length - 1) + ')'; } } function updateFailedCount() { const count = failedContainer.children.length - 1; failedToggleButton.textContent = (failedContainer.style.display === 'none' ? '显示' : '隐藏') + '失败项 (' + count + ')'; if (count > 0) { failedToggleButton.style.display = 'block'; noErrorsMessage.style.display = 'none'; } else { failedToggleButton.style.display = 'none'; noErrorsMessage.style.display = 'block'; } } function addFailedFileNotification(fileName) { const failedItem = document.createElement('div'); failedItem.className = 'failed-file'; failedItem.style.cssText = ` display: flex; justify-content: space-between; align-items: center; padding: 10px; margin-bottom: 10px; background-color: #fff0f0; border-left: 4px solid #ff4d4d; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); transition: all 0.3s ease; `; const fileNameSpan = document.createElement('span'); fileNameSpan.style.cssText = ` color: #ff4d4d; font-weight: bold; margin-right: 10px; `; fileNameSpan.textContent = `❌ 添加失败: ${fileName}`; const retryButton = document.createElement('button'); retryButton.className = 'retry-btn'; retryButton.textContent = '重试'; retryButton.style.cssText = ` background-color: #ff9999; color: white; border: none; padding: 6px 12px; border-radius: 20px; cursor: pointer; font-weight: bold; transition: all 0.3s ease; outline: none; box-shadow: 0 2px 4px rgba(255, 77, 77, 0.3); `; retryButton.onmouseover = () => { retryButton.style.backgroundColor = '#ff7777'; retryButton.style.boxShadow = '0 4px 8px rgba(255, 77, 77, 0.5)'; }; retryButton.onmouseout = () => { retryButton.style.backgroundColor = '#ff9999'; retryButton.style.boxShadow = '0 2px 4px rgba(255, 77, 77, 0.3)'; }; retryButton.onclick = () => { failedItem.style.opacity = '0'; failedItem.style.transform = 'translateX(20px)'; setTimeout(() => { failedItem.remove(); const resource = Object.values(course_resources).find(r => r.name === fileName); if (resource) { addLinkWithRetry(resource); } updateFailedCount(); if (failedContainer.children.length === 1) { noErrorsMessage.style.display = 'block'; } }, 300); }; failedItem.appendChild(fileNameSpan); failedItem.appendChild(retryButton); failedContainer.appendChild(failedItem); updateFailedCount(); // 添加入场动画 setTimeout(() => { failedItem.style.opacity = '1'; failedItem.style.transform = 'translateX(0)'; }, 10); } noErrorsMessage.style.display = 'none'; } let toggleButton; let downloadsContainer = document.getElementById('downloadsContainer'); if (!downloadsContainer) { // 创建进度条容器 downloadsContainer = document.createElement('div'); downloadsContainer.id = 'downloadsContainer'; // 设置容器样式 Object.assign(downloadsContainer.style, { position: 'fixed', bottom: '150px', left: '10px', right: 'auto', zIndex: '9999', backgroundColor: '#FFF3E0', boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2)', borderRadius: '8px', padding: '30px', width: 'auto', minWidth: '300px', boxSizing: 'border-box', margin: '0 auto', border: '1px solid #FFD180', transition: 'transform 0.3s ease-in-out', maxHeight: '190px', overflowY: 'auto', overflowX: 'hidden', transform: 'translateX(-90%)' }); // 添加拖拽事件监听 downloadsContainer.addEventListener('dragover', (e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; }); downloadsContainer.addEventListener('drop', (e) => { e.preventDefault(); const downloadUrl = e.dataTransfer.getData('text/plain'); const filename = e.dataTransfer.getData('text/filename'); if (downloadUrl && filename) { courseDownload(downloadUrl, filename); } }); // 创建收起/展示按钮 toggleButton = document.createElement('button'); toggleButton.title = '点击展开/收折进度条'; toggleButton.textContent = '▶'; // 设置按钮样式 Object.assign(toggleButton.style, { position: 'absolute', top: '50%', right: '8px', transform: 'translateY(-50%) rotate(0deg)', zIndex: '10000', border: 'none', boxShadow: '0 2px 4px 0 rgba(0, 0, 0, 0.24), 0 4px 5px 0 rgba(0, 0, 0, 0.19)', background: 'linear-gradient(45deg, #FFC107, #FF9800)', borderRadius: '8px', padding: '4px 8px', paddingRight: '2px', paddingLeft: '2px', color: 'white', fontSize: '12px', cursor: 'pointer', transition: 'background-color 0.3s, box-shadow 0.3s, transform 0.3s', textShadow: '0 0 0px transparent' }); // 按钮悬停效果 toggleButton.onmouseover = () => { Object.assign(toggleButton.style, { background: 'linear-gradient(45deg, #FFEB3B, #FFC107)', boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.24), 0 6px 10px 0 rgba(0, 0, 0, 0.19)', transform: 'translateY(-60%) rotate(0deg)' }); }; toggleButton.onmouseout = () => { Object.assign(toggleButton.style, { background: 'linear-gradient(45deg, #FFC107, #FF9800)', boxShadow: '0 2px 4px 0 rgba(0, 0, 0, 0.24), 0 4px 5px 0 rgba(0, 0, 0, 0.19)', transform: 'translateY(-50%) rotate(0deg)' }); }; let isCollapsed = true; // 按钮点击事件 toggleButton.onclick = () => { isCollapsed = !isCollapsed; downloadsContainer.style.transform = isCollapsed ? 'translateX(-90%)' : 'translateX(0)'; toggleButton.textContent = isCollapsed ? '▶' : '◀'; toggleButton.style.transform = 'translateY(-50%) rotate(360deg)'; }; downloadsContainer.appendChild(toggleButton); // 创建 dotlottie-player 容器 let lottieContainer = document.createElement('div'); Object.assign(lottieContainer.style, { position: 'absolute', top: '0', left: '0', width: '100%', height: '100%', zIndex: '-1' }); lottieContainer.innerHTML = ` <dotlottie-player src="https://lottie.host/0500ecdb-7f3b-4f73-ab09-155a70f85ce3/ZCLltVc7A4.json" background="transparent" speed="1" style="width: 100%; height: 100%;" loop autoplay> </dotlottie-player>`; downloadsContainer.prepend(lottieContainer); } function addDownloadsContainer() { document.body.appendChild(downloadsContainer); } function updateContainerPosition() { let windowHeight = window.innerHeight; let downloadsContainerHeight = downloadsContainer.offsetHeight; let currentTop = downloadsContainer.getBoundingClientRect().top; if (currentTop + downloadsContainerHeight > windowHeight) { let newTop = windowHeight - downloadsContainerHeight - 10; downloadsContainer.style.top = `${newTop}px`; downloadsContainer.style.bottom = 'auto'; } } function updateIndicator() { let indicator = document.getElementById('progressIndicator'); let progressBarCount = downloadsContainer.querySelectorAll('.progressBar').length; if (!indicator) { indicator = document.createElement('div'); indicator.id = 'progressIndicator'; Object.assign(indicator.style, { position: 'absolute', top: '10px', right: '5px', backgroundColor: '#f00', backgroundImage: 'linear-gradient(to bottom right, #ffcc80, #ff8c00)', color: 'white', textShadow: '0px 1px 2px rgba(0, 0, 0, 0.7)', borderRadius: '50%', width: '20px', height: '20px', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '12px', fontWeight: 'bold', boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.4)', transition: 'background-color 0.3s, box-shadow 0.3s' }); downloadsContainer.appendChild(indicator); } indicator.textContent = progressBarCount; } function updateDownloadsContainerVisibility() { const nonEmptyNodes = Array.from(downloadsContainer.children).filter(child => !child.classList.contains('slide-out') && child.tagName !== 'P' && child !== downloadsContainer.firstChild && child !== toggleButton && child.id !== 'progressIndicator'); if (nonEmptyNodes.length === 0) { if (!downloadsContainer.querySelector('p')) { let emptyText = document.createElement('p'); emptyText.innerHTML = ` 暂无下载内容 ᓚᘏᗢ<br> <span style="font-size: 10px;">(可拖动文件下载)</span> `; Object.assign(emptyText.style, { color: '#FF9800', textAlign: 'center', fontFamily: 'Microsoft YaHei', fontSize: '16px', fontWeight: 'bold', padding: '20px', marginTop: '15px', borderRadius: '8px', opacity: '0', transition: 'opacity 1s ease-in-out, transform 1s', transform: 'translateY(-20px)', top: '50%' }); setTimeout(() => { emptyText.style.opacity = '1'; emptyText.style.transform = 'translateY(0)'; }, 100); emptyText.animate([ { transform: 'translateY(0)' }, { transform: 'translateY(-10px)' }, { transform: 'translateY(0)' } ], { duration: 3000, iterations: Infinity, easing: 'ease-in-out' }); downloadsContainer.appendChild(emptyText); } downloadsContainer.style.display = isProgressBarVisible ? 'block' : 'none'; } else { let emptyText = downloadsContainer.querySelector('p'); if (emptyText) { downloadsContainer.removeChild(emptyText); } } toggleButton.style.display = isProgressBarVisible ? 'block' : 'none'; } updateIndicator() updateDownloadsContainerVisibility(); function courseDownload(file_url, file_name) { return new Promise((resolve, reject) => { const useThirdPartyDownload = localStorage.getItem('useThirdPartyDownload') === 'true'; const token = getCookie(); const downloadsContainer = document.getElementById('downloadsContainer'); const controller = new AbortController(); const signal = controller.signal; // 创建进度条相关元素 const progressText = document.createElement('span'); const progressBar = document.createElement('div'); const progressBarContainer = document.createElement('div'); const progressContainer = document.createElement('div'); const progressPercentText = document.createElement('span'); // 设置样式 const styles = { progressText: { color: '#FF9800', fontWeight: 'bold', fontSize: '16px', fontFamily: 'Microsoft YaHei', textShadow: '1px 1px 2px rgba(0, 0, 0, 0.1)', padding: '5px 0', borderRadius: '4px' }, progressBar: { height: '18px', width: '0%', borderRadius: '8px', className: 'progressBar' }, progressBarContainer: { background: '#E0E0E0', borderRadius: '8px', height: '18px', width: '100%', overflow: 'hidden', position: 'relative' }, progressContainer: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'space-around' }, progressPercentText: { fontFamily: 'Arial Rounded MT Bold, Helvetica Rounded, Arial, sans-serif', fontSize: '16px', marginLeft: '10px', position: 'absolute', left: '0', top: '0', width: '100%', textAlign: 'center', lineHeight: '18px', zIndex: '1', fontWeight: 'bold' } }; // 应用样式 Object.assign(progressText.style, styles.progressText); Object.assign(progressBar.style, styles.progressBar); Object.assign(progressBarContainer.style, styles.progressBarContainer); Object.assign(progressContainer.style, styles.progressContainer); Object.assign(progressPercentText.style, styles.progressPercentText); progressText.innerText = `正在下载: ${file_name}`; progressBar.className = 'progressBar'; // 组装进度条元素 progressBarContainer.appendChild(progressBar); progressContainer.appendChild(progressText); progressContainer.appendChild(progressBarContainer); progressBarContainer.appendChild(progressPercentText); progressContainer.classList.add('slide-in'); updateIndicator(); updateDownloadsContainerVisibility(); downloadsContainer.appendChild(progressContainer); window.abortControllers = window.abortControllers || {}; window.abortControllers[file_name] = controller; // 创建控制容器 const controlContainer = document.createElement('div'); const fileSizeSpan = document.createElement('span'); const stopButton = document.createElement('button'); Object.assign(controlContainer.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' }); Object.assign(fileSizeSpan.style, { fontFamily: 'Microsoft YaHei', fontSize: '14px', marginRight: 'auto', fontWeight: 'bold' }); Object.assign(stopButton.style, { padding: '5px 10px', border: 'none', borderRadius: '5px', backgroundColor: '#FF4136', color: 'white', cursor: 'pointer', fontSize: '14px', fontWeight: 'bold', boxShadow: '0 2px 4px rgba(0, 0, 0, 0.2)', transition: 'transform 0.3s ease', marginTop: '10px' }); stopButton.textContent = '停止'; stopButton.onmouseover = () => { stopButton.style.transform = 'scale(1.2)'; }; stopButton.onmouseout = () => { stopButton.style.transform = 'scale(1)'; }; stopButton.onclick = () => { console.log(`尝试停止下载: ${file_name}`); controller.abort(); progressContainer.classList.add('slide-out'); progressContainer.addEventListener('animationend', () => { progressContainer.remove(); updateIndicator(); updateDownloadsContainerVisibility(); }, { once: true }); }; controlContainer.appendChild(fileSizeSpan); controlContainer.appendChild(stopButton); progressContainer.appendChild(controlContainer); addProgressBarStyles(); updateIndicator(); updateDownloadsContainerVisibility(); updateContainerPosition(); // 保存下载历史 saveDownloadHistory(file_name, file_url); if (useThirdPartyDownload) { handleThirdPartyDownload(file_url, file_name, progressBar, progressPercentText, progressContainer, resolve); } else { handleFetchDownload(file_url, token, signal, fileSizeSpan, progressBar, progressPercentText, progressContainer, file_name, resolve, reject); } }); } function addProgressBarStyles() { if (!document.getElementById('progress-bar-styles')) { let styles = document.createElement('style'); styles.id = 'progress-bar-styles'; styles.textContent = ` .progressBar { background-color: #FF9800; background-image: repeating-linear-gradient( 45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent ); background-size: 40px 40px; animation: moveBackground 2s linear infinite; } @keyframes moveBackground { from { background-position: 0 0; } to { background-position: 40px 0; } } .slide-in { animation: slideIn 0.5s ease-out forwards; } .slide-out { animation: slideOut 0.5s ease-in forwards; } @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slideOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } } `; document.head.appendChild(styles); } } function bytesToSize(bytes) { const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; if (bytes === 0) return '0 Byte'; const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i]; } function handleThirdPartyDownload(file_url, file_name, progressBar, progressPercentText, progressContainer, resolve) { const downloadLink = document.createElement('a'); downloadLink.href = file_url; downloadLink.download = file_name; downloadLink.setAttribute('data-downloadurl', `application/octet-stream:${file_name}:${file_url}`); document.body.appendChild(downloadLink); downloadLink.click(); document.body.removeChild(downloadLink); let progress = 0; const interval = setInterval(() => { progress += 10; if (progress <= 100) { progressBar.style.width = `${progress}%`; progressPercentText.innerText = `${progress}%`; } else { clearInterval(interval); progressContainer.classList.add('slide-out'); progressContainer.addEventListener('animationend', () => { progressContainer.remove(); updateIndicator(); updateDownloadsContainerVisibility(); }, { once: true }); } }, 200); resolve(); } function handleFetchDownload(file_url, token, signal, fileSizeSpan, progressBar, progressPercentText, progressContainer, file_name, resolve, reject) { fetch(file_url, { signal, headers: { Authorization: `Bearer ${token}` } }) .then(response => { const contentLength = response.headers.get('Content-Length'); if (contentLength) { const fileSize = bytesToSize(contentLength); fileSizeSpan.innerText = `文件大小: ${fileSize}`; } else { fileSizeSpan.innerText = `无法获取文件大小`; updateIndicator(); updateDownloadsContainerVisibility(); } const reader = response.body.getReader(); let receivedBytes = 0; let chunks = []; function processResult(result) { if (result.done) { const blob = new Blob(chunks, { type: 'application/octet-stream' }); const downloadUrl = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = downloadUrl; a.download = file_name; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(downloadUrl); progressContainer.classList.add('slide-out'); progressContainer.addEventListener('animationend', () => { progressContainer.remove(); updateIndicator(); updateDownloadsContainerVisibility(); }, { once: true }); return; } chunks.push(result.value); receivedBytes += result.value.length; resolve(); let percentComplete = (receivedBytes / contentLength) * 100; progressBar.style.width = `${percentComplete.toFixed(2)}%`; progressPercentText.innerText = `${percentComplete.toFixed(2)}%`; reader.read().then(processResult); } reader.read().then(processResult); }) .catch(e => { if (e.name === 'AbortError') { console.error(`${file_name} 的下载被用户取消了`); } else { console.error(e); } progressContainer.remove(); updateIndicator(); updateDownloadsContainerVisibility(); reject(e); }); } window.updateUI = function() { const download_list = document.getElementById("download_list"); const container = createOrUpdateContainer(download_list); // 创建或更新 UI 组件 createLottieAnimation(download_list); setupDragAndDrop(); createOrUpdateSearchInput(container); createOrUpdateQuickFilterSelect(container); createOrUpdateSortSelect(container); createOrUpdateSelectAllCheckbox(download_list); createOrUpdateBulkDownloadButton(download_list); createOrUpdateTreeViewButton(download_list); } function createLottieAnimation(parent) { if (!document.getElementById("lottie-animation-container")) { const lottieContainer = document.createElement("div"); lottieContainer.id = "lottie-animation-container"; lottieContainer.innerHTML = ` <dotlottie-player src="https://lottie.host/f6cfdc36-5c9a-4dac-bb71-149cdf2e7d92/VRIhn9vXE5.json" background="transparent" speed="1" style="width: 300px; height: 300px; transform: scaleX(-1);" loop autoplay> </dotlottie-player>`; Object.assign(lottieContainer.style, { position: "absolute", top: "50%", right: "0", transform: "translateY(-50%)", zIndex: "-1" }); parent.appendChild(lottieContainer); } } function setupDragAndDrop() { document.querySelectorAll("#download_list .file-item a").forEach(fileLink => { fileLink.draggable = true; fileLink.addEventListener('dragstart', (event) => { event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.setData('text/plain', fileLink.href); event.dataTransfer.setData('text/filename', fileLink.getAttribute('data-origin-name')); }); }); } function createOrUpdateContainer(parent) { let container = document.getElementById("searchAndFilterContainer"); if (!container) { container = document.createElement("div"); container.id = "searchAndFilterContainer"; Object.assign(container.style, { position: "relative", display: "flex", marginBottom: '10px' }); parent.prepend(container); } return container; } function createOrUpdateSearchInput(container) { let searchInput = document.getElementById("searchInput"); if (!searchInput) { searchInput = document.createElement("input"); searchInput.type = "text"; searchInput.placeholder = "搜索文件名/后缀名"; searchInput.id = "searchInput"; searchInput.className = "course-search-input"; Object.assign(searchInput.style, { padding: '5px', marginRight: '10px', border: '2px solid #ffa500', boxShadow: 'inset 0 1px 3px rgba(0,0,0,0.1)', borderRadius: '4px', outline: 'none', color: '#ffa500', fontWeight: 'bold', backgroundColor: '#fffbe6' }); searchInput.addEventListener("input", () => filterList(searchInput.value)); container.appendChild(searchInput); } searchInput.value = window.currentSearchKeyword || ''; } function createOrUpdateQuickFilterSelect(container) { let quickFilterSelect = document.getElementById("quickFilterSelect"); if (!quickFilterSelect) { quickFilterSelect = document.createElement("select"); quickFilterSelect.id = "quickFilterSelect"; applyCommonSelectStyle(quickFilterSelect); quickFilterSelect.addEventListener("change", () => filterListByCategory(quickFilterSelect.value)); container.appendChild(quickFilterSelect); } updateSelectOptions(quickFilterSelect, window.quickFilters); // 保持当前的筛选类别 quickFilterSelect.value = window.currentFilterCategory || ''; } function createOrUpdateSortSelect(container) { let sortSelect = document.getElementById("sortSelect"); if (!sortSelect) { sortSelect = document.createElement("select"); sortSelect.id = "sortSelect"; applyCommonSelectStyle(sortSelect); sortSelect.addEventListener("change", () => sortList(sortSelect.value)); container.appendChild(sortSelect); } // 保存当前选中的值 const currentValue = sortSelect.value; updateSelectOptions(sortSelect, [ { value: '', label: '排序方式' }, { value: 'date_asc', label: '日期升序' }, { value: 'date_desc', label: '日期降序' }, { value: 'xiaoya_order', label: '小雅排序' } ]); if (currentValue) { sortSelect.value = currentValue; } } function applyCommonSelectStyle(select) { Object.assign(select.style, { padding: '5px', marginRight: '10px', border: '2px solid #ffa500', boxShadow: 'inset 0 1px 3px rgba(0,0,0,0.1)', borderRadius: '4px', outline: 'none', cursor: 'pointer', color: '#ffa500', fontWeight: 'bold', backgroundColor: '#fffbe6' }); } function updateSelectOptions(select, options) { select.innerHTML = ''; options.forEach(option => { const opt = document.createElement("option"); opt.value = option.value; opt.text = option.label; opt.style.fontWeight = 'bold'; select.appendChild(opt); }); } function createOrUpdateSelectAllCheckbox(parent) { if (!document.getElementById("selectAllCheckbox")) { const checkboxContainer = document.createElement('div'); Object.assign(checkboxContainer.style, { display: 'flex', position: "relative", alignItems: 'center', padding: '5px 10px', marginBottom: '10px' }); const selectAllCheckbox = document.createElement('input'); selectAllCheckbox.type = 'checkbox'; selectAllCheckbox.className = 'custom-checkbox'; selectAllCheckbox.id = 'selectAllCheckbox'; selectAllCheckbox.style.marginRight = '10px'; const selectAllLabel = document.createElement('label'); selectAllLabel.htmlFor = 'selectAllCheckbox'; selectAllLabel.textContent = '全选'; Object.assign(selectAllLabel.style, { fontWeight: 'bold', color: '#FFA500', userSelect: 'none' }); checkboxContainer.appendChild(selectAllCheckbox); checkboxContainer.appendChild(selectAllLabel); parent.prepend(checkboxContainer); selectAllCheckbox.addEventListener('change', handleSelectAllChange); } } function handleSelectAllChange() { const selectAllCheckbox = document.getElementById("selectAllCheckbox"); const checkboxes = document.querySelectorAll("#download_list input[type='checkbox']:not(#selectAllCheckbox)"); checkboxes.forEach(checkbox => { if (checkbox.getAttribute('data-visible') === 'true') { checkbox.checked = selectAllCheckbox.checked; checkbox.dispatchEvent(new Event('change', { bubbles: true, cancelable: true })); } }); // 更新树状图 syncTreeWithDownloadList(); } // 按钮通用样式 function applyCommonButtonStyle(button, gradientColors) { Object.assign(button.style, { background: `linear-gradient(90deg, ${gradientColors.join(", ")})`, backgroundSize: '200% 100%', animation: 'flowingGradient 3s ease infinite', color: 'white', border: 'none', padding: '10px 20px', borderRadius: '5px', cursor: 'pointer', boxShadow: '0 2px 4px rgba(0,0,0,0.2)', transition: 'filter 0.3s, transform 0.3s', position: 'fixed', top: '15px', zIndex: 10000 }); button.addEventListener('mouseover', () => { button.style.transform = 'scale(1.05)'; button.style.backgroundColor = '#ffd564'; }); button.addEventListener('mouseout', () => { button.style.transform = 'scale(1)'; button.style.backgroundColor = '#fcbb34'; }); } function createOrUpdateBulkDownloadButton(parent) { if (!document.getElementById("bulkDownloadButton")) { const bulkDownloadButton = document.createElement('button'); const bulkDownloadButtonGradient = ['#ffa500', '#ff8c00', '#ffa500']; bulkDownloadButton.innerHTML = '<span style="font-weight: bold;">批量下载</span>'; bulkDownloadButton.id = "bulkDownloadButton"; bulkDownloadButton.title = '点击下载所选文件'; applyCommonButtonStyle(bulkDownloadButton, bulkDownloadButtonGradient); bulkDownloadButton.style.right = '15px'; bulkDownloadButton.addEventListener('click', handleBulkDownload); parent.appendChild(bulkDownloadButton); } } async function handleBulkDownload() { console.log('开始批量下载'); const checkboxes = document.querySelectorAll("#download_list input[type='checkbox']:checked"); const useThirdPartyDownload = localStorage.getItem('useThirdPartyDownload') === 'true'; const downloadQueue = Array.from(checkboxes) .filter(checkbox => checkbox.checked && checkbox.getAttribute('data-visible') === 'true') .map(checkbox => { const container = checkbox.closest('div'); const link = container.querySelector('a'); return { url: link.href, name: link.getAttribute('data-origin-name') }; }); console.log(`队列中的文件数: ${downloadQueue.length}`); for (let file of downloadQueue) { try { console.log(`正在处理文件: ${file.name}`); await courseDownload(file.url, file.name); console.log(`成功添加下载: ${file.name}`); if (useThirdPartyDownload) { console.log(`等待 1 秒后继续下一个下载...`); await new Promise(resolve => setTimeout(resolve, 1000)); } } catch (error) { console.error(`添加 ${file.name} 到下载队列时发生错误:`, error); } } console.log('批量下载任务添加完成'); } function sortList(order) { const downloadList = document.getElementById("download_list"); const items = Array.from(downloadList.querySelectorAll(".file-item")); // 构建文件夹 ID 到索引的映射(用于小雅排序) const folderIndexMap = {}; svgElementIds.forEach((id, index) => { folderIndexMap[id] = index; }); items.sort((a, b) => { const aParentId = a.querySelector('a').getAttribute('data-parent-id'); const bParentId = b.querySelector('a').getAttribute('data-parent-id'); const aId = a.querySelector('a').getAttribute('data-resource-id'); const bId = b.querySelector('a').getAttribute('data-resource-id'); if (order === 'xiaoya_order') { const aFolderIndex = folderIndexMap[aParentId] || Infinity; const bFolderIndex = folderIndexMap[bParentId] || Infinity; if (aFolderIndex !== bFolderIndex) { return aFolderIndex - bFolderIndex; // 不同文件夹,按文件夹顺序排序 } else { return aId.localeCompare(bId); // 同一文件夹,按文件 ID 排序 } } else { const aDate = new Date(a.querySelector('a').getAttribute('data-created-at')); const bDate = new Date(b.querySelector('a').getAttribute('data-created-at')); if (order === 'date_asc') { return aDate - bDate; } else if (order === 'date_desc') { return bDate - aDate; } } }); items.forEach(item => downloadList.appendChild(item)); // 重新应用筛选条件 applyFilters(); } window.toggleListVisibility = function() { var download_list = document.getElementById("download_list"); // 切换列表的显示和隐藏 if (download_list.style.maxHeight === '0px' || download_list.style.maxHeight === '') { // 展开列表 download_list.style.maxHeight = "300px"; download_list.style.opacity = "1"; download_list.style.overflowY = "auto"; } else { // 折叠列表 download_list.style.maxHeight = "0"; download_list.style.opacity = "0"; } } function filterList(keyword) { window.currentSearchKeyword = keyword.toLowerCase(); const searchInput = document.getElementById("searchInput"); if (searchInput) { searchInput.value = keyword; } applyFilters(); } function filterListByCategory(categoryValue) { window.currentFilterCategory = categoryValue; applyFilters(); } function applyFilters() { var searchKeyword = window.currentSearchKeyword; var filterCategory = window.currentFilterCategory; var extensions = filterCategory ? filterCategory.split(',').map(ext => ext.trim()) : []; var containers = document.querySelectorAll("#download_list .file-item"); containers.forEach(function(container) { var file = container.querySelector("a"); var checkbox = container.querySelector("input[type='checkbox']"); var fileName = file.getAttribute('data-origin-name').toLowerCase(); var fileExtension = fileName.slice(((fileName.lastIndexOf(".") - 1) >>> 0) + 2); var isSearchMatch = searchKeyword === '' || fileName.includes(searchKeyword); var isFilterMatch = filterCategory === "" || extensions.includes(fileExtension); var isVisible = isSearchMatch && isFilterMatch; container.style.display = isVisible ? "flex" : "none"; checkbox.setAttribute('data-visible', isVisible.toString()); }); const selectAllCheckbox = document.getElementById('selectAllCheckbox'); if (selectAllCheckbox) { const visibleCheckboxes = Array.from(document.querySelectorAll("#download_list .file-item input[type='checkbox'][data-visible='true']")); const allVisibleChecked = visibleCheckboxes.every(checkbox => checkbox.checked); selectAllCheckbox.checked = visibleCheckboxes.length > 0 && allVisibleChecked; } var visibleItems = document.querySelectorAll("#download_list .file-item[style*='display: flex']"); // 获取下载列表容器 var download_list = document.getElementById("download_list"); var noResultsMessage = download_list.querySelector('p'); if (visibleItems.length === 0) { if (!noResultsMessage) { noResultsMessage = document.createElement('p'); noResultsMessage.style.color = '#FFA500'; noResultsMessage.style.textAlign = 'center'; noResultsMessage.style.fontWeight = 'bold'; noResultsMessage.innerHTML = '<span style="font-size: 1.2em;"><span style="color: red;">没有</span></span>课件( ´・・)ノ(._.`)'; download_list.appendChild(noResultsMessage); } } else { if (noResultsMessage) { download_list.removeChild(noResultsMessage); } } } // 配置对象 const TREE_VIEW_CONFIG = { buttonId: 'treeViewButton', containerId: 'treeContainer', contentId: 'treeContent', loadingId: 'loadingAnimation', buttonGradient: ['#27B4DB', '#2DC3FF', '#03A9F4'], buttonText: '课程结构', containerStyles: { width: '80%', minHeight: '600px', maxWidth: '600px', maxHeight: '80vh', backgroundColor: '#fefefe', padding: '20px', borderRadius: '10px', boxShadow: '0 4px 8px rgba(0,0,0,0.1)', zIndex: 10000 } }; // 主函数 function createOrUpdateTreeViewButton(parent) { let treeViewButton = document.getElementById(TREE_VIEW_CONFIG.buttonId); let treeContainer = document.getElementById(TREE_VIEW_CONFIG.containerId); if (!treeViewButton) { treeViewButton = createTreeViewButton(); treeViewButton.title = '点击构建树状图'; parent.appendChild(treeViewButton); } if (!treeContainer) { treeContainer = createTreeContainer(); document.body.appendChild(treeContainer); makeDraggable(treeContainer); } treeContainer.innerHTML = ''; const dragHandle = createDragHandle(); treeContainer.appendChild(dragHandle); const closeButton = createCloseButton(treeContainer); dragHandle.appendChild(closeButton); const searchContainer = createSearchContainer(); treeContainer.appendChild(searchContainer); const contentWrapper = document.createElement('div'); contentWrapper.id = 'treeContentWrapper'; contentWrapper.style.cssText = ` max-height: calc(80vh - 100px); overflow-y: auto; padding: 20px; `; treeContainer.appendChild(contentWrapper); const loadingAnimation = createLoadingAnimation(); const treeContent = createTreeContent(); contentWrapper.appendChild(loadingAnimation); contentWrapper.appendChild(treeContent); const searchInput = searchContainer.querySelector('#treeSearchInput'); const searchButton = searchContainer.querySelector('#treeSearchButton'); const prevButton = searchContainer.querySelector('#treePrevButton'); const nextButton = searchContainer.querySelector('#treeNextButton'); let currentMatchIndex = -1; let matches = []; let highlightTimeout; if (searchInput && searchButton && prevButton && nextButton) { searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { performTreeSearch(); } }); searchButton.addEventListener('click', performTreeSearch); prevButton.addEventListener('click', () => navigateMatches(-1)); nextButton.addEventListener('click', () => navigateMatches(1)); } function performTreeSearch() { if (!searchInput) return; const searchTerm = searchInput.value.toLowerCase().trim(); if (searchTerm === '') { return; } const treeItems = treeContainer.querySelectorAll('.item-content span'); matches = []; removeAllHighlights(); treeItems.forEach(item => { const text = item.textContent.toLowerCase(); if (text.includes(searchTerm)) { matches.push(item); expandParentFolders(item.closest('li')); } }); currentMatchIndex = matches.length > 0 ? 0 : -1; updateNavigationButtons(); if (matches.length > 0) { highlightCurrentMatch(); scrollToMatch(currentMatchIndex); } } function navigateMatches(direction) { if (matches.length === 0) return; currentMatchIndex += direction; if (currentMatchIndex < 0) currentMatchIndex = matches.length - 1; if (currentMatchIndex >= matches.length) currentMatchIndex = 0; updateNavigationButtons(); highlightCurrentMatch(); scrollToMatch(currentMatchIndex); } function highlightCurrentMatch() { removeAllHighlights(); if (currentMatchIndex >= 0 && currentMatchIndex < matches.length) { const currentMatch = matches[currentMatchIndex]; const highlightSpan = document.createElement('span'); highlightSpan.className = 'highlight'; highlightSpan.textContent = currentMatch.textContent; currentMatch.textContent = ''; currentMatch.appendChild(highlightSpan); scrollToMatch(currentMatchIndex); clearTimeout(highlightTimeout); highlightTimeout = setTimeout(() => { if (highlightSpan.parentNode) { currentMatch.textContent = highlightSpan.textContent; } }, 2000); } } function removeAllHighlights() { treeContainer.querySelectorAll('.highlight').forEach(el => { if (el.parentNode) { el.parentNode.textContent = el.textContent; } }); } function scrollToMatch(index) { if (index >= 0 && index < matches.length) { const contentWrapper = document.getElementById('treeContentWrapper'); const matchElement = matches[index]; const wrapperRect = contentWrapper.getBoundingClientRect(); const matchRect = matchElement.getBoundingClientRect(); contentWrapper.scrollTop += matchRect.top - wrapperRect.top - wrapperRect.height / 2 + matchRect.height / 2; matchElement.scrollIntoView({block: "nearest", inline: "nearest"}); } } function updateNavigationButtons() { prevButton.disabled = matches.length === 0; nextButton.disabled = matches.length === 0; } function expandParentFolders(element) { let current = element; while (current !== treeContainer) { if (current.tagName === 'LI') { const folderToggle = current.querySelector('.folder-toggle'); const ul = current.querySelector('ul'); if (folderToggle && ul) { folderToggle.classList.add('open'); ul.classList.remove('collapsed'); ul.classList.add('expanded'); ul.style.display = 'block'; ul.style.height = 'auto'; } } current = current.parentElement; } } setupTreeViewButtonEvents(treeViewButton, treeContainer, treeContent, loadingAnimation); setupTreeCheckboxEvents(treeContainer); addTreeViewStyles(); setInitialFoldState(treeContainer); } function setInitialFoldState(container) { const rootUl = container.querySelector('ul'); if (rootUl) { const subFolders = rootUl.querySelectorAll('li > ul'); subFolders.forEach(ul => { if (!ul.classList.contains('collapsed')) { ul.classList.add('collapsed'); ul.style.display = 'none'; const toggle = ul.parentElement.querySelector('.folder-toggle'); if (toggle) { toggle.classList.remove('open'); } } }); } } // 创建树状图按钮 function createTreeViewButton() { const button = document.createElement('button'); button.id = TREE_VIEW_CONFIG.buttonId; button.innerHTML = `<span style="font-weight: bold;">${TREE_VIEW_CONFIG.buttonText}</span>`; applyCommonButtonStyle(button, TREE_VIEW_CONFIG.buttonGradient); button.style.right = '120px'; return button; } function createDragHandle() { const dragHandle = document.createElement('div'); dragHandle.className = 'drag-handle'; dragHandle.innerHTML = ` <span>课程结构</span> <span class="drag-icon">☰</span> <span class="drag-hint">拖动区域</span> `; dragHandle.style.position = 'sticky'; dragHandle.style.top = '0'; dragHandle.style.zIndex = '1'; return dragHandle; } function createSearchContainer() { const searchContainer = document.createElement('div'); searchContainer.className = 'search-container'; searchContainer.innerHTML = ` <input type="text" id="treeSearchInput" placeholder="搜索..." style="font-weight: bold;" /> <button id="treeSearchButton" class="search-button" title="搜索"> <svg class="search-icon" viewBox="0 0 24 24"> <path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/> </svg> </button> <button id="treePrevButton" class="search-button" title="上一个"> <svg class="search-icon" viewBox="0 0 24 24"> <path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/> </svg> </button> <button id="treeNextButton" class="search-button" title="下一个"> <svg class="search-icon" viewBox="0 0 24 24"> <path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/> </svg> </button> `; return searchContainer; } // 创建树状图容器 function createTreeContainer() { const container = document.createElement('div'); container.id = TREE_VIEW_CONFIG.containerId; Object.assign(container.style, TREE_VIEW_CONFIG.containerStyles, { display: 'none', position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%) scale(1)', overflowY: 'hidden', transition: 'opacity 0.3s ease-out, transform 0.3s ease-out' }); container.close = function() { this.style.opacity = '0'; this.style.transform = 'translate(-50%, -50%) scale(0.95)'; this.addEventListener('transitionend', function closeHandler() { this.style.display = 'none'; this.removeEventListener('transitionend', closeHandler); disableControls(false); // 在关闭容器时启用控件 }, { once: true }); }; return container; } // 创建树状图内容区 function createTreeContent() { const content = document.createElement('div'); content.id = TREE_VIEW_CONFIG.contentId; content.innerHTML = ` <div style=" display: flex; justify-content: center; align-items: center; height: 600px; width: 100%; "> <div style="text-align: center; padding: 20px;"> <div style="font-size: 48px; margin-bottom: 20px;">🌱</div> <h3 style="margin-bottom: 15px;"> <span style=" background: linear-gradient(90deg, #1e90ff, #00bfff, #87cefa); background-size: 200% 100%; animation: flowingGradient 5s ease infinite; -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: bold; font-size: 24px; ">课程结构已改变</span> </h3> <p style=" font-size: 14px; font-weight: bold; color: #4682b4; max-width: 300px; margin: 0 auto; line-height: 1.6; "> 请重新构建树状图(ˉ﹃ˉ) </p> </div> </div> `; return content; } // 创建加载动画 function createLoadingAnimation() { const loading = document.createElement('div'); loading.id = TREE_VIEW_CONFIG.loadingId; loading.style.cssText = ` display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(255, 255, 255, 0.9); z-index: 10001; `; const container = document.createElement('div'); container.style.cssText = ` position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; `; loading.appendChild(container); const loader = document.createElement('div'); loader.className = 'loader'; container.appendChild(loader); const style = document.createElement('style'); style.textContent = ` .loader { font-weight: bold; font-family: monospace; font-size: 30px; display: inline-grid; color: #3498db; } .loader:before, .loader:after { content: "正在构建……"; grid-area: 1/1; line-height: 1em; -webkit-mask: linear-gradient(90deg, #000 50%, #0000 0) 0 50%/2ch 100%; -webkit-mask-position: calc(var(--s,0)*1ch) 50%; animation: l30 2s infinite; } .loader:after { --s:-1; } @keyframes l30 { 33% {transform: translateY(calc(var(--s,1)*50%));-webkit-mask-position:calc(var(--s,0)*1ch) 50%} 66% {transform: translateY(calc(var(--s,1)*50%));-webkit-mask-position:calc(var(--s,0)*1ch + 1ch) 50%} 100% {transform: translateY(calc(var(--s,1)*0%)); -webkit-mask-position:calc(var(--s,0)*1ch + 1ch) 50%} } `; document.head.appendChild(style); return loading; } // 创建关闭按钮 function createCloseButton(treeContainer) { const closeButton = document.createElement('span'); closeButton.textContent = '×'; closeButton.style.cssText = ` color: #aaa; float: right; font-size: 28px; font-weight: bold; cursor: pointer; margin-top: -10px; margin-right: -10px; padding: 5px; z-index: 10001; `; closeButton.addEventListener('click', (e) => { e.stopPropagation(); treeContainer.close(); // 使用容器的 close 方法 }); return closeButton; } // 设置树状图按钮事件 function setupTreeViewButtonEvents(button, container, content, loading) { button.onclick = () => { if (course_resources) { if (container.style.display === 'none') { // 打开树状图 container.style.display = 'block'; container.style.opacity = '0'; container.style.transform = 'translate(-50%, -50%) scale(0.95)'; loading.style.display = 'block'; content.innerHTML = ''; // 禁用搜索框、筛选框和排序框 disableControls(true); requestAnimationFrame(() => { container.style.transition = 'opacity 0.3s ease-out, transform 0.3s ease-out'; container.style.opacity = '1'; container.style.transform = 'translate(-50%, -50%) scale(1)'; }); const itemCount = countTreeItems(course_resources); const animationDuration = Math.min(Math.max(itemCount * 10, 500), 3000); setTimeout(() => { content.innerHTML = createTreeHTML(course_resources); addFolderToggle(content); syncTreeWithDownloadList(); setInitialFoldState(container); loading.style.display = 'none'; content.style.opacity = '0'; requestAnimationFrame(() => { content.style.transition = 'opacity 0.3s ease-out'; content.style.opacity = '1'; }); // 启用搜索功能 const searchInput = container.querySelector('#treeSearchInput'); const searchButton = container.querySelector('#treeSearchButton'); if (searchInput && searchButton) { searchInput.disabled = false; searchButton.disabled = false; } }, animationDuration); } else { // 关闭树状图 container.close(); } } else { alert('课程资源尚未加载,请稍后再试。'); } }; } // 递归计算树状图中的项目数量 function countTreeItems(resources) { if (!resources) return 0; if (Array.isArray(resources)) { let count = 0; for (const resource of resources) { count++; // 计算当前项 if (resource.children) { count += countTreeItems(resource.children); } } return count; } else if (typeof resources === 'object') { let count = 1; // 计算当前对象 for (const key in resources) { if (resources.hasOwnProperty(key) && typeof resources[key] === 'object') { count += countTreeItems(resources[key]); } } return count; } else { return 1; } } function disableControls(disable) { const container = document.getElementById('searchAndFilterContainer'); const searchInput = document.getElementById('searchInput'); const quickFilterSelect = document.getElementById('quickFilterSelect'); const sortSelect = document.getElementById('sortSelect'); if (container) { container.className = disable ? 'disabled' : ''; } if (searchInput) { searchInput.disabled = disable; searchInput.style.color = disable ? '#888' : '#ffa500'; } if (quickFilterSelect) quickFilterSelect.disabled = disable; if (sortSelect) sortSelect.disabled = disable; } // 设置树状图复选框事件 function setupTreeCheckboxEvents(container) { container.addEventListener('change', e => { if (e.target.classList.contains('tree-checkbox')) { handleTreeCheckboxChange(e.target); } }); } function handleTreeCheckboxChange(checkbox) { // 使用 requestAnimationFrame 来批量更新 UI requestAnimationFrame(() => { batchUpdate(checkbox); updateSelectAllCheckbox(); syncTreeWithDownloadList(); }); } // 创建树状图 HTML function createTreeHTML() { let tree = {}; let folderOrder = {}; let folderNames = {}; // 从 course_resources 中获取所有文件夹信息 for (let id in course_resources) { let resource = course_resources[id]; if (resource.type === 1) { folderNames[id] = resource.name; } } svgElementIds.forEach((id, index) => { folderOrder[id] = index; }); document.querySelectorAll("#download_list .file-item").forEach(fileItem => { let fileInfo = fileItem.querySelector('a'); let checkbox = fileItem.querySelector('input[type="checkbox"]'); let resource = { id: fileInfo.getAttribute('data-resource-id'), parent_id: fileInfo.getAttribute('data-parent-id'), name: fileInfo.getAttribute('data-origin-name'), path: fileInfo.getAttribute('data-path'), checkbox: checkbox }; let pathIds = resource.path.split('/'); let current = tree; let currentPath = []; pathIds.forEach((id, index) => { let name = folderNames[id] || (index === pathIds.length - 1 ? resource.name : id); currentPath.push(id); if (!current[name]) { current[name] = { children: {}, resources: [], order: folderOrder[id] || Infinity, id: id, name: name, isFolder: index < pathIds.length - 1, path: currentPath.join('/') }; } if (index === pathIds.length - 1) { current[name].resources.push(resource); current[name].id = resource.id; current[name].name = resource.name; } current = current[name].children; }); }); return buildTreeHTML(tree); } // 递归构建树状图 HTML function buildTreeHTML(node, parentPath = '', level = 0) { let html = '<ul' + (level > 0 ? ' class="collapsed"' : '') + '>'; let folderEntries = []; let fileEntries = []; for (let key in node) { let item = node[key]; if (item.isFolder) { folderEntries.push({ key, item }); } else { fileEntries.push({ key, item }); } } folderEntries.sort((a, b) => a.item.order - b.item.order); for (let { item } of folderEntries) { let currentPath = item.path; let folderContent = buildTreeHTML(item.children, currentPath, level + 1); html += ` <li> <div class="item-content"> <input type="checkbox" class="tree-checkbox folder-checkbox" data-path="${currentPath}"> <span class="folder-toggle"></span> <span class="folder">${item.name}</span> </div> ${folderContent} </li> `; } fileEntries.sort((a, b) => a.item.name.localeCompare(b.item.name)); for (let { item } of fileEntries) { for (let resource of item.resources) { let isChecked = resource.checkbox.checked ? 'checked' : ''; let fileIcon = getFileIcon(resource.name); html += ` <li> <div class="item-content"> <input type="checkbox" class="tree-checkbox" data-path="${resource.path}" ${isChecked}> <span>${getFileIcon(resource.name)} ${resource.name}</span> </div> </li> `; } } html += '</ul>'; return html; } // 批量更新复选框状态 function batchUpdate(checkbox) { const isChecked = checkbox.checked; const listItem = checkbox.closest('li'); const childCheckboxes = listItem.querySelectorAll('input.tree-checkbox'); const path = checkbox.getAttribute('data-path'); // 批量更新子复选框 childCheckboxes.forEach(child => { child.checked = isChecked; }); // 批量更新文件列表复选框 const fileListCheckboxes = document.querySelectorAll(`#download_list .file-item a[data-path^="${path}"]`); fileListCheckboxes.forEach(fileInfo => { const fileCheckbox = fileInfo.parentElement.querySelector('input[type="checkbox"]'); if (fileCheckbox.checked !== isChecked) { fileCheckbox.checked = isChecked; } }); // 更新父级复选框 updateParentCheckboxes(checkbox); } // 获取文件图标 function getFileIcon(filename) { let extension = filename.split('.').pop().toLowerCase(); let icon = '📄'; let colorClass = ''; for (let filter of window.quickFilters) { if (filter.value.includes(extension)) { switch (filter.label) { case "文档": icon = '📄'; colorClass = 'ext-other'; if (extension === 'ppt' || extension === 'pptx') { colorClass = 'ext-ppt'; } else if (extension === 'doc' || extension === 'docx') { colorClass = 'ext-doc'; } else if (extension === 'xls' || extension === 'xlsx') { colorClass = 'ext-xls'; } else if (extension === 'txt') { colorClass = 'ext-txt'; } else if (extension === 'pdf') { colorClass = 'ext-pdf'; } break; case "图片": icon = '🖼️'; colorClass = 'ext-img'; break; case "压缩包": icon = '📦'; colorClass = 'ext-zip'; break; } break; } } return `${icon} <span class="file-extension ${colorClass}">${extension}</span>`; } // 添加树状图样式 function addTreeViewStyles() { const style = document.createElement('style'); style.textContent = ` #${TREE_VIEW_CONFIG.containerId} { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 10000; background-color: white; border-radius: 10px; box-shadow: 0 0 20px rgba(0,0,0,0.15); display: flex; flex-direction: column; max-height: 80vh; } .drag-handle { flex-shrink: 0; z-index: 2; background-color: #e6f7ff; border-bottom: 1px solid #b3e0ff; padding: 10px 15px; cursor: move; user-select: none; display: flex; justify-content: space-between; align-items: center; font-weight: bold; color: #0066cc; border-top-left-radius: 10px; border-top-right-radius: 10px; } #treeContainer > div:not(.drag-handle) { flex-grow: 1; overflow-y: auto; } .drag-icon { font-size: 20px; color: #0066cc; } .drag-hint { font-size: 12px; color: #666; margin-right: 10px; } .drag-handle:hover { background-color: #cce5ff; } .drag-handle:active { background-color: #b3d9ff; } #treeContainer { color: #333; background-color: #f0f8ff; padding: 30px; border-radius: 15px; box-shadow: 0 10px 30px rgba(135, 206, 235, 0.2); transition: all 0.3s ease; } #treeContentWrapper { flex-grow: 1; overflow-y: auto; } #treeContainer:hover { box-shadow: 0 15px 40px rgba(135, 206, 235, 0.3); } #treeContainer ul { overflow: hidden; list-style-type: none; padding-left: 25px; position: relative; transition: height 0.3s ease-out; } #treeContainer ul.collapsed { display: none; } #treeContainer li { margin: 20px 0; position: relative; } #treeContainer li::before { content: ""; position: absolute; top: -10px; left: -20px; border-left: 2px solid rgba(135, 206, 235, 0.5); border-bottom: 2px solid rgba(135, 206, 235, 0.5); width: 20px; height: 25px; border-bottom-left-radius: 8px; } #treeContainer .item-content { display: flex; align-items: center; padding: 12px 18px; border-radius: 40px; transition: all 0.3s ease; background-color: #fff; box-shadow: 0 4px 10px rgba(135, 206, 235, 0.1); } #treeContainer .item-content span:not(.folder-toggle):not(.folder):not(.file-extension) { color: #555; font-size: 14px; font-weight: bold; max-width: 80%; } #treeContainer .item-content:hover { background-color: #e6f7ff; transform: translateX(5px); transition: all 0.1s ease-out; } #treeContainer .folder { cursor: pointer; font-weight: 600; margin-left: 10px; color: #1e90ff; } #treeContainer .folder-toggle { cursor: pointer; display: flex; justify-content: center; align-items: center; width: 28px; height: 28px; background-color: #87CEEB; border-radius: 50%; margin-right: 12px; transition: transform 0.1s ease-out; box-shadow: 0 4px 10px rgba(135, 206, 235, 0.2); position: relative; } #treeContainer .folder-toggle::after { content: '▶'; color: #fff; font-size: 14px; transition: all 0.3s ease; } #treeContainer .folder-toggle:hover { background-color: #1e90ff; transform: rotate(15deg) scale(1.1); } #treeContainer .folder-toggle.open { transform: rotate(90deg); background-color: #1e90ff; } #treeContainer .tree-checkbox { margin-right: 12px; appearance: none; -webkit-appearance: none; display: flex; justify-content: center; align-items: center; width: 22px; height: 22px; border: 2px solid #87CEEB; border-radius: 5px; outline: none; transition: all 0.3s ease; position: relative; cursor: pointer; } #treeContainer .tree-checkbox:checked { background-color: #1e90ff; border-color: #1e90ff; } #treeContainer .tree-checkbox:checked::after { content: '✓'; color: white; font-size: 16px; font-weight: bold; } #treeContainer .tree-checkbox:indeterminate { background-color: #87CEEB; } #treeContainer .tree-checkbox:indeterminate::after { content: '-'; color: white; font-size: 20px; font-weight: bold; } @keyframes ripple { 0% { box-shadow: 0 0 0 0 rgba(135, 206, 235, 0.3); } 100% { box-shadow: 0 0 0 20px rgba(135, 206, 235, 0); } } #treeContainer .item-content:active { animation: ripple 0.6s linear; } @keyframes expand { from { height: 0; opacity: 0; transform: translateY(-50px); } to { height: auto; opacity: 1; transform: translateY(0); } } @keyframes collapse { from { height: auto; opacity: 1; transform: translateY(0); } to { height: 0; opacity: 0; transform: translateY(-50px); } } #treeContainer .expanded { animation: expand 0.3s ease-out forwards; } #treeContainer .collapsed { animation: collapse 0.4s ease-out forwards; } #treeContainer .folder::before { content: '📁'; margin-right: 8px; font-size: 18px; } @keyframes spin { 0% { transform: translate(-50%, -50%) rotate(0deg); } 100% { transform: translate(-50%, -50%) rotate(360deg); } } #searchAndFilterContainer { position: relative; } #searchAndFilterContainer.disabled::after { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; z-index: 10; } .course-search-input:disabled { background-color: #f0f0f0 !important; color: #888 !important; border-color: #ccc !important; opacity: 0.7; } .course-search-input:disabled::placeholder { color: #888 !important; } #quickFilterSelect:disabled, #sortSelect:disabled { background-color: #f0f0f0 !important; color: #888 !important; border-color: #ccc !important; opacity: 0.7; } .tree-content-fade-in { animation: fadeIn 0.3s ease-out; } .file-extension { font-size: 10px; padding: 2px 4px; border-radius: 3px; margin-left: 5px; font-weight: bold; color: white; } .ext-ppt { background-color: #ed6c47; } .ext-doc { background-color: #0074D9; } .ext-xls { background-color: #2ECC40; } .ext-txt { background-color: #B10DC9; } .ext-img { background-color: #FFDC00; } .ext-zip { background-color: #AAAAAA; } .ext-pdf { background-color: #cc2121; } .ext-other { background-color: #FF851B; } .search-container { top: 0; flex-shrink: 0; z-index: 1; position: sticky; padding: 15px; display: flex; background-color: #e6f7ff; align-items: center; border-bottom: 1px solid #b3e0ff; transition: all 0.3s ease; } .search-icon { width: 20px; height: 20px; fill: white; } #treeSearchInput { flex-grow: 1; padding: 10px 15px; margin-right: 10px; border: 2px solid #87CEEB; border-radius: 25px; font-size: 14px; outline: none; transition: all 0.3s ease; background-color: white; } #treeSearchInput:focus { border-color: #1e90ff; box-shadow: 0 0 5px rgba(30, 144, 255, 0.5); } .search-button { background-color: #87CEEB; color: white; border: none; border-radius: 50%; width: 40px; height: 40px; display: flex; justify-content: center; align-items: center; cursor: pointer; transition: all 0.3s ease; margin-left: 10px; padding: 0; } .search-button:hover { background-color: #1e90ff; transform: scale(1.05); } .search-button:active { transform: scale(0.95); } .search-button:disabled { background-color: #cccccc; cursor: not-allowed; opacity: 0.7; } .search-button:disabled:hover { transform: none; } .search-button:disabled .search-icon { fill: #999999; } @keyframes simpleHighlight { 0% { background-color: rgba(255, 255, 0, 0.7); } 100% { background-color: transparent; } } #treeContainer .highlight { background-color: rgba(255, 255, 0, 0.3); border-radius: 3px; animation: simpleHighlight 2s ease-out forwards; } #treePrevButton, #treeNextButton { background-color: #87CEEB; } #treePrevButton:hover:not(:disabled), #treeNextButton:hover:not(:disabled) { background-color: #1e90ff; } `; document.head.appendChild(style); } // 添加文件夹展开/折叠功能 function addFolderToggle(container) { container.removeEventListener('click', folderToggleHandler); container.addEventListener('click', folderToggleHandler); } function folderToggleHandler(e) { const toggle = e.target.closest('.folder-toggle'); if (toggle) { e.stopPropagation(); const li = toggle.closest('li'); const ul = li.querySelector('ul'); if (ul) { toggle.classList.toggle('open'); ul.classList.toggle('collapsed'); ul.classList.toggle('expanded'); if (ul.classList.contains('expanded')) { ul.style.display = 'block'; ul.style.height = 'auto'; } else { ul.style.height = '0px'; ul.addEventListener('transitionend', function handler() { if (ul.classList.contains('collapsed')) { ul.style.display = 'none'; } ul.removeEventListener('transitionend', handler); }, { once: true }); } } } } // 更新父级复选框状态 function updateParentCheckboxes(checkbox) { let currentCheckbox = checkbox; while (currentCheckbox) { const parentLi = currentCheckbox.closest('li').parentElement.closest('li'); if (!parentLi) break; const parentCheckbox = parentLi.querySelector('.tree-checkbox'); updateFolderState(parentCheckbox); currentCheckbox = parentCheckbox; } } function updateFileListCheckbox(treeCheckbox) { const path = treeCheckbox.getAttribute('data-path'); const isChecked = treeCheckbox.checked; document.querySelectorAll(`#download_list .file-item a[data-path^="${path}"]`).forEach(fileInfo => { const checkbox = fileInfo.parentElement.querySelector('input[type="checkbox"]'); if (checkbox.checked !== isChecked) { checkbox.checked = isChecked; checkbox.dispatchEvent(new Event('change', { bubbles: true })); } }); updateSelectAllCheckbox(); } // 更新"全选"复选框 function updateSelectAllCheckbox() { const selectAllCheckbox = document.getElementById('selectAllCheckbox'); if (selectAllCheckbox) { const allCheckboxes = document.querySelectorAll("#download_list .file-item input[type='checkbox']"); let allChecked = true; for (let checkbox of allCheckboxes) { if (!checkbox.checked) { allChecked = false; break; } } selectAllCheckbox.checked = allChecked; } } // 同步树状图与下载列表 function syncTreeWithDownloadList() { const treeCheckboxes = document.querySelectorAll('#treeContainer .tree-checkbox'); const downloadCheckboxes = document.querySelectorAll('#download_list .file-item input[type="checkbox"]'); const downloadCheckboxMap = new Map(); downloadCheckboxes.forEach(cb => { const path = cb.parentElement.querySelector('a').getAttribute('data-path'); downloadCheckboxMap.set(path, cb.checked); }); treeCheckboxes.forEach(treeCheckbox => { const path = treeCheckbox.getAttribute('data-path'); if (downloadCheckboxMap.has(path)) { treeCheckbox.checked = downloadCheckboxMap.get(path); } }); // 从叶子节点开始更新文件夹状态 const folderCheckboxes = Array.from(document.querySelectorAll('#treeContainer .folder-checkbox')).reverse(); folderCheckboxes.forEach(updateFolderState); } // 更新树状图复选框 function updateTreeCheckbox(downloadCheckbox) { let path = downloadCheckbox.parentElement.querySelector('a').getAttribute('data-path'); let treeCheckbox = document.querySelector(`#treeContainer .tree-checkbox[data-path="${path}"]`); if (treeCheckbox) { treeCheckbox.checked = downloadCheckbox.checked; updateParentCheckboxes(treeCheckbox); } } // 更新文件夹状态 function updateFolderState(folderCheckbox) { const folderLi = folderCheckbox.closest('li'); const allDescendants = folderLi.querySelectorAll(':scope > ul .tree-checkbox'); let allChecked = true; let anyChecked = false; for (let cb of allDescendants) { if (cb.checked || cb.indeterminate) { anyChecked = true; } if (!cb.checked) { allChecked = false; } if (anyChecked && !allChecked) break; } folderCheckbox.checked = allChecked; folderCheckbox.indeterminate = !allChecked && anyChecked; } function makeDraggable(container) { const dragHandle = container.querySelector('.drag-handle'); let isDragging = false; let startX, startY, startLeft, startTop; function onMouseDown(e) { if (e.target.closest('.drag-handle') && !e.target.closest('.search-container')) { isDragging = true; startX = e.clientX; startY = e.clientY; startLeft = container.offsetLeft; startTop = container.offsetTop; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); e.preventDefault(); } } function onMouseMove(e) { if (!isDragging) return; const dx = e.clientX - startX; const dy = e.clientY - startY; container.style.left = `${startLeft + dx}px`; container.style.top = `${startTop + dy}px`; } function onMouseUp() { isDragging = false; document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } container.addEventListener('mousedown', onMouseDown); } function add_download_button() { // 创建下载图标容器 var downloadIconContainer = document.createElement('div'); downloadIconContainer.id = "download_icon_container"; downloadIconContainer.innerHTML = ` <dotlottie-player class="download-icon" src="https://lottie.host/604bb467-91d8-46f3-a7ce-786e25f8fded/alw6gwjRdU.json" background="transparent" speed="1" style="width: 60px; height: 60px; margin: -15px;" loop autoplay onclick="toggleListVisibility()" title="点击展开或关闭列表"></dotlottie-player> `; // 设置下载图标容器的样式 downloadIconContainer.style.cssText = ` position: fixed; right: 10px; bottom: 10px; z-index: 9000; cursor: pointer; `; // 创建下载列表容器 var downloadListContainer = document.createElement('div'); downloadListContainer.id = "download_list"; downloadListContainer.style.cssText = ` z-index: 999; backdrop-filter: blur(10px); border: 2px solid #fcbb34; border-radius: 5px; max-height: 0; overflow-y: hidden; padding: 20px; flex-direction: column; align-items: flex-start; transition: max-height 0.5s ease-in-out, opacity 0.5s ease-in-out; opacity: 0; position: fixed; right: 30px; bottom: 50px; background-color: white; `; downloadListContainer.innerHTML = ` <h3 style="text-align: center;"> <span style=" background: linear-gradient(90deg, #ffa500, #ff8c00, #ffa500); background-size: 200% 100%; animation: flowingGradient 3s ease infinite; -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight:bold; ">暂无课件♪(´▽`)</span> </h3> <br> <p style="text-align: center; font-size: 12px; color: #888; font-weight:bold;">(在课程首页才能获取到资源)</p> `; // 添加下载图标的样式 var downloadIconStyle = document.createElement('style'); downloadIconStyle.innerHTML = ` .download-icon { padding: 2px; margin: -20px; background-color: rgba(255, 255, 255, 0); border-radius: 10px; transition: transform 0.3s ease; // 确保动画效果的平滑过渡 cursor: pointer; } .download-icon:hover { background-color: rgba(255, 255, 255, 0.3); transform: scale(1.1); // 悬停时放大 } `; document.head.appendChild(downloadIconStyle); document.body.appendChild(downloadIconContainer); document.body.appendChild(downloadListContainer); } // 获取token令牌 function getCookie(keyword = 'prd-access-token') { const cookies = document.cookie.split('; '); for (const cookie of cookies) { const [name, value] = cookie.split('='); if (name.includes(keyword)) { return value; } } return null; // 未找到匹配的 Cookie } function showDownloadHistory() { if (historyPopup) { return; } historyPopup = document.createElement('div'); Object.assign(historyPopup.style, { position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', backgroundColor: '#fff', padding: '30px', borderRadius: '15px', boxShadow: '0 10px 25px rgba(0, 0, 0, 0.2)', zIndex: '10000', width: '80%', maxWidth: '800px', // 增加宽度以适应搜索框 maxHeight: '80%', display: 'flex', flexDirection: 'column', overflow: 'hidden', opacity: '0', }); const title = document.createElement('h2'); title.textContent = '下载历史'; Object.assign(title.style, { textAlign: 'center', color: '#fcbb34', marginTop: '0', marginBottom: '20px', fontWeight: 'bold', }); historyPopup.historyListElement = document.createElement('ul'); Object.assign(historyPopup.historyListElement.style, { listStyleType: 'none', padding: '0', marginBottom: '20px', overflowY: 'auto', flex: '1' }); const buttonContainer = document.createElement('div'); Object.assign(buttonContainer.style, { display: 'flex', justifyContent: 'space-between', marginTop: '20px' }); const clearButton = document.createElement('button'); clearButton.textContent = '清空历史'; Object.assign(clearButton.style, { padding: '10px 20px', border: 'none', borderRadius: '5px', backgroundColor: '#fcbb34', color: '#fff', cursor: 'pointer', fontWeight: 'bold', transition: 'background-color 0.3s' }); clearButton.onmouseover = () => { clearButton.style.backgroundColor = '#fba100'; }; clearButton.onmouseout = () => { clearButton.style.backgroundColor = '#fcbb34'; }; clearButton.onclick = () => { if (confirm('确定要清空所有下载历史吗?此操作不可撤销。')) { const items = historyPopup.historyListElement.querySelectorAll('li'); // 为每个项目添加移除动画 items.forEach((item, index) => { setTimeout(() => { item.classList.add('remove-history-item'); }, index * 50); // 错开动画开始时间 }); // 等待所有动画完成后清空历史 setTimeout(() => { downloadHistory = []; localStorage.removeItem('downloadHistory'); updateHistoryList(); }, items.length * 50 + 500); // 等待所有项目的动画完成,再加上一些额外时间 } }; const closeButton = document.createElement('button'); closeButton.textContent = '关闭'; Object.assign(closeButton.style, { padding: '10px 20px', border: 'none', borderRadius: '5px', backgroundColor: '#ccc', color: '#fff', cursor: 'pointer', fontWeight: 'bold', transition: 'background-color 0.3s' }); closeButton.onmouseover = () => { closeButton.style.backgroundColor = '#bbb'; }; closeButton.onmouseout = () => { closeButton.style.backgroundColor = '#ccc'; }; closeButton.onclick = () => { const rect = historyPopup.getBoundingClientRect(); const startX = rect.left; const startY = rect.top; // 设置起始位置 historyPopup.style.top = startY + 'px'; historyPopup.style.left = startX + 'px'; historyPopup.style.transform = 'none'; // 添加关闭动画类 historyPopup.classList.remove('popup-show'); historyPopup.classList.add('popup-hide'); historyPopup.addEventListener('animationend', function() { document.body.removeChild(historyPopup); historyPopup = null; // 重置historyPopup }, { once: true }); }; buttonContainer.appendChild(clearButton); buttonContainer.appendChild(closeButton); historyPopup.appendChild(title); historyPopup.appendChild(historyPopup.historyListElement); historyPopup.appendChild(buttonContainer); document.body.appendChild(historyPopup); // 添加搜索框 const searchContainer = createSearchBox(); historyPopup.insertBefore(searchContainer, historyPopup.historyListElement); document.body.appendChild(historyPopup); // 触发显示动画 setTimeout(() => { historyPopup.classList.add('popup-show'); }, 10); updateHistoryList(); } function createSearchBox() { const searchContainer = document.createElement('div'); Object.assign(searchContainer.style, { margin: '0 0 20px 0', position: 'relative', width: '100%' }); const searchInput = document.createElement('input'); Object.assign(searchInput.style, { width: '100%', padding: '10px 40px 10px 15px', fontSize: '16px', border: '2px solid #fcbb34', borderRadius: '25px', outline: 'none', boxSizing: 'border-box', transition: 'all 0.3s ease' }); searchInput.placeholder = '搜索下载历史...'; const searchIcon = document.createElement('div'); Object.assign(searchIcon.style, { position: 'absolute', right: '15px', top: '50%', transform: 'translateY(-50%)', width: '20px', height: '20px', backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23fcbb34"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>')`, backgroundSize: 'cover', cursor: 'pointer' }); searchContainer.appendChild(searchInput); searchContainer.appendChild(searchIcon); // 添加搜索功能 searchInput.addEventListener('input', () => { filterHistory(searchInput.value); }); return searchContainer; } function filterHistory(searchTerm) { const items = historyPopup.historyListElement.querySelectorAll('li'); items.forEach((item, index) => { const text = item.textContent.toLowerCase(); const matchesSearch = text.includes(searchTerm.toLowerCase()); if (matchesSearch) { item.style.display = ''; resetListItemStyles(item); // 添加动画效果 item.style.opacity = '0'; item.style.transform = 'translateY(-20px)'; setTimeout(() => { item.style.opacity = '1'; item.style.transform = 'translateY(0)'; }, index * 50); // 错开动画开始时间 } else { item.style.display = 'none'; } }); } function resetListItemStyles(item) { Object.assign(item.style, { padding: '10px', borderBottom: '1px solid #eee', color: '#333', display: 'flex', justifyContent: 'space-between', alignItems: 'center', transition: 'opacity 0.3s, transform 0.3s' // 添加过渡效果 }); const redownloadButton = item.querySelector('button'); if (redownloadButton) { Object.assign(redownloadButton.style, { padding: '5px 10px', border: 'none', borderRadius: '3px', backgroundColor: '#4CAF50', color: '#fff', cursor: 'pointer', fontSize: '12px', transition: 'background-color 0.3s' }); } } function updateHistoryList() { if (!historyPopup || !historyPopup.historyListElement) return; const historyListElement = historyPopup.historyListElement; historyListElement.innerHTML = ''; // 过滤并保留最近3天的历史记录 const threeDaysAgo = new Date().getTime() - (3 * 24 * 60 * 60 * 1000); downloadHistory = downloadHistory.filter(item => item.time > threeDaysAgo); if (downloadHistory.length === 0) { const noHistory = showNoHistoryMessage(historyListElement); requestAnimationFrame(() => { noHistory.style.opacity = '1'; noHistory.style.transform = 'translateY(0)'; }); } else { downloadHistory.forEach((item, index) => { const listItem = createHistoryListItem(item, index); historyListElement.appendChild(listItem); // 添加一个小延迟,使动画错开 setTimeout(() => { listItem.style.opacity = '1'; listItem.style.transform = 'translateY(0)'; }, index * 50); }); } // 更新本地存储 localStorage.setItem('downloadHistory', JSON.stringify(downloadHistory)); } function showNoHistoryMessage(historyListElement) { const noHistory = document.createElement('div'); noHistory.id = 'noHistoryMessage'; noHistory.innerHTML = ` <svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 24 24" fill="none" stroke="#fcbb34" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="10"></circle> <line x1="12" y1="8" x2="12" y2="12"></line> <line x1="12" y1="16" x2="12.01" y2="16"></line> </svg> <p>暂无下载历史</p> <span>开始下载以添加记录 (⌐■_■)</span> `; Object.assign(noHistory.style, { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', color: '#888', fontFamily: "'Microsoft YaHei', sans-serif", textAlign: 'center', opacity: '0', transform: 'translateY(-20px)', transition: 'opacity 0.5s, transform 0.5s' }); const p = noHistory.querySelector('p'); Object.assign(p.style, { fontSize: '18px', fontWeight: 'bold', margin: '10px 0 5px', color: '#fcbb34' }); const span = noHistory.querySelector('span'); Object.assign(span.style, { fontSize: '14px', opacity: '0.8', fontWeight: 'bold' }); historyListElement.appendChild(noHistory); return noHistory; } function createHistoryListItem(item, index) { const listItem = document.createElement('li'); Object.assign(listItem.style, { padding: '10px', borderBottom: '1px solid #eee', color: '#333', display: 'flex', justifyContent: 'space-between', alignItems: 'center', opacity: '0', // 初始设置为透明 transform: 'translateY(-20px)' // 初始位置略微向上 }); const itemInfo = document.createElement('span'); itemInfo.textContent = `${index + 1}. ${item.filename} - ${new Date(item.time).toLocaleString()}`; const redownloadButton = createRedownloadButton(item); listItem.appendChild(itemInfo); listItem.appendChild(redownloadButton); return listItem; } function createRedownloadButton(item) { const redownloadButton = document.createElement('button'); redownloadButton.textContent = '重新下载'; Object.assign(redownloadButton.style, { padding: '5px 10px', border: 'none', borderRadius: '3px', backgroundColor: '#4CAF50', color: '#fff', cursor: 'pointer', fontSize: '12px', transition: 'background-color 0.3s' }); redownloadButton.onmouseover = () => { redownloadButton.style.backgroundColor = '#45a049'; }; redownloadButton.onmouseout = () => { redownloadButton.style.backgroundColor = '#4CAF50'; }; redownloadButton.onclick = () => { courseDownload(item.url, item.filename); }; return redownloadButton; } function createDownloadHistoryPopup() { const popup = document.createElement('div'); popup.id = 'downloadHistoryPopup'; Object.assign(popup.style, { display: 'none', position: 'fixed', zIndex: '10002', left: '50%', top: '50%', transform: 'translate(-50%, -50%)', backgroundColor: '#fff', padding: '30px', borderRadius: '15px', boxShadow: '0 10px 25px rgba(0, 0, 0, 0.2)', maxWidth: '80%', maxHeight: '80%', overflowY: 'auto', opacity: '0', transition: 'opacity 0.3s ease' }); document.body.appendChild(popup); } function saveDownloadHistory(filename, url) { const historyItem = { filename, url, time: new Date().getTime() }; downloadHistory.unshift(historyItem); // 添加到数组开头 // 过滤并保留最近3天的历史记录 const threeDaysAgo = new Date().getTime() - (3 * 24 * 60 * 60 * 1000); downloadHistory = downloadHistory.filter(item => item.time > threeDaysAgo); localStorage.setItem('downloadHistory', JSON.stringify(downloadHistory)); if (historyPopup && historyPopup.historyListElement) { // 移除"暂无下载历史"的消息(如果存在) const noHistoryMessage = historyPopup.historyListElement.querySelector('#noHistoryMessage'); if (noHistoryMessage) { noHistoryMessage.remove(); } const newListItem = createHistoryListItem(historyItem, 0); historyPopup.historyListElement.insertBefore(newListItem, historyPopup.historyListElement.firstChild); // 触发动画 setTimeout(() => { newListItem.classList.add('new-history-item'); }, 10); // 更新其他项的序号 const existingItems = historyPopup.historyListElement.querySelectorAll('li'); existingItems.forEach((item, index) => { if (index > 0) { const itemInfo = item.querySelector('span'); itemInfo.textContent = itemInfo.textContent.replace(/^\d+\./, `${index + 1}.`); } }); } } function loadDownloadHistory() { const savedHistory = localStorage.getItem('downloadHistory'); if (savedHistory) { downloadHistory = JSON.parse(savedHistory); // 在加载时也过滤掉超过3天的记录 const threeDaysAgo = new Date().getTime() - (3 * 24 * 60 * 60 * 1000); downloadHistory = downloadHistory.filter(item => item.time > threeDaysAgo); localStorage.setItem('downloadHistory', JSON.stringify(downloadHistory)); } } function initializeControlPanel() { const controlPanel = document.createElement('div'); const checkboxesContainer = document.createElement('div'); const downloadInterfaceCheckbox = document.createElement('input'); const downloadButtonCheckbox = document.createElement('input'); const progressBarCheckbox = document.createElement('input'); const videoCheckbox = document.createElement('input'); const toggleButton = document.createElement('button'); const tipsDisplay = document.createElement('div'); const showCourseNameText = document.createElement('div'); let isControlPanelVisible = false; // 创建Lottie动画播放器元素 const lottiePlayer = document.createElement('dotlottie-player'); lottiePlayer.setAttribute('src', "https://lottie.host/4f5910c1-63a3-4ffa-965c-7c0e46a29928/PCa2EgPj4N.json"); lottiePlayer.setAttribute('background', 'transparent'); lottiePlayer.setAttribute('speed', '1'); lottiePlayer.style.width = '100%'; lottiePlayer.style.height = '100%'; lottiePlayer.style.position = 'absolute'; lottiePlayer.style.zIndex = '-2'; lottiePlayer.style.top = '0'; lottiePlayer.style.left = '0'; lottiePlayer.setAttribute('loop', ''); lottiePlayer.setAttribute('autoplay', ''); // 模糊效果 const blurredBackground = document.createElement('div'); blurredBackground.style.position = 'absolute'; blurredBackground.style.top = '0'; blurredBackground.style.left = '0'; blurredBackground.style.right = '0'; blurredBackground.style.bottom = '0'; blurredBackground.style.zIndex = '-1'; blurredBackground.style.backdropFilter = 'blur(3px)'; blurredBackground.style.backgroundColor = 'rgba(255, 255, 255, 0.6)'; // 将 blurredBackground 添加到 controlPanel 中 controlPanel.appendChild(blurredBackground); // 设置控制面板样式 controlPanel.style.position = 'fixed'; controlPanel.style.top = '210px'; controlPanel.style.right = isControlPanelVisible ? '40px' : '-700px'; controlPanel.style.zIndex = '10000'; controlPanel.style.backgroundColor = 'transparent'; controlPanel.style.padding = '10px'; controlPanel.style.borderRadius = '5px'; controlPanel.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)'; controlPanel.style.border = '1px solid #fcbb34'; controlPanel.style.transition = 'right 0.3s ease-in-out, width 0.3s ease-in-out, height 0.3s ease-in-out'; // 添加 width 和 height 的过渡 controlPanel.style.overflow = 'hidden'; controlPanel.style.width = '600px'; controlPanel.style.height = '260px'; controlPanel.appendChild(lottiePlayer); controlPanel.appendChild(tipsDisplay); controlPanel.appendChild(showCourseNameText); // 创建横向菜单容器 const menuContainer = document.createElement('div'); menuContainer.style.display = 'flex'; menuContainer.style.justifyContent = 'space-around'; menuContainer.style.marginBottom = '15px'; menuContainer.style.borderBottom = '2px solid #fcbb34'; menuContainer.style.padding = '10px 0'; const menuItems = [ { text: '<strong>消息管理</strong>', category: 'message', icon: '📨' }, { text: '<strong>下载管理</strong>', category: 'download', icon: '📥' }, { text: '<strong>导出功能</strong>', category: 'export', icon: '📤' }, { text: '<strong>显示设置</strong>', category: 'display', icon: '🖥️' }, { text: '<strong>检查更新</strong>', category: 'update', icon: '🔎' } ]; menuItems.forEach(item => { const menuItem = document.createElement('div'); menuItem.innerHTML = `${item.icon} <span>${item.text}</span>`; menuItem.style.padding = '8px 12px'; menuItem.style.cursor = 'pointer'; menuItem.style.borderRadius = '20px'; menuItem.style.transition = 'all 0.3s ease'; menuItem.dataset.category = item.category; menuItem.addEventListener('mouseover', () => { menuItem.style.backgroundColor = 'rgba(252, 187, 52, 0.2)'; }); menuItem.addEventListener('mouseout', () => { if (!menuItem.classList.contains('active')) { menuItem.style.backgroundColor = 'transparent'; } }); menuContainer.appendChild(menuItem); }); menuContainer.addEventListener('click', (event) => { const clickedItem = event.target.closest('div[data-category]'); if (clickedItem) { const category = clickedItem.dataset.category; // 更新菜单项样式 menuContainer.querySelectorAll('div[data-category]').forEach(mi => { mi.classList.toggle('active', mi.dataset.category === category); mi.style.backgroundColor = mi.dataset.category === category ? 'rgba(252, 187, 52, 0.2)' : 'transparent'; mi.style.color = mi.dataset.category === category ? '#fcbb34' : '#000'; mi.style.fontWeight = mi.dataset.category === category ? 'bold' : 'normal'; }); if (category === 'display') { controlPanel.style.width = '620px'; controlPanel.style.height = '390px'; } else if (category === 'update') { controlPanel.style.width = '610px'; controlPanel.style.height = '330px'; } else { controlPanel.style.width = '600px'; controlPanel.style.height = '260px'; } // 更新容器可见性 [messageContainer, downloadContainer, exportContainer, displayContainer, updateContainer].forEach(container => { container.style.display = container.dataset.category === category ? 'block' : 'none'; }); console.log(`Switched to category: ${category}`); } }); // 添加一个动画效果 const styleSheet = document.createElement('style'); styleSheet.type = 'text/css'; styleSheet.innerText = ` @keyframes fadeIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } } #controlPanel > div { animation: fadeIn 0.3s ease-out; } `; document.head.appendChild(styleSheet); // 将菜单容器添加到控制面板 controlPanel.prepend(menuContainer); // 创建一个容器来包装按钮和装饰元素 const Beautifulupdater = document.createElement('div'); Beautifulupdater.style.position = 'relative'; Beautifulupdater.style.width = '200px'; Beautifulupdater.style.margin = '20px auto'; Beautifulupdater.style.display = 'flex'; Beautifulupdater.style.flexDirection = 'column'; Beautifulupdater.style.justifyContent = 'center'; Beautifulupdater.style.alignItems = 'center'; // 创建更新按钮容器 const updateButtonContainer = document.createElement('div'); updateButtonContainer.style.textAlign = 'center'; // 居中对齐 updateButtonContainer.style.marginTop = '20px'; // 创建更新按钮 const updateButton = document.createElement('button'); updateButton.id = 'updateButton'; updateButton.textContent = '检查更新'; updateButton.style.padding = '10px 20px'; updateButton.style.border = 'none'; updateButton.style.borderRadius = '25px'; updateButton.style.background = 'linear-gradient(90deg, #ffa500, #ff8c00, #ffa500)'; updateButton.style.backgroundSize = '200% 100%'; updateButton.style.animation = 'flowingGradient 3s ease infinite'; updateButton.style.color = '#fff'; updateButton.style.cursor = 'pointer'; updateButton.style.fontWeight = 'bold'; updateButton.style.fontSize = '16px'; updateButton.style.transition = 'all 0.3s ease'; updateButton.style.zIndex = '2'; updateButtonContainer.appendChild(updateButton); // 添加悬浮和点击效果 updateButton.onmouseover = () => { updateButton.style.transform = 'scale(1.1)'; updateButton.style.boxShadow = '0 0 15px rgba(255, 165, 0, 0.7)'; }; updateButton.onmouseout = () => { updateButton.style.transform = 'scale(1)'; updateButton.style.boxShadow = 'none'; }; updateButton.onmousedown = () => { updateButton.style.transform = 'scale(0.95)'; }; updateButton.onmouseup = () => { updateButton.style.transform = 'scale(1.1)'; }; // 创建版本号容器 const versionContainer = document.createElement('div'); versionContainer.style.textAlign = 'center'; // 居中对齐 // 添加版本号显示 const versionDisplay = document.createElement('div'); versionDisplay.textContent = `V${GM_info.script.version}`; versionDisplay.style.cssText = ` margin-top: 10px; font-size: 14px; font-weight: bold; color: #fff; padding: 4px 8px; border-radius: 4px; text-align: center; background: linear-gradient(45deg, #f0932b, #fcbb34); box-shadow: 0 0 10px rgba(255, 165, 0, 0.5); animation: glowPulse 2s ease-in-out infinite; cursor: pointer; `; versionContainer.appendChild(versionDisplay); // 添加点击计数器和事件监听器 let clickCount = 0; let lastClickTime = 0; versionDisplay.addEventListener('click', (e) => { e.stopPropagation(); // 防止事件冒泡 const currentTime = new Date().getTime(); if (currentTime - lastClickTime > 1000) { clickCount = 0; } clickCount++; lastClickTime = currentTime; if (clickCount === 5) { activateEasterEgg(); clickCount = 0; } }); // 添加视觉反馈 versionDisplay.addEventListener('mousedown', () => { versionDisplay.style.transform = 'scale(0.95)'; }); versionDisplay.addEventListener('mouseup', () => { versionDisplay.style.transform = 'scale(1)'; }); // 创建装饰元素 const createDecorElement = (color, size, top, left, animationDelay) => { const element = document.createElement('div'); element.style.position = 'absolute'; element.style.width = `${size}px`; element.style.height = `${size}px`; element.style.borderRadius = '50%'; element.style.backgroundColor = color; element.style.top = `${top}px`; element.style.left = `${left}px`; element.style.animation = `float 3s ease-in-out infinite`; element.style.animationDelay = `${animationDelay}s`; element.style.zIndex = '1'; return element; }; // 添加装饰元素 Beautifulupdater.appendChild(createDecorElement('#FFD700', 10, 10, 20, 0)); Beautifulupdater.appendChild(createDecorElement('#FFA07A', 15, 70, 30, 0.5)); Beautifulupdater.appendChild(createDecorElement('#98FB98', 12, 20, 160, 1)); Beautifulupdater.appendChild(createDecorElement('#87CEFA', 8, 80, 170, 1.5)); // 添加到容器 Beautifulupdater.appendChild(versionContainer); Beautifulupdater.appendChild(updateButtonContainer); // 添加动画样式 const style = document.createElement('style'); style.textContent = ` @keyframes flowingGradient { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } @keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-10px); } } `; document.head.appendChild(style); function compareVersions(currentVersion, latestVersion) { const currentParts = currentVersion.split('.').map(Number); const latestParts = latestVersion.split('.').map(Number); const maxLength = Math.max(currentParts.length, latestParts.length); for (let i = 0; i < maxLength; i++) { // 确保每个部分的位数相同 const currentPart = String(currentParts[i] || 0).padStart(3, '0'); // 补零到3位 const latestPart = String(latestParts[i] || 0).padStart(3, '0'); if (latestPart > currentPart) { return -1; // 最新版本较大 } else if (currentPart > latestPart) { return 1; // 当前版本较大 } } return 0; // 版本相同 } // 按钮点击事件:检查更新 updateButton.onclick = function() { const scriptInfo = GM_info; const currentVersion = scriptInfo.script.version; // 构建元数据文件的 URL const metaURL = scriptInfo.script.downloadURL.replace(/\.user\.js$/, '.meta.js') + '?t=' + Date.now(); fetch(metaURL) .then(response => response.text()) .then(text => { console.log('Fetched meta data:', text); // 打印获取到的元数据内容 const match = text.match(/\/\/\s*@version\s+([\d.]+)/); if (match && match[1]) { const latestVersion = match[1]; const comparisonResult = compareVersions(currentVersion, latestVersion); if (comparisonResult < 0) { if (confirm(`检测到新版本 ${latestVersion},是否立即更新?`)) { window.location.href = scriptInfo.script.downloadURL; } } else { alert('当前已是最新版本!'); } } else { throw new Error('无法从更新信息中提取版本号。'); } }) .catch(error => { console.error('更新检查失败:', error); alert('更新检查失败,请稍后再试。'); }); }; // 定义CSS const hoverGlowName = 'hover-glow'; const styleSheet1 = document.createElement('style'); styleSheet1.type = 'text/css'; styleSheet1.innerText = ` @keyframes ${hoverGlowName} { from { box-shadow: 0 0 8px #fcbb34; } to { box-shadow: 0 0 20px #fcbb34, 0 0 30px #fcbb34; } } #toggleButton:hover { animation: ${hoverGlowName} 1s ease-in-out infinite alternate; } .switch { position: relative; display: inline-block; width: 50px; height: 26px; } .switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 26px; } .slider:before { position: absolute; content: ""; height: 20px; width: 20px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; } input:checked + .slider { background-color: #FF8C00; /* 改为橙色 */ } input:checked + .slider:before { transform: translateX(24px); } /* 显示/隐藏图标 */ .slider:after { content: "•"; position: absolute; top: 50%; left: 30px; transform: translateY(-50%); color: white; font-size: 20px; opacity: 0; transition: .4s; } input:checked + .slider:after { opacity: 1; left: 8px; } /* 悬停效果 */ .slider:hover { box-shadow: 0 0 5px rgba(255, 140, 0, 0.5); /* 改为橙色 */ } .slider:hover:before { animation: pulse 1.5s infinite; } /* 脉冲动画 */ @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(255, 140, 0, 0.7); } /* 改为橙色 */ 70% { box-shadow: 0 0 0 10px rgba(255, 140, 0, 0); } /* 改为橙色 */ 100% { box-shadow: 0 0 0 0 rgba(255, 140, 0, 0); } /* 改为橙色 */ } .switch:hover .slider:before { animation: pulse 1.5s infinite; } /* 标签 */ .switch::after { content: attr(data-label); position: absolute; right: -65px; top: 50%; transform: translateY(-50%); font-size: 12px; color: #555; } input:checked + .slider:before { transform: translateX(24px); transition: .4s cubic-bezier(0.68, -0.55, 0.27, 1.55); } .slider:before { transition: .4s, transform .2s; } input:checked + .slider:before { transform: translateX(24px) scale(1.1); } .slider { background: linear-gradient(to right, #ccc 50%, #FF8C00 50%); background-size: 200% 100%; background-position: left bottom; transition: all .4s ease; } input:checked + .slider { background-position: right bottom; } #allTipsContainer::-webkit-scrollbar { width: 10px; } #allTipsContainer::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 10px; } #allTipsContainer::-webkit-scrollbar-thumb { background: #fcbb34; border-radius: 10px; } #allTipsContainer::-webkit-scrollbar-thumb:hover { background: #e67e22; } .tip-item { background-color: #fff8e1; border-left: 4px solid #fcbb34; padding: 15px; margin-bottom: 15px; border-radius: 5px; transition: all 0.2s ease; } .tip-item:hover { transform: translateX(5px); box-shadow: 0 5px 15px rgba(0,0,0,0.1); } #closeTipsBtn { background-color: #fcbb34; color: white; border: none; padding: 10px 20px; border-radius: 25px; cursor: pointer; font-size: 16px; font-weight: bold; transition: all 0.2s ease; display: block; margin: 20px auto 0; } #closeTipsBtn:hover { background-color: #e67e22; transform: scale(1.05); } @keyframes fadeIn { from { opacity: 0; transform: translate(-50%, -60%); } to { opacity: 1; transform: translate(-50%, -50%); } } #allTipsContainer.show { animation: fadeIn 0.3s ease-out forwards; } ${style.textContent} @keyframes popOutToCenter { 0% { transform: translate(var(--start-x), var(--start-y)) scale(0.7); opacity: 0; } 100% { transform: translate(-50%, -50%) scale(1); opacity: 1; } } @keyframes popInToOrigin { 0% { transform: translate(-50%, -50%) scale(1); opacity: 1; } 100% { transform: translate(var(--end-x), var(--end-y)) scale(0.7); opacity: 0; } } @keyframes fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } @keyframes typing { from { width: 0 } to { width: 100% } } @keyframes blink-caret { from, to { border-color: transparent } 50% { border-color: #fcbb34; } } .tip-item { background: linear-gradient(45deg, #f3f4f6, #fff); border-left: 4px solid #fcbb34; padding: 15px; margin-bottom: 15px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); transition: all 0.3s ease; animation: fadeInUp 0.5s ease-out both; animation-play-state: paused; } opacity: 0; } .tip-item:hover { transform: translateX(5px); box-shadow: 0 5px 15px rgba(0,0,0,0.1); } .tip-number { display: inline-block; background-color: #fcbb34; color: white; width: 24px; height: 24px; line-height: 24px; text-align: center; border-radius: 50%; margin-right: 10px; } .tip-text { display: inline-block; vertical-align: middle; } #allTipsContainer h2 { font-size: 28px; font-weight: bold; text-align: center; margin-bottom: 20px; color: #fcbb34; overflow: hidden; border-right: .15em solid #fcbb34; white-space: nowrap; letter-spacing: .15em; animation: typing 3.5s steps(40, end), blink-caret .75s step-end infinite; } `; document.head.appendChild(styleSheet1); // 设置切换按钮样式 const gearIcon = document.createElement('span'); gearIcon.textContent = '⚙️'; gearIcon.style.fontSize = '16px'; // 保持与按钮相同的字体大小 // 齿轮图标的样式 gearIcon.style.transition = 'transform 0.3s ease'; gearIcon.style.display = 'inline-block'; gearIcon.style.verticalAlign = 'middle'; // 将齿轮图标添加到按钮中 toggleButton.appendChild(gearIcon); toggleButton.id = 'toggleButton'; toggleButton.style.fontSize = '16px'; toggleButton.title = '控制面板'; toggleButton.style.position = 'fixed'; toggleButton.style.top = '210px'; toggleButton.style.right = '0'; toggleButton.style.zIndex = '10001'; toggleButton.style.backgroundColor = '#fcbb34'; toggleButton.style.boxShadow = '0 4px 8px 0 rgba(0, 0, 0, 0.2)'; toggleButton.style.color = '#fff'; toggleButton.style.border = 'none'; toggleButton.style.borderRadius = '5px 0 0 5px'; toggleButton.style.padding = '10px'; toggleButton.style.cursor = 'pointer'; toggleButton.style.transition = 'right 0.3s ease-in-out, transform 0.3s ease'; // 添加悬停效果 toggleButton.style.transform = 'scale(1)'; // 默认比例 toggleButton.addEventListener('mouseover', function() { toggleButton.style.transform = 'scale(1.1)'; // 悬停时放大 }); toggleButton.addEventListener('mouseout', function() { toggleButton.style.transform = 'scale(1)'; // 鼠标移出时恢复原大小 }); // 切换按钮点击事件 toggleButton.addEventListener('click', function() { isControlPanelVisible = !isControlPanelVisible; controlPanel.style.right = isControlPanelVisible ? '40px' : '-700px'; gearIcon.style.transform = 'rotate(360deg)'; gearIcon.addEventListener('transitionend', function() { gearIcon.style.transform = 'none'; }, { once: true }); }); // 创建分类容器 const messageContainer = document.createElement('div'); messageContainer.dataset.category = 'message'; messageContainer.style.justifyContent = 'space-between'; messageContainer.style.width = '100%'; const downloadContainer = document.createElement('div'); downloadContainer.dataset.category = 'download'; downloadContainer.style.justifyContent = 'space-between'; downloadContainer.style.width = '100%'; const exportContainer = document.createElement('div'); exportContainer.dataset.category = 'export'; exportContainer.style.justifyContent = 'space-between'; exportContainer.style.width = '100%'; const displayContainer = document.createElement('div'); displayContainer.dataset.category = 'display'; displayContainer.style.justifyContent = 'space-between'; displayContainer.style.width = '100%'; const updateContainer = document.createElement('div'); updateContainer.dataset.category = 'update'; updateContainer.style.justifyContent = 'space-between'; updateContainer.style.width = '100%'; // 初始化复选框状态 function initializeCheckboxState(checkbox, index) { const savedState = localStorage.getItem(`checkbox-${index}`); checkbox.checked = savedState === null ? true : savedState === 'true'; progressBarCheckbox.addEventListener('change', () => { isProgressBarVisible = progressBarCheckbox.checked; updateVisibility(); }); } // 保存复选框状态 function saveCheckboxState(checkbox, index) { localStorage.setItem(`checkbox-${index}`, checkbox.checked); } // 初始化复选框 [downloadInterfaceCheckbox, downloadButtonCheckbox, progressBarCheckbox, videoCheckbox].forEach((checkbox, index) => { checkbox.type = 'checkbox'; initializeCheckboxState(checkbox, index); // 调用函数以应用保存的状态 checkbox.id = `checkbox-${index}`; checkbox.style.display = 'none'; // 隐藏原始复选框 const label = document.createElement('label'); label.className = 'switch'; label.htmlFor = `checkbox-${index}`; const slider = document.createElement('span'); slider.className = 'slider'; label.appendChild(checkbox); label.appendChild(slider); const labelText = document.createElement('span'); labelText.textContent = ['右上角下载栏', '右下角下载栏', '左下角进度条', '右侧视频组件'][index]; // 增强文字样式 labelText.style.color = '#fcbb34'; labelText.style.marginRight = '15px'; labelText.style.fontWeight = '600'; labelText.style.fontSize = '16px'; // 略微增大字体 labelText.style.fontFamily = 'Microsoft YaHei' labelText.style.letterSpacing = '0.5px'; labelText.style.textShadow = '1px 1px 2px rgba(0, 0, 0, 0.1)'; labelText.style.transition = 'all 0.3s ease'; labelText.style.cursor = 'pointer'; // 添加悬停效果 labelText.addEventListener('mouseover', () => { labelText.style.transform = 'translateY(-2px)'; labelText.style.textShadow = '2px 2px 4px rgba(0, 0, 0, 0.2)'; }); labelText.addEventListener('mouseout', () => { labelText.style.transform = 'translateY(0)'; labelText.style.textShadow = '1px 1px 2px rgba(0, 0, 0, 0.1)'; }); checkbox.addEventListener('change', () => { if (index === 1) { updateDownloadListVisibility(); // 仅当右下角下载列表复选框变化时,调用此函数 } else { updateVisibility(); // 否则调用一般的更新可见性函数 } saveCheckboxState(checkbox, index); // 保存复选框状态 }); const container = document.createElement('div'); container.style.display = 'flex'; container.style.justifyContent = 'space-between'; container.style.alignItems = 'center'; container.style.padding = '10px'; container.style.position = 'relative'; // 为伪元素定位 // 添加文字 container.appendChild(labelText); // 创建装饰元素容器 const decorationContainer = document.createElement('div'); decorationContainer.style.display = 'flex'; decorationContainer.style.alignItems = 'center'; decorationContainer.style.margin = '0 15px'; // 添加装饰线 const decorativeLine = document.createElement('div'); decorativeLine.style.width = '40px'; decorativeLine.style.height = '2px'; decorativeLine.style.background = 'linear-gradient(to right, #ff9800, #ff5722)'; // 添加动态小圆点 (第一个) const dotContainer1 = document.createElement('div'); dotContainer1.style.position = 'relative'; dotContainer1.style.width = '20px'; dotContainer1.style.height = '20px'; const dot1 = document.createElement('div'); dot1.style.position = 'absolute'; dot1.style.top = '50%'; dot1.style.left = '50%'; dot1.style.width = '8px'; dot1.style.height = '8px'; dot1.style.borderRadius = '50%'; dot1.style.backgroundColor = '#ff9800'; dot1.style.transform = 'translate(-50%, -50%)'; dot1.style.animation = 'pulse 2s infinite'; dotContainer1.appendChild(dot1); // 添加动态小圆点 (第二个) const dotContainer2 = document.createElement('div'); dotContainer2.style.position = 'relative'; dotContainer2.style.width = '20px'; dotContainer2.style.height = '20px'; const dot2 = document.createElement('div'); dot2.style.position = 'absolute'; dot2.style.top = '50%'; dot2.style.left = '50%'; dot2.style.width = '8px'; dot2.style.height = '8px'; dot2.style.borderRadius = '50%'; dot2.style.backgroundColor = '#ff9800'; dot2.style.transform = 'translate(-50%, -50%)'; dot2.style.animation = 'pulse 2s infinite'; dotContainer2.appendChild(dot2); // 将装饰元素添加到装饰容器 decorationContainer.appendChild(dotContainer1); decorationContainer.appendChild(decorativeLine); decorationContainer.appendChild(dotContainer2); // 将装饰容器添加到主容器 container.appendChild(decorationContainer); // 添加开关 container.appendChild(label); checkboxesContainer.appendChild(container); }); // 创建第一个按钮容器(用于已读和清空消息按钮) const messageButtonContainer = document.createElement('div'); messageButtonContainer.style.display = 'flex'; messageButtonContainer.style.justifyContent = 'space-between'; messageButtonContainer.style.marginTop = '10px'; // 创建"已读所有消息"按钮 const markReadButton = document.createElement('button'); markReadButton.id = 'markReadButton'; markReadButton.textContent = '已读所有消息'; markReadButton.title = '刷新页面以更新状态'; // 添加样式 markReadButton.style.cssText = ` background-color: #2ecc71; /* 绿色 */ border: none; color: white; padding: 10px 20px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; font-weight: bold; border-radius: 5px; cursor: pointer; transition: all 0.3s ease; box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); `; // 添加hover效果 markReadButton.onmouseover = function() { markReadButton.style.backgroundColor = '#27ae60'; /* 深绿色 */ markReadButton.style.transform = 'translateY(-2px)'; /* 轻微上移 */ markReadButton.style.boxShadow = '0px 6px 8px rgba(0, 0, 0, 0.15)'; /* 加深阴影 */ }; markReadButton.onmouseout = function() { markReadButton.style.backgroundColor = '#2ecc71'; /* 回复绿色 */ markReadButton.style.transform = 'translateY(0)'; /* 回复原位 */ markReadButton.style.boxShadow = '0px 4px 6px rgba(0, 0, 0, 0.1)'; /* 回复阴影 */ }; // 绑定点击事件处理函数 markReadButton.onclick = function() { markAllMessagesAsRead(); }; // 定义标记所有消息为已读的函数 async function markAllMessagesAsRead() { const token = getCookie(); const hostname = window.location.hostname; const pageSize = 20; const messageTypes = ["todo", "group", "personal", "system"]; let hasUnreadMessages = false; for (const messageType of messageTypes) { try { let pageIndex = 1; let unreadMessageIds = []; while (true) { const apiUrl = `https://${hostname}/api/jx-iresource/message/im/${messageType}?page_size=${pageSize}&page_index=${pageIndex}&msg_status=0`; const response = await fetch(apiUrl, { headers: { "Authorization": `Bearer ${token}` } }); if (response.ok) { const data = await response.json(); const messages = data.data.list; if (messages.length === 0 || pageIndex * pageSize >= data.data.total) { break; // 没有更多消息或已经获取所有消息,退出循环 } unreadMessageIds = unreadMessageIds.concat(messages.map(message => message.id)); pageIndex++; } else { console.error(`获取未读${messageType}消息失败:`, response.status, response.statusText); alert(`获取未读${messageType}消息失败,请重试`); return; } } if (unreadMessageIds.length > 0) { hasUnreadMessages = true; const updateResponse = await fetch(`https://${hostname}/api/jx-iresource/message/im/updateStatus`, { method: "PUT", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }, body: JSON.stringify({ message_ids: unreadMessageIds, status: 1 }) }); if (updateResponse.ok) { console.log(`所有${messageType}消息已标记为已读`); } else { console.error(`标记${messageType}消息失败:`, updateResponse.status, updateResponse.statusText); alert(`标记${messageType}消息失败,请重试`); return; } } else { console.log(`没有未读${messageType}消息`); } } catch (error) { console.error('Error:', error); alert(`标记${messageType}消息失败,请重试`); return; } } if (hasUnreadMessages) { alert("所有消息已标记为已读"); } else { console.log("没有未读消息"); alert("没有未读消息"); } // 调用API更新未读消息计数 try { const updateCountResponse = await fetch(`https://${hostname}/api/jx-iresource/message/im/un_read_count`, { headers: { "Authorization": `Bearer ${token}` } }); if (updateCountResponse.ok) { const countData = await updateCountResponse.json(); console.log("未读消息计数已更新:", countData); // 这里可以添加更新页面显示的逻辑 } else { console.error("更新未读消息计数失败:", updateCountResponse.status, updateCountResponse.statusText); } } catch (error) { console.error("更新未读消息计数时发生错误:", error); } } // 创建"清空所有消息"按钮 const clearMessagesButton = document.createElement('button'); clearMessagesButton.id = 'clearMessagesButton'; clearMessagesButton.textContent = '清空所有消息'; clearMessagesButton.title = '刷新页面以更新状态'; // 添加样式 clearMessagesButton.style.cssText = ` background-color: #e74c3c; /* 红色 */ border: none; color: white; padding: 10px 20px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; font-weight: bold; border-radius: 5px; cursor: pointer; transition: all 0.3s ease; box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); `; // 添加hover效果 clearMessagesButton.onmouseover = function() { clearMessagesButton.style.backgroundColor = '#c0392b'; /* 深红色 */ clearMessagesButton.style.transform = 'translateY(-2px)'; /* 轻微上移 */ clearMessagesButton.style.boxShadow = '0px 6px 8px rgba(0, 0, 0, 0.15)'; /* 加深阴影 */ }; clearMessagesButton.onmouseout = function() { clearMessagesButton.style.backgroundColor = '#e74c3c'; /* 回复红色 */ clearMessagesButton.style.transform = 'translateY(0)'; /* 回复原位 */ clearMessagesButton.style.boxShadow = '0px 4px 6px rgba(0, 0, 0, 0.1)'; /* 回复阴影 */ }; // 绑定点击事件处理函数 clearMessagesButton.onclick = function() { if (confirm("确定要清空所有消息吗?该操作无法撤销。")) { clearAllMessages(); } }; async function clearAllMessages() { const token = getCookie(); const hostname = window.location.hostname; const pageSize = 20; const messageTypes = ["todo", "group", "personal", "system"]; let hasMessagesToClear = false; for (const messageType of messageTypes) { try { let pageIndex = 1; let messageIdsToClear = []; while (true) { const apiUrl = `https://${hostname}/api/jx-iresource/message/im/${messageType}?page_size=${pageSize}&page_index=${pageIndex}`; const response = await fetch(apiUrl, { headers: { "Authorization": `Bearer ${token}` } }); if (response.ok) { const data = await response.json(); const messages = data.data.list; if (messages.length === 0 || pageIndex * pageSize >= data.data.total) { break; // 没有更多消息或已经获取所有消息,退出循环 } messageIdsToClear = messageIdsToClear.concat(messages.map(message => message.id)); pageIndex++; } else { console.error(`获取${messageType}消息失败:`, response.status, response.statusText); alert(`获取${messageType}消息失败,请重试`); return; } } if (messageIdsToClear.length > 0) { hasMessagesToClear = true; const clearResponse = await fetch(`https://${hostname}/api/jx-iresource/message/im/selected/empty`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }, body: JSON.stringify({ message_ids: messageIdsToClear }) }); if (clearResponse.ok) { console.log(`所有${messageType}消息已清空`); } else { const errorText = await clearResponse.text(); console.error(`清空${messageType}消息失败:`, errorText); alert(`清空${messageType}消息失败: ${errorText}`); return; } } else { console.log(`没有${messageType}消息可清空`); } } catch (error) { console.error('Error:', error); alert(`清空${messageType}消息出错: ${error.message}`); return; } } if (hasMessagesToClear) { alert("所有消息已清空"); } else { console.log("没有消息可清空"); alert("没有消息可清空"); } // 调用API更新未读消息计数 try { const updateCountResponse = await fetch(`https://${hostname}/api/jx-iresource/message/im/un_read_count`, { headers: { "Authorization": `Bearer ${token}` } }); if (updateCountResponse.ok) { const countData = await updateCountResponse.json(); console.log("未读消息计数已更新:", countData); // 这里可以添加更新页面显示的逻辑 } else { console.error("更新未读消息计数失败:", updateCountResponse.status, updateCountResponse.statusText); } } catch (error) { console.error("更新未读消息计数时发生错误:", error); } } // 将按钮添加到第一个容器 messageButtonContainer.appendChild(markReadButton); messageButtonContainer.appendChild(clearMessagesButton); // 将第一个按钮容器添加到控制面板 controlPanel.appendChild(messageButtonContainer); // 创建第二个按钮容器(用于下载历史按钮和未来可能的其他按钮) const downloadButtonContainer = document.createElement('div'); downloadButtonContainer.style.display = 'flex'; downloadButtonContainer.style.justifyContent = 'space-between'; downloadButtonContainer.style.marginTop = '10px'; // 创建"查看下载历史"按钮 const showHistoryButton = document.createElement('button'); showHistoryButton.id = 'showHistoryButton'; showHistoryButton.textContent = '查看下载历史'; showHistoryButton.title = '导出的文件不会被记录'; // 添加样式 showHistoryButton.style.cssText = ` background-color: #3498db; /* 蓝色 */ border: none; color: white; padding: 10px 20px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; font-weight: bold; border-radius: 5px; cursor: pointer; transition: all 0.3s ease; box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); `; // 添加hover效果 showHistoryButton.onmouseover = function() { showHistoryButton.style.backgroundColor = '#2980b9'; /* 深蓝色 */ showHistoryButton.style.transform = 'translateY(-2px)'; /* 轻微上移 */ showHistoryButton.style.boxShadow = '0px 6px 8px rgba(0, 0, 0, 0.15)'; /* 加深阴影 */ }; showHistoryButton.onmouseout = function() { showHistoryButton.style.backgroundColor = '#3498db'; /* 回复蓝色 */ showHistoryButton.style.transform = 'translateY(0)'; /* 回复原位 */ showHistoryButton.style.boxShadow = '0px 4px 6px rgba(0, 0, 0, 0.1)'; /* 回复阴影 */ }; showHistoryButton.onclick = showDownloadHistory; // 将"查看下载历史"按钮添加到第二个容器 downloadButtonContainer.appendChild(showHistoryButton); // 创建第三方下载切换按钮 const thirdPartyDownloadButton = document.createElement('button'); thirdPartyDownloadButton.id = 'thirdPartyDownloadButton'; thirdPartyDownloadButton.title = '切换下载方式'; // 添加样式 thirdPartyDownloadButton.style.cssText = ` background-color: #f39c12; /* 橙色 */ border: none; color: white; padding: 10px 20px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; font-weight: bold; border-radius: 5px; cursor: pointer; transition: all 0.3s ease; box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); `; // 添加hover效果 thirdPartyDownloadButton.onmouseover = function() { thirdPartyDownloadButton.style.transform = 'translateY(-2px)'; /* 轻微上移 */ thirdPartyDownloadButton.style.boxShadow = '0px 6px 8px rgba(0, 0, 0, 0.15)'; /* 加深阴影 */ }; thirdPartyDownloadButton.onmouseout = function() { thirdPartyDownloadButton.style.transform = 'translateY(0)'; /* 回复原位 */ thirdPartyDownloadButton.style.boxShadow = '0px 4px 6px rgba(0, 0, 0, 0.1)'; /* 回复阴影 */ }; // 从localStorage中获取当前状态 let useThirdPartyDownload = localStorage.getItem('useThirdPartyDownload') === 'true'; updateThirdPartyDownloadButtonState(); thirdPartyDownloadButton.onclick = function() { useThirdPartyDownload = !useThirdPartyDownload; localStorage.setItem('useThirdPartyDownload', useThirdPartyDownload); updateThirdPartyDownloadButtonState(); }; function updateThirdPartyDownloadButtonState() { if (useThirdPartyDownload) { thirdPartyDownloadButton.textContent = '用第三方下载'; thirdPartyDownloadButton.style.backgroundColor = '#27ae60'; // 绿色 } else { thirdPartyDownloadButton.textContent = '用浏览器下载'; thirdPartyDownloadButton.style.backgroundColor = '#f39c12'; // 橙色 } } // 将按钮添加到第二个容器 downloadButtonContainer.appendChild(thirdPartyDownloadButton); // 将第二个按钮容器添加到控制面板 controlPanel.appendChild(downloadButtonContainer); // 创建第三个按钮容器 const exportButtonContainer = document.createElement('div'); exportButtonContainer.style.justifyContent = 'space-between'; exportButtonContainer.style.display = 'flex'; exportButtonContainer.style.marginTop = '10px'; // 创建"导出ef2文件"按钮 const exportEf2Button = document.createElement('button'); exportEf2Button.id = 'exportEf2Button'; exportEf2Button.textContent = '导出EF2文件'; exportEf2Button.title = 'IDM专属导入格式'; // 添加样式 exportEf2Button.style.cssText = ` background-color: #3498db; /* 蓝色 */ border: none; color: white; padding: 10px 20px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; font-weight: bold; border-radius: 5px; cursor: pointer; transition: all 0.3s ease; box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); `; // 添加hover效果 exportEf2Button.onmouseover = function() { exportEf2Button.style.backgroundColor = '#2980b9'; /* 深蓝色 */ exportEf2Button.style.transform = 'translateY(-2px)'; /* 轻微上移 */ exportEf2Button.style.boxShadow = '0px 6px 8px rgba(0, 0, 0, 0.15)'; /* 加深阴影 */ }; exportEf2Button.onmouseout = function() { exportEf2Button.style.backgroundColor = '#3498db'; /* 回复蓝色 */ exportEf2Button.style.transform = 'translateY(0)'; /* 回复原位 */ exportEf2Button.style.boxShadow = '0px 4px 6px rgba(0, 0, 0, 0.1)'; /* 回复阴影 */ }; exportEf2Button.onclick = exportToEf2; // 将"导出ef2文件"按钮添加到第三个容器 exportButtonContainer.appendChild(exportEf2Button); // 创建"导出txt文件"按钮 const exportTxtButton = document.createElement('button'); exportTxtButton.id = 'exportTxtButton'; exportTxtButton.textContent = '导出TXT文件'; exportTxtButton.title = '绝大多数下载软件都支持'; // 添加样式 exportTxtButton.style.cssText = ` background-color: #2ecc71; /* 绿色 */ border: none; color: white; padding: 10px 20px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; font-weight: bold; border-radius: 5px; cursor: pointer; transition: all 0.3s ease; box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); `; // 添加hover效果 exportTxtButton.onmouseover = function() { exportTxtButton.style.backgroundColor = '#27ae60'; /* 深绿色 */ exportTxtButton.style.transform = 'translateY(-2px)'; /* 轻微上移 */ exportTxtButton.style.boxShadow = '0px 6px 8px rgba(0, 0, 0, 0.15)'; /* 加深阴影 */ }; exportTxtButton.onmouseout = function() { exportTxtButton.style.backgroundColor = '#2ecc71'; /* 回复绿色 */ exportTxtButton.style.transform = 'translateY(0)'; /* 回复原位 */ exportTxtButton.style.boxShadow = '0px 4px 6px rgba(0, 0, 0, 0.1)'; /* 回复阴影 */ }; exportTxtButton.onclick = exportToTxt; // 将"导出TXT文件"按钮添加到第三个容器 exportButtonContainer.appendChild(exportTxtButton); // 将第三个按钮容器添加到控制面板 controlPanel.appendChild(exportButtonContainer); addShowCourseNameText(); addTipsDisplay(); function exportToEf2() { const checkedCheckboxes = document.querySelectorAll("#download_list input[type='checkbox']:checked:not(#selectAllCheckbox)"); console.log('Checked checkboxes for EF2 export:', checkedCheckboxes.length); const selectedFiles = Array.from(checkedCheckboxes).reduce((acc, checkbox) => { const container = checkbox.closest('div'); if (checkbox.id === 'selectAllCheckbox') { console.log('Skipping select all checkbox'); return acc; // 跳过全选按钮 } if (!container) { console.error('Cannot find container for checkbox:', checkbox); return acc; } const link = container.querySelector('a'); if (!link) { console.error('Cannot find link in container:', container); return acc; } const url = link.href; const filename = link.getAttribute('data-origin-name'); if (!url || !filename) { console.error('Invalid URL or filename:', url, filename); return acc; } acc.push({ url, filename }); return acc; }, []); console.log('Selected files for EF2 export:', selectedFiles.length); if (selectedFiles.length === 0) { alert('请选择要导出的文件'); return; } const referer = window.location.href; const cookieString = document.cookie; let ef2Content = ''; selectedFiles.forEach(file => { ef2Content += '<\r\n'; ef2Content += `${file.url}\r\n`; ef2Content += `referer: ${referer}\r\n`; ef2Content += `cookie: ${cookieString}\r\n`; ef2Content += `User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0\r\n`; ef2Content += '>\r\n'; }); ef2Content += '\r\n'; console.log('EF2 content length:', ef2Content.length); const blob = new Blob([ef2Content], { type: 'text/plain' }); const downloadLink = document.createElement('a'); downloadLink.href = URL.createObjectURL(blob); downloadLink.download = '课件链接.ef2'; document.body.appendChild(downloadLink); downloadLink.click(); document.body.removeChild(downloadLink); } function exportToTxt() { const checkedCheckboxes = document.querySelectorAll("#download_list input[type='checkbox']:checked:not(#selectAllCheckbox)"); console.log('Checked checkboxes for TXT export:', checkedCheckboxes.length); const selectedFiles = Array.from(checkedCheckboxes).reduce((acc, checkbox) => { const container = checkbox.closest('div'); if (checkbox.id === 'selectAllCheckbox') { console.log('Skipping select all checkbox'); return acc; // 跳过全选按钮 } if (!container) { console.error('Cannot find container for checkbox:', checkbox); return acc; } const link = container.querySelector('a'); if (!link) { console.error('Cannot find link in container:', container); return acc; } const url = link.href; if (!url) { console.error('Invalid URL:', url); return acc; } acc.push(url); return acc; }, []); console.log('Selected files for TXT export:', selectedFiles.length); if (selectedFiles.length === 0) { alert('请选择要导出的文件'); return; } let txtContent = selectedFiles.join('\r\n'); console.log('TXT content length:', txtContent.length); const blob = new Blob([txtContent], { type: 'text/plain;charset=utf-8;' }); const downloadLink = document.createElement('a'); downloadLink.href = URL.createObjectURL(blob); downloadLink.download = '课件链接.txt'; document.body.appendChild(downloadLink); downloadLink.click(); document.body.removeChild(downloadLink); } // 获取课程名称 function getCourseName() { const footer = document.querySelector('.img_footer'); if (footer) { const groupNameElement = footer.querySelector('.group_name'); if (groupNameElement) { return groupNameElement.textContent; // 返回课程名称 } } return '未知'; // 如果没有找到课程名称,返回默认文本 } function addShowCourseNameText() { showCourseNameText.style.marginTop = '10px'; showCourseNameText.style.marginBottom = '10px'; showCourseNameText.style.fontSize = '16px'; // 减小字体大小 showCourseNameText.style.backgroundColor = 'transparent'; showCourseNameText.style.width = '100%'; showCourseNameText.style.textAlign = 'center'; showCourseNameText.style.fontWeight = '600'; // 稍微减轻字体粗细 showCourseNameText.style.letterSpacing = '0.5px'; // 减少字母间距 showCourseNameText.style.padding = '6px'; // 减少内边距 showCourseNameText.style.boxSizing = 'border-box'; showCourseNameText.className = 'glowing-text animated-text'; // 添加 CSS 动画和样式 const style = document.createElement('style'); style.textContent = ` @keyframes float { 0% { transform: translateY(0px); } 50% { transform: translateY(-3px); } 100% { transform: translateY(0px); } } @keyframes colorChange { 0% { color: #ff6600; text-shadow: 0 0 5px rgba(255, 102, 0, 0.7); } 50% { color: #ff9900; text-shadow: 0 0 8px rgba(255, 153, 0, 0.8); } 100% { color: #ff6600; text-shadow: 0 0 5px rgba(255, 102, 0, 0.7); } } @keyframes borderBlink { 0% { box-shadow: 0 0 0 1px rgba(255, 165, 0, 0.3); } 50% { box-shadow: 0 0 0 1px rgba(255, 165, 0, 0.8); } 100% { box-shadow: 0 0 0 1px rgba(255, 165, 0, 0.3); } } .animated-text { animation: float 3s ease-in-out infinite, colorChange 5s linear infinite, borderBlink 2s linear infinite; border-radius: 5px; } `; document.head.appendChild(style); const observer = new MutationObserver((mutations, obs) => { const courseName = getCourseName(); if (courseName !== showCourseNameText.getAttribute('data-course')) { showCourseNameText.setAttribute('data-course', courseName); typeWriter(`当前课程:${courseName}`, showCourseNameText); } }); observer.observe(document.body, { childList: true, subtree: true, attributes: false }); } // 打字机效果函数 function typeWriter(text, element, index = 0) { if (index < text.length) { element.textContent = text.substring(0, index + 1); setTimeout(() => typeWriter(text, element, index + 1), 50); } } function addTipsDisplay() { tipsDisplay.id = 'tipsDisplay'; tipsDisplay.id = 'tipsDisplay'; tipsDisplay.style.padding = '15px'; tipsDisplay.style.margin = '15px auto'; tipsDisplay.style.maxWidth = '800px'; tipsDisplay.style.width = '90%'; tipsDisplay.style.backgroundColor = 'rgba(252, 187, 52, 0.1)'; tipsDisplay.style.border = '2px solid #fcbb34'; tipsDisplay.style.color = '#e67e22'; tipsDisplay.style.borderRadius = '8px'; tipsDisplay.style.fontSize = '16px'; tipsDisplay.style.fontWeight = 'bold'; tipsDisplay.style.textAlign = 'center'; tipsDisplay.style.position = 'relative'; tipsDisplay.style.overflow = 'hidden'; tipsDisplay.style.cursor = 'pointer'; tipsDisplay.style.boxShadow = '0 4px 6px rgba(252, 187, 52, 0.2)'; tipsDisplay.style.maxHeight = '4em'; tipsDisplay.style.transition = 'all 0.3s ease, max-height 0.5s ease-out'; tipsDisplay.style.lineHeight = '1.6'; tipsDisplay.title = '点击以查看/收起所有提示。'; tipsDisplay.style.opacity = '1'; tipsDisplay.style.transform = 'translateY(0)'; // 添加脉动动画 tipsDisplay.style.animation = 'pulsate 2s infinite'; // 添加闪光效果 tipsDisplay.style.backgroundImage = 'linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.5) 50%, rgba(255,255,255,0) 100%)'; tipsDisplay.style.backgroundSize = '200% 100%'; tipsDisplay.style.animation = 'pulsate 2s infinite, shine 3s infinite'; // 添加新的样式 const style = document.createElement('style'); style.textContent = ` #tipsDisplay:hover { background-color: rgba(252, 187, 52, 0.2); transform: translateY(-2px); } @keyframes fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } #tipsDisplay { animation: fadeInUp 0.5s ease-out; } @keyframes pulsate { 0% { box-shadow: 0 0 0 0 rgba(252, 187, 52, 0.4); } 70% { box-shadow: 0 0 0 10px rgba(252, 187, 52, 0); } 100% { box-shadow: 0 0 0 0 rgba(252, 187, 52, 0); } } @keyframes shine { 0% { background-position: -100% 0; } 100% { background-position: 100% 0; } } `; document.head.appendChild(style); // 提示信息列表 const tipsList = [ '右上角下载栏可下载单个文件。', '右下角下载栏可下载多个文件。', '可拖动链接至左下角进行下载。', '进度条位于左下角折叠列表中。', '搜索栏也可搜索后缀名等信息。', '右上角支持文档和压缩包下载。', '浏览器可以直接右键下载图片。', '视频可打开链接点击右键下载。', '控制面板可切换文件下载方式。', 'IDM会强制接管所有下载链接。', '文件下载历史记录仅保存三天。', '导出时先在右下角列表中勾选。', '已读或清空消息后请刷新页面。', '课程首页才能获取到教师信息。', '搜索功能基于用户的填写信息。', '打开树状图时会禁用部分组件。', '如有建议请在油猴反馈处留言。', ]; let isPaused = false; let currentTipIndex = 0; let intervalId; let isShowingAllTips = false; function changeTip() { if (!isShowingAllTips && !isPaused) { currentTipIndex = (currentTipIndex + 1) % tipsList.length; tipsDisplay.style.transition = 'opacity 0.3s, transform 0.3s'; tipsDisplay.style.opacity = '0'; tipsDisplay.style.transform = 'translateX(-100%)'; } } function updateTipDisplay() { tipsDisplay.innerHTML = '<i class="fas fa-lightbulb" style="margin-right: 10px;"></i>' + tipsList[currentTipIndex]; tipsDisplay.style.opacity = '1'; tipsDisplay.style.transform = 'translateX(0)'; } tipsDisplay.addEventListener('transitionend', (event) => { if (event.propertyName === 'opacity' && tipsDisplay.style.opacity === '0' && !isShowingAllTips) { updateTipDisplay(); } }); // 创建一个新的元素来显示所有提示 const allTipsContainer = document.createElement('div'); allTipsContainer.id = 'allTipsContainer'; allTipsContainer.style.display = 'none'; allTipsContainer.style.position = 'fixed'; allTipsContainer.style.top = '50%'; allTipsContainer.style.left = '50%'; allTipsContainer.style.transform = 'translate(-50%, -50%)'; allTipsContainer.style.backgroundColor = '#fff'; allTipsContainer.style.padding = '30px'; allTipsContainer.style.borderRadius = '15px'; allTipsContainer.style.boxShadow = '0 10px 30px rgba(0,0,0,0.2)'; allTipsContainer.style.zIndex = '10002'; allTipsContainer.style.maxWidth = '600px'; allTipsContainer.style.width = '90%'; allTipsContainer.style.maxHeight = '80vh'; allTipsContainer.style.overflowY = 'auto'; allTipsContainer.style.fontFamily = 'Microsoft YaHei, sans-serif'; allTipsContainer.style.color = '#333'; allTipsContainer.style.transition = 'none'; allTipsContainer.style.transformOrigin = 'center center'; // 获取提示框的位置 function getTipsDisplayPosition() { const rect = tipsDisplay.getBoundingClientRect(); return { left: rect.left + rect.width / 2, top: rect.top + rect.height / 2 }; } // 点击事件处理 tipsDisplay.addEventListener('click', () => { isShowingAllTips = !isShowingAllTips; clearInterval(intervalId); if (isShowingAllTips) { const position = getTipsDisplayPosition(); const startX = position.left - window.innerWidth / 2; const startY = position.top - window.innerHeight / 2; allTipsContainer.style.setProperty('--start-x', `${startX}px`); allTipsContainer.style.setProperty('--start-y', `${startY}px`); allTipsContainer.style.left = '50%'; allTipsContainer.style.top = '50%'; allTipsContainer.style.transform = `translate(${startX}px, ${startY}px) scale(0.7)`; allTipsContainer.style.opacity = '0'; allTipsContainer.innerHTML = ` <h2>所有提示</h2> ${tipsList.map((tip, index) => ` <div class="tip-item" style="animation: fadeInUp 0.5s ease-out ${index * 0.1}s both;"> <span class="tip-number">${index + 1}</span> <span class="tip-text">${tip}</span> </div> `).join('')} <button id="closeTipsBtn">关闭</button> `; allTipsContainer.style.display = 'block'; // 重置并启动打开动画 allTipsContainer.style.animation = 'none'; void allTipsContainer.offsetWidth; allTipsContainer.style.animation = 'popOutToCenter 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55) forwards'; // 添加关闭按钮事件 document.getElementById('closeTipsBtn').addEventListener('click', closeTips); } else { closeTips(); } }); tipsDisplay.addEventListener('mouseover', () => { pauseTips(); tipsDisplay.style.animation = 'pulsate 1s infinite, shine 2s infinite'; tipsDisplay.style.transform = 'scale(1.02)'; }); tipsDisplay.addEventListener('mouseout', () => { resumeTips(); tipsDisplay.style.animation = 'pulsate 2s infinite, shine 3s infinite'; tipsDisplay.style.transform = 'scale(1)'; }); function closeTips() { // 立即停止所有子元素的动画 allTipsContainer.querySelectorAll('*').forEach(el => { el.style.animation = 'none'; void el.offsetWidth; // 强制重绘 }); // 立即停止容器本身的动画 allTipsContainer.style.animation = 'none'; // 强制浏览器重新计算样式 void allTipsContainer.offsetWidth; const position = getTipsDisplayPosition(); const endX = position.left - window.innerWidth / 2; const endY = position.top - window.innerHeight / 2; allTipsContainer.style.setProperty('--end-x', `${endX}px`); allTipsContainer.style.setProperty('--end-y', `${endY}px`); // 设置关闭动画 allTipsContainer.style.animation = 'popInToOrigin 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55) forwards'; allTipsContainer.addEventListener('animationend', () => { allTipsContainer.style.display = 'none'; isShowingAllTips = false; resumeTips(); }, { once: true }); } function pauseTips() { isPaused = true; clearInterval(intervalId); // 停止移动动画 tipsDisplay.style.transition = 'none'; tipsDisplay.style.transform = 'translateX(0)'; } function resumeTips() { if (!isShowingAllTips) { isPaused = false; updateTipDisplay(); startTipInterval(); } } function startTipInterval() { clearInterval(intervalId); intervalId = setInterval(changeTip, 5000); } document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible' && !isShowingAllTips) { resumeTips(); } else { pauseTips(); } }); // 初始启动轮播 updateTipDisplay(); startTipInterval(); // 将 allTipsContainer 添加到 body document.body.appendChild(allTipsContainer); // 暴露控制函数,供外部使用 return { pauseTips, resumeTips }; } function updateVisibility() { const downloadInterface = document.getElementById('downloadInterface'); const progressBarInterface = document.getElementById('downloadsContainer'); const downloadIconContainer = document.getElementById('download_icon_container'); const downloadListContainer = document.getElementById('download_list'); const videoAssistant = document.getElementById('tm-video-assistant'); if (downloadInterface) { downloadInterface.style.display = downloadInterfaceCheckbox.checked ? 'block' : 'none'; } if (videoAssistant) { videoAssistant.style.display = videoCheckbox.checked ? 'block' : 'none'; } if (progressBarInterface) { const isVisible = progressBarCheckbox.checked; progressBarInterface.style.display = isVisible ? 'block' : 'none'; isProgressBarVisible = isVisible; // 更新 isProgressBarVisible 的值 } } let isFirstLoad = true; // 专门更新右下角下载列表可见性的函数 function updateDownloadListVisibility() { const downloadIconContainer = document.getElementById('download_icon_container'); const downloadListContainer = document.getElementById('download_list'); const isVisible = downloadButtonCheckbox.checked; if (downloadIconContainer && downloadListContainer) { downloadIconContainer.style.display = isVisible ? 'block' : 'none'; downloadListContainer.style.display = isVisible ? 'block' : 'none'; downloadListContainer.style.opacity = isVisible ? '1' : '0'; downloadListContainer.style.maxHeight = isVisible ? '300px' : '0'; } } // 将控件移动到对应的分类容器中 messageContainer.appendChild(messageButtonContainer); downloadContainer.appendChild(downloadButtonContainer); exportContainer.appendChild(exportButtonContainer); displayContainer.appendChild(checkboxesContainer); updateContainer.appendChild(Beautifulupdater); // 将分类容器添加到控制面板中 controlPanel.appendChild(messageContainer); controlPanel.appendChild(downloadContainer); controlPanel.appendChild(exportContainer); controlPanel.appendChild(displayContainer); controlPanel.appendChild(updateContainer); // 默认隐藏除了第一个分类以外的容器 downloadContainer.style.display = 'none'; exportContainer.style.display = 'none'; displayContainer.style.display = 'none'; updateContainer.style.display = 'none'; // 将控制面板和切换按钮添加到页面中 document.body.appendChild(controlPanel); document.body.appendChild(toggleButton); // 一开始就应用一次设置以匹配初始复选框状态 updateVisibility(); updateDownloadListVisibility(); } // 延迟 1 秒执行所有代码 setTimeout(() => { add_download_button(); // 添加下载按钮 initializeControlPanel(); // 初始化控制面板 window.toggleListVisibility(); // 切换下载列表的初始可见性 loadDownloadHistory(); // 加载下载历史 initVideoAssistant(); // 初始化右侧下载组件 initCourseCapture(); // 初始化右上角下载组件 addDownloadsContainer(); //添加进度条容器 console.oldLog = console.log; console.log = (...data) => { if (data[0] === 'nodesToData') { course_resources = data[1]; console.oldLog('::', course_resources); // 在彩蛋激活时立即获取教师信息 let groupId = null; for (const resourceId in course_resources) { if (course_resources[resourceId].group_id) { groupId = course_resources[resourceId].group_id; break; } } if (groupId) { getTeacherInfo(groupId).then((teacherInfo) => { if (teacherInfo) { updateTeacherInfo(teacherInfo); } else { console.error("无法获取老师信息"); } }); } else { console.warn("无法找到包含 group_id 属性的资源,无法获取老师信息"); } parseContent(); return; } console.oldLog(...data); }; // 定义快速筛选的类别选项 window.quickFilters = [ { label: "全部", value: "" }, { label: "文档", value: "doc,docx,pdf,txt,odt,rtf,html,htm,xls,xlsx,ppt,pptx,odp" }, { label: "图片", value: "jpg,jpeg,png,gif,bmp,tiff,svg,webp,tif" }, { label: "压缩包", value: "zip,rar,7z,gz,bz2,tar" }, ]; window.abortControllers = {}; // 创建 MutationObserver const observer = new MutationObserver(function(mutationsList, observer) { // 在每次发生 DOM 变化时触发回调 for(let mutation of mutationsList) { if (mutation.type === 'childList' && mutation.target.id === 'download_list') { // 更新 downloadlist window.updateUI(); break; // 处理完毕后退出循环 } } }); observer.observe(document.body, { childList: true, subtree: true }); }, 1000); // 定义要抓取的后缀名 const EXTENSIONS = [".doc", ".pdf", ".docx", ".ppt", ".pptx", ".xls", ".xlsx", ".txt", ".odt", ".rtf", ".zip", ".rar", ".7z", ".tar", ".gz", ".bz2", ".xz"]; // 全局变量 let previewLink; let isDownloading = false; // 主函数 function initCourseCapture() { const list = createDownloadInterface(); initializeXHRInterceptor(); createInitialPreviewLink(); } // 创建下载界面 function createDownloadInterface() { const list = document.createElement("div"); initializeListStyles(list); addAnimationStyles(); return list; } // 初始化列表样式 function initializeListStyles(element) { element.id = "downloadInterface"; Object.assign(element.style, { position: "fixed", top: "100px", right: "0", width: "50px", height: "50px", borderRadius: "50%", overflow: "hidden", transition: "all 0.3s ease-in-out", zIndex: "9997", background: "linear-gradient(270deg, #ffc700, #ff8c00, #ff6500)", backgroundSize: "400% 400%", boxShadow: "0 4px 8px 0 rgba(0, 0, 0, 0.2)" }); element.classList.add("dynamic-gradient"); element.innerHTML = "<h3><span style=\"font-weight: bold; font-style: italic; font-size: 16px;\">抓取到的课件</span></h3>"; element.querySelector('h3').style.opacity = 0; addLottieAnimation(element); addInteractivity(element); document.body.appendChild(element); } // 添加Lottie动画 function addLottieAnimation(element) { const lottieContainer = document.createElement('div'); Object.assign(lottieContainer.style, { position: "absolute", transition: "all 0.3s ease-in-out", width: "200%", height: "200%", left: "-60%", top: "-45%", overflow: "hidden", zIndex: "9998", pointerEvents: "none" }); const lottiePlayer = document.createElement('dotlottie-player'); lottiePlayer.setAttribute('src', "https://lottie.host/995b71c8-b7aa-45b0-bb77-94b850da5d5d/dyezqbvtia.json"); lottiePlayer.setAttribute('background', "transparent"); lottiePlayer.setAttribute('speed', "1"); lottiePlayer.setAttribute('style', "width: 125%; height: 100%; position: absolute; left: -12.5%;"); lottiePlayer.setAttribute('loop', ""); lottiePlayer.setAttribute('autoplay', ""); lottieContainer.appendChild(lottiePlayer); element.appendChild(lottieContainer); } // 添加交互性 function addInteractivity(element) { element.addEventListener("mouseover", () => { element.style.transform = "scale(1.1)"; }); element.addEventListener("mouseout", () => { element.style.transform = "scale(1)"; }); element.addEventListener("click", toggleInterfaceSize); } // 切换界面大小 function toggleInterfaceSize() { const element = document.getElementById("downloadInterface"); const lottieContainer = element.querySelector('div'); const h3 = element.querySelector('h3'); if (element.style.width === "50px") { Object.assign(element.style, { width: "350px", height: "10%", padding: "10px", borderRadius: "10px", overflow: "hidden" }); Object.assign(lottieContainer.style, { width: "70%", height: "100%", left: `${element.offsetWidth - lottieContainer.offsetWidth + 120}px`, top: "-20px" }); h3.style.opacity = 1; } else { Object.assign(element.style, { width: "50px", height: "50px", padding: "0", borderRadius: "50%", overflow: "hidden" }); Object.assign(lottieContainer.style, { width: "200%", height: "200%", left: "-60%", top: "-45%" }); h3.style.opacity = 0; } } // 添加动画样式 function addAnimationStyles() { const style = document.createElement('style'); style.innerHTML = ` @keyframes slideIn { from { opacity: 0; transform: translateX(100%); } to { opacity: 1; transform: translateX(0); } } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } @keyframes gradientBgAnimation { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } .dynamic-gradient { animation: gradientBgAnimation 15s ease infinite; } `; document.head.appendChild(style); } // 初始化XHR拦截器 function initializeXHRInterceptor() { const originalOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url, async, user, pass) { this.addEventListener("load", () => { if (EXTENSIONS.some(ext => url.includes(ext))) { handleXhrResponse(url); } }); originalOpen.call(this, method, url, true, user, pass); }; } // 处理XHR响应 function handleXhrResponse(url) { if (isDownloading) return; isDownloading = true; const token = getCookie(); const xhr = new XMLHttpRequest(); xhr.open("GET", url, true); xhr.setRequestHeader("Authorization", `Bearer ${token}`); xhr.onload = function () { const text = xhr.responseText.replace("}}", "").replace(/"/g, ""); const match = text.match(/(http|https):\/\/\S+/); const content = document.title.split('|')[0].trim(); if (match) { triggerNewLinkAnimation(); updatePreviewLink(match[0], content); } isDownloading = false; }; xhr.send(); } // 触发新链接动画 function triggerNewLinkAnimation() { const downloadInterface = document.getElementById("downloadInterface"); if (downloadInterface.style.width === "50px") { downloadInterface.style.animation = "pulse 0.5s ease-in-out"; downloadInterface.addEventListener('animationend', () => { downloadInterface.style.animation = ''; }, {once: true}); } } // 创建初始预览链接 function createInitialPreviewLink() { if (!previewLink || !document.contains(previewLink)) { createPreviewLink("#", "等待课件...( _ _)ノ|"); previewLink.removeAttribute("href"); Object.assign(previewLink.style, { pointerEvents: "none", color: "#DDD" }); } } // 创建或更新预览链接 function updatePreviewLink(href, content) { const list = document.getElementById("downloadInterface"); while (list.childNodes.length > 2) { list.removeChild(list.lastChild); } if (previewLink && document.contains(previewLink)) { Object.assign(previewLink, { href: href, textContent: content, style: { pointerEvents: "", color: "" } }); } else { createPreviewLink(href, content); } } // 创建预览链接 function createPreviewLink(href, content) { previewLink = document.createElement("a"); Object.assign(previewLink.style, { background: "linear-gradient(to right, #ffffff, #ffecb3)", backgroundClip: "text", webkitBackgroundClip: "text", color: "transparent", fontFamily: "'微软雅黑', 'Microsoft YaHei', sans-serif", fontWeight: "bold", transition: "transform 0.3s, text-shadow 0.3s", display: "inline-block", position: "relative" }); previewLink.title = "点击以下载"; previewLink.draggable = true; previewLink.dataset.downloadUrl = href; previewLink.dataset.filename = content; addPreviewLinkEventListeners(previewLink); const list = document.getElementById("downloadInterface"); list.appendChild(previewLink); list.appendChild(document.createElement("br")); Object.assign(previewLink, { href: href, target: "_blank", textContent: content }); previewLink.style.animation = "slideIn 0.5s forwards"; } // 添加预览链接事件监听器 function addPreviewLinkEventListeners(previewLink) { previewLink.addEventListener('dragstart', (event) => { event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.setData('text/plain', previewLink.dataset.downloadUrl); event.dataTransfer.setData('text/filename', previewLink.dataset.filename); }); previewLink.addEventListener('mouseover', () => { previewLink.style.transform = "scale(1.05)"; previewLink.style.textShadow = "0 0 8px rgba(255, 165, 0, 0.7)"; }); previewLink.addEventListener('mouseout', () => { previewLink.style.transform = "scale(1)"; previewLink.style.textShadow = "none"; }); previewLink.addEventListener('mousedown', () => { previewLink.style.transform = "scale(0.95)"; }); previewLink.addEventListener('mouseup', () => { previewLink.style.transform = "scale(1.05)"; }); previewLink.addEventListener("click", function (event) { event.preventDefault(); courseDownload(previewLink.href, previewLink.textContent); }); } function createToggleButton(text) { const button = document.createElement('button'); button.textContent = text; button.style.cssText = ` position: absolute; top: 10px; left: 100%; padding: 8px 16px; background-color: #fcbb34; color: white; border: none; border-radius: 0 8px 8px 0; cursor: pointer; white-space: nowrap; z-index: 10001; transition: all 0.3s ease; font-size: 14px; font-weight: bold; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); outline: none; `; button.addEventListener('mouseover', () => { button.style.backgroundColor = '#e5a72e'; button.style.transform = 'translateX(5px)'; }); button.addEventListener('mouseout', () => { button.style.backgroundColor = '#fcbb34'; button.style.transform = 'translateX(0)'; }); button.addEventListener('mousedown', () => { button.style.transform = 'translateX(2px) scale(0.98)'; }); button.addEventListener('mouseup', () => { button.style.transform = 'translateX(5px) scale(1)'; }); return button; } function createDebuggerToggleButton(text) { const button = document.createElement('button'); button.textContent = text; button.style.cssText = ` position: fixed; top: 0; left: 50%; transform: translateX(-50%); padding: 8px 16px; background-color: #4CAF50; color: white; border: none; border-radius: 0 0 8px 8px; cursor: pointer; white-space: nowrap; z-index: 10003; transition: background-color 0.3s ease; font-size: 14px; font-weight: bold; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); outline: none; `; button.addEventListener('mouseover', () => { button.style.backgroundColor = '#45a049'; }); button.addEventListener('mouseout', () => { button.style.backgroundColor = '#4CAF50'; }); return button; } // 切换容器的显示/隐藏 function toggleContainer(container, button) { const isHidden = container.style.left === '-400px'; container.style.left = isHidden ? '0' : '-400px'; button.style.backgroundColor = isHidden ? '#e69b00' : '#fcbb34'; button.textContent = isHidden ? '关闭' : button.getAttribute('data-original-text'); } // 创建并设置教师信息容器 function createTeacherInfoContainer() { const container = document.createElement("div"); container.id = "teacherInfoContainer"; container.style.cssText = ` position: fixed; top: 30vh; left: -400px; z-index: 10000; background-color: #FFF3E0; border: 1px solid #FFD180; border-radius: 0 8px 8px 0; padding: 20px; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); width: 400px; height: 130px; max-height: 60vh; transition: all 0.3s ease; font-weight: bold; `; const scrollContent = document.createElement('div'); scrollContent.style.cssText = ` height: 100%; overflow-y: auto; overflow-x: hidden; font-weight: bold; `; scrollContent.id = "teacherInfoContent"; scrollContent.innerHTML = ` <div class="loading-message" style="text-align: center; padding: 20px;"> <p style="margin-bottom: 10px; font-size: 16px;">请打开任意课程</p> <div class="loading-dots"> <span class="dot">.</span><span class="dot">.</span><span class="dot">.</span> </div> </div> `; const toggleButton = createToggleButton("教师信息"); toggleButton.setAttribute('data-original-text', "教师信息"); toggleButton.style.left = '400px'; toggleButton.style.fontFamily = '"Microsoft YaHei", sans-serif'; toggleButton.style.fontWeight = 'bold'; toggleButton.onclick = (e) => { e.stopPropagation(); toggleContainer(container, toggleButton); }; container.appendChild(scrollContent); container.appendChild(toggleButton); document.body.appendChild(container); // 添加样式 const teacher_style = document.createElement('style'); teacher_style.textContent = ` @keyframes blink { 0% { opacity: 0; } 50% { opacity: 1; } 100% { opacity: 0; } } .loading-dots .dot { animation: blink 1.4s infinite; animation-fill-mode: both; font-size: 24px; } .loading-dots .dot:nth-child(2) { animation-delay: 0.2s; } .loading-dots .dot:nth-child(3) { animation-delay: 0.4s; } `; document.head.appendChild(teacher_style); } function updateTeacherInfo(teacherInfo) { const content = document.getElementById("teacherInfoContent"); if (teacherInfo && teacherInfo.length > 0) { content.innerHTML = ` <h3 class="title" style="font-weight: bold; margin-bottom: 15px; padding-left: 10px;">教师信息</h3> <div class="teacher-list" style="font-weight: bold;"> ${teacherInfo.map((teacher) => ` <div class="teacher-item" style="margin-bottom: 10px; padding: 10px; background-color: #FFE0B2; border-radius: 5px;"> <div class="teacher-name" style="margin-bottom: 5px;">${teacher.nickname}</div> <div class="teacher-number" style="color: #757575;">${teacher.studentNumber}</div> </div> `).join("")} </div> `; } else { content.innerHTML = '<p class="no-data" style="font-weight: bold; color: #757575; text-align: center; padding: 20px;">暂无教师信息</p>'; } } // 绕过反调试按钮 function createDebuggerDisablerContainer() { const container = document.createElement('div'); container.id = "debuggerDisablerContainer"; container.style.cssText = ` position: fixed; top: -280px; left: 50%; transform: translateX(-50%); z-index: 10002; background-color: #1a1a1a; border: 2px solid #ff6600; border-radius: 0 0 10px 10px; padding: 20px; box-shadow: 0 5px 15px rgba(255, 102, 0, 0.3); width: 300px; height: 220px; transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1); `; const content = document.createElement('div'); content.style.cssText = ` height: 100%; display: flex; flex-direction: column; justify-content: space-between; align-items: center; color: #ff6600; `; content.innerHTML = ` <h3 style="margin: 0; color: #ff6600; text-align: center; font-size: 18px; letter-spacing: 2px;">反调试禁用器</h3> <div style="width: 100%; height: 2px; background: linear-gradient(to right, #1a1a1a, #ff6600, #1a1a1a);"></div> <p style="text-align: center; margin: 10px 0; font-size: 14px; font-weight: bold; color: #cccccc;">禁用小雅反调试措施</p> <button id="disableDebuggerBtn" style=" background-color: #1a1a1a; border: 2px solid #ff6600; color: #ff6600; padding: 10px 20px; text-align: center; text-decoration: none; display: inline-block; font-size: 14px; margin: 4px 2px; cursor: pointer; transition: all 0.3s ease; outline: none; letter-spacing: 1px; ">⚡点击禁用⚡</button> `; container.appendChild(content); const toggleButton = document.createElement('button'); toggleButton.id = "debuggerToggleButton" toggleButton.textContent = "🔑 打开"; toggleButton.style.cssText = ` position: fixed; top: 0; left: 50%; transform: translateX(-50%); padding: 8px 16px; background-color: #ff6600; color: #1a1a1a; border: none; border-radius: 0 0 5px 5px; cursor: pointer; z-index: 10003; transition: all 0.3s ease; font-size: 14px; font-weight: bold; letter-spacing: 1px; `; document.body.appendChild(toggleButton); document.body.appendChild(container); let isOpen = false; toggleButton.onclick = (e) => { e.stopPropagation(); isOpen = !isOpen; if (isOpen && !localStorage.getItem('debuggerDisablerShownWarning')) { // 首次打开组件时显示警告 alert("警告⚠️:此功能可能会影响网站的正常运行,请谨慎使用!"); localStorage.setItem('debuggerDisablerShownWarning', 'true'); } container.style.top = isOpen ? '0' : '-280px'; toggleButton.style.backgroundColor = isOpen ? '#ff8533' : '#ff6600'; toggleButton.textContent = isOpen ? "🔒 关闭" : "🔑 打开"; toggleButton.style.top = isOpen ? '220px' : '0'; }; toggleButton.addEventListener('mouseover', () => { toggleButton.style.backgroundColor = '#ff8533'; }); toggleButton.addEventListener('mouseout', () => { toggleButton.style.backgroundColor = isOpen ? '#ff8533' : '#ff6600'; }); const disableDebuggerBtn = document.getElementById('disableDebuggerBtn'); disableDebuggerBtn.addEventListener('mouseover', () => { disableDebuggerBtn.style.backgroundColor = '#ff6600'; disableDebuggerBtn.style.color = '#1a1a1a'; }); disableDebuggerBtn.addEventListener('mouseout', () => { disableDebuggerBtn.style.backgroundColor = '#1a1a1a'; disableDebuggerBtn.style.color = '#ff6600'; }); disableDebuggerBtn.addEventListener('click', function() { (function() { 'use strict'; Function.prototype.constructor = function() { return function() {}; }; })(); this.textContent = '反调试已禁用'; this.style.backgroundColor = '#1a1a1a'; this.style.color = '#00ff00'; this.style.borderColor = '#00ff00'; this.disabled = true; this.style.cursor = 'default'; // 添加禁用后的动画效果 container.style.animation = 'glitch 0.5s'; }); // 添加动画 const style = document.createElement('style'); style.textContent = ` @keyframes glitch { 0% { transform: translateX(-50%) translate(2px, 2px); } 25% { transform: translateX(-50%) translate(-2px, -2px); } 50% { transform: translateX(-50%) translate(2px, -2px); } 75% { transform: translateX(-50%) translate(-2px, 2px); } 100% { transform: translateX(-50%) translate(0, 0); } } `; document.head.appendChild(style); return { container, toggleButton }; } // 获取教师信息 async function getTeacherInfo(groupId) { if (!course_resources || Object.keys(course_resources).length === 0) { console.error("无法获取课程资源信息"); return; } const token = getCookie(); const apiUrl = `https://${hostname}/api/jx-iresource/statistics/group/visit`; const requestBody = { group_id: groupId, role_type: "normal" }; try { const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json; charset=UTF-8', 'Authorization': `Bearer ${token}`, }, body: JSON.stringify(requestBody) }); if (!response.ok) { throw new Error(`API 请求失败:${response.status} ${response.statusText}`); } const data = await response.json(); const teachers = data.data.teachers; return teachers.map((teacher) => ({ nickname: teacher.nickname, studentNumber: teacher.studentNumber, })); } catch (error) { console.error("获取教师信息时出错:", error); return null; } } function createUserSearchContainer() { const container = document.createElement('div'); container.id = "userSearchContainer"; container.style.cssText = ` position: fixed; top: 45vh; left: -400px; z-index: 9999; background-color: #FFF3E0; border: 1px solid #FFD180; border-radius: 0 8px 8px 0; padding: 20px; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); width: 400px; height: 150px; transition: all 0.3s ease; font-weight: bold; `; const scrollContent = document.createElement('div'); scrollContent.style.cssText = ` height: 100%; overflow-y: auto; `; scrollContent.innerHTML = ` <input type="text" id="userSearchInput" placeholder="输入电话/一卡通号" class="search-input" style=" font-weight: normal; width: 70%; padding: 10px 15px; margin-right: 10px; border: 2px solid #FFD180; border-radius: 20px; font-size: 16px; color: #333; outline: none; transition: all 0.3s ease; "> <button id="userSearchButton" class="search-button" style=" font-weight: bold; padding: 10px 20px; cursor: pointer; background-color: #FFD180; color: #333; border: none; border-radius: 20px; font-size: 16px; transition: all 0.3s ease; ">搜索</button> <div id="userSearchResults" class="search-results" style=" margin-top: 15px; font-weight: normal; "> <p class="search-hint">请输入电话或一卡通号进行搜索</p> </div> `; const toggleButton = createToggleButton("用户搜索"); toggleButton.setAttribute('data-original-text', "用户搜索"); toggleButton.style.left = '400px'; toggleButton.style.fontFamily = '"Microsoft YaHei", sans-serif'; toggleButton.style.fontWeight = 'bold'; toggleButton.onclick = (e) => { e.stopPropagation(); toggleContainer(container, toggleButton); // 检查是否是第一次打开 if (!localStorage.getItem('userSearchWarningShown')) { showUserSearchWarning(); localStorage.setItem('userSearchWarningShown', 'true'); } }; container.appendChild(scrollContent); container.appendChild(toggleButton); document.body.appendChild(container); setTimeout(() => { const input = document.getElementById("userSearchInput"); if (input) { input.addEventListener('focus', () => { input.style.borderColor = '#FFA000'; input.style.boxShadow = '0 0 5px rgba(255, 160, 0, 0.5)'; }); input.addEventListener('blur', () => { input.style.borderColor = '#FFD180'; input.style.boxShadow = 'none'; }); // 添加回车键搜索功能 input.addEventListener('keypress', async (e) => { if (e.key === 'Enter') { e.preventDefault(); await performSearch(); } }); } const button = document.getElementById("userSearchButton"); if (button) { button.addEventListener('mouseover', () => { button.style.backgroundColor = '#FFA000'; }); button.addEventListener('mouseout', () => { button.style.backgroundColor = '#FFD180'; }); button.addEventListener('click', performSearch); } }, 0); // 添加样式 const search_style = document.createElement('style'); search_style.textContent = ` #userSearchInput::placeholder { color: #999; opacity: 1; transition: opacity 0.3s ease; } #userSearchInput:focus::placeholder { opacity: 0.5; } `; document.head.appendChild(search_style); } // 执行搜索的函数 async function performSearch() { const input = document.getElementById("userSearchInput"); const resultsDiv = document.getElementById("userSearchResults"); if (input && resultsDiv) { const username = input.value.trim(); if (username.length > 0) { const userInfo = await searchUser(username); displayUserInfo(userInfo, resultsDiv); } else { resultsDiv.innerHTML = '<p class="search-hint">请输入用户名或学号进行搜索</p>'; } } } // 添加显示警告的函数 function showUserSearchWarning() { const overlay = document.createElement('div'); overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 10001; opacity: 0; transition: opacity 0.3s ease; `; document.body.appendChild(overlay); const warningDiv = document.createElement('div'); warningDiv.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: #FFF3E0; border: none; border-radius: 12px; padding: 40px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); z-index: 10002; max-width: 600px; width: 90%; text-align: center; opacity: 0; transition: opacity 0.3s ease; `; warningDiv.innerHTML = ` <h3 style=" color: #E65100; margin-top: 0; font-size: 28px; margin-bottom: 25px; font-weight: bold; ">⚠️ 警告</h3> <p style=" color: #555; font-size: 18px; line-height: 1.6; margin-bottom: 30px; ">请注意:用户搜索功能仅供学习和研究使用。不当用途可能违反相关法律法规和学校政策。请遵守相关规定,尊重他人隐私。</p> <button id="warningCloseBtn" style=" background-color: #FF9800; border: none; color: white; padding: 14px 28px; text-align: center; text-decoration: none; display: inline-block; font-size: 18px; margin: 4px 2px; cursor: pointer; border-radius: 50px; transition: background-color 0.3s ease; outline: none; ">我已了解</button> `; document.body.appendChild(warningDiv); const closeBtn = document.getElementById('warningCloseBtn'); closeBtn.onmouseover = () => { closeBtn.style.backgroundColor = '#F57C00'; }; closeBtn.onmouseout = () => { closeBtn.style.backgroundColor = '#FF9800'; }; closeBtn.onclick = () => { fadeOutAndRemove(overlay); fadeOutAndRemove(warningDiv); }; // 淡入效果 setTimeout(() => { overlay.style.opacity = '1'; warningDiv.style.opacity = '1'; }, 10); } function fadeOutAndRemove(element) { element.style.opacity = '0'; element.addEventListener('transitionend', function handler() { element.removeEventListener('transitionend', handler); element.parentNode.removeChild(element); }); } // 搜索用户函数 async function searchUser(username) { const token = getCookie(); const apiUrl = `https://${hostname}/api/jx-iresource/auth/user/info?username=${username}`; try { const response = await fetch(apiUrl, { method: 'GET', headers: { 'Authorization': `Bearer ${token}` } }); if (response.ok) { const data = await response.json(); return data.data; } else { throw new Error(`API 请求失败:${response.status} ${response.statusText}`); } } catch (error) { console.error("搜索用户时出错:", error); return null; } } // 显示用户信息函数 function displayUserInfo(userInfo, resultsDiv) { if (userInfo && userInfo.length > 0) { const user = userInfo[0]; resultsDiv.innerHTML = ` <div class="user-info"> <p><strong>用户名:</strong> ${user.loginName || "未知"}</p> <p><strong>昵称:</strong> ${user.nickname || "未知"}</p> <p><strong>姓名:</strong> ${user.realname || "未知"}</p> <p><strong>性别:</strong> ${user.sex || "未知"}</p> <p><strong>邮箱:</strong> ${user.email || "未知"}</p> <p><strong>电话:</strong> ${user.phone || "未知"}</p> <p><strong>学校:</strong> ${user.schoolName || "未知"}</p> <p><strong>QQ:</strong> ${user.qq || "未知"}</p> </div> `; } else { resultsDiv.innerHTML = '<p class="no-data">未找到该用户</p>'; } } // 初始化函数 function initContainers() { createTeacherInfoContainer(); createUserSearchContainer(); createDebuggerDisablerContainer(); } // 初始化 Konami 代码序列 let konamiCode = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a']; let konamiIndex = 0; // 监听键盘事件 document.addEventListener('keydown', (e) => { // 如果彩蛋已经被激活,则直接返回 if (isEasterEggActivated) { return; } if (e.key === konamiCode[konamiIndex]) { konamiIndex++; if (konamiIndex === konamiCode.length) { activateEasterEgg(); konamiIndex = 0; } } else { konamiIndex = 0; } }); // 激活彩蛋功能 function activateEasterEgg() { // 如果彩蛋已经被激活,则弹出提示并返回 if (isEasterEggActivated) { alert("彩蛋已激活!别再戳啦!"); // 弹出提示 return; } console.log("彩蛋已激活!"); initContainers(); // 初始化 // 在彩蛋激活时立即获取教师信息 let groupId = null; for (const resourceId in course_resources) { if (course_resources[resourceId].group_id) { groupId = course_resources[resourceId].group_id; break; } } if (groupId) { getTeacherInfo(groupId).then((teacherInfo) => { if (teacherInfo) { updateTeacherInfo(teacherInfo); } else { console.error("无法获取老师信息"); } }); } else { console.warn("无法找到包含 group_id 属性的资源,无法获取老师信息"); } // 设置彩蛋激活标志位为 true isEasterEggActivated = true; } function initVideoAssistant() { const container = document.createElement('div'); container.id = 'tm-video-assistant'; container.innerHTML = ` <div id="tm-assistant-icon"> <dotlottie-player src="https://lottie.host/e0b82942-db68-44a1-b729-df62ceb4c75e/bBXqj2oy9a.json" background="transparent" speed="1" style="width: 50px; height: 50px;" loop autoplay ></dotlottie-player> </div> <div id="tm-assistant-content"> <div id="tm-header"> <h3>视频源检测</h3> <button id="tm-download-guide-button" class="tm-button"> <span class="tm-button-icon">📽️</span> 视频下载指南 </button> </div> <div id="tm-video-title-container" class="tm-info-container"> <div class="tm-info-label">视频标题:</div> <div id="tm-video-title" class="tm-info-content">等待获取视频标题...</div> </div> <div id="tm-video-src-container" class="tm-info-container"> <div class="tm-info-label">视频链接:</div> <div id="tm-video-src" class="tm-info-content">等待检测视频源...</div> </div> <div id="tm-button-container"> <button id="tm-copy-src" class="tm-button"> <span class="tm-button-icon">📋</span> <strong>复制链接</strong> </button> <button id="tm-open-src" class="tm-button"> <span class="tm-button-icon">🔗</span> <strong>打开链接</strong> </button> </div> <p id="tm-status"></p> </div> `; document.body.appendChild(container); // 设置样式 const style = document.createElement('style'); style.textContent = ` #tm-video-assistant { position: fixed; top: 320px; right: 0; width: 50px; height: 50px; background: linear-gradient(270deg, #ffc700, #ff8c00, #ff6500); background-size: 600% 600%; animation: gradientBgAnimation 15s ease infinite; border-radius: 25px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); color: #333; overflow: hidden; z-index: 9999; transition: all 0.3s ease-in-out; cursor: pointer; } #tm-video-assistant:not(.expanded):hover { transform: scale(1.1); box-shadow: 0 6px 30px rgba(0,0,0,0.2); } @keyframes gradientBgAnimation { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } #tm-assistant-icon { width: 50px; height: 50px; display: flex; justify-content: center; align-items: center; transition: transform 0.3s ease; } #tm-assistant-content { display: none; padding: 20px; flex-direction: column; align-items: flex-start; width: 100%; box-sizing: border-box; opacity: 0; transition: opacity 0.3s ease; } #tm-video-assistant.expanded #tm-assistant-content { opacity: 1; } #tm-video-assistant h3 { margin: 0 0 10px 0; font-size: 18px; font-weight: 600; color: #333; } .tm-info-container { width: 100%; height: 80px; margin: 10px 0; padding: 10px; border-radius: 5px; transition: all 0.3s ease; box-sizing: border-box; overflow: hidden; display: flex; flex-direction: column; } #tm-video-title-container { background: rgba(255, 255, 255, 0.3); border-left: 4px solid #6495ff; } #tm-video-src-container { background: rgba(255, 255, 255, 0.2); border-left: 4px solid #ff2201; } .tm-info-label { font-size: 12px; font-weight: bold; margin-bottom: 5px; color: #555; } .tm-info-content { font-size: 14px; font-weight: bold; word-break: break-all; white-space: pre-wrap; flex-grow: 1; height: 50px; overflow-y: auto; } .tm-info-content::-webkit-scrollbar { width: 6px; } .tm-info-content::-webkit-scrollbar-track { background: rgba(255, 255, 255, 0.1); } .tm-info-content::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, 0.2); border-radius: 3px; } #tm-button-container { display: flex; justify-content: space-between; width: 100%; margin-top: 15px; } .tm-button { background: rgba(255, 140, 0, 0.8); border: none; color: white; padding: 8px 15px; border-radius: 25px; cursor: pointer; font-size: 14px; transition: all 0.3s ease; width: 48%; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 5px rgba(0,0,0,0.1); } .tm-button:hover { background: rgba(255, 215, 0, 0.9); transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.15); } .tm-button-icon { margin-right: 5px; font-size: 16px; } #tm-status { font-size: 12px; margin-top: 10px; font-style: italic; width: 100%; text-align: center; color: #555; } #tm-header { display: flex; justify-content: space-between; align-items: center; width: 100%; margin-bottom: 15px; } #tm-download-guide-button { position: absolute; top: 10px; right: 10px; background: #4CAF50; border: none; color: white; padding: 5px 10px; border-radius: 15px; cursor: pointer; font-size: 12px; font-weight: bold; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 5px rgba(0,0,0,0.2); z-index: 1; } #tm-download-guide-button:hover { background: #45a049; transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.2); } #tm-download-guide-button .tm-button-icon { margin-right: 4px; font-size: 14px; } `; document.head.appendChild(style); let lastUrl = location.href; let detectInterval; let isExpanded = false; let initialTop; async function getVideoInfo(maxAttempts = 10, interval = 1000) { const assistant = document.getElementById('tm-video-assistant'); for (let attempt = 0; attempt < maxAttempts; attempt++) { const playerDiv = document.querySelector('div.prism-player'); if (playerDiv) { const videoElement = playerDiv.querySelector('video'); if (videoElement && videoElement.src) { const newSrc = videoElement.src; const oldSrc = document.getElementById('tm-video-src').textContent; document.getElementById('tm-video-src').textContent = newSrc; // 获取并设置视频标题 const content = document.title.split('|')[0].trim(); document.getElementById('tm-video-title').textContent = content; if (newSrc !== oldSrc && !isExpanded) { assistant.style.animation = "pulse 0.5s ease-in-out"; setTimeout(() => { assistant.style.animation = ""; }, 1000); } return { src: newSrc, title: content }; } } await new Promise(resolve => setTimeout(resolve, interval)); } document.getElementById('tm-video-src').textContent = '未找到视频源'; document.getElementById('tm-video-title').textContent = '未找到视频标题'; return null; } function checkUrlAndExecute() { if (lastUrl !== location.href) { lastUrl = location.href; getVideoInfo(); } } document.getElementById('tm-copy-src').addEventListener('click', function(e) { e.stopPropagation(); const src = document.getElementById('tm-video-src').textContent; if (src && src !== '等待检测视频源...' && src !== '未找到视频源') { navigator.clipboard.writeText(src).then(() => { showStatus('链接已复制!'); }).catch(err => { console.error('复制失败:', err); showStatus('复制失败,请重试'); }); } else { showStatus('暂无可复制的链接'); } }); document.getElementById('tm-open-src').addEventListener('click', function(e) { e.stopPropagation(); const src = document.getElementById('tm-video-src').textContent; if (src && src !== '等待检测视频源...' && src !== '未找到视频源') { window.open(src, '_blank'); showStatus('已打开(小心音量爆炸哦~)'); } else { showStatus('暂无可打开的链接'); } }); document.getElementById('tm-download-guide-button').addEventListener('click', function(e) { e.stopPropagation(); showDownloadGuide(); }); function showDownloadGuide() { const guideModal = document.createElement('div'); guideModal.id = 'tm-guide-modal'; guideModal.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.9); background: linear-gradient(135deg, #FFF9C4, #FFE082); padding: 30px; border-radius: 20px; box-shadow: 0 10px 30px rgba(255, 152, 0, 0.3), 0 0 0 1px rgba(255, 152, 0, 0.1); z-index: 10000; max-width: 90%; width: 500px; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; color: #5D4037; opacity: 0; transition: all 0.3s ease-in-out; overflow: hidden; `; // 添加动态背景 const background = document.createElement('div'); background.style.cssText = ` position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(135deg, #FFF9C4, #FFE082); z-index: -1; animation: gradientBG 15s ease infinite; `; guideModal.appendChild(background); // 添加样式 const style = document.createElement('style'); style.textContent = ` @keyframes gradientBG { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } .step-icon { width: 24px; height: 24px; } .step-content { display: block; } `; document.head.appendChild(style); const keywords = ['打开', '新标签页', '三点', '下载', '右键', '另存为']; const highlightKeywords = (text) => { return keywords.reduce((acc, keyword) => { return acc.replace(new RegExp(keyword, 'g'), `<strong style="color: #E65100; font-weight: 600;">${keyword}</strong>`); }, text); }; const steps = [ { icon: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-external-link"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>', text: '打开视频链接。' }, { icon: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-play-circle"><circle cx="12" cy="12" r="10"></circle><polygon points="10 8 16 12 10 16 10 8"></polygon></svg>', text: '在新标签页中找到播放器。' }, { icon: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-more-vertical"><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg>', text: '点击右下角"三点"图标。' }, { icon: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-download"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>', text: '选择下载选项。' }, { icon: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-menu"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>', text: '如果没有下载选项,尝试右键点击视频并选择"将视频另存为……"。' } ]; guideModal.innerHTML = ` <h2 style="margin-top: 0; color: #FF8F00; text-align: center; font-size: 28px; font-weight: 600;">如何下载视频</h2> <ol style="padding-left: 0; counter-reset: item; list-style-type: none;"> ${steps.map((step, index) => ` <li style="margin-bottom: 20px; position: relative; padding-left: 70px; line-height: 1.6; opacity: 0; transform: translateY(20px); transition: all 0.5s ease; overflow: hidden; min-height: 40px;"> <span style="position: absolute; left: 0; top: 0; background: #FF8F00; color: white; width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; box-shadow: 0 2px 5px rgba(255, 143, 0, 0.3);">${index + 1}</span> <span class="step-icon" style="position: absolute; left: 35px; top: 2px;">${step.icon}</span> <span class="step-content" style="display: block; margin-left: 10px;">${highlightKeywords(step.text)}</span> </li> `).join('')} </ol> <button id="close-guide" style=" padding: 12px 25px; background: #FF8F00; color: white; border: none; border-radius: 25px; cursor: pointer; font-size: 18px; font-weight: 600; display: block; margin: 25px auto 0; transition: all 0.3s ease; box-shadow: 0 4px 6px rgba(255, 143, 0, 0.2); ">我知道了</button> `; document.body.appendChild(guideModal); // 添加打开动画 setTimeout(() => { guideModal.style.opacity = '1'; guideModal.style.transform = 'translate(-50%, -50%) scale(1)'; // 逐步显示每个步骤 const steps = guideModal.querySelectorAll('li'); steps.forEach((step, index) => { setTimeout(() => { step.style.opacity = '1'; step.style.transform = 'translateY(0)'; }, 200 * (index + 1)); }); }, 50); const closeButton = document.getElementById('close-guide'); closeButton.addEventListener('click', () => { guideModal.style.opacity = '0'; guideModal.style.transform = 'translate(-50%, -50%) scale(0.9)'; setTimeout(() => document.body.removeChild(guideModal), 300); }); closeButton.addEventListener('mouseover', () => { closeButton.style.background = '#F57C00'; closeButton.style.transform = 'translateY(-2px)'; closeButton.style.boxShadow = '0 6px 8px rgba(255, 143, 0, 0.3)'; }); closeButton.addEventListener('mouseout', () => { closeButton.style.background = '#FF8F00'; closeButton.style.transform = 'translateY(0)'; closeButton.style.boxShadow = '0 4px 6px rgba(255, 143, 0, 0.2)'; }); } function showStatus(message) { const statusElement = document.getElementById('tm-status'); statusElement.textContent = message; statusElement.style.opacity = '1'; setTimeout(() => { statusElement.style.opacity = '0'; }, 5000); } // 切换展开/收起状态 document.getElementById('tm-video-assistant').addEventListener('click', function() { if (!initialTop) { initialTop = this.offsetTop; } const centerY = initialTop + 25; const lottiePlayer = this.querySelector('dotlottie-player'); if (isExpanded) { this.style.width = '50px'; this.style.height = '50px'; this.style.borderRadius = '25px'; this.style.top = `${initialTop}px`; document.getElementById('tm-assistant-content').style.display = 'none'; lottiePlayer.style.transform = 'rotate(0deg)'; this.classList.remove('expanded'); this.style.animation = ''; } else { const expandedHeight = 420; this.style.width = '380px'; this.style.height = `${expandedHeight}px`; this.style.borderRadius = '15px'; this.style.top = `${centerY - expandedHeight / 5.5}px`; document.getElementById('tm-assistant-content').style.display = 'flex'; lottiePlayer.style.transform = 'rotate(360deg)'; this.classList.add('expanded'); } isExpanded = !isExpanded; }); // 初始执行 getVideoInfo(); detectInterval = setInterval(checkUrlAndExecute, 1000); // 监听 popstate 事件(处理浏览器前进/后退) window.addEventListener('popstate', checkUrlAndExecute); }