当点击开始按钮后,程序会为表格每一行数据创建一个线程对象,在把数据实时显示到界面并且保证不卡顿。这就需要多线程模式。在界面和线程之间建立以个单独处理的类,然后做为单例模式给界面来调用。文字不好表示,直接上图
import os
import json
import sys
from PyQt5.Qt import *
from utlis.threads import NewTaskThread
from utlis.scheduler import SCHEDULER
from utlis.dialog import AlertDialog,ProxyDialog,LogDialog
BASE_FIR=os.path.dirname(os.path.realpath(sys.argv[0]))#显示到本项目文件根目录
## C:\Users\Administrator\Desktop\amazon
#相对文件路径要从一个文件开始查找,运行。不能其他文件也用,不然会出问题
STATUS_MAPPING={
0:"初始化中",
1:"待执行",
2:"正在执行",
3:"完成并提醒",
10:"异常并停止",
11:"初始化失败",
}
class Window(QWidget):
def __init__(self):
super().__init__()
self.resize(1000,600)
self.setWindowIcon(QIcon('{}//sources//miss.png'.format(BASE_FIR)))
self.setup_ui()
self.init_table()#初始化表格
def setup_ui(self):
btn_start=QPushButton("开始")
btn_stop=QPushButton("停止")
btn_add=QPushButton("添加")
btn_reset=QPushButton("重新初始化")
btn_recheck=QPushButton("重新检测")
btn_delete=QPushButton("删除")
btn_send_stm=QPushButton("STM")
btn_ip=QPushButton("代理ip")
self.le_edit=QLineEdit()
self.le_edit.setText("EF806=1988")
self.le_edit.setPlaceholderText("请输入商品信息,例如:FR001=987")
self.table_widget=QTableWidget(0,8)
self.label=QLabel("未检测")
QMetaObject.connectSlotsByName(self)
#绑定信号与槽函数
btn_start.clicked.connect(self.event_btn_start_click)
btn_stop.clicked.connect(self.event_btn_stop_click)
btn_add.clicked.connect(self.event_btn_add_click)
btn_reset.clicked.connect(self.event_btn_reset_click)
btn_recheck.clicked.connect(self.event_btn_recheck_click)
btn_delete.clicked.connect(self.event_btn_delete_click)
btn_send_stm.clicked.connect(self.event_btn_send_stm_clilck)
btn_ip.clicked.connect(self.event_btn_ip_click)
#总体布局
layout=QVBoxLayout()
# 子布局
header_layout=QHBoxLayout()
header_layout.addWidget(btn_start)
header_layout.addWidget(btn_stop)
header_layout.addStretch()
form_layout=QHBoxLayout()
form_layout.addWidget(self.le_edit)
form_layout.addWidget(btn_add)
table_layout=QHBoxLayout()
table_layout.addWidget(self.table_widget)
foot_layout=QHBoxLayout()
foot_layout.addWidget(self.label)
foot_layout.addStretch()
for m in [btn_reset,btn_recheck,btn_delete,btn_send_stm,btn_ip]:
foot_layout.addWidget(m)
#子布局添加到总布局
for i in [header_layout,form_layout,table_layout,foot_layout]:
layout.addLayout(i)
self.setLayout(layout)
def init_table(self):
self.show_table_header_info()
file_path=os.path.join(BASE_FIR,'db','db.json')
if not os.path.exists(file_path):
QMessageBox.warning(self,"错误","db.json文件不存在")
return
with open(file_path,mode='r',encoding='utf-8')as f:
data=f.read()
data_list=json.loads(data)
self.show_table_info(data_list)
def show_table_header_info(self):
table_header = [
{"filed": "asin", 'text': 'TEXT', "width": 100},
{"filed": "title", "text": "标题", "width": 100},
{"filed": "url", "text": "URL", "width": 200},
{"filed": "price", "text": "底价", "width": 100},
{"filed": "sucess", "text": "成功次数", "width": 100},
{"filed": "error", "text": "错误次数", "width": 100},
{"filed": "states", "text": "状态", "width": 100},
{"filed": "frequeny", "text": "频率次数(次/秒)", "width": 100}
]
for i, info in enumerate(table_header):
item = QTableWidgetItem()
item.setText(info.get("text"))#这里是item.setText 后面是item = QTableWidgetItem("abc")
self.table_widget.setHorizontalHeaderItem(i, item)
self.table_widget.setColumnWidth(i, info.get("width"))
###########################################################################################
def show_table_info(self, obj_list):
"显示表格信息"
for data_obj in obj_list:
current_rows = self.table_widget.rowCount()
self.table_widget.insertRow(current_rows)
for i, val in enumerate(data_obj):
val = STATUS_MAPPING[val] if i == 6 else val
item = QTableWidgetItem(str(val))
if i in [0, 4, 5, 6]:
item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) # 单元格不可被编辑状态
self.table_widget.setItem(current_rows, i, item)
self.table_widget.setContextMenuPolicy(Qt.CustomContextMenu)
self.table_widget.customContextMenuRequested.connect(self.__right_menu)
def event_btn_start_click(self):
#TODO
SCHEDULER.start(
self,
self.task_start_callback,
self.task_count_callback,
BASE_FIR
)
def event_btn_stop_click(self):
print('stop')
pass
def event_btn_add_click(self):
text=self.le_edit.text().strip()
if not text:
QMessageBox.warning(self,"提示","商品信息不能为空")
return
asin,price=text.split("=")
price=float(price)
new_list=[asin,"","",price,0,0,2,0]
current_row=self.table_widget.rowCount()
self.table_widget.insertRow(current_row)
for i,elv in enumerate(new_list):
elv=STATUS_MAPPING[elv] if i==6 else elv
item=QTableWidgetItem(str(elv))
if i in [0, 4, 5, 6]:
item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) # 单元格不可被编辑状态
self.table_widget.setItem(current_row,i,item)
thread=NewTaskThread(current_row,self)
thread.sucess_singal.connect(self.thread_sucess_singal_callback)
thread.start()
def event_btn_reset_click(self):
row_list=self.table_widget.selectionModel().selectedRows()
if not row_list:
QMessageBox.warning(self,"错误","未选中行")
return
for row_table in row_list:
index=row_table.row()
self.table_widget.setItem(index,6,QTableWidgetItem(STATUS_MAPPING[0]))
thread=NewTaskThread(index,self)
thread.sucess_singal.connect(self.thread_sucess_singal_callback)
thread.start()
def event_btn_recheck_click(self):
row_list=self.table_widget.selectionModel().selectedRows()
if not row_list:
QMessageBox.warning(self,"提示","没有选中行")
return
for row_obj in row_list:
index=row_obj.row()
self.table_widget.setItem(index,6, QTableWidgetItem(STATUS_MAPPING[1]))
def event_btn_delete_click(self):
row_list=self.table_widget.selectionModel().selectedRows()
if not row_list:
QMessageBox.warning(self,"提醒","没有选中行")
return
row_list.reverse()#反转下
for row_obj in row_list:
index=row_obj.row()
self.table_widget.removeRow(index)
def event_btn_send_stm_clilck(self):
dialog=AlertDialog(self)
dialog.exec_()
def event_btn_ip_click(self):
print("ip")
dialog=ProxyDialog(self)
dialog.exec_()
def __right_menu(self,point):
menu=QMenu(self)
item_copy=menu.addAction(QIcon(os.path.join('sources','miss.png')),"复制")
item_log=menu.addAction(QIcon(os.path.join('sources','stock.png')),"查看日志")
item_log_clear=menu.addAction(QIcon(os.path.join('sources','roll.png')),"清除日志")
desk_point=self.mapToGlobal(QPoint(point.x(),point.y()+100))
action=menu.exec_(desk_point)
# print(action,item_copy)
#<PyQt5.QtWidgets.QAction object at 0x00000000037EA948> <PyQt5.QtWidgets.QAction object at 0x00000000037EA948>
selected_item_list=self.table_widget.selectedItems()#获取选中的QTiableWidgetItem对象,是个列表
if len(selected_item_list)==0:
return
if action==item_copy:
clipboard=QApplication.clipboard()
clipboard.setText(selected_item_list[0].text())
if action==item_log:
#获取型号
row_index=selected_item_list[0].row()
asin=self.table_widget.item(row_index,0).text().strip()
log_dialog = LogDialog(asin,self)
log_dialog.exec_()
if action==item_log_clear:
row_index=selected_item_list[0].row()
asin=self.table_widget.item(row_index,0).text().strip()
file_path=os.path.join("log","{}.log".format(asin))
if os.path.exists(file_path):
os.remove(file_path)
def thread_sucess_singal_callback(self,index,asin,text,url):
print(index,asin,text,url)
asin=QTableWidgetItem(asin)
text=QTableWidgetItem(text)
url=QTableWidgetItem(url)
self.table_widget.setItem(index,0,asin)
self.table_widget.setItem(index,1,text)
self.table_widget.setItem(index,2,url)
def task_start_callback(self,row_index):
'''对表格数据更新'''
item=QTableWidgetItem(STATUS_MAPPING[2])
item.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
self.table_widget.setItem(row_index,6,item)
def task_count_callback(self,row_index):
#原有个数加1
old_count=self.table_widget.item(row_index, 4).text().strip()
new_count=int(old_count)+1
# 重新表格赋值
cell=QTableWidgetItem(str(new_count))
cell.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
self.table_widget.setItem(row_index,4,cell)
if __name__=="__main__":
try:
app=QApplication(sys.argv)
win=Window()
win.show()
sys.exit(app.exec_())
except Exception as e:
print("e",e)
os.system("pause")
from PyQt5.QtCore import QThread,pyqtSignal
class NewTaskThread(QThread):
sucess_singal=pyqtSignal(int,str,str,str)
error_singal=pyqtSignal(int)
def __init__(self,row_index,parent=None,*args,**kwargs):
super().__init__(parent,*args,**kwargs)
self.row_index=row_index
def run(self):
import time
while True:
time.sleep(1)
self.sucess_singal.emit(self.row_index,"FE","888","http://162.com")
class TaskThread(QThread):
start_singal=pyqtSignal(int)
count_singal=pyqtSignal(int)
def __init__(self,row_index,asin,log_file_path,parent=None,*args,**kwargs):
super().__init__(parent,*args,**kwargs)
self.row_index=row_index
self.asin=asin
self.log_file_path=log_file_path
def run(self):
self.start_singal.emit(self.row_index)
import time
import random
while True:
time.sleep(random.randint(1,3))
self.count_singal.emit(self.row_index)
with open(self.log_file_path,mode='a',encoding='utf-8')as f:
f.write("日志\n")
from utlis.threads import TaskThread
import os
class Scheduler(object):
def __init__(self):
self.thread_list=[]
self.window=None
self.flag=False
def start(self,window,fn_start,fn_count,base_dir):
self.window=window
self.flag=False
#获取表格索引,为每一行创建一个线程
for row_index in range(self.window.table_widget.rowCount()):
asin=window.table_widget.item(row_index,0).text().strip()
states=window.table_widget.item(row_index,6).text().strip()
#日志文件
log_folder=os.path.join(base_dir,'log')
if not os.path.exists(log_folder):
os.makedirs(log_folder)
log_file_path=os.path.join(log_folder,"{}.log".format(asin))
#没个线程 执行&状态实时的显示在表格中 信号+回调
t=TaskThread(row_index,asin,log_file_path,window)
t.start_singal.connect(fn_start)
t.count_singal.connect(fn_count)
t.start()
SCHEDULER=Scheduler()
from PyQt5.Qt import *
import os
import json
class AlertDialog(QDialog):
def __init__(self,parent=None,*args,**kwargs):
super().__init__(parent,*args,**kwargs)
self.resize(300,200)
self.filed_dict={}
icon_path=os.path.join("sources",'stock.png')
self.setWindowIcon(QIcon(icon_path))
self.setup_ui()
def setup_ui(self):
form_data_list=[
{"title":"STM服务器","filed":"smtp"},
{"title":"发件箱","filed":"from"},
{"title":"密码","filed":"pwd"},
{"title":"收件人(多人用逗号隔开)","filed":"to"},
]
layout=QVBoxLayout()
self.setLayout(layout)
old_dic=self.read_alert_info()#读取的信息是个字典
for X in form_data_list:
label=QLabel(X["title"])
layout.addWidget(label)
txt=QLineEdit()
filed=X['filed']
txt.setText(old_dic[filed])
self.filed_dict[X['filed']]=txt #把QLineWidget加入字典
layout.addWidget(txt)
btn=QPushButton("保存")
btn.clicked.connect(self.event_btn_click)
btn.setFixedSize(50,20)
layout.addWidget(btn,0,Qt.AlignLeft)
def read_alert_info(self):
# 读取文件显示到窗口
old_data_dic = {}
file_path = os.path.join("db", "alert.txt")
if os.path.exists(file_path):
with open(file_path, 'r', encoding='utf-8')as f:
old_data_dic = json.loads(f.read())
return old_data_dic
def event_btn_click(self):
data_dict={}
for key ,line_edit_obj in self.filed_dict.items():
value=line_edit_obj.text().strip()
if not value:
QMessageBox.warning(self,"提示","邮件不能为空")
return
data_dict[key]=value
file_object=open(os.path.join("db",'alert.txt'),mode='w',encoding='utf-8')
json.dump(data_dict,file_object)
file_object.close()
class ProxyDialog(QDialog):
def __init__(self,parent=None,*args,**kwargs):
super().__init__(parent,*args,**kwargs)
self.setWindowTitle("IP代理")
self.resize(400,300)
self.setWindowIcon(QIcon(os.path.join('sources',"roll.png")))
self.__setup_ui()
def __setup_ui(self):
layout=QVBoxLayout()
self.setLayout(layout)
self.tex_eidt=QTextEdit()
btn_reset=QPushButton("重置")
btn_reset.setFixedSize(50,30)
#连接信号
btn_reset.clicked.connect(self.__btn_reset_write_info)
#初始化信息
self.__show_init_info()
#布局
layout.addWidget(self.tex_eidt)
layout.addWidget(btn_reset,0,Qt.AlignRight)
def __show_init_info(self):
# 初始化
file_path = os.path.join('db', 'proxy.txt')
data_info = ""
if os.path.exists(file_path):
with open(file_path, 'r', encoding='utf-8')as f:
data_info = f.read()
self.tex_eidt.setText(data_info)
def __btn_reset_write_info(self):
file_path=os.path.join("db","proxy.txt")
text=self.tex_eidt.toPlainText().strip()
if not text:
return
with open(file_path,mode='w',encoding='utf-8')as f:
f.write(text)
class LogDialog(QDialog):
def __init__(self,asin,parent=None,*args,**kwargs):
super().__init__(parent,*args,**kwargs)
self.resize(400,370)
self.setWindowIcon(QIcon("sources.miss.png"))
self.asin=asin
self.setup_ui()
def setup_ui(self):
layout=QVBoxLayout()
self.setLayout(layout)
self.tex_le=QTextEdit()
btn=QPushButton("点击")
btn.setFixedSize(50,30)
layout.addWidget(self.tex_le)
layout.addWidget(btn,0,Qt.AlignRight)
#读取文件出来
file_path=os.path.join("log","{}.log".format(self.asin))
if not os.path.exists(file_path):
return
with open(file_path,'r',encoding='utf-8')as f:
data=f.read()
self.tex_le.setText(data)