feat(web): 品牌区显示应用版本号 — 新增 GET /api/v1/meta,前端启动拉取展示

This commit is contained in:
Justin Gu
2026-09-06 14:34:59 +08:00
parent 99733f13d1
commit d5cf9c14a9
5 changed files with 80 additions and 3 deletions
+21
View File
@@ -61,11 +61,32 @@ class SwitchResponse(BaseModel):
message: str message: str
class MetaResponse(BaseModel):
"""GET /meta 的响应。"""
version: str
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #
# Routes # Routes
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #
@router.get("/meta", response_model=MetaResponse)
async def get_meta() -> MetaResponse:
"""应用元信息:版本号(WebUI 左上角展示)。
源码运行未装包元数据(importlib.metadata 不可用)时留空,前端不显示。
"""
try:
from importlib.metadata import version
v: str = version("easy-tdx")
except Exception: # noqa: BLE001 — 元数据缺失属正常场景,留空即可
v = ""
return MetaResponse(version=v)
@router.get("/server/hosts", response_model=HostListResponse) @router.get("/server/hosts", response_model=HostListResponse)
async def list_hosts(request: Request) -> HostListResponse: async def list_hosts(request: Request) -> HostListResponse:
"""列出所有候选 host + 当前正在使用的 host。 """列出所有候选 host + 当前正在使用的 host。
+22
View File
@@ -197,6 +197,28 @@ def test_realtime_router_endpoints():
assert any("realtime" in p for p in paths) assert any("realtime" in p for p in paths)
# ---------------------------------------------------------------------------
# Meta endpointWebUI 品牌区版本号展示)
# ---------------------------------------------------------------------------
def test_meta_endpoint_returns_version():
"""GET /api/v1/meta 应返回 version 字段(已安装时为语义化版本)。"""
pytest.importorskip("fastapi")
from fastapi.testclient import TestClient
from easy_tdx.web import create_app
client = TestClient(create_app())
resp = client.get("/api/v1/meta")
assert resp.status_code == 200
body = resp.json()
assert set(body.keys()) == {"version"}
assert isinstance(body["version"], str)
# 本测试环境下包已安装(pip install -e .),应能取到版本号
assert body["version"] != ""
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Task 10: CLI serve command # Task 10: CLI serve command
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
+19 -3
View File
@@ -1,12 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
// 根组件:侧边栏终端外壳 + 路由出口。 // 根组件:侧边栏终端外壳 + 路由出口。
// 布局借鉴专业看盘终端(侧边栏分组导航 + 底部实时连接状态徽标)。 // 布局借鉴专业看盘终端(侧边栏分组导航 + 底部实时连接状态徽标)。
import { onMounted } from 'vue' import { onMounted, ref } from 'vue'
import { fetchMeta } from './api'
import { useQuoteStore } from './stores/quotes' import { useQuoteStore } from './stores/quotes'
const quoteStore = useQuoteStore() const quoteStore = useQuoteStore()
onMounted(() => quoteStore.connect())
// 应用版本号(GET /meta),品牌区展示;取不到(旧后端/源码未装包)则不显示
const appVersion = ref('')
onMounted(async () => {
quoteStore.connect()
appVersion.value = (await fetchMeta()).version
})
const sseLabel: Record<string, string> = { const sseLabel: Record<string, string> = {
connecting: '连接中', connecting: '连接中',
@@ -21,7 +28,10 @@ const sseLabel: Record<string, string> = {
<aside class="sidebar"> <aside class="sidebar">
<div class="brand"> <div class="brand">
<span class="brand-name">easy-tdx</span> <span class="brand-name">easy-tdx</span>
<span class="brand-sub">行情终端</span> <span class="brand-sub">
行情终端
<span v-if="appVersion" class="brand-ver">v{{ appVersion }}</span>
</span>
</div> </div>
<nav class="side-nav"> <nav class="side-nav">
<div class="nav-group">行情</div> <div class="nav-group">行情</div>
@@ -109,6 +119,12 @@ const sseLabel: Record<string, string> = {
font-size: 11px; font-size: 11px;
color: var(--text-dim); color: var(--text-dim);
} }
.brand-ver {
margin-left: 4px;
font-size: 10px;
opacity: 0.75;
font-variant-numeric: tabular-nums;
}
.side-nav { .side-nav {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
+12
View File
@@ -3,6 +3,7 @@
import type { import type {
ApiError, ApiError,
AppMeta,
BacktestRequest, BacktestRequest,
BacktestResult, BacktestResult,
Bar, Bar,
@@ -455,6 +456,17 @@ export async function switchServerHost(host: string): Promise<ServerSwitchResult
return (await resp.json()) as ServerSwitchResult return (await resp.json()) as ServerSwitchResult
} }
/** 应用元信息(版本号,品牌区展示)。失败返回空串,由调用方决定是否显示。 */
export async function fetchMeta(): Promise<AppMeta> {
try {
const resp = await fetch(`${BASE}/meta`)
if (!resp.ok) return { version: '' }
return (await resp.json()) as AppMeta
} catch {
return { version: '' }
}
}
// ── 行情终端 ──────────────────────────────────────────────────────────────── // ── 行情终端 ────────────────────────────────────────────────────────────────
/** 批量拉实时五档(REST 一次性;持续刷新走 SSE,见 stores/quotes.ts)。 /** 批量拉实时五档(REST 一次性;持续刷新走 SSE,见 stores/quotes.ts)。
+6
View File
@@ -455,6 +455,12 @@ export interface ServerSwitchResult {
message: string message: string
} }
/** GET /meta 的响应:应用元信息(版本号,WebUI 品牌区展示)。 */
export interface AppMeta {
/** 语义化版本(如 "1.32.5");源码运行元数据缺失时为空串 */
version: string
}
// ── 行情终端:实时五档(SSE / POST /api/v1/security/quotes ───────────────── // ── 行情终端:实时五档(SSE / POST /api/v1/security/quotes ─────────────────
/** 单只标的实时五档行情(后端 SecurityQuote 白名单投影,SSE 与 REST 同构)。 */ /** 单只标的实时五档行情(后端 SecurityQuote 白名单投影,SSE 与 REST 同构)。 */