awesome/public/js/admin.js
cheney e11814048c
All checks were successful
Build and Deploy / deploy (push) Successful in 51s
feat: 内容源管理优化与条目编辑功能
- 内容源管理:去掉人工录入卡片,AI 密钥保存后可返显并支持小眼睛切换,布局优化
- 内容管理:新增编辑按钮与编辑页,可调整条目全部字段与标签
2026-08-31 18:02:32 +08:00

220 lines
8.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(function () {
"use strict";
async function api(url, body, method) {
var res = await fetch(url, {
method: method || "POST",
headers: { "content-type": "application/json" },
body: body === undefined ? undefined : JSON.stringify(body),
});
var json = null;
try { json = await res.json(); } catch (e) {}
if (!res.ok || (json && json.ok === false)) {
if (res.status === 401) { location.href = "/login"; return new Promise(function () {}); }
throw new Error((json && json.error && json.error.message) || "请求失败(" + res.status + "");
}
return json;
}
function toast(msg) {
var el = document.querySelector(".toast");
if (!el) return;
el.textContent = msg;
el.classList.add("show");
clearTimeout(el._t);
el._t = setTimeout(function () { el.classList.remove("show"); }, 2600);
}
function refresh() {
setTimeout(function () { location.reload(); }, 500);
}
document.querySelectorAll(".admin-nav button[data-section]").forEach(function (btn) {
btn.addEventListener("click", function () {
document.querySelectorAll(".admin-nav button").forEach(function (b) {
b.setAttribute("aria-current", "false");
});
btn.setAttribute("aria-current", "true");
var id = btn.getAttribute("data-section");
document.querySelectorAll(".admin-main > section").forEach(function (s) {
s.classList.toggle("active", s.id === id);
});
});
});
document.querySelectorAll(".tree-row[data-tree]").forEach(function (row) {
row.addEventListener("click", function (e) {
if (e.target.closest("button[data-tag-action]")) return;
var target = document.getElementById(row.getAttribute("data-tree"));
if (!target) return;
var open = target.classList.toggle("open");
row.setAttribute("aria-expanded", open ? "true" : "false");
});
});
document.addEventListener("click", async function (e) {
var btn = e.target.closest("[data-action]");
if (!btn) return;
var action = btn.getAttribute("data-action");
try {
if (action === "tag-add") {
var parentId = btn.getAttribute("data-parent-id");
var name = prompt(parentId ? "新增子标签名称:" : "新增顶层标签组名称:");
if (!name) return;
await api("/api/tags", { name: name, parentId: parentId ? Number(parentId) : null });
toast("标签已创建"); refresh();
}
if (action === "tag-rename") {
var name2 = prompt("重命名为:", btn.getAttribute("data-name"));
if (!name2 || name2 === btn.getAttribute("data-name")) return;
await api("/api/tags/" + btn.getAttribute("data-id"), { name: name2 }, "PATCH");
toast("已重命名"); refresh();
}
if (action === "tag-move") {
var target3 = prompt("移动到哪个标签之下?填其 ID留空移到顶层\n" + btn.getAttribute("data-ids"));
if (target3 === null) return;
await api("/api/tags/" + btn.getAttribute("data-id"),
{ parentId: target3.trim() ? Number(target3.trim()) : null }, "PATCH");
toast("已移动"); refresh();
}
if (action === "tag-delete") {
if (!confirm("删除该标签?其子标签将上移一级,关联关系解除。")) return;
await api("/api/tags/" + btn.getAttribute("data-id"), undefined, "DELETE");
toast("已删除"); refresh();
}
if (action === "entry-status") {
await api("/api/entries/" + btn.getAttribute("data-id") + "/status",
{ status: btn.getAttribute("data-status") }, "PATCH");
toast("状态已更新"); refresh();
}
if (action === "entry-delete") {
if (!confirm("物理删除该条目?此操作不可恢复。")) return;
await api("/api/entries/" + btn.getAttribute("data-id"), undefined, "DELETE");
toast("已删除"); refresh();
}
if (action === "comment-delete") {
if (!confirm("删除该评论?")) return;
await api("/api/comments/" + btn.getAttribute("data-id"), undefined, "DELETE");
toast("已删除"); refresh();
}
if (action === "user-disable") {
var status = btn.getAttribute("data-status") === "active" ? "disabled" : "active";
await api("/api/admin/users/" + btn.getAttribute("data-id") + "/status", { status: status }, "PATCH");
toast("账号状态已更新"); refresh();
}
} catch (err) {
toast(err.message);
}
});
var userAddForm = document.getElementById("user-add-form");
if (userAddForm) {
userAddForm.addEventListener("submit", function (e) {
e.preventDefault();
api("/api/admin/users", {
username: userAddForm.username.value.trim(),
password: userAddForm.password.value,
role: userAddForm.role.value,
})
.then(function () { toast("账号已创建"); refresh(); })
.catch(function (err) { toast(err.message); });
});
}
var aiKeyForm = document.getElementById("ai-key-form");
if (aiKeyForm) {
aiKeyForm.addEventListener("submit", function (e) {
e.preventDefault();
api("/api/admin/ai-key", { key: aiKeyForm.key.value.trim() }, "PUT")
.then(function () { toast("AI 录入密钥已更新"); refresh(); })
.catch(function (err) { toast(err.message); });
});
}
document.querySelectorAll("[data-eye-toggle]").forEach(function (btn) {
btn.addEventListener("click", function () {
var input = btn.closest("form").querySelector("input[name=key]");
var show = input.type === "password";
input.type = show ? "text" : "password";
btn.setAttribute("aria-label", show ? "隐藏密钥" : "显示密钥");
var open = btn.querySelector(".eye-open");
var closed = btn.querySelector(".eye-closed");
if (open) open.hidden = show;
if (closed) closed.hidden = !show;
});
});
document.querySelectorAll("form.source-config").forEach(function (form) {
form.addEventListener("submit", function (e) {
e.preventDefault();
var id = form.getAttribute("data-id");
api("/api/admin/sources/" + id, {
name: form.name.value,
url: form.url.value,
adapter: form.adapter.value,
cron_expr: form.cron_expr.value,
enabled: form.enabled.checked,
}, "PATCH")
.then(function () { toast("内容源已保存"); refresh(); })
.catch(function (err) { toast(err.message); });
});
});
document.querySelectorAll("[data-source-run]").forEach(function (btn) {
btn.addEventListener("click", async function () {
var id = btn.getAttribute("data-source-run");
btn.disabled = true;
try {
var json = await api("/api/admin/sources/" + id + "/run", {});
var s = json.data;
toast(s.error
? "拉取失败:" + s.error
: "完成:发现 " + s.found + " · 新增 " + s.created + " · 跳过 " + s.skipped);
if (!s.error) refresh();
} catch (err) {
toast(err.message);
} finally {
btn.disabled = false;
}
});
});
document.querySelectorAll("[data-source-runs]").forEach(function (btn) {
btn.addEventListener("click", async function () {
var box = document.getElementById("runs-" + btn.getAttribute("data-source-runs"));
if (!box) return;
if (!box.hidden) { box.hidden = true; return; }
try {
var json = await api("/api/admin/sources/" + btn.getAttribute("data-source-runs") + "/runs", undefined, "GET");
box.textContent = JSON.stringify(json.data, null, 2).slice(0, 3000);
box.hidden = false;
} catch (err) {
toast(err.message);
}
});
});
var sourceAddForm = document.getElementById("source-add-form");
if (sourceAddForm) {
sourceAddForm.addEventListener("submit", function (e) {
e.preventDefault();
api("/api/admin/sources", {
name: sourceAddForm.name.value.trim(),
url: sourceAddForm.url.value.trim(),
adapter: sourceAddForm.adapter.value,
cron_expr: sourceAddForm.cron_expr.value.trim(),
})
.then(function () { toast("拉取源已创建"); refresh(); })
.catch(function (err) { toast(err.message); });
});
}
})();