Files
qihuo/static/js/stats.js
T
dekun 6d55a54946 Fix turbo nav layout flash and stats page not loading.
Wait for page CSS before swapping content, hoist inline styles to head, and boot page scripts immediately when DOM markers exist.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 20:55:36 +08:00

165 lines
6.2 KiB
JavaScript

/* Copyright (c) 2025-2026 马建军. All rights reserved.
* 专有软件 — 未经授权禁止复制、传播、转售。
* 详见 LICENSE.zh-CN.txt
*/
(function () {
var cache = null;
function fmtNum(v, suffix) {
if (v === null || v === undefined || v === '') return '-';
var n = Number(v);
if (isNaN(n)) return String(v);
var s = Number.isInteger(n) ? String(n) : n.toFixed(2);
return suffix ? s + suffix : s;
}
function fmtMoney(v) {
if (v === null || v === undefined) return '-';
return fmtNum(v) + ' 元';
}
function fmtPct(v) {
if (v === null || v === undefined) return '-';
return fmtNum(v) + '%';
}
function setSummary(s) {
var map = {
total_trades: function () { return fmtNum(s.total_trades); },
win_rate: function () { return fmtPct(s.win_rate); },
avg_profit: function () { return fmtMoney(s.avg_profit); },
avg_loss: function () { return fmtMoney(s.avg_loss); },
profit_loss_ratio: function () { return fmtNum(s.profit_loss_ratio); },
consecutive_losses: function () { return fmtNum(s.consecutive_losses); },
max_drawdown: function () {
var amt = fmtMoney(s.max_drawdown);
var pct = s.max_drawdown_pct ? ' (' + fmtPct(s.max_drawdown_pct) + ')' : '';
return amt + pct;
},
max_loss_amount: function () { return fmtMoney(s.max_loss_amount); },
max_loss_pct: function () { return fmtPct(s.max_loss_pct); },
max_profit_amount: function () { return fmtMoney(s.max_profit_amount); },
max_profit_pct: function () { return fmtPct(s.max_profit_pct); },
total_fee: function () { return fmtMoney(s.total_fee); },
emotion_count: function () { return fmtNum(s.emotion_count); },
emotion_ratio: function () { return fmtPct(s.emotion_ratio); },
};
document.querySelectorAll('#stats-summary [data-k]').forEach(function (el) {
var key = el.getAttribute('data-k');
el.textContent = map[key] ? map[key]() : '-';
});
}
function fillViewSelect(views, selected) {
var sel = document.getElementById('stats-view-select');
if (!sel) return;
sel.innerHTML = '';
views.forEach(function (v) {
var opt = document.createElement('option');
opt.value = v.key;
opt.textContent = v.label;
if (v.key === selected) opt.selected = true;
sel.appendChild(opt);
});
}
function cellClass(key, val) {
if (key === 'total_net' || key === 'max_profit' || key === 'avg_profit') {
if (val > 0) return 'text-profit';
if (val < 0) return 'text-loss';
}
if (key === 'max_loss' || key === 'avg_loss' || key === 'total_fee') {
return 'text-loss';
}
return '';
}
function renderBreakdown(key) {
if (!cache || !cache.breakdowns) return;
var block = cache.breakdowns[key];
var head = document.getElementById('stats-breakdown-head');
var body = document.getElementById('stats-breakdown-body');
if (!block || !head || !body) return;
head.innerHTML = '';
block.columns.forEach(function (col) {
var th = document.createElement('th');
th.textContent = col.label;
head.appendChild(th);
});
body.innerHTML = '';
if (!block.rows || !block.rows.length) {
var tr = document.createElement('tr');
var td = document.createElement('td');
td.colSpan = block.columns.length;
td.className = 'text-muted';
td.textContent = '暂无数据';
tr.appendChild(td);
body.appendChild(tr);
return;
}
block.rows.forEach(function (row) {
var tr = document.createElement('tr');
block.columns.forEach(function (col) {
var td = document.createElement('td');
var val = row[col.key];
if (col.key === 'win_rate') {
td.textContent = fmtPct(val);
} else if (col.key === 'label') {
td.textContent = val || '-';
} else if (typeof val === 'number') {
td.textContent = fmtNum(val);
td.className = cellClass(col.key, val);
} else {
td.textContent = val != null ? val : '-';
}
tr.appendChild(td);
});
body.appendChild(tr);
});
}
function applyData(data) {
cache = data;
setSummary(data.summary || {});
var views = data.views || [];
var sel = document.getElementById('stats-view-select');
var current = sel && sel.value ? sel.value : (views[0] && views[0].key);
fillViewSelect(views, current);
renderBreakdown(current);
var updated = document.getElementById('stats-updated');
if (updated) {
updated.textContent = data.updated_at
? '统计更新于 ' + data.updated_at.replace('T', ' ')
: '统计已加载';
}
}
function loadStats() {
fetch('/api/stats')
.then(function (r) { return r.json(); })
.then(applyData)
.catch(function () {
var updated = document.getElementById('stats-updated');
if (updated) updated.textContent = '加载失败,请刷新页面';
});
}
function bootStatsPage() {
var viewSel = document.getElementById('stats-view-select');
if (viewSel && !viewSel.dataset.statsBound) {
viewSel.dataset.statsBound = '1';
viewSel.addEventListener('change', function () {
renderBreakdown(this.value);
});
}
loadStats();
}
if (window.qihuoPageBoot) window.qihuoPageBoot(bootStatsPage, '#stats-summary');
else if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(bootStatsPage);
else document.addEventListener('DOMContentLoaded', bootStatsPage);
})();