/* 自动化监控页:舆情驱动 + 持仓跟踪 双选项卡 */
let autoSettings = null;
/* ===================== 选项卡 ===================== */
$$('.tab-item').forEach(t => t.onclick = () => {
$$('.tab-item').forEach(x => x.classList.toggle('active', x === t));
$$('.tab-panel').forEach(p => p.style.display = (p.id === 'panel-' + t.dataset.tab) ? '' : 'none');
});
/* ===================== 加载 ===================== */
async function loadAuto() {
try {
autoSettings = await api('/api/settings');
const mono = autoSettings.monitor, tr = autoSettings.tracking;
// 舆情
$('#monitorEnabled').checked = !!mono.enabled;
$('#monitorInterval').value = mono.interval_min;
$('#monitorImportance').value = mono.importance_threshold;
$('#monitorSentiment').value = mono.sentiment_weight;
$('#monitorKeywords').value = (mono.keywords || []).join(',');
const cats = mono.categories || [];
$$('#monitorCats input').forEach(c => c.checked = cats.includes(c.value));
renderMonitorState(autoSettings.state);
// 静默期(舆情)
const mq = (autoSettings.quiet && autoSettings.quiet.monitor) || {};
$('#monitorQuietEnabled').checked = !!mq.enabled;
$('#monitorQuietRanges').value = mq.ranges || '';
// 持仓跟踪
$('#trackingEnabled').checked = !!tr.enabled;
$('#trackingInterval').value = tr.interval_min;
$('#trackingImpact').value = tr.impact_threshold;
$('#trackingNotify').checked = !!tr.notify;
renderTrackingState(tr);
// 静默期(跟踪)
const tq = (autoSettings.quiet && autoSettings.quiet.tracking) || {};
$('#trackingQuietEnabled').checked = !!tq.enabled;
$('#trackingQuietRanges').value = tq.ranges || '';
loadLog();
loadTrackReports();
loadPositions();
loadTargets();
} catch (e) { toast('加载失败'); }
}
function renderMonitorState(state) {
$('#monitorState').innerHTML = `
已处理新闻水位
#${state.last_news_id || 0}
上次扫描
${state.last_scan || '—'}
上次通知
${state.last_sent_count || 0} 条
`;
}
function renderTrackingState(tr) {
$('#trackingState').innerHTML = `
自动跟踪
${tr.enabled ? '✅ 开启' : '⏸ 关闭'}(每 ${tr.interval_min} 分钟)
上次运行
${tr.last_run || '—'}
最近跟踪
${tr.last_stock || '—'}
最近告警影响度
${tr.last_alert ? tr.last_alert + '/100' : '—'}
`;
}
function collectMonitor() {
const cats = $$('#monitorCats input:checked').map(c => c.value);
return {
monitor_enabled: $('#monitorEnabled').checked,
monitor_interval: $('#monitorInterval').value,
monitor_importance: $('#monitorImportance').value,
monitor_sentiment: $('#monitorSentiment').value,
monitor_categories: cats.join(','),
monitor_keywords: $('#monitorKeywords').value
};
}
function collectMonitorQuiet() {
return {
enabled: $('#monitorQuietEnabled').checked,
ranges: $('#monitorQuietRanges').value.trim()
};
}
function collectTrackingQuiet() {
return {
enabled: $('#trackingQuietEnabled').checked,
ranges: $('#trackingQuietRanges').value.trim()
};
}
/* ===================== 保存 ===================== */
async function saveMonitor() {
try {
const r = await api('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({
monitor: collectMonitor(),
quiet: { monitor: collectMonitorQuiet() }
}) });
toast(r.msg || '已保存');
} catch (e) { toast('保存失败:' + e.message); }
}
async function saveTracking() {
try {
const r = await api('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tracking: {
tracking_enabled: $('#trackingEnabled').checked,
tracking_interval: $('#trackingInterval').value,
tracking_impact_threshold: $('#trackingImpact').value,
tracking_notify: $('#trackingNotify').checked
}, quiet: { tracking: collectTrackingQuiet() } }) });
toast(r.msg || '已保存');
} catch (e) { toast('保存失败:' + e.message); }
}
/* ===================== 动作 ===================== */
async function scanNow() {
const btn = event.target; btn.disabled = true;
try {
const r = await api('/api/monitor/scan', { method: 'POST' });
toast(`扫描完成:检查 ${r.checked} 条,重要 ${r.important} 条,发送 ${r.sent} 条`);
loadAuto();
} catch (e) { toast('扫描失败:' + e.message); }
btn.disabled = false;
}
async function runTrackAll() {
const btn = event.target; btn.disabled = true;
try {
const r = await api('/api/tracking/run', { method: 'POST' });
toast(r.msg || '已启动');
setTimeout(loadTrackReports, 5000);
setTimeout(loadTrackReports, 30000);
setTimeout(loadTrackReports, 70000);
} catch (e) { toast('启动失败:' + e.message); }
btn.disabled = false;
}
/* ===================== 持仓(自选股)管理 ===================== */
async function loadPositions() {
try {
const d = await api('/api/watchlist');
const items = d.items || [];
if (!items.length) {
$('#posManage').innerHTML = '暂无持仓。在下方搜索添加,或到
股票池 / 个股详情页 ⭐ 添加
';
return;
}
$('#posManage').innerHTML = `
| 股票 | 现价 | 今日 | 评分 | 操作 |
${items.map(p => `
| ${p.name} ${p.code} · ${p.industry} |
${p.close} |
${fmtPct(p.change_pct)} |
${p.score} |
|
`).join('')}
`;
} catch (e) { $('#posManage').innerHTML = '加载失败
'; }
}
async function addPos() {
const kw = $('#posSearch').value.trim();
if (!kw) return;
try {
const d = await api('/api/stocks?keyword=' + encodeURIComponent(kw) + '&per=5');
const items = d.items || [];
if (!items.length) { toast('未找到该股票'); return; }
// 精确匹配或取第一个
const hit = items.find(x => x.code === kw || x.name === kw) || items[0];
await api('/api/watchlist/' + hit.code, { method: 'POST' });
toast(`已添加 ${hit.name} 为持仓`);
$('#posSearch').value = '';
loadPositions();
} catch (e) { toast('添加失败'); }
}
async function removePos(code) {
try {
await api('/api/watchlist/' + code, { method: 'DELETE' });
toast('已移除持仓');
loadPositions();
} catch (e) { toast('操作失败'); }
}
/* ===================== 概念/主题/股票目标管理 ===================== */
async function loadTargets() {
try {
const d = await api('/api/targets');
const items = d.items || [];
if (!items.length) {
$('#tgtManage').innerHTML = '暂无跟踪目标,添加概念/主题(如 AI算力、低空经济)或个股目标
';
return;
}
$('#tgtManage').innerHTML = `
| 类型 | 目标 | 检索关键词 | 状态 | 操作 |
${items.map(t => `
| ${t.type === 'concept' ? '概念/主题' : '股票'} |
${escapeHtml(t.name)}${t.code ? ` ${t.code} ` : ''} |
${escapeHtml(t.keywords || '—')} |
${t.enabled ? '跟踪中' : '停用'} |
|
`).join('')}
`;
} catch (e) { $('#tgtManage').innerHTML = '加载失败
'; }
}
async function addTarget() {
const type = $('#tgtType').value;
const name = $('#tgtName').value.trim();
const keywords = $('#tgtKeywords').value.trim();
if (!name) { toast('请输入目标名称'); return; }
try {
await api('/api/targets', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type, name, keywords }) });
toast('已添加跟踪目标');
$('#tgtName').value = ''; $('#tgtKeywords').value = '';
loadTargets();
} catch (e) { toast('添加失败:' + e.message); }
}
async function removeTarget(id) {
try {
await api('/api/targets/' + id, { method: 'DELETE' });
toast('已移除目标');
loadTargets();
} catch (e) { toast('操作失败'); }
}
/* ===================== 日志与报告 ===================== */
async function loadLog() {
try {
const d = await api('/api/monitor/log');
const items = d.items || [];
if (!items.length) return;
$('#logTb').innerHTML = items.map(n => `
| ${n.sent_at} |
${escapeHtml(n.title)} |
${n.category} |
${n.sentiment > 0 ? '利好' : n.sentiment < 0 ? '利空' : '中性'} (${Number(n.sentiment).toFixed(2)}) |
${n.importance} |
${n.status === 'sent' ? '已发送' : n.status === 'skipped' ? '已跳过' : n.status === 'quiet' ? '🌙 静默' : '失败'} |
`).join('');
} catch (e) {}
}
async function loadTrackReports() {
try {
const d = await api('/api/tracking');
const items = d.items || [];
if (!items.length) return;
$('#trackTb').innerHTML = items.slice(0, 10).map(r => {
let meta = {};
try { meta = JSON.parse(r.meta || '{}'); } catch (e) {}
const sigCls = meta.impact_score >= 65 ? 'tag-强烈推荐' : meta.impact_score >= 45 ? 'tag-推荐' : 'tag-关注';
const isConcept = r.target_type === 'concept';
return `
| ${r.created_at} |
${isConcept ? '概念' : '股票'} |
${r.stock_name}${isConcept ? '' : ` ${r.code} `} |
${meta.impact_score || '—'} |
${meta.change_kind || '—'} |
${escapeHtml(meta.summary || '')} |
|
`;
}).join('');
} catch (e) {}
}
async function showTrack(id) {
try {
const d = await api('/api/tracking/' + id);
const meta = d.meta || {}, src = d.sources || {};
const sigCls = meta.impact_score >= 65 ? 'tag-强烈推荐' : meta.impact_score >= 45 ? 'tag-推荐' : 'tag-关注';
const isConcept = d.target_type === 'concept';
// 概念报告:展示关键词/资讯/受益个股;股票报告:四环节数据源
let sourceHtml = '';
if (isConcept) {
const dc = (meta.news_counts || {}).direct || 0, rc = (meta.news_counts || {}).rag || 0, nc = (meta.news_counts || {}).related || 0;
sourceHtml = `
🔎 相关资讯(${dc} 条)
${escapeHtml(src.db_news || '')}
🧠 语义检索命中(${rc} 条)
${escapeHtml(src.rag_news || '')}
🏢 受益个股(${nc} 只)
${escapeHtml(src.related || '')} `;
} else {
const newsBlock = (label, icon, seg) => {
if (!seg) return '';
const txt = ((seg.db || '') + (seg.rag || '')).trim();
return `${icon} ${label} ${txt.includes('[') ? txt.split('[').length - 1 : 0}
${escapeHtml(txt || '暂无')} `;
};
sourceHtml = newsBlock('个股直接动态', '📄', src.direct) +
newsBlock('上游产业链(供给/成本)', '⬆️', src.upstream) +
newsBlock('下游产业链(需求/景气)', '⬇️', src.downstream) +
newsBlock('同业竞争', '🏢', src.peers);
}
openModal(`
${isConcept ? '🎯 概念主题跟踪' : '🧭 产业链跟踪'}:${d.stock_name}${d.code && !isConcept ? '(' + d.code + ')' : ''}
${isConcept ? '概念/主题' : d.industry}
影响度 ${meta.impact_score || '—'}/100
${meta.change_kind || '—'}
${d.created_at}
${mdRender(d.report)}
数据源(智能体参考内容)
${sourceHtml}
${isConcept ? '' : `
查看个股 →`}
`);
} catch (e) { toast('加载失败'); }
}
loadAuto();