From 3f9bffb9eae08247779bb8cde046444f76856884 Mon Sep 17 00:00:00 2001 From: qing <1776177662@qq.com> Date: Wed, 29 Jul 2026 11:40:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(dnfs):=20=E8=8A=82=E7=82=B9=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - dnfs.js: 卡片新增移动按钮,startMove 暂存节点,底部悬浮移动条 提供移动到当前层/取消;confirmMove 经 update_note 改 pid 完成迁移; 拦截移动到自身与已在当前层的情况 - dnfs.html: 新增 #movebar 容器 - theme.css: act-move 描边按钮与 movebar 底部悬浮条样式 --- static/public/css/theme.css | 37 ++++++++++++ static/public/dnfs.html | 1 + static/public/js/dnfs.js | 115 ++++++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+) diff --git a/static/public/css/theme.css b/static/public/css/theme.css index 0ace46c..1e4ccfe 100644 --- a/static/public/css/theme.css +++ b/static/public/css/theme.css @@ -327,6 +327,43 @@ input[type="button"]:disabled { .act-btn:hover { border-color: var(--accent); } .act-del { background: var(--error-bg); color: var(--error); } .act-del:hover { border-color: var(--error); } +.act-move { background: transparent; color: var(--fg-secondary); border-color: var(--border); } +.act-move:hover { border-color: var(--accent); color: var(--accent-dark); } + +/* ===== Move Bar (dnfs 节点移动条) ===== */ +.movebar { + position: fixed; + left: 50%; + bottom: 1.25rem; + transform: translateX(-50%); + z-index: 110; + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + gap: 0.5rem; + padding: 0.5rem 0.8rem; + border-radius: var(--radius-md); + background: rgba(253, 246, 238, 0.85); + border: 1px solid var(--accent); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + box-shadow: var(--shadow-md); + font-size: 0.82rem; + max-width: 92vw; +} +@media (prefers-color-scheme: dark) { + .movebar { background: rgba(30, 23, 18, 0.85); } +} +.movebar:empty { display: none; } +.move-title { + max-width: 180px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--fg-secondary); + font-weight: 600; +} /* ===== Minimap (dnfs) ===== */ .minimap { diff --git a/static/public/dnfs.html b/static/public/dnfs.html index 5ea84ae..56d0f4e 100644 --- a/static/public/dnfs.html +++ b/static/public/dnfs.html @@ -10,6 +10,7 @@
+

节点操作

diff --git a/static/public/js/dnfs.js b/static/public/js/dnfs.js index aa27232..5336449 100644 --- a/static/public/js/dnfs.js +++ b/static/public/js/dnfs.js @@ -280,6 +280,110 @@ async function updateNode(id, pid, title, content) { } } +/* by qing — 节点移动:本质是 pid 变更的 update_note,title/content 原样携带 */ +async function moveNode(id, pid, title, content) { + try { + obj = { + "id": Number(id), + "pid": pid, + "title": title, + "content": content + } + jsonBody = JSON.stringify(obj) + const res = await fetch('/update_note', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Target-Url': 'http://127.0.0.1:8006' + }, + body: jsonBody + }); + + const responseText = await res.text(); + let result; + + try { + result = JSON.parse(responseText); + } catch { + console.error(`更新笔记接口返回非 JSON 内容(HTTP ${res.status}):`, responseText); + throw new Error('服务器响应格式异常,请稍后重试'); + } + + if (res.status === 401) { + // 凭证失效(过期或网关重启),跳转重新登录 + location.href = '/login.html'; + return; + } + + if (!res.ok) { + throw new Error(result.error === "It's not your note" ? '只能移动自己的节点' : (result.error || `移动节点失败(HTTP ${res.status})`)); + } + + var cur = (stack.length == 0) ? 0 : stack[stack.length - 1]; + getNode(cur).then(load_notes); + getNode(cur).then(loadMap); + + alert('移动成功'); + return result; + + } catch (err) { + alert(err.message || '接口请求失败,请检查后端服务是否启动'); + console.error('移动节点请求异常:', err); + } +} + +/* by qing — 点击卡片"移动":暂存节点信息,底部弹出移动条 */ +function startMove(item) { + moving_node = { id: Number(item.id), pid: item.pid, title: item.title, content: item.content }; + renderMoveBar(); +} + +/* by qing — 渲染底部移动条:显示待移动节点,提供"移动到当前层"和"取消" */ +function renderMoveBar() { + movebar.replaceChildren(); + if (moving_node == null) { + return; + } + let tip = document.createElement('span'); + tip.className = "move-title"; + tip.textContent = "移动中:" + moving_node.title; + let ok_btn = document.createElement('button'); + ok_btn.className = "act-btn act-edit"; + ok_btn.textContent = "移动到当前层"; + ok_btn.addEventListener('click', confirmMove); + let cancel_btn = document.createElement('button'); + cancel_btn.className = "act-btn act-del"; + cancel_btn.textContent = "取消"; + cancel_btn.addEventListener('click', function() { + moving_node = null; + renderMoveBar(); + }); + movebar.appendChild(tip); + movebar.appendChild(ok_btn); + movebar.appendChild(cancel_btn); +} + +/* by qing — 执行移动:把待移动节点挂到当前层(stack 顶,根为 0) */ +async function confirmMove() { + if (moving_node == null) { + return; + } + var target_pid = (stack.length == 0) ? 0 : stack[stack.length - 1]; + if (target_pid == moving_node.id) { + alert('不能移动到节点自身内部'); + return; + } + if (target_pid == moving_node.pid) { + alert('该节点已在当前层'); + return; + } + var result = await moveNode(moving_node.id, target_pid, moving_node.title, moving_node.content); + if (result != null) { + moving_node = null; + renderMoveBar(); + } +} + /* by shufeng2012 */ function renderMarkdown(element, content) { /* @@ -295,6 +399,7 @@ const ICON_HEART = '