Dev qing #14

Merged
qing merged 39 commits from dev_qing into main 2026-07-30 19:47:22 +08:00
5 changed files with 131 additions and 6 deletions
Showing only changes of commit 8c1956cdeb - Show all commits

No files matched your search

+22
View File
@@ -273,6 +273,28 @@ def get_notes():
return jsonify(result), 200
# 当前用户身份查询:配合前端用户面板,返回 user / is_admin。
# 与 get_notes 同款可选解析:无 Token 或畸形 Token 按未登录处理(不 500)。
@app.route('/whoami', methods=["POST"])
def whoami():
info = None
auth: str = request.headers.get('Authorization', '')
if auth.startswith('Bearer '):
try:
info = decode_token(auth[7:])
except Exception:
info = None
if info is None:
return jsonify({'login': False}), 200
return jsonify({
'login': True,
'user': info.get('user'),
'is_admin': bool(info.get('is_admin'))
}), 200
# eg: {"id": 1}
@app.route('/delete_note', methods=['POST'])
+16
View File
@@ -474,6 +474,22 @@ def proxy_update_note():
return _cors(make_response(_json.dumps({"error": str(e)}), 502, {"Content-Type": "application/json"}))
@app.route('/whoami', methods=['POST', 'OPTIONS'])
def proxy_whoami():
if request.method == "OPTIONS": return _cors(make_response("", 204))
try:
data = request.get_json(force=True, silent=True) or {}
h = {"Content-Type": "application/json"}
if "Authorization" in request.headers: h["Authorization"] = request.headers["Authorization"]
elif request.cookies.get("q4_token"): h["Authorization"] = f"Bearer {request.cookies['q4_token']}" # HttpOnly Cookie 模式:Cookie 转 Bearer
req = _urlreq.Request(f"{DNFS_URL}/whoami", data=_json.dumps(data).encode(), headers=h, method="POST")
with _urlreq.urlopen(req, timeout=15) as r:
return _cors(make_response(r.read().decode(), 200, {"Content-Type": "application/json"}))
except Exception as e:
logs.warning("static", "dnfs whoami 代理失败", extra={"error": str(e)})
return _cors(make_response(_json.dumps({"error": str(e)}), 502, {"Content-Type": "application/json"}))
@app.route('/api/proxy_send_code', methods=['POST'])
def proxy_send_code():
from flask import request, jsonify
+33 -6
View File
@@ -365,6 +365,33 @@ input[type="button"]:disabled {
font-weight: 600;
}
/* ===== User Panel (dnfs 左上角用户信息,镜像 minimap 样式) ===== */
.userpanel {
position: fixed;
left: 1rem;
top: 1rem;
z-index: 100;
display: flex;
flex-direction: column;
gap: 0.4rem;
max-height: 80vh;
overflow-y: auto;
padding: 0.6rem 0.75rem;
border-radius: var(--radius-md);
background: rgba(253, 246, 238, 0.72);
border: 1px solid var(--border-light);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
box-shadow: var(--shadow-md);
transition: background 0.2s, border-color 0.2s;
}
@media (prefers-color-scheme: dark) {
.userpanel {
background: rgba(30, 23, 18, 0.72);
border-color: var(--border);
}
}
/* ===== Minimap (dnfs) ===== */
.minimap {
position: fixed;
@@ -392,8 +419,8 @@ input[type="button"]:disabled {
border-color: var(--border);
}
}
.minimap:empty { display: none; }
.minimap .tag {
.minimap:empty, .userpanel:empty { display: none; }
.minimap .tag, .userpanel .tag {
display: inline-block;
max-width: 140px;
overflow: hidden;
@@ -416,18 +443,18 @@ input[type="button"]:disabled {
border-radius: 4px;
}
.minimap::-webkit-scrollbar-thumb:hover { background: var(--muted); }
.minimap .tag:hover {
.minimap .tag:hover, .userpanel .tag:hover {
color: var(--accent-dark);
background: var(--accent-soft);
border-color: var(--accent);
}
.minimap .tag.back {
.minimap .tag.back, .userpanel .tag.back {
color: var(--accent);
background: var(--accent-soft);
border-color: var(--accent);
margin-bottom: 0.25rem;
}
.minimap .tag.fold-btn {
.minimap .tag.fold-btn, .userpanel .tag.fold-btn {
color: var(--fg-secondary);
background: transparent;
border: 1px solid var(--border);
@@ -438,7 +465,7 @@ input[type="button"]:disabled {
cursor: pointer;
transition: color 0.15s, background 0.15s, border-color 0.15s;
}
.minimap .tag.fold-btn:hover {
.minimap .tag.fold-btn:hover, .userpanel .tag.fold-btn:hover {
color: var(--accent-dark);
background: var(--accent-soft);
border-color: var(--accent);
+1
View File
@@ -9,6 +9,7 @@
</head>
<body>
<div class="box">
<div id="userpanel" class="userpanel"></div>
<div id="minimap" class="minimap"></div>
<div id="movebar" class="movebar"></div>
<div class="recommend-list" id="recommend-list"></div>
+59
View File
@@ -654,5 +654,64 @@ function loadMap(children_json) {
}
}
/* by qing — 用户信息面板:左上角,默认折叠;展示用户名 / 角色 / 退出登录。
* 身份来自 dnfs /whoami(HttpOnly Cookie 模式下 JS 无法读 Token,由后端解 payload 返回) */
var userpanel = document.getElementById("userpanel");
var userpanel_expand = false;
var current_user = null;
async function loadUserPanel() {
try {
const res = await fetch('/whoami', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: '{}'
});
if (!res.ok) return;
current_user = await res.json();
renderUserPanel();
} catch (e) {
console.error('获取用户信息失败:', e);
}
}
function renderUserPanel() {
if (current_user == null) return;
userpanel.replaceChildren();
let fold = document.createElement("div");
fold.className = "tag fold-btn";
if (userpanel_expand) {
fold.textContent = "v 折叠";
} else {
fold.textContent = "> " + (current_user.login ? current_user.user : "未登录");
}
fold.addEventListener("click", () => {
userpanel_expand = !userpanel_expand;
renderUserPanel();
});
userpanel.appendChild(fold);
if (!userpanel_expand || !current_user.login) return;
let name_tag = document.createElement("div");
name_tag.className = "tag";
name_tag.textContent = current_user.user;
userpanel.appendChild(name_tag);
let role_tag = document.createElement("div");
role_tag.className = "tag";
role_tag.textContent = current_user.is_admin ? "管理员" : "普通用户";
userpanel.appendChild(role_tag);
let logout_tag = document.createElement("div");
logout_tag.className = "tag back";
logout_tag.textContent = "退出登录";
logout_tag.addEventListener("click", async () => {
try { await fetch('/logout', {method: 'POST'}); } catch (e) {}
location.href = '/login.html';
});
userpanel.appendChild(logout_tag);
}
getNode(0).then(load_notes);
getNode(0).then(loadMap)
loadUserPanel();