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