中控条件单手机端改为卡片列表加表单,电脑端表格保持不变。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+141
-79
@@ -53,6 +53,135 @@
|
|||||||
return "st-done";
|
return "st-done";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isHubPhone() {
|
||||||
|
return (
|
||||||
|
document.documentElement.classList.contains("hub-phone-html") ||
|
||||||
|
!!(document.body && document.body.classList.contains("hub-phone"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function orderTriggerText(o) {
|
||||||
|
const side = o.trigger_side === "lte" ? "下穿≤" : "上穿≥";
|
||||||
|
const px = o.trigger_px != null ? String(o.trigger_px) : "";
|
||||||
|
return side + px;
|
||||||
|
}
|
||||||
|
|
||||||
|
function orderExitText(o) {
|
||||||
|
const bits = [];
|
||||||
|
if (o.profit_exit_enabled) bits.push("翻倍×" + (o.profit_exit_mult || ""));
|
||||||
|
if (o.target_index != null) bits.push("目标" + o.target_index);
|
||||||
|
return bits.join(" · ");
|
||||||
|
}
|
||||||
|
|
||||||
|
function orderActionHtml(o) {
|
||||||
|
const canCancel = o.status === "armed" || o.status === "triggering";
|
||||||
|
const canExit = o.status === "open";
|
||||||
|
let actions = "";
|
||||||
|
if (canCancel) {
|
||||||
|
actions +=
|
||||||
|
'<button type="button" class="btn btn-sm btn-danger" data-cancel="' +
|
||||||
|
encodeURIComponent(o.hub_order_id || "") +
|
||||||
|
'" data-iid="' +
|
||||||
|
encodeURIComponent(String(o.instance_id || "")) +
|
||||||
|
'">取消</button>';
|
||||||
|
}
|
||||||
|
if (canExit) {
|
||||||
|
actions +=
|
||||||
|
'<button type="button" class="btn btn-sm" data-exit="' +
|
||||||
|
encodeURIComponent(o.hub_order_id || "") +
|
||||||
|
'" data-iid="' +
|
||||||
|
encodeURIComponent(String(o.instance_id || "")) +
|
||||||
|
'" data-target="' +
|
||||||
|
encodeURIComponent(o.target_index != null ? String(o.target_index) : "") +
|
||||||
|
'" data-pe="' +
|
||||||
|
(o.profit_exit_enabled ? "1" : "0") +
|
||||||
|
'" data-mult="' +
|
||||||
|
encodeURIComponent(String(o.profit_exit_mult || "1")) +
|
||||||
|
'">改出场</button>';
|
||||||
|
}
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindOrderActions(box) {
|
||||||
|
if (!box) return;
|
||||||
|
box.querySelectorAll("[data-cancel]").forEach(function (btn) {
|
||||||
|
btn.addEventListener("click", async function () {
|
||||||
|
if (!confirm("确认取消该条件单?")) return;
|
||||||
|
const oid = decodeURIComponent(btn.getAttribute("data-cancel") || "");
|
||||||
|
const iid = decodeURIComponent(btn.getAttribute("data-iid") || "");
|
||||||
|
const rr = await fetch("/api/cond_orders/" + encodeURIComponent(oid) + "/cancel", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ instance_id: Number(iid) }),
|
||||||
|
});
|
||||||
|
const dd = await rr.json();
|
||||||
|
alert(dd.msg || (dd.ok ? "已取消" : "失败"));
|
||||||
|
refresh();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
box.querySelectorAll("[data-exit]").forEach(function (btn) {
|
||||||
|
btn.addEventListener("click", async function () {
|
||||||
|
const oid = decodeURIComponent(btn.getAttribute("data-exit") || "");
|
||||||
|
const iid = decodeURIComponent(btn.getAttribute("data-iid") || "");
|
||||||
|
const curT = decodeURIComponent(btn.getAttribute("data-target") || "");
|
||||||
|
const curM = decodeURIComponent(btn.getAttribute("data-mult") || "1");
|
||||||
|
const pe = btn.getAttribute("data-pe") === "1";
|
||||||
|
const t = prompt("目标指数(留空清除)", curT);
|
||||||
|
if (t === null) return;
|
||||||
|
const peOn = confirm("是否启用翻倍出场?\n当前:" + (pe ? "开" : "关"));
|
||||||
|
let mult = curM;
|
||||||
|
if (peOn) {
|
||||||
|
const m = prompt("翻倍倍数", curM);
|
||||||
|
if (m === null) return;
|
||||||
|
mult = m;
|
||||||
|
}
|
||||||
|
const rr = await fetch("/api/cond_orders/" + encodeURIComponent(oid) + "/exit", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
instance_id: Number(iid),
|
||||||
|
target_index: t === "" ? "" : t,
|
||||||
|
profit_exit_enabled: peOn,
|
||||||
|
profit_exit_mult: mult,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const dd = await rr.json();
|
||||||
|
alert(dd.msg || (dd.ok ? "已更新" : "失败"));
|
||||||
|
refresh();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPhoneOrderCard(o) {
|
||||||
|
const exit = orderExitText(o);
|
||||||
|
const meta = [];
|
||||||
|
if (o.deviation_pct != null) meta.push("偏离 " + o.deviation_pct + "%");
|
||||||
|
if (o.min_expiry_hours != null) meta.push("到期 " + o.min_expiry_hours + "h");
|
||||||
|
if (exit) meta.push(exit);
|
||||||
|
const actions = orderActionHtml(o);
|
||||||
|
return (
|
||||||
|
'<article class="hub-phone-cond">' +
|
||||||
|
'<div class="hub-phone-cond-head">' +
|
||||||
|
"<strong>" +
|
||||||
|
escapeHtml(o.instance_name || "") +
|
||||||
|
"</strong>" +
|
||||||
|
'<span class="cond-st ' +
|
||||||
|
statusClass(o) +
|
||||||
|
'">' +
|
||||||
|
escapeHtml(statusLabel(o)) +
|
||||||
|
"</span></div>" +
|
||||||
|
'<div class="hub-phone-cond-main">' +
|
||||||
|
escapeHtml((o.underlying || "") + " " + (o.opt_type === "C" ? "Call" : "Put")) +
|
||||||
|
" · " +
|
||||||
|
escapeHtml(orderTriggerText(o)) +
|
||||||
|
"</div>" +
|
||||||
|
(meta.length ? '<div class="hub-phone-cond-meta muted">' + escapeHtml(meta.join(" · ")) + "</div>" : "") +
|
||||||
|
(o.message ? '<div class="hub-phone-cond-msg muted">' + escapeHtml(o.message) + "</div>" : "") +
|
||||||
|
(actions ? '<div class="hub-phone-cond-actions">' + actions + "</div>" : "") +
|
||||||
|
"</article>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function instanceMode(it) {
|
function instanceMode(it) {
|
||||||
const m = String((it && it.mode) || "").toLowerCase();
|
const m = String((it && it.mode) || "").toLowerCase();
|
||||||
return m === "sim" || m === "live" ? m : "";
|
return m === "sim" || m === "live" ? m : "";
|
||||||
@@ -155,41 +284,20 @@
|
|||||||
box.innerHTML = '<div class="cond-empty">暂无条件单</div>';
|
box.innerHTML = '<div class="cond-empty">暂无条件单</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isHubPhone()) {
|
||||||
|
box.innerHTML =
|
||||||
|
'<div class="hub-phone-cond-list">' +
|
||||||
|
list.map(renderPhoneOrderCard).join("") +
|
||||||
|
"</div>";
|
||||||
|
bindOrderActions(box);
|
||||||
|
return;
|
||||||
|
}
|
||||||
box.innerHTML =
|
box.innerHTML =
|
||||||
'<div class="overview-table-wrap cond-table-wrap"><table class="overview-table"><thead><tr>' +
|
'<div class="overview-table-wrap cond-table-wrap"><table class="overview-table"><thead><tr>' +
|
||||||
"<th>客户端</th><th>状态</th><th>标的</th><th>触发</th><th>偏离</th><th>到期h</th><th>出场</th><th>说明</th><th>操作</th>" +
|
"<th>客户端</th><th>状态</th><th>标的</th><th>触发</th><th>偏离</th><th>到期h</th><th>出场</th><th>说明</th><th>操作</th>" +
|
||||||
"</tr></thead><tbody>" +
|
"</tr></thead><tbody>" +
|
||||||
list
|
list
|
||||||
.map(function (o) {
|
.map(function (o) {
|
||||||
const side = o.trigger_side === "lte" ? "下穿 ≤" : "上穿 ≥";
|
|
||||||
const exit =
|
|
||||||
(o.profit_exit_enabled ? "翻倍×" + (o.profit_exit_mult || "") : "") +
|
|
||||||
(o.target_index != null ? " 目标" + o.target_index : "");
|
|
||||||
const canCancel = o.status === "armed" || o.status === "triggering";
|
|
||||||
const canExit = o.status === "open";
|
|
||||||
let actions = "";
|
|
||||||
if (canCancel) {
|
|
||||||
actions +=
|
|
||||||
'<button type="button" class="btn btn-sm btn-danger" data-cancel="' +
|
|
||||||
encodeURIComponent(o.hub_order_id || "") +
|
|
||||||
'" data-iid="' +
|
|
||||||
encodeURIComponent(String(o.instance_id || "")) +
|
|
||||||
'">取消</button>';
|
|
||||||
}
|
|
||||||
if (canExit) {
|
|
||||||
actions +=
|
|
||||||
'<button type="button" class="btn btn-sm" data-exit="' +
|
|
||||||
encodeURIComponent(o.hub_order_id || "") +
|
|
||||||
'" data-iid="' +
|
|
||||||
encodeURIComponent(String(o.instance_id || "")) +
|
|
||||||
'" data-target="' +
|
|
||||||
encodeURIComponent(o.target_index != null ? String(o.target_index) : "") +
|
|
||||||
'" data-pe="' +
|
|
||||||
(o.profit_exit_enabled ? "1" : "0") +
|
|
||||||
'" data-mult="' +
|
|
||||||
encodeURIComponent(String(o.profit_exit_mult || "1")) +
|
|
||||||
'">改出场</button>';
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
"<tr>" +
|
"<tr>" +
|
||||||
"<td>" +
|
"<td>" +
|
||||||
@@ -204,7 +312,7 @@
|
|||||||
escapeHtml((o.underlying || "") + " " + (o.opt_type || "")) +
|
escapeHtml((o.underlying || "") + " " + (o.opt_type || "")) +
|
||||||
"</td>" +
|
"</td>" +
|
||||||
"<td>" +
|
"<td>" +
|
||||||
escapeHtml(side + " " + (o.trigger_px != null ? o.trigger_px : "")) +
|
escapeHtml(orderTriggerText(o).replace("≤", " ≤ ").replace("≥", " ≥ ")) +
|
||||||
"</td>" +
|
"</td>" +
|
||||||
"<td>" +
|
"<td>" +
|
||||||
escapeHtml(o.deviation_pct != null ? o.deviation_pct + "%" : "—") +
|
escapeHtml(o.deviation_pct != null ? o.deviation_pct + "%" : "—") +
|
||||||
@@ -213,66 +321,20 @@
|
|||||||
escapeHtml(o.min_expiry_hours != null ? String(o.min_expiry_hours) : "—") +
|
escapeHtml(o.min_expiry_hours != null ? String(o.min_expiry_hours) : "—") +
|
||||||
"</td>" +
|
"</td>" +
|
||||||
"<td>" +
|
"<td>" +
|
||||||
escapeHtml(exit || "—") +
|
escapeHtml(orderExitText(o) || "—") +
|
||||||
"</td>" +
|
"</td>" +
|
||||||
'<td class="muted">' +
|
'<td class="muted">' +
|
||||||
escapeHtml(o.message || "") +
|
escapeHtml(o.message || "") +
|
||||||
"</td>" +
|
"</td>" +
|
||||||
'<td><div class="overview-actions">' +
|
'<td><div class="overview-actions">' +
|
||||||
actions +
|
orderActionHtml(o) +
|
||||||
"</div></td>" +
|
"</div></td>" +
|
||||||
"</tr>"
|
"</tr>"
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.join("") +
|
.join("") +
|
||||||
"</tbody></table></div>";
|
"</tbody></table></div>";
|
||||||
|
bindOrderActions(box);
|
||||||
box.querySelectorAll("[data-cancel]").forEach(function (btn) {
|
|
||||||
btn.addEventListener("click", async function () {
|
|
||||||
if (!confirm("确认取消该条件单?")) return;
|
|
||||||
const oid = decodeURIComponent(btn.getAttribute("data-cancel") || "");
|
|
||||||
const iid = decodeURIComponent(btn.getAttribute("data-iid") || "");
|
|
||||||
const rr = await fetch("/api/cond_orders/" + encodeURIComponent(oid) + "/cancel", {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({ instance_id: Number(iid) }),
|
|
||||||
});
|
|
||||||
const dd = await rr.json();
|
|
||||||
alert(dd.msg || (dd.ok ? "已取消" : "失败"));
|
|
||||||
refresh();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
box.querySelectorAll("[data-exit]").forEach(function (btn) {
|
|
||||||
btn.addEventListener("click", async function () {
|
|
||||||
const oid = decodeURIComponent(btn.getAttribute("data-exit") || "");
|
|
||||||
const iid = decodeURIComponent(btn.getAttribute("data-iid") || "");
|
|
||||||
const curT = decodeURIComponent(btn.getAttribute("data-target") || "");
|
|
||||||
const curM = decodeURIComponent(btn.getAttribute("data-mult") || "1");
|
|
||||||
const pe = btn.getAttribute("data-pe") === "1";
|
|
||||||
const t = prompt("目标指数(留空清除)", curT);
|
|
||||||
if (t === null) return;
|
|
||||||
const peOn = confirm("是否启用翻倍出场?\n当前:" + (pe ? "开" : "关"));
|
|
||||||
let mult = curM;
|
|
||||||
if (peOn) {
|
|
||||||
const m = prompt("翻倍倍数", curM);
|
|
||||||
if (m === null) return;
|
|
||||||
mult = m;
|
|
||||||
}
|
|
||||||
const rr = await fetch("/api/cond_orders/" + encodeURIComponent(oid) + "/exit", {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({
|
|
||||||
instance_id: Number(iid),
|
|
||||||
target_index: t === "" ? "" : t,
|
|
||||||
profit_exit_enabled: peOn,
|
|
||||||
profit_exit_mult: mult,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
const dd = await rr.json();
|
|
||||||
alert(dd.msg || (dd.ok ? "已更新" : "失败"));
|
|
||||||
refresh();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createOrder() {
|
async function createOrder() {
|
||||||
|
|||||||
@@ -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
@@ -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 {
|
||||||
|
|||||||
@@ -80,6 +80,6 @@
|
|||||||
</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 %}
|
||||||
|
|||||||
Reference in New Issue
Block a user