条件单客户端勾选按 sim / live 分组,没有对应模式则不显示该组。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-04 18:14:46 +08:00
parent 10515ad1b1
commit b9f76f4d53
5 changed files with 89 additions and 34 deletions
+5
View File
@@ -635,6 +635,10 @@ def api_cond_order_candidates():
has_pos = isinstance(brief, list) and len(brief) > 0 has_pos = isinstance(brief, list) and len(brief) > 0
locked = bool(snap.get("cond_order_locked")) locked = bool(snap.get("cond_order_locked"))
selectable = bool(row.get("online")) and not has_pos and not locked selectable = bool(row.get("online")) and not has_pos and not locked
inst = snap.get("instance") if isinstance(snap.get("instance"), dict) else {}
mode = str(inst.get("mode") or "").strip().lower()
if mode not in ("sim", "live"):
mode = ""
out.append( out.append(
{ {
"id": row.get("id"), "id": row.get("id"),
@@ -643,6 +647,7 @@ def api_cond_order_candidates():
"has_position": has_pos, "has_position": has_pos,
"cond_order_locked": locked, "cond_order_locked": locked,
"selectable": selectable, "selectable": selectable,
"mode": mode,
"error": row.get("error") or "", "error": row.get("error") or "",
} }
) )
+80 -32
View File
@@ -53,9 +53,76 @@
return "st-done"; return "st-done";
} }
function instanceMode(it) {
const m = String((it && it.mode) || "").toLowerCase();
return m === "sim" || m === "live" ? m : "";
}
function renderCandidateCard(it, checkedIds) {
const disabled = !it.selectable;
const tip = disabled
? it.has_position
? "已有持仓"
: it.cond_order_locked
? "条件单占用"
: it.online
? "不可选"
: "离线"
: "可选";
const id = String(it.id);
const checked = !disabled && checkedIds.indexOf(id) >= 0;
return (
'<label class="cond-inst' +
(disabled ? " is-off" : "") +
'" title="' +
escapeHtml(tip) +
'">' +
'<input type="checkbox" name="cf-inst" value="' +
encodeURIComponent(id) +
'"' +
(disabled ? " disabled" : "") +
(checked ? " checked" : "") +
">" +
'<span class="cond-inst-name">' +
escapeHtml(it.name || "") +
"</span>" +
'<span class="cond-inst-tag">' +
escapeHtml(tip) +
"</span></label>"
);
}
function renderCandidateGroup(mode, items, checkedIds) {
if (!items.length) return "";
const label =
mode === "sim" || mode === "live"
? '<div class="cond-inst-group-label"><span class="overview-mode-tag overview-mode-tag--' +
mode +
'">' +
mode +
"</span></div>"
: "";
return (
'<div class="cond-inst-group">' +
label +
'<div class="cond-inst-grid">' +
items
.map(function (it) {
return renderCandidateCard(it, checkedIds);
})
.join("") +
"</div></div>"
);
}
async function loadCandidates() { async function loadCandidates() {
const box = document.getElementById("cf-instances"); const box = document.getElementById("cf-instances");
if (!box) return; if (!box) return;
const checkedIds = Array.prototype.slice
.call(box.querySelectorAll('input[name="cf-inst"]:checked'))
.map(function (el) {
return decodeURIComponent(el.value || "");
});
const r = await fetch("/api/cond_orders/candidates"); const r = await fetch("/api/cond_orders/candidates");
const d = await r.json(); const d = await r.json();
const list = d.instances || []; const list = d.instances || [];
@@ -63,38 +130,19 @@
box.innerHTML = '<span class="muted">暂无启用实例</span>'; box.innerHTML = '<span class="muted">暂无启用实例</span>';
return; return;
} }
box.innerHTML = list const sim = [];
.map(function (it) { const live = [];
const disabled = !it.selectable; const other = [];
const tip = disabled list.forEach(function (it) {
? it.has_position const m = instanceMode(it);
? "已有持仓" if (m === "sim") sim.push(it);
: it.cond_order_locked else if (m === "live") live.push(it);
? "条件单占用" else other.push(it);
: it.online });
? "不可选" box.innerHTML =
: "离线" renderCandidateGroup("sim", sim, checkedIds) +
: "可选"; renderCandidateGroup("live", live, checkedIds) +
return ( renderCandidateGroup("", other, checkedIds);
'<label class="cond-inst' +
(disabled ? " is-off" : "") +
'" title="' +
escapeHtml(tip) +
'">' +
'<input type="checkbox" name="cf-inst" value="' +
encodeURIComponent(String(it.id)) +
'"' +
(disabled ? " disabled" : "") +
">" +
'<span class="cond-inst-name">' +
escapeHtml(it.name || "") +
"</span>" +
'<span class="cond-inst-tag">' +
escapeHtml(tip) +
"</span></label>"
);
})
.join("");
} }
async function loadOrders() { async function loadOrders() {
+2
View File
@@ -1350,6 +1350,8 @@ input.readonly {
min-height: 62px; min-height: 62px;
} }
.cond-switch input { width: auto; margin: 0; } .cond-switch input { width: auto; margin: 0; }
.cond-inst-group + .cond-inst-group { margin-top: 14px; }
.cond-inst-group-label { margin-bottom: 8px; }
.cond-inst-grid { .cond-inst-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}期权中控{% endblock %}</title> <title>{% block title %}期权中控{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='hub.css') }}?v=19"> <link rel="stylesheet" href="{{ url_for('static', filename='hub.css') }}?v=20">
</head> </head>
<body class="{% if request.path.startswith('/login') %}page-login{% elif request.path.startswith('/desk') %}page-desk{% elif request.path.startswith('/stats') %}page-stats{% elif request.path.startswith('/instances') %}page-instances{% elif request.path == '/' %}page-dashboard{% endif %}"> <body class="{% if request.path.startswith('/login') %}page-login{% elif request.path.startswith('/desk') %}page-desk{% elif request.path.startswith('/stats') %}page-stats{% elif request.path.startswith('/instances') %}page-instances{% elif request.path == '/' %}page-dashboard{% endif %}">
{% if session.get('logged_in') %} {% if session.get('logged_in') %}
+1 -1
View File
@@ -62,7 +62,7 @@
<div class="cond-section"> <div class="cond-section">
<div class="cond-section-title">客户端</div> <div class="cond-section-title">客户端</div>
<div id="cf-instances" class="cond-inst-grid"></div> <div id="cf-instances"></div>
</div> </div>
<div class="cond-actions"> <div class="cond-actions">