diff --git a/backend/app/main.py b/backend/app/main.py index b7cdb0d..ed83f19 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -128,6 +128,41 @@ _DIST = resolve_frontend_dist() if (_DIST / "assets").is_dir(): app.mount("/assets", StaticFiles(directory=str(_DIST / "assets")), name="assets") +_ICONS = _DIST / "icons" +if _ICONS.is_dir(): + app.mount("/icons", StaticFiles(directory=str(_ICONS)), name="icons") + + +def _dist_file(name: str) -> Path: + return _DIST / name + + +@app.get("/manifest.webmanifest") +async def web_manifest(): + path = _dist_file("manifest.webmanifest") + if not path.exists(): + raise HTTPException(status_code=404, detail="manifest missing") + return FileResponse( + path, + media_type="application/manifest+json", + headers={"Cache-Control": "no-cache"}, + ) + + +@app.get("/sw.js") +async def service_worker(): + path = _dist_file("sw.js") + if not path.exists(): + raise HTTPException(status_code=404, detail="service worker missing") + return FileResponse( + path, + media_type="application/javascript", + headers={ + "Cache-Control": "no-cache", + "Service-Worker-Allowed": "/", + }, + ) + @app.get("/") async def index_page(): diff --git a/frontend/index.html b/frontend/index.html index dc4bf04..8d79815 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,7 +2,24 @@ - + + + + + + + + + + + + OKX 自动化对冲系统 diff --git a/frontend/public/icons/apple-touch-icon.png b/frontend/public/icons/apple-touch-icon.png new file mode 100644 index 0000000..7e38155 Binary files /dev/null and b/frontend/public/icons/apple-touch-icon.png differ diff --git a/frontend/public/icons/favicon-32.png b/frontend/public/icons/favicon-32.png new file mode 100644 index 0000000..d729280 Binary files /dev/null and b/frontend/public/icons/favicon-32.png differ diff --git a/frontend/public/icons/icon-192.png b/frontend/public/icons/icon-192.png new file mode 100644 index 0000000..a8bca0c Binary files /dev/null and b/frontend/public/icons/icon-192.png differ diff --git a/frontend/public/icons/icon-512.png b/frontend/public/icons/icon-512.png new file mode 100644 index 0000000..c77ad6e Binary files /dev/null and b/frontend/public/icons/icon-512.png differ diff --git a/frontend/public/manifest.webmanifest b/frontend/public/manifest.webmanifest new file mode 100644 index 0000000..b64978e --- /dev/null +++ b/frontend/public/manifest.webmanifest @@ -0,0 +1,34 @@ +{ + "name": "OKX 自动化对冲系统", + "short_name": "对冲系统", + "description": "ETH 永续+期权对冲监控与策略控制台", + "start_url": "/plan", + "scope": "/", + "display": "standalone", + "display_override": ["standalone", "minimal-ui", "browser"], + "orientation": "any", + "background_color": "#0b0e11", + "theme_color": "#0b0e11", + "lang": "zh-CN", + "categories": ["finance", "productivity"], + "icons": [ + { + "src": "/icons/icon-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/icons/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/icons/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/frontend/public/sw.js b/frontend/public/sw.js new file mode 100644 index 0000000..368c2d8 --- /dev/null +++ b/frontend/public/sw.js @@ -0,0 +1,38 @@ +/* Minimal service worker: makes the app installable on desktop & tablet. */ +const CACHE = "eth-hedge-shell-v1"; +const PRECACHE = ["/", "/plan", "/manifest.webmanifest", "/icons/icon-192.png"]; + +self.addEventListener("install", (event) => { + event.waitUntil( + caches.open(CACHE).then((cache) => cache.addAll(PRECACHE)).then(() => self.skipWaiting()), + ); +}); + +self.addEventListener("activate", (event) => { + event.waitUntil( + caches.keys().then((keys) => + Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))), + ).then(() => self.clients.claim()), + ); +}); + +self.addEventListener("fetch", (event) => { + const req = event.request; + if (req.method !== "GET") return; + const url = new URL(req.url); + if (url.origin !== self.location.origin) return; + // Never cache API / health / websocket-ish paths + if (url.pathname.startsWith("/api") || url.pathname === "/health") return; + + event.respondWith( + fetch(req) + .then((res) => { + if (res.ok && (url.pathname.startsWith("/assets") || url.pathname === "/")) { + const copy = res.clone(); + caches.open(CACHE).then((cache) => cache.put(req, copy)); + } + return res; + }) + .catch(() => caches.match(req).then((hit) => hit || caches.match("/"))), + ); +}); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a8c4591..2e1c23e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,6 +1,7 @@ import type { ReactNode } from "react"; import { NavLink, Navigate, Route, Routes } from "react-router-dom"; import { clearSession, getToken, getUsername } from "./api/client"; +import InstallBadge from "./components/InstallBadge"; import LoginPage from "./pages/Login"; import PlanPage from "./pages/Plan"; import TradesPage from "./pages/Trades"; @@ -14,6 +15,7 @@ function Shell({ children }: { children: ReactNode }) {
OKX 自动化对冲系统
+