2025-12-2genxin

This commit is contained in:
2025-12-02 14:58:52 +08:00
parent 4fef65bd93
commit be0954828c
36 changed files with 3352 additions and 1638 deletions

View File

@@ -1,222 +0,0 @@
# 🚀 微信公众号文章爬虫系统 - Web界面
一个现代化的Web界面用于管理微信公众号文章爬虫功能。
## 📋 功能特性
### 🍪 Cookie 配置
- 便捷的Cookie输入和保存
- Cookie示例和验证
- 实时状态反馈
### 📄 下载单篇文章
- 支持微信文章链接输入
- 可选择保存图片和内容
- 实时下载进度显示
### 📋 获取文章列表
- Access Token URL输入
- 自定义获取页数
- 批量文章信息获取
### 📦 批量下载文章
- 公众号名称或链接输入
- 批量下载文章详情
- 智能进度跟踪
### 📊 数据管理
- 已下载数据概览
- 文章统计信息
- 快速文件夹访问
## 🛠 使用方法
### 方法1快速启动推荐
1. **双击启动脚本**
```
start_web.bat
```
2. **自动打开浏览器**
- 系统会自动检测Python或使用PowerShell
- 默认地址:`http://localhost:8000` 或 `http://localhost:8080`
### 方法2手动启动
#### 使用Python推荐
```bash
cd frontend
python -m http.server 8000
```
#### 使用Node.js
```bash
cd frontend
npx http-server -p 8000
```
#### 使用其他Web服务器
- 将frontend文件夹作为Web根目录即可
## 🎮 界面使用说明
### 主界面
- **功能卡片**:点击不同卡片进入对应功能
- **现代UI**:响应式设计,支持桌面和移动端
- **状态指示**:实时显示操作状态和进度
### Cookie配置页面
1. 点击"Cookie 配置"卡片
2. 粘贴从Fiddler获取的Cookie内容
3. 点击"保存Cookie"按钮
4. 等待保存成功提示
### 下载单篇文章
1. 进入"下载单篇文章"功能
2. 输入微信文章完整链接
3. 选择是否保存图片和内容
4. 点击"开始下载"查看进度
### 获取文章列表
1. 进入"获取文章列表"功能
2. 粘贴包含认证参数的完整URL
3. 设置获取页数(可选)
4. 点击"开始获取"执行任务
### 批量下载
1. 进入"批量下载文章"功能
2. 输入公众号名称或任意文章链接
3. 选择保存选项
4. 点击"开始批量下载"
### 数据管理
1. 进入"数据管理"功能
2. 点击"刷新列表"查看已下载数据
3. 可以查看文章详情或打开文件夹
## 🎨 界面特性
### 响应式设计
- ✅ 桌面端优化体验
- ✅ 平板和手机端兼容
- ✅ 自适应布局
### 现代化UI
- 🎨 渐变色彩搭配
- 💫 平滑动画效果
- 📱 卡片式设计语言
- 🌟 悬停交互反馈
### 交互体验
- ⌨️ 快捷键支持ESC返回Ctrl+Enter执行
- 🔄 实时进度条
- 📊 状态指示器
- 🔔 操作反馈提示
## 🔧 技术架构
### 前端技术栈
- **HTML5**: 现代语义化标记
- **CSS3**: Flexbox/Grid + 动画
- **JavaScript**: ES6+ + jQuery
- **响应式**: Mobile-First设计
### 文件结构
```
frontend/
├── index.html # 主页面
├── css/
│ └── style.css # 样式文件
├── js/
│ └── app.js # 应用逻辑
├── start_web.bat # 启动脚本
└── README.md # 说明文档
```
### 与后端交互
- 目前为演示版本,使用前端模拟
- 预留了完整的API接口结构
- 支持与命令行程序集成
## 🚀 部署选项
### 本地开发
```bash
# 克隆项目
cd frontend
# 启动开发服务器
python -m http.server 8000
# 或
npx http-server -p 8000
```
### 生产环境
- **Nginx**: 部署静态文件
- **Apache**: 配置虚拟主机
- **IIS**: Windows服务器部署
- **Docker**: 容器化部署
## 📊 浏览器兼容性
| 浏览器 | 版本 | 支持状态 |
|--------|------|---------|
| Chrome | 60+ | ✅ 完全支持 |
| Firefox | 55+ | ✅ 完全支持 |
| Safari | 12+ | ✅ 完全支持 |
| Edge | 79+ | ✅ 完全支持 |
| IE | 11 | ⚠️ 基础支持 |
## 🐛 常见问题
### Q: 页面打不开或样式异常?
A: 确保所有文件在同一目录下使用HTTP服务器访问不是file://协议)
### Q: 功能按钮点击无反应?
A: 检查浏览器控制台是否有JavaScript错误确保jQuery正常加载
### Q: 进度条不显示?
A: 当前为演示版本进度为模拟效果。实际部署需要连接后端API
### Q: 如何连接实际的后端?
A: 修改`js/app.js`中的API调用部分替换模拟逻辑为实际HTTP请求
## 🔮 后续计划
### v1.1 计划功能
- [ ] 真实后端API集成
- [ ] WebSocket实时通信
- [ ] 文件上传拖拽功能
- [ ] 任务队列管理
### v1.2 计划功能
- [ ] 用户认证系统
- [ ] 多公众号管理
- [ ] 数据可视化图表
- [ ] 导出功能增强
## 📄 开源协议
本项目仅供学习和研究使用,请遵守相关法律法规和服务条款。
## 🤝 贡献指南
1. Fork 本项目
2. 创建功能分支
3. 提交更改
4. 发起 Pull Request
## 📞 支持联系
如有问题或建议,请通过以下方式联系:
- 📧 邮箱: your-email@example.com
- 💬 Issues: 在GitHub提交Issue
- 📱 QQ群: 123456789
---
**⚠️ 免责声明**: 本工具仅供学习交流使用,请遵守相关法律法规和平台服务条款。使用者需自行承担使用风险。
**🌟 如果这个项目对您有帮助请给个Star支持一下**

View File

