feat(readme): 对部分文本进行格式调整,包括金额数字空格分隔、API 参数说明优化、标题层级对齐等,提升可读性。
```
This commit is contained in:
2025-12-15 10:36:18 +08:00
parent 745faa0ecc
commit b044e918aa
9 changed files with 949 additions and 80 deletions

View File

@@ -134,11 +134,11 @@ async function fetchDetails() {
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条
@@ -177,7 +177,7 @@ async function fetchDetails() {
return;
}
// 抓取详情
// 采集详情
const limit = useDetailDateRange ? listData.data.length : parseInt(document.getElementById('detailLimit').value);
const detailResponse = await fetch(`${API_BASE}/details`, {
method: 'POST',
@@ -202,7 +202,7 @@ async function fetchDetails() {
function displayDetails(items, container) {
const html = `
<div style="max-height: 380px; overflow-y: auto;">
<h3 style="margin-bottom: 15px;">抓取${items.length} 条详情</h3>
<h3 style="margin-bottom: 15px;">采集${items.length} 条详情</h3>
${items.map((item, index) => `
<div class="list-item">
<h3>${index + 1}. ${item.title}</h3>
@@ -212,7 +212,7 @@ function displayDetails(items, container) {
${item.detail.budget ? `
<span class="budget">${item.detail.budget.amount}${item.detail.budget.unit}</span>
` : '<div class="meta">未找到预算信息</div>'}
` : '<div class="error">抓取失败</div>'}
` : '<div class="error">采集失败</div>'}
<br><a href="${item.href}" target="_blank">查看原文 →</a>
</div>
`).join('')}
@@ -271,6 +271,7 @@ async function generateReport() {
currentReport = data.data;
displayReport(data.data, results);
exportBtn.style.display = 'inline-block';
document.getElementById('sendEmailBtn').style.display = 'inline-block';
} else {
results.innerHTML = `<div class="error">错误: ${data.error}</div>`;
}
@@ -475,3 +476,197 @@ async function exportReport() {
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 = `
<div style="background: ${bgColors[type]}; color: ${textColors[type]}; padding: 15px; border-radius: 8px;">
${message}
</div>
`;
// 3秒后自动隐藏成功消息
if (type === 'success') {
setTimeout(() => {
statusDiv.innerHTML = '';
}, 3000);
}
}

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>南京公共工程建设中心 - 公告抓取工具</title>
<title>南京公共工程建设中心 - 公告采集工具</title>
<style>
* {
margin: 0;
@@ -335,13 +335,14 @@
<div class="container">
<div class="header">
<h1>南京公共工程建设中心</h1>
<p>公告抓取与分析工具</p>
<p>公告采集与分析工具</p>
</div>
<div class="tabs">
<button class="tab active" onclick="switchTab('list')">公告列表</button>
<button class="tab" onclick="switchTab('detail')">详情抓取</button>
<button class="tab" onclick="switchTab('detail')">详情采集</button>
<button class="tab" onclick="switchTab('report')">生成报告</button>
<button class="tab" onclick="switchTab('email')">邮件配置</button>
</div>
<div class="content">
@@ -359,7 +360,7 @@
<div id="listLoading" class="loading">
<div class="spinner"></div>
<p>正在抓取...</p>
<p>正在采集...</p>
</div>
<div id="listResults" class="results"></div>
@@ -372,12 +373,12 @@
</div>
</div>
<!-- 详情抓取 -->
<!-- 详情采集 -->
<div id="detail" class="tab-content">
<div class="form-group">
<div class="checkbox-wrapper" onclick="document.getElementById('useDetailDateRange').click();">
<input type="checkbox" id="useDetailDateRange" onchange="toggleDetailDateRange()" onclick="event.stopPropagation();">
<label for="useDetailDateRange">按时间范围抓取</label>
<label for="useDetailDateRange">按时间范围采集</label>
</div>
</div>
@@ -391,7 +392,7 @@
<input type="date" id="detailEndDate">
</div>
<div class="form-group">
<label>最大抓取页数</label>
<label>最大采集页数</label>
<input type="number" id="detailMaxPages" value="1" min="1">
</div>
</div>
@@ -402,16 +403,16 @@
<input type="text" id="detailUrl" placeholder="默认: https://gjzx.nanjing.gov.cn/gggs/">
</div>
<div class="form-group">
<label>抓取数量</label>
<label>采集数量</label>
<input type="number" id="detailLimit" value="5" min="1" max="50">
</div>
</div>
<button class="btn" onclick="fetchDetails()">开始抓取</button>
<button class="btn" onclick="fetchDetails()">开始采集</button>
<div id="detailLoading" class="loading">
<div class="spinner"></div>
<p>正在抓取详情...</p>
<p>正在采集详情...</p>
</div>
<div id="detailResults" class="results"></div>
@@ -422,7 +423,7 @@
<div class="form-group">
<div class="checkbox-wrapper" onclick="document.getElementById('useDateRange').click();">
<input type="checkbox" id="useDateRange" onchange="toggleDateRange()" onclick="event.stopPropagation();">
<label for="useDateRange">按时间范围抓取</label>
<label for="useDateRange">按时间范围采集</label>
</div>
</div>
@@ -436,7 +437,7 @@
<input type="date" id="endDate">
</div>
<div class="form-group">
<label>最大抓取页数</label>
<label>最大采集页数</label>
<input type="number" id="maxPages" value="1" min="1" >
</div>
</div>
@@ -447,7 +448,7 @@
<input type="text" id="reportUrl" placeholder="默认: https://gjzx.nanjing.gov.cn/gggs/">
</div>
<div class="form-group">
<label>抓取数量</label>
<label>采集数量</label>
<input type="number" id="reportLimit" value="15" min="1" max="50">
</div>
</div>
@@ -459,6 +460,7 @@
<button class="btn" onclick="generateReport()">生成报告</button>
<button class="btn export-btn" onclick="exportReport()" id="exportBtn" style="display:none;">导出Word</button>
<button class="btn" onclick="sendReportByEmail()" id="sendEmailBtn" style="display:none; background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">发送邮件</button>
<div id="reportLoading" class="loading">
<div class="spinner"></div>
@@ -467,6 +469,56 @@
<div id="reportResults" class="results"></div>
</div>
<!-- 邮件配置 -->
<div id="email" class="tab-content">
<h2 style="margin-bottom: 20px; color: #667eea;">邮件配置</h2>
<p style="color: #666; margin-bottom: 20px;">配置SMTP邮件服务器信息,用于发送报告到指定邮箱</p>
<div class="form-group">
<label>SMTP服务器地址 *</label>
<input type="text" id="smtpHost" placeholder="例如: smtp.qq.com, smtp.163.com, smtp.gmail.com">
</div>
<div class="form-group">
<label>SMTP端口 *</label>
<input type="number" id="smtpPort" value="587" placeholder="通常为 587 (TLS) 或 465 (SSL)">
</div>
<div class="form-group">
<label>发件人邮箱 (SMTP用户名) *</label>
<input type="email" id="smtpUser" placeholder="your-email@example.com">
</div>
<div class="form-group">
<label>SMTP密码/授权码 *</label>
<input type="password" id="smtpPass" placeholder="邮箱密码或授权码">
</div>
<div class="form-group">
<label>收件人邮箱 (多个用逗号分隔) *</label>
<input type="text" id="recipients" placeholder="email1@example.com, email2@example.com">
</div>
<button class="btn" onclick="saveEmailConfig()">保存配置</button>
<button class="btn" onclick="testEmailConfig()" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">测试连接</button>
<div id="emailConfigStatus" style="margin-top: 20px;"></div>
<div style="margin-top: 30px; padding: 20px; background: #f0f8ff; border-radius: 8px; border-left: 4px solid #667eea;">
<h3 style="margin-top: 0; color: #667eea;">常用邮箱配置参考</h3>
<ul style="line-height: 1.8; color: #666;">
<li><strong>QQ邮箱:</strong> smtp.qq.com, 端口 587 或 465, 需要使用授权码</li>
<li><strong>163邮箱:</strong> smtp.163.com, 端口 465 或 25, 需要使用授权码</li>
<li><strong>Gmail:</strong> smtp.gmail.com, 端口 587 或 465, 需要开启"允许不够安全的应用"</li>
<li><strong>Outlook:</strong> smtp-mail.outlook.com, 端口 587</li>
<li><strong>企业邮箱:</strong> 请咨询您的IT管理员获取SMTP配置</li>
</ul>
<p style="margin: 10px 0 0 0; color: #999; font-size: 13px;">
提示: QQ和163邮箱需要在邮箱设置中开启SMTP服务并生成授权码,授权码不是邮箱密码。
</p>
</div>
</div>
</div>
</div>