Dev qing #14
No files matched your search
@@ -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'])
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
Reference in new issue
Block a user