function selectAuthMode(mode) { state.authMode = mode === "register" ? "register" : "login"; document.querySelectorAll("[data-auth-mode]").forEach((button) => { button.classList.toggle("active", button.dataset.authMode === state.authMode); }); const registering = state.authMode === "register"; document.querySelector("#authConfirmField").hidden = !registering; document.querySelector("#authPasswordConfirm").required = registering; document.querySelector("#authPassword").autocomplete = registering ? "new-password" : "current-password"; document.querySelector("#authSubmitButton").textContent = registering ? "注册并进入" : "登录"; document.querySelector("#authError").hidden = true; } async function submitAuthForm(event) { event.preventDefault(); const username = document.querySelector("#authUsername").value.trim(); const password = document.querySelector("#authPassword").value; const errorElement = document.querySelector("#authError"); if (state.authMode === "register" && password !== document.querySelector("#authPasswordConfirm").value) { errorElement.textContent = "两次输入的密码不一致。"; errorElement.hidden = false; return; } const button = document.querySelector("#authSubmitButton"); button.disabled = true; try { const session = await apiRequest(`/api/auth/${state.authMode}`, "POST", { username, password }); document.querySelector("#authForm").reset(); await applyAuthenticatedSession(session); } catch (error) { errorElement.textContent = error.message || "账号操作失败"; errorElement.hidden = false; } finally { button.disabled = false; } } async function applyAuthenticatedSession(session) { state.user = session.user; state.csrfToken = session.csrf_token || ""; setText("accountName", session.user?.username || "账号"); const isAdmin = session.user?.role === "admin"; updateAccountIdentityBadges(session.user?.membership || {}); document.querySelector("#settingsButton").hidden = !isAdmin; document.querySelector("#syncButton").hidden = !isAdmin; document.querySelector("#reasonForm").hidden = !isAdmin; document.querySelector("#sectorPhaseManager").hidden = !isAdmin; document.querySelector("#authGate").hidden = true; applyMembershipAccess(); await startAuthenticatedApp(); } function showAuthGate(message = "") { state.user = null; state.csrfToken = ""; const params = new URLSearchParams(); if (message) params.set("notice", message); const query = params.toString(); window.location.replace("/login/" + (query ? `?${query}` : "")); } async function logoutAccount() { toggleAccountDropdown(false); try { await apiRequest("/api/auth/logout", "POST", {}); } catch (error) { showToast(error.message || "退出失败"); return; } window.location.reload(); } function hasMemberAccess() { return state.user?.role === "admin" || Boolean(state.user?.membership?.active); } function updateAccountIdentityBadges(membership = {}) { const isAdmin = state.user?.role === "admin" || Boolean(membership.is_admin); const subscribed = Boolean(membership.subscribed); document.querySelector("#accountAdminBadge").hidden = !isAdmin; const vipBadge = document.querySelector("#accountVipBadge"); vipBadge.hidden = false; vipBadge.classList.toggle("is-nonmember", !subscribed); setText("accountVipLabel", subscribed ? "会员" : "非会员"); vipBadge.title = subscribed ? "查看会员状态" : "查看会员权益"; } function applyMembershipAccess() { const unlocked = hasMemberAccess(); document.querySelectorAll(".member-feature-view").forEach((view) => { view.classList.toggle("member-locked", !unlocked); const gate = view.querySelector(".member-gate"); if (gate) gate.hidden = unlocked; view.querySelectorAll("button, input, textarea, select").forEach((control) => { if (control.closest(".member-gate") || control.hasAttribute("data-member-navigation")) return; if (!unlocked) { if (!("memberDisabled" in control.dataset)) { control.dataset.memberDisabled = String(control.disabled); } control.disabled = true; } else if ("memberDisabled" in control.dataset) { control.disabled = control.dataset.memberDisabled === "true"; delete control.dataset.memberDisabled; } }); }); const assistantButton = document.querySelector("#assistantButton"); assistantButton.classList.toggle("member-locked-control", !unlocked); assistantButton.title = unlocked ? "复盘助手" : "复盘助手(会员可用)"; updateAssistantControls(); } function selectAccountPanel(panel) { const selected = ["profile", "membership", "password"].includes(panel) ? panel : "profile"; const titles = { profile: "个人资料", membership: "会员状态", password: "修改密码" }; setText("accountDialogTitle", titles[selected]); document.querySelectorAll("[data-account-panel-content]").forEach((section) => { section.hidden = section.dataset.accountPanelContent !== selected; }); document.querySelector("#connectionStatus").hidden = selected !== "membership"; return selected; } async function openSettings(panel = "profile") { selectAccountPanel(panel); toggleAccountDropdown(false); toggleHeaderCommandMenu(false); const status = document.querySelector("#connectionStatus"); status.className = "connection-status"; status.textContent = "正在读取账号状态"; openModalDialog(elements.settingsDialog); try { const payload = await apiRequest("/api/account/status"); const access = payload.llm_access || {}; const membership = access.membership || {}; if (state.user) { state.user.membership = membership; updateAccountIdentityBadges(membership); applyMembershipAccess(); } status.textContent = membership.active ? "账户权益已同步" : "账户信息已同步"; status.classList.toggle("connected", true); setText("membershipBadge", membership.subscribed ? "会员有效" : membership.is_admin ? "管理员权限" : "普通用户"); setText("membershipStateValue", membership.subscribed ? "已开通" : membership.is_admin ? "管理员可用" : "未开通"); setText("membershipRemainingValue", membership.subscribed && membership.expires_at ? `${number(membership.remaining_days)} 天` : membership.is_admin || membership.subscribed ? "长期有效" : "--"); setText("membershipDetail", membership.subscribed ? `${membership.plan || "会员"}${membership.expires_at ? ` · 有效至 ${membershipDateDisplay(membership.expires_at, true)}` : " · 长期有效"}` : membership.is_admin ? "管理员拥有智能功能管理权限,但不会因此显示为已开通会员。" : "开通会员后可使用智能选股、问师、问天、复盘助手等智能功能。"); setText("membershipQuotaHint", `会员默认每日智能分析额度 ${number(access.daily_limit)} 次,由管理员统一设置。`); setText("membershipUsage", membership.active ? `今日已用 ${number(access.used_today)} 次` : "今日智能分析:--"); setText("membershipUsageSummary", membership.active ? `${number(access.used_today)} / ${number(access.daily_limit)}` : "--"); setText("membershipRemainingUsage", membership.is_admin ? "不限" : membership.active ? `${number(access.remaining_calls)} 次` : "--"); const birth = payload.birth_profile || {}; if (birth.birth_datetime) { const [birthDate, birthTime] = String(birth.birth_datetime).split("T"); document.querySelector("#accountBirthDate").value = birthDate || ""; document.querySelector("#accountBirthTime").value = (birthTime || "").slice(0, 5); document.querySelector("#accountBirthGender").value = birth.gender || "unspecified"; } setText("birthProfileStatus", payload.birth_profile_configured ? "已加密保存" : "尚未设置"); document.querySelector("#deleteBirthProfileButton").disabled = !payload.birth_profile_configured; } catch (error) { status.hidden = false; status.textContent = "账户状态暂时无法同步"; showToast(error.message || "账号信息加载失败"); } } async function changeAccountPassword(event) { event.preventDefault(); const form = event.currentTarget; const button = form.querySelector("button[type='submit']"); button.disabled = true; try { await apiRequest("/api/account/password", "POST", { current_password: document.querySelector("#currentPassword").value, new_password: document.querySelector("#newPassword").value, confirm_password: document.querySelector("#confirmPassword").value, }); form.reset(); showToast("密码已更新"); } catch (error) { showToast(error.message || "密码更新失败"); } finally { button.disabled = false; } } async function switchAccount() { toggleAccountDropdown(false); window.location.assign("/login/"); } function membershipDateDisplay(value) { if (!value) return ""; const parsed = new Date(value); if (Number.isNaN(parsed.getTime())) return String(value).slice(0, 10); return new Intl.DateTimeFormat("zh-CN", { year: "numeric", month: "2-digit", day: "2-digit" }).format(parsed); } function toggleAccountDropdown(force, returnFocus = false) { const menu = document.querySelector("#accountDropdown"); const button = document.querySelector("#accountButton"); if (!menu || !button) return; const open = typeof force === "boolean" ? force : menu.hidden; menu.hidden = !open; button.setAttribute("aria-expanded", String(open)); document.querySelector(".account-menu-shell")?.classList.toggle("is-open", open); if (open) { setText("accountMenuName", state.user?.username || "当前账号"); const membership = state.user?.membership || {}; setText("accountMenuRole", state.user?.role === "admin" ? (membership.subscribed ? "管理员 · 会员" : "管理员") : membership.subscribed ? "会员用户" : "普通用户"); } else if (returnFocus) { button.focus(); } } function handleAccountMenuKeydown(event) { const menu = document.querySelector("#accountDropdown"); if (!menu) return; if (menu.hidden) { if (document.activeElement?.id === "accountButton" && event.key === "ArrowDown") { event.preventDefault(); toggleAccountDropdown(true); menu.querySelector('[role="menuitem"]')?.focus(); } return; } const items = [...menu.querySelectorAll('[role="menuitem"]:not(:disabled)')]; if (!items.length) return; const current = items.indexOf(document.activeElement); if (event.key === "ArrowDown" || event.key === "ArrowUp") { event.preventDefault(); const offset = event.key === "ArrowDown" ? 1 : -1; items[(current + offset + items.length) % items.length].focus(); } else if (event.key === "Home" || event.key === "End") { event.preventDefault(); items[event.key === "Home" ? 0 : items.length - 1].focus(); } } function bindSessionEvents() { document.querySelectorAll("[data-auth-mode]").forEach((button) => { button.addEventListener("click", () => selectAuthMode(button.dataset.authMode)); }); document.querySelector("#authForm").addEventListener("submit", submitAuthForm); document.addEventListener("click", (event) => { if (!event.target.closest(".account-menu-shell")) toggleAccountDropdown(false); }); window.addEventListener("keydown", handleGlobalSearchShortcut); document.querySelectorAll("[data-open-account]").forEach((button) => { button.addEventListener("click", () => openSettings("membership")); }); document.querySelector("#accountButton").addEventListener("click", (event) => { event.stopPropagation(); toggleAccountDropdown(); }); document.querySelector("#accountVipBadge").addEventListener("click", () => openSettings("membership")); document.querySelectorAll("[data-account-panel]").forEach((button) => { button.addEventListener("click", () => openSettings(button.dataset.accountPanel)); }); document.querySelector("#switchAccountMenuButton").addEventListener("click", switchAccount); document.querySelector("#logoutMenuButton").addEventListener("click", logoutAccount); document.querySelector("#closeSettingsDialog").addEventListener("click", () => elements.settingsDialog.close()); document.querySelector("#accountBirthForm").addEventListener("submit", saveAccountBirthProfile); document.querySelector("#deleteBirthProfileButton").addEventListener("click", deleteAccountBirthProfile); document.querySelector("#passwordForm").addEventListener("submit", changeAccountPassword); document.addEventListener("keydown", (event) => { if (event.key === "Escape") toggleAccountDropdown(false, true); handleAccountMenuKeydown(event); }); }