『pyqt5 从0基础开始项目实战』10.日志记录 鼠标右键打开(保姆级图文)

news2025/1/12 6:10:29

目录

    • 导包和框架代码
    • 实现右键功能
    • 实现日志展示弹窗
    • 编写一个日志文件用于测试日志展示
    • 完整代码
      • main.py
      • threads.py
      • dialog.py
    • 总结


欢迎关注 『pyqt5 从0基础开始项目实战』 专栏,持续更新中
欢迎关注 『pyqt5 从0基础开始项目实战』 专栏,持续更新中

导包和框架代码

请查阅上文获取源码,此处只列举第三方库

import os
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QDesktopWidget, QHBoxLayout, QVBoxLayout
from PyQt5.QtWidgets import QPushButton, QLineEdit, QTableWidget, QTableWidgetItem, QLabel
from PyQt5.QtWidgets import QMessageBox, QMenu

## 开启右键选中表格行的功能 复制,查看日志,清除日志
    def init_table(self):
        # # 开启右键复制功能,在表格中点击右键时,自动触发 right_menu 函数
        table_widget.setContextMenuPolicy(Qt.CustomContextMenu)#设置启用右键菜单
        table_widget.customContextMenuRequested.connect(self.table_right_menu)#右键菜单的实现函数

        table_layout.addWidget(table_widget)  # 把表格添加到表格布局中
        return table_layout

在这里插入图片描述


实现右键功能

action = menu.exec_(self.table_widget.mapToGlobal(pos))# 获取选中的选项,并进行相应的操作

    #右键菜单功能
    def table_right_menu(self, pos):

        # 只有选中一行时,才支持右键 selected_item_list可以是一个多行的列表
        # 我们只处理选中的第一行,所以要 selected_item_list[0] 表示选中的多行中的第一行
        selected_item_list = self.table_widget.selectedItems()
        if len(selected_item_list) == 0:
            return

        menu = QMenu()
        item_copy = menu.addAction("复制")
        item_log = menu.addAction("查看日志")
        item_log_clear = menu.addAction("清除日志")

        action = menu.exec_(self.table_widget.mapToGlobal(pos))# 获取选中的对象

        if action == item_copy:
            # 赋值当前型号 B08166SLDF
            clipboard = QApplication.clipboard()#新建剪切板对象
            clipboard.setText(selected_item_list[0].text())#剪切板复制得到ASIN

        if action == item_log:
            # 查看日志,在对话框中显示日志信息
            # 获取选中的型号
            row_index = selected_item_list[0].row()#得到选中第一行的行号
            asin = self.table_widget.item(row_index, 0).text().strip()#得到选中行的商品ASIN

            from utils.dialog import LogDialog
            dialog = LogDialog(asin)

            #打开我们写好的日志窗口,针对每个商品创建的日志的保存和修改也在这个窗口中实现
            dialog.setWindowModality(Qt.ApplicationModal)

            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)#删除日志文件

实现日志展示弹窗



