import sysfrom datetime import datetimefrom PyQt6.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QTextEdit, QGroupBox, QGridLayout, QProgressBar, QSplitter, QMessageBox, QDateTimeEdit, QTabWidget, QTableWidget, QTableWidgetItem, QHeaderView, QFileDialog)from PyQt6.QtCore import Qt, QThread, pyqtSignal, QDateTimefrom PyQt6.QtGui import QFont, QTextCursorclass WorkThread(QThread): """工作线程 - 用于执行耗时操作""" log_signal = pyqtSignal(str) progress_signal = pyqtSignal(int) finished_signal = pyqtSignal(bool, str) result_signal = pyqtSignal(dict) def __init__(self, config): super().__init__() self.config = config def run(self): """执行任务(此处调用原有功能)""" passclass LogTextEdit(QTextEdit): """自定义日志文本框""" def __init__(self, parent=None): super().__init__(parent) self.setReadOnly(True) self.setFont(QFont("Consolas", 10)) self.setStyleSheet(""" QTextEdit { background-color: #1e1e1e; color: #d4d4d4; border: 1px solid #3c3c3c; border-radius: 5px; padding: 5px; } """) def append_log(self, message): """添加日志消息""" self.append(message) cursor = self.textCursor() cursor.movePosition(QTextCursor.MoveOperation.End) self.setTextCursor(cursor)class MainWindow(QMainWindow): """主窗口""" def __init__(self): super().__init__() self.work_thread = None self.init_ui() def init_ui(self): """初始化UI""" self.setWindowTitle("微信公众号文章数据采集工具(欢迎关注微信公众号:码海听潮)") self.setMinimumSize(1200, 800) # 设置窗口样式 self.setStyleSheet(""" QMainWindow { background-color: #f5f5f5; } QGroupBox { font-weight: bold; border: 1px solid #cccccc; border-radius: 5px; margin-top: 10px; padding-top: 10px; background-color: white; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px 0 5px; } QLabel { color: #333333; } QLineEdit, QDateTimeEdit { padding: 5px; border: 1px solid #cccccc; border-radius: 3px; background-color: white; } QLineEdit:focus, QDateTimeEdit:focus { border-color: #0078d4; } QPushButton { background-color: #0078d4; color: white; border: none; border-radius: 3px; padding: 8px 15px; font-weight: bold; } QPushButton:hover { background-color: #106ebe; } QPushButton:pressed { background-color: #005a9e; } QPushButton:disabled { background-color: #cccccc; color: #666666; } QProgressBar { border: 1px solid #cccccc; border-radius: 3px; text-align: center; background-color: white; } QProgressBar::chunk { background-color: #0078d4; border-radius: 3px; } QTabWidget::pane { border: 1px solid #cccccc; border-radius: 5px; background-color: white; } QTabBar::tab { background-color: #f0f0f0; padding: 8px 20px; margin-right: 2px; border-top-left-radius: 4px; border-top-right-radius: 4px; } QTabBar::tab:selected { background-color: white; border-bottom: 2px solid #0078d4; } QTableWidget { border: none; gridline-color: #e0e0e0; } QTableWidget::item { padding: 5px; } QHeaderView::section { background-color: #f0f0f0; padding: 5px; border: 1px solid #e0e0e0; font-weight: bold; } """) # 中央部件 central_widget = QWidget() self.setCentralWidget(central_widget) # 主布局 main_layout = QVBoxLayout(central_widget) main_layout.setSpacing(10) main_layout.setContentsMargins(10, 10, 10, 10) # 创建分割器 splitter = QSplitter(Qt.Orientation.Vertical) # 顶部配置区域 config_widget = self.create_config_widget() splitter.addWidget(config_widget) # 底部日志区域 log_widget = self.create_log_widget() splitter.addWidget(log_widget) # 设置分割比例 splitter.setSizes([400, 400]) main_layout.addWidget(splitter) # 底部按钮区域(居中显示) button_layout = QHBoxLayout() button_layout.addStretch() # 左侧弹簧 self.start_btn = QPushButton("开始采集") self.start_btn.clicked.connect(self.start_task) self.start_btn.setMinimumWidth(120) self.stop_btn = QPushButton("停止") self.stop_btn.clicked.connect(self.stop_task) self.stop_btn.setEnabled(False) self.stop_btn.setMinimumWidth(120) self.stop_btn.setStyleSheet(""" QPushButton { background-color: #d9534f; } QPushButton:hover { background-color: #c9302c; } """) button_layout.addWidget(self.start_btn) button_layout.addSpacing(20) # 按钮间距 button_layout.addWidget(self.stop_btn) button_layout.addStretch() # 右侧弹簧 main_layout.addLayout(button_layout) def create_config_widget(self): """创建配置区域""" widget = QWidget() layout = QVBoxLayout(widget) # 基本配置组 basic_group = QGroupBox("基本配置") basic_layout = QGridLayout(basic_group) basic_layout.setSpacing(10) # 公众号名称 basic_layout.addWidget(QLabel("公众号名称:"), 0, 0) self.wechat_name_edit = QLineEdit("码海听潮") self.wechat_name_edit.setPlaceholderText("请输入公众号名称") basic_layout.addWidget(self.wechat_name_edit, 0, 1) # 代理配置 basic_layout.addWidget(QLabel("代理地址:"), 1, 0) proxy_layout = QHBoxLayout() self.proxy_host_edit = QLineEdit("127.0.0.1") self.proxy_host_edit.setMaximumWidth(150) proxy_layout.addWidget(self.proxy_host_edit) proxy_layout.addWidget(QLabel(":")) self.proxy_port_edit = QLineEdit("8888") self.proxy_port_edit.setMaximumWidth(80) proxy_layout.addWidget(self.proxy_port_edit) proxy_layout.addStretch() basic_layout.addLayout(proxy_layout, 1, 1) # 日期范围 basic_layout.addWidget(QLabel("开始日期:"), 2, 0) self.start_date_edit = QDateTimeEdit() self.start_date_edit.setCalendarPopup(True) self.start_date_edit.setDateTime(QDateTime(2025, 8, 7, 0, 0)) self.start_date_edit.setDisplayFormat("yyyy-MM-dd") basic_layout.addWidget(self.start_date_edit, 2, 1) basic_layout.addWidget(QLabel("结束日期:"), 3, 0) self.end_date_edit = QDateTimeEdit() self.end_date_edit.setCalendarPopup(True) self.end_date_edit.setDateTime(QDateTime(2025, 8, 14, 0, 0)) self.end_date_edit.setDisplayFormat("yyyy-MM-dd") basic_layout.addWidget(self.end_date_edit, 3, 1) # 输出文件 basic_layout.addWidget(QLabel("输出文件:"), 4, 0) output_layout = QHBoxLayout() self.output_file_edit = QLineEdit("公众号文章列表.xlsx") output_layout.addWidget(self.output_file_edit) browse_btn = QPushButton("浏览...") browse_btn.setMaximumWidth(80) browse_btn.clicked.connect(self.browse_output_file) browse_btn.setStyleSheet(""" QPushButton { background-color: #6c757d; padding: 5px 10px; } QPushButton:hover { background-color: #5a6268; } """) output_layout.addWidget(browse_btn) basic_layout.addLayout(output_layout, 4, 1) layout.addWidget(basic_group) # 进度组 progress_group = QGroupBox("进度") progress_layout = QVBoxLayout(progress_group) self.progress_bar = QProgressBar() self.progress_bar.setValue(0) progress_layout.addWidget(self.progress_bar) self.status_label = QLabel("就绪") self.status_label.setStyleSheet("color: #28a745; font-weight: bold;") progress_layout.addWidget(self.status_label) layout.addWidget(progress_group) return widget def create_log_widget(self): """创建日志区域""" widget = QWidget() layout = QVBoxLayout(widget) # 标签页 tab_widget = QTabWidget() # 运行日志标签页 log_tab = QWidget() log_layout = QVBoxLayout(log_tab) self.log_text = LogTextEdit() log_layout.addWidget(self.log_text) tab_widget.addTab(log_tab, "运行日志") # 统计信息标签页 stats_tab = QWidget() stats_layout = QVBoxLayout(stats_tab) self.stats_table = QTableWidget() self.stats_table.setColumnCount(2) self.stats_table.setHorizontalHeaderLabels(["项目", "数值"]) self.stats_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) self.stats_table.verticalHeader().setVisible(False) stats_layout.addWidget(self.stats_table) tab_widget.addTab(stats_tab, "统计信息") layout.addWidget(tab_widget) return widget def browse_output_file(self): """浏览输出文件路径""" file_path, _ = QFileDialog.getSaveFileName( self, "保存文件", self.output_file_edit.text(), "Excel文件 (*.xlsx)" ) if file_path: self.output_file_edit.setText(file_path) def start_task(self): """开始任务""" # 获取配置 config = { 'target_wechat': self.wechat_name_edit.text(), 'proxy_host': self.proxy_host_edit.text(), 'proxy_port': int(self.proxy_port_edit.text()), 'start_date': self.start_date_edit.date().toString("yyyy-MM-dd"), 'end_date': self.end_date_edit.date().toString("yyyy-MM-dd"), 'output_file': self.output_file_edit.text() } # 验证配置 if not config['target_wechat']: QMessageBox.warning(self, "警告", "请输入公众号名称") return # 禁用开始按钮,启用停止按钮 self.start_btn.setEnabled(False) self.stop_btn.setEnabled(True) self.progress_bar.setValue(0) self.status_label.setText("运行中...") self.status_label.setStyleSheet("color: #0078d4; font-weight: bold;") self.log_text.clear() # 创建并启动工作线程 self.work_thread = WorkThread(config) self.work_thread.log_signal.connect(self.log_text.append_log) self.work_thread.progress_signal.connect(self.progress_bar.setValue) self.work_thread.finished_signal.connect(self.on_task_finished) self.work_thread.result_signal.connect(self.on_task_result) self.work_thread.start() def stop_task(self): """停止任务""" if self.work_thread and self.work_thread.isRunning(): self.work_thread.terminate() self.work_thread.wait() self.log_text.append_log("[警告] 用户手动停止任务") self.on_task_finished(False, "任务已停止") def on_task_finished(self, success, message): """任务完成回调""" self.start_btn.setEnabled(True) self.stop_btn.setEnabled(False) self.progress_bar.setValue(100 if success else 0) if success: self.status_label.setText("完成") self.status_label.setStyleSheet("color: #28a745; font-weight: bold;") QMessageBox.information(self, "完成", message) else: self.status_label.setText("失败") self.status_label.setStyleSheet("color: #dc3545; font-weight: bold;") QMessageBox.critical(self, "错误", message) def on_task_result(self, result): """任务结果回调""" # 更新统计信息表格 self.stats_table.setRowCount(3) self.stats_table.setItem(0, 0, QTableWidgetItem("文章总数")) self.stats_table.setItem(0, 1, QTableWidgetItem(str(result.get('total', 0)))) self.stats_table.setItem(1, 0, QTableWidgetItem("成功捕获")) self.stats_table.setItem(1, 1, QTableWidgetItem(str(result.get('captured', 0)))) self.stats_table.setItem(2, 0, QTableWidgetItem("输出文件")) self.stats_table.setItem(2, 1, QTableWidgetItem(result.get('output_file', ''))) # 设置表格样式 for i in range(3): for j in range(2): item = self.stats_table.item(i, j) if item: item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)def main(): """主函数""" app = QApplication(sys.argv) app.setStyle('Fusion') window = MainWindow() window.show() sys.exit(app.exec())if __name__ == "__main__": main()