From 81f7a7c4511cb0da51b0f7435480ab179d81b8e2 Mon Sep 17 00:00:00 2001 From: dekun Date: Mon, 6 Jul 2026 19:08:11 +0800 Subject: [PATCH] fix: entry model sub-select linkage via DOM options and event delegation Co-authored-by: Cursor --- lib/common/static/order_entry_model.js | 360 +++++++++++------- lib/instance/templates/embed_shell.html | 2 +- lib/instance/templates/index.html | 2 +- .../templates/order_entry_model_fields.html | 51 +-- 4 files changed, 244 insertions(+), 171 deletions(-) diff --git a/lib/common/static/order_entry_model.js b/lib/common/static/order_entry_model.js index 6bfe2d3..e2ca41d 100644 --- a/lib/common/static/order_entry_model.js +++ b/lib/common/static/order_entry_model.js @@ -1,146 +1,214 @@ -(function (global) { - function categoriesData() { - return global.ORDER_ENTRY_MODEL_CATEGORIES || []; - } - - function codeToCategoryMap() { - return global.ORDER_ENTRY_MODEL_CODE_TO_CATEGORY || {}; - } - - function tradeStyleForCode(code) { - var map = global.ORDER_ENTRY_MODEL_TRADE_STYLE || {}; - return map[code] || "trend"; - } - - function findOption(catKey, code) { - var cats = categoriesData(); - for (var i = 0; i < cats.length; i++) { - if (cats[i].key !== catKey) continue; - var opts = cats[i].options || []; - for (var j = 0; j < opts.length; j++) { - if (opts[j].code === code) return opts[j]; - } - } - return null; - } - - function rebuildEntryModelSubSelect(preserveCode) { - var catSel = document.getElementById("order-entry-category"); - var modelSel = document.getElementById("order-entry-model"); - if (!catSel || !modelSel) return; - - var catKey = catSel.value; - var cats = categoriesData(); - var cat = null; - for (var i = 0; i < cats.length; i++) { - if (cats[i].key === catKey) { - cat = cats[i]; - break; - } - } - - modelSel.innerHTML = ""; - var placeholder = document.createElement("option"); - placeholder.value = ""; - placeholder.textContent = "类型"; - modelSel.appendChild(placeholder); - - if (!cat || !cat.options || !cat.options.length) { - modelSel.disabled = true; - modelSel.value = ""; - syncOrderEntryModelTradeStyle(); - return; - } - - modelSel.disabled = false; - var pick = preserveCode || ""; - for (var k = 0; k < cat.options.length; k++) { - var o = cat.options[k]; - var opt = document.createElement("option"); - opt.value = o.code; - opt.textContent = o.label; - if (o.trade_style) opt.setAttribute("data-trade-style", o.trade_style); - if (o.help) opt.title = o.help; - modelSel.appendChild(opt); - } - - if (pick && findOption(catKey, pick)) { - modelSel.value = pick; - } else if (cat.options.length === 1) { - modelSel.value = cat.options[0].code; - } else { - modelSel.value = ""; - } - syncOrderEntryModelTradeStyle(); - } - - function syncOrderEntryModelTradeStyle() { - var modelSel = document.getElementById("order-entry-model"); - var hidden = document.getElementById("order-trade-style-hidden"); - var hint = document.getElementById("order-trade-style-hint"); - if (!modelSel || !hidden) return; - var labels = { trend: "趋势单", swing: "波段单" }; - var code = modelSel.value || ""; - var ts = tradeStyleForCode(code); - hidden.value = ts; - if (hint) hint.textContent = labels[ts] || ts; - } - - function initOrderEntryModelSelect(root) { - var scope = root && root.querySelector ? root : document; - var catSel = - scope === document - ? document.getElementById("order-entry-category") - : scope.querySelector("#order-entry-category"); - var modelSel = - scope === document - ? document.getElementById("order-entry-model") - : scope.querySelector("#order-entry-model"); - if (!catSel || !modelSel) return; - - if (catSel.dataset.entryModelWired !== "1") { - catSel.dataset.entryModelWired = "1"; - catSel.addEventListener("change", function () { - rebuildEntryModelSubSelect(""); - }); - } - if (modelSel.dataset.entryModelWired !== "1") { - modelSel.dataset.entryModelWired = "1"; - modelSel.addEventListener("change", syncOrderEntryModelTradeStyle); - } - - var presetCode = modelSel.getAttribute("data-preset-code") || modelSel.value || ""; - if (presetCode) { - var catMap = codeToCategoryMap(); - var catKey = catMap[presetCode]; - if (catKey) { - catSel.value = catKey; - rebuildEntryModelSubSelect(presetCode); - return; - } - } - rebuildEntryModelSubSelect(""); - } - - global.paintOrderLeverageHint = function (leverage) { - var hidden = document.getElementById("order-leverage"); - var hint = document.getElementById("order-leverage-hint"); - if (!hidden && !hint) return; - var lev = parseInt(leverage, 10); - if (!Number.isFinite(lev) || lev <= 0) { - if (hint) hint.textContent = "杠杆 —"; - if (hidden) hidden.value = ""; - return; - } - if (hidden) hidden.value = String(lev); - if (hint) hint.textContent = "杠杆 " + lev + "x"; - }; - - global.initOrderEntryModelSelect = initOrderEntryModelSelect; - global.syncOrderEntryModelTradeStyle = syncOrderEntryModelTradeStyle; - global.rebuildEntryModelSubSelect = rebuildEntryModelSubSelect; - - document.addEventListener("DOMContentLoaded", function () { - initOrderEntryModelSelect(); - }); -})(typeof window !== "undefined" ? window : globalThis); +(function (global) { + var delegated = false; + + function queryInScope(scope, id) { + if (scope && scope.querySelector) return scope.querySelector("#" + id); + return document.getElementById(id); + } + + function categoriesData() { + return global.ORDER_ENTRY_MODEL_CATEGORIES || []; + } + + function codeToCategoryMap() { + return global.ORDER_ENTRY_MODEL_CODE_TO_CATEGORY || {}; + } + + function tradeStyleForCode(code, modelSel) { + if (modelSel && code) { + var opt = modelSel.querySelector('option[value="' + code.replace(/"/g, '\\"') + '"]'); + if (opt) { + var ds = opt.getAttribute("data-trade-style"); + if (ds === "swing" || ds === "trend") return ds; + } + } + var map = global.ORDER_ENTRY_MODEL_TRADE_STYLE || {}; + return map[code] || "trend"; + } + + function findOption(catKey, code) { + var cats = categoriesData(); + for (var i = 0; i < cats.length; i++) { + if (cats[i].key !== catKey) continue; + var opts = cats[i].options || []; + for (var j = 0; j < opts.length; j++) { + if (opts[j].code === code) return opts[j]; + } + } + return null; + } + + function filterDomSubOptions(modelSel, catKey, preserveCode) { + var tagged = modelSel.querySelectorAll("option[data-entry-category]"); + if (!tagged.length) return false; + + var any = false; + for (var i = 0; i < tagged.length; i++) { + var opt = tagged[i]; + var show = !!catKey && opt.getAttribute("data-entry-category") === catKey; + opt.hidden = !show; + opt.disabled = !show; + if (show) any = true; + } + + modelSel.disabled = !any; + if (!any) { + modelSel.value = ""; + return true; + } + + var pick = preserveCode || ""; + if (pick) { + var picked = modelSel.querySelector('option[value="' + pick.replace(/"/g, '\\"') + '"]:not([disabled])'); + if (picked) { + modelSel.value = pick; + return true; + } + } + + var visible = []; + for (var k = 0; k < tagged.length; k++) { + if (!tagged[k].disabled) visible.push(tagged[k]); + } + if (visible.length === 1) modelSel.value = visible[0].value; + else modelSel.value = ""; + return true; + } + + function rebuildFromCategories(modelSel, catKey, preserveCode) { + var cats = categoriesData(); + var cat = null; + for (var i = 0; i < cats.length; i++) { + if (cats[i].key === catKey) { + cat = cats[i]; + break; + } + } + + modelSel.innerHTML = ""; + var placeholder = document.createElement("option"); + placeholder.value = ""; + placeholder.textContent = "类型"; + modelSel.appendChild(placeholder); + + if (!cat || !cat.options || !cat.options.length) { + modelSel.disabled = true; + modelSel.value = ""; + return; + } + + modelSel.disabled = false; + var pick = preserveCode || ""; + for (var k = 0; k < cat.options.length; k++) { + var o = cat.options[k]; + var opt = document.createElement("option"); + opt.value = o.code; + opt.textContent = o.label; + if (o.trade_style) opt.setAttribute("data-trade-style", o.trade_style); + if (o.help) opt.title = o.help; + modelSel.appendChild(opt); + } + + if (pick && findOption(catKey, pick)) { + modelSel.value = pick; + } else if (cat.options.length === 1) { + modelSel.value = cat.options[0].code; + } else { + modelSel.value = ""; + } + } + + function rebuildEntryModelSubSelect(preserveCode, scope) { + var root = scope && scope.querySelector ? scope : document; + var catSel = queryInScope(root, "order-entry-category"); + var modelSel = queryInScope(root, "order-entry-model"); + if (!catSel || !modelSel) return; + + var catKey = catSel.value; + if (filterDomSubOptions(modelSel, catKey, preserveCode)) { + syncOrderEntryModelTradeStyle(modelSel); + return; + } + rebuildFromCategories(modelSel, catKey, preserveCode); + syncOrderEntryModelTradeStyle(modelSel); + } + + function syncOrderEntryModelTradeStyle(modelSel) { + if (!modelSel) modelSel = document.getElementById("order-entry-model"); + var hidden = document.getElementById("order-trade-style-hidden"); + var hint = document.getElementById("order-trade-style-hint"); + if (!modelSel || !hidden) return; + var labels = { trend: "趋势单", swing: "波段单" }; + var code = modelSel.value || ""; + var ts = tradeStyleForCode(code, modelSel); + hidden.value = ts; + if (hint) hint.textContent = labels[ts] || ts; + } + + function wireDelegation() { + if (delegated) return; + delegated = true; + document.addEventListener( + "change", + function (ev) { + var t = ev.target; + if (!t || !t.id) return; + if (t.id === "order-entry-category") { + rebuildEntryModelSubSelect(""); + return; + } + if (t.id === "order-entry-model") { + syncOrderEntryModelTradeStyle(t); + } + }, + false + ); + } + + function initOrderEntryModelSelect(root) { + wireDelegation(); + var scope = root && root.querySelector ? root : document; + var catSel = queryInScope(scope, "order-entry-category"); + var modelSel = queryInScope(scope, "order-entry-model"); + if (!catSel || !modelSel) return; + + var presetCode = modelSel.getAttribute("data-preset-code") || modelSel.value || ""; + if (presetCode) { + var catMap = codeToCategoryMap(); + var catKey = catMap[presetCode]; + if (catKey) { + catSel.value = catKey; + rebuildEntryModelSubSelect(presetCode, scope); + return; + } + } + rebuildEntryModelSubSelect("", scope); + } + + global.paintOrderLeverageHint = function (leverage) { + var hidden = document.getElementById("order-leverage"); + var hint = document.getElementById("order-leverage-hint"); + if (!hidden && !hint) return; + var lev = parseInt(leverage, 10); + if (!Number.isFinite(lev) || lev <= 0) { + if (hint) hint.textContent = "杠杆 —"; + if (hidden) hidden.value = ""; + return; + } + if (hidden) hidden.value = String(lev); + if (hint) hint.textContent = "杠杆 " + lev + "x"; + }; + + global.initOrderEntryModelSelect = initOrderEntryModelSelect; + global.syncOrderEntryModelTradeStyle = syncOrderEntryModelTradeStyle; + global.rebuildEntryModelSubSelect = rebuildEntryModelSubSelect; + + wireDelegation(); + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", function () { + initOrderEntryModelSelect(); + }); + } else { + initOrderEntryModelSelect(); + } +})(typeof window !== "undefined" ? window : globalThis); diff --git a/lib/instance/templates/embed_shell.html b/lib/instance/templates/embed_shell.html index 249916d..633ca4a 100644 --- a/lib/instance/templates/embed_shell.html +++ b/lib/instance/templates/embed_shell.html @@ -127,7 +127,7 @@ const ORDER_ENTRY_MODEL_TRADE_STYLE = {{ entry_model_trade_style_map | tojson }} const ORDER_ENTRY_MODEL_CATEGORIES = {{ entry_model_categories | tojson }}; const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | tojson }}; - + diff --git a/lib/instance/templates/index.html b/lib/instance/templates/index.html index 265f997..6a0177d 100644 --- a/lib/instance/templates/index.html +++ b/lib/instance/templates/index.html @@ -559,7 +559,7 @@ const ORDER_ENTRY_MODEL_TRADE_STYLE = {{ entry_model_trade_style_map | tojson }} const ORDER_ENTRY_MODEL_CATEGORIES = {{ entry_model_categories | tojson }}; const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | tojson }}; - + diff --git a/lib/instance/templates/order_entry_model_fields.html b/lib/instance/templates/order_entry_model_fields.html index 6a01eb1..b1608d9 100644 --- a/lib/instance/templates/order_entry_model_fields.html +++ b/lib/instance/templates/order_entry_model_fields.html @@ -1,23 +1,28 @@ -{# 趋势户:两级开仓类型 → 自动 trade_style;日内户:仍选手动 trend/swing #} -{% macro order_entry_type_fields() -%} -{% if order_entry_profile == 'trend_div' %} -
- - - - 趋势单 -
-{% else %} - -{% endif %} -{%- endmacro %} +{# 趋势户:两级开仓类型 → 自动 trade_style;日内户:仍选手动 trend/swing #} +{% macro order_entry_type_fields() -%} +{% if order_entry_profile == 'trend_div' %} +
+ + + + 趋势单 +
+{% else %} + +{% endif %} +{%- endmacro %}