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;