From 2b66374e90ada5e53d5948cd8d9ae321b11b8fe2 Mon Sep 17 00:00:00 2001 From: bia Date: Mon, 8 Jun 2026 18:53:48 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=9A=E5=87=BA=E5=8F=A3=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E9=87=8D=E8=AE=BE=E8=AE=A1=20+=20=E5=85=A8=E5=B1=80=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 选择/随机/战斗节点改为每出口一行、右对齐、行高25px对齐右侧端口 - 去掉重复的选项合并预览,顶部改为紧凑角标 - 未捕获错误/操作失败统一 toast 提示,便于同事发现反馈 --- web/static/app.js | 7 +++++++ web/static/graph.js | 32 +++++++++++++++++++------------- web/static/style.css | 15 +++++++++++++++ 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/web/static/app.js b/web/static/app.js index 036d901..772d02b 100644 --- a/web/static/app.js +++ b/web/static/app.js @@ -281,6 +281,13 @@ clearTimeout(toastTimer); toastTimer = setTimeout(() => el.classList.remove("show"), 2600); } + // 未捕获错误 → toast 提示(便于同事发现并反馈,而不是默默白屏) + window.addEventListener("error", e => { try { toast("⚠ 页面出错:" + (e.message || "未知")); } catch (_) {} }); + window.addEventListener("unhandledrejection", e => { + const m = String((e.reason && e.reason.message) || e.reason || "未知"); + if (m.includes("未授权")) return; + try { toast("⚠ 操作失败:" + m); } catch (_) {} + }); // ---------- 撤销 / 重做(Ctrl+Z / Ctrl+Y) ---------- let undoStack = [], redoStack = [], snapTimer = null; diff --git a/web/static/graph.js b/web/static/graph.js index 03f4bb9..c0d3014 100644 --- a/web/static/graph.js +++ b/web/static/graph.js @@ -41,11 +41,11 @@ function getOutlets(node) { const k = node.kind; if (k === "choice" || k === "choice_once") - return (node.options || []).map((o, i) => ({ label: "选" + (i + 1) + (o.text ? "·" + o.text.slice(0, 6) : ""), target: o.goto || "" })); + return (node.options || []).map((o, i) => ({ label: (i + 1) + ". " + (o.text ? o.text.slice(0, 14) : "(空选项)"), target: o.goto || "" })); if (k === "random") - return (node.branches || []).map((b) => ({ label: "权" + (b.weight != null ? b.weight : ""), target: b.goto || "" })); + return (node.branches || []).map((b, i) => ({ label: (i + 1) + ". 权重 " + (b.weight != null ? b.weight : "?"), target: b.goto || "" })); if (k === "fight") - return [{ label: "胜", target: node.win || "" }, { label: "败", target: node.lose || "" }]; + return [{ label: "① 胜", target: node.win || "" }, { label: "② 败", target: node.lose || "" }]; if (isEnding(node)) return []; return [{ label: "next", target: node.next || "" }]; // 线性 } @@ -63,19 +63,25 @@ // ---------- 节点 HTML ---------- function nodeInner(ir, node, isStart) { const names = roleNames(ir), end = isEnding(node); - const sm = summary(ir, names, end ? Object.assign({ kind: "ending" }, node) : node); - let h = '
' + (isStart ? '▶ 开头 ' : '') + '#' + esc(node.id) + '
' - + '
' + esc(sm[0]) + '
' - + '
' + esc(sm[1] || "") + '
'; + const startTag = isStart ? '▶ 开头 ' : ''; if (end) { + const sm = summary(ir, names, Object.assign({ kind: "ending" }, node)); const g = (node.grants && node.grants.length) ? node.grants.map(gr => grantStr(ir, names, gr)).join(",") : "无奖励"; - h += '
' + esc(g) + '
'; - } else { - const outs = getOutlets(node); - if (outs.length > 1) - h += '
' + outs.map((o, i) => '' + (i + 1) + "·" + esc(o.label) + '').join("") + '
'; + return '
' + startTag + '#' + esc(node.id) + '
' + + '
' + esc(sm[0]) + '
' + esc(sm[1] || "") + '
' + + '
' + esc(g) + '
'; } - return h; + const outs = getOutlets(node); + // 多出口节点(选择/随机/战斗):顶部角标 + 每出口一行(右对齐,行高匹配端口间距 → 与黄点平齐) + if (outs.length > 1) { + const head = summary(ir, names, node)[0]; + return '
' + (isStart ? ' ' : '') + '#' + esc(node.id) + ' ' + esc(head) + '
' + + '
' + outs.map(o => '
' + esc(o.label) + '
').join("") + '
'; + } + // 线性 / 单出口节点 + const sm = summary(ir, names, node); + return '
' + startTag + '#' + esc(node.id) + '
' + + '
' + esc(sm[0]) + '
' + esc(sm[1] || "") + '
'; } // ---------- 自动布局:最长路径分层 + 每层顺序铺开 ---------- diff --git a/web/static/style.css b/web/static/style.css index acd9b0e..cbbf08a 100644 --- a/web/static/style.css +++ b/web/static/style.css @@ -117,6 +117,21 @@ button.mini { padding:2px 8px; font-size:12px; } #drawflow .drawflow-node.isstart { border-color:#7ad88a; } .drawflow-node .startflag { color:#7ad88a; font-weight:bold; } +/* 多出口节点:每出口一行,与右侧端口(间距25px、垂直居中)平齐 */ +#drawflow .kind-choice .drawflow_content_node, +#drawflow .kind-choice_once .drawflow_content_node, +#drawflow .kind-random .drawflow_content_node, +#drawflow .kind-fight .drawflow_content_node { position:relative; } +.ch-tag { position:absolute; top:-14px; left:0; right:0; font-size:11px; font-weight:bold; + white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } +.ch-tag .nid { color:#6a6256; font-weight:normal; } +#drawflow .kind-choice .ch-tag, #drawflow .kind-choice_once .ch-tag { color:#9ec0f0; } +#drawflow .kind-fight .ch-tag { color:#d87878; } +#drawflow .kind-random .ch-tag { color:#c0a0e0; } +.ch-opts { margin-top:2px; } /* 微调对齐端口 top:2px */ +.ch-opt { height:25px; line-height:25px; text-align:right; font-size:12px; color:#dfe7f2; + overflow:hidden; text-overflow:ellipsis; white-space:nowrap; padding-right:3px; } + /* ---- toast ---- */ #toast { position:fixed; left:50%; bottom:38px; transform:translateX(-50%) translateY(10px); background:#2a2316; color:#f3dca0; border:1px solid #6a5630; border-radius:7px;