feat(web): require login only for candidates and system

This commit is contained in:
kairee committed 2026-11-01 03:09:21 +08:00
1 parent 55c40db047
commit 6aefe9ce87
6 files changed
+130 -32

No files matched your search

+7
View File
@@ -50,3 +50,10 @@ export async function health() {
const payload = await response.json();
return payload?.data ?? payload;
}
export async function session() {
const response = await fetch("/api/session", { credentials: "same-origin" });
if (!response.ok) throw new ApiError("无法连接看板服务", response.status);
const payload = await response.json();
return payload?.data ?? payload;
}
+42 -11
View File
@@ -1,7 +1,6 @@
import { health } from "./api.js";
import { session } from "./api.js";
import { disposeAll } from "./charts.js";
import { escapeHtml } from "./format.js";
import { onRoute, startRouter } from "./router.js";
import { onRoute, parseLocation, startRouter } from "./router.js";
import { renderCandidates } from "./views/candidates.js";
import { renderFans } from "./views/fans.js";
import { renderHome } from "./views/home.js";
@@ -14,8 +13,8 @@ const NAV_ITEMS = [
{ path: "/home", label: "首页", icon: "home" },
{ path: "/rank", label: "榜单", icon: "rank" },
{ path: "/fans", label: "粉丝", icon: "fans" },
{ path: "/candidates", label: "候选", icon: "candidate" },
{ path: "/system", label: "系统", icon: "system" },
{ path: "/candidates", label: "候选", icon: "candidate", protected: true },
{ path: "/system", label: "系统", icon: "system", protected: true },
];
const ROUTES = [
@@ -24,10 +23,32 @@ const ROUTES = [
{ pattern: /^\/video\/(\d+)$/, view: renderVideo, title: "视频详情" },
{ pattern: /^\/up\/(\d+)$/, view: renderUp, title: "UP 详情" },
{ pattern: /^\/fans$/, view: renderFans, title: "粉丝" },
{ pattern: /^\/candidates$/, view: renderCandidates, title: "候选池" },
{ pattern: /^\/system$/, view: renderSystem, title: "系统状态" },
{
pattern: /^\/candidates$/, view: renderCandidates, title: "候选池",
beforeEnter: requireAuth,
},
{
pattern: /^\/system$/, view: renderSystem, title: "系统状态",
beforeEnter: requireAuth,
},
];
let authState = { authEnabled: false, authenticated: true };
function canAccessProtected() {
return !authState.authEnabled || authState.authenticated;
}
function requireAuth() {
if (canAccessProtected()) return true;
const { path, query } = parseLocation();
const search = query.toString();
window.location.assign(
`/login?next=${encodeURIComponent(`${path}${search ? `?${search}` : ""}`)}`,
);
return false;
}
function icon(name) {
const paths = {
home: '<path d="M4 10.5 12 4l8 6.5V20a1 1 0 0 1-1 1h-5v-6H10v6H5a1 1 0 0 1-1-1z"/>',
@@ -42,7 +63,10 @@ function icon(name) {
function renderNav() {
const nav = document.querySelector("#bottom-nav");
nav.innerHTML = NAV_ITEMS.map((item) => `
const items = NAV_ITEMS.filter(
(item) => !item.protected || canAccessProtected(),
);
nav.innerHTML = items.map((item) => `
<a class="nav-item" href="${item.path}" data-nav="${item.path}">
${icon(item.icon)}<span class="nav-label">${item.label}</span>
</a>`).join("");
@@ -88,19 +112,26 @@ window.addEventListener("error", (event) => {
}, true);
async function boot() {
renderNav();
const banner = document.querySelector("#preview-banner");
const hint = document.querySelector("#topbar-hint");
const authLink = document.querySelector("#topbar-auth");
try {
const payload = await health();
const data = payload?.data || payload;
const data = await session();
authState = {
authEnabled: Boolean(data?.auth_enabled),
authenticated: data?.authenticated !== false,
};
if (data?.preview) {
banner.hidden = false;
if (hint) hint.hidden = false;
}
if (authLink && authState.authEnabled && !authState.authenticated) {
authLink.hidden = false;
}
} catch {
toast("无法连接看板服务", "error");
}
renderNav();
onRoute(({ path, route }) => {
disposeAll();
setTitle(route, path);
+1
View File
@@ -20,6 +20,7 @@
<small id="topbar-title">最新动态</small>
</span>
</a>
<a class="topbar-hint" id="topbar-auth" href="/login" hidden>登录</a>
<span class="topbar-hint" id="topbar-hint" hidden>预览</span>
</header>
<div class="preview-banner" id="preview-banner" hidden>预览数据 · 不影响真实数据库</div>
+4
View File
@@ -125,6 +125,10 @@ async function render(options = {}) {
finishRender();
return;
}
if (matched.route.beforeEnter && matched.route.beforeEnter() === false) {
finishRender();
return;
}
current = { path, query, route: matched.route };
for (const listener of listeners) {
try {