pyqtwebengine=5.12
PyQt5==5.12
class MyWindow(QMainWindow):def __init__(self):super(MyWindow, self).__init__()self.browser = QWebEngineView(self) # 如果不写self则新生成一个窗口self.browser.setWindowTitle('技术领域占比分析')self.browser.setWindowIcon(QIcon(LOGO_PATH))self.browser.raise_()self.browser.setFixedSize(QSize(1200, 850))self.browser.move(0, 0)self.browser.setUrl(QUrl('https://www.taobao.com')) # 网络连接# self.browser.load(QUrl.fromLocalFile('D:/1.htmml')) # 本地h5self.browser.show()if __name__ == '__main__':app = QApplication(sys.argv)window = MyWindow()window.show()sys.exit(app.exec_())
webview套壳 防多开 多次点击激活旧窗口
文件结构如下:
依赖
loguru==0.6.0
pyinstaller
pywin32==306
PyQt5==5.12
pyqtwebengine==5.12
Pillow
config.ini
[Section1]
open_loadfile = 1
window_title = 鸿运兑奖系统
window_width = 1580
window_height = 800
prohibit_max_window = 0
window_max = 0
open_url = https://www.taobao.com
min_window_width = 1300
min_window_height = 700
settings.py
import osBASE_DIR = os.path.dirname(__file__)
STATIC_FILE = os.path.join(BASE_DIR, 'static') # 静态资源路径
INDEX_FILE = os.path.join(STATIC_FILE, 'index.html')
CONFIG_PATH = os.path.join(BASE_DIR, 'config') # 图标文件
LOGO_PATH = os.path.join(CONFIG_PATH, "logo.png") # 图标路径
INI_PATH = os.path.join(CONFIG_PATH, "config.ini") # 配置文件
tools.py
import configparserclass MyINIFile:def __init__(self, filename):self.filename = filenameself.config = configparser.ConfigParser()self.config.read(self.filename)def read_value(self, section, key):try:return self.config.get(section, key)except configparser.Error as e:print(f"Error reading value: {e}")return Nonedef write_value(self, section, key, value):try:if not self.config.has_section(section):self.config.add_section(section)self.config.set(section, key, value)with open(self.filename, 'w') as configfile:self.config.write(configfile)except configparser.Error as e:print(f"Error writing value: {e}")def delete_value(self, section, key):try:if self.config.has_section(section) and self.config.has_option(section, key):self.config.remove_option(section, key)with open(self.filename, 'w') as configfile:self.config.write(configfile)else:print(f"Section '{section}' or option '{key}' does not exist.")except configparser.Error as e:print(f"Error deleting value: {e}")def update_value(self, section, key, value):try:if self.config.has_section(section) and self.config.has_option(section, key):self.config.set(section, key, value)with open(self.filename, 'w') as configfile:self.config.write(configfile)else:print(f"Section '{section}' or option '{key}' does not exist.")except configparser.Error as e:print(f"Error updating value: {e}")
ui.py
from PyQt5.QtCore import Qt, QUrl
from PyQt5.QtGui import QIcon
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QMainWindow, QSystemTrayIcon, QMenu, QAction, \QApplicationfrom settings import LOGO_PATH, INDEX_FILE, INI_PATH
from tools import MyINIFileclass MyWindow(QMainWindow):def __init__(self):config = MyINIFile(INI_PATH)self.open_loadfile = config.read_value('Section1', 'open_loadfile')self._window_title = config.read_value('Section1', 'window_title') # 窗口标题self._window_width = int(config.read_value('Section1', 'window_width'))# 窗口默认宽高self._window_height = int(config.read_value('Section1', 'window_height')) # 窗口默认宽高self.prohibit_max_window = config.read_value('Section1', 'prohibit_max_window') # 是否禁止最大化self.window_max = config.read_value('Section1', 'window_max') # 是否默认最大化显示self.open_url = config.read_value('Section1', 'open_url') # 网络链接self._min_window_width = int(config.read_value('Section1', 'min_window_width')) # 最小窗口self._min_window_height = int(config.read_value('Section1', 'min_window_height'))super(MyWindow, self).__init__()self.setWindowTitle(self._window_title) # 设置窗口标题self.setMinimumSize(self._min_window_width, self._min_window_height)self.resize(self._window_width, self._window_height)self.setWindowIcon(QIcon(LOGO_PATH)) # 设置窗口图标,icon.png 是您的图标文件路径if self.prohibit_max_window == '1':self.setWindowFlags(self.windowFlags() & ~Qt.WindowMaximizeButtonHint) # 禁止最大化按钮if self.window_max == '1':self.showMaximized() # 最大化窗口self.browser = QWebEngineView(self) # 如果不写self则新生成一个窗口if self.open_loadfile == '1':self.browser.load(QUrl.fromLocalFile(INDEX_FILE)) # 本地h5else:self.browser.setUrl(QUrl(self.open_url)) #self.browser.show()def resizeEvent(self, event):# 设置 WebEngineView 的大小self.browser.resize(self.size()) # 使用窗口的大小super().resizeEvent(event)def tray(self):'''系统托盘'''# 创建系统托盘图标self.tray_icon = QSystemTrayIcon(self)self.tray_icon.setIcon(QIcon(LOGO_PATH)) # 替换为你的图标路径self.tray_icon.setToolTip(self._window_title) # 这里设置鼠标悬浮时显示的文字self.tray_menu = QMenu() # 创建右键菜单open_window_action = QAction("打开主界面", self) # 添加退出动作exit_action = QAction("退出系统", self) # 添加退出动作self.tray_menu.addAction(open_window_action) # 添加到菜单self.tray_menu.addAction(exit_action)open_window_action.triggered.connect(self.open_window_action)exit_action.triggered.connect(self.exit_app) # 连接托盘图标的点击事件self.tray_icon.activated.connect(self.tray_icon_clicked) # 图标左键被点击self.tray_icon.setContextMenu(self.tray_menu) # 将菜单设置到托盘图标self.tray_icon.show() # 显示托盘图标def tray_icon_clicked(self, reason):if reason == QSystemTrayIcon.Trigger: # 单击托盘图标if self.isHidden():self.show() # 显示窗口if self.isMinimized():self.showNormal() # 还原窗口self.activateWindow() # 激活窗口self.raise_() # 确保窗口在最上层def open_window_action(self):'''打开主界面'''if self.isHidden():self.show() # 显示窗口if self.isMinimized():self.showNormal() # 还原窗口self.activateWindow() # 激活窗口self.raise_() # 确保窗口在最上层def exit_app(self):QApplication.quit() # 退出应用def closeEvent(self, event):'''窗口被关闭事件'''event.ignore()self.hide()
main.py
import socket
import sys
from threading import Threadfrom PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtWidgets import QApplication
from loguru import logger
from ui import MyWindowclass Communicator(QObject):activate_signal = pyqtSignal()def check_if_running(port):"""检查端口是否被占用,意味着应用正在运行"""with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:try:s.bind(('127.0.0.1', port))return False # 绑定成功,表示没有其他实例在运行except OSError:return True # 绑定失败,表示端口被占用def activate_existing_instance(port):"""激活已运行的实例"""with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:s.sendto(b'activate', ('127.0.0.1', port))def start_server(port, comm):"""启动服务器以监听激活请求"""with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:s.bind(('127.0.0.1', port))logger.debug('启动成功')while True:data, addr = s.recvfrom(1024)if data == b'activate':logger.info("激活请求收到, 启动窗口")comm.activate_signal.emit() # 发射信号以激活窗口if __name__ == '__main__':app = QApplication(sys.argv)port = 12345 # 选择一个未被占用的端口comm = Communicator()main_window = MyWindow()comm.activate_signal.connect(main_window.open_window_action)if check_if_running(port):activate_existing_instance(port)logger.error("应用已经在运行,激活窗口。")sys.exit(0)# 启动服务器监听激活请求server_thread = Thread(target=start_server, args=(port, comm))server_thread.daemon = Trueserver_thread.start()main_window.tray()# 创建并显示主窗口main_window.show()sys.exit(app.exec_())
pack.py
# coding=utf-8# @Time : 2023-08-17 02:04
# @Author : XiaoYi
# @Email: 1206154726@qq.com
import os
import shutil
from loguru import loggerdesktop_dist = os.path.join(os.path.join(os.path.expanduser("~"), 'Desktop'), 'dist')
desktop_main = os.path.join(desktop_dist, 'main')
# from urllib.parse import quote as url_quote py3.8以上logger.debug(f'----------------------------开始构建程序--------------------------')
os.system('pyinstaller -w main.py -i logo.ico') # 执行打包指令 pyinstaller -w main.py -i logo.ico 无调试窗口
logger.success(f'------->基础镜像结束,开始打包静态资源')shutil.move(os.path.join(os.getcwd(), 'dist'), desktop_dist) # 移动文件到桌面
logger.debug(f'------->【dist】基础镜像结束')shutil.rmtree(os.path.join(os.getcwd(), 'build')) # 删除打包构建文件
os.remove(os.path.join(os.getcwd(), 'main.spec')) # 删除打包构建文件
logger.warning(f'------->【编译文件清除】清除结束')shutil.copytree(os.path.join(os.getcwd(), 'config'), os.path.join(desktop_main, 'config'))
shutil.copytree(os.path.join(os.getcwd(), 'static'), os.path.join(desktop_main, 'static'))
os.rename(os.path.join(desktop_main, 'main.exe'), os.path.join(desktop_main, 'taoke.exe')) # 重命名打包后的文件os.rename(desktop_main, os.path.join(desktop_dist, 'taoke')) # 重命名打包后的文件
logger.success(f'-------【打包结束】----------')