feat(品牌): 支持 _local 外部优先 Logo 与图标覆盖

新增 /admin/brand-assets 静态挂载,并在前端与桌面端统一采用外部优先、内置兜底的品牌资源解析;同时补充 BRANDING 文档与 README 入口说明。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-05-07 10:12:16 +08:00
parent b15ff4c31c
commit acf1266e90
7 changed files with 106 additions and 13 deletions

View file

@ -8,6 +8,7 @@ import asyncio
import os
import shutil
from contextlib import asynccontextmanager
from pathlib import Path
from typing import Any
import threading
@ -230,6 +231,8 @@ async def _lifespan(app: FastAPI): # type: ignore[no-untyped-def]
def create_app() -> FastAPI:
app = FastAPI(title="ops-gateway", version="0.1", lifespan=_lifespan)
brand_assets_dir = (Path(PROJECT_ROOT) / "_local" / "branding").resolve()
app.mount("/admin/brand-assets", StaticFiles(directory=str(brand_assets_dir), check_dir=False), name="admin-brand-assets")
app.mount("/admin/assets", StaticFiles(directory=str(admin_static_dir())), name="admin-assets")
app.include_router(build_admin_router())
app.include_router(weixin_ilink_router)
@ -239,7 +242,8 @@ def create_app() -> FastAPI:
resp = await call_next(request)
# Avoid stale JS/CSS after refactors (especially in Electron webview).
# The admin SPA and /chat both load from /admin/assets/...
if str(request.url.path or "").startswith("/admin/assets/"):
req_path = str(request.url.path or "")
if req_path.startswith("/admin/assets/") or req_path.startswith("/admin/brand-assets/"):
resp.headers["Cache-Control"] = "no-store, max-age=0"
resp.headers["Pragma"] = "no-cache"
return resp