v1.0.3: 回收站功能 - 删除任务进回收站可恢复, 仅可手动彻底删除/清空

This commit is contained in:
2026-08-11 13:25:28 +08:00
parent 1de2a0af54
commit e70a3a877f
6 changed files with 236 additions and 8 deletions
+72 -4
View File
@@ -63,6 +63,7 @@ async function loadStats() {
$("stImgs").textContent = s.images;
$("stDisk").textContent = s.disk_mb >= 1024
? (s.disk_mb / 1024).toFixed(1) + " GB" : s.disk_mb + " MB";
$("trashCount").textContent = s.trash || 0;
} catch (e) { /* 统计失败忽略 */ }
}
@@ -152,10 +153,75 @@ async function actTask(tid, act) {
async function delTask(tid) {
const t = state.tasks.find((x) => x.id === tid);
if (!confirm(`确定删除任务「${t ? t.name : tid}」?\n运行记录与任务配置将被删除(已爬取的文件保留在磁盘上)`)) return;
if (!confirm(`确定删除任务「${t ? t.name : tid}」?\n任务将移入回收站,可在回收站中恢复或彻底删除`)) return;
try {
await api(`/api/tasks/${tid}`, { method: "DELETE" });
toast("已删除");
const r = await api(`/api/tasks/${tid}`, { method: "DELETE" });
toast(r.msg || "已删除");
loadTasks();
} catch (e) { toast(e.message, true); }
}
/* ---------------- 回收站 ---------------- */
async function openTrash() {
try {
const items = await api("/api/trash");
state.trashItems = items;
renderTrash(items);
showModal("trashModal");
} catch (e) { toast(e.message, true); }
}
function renderTrash(items) {
const body = $("trashBody");
if (!items.length) {
body.innerHTML = `<div class="empty" style="padding:40px 0"><div class="empty-icon">🗑️</div><p>回收站是空的</p></div>`;
return;
}
body.innerHTML = items.map((t) => `
<div class="trash-item">
<div class="card-head" style="min-width:0">
<span class="card-name" title="${esc(t.name)}">${esc(t.name)}</span>
<span class="badge ${esc(t.mode)}">${MODE_LABEL[t.mode] || esc(t.mode)}</span>
</div>
<div class="card-line">删除时间: <b>${esc(t.deleted_at)}</b> · 运行次数: <b>${t.runs_count || 0}</b></div>
<div class="card-line">输出目录: <b>${esc(t.out_dir || "")}</b></div>
<div class="card-actions">
<button class="btn sm primary" onclick="restoreTrash('${t.id}')">↩️ 恢复</button>
<button class="btn sm danger" onclick="purgeTrashTask('${t.id}')">🗑️ 彻底删除</button>
</div>
</div>`).join("")
+ `<div class="trash-foot">
<span class="card-line">共 ${items.length} 项 · 彻底删除将移除任务配置与运行记录(默认输出目录一并清理)</span>
<button class="btn danger" onclick="clearTrash()">🧹 清空回收站</button>
</div>`;
}
async function restoreTrash(tid) {
try {
const r = await api(`/api/trash/${tid}/restore`, { method: "POST" });
toast(r.msg || "已恢复");
openTrash();
loadTasks();
} catch (e) { toast(e.message, true); }
}
async function purgeTrashTask(tid) {
const t = state.trashItems && state.trashItems.find((x) => x.id === tid);
if (!confirm(`彻底删除任务「${t ? t.name : tid}」?\n任务配置与所有运行记录将永久删除,不可恢复!`)) return;
try {
const r = await api(`/api/trash/${tid}`, { method: "DELETE" });
toast(r.files_removed ? "已彻底删除(含输出文件)" : "已彻底删除(自定义输出目录已保留)");
openTrash();
loadTasks();
} catch (e) { toast(e.message, true); }
}
async function clearTrash() {
if (!confirm(`确定清空回收站?\n回收站中所有任务将永久删除,不可恢复!`)) return;
try {
const r = await api("/api/trash", { method: "DELETE" });
toast(`已清空 ${r.purged}`);
openTrash();
loadTasks();
} catch (e) { toast(e.message, true); }
}
@@ -633,6 +699,7 @@ try {
$("btnNew").onclick = openCreate;
$("btnNew2").onclick = openCreate;
$("btnRefresh").onclick = loadTasks;
$("btnTrash").onclick = openTrash;
$("btnProbe").onclick = () => {
const p = collectProbeFromForm();
if (!p.seed_url) { toast("请先填写起始网址", true); return; }
@@ -650,6 +717,7 @@ document.querySelectorAll("[data-close-detail]").forEach((b) => b.onclick = () =
document.querySelectorAll("[data-close-preview]").forEach((b) => b.onclick = () => { $("previewFrame").src = "about:blank"; hideModal("previewModal"); });
document.querySelectorAll("[data-close-probe]").forEach((b) => b.onclick = () => hideModal("probeModal"));
document.querySelectorAll("[data-close-meta]").forEach((b) => b.onclick = () => hideModal("metaModal"));
document.querySelectorAll("[data-close-trash]").forEach((b) => b.onclick = () => hideModal("trashModal"));
/* 表单改动监听 -> 脏标记 */
$("taskForm").addEventListener("input", () => { formDirty = true; });
@@ -676,7 +744,7 @@ document.addEventListener("keydown", (e) => {
}
stopLogPoll();
$("previewFrame").src = "about:blank";
["detailModal", "probeModal", "metaModal", "previewModal"].forEach((id) => $(id).classList.add("hidden"));
["detailModal", "probeModal", "metaModal", "trashModal", "previewModal"].forEach((id) => $(id).classList.add("hidden"));
}
});
+12
View File
@@ -12,6 +12,7 @@
<div class="header-right">
<span class="status-pill" id="statusPill">运行中任务: 0</span>
<button id="btnTheme" class="btn ghost" title="切换日间/夜间主题">☀️</button>
<button id="btnTrash" class="btn ghost" title="回收站">🗑️ 回收站 <span id="trashCount" class="trash-count">0</span></button>
<button id="btnRefresh" class="btn ghost">⟳ 刷新</button>
<button id="btnNew" class="btn primary"> 新建任务</button>
</div>
@@ -143,6 +144,17 @@
</div>
</div>
<!-- 回收站弹窗 -->
<div id="trashModal" class="modal-overlay hidden">
<div class="modal wide">
<div class="modal-head">
<span>🗑️ 回收站(删除的任务统一在这里,可恢复或彻底删除)</span>
<button class="btn ghost sm" data-close-trash></button>
</div>
<div id="trashBody" class="detail-wrap"></div>
</div>
</div>
<!-- 试爬取结果弹窗 -->
<div id="probeModal" class="modal-overlay hidden">
<div class="modal wide">
+15
View File
@@ -215,6 +215,21 @@ td.title-cell { max-width: 220px; overflow: hidden; text-overflow: ellipsis; }
/* ---------- preview ---------- */
#previewFrame { flex: 1; border: none; background: var(--preview-bg); border-radius: 0 0 14px 14px; }
/* ---------- 回收站 ---------- */
.trash-count {
display: inline-block; min-width: 18px; padding: 0 5px; border-radius: 9px;
background: var(--red); color: #fff; font-size: 11px; line-height: 18px;
text-align: center; margin-left: 2px;
}
.trash-item {
border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px;
display: flex; flex-direction: column; gap: 6px; background: var(--panel2);
}
.trash-foot {
display: flex; justify-content: space-between; align-items: center;
gap: 10px; padding-top: 8px; border-top: 1px solid var(--border); margin-top: 4px;
}
/* ---------- 试爬取结果 / 元数据 ---------- */
.probe-summary { display: flex; flex-wrap: wrap; gap: 16px; font-size: 13px; }
.probe-list {