-
-
@@ -1126,13 +1138,6 @@
return parts.join(' · ');
}
- function updateBannerFilter() {
- const el = document.getElementById('banner-filter');
- const txt = parseFilterDisplay(currentFilter);
- if (txt) { el.textContent = txt; el.style.display = ''; }
- else { el.textContent = ''; el.style.display = 'none'; }
- }
-
function loadConfig() {
fetch('/api/get_config').then(r => r.json()).then(d => {
const p = d.save_path || '默认路径';
@@ -1140,7 +1145,6 @@
el.textContent = p; el.title = p;
document.getElementById('path-input').value = d.save_path || '';
currentFilter = d.download_filter || '';
- updateBannerFilter();
}).catch(() => {});
}
@@ -1448,6 +1452,121 @@
}).catch(() => {}).finally(() => { btn.disabled = false; });
}
+ // ── 任务队列总览渲染 ──
+ function renderTaskQueue(p) {
+ const banner = document.getElementById('task-banner');
+ const list = document.getElementById('qbanner-list');
+ const summary = document.getElementById('qbanner-summary');
+
+ const queue = Array.isArray(p.task_queue) ? p.task_queue : [];
+ const completed = Array.isArray(p.completed_chats) ? p.completed_chats : [];
+ const currentChat = p.current_chat || '';
+
+ // 若没有队列也没有当前任务,隐藏整个 banner
+ if (!queue.length && !currentChat) {
+ banner.classList.remove('show');
+ list.innerHTML = '';
+ summary.textContent = '';
+ return;
+ }
+ banner.classList.add('show');
+
+ // completed 按 chat_id 建索引
+ const completedMap = {};
+ completed.forEach(c => { completedMap[c.chat_id] = c; });
+
+ // 兜底:队列为空但有 current(多频道队列元信息还没写入时),用 current 顶一下
+ const effectiveQueue = queue.length
+ ? queue
+ : [{ chat_id: currentChat, chat_title: p.current_chat_title || currentChat }];
+
+ const paused = dlState === 'continue';
+ let doneCount = 0, runningCount = 0;
+
+ list.innerHTML = effectiveQueue.map(q => {
+ if (completedMap[q.chat_id]) {
+ doneCount++;
+ return renderDoneCard(q, completedMap[q.chat_id]);
+ } else if (q.chat_id === currentChat) {
+ runningCount++;
+ return renderActiveCard(q, p, paused);
+ } else {
+ return renderPendingCard(q);
+ }
+ }).join('');
+
+ summary.textContent = `${doneCount}/${effectiveQueue.length} 完成`;
+ }
+
+ function renderDoneCard(q, c) {
+ const title = escapeHtml(q.chat_title || q.chat_id);
+ const skipStr = (c.existing_skip && c.existing_skip > 0)
+ ? `跳过${c.skip||0},其中本次跳过${c.existing_skip}`
+ : `跳过${c.skip||0}`;
+ const totalStr = (c.total && c.total > 0)
+ ? `${c.done||0} / ${c.total} 完成`
+ : `${c.done||0} 个完成`;
+ return `
+ —
-
-
-
-
-
+
-
-
- 0 / 0
-
+ 任务队列
+
- 当前任务
+
+
`;
+ }
+
+ function renderActiveCard(q, p, paused) {
+ const title = escapeHtml(q.chat_title || p.current_chat_title || q.chat_id);
+ const dl = p.downloading_files||0, done = p.completed_files||0, skip = p.skipped_files||0;
+ const qual = p.qualified_files||0, est = p.estimated_total||0;
+ const existingSkip = p.existing_skipped || 0;
+ const rawTotal = est || qual;
+ const realTotal = Math.max(0, rawTotal - existingSkip);
+ const doneStr = rawTotal ? `${done} / ${realTotal}` : `${done}`;
+ const skipStr = existingSkip > 0 ? `跳过${skip},其中本次跳过${existingSkip}` : `跳过${skip}`;
+
+ let st = '';
+ if (p.is_checking && paused) st = `⏸ 已暂停 · 扫描中… (${skipStr})`;
+ else if (p.is_checking && dl>0) st = `🔍 扫描+下载中 (${dl}个,${doneStr},${skipStr})`;
+ else if (p.is_checking) st = `🔍 扫描中… (${doneStr},${skipStr})`;
+ else if (paused) st = `⏸ 已暂停 (${doneStr},${skipStr})`;
+ else if (dl>0) st = `🚀 下载中 (${dl}个,${doneStr},${skipStr})`;
+ else if (done>0||skip>0) st = `✅ 完成 (${doneStr},${skipStr})`;
+ else st = '⏳ 等待开始';
+
+ let progBar = '';
+ if (realTotal > 0) {
+ const pct = Math.min(100, Math.round(done * 100 / realTotal));
+ const suffix = (!est && p.is_checking) ? '(扫描中…)' : '';
+ progBar = `
+ ✅
+
+
+ ${title}
+ ${totalStr},${skipStr}
+
+
${done} / ${realTotal}${suffix}
`;
+ }
+
+ return `
+
+
`;
+ }
+
+ function renderPendingCard(q) {
+ const title = escapeHtml(q.chat_title || q.chat_id);
+ return `
+ 🚀
+
+
+ ${title}
+ ${st}
+ ${progBar}
+
+
`;
+ }
+
// ── 轮询 ──
function poll() {
fetch('/get_download_status').then(r => r.json()).then(d => {
@@ -1457,46 +1576,7 @@
fetch('/api/task_progress').then(r => r.json()).then(p => {
document.getElementById('h-skip').textContent = p.skipped_files || 0;
- const banner = document.getElementById('task-banner');
- const paused = dlState === 'continue';
- if (p.current_chat) {
- banner.classList.add('show');
- const title = p.current_chat_title || p.current_chat;
- document.getElementById('banner-chat').textContent = title;
- const dl = p.downloading_files||0, done = p.completed_files||0, skip = p.skipped_files||0;
- const qual = p.qualified_files||0, est = p.estimated_total||0;
- const existingSkip = p.existing_skipped || 0;
- // 分母优先用缓存值;没有就用当次遍历实时累加的 qualified。再扣除"本次任务已跳过",得到真正要下载的数量
- const rawTotal = est || qual;
- const realTotal = Math.max(0, rawTotal - existingSkip);
- const doneStr = rawTotal ? `${done} / ${realTotal}` : `${done}`;
- // 跳过文案:有本次跳过时追加说明
- const skipStr = existingSkip > 0 ? `跳过${skip},其中本次跳过${existingSkip}` : `跳过${skip}`;
- let st = '';
- if (p.is_checking && paused) st = `⏸ 已暂停 · 扫描中… (${skipStr})`;
- else if (p.is_checking && dl>0) st = `🔍 扫描+下载中 (${dl}个,${doneStr},${skipStr})`;
- else if (p.is_checking) st = `🔍 扫描中… (${doneStr},${skipStr})`;
- else if (paused) st = `⏸ 已暂停 (${doneStr},${skipStr})`;
- else if (dl>0) st = `🚀 下载中 (${dl}个,${doneStr},${skipStr})`;
- else if (done>0||skip>0) st = `✅ 完成 (${doneStr},${skipStr})`;
- else st = '⏳ 等待开始';
- document.getElementById('banner-state').textContent = st;
-
- // 进度条:有总数才显示;扫描中且无缓存时给"扫描中…"后缀
- const prog = document.getElementById('banner-progress');
- if (realTotal > 0) {
- prog.style.display = 'flex';
- const pct = Math.min(100, Math.round(done * 100 / realTotal));
- document.getElementById('tbp-fill').style.width = pct + '%';
- const suffix = (!est && p.is_checking) ? '(扫描中…)' : '';
- document.getElementById('tbp-text').textContent = `${done} / ${realTotal}${suffix}`;
- } else {
- prog.style.display = 'none';
- }
- } else {
- banner.classList.remove('show');
- document.getElementById('banner-progress').style.display = 'none';
- }
+ renderTaskQueue(p);
}).catch(() => {});
fetch('/get_download_list?already_down=false').then(r => r.json()).then(data => {
diff --git a/module/web.py b/module/web.py
index e8218e7..a85f37a 100644
--- a/module/web.py
+++ b/module/web.py
@@ -19,6 +19,8 @@ import utils
from module.app import Application, ChatDownloadConfig, TaskNode
from module.download_stat import (
DownloadState,
+ clear_completed_chats,
+ set_task_queue,
get_download_result,
get_download_state,
get_total_download_speed,
@@ -840,12 +842,16 @@ def api_save_and_restart():
# Add to channel history
_add_to_channel_history(chat_id, chat_title, chat_type)
-
+
+ # 重置队列总览元信息(单频道场景队列就一项)
+ clear_completed_chats()
+ set_task_queue([{"chat_id": chat_id, "chat_title": chat_title or chat_id}])
+
# Trigger restart
if _app:
_app.restart_program = True
logger.info(f"Restart flag set, new chat_id: {chat_id} ({chat_title})")
-
+
return jsonify({
"success": True,
"message": f"配置已保存,正在重启下载 {chat_title or chat_id}..."
@@ -897,6 +903,13 @@ def api_save_and_restart_multi():
for it in normalized:
_add_to_channel_history(it["chat_id"], it["chat_title"], it["chat_type"])
+ # 重置并设置本次任务的完整队列,供前端渲染"任务队列"卡片
+ clear_completed_chats()
+ set_task_queue([
+ {"chat_id": it["chat_id"], "chat_title": it["chat_title"] or it["chat_id"]}
+ for it in normalized
+ ])
+
# 触发重启
if _app:
_app.restart_program = True
⏳
+
+
+ ${title}
+ 排队中…
+