class LogDialog(QDialog):
    def __init__(self, asin, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.asin = asin

        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("日志记录")
        self.resize(500, 400)
        layout = QVBoxLayout()
        text_edit = QTextEdit()
        text_edit.setText("")

        layout.addWidget(text_edit)
        self.setLayout(layout)

        # 读取日志展示出来
        file_path = os.path.join("log", "{}.log".format(self.asin))
        if not os.path.exists(file_path):#如果日志不存在,直接返回
            return

        #读取日志
        with open(file_path,mode='r',encoding='utf-8') as f:
            content = f.read()
        #展示日志
        text_edit.setText(content)


编写一个日志文件用于测试日志展示

日志的名字是商品的ASNI.log
内容大家随便填写
在这里插入图片描述
日志展示的最终效果
在这里插入图片描述


完整代码

main.py

import os
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QDesktopWidget, QHBoxLayout, QVBoxLayout
from PyQt5.QtWidgets import QPushButton, QLineEdit, QTableWidget, QTableWidgetItem, QLabel
from PyQt5.QtWidgets import QMessageBox, QMenu
# from utils.dialog import LogDialog

#拿到在另外一台计算机上执行程序代码所存储的文件路径。
BASE_DIR = os.path.dirname(os.path.realpath(sys.argv[0]))

STATUS_MAPPING = {
    0: "初始化中",
    1: "待执行",
    2: "正在执行",
    3: "完成并提醒",
    10: "异常并停止",
    11: "初始化失败",
}



class MainWindow(QWidget):
    def __init__(self):
        # 用super 继承父类的初始化
        super().__init__()

        self.txt_asin=None

        # 设置窗口的窗体标题
        self.setWindowTitle('发现你走远了的xx系统')

        # 设置窗体的尺寸
        self.resize(1228, 450)

        # 设置窗体位置
        # 获取整个窗口部分的宽高和左上角坐标信息,返回值是一个QRect类型,(x,y width,height)
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()  # 得到屏幕中间的位置信息
        qr.moveCenter(cp)  # 让我们的窗体移动到屏幕中间

        # 创建窗口总布局
        layout = QVBoxLayout()

        # 讲调用方法生成的顶部菜单布局添加到总布局
        layout.addLayout(self.init_header())
        layout.addLayout(self.init_form())
        layout.addLayout(self.init_table())
        layout.addLayout(self.init_footer())

        # 给窗体设置元素的排列方式
        self.setLayout(layout)

    def init_header(self):
        # 1.顶部菜单布局
        header_layout = QHBoxLayout()  # 创建顶部菜单布局
        # 1.1 放入按钮
        btn_start = QPushButton("开始")  # 新建一个开始按钮
        header_layout.addWidget(btn_start)  # 将开始按钮添加到顶部菜单布局
        btn_stop = QPushButton("停止")  # 新建一个开始按钮
        header_layout.addWidget(btn_stop)  # 将开始按钮添加到顶部菜单布局
        # 1.2 加入弹簧
        header_layout.addStretch()

        return header_layout

    def init_form(self):
        # 2.添加内容布局
        form_layout = QHBoxLayout()  # 创建添加内容布局

        # 2.1 输入框
        txt_asin = QLineEdit()  # 新建一个输入框对象
        txt_asin.setText("B07YN82X3B=100")  # 设置默认的form数据
        txt_asin.setPlaceholderText("请输入商品ID和价格,例如:B0818JJQQ8=88")  # 设置灰色的提示信息
        self.txt_asin=txt_asin
        form_layout.addWidget(txt_asin)  # 将输入框加入到布局中

        # 2.2 添加按钮
        btn_add = QPushButton("添加")  # 新建一个添加按钮
        btn_add.clicked.connect(self.event_add_click)#新增一个点击事件
        form_layout.addWidget(btn_add)  # 将添加按钮添加到form布局

        return form_layout

    def init_table(self):
        # 3.表格数据展示布局
        table_layout = QHBoxLayout()
        # 3.1 创建表格
        self.table_widget=table_widget = QTableWidget(0, 8)  # 新建一个0行8列的表格
        # # 修改表格索引名
        # item=QTableWidgetItem()
        # item.setText("标题0")
        # table_widget.setHorizontalHeaderItem(0,item)
        # table_widget.setColumnWidth(0,150)#设置水平单元格0号位置的宽度 150
        #
        # item2=QTableWidgetItem()
        # item2.setText("网址1")
        # table_widget.setHorizontalHeaderItem(1,item2)
        # table_widget.setColumnWidth(1,400)#设置水平单元格1号位置的宽度 400
        #
        # item3=QTableWidgetItem()
        # item3.setText("行索引0")
        # table_widget.setVerticalHeaderItem(0,item3)
        table_header = [
            {"field": "asin", "text": "ASIN", 'width': 120},
            {"field": "title", "text": "标题", 'width': 150},
            {"field": "url", "text": "URL", 'width': 400},
            {"field": "price", "text": "底价", 'width': 100},
            {"field": "success", "text": "成功次数", 'width': 100},
            {"field": "error", "text": "503次数", 'width': 100},
            {"field": "status", "text": "状态", 'width': 100},
            {"field": "frequency", "text": "频率(N秒/次)", 'width': 100},
        ]
        for idx, info in enumerate(table_header):
            item = QTableWidgetItem()
            item.setText(info['text'])
            table_widget.setHorizontalHeaderItem(idx, item)
            table_widget.setColumnWidth(idx, info['width'])

        # 3.2 初始化表格数据
        # 读取数据文件
        import json
        file_path = os.path.join(BASE_DIR, "db", "db.json")
        with open(file_path, mode='r', encoding='utf-8') as f:
            data = f.read()
        data_list = json.loads(data)#读取得到了json数据


        current_row_count = table_widget.rowCount()  # 当前表格有多少行
        for row_list in data_list:#每有一行json数据,我们就需要遍历一轮增加一行数据
            table_widget.insertRow(current_row_count)#增加一行
            # print(row_list)  # ['B08166SLDF', 'AMD er', 'https://www.amazon.', 300.0, 0, 166, 1, 5]
            # 把row_list写入这一行
            for i, ele in enumerate(row_list):#enumerate中 i表示索引id,ele表示数据值
                # 一个多目运算 如果i==6(此时是状态的数据) 如果STATUS_MAPPING中能够找到ele的索引,那么我们把ele设置成STATUS_MAPPING中的内容
                ele = STATUS_MAPPING[ele] if i == 6 else ele

                cell = QTableWidgetItem(str(ele))#注意我们的数据格式转为str,比如说状态的数据可能本身是int类型
                if i in [0, 4, 5, 6]:
                    # 不可修改
                    cell.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)#指定索引单元格不能选中和修改
                table_widget.setItem(current_row_count, i, cell)#写入数据到指定单元格

            current_row_count += 1 #行数+1,这样下次遍历行时会在新的行下面新增一行
            # 如果不新增行其实也可以用  current_row_count=table_widget.rowCount()替代


        # # 开启右键复制功能,在表格中点击右键时,自动触发 right_menu 函数
        table_widget.setContextMenuPolicy(Qt.CustomContextMenu)#设置启用右键菜单
        table_widget.customContextMenuRequested.connect(self.table_right_menu)#右键菜单的实现函数

        table_layout.addWidget(table_widget)  # 把表格添加到表格布局中
        return table_layout

    #右键菜单功能
    def table_right_menu(self, pos):

        # 只有选中一行时,才支持右键 selected_item_list可以是一个多行的列表
        # 我们只处理选中的第一行,所以要 selected_item_list[0] 表示选中的多行中的第一行
        selected_item_list = self.table_widget.selectedItems()
        if len(selected_item_list) == 0:
            return

        menu = QMenu()
        item_copy = menu.addAction("复制")
        item_log = menu.addAction("查看日志")
        item_log_clear = menu.addAction("清除日志")

        action = menu.exec_(self.table_widget.mapToGlobal(pos))# 获取选中的对象

        if action == item_copy:
            # 赋值当前型号 B08166SLDF
            clipboard = QApplication.clipboard()#新建剪切板对象
            clipboard.setText(selected_item_list[0].text())#剪切板复制得到ASIN

        if action == item_log:
            # 查看日志,在对话框中显示日志信息
            # 获取选中的型号
            row_index = selected_item_list[0].row()#得到选中第一行的行号
            asin = self.table_widget.item(row_index, 0).text().strip()#得到选中行的商品ASIN

            from utils.dialog import LogDialog
            dialog = LogDialog(asin)

            #打开我们写好的日志窗口,针对每个商品创建的日志的保存和修改也在这个窗口中实现
            dialog.setWindowModality(Qt.ApplicationModal)

            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 init_footer(self):
        # 4.底部菜单
        footer_layout = QHBoxLayout()

        label_status = QLabel("未检测", self)
        footer_layout.addWidget(label_status)

        footer_layout.addStretch()  # 添加弹簧,更加美观

        btn_reset = QPushButton("重新初始化")
        btn_reset.clicked.connect(self.event_reset_click)#新增一个点击事件
        footer_layout.addWidget(btn_reset)

        btn_recheck = QPushButton("重新检测")
        footer_layout.addWidget(btn_recheck)

        btn_reset_count = QPushButton("次数清零")
        btn_reset_count.clicked.connect(self.event_reset_count_click)
        footer_layout.addWidget(btn_reset_count)

        btn_delete = QPushButton("删除检测项")
        btn_delete.clicked.connect(self.event_delete_click)
        footer_layout.addWidget(btn_delete)

        btn_alert = QPushButton("SMTP报警配置")
        btn_alert.clicked.connect(self.event_alert_click)
        footer_layout.addWidget(btn_alert)

        btn_proxy = QPushButton("代理IP")
        btn_proxy.clicked.connect(self.event_proxy_click)
        footer_layout.addWidget(btn_proxy)

        return footer_layout

    # 添加数据按钮事件
    def event_add_click(self):
        # 1.获取输入框中的内容
        text = self.txt_asin.text()
        text = text.strip()#去掉空格
        if not text:#如果输入的是空格空字符
            QMessageBox.warning(self, "错误", "商品的ASIN输入错误")
            return
        # B07YN82X3B=100
        asin, price = text.split("=")#把数据分为id 和 价格
        price = float(price)#价格转为浮点型
        print(asin, price)

        # 2.加入到表格中(型号、底价)
        new_row_list = [asin, "", "", price, 0, 0, 0, 5]

        # 写入表格,具体操作和之前初始化表格一样
        current_row_count = self.table_widget.rowCount() # 当前表格有多少行
        self.table_widget.insertRow(current_row_count)
        for i, ele in enumerate(new_row_list):
            ele = STATUS_MAPPING[ele] if i == 6 else ele
            cell = QTableWidgetItem(str(ele))
            if i in [0, 4, 5, 6]:
                # 不可修改
                cell.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
            self.table_widget.setItem(current_row_count, i, cell)

        # 3.发送请求自动获取标题
        # 注意:不能再主线程中做数据获取的事,创建一个线程去做数据获取,爬取到数据再更新到窗体应用(信号)。
        from utils.threads import NewTaskThread #导入我们写的线程py文件中的类

        thread = NewTaskThread(current_row_count, asin, self)#初始化线程中类的对象,传入了 表格总共的行数{current_row_count} 和 当前在表格的第几行{asin} 以及自身这个对象 {self}
        thread.success.connect(self.init_task_success_callback)#添加数据成功事件成功的回调,马上开始初始化数据
        thread.error.connect(self.init_task_error_callback)#事件失败的回调,弹框提示错误
        thread.start()#线程开始运行

        pass

    # 添加数据成功事件成功的回调,得到数据后马上开始初始化数据
    def init_task_success_callback(self, row_index, asin, title, url):
        print("成功",row_index, asin, title, url)
        # 更新标题
        cell_title = QTableWidgetItem(title)
        self.table_widget.setItem(row_index, 1, cell_title)

        # 更新URL
        cell_url = QTableWidgetItem(url)
        self.table_widget.setItem(row_index, 2, cell_url)

        # 更新状态 成功后未待执行
        cell_status = QTableWidgetItem(STATUS_MAPPING[1])
        cell_status.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
        self.table_widget.setItem(row_index, 6, cell_status)

        # 输入框清空
        self.txt_asin.clear()

    # 添加数据失败事件成功的回调,得到数据后马上开始初始化数据
    def init_task_error_callback(self, row_index, asin, title, url):
        print("错误",row_index, asin, title, url)
        # 更新标题
        cell_title = QTableWidgetItem(title)
        self.table_widget.setItem(row_index, 1, cell_title)

        # 更新URL
        cell_url = QTableWidgetItem(url)
        self.table_widget.setItem(row_index, 2, cell_url)

        # 更新状态为 初始化失败
        cell_status = QTableWidgetItem(STATUS_MAPPING[11])
        cell_status.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
        self.table_widget.setItem(row_index, 6, cell_status)

    def event_reset_click(self):
        # 1.获取已经选中的行
        row_list = self.table_widget.selectionModel().selectedRows()
        if not row_list:#如果没有选中行,提示错误
            QMessageBox.warning(self, "错误", "请选择要重新初始化的行")
            return
        # 2.获取每一行进行重新初始化
        for row_object in row_list:
            index = row_object.row()
            print("选中的行:", index)
            # 获取信号
            asin = self.table_widget.item(index, 0).text().strip()#得到了商品id

            # 状态重新初始化
            cell_status = QTableWidgetItem(STATUS_MAPPING[0])#把状态设置成初始化中
            print("选中的行:", index,"---",STATUS_MAPPING[0]) # 选中的行: 1 --- 初始化中
            cell_status.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
            self.table_widget.setItem(index, 6, cell_status)

            # 创建线程去进行初始化动作
            from utils.threads import NewTaskThread
            thread = NewTaskThread(index, asin, self)#传入 选中的行/商品id/窗体对象
            thread.success.connect(self.init_task_success_callback)
            thread.error.connect(self.init_task_error_callback)
            thread.start()

    # 点击数量清零
    def event_reset_count_click(self):
        # 1.获取已经选中的行
        row_list = self.table_widget.selectionModel().selectedRows()
        if not row_list:
            QMessageBox.warning(self, "错误", "请选择要操作的行")
            return
        # 2.获取每一行进行重新初始化
        for row_object in row_list:
            index = row_object.row()
            # print("选中的行:", index)
            # # 获取信号
            # asin = self.table_widget.item(index, 0).text().strip()

            # 状态重新初始化
            cell_status = QTableWidgetItem(str(0))#得到一个字符串格式的 0
            cell_status.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
            self.table_widget.setItem(index, 4, cell_status)#设置成功次数清零

            cell_status = QTableWidgetItem(str(0))#得到一个字符串格式的 0
            cell_status.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
            self.table_widget.setItem(index, 5, cell_status)#设置503次数清零

    # 点击删除
    def event_delete_click(self):
        # 1.获取已经选中的行
        row_list = self.table_widget.selectionModel().selectedRows()
        if not row_list:
            QMessageBox.warning(self, "错误", "请选择要操作的行")
            return

        # 2.翻转,先删除掉行号大的数据,然后在删除行号小的数据,避免删除行号小的数据变动行号大的数据。
        row_list.reverse()
        print (row_list)

        # 3.删除
        for row_object in row_list:
            index = row_object.row()
            print("当前删除的行是:",index)
            self.table_widget.removeRow(index)

    # 点击邮件配置
    def event_alert_click(self):
        # 创建弹窗并在弹窗中进行设置

        from utils.dialog import AlertDialog

        dialog = AlertDialog()
        dialog.setWindowModality(Qt.ApplicationModal)
        dialog.exec_()

    # 点击代理
    def event_proxy_click(self):
        from utils.dialog import ProxyDialog

        dialog = ProxyDialog()
        dialog.setWindowModality(Qt.ApplicationModal)
        dialog.exec_()



