/** * 网站管理页面交互逻辑 */ var currentSiteId = null; var parsedImportData = null; // ===== 网站选择 ===== function selectWebsite(siteId, el) { currentSiteId = siteId; document.querySelector("#currentSiteId").value = siteId; document.querySelectorAll(".site-item").forEach(function(s) { s.classList.remove("active"); }); if (el) el.classList.add("active"); fetch("/api/websites/" + siteId).then(function(r) { return r.json(); }).then(renderSiteDetail); } function renderSiteDetail(site) { var fName = getFolderName(site.folder_id); document.querySelector("#siteHeaderInfo").innerHTML = '' + fName + ' / ' + '' + site.name + '' + '' + site.url + ''; document.querySelector("#editSiteBtn").classList.remove("hidden"); document.querySelector("#addAccountBtn").classList.remove("hidden"); var cards = document.querySelector("#accountCards"); var accounts = site.accounts || []; if (!accounts.length) { cards.innerHTML = '

暂无账号

'; return; } cards.innerHTML = accounts.map(function(a) { return '
' + '
' + '👤 ' + (a.remark || '账号') + '' + '
' + '' + '' + '
' + '
' + '
' + '账号' + esc(a.username) + '' + '' + '密码••••••••' + '
' + '' + '' + '
' + '说明' + (a.remark || '-') + '' + '
'; }).join(""); } function esc(s) { return (s||"").replace(/'/g,"\\'"); } function togglePassword(id) { var el = document.querySelector("#pw-" + id); if (!el) return; el.textContent = el.textContent === "••••••••" ? el.dataset.pw : "••••••••"; } function copyText(t) { navigator.clipboard.writeText(t).then(function() { var toast = document.createElement("div"); toast.className = "toast"; toast.textContent = "已复制"; document.body.appendChild(toast); setTimeout(function() { toast.remove(); }, 1500); }); } function getFolderName(fid) { if (!fid) return "未分类"; var el = document.querySelector('.folder-group[data-folder-id="' + fid + '"] .folder-item span:nth-child(2)'); return el ? el.textContent.replace(/📁\s*/, "").trim() : "未分类"; } // ===== 文件夹 ===== function toggleFolder(el) { var ch = el.parentElement.querySelector(".folder-children"); var arrow = el.querySelector(".folder-arrow"); if (!ch) return; if (ch.style.display === "none") { ch.style.display = "block"; arrow.textContent = "▼"; } else { ch.style.display = "none"; arrow.textContent = "▶"; } } function toggleFolderMenu(btn) { document.querySelectorAll(".folder-menu").forEach(function(m) { if (m !== btn.nextElementSibling) m.classList.add("hidden"); }); var menu = btn.nextElementSibling; if (menu) menu.classList.toggle("hidden"); setTimeout(function() { document.addEventListener("click", function c() { if (menu) menu.classList.add("hidden"); document.removeEventListener("click", c); }, { once: true }); }, 0); } function renameFolder(id) { var n = prompt("新名称:"); if (n) fetch("/api/folders/" + id, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: n }) }).then(function() { window.location.reload(); }); } function deleteFolder(id) { if (confirm("确定删除?内容将移至「未分类」")) fetch("/api/folders/" + id, { method: "DELETE" }).then(function() { window.location.reload(); }); } function showAddFolder() { var n = prompt("文件夹名:"); if (n) fetch("/api/folders", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: n, type: "website" }) }).then(function() { window.location.reload(); }); } // ===== 智能导入 ===== function showImportModal() { document.querySelector("#importModal").classList.remove("hidden"); document.querySelector("#parseResult").classList.add("hidden"); document.querySelector("#importText").value = ""; document.querySelector("#importNewFolder").value = ""; } function hideImportModal() { document.querySelector("#importModal").classList.add("hidden"); } function parseImport() { var text = document.querySelector("#importText").value.trim(); if (!text) return alert("请先粘贴文本"); fetch("/api/import/parse", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text: text }) }) .then(function(r) { return r.json(); }) .then(function(d) { if (!d.ok) return alert(d.error); parsedImportData = d; document.querySelector("#parseResult").classList.remove("hidden"); document.querySelector("#parseGrid").innerHTML = '网址' + d.url + '' + '网站名' + d.site_name + '' + '账号' + d.username + '' + '密码' + d.password + '' + '备注' + (d.remark || '(无)') + ''; var alert = document.querySelector("#duplicateAlert"); if (d.action === "append") { alert.classList.remove("hidden"); alert.innerHTML = '⚠️ ' + d.matched_site.name + ' 已存在(' + d.matched_site.account_count + '个账号),将追加进去'; if (d.matched_site.folder_id) document.querySelector("#importFolderSelect").value = d.matched_site.folder_id; } else { alert.classList.add("hidden"); } }); } function confirmImport() { if (!parsedImportData) return; var d = parsedImportData; var fid = document.querySelector("#importFolderSelect").value || null; var nf = document.querySelector("#importNewFolder").value.trim(); if (nf) { fetch("/api/folders", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: nf, type: "website" }) }) .then(function(r) { return r.json(); }).then(function(f) { doImport(d, f.id); }); } else { doImport(d, fid); } } function doImport(d, fid) { fetch("/api/websites").then(function(r) { return r.json(); }).then(function(sites) { var s = sites.find(function(x) { return x.url === d.url; }); if (s) { addAccountToSite(s.id, d); } else { fetch("/api/websites", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: d.site_name, url: d.url, folder_id: fid }) }) .then(function(r) { return r.json(); }).then(function(ns) { addAccountToSite(ns.id, d); }); } }); } function addAccountToSite(sid, d) { fetch("/api/websites/" + sid + "/accounts", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: d.username, password: d.password, remark: d.remark }) }) .then(function() { hideImportModal(); window.location.reload(); }); } // ===== 网站弹窗 ===== function showAddWebsite() { document.querySelector("#websiteModalTitle").textContent = "添加网站"; document.querySelector("#websiteName").value = ""; document.querySelector("#websiteUrl").value = ""; document.querySelector("#websiteFolder").value = ""; document.querySelector("#editWebsiteId").value = ""; document.querySelector("#websiteModal").classList.remove("hidden"); } function hideWebsiteModal() { document.querySelector("#websiteModal").classList.add("hidden"); } function editWebsite() { if (!currentSiteId) return; document.querySelector("#websiteModalTitle").textContent = "编辑网站"; document.querySelector("#editWebsiteId").value = currentSiteId; fetch("/api/websites/" + currentSiteId).then(function(r) { return r.json(); }).then(function(s) { document.querySelector("#websiteName").value = s.name; document.querySelector("#websiteUrl").value = s.url; document.querySelector("#websiteFolder").value = s.folder_id || ""; document.querySelector("#websiteModal").classList.remove("hidden"); }); } function saveWebsite() { var id = document.querySelector("#editWebsiteId").value; var data = { name: document.querySelector("#websiteName").value, url: document.querySelector("#websiteUrl").value, folder_id: document.querySelector("#websiteFolder").value || null }; fetch(id ? "/api/websites/" + id : "/api/websites", { method: id ? "PUT" : "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }) .then(function() { hideWebsiteModal(); window.location.reload(); }); } // ===== 账号弹窗 ===== function showAddAccount() { if (!currentSiteId) return alert("请先选择网站"); document.querySelector("#accountModalTitle").textContent = "添加账号"; document.querySelector("#accountUsername").value = ""; document.querySelector("#accountPassword").value = ""; document.querySelector("#accountRemark").value = ""; document.querySelector("#editAccountId").value = ""; document.querySelector("#accountPassword").type = "password"; document.querySelector("#accountModal").classList.remove("hidden"); } function hideAccountModal() { document.querySelector("#accountModal").classList.add("hidden"); } function editAccount(accId) { document.querySelector("#accountModalTitle").textContent = "编辑账号"; document.querySelector("#editAccountId").value = accId; document.querySelector("#accountPassword").type = "password"; fetch("/api/accounts/" + accId).then(function(r) { return r.json(); }).then(function(a) { document.querySelector("#accountUsername").value = a.username || ""; document.querySelector("#accountPassword").value = a.password || ""; document.querySelector("#accountRemark").value = a.remark || ""; document.querySelector("#accountModal").classList.remove("hidden"); }); } function togglePwVisibility() { var pw = document.querySelector("#accountPassword"); var btn = pw.parentElement.querySelector("button"); if (pw.type === "password") { pw.type = "text"; btn.textContent = "隐藏"; } else { pw.type = "password"; btn.textContent = "显示"; } } function saveAccount() { var sid = document.querySelector("#currentSiteId").value; var aid = document.querySelector("#editAccountId").value; var data = { username: document.querySelector("#accountUsername").value, password: document.querySelector("#accountPassword").value, remark: document.querySelector("#accountRemark").value }; if (aid) { fetch("/api/accounts/" + aid, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }) .then(function() { hideAccountModal(); selectWebsite(sid); }); } else { fetch("/api/websites/" + sid + "/accounts", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }) .then(function() { hideAccountModal(); selectWebsite(sid); }); } } function deleteAccount(accId) { if (!confirm("确定删除?")) return; fetch("/api/accounts/" + accId, { method: "DELETE" }).then(function() { selectWebsite(currentSiteId); }); } // ===== 搜索 ===== function filterSites() { var q = document.querySelector("#siteSearch").value.toLowerCase().trim(); document.querySelectorAll(".site-item").forEach(function(item) { if (!q) { item.style.display = "flex"; return; } var name = (item.querySelector(".site-name")?.textContent || "").toLowerCase(); var url = (item.dataset.siteUrl || "").toLowerCase(); item.style.display = (name.indexOf(q) !== -1 || url.indexOf(q) !== -1) ? "flex" : "none"; }); }