中控条件单手机端改为卡片列表加表单,电脑端表格保持不变。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-05 19:29:33 +08:00
parent a835e3aeec
commit 747ec62e34
4 changed files with 555 additions and 419 deletions
+395 -333
View File
@@ -1,333 +1,395 @@
(function (global) { (function (global) {
function escapeHtml(s) { function escapeHtml(s) {
return String(s) return String(s)
.replace(/&/g, "&amp;") .replace(/&/g, "&amp;")
.replace(/</g, "&lt;") .replace(/</g, "&lt;")
.replace(/>/g, "&gt;") .replace(/>/g, "&gt;")
.replace(/"/g, "&quot;"); .replace(/"/g, "&quot;");
} }
function leftTriggerSide(optType) { function leftTriggerSide(optType) {
return String(optType || "P").toUpperCase() === "C" ? "lte" : "gte"; return String(optType || "P").toUpperCase() === "C" ? "lte" : "gte";
} }
function triggerHintText(optType, px) { function triggerHintText(optType, px) {
const put = String(optType || "P").toUpperCase() !== "C"; const put = String(optType || "P").toUpperCase() !== "C";
const price = px && String(px).trim() !== "" ? String(px) : "触发价"; const price = px && String(px).trim() !== "" ? String(px) : "触发价";
if (put) { if (put) {
return "看跌左侧单:指数上穿 " + price + " 后,在偏离区内开最近实值 Put。"; return "看跌左侧单:指数上穿 " + price + " 后,在偏离区内开最近实值 Put。";
} }
return "看涨左侧单:指数下穿 " + price + " 后,在偏离区内开最近实值 Call。"; return "看涨左侧单:指数下穿 " + price + " 后,在偏离区内开最近实值 Call。";
} }
function syncTriggerHint() { function syncTriggerHint() {
const hint = document.getElementById("cf-trigger-hint"); const hint = document.getElementById("cf-trigger-hint");
const typeEl = document.getElementById("cf-opt-type"); const typeEl = document.getElementById("cf-opt-type");
const pxEl = document.getElementById("cf-trigger-px"); const pxEl = document.getElementById("cf-trigger-px");
if (!hint || !typeEl) return; if (!hint || !typeEl) return;
hint.textContent = triggerHintText(typeEl.value, pxEl && pxEl.value); hint.textContent = triggerHintText(typeEl.value, pxEl && pxEl.value);
hint.className = "cond-trigger-hint " + (typeEl.value === "C" ? "is-call" : "is-put"); hint.className = "cond-trigger-hint " + (typeEl.value === "C" ? "is-call" : "is-put");
} }
function statusLabel(o) { function statusLabel(o) {
const st = o.status || ""; const st = o.status || "";
const reason = o.end_reason || ""; const reason = o.end_reason || "";
if (st === "done") { if (st === "done") {
const map = { const map = {
leave_up: "结束·上破", leave_up: "结束·上破",
leave_down: "结束·下破", leave_down: "结束·下破",
cancel: "已取消", cancel: "已取消",
closed: "已完成", closed: "已完成",
}; };
return map[reason] || "已结束"; return map[reason] || "已结束";
} }
const map2 = { armed: "待命", triggering: "触发中", open: "持仓中" }; const map2 = { armed: "待命", triggering: "触发中", open: "持仓中" };
return map2[st] || st || "—"; return map2[st] || st || "—";
} }
function statusClass(o) { function statusClass(o) {
const st = o.status || ""; const st = o.status || "";
if (st === "open") return "st-open"; if (st === "open") return "st-open";
if (st === "triggering") return "st-trig"; if (st === "triggering") return "st-trig";
if (st === "armed") return "st-armed"; if (st === "armed") return "st-armed";
return "st-done"; return "st-done";
} }
function instanceMode(it) { function isHubPhone() {
const m = String((it && it.mode) || "").toLowerCase(); return (
return m === "sim" || m === "live" ? m : ""; document.documentElement.classList.contains("hub-phone-html") ||
} !!(document.body && document.body.classList.contains("hub-phone"))
);
function renderCandidateCard(it, checkedIds) { }
const disabled = !it.selectable;
const tip = disabled function orderTriggerText(o) {
? it.has_position const side = o.trigger_side === "lte" ? "下穿≤" : "上穿≥";
? "已有持仓" const px = o.trigger_px != null ? String(o.trigger_px) : "";
: it.cond_order_locked return side + px;
? "条件单占用" }
: it.online
? "不可选" function orderExitText(o) {
: "离线" const bits = [];
: "可选"; if (o.profit_exit_enabled) bits.push("翻倍×" + (o.profit_exit_mult || ""));
const id = String(it.id); if (o.target_index != null) bits.push("目标" + o.target_index);
const checked = !disabled && checkedIds.indexOf(id) >= 0; return bits.join(" · ");
return ( }
'<label class="cond-inst' +
(disabled ? " is-off" : "") + function orderActionHtml(o) {
'" title="' + const canCancel = o.status === "armed" || o.status === "triggering";
escapeHtml(tip) + const canExit = o.status === "open";
'">' + let actions = "";
'<input type="checkbox" name="cf-inst" value="' + if (canCancel) {
encodeURIComponent(id) + actions +=
'"' + '<button type="button" class="btn btn-sm btn-danger" data-cancel="' +
(disabled ? " disabled" : "") + encodeURIComponent(o.hub_order_id || "") +
(checked ? " checked" : "") + '" data-iid="' +
">" + encodeURIComponent(String(o.instance_id || "")) +
'<span class="cond-inst-name">' + '">取消</button>';
escapeHtml(it.name || "") + }
"</span>" + if (canExit) {
'<span class="cond-inst-tag">' + actions +=
escapeHtml(tip) + '<button type="button" class="btn btn-sm" data-exit="' +
"</span></label>" encodeURIComponent(o.hub_order_id || "") +
); '" data-iid="' +
} encodeURIComponent(String(o.instance_id || "")) +
'" data-target="' +
function renderCandidateGroup(mode, items, checkedIds) { encodeURIComponent(o.target_index != null ? String(o.target_index) : "") +
if (!items.length) return ""; '" data-pe="' +
const label = (o.profit_exit_enabled ? "1" : "0") +
mode === "sim" || mode === "live" '" data-mult="' +
? '<div class="cond-inst-group-label"><span class="overview-mode-tag overview-mode-tag--' + encodeURIComponent(String(o.profit_exit_mult || "1")) +
mode + '">改出场</button>';
'">' + }
mode + return actions;
"</span></div>" }
: "";
return ( function bindOrderActions(box) {
'<div class="cond-inst-group">' + if (!box) return;
label + box.querySelectorAll("[data-cancel]").forEach(function (btn) {
'<div class="cond-inst-grid">' + btn.addEventListener("click", async function () {
items if (!confirm("确认取消该条件单?")) return;
.map(function (it) { const oid = decodeURIComponent(btn.getAttribute("data-cancel") || "");
return renderCandidateCard(it, checkedIds); const iid = decodeURIComponent(btn.getAttribute("data-iid") || "");
}) const rr = await fetch("/api/cond_orders/" + encodeURIComponent(oid) + "/cancel", {
.join("") + method: "POST",
"</div></div>" headers: { "Content-Type": "application/json" },
); body: JSON.stringify({ instance_id: Number(iid) }),
} });
const dd = await rr.json();
async function loadCandidates() { alert(dd.msg || (dd.ok ? "已取消" : "失败"));
const box = document.getElementById("cf-instances"); refresh();
if (!box) return; });
const checkedIds = Array.prototype.slice });
.call(box.querySelectorAll('input[name="cf-inst"]:checked')) box.querySelectorAll("[data-exit]").forEach(function (btn) {
.map(function (el) { btn.addEventListener("click", async function () {
return decodeURIComponent(el.value || ""); const oid = decodeURIComponent(btn.getAttribute("data-exit") || "");
}); const iid = decodeURIComponent(btn.getAttribute("data-iid") || "");
const r = await fetch("/api/cond_orders/candidates"); const curT = decodeURIComponent(btn.getAttribute("data-target") || "");
const d = await r.json(); const curM = decodeURIComponent(btn.getAttribute("data-mult") || "1");
const list = d.instances || []; const pe = btn.getAttribute("data-pe") === "1";
if (!list.length) { const t = prompt("目标指数(留空清除)", curT);
box.innerHTML = '<span class="muted">暂无启用实例</span>'; if (t === null) return;
return; const peOn = confirm("是否启用翻倍出场?\n当前:" + (pe ? "开" : "关"));
} let mult = curM;
const sim = []; if (peOn) {
const live = []; const m = prompt("翻倍倍数", curM);
const other = []; if (m === null) return;
list.forEach(function (it) { mult = m;
const m = instanceMode(it); }
if (m === "sim") sim.push(it); const rr = await fetch("/api/cond_orders/" + encodeURIComponent(oid) + "/exit", {
else if (m === "live") live.push(it); method: "POST",
else other.push(it); headers: { "Content-Type": "application/json" },
}); body: JSON.stringify({
box.innerHTML = instance_id: Number(iid),
renderCandidateGroup("sim", sim, checkedIds) + target_index: t === "" ? "" : t,
renderCandidateGroup("live", live, checkedIds) + profit_exit_enabled: peOn,
renderCandidateGroup("", other, checkedIds); profit_exit_mult: mult,
} }),
});
async function loadOrders() { const dd = await rr.json();
const box = document.getElementById("cond-list"); alert(dd.msg || (dd.ok ? "已更新" : "失败"));
if (!box) return; refresh();
const r = await fetch("/api/cond_orders"); });
const d = await r.json(); });
const list = d.orders || []; }
if (!list.length) {
box.innerHTML = '<div class="cond-empty">暂无条件单</div>'; function renderPhoneOrderCard(o) {
return; const exit = orderExitText(o);
} const meta = [];
box.innerHTML = if (o.deviation_pct != null) meta.push("偏离 " + o.deviation_pct + "%");
'<div class="overview-table-wrap cond-table-wrap"><table class="overview-table"><thead><tr>' + if (o.min_expiry_hours != null) meta.push("到期 " + o.min_expiry_hours + "h");
"<th>客户端</th><th>状态</th><th>标的</th><th>触发</th><th>偏离</th><th>到期h</th><th>出场</th><th>说明</th><th>操作</th>" + if (exit) meta.push(exit);
"</tr></thead><tbody>" + const actions = orderActionHtml(o);
list return (
.map(function (o) { '<article class="hub-phone-cond">' +
const side = o.trigger_side === "lte" ? "下穿 ≤" : "上穿 ≥"; '<div class="hub-phone-cond-head">' +
const exit = "<strong>" +
(o.profit_exit_enabled ? "翻倍×" + (o.profit_exit_mult || "") : "") + escapeHtml(o.instance_name || "") +
(o.target_index != null ? " 目标" + o.target_index : ""); "</strong>" +
const canCancel = o.status === "armed" || o.status === "triggering"; '<span class="cond-st ' +
const canExit = o.status === "open"; statusClass(o) +
let actions = ""; '">' +
if (canCancel) { escapeHtml(statusLabel(o)) +
actions += "</span></div>" +
'<button type="button" class="btn btn-sm btn-danger" data-cancel="' + '<div class="hub-phone-cond-main">' +
encodeURIComponent(o.hub_order_id || "") + escapeHtml((o.underlying || "") + " " + (o.opt_type === "C" ? "Call" : "Put")) +
'" data-iid="' + " · " +
encodeURIComponent(String(o.instance_id || "")) + escapeHtml(orderTriggerText(o)) +
'">取消</button>'; "</div>" +
} (meta.length ? '<div class="hub-phone-cond-meta muted">' + escapeHtml(meta.join(" · ")) + "</div>" : "") +
if (canExit) { (o.message ? '<div class="hub-phone-cond-msg muted">' + escapeHtml(o.message) + "</div>" : "") +
actions += (actions ? '<div class="hub-phone-cond-actions">' + actions + "</div>" : "") +
'<button type="button" class="btn btn-sm" data-exit="' + "</article>"
encodeURIComponent(o.hub_order_id || "") + );
'" data-iid="' + }
encodeURIComponent(String(o.instance_id || "")) +
'" data-target="' + function instanceMode(it) {
encodeURIComponent(o.target_index != null ? String(o.target_index) : "") + const m = String((it && it.mode) || "").toLowerCase();
'" data-pe="' + return m === "sim" || m === "live" ? m : "";
(o.profit_exit_enabled ? "1" : "0") + }
'" data-mult="' +
encodeURIComponent(String(o.profit_exit_mult || "1")) + function renderCandidateCard(it, checkedIds) {
'">改出场</button>'; const disabled = !it.selectable;
} const tip = disabled
return ( ? it.has_position
"<tr>" + ? "已有持仓"
"<td>" + : it.cond_order_locked
escapeHtml(o.instance_name || "") + ? "条件单占用"
"</td>" + : it.online
'<td><span class="cond-st ' + ? "不可选"
statusClass(o) + : "离线"
'">' + : "可选";
escapeHtml(statusLabel(o)) + const id = String(it.id);
"</span></td>" + const checked = !disabled && checkedIds.indexOf(id) >= 0;
"<td>" + return (
escapeHtml((o.underlying || "") + " " + (o.opt_type || "")) + '<label class="cond-inst' +
"</td>" + (disabled ? " is-off" : "") +
"<td>" + '" title="' +
escapeHtml(side + " " + (o.trigger_px != null ? o.trigger_px : "")) + escapeHtml(tip) +
"</td>" + '">' +
"<td>" + '<input type="checkbox" name="cf-inst" value="' +
escapeHtml(o.deviation_pct != null ? o.deviation_pct + "%" : "—") + encodeURIComponent(id) +
"</td>" + '"' +
"<td>" + (disabled ? " disabled" : "") +
escapeHtml(o.min_expiry_hours != null ? String(o.min_expiry_hours) : "—") + (checked ? " checked" : "") +
"</td>" + ">" +
"<td>" + '<span class="cond-inst-name">' +
escapeHtml(exit || "—") + escapeHtml(it.name || "") +
"</td>" + "</span>" +
'<td class="muted">' + '<span class="cond-inst-tag">' +
escapeHtml(o.message || "") + escapeHtml(tip) +
"</td>" + "</span></label>"
'<td><div class="overview-actions">' + );
actions + }
"</div></td>" +
"</tr>" function renderCandidateGroup(mode, items, checkedIds) {
); if (!items.length) return "";
}) const label =
.join("") + mode === "sim" || mode === "live"
"</tbody></table></div>"; ? '<div class="cond-inst-group-label"><span class="overview-mode-tag overview-mode-tag--' +
mode +
box.querySelectorAll("[data-cancel]").forEach(function (btn) { '">' +
btn.addEventListener("click", async function () { mode +
if (!confirm("确认取消该条件单?")) return; "</span></div>"
const oid = decodeURIComponent(btn.getAttribute("data-cancel") || ""); : "";
const iid = decodeURIComponent(btn.getAttribute("data-iid") || ""); return (
const rr = await fetch("/api/cond_orders/" + encodeURIComponent(oid) + "/cancel", { '<div class="cond-inst-group">' +
method: "POST", label +
headers: { "Content-Type": "application/json" }, '<div class="cond-inst-grid">' +
body: JSON.stringify({ instance_id: Number(iid) }), items
}); .map(function (it) {
const dd = await rr.json(); return renderCandidateCard(it, checkedIds);
alert(dd.msg || (dd.ok ? "已取消" : "失败")); })
refresh(); .join("") +
}); "</div></div>"
}); );
box.querySelectorAll("[data-exit]").forEach(function (btn) { }
btn.addEventListener("click", async function () {
const oid = decodeURIComponent(btn.getAttribute("data-exit") || ""); async function loadCandidates() {
const iid = decodeURIComponent(btn.getAttribute("data-iid") || ""); const box = document.getElementById("cf-instances");
const curT = decodeURIComponent(btn.getAttribute("data-target") || ""); if (!box) return;
const curM = decodeURIComponent(btn.getAttribute("data-mult") || "1"); const checkedIds = Array.prototype.slice
const pe = btn.getAttribute("data-pe") === "1"; .call(box.querySelectorAll('input[name="cf-inst"]:checked'))
const t = prompt("目标指数(留空清除)", curT); .map(function (el) {
if (t === null) return; return decodeURIComponent(el.value || "");
const peOn = confirm("是否启用翻倍出场?\n当前:" + (pe ? "开" : "关")); });
let mult = curM; const r = await fetch("/api/cond_orders/candidates");
if (peOn) { const d = await r.json();
const m = prompt("翻倍倍数", curM); const list = d.instances || [];
if (m === null) return; if (!list.length) {
mult = m; box.innerHTML = '<span class="muted">暂无启用实例</span>';
} return;
const rr = await fetch("/api/cond_orders/" + encodeURIComponent(oid) + "/exit", { }
method: "POST", const sim = [];
headers: { "Content-Type": "application/json" }, const live = [];
body: JSON.stringify({ const other = [];
instance_id: Number(iid), list.forEach(function (it) {
target_index: t === "" ? "" : t, const m = instanceMode(it);
profit_exit_enabled: peOn, if (m === "sim") sim.push(it);
profit_exit_mult: mult, else if (m === "live") live.push(it);
}), else other.push(it);
}); });
const dd = await rr.json(); box.innerHTML =
alert(dd.msg || (dd.ok ? "已更新" : "失败")); renderCandidateGroup("sim", sim, checkedIds) +
refresh(); renderCandidateGroup("live", live, checkedIds) +
}); renderCandidateGroup("", other, checkedIds);
}); }
}
async function loadOrders() {
async function createOrder() { const box = document.getElementById("cond-list");
const status = document.getElementById("cf-status"); if (!box) return;
const ids = Array.prototype.slice const r = await fetch("/api/cond_orders");
.call(document.querySelectorAll('input[name="cf-inst"]:checked')) const d = await r.json();
.map(function (el) { const list = d.orders || [];
return Number(decodeURIComponent(el.value)); if (!list.length) {
}); box.innerHTML = '<div class="cond-empty">暂无条件单</div>';
if (!ids.length) { return;
alert("请选择至少一台可选客户端"); }
return; if (isHubPhone()) {
} box.innerHTML =
const optType = document.getElementById("cf-opt-type").value; '<div class="hub-phone-cond-list">' +
const body = { list.map(renderPhoneOrderCard).join("") +
instance_ids: ids, "</div>";
underlying: document.getElementById("cf-underlying").value, bindOrderActions(box);
opt_type: optType, return;
trigger_side: leftTriggerSide(optType), }
trigger_px: document.getElementById("cf-trigger-px").value, box.innerHTML =
deviation_pct: document.getElementById("cf-deviation").value, '<div class="overview-table-wrap cond-table-wrap"><table class="overview-table"><thead><tr>' +
min_expiry_hours: document.getElementById("cf-expiry-h").value, "<th>客户端</th><th>状态</th><th>标的</th><th>触发</th><th>偏离</th><th>到期h</th><th>出场</th><th>说明</th><th>操作</th>" +
target_index: document.getElementById("cf-target").value, "</tr></thead><tbody>" +
profit_exit_enabled: document.getElementById("cf-pe").checked, list
profit_exit_mult: document.getElementById("cf-pe-mult").value, .map(function (o) {
}; return (
if (status) status.textContent = "下发中…"; "<tr>" +
const r = await fetch("/api/cond_orders", { "<td>" +
method: "POST", escapeHtml(o.instance_name || "") +
headers: { "Content-Type": "application/json" }, "</td>" +
body: JSON.stringify(body), '<td><span class="cond-st ' +
}); statusClass(o) +
const d = await r.json(); '">' +
if (status) status.textContent = d.msg || ""; escapeHtml(statusLabel(o)) +
alert(d.msg || (d.ok ? "已下发" : "失败")); "</span></td>" +
refresh(); "<td>" +
} escapeHtml((o.underlying || "") + " " + (o.opt_type || "")) +
"</td>" +
async function refresh() { "<td>" +
await Promise.all([loadCandidates(), loadOrders()]); escapeHtml(orderTriggerText(o).replace("≤", " ≤ ").replace("≥", " ≥ ")) +
} "</td>" +
"<td>" +
function boot() { escapeHtml(o.deviation_pct != null ? o.deviation_pct + "%" : "—") +
const btn = document.getElementById("btn-cond-refresh"); "</td>" +
if (btn) btn.addEventListener("click", refresh); "<td>" +
const create = document.getElementById("btn-cond-create"); escapeHtml(o.min_expiry_hours != null ? String(o.min_expiry_hours) : "—") +
if (create) create.addEventListener("click", createOrder); "</td>" +
const typeEl = document.getElementById("cf-opt-type"); "<td>" +
const pxEl = document.getElementById("cf-trigger-px"); escapeHtml(orderExitText(o) || "—") +
if (typeEl) typeEl.addEventListener("change", syncTriggerHint); "</td>" +
if (pxEl) pxEl.addEventListener("input", syncTriggerHint); '<td class="muted">' +
syncTriggerHint(); escapeHtml(o.message || "") +
refresh(); "</td>" +
setInterval(refresh, 10000); '<td><div class="overview-actions">' +
} orderActionHtml(o) +
"</div></td>" +
global.HubCondOrders = { boot: boot, refresh: refresh }; "</tr>"
})(window); );
})
.join("") +
"</tbody></table></div>";
bindOrderActions(box);
}
async function createOrder() {
const status = document.getElementById("cf-status");
const ids = Array.prototype.slice
.call(document.querySelectorAll('input[name="cf-inst"]:checked'))
.map(function (el) {
return Number(decodeURIComponent(el.value));
});
if (!ids.length) {
alert("请选择至少一台可选客户端");
return;
}
const optType = document.getElementById("cf-opt-type").value;
const body = {
instance_ids: ids,
underlying: document.getElementById("cf-underlying").value,
opt_type: optType,
trigger_side: leftTriggerSide(optType),
trigger_px: document.getElementById("cf-trigger-px").value,
deviation_pct: document.getElementById("cf-deviation").value,
min_expiry_hours: document.getElementById("cf-expiry-h").value,
target_index: document.getElementById("cf-target").value,
profit_exit_enabled: document.getElementById("cf-pe").checked,
profit_exit_mult: document.getElementById("cf-pe-mult").value,
};
if (status) status.textContent = "下发中…";
const r = await fetch("/api/cond_orders", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
const d = await r.json();
if (status) status.textContent = d.msg || "";
alert(d.msg || (d.ok ? "已下发" : "失败"));
refresh();
}
async function refresh() {
await Promise.all([loadCandidates(), loadOrders()]);
}
function boot() {
const btn = document.getElementById("btn-cond-refresh");
if (btn) btn.addEventListener("click", refresh);
const create = document.getElementById("btn-cond-create");
if (create) create.addEventListener("click", createOrder);
const typeEl = document.getElementById("cf-opt-type");
const pxEl = document.getElementById("cf-trigger-px");
if (typeEl) typeEl.addEventListener("change", syncTriggerHint);
if (pxEl) pxEl.addEventListener("input", syncTriggerHint);
syncTriggerHint();
refresh();
setInterval(refresh, 10000);
}
global.HubCondOrders = { boot: boot, refresh: refresh };
})(window);
+74
View File
@@ -1979,6 +1979,80 @@ html.hub-phone-html .table-wrap {
overflow-x: auto; overflow-x: auto;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
} }
html.hub-phone-html body.page-cond-orders .cond-lead {
display: none;
}
html.hub-phone-html body.page-cond-orders .cond-layout {
display: flex;
flex-direction: column;
gap: 14px;
}
html.hub-phone-html body.page-cond-orders .cond-layout > .cond-card:last-child {
order: -1;
}
html.hub-phone-html body.page-cond-orders .cond-card {
padding: 14px;
border-radius: 14px;
}
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 {
grid-template-columns: 1fr;
}
html.hub-phone-html body.page-cond-orders .cond-actions {
flex-direction: column;
align-items: stretch;
}
html.hub-phone-html body.page-cond-orders .cond-actions .btn {
width: 100%;
}
.hub-phone-cond-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.hub-phone-cond {
padding: 12px 12px 10px;
border-radius: 12px;
border: 1px solid rgba(56, 189, 248, 0.18);
background: rgba(8, 16, 30, 0.72);
}
.hub-phone-cond-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 6px;
}
.hub-phone-cond-head strong {
font-size: 0.95rem;
}
.hub-phone-cond-main {
font-size: 0.98rem;
font-weight: 600;
line-height: 1.4;
}
.hub-phone-cond-meta,
.hub-phone-cond-msg {
margin-top: 4px;
font-size: 0.78rem;
line-height: 1.4;
}
.hub-phone-cond-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 10px;
}
html.hub-phone-html .login-wrap { html.hub-phone-html .login-wrap {
padding: 48px 16px 24px; padding: 48px 16px 24px;
} }
+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="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="apple-touch-icon" href="{{ url_for('static', filename='icons/apple-touch-icon.png') }}?v=2">
<link rel="manifest" href="/manifest.webmanifest"> <link rel="manifest" href="/manifest.webmanifest">
<link rel="stylesheet" href="{{ url_for('static', filename='hub.css') }}?v=40"> <link rel="stylesheet" href="{{ url_for('static', filename='hub.css') }}?v=41">
<script> <script>
(function () { (function () {
try { try {
+85 -85
View File
@@ -1,85 +1,85 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}条件单 · 期权中控{% endblock %} {% block title %}条件单 · 期权中控{% endblock %}
{% block body %} {% block body %}
<section class="page-head cond-head"> <section class="page-head cond-head">
<div> <div>
<h1>左侧条件开仓</h1> <h1>左侧条件开仓</h1>
<p class="muted cond-lead">中控只下发;盯盘、买币、开平仓都在客户端。有仓或已占用的机器不可选。</p> <p class="muted cond-lead">中控只下发;盯盘、买币、开平仓都在客户端。有仓或已占用的机器不可选。</p>
</div> </div>
<button type="button" class="btn" id="btn-cond-refresh">刷新</button> <button type="button" class="btn" id="btn-cond-refresh">刷新</button>
</section> </section>
<div class="cond-layout"> <div class="cond-layout">
<section class="panel cond-card"> <section class="panel cond-card">
<div class="cond-card-head"> <div class="cond-card-head">
<h2>新建条件单</h2> <h2>新建条件单</h2>
<span class="cond-chip">左侧单</span> <span class="cond-chip">左侧单</span>
</div> </div>
<div class="cond-section"> <div class="cond-section">
<div class="cond-section-title">入场</div> <div class="cond-section-title">入场</div>
<div class="cond-grid"> <div class="cond-grid">
<label>标的 <label>标的
<select id="cf-underlying"> <select id="cf-underlying">
<option value="ETH">ETH</option> <option value="ETH">ETH</option>
<option value="BTC">BTC</option> <option value="BTC">BTC</option>
</select> </select>
</label> </label>
<label>方向 <label>方向
<select id="cf-opt-type"> <select id="cf-opt-type">
<option value="P">看跌 Put</option> <option value="P">看跌 Put</option>
<option value="C">看涨 Call</option> <option value="C">看涨 Call</option>
</select> </select>
</label> </label>
<label>触发价 <label>触发价
<input id="cf-trigger-px" type="number" step="any" placeholder="如 3000" required> <input id="cf-trigger-px" type="number" step="any" placeholder="如 3000" required>
</label> </label>
<label>偏离 % <label>偏离 %
<input id="cf-deviation" type="number" step="0.1" value="1" placeholder="1" required> <input id="cf-deviation" type="number" step="0.1" value="1" placeholder="1" required>
</label> </label>
<label>最短到期(小时) <label>最短到期(小时)
<input id="cf-expiry-h" type="number" step="1" value="24" placeholder="24" required> <input id="cf-expiry-h" type="number" step="1" value="24" placeholder="24" required>
</label> </label>
<div class="cond-trigger-hint" id="cf-trigger-hint"></div> <div class="cond-trigger-hint" id="cf-trigger-hint"></div>
</div> </div>
</div> </div>
<div class="cond-section"> <div class="cond-section">
<div class="cond-section-title">出场</div> <div class="cond-section-title">出场</div>
<div class="cond-grid"> <div class="cond-grid">
<label>目标指数(可选) <label>目标指数(可选)
<input id="cf-target" type="number" step="any" placeholder="如 2800"> <input id="cf-target" type="number" step="any" placeholder="如 2800">
</label> </label>
<label>翻倍倍数 <label>翻倍倍数
<input id="cf-pe-mult" type="number" step="0.1" value="1" placeholder="1"> <input id="cf-pe-mult" type="number" step="0.1" value="1" placeholder="1">
</label> </label>
<label class="cond-switch"> <label class="cond-switch">
<input id="cf-pe" type="checkbox" checked> <input id="cf-pe" type="checkbox" checked>
<span>翻倍出场</span> <span>翻倍出场</span>
</label> </label>
</div> </div>
</div> </div>
<div class="cond-section"> <div class="cond-section">
<div class="cond-section-title">客户端</div> <div class="cond-section-title">客户端</div>
<div id="cf-instances"></div> <div id="cf-instances"></div>
</div> </div>
<div class="cond-actions"> <div class="cond-actions">
<button type="button" class="btn" id="btn-cond-create">下发条件单</button> <button type="button" class="btn" id="btn-cond-create">下发条件单</button>
<span class="muted" id="cf-status"></span> <span class="muted" id="cf-status"></span>
</div> </div>
</section> </section>
<section class="panel cond-card"> <section class="panel cond-card">
<div class="cond-card-head"> <div class="cond-card-head">
<h2>条件单列表</h2> <h2>条件单列表</h2>
</div> </div>
<div id="cond-list" class="muted">加载中…</div> <div id="cond-list" class="muted">加载中…</div>
</section> </section>
</div> </div>
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
<script src="{{ url_for('static', filename='cond_orders.js') }}"></script> <script src="{{ url_for('static', filename='cond_orders.js') }}?v=2"></script>
<script>HubCondOrders.boot();</script> <script>HubCondOrders.boot();</script>
{% endblock %} {% endblock %}