feat: circular fund account cards with fullscreen detail view
Show per-exchange balances as clickable circles with mini curves; open a fullscreen panel for equity history and drawdown. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4488,68 +4488,121 @@ body.hub-page-ai #page-ai {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.funds-section-title {
|
.funds-section-title {
|
||||||
margin: 0 0 10px;
|
margin: 0 0 4px;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
.funds-accounts {
|
.funds-section-hint {
|
||||||
display: grid;
|
margin: 0 0 14px;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
font-size: 0.75rem;
|
||||||
gap: 12px;
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
.funds-ac-card {
|
.funds-accounts {
|
||||||
background: var(--panel);
|
display: flex;
|
||||||
border: 1px solid var(--border-soft);
|
flex-wrap: wrap;
|
||||||
border-radius: var(--radius);
|
gap: 18px 22px;
|
||||||
padding: 12px 14px;
|
justify-content: flex-start;
|
||||||
|
padding: 4px 0 8px;
|
||||||
|
}
|
||||||
|
.funds-ac-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
.funds-ac-card.is-off {
|
|
||||||
opacity: 0.72;
|
|
||||||
}
|
|
||||||
.funds-ac-head {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
width: 118px;
|
||||||
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
.funds-ac-head h3 {
|
.funds-ac-circle {
|
||||||
margin: 0;
|
position: relative;
|
||||||
font-size: 0.92rem;
|
width: 112px;
|
||||||
|
height: 112px;
|
||||||
|
padding: 0;
|
||||||
|
border: 2px solid var(--border-soft);
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--panel);
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: border-color 0.15s, box-shadow 0.15s, transform 0.12s;
|
||||||
}
|
}
|
||||||
.funds-ac-badge {
|
.funds-ac-circle:hover:not(:disabled) {
|
||||||
font-size: 0.68rem;
|
border-color: var(--accent, #3b82f6);
|
||||||
padding: 2px 8px;
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.12);
|
||||||
border-radius: 999px;
|
transform: translateY(-1px);
|
||||||
border: 1px solid var(--border-soft);
|
|
||||||
color: var(--muted);
|
|
||||||
}
|
}
|
||||||
.funds-ac-stats {
|
.funds-ac-circle:focus-visible {
|
||||||
display: grid;
|
outline: 2px solid var(--accent, #3b82f6);
|
||||||
grid-template-columns: 1fr 1fr;
|
outline-offset: 3px;
|
||||||
gap: 6px 10px;
|
|
||||||
font-size: 0.78rem;
|
|
||||||
}
|
}
|
||||||
.funds-ac-stats .k {
|
.funds-ac-circle.is-off {
|
||||||
color: var(--muted);
|
opacity: 0.62;
|
||||||
margin-right: 6px;
|
cursor: default;
|
||||||
}
|
}
|
||||||
.funds-ac-stats .v {
|
.funds-ac-circle.is-off:hover {
|
||||||
font-variant-numeric: tabular-nums;
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
border-color: var(--border-soft);
|
||||||
}
|
}
|
||||||
.funds-ac-chart {
|
.funds-ac-circle-chart {
|
||||||
height: 72px;
|
position: absolute;
|
||||||
min-height: 72px;
|
inset: 6px;
|
||||||
border-radius: 6px;
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
background: var(--inset-surface);
|
background: var(--inset-surface);
|
||||||
font-size: 0.72rem;
|
font-size: 0.68rem;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.25;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
.funds-ac-circle-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 2px;
|
||||||
|
padding: 10px;
|
||||||
|
background: radial-gradient(circle at 50% 55%, rgba(11, 14, 24, 0.55) 0%, rgba(11, 14, 24, 0.82) 68%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
html[data-theme="light"] .funds-ac-circle-overlay {
|
||||||
|
background: radial-gradient(circle at 50% 55%, rgba(240, 244, 249, 0.45) 0%, rgba(240, 244, 249, 0.88) 68%);
|
||||||
|
}
|
||||||
|
.funds-ac-circle-name {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
max-width: 88px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.funds-ac-circle-amt {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
.funds-ac-circle-badge {
|
||||||
|
font-size: 0.66rem;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid var(--border-soft);
|
||||||
|
color: var(--muted);
|
||||||
|
text-align: center;
|
||||||
|
max-width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.funds-ac-circle-badge.is-ok {
|
||||||
|
color: var(--green);
|
||||||
|
border-color: rgba(34, 197, 94, 0.35);
|
||||||
}
|
}
|
||||||
.funds-empty {
|
.funds-empty {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
@@ -4557,6 +4610,100 @@ body.hub-page-ai #page-ai {
|
|||||||
padding: 12px 0;
|
padding: 12px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.funds-fullscreen {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 160;
|
||||||
|
background: var(--fs-scrim);
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
overflow: auto;
|
||||||
|
padding: 16px 20px 24px;
|
||||||
|
}
|
||||||
|
.funds-fullscreen.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.funds-fs-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 0;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.funds-fs-panel {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
max-width: min(1200px, 96vw);
|
||||||
|
margin: 0 auto;
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border-soft);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 16px 18px 20px;
|
||||||
|
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.28);
|
||||||
|
}
|
||||||
|
.funds-fs-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
border-bottom: 1px solid var(--border-soft);
|
||||||
|
}
|
||||||
|
.funds-fs-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.15rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.funds-fs-sub {
|
||||||
|
margin: 4px 0 0;
|
||||||
|
font-size: 0.76rem;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
.funds-fs-summary {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
.funds-fs-stat {
|
||||||
|
background: var(--inset-surface);
|
||||||
|
border: 1px solid var(--border-soft);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.funds-fs-stat .k {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
.funds-fs-stat .v {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
.funds-fs-stat .v.pos {
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
.funds-fs-stat .v.neg {
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
.funds-fs-chart-host {
|
||||||
|
height: min(52vh, 420px);
|
||||||
|
min-height: 260px;
|
||||||
|
border: 1px solid var(--border-soft);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: var(--chart-surface, var(--panel));
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
body.funds-fullscreen-open {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
/* —— 币种档案 —— */
|
/* —— 币种档案 —— */
|
||||||
.archive-toolbar {
|
.archive-toolbar {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@@ -15,10 +15,26 @@
|
|||||||
const elAccounts = document.getElementById("funds-accounts");
|
const elAccounts = document.getElementById("funds-accounts");
|
||||||
const elBtnRefresh = document.getElementById("funds-btn-refresh");
|
const elBtnRefresh = document.getElementById("funds-btn-refresh");
|
||||||
|
|
||||||
|
const elFs = document.getElementById("funds-fullscreen");
|
||||||
|
const elFsBackdrop = document.getElementById("funds-fs-backdrop");
|
||||||
|
const elFsClose = document.getElementById("funds-fs-close");
|
||||||
|
const elFsTitle = document.getElementById("funds-fs-title");
|
||||||
|
const elFsSub = document.getElementById("funds-fs-sub");
|
||||||
|
const elFsTotal = document.getElementById("funds-fs-total");
|
||||||
|
const elFsFunding = document.getElementById("funds-fs-funding");
|
||||||
|
const elFsTrading = document.getElementById("funds-fs-trading");
|
||||||
|
const elFsDelta = document.getElementById("funds-fs-delta");
|
||||||
|
const elFsDd = document.getElementById("funds-fs-dd");
|
||||||
|
const elFsChartHost = document.getElementById("funds-fs-chart");
|
||||||
|
|
||||||
let chart = null;
|
let chart = null;
|
||||||
let lineSeries = null;
|
let lineSeries = null;
|
||||||
|
let fsChart = null;
|
||||||
|
let fsLineSeries = null;
|
||||||
let inited = false;
|
let inited = false;
|
||||||
let loading = false;
|
let loading = false;
|
||||||
|
let lastOverview = null;
|
||||||
|
let fsAccountKey = "";
|
||||||
|
|
||||||
function fmt(n, d) {
|
function fmt(n, d) {
|
||||||
if (n == null || n === "" || !Number.isFinite(Number(n))) return "—";
|
if (n == null || n === "" || !Number.isFinite(Number(n))) return "—";
|
||||||
@@ -64,6 +80,15 @@
|
|||||||
if (elChartHost) elChartHost.innerHTML = "";
|
if (elChartHost) elChartHost.innerHTML = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function destroyFsChart() {
|
||||||
|
if (fsChart) {
|
||||||
|
fsChart.remove();
|
||||||
|
fsChart = null;
|
||||||
|
fsLineSeries = null;
|
||||||
|
}
|
||||||
|
if (elFsChartHost) elFsChartHost.innerHTML = "";
|
||||||
|
}
|
||||||
|
|
||||||
function chartPalette() {
|
function chartPalette() {
|
||||||
const light = document.documentElement.getAttribute("data-theme") === "light";
|
const light = document.documentElement.getAttribute("data-theme") === "light";
|
||||||
return light
|
return light
|
||||||
@@ -71,11 +96,9 @@
|
|||||||
: { bg: "#0b0e18", text: "#9aa4b8", border: "#2a3348", line: "#3b82f6" };
|
: { bg: "#0b0e18", text: "#9aa4b8", border: "#2a3348", line: "#3b82f6" };
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureChart() {
|
function createAreaChart(host) {
|
||||||
if (!elChartHost || !window.LightweightCharts) return;
|
|
||||||
if (chart) return;
|
|
||||||
const p = chartPalette();
|
const p = chartPalette();
|
||||||
chart = LightweightCharts.createChart(elChartHost, {
|
const c = LightweightCharts.createChart(host, {
|
||||||
layout: { background: { color: p.bg }, textColor: p.text },
|
layout: { background: { color: p.bg }, textColor: p.text },
|
||||||
grid: {
|
grid: {
|
||||||
vertLines: { color: p.border, visible: true },
|
vertLines: { color: p.border, visible: true },
|
||||||
@@ -87,7 +110,7 @@
|
|||||||
handleScroll: { mouseWheel: true, pressedMouseMove: true },
|
handleScroll: { mouseWheel: true, pressedMouseMove: true },
|
||||||
handleScale: { axisPressedMouseMove: true, mouseWheel: true, pinch: true },
|
handleScale: { axisPressedMouseMove: true, mouseWheel: true, pinch: true },
|
||||||
});
|
});
|
||||||
lineSeries = chart.addAreaSeries({
|
const s = c.addAreaSeries({
|
||||||
lineColor: p.line,
|
lineColor: p.line,
|
||||||
topColor: p.line + "44",
|
topColor: p.line + "44",
|
||||||
bottomColor: p.line + "08",
|
bottomColor: p.line + "08",
|
||||||
@@ -95,11 +118,28 @@
|
|||||||
priceFormat: { type: "price", precision: 2, minMove: 0.01 },
|
priceFormat: { type: "price", precision: 2, minMove: 0.01 },
|
||||||
});
|
});
|
||||||
new ResizeObserver(function () {
|
new ResizeObserver(function () {
|
||||||
if (chart && elChartHost) {
|
if (c && host) {
|
||||||
chart.applyOptions({ width: elChartHost.clientWidth, height: elChartHost.clientHeight });
|
c.applyOptions({ width: host.clientWidth, height: host.clientHeight });
|
||||||
}
|
}
|
||||||
}).observe(elChartHost);
|
}).observe(host);
|
||||||
chart.applyOptions({ width: elChartHost.clientWidth, height: elChartHost.clientHeight });
|
c.applyOptions({ width: host.clientWidth, height: host.clientHeight });
|
||||||
|
return { chart: c, series: s };
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureChart() {
|
||||||
|
if (!elChartHost || !window.LightweightCharts) return;
|
||||||
|
if (chart) return;
|
||||||
|
const built = createAreaChart(elChartHost);
|
||||||
|
chart = built.chart;
|
||||||
|
lineSeries = built.series;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureFsChart() {
|
||||||
|
if (!elFsChartHost || !window.LightweightCharts) return;
|
||||||
|
if (fsChart) return;
|
||||||
|
const built = createAreaChart(elFsChartHost);
|
||||||
|
fsChart = built.chart;
|
||||||
|
fsLineSeries = built.series;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMiniChart(host, series) {
|
function renderMiniChart(host, series) {
|
||||||
@@ -120,7 +160,12 @@
|
|||||||
handleScroll: false,
|
handleScroll: false,
|
||||||
handleScale: false,
|
handleScale: false,
|
||||||
});
|
});
|
||||||
const s = mini.addLineSeries({ color: p.line, lineWidth: 1.5 });
|
const s = mini.addAreaSeries({
|
||||||
|
lineColor: p.line,
|
||||||
|
topColor: p.line + "55",
|
||||||
|
bottomColor: p.line + "05",
|
||||||
|
lineWidth: 1.5,
|
||||||
|
});
|
||||||
s.setData(data);
|
s.setData(data);
|
||||||
mini.timeScale().fitContent();
|
mini.timeScale().fitContent();
|
||||||
const w = host.clientWidth;
|
const w = host.clientWidth;
|
||||||
@@ -128,6 +173,21 @@
|
|||||||
if (w > 0 && h > 0) mini.applyOptions({ width: w, height: h });
|
if (w > 0 && h > 0) mini.applyOptions({ width: w, height: h });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function accountStatus(ac) {
|
||||||
|
if (!ac || !ac.monitored) return { text: "未监控", cls: "" };
|
||||||
|
if (ac.data_ok) return { text: "已监控", cls: "is-ok" };
|
||||||
|
return { text: "余额未齐", cls: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
function monitoredLabel(ac) {
|
||||||
|
return ac && ac.monitored ? "暂无曲线" : "未参与";
|
||||||
|
}
|
||||||
|
|
||||||
|
function shortAmt(ac) {
|
||||||
|
if (!ac || !ac.monitored || !ac.data_ok) return "—";
|
||||||
|
return fmt(ac.total_usdt, 0) + " U";
|
||||||
|
}
|
||||||
|
|
||||||
function renderAccounts(accounts) {
|
function renderAccounts(accounts) {
|
||||||
if (!elAccounts) return;
|
if (!elAccounts) return;
|
||||||
if (!accounts || !accounts.length) {
|
if (!accounts || !accounts.length) {
|
||||||
@@ -137,66 +197,139 @@
|
|||||||
elAccounts.innerHTML = accounts
|
elAccounts.innerHTML = accounts
|
||||||
.map(function (ac) {
|
.map(function (ac) {
|
||||||
const monitored = !!ac.monitored;
|
const monitored = !!ac.monitored;
|
||||||
const cls = monitored ? "" : " is-off";
|
const offCls = monitored ? "" : " is-off";
|
||||||
const total = monitored && ac.data_ok ? fmt(ac.total_usdt, 2) + " U" : "—";
|
const st = accountStatus(ac);
|
||||||
const funding = monitored && ac.funding_usdt != null ? fmt(ac.funding_usdt, 2) : "—";
|
const clickable = monitored ? "" : ' disabled aria-disabled="true"';
|
||||||
const trading = monitored && ac.trading_usdt != null ? fmt(ac.trading_usdt, 2) : "—";
|
|
||||||
const dd = ac.drawdown || {};
|
|
||||||
const ddU = dd.max_drawdown_u != null ? fmt(dd.max_drawdown_u, 2) + " U" : "—";
|
|
||||||
const ddPct = dd.max_drawdown_pct != null ? fmt(dd.max_drawdown_pct, 2) + "%" : "—";
|
|
||||||
const status = monitored ? (ac.data_ok ? "已监控" : "余额未齐") : "未监控";
|
|
||||||
return (
|
return (
|
||||||
'<article class="funds-ac-card' +
|
'<div class="funds-ac-item">' +
|
||||||
cls +
|
'<button type="button" class="funds-ac-circle' +
|
||||||
|
offCls +
|
||||||
'" data-key="' +
|
'" data-key="' +
|
||||||
(ac.key || "") +
|
(ac.key || "") +
|
||||||
|
'"' +
|
||||||
|
clickable +
|
||||||
|
' title="' +
|
||||||
|
(monitored ? "点击查看资金曲线" : "未监控,不参与合计") +
|
||||||
'">' +
|
'">' +
|
||||||
'<div class="funds-ac-head">' +
|
'<div class="funds-ac-circle-chart" aria-hidden="true"></div>' +
|
||||||
'<h3>' +
|
'<div class="funds-ac-circle-overlay">' +
|
||||||
|
'<span class="funds-ac-circle-name">' +
|
||||||
(ac.name || ac.key || "—") +
|
(ac.name || ac.key || "—") +
|
||||||
"</h3>" +
|
"</span>" +
|
||||||
'<span class="funds-ac-badge">' +
|
'<span class="funds-ac-circle-amt">' +
|
||||||
status +
|
shortAmt(ac) +
|
||||||
"</span>" +
|
"</span>" +
|
||||||
"</div>" +
|
"</div>" +
|
||||||
'<div class="funds-ac-stats">' +
|
"</button>" +
|
||||||
'<div><span class="k">总资金</span><span class="v">' +
|
'<span class="funds-ac-circle-badge ' +
|
||||||
total +
|
st.cls +
|
||||||
"</span></div>" +
|
'">' +
|
||||||
'<div><span class="k">资金户</span><span class="v">' +
|
st.text +
|
||||||
funding +
|
"</span>" +
|
||||||
"</span></div>" +
|
"</div>"
|
||||||
'<div><span class="k">交易户</span><span class="v">' +
|
|
||||||
trading +
|
|
||||||
"</span></div>" +
|
|
||||||
'<div><span class="k">最大回撤</span><span class="v">' +
|
|
||||||
ddU +
|
|
||||||
" / " +
|
|
||||||
ddPct +
|
|
||||||
"</span></div>" +
|
|
||||||
"</div>" +
|
|
||||||
'<div class="funds-ac-chart" aria-hidden="true"></div>' +
|
|
||||||
"</article>"
|
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.join("");
|
.join("");
|
||||||
|
|
||||||
elAccounts.querySelectorAll(".funds-ac-card").forEach(function (card, idx) {
|
elAccounts.querySelectorAll(".funds-ac-circle").forEach(function (btn, idx) {
|
||||||
const ac = accounts[idx];
|
const ac = accounts[idx];
|
||||||
const host = card.querySelector(".funds-ac-chart");
|
const host = btn.querySelector(".funds-ac-circle-chart");
|
||||||
if (ac && ac.monitored && host) {
|
if (ac && ac.monitored && host) {
|
||||||
renderMiniChart(host, ac.series || []);
|
renderMiniChart(host, ac.series || []);
|
||||||
} else if (host) {
|
} else if (host) {
|
||||||
host.textContent = monitoredLabel(ac);
|
host.textContent = monitoredLabel(ac);
|
||||||
}
|
}
|
||||||
|
if (ac && ac.monitored) {
|
||||||
|
btn.addEventListener("click", function () {
|
||||||
|
openAccountFullscreen(ac.key);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function monitoredLabel(ac) {
|
function findAccount(key) {
|
||||||
return ac && ac.monitored ? "暂无曲线" : "未参与合计";
|
const accounts = (lastOverview && lastOverview.accounts) || [];
|
||||||
|
return accounts.find(function (ac) {
|
||||||
|
return String(ac.key || "") === String(key || "");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAccountFullscreen() {
|
||||||
|
fsAccountKey = "";
|
||||||
|
destroyFsChart();
|
||||||
|
if (elFs) {
|
||||||
|
elFs.classList.add("hidden");
|
||||||
|
elFs.setAttribute("aria-hidden", "true");
|
||||||
|
}
|
||||||
|
document.body.classList.remove("funds-fullscreen-open");
|
||||||
|
}
|
||||||
|
|
||||||
|
function openAccountFullscreen(key) {
|
||||||
|
const ac = findAccount(key);
|
||||||
|
if (!ac || !ac.monitored) return;
|
||||||
|
fsAccountKey = String(key || "");
|
||||||
|
const dd = ac.drawdown || {};
|
||||||
|
const meta = lastOverview || {};
|
||||||
|
if (elFsTitle) elFsTitle.textContent = ac.name || ac.key || "—";
|
||||||
|
if (elFsSub) {
|
||||||
|
const parts = [
|
||||||
|
"资金户 + 交易户(不含浮盈)",
|
||||||
|
"交易日 " + (meta.trading_day || "—"),
|
||||||
|
"自 " + (meta.history_start_day || "2026-06-09") + " 起",
|
||||||
|
];
|
||||||
|
elFsSub.textContent = parts.join(" · ");
|
||||||
|
}
|
||||||
|
if (elFsTotal) {
|
||||||
|
elFsTotal.textContent =
|
||||||
|
ac.data_ok && ac.total_usdt != null ? fmt(ac.total_usdt, 2) + " U" : "—";
|
||||||
|
}
|
||||||
|
if (elFsFunding) {
|
||||||
|
elFsFunding.textContent =
|
||||||
|
ac.funding_usdt != null ? fmt(ac.funding_usdt, 2) + " U" : "—";
|
||||||
|
}
|
||||||
|
if (elFsTrading) {
|
||||||
|
elFsTrading.textContent =
|
||||||
|
ac.trading_usdt != null ? fmt(ac.trading_usdt, 2) + " U" : "—";
|
||||||
|
}
|
||||||
|
if (elFsDelta) {
|
||||||
|
elFsDelta.textContent = fmtDelta(ac.day_delta_usdt);
|
||||||
|
elFsDelta.className = "v " + deltaClass(ac.day_delta_usdt);
|
||||||
|
}
|
||||||
|
if (elFsDd) {
|
||||||
|
const ddU = dd.max_drawdown_u != null ? fmt(dd.max_drawdown_u, 2) + " U" : "—";
|
||||||
|
const ddPct = dd.max_drawdown_pct != null ? fmt(dd.max_drawdown_pct, 2) + "%" : "—";
|
||||||
|
elFsDd.textContent = ddU + " / " + ddPct;
|
||||||
|
}
|
||||||
|
if (elFs) {
|
||||||
|
elFs.classList.remove("hidden");
|
||||||
|
elFs.setAttribute("aria-hidden", "false");
|
||||||
|
document.body.classList.add("funds-fullscreen-open");
|
||||||
|
}
|
||||||
|
destroyFsChart();
|
||||||
|
const pts = seriesToChartData(ac.series || []);
|
||||||
|
if (pts.length) {
|
||||||
|
ensureFsChart();
|
||||||
|
if (fsLineSeries) {
|
||||||
|
fsLineSeries.setData(pts);
|
||||||
|
fsChart.timeScale().fitContent();
|
||||||
|
}
|
||||||
|
requestAnimationFrame(function () {
|
||||||
|
if (fsChart && elFsChartHost) {
|
||||||
|
fsChart.applyOptions({
|
||||||
|
width: elFsChartHost.clientWidth,
|
||||||
|
height: elFsChartHost.clientHeight,
|
||||||
|
});
|
||||||
|
fsChart.timeScale().fitContent();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (elFsChartHost) {
|
||||||
|
elFsChartHost.innerHTML =
|
||||||
|
'<p class="funds-empty">暂无历史曲线,请保持监控板运行以积累快照</p>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderOverview(data) {
|
function renderOverview(data) {
|
||||||
|
lastOverview = data;
|
||||||
const totals = data.totals || {};
|
const totals = data.totals || {};
|
||||||
const dd = totals.drawdown || {};
|
const dd = totals.drawdown || {};
|
||||||
if (elTotal) {
|
if (elTotal) {
|
||||||
@@ -235,6 +368,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
renderAccounts(data.accounts || []);
|
renderAccounts(data.accounts || []);
|
||||||
|
if (fsAccountKey) {
|
||||||
|
const ac = findAccount(fsAccountKey);
|
||||||
|
if (ac && ac.monitored) openAccountFullscreen(fsAccountKey);
|
||||||
|
else closeAccountFullscreen();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
@@ -259,8 +397,14 @@
|
|||||||
|
|
||||||
function bind() {
|
function bind() {
|
||||||
if (elBtnRefresh) elBtnRefresh.addEventListener("click", load);
|
if (elBtnRefresh) elBtnRefresh.addEventListener("click", load);
|
||||||
|
if (elFsBackdrop) elFsBackdrop.addEventListener("click", closeAccountFullscreen);
|
||||||
|
if (elFsClose) elFsClose.addEventListener("click", closeAccountFullscreen);
|
||||||
|
document.addEventListener("keydown", function (ev) {
|
||||||
|
if (ev.key === "Escape" && fsAccountKey) closeAccountFullscreen();
|
||||||
|
});
|
||||||
document.addEventListener("hub-theme-change", function () {
|
document.addEventListener("hub-theme-change", function () {
|
||||||
destroyChart();
|
destroyChart();
|
||||||
|
destroyFsChart();
|
||||||
load();
|
load();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -275,6 +419,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function destroy() {
|
function destroy() {
|
||||||
|
closeAccountFullscreen();
|
||||||
destroyChart();
|
destroyChart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -357,9 +357,31 @@
|
|||||||
<p id="funds-meta" class="funds-meta"></p>
|
<p id="funds-meta" class="funds-meta"></p>
|
||||||
<div id="funds-chart-total" class="funds-chart-host"></div>
|
<div id="funds-chart-total" class="funds-chart-host"></div>
|
||||||
<h2 class="funds-section-title">分户资金</h2>
|
<h2 class="funds-section-title">分户资金</h2>
|
||||||
|
<p class="funds-section-hint">圆形为各交易所快照,点击查看资金曲线与回撤</p>
|
||||||
<div id="funds-accounts" class="funds-accounts"></div>
|
<div id="funds-accounts" class="funds-accounts"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="funds-fullscreen" class="funds-fullscreen hidden" aria-hidden="true">
|
||||||
|
<button type="button" id="funds-fs-backdrop" class="funds-fs-backdrop" aria-label="关闭全屏"></button>
|
||||||
|
<div class="funds-fs-panel">
|
||||||
|
<div class="funds-fs-head">
|
||||||
|
<div>
|
||||||
|
<h2 id="funds-fs-title" class="funds-fs-title">—</h2>
|
||||||
|
<p id="funds-fs-sub" class="funds-fs-sub"></p>
|
||||||
|
</div>
|
||||||
|
<button type="button" id="funds-fs-close" class="ghost">关闭</button>
|
||||||
|
</div>
|
||||||
|
<section class="funds-fs-summary">
|
||||||
|
<div class="funds-fs-stat"><span class="k">总资金</span><span id="funds-fs-total" class="v">—</span></div>
|
||||||
|
<div class="funds-fs-stat"><span class="k">资金户</span><span id="funds-fs-funding" class="v">—</span></div>
|
||||||
|
<div class="funds-fs-stat"><span class="k">交易户</span><span id="funds-fs-trading" class="v">—</span></div>
|
||||||
|
<div class="funds-fs-stat"><span class="k">较昨日</span><span id="funds-fs-delta" class="v">—</span></div>
|
||||||
|
<div class="funds-fs-stat"><span class="k">最大回撤</span><span id="funds-fs-dd" class="v">—</span></div>
|
||||||
|
</section>
|
||||||
|
<div id="funds-fs-chart" class="funds-fs-chart-host"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="page-ai" class="page hidden">
|
<div id="page-ai" class="page hidden">
|
||||||
<div class="page-head">
|
<div class="page-head">
|
||||||
<h1><span class="head-tag">AI</span> 教练</h1>
|
<h1><span class="head-tag">AI</span> 教练</h1>
|
||||||
|
|||||||
Reference in New Issue
Block a user