feat: 页面列表支持拖拽排序(order字段+order接口), 持久化到后端

This commit is contained in:
ylhan
2026-09-22 00:27:54 +08:00
parent e7ddce06ea
commit 37e26be04b
+51 -6
View File
@@ -132,7 +132,10 @@ INDEX_HTML = r"""<!DOCTYPE html>
button:hover,.btn:hover{opacity:.9}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:14px}
.tile{background:var(--card);border:1px solid var(--border);border-radius:16px;padding:16px;
text-decoration:none;color:var(--text);display:block;transition:.16s;box-shadow:0 2px 10px rgba(20,30,70,.05)}
text-decoration:none;color:var(--text);display:block;transition:.16s;box-shadow:0 2px 10px rgba(20,30,70,.05);
cursor:grab}
.tile:active{cursor:grabbing}
.tile.drag-over{border-color:var(--brand);box-shadow:0 0 0 2px rgba(99,102,241,.25)}
.tile:hover{transform:translateY(-2px);box-shadow:0 10px 24px rgba(20,30,70,.1)}
.tile h3{font-size:16px;margin-bottom:6px}
.tile p{font-size:13px;color:var(--muted);line-height:1.5;
@@ -218,13 +221,32 @@ function renderList(){
<button onclick="logout()">👤 退出</button>`;
const a=$('#app');
if(!state.pages.length){ a.innerHTML=`<div class="empty">还没有内容。<br><button class="primary" onclick="newPage()">创建第一个页面</button></div>`; return; }
a.innerHTML = `<div class="grid">${state.pages.map(p=>`
<a class="tile" onclick="openPage('${p.id}')">
a.innerHTML = `<div class="grid" ondrop="dropEv(event)" ondragover="event.preventDefault()">${state.pages.map(p=>`
<a class="tile" draggable="true" data-id="${p.id}" ondragstart="dragStart('${p.id}')" ondragover="event.preventDefault()" onclick="openPage('${p.id}')">
<h3>${esc(p.title||'未命名')}</h3>
<p>${esc(p.summary||'')}</p>
<time>${fmt(p.updated)}</time>
</a>`).join('')}</div>`;
}
let dragId=null;
function dragStart(id){ dragId=id; }
function dropEv(ev){
ev.preventDefault();
if(!dragId) return;
const tile=ev.target.closest('.tile');
if(!tile) return;
const tid=tile.getAttribute('data-id');
if(!tid || tid===dragId){ dragId=null; return; }
const from=state.pages.findIndex(p=>p.id===dragId);
const to=state.pages.findIndex(p=>p.id===tid);
if(from<0 || to<0){ dragId=null; return; }
const [moved]=state.pages.splice(from,1);
state.pages.splice(to,0,moved);
dragId=null;
renderList();
api('/api/pages/order',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({order:state.pages.map(p=>p.id)})})
.catch(e=>toast('排序保存失败: '+e.message));
}
async function openPage(id){
try{
const p = await api('/api/pages/'+encodeURIComponent(id));
@@ -475,6 +497,8 @@ class Handler(BaseHTTPRequestHandler):
return self._logout()
if not self._user():
return self._auth_denied()
if path == "/api/pages/order":
return self._reorder()
if path == "/api/pages":
return self._create_page()
if path == "/api/upload":
@@ -514,9 +538,10 @@ class Handler(BaseHTTPRequestHandler):
pages = _read_pages()
out = [{"id": pid, "title": p.get("title", ""),
"summary": _summary(p.get("content", "")),
"updated": p.get("updated")}
"updated": p.get("updated"),
"order": p.get("order", 1 << 30)}
for pid, p in pages.items()]
out.sort(key=lambda x: x["updated"] or "", reverse=True)
out.sort(key=lambda x: x.get("order", 1 << 30))
self._json(out)
def _get_page(self, pid):
@@ -535,9 +560,10 @@ class Handler(BaseHTTPRequestHandler):
pages = _read_pages()
pid = uuid.uuid4().hex[:12]
now = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
nxt = max((p.get("order", 0) for p in pages.values()), default=-1) + 1
pages[pid] = {"title": data["title"].strip(),
"content": data.get("content", ""),
"created": now, "updated": now}
"created": now, "updated": now, "order": nxt}
_write_pages(pages)
self._json({"id": pid}, 201)
@@ -566,6 +592,25 @@ class Handler(BaseHTTPRequestHandler):
_write_pages(pages)
self._json({"ok": True})
def _reorder(self):
data = self._read_json()
order = data.get("order") if isinstance(data, dict) else None
if not isinstance(order, list):
return self._json({"error": "bad order"}, 400)
with _lock:
pages = _read_pages()
for i, pid in enumerate(order):
if pid in pages:
pages[pid]["order"] = i
# 未在列表中的页面补一个靠后的 order
have = set(order)
big = len(order)
for pid in pages:
if pid not in have:
pages[pid]["order"] = pages[pid].get("order", big)
_write_pages(pages)
self._json({"ok": True})
# ---- 图片 ----
def _upload(self):
data = self._read_json()