上传 frontend 文件夹
This commit is contained in:
222
frontend/README.md
Normal file
222
frontend/README.md
Normal file
@@ -0,0 +1,222 @@
|
||||
# 🚀 微信公众号文章爬虫系统 - 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支持一下!**
|
||||
482
frontend/css/style.css
Normal file
482
frontend/css/style.css
Normal file
@@ -0,0 +1,482 @@
|
||||
/* 全局样式 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.header {
|
||||
text-align: center;
|
||||
color: white;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.1em;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* 主内容区域 */
|
||||
.main-content {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
/* 功能卡片网格 */
|
||||
.feature-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 25px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
border-radius: 12px;
|
||||
padding: 25px;
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
font-size: 3em;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
font-size: 1.3em;
|
||||
margin-bottom: 10px;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.card p {
|
||||
color: #7f8c8d;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.btn {
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: linear-gradient(135deg, #56ab2f 0%, #a8e6cf 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(86, 171, 47, 0.4);
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
background: linear-gradient(135deg, #3498db 0%, #74b9ff 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-info:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(52, 152, 219, 0.4);
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: linear-gradient(135deg, #f39c12 0%, #fdcb6e 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-warning:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(243, 156, 18, 0.4);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: linear-gradient(135deg, #95a5a6 0%, #bdc3c7 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(149, 165, 166, 0.4);
|
||||
}
|
||||
|
||||
/* 区域样式 */
|
||||
.section {
|
||||
animation: fadeIn 0.5s ease-in;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 2px solid #ecf0f1;
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
color: #2c3e50;
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
/* 表单样式 */
|
||||
.form-group {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group textarea,
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
padding: 12px 15px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
transition: border-color 0.3s ease;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.form-group input:focus,
|
||||
.form-group textarea:focus,
|
||||
.form-group select:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
|
||||
.form-group small {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
color: #7f8c8d;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
margin-right: 20px;
|
||||
margin-bottom: 10px;
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
.checkbox-label input[type="checkbox"] {
|
||||
width: auto !important;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
/* 结果显示区域 */
|
||||
.result {
|
||||
margin-top: 25px;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.result.success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.result.error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.result.info {
|
||||
background-color: #d1ecf1;
|
||||
color: #0c5460;
|
||||
border: 1px solid #bee5eb;
|
||||
}
|
||||
|
||||
.result.loading {
|
||||
background-color: #fff3cd;
|
||||
color: #856404;
|
||||
border: 1px solid #ffeaa7;
|
||||
}
|
||||
|
||||
/* 数据列表样式 */
|
||||
.data-list {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.data-item {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 15px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.data-item:hover {
|
||||
transform: translateX(5px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.data-item h4 {
|
||||
color: #2c3e50;
|
||||
margin-bottom: 10px;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.data-item-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.data-item-stats {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-size: 0.9em;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
.data-item-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* 进度条样式 */
|
||||
.progress-container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background-color: #ecf0f1;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
font-size: 0.9em;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
/* 状态指示器 */
|
||||
.status-indicator {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.status-success { background-color: #27ae60; }
|
||||
.status-error { background-color: #e74c3c; }
|
||||
.status-warning { background-color: #f39c12; }
|
||||
.status-info { background-color: #3498db; }
|
||||
|
||||
/* 底部样式 */
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
color: white;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 工具提示 */
|
||||
.tooltip {
|
||||
position: relative;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.tooltip:hover::after {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: #2c3e50;
|
||||
color: white;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9em;
|
||||
white-space: nowrap;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.feature-cards {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.data-item-info {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.data-item-actions {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.header h1 {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.loading-spinner {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid #f3f3f3;
|
||||
border-top: 2px solid #667eea;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 文本对齐工具类 */
|
||||
.text-center { text-align: center; }
|
||||
.text-left { text-align: left; }
|
||||
.text-right { text-align: right; }
|
||||
|
||||
/* 间距工具类 */
|
||||
.mt-10 { margin-top: 10px; }
|
||||
.mt-20 { margin-top: 20px; }
|
||||
.mb-10 { margin-bottom: 10px; }
|
||||
.mb-20 { margin-bottom: 20px; }
|
||||
|
||||
/* 显示/隐藏工具类 */
|
||||
.hidden { display: none !important; }
|
||||
.visible { display: block !important; }
|
||||
167
frontend/index.html
Normal file
167
frontend/index.html
Normal file
@@ -0,0 +1,167 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>微信公众号文章爬虫系统</title>
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<h1>🚀 微信公众号文章爬虫系统</h1>
|
||||
<p class="subtitle">Wechat Official Account Article Crawler</p>
|
||||
</header>
|
||||
|
||||
<div class="main-content">
|
||||
<!-- 功能选择卡片 -->
|
||||
<div class="feature-cards">
|
||||
<div class="card" id="card-homepage">
|
||||
<div class="card-icon">🏠</div>
|
||||
<h3>提取公众号主页</h3>
|
||||
<p>输入文章链接获取公众号主页链接</p>
|
||||
<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>
|
||||
<p>获取公众号所有文章列表</p>
|
||||
<button class="btn btn-primary" onclick="showSection('list')">进入</button>
|
||||
</div>
|
||||
|
||||
<div class="card" id="card-batch">
|
||||
<div class="card-icon">📦</div>
|
||||
<h3>批量下载文章</h3>
|
||||
<p>批量下载文章详细内容</p>
|
||||
<button class="btn btn-primary" onclick="showSection('batch')">进入</button>
|
||||
</div>
|
||||
|
||||
<div class="card" id="card-data">
|
||||
<div class="card-icon">📊</div>
|
||||
<h3>数据管理</h3>
|
||||
<p>查看已下载的文章数据</p>
|
||||
<button class="btn btn-primary" onclick="showSection('data')">进入</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提取公众号主页区域 -->
|
||||
<div class="section" id="section-homepage" 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="homepage-url" placeholder="请输入公众号下任意一篇已发布的文章链接...">
|
||||
<small>支持公众号文章完整URL,无需Cookie即可获取公众号主页链接</small>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-success" onclick="extractHomepage()">提取主页链接</button>
|
||||
<button class="btn btn-info" onclick="loadExampleUrl()">查看示例</button>
|
||||
</div>
|
||||
<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">
|
||||
<h2>📋 获取文章列表</h2>
|
||||
<button class="btn btn-secondary" onclick="showSection('home')">返回</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Access Token URL:</label>
|
||||
<textarea id="access-token" placeholder="请粘贴从Fiddler获取的完整URL..." rows="4"></textarea>
|
||||
<small>包含 __biz, uin, key, pass_ticket 等参数的完整URL</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>获取页数:</label>
|
||||
<input type="number" id="pages" value="1" min="1" max="999">
|
||||
<small>留空表示获取全部</small>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-success" onclick="getArticleList()">开始获取</button>
|
||||
</div>
|
||||
<div class="result" id="list-result"></div>
|
||||
</div>
|
||||
|
||||
<!-- 批量下载区域 -->
|
||||
<div class="section" id="section-batch" 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="official-account" placeholder="请输入公众号名称或任意一篇文章链接...">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="batch-save-image"> 保存图片
|
||||
</label>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="batch-save-content" checked> 保存内容
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-success" onclick="batchDownload()">开始批量下载</button>
|
||||
</div>
|
||||
<div class="result" id="batch-result"></div>
|
||||
</div>
|
||||
|
||||
<!-- 数据管理区域 -->
|
||||
<div class="section" id="section-data" style="display:none;">
|
||||
<div class="section-header">
|
||||
<h2>📊 数据管理</h2>
|
||||
<button class="btn btn-secondary" onclick="showSection('home')">返回</button>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-info" onclick="loadDataList()">刷新列表</button>
|
||||
<button class="btn btn-warning" onclick="openDataFolder()">打开数据文件夹</button>
|
||||
</div>
|
||||
<div class="data-list" id="data-list">
|
||||
<p class="text-center">点击"刷新列表"加载数据...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<p>© 2025 微信公众号文章爬虫系统 | 仅供学习使用</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
509
frontend/js/app.js
Normal file
509
frontend/js/app.js
Normal file
@@ -0,0 +1,509 @@
|
||||
// 全局变量
|
||||
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 downloadSingleArticle() {
|
||||
alert('此功能需要后端命令行支持。\n\n请使用命令行程序:\n1. 运行 wechat-crawler.exe\n2. 选择对应功能进行下载');
|
||||
showResult('single', 'info', '请使用命令行程序执行下载功能');
|
||||
}
|
||||
|
||||
// 获取文章列表
|
||||
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 style="margin-top: 15px;">
|
||||
<button class="btn btn-info" onclick="loadDataList()">📊 查看数据列表</button>
|
||||
</div>
|
||||
</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分钟超时
|
||||
});
|
||||
}
|
||||
|
||||
// 加载数据列表
|
||||
function loadDataList() {
|
||||
showResult('data', 'loading', '正在加载数据列表...');
|
||||
|
||||
// 调用后端API
|
||||
$.ajax({
|
||||
url: `${API_BASE_URL}/data/list`,
|
||||
method: 'GET',
|
||||
success: function(response) {
|
||||
if (response.success && response.data) {
|
||||
displayDataList(response.data);
|
||||
hideResult('data');
|
||||
} else {
|
||||
showResult('data', 'error', '加载失败');
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
let errorMsg = '请求失败:' + error;
|
||||
if (xhr.status === 0) {
|
||||
errorMsg = '无法连接到后端服务器,请确保 API 服务器已启动';
|
||||
}
|
||||
showResult('data', 'error', errorMsg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 显示数据列表
|
||||
function displayDataList(dataList) {
|
||||
let html = '';
|
||||
|
||||
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目录。');
|
||||
}
|
||||
|
||||
// 任务管理函数
|
||||
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 '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 执行当前操作
|
||||
- 所有操作都会显示详细进度
|
||||
====================================
|
||||
`);
|
||||
88
frontend/start_web.bat
Normal file
88
frontend/start_web.bat
Normal file
@@ -0,0 +1,88 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
title 微信公众号文章爬虫系统 - Web界面
|
||||
|
||||
echo.
|
||||
echo ===============================================
|
||||
echo 🚀 微信公众号文章爬虫系统
|
||||
echo Web界面启动
|
||||
echo ===============================================
|
||||
echo.
|
||||
|
||||
:: 检查Python是否安装
|
||||
python --version >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo ❌ 未检测到Python,正在尝试其他方法...
|
||||
goto :use_powershell
|
||||
) else (
|
||||
echo ✅ 检测到Python环境
|
||||
goto :use_python
|
||||
)
|
||||
|
||||
:use_python
|
||||
echo 📱 使用Python启动Web服务器...
|
||||
echo 🌐 服务地址: http://localhost:8000
|
||||
echo ⏰ 启动时间: %date% %time%
|
||||
echo.
|
||||
echo 💡 提示: 按 Ctrl+C 停止服务器
|
||||
echo ===============================================
|
||||
echo.
|
||||
|
||||
cd /d "%~dp0"
|
||||
python -m http.server 8000
|
||||
goto :end
|
||||
|
||||
:use_powershell
|
||||
echo 📱 使用PowerShell启动Web服务器...
|
||||
echo 🌐 服务地址: http://localhost:8080
|
||||
echo ⏰ 启动时间: %date% %time%
|
||||
echo.
|
||||
echo 💡 提示: 按 Ctrl+C 停止服务器
|
||||
echo ===============================================
|
||||
echo.
|
||||
|
||||
cd /d "%~dp0"
|
||||
powershell -Command "
|
||||
$listener = New-Object System.Net.HttpListener
|
||||
$listener.Prefixes.Add('http://localhost:8080/')
|
||||
$listener.Start()
|
||||
Write-Host '✅ Web服务器已启动: http://localhost:8080'
|
||||
Write-Host '🌐 正在打开浏览器...'
|
||||
Start-Process 'http://localhost:8080'
|
||||
|
||||
while ($listener.IsListening) {
|
||||
$context = $listener.GetContext()
|
||||
$request = $context.Request
|
||||
$response = $context.Response
|
||||
|
||||
$path = $request.Url.LocalPath
|
||||
if ($path -eq '/') { $path = '/index.html' }
|
||||
|
||||
$filePath = Join-Path (Get-Location) $path.TrimStart('/')
|
||||
|
||||
if (Test-Path $filePath) {
|
||||
$content = [System.IO.File]::ReadAllBytes($filePath)
|
||||
$response.ContentType = switch ([System.IO.Path]::GetExtension($filePath).ToLower()) {
|
||||
'.html' { 'text/html; charset=utf-8' }
|
||||
'.css' { 'text/css; charset=utf-8' }
|
||||
'.js' { 'application/javascript; charset=utf-8' }
|
||||
'.json' { 'application/json; charset=utf-8' }
|
||||
default { 'text/plain; charset=utf-8' }
|
||||
}
|
||||
$response.ContentLength64 = $content.Length
|
||||
$response.OutputStream.Write($content, 0, $content.Length)
|
||||
} else {
|
||||
$response.StatusCode = 404
|
||||
$response.StatusDescription = 'Not Found'
|
||||
}
|
||||
|
||||
$response.OutputStream.Close()
|
||||
}
|
||||
"
|
||||
|
||||
:end
|
||||
echo.
|
||||
echo ===============================================
|
||||
echo 服务器已停止运行
|
||||
echo ===============================================
|
||||
pause
|
||||
1753
frontend/user-center.html
Normal file
1753
frontend/user-center.html
Normal file
File diff suppressed because it is too large
Load Diff
178
frontend/前端功能问题说明.md
Normal file
178
frontend/前端功能问题说明.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# 🔧 前端功能问题说明和解决方案
|
||||
|
||||
## ❌ 当前问题
|
||||
|
||||
前端的所有功能(除了"提取公众号主页")都**无法正常工作**,原因如下:
|
||||
|
||||
### 问题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:批量下载文章
|
||||
```
|
||||
|
||||
### 方案B:Web界面(需要修复)
|
||||
**需要完成的工作**:
|
||||
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界面可作为未来优化项目。
|
||||
Reference in New Issue
Block a user