fix(hub): archive chart perpetual label and trade row highlight
Label archive K-lines as USDT swap in title and API; highlight the full trade row when opening its chart. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -67,6 +67,20 @@ def normalize_chart_timeframe(raw: str | None, default: str = "5m") -> str:
|
||||
return tf if tf in CHART_TIMEFRAMES else default
|
||||
|
||||
|
||||
def normalize_perpetual_symbol(symbol: str) -> str:
|
||||
"""BTC/USDT → BTC/USDT:USDT(与四所 ccxt swap 行情一致)。"""
|
||||
sym = (symbol or "").strip().upper()
|
||||
if not sym:
|
||||
return ""
|
||||
if ":" in sym:
|
||||
return sym
|
||||
if "/" in sym:
|
||||
base, quote = sym.split("/", 1)
|
||||
quote_clean = quote.split(":")[0]
|
||||
return f"{base}/{quote_clean}:{quote_clean}"
|
||||
return sym
|
||||
|
||||
|
||||
def sync_timeframe_for_display(timeframe: str) -> str:
|
||||
"""展示周期对应的入库 / 同步周期。"""
|
||||
tf = normalize_chart_timeframe(timeframe)
|
||||
|
||||
@@ -17,6 +17,7 @@ from hub_ohlcv_lib import (
|
||||
TIMEFRAME_MS,
|
||||
aggregate_ohlcv_bars,
|
||||
normalize_chart_timeframe,
|
||||
normalize_perpetual_symbol,
|
||||
)
|
||||
from hub_trades_lib import (
|
||||
display_entry_type_label,
|
||||
@@ -927,10 +928,13 @@ def resolve_archive_chart(
|
||||
if not candles:
|
||||
return {"ok": False, "msg": "视窗内无 K 线"}
|
||||
|
||||
ex_sym = normalize_perpetual_symbol(sym)
|
||||
return {
|
||||
"ok": True,
|
||||
"exchange_key": ex_k,
|
||||
"symbol": sym,
|
||||
"exchange_symbol": ex_sym,
|
||||
"market_type": "swap",
|
||||
"timeframe": tf,
|
||||
"mode": (mode or "hold").strip().lower(),
|
||||
"range_mode": rm,
|
||||
|
||||
@@ -5800,13 +5800,15 @@ body.funds-fullscreen-open {
|
||||
cursor: pointer;
|
||||
}
|
||||
.archive-trade-row.is-active {
|
||||
background: var(--inset-surface);
|
||||
background: color-mix(in srgb, var(--accent) 16%, var(--inset-surface));
|
||||
box-shadow: inset 3px 0 0 var(--accent);
|
||||
}
|
||||
.archive-trade-row.archive-trade-sick td {
|
||||
color: var(--red);
|
||||
}
|
||||
.archive-trade-row.archive-trade-sick.is-active {
|
||||
background: var(--inset-surface);
|
||||
background: color-mix(in srgb, var(--accent) 12%, color-mix(in srgb, var(--red) 8%, var(--panel)));
|
||||
box-shadow: inset 3px 0 0 var(--accent);
|
||||
}
|
||||
.archive-trade-row.archive-trade-sick .archive-tag-select,
|
||||
.archive-trade-row.archive-trade-sick .archive-note-input {
|
||||
|
||||
@@ -66,6 +66,8 @@
|
||||
let inited = false;
|
||||
let markAuto = true;
|
||||
let lastCandles = [];
|
||||
let chartExchangeSymbol = "";
|
||||
let chartMarketType = "swap";
|
||||
let searchTimer = null;
|
||||
|
||||
function esc(s) {
|
||||
@@ -309,13 +311,27 @@
|
||||
scheduleChartResize();
|
||||
}
|
||||
|
||||
function formatChartContractLabel(sym, exchangeSymbol, marketType) {
|
||||
const base = String(sym || "—");
|
||||
const mt = String(marketType || "").toLowerCase();
|
||||
if (mt === "swap" || (exchangeSymbol && String(exchangeSymbol).indexOf(":") >= 0)) {
|
||||
return base + " 永续";
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
function updateChartTitle() {
|
||||
if (!elChartTitle) return;
|
||||
if (!selected) {
|
||||
elChartTitle.textContent = "—";
|
||||
return;
|
||||
}
|
||||
elChartTitle.textContent = selected.symbol + " · " + exchangeLabel(selected.exchange_key);
|
||||
const label = formatChartContractLabel(
|
||||
selected.symbol,
|
||||
chartExchangeSymbol,
|
||||
chartMarketType
|
||||
);
|
||||
elChartTitle.textContent = label + " · " + exchangeLabel(selected.exchange_key);
|
||||
}
|
||||
|
||||
async function apiFetch(url, opts) {
|
||||
@@ -871,6 +887,8 @@
|
||||
setStatus(j.detail || "K 线加载失败");
|
||||
return;
|
||||
}
|
||||
chartExchangeSymbol = j.exchange_symbol || "";
|
||||
chartMarketType = j.market_type || "swap";
|
||||
if (chart) {
|
||||
destroyChart();
|
||||
}
|
||||
@@ -900,7 +918,14 @@
|
||||
}
|
||||
updateChartTitle();
|
||||
scheduleChartResize();
|
||||
setStatus("K 线 " + candles.length + " 根 · " + timeframe);
|
||||
setStatus(
|
||||
"K 线 " +
|
||||
candles.length +
|
||||
" 根 · " +
|
||||
timeframe +
|
||||
" · " +
|
||||
formatChartContractLabel(selected.symbol, chartExchangeSymbol, chartMarketType)
|
||||
);
|
||||
}
|
||||
|
||||
async function openTradeChart(tr) {
|
||||
@@ -913,6 +938,7 @@
|
||||
}
|
||||
selected = { exchange_key: exKey, symbol: sym };
|
||||
selectedTradeId = String(tr.trade_id || tr.id);
|
||||
renderTrades();
|
||||
setChartOpen(true);
|
||||
await loadSymbolTradesForChart(exKey, sym);
|
||||
await loadChart();
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
|
||||
<noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript>
|
||||
<link rel="stylesheet" href="/assets/app.css?v=20260612-trade-symbol-col" />
|
||||
<link rel="stylesheet" href="/assets/app.css?v=20260612-archive-chart-row" />
|
||||
<link rel="stylesheet" href="/assets/dashboard.css?v=20260612-dash-monitor-count" />
|
||||
</head>
|
||||
<body>
|
||||
@@ -584,7 +584,7 @@
|
||||
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
|
||||
<script src="/assets/chart_draw.js?v=20260609-market-day-split"></script>
|
||||
<script src="/assets/chart.js?v=20260609-market-day-split"></script>
|
||||
<script src="/assets/archive.js?v=20260612-trade-symbol-col"></script>
|
||||
<script src="/assets/archive.js?v=20260612-archive-chart-row"></script>
|
||||
<script src="/assets/funds.js?v=20260609-hub-funds-fold"></script>
|
||||
<script src="/assets/dashboard.js?v=20260612-dash-monitor-count"></script>
|
||||
<script src="/assets/ai_review_render.js?v=3"></script>
|
||||
|
||||
Reference in New Issue
Block a user