diff --git a/lib/common/static/instance_settings_prefs.js b/lib/common/static/instance_settings_prefs.js
index 358afa1..44bc8c7 100644
--- a/lib/common/static/instance_settings_prefs.js
+++ b/lib/common/static/instance_settings_prefs.js
@@ -65,13 +65,37 @@
return document.getElementById("display-prefs-form");
}
- function envConfigGrid() {
+ function envConfigRoot() {
const pane = document.querySelector(".embed-tab-pane.is-active-pane");
if (pane) {
- const inPane = pane.querySelector("#env-config-grid");
+ const inPane = pane.querySelector(".env-config-page");
if (inPane) return inPane;
}
- return document.getElementById("env-config-grid");
+ return document.querySelector(".env-config-page");
+ }
+
+ function bindEnvTabs(scope) {
+ const root = scope || envConfigRoot();
+ if (!root || root.getAttribute("data-env-tabs-bound") === "1") return;
+ const tabs = root.querySelectorAll(".env-tab-btn");
+ const panels = root.querySelectorAll(".env-panel");
+ if (!tabs.length) return;
+ root.setAttribute("data-env-tabs-bound", "1");
+ tabs.forEach((btn) => {
+ btn.addEventListener("click", () => {
+ const idx = btn.getAttribute("data-env-tab");
+ tabs.forEach((b) => {
+ const on = b.getAttribute("data-env-tab") === idx;
+ b.classList.toggle("is-active", on);
+ b.setAttribute("aria-selected", on ? "true" : "false");
+ });
+ panels.forEach((p) => {
+ const on = p.getAttribute("data-env-panel") === idx;
+ p.classList.toggle("is-active", on);
+ p.hidden = !on;
+ });
+ });
+ });
}
async function loadDisplayPrefsForm(force) {
@@ -140,26 +164,32 @@
let envSchemaGroups = [];
- function renderEnvField(field) {
- const card = document.createElement("div");
- card.className = "env-field-card";
+ function renderEnvFieldRow(field) {
+ const row = document.createElement("div");
+ row.className = "env-field-row" + (field.restart_required ? " env-field-row--restart" : "");
const label = document.createElement("label");
label.className = "env-field-label";
+ label.htmlFor = "env-f-" + field.key;
label.textContent = field.label || field.key;
- card.appendChild(label);
+ if (field.restart_required) {
+ const mark = document.createElement("span");
+ mark.className = "env-restart-mark";
+ mark.title = "需重启";
+ mark.textContent = "*";
+ label.appendChild(mark);
+ }
+ row.appendChild(label);
if (field.note) {
const note = document.createElement("div");
note.className = "env-field-note muted";
note.textContent = field.note;
- card.appendChild(note);
+ row.appendChild(note);
}
let input;
if (field.type === "bool") {
input = document.createElement("select");
- [
- ["true", "开启"],
- ["false", "关闭"],
- ].forEach(([v, text]) => {
+ input.id = "env-f-" + field.key;
+ [["true", "开启"], ["false", "关闭"]].forEach(([v, text]) => {
const o = document.createElement("option");
o.value = v;
o.textContent = text;
@@ -169,6 +199,7 @@
input.value = cur === "true" || cur === "1" ? "true" : "false";
} else {
input = document.createElement("input");
+ input.id = "env-f-" + field.key;
input.type = field.sensitive ? "password" : "text";
if (field.sensitive && field.has_value) {
input.placeholder = field.masked || "留空不修改";
@@ -178,52 +209,88 @@
}
input.dataset.envKey = field.key;
input.className = "env-field-input";
- card.appendChild(input);
- const badge = document.createElement("span");
- badge.className = "env-field-badge " + (field.restart_required ? "env-badge-restart" : "env-badge-hot");
- badge.textContent = field.restart_required ? "需重启" : "保存即生效";
- card.appendChild(badge);
- return card;
+ row.appendChild(input);
+ return row;
+ }
+
+ function renderEnvConfigBody(groups) {
+ const body = document.createElement("div");
+ body.className = "env-config-body card";
+ body.id = "env-config-body";
+ body.setAttribute("data-env-ssr", "1");
+ const tabBar = document.createElement("div");
+ tabBar.className = "env-config-tabs";
+ tabBar.setAttribute("role", "tablist");
+ const panelsWrap = document.createElement("div");
+ panelsWrap.className = "env-config-panels";
+ panelsWrap.id = "env-config-grid";
+ groups.forEach((group, idx) => {
+ const tab = document.createElement("button");
+ tab.type = "button";
+ tab.className = "env-tab-btn" + (idx === 0 ? " is-active" : "");
+ tab.setAttribute("role", "tab");
+ tab.dataset.envTab = String(idx);
+ tab.setAttribute("aria-selected", idx === 0 ? "true" : "false");
+ tab.textContent = group.title || "其他";
+ tabBar.appendChild(tab);
+ const panel = document.createElement("section");
+ panel.className = "env-panel" + (idx === 0 ? " is-active" : "");
+ panel.setAttribute("role", "tabpanel");
+ panel.dataset.envPanel = String(idx);
+ if (idx !== 0) panel.hidden = true;
+ if (group.has_restart) {
+ const hint = document.createElement("p");
+ hint.className = "env-panel-hint";
+ hint.textContent = "本组含需重启项,修改后请点「保存并重启」。";
+ panel.appendChild(hint);
+ }
+ const grid = document.createElement("div");
+ grid.className = "env-form-grid";
+ (group.fields || []).forEach((field) => grid.appendChild(renderEnvFieldRow(field)));
+ panel.appendChild(grid);
+ panelsWrap.appendChild(panel);
+ });
+ body.appendChild(tabBar);
+ body.appendChild(panelsWrap);
+ return body;
}
async function loadEnvConfig(force) {
- const grid = envConfigGrid();
- if (!grid) return;
- if (!force && grid.getAttribute("data-env-ssr") === "1" && grid.querySelector("[data-env-key]")) {
+ const root = envConfigRoot();
+ const body = root && root.querySelector("#env-config-body");
+ if (!force && body && body.getAttribute("data-env-ssr") === "1" && body.querySelector("[data-env-key]")) {
+ bindEnvTabs(root);
return;
}
- return loadEnvConfigIn(grid);
+ return loadEnvConfigIn(root);
}
- async function loadEnvConfigIn(grid) {
- grid.innerHTML = '
加载配置中…
';
+ async function loadEnvConfigIn(root) {
+ const page = root || envConfigRoot() || document.querySelector(".env-config-page");
+ if (!page) return;
+ page.removeAttribute("data-env-tabs-bound");
+ const loading = document.createElement("div");
+ loading.className = "env-config-loading-wrap card";
+ loading.id = "env-config-body";
+ loading.innerHTML = '加载配置中…
';
+ const oldBody = page.querySelector("#env-config-body");
+ const oldGrid = page.querySelector("#env-config-grid.env-config-loading-wrap");
+ if (oldBody) oldBody.replaceWith(loading);
+ else if (oldGrid) oldGrid.replaceWith(loading);
try {
const data = await fetchJson("/api/settings/env");
envSchemaGroups = data.groups || [];
- grid.innerHTML = "";
- envSchemaGroups.forEach((group) => {
- const card = document.createElement("div");
- card.className = "card env-group-card";
- const h = document.createElement("h3");
- h.className = "env-group-title";
- h.textContent = group.title || "其他";
- card.appendChild(h);
- const inner = document.createElement("div");
- inner.className = "env-group-fields";
- (group.fields || []).forEach((field) => inner.appendChild(renderEnvField(field)));
- card.appendChild(inner);
- grid.appendChild(card);
- });
- grid.setAttribute("data-env-ssr", "1");
+ loading.replaceWith(renderEnvConfigBody(envSchemaGroups));
+ bindEnvTabs(page);
} catch (e) {
- grid.innerHTML = '' + (e.message || "加载失败") + "";
+ loading.innerHTML = '' + (e.message || "加载失败") + "";
}
}
function collectEnvValues() {
- const grid = envConfigGrid();
+ const root = envConfigRoot();
const values = {};
- const scope = grid || document;
+ const scope = root || document;
scope.querySelectorAll(".env-field-input[data-env-key]").forEach((el) => {
values[el.dataset.envKey] = el.value;
});
@@ -310,6 +377,7 @@
bindEvents();
loadDisplayPrefsForm(false);
loadEnvConfig(false);
+ bindEnvTabs();
if (global.__INSTANCE_DISPLAY__) applyDisplayToNav(global.__INSTANCE_DISPLAY__);
}
diff --git a/lib/common/static/instance_theme.css b/lib/common/static/instance_theme.css
index 510ba1d..10c6a84 100644
--- a/lib/common/static/instance_theme.css
+++ b/lib/common/static/instance_theme.css
@@ -2185,85 +2185,172 @@ html[data-theme="light"] .journal-detail-img-thumb {
display: none !important;
}
-/* ── env 配置页(三列卡片) ── */
+/* ── env 配置页(Tab + 双列表单) ── */
.env-config-page {
margin-top: 12px;
}
.env-config-head {
- padding: 12px 14px;
+ padding: 14px 16px;
margin-bottom: 12px;
}
+.env-config-head-row {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 12px 16px;
+}
+
+.env-config-head h2 {
+ margin: 0 0 4px;
+ font-size: 1rem;
+}
+
+.env-config-head-hint {
+ margin: 0;
+ font-size: 0.78rem;
+ max-width: 42rem;
+}
+
.env-config-toolbar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
- margin-top: 10px;
+ flex-shrink: 0;
}
-.env-config-grid {
+.env-config-body {
+ padding: 0;
+ overflow: hidden;
+}
+
+.env-config-tabs {
+ display: flex;
+ flex-wrap: nowrap;
+ gap: 0;
+ overflow-x: auto;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
+ padding: 0 8px;
+ scrollbar-width: thin;
+}
+
+.env-tab-btn {
+ flex: 0 0 auto;
+ border: none;
+ background: transparent;
+ color: var(--muted, #8892b0);
+ font-size: 0.8rem;
+ padding: 10px 14px;
+ cursor: pointer;
+ border-bottom: 2px solid transparent;
+ margin-bottom: -1px;
+ white-space: nowrap;
+ transition: color 0.15s, border-color 0.15s;
+}
+
+.env-tab-btn:hover {
+ color: #c5cae0;
+}
+
+.env-tab-btn.is-active {
+ color: #e8ecff;
+ border-bottom-color: var(--accent, #7c6cf0);
+ font-weight: 600;
+}
+
+.env-config-panels {
+ padding: 14px 16px 16px;
+}
+
+.env-panel-hint {
+ margin: 0 0 12px;
+ padding: 8px 10px;
+ font-size: 0.75rem;
+ border-radius: 6px;
+ background: rgba(251, 191, 36, 0.08);
+ color: #e8c468;
+ border: 1px solid rgba(251, 191, 36, 0.15);
+}
+
+.env-form-grid {
display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 12px;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 12px 20px;
align-items: start;
}
-.env-group-card {
- padding: 10px 12px;
+.env-field-row {
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
min-width: 0;
}
-.env-group-title {
- margin: 0 0 8px;
- font-size: 0.88rem;
- font-weight: 600;
-}
-
-.env-group-fields {
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
-
-.env-field-card {
- display: flex;
- flex-direction: column;
- gap: 4px;
+.env-field-row--restart .env-field-label {
+ color: #d4c4a0;
}
.env-field-label {
- font-size: 0.72rem;
+ font-size: 0.8rem;
font-weight: 600;
- color: #a8b0cc;
+ color: #c5cae0;
+ line-height: 1.3;
+}
+
+.env-restart-mark {
+ color: #fbbf24;
+ font-weight: 700;
+ margin-left: 2px;
}
.env-field-note {
- font-size: 0.68rem;
+ font-size: 0.72rem;
line-height: 1.35;
+ margin-top: -2px;
}
.env-field-input {
width: 100%;
- font-size: 0.78rem;
+ font-size: 0.82rem;
+ padding: 7px 10px;
+ border-radius: 6px;
+ box-sizing: border-box;
}
-.env-field-badge {
- font-size: 0.65rem;
- align-self: flex-start;
- padding: 1px 6px;
- border-radius: 4px;
+.env-config-loading-wrap {
+ padding: 24px;
+ text-align: center;
}
-.env-badge-hot {
- background: rgba(56, 178, 120, 0.15);
- color: #6ee7a8;
+/* 兼容旧结构 */
+.env-config-grid {
+ display: block;
}
+.env-group-card,
+.env-field-card,
+.env-field-badge,
+.env-badge-hot,
.env-badge-restart {
- background: rgba(251, 191, 36, 0.12);
- color: #fbbf24;
+ display: none;
+}
+
+@media (max-width: 900px) {
+ .env-form-grid {
+ grid-template-columns: minmax(0, 1fr);
+ }
+}
+
+@media (max-width: 720px) {
+ .env-config-head-row {
+ flex-direction: column;
+ }
+ .env-config-toolbar {
+ width: 100%;
+ }
}
.display-prefs-form {
@@ -2314,13 +2401,13 @@ html[data-theme="light"] .journal-detail-img-thumb {
}
@media (max-width: 1100px) {
- .env-config-grid {
+ .env-form-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 720px) {
- .env-config-grid {
+ .env-form-grid {
grid-template-columns: minmax(0, 1fr);
}
.settings-password-form {
diff --git a/lib/env/env_ui_manifest.py b/lib/env/env_ui_manifest.py
index a30e6eb..fcd4819 100644
--- a/lib/env/env_ui_manifest.py
+++ b/lib/env/env_ui_manifest.py
@@ -207,7 +207,11 @@ def build_env_ui_payload(
_build_field(key, label, note, schema, values)
for key, label, note in sec["fields"]
]
- groups.append({"title": sec["title"], "fields": fields})
+ groups.append({
+ "title": sec["title"],
+ "fields": fields,
+ "has_restart": any(f.get("restart_required") for f in fields),
+ })
return groups
diff --git a/lib/instance/templates/embed_shell.html b/lib/instance/templates/embed_shell.html
index 2122892..cab9322 100644
--- a/lib/instance/templates/embed_shell.html
+++ b/lib/instance/templates/embed_shell.html
@@ -7,7 +7,7 @@
-
+
{{ exchange_display }} · 加密货币 | 交易监控复盘系统
@@ -103,7 +103,7 @@ const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | toj
-
+