Show WeCom machine name after brand title on shell and login.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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")
|
@router.get("/me")
|
||||||
async def me(
|
async def me(
|
||||||
username: Annotated[str, Depends(require_user)],
|
username: Annotated[str, Depends(require_user)],
|
||||||
settings: Annotated[Settings, Depends(get_settings)],
|
settings: Annotated[Settings, Depends(get_settings)],
|
||||||
) -> dict:
|
) -> dict:
|
||||||
|
from ..notify import wecom
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"username": username,
|
"username": username,
|
||||||
"env_name": settings.env_name,
|
"env_name": settings.env_name,
|
||||||
"mode": settings.mode,
|
"mode": settings.mode,
|
||||||
"sim": settings.is_sim,
|
"sim": settings.is_sim,
|
||||||
|
"machine_name": wecom.wecom_machine_name() or "",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+25
-3
@@ -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 { NavLink, Navigate, Route, Routes } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
clearSession,
|
clearSession,
|
||||||
|
fetchMachineName,
|
||||||
getToken,
|
getToken,
|
||||||
getUsername,
|
getUsername,
|
||||||
startAuthTokenAutoRefresh,
|
startAuthTokenAutoRefresh,
|
||||||
@@ -22,6 +23,17 @@ const NAV = [
|
|||||||
|
|
||||||
function Shell({ children }: { children: ReactNode }) {
|
function Shell({ children }: { children: ReactNode }) {
|
||||||
const user = getUsername();
|
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 (
|
return (
|
||||||
<div className="app-shell">
|
<div className="app-shell">
|
||||||
<div className="app-container">
|
<div className="app-container">
|
||||||
@@ -35,8 +47,18 @@ function Shell({ children }: { children: ReactNode }) {
|
|||||||
width={28}
|
width={28}
|
||||||
height={28}
|
height={28}
|
||||||
/>
|
/>
|
||||||
<span className="brand-full">比特骆驼自动化对冲系统</span>
|
<span className="brand-full">
|
||||||
<span className="brand-short">比特骆驼</span>
|
比特骆驼自动化对冲系统
|
||||||
|
{machineTag ? (
|
||||||
|
<span className="brand-machine">{machineTag}</span>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
|
<span className="brand-short">
|
||||||
|
比特骆驼
|
||||||
|
{machineTag ? (
|
||||||
|
<span className="brand-machine">{machineTag}</span>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="brand-actions">
|
<div className="brand-actions">
|
||||||
<span className="meta mono topnav-user">{user}</span>
|
<span className="meta mono topnav-user">{user}</span>
|
||||||
|
|||||||
@@ -32,6 +32,18 @@ export function getUsername(): string | null {
|
|||||||
return localStorage.getItem(USER_KEY);
|
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 {
|
function tokenExpiresAtMs(): number | null {
|
||||||
const raw = localStorage.getItem(TOKEN_EXP_KEY);
|
const raw = localStorage.getItem(TOKEN_EXP_KEY);
|
||||||
if (!raw) return null;
|
if (!raw) return null;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { FormEvent, useEffect, useState } from "react";
|
import { FormEvent, useEffect, useState } from "react";
|
||||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||||
import { login, setSession } from "../api/client";
|
import { fetchMachineName, login, setSession } from "../api/client";
|
||||||
|
|
||||||
type ExchangeRes = {
|
type ExchangeRes = {
|
||||||
token: string;
|
token: string;
|
||||||
@@ -16,6 +16,17 @@ export default function LoginPage() {
|
|||||||
const [err, setErr] = useState("");
|
const [err, setErr] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [ticketBusy, setTicketBusy] = 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(() => {
|
useEffect(() => {
|
||||||
const ticket = (params.get("ticket") || "").trim();
|
const ticket = (params.get("ticket") || "").trim();
|
||||||
@@ -91,7 +102,12 @@ export default function LoginPage() {
|
|||||||
width={48}
|
width={48}
|
||||||
height={48}
|
height={48}
|
||||||
/>
|
/>
|
||||||
<h1>比特骆驼自动化对冲系统</h1>
|
<h1>
|
||||||
|
比特骆驼自动化对冲系统
|
||||||
|
{machineName ? (
|
||||||
|
<span className="brand-machine">【{machineName}】</span>
|
||||||
|
) : null}
|
||||||
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
{err ? <div className="err">{err}</div> : null}
|
{err ? <div className="err">{err}</div> : null}
|
||||||
<div className="field">
|
<div className="field">
|
||||||
|
|||||||
@@ -124,6 +124,12 @@ input {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.brand-machine {
|
||||||
|
margin-left: 0.15em;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
.brand-actions {
|
.brand-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
Reference in New Issue
Block a user