fix: 市价加仓预览后无法执行滚仓

修复 embed 壳拦截 roll-form 提交,以及策略 tab 切换后未重新绑定顺势加仓脚本的问题。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-02 21:42:12 +08:00
parent bfa3352122
commit 9d9d0af31e
8 changed files with 275 additions and 243 deletions
+1 -1
View File
@@ -841,7 +841,7 @@
<script src="/static/ai_review_render.js?v=2"></script> <script src="/static/ai_review_render.js?v=2"></script>
<script src="/static/form_submit_guard.js?v=2"></script> <script src="/static/form_submit_guard.js?v=2"></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/strategy_roll.js?v=5"></script> <script src="/static/strategy_roll.js?v=6"></script>
<script> <script>
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }}; const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }}; const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
+1 -1
View File
@@ -808,7 +808,7 @@
<script src="/static/ai_review_render.js?v=2"></script> <script src="/static/ai_review_render.js?v=2"></script>
<script src="/static/form_submit_guard.js?v=2"></script> <script src="/static/form_submit_guard.js?v=2"></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/strategy_roll.js?v=5"></script> <script src="/static/strategy_roll.js?v=6"></script>
<script> <script>
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }}; const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }}; const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
+1 -1
View File
@@ -808,7 +808,7 @@
<script src="/static/ai_review_render.js?v=2"></script> <script src="/static/ai_review_render.js?v=2"></script>
<script src="/static/form_submit_guard.js?v=2"></script> <script src="/static/form_submit_guard.js?v=2"></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/strategy_roll.js?v=5"></script> <script src="/static/strategy_roll.js?v=6"></script>
<script> <script>
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }}; const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }}; const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
+1 -1
View File
@@ -837,7 +837,7 @@
<script src="/static/ai_review_render.js?v=2"></script> <script src="/static/ai_review_render.js?v=2"></script>
<script src="/static/form_submit_guard.js?v=2"></script> <script src="/static/form_submit_guard.js?v=2"></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/strategy_roll.js?v=5"></script> <script src="/static/strategy_roll.js?v=6"></script>
<script> <script>
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }}; const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }}; const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
+4 -1
View File
@@ -15,7 +15,7 @@
let loadingTab = false; let loadingTab = false;
/** 自带校验后 form.submit() 的表单,勿在捕获阶段再 fetch 一份(会双发 POST */ /** 自带校验后 form.submit() 的表单,勿在捕获阶段再 fetch 一份(会双发 POST */
const CUSTOM_SUBMIT_FORM_IDS = new Set(["add-order-form", "key-form"]); const CUSTOM_SUBMIT_FORM_IDS = new Set(["add-order-form", "key-form", "roll-form"]);
function isEmbedShell() { function isEmbedShell() {
return document.body && document.body.getAttribute("data-embed-shell") === "1"; return document.body && document.body.getAttribute("data-embed-shell") === "1";
@@ -71,6 +71,9 @@
if (tab === "key_monitor" && global.KeyMonitorForm && typeof global.KeyMonitorForm.init === "function") { if (tab === "key_monitor" && global.KeyMonitorForm && typeof global.KeyMonitorForm.init === "function") {
global.KeyMonitorForm.init(); global.KeyMonitorForm.init();
} }
if (tab === "strategy" && typeof global.initStrategyRollForm === "function") {
global.initStrategyRollForm();
}
if (tab === "records") { if (tab === "records") {
if (typeof global.loadJournals === "function") global.loadJournals(); if (typeof global.loadJournals === "function") global.loadJournals();
if (typeof global.loadReviews === "function") global.loadReviews(); if (typeof global.loadReviews === "function") global.loadReviews();
+264 -235
View File
@@ -30,260 +30,289 @@
window.syncRollFormMode = syncRollFormMode; window.syncRollFormMode = syncRollFormMode;
const form = document.getElementById("roll-form"); function isEmbedShell() {
if (!form) return; return document.body && document.body.getAttribute("data-embed-shell") === "1";
if (form.dataset.rollJsInit === "1") return;
form.dataset.rollJsInit = "1";
const symbolSel = document.getElementById("roll-symbol");
const dirInput = document.getElementById("roll-direction");
const modeSel = document.getElementById("roll-add-mode");
const riskBanner = document.getElementById("roll-risk-banner");
const previewBtn = document.getElementById("roll-preview-btn");
const submitBtn = document.getElementById("roll-submit-btn");
const previewBox = document.getElementById("roll-preview-box");
const previewText = document.getElementById("roll-preview-text");
const countdownEl = document.getElementById("roll-countdown");
const trendLocked = submitBtn && submitBtn.getAttribute("data-trend-locked") === "1";
let countdownTimer = null;
let previewOk = false;
let lastPreviewMode = "";
let monitorSubmitting = false;
function isMarketMode() {
return (modeSel.value || "market") === "market";
} }
function isMonitorMode() { function submitRollForm(form) {
const m = modeSel.value || "market"; if (isEmbedShell() && window.InstanceEmbed && typeof window.InstanceEmbed.postFormAndReload === "function") {
return m === "fib_618" || m === "fib_786" || m === "breakout"; window.InstanceEmbed.postFormAndReload(form, "执行中…");
}
function selectedOption() {
return symbolSel.options[symbolSel.selectedIndex];
}
function syncDirectionLock() {
const opt = selectedOption();
if (!opt || !opt.value) {
riskBanner.textContent = "当前风险:请选择持仓币种";
return; return;
} }
const dir = opt.getAttribute("data-direction") || "long"; if (window.FormSubmitGuard && typeof window.FormSubmitGuard.nativeSubmitOnce === "function") {
const rp = opt.getAttribute("data-risk-percent") || "—"; window.FormSubmitGuard.nativeSubmitOnce(form, "执行中…");
dirInput.value = dir;
riskBanner.textContent =
"当前风险:" + rp + "%(来自监控单 #" + (opt.getAttribute("data-monitor-id") || "?") + "";
}
function syncSubmitButton() {
if (!submitBtn || trendLocked) return;
if (isMonitorMode()) {
submitBtn.disabled = false;
return; return;
} }
submitBtn.disabled = !previewOk || !!countdownTimer; form.submit();
} }
function clearMessageBox() { function initStrategyRollForm() {
if (!previewBox) return; const form = document.getElementById("roll-form");
previewBox.style.display = "none"; if (!form) return;
previewBox.classList.remove("is-error", "is-preview"); if (form.dataset.rollJsInit === "1") return;
if (previewText) previewText.textContent = ""; form.dataset.rollJsInit = "1";
if (countdownEl) countdownEl.style.display = "none";
}
function showReject(msg) { const symbolSel = document.getElementById("roll-symbol");
if (!previewBox || !previewText) return; const dirInput = document.getElementById("roll-direction");
previewBox.style.display = "block"; const modeSel = document.getElementById("roll-add-mode");
previewBox.classList.remove("is-preview"); const riskBanner = document.getElementById("roll-risk-banner");
previewBox.classList.add("is-error"); const previewBtn = document.getElementById("roll-preview-btn");
previewText.textContent = msg || "无法执行"; const submitBtn = document.getElementById("roll-submit-btn");
if (countdownEl) countdownEl.style.display = "none"; const previewBox = document.getElementById("roll-preview-box");
previewBox.scrollIntoView({ behavior: "smooth", block: "nearest" }); const previewText = document.getElementById("roll-preview-text");
} const countdownEl = document.getElementById("roll-countdown");
const trendLocked = submitBtn && submitBtn.getAttribute("data-trend-locked") === "1";
function showPreviewResult(p) { let countdownTimer = null;
if (!previewBox || !previewText) return; let previewOk = false;
previewBox.style.display = "block"; let lastPreviewMode = "";
previewBox.classList.remove("is-error"); let monitorSubmitting = false;
previewBox.classList.add("is-preview");
previewText.innerHTML =
"<strong>" +
(p.add_mode_label || "") +
"</strong> · 约 <strong>" +
(p.add_amount_display != null ? p.add_amount_display : p.add_amount_raw) +
"</strong> 张<br>" +
"加仓参考价 " +
(p.add_price_display != null ? p.add_price_display : p.add_price) +
" · 新止损 " +
(p.new_sl_display != null ? p.new_sl_display : p.new_stop_loss) +
"<br>" +
"合并均价 " +
p.avg_entry_after +
" · 打到止损约 " +
p.loss_at_sl_usdt +
"U(风险预算 " +
(p.risk_budget_usdt != null ? p.risk_budget_usdt : "—") +
"U";
}
function syncFieldVisibility() { function isMarketMode() {
syncRollFormMode(form, modeSel.value || "market"); return (modeSel.value || "market") === "market";
resetPreview();
}
function resetPreview() {
previewOk = false;
monitorSubmitting = false;
clearMessageBox();
if (countdownTimer) {
clearInterval(countdownTimer);
countdownTimer = null;
} }
syncSubmitButton();
}
function formPayload() { function isMonitorMode() {
const fd = new FormData(form); const m = modeSel.value || "market";
const obj = {}; return m === "fib_618" || m === "fib_786" || m === "breakout";
fd.forEach(function (v, k) {
if (v !== "") obj[k] = v;
});
return obj;
}
function requestPreview() {
return fetch("/strategy/roll/preview", {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify(formPayload()),
credentials: "same-origin",
}).then(function (r) {
return r.json();
});
}
function runPreview() {
resetPreview();
if (!symbolSel.value) {
showReject("请先选择持仓币种");
return;
} }
if (previewBtn) previewBtn.disabled = true;
requestPreview()
.then(function (data) {
if (previewBtn) previewBtn.disabled = false;
if (!data.ok) {
showReject(data.msg || "预览失败");
return;
}
const p = data.preview || {};
lastPreviewMode = p.add_mode || modeSel.value;
showPreviewResult(p);
previewOk = true;
if (lastPreviewMode === "market") {
startCountdown(10);
} else {
syncSubmitButton();
}
})
.catch(function () {
if (previewBtn) previewBtn.disabled = false;
showReject("预览请求失败,请稍后重试");
});
}
function runMonitorSubmit() { function selectedOption() {
if (monitorSubmitting) return; return symbolSel.options[symbolSel.selectedIndex];
if (!symbolSel.value) {
showReject("请先选择持仓币种");
return;
} }
monitorSubmitting = true;
if (submitBtn) submitBtn.disabled = true;
requestPreview()
.then(function (data) {
monitorSubmitting = false;
if (submitBtn && !trendLocked) submitBtn.disabled = false;
if (!data.ok) {
showReject(data.msg || "无法提交监控");
return;
}
const p = data.preview || {};
const modeLabel = modeSel.options[modeSel.selectedIndex].text;
const summary =
"约 " +
(p.add_amount_display != null ? p.add_amount_display : p.add_amount_raw) +
" 张 · 触发参考价 " +
(p.add_price_display != null ? p.add_price_display : p.add_price) +
" · 新止损 " +
(p.new_sl_display != null ? p.new_sl_display : p.new_stop_loss);
if (!confirm("确认提交「" + modeLabel + "」?\n" + summary)) {
return;
}
form.submit();
})
.catch(function () {
monitorSubmitting = false;
if (submitBtn && !trendLocked) submitBtn.disabled = false;
showReject("校验请求失败,请稍后重试");
});
}
function startCountdown(sec) { function syncDirectionLock() {
let left = sec; const opt = selectedOption();
if (submitBtn) submitBtn.disabled = true; if (!opt || !opt.value) {
if (countdownEl) { riskBanner.textContent = "当前风险:请选择持仓币种";
countdownEl.style.display = "block";
countdownEl.textContent = "市价加仓:" + left + " 秒后可执行(修改表单将取消预览)";
}
countdownTimer = setInterval(function () {
left -= 1;
if (left <= 0) {
clearInterval(countdownTimer);
countdownTimer = null;
if (countdownEl) countdownEl.textContent = "可以执行市价加仓";
syncSubmitButton();
return; return;
} }
if (countdownEl) countdownEl.textContent = "市价加仓:" + left + " 秒后可执行"; const dir = opt.getAttribute("data-direction") || "long";
}, 1000); const rp = opt.getAttribute("data-risk-percent") || "—";
dirInput.value = dir;
riskBanner.textContent =
"当前风险:" + rp + "%(来自监控单 #" + (opt.getAttribute("data-monitor-id") || "?") + "";
}
function syncSubmitButton() {
if (!submitBtn || trendLocked) return;
if (isMonitorMode()) {
submitBtn.disabled = false;
submitBtn.removeAttribute("disabled");
return;
}
const blocked = !previewOk || !!countdownTimer;
submitBtn.disabled = blocked;
if (!blocked) submitBtn.removeAttribute("disabled");
}
function clearMessageBox() {
if (!previewBox) return;
previewBox.style.display = "none";
previewBox.classList.remove("is-error", "is-preview");
if (previewText) previewText.textContent = "";
if (countdownEl) countdownEl.style.display = "none";
}
function showReject(msg) {
if (!previewBox || !previewText) return;
previewBox.style.display = "block";
previewBox.classList.remove("is-preview");
previewBox.classList.add("is-error");
previewText.textContent = msg || "无法执行";
if (countdownEl) countdownEl.style.display = "none";
previewBox.scrollIntoView({ behavior: "smooth", block: "nearest" });
}
function showPreviewResult(p) {
if (!previewBox || !previewText) return;
previewBox.style.display = "block";
previewBox.classList.remove("is-error");
previewBox.classList.add("is-preview");
previewText.innerHTML =
"<strong>" +
(p.add_mode_label || "") +
"</strong> · 约 <strong>" +
(p.add_amount_display != null ? p.add_amount_display : p.add_amount_raw) +
"</strong> 张<br>" +
"加仓参考价 " +
(p.add_price_display != null ? p.add_price_display : p.add_price) +
" · 新止损 " +
(p.new_sl_display != null ? p.new_sl_display : p.new_stop_loss) +
"<br>" +
"合并均价 " +
p.avg_entry_after +
" · 打到止损约 " +
p.loss_at_sl_usdt +
"U(风险预算 " +
(p.risk_budget_usdt != null ? p.risk_budget_usdt : "—") +
"U";
}
function syncFieldVisibility() {
syncRollFormMode(form, modeSel.value || "market");
resetPreview();
}
function resetPreview() {
previewOk = false;
monitorSubmitting = false;
clearMessageBox();
if (countdownTimer) {
clearInterval(countdownTimer);
countdownTimer = null;
}
syncSubmitButton();
}
function formPayload() {
const fd = new FormData(form);
const obj = {};
fd.forEach(function (v, k) {
if (v !== "") obj[k] = v;
});
return obj;
}
function requestPreview() {
return fetch("/strategy/roll/preview", {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify(formPayload()),
credentials: "same-origin",
}).then(function (r) {
return r.json();
});
}
function runPreview() {
resetPreview();
if (!symbolSel.value) {
showReject("请先选择持仓币种");
return;
}
if (previewBtn) previewBtn.disabled = true;
requestPreview()
.then(function (data) {
if (previewBtn) previewBtn.disabled = false;
if (!data.ok) {
showReject(data.msg || "预览失败");
return;
}
const p = data.preview || {};
lastPreviewMode = p.add_mode || modeSel.value;
showPreviewResult(p);
previewOk = true;
if (lastPreviewMode === "market") {
startCountdown(10);
} else {
syncSubmitButton();
}
})
.catch(function () {
if (previewBtn) previewBtn.disabled = false;
showReject("预览请求失败,请稍后重试");
});
}
function runMonitorSubmit() {
if (monitorSubmitting) return;
if (!symbolSel.value) {
showReject("请先选择持仓币种");
return;
}
monitorSubmitting = true;
if (submitBtn) submitBtn.disabled = true;
requestPreview()
.then(function (data) {
monitorSubmitting = false;
if (submitBtn && !trendLocked) {
submitBtn.disabled = false;
submitBtn.removeAttribute("disabled");
}
if (!data.ok) {
showReject(data.msg || "无法提交监控");
return;
}
const p = data.preview || {};
const modeLabel = modeSel.options[modeSel.selectedIndex].text;
const summary =
"约 " +
(p.add_amount_display != null ? p.add_amount_display : p.add_amount_raw) +
" 张 · 触发参考价 " +
(p.add_price_display != null ? p.add_price_display : p.add_price) +
" · 新止损 " +
(p.new_sl_display != null ? p.new_sl_display : p.new_stop_loss);
if (!confirm("确认提交「" + modeLabel + "」?\n" + summary)) {
return;
}
submitRollForm(form);
})
.catch(function () {
monitorSubmitting = false;
if (submitBtn && !trendLocked) {
submitBtn.disabled = false;
submitBtn.removeAttribute("disabled");
}
showReject("校验请求失败,请稍后重试");
});
}
function startCountdown(sec) {
let left = sec;
if (submitBtn) submitBtn.disabled = true;
if (countdownEl) {
countdownEl.style.display = "block";
countdownEl.textContent = "市价加仓:" + left + " 秒后可执行(修改表单将取消预览)";
}
countdownTimer = setInterval(function () {
left -= 1;
if (left <= 0) {
clearInterval(countdownTimer);
countdownTimer = null;
if (countdownEl) countdownEl.textContent = "可以执行市价加仓";
syncSubmitButton();
return;
}
if (countdownEl) countdownEl.textContent = "市价加仓:" + left + " 秒后可执行";
}, 1000);
}
symbolSel.addEventListener("change", function () {
syncDirectionLock();
resetPreview();
});
modeSel.addEventListener("change", syncFieldVisibility);
form.addEventListener("input", resetPreview);
form.addEventListener("change", function (e) {
if (e.target !== previewBtn) resetPreview();
});
if (previewBtn) previewBtn.addEventListener("click", runPreview);
form.addEventListener("submit", function (e) {
e.preventDefault();
if (isMonitorMode()) {
runMonitorSubmit();
return;
}
if (!previewOk) {
showReject("请先点击「预览」并通过校验");
return;
}
if (submitBtn && submitBtn.disabled) {
showReject("请等待 10 秒确认倒计时结束后再执行市价加仓");
return;
}
const modeLabel = modeSel.options[modeSel.selectedIndex].text;
if (!confirm("确认提交「" + modeLabel + "」?")) {
return;
}
submitRollForm(form);
});
syncDirectionLock();
syncFieldVisibility();
} }
symbolSel.addEventListener("change", function () { window.initStrategyRollForm = initStrategyRollForm;
syncDirectionLock(); initStrategyRollForm();
resetPreview();
});
modeSel.addEventListener("change", syncFieldVisibility);
form.addEventListener("input", resetPreview);
form.addEventListener("change", function (e) {
if (e.target !== previewBtn) resetPreview();
});
if (previewBtn) previewBtn.addEventListener("click", runPreview);
form.addEventListener("submit", function (e) {
if (isMonitorMode()) {
e.preventDefault();
runMonitorSubmit();
return;
}
if (!previewOk) {
e.preventDefault();
showReject("请先点击「预览」并通过校验");
return;
}
if (submitBtn && submitBtn.disabled) {
e.preventDefault();
showReject("请等待 10 秒确认倒计时结束后再执行市价加仓");
return;
}
const modeLabel = modeSel.options[modeSel.selectedIndex].text;
if (!confirm("确认提交「" + modeLabel + "」?")) {
e.preventDefault();
}
});
syncDirectionLock();
syncFieldVisibility();
})(); })();
+2 -2
View File
@@ -118,9 +118,9 @@
<script src="/static/ai_review_render.js?v=2"></script> <script src="/static/ai_review_render.js?v=2"></script>
<script src="/static/form_submit_guard.js?v=2"></script> <script src="/static/form_submit_guard.js?v=2"></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/strategy_roll.js?v=5"></script> <script src="/static/strategy_roll.js?v=6"></script>
<script src="/static/key_monitor_form.js?v=2"></script> <script src="/static/key_monitor_form.js?v=2"></script>
{% include 'embed_boot_scripts.html' %} {% include 'embed_boot_scripts.html' %}
<script src="/static/instance_embed.js?v=5"></script> <script src="/static/instance_embed.js?v=6"></script>
</body> </body>
</html> </html>
+1 -1
View File
@@ -14,6 +14,6 @@
{% include 'strategy_roll_panel.html' %} {% include 'strategy_roll_panel.html' %}
<p class="rule-tip" style="margin-top:12px"><a href="/strategy/roll/docs" style="color:#8fc8ff">顺势加仓完整逻辑说明</a></p> <p class="rule-tip" style="margin-top:12px"><a href="/strategy/roll/docs" style="color:#8fc8ff">顺势加仓完整逻辑说明</a></p>
</div> </div>
<script src="/static/strategy_roll.js?v=5"></script> <script src="/static/strategy_roll.js?v=6"></script>
</body> </body>
</html> </html>