main
parent
c166be9e62
commit
92010207b5
@ -0,0 +1,16 @@ |
|||||||
|
图片下载后端工具, 前端可用油猴脚本(随意, 只要能发起 post请求即可) |
||||||
|
|
||||||
|
post 数据格式为 |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"title": "文件夹名称", |
||||||
|
"source": "网站来源, 用于下载保存的文件夹", |
||||||
|
"imgs": { |
||||||
|
"0001": "https://icon-icons.com/images/menu_photos.png", |
||||||
|
"0002": "https://icon-icons.com/images/flags/zh.webp" |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
端口: 55830 |
||||||
@ -0,0 +1,420 @@ |
|||||||
|
// ==UserScript==
|
||||||
|
// @name hd4k_downloader_simple
|
||||||
|
// @namespace http://tampermonkey.net/
|
||||||
|
// @version 1.2
|
||||||
|
// @description 简单直接的自动翻页图片爬取
|
||||||
|
// @author Your Name
|
||||||
|
// @match *://*/*
|
||||||
|
// @grant GM_xmlhttpRequest
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
(function() { |
||||||
|
'use strict'; |
||||||
|
|
||||||
|
const CONFIG = { |
||||||
|
maxPages: 50, |
||||||
|
pageDelay: 1500, |
||||||
|
backendUrl: 'http://127.0.0.1:55830/api/save_json' |
||||||
|
}; |
||||||
|
|
||||||
|
let isCrawling = false; |
||||||
|
let allImages = {}; |
||||||
|
let currentPage = 1; |
||||||
|
let imgIndex = 1; |
||||||
|
let crawledUrls = []; |
||||||
|
const source = 'hd4k'; |
||||||
|
|
||||||
|
const createButton = () => { |
||||||
|
const button = document.createElement('button'); |
||||||
|
button.textContent = '开始爬取'; |
||||||
|
button.id = 'hd4k-btn'; |
||||||
|
|
||||||
|
button.style.position = 'fixed'; |
||||||
|
button.style.top = '14%'; |
||||||
|
button.style.right = '1%'; |
||||||
|
button.style.transform = 'translateY(-50%)'; |
||||||
|
button.style.padding = '8px 16px'; |
||||||
|
button.style.fontSize = '12px'; |
||||||
|
button.style.fontWeight = 'bold'; |
||||||
|
button.style.backgroundColor = '#2c80ff'; |
||||||
|
button.style.color = '#fff'; |
||||||
|
button.style.border = 'none'; |
||||||
|
button.style.borderRadius = '8px'; |
||||||
|
button.style.cursor = 'pointer'; |
||||||
|
button.style.zIndex = '10000'; |
||||||
|
button.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)'; |
||||||
|
button.style.transition = 'all 0.3s ease'; |
||||||
|
|
||||||
|
button.addEventListener('mouseenter', () => { |
||||||
|
if (!isCrawling) { |
||||||
|
button.style.backgroundColor = '#1a6ee0'; |
||||||
|
button.style.transform = 'translateY(-50%) scale(1.05)'; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
button.addEventListener('mouseleave', () => { |
||||||
|
if (!isCrawling) { |
||||||
|
button.style.backgroundColor = '#2c80ff'; |
||||||
|
button.style.transform = 'translateY(-50%) scale(1)'; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
button.addEventListener('click', startCrawling); |
||||||
|
|
||||||
|
return button; |
||||||
|
}; |
||||||
|
|
||||||
|
const createStatusDisplay = () => { |
||||||
|
const statusDiv = document.createElement('div'); |
||||||
|
statusDiv.id = 'hd4k-status'; |
||||||
|
statusDiv.style.position = 'fixed'; |
||||||
|
statusDiv.style.top = '18%'; |
||||||
|
statusDiv.style.right = '1%'; |
||||||
|
statusDiv.style.padding = '10px'; |
||||||
|
statusDiv.style.backgroundColor = 'rgba(0,0,0,0.85)'; |
||||||
|
statusDiv.style.color = '#fff'; |
||||||
|
statusDiv.style.borderRadius = '5px'; |
||||||
|
statusDiv.style.fontSize = '12px'; |
||||||
|
statusDiv.style.zIndex = '9999'; |
||||||
|
statusDiv.style.minWidth = '180px'; |
||||||
|
statusDiv.style.display = 'none'; |
||||||
|
|
||||||
|
return statusDiv; |
||||||
|
}; |
||||||
|
|
||||||
|
const updateStatus = (message) => { |
||||||
|
const statusDiv = document.getElementById('hd4k-status'); |
||||||
|
if (statusDiv) { |
||||||
|
statusDiv.innerHTML = message; |
||||||
|
statusDiv.style.display = 'block'; |
||||||
|
} |
||||||
|
console.log(`[状态] ${message}`); |
||||||
|
}; |
||||||
|
|
||||||
|
const getCurrentPageImages = () => { |
||||||
|
const images = document.querySelectorAll('img'); |
||||||
|
const imageUrls = []; |
||||||
|
const seenUrls = new Set(); |
||||||
|
|
||||||
|
images.forEach(img => { |
||||||
|
let src = img.src || img.dataset.src || img.dataset.original || img.currentSrc; |
||||||
|
|
||||||
|
if (src && src.trim() && !src.startsWith('data:') && !src.startsWith('blob:')) { |
||||||
|
let fullUrl = src; |
||||||
|
if (src.startsWith('//')) { |
||||||
|
fullUrl = window.location.protocol + src; |
||||||
|
} else if (src.startsWith('/')) { |
||||||
|
fullUrl = window.location.origin + src; |
||||||
|
} else if (!src.startsWith('http')) { |
||||||
|
fullUrl = new URL(src, window.location.href).href; |
||||||
|
} |
||||||
|
|
||||||
|
const isImage = /\.(jpg|jpeg|png|gif|webp|bmp|tiff)(\?.*)?$/i.test(fullUrl); |
||||||
|
if (isImage && !seenUrls.has(fullUrl)) { |
||||||
|
seenUrls.add(fullUrl); |
||||||
|
imageUrls.push(fullUrl); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
return imageUrls; |
||||||
|
}; |
||||||
|
|
||||||
|
const buildPageUrl = (pageNum) => { |
||||||
|
const currentUrl = window.location.href; |
||||||
|
const htmlIndex = currentUrl.indexOf('html'); |
||||||
|
|
||||||
|
if (htmlIndex === -1) { |
||||||
|
console.error('URL中没有找到html'); |
||||||
|
return currentUrl; |
||||||
|
} |
||||||
|
|
||||||
|
const basePart = currentUrl.substring(0, htmlIndex + 4); |
||||||
|
|
||||||
|
if (pageNum === 1) { |
||||||
|
return basePart; |
||||||
|
} else { |
||||||
|
return basePart + '/' + pageNum; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const getCurrentPageNumber = () => { |
||||||
|
const currentUrl = window.location.href; |
||||||
|
const htmlIndex = currentUrl.indexOf('html'); |
||||||
|
|
||||||
|
if (htmlIndex === -1) return 1; |
||||||
|
|
||||||
|
const afterHtml = currentUrl.substring(htmlIndex + 4); |
||||||
|
const match = afterHtml.match(/^\/(\d+)/); |
||||||
|
|
||||||
|
if (match) { |
||||||
|
const pageNum = parseInt(match[1], 10); |
||||||
|
if (!isNaN(pageNum) && pageNum > 0) { |
||||||
|
return pageNum; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return 1; |
||||||
|
}; |
||||||
|
|
||||||
|
const sendToBackend = (data) => { |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
GM_xmlhttpRequest({ |
||||||
|
method: 'POST', |
||||||
|
url: CONFIG.backendUrl, |
||||||
|
headers: { |
||||||
|
'Content-Type': 'application/json' |
||||||
|
}, |
||||||
|
data: JSON.stringify(data), |
||||||
|
onload: function(response) { |
||||||
|
if (response.status >= 200 && response.status < 300) { |
||||||
|
resolve(response); |
||||||
|
} else { |
||||||
|
reject(new Error(`HTTP ${response.status}: ${response.statusText}`)); |
||||||
|
} |
||||||
|
}, |
||||||
|
onerror: function(error) { |
||||||
|
reject(error); |
||||||
|
}, |
||||||
|
timeout: 10000 |
||||||
|
}); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const sendAllData = async () => { |
||||||
|
updateStatus('整理数据并发送到后端...'); |
||||||
|
|
||||||
|
const finalImages = {}; |
||||||
|
let totalCount = 0; |
||||||
|
|
||||||
|
const sortedPages = Object.keys(allImages).map(Number).sort((a, b) => a - b); |
||||||
|
|
||||||
|
for (const page of sortedPages) { |
||||||
|
if (allImages[page]) { |
||||||
|
for (const imgUrl of allImages[page]) { |
||||||
|
const key = String(imgIndex).padStart(4, '0'); |
||||||
|
finalImages[key] = imgUrl; |
||||||
|
imgIndex++; |
||||||
|
totalCount++; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const data = { |
||||||
|
title: document.title || '无标题', |
||||||
|
source: source, |
||||||
|
url: buildPageUrl(1), |
||||||
|
totalPages: sortedPages.length, |
||||||
|
totalImages: totalCount, |
||||||
|
imgs: finalImages |
||||||
|
}; |
||||||
|
|
||||||
|
console.log('准备发送的数据:', data); |
||||||
|
|
||||||
|
try { |
||||||
|
await sendToBackend(data); |
||||||
|
updateStatus(`✅ 发送成功!<br>共 ${sortedPages.length} 页<br>${totalCount} 张图片`); |
||||||
|
return true; |
||||||
|
} catch (error) { |
||||||
|
updateStatus(`❌ 发送失败: ${error.message}`); |
||||||
|
return false; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const beginPageProcessing = async () => { |
||||||
|
const isCrawlSession = sessionStorage.getItem('hd4k_crawling') === 'true'; |
||||||
|
|
||||||
|
if (!isCrawlSession) { |
||||||
|
console.log('不在爬取会话中,停止处理'); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const currentUrl = window.location.href; |
||||||
|
|
||||||
|
if (crawledUrls.includes(currentUrl)) { |
||||||
|
updateStatus('检测到重复URL,停止爬取'); |
||||||
|
await finishCrawling(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
crawledUrls.push(currentUrl); |
||||||
|
sessionStorage.setItem('hd4k_crawled_urls', JSON.stringify(crawledUrls)); |
||||||
|
|
||||||
|
const urlPageNum = getCurrentPageNumber(); |
||||||
|
|
||||||
|
if (currentPage !== urlPageNum) { |
||||||
|
currentPage = urlPageNum; |
||||||
|
} |
||||||
|
|
||||||
|
updateStatus(`处理第 ${currentPage} 页...`); |
||||||
|
const imageUrls = getCurrentPageImages(); |
||||||
|
console.log(`第 ${currentPage} 页找到 ${imageUrls.length} 张图片`); |
||||||
|
|
||||||
|
if (imageUrls.length > 0) { |
||||||
|
allImages[currentPage] = imageUrls; |
||||||
|
sessionStorage.setItem('hd4k_all_images', JSON.stringify(allImages)); |
||||||
|
updateStatus(`第 ${currentPage} 页: 找到 ${imageUrls.length} 张图片`); |
||||||
|
|
||||||
|
setTimeout(async () => { |
||||||
|
const nextPage = currentPage + 1; |
||||||
|
|
||||||
|
if (nextPage > CONFIG.maxPages) { |
||||||
|
updateStatus(`已达到最大页数 ${CONFIG.maxPages}`); |
||||||
|
await finishCrawling(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const nextUrl = buildPageUrl(nextPage); |
||||||
|
|
||||||
|
if (crawledUrls.includes(nextUrl)) { |
||||||
|
updateStatus('下一页URL已爬取过,停止爬取'); |
||||||
|
await finishCrawling(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
updateStatus(`准备跳转到第 ${nextPage} 页`); |
||||||
|
sessionStorage.setItem('hd4k_current_page', nextPage.toString()); |
||||||
|
|
||||||
|
setTimeout(() => { |
||||||
|
window.location.href = nextUrl; |
||||||
|
}, CONFIG.pageDelay); |
||||||
|
|
||||||
|
}, CONFIG.pageDelay); |
||||||
|
|
||||||
|
} else { |
||||||
|
updateStatus(`第 ${currentPage} 页: 无图片`); |
||||||
|
|
||||||
|
setTimeout(async () => { |
||||||
|
await finishCrawling(); |
||||||
|
}, CONFIG.pageDelay); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const startCrawling = async () => { |
||||||
|
if (isCrawling) { |
||||||
|
alert('正在爬取中,请稍候...'); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const button = document.getElementById('hd4k-btn'); |
||||||
|
button.textContent = '爬取中...'; |
||||||
|
button.style.backgroundColor = '#ff9800'; |
||||||
|
button.disabled = true; |
||||||
|
|
||||||
|
isCrawling = true; |
||||||
|
allImages = {}; |
||||||
|
crawledUrls = []; |
||||||
|
currentPage = 1; |
||||||
|
imgIndex = 1; |
||||||
|
|
||||||
|
sessionStorage.removeItem('hd4k_all_images'); |
||||||
|
sessionStorage.removeItem('hd4k_crawled_urls'); |
||||||
|
|
||||||
|
updateStatus('开始自动翻页爬取...'); |
||||||
|
|
||||||
|
const firstPageUrl = buildPageUrl(1); |
||||||
|
const currentUrl = window.location.href; |
||||||
|
|
||||||
|
sessionStorage.setItem('hd4k_crawling', 'true'); |
||||||
|
sessionStorage.setItem('hd4k_current_page', '1'); |
||||||
|
|
||||||
|
if (currentUrl !== firstPageUrl) { |
||||||
|
updateStatus(`跳转到第一页`); |
||||||
|
window.location.href = firstPageUrl; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
beginPageProcessing(); |
||||||
|
}; |
||||||
|
|
||||||
|
const finishCrawling = async () => { |
||||||
|
sessionStorage.removeItem('hd4k_crawling'); |
||||||
|
sessionStorage.removeItem('hd4k_current_page'); |
||||||
|
|
||||||
|
if (Object.keys(allImages).length > 0) { |
||||||
|
await sendAllData(); |
||||||
|
} else { |
||||||
|
updateStatus('未找到任何图片数据'); |
||||||
|
} |
||||||
|
|
||||||
|
const button = document.getElementById('hd4k-btn'); |
||||||
|
button.textContent = '开始爬取'; |
||||||
|
button.style.backgroundColor = '#2c80ff'; |
||||||
|
button.disabled = false; |
||||||
|
isCrawling = false; |
||||||
|
|
||||||
|
setTimeout(() => { |
||||||
|
const statusDiv = document.getElementById('hd4k-status'); |
||||||
|
if (statusDiv) { |
||||||
|
statusDiv.style.display = 'none'; |
||||||
|
} |
||||||
|
}, 5000); |
||||||
|
}; |
||||||
|
|
||||||
|
const onPageLoad = () => { |
||||||
|
const isCrawlSession = sessionStorage.getItem('hd4k_crawling') === 'true'; |
||||||
|
|
||||||
|
if (isCrawlSession) { |
||||||
|
isCrawling = true; |
||||||
|
currentPage = getCurrentPageNumber(); |
||||||
|
|
||||||
|
const savedImages = sessionStorage.getItem('hd4k_all_images'); |
||||||
|
const savedUrls = sessionStorage.getItem('hd4k_crawled_urls'); |
||||||
|
|
||||||
|
if (savedImages) { |
||||||
|
allImages = JSON.parse(savedImages); |
||||||
|
} |
||||||
|
|
||||||
|
if (savedUrls) { |
||||||
|
crawledUrls = JSON.parse(savedUrls); |
||||||
|
} |
||||||
|
|
||||||
|
setTimeout(() => { |
||||||
|
beginPageProcessing(); |
||||||
|
}, 1500); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const init = () => { |
||||||
|
if (!document.getElementById('hd4k-btn')) { |
||||||
|
const button = createButton(); |
||||||
|
const statusDiv = createStatusDisplay(); |
||||||
|
|
||||||
|
document.body.appendChild(button); |
||||||
|
document.body.appendChild(statusDiv); |
||||||
|
|
||||||
|
updateStatus('HD4K下载器已加载<br>点击按钮开始自动翻页爬取'); |
||||||
|
setTimeout(() => { |
||||||
|
const statusDiv = document.getElementById('hd4k-status'); |
||||||
|
if (statusDiv) { |
||||||
|
statusDiv.style.display = 'none'; |
||||||
|
} |
||||||
|
}, 3000); |
||||||
|
|
||||||
|
onPageLoad(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
if (document.readyState === 'loading') { |
||||||
|
document.addEventListener('DOMContentLoaded', init); |
||||||
|
} else { |
||||||
|
const isCrawlSession = sessionStorage.getItem('hd4k_crawling') === 'true'; |
||||||
|
if (isCrawlSession) { |
||||||
|
isCrawling = true; |
||||||
|
const button = createButton(); |
||||||
|
button.textContent = '爬取中...'; |
||||||
|
button.style.backgroundColor = '#ff9800'; |
||||||
|
button.disabled = true; |
||||||
|
document.body.appendChild(button); |
||||||
|
|
||||||
|
const statusDiv = createStatusDisplay(); |
||||||
|
document.body.appendChild(statusDiv); |
||||||
|
updateStatus('检测到未完成的爬取任务,继续执行...'); |
||||||
|
|
||||||
|
setTimeout(onPageLoad, 1500); |
||||||
|
} else { |
||||||
|
init(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
})(); |
||||||
@ -1,158 +0,0 @@ |
|||||||
// ==UserScript==
|
|
||||||
// @name hd4k_downloader
|
|
||||||
// @namespace http://tampermonkey.net/
|
|
||||||
// @version 1.0
|
|
||||||
// @description 提取页面图片并发送到后端
|
|
||||||
// @author Jack
|
|
||||||
// @match *://*/*
|
|
||||||
// @grant GM_xmlhttpRequest
|
|
||||||
// ==/UserScript==
|
|
||||||
|
|
||||||
(function() { |
|
||||||
'use strict'; |
|
||||||
|
|
||||||
// 创建按钮
|
|
||||||
const createButton = () => { |
|
||||||
const button = document.createElement('button'); |
|
||||||
button.textContent = '提取图片'; |
|
||||||
button.id = 'hd4k-extract-btn'; |
|
||||||
|
|
||||||
// 按钮样式 - 修改为蓝色系
|
|
||||||
button.style.position = 'fixed'; |
|
||||||
button.style.top = '12.5%'; |
|
||||||
button.style.right = '1%'; |
|
||||||
button.style.transform = 'translateY(-50%)'; |
|
||||||
button.style.padding = '6px 12px'; |
|
||||||
button.style.fontSize = '12px'; |
|
||||||
button.style.fontWeight = 'bold'; |
|
||||||
button.style.backgroundColor = '#2c80ff'; |
|
||||||
button.style.color = '#fff'; |
|
||||||
button.style.border = 'none'; |
|
||||||
button.style.borderRadius = '8px'; |
|
||||||
button.style.cursor = 'pointer'; |
|
||||||
button.style.zIndex = '10000'; |
|
||||||
button.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)'; |
|
||||||
button.style.transition = 'all 0.3s ease'; |
|
||||||
|
|
||||||
// 悬停效果
|
|
||||||
button.addEventListener('mouseenter', () => { |
|
||||||
button.style.backgroundColor = '#1a6ee0'; |
|
||||||
button.style.transform = 'translateY(-50%) scale(1.05)'; |
|
||||||
}); |
|
||||||
|
|
||||||
button.addEventListener('mouseleave', () => { |
|
||||||
button.style.backgroundColor = '#2c80ff'; |
|
||||||
button.style.transform = 'translateY(-50%) scale(1)'; |
|
||||||
}); |
|
||||||
|
|
||||||
// 点击事件
|
|
||||||
button.addEventListener('click', extractAndSendImages); |
|
||||||
|
|
||||||
return button; |
|
||||||
}; |
|
||||||
|
|
||||||
// 获取所有图片URL
|
|
||||||
const getAllImageUrls = () => { |
|
||||||
const images = document.querySelectorAll('img'); |
|
||||||
const imageUrls = {}; |
|
||||||
let index = 1; |
|
||||||
|
|
||||||
images.forEach(img => { |
|
||||||
let src = img.src || img.dataset.src || img.currentSrc; |
|
||||||
|
|
||||||
// 过滤掉空URL、base64和数据URL
|
|
||||||
if (src && !src.startsWith('data:') && !src.startsWith('blob:')) { |
|
||||||
const key = String(index).padStart(4, '0'); |
|
||||||
imageUrls[key] = src; |
|
||||||
index++; |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
return imageUrls; |
|
||||||
}; |
|
||||||
|
|
||||||
// 提取并发送数据
|
|
||||||
const extractAndSendImages = () => { |
|
||||||
try { |
|
||||||
// 获取当前页面信息
|
|
||||||
const title = document.title || '无标题'; |
|
||||||
const url = window.location.href; |
|
||||||
const imageUrls = getAllImageUrls(); |
|
||||||
|
|
||||||
// 准备数据
|
|
||||||
const data = { |
|
||||||
title: title, |
|
||||||
url: url, |
|
||||||
imgs: imageUrls |
|
||||||
}; |
|
||||||
|
|
||||||
console.log('提取的数据:', data); |
|
||||||
|
|
||||||
// 显示加载状态
|
|
||||||
const button = document.getElementById('hd4k-extract-btn'); |
|
||||||
const originalText = button.textContent; |
|
||||||
button.textContent = '处理中...'; |
|
||||||
button.disabled = true; |
|
||||||
|
|
||||||
// 发送到后端
|
|
||||||
GM_xmlhttpRequest({ |
|
||||||
method: 'POST', |
|
||||||
url: 'http://127.0.0.1:55830/api/save_imgs', |
|
||||||
headers: { |
|
||||||
'Content-Type': 'application/json' |
|
||||||
}, |
|
||||||
data: JSON.stringify(data), |
|
||||||
onload: function(response) { |
|
||||||
console.log('发送成功:', response); |
|
||||||
|
|
||||||
// 恢复按钮状态并显示成功
|
|
||||||
button.textContent = '成功!'; |
|
||||||
button.style.backgroundColor = '#28a745'; |
|
||||||
|
|
||||||
setTimeout(() => { |
|
||||||
button.textContent = originalText; |
|
||||||
button.style.backgroundColor = '#2c80ff'; |
|
||||||
button.disabled = false; |
|
||||||
}, 1500); |
|
||||||
}, |
|
||||||
onerror: function(error) { |
|
||||||
console.error('发送失败:', error); |
|
||||||
|
|
||||||
// 恢复按钮状态并显示错误
|
|
||||||
button.textContent = '失败!'; |
|
||||||
button.style.backgroundColor = '#dc3545'; |
|
||||||
|
|
||||||
setTimeout(() => { |
|
||||||
button.textContent = originalText; |
|
||||||
button.style.backgroundColor = '#2c80ff'; |
|
||||||
button.disabled = false; |
|
||||||
}, 1500); |
|
||||||
|
|
||||||
alert('发送失败,请检查后端服务是否运行: ' + error.statusText); |
|
||||||
}, |
|
||||||
timeout: 10000 |
|
||||||
}); |
|
||||||
|
|
||||||
} catch (error) { |
|
||||||
console.error('提取图片时出错:', error); |
|
||||||
alert('提取图片时出错: ' + error.message); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
// 初始化 - 添加按钮到页面
|
|
||||||
const init = () => { |
|
||||||
// 确保按钮不会重复添加
|
|
||||||
if (!document.getElementById('hd4k-extract-btn')) { |
|
||||||
const button = createButton(); |
|
||||||
document.body.appendChild(button); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
// 页面加载完成后初始化
|
|
||||||
if (document.readyState === 'loading') { |
|
||||||
document.addEventListener('DOMContentLoaded', init); |
|
||||||
} else { |
|
||||||
init(); |
|
||||||
} |
|
||||||
|
|
||||||
})(); |
|
||||||
@ -1,84 +0,0 @@ |
|||||||
<!DOCTYPE html> |
|
||||||
<html lang="zh-CN"> |
|
||||||
<head> |
|
||||||
<meta charset="UTF-8"> |
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
||||||
<title>HD4K 下载管理</title> |
|
||||||
<link rel="stylesheet" href="index.css"> |
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
||||||
</head> |
|
||||||
<body> |
|
||||||
<div class="container"> |
|
||||||
<header> |
|
||||||
<h1><i class="fas fa-download"></i> HD4K 下载管理</h1> |
|
||||||
<p class="subtitle">管理未完成的下载任务</p> |
|
||||||
</header> |
|
||||||
|
|
||||||
<div class="control-panel"> |
|
||||||
<button id="reloadBtn" class="btn btn-primary"> |
|
||||||
<i class="fas fa-sync-alt"></i> 重载下载文件夹 |
|
||||||
</button> |
|
||||||
<button id="cleanupBtn" class="btn btn-warning"> |
|
||||||
<i class="fas fa-trash-alt"></i> 清理JSON文件 |
|
||||||
</button> |
|
||||||
<div class="status" id="statusMessage"></div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div class="folders-container"> |
|
||||||
<h2><i class="fas fa-folder-open"></i> 未完成的任务</h2> |
|
||||||
<div id="foldersList" class="folders-list"> |
|
||||||
<!-- 文件夹列表将在这里动态生成 --> |
|
||||||
</div> |
|
||||||
<div id="noFolders" class="no-folders"> |
|
||||||
<i class="fas fa-check-circle"></i> |
|
||||||
<p>所有下载任务已完成!</p> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div class="api-info"> |
|
||||||
<h3><i class="fas fa-info-circle"></i> API 信息</h3> |
|
||||||
<p>图片下载API: <code>POST http://127.0.0.1:<span id="port">55830</span>/api/save_imgs</code></p> |
|
||||||
<p>请求格式: <code>{"title": "漫画标题", "imgs": {"001": "url1", "002": "url2"}}</code></p> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<!-- 下载进度模态框 --> |
|
||||||
<div id="downloadModal" class="modal"> |
|
||||||
<div class="modal-content"> |
|
||||||
<div class="modal-header"> |
|
||||||
<h3 id="modalTitle">下载进度</h3> |
|
||||||
<button class="close-btn">×</button> |
|
||||||
</div> |
|
||||||
<div class="modal-body"> |
|
||||||
<div class="progress-container"> |
|
||||||
<div class="progress-bar" id="progressBar"></div> |
|
||||||
<div class="progress-text" id="progressText">0%</div> |
|
||||||
</div> |
|
||||||
<div class="download-stats"> |
|
||||||
<div class="stat-item"> |
|
||||||
<span class="stat-label">总文件数:</span> |
|
||||||
<span class="stat-value" id="totalFiles">0</span> |
|
||||||
</div> |
|
||||||
<div class="stat-item"> |
|
||||||
<span class="stat-label">已下载:</span> |
|
||||||
<span class="stat-value success" id="downloadedFiles">0</span> |
|
||||||
</div> |
|
||||||
<div class="stat-item"> |
|
||||||
<span class="stat-label">待下载:</span> |
|
||||||
<span class="stat-value pending" id="pendingFiles">0</span> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
<div class="log-container"> |
|
||||||
<h4>下载日志</h4> |
|
||||||
<div class="log-content" id="downloadLog"></div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
<div class="modal-footer"> |
|
||||||
<button id="closeModalBtn" class="btn btn-secondary">关闭</button> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<script src="index.js"></script> |
|
||||||
</body> |
|
||||||
</html> |
|
||||||
@ -1,255 +0,0 @@ |
|||||||
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(); |
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,44 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html lang="zh-CN"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8"> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||||
|
<title>HD4K 下载管理</title> |
||||||
|
<link rel="stylesheet" href="index.css"> |
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div class="container"> |
||||||
|
<header> |
||||||
|
<h1><i class="fas fa-download"></i> HD4K 下载管理</h1> |
||||||
|
<p class="subtitle">管理未完成的下载任务</p> |
||||||
|
</header> |
||||||
|
|
||||||
|
<div class="control-panel"> |
||||||
|
<button id="reloadBtn" class="btn btn-primary"> |
||||||
|
<i class="fas fa-sync-alt"></i> 读取文件 |
||||||
|
</button> |
||||||
|
<button id="downloadAllBtn" class="btn btn-success"> |
||||||
|
<i class="fas fa-cloud-download-alt"></i> 下载全部 |
||||||
|
</button> |
||||||
|
<button id="cleanupBtn" class="btn btn-warning"> |
||||||
|
<i class="fas fa-trash-alt"></i> 清理JSON文件 |
||||||
|
</button> |
||||||
|
<div class="status" id="statusMessage"></div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="folders-container"> |
||||||
|
<h2><i class="fas fa-folder-open"></i> 未完成的任务</h2> |
||||||
|
<div id="foldersList" class="folders-list"> |
||||||
|
<!-- 文件夹列表将在这里动态生成 --> |
||||||
|
</div> |
||||||
|
<div id="noFolders" class="no-folders"> |
||||||
|
<i class="fas fa-check-circle"></i> |
||||||
|
<p>所有下载任务已完成!</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<script src="index.js"></script> |
||||||
|
</body> |
||||||
|
</html> |
||||||
@ -0,0 +1,274 @@ |
|||||||
|
class DownloadManager { |
||||||
|
constructor() { |
||||||
|
// 使用默认端口
|
||||||
|
this.port = window.location.port || '55830'; |
||||||
|
this.baseUrl = `http://127.0.0.1:${this.port}/api`; |
||||||
|
this.currentFolders = []; |
||||||
|
|
||||||
|
console.log('DownloadManager初始化完成,baseUrl:', this.baseUrl); |
||||||
|
|
||||||
|
this.init(); |
||||||
|
} |
||||||
|
|
||||||
|
init() { |
||||||
|
// 绑定事件
|
||||||
|
const reloadBtn = document.getElementById('reloadBtn'); |
||||||
|
const downloadAllBtn = document.getElementById('downloadAllBtn'); |
||||||
|
const cleanupBtn = document.getElementById('cleanupBtn'); |
||||||
|
|
||||||
|
if (reloadBtn) { |
||||||
|
reloadBtn.addEventListener('click', () => { |
||||||
|
console.log('点击了读取文件按钮'); |
||||||
|
this.reloadFolders(); |
||||||
|
}); |
||||||
|
} else { |
||||||
|
console.error('未找到reloadBtn按钮'); |
||||||
|
} |
||||||
|
|
||||||
|
if (downloadAllBtn) { |
||||||
|
downloadAllBtn.addEventListener('click', () => { |
||||||
|
console.log('点击了下载全部按钮'); |
||||||
|
this.downloadAll(); |
||||||
|
}); |
||||||
|
} else { |
||||||
|
console.error('未找到downloadAllBtn按钮'); |
||||||
|
} |
||||||
|
|
||||||
|
if (cleanupBtn) { |
||||||
|
cleanupBtn.addEventListener('click', () => { |
||||||
|
console.log('点击了清理JSON文件按钮'); |
||||||
|
this.cleanupJson(); |
||||||
|
}); |
||||||
|
} else { |
||||||
|
console.error('未找到cleanupBtn按钮'); |
||||||
|
} |
||||||
|
|
||||||
|
console.log('事件绑定完成'); |
||||||
|
|
||||||
|
// 初始加载
|
||||||
|
setTimeout(() => { |
||||||
|
this.reloadFolders(); |
||||||
|
}, 500); |
||||||
|
} |
||||||
|
|
||||||
|
showStatus(message, type = 'info') { |
||||||
|
const statusEl = document.getElementById('statusMessage'); |
||||||
|
if (statusEl) { |
||||||
|
console.log('状态消息:', message, '类型:', type); |
||||||
|
statusEl.textContent = message; |
||||||
|
statusEl.style.borderLeftColor = type === 'error' ? '#ef4444' : |
||||||
|
type === 'warning' ? '#f59e0b' : '#10b981'; |
||||||
|
} else { |
||||||
|
console.log('状态消息(找不到显示元素):', message); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async reloadFolders() { |
||||||
|
try { |
||||||
|
this.showStatus('正在读取文件...', 'info'); |
||||||
|
|
||||||
|
const reloadBtn = document.getElementById('reloadBtn'); |
||||||
|
if (reloadBtn) { |
||||||
|
reloadBtn.disabled = true; |
||||||
|
} |
||||||
|
|
||||||
|
const url = `${this.baseUrl}/reload_folders`; |
||||||
|
console.log('发送请求到:', url); |
||||||
|
|
||||||
|
const response = await fetch(url); |
||||||
|
console.log('响应状态:', response.status, response.statusText); |
||||||
|
|
||||||
|
if (!response.ok) { |
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`); |
||||||
|
} |
||||||
|
|
||||||
|
const data = await response.json(); |
||||||
|
console.log('收到响应数据:', data); |
||||||
|
|
||||||
|
if (data.success) { |
||||||
|
this.currentFolders = data.folders || []; |
||||||
|
this.displayFolders(this.currentFolders); |
||||||
|
const pendingCount = this.currentFolders.reduce((sum, folder) => sum + (folder.pending || 0), 0); |
||||||
|
this.showStatus(`已读取 ${this.currentFolders.length} 个任务,共 ${pendingCount} 个文件待下载`, 'success'); |
||||||
|
} else { |
||||||
|
throw new Error(data.message || '加载失败'); |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
console.error('加载失败:', error); |
||||||
|
this.showStatus(`加载失败: ${error.message}`, 'error'); |
||||||
|
this.displayFolders([]); |
||||||
|
} finally { |
||||||
|
const reloadBtn = document.getElementById('reloadBtn'); |
||||||
|
if (reloadBtn) { |
||||||
|
reloadBtn.disabled = false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
displayFolders(folders) { |
||||||
|
const foldersList = document.getElementById('foldersList'); |
||||||
|
const noFolders = document.getElementById('noFolders'); |
||||||
|
|
||||||
|
if (!foldersList || !noFolders) { |
||||||
|
console.error('找不到文件夹列表容器'); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const incompleteFolders = folders.filter(folder => (folder.progress || 0) < 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 || '未命名')} |
||||||
|
<span style="font-size: 0.9rem; color: #64748b; margin-left: 10px;"> |
||||||
|
(${folder.web_site || 'default'}) |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
<div class="folder-stats"> |
||||||
|
<span>总文件: ${folder.total || 0}</span> |
||||||
|
<span>已下载: ${folder.downloaded || 0}</span> |
||||||
|
<span style="color: #f59e0b; font-weight: 600;">待下载: ${folder.pending || 0}</span> |
||||||
|
</div> |
||||||
|
<div class="progress-container"> |
||||||
|
<div class="progress-wrapper"> |
||||||
|
<div class="progress"> |
||||||
|
<div class="progress-fill" style="width: ${folder.progress || 0}%"></div> |
||||||
|
</div> |
||||||
|
<div class="progress-text">${(folder.progress || 0).toFixed(1)}%</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
`).join('');
|
||||||
|
} |
||||||
|
|
||||||
|
async downloadAll() { |
||||||
|
if (this.currentFolders.length === 0) { |
||||||
|
this.showStatus('没有需要下载的任务', 'warning'); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
this.showStatus('开始下载所有任务...', 'info'); |
||||||
|
const downloadAllBtn = document.getElementById('downloadAllBtn'); |
||||||
|
if (downloadAllBtn) { |
||||||
|
downloadAllBtn.disabled = true; |
||||||
|
} |
||||||
|
|
||||||
|
// 依次下载每个文件夹
|
||||||
|
for (let i = 0; i < this.currentFolders.length; i++) { |
||||||
|
const folder = this.currentFolders[i]; |
||||||
|
if ((folder.pending || 0) > 0) { |
||||||
|
await this.downloadFolder(folder); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
this.showStatus('所有任务下载完成', 'success'); |
||||||
|
|
||||||
|
// 等待1秒后自动刷新
|
||||||
|
setTimeout(() => { |
||||||
|
this.reloadFolders(); |
||||||
|
}, 1000); |
||||||
|
|
||||||
|
} catch (error) { |
||||||
|
console.error('下载失败:', error); |
||||||
|
this.showStatus(`下载失败: ${error.message}`, 'error'); |
||||||
|
} finally { |
||||||
|
const downloadAllBtn = document.getElementById('downloadAllBtn'); |
||||||
|
if (downloadAllBtn) { |
||||||
|
downloadAllBtn.disabled = false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async downloadFolder(folder) { |
||||||
|
try { |
||||||
|
const pathParts = folder.folder.split('/'); |
||||||
|
let webSiteName = 'default'; |
||||||
|
if (pathParts.length >= 2) { |
||||||
|
webSiteName = pathParts[pathParts.length - 2]; |
||||||
|
} |
||||||
|
|
||||||
|
const response = await fetch(`${this.baseUrl}/download_missing`, { |
||||||
|
method: 'POST', |
||||||
|
headers: { |
||||||
|
'Content-Type': 'application/json', |
||||||
|
}, |
||||||
|
body: JSON.stringify({ |
||||||
|
folder: folder.folder, |
||||||
|
title: folder.title || '未命名', |
||||||
|
web_site: webSiteName |
||||||
|
}) |
||||||
|
}); |
||||||
|
|
||||||
|
if (!response.ok) { |
||||||
|
const errorText = await response.text(); |
||||||
|
throw new Error(`HTTP ${response.status}: ${errorText}`); |
||||||
|
} |
||||||
|
|
||||||
|
const data = await response.json(); |
||||||
|
|
||||||
|
if (!data.success) { |
||||||
|
console.warn(`文件夹 ${folder.title} 下载失败: ${data.message}`); |
||||||
|
} |
||||||
|
|
||||||
|
} catch (error) { |
||||||
|
console.error(`下载文件夹 ${folder.title} 失败:`, error); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async cleanupJson() { |
||||||
|
if (!confirm('确定要删除所有JSON文件吗?此操作不可恢复。')) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
this.showStatus('正在清理JSON文件...', 'info'); |
||||||
|
const cleanupBtn = document.getElementById('cleanupBtn'); |
||||||
|
if (cleanupBtn) { |
||||||
|
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 { |
||||||
|
const cleanupBtn = document.getElementById('cleanupBtn'); |
||||||
|
if (cleanupBtn) { |
||||||
|
cleanupBtn.disabled = false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
escapeHtml(text) { |
||||||
|
const div = document.createElement('div'); |
||||||
|
div.textContent = text; |
||||||
|
return div.innerHTML; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 初始化应用
|
||||||
|
const downloadManager = new DownloadManager(); |
||||||
Loading…
Reference in new issue