From 8236d1ef5a59306b4b839431d2256206aec2cfa8 Mon Sep 17 00:00:00 2001 From: dekun Date: Thu, 1 Oct 2026 12:10:07 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=B8=AD=E6=8E=A7=E7=A7=91?= =?UTF-8?q?=E6=8A=80=E9=A3=8E=E7=95=8C=E9=9D=A2=EF=BC=9A=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E5=B1=85=E4=B8=AD=E3=80=81=E6=B5=AE=E7=9B=88=E9=85=8D=E8=89=B2?= =?UTF-8?q?=E3=80=81=E9=BB=98=E8=AE=A4=E6=89=93=E5=BC=80=E9=A6=96=E4=B8=AA?= =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 总览改名期权持仓总览,内容区 1800 居中,去掉左上角仓库名。 Co-authored-by: Cursor --- app.py | 12 +- static/hub.css | 268 +++++++++++++++++++++++++++++---------- static/hub.js | 15 ++- templates/base.html | 3 +- templates/dashboard.html | 2 +- templates/login.html | 24 ++-- 6 files changed, 240 insertions(+), 84 deletions(-) diff --git a/app.py b/app.py index a9b3d7a..1f6fb4d 100644 --- a/app.py +++ b/app.py @@ -91,13 +91,13 @@ def login(): if u == hub_username() and p == hub_password(): session["logged_in"] = True session.permanent = True - nxt = (request.args.get("next") or request.form.get("next") or "/").strip() or "/" + nxt = (request.args.get("next") or request.form.get("next") or "/desk").strip() or "/desk" if not nxt.startswith("/"): - nxt = "/" + nxt = "/desk" return redirect(nxt) flash("用户名或密码错误") if session.get("logged_in"): - return redirect("/") + return redirect("/desk") return render_template("login.html") @@ -195,7 +195,11 @@ def settings_page(): @app.route("/desk") @login_required def desk(): + instances = list_instances(enabled_only=True) iid = request.args.get("id") + # 未指定客户端时默认打开第一个 + if (not iid or not str(iid).isdigit()) and instances: + return redirect(url_for("desk", id=instances[0]["id"])) inst = None open_url = "" if iid and str(iid).isdigit(): @@ -209,7 +213,7 @@ def desk(): ) return render_template( "desk.html", - instances=list_instances(enabled_only=True), + instances=instances, current=inst, open_url=open_url, ) diff --git a/static/hub.css b/static/hub.css index 08c4c80..3956181 100644 --- a/static/hub.css +++ b/static/hub.css @@ -1,65 +1,186 @@ +@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=Orbitron:wght@500;600&display=swap"); + :root { - --bg: #0f1219; - --panel: #171c27; - --line: #2a3348; - --text: #e8ecf5; - --muted: #8b95ab; - --accent: #6c5ce7; - --ok: #3dd68c; - --bad: #ff6b6b; + --bg: #070b14; + --bg-2: #0c1424; + --panel: rgba(16, 24, 40, 0.82); + --panel-solid: #101828; + --line: rgba(56, 189, 248, 0.22); + --line-strong: rgba(56, 189, 248, 0.45); + --text: #e8f1ff; + --muted: #8aa0b8; + --accent: #22d3ee; + --accent-2: #38bdf8; + --accent-deep: #0ea5e9; + --ok: #34d399; + --bad: #fb7185; + --shadow: 0 0 0 1px rgba(56, 189, 248, 0.08), 0 18px 50px rgba(0, 0, 0, 0.45); + --radius: 14px; + --font: "IBM Plex Sans", "Segoe UI", sans-serif; + --font-display: "Orbitron", "IBM Plex Sans", sans-serif; } * { box-sizing: border-box; } +html, body { min-height: 100%; } body { margin: 0; - font-family: "Segoe UI", system-ui, sans-serif; - background: var(--bg); + font-family: var(--font); color: var(--text); + background: + radial-gradient(1200px 600px at 12% -10%, rgba(14, 165, 233, 0.18), transparent 55%), + radial-gradient(900px 500px at 90% 10%, rgba(34, 211, 238, 0.1), transparent 50%), + linear-gradient(180deg, var(--bg-2), var(--bg) 40%, #05080f); + background-attachment: fixed; } -a { color: #9ec1ff; text-decoration: none; } +body::before { + content: ""; + position: fixed; + inset: 0; + pointer-events: none; + z-index: 0; + opacity: 0.35; + background-image: + linear-gradient(rgba(56, 189, 248, 0.06) 1px, transparent 1px), + linear-gradient(90deg, rgba(56, 189, 248, 0.06) 1px, transparent 1px); + background-size: 48px 48px; + mask-image: radial-gradient(ellipse at center, #000 35%, transparent 80%); +} +body > * { position: relative; z-index: 1; } +a { color: var(--accent-2); text-decoration: none; } +a:hover { color: #7dd3fc; } + .topbar { display: flex; - justify-content: space-between; + justify-content: flex-start; align-items: center; - padding: 10px 16px; + padding: 12px 20px; border-bottom: 1px solid var(--line); - background: #12161f; + background: rgba(8, 14, 26, 0.75); + backdrop-filter: blur(12px); } -.topbar nav a { margin-left: 14px; color: var(--muted); } -.topbar nav a.active { color: #fff; } -.brand { font-weight: 700; } +.topbar nav a { + margin-right: 16px; + color: var(--muted); + font-weight: 500; + letter-spacing: 0.02em; +} +.topbar nav a:last-child { margin-right: 0; } +.topbar nav a.active { + color: var(--accent); + text-shadow: 0 0 18px rgba(34, 211, 238, 0.35); +} + .main { padding: 16px; max-width: 1200px; margin: 0 auto; } -/* 总览/工作台:带鱼屏两侧留边,内容区限宽居中 */ body.page-desk .main, body.page-dashboard .main { - max-width: 1680px; + max-width: 1800px; width: 100%; margin: 0 auto; padding: 12px clamp(16px, 3vw, 48px) 20px; } +body.page-login { + min-height: 100vh; +} +body.page-login .main { + max-width: none; + min-height: 100vh; + margin: 0; + padding: 24px; + display: flex; + align-items: center; + justify-content: center; +} + .page-head { display: flex; align-items: center; gap: 12px; } +.page-head h1 { + margin: 0; + font-family: var(--font-display); + font-size: 1.35rem; + font-weight: 600; + letter-spacing: 0.04em; +} .muted { color: var(--muted); font-size: 0.9rem; } .flash { - background: #2a2140; - border: 1px solid #5b4a8a; + background: rgba(14, 165, 233, 0.12); + border: 1px solid var(--line-strong); padding: 8px 12px; - border-radius: 8px; + border-radius: 10px; margin-bottom: 12px; + box-shadow: var(--shadow); +} + +/* —— 登录 —— */ +.login-wrap { + width: 100%; + max-width: 420px; } .login-card { - max-width: 360px; - margin: 10vh auto; - background: var(--panel); - border: 1px solid var(--line); - border-radius: 12px; - padding: 24px; + width: 100%; + margin: 0; + padding: 32px 28px 28px; + border-radius: 18px; + background: linear-gradient(160deg, rgba(18, 32, 54, 0.92), rgba(10, 16, 28, 0.95)); + border: 1px solid var(--line-strong); + box-shadow: var(--shadow), inset 0 1px 0 rgba(255, 255, 255, 0.04); + backdrop-filter: blur(16px); } +.login-card h1 { + margin: 0 0 8px; + font-family: var(--font-display); + font-size: 1.45rem; + font-weight: 600; + letter-spacing: 0.06em; + background: linear-gradient(90deg, #e0f2fe, var(--accent)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} +.login-card .login-sub { + margin: 0 0 22px; + color: var(--muted); + font-size: 0.88rem; + line-height: 1.45; +} +.login-card .stack label { + display: block; + margin: 0 0 14px; + font-size: 0.82rem; + color: #b6c7da; + font-weight: 500; +} +.login-card input { + margin-top: 6px; + padding: 11px 12px; + border-radius: 10px; + border: 1px solid var(--line); + background: rgba(5, 10, 18, 0.85); + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} +.login-card input:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.15); +} +.login-card button[type="submit"] { + width: 100%; + margin-top: 8px; + padding: 12px 14px; + font-weight: 600; + letter-spacing: 0.08em; + background: linear-gradient(90deg, var(--accent-deep), var(--accent)); + color: #041018; + box-shadow: 0 8px 24px rgba(14, 165, 233, 0.28); +} +.login-card button[type="submit"]:hover { + filter: brightness(1.05); +} + .stack label, .form-grid label { display: block; margin: 8px 0; } input[type="text"], input[type="password"], input:not([type]) { width: 100%; padding: 8px 10px; border-radius: 8px; border: 1px solid var(--line); - background: #0d1017; + background: #0a1220; color: var(--text); } button, .btn { @@ -68,17 +189,21 @@ button, .btn { padding: 8px 12px; border-radius: 8px; border: 0; - background: var(--accent); - color: #fff; + background: linear-gradient(90deg, var(--accent-deep), #06b6d4); + color: #041018; + font-weight: 600; cursor: pointer; } -.btn-danger { background: #8b3a3a; } +.btn:hover, button:hover { filter: brightness(1.06); } +.btn-danger { background: #9f1239; color: #fff; } .panel { background: var(--panel); border: 1px solid var(--line); - border-radius: 12px; + border-radius: var(--radius); padding: 14px; margin: 14px 0; + box-shadow: var(--shadow); + backdrop-filter: blur(10px); } .form-grid { display: grid; @@ -92,8 +217,10 @@ button, .btn { overflow-x: auto; margin-top: 12px; border: 1px solid var(--line); - border-radius: 12px; + border-radius: var(--radius); background: var(--panel); + box-shadow: var(--shadow); + backdrop-filter: blur(10px); } .overview-table { width: 100%; @@ -103,15 +230,16 @@ button, .btn { .overview-table th, .overview-table td { padding: 10px 12px; - border-bottom: 1px solid var(--line); + border-bottom: 1px solid rgba(56, 189, 248, 0.12); text-align: left; vertical-align: top; } .overview-table th { - color: var(--muted); + color: var(--accent-2); font-weight: 600; - background: #12161f; + background: rgba(8, 16, 30, 0.9); white-space: nowrap; + letter-spacing: 0.03em; } .overview-table tr:last-child td { border-bottom: 0; } .overview-table .status-on { color: var(--ok); } @@ -134,13 +262,15 @@ button, .btn { .card { background: var(--panel); border: 1px solid var(--line); - border-radius: 12px; + border-radius: var(--radius); padding: 12px; + box-shadow: var(--shadow); } .card .status-on { color: var(--ok); } .card .status-off { color: var(--bad); } .card table { width: 100%; font-size: 0.8rem; border-collapse: collapse; } .card td, .card th { border-bottom: 1px solid var(--line); padding: 4px 2px; text-align: left; } + .desk { display: grid; grid-template-columns: 220px minmax(0, 1fr); @@ -155,10 +285,12 @@ button, .btn { .desk-side { background: var(--panel); border: 1px solid var(--line); - border-radius: 12px; + border-radius: var(--radius); padding: 10px; min-width: 0; overflow: hidden; + box-shadow: var(--shadow); + backdrop-filter: blur(10px); } .desk-side-head { display: flex; @@ -169,8 +301,11 @@ button, .btn { } .desk-side-title { margin: 0; - font-size: 1rem; + font-size: 0.95rem; + font-family: var(--font-display); + letter-spacing: 0.04em; white-space: nowrap; + color: var(--accent-2); } .desk-side-toggle { flex: 0 0 auto; @@ -180,34 +315,24 @@ button, .btn { padding: 0; border-radius: 8px; border: 1px solid var(--line); - background: #12161f; + background: rgba(8, 16, 30, 0.9); color: var(--text); font-size: 1.1rem; line-height: 1; cursor: pointer; } -.desk-side-toggle:hover { border-color: var(--accent); } -.desk-side-toggle--bar { - display: none; -} +.desk-side-toggle:hover { border-color: var(--accent); color: var(--accent); } +.desk-side-toggle--bar { display: none; } .desk.is-side-collapsed .desk-side-title, .desk.is-side-collapsed .desk-list-name, -.desk.is-side-collapsed .desk-list-empty { - display: none; -} -.desk.is-side-collapsed .desk-side { - padding: 8px 6px; -} +.desk.is-side-collapsed .desk-list-empty { display: none; } +.desk.is-side-collapsed .desk-side { padding: 8px 6px; } .desk.is-side-collapsed .desk-side-head { justify-content: center; margin-bottom: 0; } -.desk.is-side-collapsed .desk-side-toggle { - transform: rotate(180deg); -} -.desk.is-side-collapsed .desk-list { - display: none; -} +.desk.is-side-collapsed .desk-side-toggle { transform: rotate(180deg); } +.desk.is-side-collapsed .desk-list { display: none; } .desk.is-side-collapsed .desk-side-toggle--bar { display: inline-flex; align-items: center; @@ -217,12 +342,22 @@ button, .btn { .desk-list li { margin: 6px 0; } .desk-list li a { display: block; - padding: 6px 8px; + padding: 7px 9px; border-radius: 8px; color: var(--muted); + border: 1px solid transparent; +} +.desk-list li a:hover { + background: rgba(34, 211, 238, 0.08); + color: #fff; + border-color: rgba(34, 211, 238, 0.2); +} +.desk-list li.active a { + color: #fff; + font-weight: 600; + background: rgba(34, 211, 238, 0.16); + border-color: rgba(34, 211, 238, 0.35); } -.desk-list li a:hover { background: rgba(108, 92, 231, 0.12); color: #fff; } -.desk-list li.active a { color: #fff; font-weight: 600; background: rgba(108, 92, 231, 0.22); } .desk-main { display: flex; flex-direction: column; min-height: calc(100vh - 70px); min-width: 0; } .desk-toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; @@ -233,8 +368,9 @@ button, .btn { width: 100%; min-height: calc(100vh - 130px); border: 1px solid var(--line); - border-radius: 10px; + border-radius: var(--radius); background: #000; + box-shadow: var(--shadow); } .empty { padding: 40px; color: var(--muted); } .settings-card { max-width: 480px; } @@ -243,17 +379,19 @@ input.readonly { opacity: 0.75; cursor: default; } +.pnl-up { color: var(--ok); font-weight: 600; } +.pnl-down { color: var(--bad); font-weight: 600; } +.pnl-flat { color: var(--muted); } + @media (max-width: 800px) { .desk, - .desk.is-side-collapsed { - grid-template-columns: 1fr; - } + .desk.is-side-collapsed { grid-template-columns: 1fr; } .desk-side-toggle--bar { display: none !important; } } @media (min-width: 2000px) { body.page-desk .main, body.page-dashboard .main { - max-width: 1760px; + max-width: 1800px; padding-left: clamp(32px, 6vw, 96px); padding-right: clamp(32px, 6vw, 96px); } diff --git a/static/hub.js b/static/hub.js index 8b87126..175143e 100644 --- a/static/hub.js +++ b/static/hub.js @@ -6,6 +6,16 @@ return (n > 0 ? "+" : "") + n.toFixed(2) + " U"; } + function pnlClass(v) { + const n = Number(v); + if (!Number.isFinite(n) || n === 0) return "pnl-flat"; + return n > 0 ? "pnl-up" : "pnl-down"; + } + + function fmtPnlHtml(v) { + return '' + escapeHtml(fmtPnl(v)) + ""; + } + function escapeHtml(s) { return String(s) .replace(/&/g, "&") @@ -20,8 +30,7 @@ const items = list.slice(0, 8).map(function (p) { const id = escapeHtml(p.inst_id || ""); const sheets = escapeHtml(String(p.sheets != null ? p.sheets : "—")); - const pnl = escapeHtml(fmtPnl(p.pnl_usdt)); - return "
  • " + id + " · " + sheets + "张 · " + pnl + "
  • "; + return "
  • " + id + " · " + sheets + "张 · " + fmtPnlHtml(p.pnl_usdt) + "
  • "; }); const more = list.length > 8 @@ -59,7 +68,7 @@ escapeHtml(inst.mode || "—") + "" + "" + - escapeHtml(fmtPnl(pnl)) + + fmtPnlHtml(pnl) + "" + "" + positionSummary(brief) + diff --git a/templates/base.html b/templates/base.html index 2da854a..426c62a 100644 --- a/templates/base.html +++ b/templates/base.html @@ -6,10 +6,9 @@ {% block title %}期权中控{% endblock %} - + {% if session.get('logged_in') %}
    -
    crypto_okx_hub