From 38ff40111ac15702c9469a1663e40ce410ec851d Mon Sep 17 00:00:00 2001 From: dekun Date: Mon, 15 Jun 2026 14:30:01 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=B3=E9=94=AE=E4=BD=8D=E4=B8=8E=E4=BB=8A?= =?UTF-8?q?=E6=97=A5=E8=AE=A1=E5=88=92=E5=88=97=E8=A1=A8=E5=AE=9E=E6=97=B6?= =?UTF-8?q?=E7=8E=B0=E4=BB=B7=E5=8F=8A=E8=B7=9D=E5=8C=BA=E9=97=B4=E8=B7=9D?= =?UTF-8?q?=E7=A6=BB=EF=BC=881s=E8=BD=AE=E8=AF=A2=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- app.py | 70 +++++++++++++++++++++++++++++++++++++++++++- static/js/keys.js | 39 ++++++++++++++++++++++++ static/js/plans.js | 45 ++++++++++++++++++++++++++++ templates/base.html | 6 +++- templates/keys.html | 13 ++++++-- templates/plans.html | 11 +++++-- 6 files changed, 177 insertions(+), 7 deletions(-) create mode 100644 static/js/keys.js create mode 100644 static/js/plans.js diff --git a/app.py b/app.py index 83efc23..82b0258 100644 --- a/app.py +++ b/app.py @@ -481,7 +481,75 @@ def api_symbol_search(): q = request.args.get("q", "") return jsonify(search_symbols(q)) -# —————————————— 页面路由 —————————————— + +@app.route("/api/key_prices") +@login_required +def api_key_prices(): + """关键位监控列表:批量现价与距上/下沿距离。""" + conn = get_db() + rows = conn.execute( + "SELECT id, symbol, market_code, sina_code, upper, lower " + "FROM key_monitors WHERE status='active' OR status IS NULL" + ).fetchall() + conn.close() + out = [] + for r in rows: + sym = r["symbol"] + market = r["market_code"] or "" + sina = r["sina_code"] or "" + upper = float(r["upper"]) + lower = float(r["lower"]) + price = fetch_price(sym, market, sina) + dist_upper = None + dist_lower = None + if price is not None: + dist_upper = round(upper - price, 2) + dist_lower = round(price - lower, 2) + out.append({ + "id": r["id"], + "price": price, + "dist_upper": dist_upper, + "dist_lower": dist_lower, + }) + return jsonify(out) + + +@app.route("/api/plan_prices") +@login_required +def api_plan_prices(): + """今日计划:批量现价与距决策区间上/下沿距离。""" + today = today_str() + conn = get_db() + rows = conn.execute( + "SELECT id, symbol, market_code, sina_code, zone_upper, zone_lower " + "FROM order_plans WHERE plan_date=? AND status IN ('planned', 'active')", + (today,), + ).fetchall() + conn.close() + out = [] + for r in rows: + sym = r["symbol"] + market = r["market_code"] or "" + sina = r["sina_code"] or "" + upper = float(r["zone_upper"]) + lower = float(r["zone_lower"]) + price = fetch_price(sym, market, sina) + dist_upper = None + dist_lower = None + in_zone = False + if price is not None: + dist_upper = round(upper - price, 2) + dist_lower = round(price - lower, 2) + in_zone = lower <= price <= upper + out.append({ + "id": r["id"], + "price": price, + "dist_upper": dist_upper, + "dist_lower": dist_lower, + "in_zone": in_zone, + }) + return jsonify(out) + @app.route("/") @login_required diff --git a/static/js/keys.js b/static/js/keys.js new file mode 100644 index 0000000..3ff4f3f --- /dev/null +++ b/static/js/keys.js @@ -0,0 +1,39 @@ +(function () { + var timer = null; + + function fmtDist(v) { + if (v === null || v === undefined) return '--'; + return v.toFixed(2); + } + + function pollPrices() { + var list = document.getElementById('key-monitor-list'); + if (!list || !list.querySelector('.key-item')) return; + + fetch('/api/key_prices') + .then(function (r) { return r.json(); }) + .then(function (rows) { + rows.forEach(function (row) { + var el = list.querySelector('.key-item[data-key-id="' + row.id + '"]'); + if (!el) return; + var priceEl = el.querySelector('.live-price'); + var upEl = el.querySelector('.dist-up'); + var downEl = el.querySelector('.dist-down'); + if (priceEl) { + priceEl.textContent = row.price != null ? row.price : '--'; + } + if (upEl) upEl.textContent = fmtDist(row.dist_upper); + if (downEl) downEl.textContent = fmtDist(row.dist_lower); + }); + }) + .catch(function () { /* ignore */ }); + } + + function startPolling() { + if (timer) clearInterval(timer); + pollPrices(); + timer = setInterval(pollPrices, 1000); + } + + document.addEventListener('DOMContentLoaded', startPolling); +})(); diff --git a/static/js/plans.js b/static/js/plans.js new file mode 100644 index 0000000..4021afc --- /dev/null +++ b/static/js/plans.js @@ -0,0 +1,45 @@ +(function () { + var timer = null; + + function fmtDist(v) { + if (v === null || v === undefined) return '--'; + return v.toFixed(2); + } + + function pollPrices() { + var list = document.getElementById('plan-monitor-list'); + if (!list || !list.querySelector('.plan-item')) return; + + fetch('/api/plan_prices') + .then(function (r) { return r.json(); }) + .then(function (rows) { + rows.forEach(function (row) { + var el = list.querySelector('.plan-item[data-plan-id="' + row.id + '"]'); + if (!el) return; + var priceEl = el.querySelector('.live-price'); + var distEl = el.querySelector('.live-dist'); + var upEl = el.querySelector('.dist-up'); + var downEl = el.querySelector('.dist-down'); + if (priceEl) { + priceEl.textContent = row.price != null ? row.price : '--'; + } + if (row.in_zone && distEl) { + distEl.innerHTML = '在区间内'; + } else if (distEl && upEl && downEl) { + distEl.innerHTML = + '距上 ' + fmtDist(row.dist_upper) + '' + + ' · 距下 ' + fmtDist(row.dist_lower) + ''; + } + }); + }) + .catch(function () { /* ignore */ }); + } + + function startPolling() { + if (timer) clearInterval(timer); + pollPrices(); + timer = setInterval(pollPrices, 1000); + } + + document.addEventListener('DOMContentLoaded', startPolling); +})(); diff --git a/templates/base.html b/templates/base.html index ded7514..c6acb5f 100644 --- a/templates/base.html +++ b/templates/base.html @@ -331,7 +331,11 @@ .review-detail-image{flex-shrink:0;padding-top:.75rem;border-top:1px solid var(--table-border)} .review-detail-image img{width:100%;border-radius:10px;border:1px solid var(--card-border)} .review-detail-image .no-img{color:var(--text-muted);font-size:.85rem;padding:2rem;text-align:center;background:var(--card-inner);border-radius:10px} - .modal-close{float:right;color:var(--text-muted);cursor:pointer;font-size:1.2rem} + .key-live{display:flex;flex-direction:column;align-items:center;gap:.15rem;min-width:100px;font-size:.8rem} + .key-live .live-price{font-size:1rem;font-weight:600;color:var(--accent)} + .key-live .live-dist{color:var(--text-muted);font-size:.72rem;white-space:nowrap} + .key-live .live-dist span{color:var(--text-primary)} + .list-item.key-item{gap:.65rem} .calc-readonly{background:var(--calc-bg);color:var(--accent)} @media(max-width:1100px){ .split-grid{grid-template-columns:1fr} diff --git a/templates/keys.html b/templates/keys.html index 695ef4a..0c8a6e5 100644 --- a/templates/keys.html +++ b/templates/keys.html @@ -8,7 +8,7 @@
- + @@ -35,13 +35,17 @@
-
+
{% for k in keys %} -
+
{{ k.symbol_name or k.symbol }} {{ k.monitor_type }} {{ '多' if k.direction == 'long' else '空' }}
+
+ -- + 距上 -- · 距下 -- +
上{{ k.upper }} 下{{ k.lower }}
@@ -76,3 +80,6 @@
{% endblock %} +{% block extra_js %} + +{% endblock %} diff --git a/templates/plans.html b/templates/plans.html index d32b559..aefc034 100644 --- a/templates/plans.html +++ b/templates/plans.html @@ -34,15 +34,19 @@
-
+
{% for p in plans %} -
+
{{ p.symbol_name or p.symbol }} {{ '多' if p.direction == 'long' else '空' }} {% if p.status == 'planned' %}待触发 {% else %}已激活{% endif %}
+
+ -- + 距上 -- · 距下 -- +
区间{{ p.zone_lower }}~{{ p.zone_upper }} {% if p.decision_reason %} · {{ p.decision_reason }}{% endif %} @@ -93,3 +97,6 @@
{% endblock %} +{% block extra_js %} + +{% endblock %}