const API_BASE = 'http://localhost:3000/api'; let currentReport = null; let currentListPage = 1; function toggleDateRange() { const useDateRange = document.getElementById('useDateRange').checked; document.getElementById('dateRangeFields').style.display = useDateRange ? 'block' : 'none'; document.getElementById('normalFields').style.display = useDateRange ? 'none' : 'block'; } function toggleDetailDateRange() { const useDetailDateRange = document.getElementById('useDetailDateRange').checked; document.getElementById('detailDateRangeFields').style.display = useDetailDateRange ? 'block' : 'none'; document.getElementById('detailNormalFields').style.display = useDetailDateRange ? 'none' : 'block'; } function switchTab(tabName) { // 隐藏所有标签内容 document.querySelectorAll('.tab-content').forEach(content => { content.classList.remove('active'); }); document.querySelectorAll('.tab').forEach(tab => { tab.classList.remove('active'); }); // 显示选中的标签 document.getElementById(tabName).classList.add('active'); event.target.classList.add('active'); } async function fetchList(pageNum) { const url = document.getElementById('listUrl').value; const page = pageNum || parseInt(document.getElementById('listPage').value) || 1; const loading = document.getElementById('listLoading'); const results = document.getElementById('listResults'); const pagination = document.getElementById('listPagination'); currentListPage = page; document.getElementById('listPage').value = page; loading.classList.add('active'); results.innerHTML = ''; pagination.style.display = 'none'; try { const response = await fetch(`${API_BASE}/list?url=${encodeURIComponent(url)}&page=${page}`); const data = await response.json(); if (data.success) { displayList(data.data, results); updateListPagination(page, data.data.length > 0); } else { results.innerHTML = `
错误: ${data.error}
`; } } catch (error) { results.innerHTML = `
请求失败: ${error.message}
`; } finally { loading.classList.remove('active'); } } function goToListPage(page) { if (page < 1) return; fetchList(page); } function updateListPagination(page, hasData) { const pagination = document.getElementById('listPagination'); const currentPageSpan = document.getElementById('listCurrentPage'); const prevBtn = document.getElementById('listPrevPage'); const firstBtn = document.getElementById('listFirstPage'); const nextBtn = document.getElementById('listNextPage'); if (hasData) { pagination.style.display = 'flex'; currentPageSpan.textContent = page; prevBtn.disabled = page <= 1; firstBtn.disabled = page <= 1; nextBtn.disabled = !hasData; } } function displayList(items, container) { if (items.length === 0) { container.innerHTML = '

没有找到公告

'; return; } const html = `

找到 ${items.length} 条公告

${items.map((item, index) => `

${index + 1}. ${item.title}

发布日期: ${item.date}
查看详情 →
`).join('')}
`; container.innerHTML = html; } async function fetchDetails() { const useDetailDateRange = document.getElementById('useDetailDateRange').checked; const loading = document.getElementById('detailLoading'); const results = document.getElementById('detailResults'); loading.classList.add('active'); results.innerHTML = ''; try { let listData; if (useDetailDateRange) { // 时间范围模式 const startDate = document.getElementById('detailStartDate').value; const endDate = document.getElementById('detailEndDate').value; const maxPages = parseInt(document.getElementById('detailMaxPages').value); if (!startDate && !endDate) { results.innerHTML = '
请至少填写开始日期或结束日期
'; loading.classList.remove('active'); return; } const dateRangeResponse = await fetch(`${API_BASE}/list-daterange`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ startDate, endDate, maxPages }) }); listData = await dateRangeResponse.json(); } else { // 普通模式 - 按数量采集多页 const url = document.getElementById('detailUrl').value; const limit = parseInt(document.getElementById('detailLimit').value); // 采集多页直到获得足够数量 const allItems = []; let page = 1; const maxPagesToFetch = Math.ceil(limit / 10) + 1; // 假设每页约10条 while (allItems.length < limit && page <= maxPagesToFetch) { const listResponse = await fetch(`${API_BASE}/list?url=${encodeURIComponent(url)}&page=${page}`); const pageData = await listResponse.json(); if (!pageData.success) { if (allItems.length === 0) { results.innerHTML = `
错误: ${pageData.error}
`; loading.classList.remove('active'); return; } break; } if (pageData.data.length === 0) { break; } allItems.push(...pageData.data); page++; // 如果还需要更多数据且未到达上限,稍作延迟 if (allItems.length < limit && page <= maxPagesToFetch) { await new Promise(resolve => setTimeout(resolve, 500)); } } listData = { success: true, data: allItems }; } if (!listData.success) { results.innerHTML = `
错误: ${listData.error}
`; return; } // 采集详情 const limit = useDetailDateRange ? listData.data.length : parseInt(document.getElementById('detailLimit').value); const detailResponse = await fetch(`${API_BASE}/details`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ items: listData.data, limit }) }); const detailData = await detailResponse.json(); if (detailData.success) { displayDetails(detailData.data, results); } else { results.innerHTML = `
错误: ${detailData.error}
`; } } catch (error) { results.innerHTML = `
请求失败: ${error.message}
`; } finally { loading.classList.remove('active'); } } function displayDetails(items, container) { const html = `

采集了 ${items.length} 条详情

${items.map((item, index) => `

${index + 1}. ${item.title}

发布日期: ${item.date}
${item.detail ? `
发布时间: ${item.detail.publishTime || '未知'}
${item.detail.budget ? ` ${item.detail.budget.amount}${item.detail.budget.unit} ` : '
未找到预算信息
'} ` : '
采集失败
'}
查看原文 →
`).join('')}
`; container.innerHTML = html; } async function generateReport() { const useDateRange = document.getElementById('useDateRange').checked; const threshold = parseFloat(document.getElementById('reportThreshold').value); const loading = document.getElementById('reportLoading'); const results = document.getElementById('reportResults'); const exportBtn = document.getElementById('exportBtn'); loading.classList.add('active'); results.innerHTML = ''; exportBtn.style.display = 'none'; try { let response; if (useDateRange) { // 时间范围模式 const startDate = document.getElementById('startDate').value; const endDate = document.getElementById('endDate').value; const maxPages = parseInt(document.getElementById('maxPages').value); if (!startDate && !endDate) { results.innerHTML = '
请至少填写开始日期或结束日期
'; loading.classList.remove('active'); return; } response = await fetch(`${API_BASE}/report-daterange`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ startDate, endDate, threshold, maxPages }) }); } else { // 普通模式 const url = document.getElementById('reportUrl').value; const limit = parseInt(document.getElementById('reportLimit').value); response = await fetch(`${API_BASE}/report`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url, limit, threshold }) }); } const data = await response.json(); if (data.success) { currentReport = data.data; displayReport(data.data, results); exportBtn.style.display = 'inline-block'; document.getElementById('sendEmailBtn').style.display = 'inline-block'; } else { results.innerHTML = `
错误: ${data.error}
`; } } catch (error) { results.innerHTML = `
请求失败: ${error.message}
`; } finally { loading.classList.remove('active'); } } function displayReport(report, container) { const html = `

统计摘要

总项目数
${report.summary.total_count}
符合条件
${report.summary.filtered_count}
总金额
${report.summary.total_amount}
阈值
${report.summary.threshold}
${report.projects.length === 0 ? '

暂无符合条件的项目

' : `

项目列表

${report.projects.map((project, index) => `

${index + 1}. ${project.title}

发布日期: ${project.date}
发布时间: ${project.publish_time}
${project.budget.amount}${project.budget.unit}
金额描述: ${project.budget.text}

查看详情 →
`).join('')} `} `; container.innerHTML = html; } async function exportReport() { if (!currentReport) return; // 检查docx库是否加载 if (!window.docx) { alert('Word导出库正在加载中,请稍后再试...'); return; } const report = currentReport; const { Document, Packer, Paragraph, TextRun, HeadingLevel, AlignmentType } = window.docx; // 构建文档段落 const paragraphs = []; // 标题 paragraphs.push( new Paragraph({ text: '南京公共工程建设项目报告', heading: HeadingLevel.HEADING_1, alignment: AlignmentType.CENTER, spacing: { after: 200 } }) ); // 生成时间 paragraphs.push( new Paragraph({ children: [ new TextRun({ text: '生成时间: ', bold: true }), new TextRun({ text: new Date(report.summary.generated_at).toLocaleString('zh-CN') }) ], spacing: { after: 200 } }) ); // 统计摘要标题 paragraphs.push( new Paragraph({ text: '统计摘要', heading: HeadingLevel.HEADING_2, spacing: { before: 200, after: 100 } }) ); // 统计数据 paragraphs.push( new Paragraph({ children: [new TextRun({ text: `• 总项目数: ${report.summary.total_count}` })], spacing: { after: 50 } }), new Paragraph({ children: [new TextRun({ text: `• 超过${report.summary.threshold}的项目: ${report.summary.filtered_count}` })], spacing: { after: 50 } }), new Paragraph({ children: [new TextRun({ text: `• 总金额: ${report.summary.total_amount}` })], spacing: { after: 200 } }) ); // 项目列表标题 paragraphs.push( new Paragraph({ text: '项目列表', heading: HeadingLevel.HEADING_2, spacing: { before: 200, after: 100 } }) ); // 项目详情 if (report.projects.length === 0) { paragraphs.push( new Paragraph({ text: '暂无符合条件的项目。', spacing: { after: 100 } }) ); } else { report.projects.forEach((project, index) => { // 项目标题 paragraphs.push( new Paragraph({ text: `${index + 1}. ${project.title}`, heading: HeadingLevel.HEADING_3, spacing: { before: 150, after: 100 } }) ); // 项目详情 paragraphs.push( new Paragraph({ children: [ new TextRun({ text: '发布日期: ', bold: true }), new TextRun({ text: project.date }) ], spacing: { after: 50 } }), new Paragraph({ children: [ new TextRun({ text: '发布时间: ', bold: true }), new TextRun({ text: project.publish_time }) ], spacing: { after: 50 } }), new Paragraph({ children: [ new TextRun({ text: '预算金额: ', bold: true }), new TextRun({ text: `${project.budget.amount}${project.budget.unit}` }) ], spacing: { after: 50 } }), new Paragraph({ children: [ new TextRun({ text: '链接: ', bold: true }), new TextRun({ text: project.url, color: '0000FF' }) ], spacing: { after: 50 } }), new Paragraph({ children: [ new TextRun({ text: '金额描述: ', bold: true }), new TextRun({ text: project.budget.text }) ], spacing: { after: 100 } }) ); }); } // 创建文档 const doc = new Document({ sections: [{ properties: {}, children: paragraphs }] }); // 生成并下载Word文件 const blob = await Packer.toBlob(doc); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `report_${new Date().getTime()}.docx`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } // ========== 邮件功能 ========== // 页面加载时加载邮件配置 document.addEventListener('DOMContentLoaded', function() { loadEmailConfig(); }); // 保存邮件配置到localStorage function saveEmailConfig() { const config = { smtpHost: document.getElementById('smtpHost').value, smtpPort: parseInt(document.getElementById('smtpPort').value) || 587, smtpUser: document.getElementById('smtpUser').value, smtpPass: document.getElementById('smtpPass').value, recipients: document.getElementById('recipients').value }; // 验证配置 if (!config.smtpHost || !config.smtpUser || !config.smtpPass || !config.recipients) { showEmailStatus('请填写所有必填项', 'error'); return; } // 保存到localStorage localStorage.setItem('emailConfig', JSON.stringify(config)); showEmailStatus('邮件配置已保存', 'success'); } // 从localStorage加载邮件配置 function loadEmailConfig() { const configStr = localStorage.getItem('emailConfig'); if (configStr) { try { const config = JSON.parse(configStr); document.getElementById('smtpHost').value = config.smtpHost || ''; document.getElementById('smtpPort').value = config.smtpPort || 587; document.getElementById('smtpUser').value = config.smtpUser || ''; document.getElementById('smtpPass').value = config.smtpPass || ''; document.getElementById('recipients').value = config.recipients || ''; } catch (e) { console.error('加载邮件配置失败:', e); } } } // 测试邮件配置 async function testEmailConfig() { const config = { smtpHost: document.getElementById('smtpHost').value, smtpPort: parseInt(document.getElementById('smtpPort').value) || 587, smtpUser: document.getElementById('smtpUser').value, smtpPass: document.getElementById('smtpPass').value, recipients: document.getElementById('recipients').value }; // 验证配置 if (!config.smtpHost || !config.smtpUser || !config.smtpPass || !config.recipients) { showEmailStatus('请填写所有必填项', 'error'); return; } // 创建测试报告 const testReport = { summary: { total_count: 1, filtered_count: 1, threshold: '50万元', total_amount: '100.00万元', generated_at: new Date().toISOString() }, projects: [{ title: '这是一封测试邮件', date: new Date().toLocaleDateString('zh-CN'), publish_time: new Date().toLocaleString('zh-CN'), budget: { amount: 100, unit: '万元', text: '测试金额', originalUnit: '万元' }, url: 'https://gjzx.nanjing.gov.cn' }] }; showEmailStatus('正在发送测试邮件...', 'info'); try { const response = await fetch(`${API_BASE}/send-email`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ emailConfig: config, report: testReport }) }); const data = await response.json(); if (data.success) { showEmailStatus('测试邮件发送成功!请检查收件箱', 'success'); } else { showEmailStatus(`发送失败: ${data.error}`, 'error'); } } catch (error) { showEmailStatus(`请求失败: ${error.message}`, 'error'); } } // 发送报告到邮箱 async function sendReportByEmail() { if (!currentReport) { alert('请先生成报告'); return; } // 从localStorage加载邮件配置 const configStr = localStorage.getItem('emailConfig'); if (!configStr) { alert('请先在"邮件配置"标签页配置邮件服务器'); return; } let emailConfig; try { emailConfig = JSON.parse(configStr); } catch (e) { alert('邮件配置格式错误,请重新配置'); return; } // 验证配置 if (!emailConfig.smtpHost || !emailConfig.smtpUser || !emailConfig.smtpPass || !emailConfig.recipients) { alert('邮件配置不完整,请在"邮件配置"标签页检查配置'); return; } const sendBtn = document.getElementById('sendEmailBtn'); const originalText = sendBtn.textContent; sendBtn.disabled = true; sendBtn.textContent = '正在发送...'; try { const response = await fetch(`${API_BASE}/send-email`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ emailConfig: emailConfig, report: currentReport }) }); const data = await response.json(); if (data.success) { alert('报告已成功发送到邮箱!'); } else { alert(`发送失败: ${data.error}`); } } catch (error) { alert(`请求失败: ${error.message}`); } finally { sendBtn.disabled = false; sendBtn.textContent = originalText; } } // 显示邮件配置状态 function showEmailStatus(message, type) { const statusDiv = document.getElementById('emailConfigStatus'); const bgColors = { success: '#d4edda', error: '#f8d7da', info: '#d1ecf1' }; const textColors = { success: '#155724', error: '#721c24', info: '#0c5460' }; statusDiv.innerHTML = `
${message}
`; // 3秒后自动隐藏成功消息 if (type === 'success') { setTimeout(() => { statusDiv.innerHTML = ''; }, 3000); } }