Files
haha/gui_app.py

954 lines
39 KiB
Python
Raw Normal View History

2026-01-17 20:38:27 +08:00
import sys
import os
import re
import json
from datetime import datetime, timedelta
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QHBoxLayout, QPushButton, QTableWidget, QTableWidgetItem,
QFileDialog, QMessageBox, QHeaderView, QLabel, QMenu,
QInputDialog, QDialog, QDialogButtonBox, QFormLayout,
2026-01-18 06:11:21 +08:00
QLineEdit, QSpinBox, QMessageBox, QToolBar, QAction,
QProgressBar)
2026-01-17 20:38:27 +08:00
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtGui import QKeySequence
import pandas as pd
2026-01-18 06:11:21 +08:00
class TaskWorker(QThread):
progress = pyqtSignal(int, int, str)
finished = pyqtSignal(int, int, list)
error = pyqtSignal(str)
2026-01-19 17:24:30 +08:00
def __init__(self, tasks, base_folder_path=None):
2026-01-18 06:11:21 +08:00
super().__init__()
self.tasks = tasks
2026-01-19 17:24:30 +08:00
self.base_folder_path = base_folder_path
2026-01-18 06:11:21 +08:00
def run(self):
try:
from main import Pdd
from loguru import logger
success_count = 0
fail_count = 0
error_messages = []
total = len(self.tasks)
2026-01-19 17:24:30 +08:00
# 获取大文件夹路径(从主窗口传递)
base_folder_path = getattr(self, 'base_folder_path', None)
2026-01-18 06:11:21 +08:00
for idx, data in enumerate(self.tasks, 1):
2026-01-19 17:24:30 +08:00
user_id = data.get('多多 id', '')
2026-01-18 06:11:21 +08:00
topics = data.get('话题', '')
time_start = data.get('计算后的发布时间', '')
url = data.get('达人链接', '')
2026-01-19 17:24:30 +08:00
index = data.get('序号', '')
2026-01-18 06:11:21 +08:00
2026-01-19 17:24:30 +08:00
self.progress.emit(idx, total, f"正在执行任务 {idx}/{total} - 多多: {user_id}")
2026-01-18 06:11:21 +08:00
if not url or not user_id:
2026-01-19 17:24:30 +08:00
error_msg = f"任务 {idx}: 缺少必需参数 - 多多={user_id}, 达人链接={url}"
2026-01-18 06:11:21 +08:00
logger.warning(error_msg)
error_messages.append(error_msg)
fail_count += 1
continue
# 处理话题格式
if topics:
topic_list = []
for sep in ['', '', '-']:
if sep in topics:
topic_list = [t.strip() for t in topics.split(sep) if t.strip()]
break
if not topic_list:
topic_list = [topics.strip()] if topics.strip() else []
ht = ' '.join([f"#{t}#" for t in topic_list if t])
else:
ht = ""
try:
logger.info(
2026-01-19 17:24:30 +08:00
f"开始执行任务 {idx} - 多多: {user_id}, 达人链接: {url}, 大文件夹路径: {base_folder_path}, 序号: {index}"
2026-01-18 06:11:21 +08:00
)
pdd = Pdd(
url=url,
user_id=user_id,
time_start=time_start if time_start else None,
ht=ht,
index=index,
)
2026-01-19 17:24:30 +08:00
# 使用输入框中的大文件夹路径
folder_path = base_folder_path if base_folder_path and os.path.exists(base_folder_path) else None
2026-01-18 06:11:21 +08:00
logger.info(f"调用 pdd.action(folder_path={folder_path})")
pdd.action(folder_path=folder_path)
logger.info(f"任务 {idx} 执行成功")
success_count += 1
except Exception as e:
2026-01-19 17:24:30 +08:00
error_msg = f"任务 {idx} 执行失败 - 多多: {user_id}, 错误: {str(e)}"
2026-01-18 06:11:21 +08:00
logger.error(error_msg)
logger.exception("详细错误信息:")
error_messages.append(error_msg)
fail_count += 1
continue
self.finished.emit(success_count, fail_count, error_messages)
except Exception as e:
self.error.emit(str(e))
2026-01-17 20:38:27 +08:00
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("拼多多MCN发布管理工具")
self.setGeometry(100, 100, 1200, 800)
# 存储原始数据和处理后的数据
self.raw_data = []
self.processed_data = []
2026-01-18 06:11:21 +08:00
self.worker = None
2026-01-17 20:38:27 +08:00
# 创建主窗口部件
main_widget = QWidget()
self.setCentralWidget(main_widget)
# 创建布局
layout = QVBoxLayout()
main_widget.setLayout(layout)
2026-01-19 17:24:30 +08:00
# 添加大文件夹路径输入框
folder_layout = QHBoxLayout()
folder_label = QLabel("大文件夹路径:")
self.folder_path_input = QLineEdit()
self.folder_path_input.setPlaceholderText("请输入大文件夹路径例如C:\\Users\\user\\data")
folder_browse_btn = QPushButton("浏览...")
folder_browse_btn.clicked.connect(self.browse_folder)
folder_layout.addWidget(folder_label)
folder_layout.addWidget(self.folder_path_input)
folder_layout.addWidget(folder_browse_btn)
layout.addLayout(folder_layout)
# 自动查找桌面上的"多多发文文件"文件夹
default_folder = self.find_default_folder()
if default_folder:
self.folder_path_input.setText(default_folder)
2026-01-17 20:38:27 +08:00
# 创建工具栏
toolbar = QToolBar("主工具栏")
self.addToolBar(toolbar)
# 导入Excel
2026-01-18 06:11:21 +08:00
self.import_action = QAction("导入Excel", self)
self.import_action.setShortcut(QKeySequence("Ctrl+O"))
self.import_action.triggered.connect(self.import_excel)
toolbar.addAction(self.import_action)
2026-01-17 20:38:27 +08:00
# 添加行
2026-01-18 06:11:21 +08:00
self.add_row_action = QAction("添加行", self)
self.add_row_action.setShortcut(QKeySequence("Ctrl+N"))
self.add_row_action.triggered.connect(self.add_row)
toolbar.addAction(self.add_row_action)
2026-01-17 20:38:27 +08:00
# 批量添加行
2026-01-18 06:11:21 +08:00
self.batch_add_action = QAction("批量添加", self)
self.batch_add_action.triggered.connect(self.batch_add_rows)
toolbar.addAction(self.batch_add_action)
2026-01-17 20:38:27 +08:00
# 删除行
2026-01-18 06:11:21 +08:00
self.delete_row_action = QAction("删除选中行", self)
self.delete_row_action.setShortcut(QKeySequence("Delete"))
self.delete_row_action.triggered.connect(self.delete_selected_rows)
toolbar.addAction(self.delete_row_action)
2026-01-17 20:38:27 +08:00
toolbar.addSeparator()
# 保存配置
2026-01-18 06:11:21 +08:00
self.save_action = QAction("保存配置", self)
self.save_action.setShortcut(QKeySequence("Ctrl+S"))
self.save_action.triggered.connect(self.save_config)
toolbar.addAction(self.save_action)
2026-01-17 20:38:27 +08:00
# 加载配置
2026-01-18 06:11:21 +08:00
self.load_action = QAction("加载配置", self)
self.load_action.setShortcut(QKeySequence("Ctrl+L"))
self.load_action.triggered.connect(self.load_config)
toolbar.addAction(self.load_action)
2026-01-17 20:38:27 +08:00
toolbar.addSeparator()
# 执行任务
execute_action = QAction("执行任务", self)
execute_action.setShortcut(QKeySequence("F5"))
execute_action.triggered.connect(self.execute_tasks)
execute_action.setEnabled(False)
self.execute_btn = execute_action
toolbar.addAction(execute_action)
# 导出Excel
export_action = QAction("导出Excel", self)
export_action.triggered.connect(self.export_excel)
export_action.setEnabled(False)
self.export_btn = export_action
toolbar.addAction(export_action)
2026-01-18 06:11:21 +08:00
# 导出Excel模板
self.export_template_action = QAction("导出Excel模板", self)
self.export_template_action.triggered.connect(self.export_template)
toolbar.addAction(self.export_template_action)
2026-01-17 20:38:27 +08:00
# 创建表格
self.table = QTableWidget()
self.table.setColumnCount(9)
self.table.setHorizontalHeaderLabels([
2026-01-19 17:24:30 +08:00
"多多 id", "序号", "话题", "定时发布", "间隔时间(分钟)",
"达人链接", "执行人", "情况", "计算后的发布时间"
2026-01-17 20:38:27 +08:00
])
# 设置表格列宽自适应
header = self.table.horizontalHeader()
header.setSectionResizeMode(QHeaderView.Stretch)
# 允许编辑
self.table.setEditTriggers(QTableWidget.DoubleClicked | QTableWidget.SelectedClicked | QTableWidget.EditKeyPressed)
# 允许选择多行
self.table.setSelectionBehavior(QTableWidget.SelectRows)
self.table.setSelectionMode(QTableWidget.ExtendedSelection)
2026-01-18 06:11:21 +08:00
# UI优化
self.table.setAlternatingRowColors(True)
self.table.setWordWrap(False)
2026-01-17 20:38:27 +08:00
# 连接单元格变化信号,实时更新处理后的数据
self.table.cellChanged.connect(self.on_cell_changed)
# 设置右键菜单
self.table.setContextMenuPolicy(Qt.CustomContextMenu)
self.table.customContextMenuRequested.connect(self.show_context_menu)
layout.addWidget(self.table)
2026-01-18 06:11:21 +08:00
# 进度条
self.progress_bar = QProgressBar()
self.progress_bar.setRange(0, 100)
self.progress_bar.setValue(0)
layout.addWidget(self.progress_bar)
2026-01-17 20:38:27 +08:00
# 状态标签
self.status_label = QLabel("就绪")
layout.addWidget(self.status_label)
2026-01-19 17:24:30 +08:00
def find_default_folder(self):
"""自动查找桌面上的'多多发文文件'文件夹"""
try:
# 获取桌面路径(跨平台)
home = os.path.expanduser("~")
# 尝试常见的桌面路径
desktop_paths = [
os.path.join(home, "Desktop", "多多发文文件"),
os.path.join(home, "桌面", "多多发文文件"), # 中文系统
os.path.join(home, "Desktop"),
os.path.join(home, "桌面"),
]
# 首先查找"多多发文文件"文件夹
for desktop_path in desktop_paths:
if os.path.exists(desktop_path) and os.path.isdir(desktop_path):
if "多多发文文件" in desktop_path:
return desktop_path
# 如果桌面路径存在,检查是否有"多多发文文件"子文件夹
target_folder = os.path.join(desktop_path, "多多发文文件")
if os.path.exists(target_folder) and os.path.isdir(target_folder):
return target_folder
return None
except Exception as e:
print(f"查找默认文件夹时出错:{e}")
return None
def browse_folder(self):
"""浏览选择文件夹"""
folder_path = QFileDialog.getExistingDirectory(self, "选择大文件夹路径")
if folder_path:
self.folder_path_input.setText(folder_path)
2026-01-17 20:38:27 +08:00
def import_excel(self):
"""导入Excel文件"""
file_path, _ = QFileDialog.getOpenFileName(
self, "选择Excel文件", "", "Excel Files (*.xlsx *.xls)"
)
if not file_path:
return
try:
# 读取Excel文件
df = pd.read_excel(file_path)
# 检查必需的列
2026-01-19 17:24:30 +08:00
required_columns = ['多多 id', '话题(以中文"-"分隔)',
2026-01-17 20:38:27 +08:00
'定时发布', '间隔时间', '达人链接', '情况']
2026-01-19 17:24:30 +08:00
optional_columns = ['序号', '执行人'] # 可选字段
2026-01-17 20:38:27 +08:00
# 检查列名是否存在(允许部分匹配)
missing_columns = []
column_mapping = {}
# 检查必需字段
for req_col in required_columns:
found = False
for col in df.columns:
if req_col in str(col) or str(col) in req_col:
column_mapping[req_col] = col
found = True
break
if not found:
missing_columns.append(req_col)
# 检查可选字段
for opt_col in optional_columns:
found = False
for col in df.columns:
if opt_col in str(col) or str(col) in opt_col:
column_mapping[opt_col] = col
found = True
break
if missing_columns:
QMessageBox.warning(
self, "错误",
f"Excel文件缺少以下必需的列\n{', '.join(missing_columns)}"
)
return
# 读取数据
self.raw_data = []
for index, row in df.iterrows():
2026-01-19 17:24:30 +08:00
# 获取序号字段(如果存在)
2026-01-17 20:38:27 +08:00
index_value = ''
2026-01-19 17:24:30 +08:00
if '序号' in column_mapping:
index_value = str(row[column_mapping['序号']]) if pd.notna(row[column_mapping['序号']]) else ''
# 获取执行人字段(如果存在)
executor_value = ''
if '执行人' in column_mapping:
executor_value = str(row[column_mapping['执行人']]) if pd.notna(row[column_mapping['执行人']]) else ''
2026-01-17 20:38:27 +08:00
data = {
2026-01-19 17:24:30 +08:00
'多多 id': str(row[column_mapping['多多 id']]) if pd.notna(row[column_mapping['多多 id']]) else '',
'序号': index_value,
2026-01-17 20:38:27 +08:00
'话题': str(row[column_mapping['话题(以中文"-"分隔)']]) if pd.notna(row[column_mapping['话题(以中文"-"分隔)']]) else '',
'定时发布': str(row[column_mapping['定时发布']]) if pd.notna(row[column_mapping['定时发布']]) else '',
'间隔时间': str(row[column_mapping['间隔时间']]) if pd.notna(row[column_mapping['间隔时间']]) else '',
'达人链接': str(row[column_mapping['达人链接']]) if pd.notna(row[column_mapping['达人链接']]) else '',
2026-01-19 17:24:30 +08:00
'执行人': executor_value,
2026-01-17 20:38:27 +08:00
'情况': str(row[column_mapping['情况']]) if pd.notna(row[column_mapping['情况']]) else '',
}
self.raw_data.append(data)
# 处理数据:计算间隔时间
self.process_data()
# 显示数据
self.display_data()
self.status_label.setText(f"成功导入 {len(self.processed_data)} 条数据")
self.enable_action_buttons()
self.enable_action_buttons()
except Exception as e:
QMessageBox.critical(self, "错误", f"导入Excel文件时出错\n{str(e)}")
import traceback
traceback.print_exc()
def process_data(self):
2026-01-19 17:24:30 +08:00
"""处理数据,计算相同多多 id 的间隔时间"""
2026-01-17 20:38:27 +08:00
self.processed_data = []
2026-01-19 17:24:30 +08:00
# 按多多 id 分组
2026-01-17 20:38:27 +08:00
user_groups = {}
for data in self.raw_data:
2026-01-19 17:24:30 +08:00
user_id = data['多多 id']
2026-01-17 20:38:27 +08:00
if user_id not in user_groups:
user_groups[user_id] = []
user_groups[user_id].append(data)
2026-01-19 17:24:30 +08:00
def parse_time(time_str):
"""解析时间字符串"""
if not time_str:
return None
for fmt in ["%Y-%m-%d %H:%M:%S", "%Y/%m/%d %H:%M:%S",
"%Y-%m-%d %H:%M", "%Y/%m/%d %H:%M"]:
try:
return datetime.strptime(time_str, fmt)
except ValueError:
continue
return None
def parse_interval(interval_str):
"""解析间隔时间(分钟)"""
if not interval_str:
return 0
numbers = re.findall(r'\d+', str(interval_str))
return int(numbers[0]) if numbers else 0
2026-01-17 20:38:27 +08:00
# 处理每个用户组
for user_id, user_data_list in user_groups.items():
2026-01-19 17:24:30 +08:00
# 处理每条数据,按顺序计算
2026-01-17 20:38:27 +08:00
for index, data in enumerate(user_data_list):
processed_item = data.copy()
2026-01-19 17:24:30 +08:00
current_time_str = data.get('定时发布', '').strip()
current_time = parse_time(current_time_str)
2026-01-17 20:38:27 +08:00
2026-01-19 17:24:30 +08:00
if current_time:
# 如果当前数据有定时发布时间,直接使用
processed_item['计算后的发布时间'] = current_time_str
2026-01-17 20:38:27 +08:00
else:
2026-01-19 17:24:30 +08:00
# 如果当前数据没有定时发布时间,尝试根据前一条数据计算
if index > 0:
# 获取前一条数据的计算后的发布时间
prev_calculated_time_str = self.processed_data[-1].get('计算后的发布时间', '').strip()
prev_calculated_time = parse_time(prev_calculated_time_str)
if prev_calculated_time:
# 前一条数据有计算后的发布时间,尝试获取间隔时间
# 优先从当前数据获取间隔时间,如果没有则从前一条数据获取
interval_str = data.get('间隔时间', '').strip()
if not interval_str:
interval_str = user_data_list[index - 1].get('间隔时间', '').strip()
2026-01-17 20:38:27 +08:00
2026-01-19 17:24:30 +08:00
interval_minutes = parse_interval(interval_str)
2026-01-17 20:38:27 +08:00
2026-01-19 17:24:30 +08:00
if interval_minutes > 0:
# 根据前一条时间 + 间隔时间计算
calculated_time = prev_calculated_time + timedelta(minutes=interval_minutes)
processed_item['计算后的发布时间'] = calculated_time.strftime("%Y-%m-%d %H:%M:%S")
else:
# 没有间隔时间,无法计算
processed_item['计算后的发布时间'] = ''
else:
# 前一条数据也没有计算后的发布时间,无法计算
2026-01-17 20:38:27 +08:00
processed_item['计算后的发布时间'] = ''
else:
2026-01-19 17:24:30 +08:00
# 第一条数据没有定时发布时间,无法计算
2026-01-17 20:38:27 +08:00
processed_item['计算后的发布时间'] = ''
self.processed_data.append(processed_item)
def display_data(self):
"""在表格中显示数据"""
2026-01-18 06:11:21 +08:00
self.table.blockSignals(True)
2026-01-17 20:38:27 +08:00
self.table.setRowCount(len(self.processed_data))
for row, data in enumerate(self.processed_data):
2026-01-19 17:24:30 +08:00
# 多多 id
self.table.setItem(row, 0, QTableWidgetItem(str(data.get('多多 id', ''))))
2026-01-17 20:38:27 +08:00
2026-01-19 17:24:30 +08:00
# 序号
self.table.setItem(row, 1, QTableWidgetItem(str(data.get('序号', ''))))
2026-01-17 20:38:27 +08:00
# 话题
2026-01-19 17:24:30 +08:00
self.table.setItem(row, 2, QTableWidgetItem(str(data.get('话题', ''))))
2026-01-17 20:38:27 +08:00
# 定时发布
2026-01-19 17:24:30 +08:00
self.table.setItem(row, 3, QTableWidgetItem(str(data.get('定时发布', ''))))
2026-01-17 20:38:27 +08:00
# 间隔时间
2026-01-19 17:24:30 +08:00
self.table.setItem(row, 4, QTableWidgetItem(str(data.get('间隔时间', ''))))
2026-01-17 20:38:27 +08:00
# 达人链接
2026-01-19 17:24:30 +08:00
self.table.setItem(row, 5, QTableWidgetItem(str(data.get('达人链接', ''))))
# 执行人
self.table.setItem(row, 6, QTableWidgetItem(str(data.get('执行人', ''))))
2026-01-17 20:38:27 +08:00
# 情况
self.table.setItem(row, 7, QTableWidgetItem(str(data.get('情况', ''))))
# 计算后的发布时间
2026-01-18 06:11:21 +08:00
calc_item = QTableWidgetItem(str(data.get('计算后的发布时间', '')))
calc_item.setFlags(calc_item.flags() & ~Qt.ItemIsEditable)
self.table.setItem(row, 8, calc_item)
self.table.blockSignals(False)
2026-01-17 20:38:27 +08:00
def on_cell_changed(self, row, column):
"""当单元格内容改变时,重新处理数据"""
if column == 8: # 计算后的发布时间列不允许直接编辑
return
# 从表格中读取数据
self.sync_table_to_raw_data()
# 重新处理数据
self.process_data()
# 更新显示(跳过计算后的发布时间列的更新,避免循环)
self.update_table_from_processed_data()
def sync_table_to_raw_data(self):
"""从表格同步数据到raw_data"""
self.raw_data = []
for row in range(self.table.rowCount()):
data = {
2026-01-19 17:24:30 +08:00
'多多 id': self.table.item(row, 0).text() if self.table.item(row, 0) else '',
'序号': self.table.item(row, 1).text() if self.table.item(row, 1) else '',
'话题': self.table.item(row, 2).text() if self.table.item(row, 2) else '',
'定时发布': self.table.item(row, 3).text() if self.table.item(row, 3) else '',
'间隔时间': self.table.item(row, 4).text() if self.table.item(row, 4) else '',
'达人链接': self.table.item(row, 5).text() if self.table.item(row, 5) else '',
'执行人': self.table.item(row, 6).text() if self.table.item(row, 6) else '',
2026-01-17 20:38:27 +08:00
'情况': self.table.item(row, 7).text() if self.table.item(row, 7) else '',
}
self.raw_data.append(data)
def update_table_from_processed_data(self):
"""从processed_data更新表格只更新计算后的发布时间列"""
# 临时断开信号避免触发cellChanged
self.table.blockSignals(True)
for row in range(min(len(self.processed_data), self.table.rowCount())):
calculated_time = self.processed_data[row].get('计算后的发布时间', '')
if self.table.item(row, 8):
self.table.item(row, 8).setText(str(calculated_time))
else:
2026-01-18 06:11:21 +08:00
item = QTableWidgetItem(str(calculated_time))
item.setFlags(item.flags() & ~Qt.ItemIsEditable)
self.table.setItem(row, 8, item)
2026-01-17 20:38:27 +08:00
self.table.blockSignals(False)
def show_context_menu(self, position):
"""显示右键菜单"""
menu = QMenu(self)
add_action = menu.addAction("添加行")
add_action.triggered.connect(self.add_row)
batch_add_action = menu.addAction("批量添加行")
batch_add_action.triggered.connect(self.batch_add_rows)
menu.addSeparator()
delete_action = menu.addAction("删除选中行")
delete_action.triggered.connect(self.delete_selected_rows)
menu.addSeparator()
copy_action = menu.addAction("复制行")
copy_action.triggered.connect(self.copy_selected_rows)
paste_action = menu.addAction("粘贴行")
paste_action.triggered.connect(self.paste_rows)
menu.exec_(self.table.viewport().mapToGlobal(position))
def add_row(self):
"""添加一行空数据"""
row_count = self.table.rowCount()
self.table.insertRow(row_count)
# 初始化空单元格
for col in range(9):
2026-01-18 06:11:21 +08:00
item = QTableWidgetItem("")
if col == 8:
item.setFlags(item.flags() & ~Qt.ItemIsEditable)
self.table.setItem(row_count, col, item)
2026-01-17 20:38:27 +08:00
# 更新数据
self.sync_table_to_raw_data()
self.process_data()
self.display_data()
# 启用按钮
self.enable_action_buttons()
def batch_add_rows(self):
"""批量添加行"""
count, ok = QInputDialog.getInt(self, "批量添加", "请输入要添加的行数:", 1, 1, 1000, 1)
if ok and count > 0:
row_count = self.table.rowCount()
self.table.setRowCount(row_count + count)
# 初始化新行
for i in range(count):
for col in range(9):
2026-01-18 06:11:21 +08:00
item = QTableWidgetItem("")
if col == 8:
item.setFlags(item.flags() & ~Qt.ItemIsEditable)
self.table.setItem(row_count + i, col, item)
2026-01-17 20:38:27 +08:00
# 更新数据
self.sync_table_to_raw_data()
self.process_data()
self.display_data()
# 启用按钮
self.enable_action_buttons()
QMessageBox.information(self, "成功", f"已添加 {count}")
def delete_selected_rows(self):
"""删除选中的行"""
selected_rows = set()
for item in self.table.selectedItems():
selected_rows.add(item.row())
if not selected_rows:
QMessageBox.warning(self, "警告", "请先选择要删除的行")
return
# 按行号降序排列,从后往前删除
rows_to_delete = sorted(selected_rows, reverse=True)
reply = QMessageBox.question(
self, "确认",
f"确定要删除 {len(rows_to_delete)} 行吗?",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
for row in rows_to_delete:
self.table.removeRow(row)
# 更新数据
self.sync_table_to_raw_data()
self.process_data()
self.display_data()
# 更新按钮状态
self.enable_action_buttons()
def copy_selected_rows(self):
"""复制选中的行"""
selected_rows = set()
for item in self.table.selectedItems():
selected_rows.add(item.row())
if not selected_rows:
QMessageBox.warning(self, "警告", "请先选择要复制的行")
return
# 将数据序列化为JSON字符串存储到剪贴板
rows_data = []
for row in sorted(selected_rows):
row_data = {}
for col in range(9):
item = self.table.item(row, col)
header = self.table.horizontalHeaderItem(col).text()
row_data[header] = item.text() if item else ""
rows_data.append(row_data)
clipboard = QApplication.clipboard()
clipboard.setText(json.dumps(rows_data, ensure_ascii=False, indent=2))
QMessageBox.information(self, "成功", f"已复制 {len(rows_data)} 行到剪贴板")
def paste_rows(self):
"""粘贴行"""
clipboard = QApplication.clipboard()
text = clipboard.text()
try:
rows_data = json.loads(text)
if not isinstance(rows_data, list):
QMessageBox.warning(self, "错误", "剪贴板中没有有效的行数据")
return
# 列名映射
column_mapping = {
2026-01-19 17:24:30 +08:00
"多多 id": 0, "序号": 1, "话题": 2,
"定时发布": 3, "间隔时间(分钟)": 4, "达人链接": 5,
"执行人": 6, "情况": 7, "计算后的发布时间": 8
2026-01-17 20:38:27 +08:00
}
# 在末尾插入新行
current_row = self.table.rowCount()
self.table.setRowCount(current_row + len(rows_data))
for i, row_data in enumerate(rows_data):
if isinstance(row_data, dict):
for key, value in row_data.items():
col = column_mapping.get(key)
if col is not None:
2026-01-18 06:11:21 +08:00
item = QTableWidgetItem(str(value))
if col == 8:
item.setFlags(item.flags() & ~Qt.ItemIsEditable)
self.table.setItem(current_row + i, col, item)
2026-01-17 20:38:27 +08:00
# 更新数据
self.sync_table_to_raw_data()
self.process_data()
self.display_data()
# 启用按钮
self.enable_action_buttons()
QMessageBox.information(self, "成功", f"已粘贴 {len(rows_data)}")
except json.JSONDecodeError:
QMessageBox.warning(self, "错误", "剪贴板中的数据格式不正确")
except Exception as e:
QMessageBox.critical(self, "错误", f"粘贴时出错:{str(e)}")
def save_config(self):
"""保存配置到JSON文件"""
if not self.raw_data:
QMessageBox.warning(self, "警告", "没有可保存的数据")
return
file_path, _ = QFileDialog.getSaveFileName(
self, "保存配置", "配置.json", "JSON Files (*.json)"
)
if not file_path:
return
try:
# 从表格同步数据
self.sync_table_to_raw_data()
# 保存到JSON
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(self.raw_data, f, ensure_ascii=False, indent=2)
QMessageBox.information(self, "成功", f"配置已保存到:\n{file_path}")
self.status_label.setText(f"配置已保存:{file_path}")
except Exception as e:
QMessageBox.critical(self, "错误", f"保存配置时出错:\n{str(e)}")
def load_config(self):
"""从JSON文件加载配置"""
file_path, _ = QFileDialog.getOpenFileName(
self, "加载配置", "", "JSON Files (*.json)"
)
if not file_path:
return
try:
# 读取JSON
with open(file_path, 'r', encoding='utf-8') as f:
self.raw_data = json.load(f)
# 处理数据
self.process_data()
# 显示数据
self.display_data()
# 启用按钮
self.enable_action_buttons()
QMessageBox.information(self, "成功", f"已加载 {len(self.raw_data)} 条配置")
self.status_label.setText(f"已加载配置:{file_path}")
except Exception as e:
QMessageBox.critical(self, "错误", f"加载配置时出错:\n{str(e)}")
def enable_action_buttons(self):
"""根据数据状态启用/禁用按钮"""
has_data = len(self.raw_data) > 0
self.execute_btn.setEnabled(has_data)
self.export_btn.setEnabled(has_data)
2026-01-18 06:11:21 +08:00
def set_busy(self, busy):
"""执行任务时禁用交互,防止卡死"""
self.import_action.setEnabled(not busy)
self.add_row_action.setEnabled(not busy)
self.batch_add_action.setEnabled(not busy)
self.delete_row_action.setEnabled(not busy)
self.save_action.setEnabled(not busy)
self.load_action.setEnabled(not busy)
self.export_template_action.setEnabled(not busy)
self.execute_btn.setEnabled(not busy and len(self.raw_data) > 0)
self.export_btn.setEnabled(not busy and len(self.raw_data) > 0)
self.table.setEnabled(not busy)
def on_worker_progress(self, current, total, message):
"""后台任务进度更新"""
if total > 0:
percent = int((current / total) * 100)
self.progress_bar.setValue(percent)
self.status_label.setText(message)
def on_worker_finished(self, success_count, fail_count, error_messages):
"""后台任务完成"""
result_msg = f"任务执行完成!\n成功: {success_count}\n失败: {fail_count}"
if error_messages:
result_msg += f"\n\n错误详情:\n" + "\n".join(error_messages[:5])
if len(error_messages) > 5:
result_msg += f"\n... 还有 {len(error_messages) - 5} 个错误(请查看日志)"
self.progress_bar.setValue(100)
self.status_label.setText(f"执行完成 - 成功: {success_count}, 失败: {fail_count}")
self.set_busy(False)
self.worker = None
QMessageBox.information(self, "执行结果", result_msg)
def on_worker_error(self, error_message):
"""后台任务异常"""
self.progress_bar.setValue(0)
self.status_label.setText("执行失败")
self.set_busy(False)
self.worker = None
QMessageBox.critical(self, "错误", f"执行任务时出错:\n{error_message}")
2026-01-17 20:38:27 +08:00
def execute_tasks(self):
"""执行任务调用main.py中的Pdd类"""
2026-01-18 06:11:21 +08:00
# 同步最新编辑内容
self.sync_table_to_raw_data()
self.process_data()
self.update_table_from_processed_data()
2026-01-17 20:38:27 +08:00
if not self.processed_data:
QMessageBox.warning(self, "警告", "没有可执行的数据")
return
2026-01-19 17:24:30 +08:00
# 检查大文件夹路径
base_folder_path = self.folder_path_input.text().strip()
if not base_folder_path:
# 如果用户没有填写,自动查找桌面上的"多多发文文件"文件夹
default_folder = self.find_default_folder()
if default_folder:
base_folder_path = default_folder
self.folder_path_input.setText(default_folder)
QMessageBox.information(
self, "提示",
f"已自动使用桌面上的默认文件夹:\n{default_folder}"
)
else:
reply = QMessageBox.question(
self, "提示",
"未设置大文件夹路径,且未找到桌面上的'多多发文文件'文件夹。\n将使用空路径执行任务。\n是否继续?",
QMessageBox.Yes | QMessageBox.No
)
if reply != QMessageBox.Yes:
return
# 验证路径是否存在
if base_folder_path and not os.path.exists(base_folder_path):
QMessageBox.warning(
self, "警告",
f"大文件夹路径不存在:\n{base_folder_path}\n\n请检查路径是否正确。"
)
return
2026-01-17 20:38:27 +08:00
reply = QMessageBox.question(
self, "确认",
2026-01-19 17:24:30 +08:00
f"确定要执行 {len(self.processed_data)} 个任务吗?\n大文件夹路径:{base_folder_path if base_folder_path else '未设置'}",
2026-01-17 20:38:27 +08:00
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
2026-01-18 06:11:21 +08:00
if self.worker is not None:
QMessageBox.warning(self, "提示", "任务正在执行中,请稍后")
return
self.progress_bar.setValue(0)
self.status_label.setText("开始执行任务...")
self.set_busy(True)
tasks = [dict(item) for item in self.processed_data]
2026-01-19 17:24:30 +08:00
self.worker = TaskWorker(tasks, base_folder_path=base_folder_path if base_folder_path else None)
2026-01-18 06:11:21 +08:00
self.worker.progress.connect(self.on_worker_progress)
self.worker.finished.connect(self.on_worker_finished)
self.worker.error.connect(self.on_worker_error)
self.worker.start()
2026-01-17 20:38:27 +08:00
def export_template(self):
"""导出Excel模板"""
file_path, _ = QFileDialog.getSaveFileName(
self, "保存Excel模板", "配置模板.xlsx", "Excel Files (*.xlsx)"
)
if not file_path:
return
try:
# 创建模板数据(空行)
template_data = {
2026-01-19 17:24:30 +08:00
'多多 id': ['示例1050100241'],
'序号': ['示例1'],
2026-01-17 20:38:27 +08:00
'话题(以中文"-"分隔)': ['示例python-自动化-技术'],
'定时发布': ['示例2026-01-28 09:30:00'],
'间隔时间': ['示例30单位分钟'],
'达人链接': ['示例https://www.xiaohongshu.com/explore/xxx'],
2026-01-19 17:24:30 +08:00
'执行人': ['示例:张三'],
2026-01-17 20:38:27 +08:00
'情况': ['备注信息']
}
# 创建DataFrame并导出
df = pd.DataFrame(template_data)
df.to_excel(file_path, index=False, engine='openpyxl')
QMessageBox.information(
self, "成功",
f"Excel模板已导出到\n{file_path}\n\n"
"提示:\n"
"1. 索引字段用于标识文件序号\n"
"2. 话题使用中文破折号\"\"或短横线\"\"分隔\n"
"3. 相同用户ID的第一条数据使用定时发布时间后续根据间隔时间自动计算\n"
2026-01-19 17:24:30 +08:00
"4. 间隔时间单位为分钟\n"
"5. 大文件夹路径请在GUI程序顶部的输入框中设置"
2026-01-17 20:38:27 +08:00
)
self.status_label.setText(f"Excel模板已导出到{file_path}")
except Exception as e:
QMessageBox.critical(self, "错误", f"导出Excel模板时出错\n{str(e)}")
import traceback
traceback.print_exc()
def export_excel(self):
"""导出处理后的数据到Excel"""
if not self.processed_data:
QMessageBox.warning(self, "警告", "没有可导出的数据")
return
file_path, _ = QFileDialog.getSaveFileName(
self, "保存Excel文件", "", "Excel Files (*.xlsx)"
)
if not file_path:
return
try:
# 准备导出数据
export_data = []
for data in self.processed_data:
export_data.append({
2026-01-19 17:24:30 +08:00
'多多 id': data.get('多多 id', ''),
'序号': data.get('序号', ''),
2026-01-17 20:38:27 +08:00
'话题(以中文"-"分隔)': data.get('话题', ''),
'定时发布': data.get('定时发布', ''),
'间隔时间': data.get('间隔时间', ''),
'达人链接': data.get('达人链接', ''),
2026-01-19 17:24:30 +08:00
'执行人': data.get('执行人', ''),
2026-01-17 20:38:27 +08:00
'情况': data.get('情况', ''),
'计算后的发布时间': data.get('计算后的发布时间', ''),
})
# 创建DataFrame并导出
df = pd.DataFrame(export_data)
df.to_excel(file_path, index=False, engine='openpyxl')
QMessageBox.information(self, "成功", f"数据已导出到:\n{file_path}")
self.status_label.setText(f"数据已导出到:{file_path}")
except Exception as e:
QMessageBox.critical(self, "错误", f"导出Excel文件时出错\n{str(e)}")
import traceback
traceback.print_exc()
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()