fix: entry model sub-select linkage via DOM options and event delegation

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-06 19:08:11 +08:00
parent ff1bcd69d2
commit 81f7a7c451
4 changed files with 244 additions and 171 deletions
+214 -146
View File
@@ -1,146 +1,214 @@
(function (global) { (function (global) {
function categoriesData() {
return global.ORDER_ENTRY_MODEL_CATEGORIES || []; var delegated = false;
}
function codeToCategoryMap() {
return global.ORDER_ENTRY_MODEL_CODE_TO_CATEGORY || {}; function queryInScope(scope, id) {
}
if (scope && scope.querySelector) return scope.querySelector("#" + id);
function tradeStyleForCode(code) {
var map = global.ORDER_ENTRY_MODEL_TRADE_STYLE || {}; return document.getElementById(id);
return map[code] || "trend";
} }
function findOption(catKey, code) {
var cats = categoriesData();
for (var i = 0; i < cats.length; i++) { function categoriesData() {
if (cats[i].key !== catKey) continue;
var opts = cats[i].options || []; return global.ORDER_ENTRY_MODEL_CATEGORIES || [];
for (var j = 0; j < opts.length; j++) {
if (opts[j].code === code) return opts[j]; }
}
}
return null;
} function codeToCategoryMap() {
function rebuildEntryModelSubSelect(preserveCode) { return global.ORDER_ENTRY_MODEL_CODE_TO_CATEGORY || {};
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(); function tradeStyleForCode(code, modelSel) {
var cat = null;
for (var i = 0; i < cats.length; i++) { if (modelSel && code) {
if (cats[i].key === catKey) {
cat = cats[i]; var opt = modelSel.querySelector('option[value="' + code.replace(/"/g, '\\"') + '"]');
break;
} if (opt) {
}
var ds = opt.getAttribute("data-trade-style");
modelSel.innerHTML = "";
var placeholder = document.createElement("option"); if (ds === "swing" || ds === "trend") return ds;
placeholder.value = "";
placeholder.textContent = "类型"; }
modelSel.appendChild(placeholder);
}
if (!cat || !cat.options || !cat.options.length) {
modelSel.disabled = true; var map = global.ORDER_ENTRY_MODEL_TRADE_STYLE || {};
modelSel.value = "";
syncOrderEntryModelTradeStyle(); return map[code] || "trend";
return;
} }
modelSel.disabled = false;
var pick = preserveCode || "";
for (var k = 0; k < cat.options.length; k++) { function findOption(catKey, code) {
var o = cat.options[k];
var opt = document.createElement("option"); var cats = categoriesData();
opt.value = o.code;
opt.textContent = o.label; for (var i = 0; i < cats.length; i++) {
if (o.trade_style) opt.setAttribute("data-trade-style", o.trade_style);
if (o.help) opt.title = o.help; if (cats[i].key !== catKey) continue;
modelSel.appendChild(opt);
} var opts = cats[i].options || [];
if (pick && findOption(catKey, pick)) { for (var j = 0; j < opts.length; j++) {
modelSel.value = pick;
} else if (cat.options.length === 1) { if (opts[j].code === code) return opts[j];
modelSel.value = cat.options[0].code;
} else { }
modelSel.value = "";
} }
syncOrderEntryModelTradeStyle();
} return null;
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; function filterDomSubOptions(modelSel, catKey, preserveCode) {
var labels = { trend: "趋势单", swing: "波段单" };
var code = modelSel.value || ""; var tagged = modelSel.querySelectorAll("option[data-entry-category]");
var ts = tradeStyleForCode(code);
hidden.value = ts; if (!tagged.length) return false;
if (hint) hint.textContent = labels[ts] || ts;
}
function initOrderEntryModelSelect(root) { var any = false;
var scope = root && root.querySelector ? root : document;
var catSel = for (var i = 0; i < tagged.length; i++) {
scope === document
? document.getElementById("order-entry-category") var opt = tagged[i];
: scope.querySelector("#order-entry-category");
var modelSel = var show = !!catKey && opt.getAttribute("data-entry-category") === catKey;
scope === document
? document.getElementById("order-entry-model") opt.hidden = !show;
: scope.querySelector("#order-entry-model");
if (!catSel || !modelSel) return; opt.disabled = !show;
if (catSel.dataset.entryModelWired !== "1") { if (show) any = true;
catSel.dataset.entryModelWired = "1";
catSel.addEventListener("change", function () { }
rebuildEntryModelSubSelect("");
});
}
if (modelSel.dataset.entryModelWired !== "1") { modelSel.disabled = !any;
modelSel.dataset.entryModelWired = "1";
modelSel.addEventListener("change", syncOrderEntryModelTradeStyle); if (!any) {
}
modelSel.value = "";
var presetCode = modelSel.getAttribute("data-preset-code") || modelSel.value || "";
if (presetCode) { return true;
var catMap = codeToCategoryMap();
var catKey = catMap[presetCode]; }
if (catKey) {
catSel.value = catKey;
rebuildEntryModelSubSelect(presetCode);
return; var pick = preserveCode || "";
}
} if (pick) {
rebuildEntryModelSubSelect("");
} var picked = modelSel.querySelector('option[value="' + pick.replace(/"/g, '\\"') + '"]:not([disabled])');
global.paintOrderLeverageHint = function (leverage) { if (picked) {
var hidden = document.getElementById("order-leverage");
var hint = document.getElementById("order-leverage-hint"); modelSel.value = pick;
if (!hidden && !hint) return;
var lev = parseInt(leverage, 10); return true;
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";
}; var visible = [];
global.initOrderEntryModelSelect = initOrderEntryModelSelect; for (var k = 0; k < tagged.length; k++) {
global.syncOrderEntryModelTradeStyle = syncOrderEntryModelTradeStyle;
global.rebuildEntryModelSubSelect = rebuildEntryModelSubSelect; if (!tagged[k].disabled) visible.push(tagged[k]);
document.addEventListener("DOMContentLoaded", function () { }
initOrderEntryModelSelect();
}); if (visible.length === 1) modelSel.value = visible[0].value;
})(typeof window !== "undefined" ? window : globalThis);
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);
+1 -1
View File
@@ -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_CATEGORIES = {{ entry_model_categories | tojson }};
const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | tojson }}; const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | tojson }};
</script> </script>
<script src="/static/order_entry_model.js?v=4"></script> <script src="/static/order_entry_model.js?v=5"></script>
<script src="/static/manual_order_rr_preview.js?v=5"></script> <script src="/static/manual_order_rr_preview.js?v=5"></script>
<script src="/static/symbol_live_price.js?v=2"></script> <script src="/static/symbol_live_price.js?v=2"></script>
<script src="/static/strategy_roll.js?v=6"></script> <script src="/static/strategy_roll.js?v=6"></script>
+1 -1
View File
@@ -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_CATEGORIES = {{ entry_model_categories | tojson }};
const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | tojson }}; const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | tojson }};
</script> </script>
<script src="/static/order_entry_model.js?v=4"></script> <script src="/static/order_entry_model.js?v=5"></script>
<script src="/static/manual_order_rr_preview.js?v=5"></script> <script src="/static/manual_order_rr_preview.js?v=5"></script>
<script src="/static/symbol_live_price.js?v=2"></script> <script src="/static/symbol_live_price.js?v=2"></script>
<script src="/static/strategy_roll.js?v=6"></script> <script src="/static/strategy_roll.js?v=6"></script>
@@ -1,23 +1,28 @@
{# 趋势户:两级开仓类型 → 自动 trade_style;日内户:仍选手动 trend/swing #} {# 趋势户:两级开仓类型 → 自动 trade_style;日内户:仍选手动 trend/swing #}
{% macro order_entry_type_fields() -%}
{% if order_entry_profile == 'trend_div' %} {% macro order_entry_type_fields() -%}
<div class="order-entry-model-row">
<select id="order-entry-category" class="order-entry-category" required title="反转 / 顺势 / 波段" aria-label="开仓性质"> {% if order_entry_profile == 'trend_div' %}
<option value="">性质</option>
{% for cat in entry_model_categories %} <div class="order-entry-model-row">
<option value="{{ cat.key }}">{{ cat.label }}</option>
{% endfor %} <select id="order-entry-category" class="order-entry-category" required title="反转 / 顺势 / 波段" aria-label="开仓性质">
</select>
<select name="entry_model" id="order-entry-model" class="order-entry-model-sub" required disabled title="启动A/B、大分歧A/B、小分歧" aria-label="开仓类型"> <option value="">性质</option>
<option value="">类型</option>
</select> {% for cat in entry_model_categories %}
<input type="hidden" name="trade_style" id="order-trade-style-hidden" value="trend">
<span id="order-trade-style-hint" class="order-trade-style-hint" title="由开仓类型自动设定">趋势单</span> <option value="{{ cat.key }}">{{ cat.label }}</option>
</div>
{% else %} {% endfor %}
<select name="trade_style" required>
<option value="trend">趋势单</option> </select>
<option value="swing">波段单</option>
</select> <select name="entry_model" id="order-entry-model" class="order-entry-model-sub" required disabled title="启动A/B、大分歧A/B、小分歧" aria-label="开仓类型">
{% endif %}
{%- endmacro %} <option value="">类型</option>
{% for cat in entry_model_categories %}
{% for opt in cat.options %}