// ==UserScript== // @name WHUT教学平台UMOOC PDF下载器 // @name:en WHUT JXPT PDF Downloader // @namespace http://tampermonkey.net/ // @version 1.0 // @description 【正式版】一款专为武汉理工大学教学平台(jxpt.whut.edu.cn)设计的PDF下载助手。拥有现代化UI、全自动后台扫描、智能识别页面类型、精准解析真实下载地址、支持批量下载和自由拖拽等特性,提供极致的用户体验。 // @description:en [Official Release] A PDF download helper for WHUT's teaching platform (jxpt.whut.edu.cn). Features a modern UI, automatic background scanning, smart page type detection, accurate real URL parsing, batch download support, and a draggable button for the ultimate user experience. // @author 毫厘 // @match https://jxpt.whut.edu.cn/* // @icon https://www.google.com/s2/favicons?sz=64&domain=whut.edu.cn // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; let isInitialized = false; let scanningInProgress = false; let cachedFiles = []; let isDragging = false; let dragOffset = { x: 0, y: 0 }; // 防抖函数,避免重复初始化 function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // 优化的监控逻辑 const checkAndInit = debounce(() => { try { const mainFrame = document.querySelector('frame[name="mainFrame"]'); if (!mainFrame || isInitialized) return; const doc = mainFrame.contentDocument; if (!doc || !doc.body || doc.getElementById('pdf-floating-btn')) return; isInitialized = true; initializeModernDownloader(mainFrame); } catch (error) { // Silently handle cross-origin errors } }, 500); setInterval(checkAndInit, 2000); /** * 现代化UI和智能扫描系统 */ async function initializeModernDownloader(frame) { const doc = frame.contentDocument; // 1. 注入现代化CSS样式 injectModernStyles(doc); // 2. 创建现代化UI组件 createModernUI(doc); // 3. 绑定交互事件 bindUIEvents(doc, frame); // 4. 启动智能扫描 await performIntelligentScan(frame, doc); } function injectModernStyles(doc) { const style = doc.createElement('style'); style.textContent = ` /* 主按钮 - 现代化设计,自由拖拽 */ #pdf-floating-btn { position: fixed; top: 30px; right: 30px; z-index: 10000; width: 60px; height: 60px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 50%; cursor: grab; font-size: 28px; box-shadow: 0 6px 20px rgba(102, 126, 234, 0.3); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); display: flex; align-items: center; justify-content: center; user-select: none; backdrop-filter: blur(10px); border: 2px solid rgba(255, 255, 255, 0.1); } #pdf-floating-btn:hover { transform: scale(1.05); box-shadow: 0 8px 25px rgba(102, 126, 234, 0.5); background: linear-gradient(135deg, #7c8ef7 0%, #8a5fb8 100%); } #pdf-floating-btn.dragging { cursor: grabbing; transform: scale(1.1); box-shadow: 0 10px 30px rgba(102, 126, 234, 0.7); transition: none; z-index: 10001; } #pdf-floating-btn.scanning { animation: pulse 2s infinite; } @keyframes pulse { 0% { box-shadow: 0 6px 20px rgba(102, 126, 234, 0.3); } 50% { box-shadow: 0 8px 30px rgba(102, 126, 234, 0.6); } 100% { box-shadow: 0 6px 20px rgba(102, 126, 234, 0.3); } } /* 计数标记 - 优化位置和样式 */ #pdf-count-badge { position: absolute; top: -5px; right: -5px; background: linear-gradient(135deg, #ff4757 0%, #ff3838 100%); color: white; border-radius: 50%; min-width: 22px; height: 22px; font-size: 11px; font-weight: bold; display: flex; align-items: center; justify-content: center; transform: scale(0); transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55); pointer-events: none; border: 2px solid white; box-shadow: 0 2px 8px rgba(255, 71, 87, 0.4); } #pdf-count-badge.show { transform: scale(1); } /* 侧边栏 - 重新设计布局 */ #pdf-modern-sidebar { position: fixed; top: 0; right: -450px; width: 420px; height: 100vh; z-index: 9999; background: linear-gradient(145deg, #ffffff 0%, #f8fafc 100%); box-shadow: -8px 0 40px rgba(0, 0, 0, 0.12); transition: transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); display: flex; flex-direction: column; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; border-left: 1px solid rgba(226, 232, 240, 0.8); } #pdf-modern-sidebar.visible { transform: translateX(-450px); } /* 遮罩层 */ #pdf-overlay { display: none; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(15, 23, 42, 0.5); z-index: 9998; backdrop-filter: blur(4px); transition: opacity 0.3s ease; } #pdf-modern-sidebar.visible + #pdf-overlay { display: block; } /* 头部区域 - 重新设计 */ .pdf-header { padding: 28px 24px 24px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; position: relative; border-bottom: 1px solid rgba(255, 255, 255, 0.1); } .pdf-header::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: radial-gradient(circle at 30% 20%, rgba(255,255,255,0.1) 0%, transparent 50%); } .pdf-header h2 { margin: 0 0 8px 0; font-size: 22px; font-weight: 700; position: relative; letter-spacing: -0.5px; } .pdf-status { font-size: 14px; opacity: 0.95; position: relative; font-weight: 500; } .pdf-close-btn { position: absolute; top: 24px; right: 24px; background: rgba(255, 255, 255, 0.15); border: none; color: white; width: 36px; height: 36px; border-radius: 50%; cursor: pointer; font-size: 20px; transition: all 0.3s; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(10px); } .pdf-close-btn:hover { background: rgba(255, 255, 255, 0.25); transform: rotate(90deg); } /* 进度条区域 - 更清晰的视觉层次 */ .pdf-progress { background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); color: #92400e; padding: 16px 24px; font-size: 14px; border-left: 4px solid #f59e0b; margin: 0; font-weight: 500; display: flex; align-items: center; border-bottom: 1px solid rgba(245, 158, 11, 0.2); } /* 内容区域 - 优化滚动和间距 */ .pdf-content { flex: 1; overflow-y: auto; padding: 0; background: #fafbfc; } .pdf-content::-webkit-scrollbar { width: 6px; } .pdf-content::-webkit-scrollbar-track { background: transparent; } .pdf-content::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; } .pdf-content::-webkit-scrollbar-thumb:hover { background: #94a3b8; } /* 文件列表 - 更好的视觉组织 */ .pdf-file-list { list-style: none; margin: 0; padding: 16px 0; } .pdf-file-item { display: flex; align-items: center; padding: 18px 24px; margin: 0 16px 12px; background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08); transition: all 0.3s; border: 1px solid rgba(226, 232, 240, 0.6); } .pdf-file-item:hover { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(15, 23, 42, 0.12); border-color: rgba(102, 126, 234, 0.3); } .pdf-file-icon { width: 44px; height: 44px; background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); border-radius: 10px; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 12px; margin-right: 16px; flex-shrink: 0; box-shadow: 0 4px 12px rgba(239, 68, 68, 0.25); } .pdf-file-info { flex: 1; min-width: 0; margin-right: 12px; } .pdf-file-name { font-weight: 600; color: #1e293b; margin-bottom: 6px; font-size: 14px; line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .pdf-file-size { font-size: 12px; color: #64748b; font-weight: 500; } .pdf-download-single { background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); color: white; border: none; padding: 10px 18px; border-radius: 8px; font-size: 12px; font-weight: 600; cursor: pointer; transition: all 0.3s; box-shadow: 0 3px 10px rgba(59, 130, 246, 0.3); min-width: 70px; display: flex; align-items: center; justify-content: center; } .pdf-download-single:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 6px 16px rgba(59, 130, 246, 0.4); background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%); } .pdf-download-single:disabled { background: #94a3b8; cursor: not-allowed; transform: none; box-shadow: none; } /* 操作按钮区域 - 更突出的CTA */ .pdf-actions { padding: 24px; background: white; border-top: 1px solid #e2e8f0; box-shadow: 0 -4px 20px rgba(15, 23, 42, 0.05); } .pdf-download-all { width: 100%; padding: 16px; background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: white; border: none; border-radius: 12px; font-size: 16px; font-weight: 700; cursor: pointer; transition: all 0.3s; box-shadow: 0 4px 16px rgba(16, 185, 129, 0.3); letter-spacing: 0.5px; text-transform: uppercase; position: relative; overflow: hidden; } .pdf-download-all::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); transition: left 0.5s; } .pdf-download-all:hover:not(:disabled)::before { left: 100%; } .pdf-download-all:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(16, 185, 129, 0.4); background: linear-gradient(135deg, #059669 0%, #047857 100%); } .pdf-download-all:disabled { background: linear-gradient(135deg, #94a3b8 0%, #64748b 100%); cursor: not-allowed; transform: none; box-shadow: none; } /* 加载状态 */ .loading-spinner { display: inline-block; width: 16px; height: 16px; border: 2px solid rgba(255, 255, 255, 0.3); border-radius: 50%; border-top-color: #ffffff; animation: spin 1s ease-in-out infinite; margin-right: 8px; } @keyframes spin { to { transform: rotate(360deg); } } /* 空状态 - 更友好的设计 */ .pdf-empty-state { text-align: center; padding: 60px 24px; color: #64748b; } .pdf-empty-icon { font-size: 64px; margin-bottom: 20px; opacity: 0.6; filter: grayscale(0.3); } .pdf-empty-text { font-size: 16px; font-weight: 500; margin-bottom: 8px; color: #475569; } .pdf-empty-hint { font-size: 14px; color: #94a3b8; line-height: 1.5; } /* 响应式适配 */ @media (max-width: 480px) { #pdf-modern-sidebar { width: 100vw; right: -100vw; } #pdf-modern-sidebar.visible { transform: translateX(-100vw); } .pdf-file-item { margin: 0 8px 8px; padding: 14px 16px; } .pdf-header { padding: 20px 16px 16px; } .pdf-actions { padding: 16px; } } `; doc.head.appendChild(style); } function createModernUI(doc) { // 主浮动按钮 const floatingBtn = doc.createElement('button'); floatingBtn.id = 'pdf-floating-btn'; floatingBtn.innerHTML = '📄'; floatingBtn.className = 'scanning'; floatingBtn.title = '点击打开PDF下载器,拖拽可移动位置'; doc.body.appendChild(floatingBtn); // 计数标记 const countBadge = doc.createElement('div'); countBadge.id = 'pdf-count-badge'; countBadge.textContent = '0'; floatingBtn.appendChild(countBadge); // 侧边栏 const sidebar = doc.createElement('div'); sidebar.id = 'pdf-modern-sidebar'; sidebar.innerHTML = `