@@ -393,9 +393,9 @@
<a href="#" class="nav-link">监控中心</a>
<a href="#" class="nav-link">数据分析</a>
<a href="#" class="nav-link">帮助文档</a>
<a href="user-center.html" class="nav-link" id="userMenu" style="display: none;">👤 用户中心</a>
<a href="user-center.html" class="nav-link" id="userCenterLink">👤 用户中心</a>
<a href="login.html" class="nav-link" id="loginLink">🔐 登录</a>
<a href="#" class="nav-link" id="logoutLink" style="display: none;" onclick="logout()">🚪 退出</a>
<div id="userMenu" class="user-info"></div>
</nav>
</div>
</header>
@@ -736,11 +736,18 @@ ${article.content.replace(/<[^>]*>/g, '')}
// 登录状态管理
function checkLoginStatus() {
const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true';
const username = localStorage.getItem('username');
// 检查认证数据
const authData = localStorage.getItem('authData') || sessionStorage.getItem('authData');
if (isLoggedIn && username) {
showLoggedInState(username);
if (authData) {
try {
const auth = JSON.parse(authData);
const username = auth.user_info && auth.user_info.username ? auth.user_info.username : '用户';
showLoggedInState(username);
} catch (e) {
console.error('解析登录数据失败:', e);
showLoggedOutState();
}
} else {
showLoggedOutState();
}
@@ -748,38 +755,87 @@ ${article.content.replace(/<[^>]*>/g, '')}
// 显示已登录状态
function showLoggedInState(username) {
document.getElementById('loginLink').style.display = 'none';
document.getElementById('userMenu').style.display = 'block';
document.getElementById('logoutLink').style.display = 'block';
const loginLink = document.getElementById('loginLink');
const userMenu = document.getElementById('userMenu');
// 为用户中心菜单添加点击事件
document.getElementById('userMenu').onclick = function() {
window.location.href = 'user-center.html';
};
if (loginLink) loginLink.style.display = 'none';
if (userMenu) {
userMenu.innerHTML = `
<span style="margin-right: 10px;">👋 ${username}</span>
<button onclick="logout()" style="
background: rgba(255, 255, 255, 0.2);
color: white;
border: 1px solid rgba(255, 255, 255, 0.3);
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
transition: all 0.3s;
" onmouseover="this.style.background='rgba(255, 255, 255, 0.3)'"
onmouseout="this.style.background='rgba(255, 255, 255, 0.2)'">
退出登录
</button>
`;
userMenu.style.display = 'flex';
}
}
// 显示未登录状态
function showLoggedOutState() {
document.getElementById('loginLink').style.display = 'block';
document.getElementById('userMenu').style.display = 'none';
document.getElementById('logoutLink').style.display = 'none';
const loginLink = document.getElementById('loginLink');
const userMenu = document.getElementById('userMenu');
if (loginLink) loginLink.style.display = 'inline';
if (userMenu) userMenu.innerHTML = '';
}
// 登出功能
function logout() {
if (confirm('确定要退出登录吗?')) {
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('username');
localStorage.removeItem('token');
sessionStorage.removeItem('userSession');
// 获取认证数据
const authData = localStorage.getItem('authData') || sessionStorage.getItem('authData');
let token = '';
showLoggedOutState();
alert('已成功退出登录!');
// 如果在用户中心页面,跳转到首页
if (window.location.pathname.includes('user-center.html')) {
window.location.href = 'frontend.html';
if (authData) {
try {
const auth = JSON.parse(authData);
token = auth.token || '';
} catch (e) {
console.error('解析token失败:', e);
}
}
// 调用后端登出API
fetch('http://localhost:8080/api/user/logout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': token ? `Bearer ${token}` : ''
}
}).then(response => {
// 无论API调用成功与否都清除本地数据
localStorage.removeItem('authData');
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('username');
localStorage.removeItem('token');
sessionStorage.removeItem('authData');
sessionStorage.removeItem('userSession');
alert('已成功退出登录!');
window.location.href = 'login.html';
}).catch(error => {
console.error('登出请求失败:', error);
// 即使API调用失败也清除本地数据
localStorage.removeItem('authData');
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('username');
localStorage.removeItem('token');
sessionStorage.removeItem('authData');
sessionStorage.removeItem('userSession');
alert('已成功退出登录!');
window.location.href = 'login.html';
});
}
}

View File

@@ -150,6 +150,21 @@ body {
box-shadow: 0 8px 25px rgba(149, 165, 166, 0.4);
}
/* 按钮禁用状态 */
.btn:disabled,
.btn.disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none !important;
box-shadow: none !important;
}
.btn:disabled:hover,
.btn.disabled:hover {
transform: none !important;
box-shadow: none !important;
}
/* 区域样式 */
.section {
animation: fadeIn 0.5s ease-in;
@@ -479,4 +494,28 @@ body {
/* 显示/隐藏工具类 */
.hidden { display: none !important; }
.visible { display: block !important; }
.visible { display: block !important; }
/* 帮助文本样式 */
.help-text {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
margin-bottom: 25px;
border-left: 4px solid #667eea;
}
.help-text p {
margin: 10px 0;
color: #2c3e50;
}
.help-text ul {
margin: 10px 0 10px 20px;
color: #555;
}
.help-text li {
margin: 8px 0;
line-height: 1.6;
}

View File

@@ -1053,9 +1053,9 @@
<!-- 头部 -->
<header class="header">
<div class="header-content">
<a href="#" class="logo">🔍 易搜高</a>
<a href="frontend.html" class="logo">🔍 易搜高</a>
<nav class="nav-menu">
<a href="#" class="nav-link active">首页</a>
<a href="frontend.html" class="nav-link active">首页</a>
<a href="#" class="nav-link">监控中心</a>
<a href="#" class="nav-link">数据分析</a>
<a href="#" class="nav-link">帮助文档</a>
@@ -1073,6 +1073,7 @@
<div class="nav-tabs">
<button class="nav-tab active" data-tab="library">📚 自媒体库</button>
<button class="nav-tab" data-tab="monitor">👁️ 监控账号</button>
<button class="nav-tab" onclick="window.location.href='index.html'" style="background: linear-gradient(135deg, #28a745 0%, #20c997 100%); color: white; margin-top: 10px;">🚀 爬虫系统</button>
</div>
</div>

View File

@@ -310,9 +310,9 @@
<a href="#" class="nav-link">监控中心</a>
<a href="#" class="nav-link">数据分析</a>
<a href="#" class="nav-link">帮助文档</a>
<a href="user-center.html" class="nav-link" id="userMenu" style="display: none;">👤 用户中心</a>
<a href="user-center.html" class="nav-link" id="userCenterLink">👤 用户中心</a>
<a href="login.html" class="nav-link" id="loginLink">🔐 登录</a>
<a href="#" class="nav-link" id="logoutLink" style="display: none;" onclick="logout()">🚪 退出</a>
<div id="userMenu" class="user-info"></div>
</nav>
</div>
<h1>历史文章</h1>
@@ -629,11 +629,18 @@
// 登录状态管理
function checkLoginStatus() {
const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true';
const username = localStorage.getItem('username');
// 检查认证数据
const authData = localStorage.getItem('authData') || sessionStorage.getItem('authData');
if (isLoggedIn && username) {
showLoggedInState(username);
if (authData) {
try {
const auth = JSON.parse(authData);
const username = auth.user_info && auth.user_info.username ? auth.user_info.username : '用户';
showLoggedInState(username);
} catch (e) {
console.error('解析登录数据失败:', e);
showLoggedOutState();
}
} else {
showLoggedOutState();
}
@@ -641,38 +648,68 @@
// 显示已登录状态
function showLoggedInState(username) {
document.getElementById('loginLink').style.display = 'none';
document.getElementById('userMenu').style.display = 'block';
document.getElementById('logoutLink').style.display = 'block';
const loginLink = document.getElementById('loginLink');
const userMenu = document.getElementById('userMenu');
// 为用户中心菜单添加点击事件
document.getElementById('userMenu').onclick = function() {
window.location.href = 'user-center.html';
};
if (loginLink) loginLink.style.display = 'none';
if (userMenu) {
userMenu.innerHTML = `
<div class="user-info">
<span class="user-name">${username}</span>
<button class="logout-btn" onclick="logout()" style="background: rgba(255,255,255,0.2); color: white; border: 1px solid rgba(255,255,255,0.3); padding: 6px 16px; border-radius: 6px; cursor: pointer; font-size: 14px; transition: all 0.3s;">退出登录</button>
</div>
`;
userMenu.style.display = 'flex';
}
}
// 显示未登录状态
function showLoggedOutState() {
document.getElementById('loginLink').style.display = 'block';
document.getElementById('userMenu').style.display = 'none';
document.getElementById('logoutLink').style.display = 'none';
const loginLink = document.getElementById('loginLink');
const userMenu = document.getElementById('userMenu');
if (loginLink) loginLink.style.display = 'inline';
if (userMenu) userMenu.innerHTML = '';
}
// 登出功能
function logout() {
if (confirm('确定要退出登录吗?')) {
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('username');
localStorage.removeItem('token');
sessionStorage.removeItem('userSession');
// 获取认证数据
const authData = localStorage.getItem('authData') || sessionStorage.getItem('authData');
let token = '';
showLoggedOutState();
alert('已成功退出登录!');
// 如果在用户中心页面,跳转到首页
if (window.location.pathname.includes('user-center.html')) {
window.location.href = 'frontend.html';
if (authData) {
try {
const auth = JSON.parse(authData);
token = auth.token || '';
} catch (e) {
console.error('解析token失败:', e);
}
}
// 调用后端API登出
fetch('http://localhost:8080/api/user/logout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': token
},
body: JSON.stringify({ token: token })
})
.finally(() => {
// 清除所有认证信息
localStorage.removeItem('authData');
sessionStorage.removeItem('authData');
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('username');
localStorage.removeItem('token');
sessionStorage.removeItem('userSession');
showLoggedOutState();
alert('已成功退出登录!');
window.location.href = 'login.html';
});
}
}

View File

@@ -7,6 +7,22 @@
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<!-- 头部导航 -->
<header class="top-navbar" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 15px 0; box-shadow: 0 2px 10px rgba(0,0,0,0.1); position: sticky; top: 0; z-index: 100;">
<div class="navbar-content" style="max-width: 1200px; margin: 0 auto; padding: 0 20px; display: flex; align-items: center; justify-content: space-between;">
<a href="frontend.html" class="logo" style="font-size: 24px; font-weight: bold; text-decoration: none; color: white;">🔍 易搜高</a>
<nav class="nav-menu" style="display: flex; gap: 15px; align-items: center;">
<a href="frontend.html" class="nav-link" style="color: white; text-decoration: none; font-size: 14px; font-weight: 500; padding: 8px 16px; border-radius: 6px; transition: all 0.3s; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2);" onmouseover="this.style.background='rgba(255, 255, 255, 0.2)'" onmouseout="this.style.background='rgba(255, 255, 255, 0.1)'">首页</a>
<a href="#" class="nav-link" style="color: white; text-decoration: none; font-size: 14px; font-weight: 500; padding: 8px 16px; border-radius: 6px; transition: all 0.3s; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2);" onmouseover="this.style.background='rgba(255, 255, 255, 0.2)'" onmouseout="this.style.background='rgba(255, 255, 255, 0.1)'">监控中心</a>
<a href="#" class="nav-link" style="color: white; text-decoration: none; font-size: 14px; font-weight: 500; padding: 8px 16px; border-radius: 6px; transition: all 0.3s; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2);" onmouseover="this.style.background='rgba(255, 255, 255, 0.2)'" onmouseout="this.style.background='rgba(255, 255, 255, 0.1)'">数据分析</a>
<a href="#" class="nav-link" style="color: white; text-decoration: none; font-size: 14px; font-weight: 500; padding: 8px 16px; border-radius: 6px; transition: all 0.3s; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2);" onmouseover="this.style.background='rgba(255, 255, 255, 0.2)'" onmouseout="this.style.background='rgba(255, 255, 255, 0.1)'">帮助文档</a>
<a href="user-center.html" class="nav-link" id="userCenterLink" style="color: white; text-decoration: none; font-size: 14px; font-weight: 500; padding: 8px 16px; border-radius: 6px; transition: all 0.3s; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2);" onmouseover="this.style.background='rgba(255, 255, 255, 0.2)'" onmouseout="this.style.background='rgba(255, 255, 255, 0.1)'">👤 用户中心</a>
<a href="login.html" class="nav-link" id="loginLink" style="color: white; text-decoration: none; font-size: 14px; font-weight: 500; padding: 8px 16px; border-radius: 6px; transition: all 0.3s; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2);" onmouseover="this.style.background='rgba(255, 255, 255, 0.2)'" onmouseout="this.style.background='rgba(255, 255, 255, 0.1)'">🔐 登录</a>
<div id="userMenu" class="user-info" style="display: flex; align-items: center; gap: 10px;"></div>
</nav>
</div>
</header>
<div class="container">
<header class="header">
<h1>🚀 微信公众号文章爬虫系统</h1>
@@ -23,13 +39,6 @@
<button class="btn btn-primary" onclick="showSection('homepage')">进入</button>
</div>
<div class="card" id="card-single">
<div class="card-icon">📄</div>
<h3>下载单篇文章</h3>
<p>根据链接下载单篇文章</p>
<button class="btn btn-primary" onclick="showSection('single')">进入</button>
</div>
<div class="card" id="card-list">
<div class="card-icon">📋</div>
<h3>获取文章列表</h3>
@@ -44,11 +53,11 @@
<button class="btn btn-primary" onclick="showSection('batch')">进入</button>
</div>
<div class="card" id="card-data">
<div class="card" id="card-detail">
<div class="card-icon">📊</div>
<h3>数据管理</h3>
<p>查看已下载的文章数据</p>
<button class="btn btn-primary" onclick="showSection('data')">进入</button>
<h3>获取文章详情</h3>
<p>获取文章阅读量、点赞数、评论等详细信息</p>
<button class="btn btn-primary" onclick="showSection('detail')">进入</button>
</div>
</div>
@@ -70,30 +79,6 @@
<div class="result" id="homepage-result"></div>
</div>
<!-- 下载单篇文章区域 -->
<div class="section" id="section-single" style="display:none;">
<div class="section-header">
<h2>📄 下载单篇文章</h2>
<button class="btn btn-secondary" onclick="showSection('home')">返回</button>
</div>
<div class="form-group">
<label>文章链接:</label>
<input type="text" id="article-url" placeholder="请输入微信文章链接...">
</div>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" id="save-image" checked> 保存图片
</label>
<label class="checkbox-label">
<input type="checkbox" id="save-content" checked> 保存内容
</label>
</div>
<div class="form-actions">
<button class="btn btn-success" onclick="downloadSingleArticle()">开始下载</button>
</div>
<div class="result" id="single-result"></div>
</div>
<!-- 获取文章列表区域 -->
<div class="section" id="section-list" style="display:none;">
<div class="section-header">
@@ -140,20 +125,43 @@
<div class="result" id="batch-result"></div>
</div>
<!-- 数据管理区域 -->
<div class="section" id="section-data" style="display:none;">
<!-- 获取文章详情区域 -->
<div class="section" id="section-detail" style="display:none;">
<div class="section-header">
<h2>📊 数据管理</h2>
<h2>📊 获取文章详情</h2>
<button class="btn btn-secondary" onclick="showSection('home')">返回</button>
</div>
<div class="help-text">
<p><strong>💡 功能说明:</strong></p>
<ul>
<li>📈 自动获取公众号所有文章列表</li>
<li>📊 下载每篇文章的阅读量、点赞数、分享数等统计数据</li>
<li>💬 获取文章评论信息(如果有)</li>
<li>📁 保存为TXT格式便于查看和分析</li>
</ul>
<p><strong>⚠️ 注意事项:</strong></p>
<ul>
<li>需要提供有效的Access Token URL包含登录凭证</li>
<li>处理时间较长,请耐心等待</li>
<li>数据将保存在 data/公众号名称/文章详细 目录下</li>
</ul>
</div>
<div class="form-group">
<label>Access Token URL</label>
<textarea id="detail-access-token" placeholder="请粘贴从Fiddler获取的完整URL...\n\n步骤\n1. 在浏览器中登录微信公众号平台\n2. 访问目标公众号主页\n3. 向下滚动加载文章列表\n4. 在Fiddler中找到profile_ext?action=getmsg请求\n5. 复制完整的URL包含__biz、uin、key、pass_ticket等参数" rows="6"></textarea>
<small>示例https://mp.weixin.qq.com/mp/profile_ext?action=getmsg&__biz=...&uin=...&key=...&pass_ticket=...</small>
</div>
<div class="form-group">
<label>获取页数:</label>
<input type="number" id="detail-pages" value="0" min="0" max="999" placeholder="留空或0表示获取全部">
<small>每页约10篇文章留空或输入0表示获取全部文章</small>
</div>
<div class="form-actions">
<button class="btn btn-info" onclick="loadDataList()">刷新列表</button>
<button class="btn btn-warning" onclick="openDataFolder()">打开数据文件夹</button>
<button class="btn btn-success" onclick="getArticleDetail()">开始获取</button>
<button class="btn btn-info" onclick="loadDetailExample()">查看示例</button>
</div>
<div class="data-list" id="data-list">
<p class="text-center">点击"刷新列表"加载数据...</p>
</div>
</div>
<div class="result" id="detail-result"></div>
</div>
</div>
<footer class="footer">
@@ -163,5 +171,116 @@
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="js/app.js"></script>
<script>
// 登录状态管理
function checkLoginStatus() {
// 检查认证数据
const authData = localStorage.getItem('authData') || sessionStorage.getItem('authData');
if (authData) {
try {
const auth = JSON.parse(authData);
const username = auth.user_info && auth.user_info.username ? auth.user_info.username : '用户';
showLoggedInState(username);
} catch (e) {
console.error('解析登录数据失败:', e);
showLoggedOutState();
}
} else {
showLoggedOutState();
}
}
// 显示已登录状态
function showLoggedInState(username) {
const loginLink = document.getElementById('loginLink');
const userMenu = document.getElementById('userMenu');
if (loginLink) loginLink.style.display = 'none';
if (userMenu) {
userMenu.innerHTML = `
<span style="margin-right: 10px;">👋 ${username}</span>
<button onclick="logout()" style="
background: rgba(255, 255, 255, 0.2);
color: white;
border: 1px solid rgba(255, 255, 255, 0.3);
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
transition: all 0.3s;
" onmouseover="this.style.background='rgba(255, 255, 255, 0.3)'"
onmouseout="this.style.background='rgba(255, 255, 255, 0.2)'">
退出登录
</button>
`;
userMenu.style.display = 'flex';
}
}
// 显示未登录状态
function showLoggedOutState() {
const loginLink = document.getElementById('loginLink');
const userMenu = document.getElementById('userMenu');
if (loginLink) loginLink.style.display = 'inline';
if (userMenu) userMenu.innerHTML = '';
}
// 登出功能
function logout() {
if (confirm('确定要退出登录吗?')) {
// 获取认证数据
const authData = localStorage.getItem('authData') || sessionStorage.getItem('authData');
let token = '';
if (authData) {
try {
const auth = JSON.parse(authData);
token = auth.token || '';
} catch (e) {
console.error('解析token失败:', e);
}
}
// 调用后端登出API
fetch('http://localhost:8080/api/user/logout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': token ? `Bearer ${token}` : ''
}
}).then(response => {
// 无论API调用成功与否都清除本地数据
localStorage.removeItem('authData');
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('username');
localStorage.removeItem('token');
sessionStorage.removeItem('authData');
sessionStorage.removeItem('userSession');
alert('已成功退出登录!');
window.location.href = 'login.html';
}).catch(error => {
console.error('登出请求失败:', error);
// 即使API调用失败也清除本地数据
localStorage.removeItem('authData');
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('username');
localStorage.removeItem('token');
sessionStorage.removeItem('authData');
sessionStorage.removeItem('userSession');
alert('已成功退出登录!');
window.location.href = 'login.html';
});
}
}
// 页面加载时检查登录状态
document.addEventListener('DOMContentLoaded', function() {
checkLoginStatus();
});
</script>
</body>
</html>

View File

@@ -112,11 +112,6 @@ function openInNewTab(url) {
}
// 下载单篇文章
function downloadSingleArticle() {
alert('此功能需要后端命令行支持。\n\n请使用命令行程序\n1. 运行 wechat-crawler.exe\n2. 选择对应功能进行下载');
showResult('single', 'info', '请使用命令行程序执行下载功能');
}
// 获取文章列表
function getArticleList() {
const accessToken = $('#access-token').val().trim();
@@ -197,7 +192,7 @@ function batchDownload() {
isTaskRunning = true;
showResult('batch', 'loading', '正在批量下载文章,请稍候...');
// 调用后端API同步等待
// 调用后端 API同步等待
$.ajax({
url: `${API_BASE_URL}/article/batch`,
method: 'POST',
@@ -217,9 +212,6 @@ function batchDownload() {
<p><strong>公众号:</strong>${data.account}</p>
<p><strong>文章数量:</strong>${data.articleCount} 篇</p>
<p><strong>保存路径:</strong>${data.path}</p>
<div style="margin-top: 15px;">
<button class="btn btn-info" onclick="loadDataList()">📊 查看数据列表</button>
</div>
</div>
`;
showResult('batch', 'success', resultHtml);
@@ -241,92 +233,190 @@ function batchDownload() {
});
}
// 加载数据列表
function loadDataList() {
showResult('data', 'loading', '正在加载数据列表...');
// 获取文章详情功能4
function getArticleDetail() {
const accessToken = $('#detail-access-token').val().trim();
const pages = parseInt($('#detail-pages').val()) || 0;
const submitBtn = $('#section-detail .btn-success');
// 调用后端API
// 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}/data/list`,
method: 'GET',
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) {
displayDataList(response.data);
hideResult('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('data', 'error', '加载失败');
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) {
let errorMsg = '请求失败' + error;
console.error('❌ 请求失败:', { xhr, status, error });
// 恢复任务状态和按钮
isTaskRunning = false;
submitBtn.prop('disabled', false)
.removeClass('disabled')
.html('开始获取');
let errorMsg = '请求失败';
let errorDetail = '';
if (xhr.status === 0) {
errorMsg = '无法连接到后端服务器,请确保 API 服务器已启动';
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('data', 'error', errorMsg);
}
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需要较长时间
});
}
// 显示数据列表
function displayDataList(dataList) {
let html = '';
// 加载示例 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&...';
if (!dataList || dataList.length === 0) {
html = '<p class="text-center" style="padding: 20px; color: #666;">暂无数据,请先使用其他功能爬取文章</p>';
} else {
dataList.forEach(item => {
const safeItemName = (item.name || '').replace(/'/g, "\\'");
const safeItemPath = (item.path || '').replace(/'/g, "\\'").replace(/\\/g, '\\\\');
html += `
<div class="data-item">
<h4><span class="status-indicator status-success"></span>${item.name || '未知'}</h4>
<div class="data-item-info">
<div class="data-item-stats">
<div class="stat-item">
<span>📊</span>
<span>${item.articleCount || 0} 篇文章</span>
</div>
<div class="stat-item">
<span>📅</span>
<span>${item.lastUpdate || '未知'}</span>
</div>
<div class="stat-item">
<span>📁</span>
<span>${item.path || '未知'}</span>
</div>
</div>
<div class="data-item-actions">
<button class="btn btn-info" onclick="viewArticles('${safeItemName}')">查看文章</button>
<button class="btn btn-warning" onclick="alert('文件夹路径:${safeItemPath}')">查看路径</button>
</div>
</div>
</div>
`;
});
}
$('#data-list').html(html);
}
// 查看文章列表
function viewArticles(accountName) {
alert(`查看 ${accountName} 的文章列表
这里将展示该公众号的所有文章,包括:
- 文章标题
- 发布时间
- 文件大小
- 下载状态等`);
}
// 打开文件夹
function openFolder(path) {
alert(`打开文件夹: ${path}\n\n在实际环境中,这里会调用系统命令打开文件资源管理器。`);
}
// 打开数据文件夹
function openDataFolder() {
alert('打开数据文件夹\n\n在实际环境中这里会打开data目录。');
$('#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. 粘贴到此处');
}
// 任务管理函数
@@ -470,6 +560,9 @@ $(document).keydown(function(e) {
case 'batch':
batchDownload();
break;
case 'detail':
getArticleDetail();
break;
case 'data':
loadDataList();
break;

View File

@@ -568,7 +568,7 @@
};
// 调用后端登录API
fetch('http://localhost:8000/api/user/login', {
fetch('http://localhost:8080/api/user/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -576,17 +576,18 @@
body: JSON.stringify(loginData)
})
.then(response => {
console.log('✅ 收到响应,状态码:', response.status);
if (!response.ok) {
// 处理HTTP错误状态码
if (response.status === 500) {
throw new Error('服务器内部错误');
} else if (response.status === 401) {
return response.json().then(data => {
throw new Error(data.msg || '用户名或密码错误');
throw new Error(data.message || '用户名或密码错误');
});
} else if (response.status === 400) {
return response.json().then(data => {
throw new Error(data.msg || '请求参数错误');
throw new Error(data.message || '请求参数错误');
});
} else {
throw new Error('网络响应异常');
@@ -595,13 +596,14 @@
return response.json();
})
.then(data => {
console.log('✅ 收到数据:', data);
// 检查响应状态
if (data.code === 200) {
if (data.success || data.code === 200) {
// 登录成功,存储认证信息
const authData = {
token: data.token,
user_id: data.user_id,
user_info: data.user_info,
token: data.data.token,
user_id: data.data.user_id,
user_info: data.data.user_info,
loginTime: new Date().toISOString(),
remember: remember
};
@@ -635,19 +637,19 @@
// 根据错误码显示不同的错误信息
switch(data.code) {
case 401:
errorMessage = data.msg || '用户名或密码错误';
errorMessage = data.message || '用户名或密码错误';
break;
case 400:
errorMessage = data.msg || '请求参数错误,请检查输入';
errorMessage = data.message || '请求参数错误,请检查输入';
break;
case 404:
errorMessage = data.msg || '账号不存在';
errorMessage = data.message || '账号不存在';
break;
case 500:
errorMessage = data.msg || '服务器内部错误,请稍后再试';
errorMessage = data.message || '服务器内部错误,请稍后再试';
break;
default:
errorMessage = data.msg || '登录失败,请检查用户名和密码';
errorMessage = data.message || '登录失败,请检查用户名和密码';
}
showError(errorMessage);
@@ -657,12 +659,17 @@
})
.catch(error => {
// 捕获网络或其他错误
console.error('登录请求失败:', error);
console.error('登录请求失败:', error);
console.error('错误类型:', error.name);
console.error('错误消息:', error.message);
// 根据错误信息显示具体的错误提示
let errorMessage = '登录失败,请稍后再试';
if (error.message.includes('账号不存在')) {
// 检查是否是网络错误
if (error.name === 'TypeError' && error.message.includes('Failed to fetch')) {
errorMessage = '无法连接到服务器,请确保:\n1. API服务器正在运行 (http://localhost:8080)\n2. 检查网络连接';
} else if (error.message.includes('账号不存在')) {
errorMessage = '账号不存在,请先注册';
} else if (error.message.includes('密码错误')) {
errorMessage = '密码错误,请重新输入';
@@ -701,15 +708,25 @@
window.addEventListener('load', function() {
const authData = localStorage.getItem('authData') || sessionStorage.getItem('authData');
if (authData) {
const auth = JSON.parse(authData);
showSuccess(`欢迎回来,${auth.user_info.username}`);
// 如果已经登录,可以选择直接跳转
setTimeout(() => {
if (confirm('检测到您已登录,是否跳转到首页?')) {
window.location.href = 'frontend.html';
try {
const auth = JSON.parse(authData);
// 检查user_info是否存在且有username
if (auth.user_info && auth.user_info.username) {
showSuccess(`欢迎回来,${auth.user_info.username}`);
// 如果已经登录,可以选择直接跳转
setTimeout(() => {
if (confirm('检测到您已登录,是否跳转到首页?')) {
window.location.href = 'frontend.html';
}
}, 1000);
}
}, 1000);
} catch (e) {
console.error('解析登录数据失败:', e);
// 清除无效的登录数据
localStorage.removeItem('authData');
sessionStorage.removeItem('authData');
}
}
});

View File

@@ -639,7 +639,7 @@
};
// 调用后端注册API
fetch('http://localhost:8000/api/user/register', {
fetch('http://localhost:8080/api/user/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -654,7 +654,7 @@
})
.then(data => {
// 检查响应状态
if (data.code === 200) {
if (data.success || data.code === 200) {
// 注册成功
showSuccess('注册成功!正在跳转到登录页面...');
@@ -664,7 +664,7 @@
}, 1500);
} else {
// 注册失败,显示错误信息
showError(data.msg || '注册失败,请稍后重试');
showError(data.message || '注册失败,请稍后重试');
registerBtn.textContent = '注册账号';
registerBtn.disabled = false;
}

View File

@@ -27,6 +27,9 @@ echo.
echo 💡 提示: 按 Ctrl+C 停止服务器
echo ===============================================
echo.
echo 🌐 正在打开浏览器...
start http://localhost:8000/frontend.html
echo.
cd /d "%~dp0"
python -m http.server 8000
@@ -48,7 +51,7 @@ $listener.Prefixes.Add('http://localhost:8080/')
$listener.Start()
Write-Host '✅ Web服务器已启动: http://localhost:8080'
Write-Host '🌐 正在打开浏览器...'
Start-Process 'http://localhost:8080'
Start-Process 'http://localhost:8080/frontend.html'
while ($listener.IsListening) {
$context = $listener.GetContext()
@@ -56,7 +59,7 @@ while ($listener.IsListening) {
$response = $context.Response
$path = $request.Url.LocalPath
if ($path -eq '/') { $path = '/index.html' }
if ($path -eq '/') { $path = '/frontend.html' }
$filePath = Join-Path (Get-Location) $path.TrimStart('/')

View File

@@ -397,14 +397,15 @@
<!-- 头部导航 -->
<header class="header">
<div class="header-content">
<a href="frontend.html" class="logo">易搜高</a>
<a href="frontend.html" class="logo">🔍 易搜高</a>
<nav class="nav-menu">
<a href="frontend.html">首页监控</a>
<a href="history-articles.html">历史文章</a>
<a href="user-center.html" class="active">用户中心</a>
<!-- 用户信息和登出按钮会通过JavaScript动态添加 -->
<div id="userMenu"></div>
<a href="login.html" id="loginLink" class="login-link">登录/注册</a>
<a href="frontend.html" class="nav-link">首页</a>
<a href="#" class="nav-link">监控中心</a>
<a href="#" class="nav-link">数据分析</a>
<a href="#" class="nav-link">帮助文档</a>
<a href="user-center.html" class="nav-link active" id="userCenterLink">👤 用户中心</a>
<a href="login.html" class="nav-link" id="loginLink">🔐 登录</a>
<div id="userMenu" class="user-info"></div>
</nav>
</div>
</header>
@@ -441,7 +442,6 @@
</h2>
<div class="info-card">
<!-- 修复个人信息区域中重复的ID -->
<div class="info-row">
<span class="info-label">用户名</span>
<span class="info-value" id="info-username">--</span>
@@ -465,6 +465,23 @@
<span class="info-value" id="info-status">--</span>
</div>
</div>
<!-- 编辑个人信息表单 -->
<h3 class="section-title" style="font-size: 18px; margin-top: 30px;">
<span class="section-icon">✏️</span>
编辑信息
</h3>
<form id="edit-info-form" onsubmit="return saveUserInfo(event)">
<div class="form-group">
<label for="edit-email" class="form-label">邮箱</label>
<input type="email" id="edit-email" class="form-input" placeholder="请输入邮箱">
</div>
<div class="form-group">
<label for="edit-bio" class="form-label">个人简介</label>
<textarea id="edit-bio" class="form-input" rows="4" placeholder="介绍一下自己..."></textarea>
</div>
<button type="submit" class="btn-primary">保存修改</button>
</form>
</section>
<!-- 密码修改部分 -->
@@ -742,6 +759,7 @@
}
</style>
<script>
// 添加缺失的函数定义,确保功能完整性
function showMessage(message, type = 'info') {
const messageElement = document.getElementById('message');
@@ -759,44 +777,64 @@
function checkLoginStatus() {
// 检查本地存储中的认证信息
const authToken = localStorage.getItem('authToken') || localStorage.getItem('token') || sessionStorage.getItem('token');
const username = localStorage.getItem('username') || localStorage.getItem('nickname');
const authData = localStorage.getItem('authData') || sessionStorage.getItem('authData');
if (authToken && username) {
// 用户已登录更新UI
document.getElementById('loginLink').style.display = 'none';
const userMenu = document.getElementById('userMenu');
if (userMenu) {
userMenu.innerHTML = `
<div class="user-info">
<span class="user-name">${username}</span>
<button class="logout-btn" onclick="logout()">退出登录</button>
</div>
`;
if (authData) {
try {
const auth = JSON.parse(authData);
const username = auth.user_info && auth.user_info.username ? auth.user_info.username : '用户';
// 用户已登录更新UI
const loginLink = document.getElementById('loginLink');
if (loginLink) loginLink.style.display = 'none';
const userMenu = document.getElementById('userMenu');
if (userMenu) {
userMenu.innerHTML = `
<div class="user-info">
<span class="user-name">${username}</span>
<button class="logout-btn" onclick="logout()">退出登录</button>
</div>
`;
}
// 加载用户信息
loadUserData(auth);
} catch (e) {
console.error('解析登录数据失败:', e);
window.location.href = 'login.html';
}
// 加载用户信息
loadUserData();
} else {
// 用户未登录,跳转到登录页面
window.location.href = 'login.html';
window.location.href = 'login.html?redirect=' + encodeURIComponent(window.location.href);
}
}
function logout() {
// 清除本地存储的认证信息
localStorage.removeItem('authToken');
localStorage.removeItem('username');
localStorage.removeItem('userSettings');
localStorage.removeItem('lastLogin');
const authData = localStorage.getItem('authData') || sessionStorage.getItem('authData');
let token = '';
// 尝试调用登出API使用fetch而不是apiCall避免依赖本地函数
fetch('http://localhost:8000/api/user/logout', {
if (authData) {
try {
const auth = JSON.parse(authData);
token = auth.token;
} catch (e) {
console.error('解析token失败:', e);
}
}
// 清除本地存储的认证信息
localStorage.removeItem('authData');
sessionStorage.removeItem('authData');
// 调用登出API
fetch('http://localhost:8080/api/user/logout', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
'Authorization': token
},
credentials: 'include'
body: JSON.stringify({ token: token })
}).finally(() => {
// 无论API调用成功与否都跳转到登录页面
window.location.href = 'login.html';
@@ -804,64 +842,53 @@
}
function clearAuthData() {
localStorage.removeItem('authToken');
localStorage.removeItem('username');
localStorage.removeItem('userSettings');
localStorage.removeItem('authData');
sessionStorage.removeItem('authData');
}
function loadUserData() {
// 显示加载状态
showLoading(true);
function loadUserData(authData) {
console.log('🔍 加载用户数据:', authData);
// 获取认证token
const authToken = localStorage.getItem('authToken') || localStorage.getItem('token') || sessionStorage.getItem('token');
if (!authData || !authData.user_info) {
console.error('❌ 无效的认证数据');
window.location.href = 'login.html';
return;
}
// 调用后端API获取用户信息
fetch('http://localhost:8000/api/user/info', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
credentials: 'include'
})
.then(response => {
if (!response.ok) {
if (response.status === 401) {
// 认证失败,清除认证信息并重定向到登录页面
clearAuthData();
window.location.href = 'login.html';
throw new Error('认证失败,请重新登录');
}
throw new Error(`HTTP error! status: ${response.status}`);
// 从authData中提取用户信息
const userInfo = authData.user_info;
// 格式化日期
const formatDate = (dateStr) => {
if (!dateStr) return '--';
try {
const date = new Date(dateStr);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
} catch (e) {
return dateStr;
}
return response.json();
})
.then(data => {
if (data && data.data) {
// 使用后端返回的用户数据
const userData = data.data;
// 更新用户信息显示
updateUserInfoDisplay(userData);
}
hideLoading();
})
.catch(error => {
console.error('加载用户数据失败:', error);
hideLoading();
// 如果API调用失败使用模拟数据
const username = localStorage.getItem('username') || '测试用户';
const userData = {
username: username,
email: 'test@example.com',
nickname: username,
role: '普通用户',
joinDate: new Date().toLocaleDateString('zh-CN'),
lastLogin: new Date().toLocaleString('zh-CN')
};
updateUserInfoDisplay(userData);
});
};
// 准备显示的用户数据
const userData = {
username: userInfo.username || '--',
email: userInfo.email || '--',
joinDate: formatDate(userInfo.created_at),
lastLogin: formatDate(userInfo.last_login_at),
status: userInfo.status === 1 ? 'active' : 'inactive',
bio: userInfo.bio || ''
};
console.log('✅ 格式化后的用户数据:', userData);
// 更新用户信息显示
updateUserInfoDisplay(userData);
}
// 增强版API调用包装函数
@@ -1195,6 +1222,77 @@
// 保存用户名用于账号注销确认
document.getElementById('username-display').textContent = userData.username || '';
// 填充编辑表单
const emailInput = document.getElementById('edit-email');
const bioInput = document.getElementById('edit-bio');
if (emailInput) emailInput.value = userData.email || '';
if (bioInput) bioInput.value = userData.bio || '';
}
// 保存用户信息
function saveUserInfo(event) {
event.preventDefault();
const email = document.getElementById('edit-email').value;
const bio = document.getElementById('edit-bio').value;
// 验证邮箱
if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
showMessage('请输入有效的邮箱地址', 'error');
return false;
}
// 获取认证信息
const authData = localStorage.getItem('authData') || sessionStorage.getItem('authData');
if (!authData) {
showMessage('未登录,请先登录', 'error');
setTimeout(() => {
window.location.href = 'login.html';
}, 1500);
return false;
}
let token, userId;
try {
const auth = JSON.parse(authData);
token = auth.token;
userId = auth.user_id;
} catch (e) {
showMessage('认证信息错误', 'error');
return false;
}
// 调用API更新用户信息
fetch('http://localhost:8080/api/user/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': token
},
body: JSON.stringify({
user_id: userId,
email: email,
bio: bio
})
})
.then(response => response.json())
.then(data => {
console.log('✅ 更新响应:', data);
if (data.success) {
showMessage('信息保存成功!', 'success');
// 更新显示的邮箱
document.getElementById('info-email').textContent = email;
} else {
showMessage(data.message || '保存失败', 'error');
}
})
.catch(error => {
console.error('❌ 保存错误:', error);
showMessage('网络错误,请稍后再试', 'error');
});
return false;
}
function showLoading(show) {
@@ -1624,8 +1722,9 @@
}
});
});
</script>
// 添加消息提示的CSS样式
<style>
/* 消息提示样式 */
.message-toast {
position: fixed;
@@ -1750,4 +1849,7 @@
.password-match-message.mismatch {
color: #ff4d4f;
}
}</style>
</main>
</body>
</html>

View File

@@ -1,178 +0,0 @@
# 🔧 前端功能问题说明和解决方案
## ❌ 当前问题
前端的所有功能(除了"提取公众号主页")都**无法正常工作**,原因如下:
### 问题1前端是纯模拟未调用真实后端
当前前端代码中的所有下载功能都是**模拟执行**
```javascript
// 这只是模拟,没有真正下载
const progressInterval = setInterval(() => {
progress += Math.random() * 20;
if (progress >= 100) {
endTask('single', 'success', '文章下载完成!'); // 假的成功提示
}
}, 800);
```
### 问题2浏览器无法直接执行本地程序
Web前端在浏览器中运行出于安全限制**无法直接调用本地的exe程序**。
## ✅ 解决方案
需要搭建一个**HTTP API服务器**作为桥梁,连接前端和后端程序。
### 方案架构
```
前端网页 (浏览器)
↓ HTTP请求
API服务器 (Go/Node.js)
↓ 执行命令
后端爬虫程序 (wechat-crawler.exe)
```
## 🚀 实施步骤
### 步骤1已创建API服务器代码
文件:`backend/api/server.go`
主要功能:
- ✅ 提取公众号主页 (`/api/homepage/extract`)
- ⏳ 下载单篇文章 (`/api/article/download`)
- ⏳ 获取文章列表 (`/api/article/list`)
- ⏳ 批量下载 (`/api/article/batch`)
- ✅ 获取数据列表 (`/api/data/list`)
### 步骤2编译API服务器
```bash
cd d:\workspace\Access_wechat_article\backend\api
go build -o api_server.exe server.go
```
### 步骤3启动API服务器
```bash
cd d:\workspace\Access_wechat_article\backend
.\api\api_server.exe
```
服务器将运行在 `http://localhost:8080`
### 步骤4修复前端代码
前端`js/app.js`文件被意外破坏需要修复第68行的代码错误。
**问题代码**第68行
```javascript
<button class="btn btn-info" onclick="copyToClipboard('${homepageUrl.replace(/'/g, "\\'")}')"入下载功能中`
```
**应该是**
```javascript
<button class="btn btn-info" onclick="copyToClipboard('${homepageUrl.replace(/'/g, "\\'")}')">📋 复制链接</button>
<button class="btn btn-warning" onclick="openInNewTab('${homepageUrl}')">🔗 打开主页</button>
```
## 📋 当前可用功能
### ✅ 已实现功能
1. **提取公众号主页** - 通过API服务器调用后端程序
### ⏳ 需要完善的功能
2. **下载单篇文章** - 需要后端添加对应的命令行接口
3. **获取文章列表** - 需要后端添加对应的命令行接口
4. **批量下载** - 可使用现有的功能5
5. **数据管理** - 已有API前端需要调用
## 🔨 完整解决方案
由于问题比较复杂,建议采用以下简化方案:
### 方案A命令行方式推荐
**优点**
- 简单直接,无需额外开发
- 稳定可靠
- 功能完整
**使用方法**
```bash
# 直接运行后端程序
cd backend
.\wechat-crawler.exe
# 按菜单选择功能
数字键1提取公众号主页
数字键3获取文章列表
数字键5批量下载文章
```
### 方案BWeb界面需要修复
**需要完成的工作**
1. ✅ API服务器已创建
2. ❌ 前端JS代码需要修复
3. ❌ 后端需要添加更多命令行接口
4. ❌ 前端需要修改为调用真实API
**工作量**约2-3小时开发时间
## 💡 临时解决方案
在API服务器和前端代码完全修复之前建议
### 1. 使用命令行程序
```bash
cd d:\workspace\Access_wechat_article\backend
.\wechat-crawler.exe
```
### 2. 只使用"提取公众号主页"功能
这个功能已经可以正常工作通过API服务器
### 3. 其他功能直接在命令行执行
- 功能3获取文章列表
- 功能5批量下载文章
## 📊 功能对比
| 功能 | 命令行 | Web界面 | 状态 |
|------|--------|---------|------|
| 提取公众号主页 | ✅ | ✅ | 可用 |
| 获取文章列表 | ✅ | ❌ | 仅命令行 |
| 批量下载文章 | ✅ | ❌ | 仅命令行 |
| 数据查看 | ✅ | ⏳ | 需修复 |
## 🎯 下一步建议
### 选项1继续使用命令行推荐
- 功能完整且稳定
- 无需额外开发
- 立即可用
### 选项2完善Web界面
需要完成:
1. 修复前端JS代码错误
2. 实现完整的API调用逻辑
3. 测试所有功能
**预计时间**2-3小时
## 🔍 错误定位
当前前端代码的主要问题在:
- 文件:`frontend/js/app.js`
- 行号第68行
- 问题:字符串拼接错误,导致语法错误
## 📞 技术支持
如需完善Web界面建议
1. 先修复`app.js`第68行的语法错误
2. 测试API服务器是否正常运行
3. 逐个功能进行调试和完善
---
**当前状态**建议优先使用命令行程序功能完整且稳定。Web界面可作为未来优化项目。