You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
255 lines
9.5 KiB
255 lines
9.5 KiB
class DownloadManager {
|
|
constructor() {
|
|
this.port = window.location.port || '55830';
|
|
this.baseUrl = `http://127.0.0.1:${this.port}/api`;
|
|
this.currentDownload = null;
|
|
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
document.getElementById('port').textContent = this.port;
|
|
|
|
// 绑定事件
|
|
document.getElementById('reloadBtn').addEventListener('click', () => this.reloadFolders());
|
|
document.getElementById('cleanupBtn').addEventListener('click', () => this.cleanupJson());
|
|
document.getElementById('closeModalBtn').addEventListener('click', () => this.hideModal());
|
|
document.querySelector('.close-btn').addEventListener('click', () => this.hideModal());
|
|
|
|
// 点击模态框外部关闭
|
|
document.getElementById('downloadModal').addEventListener('click', (e) => {
|
|
if (e.target === document.getElementById('downloadModal')) {
|
|
this.hideModal();
|
|
}
|
|
});
|
|
|
|
// 初始加载
|
|
this.reloadFolders();
|
|
}
|
|
|
|
showStatus(message, type = 'info') {
|
|
const statusEl = document.getElementById('statusMessage');
|
|
statusEl.textContent = message;
|
|
statusEl.style.borderLeftColor = type === 'error' ? '#ef4444' :
|
|
type === 'warning' ? '#f59e0b' : '#10b981';
|
|
}
|
|
|
|
showModal(title) {
|
|
document.getElementById('modalTitle').textContent = title;
|
|
document.getElementById('downloadModal').style.display = 'flex';
|
|
this.resetModal();
|
|
}
|
|
|
|
hideModal() {
|
|
document.getElementById('downloadModal').style.display = 'none';
|
|
if (this.currentDownload && this.currentDownload.abort) {
|
|
this.currentDownload.abort();
|
|
}
|
|
}
|
|
|
|
resetModal() {
|
|
document.getElementById('progressBar').style.width = '0%';
|
|
document.getElementById('progressText').textContent = '0%';
|
|
document.getElementById('totalFiles').textContent = '0';
|
|
document.getElementById('downloadedFiles').textContent = '0';
|
|
document.getElementById('pendingFiles').textContent = '0';
|
|
document.getElementById('downloadLog').innerHTML = '';
|
|
}
|
|
|
|
updateProgress(progress, stats) {
|
|
document.getElementById('progressBar').style.width = `${progress}%`;
|
|
document.getElementById('progressText').textContent = `${Math.round(progress)}%`;
|
|
|
|
if (stats) {
|
|
document.getElementById('totalFiles').textContent = stats.total || 0;
|
|
document.getElementById('downloadedFiles').textContent = stats.downloaded || 0;
|
|
document.getElementById('pendingFiles').textContent = stats.pending || 0;
|
|
}
|
|
}
|
|
|
|
addLogEntry(message, type = 'info') {
|
|
const logEl = document.getElementById('downloadLog');
|
|
const entry = document.createElement('div');
|
|
entry.className = `log-entry ${type}`;
|
|
entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
|
|
logEl.appendChild(entry);
|
|
logEl.scrollTop = logEl.scrollHeight;
|
|
}
|
|
|
|
async reloadFolders() {
|
|
try {
|
|
this.showStatus('正在加载文件夹...', 'info');
|
|
document.getElementById('reloadBtn').disabled = true;
|
|
|
|
const response = await fetch(`${this.baseUrl}/reload_folders`);
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
this.displayFolders(data.folders);
|
|
this.showStatus(`已加载 ${data.folders.length} 个未完成的任务`, 'success');
|
|
} else {
|
|
throw new Error(data.message || '加载失败');
|
|
}
|
|
} catch (error) {
|
|
console.error('加载失败:', error);
|
|
this.showStatus(`加载失败: ${error.message}`, 'error');
|
|
this.displayFolders([]);
|
|
} finally {
|
|
document.getElementById('reloadBtn').disabled = false;
|
|
}
|
|
}
|
|
|
|
displayFolders(folders) {
|
|
const foldersList = document.getElementById('foldersList');
|
|
const noFolders = document.getElementById('noFolders');
|
|
|
|
// 过滤掉进度为100%的文件夹
|
|
const incompleteFolders = folders.filter(folder => folder.progress < 100);
|
|
|
|
if (incompleteFolders.length === 0) {
|
|
foldersList.innerHTML = '';
|
|
noFolders.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
noFolders.style.display = 'none';
|
|
|
|
foldersList.innerHTML = incompleteFolders.map(folder => `
|
|
<div class="folder-item">
|
|
<div class="folder-info">
|
|
<div class="folder-title">
|
|
<i class="fas fa-book"></i>
|
|
${this.escapeHtml(folder.title)}
|
|
</div>
|
|
<div class="folder-stats">
|
|
<span>总文件: ${folder.total}</span>
|
|
<span>已下载: ${folder.downloaded}</span>
|
|
<span>缺失: ${folder.total - folder.downloaded}</span>
|
|
</div>
|
|
<div class="progress-container">
|
|
<div class="progress-wrapper">
|
|
<div class="progress">
|
|
<div class="progress-fill" style="width: ${folder.progress}%"></div>
|
|
</div>
|
|
<div class="progress-text">${folder.progress.toFixed(1)}%</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="folder-actions">
|
|
<button class="btn btn-primary btn-small" onclick="downloadManager.downloadFolder('${this.escapeAttr(folder.folder)}', '${this.escapeAttr(folder.title)}')">
|
|
<i class="fas fa-download"></i> 下载缺失文件 (${folder.total - folder.downloaded})
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
async downloadFolder(folderPath, title) {
|
|
try {
|
|
this.showModal(`正在下载: ${title}`);
|
|
this.addLogEntry('开始下载缺失文件...', 'info');
|
|
|
|
console.log('发送请求:', { folder: folderPath, title: title });
|
|
|
|
const response = await fetch(`${this.baseUrl}/download_missing`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
folder: folderPath,
|
|
title: title
|
|
})
|
|
});
|
|
|
|
// 检查响应状态
|
|
if (!response.ok) {
|
|
const errorText = await response.text();
|
|
console.error('HTTP错误:', response.status, errorText);
|
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
console.log('收到响应:', data);
|
|
|
|
if (data.success) {
|
|
// 更新进度
|
|
const totalSaved = (data.saved || 0) + (data.skipped || 0);
|
|
const total = data.total || 1;
|
|
const progress = totalSaved / total * 100;
|
|
|
|
this.updateProgress(progress, {
|
|
total: total,
|
|
downloaded: totalSaved,
|
|
pending: data.failed || 0
|
|
});
|
|
|
|
// 添加日志
|
|
if (data.details) {
|
|
data.details.forEach(detail => {
|
|
if (detail.status === 'success') {
|
|
this.addLogEntry(`成功下载: ${detail.key}`, 'success');
|
|
} else if (detail.status === 'failed') {
|
|
this.addLogEntry(`下载失败 ${detail.key}: ${detail.message}`, 'error');
|
|
} else if (detail.status === 'skipped') {
|
|
this.addLogEntry(`跳过: ${detail.key} (${detail.message})`, 'info');
|
|
}
|
|
});
|
|
}
|
|
|
|
this.addLogEntry(`下载完成: ${data.message}`, 'success');
|
|
|
|
// 刷新文件夹列表
|
|
setTimeout(() => this.reloadFolders(), 1000);
|
|
} else {
|
|
this.addLogEntry(`下载失败: ${data.message}`, 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('下载失败:', error);
|
|
this.addLogEntry(`下载失败: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
async cleanupJson() {
|
|
if (!confirm('确定要删除所有JSON文件吗?此操作不可恢复。')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
this.showStatus('正在清理JSON文件...', 'info');
|
|
document.getElementById('cleanupBtn').disabled = true;
|
|
|
|
const response = await fetch(`${this.baseUrl}/cleanup_json`, {
|
|
method: 'POST'
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
this.showStatus(data.message, 'success');
|
|
// 刷新文件夹列表
|
|
setTimeout(() => this.reloadFolders(), 500);
|
|
} else {
|
|
throw new Error(data.message || '清理失败');
|
|
}
|
|
} catch (error) {
|
|
console.error('清理失败:', error);
|
|
this.showStatus(`清理失败: ${error.message}`, 'error');
|
|
} finally {
|
|
document.getElementById('cleanupBtn').disabled = false;
|
|
}
|
|
}
|
|
|
|
escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
escapeAttr(text) {
|
|
return this.escapeHtml(text).replace(/"/g, '"');
|
|
}
|
|
}
|
|
|
|
// 初始化应用
|
|
const downloadManager = new DownloadManager(); |