fix
build-and-deploy / build-and-deploy (push) Successful in 31s

This commit is contained in:
Cheney
2026-08-07 14:46:52 +08:00
parent e3e5d5bd8f
commit f9f3184f98
9 changed files with 572 additions and 70 deletions
+94 -4
View File
@@ -269,10 +269,11 @@ body {
background: #f8fbff;
}
.col-key { width: 22%; }
.col-value { width: 26%; }
.col-type { width: 10%; }
.col-desc { width: 30%; }
.col-key { width: 18%; }
.col-value { width: 24%; }
.col-type { width: 8%; }
.col-status { width: 12%; }
.col-desc { width: 26%; }
.col-actions { width: 12%; }
.config-key-cell {
@@ -294,6 +295,59 @@ body {
color: #0078d4;
}
/* 状态徽标:必填 / 存在 / 不存在 */
.config-status-cell {
white-space: nowrap;
}
.status-badge {
display: inline-block;
padding: 1px 8px;
border-radius: 10px;
font-size: 11px;
margin-right: 4px;
border: 1px solid transparent;
}
.badge-required {
background: #fff3e0;
color: #b45309;
border-color: #f0c98f;
}
.badge-exists {
background: #e8f5e9;
color: #2e7d32;
border-color: #b5dfb8;
}
.badge-missing {
background: #fdecea;
color: #c62828;
border-color: #f2b8b5;
}
/* 不存在(未生效)的配置行 */
.row-not-exists td {
color: #999;
background: #fafafa;
}
.row-not-exists .config-key-cell {
color: #999;
text-decoration: line-through;
}
.value-missing {
color: #bbb;
}
/* 默认值回退展示(配置值为空时) */
.value-default {
color: #8a8a8a;
font-style: italic;
}
.empty-row td {
text-align: center;
color: #999;
@@ -353,6 +407,42 @@ body {
overflow: hidden;
}
/* 右侧抽屉:配置项编辑/查看使用,占屏幕至少 60% */
.drawer-overlay {
justify-content: flex-end;
align-items: stretch;
padding: 0;
}
.drawer-overlay .modal {
width: 68vw;
max-width: none;
height: 100vh;
max-height: 100vh;
border-radius: 0;
display: flex;
flex-direction: column;
box-shadow: -4px 0 24px rgba(0, 0, 0, 0.25);
animation: drawer-in 0.22s ease-out;
}
.drawer-overlay .modal-body {
flex: 1;
max-height: none;
overflow-y: auto;
}
/* 抽屉内表单内容限宽,避免超宽屏下字段过度拉伸 */
.drawer-overlay .modal-body .form-group,
.drawer-overlay .modal-body .form-row {
max-width: 780px;
}
@keyframes drawer-in {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
.modal-sm {
width: 360px;
}
+32 -4
View File
@@ -98,13 +98,14 @@
<th class="col-key">键名</th>
<th class="col-value">值</th>
<th class="col-type">类型</th>
<th class="col-status">状态</th>
<th class="col-desc">说明</th>
<th class="col-actions">操作</th>
</tr>
</thead>
<tbody id="config-tbody">
<tr class="empty-row">
<td colspan="5">请在左侧选择节点查看配置</td>
<td colspan="6">请在左侧选择节点查看配置</td>
</tr>
</tbody>
</table>
@@ -204,8 +205,8 @@
</div>
</div>
<!-- 模态框:新增/编辑配置项 -->
<div class="modal-overlay" id="modal-config" style="display:none">
<!-- 抽屉:新增/编辑/查看配置项(右侧滑出,占屏幕 68%) -->
<div class="modal-overlay drawer-overlay" id="modal-config" style="display:none">
<div class="modal">
<div class="modal-header">
<h3 id="modal-config-title">新增配置项</h3>
@@ -236,6 +237,10 @@
<label>值</label>
<input type="text" id="config-value" placeholder="配置值">
</div>
<div class="form-group">
<label>默认值 <span class="hint">(规划模式,编辑模式下值为空时展示回退)</span></label>
<input type="text" id="config-default-value" placeholder="配置值为空时展示/回退的默认值">
</div>
<div class="form-row">
<div class="form-group form-half">
<label>类型</label>
@@ -248,7 +253,30 @@
</div>
<div class="form-group form-half">
<label>值范围 <span class="hint">(规划模式)</span></label>
<input type="text" id="config-value-range" placeholder="例如:1-100 或 枚举: A,B">
<input type="text" id="config-value-range" placeholder="例如:1-100、枚举: A,B 或 /正则/">
</div>
</div>
<div class="form-group" id="config-range-preset-group" style="display:none">
<label>常见格式 <span class="hint">(string 类型:值范围按正则校验,选择后自动填入)</span></label>
<select id="config-range-preset">
<option value="">— 选择常见格式 —</option>
<option value="phone">📱 手机号(中国大陆 11 位)</option>
<option value="email">✉️ 邮箱</option>
<option value="idcard">🪪 身份证(18 位)</option>
<option value="ipv4">🌐 IPv4 地址</option>
<option value="url">🔗 URL 链接</option>
<option value="port">🔌 端口号(1-65535)</option>
<option value="alphanumeric">🔤 字母数字</option>
</select>
</div>
<div class="form-row">
<div class="form-group form-half">
<label>是否必须 <span class="hint">(规划模式)</span></label>
<label class="checkbox-label"><input type="checkbox" id="config-required"> 编辑模式下必须填写值</label>
</div>
<div class="form-group form-half">
<label>是否存在 <span class="hint">(编辑模式)</span></label>
<label class="checkbox-label"><input type="checkbox" id="config-exists" checked> 该配置项当前存在/生效</label>
</div>
</div>
<div class="form-group">
+104 -7
View File
@@ -66,6 +66,20 @@ const MODE_NAMES = { plan: "规划", edit: "编辑", readonly: "只读" };
*/
const MAPPING_NAMES = { path: "路径", label: "标签" };
/**
* string 类型值范围的常见格式正则预设(前端选择后填入值范围输入框,后端按 /正则/ 校验)
* regex 为去掉首尾斜杠与修饰符的正则体
*/
const RANGE_PRESETS = {
phone: { label: "手机号", regex: "^1[3-9]\\d{9}$" },
email: { label: "邮箱", regex: "^[\\w.%+-]+@[\\w.-]+\\.[A-Za-z]{2,}$" },
idcard: { label: "身份证", regex: "^\\d{17}[\\dXx]$" },
ipv4: { label: "IPv4", regex: "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}$" },
url: { label: "URL", regex: "^https?:\\/\\/[\\w.-]+(?:\\.[a-zA-Z]{2,})?(?:\\/[^\\s]*)?$" },
port: { label: "端口号", regex: "^([1-9]\\d{0,3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])$" },
alphanumeric: { label: "字母数字", regex: "^[A-Za-z0-9]+$" },
};
/**
* 应用当前模式到界面(更新样式类、按钮状态、状态栏)
* @returns {void}
@@ -197,10 +211,16 @@ function renderDimensionBar() {
return;
}
box.style.display = "";
// 填充筛选下拉
// 填充筛选下拉(用 DOM 构建避免属性上下文注入)
const sel = document.getElementById(it.sel);
const values = collectDimValues(it.field);
sel.innerHTML = '<option value="">全部</option>' + values.map((v) => `<option value="${escapeHtml(v)}">${escapeHtml(v)}</option>`).join("");
sel.innerHTML = '<option value="">全部</option>';
values.forEach((v) => {
const opt = document.createElement("option");
opt.value = v;
opt.textContent = v;
sel.appendChild(opt);
});
sel.value = state.dimFilter[it.key] || "";
// 展示映射方式
document.getElementById(it.map).textContent = `[${MAPPING_NAMES[p[it.mapping]] || "路径"}]`;
@@ -231,16 +251,28 @@ function renderConfigTable(configs, path) {
tbody.innerHTML = "";
if (!configs || configs.length === 0) {
tbody.innerHTML = '<tr class="empty-row"><td colspan="5">当前节点暂无配置项</td></tr>';
tbody.innerHTML = '<tr class="empty-row"><td colspan="6">当前节点暂无配置项</td></tr>';
return;
}
configs.forEach((cfg) => {
const exists = cfg.exists !== false;
const tr = document.createElement("tr");
if (!exists) tr.classList.add("row-not-exists");
const statusHtml = [];
if (cfg.required === true) statusHtml.push('<span class="status-badge badge-required">必填</span>');
statusHtml.push(
exists
? '<span class="status-badge badge-exists">存在</span>'
: '<span class="status-badge badge-missing">不存在</span>'
);
tr.innerHTML = `
<td class="config-key-cell">${escapeHtml(cfg.key)}</td>
<td class="config-value-cell">${escapeHtml(String(cfg.value))}</td>
<td class="config-value-cell">${buildValueCell(cfg, exists)}</td>
<td><span class="type-badge">${escapeHtml(cfg.type)}</span></td>
<td class="config-status-cell">${statusHtml.join("")}</td>
<td>${escapeHtml(cfg.description)}</td>
<td>
<button class="row-btn view" data-action="view" data-id="${cfg.id}">查看</button>
@@ -252,6 +284,24 @@ function renderConfigTable(configs, path) {
});
}
/**
* 构建表格值列 HTML:存在且值非空显示实际值;值为空但配置了默认值时回退展示默认值;不存在显示 —
* @param {Object} cfg - 配置项
* @param {boolean} exists - 是否存在
* @returns {string} 值列 HTML
*/
function buildValueCell(cfg, exists) {
if (!exists) return '<span class="value-missing">—</span>';
const value = String(cfg.value);
if (value.trim() !== "") return escapeHtml(value);
const hasDefault =
cfg.defaultValue !== undefined && cfg.defaultValue !== null && String(cfg.defaultValue).trim() !== "";
if (hasDefault) {
return `<span class="value-default" title="配置值为空,回退展示默认值">默认 ${escapeHtml(String(cfg.defaultValue))}</span>`;
}
return "";
}
/**
* 根据当前项目维度开关,控制配置模态框中维度字段的显示
* @returns {void}
@@ -287,16 +337,36 @@ function openConfigModal(config, readonly) {
document.getElementById("config-environment").value = preEnv;
document.getElementById("config-group").value = preGroup;
document.getElementById("config-value").value = config ? config.value : "";
document.getElementById("config-default-value").value =
config && config.defaultValue !== undefined && config.defaultValue !== null ? config.defaultValue : "";
// 编辑模式下提示默认值(留空则回退展示默认值)
document.getElementById("config-value").placeholder =
config && config.defaultValue !== undefined && config.defaultValue !== null && String(config.defaultValue).trim() !== ""
? `留空则使用默认值:${config.defaultValue}`
: "配置值";
document.getElementById("config-type").value = config ? config.type : "string";
document.getElementById("config-value-range").value = config ? config.valueRange || "" : "";
document.getElementById("config-description").value = config ? config.description : "";
document.getElementById("config-purpose").value = config ? config.purpose || "" : "";
document.getElementById("config-required").checked = config ? config.required === true : false;
document.getElementById("config-exists").checked = config ? config.exists !== false : true;
document.getElementById("config-range-preset").value = "";
updateDimensionFields();
updateRangePresetVisibility();
applyFieldPermissions(readonly);
openModal("modal-config");
}
/**
* 根据配置类型显示/隐藏“常见格式”正则预设下拉(仅 string 类型提供)
* @returns {void}
*/
function updateRangePresetVisibility() {
const isString = document.getElementById("config-type").value === "string";
document.getElementById("config-range-preset-group").style.display = isString ? "" : "none";
}
/**
* 根据模式和是否只读查看,设置配置模态框各字段的可编辑性
* 规划模式:结构字段(类型/说明/用途/范围/键名/维度)可改,值不可改
@@ -307,16 +377,21 @@ function openConfigModal(config, readonly) {
*/
function applyFieldPermissions(readonly) {
const structFields = [
"config-key", "config-type", "config-value-range",
"config-key", "config-type", "config-value-range", "config-required",
"config-default-value",
"config-description", "config-purpose",
"config-idc", "config-environment", "config-group",
];
const valueField = "config-value";
const existsField = "config-exists";
const presetField = "config-range-preset";
const saveBtn = document.getElementById("btn-save-config");
const disableAll = (disabled) => {
structFields.forEach((id) => (document.getElementById(id).disabled = disabled));
document.getElementById(valueField).disabled = disabled;
document.getElementById(existsField).disabled = disabled;
document.getElementById(presetField).disabled = disabled;
};
if (readonly || state.mode === "readonly") {
@@ -328,14 +403,18 @@ function applyFieldPermissions(readonly) {
saveBtn.style.display = "";
if (state.mode === "plan") {
// 规划模式:可改结构,值不可改
// 规划模式:可改结构(含是否必须),值/存在性不可改
structFields.forEach((id) => (document.getElementById(id).disabled = false));
document.getElementById(valueField).disabled = true;
document.getElementById(existsField).disabled = true;
document.getElementById(presetField).disabled = false;
} else if (state.mode === "edit") {
// 编辑模式:新增时结构可填;编辑已有项时仅值可改(键名/维度也允许调整路径)
// 编辑模式:新增时结构可填;编辑已有项时仅值/存在性可改(键名/维度也允许调整路径)
const isNew = !state.editingConfigId;
document.getElementById("config-type").disabled = !isNew;
document.getElementById("config-value-range").disabled = !isNew;
document.getElementById("config-required").disabled = !isNew;
document.getElementById("config-default-value").disabled = !isNew;
document.getElementById("config-description").disabled = !isNew;
document.getElementById("config-purpose").disabled = !isNew;
document.getElementById("config-key").disabled = false;
@@ -343,6 +422,8 @@ function applyFieldPermissions(readonly) {
document.getElementById("config-environment").disabled = false;
document.getElementById("config-group").disabled = false;
document.getElementById(valueField).disabled = false;
document.getElementById(existsField).disabled = false;
document.getElementById(presetField).disabled = !isNew;
}
}
@@ -359,6 +440,9 @@ function collectConfigForm() {
value: document.getElementById("config-value").value,
type: document.getElementById("config-type").value,
valueRange: document.getElementById("config-value-range").value.trim(),
defaultValue: document.getElementById("config-default-value").value,
required: document.getElementById("config-required").checked,
exists: document.getElementById("config-exists").checked,
description: document.getElementById("config-description").value.trim(),
purpose: document.getElementById("config-purpose").value.trim(),
};
@@ -632,6 +716,19 @@ function bindEvents() {
});
document.getElementById("btn-save-config").addEventListener("click", saveConfig);
// 类型切换:仅 string 类型展示“常见格式”预设
document.getElementById("config-type").addEventListener("change", updateRangePresetVisibility);
// 常见格式预设:选择后自动填入值范围(/正则/ 格式)
document.getElementById("config-range-preset").addEventListener("change", (e) => {
const key = e.target.value;
if (!key) return;
const preset = RANGE_PRESETS[key];
if (!preset) return;
document.getElementById("config-value-range").value = `/${preset.regex}/`;
toast(`已填入${preset.label}格式正则,可继续编辑`, "info");
});
// 维度筛选下拉
document.getElementById("dim-idc-select").addEventListener("change", (e) => {
state.dimFilter.idc = e.target.value;