From ea5c6cddb447716b076c5a64aa75d7c056f142eb Mon Sep 17 00:00:00 2001 From: dekun Date: Tue, 23 Jun 2026 19:37:16 +0800 Subject: [PATCH] Add per-card save and collapse on settings page Each settings section and exchange card gets its own save button and fold toggle with state persisted in localStorage. Co-authored-by: Cursor --- manual_trading_hub/static/app.css | 160 +++++++++++++++++++++++++++ manual_trading_hub/static/app.js | 153 +++++++++++++++++++++---- manual_trading_hub/static/index.html | 57 +++++++--- 3 files changed, 337 insertions(+), 33 deletions(-) diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css index 0ca343b..a4f6024 100644 --- a/manual_trading_hub/static/app.css +++ b/manual_trading_hub/static/app.css @@ -2642,8 +2642,168 @@ button.btn-sm { .settings-display-panel, .settings-macro-panel, .settings-supervisor-panel { + margin-bottom: 0; +} + +.settings-section { margin-bottom: 16px; +} + +.settings-section-head { + display: flex; + align-items: center; + gap: 10px; padding: 14px 16px; + border-bottom: 1px solid var(--border-soft); +} + +.settings-section.is-collapsed .settings-section-head { + border-bottom-color: transparent; +} + +.settings-section-head .settings-display-title { + flex: 1; + margin: 0; + min-width: 0; +} + +.settings-section-head-actions { + display: flex; + align-items: center; + gap: 8px; + flex-shrink: 0; +} + +.settings-section-fold { + flex-shrink: 0; + width: 28px; + height: 28px; + padding: 0; + border: 1px solid var(--border-soft); + border-radius: 6px; + background: color-mix(in srgb, var(--panel) 90%, var(--accent) 10%); + color: var(--accent); + cursor: pointer; + font-size: 0; + line-height: 1; + transition: transform 0.15s ease, border-color 0.15s ease; + position: relative; +} + +.settings-section-fold::before { + content: "▾"; + font-size: 0.85rem; + line-height: 28px; + display: block; + text-align: center; +} + +.settings-section-fold:hover { + border-color: color-mix(in srgb, var(--accent) 50%, var(--border-soft)); +} + +.settings-section.is-collapsed .settings-section-fold::before { + content: "▸"; +} + +.settings-section-save { + flex-shrink: 0; + font-size: 0.82rem; + padding: 6px 14px; +} + +.settings-section-body { + padding: 14px 16px; +} + +.settings-section.is-collapsed .settings-section-body { + display: none; +} + +.settings-page-toolbar { + margin-top: 4px; +} + +.settings-card-topbar { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 12px; + padding-bottom: 10px; + border-bottom: 1px dashed var(--border-soft); +} + +.settings-card-fold { + flex-shrink: 0; + width: 26px; + height: 26px; + padding: 0; + border: 1px solid var(--border-soft); + border-radius: 6px; + background: transparent; + color: var(--muted); + cursor: pointer; + font-size: 0; + line-height: 1; + transition: color 0.15s ease, border-color 0.15s ease; + position: relative; +} + +.settings-card-fold::before { + content: "▾"; + font-size: 0.8rem; + line-height: 26px; + display: block; + text-align: center; +} + +.settings-card-fold:hover { + color: var(--accent); + border-color: color-mix(in srgb, var(--accent) 40%, var(--border-soft)); +} + +.settings-card.is-collapsed .settings-card-fold::before { + content: "▸"; +} + +.settings-card-title { + flex: 1; + min-width: 0; + font-size: 0.92rem; + font-weight: 600; + color: var(--text); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.settings-card-save { + flex-shrink: 0; + font-size: 0.78rem; + padding: 5px 12px; +} + +.settings-card-body { + display: block; +} + +.settings-card.is-collapsed .settings-card-body { + display: none; +} + +@media (max-width: 720px) { + .settings-section-head { + flex-wrap: wrap; + } + + .settings-section-head-actions { + width: 100%; + justify-content: flex-end; + } + + .settings-card-topbar { + flex-wrap: wrap; + } } .settings-display-title { diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index f86842f..83a49fd 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -3648,6 +3648,105 @@ renderSettingsList(data); }; }); + bindSettingsCardFolds(list); + list.querySelectorAll(".settings-card-save").forEach((btn) => { + btn.addEventListener("click", () => { + void saveSettingsSection("exchange", { label: btn.dataset.label || "账户" }); + }); + }); + } + + const SETTINGS_FOLD_KEY = "hub_settings_section_fold"; + + function settingsFoldStorageKey(section, cardKey) { + return cardKey ? `${SETTINGS_FOLD_KEY}_${section}_${cardKey}` : `${SETTINGS_FOLD_KEY}_${section}`; + } + + function getSettingsFoldState(section, cardKey) { + try { + return localStorage.getItem(settingsFoldStorageKey(section, cardKey)) === "1"; + } catch (_) { + return false; + } + } + + function setSettingsFoldState(section, collapsed, cardKey) { + try { + localStorage.setItem(settingsFoldStorageKey(section, cardKey), collapsed ? "1" : "0"); + } catch (_) {} + } + + function applySettingsSectionFold(el) { + const section = el.dataset.settingsSection; + if (!section) return; + const collapsed = getSettingsFoldState(section); + el.classList.toggle("is-collapsed", collapsed); + const btn = el.querySelector(":scope > .settings-section-head > .settings-section-fold"); + if (btn) btn.setAttribute("aria-expanded", collapsed ? "false" : "true"); + } + + function applySettingsCardFold(card) { + const key = card.dataset.key || card.dataset.idx || ""; + const collapsed = getSettingsFoldState("exchange", String(key)); + card.classList.toggle("is-collapsed", collapsed); + const btn = card.querySelector(".settings-card-fold"); + if (btn) btn.setAttribute("aria-expanded", collapsed ? "false" : "true"); + } + + function bindSettingsCardFolds(root) { + (root || document).querySelectorAll(".settings-card").forEach((card) => { + if (card.dataset.foldBound === "1") return; + card.dataset.foldBound = "1"; + applySettingsCardFold(card); + const foldBtn = card.querySelector(".settings-card-fold"); + if (!foldBtn) return; + foldBtn.addEventListener("click", () => { + const key = card.dataset.key || card.dataset.idx || ""; + const collapsed = !card.classList.contains("is-collapsed"); + card.classList.toggle("is-collapsed", collapsed); + foldBtn.setAttribute("aria-expanded", collapsed ? "false" : "true"); + setSettingsFoldState("exchange", collapsed, String(key)); + }); + }); + } + + function initSettingsSectionFolds() { + document.querySelectorAll(".settings-section[data-settings-section]").forEach((el) => { + applySettingsSectionFold(el); + if (el.dataset.foldBound === "1") return; + el.dataset.foldBound = "1"; + const foldBtn = el.querySelector(":scope > .settings-section-head > .settings-section-fold"); + if (foldBtn) { + foldBtn.addEventListener("click", () => { + const section = el.dataset.settingsSection; + const collapsed = !el.classList.contains("is-collapsed"); + el.classList.toggle("is-collapsed", collapsed); + foldBtn.setAttribute("aria-expanded", collapsed ? "false" : "true"); + setSettingsFoldState(section, collapsed); + }); + } + }); + document.querySelectorAll(".settings-section-save").forEach((btn) => { + if (btn.dataset.saveBound === "1") return; + btn.dataset.saveBound = "1"; + btn.addEventListener("click", () => { + const section = btn.dataset.settingsSection || ""; + if (section === "macro") { + const form = document.getElementById("macro-event-form"); + if (form) form.requestSubmit(); + return; + } + const label = + section === "display" + ? "显示与导航" + : section === "supervisor" + ? "交易监管" + : section === "exchanges" + ? "交易所账户" + : "设置"; + void saveSettingsSection(section, { label }); + }); + }); } function macroDatetimeLocalToApi(v) { @@ -3798,6 +3897,7 @@ syncDisplayPrefsUI(data); syncSupervisorSettingsUI(data); renderSettingsList(data); + initSettingsSectionFolds(); }); } @@ -3806,7 +3906,15 @@ const envOff = ex.env_disabled ? '环境变量强制关' : ""; - return `
+ const cardKey = esc(ex.key || ex.id || String(idx)); + const cardTitle = esc(ex.name || ex.key || `账户 ${idx + 1}`); + return `
+
+ + ${cardTitle} + +
+
${envOff} @@ -3825,6 +3933,7 @@
+
`; } @@ -3888,7 +3997,8 @@ }; } - async function saveSettings() { + async function saveSettingsSection(section, opts) { + const options = opts || {}; const body = collectSettingsFromUI(); try { const r = await apiFetch("/api/settings", { @@ -3897,28 +4007,33 @@ body: JSON.stringify(body), }); const j = await r.json(); - if (j.ok) { - showToast("设置已保存(已写入 hub_settings.json)"); - if (j.settings) { - settingsCache = j.settings; - syncDisplayPrefsUI(j.settings); - syncSupervisorSettingsUI(j.settings); - renderSettingsList(j.settings); - loadSettingsMetaLine(); - } else { - await loadSettingsUI(); - } - if (lastMonitorRows.length) renderMonitorGrid(lastMonitorRows); - if (!pageNavAllowed(currentPage())) { - history.replaceState({}, "", "/monitor"); - setActiveNav(); - } - } else showToast("保存失败", true); + if (!j.ok) { + showToast("保存失败", true); + return; + } + const label = options.label || "设置"; + showToast(`${label}已保存`); + if (j.settings) { + settingsCache = j.settings; + syncDisplayPrefsUI(j.settings); + syncSupervisorSettingsUI(j.settings); + renderSettingsList(j.settings); + loadSettingsMetaLine(); + } + if (lastMonitorRows.length) renderMonitorGrid(lastMonitorRows); + if (!pageNavAllowed(currentPage())) { + history.replaceState({}, "", "/monitor"); + setActiveNav(); + } } catch (e) { showToast(String(e), true); } } + async function saveSettings() { + await saveSettingsSection("all", { label: "全部设置" }); + } + document.getElementById("btn-logout").onclick = async () => { try { await fetch("/api/auth/logout", { method: "POST" }); diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index f783803..9504da0 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -843,8 +843,13 @@

-
-

显示与导航

+
+
+ +

显示与导航

+ +
+

保存至 hub_settings.json,换浏览器同样生效。关闭导航后对应页面将不可从顶栏进入,直接访问 URL 会跳回监控区。

-
-
-

宏观关键数据(风控前置)

+
+
+
+
+ +

宏观关键数据(风控前置)

+ +
+

手动录入 FOMC / CPI / 就业数据发布时间(北京时间)。监控区在发布前后各 1 小时提示风险:有仓注意仓位,无仓建议等待。仅提醒,不拦截下单。

@@ -903,9 +914,15 @@
-
-
-

交易监管 · 企业微信

+
+ +
+
+ +

交易监管 · 企业微信

+ +
+

与四所实例策略通知独立;手动/中控开平仓与新开仓会推送至此 Webhook。链接可在下方单独修改。

@@ -947,13 +964,25 @@
+ +
+
+
+ +

交易所账户

+
+ + +
+
+
+
+
+
+
+ +
-
- - - -
-