You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
buffer_platform/app/static/review_plan.js

681 lines
28 KiB

const API_BASE = '/api/v1/review';
const DATA_API_BASE = '/api/v1/data';
let currentPlanId = null;
let currentPlanData = null;
// ==================== 初始化 ====================
document.addEventListener('DOMContentLoaded', function () {
setupEventListeners();
loadLatestPlan();
});
function setupEventListeners() {
document.getElementById('date-selector').addEventListener('change', function (e) {
const selectedDate = e.target.value;
if (selectedDate) {
loadPlanByDate(selectedDate);
}
});
document.getElementById('btn-generate').addEventListener('click', function () {
handleGenerate();
});
document.getElementById('btn-clear').addEventListener('click', function () {
handleClear();
});
}
// ==================== 生成复盘 ====================
function handleGenerate() {
const dateSelector = document.getElementById('date-selector');
let reviewDate = dateSelector.value;
if (!reviewDate) {
// 未选日期时默认使用当天
reviewDate = new Date().toISOString().split('T')[0];
}
doGenerate(reviewDate);
}
function doGenerate(dateStr) {
showLoading();
fetch(`${API_BASE}/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ review_date: dateStr })
})
.then(res => res.json())
.then(result => {
hideLoading();
if (result.success) {
const s = result.data.summary;
alert(
`复盘与计划生成成功!\n` +
`日期: ${s.review_date} ${s.week_day}\n` +
`品种数: ${s.symbol_count}\n` +
`交易机会: ${s.opportunity_count}`
);
loadLatestPlan();
} else {
alert('生成失败: ' + (result.message || '未知错误'));
}
})
.catch(err => {
hideLoading();
console.error('生成复盘计划失败:', err);
alert('生成失败,请稍后重试');
});
}
// ==================== 清除数据 ====================
function handleClear() {
if (!confirm('确定要清除所有复盘与交易计划数据吗?此操作不可恢复。')) return;
fetch(`${API_BASE}/v2/clear`, { method: 'DELETE' })
.then(res => res.json())
.then(data => {
if (data.success) {
alert('复盘数据已清除');
document.getElementById('date-selector').value = '';
currentPlanId = null;
currentPlanData = null;
hideAllSections();
showSection('empty-state');
loadLatestPlan();
} else {
alert(data.message || '清除失败');
}
})
.catch(err => {
console.error('清除数据失败:', err);
alert('清除失败,请稍后重试');
});
}
// ==================== 加载数据 ====================
function loadLatestPlan() {
fetch(`${API_BASE}/v2/plans?limit=1`)
.then(res => res.json())
.then(data => {
if (data.success && data.data && data.data.length > 0) {
const plan = data.data[0];
const dateSelector = document.getElementById('date-selector');
if (dateSelector && plan.review_date) {
dateSelector.value = plan.review_date;
}
loadAndRender(plan.id);
} else {
hideAllSections();
showSection('empty-state');
}
})
.catch(err => {
console.error('加载最新计划失败:', err);
});
}
function loadPlanByDate(date) {
showLoading();
fetch(`${API_BASE}/v2/plan/by-date/${date}`)
.then(res => res.json())
.then(data => {
hideLoading();
if (data.success && data.data) {
currentPlanId = data.data.meta.id;
currentPlanData = data.data;
renderAll(data.data);
} else {
hideAllSections();
showSection('empty-state');
}
})
.catch(err => {
hideLoading();
console.error('按日期加载计划失败:', err);
});
}
function loadAndRender(planId) {
showLoading();
fetch(`${API_BASE}/v2/plan/${planId}`)
.then(res => res.json())
.then(result => {
hideLoading();
if (result.success && result.data) {
currentPlanId = planId;
currentPlanData = result.data;
renderAll(result.data);
} else {
alert('加载计划详情失败');
}
})
.catch(err => {
hideLoading();
console.error('加载计划详情失败:', err);
alert('加载失败,请稍后重试');
});
}
// ==================== 渲染总入口 ====================
function renderAll(data) {
// 显示内容区域
const contentArea = document.getElementById('content-area');
if (contentArea) contentArea.style.display = '';
hideSection('empty-state');
const { meta, scores, plans, sectors } = data;
renderHero(meta);
renderStats(meta, scores);
renderRisk(meta);
renderGreenSection(plans, scores);
renderYellowSection(scores);
renderRedSection(scores);
renderRankingTable(scores);
renderDetails(scores);
renderSectors(sectors);
}
// ==================== Hero 区域 ====================
function renderHero(meta) {
const el = document.getElementById('hero-section');
if (!el) return;
el.innerHTML = `
<div class="hero-title">
<h1>${meta.review_date} <span class="hero-weekday">${meta.week_day || ''}</span></h1>
</div>
<div class="hero-subtitle">${meta.data_basis || ''}</div>
<div class="hero-conclusion">${meta.core_conclusion || ''}</div>
`;
showSection('hero-section');
}
// ==================== 统计卡片 ====================
function renderStats(meta, scores) {
const el = document.getElementById('stats-grid');
if (!el) return;
const top3 = (scores || []).slice(0, 3);
const top3Html = top3.map((s, i) =>
`<span class="stat-top-item">${i + 1}. ${s.symbol} ${s.name || ''} <b>${s.composite_score}</b></span>`
).join('');
const bullCount = meta.bull_count || 0;
const bearCount = meta.bear_count || 0;
const neutralCount = meta.neutral_count || 0;
const oppCount = meta.opportunity_count || 0;
el.innerHTML = `
<div class="stat-card">
<div class="stat-card-label">核心结论</div>
<div class="stat-card-value">${meta.core_conclusion || '-'}</div>
</div>
<div class="stat-card">
<div class="stat-card-label">综合评分 Top 3</div>
<div class="stat-card-value stat-top-list">${top3Html || '暂无'}</div>
</div>
<div class="stat-card">
<div class="stat-card-label">市场概览</div>
<div class="stat-card-value stat-overview">
<span class="stat-bull">多头 <b>${bullCount}</b></span>
<span class="stat-bear">空头 <b>${bearCount}</b></span>
<span class="stat-neutral">中性 <b>${neutralCount}</b></span>
<span class="stat-opp">机会 <b>${oppCount}</b></span>
</div>
</div>
`;
showSection('stats-grid');
}
// ==================== 风险提示 ====================
function renderRisk(meta) {
const el = document.getElementById('risk-banner');
if (!el) return;
const warnings = meta.risk_warnings || [];
if (warnings.length === 0) {
el.innerHTML = `<div class="risk-title">⚠️ 风险提示</div><div class="risk-list">暂无特别风险提示</div>`;
} else {
const items = warnings.map(w => `<li class="risk-item">${w}</li>`).join('');
el.innerHTML = `
<div class="risk-title"> 风险提示</div>
<ul class="risk-list">${items}</ul>
`;
}
showSection('risk-banner');
}
// ==================== 绿色机会区 ====================
function renderGreenSection(plans, scores) {
const el = document.getElementById('green-section');
if (!el) return;
if (!plans || plans.length === 0) {
el.innerHTML = `<div class="section-title green-title">🟢 交易机会(绿色区)</div><div class="empty-hint">暂无交易机会</div>`;
showSection('green-section');
return;
}
const scoreMap = {};
(scores || []).forEach(s => { scoreMap[s.symbol] = s; });
const cards = plans.map(plan => {
const s = scoreMap[plan.symbol] || {};
const dirClass = plan.direction === 'long' ? 'dir-long' : 'dir-short';
const dirText = plan.direction === 'long' ? '做多' : '做空';
return `
<div class="opp-card green-card">
<div class="opp-card-header">
<a href="javascript:void(0)" class="symbol-link" onclick="showDetail('${plan.symbol}','${plan.name || ''}')">${plan.symbol} ${plan.name || ''}</a>
<span class="score-badge">${plan.composite_score}</span>
</div>
<div class="opp-card-rows">
<div class="opp-row"><span class="opp-label">方向</span><span class="${dirClass}">${dirText}</span></div>
<div class="opp-row"><span class="opp-label">收盘价</span><span>${formatPrice(s.close_price)}</span></div>
<div class="opp-row"><span class="opp-label">涨跌幅</span>${formatChange(s.change_pct)}</div>
<div class="opp-row"><span class="opp-label">量比</span><span>${s.volume_ratio != null ? s.volume_ratio.toFixed(2) : '-'}</span></div>
</div>
<div class="score-bars">
<div class="score-bar-row"><span class="score-bar-label">振幅</span>${scoreBar(plan.amplitude_score)}</div>
<div class="score-bar-row"><span class="score-bar-label">量能</span>${scoreBar(plan.volume_score)}</div>
<div class="score-bar-row"><span class="score-bar-label">趋势</span>${scoreBar(plan.trend_score)}</div>
<div class="score-bar-row"><span class="score-bar-label">活跃度</span>${scoreBar(plan.activity_score)}</div>
</div>
<div class="plan-box">
<div class="plan-box-row"><span class="plan-box-label">入场区间</span><span class="plan-box-val">${formatPrice(plan.entry_low)} - ${formatPrice(plan.entry_high)}</span></div>
<div class="plan-box-row"><span class="plan-box-label">止损</span><span class="plan-box-val stop">${formatPrice(plan.stop_loss)}</span></div>
<div class="plan-box-row"><span class="plan-box-label">目标1</span><span class="plan-box-val target">${formatPrice(plan.target1)}</span></div>
<div class="plan-box-row"><span class="plan-box-label">目标2</span><span class="plan-box-val target">${formatPrice(plan.target2)}</span></div>
<div class="plan-box-row"><span class="plan-box-label">触发条件</span><span class="plan-box-val trigger">${plan.trigger || '-'}</span></div>
</div>
</div>`;
}).join('');
el.innerHTML = `
<div class="section-title green-title">🟢 交易机会绿色区</div>
<div class="opp-grid">${cards}</div>
`;
showSection('green-section');
}
// ==================== 黄色关注区 ====================
function renderYellowSection(scores) {
const el = document.getElementById('yellow-section');
if (!el) return;
const yellowItems = (scores || []).filter(s => s.category === 'yellow');
if (yellowItems.length === 0) {
el.innerHTML = `<div class="section-title yellow-title">🟡 关注列表(黄色区)</div><div class="empty-hint">暂无关注品种</div>`;
showSection('yellow-section');
return;
}
const items = yellowItems.map(s => `
<div class="watch-item yellow-item">
<div class="watch-left">
<a href="javascript:void(0)" class="symbol-link" onclick="showDetail('${s.symbol}','${s.name || ''}')">${s.symbol} ${s.name || ''}</a>
${directionTag(s.direction_tag)}
</div>
<div class="watch-mid">
${scoreBar(s.composite_score)}
</div>
<div class="watch-right">
<span class="watch-score">${s.composite_score}</span>
${formatChange(s.change_pct)}
</div>
</div>
`).join('');
el.innerHTML = `
<div class="section-title yellow-title">🟡 关注列表黄色区</div>
<div class="watch-list">${items}</div>
`;
showSection('yellow-section');
}
// ==================== 红色风险区 ====================
function renderRedSection(scores) {
const el = document.getElementById('red-section');
if (!el) return;
const redItems = (scores || []).filter(s => s.category === 'red');
if (redItems.length === 0) {
el.innerHTML = `<div class="section-title red-title">🔴 规避列表(红色区)</div><div class="empty-hint">暂无规避品种</div>`;
showSection('red-section');
return;
}
const items = redItems.map(s => `
<div class="watch-item red-item">
<div class="watch-left">
<a href="javascript:void(0)" class="symbol-link" onclick="showDetail('${s.symbol}','${s.name || ''}')">${s.symbol} ${s.name || ''}</a>
${directionTag(s.direction_tag)}
</div>
<div class="watch-mid">
${scoreBar(s.composite_score)}
</div>
<div class="watch-right">
<span class="watch-score">${s.composite_score}</span>
${formatChange(s.change_pct)}
</div>
</div>
`).join('');
el.innerHTML = `
<div class="section-title red-title">🔴 规避列表红色区</div>
<div class="watch-list">${items}</div>
`;
showSection('red-section');
}
// ==================== 排名表格 ====================
function renderRankingTable(scores) {
const el = document.getElementById('ranking-table');
if (!el) return;
if (!scores || scores.length === 0) {
el.innerHTML = `<div class="section-title">📊 综合排名</div><div class="empty-hint">暂无排名数据</div>`;
showSection('ranking-table');
return;
}
const rows = scores.map((s, i) => {
const rank = s.rank || (i + 1);
const rankClass = rank <= 3 ? 'rank-top' : '';
return `
<tr>
<td class="${rankClass}">${rank}</td>
<td><a href="javascript:void(0)" class="symbol-link" onclick="showDetail('${s.symbol}','${s.name || ''}')">${s.symbol}</a></td>
<td>${s.name || ''}</td>
<td>${formatPrice(s.close_price)}</td>
<td><div class="table-score-bar-wrap">${scoreBar(s.composite_score)}<span class="table-score-num">${s.composite_score}</span></div></td>
<td>${s.amplitude_pct != null ? s.amplitude_pct.toFixed(2) + '%' : '-'}</td>
<td>${formatChange(s.change_pct)}</td>
<td>${s.volume_ratio != null ? s.volume_ratio.toFixed(2) : '-'}</td>
<td>${s.trend_score != null ? s.trend_score : '-'}</td>
<td>${directionTag(s.direction_tag)}</td>
<td>${s.activity_score != null ? s.activity_score : '-'}</td>
</tr>`;
}).join('');
el.innerHTML = `
<div class="section-title">📊 综合排名</div>
<div class="table-wrap">
<table class="ranking-tbl">
<thead>
<tr>
<th>#</th>
<th>代码</th>
<th>名称</th>
<th>收盘价</th>
<th>综合评分</th>
<th>振幅%</th>
<th>涨跌幅</th>
<th>量比</th>
<th>趋势</th>
<th>方向</th>
<th>活跃度</th>
</tr>
</thead>
<tbody>${rows}</tbody>
</table>
</div>
`;
showSection('ranking-table');
}
// ==================== 品种详情卡片 ====================
function renderDetails(scores) {
const el = document.getElementById('details-section');
if (!el) return;
if (!scores || scores.length === 0) {
el.innerHTML = `<div class="section-title">📋 品种详情</div><div class="empty-hint">暂无详情数据</div>`;
showSection('details-section');
return;
}
const cards = scores.map(s => {
const pivotTags = [];
if (s.pivot != null) pivotTags.push(`<span class="pivot-tag pivot">Pivot ${formatPrice(s.pivot)}</span>`);
if (s.r1 != null) pivotTags.push(`<span class="pivot-tag r1">R1 ${formatPrice(s.r1)}</span>`);
if (s.r2 != null) pivotTags.push(`<span class="pivot-tag r2">R2 ${formatPrice(s.r2)}</span>`);
if (s.s1 != null) pivotTags.push(`<span class="pivot-tag s1">S1 ${formatPrice(s.s1)}</span>`);
if (s.s2 != null) pivotTags.push(`<span class="pivot-tag s2">S2 ${formatPrice(s.s2)}</span>`);
return `
<div class="detail-card">
<div class="detail-card-header">
<span class="detail-symbol">${s.symbol} ${s.name || ''}</span>
<span class="detail-score">${s.composite_score}</span>
</div>
<div class="detail-metrics">
<div class="detail-metric"><span class="dm-label">收盘价</span><span class="dm-val">${formatPrice(s.close_price)}</span></div>
<div class="detail-metric"><span class="dm-label">振幅</span><span class="dm-val">${s.amplitude_pct != null ? s.amplitude_pct.toFixed(2) + '%' : '-'}</span></div>
<div class="detail-metric"><span class="dm-label">涨跌幅</span><span class="dm-val">${formatChange(s.change_pct)}</span></div>
<div class="detail-metric"><span class="dm-label">量比</span><span class="dm-val">${s.volume_ratio != null ? s.volume_ratio.toFixed(2) : '-'}</span></div>
<div class="detail-metric"><span class="dm-label">振幅评分</span><span class="dm-val">${s.amplitude_score != null ? s.amplitude_score : '-'}</span></div>
<div class="detail-metric"><span class="dm-label">量能评分</span><span class="dm-val">${s.volume_score != null ? s.volume_score : '-'}</span></div>
<div class="detail-metric"><span class="dm-label">趋势评分</span><span class="dm-val">${s.trend_score != null ? s.trend_score : '-'}</span></div>
<div class="detail-metric"><span class="dm-label">活跃度</span><span class="dm-val">${s.activity_score != null ? s.activity_score : '-'}</span></div>
<div class="detail-metric"><span class="dm-label">60m趋势</span><span class="dm-val">${s.trend_60m != null ? s.trend_60m : '-'}</span></div>
<div class="detail-metric"><span class="dm-label">15m趋势</span><span class="dm-val">${s.trend_15m != null ? s.trend_15m : '-'}</span></div>
<div class="detail-metric"><span class="dm-label">5m趋势</span><span class="dm-val">${s.trend_5m != null ? s.trend_5m : '-'}</span></div>
<div class="detail-metric"><span class="dm-label">方向</span><span class="dm-val">${directionTag(s.direction_tag)}</span></div>
</div>
<div class="detail-pivots">${pivotTags.join('')}</div>
</div>`;
}).join('');
el.innerHTML = `
<div class="section-title">📋 品种详情</div>
<div class="detail-grid">${cards}</div>
`;
showSection('details-section');
}
// ==================== 板块热力 ====================
function renderSectors(sectors) {
const el = document.getElementById('sectors-section');
if (!el) return;
if (!sectors || sectors.length === 0) {
el.innerHTML = `<div class="section-title">🔥 板块热力</div><div class="empty-hint">暂无板块数据</div>`;
showSection('sectors-section');
return;
}
const cards = sectors.map(sec => {
const heatLevel = sec.heat_level || 0;
const fireIcons = '🔥'.repeat(Math.min(heatLevel, 5));
const iceIcons = heatLevel <= 1 ? '🧊' : '';
const heatIndicator = heatLevel >= 3 ? fireIcons : (heatLevel <= 1 ? iceIcons : fireIcons);
const members = (sec.members || []).map(m =>
`<span class="sector-member">${m.symbol} ${m.name || ''} <b>${m.score}</b></span>`
).join('');
return `
<div class="sector-card heat-${heatLevel}">
<div class="sector-header">
<span class="sector-name">${sec.sector_name}</span>
<span class="sector-heat">${heatIndicator}</span>
</div>
<div class="sector-stats">
<div class="sector-stat"><span class="ss-label">均分</span><span class="ss-val">${sec.avg_score != null ? sec.avg_score.toFixed(1) : '-'}</span></div>
<div class="sector-stat"><span class="ss-label">方向</span><span class="ss-val">${sec.direction || '-'}</span></div>
<div class="sector-stat"><span class="ss-label">领涨</span><span class="ss-val">${sec.leader_symbol || '-'} ${sec.leader_score != null ? sec.leader_score.toFixed(1) : ''}</span></div>
</div>
<div class="sector-members">${members || '暂无成员'}</div>
</div>`;
}).join('');
el.innerHTML = `
<div class="section-title">🔥 板块热力</div>
<div class="sector-grid">${cards}</div>
`;
showSection('sectors-section');
}
// ==================== 品种详情弹窗 ====================
function showDetail(symbol, name) {
const scores = currentPlanData ? currentPlanData.scores : [];
const s = scores.find(x => x.symbol === symbol);
if (!s) return;
const modal = document.getElementById('detail-modal');
if (!modal) return;
document.getElementById('modal-title').textContent = `${symbol} ${name || s.name || ''}`;
const body = document.getElementById('modal-body');
body.innerHTML = `
<div class="modal-section">
<div class="modal-section-title">基本信息</div>
<div class="modal-metrics-grid">
<div class="modal-metric"><span class="mm-label">收盘价</span><span class="mm-val">${formatPrice(s.close_price)}</span></div>
<div class="modal-metric"><span class="mm-label">振幅</span><span class="mm-val">${s.amplitude_pct != null ? s.amplitude_pct.toFixed(2) + '%' : '-'}</span></div>
<div class="modal-metric"><span class="mm-label">涨跌幅</span><span class="mm-val">${formatChange(s.change_pct)}</span></div>
<div class="modal-metric"><span class="mm-label">量比</span><span class="mm-val">${s.volume_ratio != null ? s.volume_ratio.toFixed(2) : '-'}</span></div>
<div class="modal-metric"><span class="mm-label">综合评分</span><span class="mm-val"><b>${s.composite_score}</b></span></div>
<div class="modal-metric"><span class="mm-label">排名</span><span class="mm-val">#${s.rank || '-'}</span></div>
</div>
</div>
<div class="modal-section">
<div class="modal-section-title">评分明细</div>
<div class="modal-metrics-grid">
<div class="modal-metric"><span class="mm-label">振幅评分</span><span class="mm-val">${s.amplitude_score}</span></div>
<div class="modal-metric"><span class="mm-label">量能评分</span><span class="mm-val">${s.volume_score}</span></div>
<div class="modal-metric"><span class="mm-label">趋势评分</span><span class="mm-val">${s.trend_score}</span></div>
<div class="modal-metric"><span class="mm-label">活跃度</span><span class="mm-val">${s.activity_score}</span></div>
</div>
</div>
<div class="modal-section">
<div class="modal-section-title">多周期趋势</div>
<div class="modal-metrics-grid">
<div class="modal-metric"><span class="mm-label">60分钟</span><span class="mm-val">${s.trend_60m != null ? s.trend_60m : '-'}</span></div>
<div class="modal-metric"><span class="mm-label">15分钟</span><span class="mm-val">${s.trend_15m != null ? s.trend_15m : '-'}</span></div>
<div class="modal-metric"><span class="mm-label">5分钟</span><span class="mm-val">${s.trend_5m != null ? s.trend_5m : '-'}</span></div>
<div class="modal-metric"><span class="mm-label">方向</span><span class="mm-val">${directionTag(s.direction_tag)}</span></div>
</div>
</div>
<div class="modal-section">
<div class="modal-section-title">枢轴点位</div>
<div class="modal-pivots">
${s.r2 != null ? `<span class="pivot-tag r2">R2 ${formatPrice(s.r2)}</span>` : ''}
${s.r1 != null ? `<span class="pivot-tag r1">R1 ${formatPrice(s.r1)}</span>` : ''}
${s.pivot != null ? `<span class="pivot-tag pivot">Pivot ${formatPrice(s.pivot)}</span>` : ''}
${s.s1 != null ? `<span class="pivot-tag s1">S1 ${formatPrice(s.s1)}</span>` : ''}
${s.s2 != null ? `<span class="pivot-tag s2">S2 ${formatPrice(s.s2)}</span>` : ''}
</div>
</div>
`;
modal.classList.add('show');
}
function closeModal() {
const modal = document.getElementById('detail-modal');
if (modal) modal.classList.remove('show');
}
// ==================== 工具函数 ====================
function scoreBarColor(score) {
if (score > 70) return 'var(--gn)';
if (score > 40) return 'var(--ac)';
if (score > 20) return 'var(--yw)';
return 'var(--rd)';
}
function scoreBar(score) {
const color = scoreBarColor(score);
const pct = Math.min(Math.max(score, 0), 100);
return `<div class="score-bar"><div class="score-bar-fill" style="width:${pct}%;background:${color}"></div><span class="score-bar-text">${score}</span></div>`;
}
function directionTag(tag) {
if (!tag) return '<span class="dir-badge">-</span>';
let cls = 'dir-neutral';
if (tag.includes('强多') || tag.includes('多头共振')) cls = 'dir-strong-long';
else if (tag.includes('多')) cls = 'dir-long';
else if (tag.includes('强空') || tag.includes('空头共振')) cls = 'dir-strong-short';
else if (tag.includes('空')) cls = 'dir-short';
return `<span class="dir-badge ${cls}">${tag}</span>`;
}
function formatPrice(price) {
if (price == null || isNaN(price)) return '-';
return Number(price).toFixed(2);
}
function formatChange(pct) {
if (pct == null || isNaN(pct)) return '<span class="change-neutral">-</span>';
const val = Number(pct);
const sign = val > 0 ? '+' : '';
const cls = val > 0 ? 'change-up' : (val < 0 ? 'change-down' : 'change-neutral');
return `<span class="${cls}">${sign}${val.toFixed(2)}%</span>`;
}
function showSection(id) {
const el = document.getElementById(id);
if (el) el.style.display = '';
}
function hideSection(id) {
const el = document.getElementById(id);
if (el) el.style.display = 'none';
}
function hideAllSections() {
const contentArea = document.getElementById('content-area');
if (contentArea) contentArea.style.display = 'none';
const ids = [
'hero-section', 'stats-grid', 'risk-banner',
'green-section', 'yellow-section', 'red-section',
'ranking-table', 'details-section', 'sectors-section',
'summary-content'
];
ids.forEach(id => hideSection(id));
}
function showLoading() {
let overlay = document.getElementById('loading-overlay');
if (!overlay) {
overlay = document.createElement('div');
overlay.id = 'loading-overlay';
overlay.innerHTML = '<div class="loading-spinner">⏳ 加载中...</div>';
document.body.appendChild(overlay);
}
overlay.style.display = 'flex';
}
function hideLoading() {
const overlay = document.getElementById('loading-overlay');
if (overlay) overlay.style.display = 'none';
}