feat: show force-close badge and countdown when FORCE_CLOSE is enabled
Add shared lib/UI for midnight force-close indicator on instance and hub pages, and document Gate intraday 0-point exit alignment. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -590,6 +590,53 @@ html[data-theme="light"] .pos-meta-item::after {
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
.force-close-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
color: #ffc870;
|
||||
background: #2a2218;
|
||||
border: 1px solid #6a5020;
|
||||
padding: 4px 12px;
|
||||
border-radius: 999px;
|
||||
letter-spacing: 0.02em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.force-close-badge .force-close-header-cd {
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
.pos-force-close-meta {
|
||||
color: #ffc870;
|
||||
}
|
||||
.pos-symbol-force-close {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 500;
|
||||
color: #ffc870;
|
||||
padding: 1px 6px;
|
||||
border-radius: 4px;
|
||||
background: rgba(255, 200, 112, 0.12);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.pos-symbol-force-close .pos-force-close-cd {
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
html[data-theme="light"] .force-close-badge {
|
||||
color: #9a6200;
|
||||
background: #fff6e8;
|
||||
border-color: #d4a84a;
|
||||
}
|
||||
html[data-theme="light"] .pos-symbol-force-close,
|
||||
html[data-theme="light"] .pos-force-close-meta {
|
||||
color: #9a6200;
|
||||
background: rgba(212, 168, 74, 0.14);
|
||||
}
|
||||
.key-time-close-wrap.is-disabled > label,
|
||||
.order-time-close-wrap.is-disabled > label {
|
||||
opacity: 0.72;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 时间平仓:表单开关 + 持仓倒计时。
|
||||
* 时间平仓 + 整点强制清仓:表单开关 + 持仓/顶栏倒计时。
|
||||
*/
|
||||
(function (global) {
|
||||
"use strict";
|
||||
@@ -16,6 +16,15 @@
|
||||
return pad2(h) + ":" + pad2(m) + ":" + pad2(r);
|
||||
}
|
||||
|
||||
function isForceCloseActive(wrap) {
|
||||
if (!wrap) return false;
|
||||
const raw =
|
||||
wrap.dataset.forceCloseActive ||
|
||||
wrap.getAttribute("data-force-close-active") ||
|
||||
"";
|
||||
return raw === "1" || raw === "true";
|
||||
}
|
||||
|
||||
function bindTimeCloseForm(checkboxId, selectId, wrapId) {
|
||||
const cb = document.getElementById(checkboxId);
|
||||
const sel = document.getElementById(selectId);
|
||||
@@ -37,6 +46,15 @@
|
||||
sync();
|
||||
}
|
||||
|
||||
function paintCountdownEl(cd, rem, active) {
|
||||
if (!cd) return;
|
||||
if (active) {
|
||||
cd.textContent = "执行中";
|
||||
return;
|
||||
}
|
||||
cd.textContent = Number.isFinite(rem) ? formatCountdown(rem) : "--:--:--";
|
||||
}
|
||||
|
||||
function paintOrderTimeClose(order) {
|
||||
if (!order || order.id == null) return;
|
||||
const wrap = document.getElementById("order-time-close-wrap-" + order.id);
|
||||
@@ -59,10 +77,71 @@
|
||||
if ((rem == null || !Number.isFinite(rem)) && order.time_close_at_ms) {
|
||||
rem = Math.max(0, Math.floor((Number(order.time_close_at_ms) - Date.now()) / 1000));
|
||||
}
|
||||
cd.textContent = Number.isFinite(rem) ? formatCountdown(rem) : "--:--:--";
|
||||
paintCountdownEl(cd, rem, false);
|
||||
wrap.dataset.closeAtMs = order.time_close_at_ms ? String(order.time_close_at_ms) : "";
|
||||
}
|
||||
|
||||
function paintOrderForceClose(order) {
|
||||
if (!order || order.id == null) return;
|
||||
const wrap = document.getElementById("order-force-close-wrap-" + order.id);
|
||||
const cd = document.getElementById("order-force-close-cd-" + order.id);
|
||||
if (!wrap || !cd) return;
|
||||
const enabled = !!order.force_close_enabled;
|
||||
if (!enabled) {
|
||||
wrap.style.display = "none";
|
||||
return;
|
||||
}
|
||||
wrap.style.display = "";
|
||||
const label = order.force_close_label || "强制清仓";
|
||||
const labelEl = wrap.querySelector(".pos-force-close-label");
|
||||
if (labelEl) labelEl.textContent = label;
|
||||
let rem =
|
||||
order.force_close_remaining_sec != null
|
||||
? Number(order.force_close_remaining_sec)
|
||||
: null;
|
||||
const atMs = order.force_close_at_ms;
|
||||
if ((rem == null || !Number.isFinite(rem)) && atMs) {
|
||||
rem = Math.max(0, Math.floor((Number(atMs) - Date.now()) / 1000));
|
||||
}
|
||||
const active = !!order.force_close_active;
|
||||
paintCountdownEl(cd, rem, active);
|
||||
wrap.dataset.forceCloseAtMs = atMs ? String(atMs) : "";
|
||||
wrap.dataset.forceCloseActive = active ? "1" : "0";
|
||||
}
|
||||
|
||||
function paintForceCloseHeader(state) {
|
||||
const wrap = document.getElementById("force-close-header-badge");
|
||||
if (!wrap) return;
|
||||
if (!state || !state.enabled) {
|
||||
wrap.style.display = "none";
|
||||
return;
|
||||
}
|
||||
wrap.style.display = "";
|
||||
const label = state.label || "强制清仓";
|
||||
const labelPrefix = label + " 已开启 · ";
|
||||
let prefixNode = wrap.querySelector(".force-close-header-prefix");
|
||||
if (!prefixNode) {
|
||||
wrap.textContent = "";
|
||||
prefixNode = document.createElement("span");
|
||||
prefixNode.className = "force-close-header-prefix";
|
||||
prefixNode.textContent = labelPrefix;
|
||||
wrap.appendChild(prefixNode);
|
||||
const cd = document.createElement("span");
|
||||
cd.className = "force-close-header-cd";
|
||||
wrap.appendChild(cd);
|
||||
} else {
|
||||
prefixNode.textContent = labelPrefix;
|
||||
}
|
||||
const cd = wrap.querySelector(".force-close-header-cd");
|
||||
let rem = state.remaining_sec != null ? Number(state.remaining_sec) : null;
|
||||
if ((rem == null || !Number.isFinite(rem)) && state.next_at_ms) {
|
||||
rem = Math.max(0, Math.floor((Number(state.next_at_ms) - Date.now()) / 1000));
|
||||
}
|
||||
paintCountdownEl(cd, rem, !!state.active);
|
||||
wrap.dataset.forceCloseAtMs = state.next_at_ms ? String(state.next_at_ms) : "";
|
||||
wrap.dataset.forceCloseActive = state.active ? "1" : "0";
|
||||
}
|
||||
|
||||
function tickLocalCountdowns() {
|
||||
document.querySelectorAll("[data-close-at-ms]").forEach(function (wrap) {
|
||||
const closeAtRaw = wrap.dataset.closeAtMs || wrap.getAttribute("data-close-at-ms") || "";
|
||||
@@ -73,10 +152,23 @@
|
||||
const rem = Math.max(0, Math.floor((closeAt - Date.now()) / 1000));
|
||||
cd.textContent = formatCountdown(rem);
|
||||
});
|
||||
document.querySelectorAll("[data-force-close-at-ms]").forEach(function (wrap) {
|
||||
const closeAtRaw =
|
||||
wrap.dataset.forceCloseAtMs || wrap.getAttribute("data-force-close-at-ms") || "";
|
||||
const cd = wrap.querySelector(".pos-force-close-cd, .force-close-header-cd");
|
||||
if (!cd) return;
|
||||
const closeAt = Number(closeAtRaw);
|
||||
if (!closeAt) return;
|
||||
const rem = Math.max(0, Math.floor((closeAt - Date.now()) / 1000));
|
||||
paintCountdownEl(cd, rem, isForceCloseActive(wrap));
|
||||
});
|
||||
}
|
||||
|
||||
function paintOrders(orders) {
|
||||
(orders || []).forEach(paintOrderTimeClose);
|
||||
(orders || []).forEach(function (order) {
|
||||
paintOrderTimeClose(order);
|
||||
paintOrderForceClose(order);
|
||||
});
|
||||
}
|
||||
|
||||
function syncKeyTimeCloseVisibility(show) {
|
||||
@@ -88,6 +180,8 @@
|
||||
global.TimeCloseUI = {
|
||||
bindTimeCloseForm: bindTimeCloseForm,
|
||||
paintOrderTimeClose: paintOrderTimeClose,
|
||||
paintOrderForceClose: paintOrderForceClose,
|
||||
paintForceCloseHeader: paintForceCloseHeader,
|
||||
paintOrders: paintOrders,
|
||||
tickLocalCountdowns: tickLocalCountdowns,
|
||||
syncKeyTimeCloseVisibility: syncKeyTimeCloseVisibility,
|
||||
|
||||
Reference in New Issue
Block a user