初始化

This commit is contained in:
zg
2026-07-15 16:08:17 +08:00
parent f0d70aded4
commit 4e0057ece5
89 changed files with 13512 additions and 0 deletions

View File

@@ -0,0 +1,133 @@
/**
* Tiptap 增强编辑器初始化
* 支持:文字颜色、背景高亮、图片、表格、任务列表、代码块
*/
let editor = null;
function initEditor(content) {
content = content || '';
if (editor) {
editor.destroy();
}
editor = new tiptap.Editor({
element: document.querySelector("#tiptapEditor"),
extensions: [
tiptap.StarterKit,
tiptap.TextStyle,
tiptap.Color,
tiptap.Highlight.configure({ multicolor: true }),
tiptap.Image.configure({ inline: true, allowBase64: true }),
tiptap.Table.configure({ resizable: true }),
tiptap.TableRow,
tiptap.TableCell,
tiptap.TableHeader,
tiptap.TaskList,
tiptap.TaskItem.configure({ nested: true }),
],
content: content,
onUpdate: function() {
updatePreview();
scheduleSave();
},
});
renderToolbar();
}
function renderToolbar() {
var toolbar = document.querySelector("#tiptapToolbar");
if (!toolbar) return;
var html = '';
// 标题
html += '<span class="tb-group">';
html += '<button class="tb-btn" data-action="h1">H1</button>';
html += '<button class="tb-btn" data-action="h2">H2</button>';
html += '<button class="tb-btn" data-action="h3">H3</button>';
html += '</span>';
// 内联格式
html += '<span class="tb-group">';
html += '<button class="tb-btn" data-action="bold" style="font-weight:bold;">B</button>';
html += '<button class="tb-btn" data-action="italic" style="font-style:italic;">I</button>';
html += '<button class="tb-btn" data-action="strike" style="text-decoration:line-through;">S</button>';
html += '</span>';
// 颜色
html += '<span class="tb-group">';
html += '<input type="color" id="textColor" title="文字颜色" style="width:22px;height:22px;border:none;cursor:pointer;padding:0;border-radius:3px;" value="#333333">';
html += '<input type="color" id="bgColor" title="背景高亮" style="width:22px;height:22px;border:none;cursor:pointer;padding:0;border-radius:3px;" value="#fff3cd">';
html += '</span>';
// 媒体
html += '<span class="tb-group">';
html += '<button class="tb-btn" data-action="image">🖼 图片</button>';
html += '<button class="tb-btn" data-action="emoji">😊 表情</button>';
html += '</span>';
// 块级元素
html += '<span class="tb-group">';
html += '<button class="tb-btn" data-action="bulletList">• 列表</button>';
html += '<button class="tb-btn" data-action="orderedList">1. 列表</button>';
html += '<button class="tb-btn" data-action="taskList">✅ 任务</button>';
html += '<button class="tb-btn" data-action="blockquote">❝ 引用</button>';
html += '<button class="tb-btn" data-action="codeBlock">&lt;/&gt; 代码</button>';
html += '</span>';
// 表格
html += '<span class="tb-group">';
html += '<button class="tb-btn" data-action="table">📋 表格</button>';
html += '</span>';
toolbar.innerHTML = html;
// 绑定按钮事件
var actions = {
'h1': function() { editor.chain().focus().toggleHeading({ level: 1 }).run(); },
'h2': function() { editor.chain().focus().toggleHeading({ level: 2 }).run(); },
'h3': function() { editor.chain().focus().toggleHeading({ level: 3 }).run(); },
'bold': function() { editor.chain().focus().toggleBold().run(); },
'italic': function() { editor.chain().focus().toggleItalic().run(); },
'strike': function() { editor.chain().focus().toggleStrike().run(); },
'image': function() {
var url = prompt('输入图片 URL支持直接粘贴图片链接');
if (url) editor.chain().focus().setImage({ src: url }).run();
},
'emoji': function() {
var emoji = prompt('输入表情符号Win+. 打开系统表情面板粘贴):');
if (emoji) editor.chain().focus().insertContent(emoji).run();
},
'bulletList': function() { editor.chain().focus().toggleBulletList().run(); },
'orderedList': function() { editor.chain().focus().toggleOrderedList().run(); },
'taskList': function() { editor.chain().focus().toggleTaskList().run(); },
'blockquote': function() { editor.chain().focus().toggleBlockquote().run(); },
'codeBlock': function() { editor.chain().focus().toggleCodeBlock().run(); },
'table': function() { editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(); },
};
toolbar.querySelectorAll('.tb-btn').forEach(function(btn) {
var action = btn.dataset.action;
if (actions[action]) {
btn.addEventListener('click', actions[action]);
}
});
// 颜色选择器
var textColor = document.querySelector('#textColor');
var bgColor = document.querySelector('#bgColor');
if (textColor) textColor.addEventListener('input', function(e) {
editor.chain().focus().setColor(e.target.value).run();
});
if (bgColor) bgColor.addEventListener('input', function(e) {
editor.chain().focus().toggleHighlight({ color: e.target.value }).run();
});
}
function updatePreview() {
var preview = document.querySelector("#previewContent");
if (preview && editor) {
preview.innerHTML = editor.getHTML();
}
}

