Files
daily-news-digest/web/public/app.js
T

79 lines
4.5 KiB
JavaScript

const $ = (selector) => document.querySelector(selector);
const escapeHtml = (value) => String(value ?? '').replace(/[&<>'"]/g, (char) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', "'": '&#39;', '"': '&quot;' }[char]));
const categoryNames = { general: '综合', ai_agent: 'AI 与 Agent', agri_hardware: '农业硬件', agri_solutions: '方案落地', agri_services: '农业服务', policy_market: '政策市场' };
function today() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
function formatDate(value) { return value ? new Intl.DateTimeFormat('zh-CN', { year: 'numeric', month: 'long', day: 'numeric', weekday: 'short' }).format(new Date(`${value}T00:00:00`)) : '—'; }
function formatTime(value) { return value ? new Intl.DateTimeFormat('zh-CN', { month: 'short', day: 'numeric' }).format(new Date(value)) : '近期'; }
async function loadHealth() {
try {
const response = await fetch('/api/health');
const label = $('#healthLabel');
if (response.ok) label.innerHTML = '<span class="status-dot"></span>数据库连接正常';
else throw new Error();
} catch { $('#healthLabel').innerHTML = '<span class="status-dot error"></span>数据库暂不可用'; }
}
function renderArticle(article) {
const title = article.translated_title || article.title || '无标题';
const summary = article.translated_summary || article.summary || '暂无摘要';
const category = categoryNames[article.section] || categoryNames[article.category] || '综合';
return `<article class="article">
<div class="article-meta"><span class="tag">${escapeHtml(category)}</span><span>${escapeHtml(article.source_name || '未知来源')}</span><span>·</span><span>${escapeHtml(formatTime(article.published_at))}</span></div>
<h3><a href="${escapeHtml(article.url)}" target="_blank" rel="noreferrer">${escapeHtml(title)}</a></h3>
<p>${escapeHtml(summary)}</p>
</article>`;
}
function renderReports(payload) {
$('#reportCount').textContent = payload.total ?? 0;
const reports = payload.reports || [];
const articleCount = reports.reduce((sum, report) => sum + (report.articles || []).length, 0);
$('#articleCount').textContent = articleCount;
$('#sourceCount').textContent = new Set(reports.flatMap((report) => (report.articles || []).map((item) => item.source_name).filter(Boolean))).size;
if (!reports.length) {
$('#reportList').innerHTML = '<div class="empty-state"><strong>还没有符合条件的日报</strong><span>n8n 完成第一次抓取后,内容会出现在这里。</span></div>';
return;
}
$('#reportList').innerHTML = reports.map((report) => `<article class="report-card card">
<div class="report-top"><div><div class="report-date">${escapeHtml(formatDate(report.report_date))}</div><h2>${escapeHtml(report.title)}</h2><p class="report-intro">${escapeHtml(report.introduction || '今日资讯已按主题整理。')}</p></div><span class="report-status">${report.status === 'published' ? '已发布' : '草稿'}</span></div>
<div class="article-grid">${(report.articles || []).map(renderArticle).join('')}</div>
</article>`).join('');
}
async function loadReports() {
const params = new URLSearchParams();
const date = $('#dateInput').value;
const q = $('#searchInput').value.trim();
const category = $('#categoryInput').value;
if (date) params.set('date', date);
if (q) params.set('q', q);
if (category) params.set('category', category);
$('#reportList').innerHTML = '<div class="empty-state"><span>正在读取日报…</span></div>';
try {
const response = await fetch(`/api/reports?${params}`);
const payload = await response.json();
if (!response.ok) throw new Error(payload.error || '读取失败');
renderReports(payload);
} catch (error) {
$('#reportList').innerHTML = `<div class="empty-state"><strong>暂时无法读取日报</strong><span>${escapeHtml(error.message)}</span></div>`;
}
}
$('#todayLabel').textContent = formatDate(today());
$('#dateInput').value = today();
$('#dateInput').addEventListener('change', loadReports);
$('#categoryInput').addEventListener('change', loadReports);
let searchTimer;
$('#searchInput').addEventListener('input', () => { clearTimeout(searchTimer); searchTimer = setTimeout(loadReports, 280); });
$('#clearFilters').addEventListener('click', () => { $('#dateInput').value = ''; $('#categoryInput').value = ''; $('#searchInput').value = ''; loadReports(); });
loadHealth();
loadReports();