120 lines
4.6 KiB
JavaScript
120 lines
4.6 KiB
JavaScript
/* 数据管理 */
|
|
const tableNames = {
|
|
stocks: '股票', stock_daily: '日线行情', news: '财经新闻', institutions: '机构',
|
|
inst_ratings: '机构评级', fund_holdings: '基金持仓', watchlist: '自选股',
|
|
analysis_cache: '研报缓存', analysis_history: 'AI分析历史',
|
|
strategy_backtests: '策略回测', market_index: '市场指数'
|
|
};
|
|
|
|
async function refreshStats() {
|
|
try {
|
|
const d = await api('/api/admin/stats');
|
|
$('#dbBox').innerHTML = Object.entries(d.tables).map(([k, v]) => `
|
|
<div class="between" style="padding:7px 0;border-bottom:1px solid #1f2630">
|
|
<span>${tableNames[k] || k}</span><span class="num"><b>${v}</b></span>
|
|
</div>`).join('');
|
|
$('#vecBox').innerHTML = `
|
|
<div class="between" style="padding:7px 0;border-bottom:1px solid #1f2630">
|
|
<span>📰 新闻向量索引 <span style="color:var(--muted)">(stock_news_v1)</span></span>
|
|
<span class="num"><b>${d.vector.news}</b> 条</span>
|
|
</div>
|
|
<div class="between" style="padding:7px 0;border-bottom:1px solid #1f2630">
|
|
<span>🏢 公司概况索引 <span style="color:var(--muted)">(stock_profiles_v1)</span></span>
|
|
<span class="num"><b>${d.vector.profiles}</b> 条</span>
|
|
</div>
|
|
<div class="between" style="padding:7px 0">
|
|
<span>数据模式</span>
|
|
<span class="tag ${d.is_mock ? 'tag-观望' : 'tag-推荐'}">${d.is_mock ? '模拟数据' : '真实数据'}</span>
|
|
</div>`;
|
|
} catch (e) {
|
|
$('#dbBox').innerHTML = '<div class="empty">加载失败</div>';
|
|
}
|
|
}
|
|
|
|
async function rebuildBt() {
|
|
if (!confirm('将重新跑全市场 6 策略 × 全部股票回测(几秒完成),确定?')) return;
|
|
try {
|
|
await api('/api/backtest/rebuild', { method: 'POST' });
|
|
toast('回测重建已启动');
|
|
setTimeout(refreshStats, 3000);
|
|
} catch (e) { toast('启动失败'); }
|
|
}
|
|
|
|
/* 定时报告 */
|
|
async function sendReport(kind) {
|
|
const btn = event.target; btn.disabled = true;
|
|
try {
|
|
const r = await api('/api/report/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ kind }) });
|
|
toast(r.msg || '已启动');
|
|
setTimeout(loadReportLog, 60000);
|
|
setTimeout(loadReportLog, 150000);
|
|
} catch (e) { toast('启动失败:' + e.message); }
|
|
btn.disabled = false;
|
|
}
|
|
|
|
/* 定时报告 · 静默期 */
|
|
async function loadReportQuiet() {
|
|
try {
|
|
const d = await api('/api/settings');
|
|
const q = (d.quiet && d.quiet.report) || {};
|
|
$('#reportQuietEnabled').checked = !!q.enabled;
|
|
$('#reportQuietRanges').value = q.ranges || '';
|
|
} catch (e) {}
|
|
}
|
|
|
|
async function saveReportQuiet() {
|
|
try {
|
|
const r = await api('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ quiet: { report: {
|
|
enabled: $('#reportQuietEnabled').checked,
|
|
ranges: $('#reportQuietRanges').value.trim()
|
|
} } }) });
|
|
toast(r.msg || '已保存');
|
|
} catch (e) { toast('保存失败:' + e.message); }
|
|
}
|
|
|
|
async function loadReportLog() {
|
|
try {
|
|
const d = await api('/api/report/log');
|
|
const items = d.items || [];
|
|
if (!items.length) return;
|
|
$('#reportLogTb').innerHTML = items.map(r => `
|
|
<tr>
|
|
<td style="color:var(--muted)">${r.sent_at}</td>
|
|
<td>${r.kind === 'premarket' ? '🌅 盘前' : '🌇 盘后'}</td>
|
|
<td>${escapeHtml(r.subject)}</td>
|
|
<td><span class="tag ${r.status === 'sent' ? 'tag-推荐' : r.status === 'quiet' ? 'tag-关注' : 'tag-负'}">${r.status === 'sent' ? '已发送' : r.status === 'quiet' ? '🌙 静默' : '失败'}</span></td>
|
|
<td style="color:var(--text2);font-size:12px">${escapeHtml(r.message || '')}</td>
|
|
</tr>`).join('');
|
|
} catch (e) {}
|
|
}
|
|
|
|
async function reseed() {
|
|
if (!confirm('将清空全部业务数据并重新生成(含向量索引重建,需 1-3 分钟),确定继续?')) return;
|
|
try {
|
|
await api('/api/admin/reseed', { method: 'POST' });
|
|
toast('重灌任务已启动');
|
|
} catch (e) {
|
|
toast('启动失败');
|
|
}
|
|
}
|
|
|
|
async function health() {
|
|
$('#healthBox').innerHTML = '<div class="loading"><span class="spin"></span> 检查中…</div>';
|
|
try {
|
|
const d = await api('/api/admin/healthcheck');
|
|
$('#healthBox').innerHTML = `<div class="kpi-grid">
|
|
${Object.entries(d).map(([k, v]) => `
|
|
<div class="kpi">
|
|
<div class="kpi-label">${k}</div>
|
|
<div class="kpi-value" style="font-size:14px;color:${v.includes('ok') ? 'var(--down)' : 'var(--up)'}">${escapeHtml(v)}</div>
|
|
</div>`).join('')}
|
|
</div>`;
|
|
} catch (e) {
|
|
$('#healthBox').innerHTML = '<div class="empty">检查失败</div>';
|
|
}
|
|
}
|
|
|
|
refreshStats();
|
|
loadReportLog();
|
|
loadReportQuiet();
|