Files
yixiaogao/frontend/js/app.js
2025-12-02 14:58:52 +08:00

602 lines
24 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 全局变量
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', '正在批量下载文章,请稍候...');
// 调用后端 API同步等待
$.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分钟超时
});
}
// 获取文章详情功能4
function getArticleDetail() {
const accessToken = $('#detail-access-token').val().trim();
const pages = parseInt($('#detail-pages').val()) || 0;
const submitBtn = $('#section-detail .btn-success');
// 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
$.ajax({
url: `${API_BASE_URL}/article/detail`,
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({
access_token: accessToken,
pages: pages
}),
beforeSend: function() {
console.log('📡 发送请求到后端 API...');
},
success: function(response) {
console.log('✅ 收到服务器响应:', response);
// 恢复任务状态和按钮
isTaskRunning = false;
submitBtn.prop('disabled', false)
.removeClass('disabled')
.html('开始获取');
if (response.success && response.data) {
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);
} else {
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>
`);
}
},
error: function(xhr, status, error) {
console.error('❌ 请求失败:', { xhr, status, error });
// 恢复任务状态和按钮
isTaskRunning = false;
submitBtn.prop('disabled', false)
.removeClass('disabled')
.html('开始获取');
let errorMsg = '请求失败';
let errorDetail = '';
if (xhr.status === 0) {
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 || '未知错误');
}
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需要较长时间
});
}
// 加载示例 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&...';
$('#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. 粘贴到此处');
}
// 任务管理函数
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;
case 'detail':
getArticleDetail();
break;
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 执行当前操作
- 所有操作都会显示详细进度
====================================
`);