feat条件单新建改为左策略右选机双栏布局。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-13 19:41:04 +08:00
parent e2a28e6c94
commit 96c78b2ed1
4 changed files with 308 additions and 128 deletions
+92 -22
View File
@@ -366,8 +366,7 @@
'">' +
'<div class="cond-inst' +
(disabled ? " is-off" : "") +
'">' +
'<label class="cond-inst-head" title="' +
'" title="' +
escapeHtml(tip) +
'">' +
'<input type="checkbox" name="cf-inst" value="' +
@@ -376,15 +375,11 @@
(disabled ? " disabled" : "") +
(checked ? " checked" : "") +
">" +
'<div class="cond-inst-main">' +
'<span class="cond-inst-name">' +
escapeHtml(it.name || "") +
"</span>" +
'<span class="cond-inst-tag">' +
escapeHtml(tip) +
"</span>" +
"</label>" +
'<div class="cond-inst-body">' +
'<span class="cond-inst-fund-line">' +
'<span class="cond-inst-fund-line" data-xfer-toggle="1">' +
'<span class="cond-inst-funds cond-inst-funds--trading" title="交易账户可用">交易 ' +
escapeHtml(fmtTradingU(it.trading_usdt)) +
"</span>" +
@@ -392,6 +387,10 @@
escapeHtml(fmtTradingU(it.funding_usdt)) +
"</span>" +
"</span>" +
"</div>" +
'<span class="cond-inst-tag">' +
escapeHtml(tip) +
"</span>" +
'<span class="cond-inst-actions">' +
'<button type="button" class="btn btn-secondary btn-sm cond-inst-xfer" data-inst-id="' +
escapeHtml(id) +
@@ -409,7 +408,6 @@
">划入资金</button>" +
"</span>" +
"</div>" +
"</div>" +
"</div>"
);
}
@@ -444,8 +442,10 @@
const list = (allCandidates || []).filter(function (it) {
return candidateMatchesUnderlying(it, uly);
});
const meta = document.getElementById("cf-inst-meta");
if (!(allCandidates || []).length) {
box.innerHTML = '<span class="muted">暂无启用实例</span>';
if (meta) meta.textContent = "暂无实例";
return;
}
if (!list.length) {
@@ -453,8 +453,13 @@
'<span class="muted">当前标的 ' +
escapeHtml(uly) +
" 下暂无匹配客户端(请确认实例 OKX_OPTIONS_UNDERLYINGS)</span>";
if (meta) meta.textContent = "当前 " + uly + " · 可选 0";
return;
}
const selectableN = list.filter(function (it) {
return !!it.selectable;
}).length;
if (meta) meta.textContent = "当前 " + uly + " · 可选 " + selectableN;
const keep = {};
(checkedIds || []).forEach(function (id) {
keep[String(id)] = true;
@@ -489,6 +494,27 @@
);
});
});
box.querySelectorAll("[data-xfer-toggle]").forEach(function (el) {
el.addEventListener("click", function (ev) {
ev.preventDefault();
ev.stopPropagation();
const card = el.closest(".cond-inst");
if (!card || card.classList.contains("is-off")) return;
card.classList.toggle("is-xfer-open");
});
});
box.querySelectorAll(".cond-inst-name").forEach(function (el) {
el.addEventListener("click", function (ev) {
const card = el.closest(".cond-inst");
if (!card || card.classList.contains("is-off")) return;
const cb = card.querySelector('input[name="cf-inst"]');
if (!cb || cb.disabled) return;
// 点名称切换勾选(避免点余额误勾)
if (ev.target === el) {
cb.checked = !cb.checked;
}
});
});
}
async function loadCandidates() {
@@ -774,6 +800,55 @@
};
}
function syncSegButtons(segId, attr, value) {
const seg = document.getElementById(segId);
if (!seg) return;
seg.querySelectorAll(".cond-seg-btn").forEach(function (btn) {
const on = String(btn.getAttribute(attr) || "") === String(value);
btn.classList.toggle("on", on);
});
}
function bindSegGroup(segId, attr, selectId, onChange) {
const seg = document.getElementById(segId);
const sel = document.getElementById(selectId);
if (!seg || !sel) return;
syncSegButtons(segId, attr, sel.value);
seg.querySelectorAll(".cond-seg-btn").forEach(function (btn) {
btn.addEventListener("click", function () {
const v = btn.getAttribute(attr);
if (!v || sel.value === v) {
if (v) syncSegButtons(segId, attr, v);
return;
}
sel.value = v;
syncSegButtons(segId, attr, v);
if (typeof onChange === "function") onChange(v);
});
});
sel.addEventListener("change", function () {
syncSegButtons(segId, attr, sel.value);
if (typeof onChange === "function") onChange(sel.value);
});
}
function syncPeMultEnabled() {
const pe = document.getElementById("cf-pe");
const mult = document.getElementById("cf-pe-mult");
if (!pe || !mult) return;
mult.disabled = !pe.checked;
}
function currentCheckedInstIds() {
const box = document.getElementById("cf-instances");
if (!box) return [];
return Array.prototype.slice
.call(box.querySelectorAll('input[name="cf-inst"]:checked'))
.map(function (el) {
return decodeURIComponent(el.value || "");
});
}
function boot() {
const btn = document.getElementById("btn-cond-refresh");
if (btn) btn.addEventListener("click", function () {
@@ -785,20 +860,15 @@
const pxEl = document.getElementById("cf-trigger-px");
if (typeEl) typeEl.addEventListener("change", syncTriggerHint);
if (pxEl) pxEl.addEventListener("input", syncTriggerHint);
const ulyEl = document.getElementById("cf-underlying");
if (ulyEl) {
ulyEl.addEventListener("change", function () {
const box = document.getElementById("cf-instances");
const checkedIds = box
? Array.prototype.slice
.call(box.querySelectorAll('input[name="cf-inst"]:checked'))
.map(function (el) {
return decodeURIComponent(el.value || "");
})
: [];
paintCandidates(checkedIds);
bindSegGroup("cf-uly-seg", "data-uly", "cf-underlying", function () {
paintCandidates(currentCheckedInstIds());
});
}
bindSegGroup("cf-side-seg", "data-side", "cf-opt-type", function () {
syncTriggerHint();
});
const pe = document.getElementById("cf-pe");
if (pe) pe.addEventListener("change", syncPeMultEnabled);
syncPeMultEnabled();
syncTriggerHint();
// 读缓存秒开 + SSE;不再前端定时现场拉实例
refresh();
+151 -61
View File
@@ -1474,58 +1474,122 @@ input.readonly {
border-radius: 999px;
padding: 3px 10px;
}
.cond-section {
border-top: 1px solid rgba(56, 189, 248, 0.12);
padding: 14px 0 4px;
.cond-create-split {
display: grid;
grid-template-columns: minmax(260px, 0.92fr) minmax(300px, 1.18fr);
gap: 16px 18px;
align-items: start;
padding-top: 2px;
}
.cond-section:first-of-type { border-top: 0; padding-top: 0; }
.cond-section-title {
.cond-col-title {
font-size: 0.78rem;
color: var(--accent-2);
letter-spacing: 0.08em;
margin-bottom: 10px;
font-weight: 600;
letter-spacing: 0.06em;
margin: 0 0 10px;
font-weight: 650;
}
.cond-grid {
.cond-block { margin-bottom: 14px; }
.cond-block:last-child { margin-bottom: 0; }
.cond-mini-label {
display: block;
font-size: 0.75rem;
color: #b6c7da;
margin-bottom: 6px;
}
.cond-native-select[hidden] { display: none !important; }
.cond-seg {
display: inline-flex;
border: 1px solid var(--line);
border-radius: 9px;
overflow: hidden;
background: rgba(8, 16, 30, 0.8);
}
.cond-seg-btn {
border: 0;
background: transparent;
color: var(--muted);
padding: 8px 14px;
font-size: 0.84rem;
cursor: pointer;
min-width: 72px;
margin: 0;
border-radius: 0;
font-weight: 500;
}
.cond-seg-btn + .cond-seg-btn { border-left: 1px solid var(--line); }
.cond-seg-btn.on {
background: rgba(56, 189, 248, 0.16);
color: var(--text);
font-weight: 650;
}
.cond-param-row {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 12px 14px;
grid-template-columns: 1.25fr 0.7fr 0.7fr;
gap: 10px;
align-items: end;
}
.cond-grid label {
.cond-field {
display: block;
margin: 0;
font-size: 0.8rem;
font-size: 0.75rem;
color: #b6c7da;
}
.cond-grid input,
.cond-grid select {
.cond-field input {
margin-top: 6px;
width: 100%;
}
.cond-field-mult { min-width: 72px; }
.cond-field-mult input { width: 72px; }
.cond-trigger-hint {
grid-column: 1 / -1;
padding: 9px 12px;
border-radius: 10px;
font-size: 0.82rem;
margin-top: 8px;
padding: 8px 10px;
border-radius: 9px;
font-size: 0.78rem;
line-height: 1.45;
border: 1px solid var(--line);
background: rgba(8, 16, 30, 0.7);
color: var(--text);
}
.cond-trigger-hint.is-put { border-color: rgba(251, 113, 133, 0.35); }
.cond-trigger-hint.is-call { border-color: rgba(52, 211, 153, 0.35); }
.cond-trigger-hint.is-put { border-color: rgba(251, 113, 133, 0.35); color: #fda4af; }
.cond-trigger-hint.is-call { border-color: rgba(52, 211, 153, 0.35); color: #6ee7b7; }
.cond-exit-row {
display: grid;
grid-template-columns: 1fr auto auto;
gap: 10px;
align-items: end;
}
.cond-switch {
display: flex !important;
align-items: center;
gap: 8px;
min-height: 62px;
margin: 0;
padding: 8px 0;
font-size: 0.84rem;
color: var(--text);
white-space: nowrap;
min-height: 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-right-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 8px;
margin-bottom: 4px;
}
.cond-right-head .cond-col-title { margin: 0; }
.cond-right-meta { font-size: 0.76rem; }
.cond-right-lead {
margin: 0 0 10px;
font-size: 0.76rem;
line-height: 1.4;
}
.cond-inst-group + .cond-inst-group { margin-top: 10px; }
.cond-inst-group-label { margin-bottom: 6px; }
.cond-inst-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 10px;
display: flex;
flex-direction: column;
gap: 8px;
}
.cond-inst-row {
display: block;
@@ -1533,10 +1597,10 @@ input.readonly {
}
.cond-inst-row.is-off { opacity: 0.5; }
.cond-inst {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 8px;
display: grid;
grid-template-columns: auto 1fr auto;
gap: 8px 10px;
align-items: start;
min-width: 0;
padding: 10px 12px;
border-radius: 10px;
@@ -1552,41 +1616,51 @@ input.readonly {
border-color: rgba(56, 189, 248, 0.55);
background: rgba(56, 189, 248, 0.12);
}
.cond-inst input { width: auto; margin: 0; flex-shrink: 0; }
.cond-inst-head {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
cursor: pointer;
.cond-inst > input[type="checkbox"] {
width: auto;
margin: 2px 0 0;
flex-shrink: 0;
grid-column: 1;
grid-row: 1;
align-self: start;
}
.cond-inst-main {
min-width: 0;
grid-column: 2;
grid-row: 1;
}
.cond-inst.is-off .cond-inst-head { cursor: not-allowed; }
.cond-inst-name {
flex: 1;
font-weight: 600;
display: block;
font-weight: 650;
font-size: 0.92rem;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
}
.cond-inst-tag { color: var(--muted); font-size: 0.75rem; flex-shrink: 0; }
.cond-inst-body {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
padding-left: 24px;
min-width: 0;
.cond-inst.is-off .cond-inst-name { cursor: not-allowed; }
.cond-inst-tag {
color: var(--muted);
font-size: 0.72rem;
flex-shrink: 0;
justify-self: end;
align-self: start;
grid-column: 3;
grid-row: 1;
}
.cond-inst-fund-line {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px 12px;
gap: 6px 12px;
margin-top: 3px;
cursor: pointer;
}
.cond-inst.is-off .cond-inst-fund-line { cursor: default; }
.cond-inst-funds {
color: var(--accent-2, #38bdf8);
font-size: 0.78rem;
font-size: 0.76rem;
font-weight: 600;
white-space: nowrap;
flex-shrink: 0;
@@ -1596,11 +1670,15 @@ input.readonly {
font-weight: 500;
}
.cond-inst-actions {
display: flex;
display: none;
align-items: center;
flex-wrap: wrap;
gap: 6px;
grid-column: 2 / -1;
grid-row: 2;
padding-top: 2px;
}
.cond-inst.is-xfer-open .cond-inst-actions { display: flex; }
.cond-inst-xfer {
flex-shrink: 0;
white-space: nowrap;
@@ -1615,6 +1693,18 @@ input.readonly {
align-items: center;
gap: 12px;
margin-top: 14px;
padding-top: 12px;
border-top: 1px solid rgba(56, 189, 248, 0.12);
}
@media (max-width: 900px) {
.cond-create-split { grid-template-columns: 1fr; }
.cond-param-row { grid-template-columns: 1fr 1fr 1fr; }
.cond-exit-row { grid-template-columns: 1fr auto auto; }
}
@media (max-width: 560px) {
.cond-param-row,
.cond-exit-row { grid-template-columns: 1fr; }
.cond-field-mult input { width: 100%; }
}
.cond-empty { padding: 28px 8px; text-align: center; }
.cond-st {
@@ -2179,16 +2269,16 @@ html.hub-phone-html body.page-cond-orders .cond-card {
html.hub-phone-html body.page-cond-orders .cond-card-head {
margin-bottom: 10px;
}
html.hub-phone-html body.page-cond-orders .cond-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px 12px;
}
html.hub-phone-html body.page-cond-orders .cond-switch {
min-height: 0;
grid-column: 1 / -1;
}
html.hub-phone-html body.page-cond-orders .cond-inst-grid {
html.hub-phone-html body.page-cond-orders .cond-create-split {
grid-template-columns: 1fr;
gap: 14px;
}
html.hub-phone-html body.page-cond-orders .cond-param-row,
html.hub-phone-html body.page-cond-orders .cond-exit-row {
grid-template-columns: 1fr;
}
html.hub-phone-html body.page-cond-orders .cond-field-mult input {
width: 100%;
}
html.hub-phone-html body.page-cond-orders .cond-actions {
flex-direction: column;
+1 -1
View File
@@ -11,7 +11,7 @@
<link rel="icon" href="{{ url_for('static', filename='icons/favicon.ico') }}?v=2" sizes="any">
<link rel="apple-touch-icon" href="{{ url_for('static', filename='icons/apple-touch-icon.png') }}?v=2">
<link rel="manifest" href="/manifest.webmanifest">
<link rel="stylesheet" href="{{ url_for('static', filename='hub.css') }}?v=52">
<link rel="stylesheet" href="{{ url_for('static', filename='hub.css') }}?v=53">
<script>
(function () {
try {
+55 -35
View File
@@ -22,55 +22,75 @@
<span class="cond-chip">左侧单</span>
</div>
<div class="cond-section">
<div class="cond-section-title">入场</div>
<div class="cond-grid">
<label>标的
<select id="cf-underlying">
<option value="ETH">ETH</option>
<div class="cond-create-split">
<div class="cond-create-left">
<div class="cond-col-title">策略</div>
<div class="cond-block">
<span class="cond-mini-label">标的</span>
<div class="cond-seg" id="cf-uly-seg" role="group" aria-label="标的">
<button type="button" class="cond-seg-btn on" data-uly="ETH">ETH</button>
<button type="button" class="cond-seg-btn" data-uly="BTC">BTC</button>
</div>
<select id="cf-underlying" class="cond-native-select" hidden>
<option value="ETH" selected>ETH</option>
<option value="BTC">BTC</option>
</select>
</label>
<label>方向
<select id="cf-opt-type">
<option value="P">看跌 Put</option>
<option value="C">看涨 Call</option>
</select>
</label>
<label>触发价
<input id="cf-trigger-px" type="number" step="any" placeholder="如 3000" required>
</label>
<label>偏离 %
<input id="cf-deviation" type="number" step="0.1" value="1" placeholder="1" required>
</label>
<label>最短到期(小时)
<input id="cf-expiry-h" type="number" step="1" value="24" placeholder="24" required>
</label>
<div class="cond-trigger-hint" id="cf-trigger-hint"></div>
</div>
</div>
<div class="cond-section">
<div class="cond-section-title">出场</div>
<div class="cond-grid">
<label>目标指数(可选)
<input id="cf-target" type="number" step="any" placeholder="如 2800">
<div class="cond-block">
<span class="cond-mini-label">方向</span>
<div class="cond-seg" id="cf-side-seg" role="group" aria-label="方向">
<button type="button" class="cond-seg-btn on" data-side="P">看跌 Put</button>
<button type="button" class="cond-seg-btn" data-side="C">看涨 Call</button>
</div>
<select id="cf-opt-type" class="cond-native-select" hidden>
<option value="P" selected>看跌 Put</option>
<option value="C">看涨 Call</option>
</select>
</div>
<div class="cond-block">
<div class="cond-param-row">
<label class="cond-field">触发价
<input id="cf-trigger-px" type="number" step="any" placeholder="如 3000" required>
</label>
<label>翻倍倍数
<input id="cf-pe-mult" type="number" step="0.1" value="1" placeholder="1">
<label class="cond-field">偏离 %
<input id="cf-deviation" type="number" step="0.1" value="1" placeholder="1" required>
</label>
<label class="cond-field">最短到期 h
<input id="cf-expiry-h" type="number" step="1" value="24" placeholder="24" required>
</label>
</div>
<div class="cond-trigger-hint is-put" id="cf-trigger-hint"></div>
</div>
<div class="cond-block">
<div class="cond-col-title">出场</div>
<div class="cond-exit-row">
<label class="cond-field">目标指数(可选)
<input id="cf-target" type="number" step="any" placeholder="如 2800">
</label>
<label class="cond-switch">
<input id="cf-pe" type="checkbox" checked>
<span>翻倍出场</span>
</label>
<label class="cond-field cond-field-mult">倍数
<input id="cf-pe-mult" type="number" step="0.1" value="1" placeholder="1">
</label>
</div>
</div>
</div>
<div class="cond-section">
<div class="cond-section-title">客户端</div>
<p class="muted" style="margin:0 0 8px;font-size:.76rem">按上方标的过滤客户端(读实例标的白名单)。展示交易/资金余额;「划入交易 / 划入资金」本页双向划转,表单不丢。</p>
<div class="cond-create-right">
<div class="cond-right-head">
<div class="cond-col-title">下发到客户端</div>
<div class="muted cond-right-meta" id="cf-inst-meta">按标的过滤</div>
</div>
<p class="muted cond-right-lead">按标的白名单过滤;点余额行可展开本页划转。</p>
<div id="cf-instances"></div>
</div>
</div>
<div class="cond-actions">
<button type="button" class="btn" id="btn-cond-create">下发条件单</button>
@@ -95,6 +115,6 @@
</div>
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='cond_orders.js') }}?v=14"></script>
<script src="{{ url_for('static', filename='cond_orders.js') }}?v=15"></script>
<script>HubCondOrders.boot();</script>
{% endblock %}