智能荐股系统 v1.0.0:股票池/行情/新闻RAG/机构持仓/多因子评分/AI深度研报

This commit is contained in:
2026-08-19 19:36:32 +08:00
commit 90a9f9b212
29 changed files with 3332 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
/* 仪表盘 */
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();