103 lines
5.1 KiB
JavaScript
103 lines
5.1 KiB
JavaScript
/* 仪表盘 */
|
||
async function loadOverview() {
|
||
try {
|
||
const d = await api('/api/overview');
|
||
renderIndexes(d.indexes, d.stat);
|
||
renderTop(d.top);
|
||
renderHeat(d.heat);
|
||
renderNews(d.news);
|
||
renderWatch(d.watchlist);
|
||
} catch (e) {
|
||
$('#indexBox').innerHTML = '<div class="card"><div class="empty">加载失败:' + escapeHtml(e.message) + '</div></div>';
|
||
}
|
||
}
|
||
|
||
function renderIndexes(indexes, stat) {
|
||
const cards = indexes.map(ix => {
|
||
const cls = pctClass(ix.chg);
|
||
return `<div class="card">
|
||
<div class="kpi-label" style="color:var(--muted);font-size:12px">${ix.label}</div>
|
||
<div class="num" style="font-size:26px;font-weight:800;margin:6px 0">${Number(ix.value).toFixed(2)}</div>
|
||
<div class="num ${cls}" style="font-weight:600">${fmtPct(ix.chg)}</div>
|
||
</div>`;
|
||
}).join('');
|
||
const s = stat || {};
|
||
$('#indexBox').innerHTML = cards + `<div class="card">
|
||
<div class="card-title"><span class="bar"></span>市场情绪</div>
|
||
<div class="mini-stats">
|
||
<div class="mini-stat"><div class="ms-label">上涨</div><div class="ms-value up">${s.up || 0}</div></div>
|
||
<div class="mini-stat"><div class="ms-label">下跌</div><div class="ms-value down">${s.down || 0}</div></div>
|
||
<div class="mini-stat"><div class="ms-label">涨停</div><div class="ms-value" style="color:var(--up)">${s.limit_up || 0}</div></div>
|
||
<div class="mini-stat"><div class="ms-label">跌停</div><div class="ms-value" style="color:var(--down)">${s.limit_down || 0}</div></div>
|
||
<div class="mini-stat"><div class="ms-label">成交额</div><div class="ms-value">${fmtAmountYi(s.amount_yi)}</div></div>
|
||
</div>
|
||
<div style="margin-top:14px;color:var(--muted);font-size:12px">股票池 ${s.total || 0} 只 · 综合评分由 趋势/动量/技术/量能/消息/机构 六因子加权得出</div>
|
||
</div>`;
|
||
}
|
||
|
||
function renderTop(list) {
|
||
if (!list || !list.length) { $('#topRecBox').innerHTML = '<div class="empty">暂无数据</div>'; return; }
|
||
$('#topRecBox').innerHTML = `<table>
|
||
<tr><th>排名</th><th>股票</th><th>现价</th><th>今日</th><th>评分</th><th>评级</th><th>推荐理由</th></tr>
|
||
${list.map((it, i) => `<tr class="row-link" onclick="location.href='/stock/${it.code}'">
|
||
<td class="num">${i + 1}</td>
|
||
<td><b>${it.name}</b><div style="color:var(--muted);font-size:12px">${it.code} · ${it.industry}</div></td>
|
||
<td class="num">${it.close}</td>
|
||
<td class="num ${pctClass(it.change_pct)}">${fmtPct(it.change_pct)}</td>
|
||
<td class="num"><b style="color:${it.score.total >= 68 ? '#f59e0b' : '#3b82f6'}">${it.score.total}</b></td>
|
||
<td><span class="tag tag-${it.score.rating}">${it.score.rating}</span></td>
|
||
<td style="max-width:260px;white-space:normal;color:var(--text2)">${escapeHtml((it.reasons || []).join(';'))}</td>
|
||
</tr>`).join('')}
|
||
</table>`;
|
||
}
|
||
|
||
function renderHeat(heat) {
|
||
if (!heat || !heat.length) { $('#heatBox').innerHTML = '<div class="empty">暂无数据</div>'; return; }
|
||
const max = Math.max(...heat.map(h => Math.abs(h.chg)), 0.01);
|
||
$('#heatBox').innerHTML = heat.map(h => {
|
||
const w = Math.min(100, Math.abs(h.chg) / max * 100);
|
||
const cls = pctClass(h.chg);
|
||
return `<div style="margin-bottom:12px">
|
||
<div class="between">
|
||
<span>${escapeHtml(h.industry)} <span style="color:var(--muted)">(${h.cnt})</span></span>
|
||
<span class="num ${cls}">${fmtPct(h.chg)}</span>
|
||
</div>
|
||
<div style="height:7px;background:#262d3a;border-radius:4px;margin-top:5px">
|
||
<div style="height:100%;width:${w}%;border-radius:4px;background:${h.chg >= 0 ? 'var(--up)' : 'var(--down)'}"></div>
|
||
</div>
|
||
</div>`;
|
||
}).join('');
|
||
}
|
||
|
||
function renderNews(news) {
|
||
if (!news || !news.length) { $('#newsBox').innerHTML = '<div class="empty">暂无数据</div>'; return; }
|
||
$('#newsBox').innerHTML = news.map(n => `
|
||
<div class="news-item" onclick="location.href='/news'">
|
||
<div class="news-title">${escapeHtml(n.title)}</div>
|
||
<div class="news-meta">
|
||
<span class="cat-tag ${n.category}">${n.category}</span>
|
||
<span class="tag tag-${n.sentiment > 0 ? '正' : n.sentiment < 0 ? '负' : '平'}">${n.sentiment > 0 ? '利好' : n.sentiment < 0 ? '利空' : '中性'}</span>
|
||
<span>${escapeHtml(n.source)}</span><span>${n.publish_date}</span>
|
||
</div>
|
||
</div>`).join('');
|
||
}
|
||
|
||
function renderWatch(list) {
|
||
if (!list || !list.length) {
|
||
$('#watchBox').innerHTML = `<div class="empty">暂无自选股<br><a href="/stocks">去股票池添加 →</a></div>`;
|
||
return;
|
||
}
|
||
$('#watchBox').innerHTML = `<table>
|
||
<tr><th>股票</th><th>现价</th><th>今日</th><th>评分</th><th>评级</th></tr>
|
||
${list.map(it => `<tr class="row-link" onclick="location.href='/stock/${it.code}'">
|
||
<td><b>${it.name}</b><div style="color:var(--muted);font-size:12px">${it.code}</div></td>
|
||
<td class="num">${it.close}</td>
|
||
<td class="num ${pctClass(it.change_pct)}">${fmtPct(it.change_pct)}</td>
|
||
<td class="num">${it.score}</td>
|
||
<td><span class="tag tag-${it.rating}">${it.rating}</span></td>
|
||
</tr>`).join('')}
|
||
</table>`;
|
||
}
|
||
|
||
loadOverview();
|