Fix header funds flicker and unify settings page top bar.

Preserve SSR fund values when account_snapshot returns null, retry on failure, and show the same instance header panel on system settings.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-08 19:08:08 +08:00
parent 279648de56
commit 0d27e13275
8 changed files with 236 additions and 190 deletions
+104 -76
View File
@@ -1039,18 +1039,23 @@ function refreshOrderDefaults(){
}
function paintRealtimePnl(v){
const pnlEl = document.getElementById("realtime-pnl");
if(!pnlEl) return;
const nodes = document.querySelectorAll('[data-funds-field="realtime-pnl"]');
if(!nodes.length) return;
if(v === null || v === undefined || Number.isNaN(Number(v))){
pnlEl.innerText = "—";
pnlEl.classList.remove("pnl-pos", "pnl-neg");
nodes.forEach((pnlEl) => {
pnlEl.innerText = "—";
pnlEl.classList.remove("pnl-pos", "pnl-neg");
});
return;
}
const n = Number(v);
const sign = n > 0 ? "+" : "";
pnlEl.innerText = `${sign}${n.toFixed(2)}U`;
pnlEl.classList.toggle("pnl-pos", n > 0);
pnlEl.classList.toggle("pnl-neg", n < 0);
const text = `${sign}${n.toFixed(2)}U`;
nodes.forEach((pnlEl) => {
pnlEl.innerText = text;
pnlEl.classList.toggle("pnl-pos", n > 0);
pnlEl.classList.toggle("pnl-neg", n < 0);
});
}
function sumOrdersFloatPnl(orders){
if(!orders || !orders.length) return null;
@@ -1091,81 +1096,104 @@ function formatOptionsFundingLabel(usdc, usdt) {
return parts.length ? parts.join(" · ") : "—";
}
function setFundsFieldText(field, text){
if(text == null || text === "") return;
document.querySelectorAll(`[data-funds-field="${field}"]`).forEach((el) => {
el.innerText = text;
});
}
function accountSnapshotFundingMissing(data){
if(!data || typeof data !== "object") return true;
const hasFunding = data.funding_usdt != null && data.funding_usdt !== "";
const hasTotal = data.total_funds != null && data.total_funds !== "";
const hasTrading = data.current_capital != null && data.current_capital !== "";
return !hasFunding && !hasTotal && !hasTrading;
}
let accountSnapshotRetryCount = 0;
function applyAccountSnapshot(data){
if(!data || typeof data !== "object") return;
if(data.funding_usdt != null && data.funding_usdt !== ""){
setFundsFieldText("total-capital", `${Number(data.funding_usdt).toFixed(2)}U`);
}
if(data.total_funds != null && data.total_funds !== ""){
setFundsFieldText("total-funds", `${Number(data.total_funds).toFixed(2)}U`);
}
if(data.current_capital != null && data.current_capital !== "" && !Number.isNaN(Number(data.current_capital))){
setFundsFieldText("current-capital", `${Number(data.current_capital).toFixed(2)}U`);
}
if(data.options_funding_usdc != null || data.options_funding_usdt != null){
const optFunding = formatOptionsFundingLabel(data.options_funding_usdc, data.options_funding_usdt);
if(optFunding !== "—") setFundsFieldText("options-funding-usdc", optFunding);
}
if(data.options_trading_usdc != null && data.options_trading_usdc !== ""){
setFundsFieldText("options-trading-usdc", `${Number(data.options_trading_usdc).toFixed(2)} USDC`);
}
if(typeof data.unrealized_pnl !== "undefined"){
paintRealtimePnl(data.unrealized_pnl);
}
if(typeof data.available_trading_usdt !== "undefined" && data.available_trading_usdt !== null){
latestAvailableUsdt = Number(data.available_trading_usdt);
}
if(data.risk_status){
document.querySelectorAll("#account-risk-badge").forEach((badge) => {
if(window.AccountRiskBadge){
AccountRiskBadge.applyToElement(badge, data.risk_status);
}else{
const st = data.risk_status.status || "normal";
badge.className = "risk-status-badge risk-status-" + st;
badge.innerText = data.risk_status.status_label || "正常";
badge.title = data.risk_status.reason || "";
}
});
}
if(data.force_close && window.TimeCloseUI && TimeCloseUI.paintForceCloseHeader){
TimeCloseUI.paintForceCloseHeader(data.force_close);
}
let canTradeText = "可开仓";
if(!data.can_trade){
const parts = [];
if(data.risk_status && data.risk_status.can_trade === false && data.risk_status.reason){
parts.push(data.risk_status.reason);
}
const ac = Number(data.active_count || 0);
const max = Number(data.max_active_positions || {{ max_active_positions }});
if(ac >= max) parts.push(`持仓 ${ac}/${max}`);
const hard = Number(data.daily_open_hard_limit != null ? data.daily_open_hard_limit : {{ daily_open_hard_limit }});
const opens = Number(data.opens_today);
if(hard > 0 && !Number.isNaN(opens) && opens >= hard) parts.push(`本交易日开仓 ${opens}/${hard} 已达上限`);
if(!parts.length) parts.push(`未到北京时间 {{ reset_hour }}:00`);
else parts.push(`或未到北京时间 {{ reset_hour }}:00`);
canTradeText = `不可开仓(${parts.join("")}`;
}
const opensToday = Number(data.opens_today);
const hardLim = Number(data.daily_open_hard_limit != null ? data.daily_open_hard_limit : {{ daily_open_hard_limit }});
const alertLim = Number(data.daily_open_alert_threshold != null ? data.daily_open_alert_threshold : {{ daily_open_alert_threshold }});
const openCntTxt = !Number.isNaN(opensToday)
? `本交易日开仓 ${opensToday}${hardLim > 0 ? ` / 硬上限 ${hardLim}` : ""}AI 提醒 ${alertLim}`
: "";
const tip = document.getElementById("order-rule-tip");
const avail = (latestAvailableUsdt !== null && !Number.isNaN(latestAvailableUsdt)) ? `;交易账户可用约${latestAvailableUsdt.toFixed(2)}U` : "";
if(tip){
tip.innerText = `规则:最多 ${data.max_active_positions || {{ max_active_positions }}} 仓;BTC {{ btc_leverage }}x / 山寨 {{ alt_leverage }}x${openCntTxt ? openCntTxt + "" : ""}${canTradeText}${avail};人工开仓盈亏比不得低于 {{ manual_min_planned_rr }}:1`;
}
}
function refreshAccountSnapshot(opts){
const options = opts || {};
const qs = options.force ? "?force=1" : "";
fetch("/api/account_snapshot" + qs).then(r=>r.json()).then(data=>{
if (typeof data.funding_usdt !== "undefined") {
const el = document.getElementById("total-capital");
if(el) el.innerText = (data.funding_usdt === null || data.funding_usdt === undefined) ? "—" : `${Number(data.funding_usdt).toFixed(2)}U`;
applyAccountSnapshot(data);
if(accountSnapshotFundingMissing(data) && !options.force && accountSnapshotRetryCount < 3){
accountSnapshotRetryCount += 1;
setTimeout(() => refreshAccountSnapshot({ silent: true, force: accountSnapshotRetryCount >= 2 }), 1200 * accountSnapshotRetryCount);
}else if(!accountSnapshotFundingMissing(data)){
accountSnapshotRetryCount = 0;
}
if (typeof data.total_funds !== "undefined") {
const el = document.getElementById("total-funds");
if(el) el.innerText = (data.total_funds === null || data.total_funds === undefined) ? "—" : `${Number(data.total_funds).toFixed(2)}U`;
}).catch(()=>{
if(!options.silent && accountSnapshotRetryCount < 3){
accountSnapshotRetryCount += 1;
setTimeout(() => refreshAccountSnapshot({ silent: true }), 1200 * accountSnapshotRetryCount);
}
if (typeof data.current_capital !== "undefined") {
const el = document.getElementById("current-capital");
if(el) el.innerText = `${Number(data.current_capital).toFixed(2)}U`;
}
if (typeof data.options_funding_usdc !== "undefined" || typeof data.options_funding_usdt !== "undefined") {
const el = document.getElementById("options-funding-usdc");
if (el) el.innerText = formatOptionsFundingLabel(data.options_funding_usdc, data.options_funding_usdt);
}
if (typeof data.options_trading_usdc !== "undefined") {
const el = document.getElementById("options-trading-usdc");
if (el) el.innerText = (data.options_trading_usdc === null || data.options_trading_usdc === undefined)
? "—" : `${Number(data.options_trading_usdc).toFixed(2)} USDC`;
}
if (typeof data.unrealized_pnl !== "undefined") {
paintRealtimePnl(data.unrealized_pnl);
}
if (typeof data.available_trading_usdt !== "undefined" && data.available_trading_usdt !== null) {
latestAvailableUsdt = Number(data.available_trading_usdt);
}
if (data.risk_status) {
const badge = document.getElementById("account-risk-badge");
if (badge) {
if (window.AccountRiskBadge) {
AccountRiskBadge.applyToElement(badge, data.risk_status);
} else {
const st = data.risk_status.status || "normal";
badge.className = "risk-status-badge risk-status-" + st;
badge.innerText = data.risk_status.status_label || "正常";
badge.title = data.risk_status.reason || "";
}
}
}
if (data.force_close && window.TimeCloseUI && TimeCloseUI.paintForceCloseHeader) {
TimeCloseUI.paintForceCloseHeader(data.force_close);
}
let canTradeText = "可开仓";
if (!data.can_trade) {
const parts = [];
if (data.risk_status && data.risk_status.can_trade === false && data.risk_status.reason) {
parts.push(data.risk_status.reason);
}
const ac = Number(data.active_count || 0);
const max = Number(data.max_active_positions || {{ max_active_positions }});
if (ac >= max) parts.push(`持仓 ${ac}/${max}`);
const hard = Number(data.daily_open_hard_limit != null ? data.daily_open_hard_limit : {{ daily_open_hard_limit }});
const opens = Number(data.opens_today);
if (hard > 0 && !Number.isNaN(opens) && opens >= hard) parts.push(`本交易日开仓 ${opens}/${hard} 已达上限`);
if (!parts.length) parts.push(`未到北京时间 {{ reset_hour }}:00`);
else parts.push(`或未到北京时间 {{ reset_hour }}:00`);
canTradeText = `不可开仓(${parts.join("")}`;
}
const opensToday = Number(data.opens_today);
const hardLim = Number(data.daily_open_hard_limit != null ? data.daily_open_hard_limit : {{ daily_open_hard_limit }});
const alertLim = Number(data.daily_open_alert_threshold != null ? data.daily_open_alert_threshold : {{ daily_open_alert_threshold }});
const openCntTxt = !Number.isNaN(opensToday)
? `本交易日开仓 ${opensToday}${hardLim > 0 ? ` / 硬上限 ${hardLim}` : ""}AI 提醒 ${alertLim}`
: "";
const tip = document.getElementById("order-rule-tip");
const avail = (latestAvailableUsdt !== null && !Number.isNaN(latestAvailableUsdt)) ? `;交易账户可用约${latestAvailableUsdt.toFixed(2)}U` : "";
if(tip){
tip.innerText = `规则:最多 ${data.max_active_positions || {{ max_active_positions }}} 仓;BTC {{ btc_leverage }}x / 山寨 {{ alt_leverage }}x${openCntTxt ? openCntTxt + "" : ""}${canTradeText}${avail};人工开仓盈亏比不得低于 {{ manual_min_planned_rr }}:1`;
}
}).catch(()=>{});
});
}
const orderSymbolEl = document.getElementById("order-symbol");