Redesign env config UI: tabs, 2-col form, remove per-field badges

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-08 21:10:54 +08:00
parent 0d2ce9c126
commit 576f6fbcfc
6 changed files with 312 additions and 136 deletions
+110 -42
View File
@@ -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 = '<div class="env-config-loading muted">加载配置中…</div>';
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 = '<div class="env-config-loading muted">加载配置中…</div>';
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 = '<span class="err">' + (e.message || "加载失败") + "</span>";
loading.innerHTML = '<span class="err">' + (e.message || "加载失败") + "</span>";
}
}
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__);
}