150
frontend/static/js/notes.js Normal file
View File

@@ -0,0 +1,150 @@
/**
* 笔记页面交互逻辑
*/
var currentNoteId = null;
var saveTimer = null;
var viewMode = "edit";
function setViewMode(mode) {
viewMode = mode;
var editPane = document.querySelector("#editPane");
var previewPane = document.querySelector("#previewPane");
document.querySelectorAll(".toggle-group button").forEach(function(b) {
b.classList.toggle("active", b.dataset.mode === mode);
});
editPane.classList.remove("hidden");
previewPane.classList.remove("hidden");
if (mode === "edit") { previewPane.classList.add("hidden"); }
else if (mode === "preview") { editPane.classList.add("hidden"); }
updatePreview();
}
function createNote() {
var folderId = getActiveFolderId();
fetch("/api/notes", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title: "无标题笔记", content: "", folder_id: folderId }),
}).then(function(r) { return r.json(); })
.then(function(note) { loadNote(note.id); refreshFolderNotes(); });
}
function loadNote(noteId) {
fetch("/api/notes/" + noteId)
.then(function(r) { return r.json(); })
.then(function(note) {
currentNoteId = note.id;
document.querySelector("#currentNoteId").value = note.id;
document.querySelector("#noteTitle").value = note.title;
initEditor(note.content || "");
document.querySelectorAll(".note-list-item").forEach(function(el) {
el.classList.toggle("active", parseInt(el.dataset.noteId) === note.id);
});
});
}
function scheduleSave() {
var s = document.querySelector("#saveStatus");
if (s) { s.textContent = "保存中…"; s.style.color = "#F5A623"; }
clearTimeout(saveTimer);
saveTimer = setTimeout(saveNote, 800);
}
function saveNote() {
if (!currentNoteId) return;
var title = document.querySelector("#noteTitle").value;
var content = editor ? editor.getHTML() : "";
fetch("/api/notes/" + currentNoteId, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title: title, content: content }),
}).then(function(r) {
if (r.ok) { var s = document.querySelector("#saveStatus"); if (s) { s.textContent = "已保存"; s.style.color = ""; } }
});
}
function filterByFolder(folderId, el) {
document.querySelectorAll(".folder-item").forEach(function(f) { f.classList.remove("active"); });
el.classList.add("active");
fetch("/api/notes?folder_id=" + folderId)
.then(function(r) { return r.json(); })
.then(function(notes) { renderNoteList(notes); });
}
function renderNoteList(notes) {
var listDiv = document.querySelector("#folderNoteList");
if (!listDiv) return;
listDiv.classList.remove("hidden");
var html = '<div style="padding:4px 0; cursor:pointer; color:var(--brand); font-size:11px;" onclick="showFolderList()">← 返回文件夹</div>';
if (!notes.length) {
html += '<div style="color:#ccc; padding:8px; text-align:center; font-size:12px;">暂无笔记</div>';
} else {
notes.forEach(function(note) {
var title = note.title || "无标题";
var date = (note.updated_at || "").substr(0, 10);
html += '<div class="note-list-item" data-note-id="' + note.id + '" onclick="loadNote(' + note.id + ')">' +
'<span class="note-title-s">' + escapeHtml(title) + '</span>' +
'<span class="note-date">' + date + '</span></div>';
});
}
listDiv.innerHTML = html;
}
function showFolderList() {
var listDiv = document.querySelector("#folderNoteList");
if (listDiv) listDiv.classList.add("hidden");
}
function getActiveFolderId() {
var active = document.querySelector(".folder-item.active");
return active ? parseInt(active.dataset.folderId) : null;
}
function refreshFolderNotes() {
var af = document.querySelector(".folder-item.active");
if (af) filterByFolder(parseInt(af.dataset.folderId), af);
}
function addFolder() {
var name = prompt("输入新文件夹名称:");
if (!name) return;
fetch("/api/folders", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: name, type: "note" }) })
.then(function() { window.location.reload(); });
}
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 close() { if (menu) menu.classList.add("hidden"); document.removeEventListener("click", close); }, { once: true });
}, 0);
}
function renameFolder(folderId) {
var name = prompt("新名称:"); if (!name) return;
fetch("/api/folders/" + folderId, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: name }) })
.then(function() { window.location.reload(); });
}
function deleteFolder(folderId) {
if (!confirm("确定删除此文件夹?内容将移至「未分类」")) return;
fetch("/api/folders/" + folderId, { method: "DELETE" }).then(function() { window.location.reload(); });
}
function deleteCurrentNote() {
if (!currentNoteId || !confirm("确定删除?")) return;
fetch("/api/notes/" + currentNoteId, { method: "DELETE" }).then(function() { window.location.reload(); });
}
function filterByTag(tagId, el) { el.classList.toggle("active"); }
function addTag() { var n = prompt("标签名:"); if (n) window.location.reload(); }
function escapeHtml(s) { return s ? s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;") : ""; }
document.addEventListener("DOMContentLoaded", function() {
document.querySelector("#noteTitle").addEventListener("input", scheduleSave);
var firstId = document.querySelector("#firstNoteId");
if (firstId && firstId.value) loadNote(parseInt(firstId.value)); else createNote();
});

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
Couldn't find the requested file /dist/tiptap-core.umd.min.js in @tiptap/core.

View File

@@ -0,0 +1,29 @@
// Tiptap 打包入口 - 将所有需要的模块挂载到全局 window.tiptap
import { Editor } from "@tiptap/core";
import { StarterKit } from "@tiptap/starter-kit";
import { Color } from "@tiptap/extension-color";
import { Highlight } from "@tiptap/extension-highlight";
import { TextStyle } from "@tiptap/extension-text-style";
import { Image } from "@tiptap/extension-image";
import { Table } from "@tiptap/extension-table";
import { TableRow } from "@tiptap/extension-table-row";
import { TableCell } from "@tiptap/extension-table-cell";
import { TableHeader } from "@tiptap/extension-table-header";
import { TaskList } from "@tiptap/extension-task-list";
import { TaskItem } from "@tiptap/extension-task-item";
// 挂载到全局,保持和 UMD 版本一样的 API
window.tiptap = {
Editor,
StarterKit,
Color,
Highlight,
TextStyle,
Image,
Table,
TableRow,
TableCell,
TableHeader,
TaskList,
TaskItem,
};

View File

@@ -0,0 +1 @@
Couldn't find the requested file /dist/tiptap-extension-code-block-lowlight.umd.min.js in @tiptap/extension-code-block-lowlight.

View File

@@ -0,0 +1 @@
Couldn't find the requested file /dist/tiptap-extension-color.umd.min.js in @tiptap/extension-color.

View File

@@ -0,0 +1 @@
Couldn't find the requested file /dist/tiptap-extension-highlight.umd.min.js in @tiptap/extension-highlight.

View File

@@ -0,0 +1 @@
Couldn't find the requested file /dist/tiptap-extension-image.umd.min.js in @tiptap/extension-image.

View File

@@ -0,0 +1 @@
Couldn't find the requested file /dist/tiptap-extension-table-cell.umd.min.js in @tiptap/extension-table-cell.

View File

@@ -0,0 +1 @@
Couldn't find the requested file /dist/tiptap-extension-table-header.umd.min.js in @tiptap/extension-table-header.

View File

@@ -0,0 +1 @@
Couldn't find the requested file /dist/tiptap-extension-table-row.umd.min.js in @tiptap/extension-table-row.

View File

@@ -0,0 +1 @@
Couldn't find the requested file /dist/tiptap-extension-table.umd.min.js in @tiptap/extension-table.

View File

@@ -0,0 +1 @@
Couldn't find the requested file /dist/tiptap-extension-task-item.umd.min.js in @tiptap/extension-task-item.

View File

@@ -0,0 +1 @@
Couldn't find the requested file /dist/tiptap-extension-task-list.umd.min.js in @tiptap/extension-task-list.

View File

@@ -0,0 +1 @@
Couldn't find the requested file /dist/tiptap-extension-text-style.umd.min.js in @tiptap/extension-text-style.

View File

@@ -0,0 +1 @@
Couldn't find the requested file /dist/tiptap-starter-kit.umd.min.js in @tiptap/starter-kit.

View File

@@ -0,0 +1,249 @@
/**
* 网站管理页面交互逻辑
*/
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 =
'<span style="font-size:11px;color:var(--text-tertiary);">' + fName + ' / </span>' +
'<span style="font-size:15px;font-weight:600;">' + site.name + '</span>' +
'<a href="' + site.url + '" target="_blank" style="font-size:11px;color:var(--brand);margin-left:8px;text-decoration:none;">' + site.url + '</a>';
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 = '<div style="text-align:center;padding:80px 20px;color:var(--text-placeholder);"><p>暂无账号</p></div>';
return;
}
cards.innerHTML = accounts.map(function(a) {
return '<div class="card" style="margin-bottom:12px;">' +
'<div class="card-header">' +
'<span class="card-title">👤 ' + (a.remark || '账号') + '</span>' +
'<div class="card-actions">' +
'<button class="card-btn edit" onclick="editAccount(' + a.id + ')">✏️ 编辑</button>' +
'<button class="card-btn danger" onclick="deleteAccount(' + a.id + ')">🗑 删除</button>' +
'</div>' +
'</div>' +
'<div class="card-body">' +
'<span class="label">账号</span><span class="value">' + esc(a.username) + '</span>' +
'<button class="card-btn" onclick="copyText(\'' + esc(a.username) + '\')">复制</button>' +
'<span class="label">密码</span><span class="value pw-hidden" id="pw-' + a.id + '" data-pw="' + esc(a.password) + '">••••••••</span>' +
'<div style="display:flex;gap:4px;">' +
'<button class="card-btn" onclick="togglePassword(' + a.id + ')">显示</button>' +
'<button class="card-btn" onclick="copyText(\'' + esc(a.password) + '\')">复制</button>' +
'</div>' +
'<span class="label">说明</span><span style="color:var(--text-secondary);font-size:12px;">' + (a.remark || '-') + '</span><span></span>' +
'</div></div>';
}).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 =
'<span style="color:var(--text-secondary);">网址</span><span>' + d.url + '</span>' +
'<span style="color:var(--text-secondary);">网站名</span><span>' + d.site_name + '</span>' +
'<span style="color:var(--text-secondary);">账号</span><span style="font-family:monospace;">' + d.username + '</span>' +
'<span style="color:var(--text-secondary);">密码</span><span style="font-family:monospace;">' + d.password + '</span>' +
'<span style="color:var(--text-secondary);">备注</span><span>' + (d.remark || '(无)') + '</span>';
var alert = document.querySelector("#duplicateAlert");
if (d.action === "append") {
alert.classList.remove("hidden");
alert.innerHTML = '⚠️ <strong>' + d.matched_site.name + '</strong> 已存在(' + 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";
});
}