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 `
保存至 hub_settings.json,换浏览器同样生效。关闭导航后对应页面将不可从顶栏进入,直接访问 URL 会跳回监控区。
-手动录入 FOMC / CPI / 就业数据发布时间(北京时间)。监控区在发布前后各 1 小时提示风险:有仓注意仓位,无仓建议等待。仅提醒,不拦截下单。
@@ -903,9 +914,15 @@与四所实例策略通知独立;手动/中控开平仓与新开仓会推送至此 Webhook。链接可在下方单独修改。
@@ -947,13 +964,25 @@