if __name__ == '__main__':
    app = QApplication(sys.argv)  # 实例化一个Application应用,所有的窗口均在其下运行

    window = MainWindow()  # 实例化窗口对象
    window.show()  # 窗口展示

    sys.exit(app.exec_())
    # app.exec_()运行主循环,并在退出时返回状态代码。
    # sys.exit(n)退出您的应用程序并返回n到父进程(通常是您的shell)

threads.py

from PyQt5.QtCore import QThread, pyqtSignal


class NewTaskThread(QThread):
    # 信号,触发信号,更新窗体中的数据
    success = pyqtSignal(int, str, str, str)  # 成功后向ui对象发送数据的声明
    error = pyqtSignal(int, str, str, str)  # 失败后向ui对象发送数据的声明

    def __init__(self, row_index, asin, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.row_index = row_index  # 行数
        self.asin = asin  # 商品id

    def run(self):
        """ 具体线程应该做的事"""
        try:
            title = "good_title_{}".format(self.asin)  # good_title_B07YN82X3B
            url = "https://blog.csdn.net/u011027547/{}".format(self.asin)

            # 获取到title和url,将这个信息填写到 表格上 & 写入文件中。
            self.success.emit(self.row_index, self.asin, title, url)
        except Exception as e:
            print(e)
            title = "监控项 {} 添加失败。".format(self.asin)
            self.error.emit(self.row_index, self.asin, title, str(e))

dialog.py

import os
import json

from PyQt5.QtWidgets import QVBoxLayout, QDialog, QPushButton, QLabel, QLineEdit, QMessageBox, QTextEdit, QHBoxLayout

from PyQt5.QtCore import Qt


class AlertDialog(QDialog):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.field_dict = {}#字段字典
        self.init_ui()#初始化ui

    def init_ui(self):
        self.setWindowTitle("报警邮件配置")
        self.resize(300, 270)

        layout = QVBoxLayout()

        form_data_list = [
            {"title": "SMTP服务器:", "filed": "smtp"},
            {"title": "发件箱:", "filed": "from"},
            {"title": "密码:", "filed": "pwd"},
            {"title": "收件人(多个用逗号分隔):", "filed": "to"},
        ]

        # 读取文件中的配置
        old_alert_dict = {}#存放读取本地配置文件中的老的配置数据
        alert_file_path = os.path.join("db", 'alert.json')#在db文件夹下找配置文件 alert.json
        if os.path.exists(alert_file_path):#判断本地是否有配置文件
            file_object = open(os.path.join("db", 'alert.json'), mode='r', encoding='utf-8')
            old_alert_dict = json.load(file_object)
            file_object.close()

        #将form_data_list中的ui界面信息编写成ui界面
        for item in form_data_list:
            lbl = QLabel()#新建一个标签
            lbl.setText(item['title'])
            layout.addWidget(lbl)

            txt = QLineEdit()#新建一个输入框
            layout.addWidget(txt)

            filed = item['filed']#写入输入框字段值
            if old_alert_dict and filed in old_alert_dict:#如果当前的字段恰好在配置文件中也有保存
                txt.setText(old_alert_dict[filed])#设置配置文件的字段值
            self.field_dict[item['filed']] = txt#设置全局变量的字段值

        btn_save = QPushButton("保存")
        btn_save.clicked.connect(self.event_save_click)#绑定保存设置鼠标事件
        layout.addWidget(btn_save, 0, Qt.AlignRight)#

        layout.addStretch(1)#添加一个弹簧
        self.setLayout(layout)

    def event_save_click(self):
        data_dict = {}
        for key, filed in self.field_dict.items():
            value = filed.text().strip()
            if not value:
                QMessageBox.warning(self, "错误", "邮件报警项不能为空")
                return
            data_dict[key] = value

        # {'smtp': '123123', 'from': '123123', 'pwd': '123123', 'to': '123123123'}
        print(data_dict)#打印保存的配置
        # 写入到本地的配置文件
        file_object = open(os.path.join("db", 'alert.json'), mode='w', encoding='utf-8')
        json.dump(data_dict, file_object)
        file_object.close()

        # 关闭对话框
        self.close()


class ProxyDialog(QDialog):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("配置代理IP")
        self.resize(500, 400)
        layout = QVBoxLayout()

        # 输入框
        text_edit = QTextEdit()
        text_edit.setPlaceholderText("可用换行来设置多个代理IP,每个代理IP设置格式为:31.40.225.250:3128")

        # 读取本地文件
        file_path = os.path.join("db", "proxy.txt")
        all_proxy = ""#存放读取得到的配置文件
        if os.path.exists(file_path):#如果本地已经有配置文件
            with open(os.path.join("db", "proxy.txt"), mode='r', encoding='utf-8') as f:
                all_proxy = f.read()
        text_edit.setText(all_proxy)

        self.text_edit = text_edit
        layout.addWidget(text_edit)

        footer_config = QHBoxLayout()

        btn_save = QPushButton("重置")
        btn_save.clicked.connect(self.event_save_click)
        footer_config.addWidget(btn_save, 0, Qt.AlignRight)

        layout.addLayout(footer_config)

        self.setLayout(layout)

    def event_save_click(self):
        # PROXY.write()
        text = self.text_edit.toPlainText()

        # 写入到代理文件中
        with open(os.path.join("db", "proxy.txt"), mode='w', encoding='utf-8') as f:
            f.write(text)
        self.close()



class LogDialog(QDialog):
    def __init__(self, asin, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.asin = asin

        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("日志记录")
        self.resize(500, 400)
        layout = QVBoxLayout()
        text_edit = QTextEdit()
        text_edit.setText("")

        layout.addWidget(text_edit)
        self.setLayout(layout)

        # 读取日志展示出来
        file_path = os.path.join("log", "{}.log".format(self.asin))
        if not os.path.exists(file_path):#如果日志不存在,直接返回
            return

        #读取日志
        with open(file_path,mode='r',encoding='utf-8') as f:
            content = f.read()
        #展示日志
        text_edit.setText(content)


总结

大家喜欢的话,给个👍,点个关注!给大家分享更多计算机专业学生的求学之路!

版权声明:

发现你走远了@mzh原创作品,转载必须标注原文链接

Copyright 2023 mzh

Crated:2023-3-1

欢迎关注 『pyqt5 从0基础开始项目实战』 专栏,持续更新中
欢迎关注 『pyqt5 从0基础开始项目实战』 专栏,持续更新中
『未完待续』


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/436256.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python常用练习小例子

Python常用练习小例子 1、输出九九乘法表 源码如下: # 九九乘法表 for i in range(1, 10):for j in range(1, i1):print({}x{}{}\t.format(i, j, i*j), end)print() # 换行,相当于print(end\n) 其中,rint({}x{}{}\t.format(i, j, i*j), e…

Kubespray v2.21.0 离线部署 Kubernetes v1.25.6 集群

文章目录 1. 前言2. 预备条件3. 配置代理4. 下载介质5. 初始化配置6. 安装部署工具6.1 配置 venv 部署环境6.2 配置容器部署环境 7. 配置互信8. 编写 inventory.ini9. 编写 offline.yml10. 部署 offline repo11. 部署 kubernetes 1. 前言 Kubespray 是 Kubernetes incubator 中…

【Python合集】程序员系列代码之“这么好的天气应该去放风筝,而不是在搬砖,好想去放风筝哦~”(附完整代码)

导语 ☽ ☽ ☽ ☽ ☽ ☽ 文案丨April 19th, 2023 ☆ ☽ ☽☽ ☽☽ ☽ 江滩边摇摇晃晃的风筝 是春日越冬归来的信号 风筝蹦蹦跳跳 看盎然春意四处热闹阿姨路过菜摊子 带把香椿回家炒蛋细子摘桑 被酸得直口水嗲嗲裹着棉袄 托起霸缸到处晒大阳妹子没管倒春寒 提前换上短…

HttpServletRequest

1、HttpServletRequest对象 在Servlet API中,定义了一个HttpServletRequest接口,它继承自ServletRequest接口,专门用于封装HTTP请求消息 1.1 获取请求行信息的相关方法 当访问Servlet时,请求消息的请求行中会包含请求方法、请求…

Spring入门案例--bean实例化

bean实例化 对象已经能交给Spring的IOC容器来创建了,但是容器是如何来创建对象的呢? 就需要研究下bean的实例化过程 ,在这块内容中主要解决两部分内容,分别是 bean是如何创建的实例化bean的三种方式, 构造方法,静态工厂 和 …

USB TO SPI / USB TO I2C 软件概要 1 --- 专业版调试器

所需设备: 1、USB转SPI_I2C适配器(专业版); 软件概述: SPI类: USB TO SPI 1.0-Slave SPI从机软件,适合单步调试,支持SPI工作模式0、1、2、3,自动跟随主机通讯速率,自动接收数据; USB TO SP…

21、指标监控

文章目录 1、SpringBoot Actuator1、简介2、1.x与2.x的不同3、如何使用4、可视化 2、Actuator Endpoint1、最常使用的端点2、Health Endpoint3、Metrics Endpoint4、管理Endpoints1、开启与禁用Endpoints2、暴露Endpoints 3、定制 Endpoint1、定制 Health 信息2、定制info信息1…

springboot集成nacos配置管理

官方文档:Nacos Spring Boot 快速开始 个人实践: Namespace定义环境,例如:开发环境、测试环境、生产环境。 Group定义不同的应用。 DataId用来区分配置,例如:mysql配置,redis配置&#xff0…

web集群

1. 简述静态网页和动态网页的区别 1.更新和维护: 静态网页内容一经发布到网站服务器上,无论是否有用户访问,这些网页内容都是保存在网站服务器上的。如果要修改网页的内容,就必须修改其源代码,然后重新上传到服务器上…

新一代异步IO框架 io_uring | 得物技术

1.Linux IO 模型分类 相比于kernel bypass 模式需要结合具体的硬件支撑来讲,native IO是日常工作中接触到比较多的一种,其中同步IO在较长一段时间内被广泛使用,通常我们接触到的IO操作主要分为网络IO和存储IO。在大流量高并发的今天&#xff…

【golang学习笔记】——(三)golang vscode编译第一个程序

这里有一个盲区的坑,先埋下,待会再讲。 一、工程创建 首先是在一个自己需要的文件夹下创建一个.go空文件,老传统,这里就是hellowrold.go,致敬原神Brian Kernighan(1978年出版的《The C Programming Langua…

数据库----------自增长约束、非空约束

目录 1.自增长约束(auto_increment) 1.概念 2.特点 3.指定自增字段初始值 4.delete和truncate在删除后自增列的变化 2.非空约束(not null) 1.概念 2.语法 3.添加非空约束 4.删除非空约束 1.自增长约束(auto_increment) 1.概念 在MysQL中,当主键定义为自增…

Can we learn better with hard samples

摘要 在深度学习中,小批量训练通常用于优化网络参数。然而,传统的小批处理方法可能无法学习到数据中代表性不足的样本和复杂的模式,从而导致泛化的时间更长。为了解决这一问题,提出了一种传统算法的一种变体,它训练网…

【基于 Arduino 的 RFID门锁】

【基于 Arduino 的 RFID门锁】 1. 概述2. 射频识别的工作原理3. RFID 和 Arduino4. Arduino RFID门锁门禁项目5. 源代码 在本教程中,我们将了解什么是 RFID,它是如何工作的以及如何制作基于 Arduino 的 RFID 门锁。您可以观看以下视频或阅读下面的书面教…

CTFWIKI-PWN-ret2syscall

该题目是在32位下 目录 先进行checksec ​编辑 ida 1.execve() 2.寄存器 3.流程图 4.我们需要先看看execve()函数的函数调用号 5.使用ROPgadget来查看 我们先进行查看eax|ret 查看 pop ebx,ecx,edx,ret 查找 /bin/sh的地址 查找int 0x80 查看字符偏移量 附上流程…

2023-04-13 工作记录--CSS/JS-ios 文本渐变色 和 文本超出省略号处理 共用时,出现省略号未显示问题

CSS/JS-ios 文本渐变色 和 文本超出省略号处理 共用时,出现省略号未显示问题 一、前言 ⭐️ 最近写项目,发现一个bug:ios 文本渐变色 和 文本超出省略号处理 共用时,出现省略号未显示问题,如下图:图1是非i…

三、vue_options之data、methods属性选项

一、data属性 data属性是传入一个函数,并且该函数需要返回一个对象: 在Vue2.x的时候,也可以传入一个对象(虽然官方推荐是一个函数);在Vue3.x的时候,必须传入一个函数,否则就会直接在浏览器中报错&#xf…

【Java开发】设计模式 12:解释器模式

1 解释器模式介绍 解释器模式是一种行为型设计模式,它提供了一种方法来解释语言、表达式或符号。 在该模式中,定义了一个表达式接口,并实现了对应的表达式类,这些类可以解释不同的符号组成的表达式,从而实现对语言的…

云原生之在kubernetes环境下部署wordpress

云原生之在kubernetes环境下部署wordpress 一、wordpress介绍1.wordpress简介2.wordpress特点 二、kubernetes集群介绍1.k8s简介2.k8s架构图 三、本次实践介绍1.本次实践简介2.本次环境规划 四、环境准备工作1.检查k8s环境2.检查系统pod状态 五、创建mysql的secret资源1.创建部…

OpenCV_contrib配置教程(详细版)

文章目录 一:前提准备1:OpenCV4.5.1、OpenCV_contrib4.5.1扩展库下载2:Cmake下载地址Download 二:cmake配置1:2: 三:vs2017编译OpenCV build文件四:环境配置 个人笔记: …