diff --git a/config.json b/config.json index 451c2d3..903e816 100644 --- a/config.json +++ b/config.json @@ -1,10 +1,11 @@ { "scheduler": { - "enabled": false, + "enabled": true, "cronTime": "0 9 * * *", - "threshold": 100000, - "description": "每天9点采集当日大于10亿的项目", - "timeRange": "today" + "winningThreshold": 0, + "bidThreshold": 0, + "description": "每天9点采集当日项目", + "timeRange": "thisMonth" }, "email": { "smtpHost": "smtp.qq.com", diff --git a/public/app.js b/public/app.js index 2716fa8..53984b0 100644 --- a/public/app.js +++ b/public/app.js @@ -90,7 +90,7 @@ function displayList(items, container) {
标段编号: ${item.bidNo}
标段名称: ${item.bidName}
发布日期: ${item.date}
- ${item.budget.amount}${item.budget.unit} + ${item.winningBid.amount}${item.winningBid.unit} ${item.href ? `
查看详情 →` : ''} `).join('')} @@ -189,7 +189,7 @@ function displayReport(report, container) {
标段编号: ${project.bidNo || '-'}
标段名称: ${project.bidName || '-'}
发布日期: ${project.date}
- ${project.budget.amount}${project.budget.unit} + ${project.winningBid.amount}${project.winningBid.unit} ${project.url ? `
查看详情 →` : ''} `).join('')} @@ -319,7 +319,7 @@ async function exportReport() { new Paragraph({ children: [ new TextRun({ text: '合同估算价: ', bold: true }), - new TextRun({ text: `${project.budget.amount}${project.budget.unit}` }) + new TextRun({ text: `${project.winningBid.amount}${project.winningBid.unit}` }) ], spacing: { after: 50 } }), @@ -597,7 +597,8 @@ async function loadSchedulerConfig() { document.getElementById('schedulerEnabled').checked = config.scheduler.enabled || false; const cronTime = config.scheduler.cronTime || '0 9 * * *'; document.getElementById('schedulerCronInput').value = cronTime; - document.getElementById('schedulerThresholdInput').value = config.scheduler.threshold || 10000; + document.getElementById('schedulerWinningThresholdInput').value = config.scheduler.winningThreshold !== undefined ? config.scheduler.winningThreshold : 10000; + document.getElementById('schedulerBidThresholdInput').value = config.scheduler.bidThreshold !== undefined ? config.scheduler.bidThreshold : 0; document.getElementById('schedulerDescription').value = config.scheduler.description || ''; // 时间段配置 @@ -705,8 +706,20 @@ async function updateSchedulerStatus() { // 更新执行计划 if (status.config) { document.getElementById('schedulerCronTime').textContent = cronToFriendlyText(status.config.cronTime); - const thresholdBillion = (status.config.threshold / 10000).toFixed(1); - document.getElementById('schedulerThreshold').textContent = `${status.config.threshold}万元 (${thresholdBillion}亿)`; + const winningThreshold = status.config.winningThreshold; + if (winningThreshold === 0) { + document.getElementById('schedulerWinningThreshold').textContent = '不筛选'; + } else { + const winningBillion = (winningThreshold / 10000).toFixed(1); + document.getElementById('schedulerWinningThreshold').textContent = `${winningThreshold}万元 (${winningBillion}亿)`; + } + const bidThreshold = status.config.bidThreshold; + if (bidThreshold === 0) { + document.getElementById('schedulerBidThreshold').textContent = '不筛选'; + } else { + const bidBillion = (bidThreshold / 10000).toFixed(1); + document.getElementById('schedulerBidThreshold').textContent = `${bidThreshold}万元 (${bidBillion}亿)`; + } } } } catch (error) { @@ -719,7 +732,8 @@ async function saveSchedulerConfig() { const schedulerConfig = { enabled: document.getElementById('schedulerEnabled').checked, cronTime: document.getElementById('schedulerCronInput').value, - threshold: parseInt(document.getElementById('schedulerThresholdInput').value), + winningThreshold: parseInt(document.getElementById('schedulerWinningThresholdInput').value), + bidThreshold: parseInt(document.getElementById('schedulerBidThresholdInput').value), description: document.getElementById('schedulerDescription').value, timeRange: document.getElementById('schedulerTimeRange').value }; @@ -834,3 +848,306 @@ function showSchedulerStatus(message, type) { }, 3000); } } + +// ========== 招标公告列表功能 ========== +let currentBidListPage = 1; + +// 获取招标公告列表 +async function fetchBidList(pageNum) { + const page = pageNum || parseInt(document.getElementById('bidListPage').value) || 1; + const loading = document.getElementById('bidListLoading'); + const results = document.getElementById('bidListResults'); + const pagination = document.getElementById('bidListPagination'); + + currentBidListPage = page; + document.getElementById('bidListPage').value = page; + + loading.classList.add('active'); + results.innerHTML = ''; + pagination.style.display = 'none'; + + try { + const response = await fetch(`${API_BASE}/bid-announce/list?page=${page}`); + const data = await response.json(); + + if (data.success) { + displayBidList(data.data, results); + updateBidListPagination(page, data.data.length > 0); + } else { + results.innerHTML = `
错误: ${data.error}
`; + } + } catch (error) { + results.innerHTML = `
请求失败: ${error.message}
`; + } finally { + loading.classList.remove('active'); + } +} + +function goToBidListPage(page) { + if (page < 1) return; + fetchBidList(page); +} + +function updateBidListPagination(page, hasData) { + const pagination = document.getElementById('bidListPagination'); + const currentPageSpan = document.getElementById('bidCurrentPage'); + const prevBtn = document.getElementById('bidPrevPage'); + const firstBtn = document.getElementById('bidFirstPage'); + const nextBtn = document.getElementById('bidNextPage'); + + if (hasData) { + pagination.style.display = 'flex'; + currentPageSpan.textContent = page; + + prevBtn.disabled = page <= 1; + firstBtn.disabled = page <= 1; + nextBtn.disabled = !hasData; + } +} + +function displayBidList(items, container) { + if (items.length === 0) { + container.innerHTML = '

没有找到招标公告

'; + return; + } + + const html = ` +
+

找到 ${items.length} 条招标公告

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

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

+
发布日期: ${item.date}
+ ${item.href ? `查看详情 →` : ''} +
+ `).join('')} +
+ `; + + container.innerHTML = html; +} + +// ========== 综合报告功能 ========== +let currentWinningReport = null; +let currentBidReport = null; + +// 初始化报告日期 +function initReportDates() { + const today = new Date(); + const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); + + document.getElementById('startDate').value = firstDayOfMonth.toISOString().split('T')[0]; + document.getElementById('endDate').value = today.toISOString().split('T')[0]; +} + +// 生成综合报告(同时包含中标和招标) +async function generateCombinedReport() { + const startDate = document.getElementById('startDate').value; + const endDate = document.getElementById('endDate').value; + const maxPages = parseInt(document.getElementById('maxPages').value) || 10; + const winningThreshold = parseFloat(document.getElementById('reportThreshold').value) * 10000 || 0; // 转换为元 + const bidThreshold = parseFloat(document.getElementById('bidReportThreshold').value) * 10000 || 0; + + if (!startDate && !endDate) { + alert('请至少填写开始日期或结束日期'); + return; + } + + const loading = document.getElementById('reportLoading'); + const loadingText = document.getElementById('reportLoadingText'); + const results = document.getElementById('reportResults'); + const sendBtn = document.getElementById('sendEmailBtn'); + + loading.classList.add('active'); + results.innerHTML = ''; + sendBtn.style.display = 'none'; + + try { + // 1. 先获取中标报告 + loadingText.textContent = '正在采集中标公示...'; + const winningResponse = await fetch(`${API_BASE}/report-daterange`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ startDate, endDate, threshold: winningThreshold, maxPages }) + }); + const winningData = await winningResponse.json(); + + // 2. 再获取招标报告 + loadingText.textContent = '正在采集招标公告...'; + const bidResponse = await fetch(`${API_BASE}/bid-announce/report`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ startDate, endDate, maxPages, threshold: bidThreshold }) + }); + const bidData = await bidResponse.json(); + + // 3. 显示综合报告 + if (winningData.success && bidData.success) { + currentWinningReport = winningData.data; + currentBidReport = bidData.data; + displayCombinedReport(winningData.data, bidData.data, results); + sendBtn.style.display = 'inline-block'; + } else { + let errorMsg = ''; + if (!winningData.success) errorMsg += `中标报告错误: ${winningData.error}\n`; + if (!bidData.success) errorMsg += `招标报告错误: ${bidData.error}`; + results.innerHTML = `
${errorMsg}
`; + } + } catch (error) { + results.innerHTML = `
请求失败: ${error.message}
`; + } finally { + loading.classList.remove('active'); + loadingText.textContent = '正在生成报告...'; + } +} + +// 显示综合报告 +function displayCombinedReport(winningReport, bidReport, container) { + const html = ` + +
+

中标公示报告

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

暂无符合条件的中标项目

' : ` +

中标项目列表 (${winningReport.projects.length} 条)

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

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

+
标段编号: ${project.bidNo || '-'}
+
标段名称: ${project.bidName || '-'}
+
发布日期: ${project.date}
+ ${project.winningBid.amount}${project.winningBid.unit} + ${project.url ? `
查看详情 →` : ''} +
+ `).join('')} +
+ `} + + +
+

招标公告报告

+
+
总公告数量
+
${bidReport.summary.total_count} 条
+
+
+
有金额信息
+
${bidReport.summary.has_amount_count || bidReport.summary.filtered_count} 条
+
+
+
金额阈值
+
${bidReport.summary.threshold || '无'}
+
+
+
合同估算总额
+
${bidReport.summary.total_amount}
+
+
+ + ${bidReport.projects.length === 0 ? '

暂无符合条件的招标项目

' : ` +

招标项目详情 (${bidReport.projects.length} 条)

+
+ ${bidReport.projects.map((item, index) => ` +
+

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

+
发布日期: ${item.date}
+ ${item.bidCode ? `
标段编码: ${item.bidCode}
` : ''} + ${item.tenderee ? `
招标人: ${item.tenderee}
` : ''} + ${item.duration ? `
工期: ${item.duration} 日历天
` : ''} + ${item.estimatedAmount ? ` + 合同估算价: ${item.estimatedAmount.amountWan} 万元 + ` : ''} +
查看详情 → +
+ `).join('')} +
+ `} + `; + + container.innerHTML = html; +} + +// 发送综合报告邮件 +async function sendCombinedReportByEmail() { + if (!currentWinningReport && !currentBidReport) { + alert('请先生成报告'); + return; + } + + // 从localStorage获取邮件配置 + const emailConfigStr = localStorage.getItem('emailConfig'); + if (!emailConfigStr) { + alert('请先在"邮件配置"页面配置邮件服务器信息'); + return; + } + + let emailConfig; + try { + emailConfig = JSON.parse(emailConfigStr); + } catch (e) { + alert('邮件配置解析失败,请重新配置'); + return; + } + + if (!emailConfig.smtpHost || !emailConfig.smtpUser || !emailConfig.smtpPass || !emailConfig.recipients) { + alert('邮件配置不完整,请检查SMTP服务器、用户名、密码和收件人'); + return; + } + + if (!confirm(`确定要将综合报告发送到以下邮箱吗?\n\n${emailConfig.recipients}`)) { + return; + } + + const sendBtn = document.getElementById('sendEmailBtn'); + const originalText = sendBtn.textContent; + sendBtn.disabled = true; + sendBtn.textContent = '正在发送...'; + + try { + const response = await fetch(`${API_BASE}/send-combined-email`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + emailConfig, + winningReport: currentWinningReport, + bidReport: currentBidReport + }) + }); + + 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; + } +} + +// 页面加载时初始化报告日期 +document.addEventListener('DOMContentLoaded', function() { + initReportDates(); +}); diff --git a/public/index.html b/public/index.html index a1c2695..d96266a 100644 --- a/public/index.html +++ b/public/index.html @@ -3,7 +3,7 @@ - 南京公共资源交易平台 - 合同估算价采集工具 + 南京公共资源交易平台 - 中标价格采集工具 + + +
+

南京公共资源交易平台 - 交通水务招标公告报告

+ +
+

报告摘要

+
+
+
总公告数量
+
${summary.total_count} 条
+
+
+
有金额信息
+
${summary.has_amount_count || summary.filtered_count} 条
+
+
+
符合筛选
+
${summary.filtered_count} 条
+
+
+
金额阈值
+
${summary.threshold || '无'}
+
+
+
合同估算总额
+
${summary.total_amount}
+
+
+ ${summary.date_range ? ` +
+
时间范围
+
+ ${summary.date_range.startDate || '不限'} 至 ${summary.date_range.endDate || '不限'} +
+
+ ` : ''} +
+ +

招标项目详情

+
+ ${projects.length === 0 ? '

暂无符合条件的项目

' : ''} + ${projects.map((project, index) => ` +
+

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

+
+ 发布日期: ${project.date} + ${project.bidCode ? ` | 标段编码: ${project.bidCode}` : ''} +
+ ${project.tenderee ? ` +
+ 招标人: ${project.tenderee} + ${project.duration ? ` | 工期: ${project.duration}日历天` : ''} +
+ ` : ''} + ${project.estimatedAmount ? ` +
+ 合同估算价: ${project.estimatedAmount.amountWan} 万元 +
+ ` : ''} +
+ ${project.url} +
+
+ `).join('')} +
+ + +
+ + + `; +} + +// 发送综合报告邮件(中标+招标) +export async function sendCombinedReportEmail(emailConfig, winningReport, bidReport) { + try { + const transporter = nodemailer.createTransport({ + host: emailConfig.smtpHost, + port: emailConfig.smtpPort || 587, + secure: emailConfig.smtpPort === 465, + auth: { + user: emailConfig.smtpUser, + pass: emailConfig.smtpPass, + }, + }); + + const htmlContent = generateCombinedReportHtml(winningReport, bidReport); + + const info = await transporter.sendMail({ + from: `"公告采集系统" <${emailConfig.smtpUser}>`, + to: emailConfig.recipients, + subject: `交通水务综合报告 - ${new Date().toLocaleDateString('zh-CN')}`, + html: htmlContent, + }); + + return { + success: true, + messageId: info.messageId, + }; + } catch (error) { + console.error('发送综合邮件失败:', error); + throw new Error(`邮件发送失败: ${error.message}`); + } +} + +// 生成综合报告HTML(中标+招标) +function generateCombinedReportHtml(winningReport, bidReport) { + return ` + + + + + + 交通水务综合报告 + + + +
+

南京公共资源交易平台 - 交通水务综合报告

+ + ${bidReport ? ` + +
+
招标公告
+
+
+
+
总公告数量
+
${bidReport.summary.total_count} 条
+
+
+
有金额信息
+
${bidReport.summary.has_amount_count || bidReport.summary.filtered_count} 条
+
+
+
符合筛选
+
${bidReport.summary.filtered_count} 条
+
+
+
金额阈值
+
${bidReport.summary.threshold || '无'}
+
+
+
合同估算总额
+
${bidReport.summary.total_amount}
+
+
+ ${bidReport.summary.date_range ? ` +
+ 时间范围: ${bidReport.summary.date_range.startDate || '不限'} 至 ${bidReport.summary.date_range.endDate || '不限'} +
+ ` : ''} +
+
+ ${bidReport.projects.length === 0 ? '

暂无符合条件的招标项目

' : ''} + ${bidReport.projects.map((project, index) => ` +
+

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

+
发布日期: ${project.date}
+ ${project.bidCode ? `
标段编码: ${project.bidCode}
` : ''} + ${project.tenderee ? `
招标人: ${project.tenderee}
` : ''} + ${project.duration ? `
工期: ${project.duration}日历天
` : ''} + ${project.estimatedAmount ? ` +
合同估算价: ${project.estimatedAmount.amountWan} 万元
+ ` : ''} +
+ ${project.url} +
+
+ `).join('')} +
+
+ ` : ''} + + ${winningReport ? ` + +
+
中标结果公示
+
+
+
+
总项目数
+
${winningReport.summary.total_count} 条
+
+
+
符合条件
+
${winningReport.summary.filtered_count} 条
+
+
+
金额阈值
+
${winningReport.summary.threshold}
+
+
+
总金额
+
${winningReport.summary.total_amount}
+
+
+ ${winningReport.summary.date_range ? ` +
+ 时间范围: ${winningReport.summary.date_range.startDate || '不限'} 至 ${winningReport.summary.date_range.endDate || '不限'} +
+ ` : ''} +
+
+ ${winningReport.projects.length === 0 ? '

暂无符合条件的中标项目

' : ''} + ${winningReport.projects.map((project, index) => ` +
+

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

+
中标日期: ${project.date}
+ ${project.bidNo ? `
标段编号: ${project.bidNo}
` : ''} + ${project.winningBid ? ` +
中标金额: ${project.winningBid.amount.toFixed(2)} ${project.winningBid.unit}
+ ` : ''} +
+ ${project.url} +
+
+ `).join('')} +
+
+ ` : ''} + + +
+ + + `; +} + // 生成HTML格式的报告 function generateReportHtml(report) { const { summary, projects } = report; @@ -45,7 +562,7 @@ function generateReportHtml(report) { - 采购公告分析报告 + 交通水务中标结果报告