Files
yixiaogao/frontend/js/app.js

602 lines
24 KiB
JavaScript
Raw Permalink Normal View History

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 执行当前操作
- 所有操作都会显示详细进度
====================================
`);