Show WeCom machine name after brand title on shell and login.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-02 11:43:02 +08:00
parent 9427d9c429
commit ead3545df8
5 changed files with 72 additions and 5 deletions
+11
View File
@@ -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 "",
}
+25 -3
View File
@@ -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 (
<div className="app-shell">
<div className="app-container">
@@ -35,8 +47,18 @@ function Shell({ children }: { children: ReactNode }) {
width={28}
height={28}
/>
<span className="brand-full"></span>
<span className="brand-short"></span>
<span className="brand-full">
{machineTag ? (
<span className="brand-machine">{machineTag}</span>
) : null}
</span>
<span className="brand-short">
{machineTag ? (
<span className="brand-machine">{machineTag}</span>
) : null}
</span>
</div>
<div className="brand-actions">
<span className="meta mono topnav-user">{user}</span>
+12
View File
@@ -32,6 +32,18 @@ export function getUsername(): string | null {
return localStorage.getItem(USER_KEY);
}
/** 与企微通知同一机器名(公开接口,登录前可用)。 */
export async function fetchMachineName(): Promise<string> {
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;
+18 -2
View File
@@ -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}
/>
<h1></h1>
<h1>
{machineName ? (
<span className="brand-machine">{machineName}</span>
) : null}
</h1>
</div>
{err ? <div className="err">{err}</div> : null}
<div className="field">
+6
View File
@@ -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;