```
feat(readme): 对部分文本进行格式调整,包括金额数字空格分隔、API 参数说明优化、标题层级对齐等,提升可读性。 ```
This commit is contained in:
205
public/app.js
205
public/app.js
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user