diff --git a/backend/app/api/auth_routes.py b/backend/app/api/auth_routes.py index 72d9c21..d4076db 100644 --- a/backend/app/api/auth_routes.py +++ b/backend/app/api/auth_routes.py @@ -98,16 +98,27 @@ async def login( ) +@router.get("/branding") +async def branding() -> dict: + """登录页/顶栏展示用(无敏感信息):与企微通知同一机器名。""" + from ..notify import wecom + + return {"machine_name": wecom.wecom_machine_name() or ""} + + @router.get("/me") async def me( username: Annotated[str, Depends(require_user)], settings: Annotated[Settings, Depends(get_settings)], ) -> dict: + from ..notify import wecom + return { "username": username, "env_name": settings.env_name, "mode": settings.mode, "sim": settings.is_sim, + "machine_name": wecom.wecom_machine_name() or "", } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b25b6af..fc28524 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,7 +1,8 @@ -import { type ReactNode, useEffect } from "react"; +import { type ReactNode, useEffect, useState } from "react"; import { NavLink, Navigate, Route, Routes } from "react-router-dom"; import { clearSession, + fetchMachineName, getToken, getUsername, startAuthTokenAutoRefresh, @@ -22,6 +23,17 @@ const NAV = [ function Shell({ children }: { children: ReactNode }) { const user = getUsername(); + const [machineName, setMachineName] = useState(""); + useEffect(() => { + let cancelled = false; + void fetchMachineName().then((name) => { + if (!cancelled) setMachineName(name); + }); + return () => { + cancelled = true; + }; + }, []); + const machineTag = machineName ? `【${machineName}】` : ""; return (
@@ -35,8 +47,18 @@ function Shell({ children }: { children: ReactNode }) { width={28} height={28} /> - 比特骆驼自动化对冲系统 - 比特骆驼 + + 比特骆驼自动化对冲系统 + {machineTag ? ( + {machineTag} + ) : null} + + + 比特骆驼 + {machineTag ? ( + {machineTag} + ) : null} +
{user} diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 6f168de..e7e19ff 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -32,6 +32,18 @@ export function getUsername(): string | null { return localStorage.getItem(USER_KEY); } +/** 与企微通知同一机器名(公开接口,登录前可用)。 */ +export async function fetchMachineName(): Promise { + try { + const res = await fetch(`${getApiBase()}/api/auth/branding`); + if (!res.ok) return ""; + const data = (await res.json()) as { machine_name?: string }; + return String(data.machine_name || "").trim(); + } catch { + return ""; + } +} + function tokenExpiresAtMs(): number | null { const raw = localStorage.getItem(TOKEN_EXP_KEY); if (!raw) return null; diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index f157127..d265083 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -1,6 +1,6 @@ import { FormEvent, useEffect, useState } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; -import { login, setSession } from "../api/client"; +import { fetchMachineName, login, setSession } from "../api/client"; type ExchangeRes = { token: string; @@ -16,6 +16,17 @@ export default function LoginPage() { const [err, setErr] = useState(""); const [loading, setLoading] = useState(false); const [ticketBusy, setTicketBusy] = useState(false); + const [machineName, setMachineName] = useState(""); + + useEffect(() => { + let cancelled = false; + void fetchMachineName().then((name) => { + if (!cancelled) setMachineName(name); + }); + return () => { + cancelled = true; + }; + }, []); useEffect(() => { const ticket = (params.get("ticket") || "").trim(); @@ -91,7 +102,12 @@ export default function LoginPage() { width={48} height={48} /> -

比特骆驼自动化对冲系统

+

+ 比特骆驼自动化对冲系统 + {machineName ? ( + 【{machineName}】 + ) : null} +

{err ?
{err}
: null}
diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index ebf38d9..37fb80c 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -124,6 +124,12 @@ input { background: transparent; } +.brand-machine { + margin-left: 0.15em; + font-weight: 700; + color: var(--accent); +} + .brand-actions { display: flex; align-items: center;