2025-11-27 18:32:24 +08:00
|
|
|
|
// 全局变量
|
|
|
|
|
|
let isTaskRunning = false;
|
|
|
|
|
|
let currentSection = 'home';
|
|
|
|
|
|
let taskCheckInterval;
|
|
|
|
|
|
const API_BASE_URL = 'http://localhost:8080/api'; // API基础地址
|
|
|
|
|
|
|
|
|
|
|
|
// DOM加载完成后初始化
|
|
|
|
|
|
$(document).ready(function() {
|
|
|
|
|
|
showSection('home');
|
|
|
|
|
|
console.log('✅ 微信公众号文章爬虫系统已加载');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 显示指定区域
|
|
|
|
|
|
function showSection(sectionName) {
|
|
|
|
|
|
// 隐藏所有区域
|
|
|
|
|
|
$('.section').hide();
|
|
|
|
|
|
$('.feature-cards').hide();
|
|
|
|
|
|
|
|
|
|
|
|
if (sectionName === 'home') {
|
|
|
|
|
|
$('.feature-cards').show();
|
|
|
|
|
|
currentSection = 'home';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$('#section-' + sectionName).show();
|
|
|
|
|
|
currentSection = sectionName;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 提取公众号主页相关函数
|
|
|
|
|
|
function extractHomepage() {
|
|
|
|
|
|
const articleUrl = $('#homepage-url').val().trim();
|
|
|
|
|
|
|
|
|
|
|
|
if (!articleUrl) {
|
|
|
|
|
|
showResult('homepage', 'error', '请输入文章链接');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!articleUrl.includes('mp.weixin.qq.com')) {
|
|
|
|
|
|
showResult('homepage', 'error', '请输入有效的微信公众号文章链接');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isTaskRunning) {
|
|
|
|
|
|
showResult('homepage', 'error', '有任务正在执行,请稍候...');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
isTaskRunning = true;
|
|
|
|
|
|
showResult('homepage', 'loading', '正在提取公众号主页链接...');
|
|
|
|
|
|
|
|
|
|
|
|
// 调用后端API
|
|
|
|
|
|
$.ajax({
|
|
|
|
|
|
url: `${API_BASE_URL}/homepage/extract`,
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
contentType: 'application/json',
|
|
|
|
|
|
data: JSON.stringify({ url: articleUrl }),
|
|
|
|
|
|
success: function(response) {
|
|
|
|
|
|
isTaskRunning = false;
|
|
|
|
|
|
if (response.success && response.data && response.data.homepage) {
|
|
|
|
|
|
const homepageUrl = response.data.homepage;
|
|
|
|
|
|
const safeUrl = homepageUrl.replace(/'/g, "\\'");
|
|
|
|
|
|
const resultHtml = `
|
|
|
|
|
|
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-top: 10px;">
|
|
|
|
|
|
<h4 style="color: #28a745; margin-bottom: 10px;">✅ 提取成功</h4>
|
|
|
|
|
|
<p><strong>公众号主页链接:</strong></p>
|
|
|
|
|
|
<div style="background: white; padding: 10px; border: 1px solid #ddd; border-radius: 4px; word-break: break-all; font-family: monospace; font-size: 0.9em;">
|
|
|
|
|
|
${homepageUrl}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style="margin-top: 15px;">
|
|
|
|
|
|
<button class="btn btn-info" onclick="copyToClipboard('${safeUrl}')" style="margin-right: 10px;">📋 复制链接</button>
|
|
|
|
|
|
<button class="btn btn-warning" onclick="openInNewTab('${safeUrl}')">🔗 打开主页</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
`;
|
|
|
|
|
|
showResult('homepage', 'success', resultHtml);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
showResult('homepage', 'error', response.message || '提取失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
error: function(xhr, status, error) {
|
|
|
|
|
|
isTaskRunning = false;
|
|
|
|
|
|
let errorMsg = '请求失败:' + error;
|
|
|
|
|
|
if (xhr.status === 0) {
|
|
|
|
|
|
errorMsg = '无法连接到后端服务器,请确保 API 服务器已启动(运行 api_server.exe)';
|
|
|
|
|
|
}
|
|
|
|
|
|
showResult('homepage', 'error', errorMsg);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成模拟的公众号主页链接
|
|
|
|
|
|
function generateMockHomepageUrl(articleUrl) {
|
|
|
|
|
|
// 从文章链接中提取__biz参数来模拟真实的主页链接
|
|
|
|
|
|
const bizMatch = articleUrl.match(/__biz=([^&]+)/);
|
|
|
|
|
|
if (bizMatch) {
|
|
|
|
|
|
const biz = bizMatch[1];
|
|
|
|
|
|
return `https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=${biz}&scene=124`;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 如果无法提取,返回示例链接
|
|
|
|
|
|
return 'https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=MzI1NjEwMTM4OA==&scene=124';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function loadExampleUrl() {
|
|
|
|
|
|
const exampleUrl = 'https://mp.weixin.qq.com/s?__biz=MzI1NjEwMTM4OA==&mid=2651232405&idx=1&sn=7c8f5b2e3d4a1b9c8e7f6a5b4c3d2e1f&chksm=f1d7e8c4c6a061d2b9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0&scene=27';
|
|
|
|
|
|
|
|
|
|
|
|
$('#homepage-url').val(exampleUrl);
|
|
|
|
|
|
showResult('homepage', 'info', '已加载文章链接示例,点击"提取主页链接"开始处理');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 打开链接的辅助函数
|
|
|
|
|
|
function openInNewTab(url) {
|
|
|
|
|
|
window.open(url, '_blank');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 下载单篇文章
|
|
|
|
|
|
// 获取文章列表
|
|
|
|
|
|
function getArticleList() {
|
|
|
|
|
|
const accessToken = $('#access-token').val().trim();
|
|
|
|
|
|
const pages = parseInt($('#pages').val()) || 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (!accessToken) {
|
|
|
|
|
|
showResult('list', 'error', '请输入Access Token URL');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isTaskRunning) {
|
|
|
|
|
|
showResult('list', 'error', '有任务正在执行,请稍候...');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
isTaskRunning = true;
|
|
|
|
|
|
showResult('list', 'loading', '正在获取文章列表,请稍候...');
|
|
|
|
|
|
|
|
|
|
|
|
// 调用后端API(同步等待)
|
|
|
|
|
|
$.ajax({
|
|
|
|
|
|
url: `${API_BASE_URL}/article/list`,
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
contentType: 'application/json',
|
|
|
|
|
|
data: JSON.stringify({ access_token: accessToken, pages: pages }),
|
|
|
|
|
|
success: function(response) {
|
|
|
|
|
|
isTaskRunning = false;
|
|
|
|
|
|
if (response.success && response.data) {
|
|
|
|
|
|
const data = response.data;
|
|
|
|
|
|
const fileExt = data.filename.endsWith('.txt') ? 'TXT文件' : 'Excel文件';
|
|
|
|
|
|
const resultHtml = `
|
|
|
|
|
|
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-top: 10px;">
|
|
|
|
|
|
<h4 style="color: #28a745; margin-bottom: 10px;">✅ 获取成功</h4>
|
|
|
|
|
|
<p><strong>公众号:</strong>${data.account}</p>
|
|
|
|
|
|
<p><strong>文件:</strong>${data.filename}</p>
|
|
|
|
|
|
<div style="margin-top: 15px;">
|
|
|
|
|
|
<a href="${API_BASE_URL}${data.download}" class="btn btn-success" download>📥 下载${fileExt}</a>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
`;
|
|
|
|
|
|
showResult('list', 'success', resultHtml);
|
|
|
|
|
|
|
|
|
|
|
|
// 自动触发下载
|
|
|
|
|
|
window.location.href = `${API_BASE_URL}${data.download}`;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
showResult('list', 'error', response.message || '获取失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
error: function(xhr, status, error) {
|
|
|
|
|
|
isTaskRunning = false;
|
|
|
|
|
|
let errorMsg = '请求失败:' + error;
|
|
|
|
|
|
if (xhr.status === 0) {
|
|
|
|
|
|
errorMsg = '无法连接到后端服务器,请确保 API 服务器已启动';
|
|
|
|
|
|
} else if (xhr.responseJSON && xhr.responseJSON.message) {
|
|
|
|
|
|
errorMsg = xhr.responseJSON.message;
|
|
|
|
|
|
}
|
|
|
|
|
|
showResult('list', 'error', errorMsg);
|
|
|
|
|
|
},
|
|
|
|
|
|
timeout: 120000 // 2分钟超时
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 批量下载文章
|
|
|
|
|
|
function batchDownload() {
|
|
|
|
|
|
const officialAccount = $('#official-account').val().trim();
|
|
|
|
|
|
const saveImage = $('#batch-save-image').is(':checked');
|
|
|
|
|
|
const saveContent = $('#batch-save-content').is(':checked');
|
|
|
|
|
|
|
|
|
|
|
|
if (!officialAccount) {
|
|
|
|
|
|
showResult('batch', 'error', '请输入公众号名称或文章链接');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isTaskRunning) {
|
|
|
|
|
|
showResult('batch', 'error', '有任务正在执行,请稍候...');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
isTaskRunning = true;
|
|
|
|
|
|
showResult('batch', 'loading', '正在批量下载文章,请稍候...');
|
|
|
|
|
|
|
2025-12-02 14:58:52 +08:00
|
|
|
|
// 调用后端 API(同步等待)
|
2025-11-27 18:32:24 +08:00
|
|
|
|
$.ajax({
|
|
|
|
|
|
url: `${API_BASE_URL}/article/batch`,
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
contentType: 'application/json',
|
|
|
|
|
|
data: JSON.stringify({
|
|
|
|
|
|
official_account: officialAccount,
|
|
|
|
|
|
save_image: saveImage,
|
|
|
|
|
|
save_content: saveContent
|
|
|
|
|
|
}),
|
|
|
|
|
|
success: function(response) {
|
|
|
|
|
|
isTaskRunning = false;
|
|
|
|
|
|
if (response.success && response.data) {
|
|
|
|
|
|
const data = response.data;
|
|
|
|
|
|
const resultHtml = `
|
|
|
|
|
|
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-top: 10px;">
|
|
|
|
|
|
<h4 style="color: #28a745; margin-bottom: 10px;">✅ ${response.message}</h4>
|
|
|
|
|
|
<p><strong>公众号:</strong>${data.account}</p>
|
|
|
|
|
|
<p><strong>文章数量:</strong>${data.articleCount} 篇</p>
|
|
|
|
|
|
<p><strong>保存路径:</strong>${data.path}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
`;
|
|
|
|
|
|
showResult('batch', 'success', resultHtml);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
showResult('batch', 'error', response.message || '批量下载失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
error: function(xhr, status, error) {
|
|
|
|
|
|
isTaskRunning = false;
|
|
|
|
|
|
let errorMsg = '请求失败:' + error;
|
|
|
|
|
|
if (xhr.status === 0) {
|
|
|
|
|
|
errorMsg = '无法连接到后端服务器,请确保 API 服务器已启动';
|
|
|
|
|
|
} else if (xhr.responseJSON && xhr.responseJSON.message) {
|
|
|
|
|
|
errorMsg = xhr.responseJSON.message;
|
|
|
|
|
|
}
|
|
|
|
|
|
showResult('batch', 'error', errorMsg);
|
|
|
|
|
|
},
|
|
|
|
|
|
timeout: 300000 // 5分钟超时
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 14:58:52 +08:00
|
|
|
|
// 获取文章详情(功能4)
|
|
|
|
|
|
function getArticleDetail() {
|
|
|
|
|
|
const accessToken = $('#detail-access-token').val().trim();
|
|
|
|
|
|
const pages = parseInt($('#detail-pages').val()) || 0;
|
|
|
|
|
|
const submitBtn = $('#section-detail .btn-success');
|
2025-11-27 18:32:24 +08:00
|
|
|
|
|
2025-12-02 14:58:52 +08:00
|
|
|
|
// 1. 验证输入
|
|
|
|
|
|
if (!accessToken) {
|
|
|
|
|
|
showResult('detail', 'error', '⚠️ 请输入Access Token URL');
|
|
|
|
|
|
// 高亮输入框
|
|
|
|
|
|
$('#detail-access-token').css('border-color', '#e74c3c').focus();
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
$('#detail-access-token').css('border-color', '');
|
|
|
|
|
|
}, 2000);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 检查是否有任务正在执行
|
|
|
|
|
|
if (isTaskRunning) {
|
|
|
|
|
|
showResult('detail', 'error', '⚠️ 有任务正在执行,请稍候...');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 设置任务状态并禁用按钮
|
|
|
|
|
|
isTaskRunning = true;
|
|
|
|
|
|
submitBtn.prop('disabled', true)
|
|
|
|
|
|
.addClass('disabled')
|
|
|
|
|
|
.html('⏳ 处理中...');
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 构建提示信息
|
|
|
|
|
|
const pagesInfo = pages > 0 ? `前${pages}页(约${pages * 10}篇文章)` : '全部文章';
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 显示加载状态
|
|
|
|
|
|
showResult('detail', 'loading', `
|
|
|
|
|
|
<div style="text-align: center;">
|
|
|
|
|
|
<div style="font-size: 1.2em; margin-bottom: 10px;">⏳ 正在获取文章详情,请耐心等待...</div>
|
|
|
|
|
|
<div style="background: #e3f2fd; padding: 12px; border-radius: 5px; margin: 15px 0; border-left: 4px solid #2196f3;">
|
|
|
|
|
|
<p style="margin: 5px 0; color: #1565c0;"><strong>📌 本次获取范围:</strong>${pagesInfo}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style="background: #fff3cd; padding: 12px; border-radius: 5px; margin: 15px 0;">
|
|
|
|
|
|
<p style="margin: 5px 0; color: #856404;"><strong>📝 执行步骤:</strong></p>
|
|
|
|
|
|
<ol style="text-align: left; margin: 10px 0; padding-left: 30px; color: #856404;">
|
|
|
|
|
|
<li>正在获取公众号文章列表...</li>
|
|
|
|
|
|
<li>正在下载每篇文章的详细数据...</li>
|
|
|
|
|
|
<li>正在保存为TXT文件...</li>
|
|
|
|
|
|
</ol>
|
|
|
|
|
|
<p style="margin: 10px 0 0 0; color: #d9534f;"><strong>⚠️ 此过程可能需要几分钟,系统会自动延时避免被封禁</strong></p>
|
|
|
|
|
|
<p style="margin: 5px 0 0 0; color: #856404; font-size: 0.9em;">💡 提示:请不要关闭页面或刷新浏览器</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
|
|
console.log('🚀 开始获取文章详情...');
|
|
|
|
|
|
console.log('📄 获取页数:', pages === 0 ? '全部' : pages);
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 调用后端 API
|
2025-11-27 18:32:24 +08:00
|
|
|
|
$.ajax({
|
2025-12-02 14:58:52 +08:00
|
|
|
|
url: `${API_BASE_URL}/article/detail`,
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
contentType: 'application/json',
|
|
|
|
|
|
data: JSON.stringify({
|
|
|
|
|
|
access_token: accessToken,
|
|
|
|
|
|
pages: pages
|
|
|
|
|
|
}),
|
|
|
|
|
|
beforeSend: function() {
|
|
|
|
|
|
console.log('📡 发送请求到后端 API...');
|
|
|
|
|
|
},
|
2025-11-27 18:32:24 +08:00
|
|
|
|
success: function(response) {
|
2025-12-02 14:58:52 +08:00
|
|
|
|
console.log('✅ 收到服务器响应:', response);
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复任务状态和按钮
|
|
|
|
|
|
isTaskRunning = false;
|
|
|
|
|
|
submitBtn.prop('disabled', false)
|
|
|
|
|
|
.removeClass('disabled')
|
|
|
|
|
|
.html('开始获取');
|
|
|
|
|
|
|
2025-11-27 18:32:24 +08:00
|
|
|
|
if (response.success && response.data) {
|
2025-12-02 14:58:52 +08:00
|
|
|
|
const data = response.data;
|
|
|
|
|
|
const resultHtml = `
|
|
|
|
|
|
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px; margin-top: 10px; border: 2px solid #28a745;">
|
|
|
|
|
|
<h4 style="color: #28a745; margin-bottom: 15px; font-size: 1.3em;">✅ 获取成功!</h4>
|
|
|
|
|
|
<div style="background: white; padding: 15px; border-radius: 5px; margin: 10px 0;">
|
|
|
|
|
|
<p style="margin: 8px 0;"><strong>📱 公众号:</strong><span style="color: #667eea;">${data.account}</span></p>
|
|
|
|
|
|
<p style="margin: 8px 0;"><strong>📊 文章数量:</strong><span style="color: #667eea;">${data.articleCount} 篇</span></p>
|
|
|
|
|
|
<p style="margin: 8px 0;"><strong>📁 保存路径:</strong><span style="color: #667eea; font-size: 0.9em;">${data.path}</span></p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style="background: #e7f3ff; padding: 15px; border-radius: 5px; margin: 15px 0; border-left: 4px solid #3498db;">
|
|
|
|
|
|
<p style="margin: 5px 0; color: #0066cc; font-weight: bold;">📊 数据包含:</p>
|
|
|
|
|
|
<ul style="margin: 10px 0; padding-left: 25px; color: #0066cc;">
|
|
|
|
|
|
<li style="margin: 5px 0;">📄 文章文本内容</li>
|
|
|
|
|
|
<li style="margin: 5px 0;">👁️ 阅读量、点赞数、转发数、在看数</li>
|
|
|
|
|
|
<li style="margin: 5px 0;">💬 评论内容及评论点赞数</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p style="margin-top: 15px; text-align: center; color: #7f8c8d; font-size: 0.9em;">💡 数据已保存在 data/${data.account}/文章详细 目录下</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
`;
|
|
|
|
|
|
showResult('detail', 'success', resultHtml);
|
2025-11-27 18:32:24 +08:00
|
|
|
|
} else {
|
2025-12-02 14:58:52 +08:00
|
|
|
|
showResult('detail', 'error', `
|
|
|
|
|
|
<div style="padding: 10px;">
|
|
|
|
|
|
<h4 style="color: #e74c3c; margin-bottom: 10px;">❌ 获取失败</h4>
|
|
|
|
|
|
<p style="margin: 10px 0;">${response.message || '未知错误,请稍后重试'}</p>
|
|
|
|
|
|
<div style="background: #fff3cd; padding: 12px; border-radius: 5px; margin-top: 15px;">
|
|
|
|
|
|
<p style="margin: 5px 0; color: #856404;"><strong>💡 可能的原因:</strong></p>
|
|
|
|
|
|
<ul style="margin: 10px 0; padding-left: 25px; color: #856404;">
|
|
|
|
|
|
<li>Access Token 已过期</li>
|
|
|
|
|
|
<li>公众号权限不足</li>
|
|
|
|
|
|
<li>网络连接问题</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
<p style="margin: 10px 0 0 0; color: #856404;">请重新从 Fiddler 获取 Access Token URL 并重试</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
`);
|
2025-11-27 18:32:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
error: function(xhr, status, error) {
|
2025-12-02 14:58:52 +08:00
|
|
|
|
console.error('❌ 请求失败:', { xhr, status, error });
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复任务状态和按钮
|
|
|
|
|
|
isTaskRunning = false;
|
|
|
|
|
|
submitBtn.prop('disabled', false)
|
|
|
|
|
|
.removeClass('disabled')
|
|
|
|
|
|
.html('开始获取');
|
|
|
|
|
|
|
|
|
|
|
|
let errorMsg = '请求失败';
|
|
|
|
|
|
let errorDetail = '';
|
|
|
|
|
|
|
2025-11-27 18:32:24 +08:00
|
|
|
|
if (xhr.status === 0) {
|
2025-12-02 14:58:52 +08:00
|
|
|
|
errorMsg = '无法连接到后端服务器';
|
|
|
|
|
|
errorDetail = `
|
|
|
|
|
|
<div style="background: #f8d7da; padding: 12px; border-radius: 5px; margin-top: 15px; border-left: 4px solid #e74c3c;">
|
|
|
|
|
|
<p style="margin: 5px 0; color: #721c24;"><strong>🔧 解决方法:</strong></p>
|
|
|
|
|
|
<ol style="margin: 10px 0; padding-left: 25px; color: #721c24;">
|
|
|
|
|
|
<li>确认后端 API 服务器已启动</li>
|
|
|
|
|
|
<li>检查服务器地址:http://localhost:8080</li>
|
|
|
|
|
|
<li>查看服务器控制台是否有错误信息</li>
|
|
|
|
|
|
</ol>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
`;
|
|
|
|
|
|
} else if (xhr.status === 404) {
|
|
|
|
|
|
errorMsg = '接口不存在';
|
|
|
|
|
|
errorDetail = `<p style="margin-top: 10px; color: #721c24;">请检查API路径配置是否正确</p>`;
|
|
|
|
|
|
} else if (xhr.status === 500) {
|
|
|
|
|
|
errorMsg = '服务器内部错误';
|
|
|
|
|
|
errorDetail = `<p style="margin-top: 10px; color: #721c24;">请查看服务器日志获取详细错误信息</p>`;
|
|
|
|
|
|
} else if (xhr.responseJSON && xhr.responseJSON.message) {
|
|
|
|
|
|
errorMsg = xhr.responseJSON.message;
|
|
|
|
|
|
} else if (status === 'timeout') {
|
|
|
|
|
|
errorMsg = '请求超时';
|
|
|
|
|
|
errorDetail = `
|
|
|
|
|
|
<p style="margin-top: 10px; color: #721c24;">
|
|
|
|
|
|
文章数量较多,处理时间超过10分钟。<br>
|
|
|
|
|
|
建议:减少文章数量或稍后查看服务器是否已生成文件。
|
|
|
|
|
|
</p>
|
|
|
|
|
|
`;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
errorMsg += ': ' + (error || '未知错误');
|
2025-11-27 18:32:24 +08:00
|
|
|
|
}
|
2025-12-02 14:58:52 +08:00
|
|
|
|
|
|
|
|
|
|
showResult('detail', 'error', `
|
|
|
|
|
|
<div style="padding: 10px;">
|
|
|
|
|
|
<h4 style="color: #e74c3c; margin-bottom: 10px;">❌ ${errorMsg}</h4>
|
|
|
|
|
|
${errorDetail}
|
|
|
|
|
|
<p style="margin-top: 15px; color: #7f8c8d; font-size: 0.9em;">
|
|
|
|
|
|
错误状态码: ${xhr.status || 'N/A'}<br>
|
|
|
|
|
|
错误类型: ${status || 'N/A'}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
`);
|
|
|
|
|
|
},
|
|
|
|
|
|
complete: function() {
|
|
|
|
|
|
console.log('🏁 请求完成');
|
|
|
|
|
|
},
|
|
|
|
|
|
timeout: 600000 // 10分钟超时(因为功能4需要较长时间)
|
2025-11-27 18:32:24 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 14:58:52 +08:00
|
|
|
|
// 加载示例 Access Token
|
|
|
|
|
|
function loadDetailExample() {
|
|
|
|
|
|
const exampleToken = 'https://mp.weixin.qq.com/mp/profile_ext?action=getmsg&__biz=MzI1NjEwMTM4OA==&uin=MTIzNDU2Nzg5&key=abcdef123456&pass_ticket=xyz789&...';
|
2025-11-27 18:32:24 +08:00
|
|
|
|
|
2025-12-02 14:58:52 +08:00
|
|
|
|
$('#detail-access-token').val(exampleToken);
|
|
|
|
|
|
showResult('detail', 'info', '已加载 Access Token 示例。<br><br><strong>注意:</strong>请从 Fiddler 中获取实际的 Access Token URL,示例仅供参考。<br><br><strong>获取步骤:</strong><br>1. 在微信客户端打开公众号主页<br>2. 在 Fiddler 中查找 URL 为 /mp/profile_ext?action=getmsg 的请求<br>3. 按 Ctrl+U 复制完整 URL<br>4. 粘贴到此处');
|
2025-11-27 18:32:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 任务管理函数
|
|
|
|
|
|
function startTask(section, message) {
|
|
|
|
|
|
isTaskRunning = true;
|
|
|
|
|
|
showResult(section, 'loading', message);
|
|
|
|
|
|
|
|
|
|
|
|
// 显示进度条
|
|
|
|
|
|
const resultDiv = $(`#${section}-result`);
|
|
|
|
|
|
resultDiv.append(`
|
|
|
|
|
|
<div class="progress-container">
|
|
|
|
|
|
<div class="progress-bar">
|
|
|
|
|
|
<div class="progress-fill"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="progress-text">0%</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
|
|
// 禁用相关按钮
|
|
|
|
|
|
disableButtons();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function updateTaskProgress(percent, message) {
|
|
|
|
|
|
const progressFill = $('.progress-fill');
|
|
|
|
|
|
const progressText = $('.progress-text');
|
|
|
|
|
|
|
|
|
|
|
|
progressFill.css('width', percent + '%');
|
|
|
|
|
|
progressText.text(Math.floor(percent) + '% - ' + message);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function endTask(section, type, message) {
|
|
|
|
|
|
isTaskRunning = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 移除进度条
|
|
|
|
|
|
$('.progress-container').remove();
|
|
|
|
|
|
|
|
|
|
|
|
showResult(section, type, message);
|
|
|
|
|
|
enableButtons();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function disableButtons() {
|
|
|
|
|
|
$('.btn').prop('disabled', true).addClass('disabled');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function enableButtons() {
|
|
|
|
|
|
$('.btn').prop('disabled', false).removeClass('disabled');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 结果显示函数
|
|
|
|
|
|
function showResult(section, type, message) {
|
|
|
|
|
|
const resultDiv = $(`#${section}-result`);
|
|
|
|
|
|
resultDiv.removeClass('success error info loading')
|
|
|
|
|
|
.addClass(type)
|
|
|
|
|
|
.html(getResultIcon(type) + message)
|
|
|
|
|
|
.show();
|
|
|
|
|
|
|
|
|
|
|
|
// 自动滚动到结果区域
|
|
|
|
|
|
resultDiv[0].scrollIntoView({ behavior: 'smooth' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function hideResult(section) {
|
|
|
|
|
|
$(`#${section}-result`).hide();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getResultIcon(type) {
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
|
case 'success': return '<span class="loading-spinner" style="display:none;"></span>✅ ';
|
|
|
|
|
|
case 'error': return '<span class="loading-spinner" style="display:none;"></span>❌ ';
|
|
|
|
|
|
case 'info': return '<span class="loading-spinner" style="display:none;"></span>ℹ️ ';
|
|
|
|
|
|
case 'loading': return '<span class="loading-spinner"></span>';
|
|
|
|
|
|
default: return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 表单验证函数
|
|
|
|
|
|
function validateUrl(url) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
new URL(url);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function validateInput(value, type) {
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
|
case 'url':
|
|
|
|
|
|
return validateUrl(value);
|
|
|
|
|
|
case 'notEmpty':
|
|
|
|
|
|
return value.trim().length > 0;
|
|
|
|
|
|
case 'number':
|
|
|
|
|
|
return !isNaN(value) && parseInt(value) > 0;
|
|
|
|
|
|
default:
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 工具函数
|
|
|
|
|
|
function formatFileSize(bytes) {
|
|
|
|
|
|
if (bytes === 0) return '0 Bytes';
|
|
|
|
|
|
const k = 1024;
|
|
|
|
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
|
|
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
|
|
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatDate(dateString) {
|
|
|
|
|
|
const date = new Date(dateString);
|
|
|
|
|
|
return date.getFullYear() + '-' +
|
|
|
|
|
|
String(date.getMonth() + 1).padStart(2, '0') + '-' +
|
|
|
|
|
|
String(date.getDate()).padStart(2, '0');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function copyToClipboard(text) {
|
|
|
|
|
|
navigator.clipboard.writeText(text).then(() => {
|
|
|
|
|
|
alert('已复制到剪贴板');
|
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
|
alert('复制失败,请手动复制');
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 快捷键支持
|
|
|
|
|
|
$(document).keydown(function(e) {
|
|
|
|
|
|
// ESC键返回首页
|
|
|
|
|
|
if (e.keyCode === 27 && currentSection !== 'home') {
|
|
|
|
|
|
showSection('home');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Ctrl+Enter 执行当前页面的主要操作
|
|
|
|
|
|
if (e.ctrlKey && e.keyCode === 13) {
|
|
|
|
|
|
switch (currentSection) {
|
|
|
|
|
|
case 'homepage':
|
|
|
|
|
|
extractHomepage();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'single':
|
|
|
|
|
|
downloadSingleArticle();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'list':
|
|
|
|
|
|
getArticleList();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'batch':
|
|
|
|
|
|
batchDownload();
|
|
|
|
|
|
break;
|
2025-12-02 14:58:52 +08:00
|
|
|
|
case 'detail':
|
|
|
|
|
|
getArticleDetail();
|
|
|
|
|
|
break;
|
2025-11-27 18:32:24 +08:00
|
|
|
|
case 'data':
|
|
|
|
|
|
loadDataList();
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 页面可见性变化时的处理
|
|
|
|
|
|
document.addEventListener('visibilitychange', function() {
|
|
|
|
|
|
if (document.hidden) {
|
|
|
|
|
|
console.log('页面已隐藏');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log('页面已显示');
|
|
|
|
|
|
// 可以在这里刷新任务状态
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 错误处理
|
|
|
|
|
|
window.onerror = function(message, source, lineno, colno, error) {
|
|
|
|
|
|
console.error('页面错误:', message, '位置:', source + ':' + lineno);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 控制台欢迎信息
|
|
|
|
|
|
console.log(`
|
|
|
|
|
|
🚀 微信公众号文章爬虫系统 Web界面
|
|
|
|
|
|
====================================
|
|
|
|
|
|
版本: 1.0.0
|
|
|
|
|
|
开发者: AI Assistant
|
|
|
|
|
|
更新时间: 2025-11-27
|
|
|
|
|
|
====================================
|
|
|
|
|
|
💡 提示:
|
|
|
|
|
|
- 按 ESC 键返回首页
|
|
|
|
|
|
- 按 Ctrl+Enter 执行当前操作
|
|
|
|
|
|
- 所有操作都会显示详细进度
|
|
|
|
|
|
====================================
|
|
|
|
|
|
`);
|