活动介绍

怎么实现class LoginWindow(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.setFixedSize(800, 500) self.setWindowTitle('鸡爪称重系统') # 创建一个QStackedWidget控件 self.stacked_widget = QStackedWidget(self) self.stacked_widget.setGeometry(0, 0, 800, 500) # 创建Login界面 login_widget = QWidget() self.stacked_widget.addWidget(login_widget) # 在Login界面上创建一个按钮 button = QPushButton('开始称重', login_widget) button.move(100, 100) button.setGeometry(300, 200, 200, 50)点击按钮后跳转下个界面,界面显示info_label

时间: 2023-12-23 09:03:09 浏览: 110
可以在startWeighing()函数中创建下一个界面,并在该界面上添加一个QLabel控件用于显示信息。 ``` def startWeighing(self): # 创建下一个界面 next_widget = QWidget() self.stacked_widget.addWidget(next_widget) # 添加一个QLabel控件用于显示信息 info_label = QLabel('这是下一个界面', next_widget) info_label.setGeometry(300, 200, 200, 50) # 切换到下一个界面 self.stacked_widget.setCurrentWidget(next_widget) ``` 当点击按钮后,会创建下一个界面并切换到该界面,界面上会显示一个文本为“这是下一个界面”的QLabel控件。您可以根据需要修改文本内容和位置。
相关问题

class LoginWindow(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.setFixedSize(800, 500) self.setWindowTitle('鸡爪称重系统')中怎么调用class MainWindow(QWidget): def __init__(self, user_id): super().__init__() self.user_id = user_id self.initUI() # 打开串口 self.ser = serial.Serial('COM7', 9600, timeout=1) def initUI(self): # 创建用于显示员工信息的控件 self.info_label = QLabel("员工信息", self) self.info_label.move(100, 50) self.info_label.setStyleSheet("font-size: 24px; color: black; background-color: #eee; border-radius: 10px;")

要在 LoginWindow 类中调用 MainWindow 类,你需要在 LoginWindow 类中添加一个按钮或者其他控件,然后给这个控件添加一个信号(signal),当这个控件被点击时,就会触发这个信号。接着在 LoginWindow 类中添加一个槽(slot),当信号被触发时,就会执行这个槽中的代码,从而打开 MainWindow 窗口。具体实现方法如下: 在 LoginWindow 类的 initUI 方法中添加一个按钮: ``` self.open_main_window_button = QPushButton("打开主窗口", self) self.open_main_window_button.move(100, 100) self.open_main_window_button.clicked.connect(self.open_main_window) ``` 然后添加一个槽函数 open_main_window: ``` def open_main_window(self): self.main_window = MainWindow(user_id) self.main_window.show() self.close() ``` 这个方法中首先创建一个 MainWindow 实例,并传入 user_id 参数。然后调用 show 方法显示 MainWindow 窗口,并关闭当前 LoginWindow 窗口。这样当点击打开主窗口的按钮时,就会触发 open_main_window 方法,从而打开 MainWindow 窗口。

class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setFixedSize(800, 600) main_layout = QVBoxLayout() central_widget = QWidget() central_widget.setLayout(main_layout) self.setCentralWidget(central_widget) button_layout = QVBoxLayout() button1 = QPushButton('当日员工工资') button1.setFixedSize(200, 50) button1.clicked.connect(self.show_query1_result) button_layout.addStretch() button_layout.addWidget(button1) button_layout.addStretch() layout = QHBoxLayout() layout.addStretch() layout.addLayout(button_layout) layout.addStretch() widget = QWidget() widget.setLayout(layout) self.setCentralWidget(widget) main_layout.addLayout(button_layout) self.query1_window = QueryResultWindow() def show_query1_result(self): db = pymysql.connect(host='39.99.214.172', user='root', password='Solotion.123', db='jj_tset') cursor = db.cursor() db_sql = """ """ cursor.execute(db_sql) result = cursor.fetchall() db.close() if len(result) == 0: QMessageBox.information(self, "提示", "今日无员工工资记录") return self.query1_window.table_widget.setRowCount(0) self.query1_window.table_widget.setColumnCount(len(result[0])) self.query1_window.table_widget.setHorizontalHeaderLabels( ["员工ID", "员工姓名", "日期", "领取鸡爪重量(KG)", "效率(每小时KG)", "出成率", "基础工资", "重量奖励", "当日总工资"]) for row_num, row_data in enumerate(result): self.query1_window.table_widget.insertRow(row_num) for col_num, col_data in enumerate(row_data): self.query1_window.table_widget.setItem(row_num, col_num, QTableWidgetItem(str(col_data))) self.query1_window.show() class QueryResultWindow(QWidget): def __init__(self): super().__init__() self.setFixedSize(800, 600) self.table_widget = QTableWidget() self.table_widget.setEditTriggers(QTableWidget.NoEditTriggers) self.table_widget.setSelectionBehavior(QTableWidget.SelectRows) layout = QVBoxLayout() layout.addWidget(self.table_widget) self.setLayout(layout) if __name__ == '__main__': app = QApplication(sys.argv) loginWindow = LoginWindow() loginWindow.show() sys.exit(app.exec_()))数据展示页面怎么设置筛选器按ID筛选结果并展示

你可以在数据展示页面添加一个QLineEdit和一个QPushButton,让用户输入员工ID并点击按钮进行筛选。 具体实现方法如下: 1. 在QueryResultWindow类的初始化函数中添加以下代码来创建QLineEdit和QPushButton: ``` self.id_line_edit = QLineEdit() self.filter_button = QPushButton('筛选') self.filter_button.clicked.connect(self.filter_result) ``` 2. 在layout中添加这两个控件: ``` layout.addWidget(self.id_line_edit) layout.addWidget(self.filter_button) ``` 3. 添加filter_result函数来执行筛选操作: ``` def filter_result(self): id = self.id_line_edit.text().strip() if len(id) == 0: QMessageBox.warning(self, '提示', '请输入员工ID') return db = pymysql.connect(host='39.99.214.172', user='root', password='Solotion.123', db='jj_tset') cursor = db.cursor() db_sql = f"SELECT * FROM table_name WHERE id = '{id}'" cursor.execute(db_sql) result = cursor.fetchall() db.close() if len(result) == 0: QMessageBox.information(self, "提示", "没有找到符合条件的记录") return self.table_widget.setRowCount(0) for row_num, row_data in enumerate(result): self.table_widget.insertRow(row_num) for col_num, col_data in enumerate(row_data): self.table_widget.setItem(row_num, col_num, QTableWidgetItem(str(col_data))) ``` 注意替换db_sql中的table_name为你的表名。这个函数会从数据库中查询符合条件的记录并展示在表格中。 4. 修改show_query1_result函数,在最开始添加以下代码,用于展示所有记录: ``` db = pymysql.connect(host='39.99.214.172', user='root', password='Solotion.123', db='jj_tset') cursor = db.cursor() db_sql = """SELECT * FROM table_name""" cursor.execute(db_sql) result = cursor.fetchall() db.close() if len(result) == 0: QMessageBox.information(self, "提示", "今日无员工工资记录") return ``` 注意同样替换db_sql中的table_name为你的表名。这个代码块会在进入数据展示页面时展示所有记录。 通过以上修改,你就可以在数据展示页面添加筛选功能了。
阅读全文

相关推荐

class LoginWindow(QWidget): def init(self): super().init() self.initUI() def initUI(self): self.setFixedSize(800, 500) self.setWindowTitle('鸡爪称重系统') # 创建一个QStackedWidget控件 self.stacked_widget = QStackedWidget(self) self.stacked_widget.setGeometry(0, 0, 800, 500) # 创建Login界面 login_widget = QWidget() self.stacked_widget.addWidget(login_widget) # 在Login界面上创建一个按钮 self.button = QPushButton('开始称重', login_widget) self.button.move(100, 100) self.button.setGeometry(300, 200, 200, 50) self.button.clicked.connect(self.go_to_next_window) def go_to_next_window(self, user_id): next_window = NextWindow(user_id) # 创建NextWindow实例 self.stacked_widget.addWidget(next_window) # 将NextWindow添加到QStackedWidget中 self.stacked_widget.setCurrentWidget(next_window) # 切换到NextWindow class NextWindow(QWidget): def init(self, user_id): super().init() self.user_id = user_id self.initUI() # 打开串口 self.ser = serial.Serial('COM7', 9600, timeout=1) def initUI(self): # 创建用于显示员工信息的控件 self.info_label = QLabel("员工信息", self) self.info_label.move(100, 50) self.info_label.setStyleSheet("font-size: 24px; color: black; background-color: #eee; border-radius: 10px;") # 创建返回按钮 back_button = QPushButton('返回', self) back_button.setGeometry(600, 400, 100, 50) back_button.clicked.connect(self.backToLoginWindow) # 设置窗口大小和标题 self.setFixedSize(800, 500) self.setWindowTitle('员工信息') def backToLoginWindow(self): self.stacked_widget.setCurrentIndex(0)这段代码中返回按钮报错

def initUI(self): self.setFixedSize(800, 500) self.setWindowTitle('鸡爪称重系统') # 创建一个QStackedWidget控件 self.stacked_widget = QStackedWidget(self) self.stacked_widget.setGeometry(0, 0, 800, 500) # 创建Login界面 login_widget = QWidget() self.stacked_widget.addWidget(login_widget) # 在Login界面上创建一个按钮 button = QPushButton('开始称重', login_widget) button.move(100, 100) button.setGeometry(300, 200, 200, 50)怎么点击按钮后跳转下个界面,class NextWindow(QWidget): def __init__(self): super().__init__() self.user_id = user_id self.initUI() # 打开串口 self.ser = serial.Serial('COM7', 9600, timeout=1) def initUI(self): # 创建用于显示员工信息的控件 self.info_label = QLabel("员工信息", self) self.info_label.move(100, 50) self.info_label.setStyleSheet("font-size: 24px; color: black; background-color: #eee; border-radius: 10px;") self.id_label = QLabel("员工ID:", self) self.id_label.move(70, 100) self.id_label.setStyleSheet("font-size: 18px; color: black;") self.name_label = QLabel("姓名:", self) self.name_label.move(70, 150) self.name_label.setStyleSheet("font-size: 18px; color: black;") self.six_label = QLabel("性别:", self) self.six_label.move(70, 200) self.six_label.setStyleSheet("font-size: 18px; color: black;") self.sfz_label = QLabel("身份证:", self) self.sfz_label.move(70, 250) self.sfz_label.setStyleSheet("font-size: 18px; color: black;") self.tel_label = QLabel("电话:", self) self.tel_label.move(70, 300) self.tel_label.setStyleSheet("font-size: 18px; color: black;") self.setFixedSize(800, 500) self.setWindowTitle('员工信息')

import sys import json import os import warnings from datetime import datetime from PyQt5.QtCore import QUrl, Qt, QDateTime from PyQt5.QtWidgets import ( QApplication, QMainWindow, QLineEdit, QPushButton, QListWidget, QTabWidget, QVBoxLayout, QWidget, QHBoxLayout, QAction, QToolBar, QStatusBar, QProgressBar, QInputDialog, QFileDialog, QMessageBox ) from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile, QWebEnginePage, QWebEngineSettings from PyQt5.QtNetwork import QNetworkProxy, QNetworkCookie # 修复代理设置导入问题 # 忽略弃用警告 warnings.filterwarnings("ignore", category=DeprecationWarning) class WebEnginePage(QWebEnginePage): def __init__(self, profile=None, parent=None): """修复参数传递问题:支持带profile和不带profile两种初始化方式""" if profile: super().__init__(profile, parent) # 使用profile初始化 else: super().__init__(parent) # 标准初始化 self.parent_window = parent.parent_window if parent and hasattr(parent, 'parent_window') else None def acceptNavigationRequest(self, url, _type, isMainFrame): # 检测视频流协议并调用播放器 if url.scheme() in ['rtmp', 'http-flv', 'ws-flv']: if self.parent_window: self.parent_window.play_with_jessibuca(url.toString()) return False return super().acceptNavigationRequest(url, _type, isMainFrame) def createWindow(self, type): # 在新标签页打开链接 if type == QWebEnginePage.WebBrowserTab: if self.parent_window: new_tab = self.parent_window.add_browser_tab("加载中...") return new_tab.page return super().createWindow(type) def javaScriptConsoleMessage(self, level, message, lineNumber, sourceID): """修复:正确映射JavaScript控制台日志级别""" # 创建日志级别映射字典 level_map = { QWebEnginePage.InfoMessageLevel: "INFO", QWebEnginePage.WarningMessageLevel: "WARNING", QWebEnginePage.ErrorMessageLevel: "ERROR" } # 获取可读的日志级别名称 level_str = level_map.get(level, "UNKNOWN") log_msg = f"[JS {level_str}] {sourceID}:{lineNumber} - {message}" if self.parent_window: self.parent_window.status_bar.showMessage(log_msg, 5000) print(log_msg) # 同时输出到控制台 class WebEngineView(QWebEngineView): def __init__(self, parent=None): super().__init__(parent) self.parent_window = parent # 修复:正确传递profile参数 self.page = WebEnginePage(profile=self.parent_window.profile, parent=self) self.setPage(self.page) self.page.loadFinished.connect(self.inject_jessibuca) def inject_jessibuca(self): self.page.runJavaScript(""" if (typeof window.Jessibuca === 'undefined') { const script = document.createElement('script'); script.src = 'https://cdn.jsdelivr.net/npm/jessibuca@latest/dist/jessibuca.js'; script.onerror = () => console.error('Jessibuca加载失败'); document.head.appendChild(script); const container = document.createElement('div'); container.id = 'jessibuca-container'; container.style.position = 'fixed'; container.style.zIndex = '99999'; container.style.top = '0'; container.style.left = '0'; container.style.width = '100%'; container.style.height = '100%'; container.style.backgroundColor = 'black'; container.style.display = 'none'; document.body.appendChild(container); } """) def createWindow(self, type): # 创建新标签页 if type == QWebEnginePage.WebBrowserTab: new_tab = WebEngineView(self.parent_window) # 修复:直接返回page属性而非调用page() return new_tab.page return super().createWindow(type) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("B站多账号浏览器") self.setMinimumSize(1200, 800) # 初始化数据存储 self.account_db = {} self.cookies_db = [] # 创建持久化Cookie配置 self.cookie_storage_path = "cookies_storage" os.makedirs(self.cookie_storage_path, exist_ok=True) self.profile = QWebEngineProfile("BiliCookieProfile", self) self.profile.setPersistentCookiesPolicy(QWebEngineProfile.ForcePersistentCookies) self.profile.setPersistentStoragePath(self.cookie_storage_path) # 关键性能优化1: 全局禁用网络代理[8](@ref) QNetworkProxy.setApplicationProxy(QNetworkProxy(QNetworkProxy.NoProxy)) # 关键性能优化2: 启用HTTP内存缓存[9](@ref) self.profile.setHttpCacheType(QWebEngineProfile.MemoryHttpCache) # 启用HTML5支持的设置 profile_settings = self.profile.settings() profile_settings.setAttribute(QWebEngineSettings.PlaybackRequiresUserGesture, False) profile_settings.setAttribute(QWebEngineSettings.FullScreenSupportEnabled, True) profile_settings.setAttribute(QWebEngineSettings.WebGLEnabled, True) profile_settings.setAttribute(QWebEngineSettings.Accelerated2dCanvasEnabled, True) profile_settings.setAttribute(QWebEngineSettings.PluginsEnabled, True) profile_settings.setAttribute(QWebEngineSettings.AllowRunningInsecureContent, True) # 主界面布局 central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) # 导航工具栏 navbar = QToolBar("导航栏") navbar.setMovable(False) # 禁止工具栏拖动提升性能 self.addToolBar(navbar) back_btn = QAction("←", self) back_btn.triggered.connect(self.navigate_back) navbar.addAction(back_btn) forward_btn = QAction("→", self) forward_btn.triggered.connect(self.navigate_forward) navbar.addAction(forward_btn) reload_btn = QAction("↻", self) reload_btn.triggered.connect(self.reload_page) navbar.addAction(reload_btn) home_btn = QAction("🏠", self) home_btn.triggered.connect(self.navigate_home) navbar.addAction(home_btn) navbar.addSeparator() self.url_bar = QLineEdit("https://www.bilibili.com") self.url_bar.setPlaceholderText("输入网址或搜索内容...") self.url_bar.returnPressed.connect(self.load_url) navbar.addWidget(self.url_bar) # 浏览器标签页 self.browser_tabs = QTabWidget() self.browser_tabs.setTabsClosable(True) self.browser_tabs.tabCloseRequested.connect(self.close_tab) self.browser_tabs.currentChanged.connect(self.update_url_bar) # 关键性能优化3: 启用标签页滚动 self.browser_tabs.setUsesScrollButtons(True) main_layout.addWidget(self.browser_tabs, 8) # 添加初始标签页 self.add_browser_tab("首页", "https://www.bilibili.com") # 管理面板 self.tabs = QTabWidget() self.cookie_list = QListWidget() self.tabs.addTab(self.cookie_list, "Cookie列表") self.account_list = QListWidget() self.tabs.addTab(self.account_list, "账号切换") self.account_list.itemClicked.connect(self.switch_account) main_layout.addWidget(self.tabs, 2) # 功能按钮 btn_layout = QHBoxLayout() self.import_btn = QPushButton("导入Cookie") self.import_btn.clicked.connect(self.import_cookies) self.save_btn = QPushButton("保存账号") self.save_btn.clicked.connect(self.save_account) self.export_btn = QPushButton("导出Cookie") self.export_btn.clicked.connect(self.export_cookies) self.new_tab_btn = QPushButton("新建标签页") self.new_tab_btn.clicked.connect(self.create_new_tab) for btn in [self.import_btn, self.save_btn, self.export_btn, self.new_tab_btn]: btn_layout.addWidget(btn) main_layout.addLayout(btn_layout) # 状态栏 self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) self.progress_bar = QProgressBar() self.progress_bar.setMaximum(100) self.progress_bar.setVisible(False) self.progress_bar.setFixedWidth(200) self.status_bar.addPermanentWidget(self.progress_bar) # 加载数据 self.load_accounts() def add_browser_tab(self, title, url=None): browser = WebEngineView(self) browser.titleChanged.connect(lambda title, b=browser: self.update_tab_title(b, title)) browser.loadProgress.connect(self.update_progress) browser.loadFinished.connect(self.on_load_finished) index = self.browser_tabs.addTab(browser, title) self.browser_tabs.setCurrentIndex(index) # 关键性能优化4: 延迟加载提升启动速度 if url: QApplication.processEvents() # 确保UI更新 browser.load(QUrl(url)) return browser def update_tab_title(self, browser, title): index = self.browser_tabs.indexOf(browser) if index != -1: self.browser_tabs.setTabText(index, title[:15] + "..." if len(title) > 15 else title) def update_progress(self, progress): self.progress_bar.setVisible(progress < 100) self.progress_bar.setValue(progress) def close_tab(self, index): """修复资源释放问题:移除多余的括号""" if self.browser_tabs.count() > 1: widget = self.browser_tabs.widget(index) # 关键修复:直接访问page属性而非调用page() page = widget.page if page: # 关键优化: 正确的资源释放顺序 profile = page.profile() profile.cookieStore().deleteAllCookies() # 先删除页面再删除视图 page.deleteLater() widget.deleteLater() self.browser_tabs.removeTab(index) else: self.create_new_tab() def create_new_tab(self): self.add_browser_tab("新标签页", "about:blank") def navigate_back(self): current_browser = self.browser_tabs.currentWidget() if current_browser: current_browser.back() def navigate_forward(self): current_browser = self.browser_tabs.currentWidget() if current_browser: current_browser.forward() def reload_page(self): current_browser = self.browser_tabs.currentWidget() if current_browser: current_browser.reload() def navigate_home(self): self.load_url("https://www.bilibili.com") def on_load_finished(self, success): browser = self.sender() if browser and self.browser_tabs.currentWidget() == browser: current_url = browser.url().toString() self.url_bar.setText(current_url) self.status_bar.showMessage("页面加载完成" if success else "页面加载失败", 2000) # 关键优化: 登录页面自动处理 if "login" in current_url or "passport" in current_url: self.status_bar.showMessage("检测到登录页面,请完成登录", 5000) self.auto_handle_login_page(browser) def auto_handle_login_page(self, browser): """自动处理登录页面的JavaScript""" browser.page.runJavaScript(""" // 尝试自动填充已知的登录表单 const loginForm = document.querySelector('form[action*="login"]'); if (loginForm) { // 尝试填充测试账号 const usernameInput = loginForm.querySelector('input[name="username"], input[name="user"]'); const passwordInput = loginForm.querySelector('input[name="password"]'); if (usernameInput && passwordInput) { usernameInput.value = "test_account"; passwordInput.value = "test_password"; console.log("自动填充了登录表单"); } } """) def update_url_bar(self): current_browser = self.browser_tabs.currentWidget() if current_browser: current_url = current_browser.url().toString() self.url_bar.setText(current_url) # 登录页面特殊处理 if "login" in current_url or "passport" in current_url: self.url_bar.setStyleSheet("background-color: #FFF8E1;") else: self.url_bar.setStyleSheet("") def load_url(self, url_text=None): if url_text is None: url_text = self.url_bar.text().strip() if not url_text: return # 关键优化: 更智能的URL处理[10](@ref) if not url_text.startswith(("https://", "https://", "file://", "ftp://")): if "." in url_text: # 包含域名 url_text = "https://" + url_text else: # 可能是搜索内容 url_text = f"https://www.bilibili.com/search?keyword={url_text}" current_browser = self.browser_tabs.currentWidget() if current_browser: current_browser.load(QUrl(url_text)) self.status_bar.showMessage(f"正在加载: {url_text}", 3000) def play_with_jessibuca(self, stream_url): """使用Jessibuca播放视频流""" current_browser = self.browser_tabs.currentWidget() if not current_browser: return self.status_bar.showMessage("Jessibuca播放中...", 3000) current_browser.page.runJavaScript(f""" const container = document.getElementById('jessibuca-container'); if (container) {{ container.style.display = 'block'; if (!window.jessibucaPlayer) {{ window.jessibucaPlayer = new Jessibuca({{ container: container, videoBuffer: 0.2, isResize: true, text: '直播加载中...', decoder: 'ffmpeg.js', forceNoOffscreen: true }}); }} window.jessibucaPlayer.play('{stream_url}'); }} """) def import_cookies(self): """导入Cookie JSON文件""" file_path, _ = QFileDialog.getOpenFileName( self, "选择Cookie文件", "", "JSON文件 (*.json)" ) if file_path: try: with open(file_path, "r", encoding='utf-8') as f: cookies = json.load(f) if isinstance(cookies, list): self.cookies_db = cookies self.cookie_list.clear() self.cookie_list.addItems([f"{c['name']}: {c['value'][:10]}..." for c in cookies]) self.status_bar.showMessage(f"成功导入 {len(cookies)} 个Cookie", 3000) self.inject_cookies(cookies) else: QMessageBox.warning(self, "错误", "无效的Cookie格式") except Exception as e: QMessageBox.critical(self, "导入失败", f"错误: {str(e)}") def export_cookies(self): """导出Cookie到文件""" file_path, _ = QFileDialog.getSaveFileName( self, "保存Cookie文件", "", "JSON文件 (*.json)" ) if file_path: try: cookies = self.get_current_cookies() with open(file_path, "w", encoding='utf-8') as f: json.dump(cookies, f, indent=2, ensure_ascii=False) self.status_bar.showMessage("Cookie导出成功", 3000) except Exception as e: QMessageBox.critical(self, "导出失败", f"错误: {str(e)}") def get_current_cookies(self): """获取当前标签页的Cookie(模拟)""" # 实际实现需要异步获取,这里简化处理 return self.cookies_db if self.cookies_db else [] def inject_cookies(self, cookies): """将Cookie注入当前页面 - 修复登录问题[9](@ref)""" current_browser = self.browser_tabs.currentWidget() if current_browser: store = current_browser.page.profile().cookieStore() # 先清除现有Cookie store.deleteAllCookies() # 异步注入新的Cookie for cookie_data in cookies: # 创建Qt的Cookie对象 qt_cookie = QNetworkCookie( cookie_data['name'].encode('utf-8'), cookie_data['value'].encode('utf-8') ) # 设置Cookie属性 if 'domain' in cookie_data: qt_cookie.setDomain(cookie_data['domain']) if 'path' in cookie_data: qt_cookie.setPath(cookie_data['path']) if 'expiry' in cookie_data: # 转换为QDateTime expiry = QDateTime.fromSecsSinceEpoch(cookie_data['expiry']) qt_cookie.setExpirationDate(expiry) # 设置安全属性 qt_cookie.setSecure(cookie_data.get('secure', False)) qt_cookie.setHttpOnly(cookie_data.get('httpOnly', False)) # 注入Cookie store.setCookie(qt_cookie, QUrl(cookie_data.get('url', 'https://www.bilibili.com'))) self.status_bar.showMessage("Cookie注入成功,请刷新页面", 3000) def save_account(self): """保存当前账号配置""" account_name, ok = QInputDialog.getText( self, "保存账号", "输入账号名称:" ) if ok and account_name: cookies = self.get_current_cookies() if cookies: self.account_db[account_name] = { "cookies": cookies, "saved_date": datetime.now().isoformat() } self.account_list.addItem(account_name) # 持久化存储账号数据 try: with open("accounts.json", "w", encoding='utf-8') as f: json.dump(self.account_db, f, indent=2, ensure_ascii=False) QMessageBox.information(self, "成功", "账号保存成功") except Exception as e: QMessageBox.critical(self, "保存失败", f"账号保存失败: {str(e)}") else: QMessageBox.warning(self, "错误", "没有获取到有效Cookie") def switch_account(self, item): """切换账号""" account_name = item.text() if account_name in self.account_db: cookies = self.account_db[account_name].get("cookies", []) self.inject_cookies(cookies) self.status_bar.showMessage(f"已切换至账号: {account_name}", 3000) # 自动刷新当前页面 self.reload_page() else: QMessageBox.warning(self, "错误", "找不到该账号的Cookie信息") def load_accounts(self): """从文件加载保存的账号""" try: if os.path.exists("accounts.json"): with open("accounts.json", "r", encoding='utf-8') as f: self.account_db = json.load(f) self.account_list.addItems(self.account_db.keys()) except Exception as e: print(f"加载账号失败: {str(e)}") def closeEvent(self, event): """窗口关闭时释放全局资源[8](@ref)""" # 清理HTTP缓存和访问记录 self.profile.clearHttpCache() self.profile.clearAllVisitedLinks() # 保存账号数据 try: with open("accounts.json", "w", encoding='utf-8') as f: json.dump(self.account_db, f, indent=2, ensure_ascii=False) except Exception as e: print(f"保存账号失败: {str(e)}") super().closeEvent(event) if __name__ == "__main__": # 关键性能优化: 启用WebEngine调试日志[9](@ref) os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--enable-logging" app = QApplication(sys.argv) # 关键性能优化: 启用Qt内置的OpenGL渲染[7](@ref) app.setAttribute(Qt.AA_UseOpenGLES) window = MainWindow() window.show() sys.exit(app.exec_())

下面会给你一段简易进销存系统的代码片段,现在出现问题我无法解决,请你帮我分析并解决问题。我会给你项目的文件结构,如果你发现所给你的文件代码无法分析出问题,可以向我索要其余需要的文件代码。 问题:采购管理模块内采购订单功能内,在新建订单添加物料时点击ok添加无效,随即程序崩溃。 文件结构: main.py user_auth ----auth_manager.py ----change_password_dialog.py ----user_auth_db.py ----first_login_dialog.py ----login_window.py ----password_utils.py ----user_model.py ----app_data.db window_main ----app_styles.qss ----main_window.py ----module_buttons.py ----navigation.py ----status_bar.py basicData_manager ----basic_data_db.py ----basic_data_manager.py ----basic_data_models.py ----customer_manager.py ----product_manager.py ----supplier_manager.py purchase_manager ----purchase_db.py ----purchase_in_manager.py ----purchase_main.py ----purchase_models.py ----purchase_order_manager.py ----purchase_payment_manager.py ----purchase_return_manager.py # purchase_manager/purchase_order_manager from PyQt5.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, QPushButton, QHeaderView, QLineEdit, QLabel, QFormLayout, QDialog, QDialogButtonBox, QMessageBox, QTabWidget, QComboBox, QDateEdit, QAbstractItemView, QSplitter, QSizePolicy ) from PyQt5.QtCore import Qt, QDate from PyQt5.QtGui import QBrush, QColor from purchase_manager.purchase_db import PurchaseDB from purchase_manager.purchase_models import PurchaseOrder, PurchaseOrderItem from basicData_manager.basic_data_db import BasicDataDB class PurchaseOrderFormDialog(QDialog): def __init__(self, parent=None, order=None): super().__init__(parent) self.setWindowTitle("编辑采购订单" if order else "新建采购订单") self.setMinimumSize(900, 650) self.db = BasicDataDB() self.purchase_db = PurchaseDB() self.order = order self.items = [] layout = QVBoxLayout() # 表单区域 form_layout = QFormLayout() # 订单编号(自动生成或只读) self.order_number_input = QLineEdit() self.order_number_input.setReadOnly(bool(order)) if not order: self.order_number_input.setText(self.generate_order_number()) # 供应商选择 self.supplier_combo = QComboBox() self.populate_suppliers() # 日期选择 self.order_date_edit = QDateEdit(QDate.currentDate()) self.order_date_edit.setCalendarPopup(True) self.expected_delivery_edit = QDateEdit(QDate.currentDate().addDays(7)) self.expected_delivery_edit.setCalendarPopup(True) form_layout.addRow("订单编号:", self.order_number_input) form_layout.addRow("供应商:", self.supplier_combo) form_layout.addRow("订单日期:", self.order_date_edit) form_layout.addRow("预计交货日期:", self.expected_delivery_edit) # 物料明细表格 self.items_table = QTableWidget() self.items_table.setColumnCount(9) self.items_table.setHorizontalHeaderLabels([ "ID", "物料编码", "物料名称", "规格", "单位", "数量", "税前单价", "税率", "金额" ]) self.items_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeToContents) self.items_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch) self.items_table.setSelectionBehavior(QTableWidget.SelectRows) self.items_table.setEditTriggers(QTableWidget.DoubleClicked) # 允许编辑数量 # 添加物料按钮 add_item_btn = QPushButton("添加物料") add_item_btn.clicked.connect(self.add_item) # 删除物料按钮 remove_item_btn = QPushButton("删除选中物料") remove_item_btn.clicked.connect(self.remove_selected_items) button_layout = QHBoxLayout() button_layout.addWidget(add_item_btn) button_layout.addWidget(remove_item_btn) button_layout.addStretch() # 合计金额 self.total_amount_label = QLabel("0.00") form_layout.addRow("合计金额:", self.total_amount_label) layout.addLayout(form_layout) layout.addWidget(QLabel("订单明细:")) layout.addWidget(self.items_table) layout.addLayout(button_layout) # 初始化数据 if order: self.load_order_data(order) else: # 初始加载空表格 self.items_table.setRowCount(0) # 按钮区域 button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) button_box.accepted.connect(self.validate_and_accept) button_box.rejected.connect(self.reject) layout.addWidget(button_box) self.items_table.itemChanged.connect(self.on_item_changed) self.setLayout(layout) def generate_order_number(self): """生成唯一的订单编号""" from datetime import datetime return f"{datetime.now().strftime('%Y%m%d%H%M%S')}" def populate_suppliers(self): """填充已审核的供应商""" try: suppliers = self.db.get_suppliers(status='approved') self.supplier_combo.clear() for supplier in suppliers: self.supplier_combo.addItem(f"{supplier[1]} - {supplier[2]}", supplier[0]) except Exception as e: print(f"加载供应商错误: {str(e)}") def load_order_data(self, order): """加载订单数据""" self.order_number_input.setText(order.order_number) self.supplier_combo.setCurrentText(order.supplier_name) self.order_date_edit.setDate(QDate.fromString(order.order_date, "yyyy-MM-dd")) self.expected_delivery_edit.setDate(QDate.fromString(order.expected_delivery_date, "yyyy-MM-dd")) # 加载订单明细 order_items = self.purchase_db.get_purchase_order_details(order.id) self.items = [] self.items_table.setRowCount(len(order_items)) for row, item in enumerate(order_items): self.items.append({ "product_id": item['product_id'], "code": item['product_code'], "name": item['product_name'], "specification": item['specification'], "unit": item['unit'], "quantity": item['quantity'], "unit_price": item['unit_price'], "tax_rate": item['tax_rate'] }) self.items_table.setItem(row, 0, self.create_readonly_item(str(item['id']))) self.items_table.setItem(row, 1, self.create_readonly_item(item['product_code'])) self.items_table.setItem(row, 2, self.create_readonly_item(item['product_name'])) self.items_table.setItem(row, 3, self.create_readonly_item(item['specification'])) self.items_table.setItem(row, 4, self.create_readonly_item(item['unit'])) self.items_table.setItem(row, 5, QTableWidgetItem(str(item['quantity']))) self.items_table.setItem(row, 6, QTableWidgetItem(f"{item['unit_price']:.2f}")) self.items_table.setItem(row, 7, QTableWidgetItem(f"{item['tax_rate'] * 100:.0f}%")) self.items_table.setItem(row, 8, self.create_readonly_item(f"{item['total_price']:.2f}")) self.update_total_amount() def add_item(self): """打开物料选择对话框""" print("打开物料选择对话框...") dialog = ProductSelectionDialog(self) if dialog.exec_() == QDialog.Accepted: print(f"物料选择对话框返回: 选择了 {len(dialog.selected_products)} 个物料") for i, product in enumerate(dialog.selected_products): print(f"添加物料 {i + 1}: ID={product['id']}, 名称={product['name']}") # 检查是否已添加过该物料 existing = next((item for item in self.items if item.get("id") == product["id"]), None) if existing: print(f"物料 {product['id']} 已存在,增加数量") # ... 现有代码 ... else: print(f"物料 {product['id']} 是新物料,添加到表格") self.add_item_to_table(product) # def add_item(self): # """打开物料选择对话框""" # dialog = ProductSelectionDialog(self) # if dialog.exec_() == QDialog.Accepted: # # # 测试输出 # print("02 - Selected Products to Add:") # print(dialog.selected_products) # # for product in dialog.selected_products: # # 检查是否已添加过该物料(通过ID检查) # existing = next((item for item in self.items if item.get("id") == product["id"]), None) # # if existing: # # 如果已存在,增加数量 # row_index = self.items.index(existing) # current_qty = int(self.items_table.item(row_index, 5).text()) # new_qty = current_qty + 1 # # # 更新表格 # self.items_table.item(row_index, 5).setText(str(new_qty)) # # # 更新内存中的数据 # self.items[row_index]["quantity"] = new_qty # else: # # 添加新物料 # self.add_item_to_table(product) def add_item_to_table(self, product): """添加物料到表格""" row = self.items_table.rowCount() self.items_table.insertRow(row) # 创建物料数据对象(包含所有必要属性) item_data = { "id": product["id"], "code": product["code"], "name": product["name"], "specification": product["specification"], "unit": product["unit"], "quantity": 1, # 默认数量为1 "unit_price": product["purchase_price"], "tax_rate": product["tax_rate"] } self.items.append(item_data) # 设置表格项 self.items_table.setItem(row, 0, self.create_readonly_item(str(product["id"]))) self.items_table.setItem(row, 1, self.create_readonly_item(product["code"])) self.items_table.setItem(row, 2, self.create_readonly_item(product["name"])) self.items_table.setItem(row, 3, self.create_readonly_item(product["specification"])) self.items_table.setItem(row, 4, self.create_readonly_item(product["unit"])) self.items_table.setItem(row, 5, QTableWidgetItem("1")) # 数量 self.items_table.setItem(row, 6, QTableWidgetItem(f"{product['purchase_price']:.2f}")) self.items_table.setItem(row, 7, QTableWidgetItem(f"{product['tax_rate'] * 100:.0f}%")) # 计算并设置总价 total_price = product['purchase_price'] * (1 + product['tax_rate']) self.items_table.setItem(row, 8, self.create_readonly_item(f"{total_price:.2f}")) self.update_total_amount() def create_readonly_item(self, text): """创建只读表格项""" item = QTableWidgetItem(text) item.setFlags(item.flags() & ~Qt.ItemIsEditable) return item def remove_selected_items(self): """删除选中物料""" selected_rows = sorted(set(index.row() for index in self.items_table.selectedIndexes())) if not selected_rows: QMessageBox.warning(self, "选择错误", "请先选择要删除的物料行") return for row in reversed(selected_rows): self.items_table.removeRow(row) del self.items[row] self.update_total_amount() def update_total_amount(self): """更新总金额""" total = 0.0 for row in range(self.items_table.rowCount()): quantity = float(self.items_table.item(row, 5).text() or 0) unit_price = float(self.items_table.item(row, 6).text() or 0) tax_rate = float(self.items_table.item(row, 7).text().rstrip('%')) / 100 total += quantity * unit_price * (1 + tax_rate) self.total_amount_label.setText(f"{total:.2f}") def validate_and_accept(self): """验证表单并接受""" if not self.items: QMessageBox.warning(self, "验证错误", "订单必须包含至少一个物料") return if self.supplier_combo.currentIndex() == -1: QMessageBox.warning(self, "验证错误", "请选择供应商") return self.accept() def get_order_data(self): """获取订单数据""" order_data = { "order_number": self.order_number_input.text(), "supplier_id": self.supplier_combo.currentData(), "order_date": self.order_date_edit.date().toString("yyyy-MM-dd"), "expected_delivery_date": self.expected_delivery_edit.date().toString("yyyy-MM-dd"), "total_amount": float(self.total_amount_label.text()), "items": [] } for row in range(self.items_table.rowCount()): item = { "product_id": int(self.items_table.item(row, 0).text()), "quantity": int(self.items_table.item(row, 5).text()), "unit_price": float(self.items_table.item(row, 6).text()), "tax_rate": float(self.items_table.item(row, 7).text().rstrip('%')) / 100 } order_data["items"].append(item) return order_data def on_item_changed(self, item): """当物料数量改变时更新金额""" if item.column() in (5, 6, 7): # 数量、单价或税率列改变 row = item.row() try: quantity = float(self.items_table.item(row, 5).text() or 0) unit_price = float(self.items_table.item(row, 6).text() or 0) tax_rate = float(self.items_table.item(row, 7).text().rstrip('%') or 0) / 100 total_price = quantity * unit_price * (1 + tax_rate) # 更新金额列 self.items_table.item(row, 8).setText(f"{total_price:.2f}") # 更新总金额 self.update_total_amount() except ValueError: # 输入无效时跳过 pass class ProductSelectionDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("选择物料") self.setFixedSize(800, 500) self.selected_product = None self.db = BasicDataDB() self.selected_products = [] layout = QVBoxLayout() # 搜索区域 search_layout = QHBoxLayout() self.search_input = QLineEdit() self.search_input.setPlaceholderText("输入物料名称或编码...") self.search_input.textChanged.connect(self.load_products) search_layout.addWidget(QLabel("搜索:")) search_layout.addWidget(self.search_input) layout.addLayout(search_layout) # 物料表格 self.products_table = QTableWidget() self.products_table.setColumnCount(8) self.products_table.setHorizontalHeaderLabels([ "选择","ID", "物料编码", "物料名称", "规格", "单位", "税前进价", "税率" ])# 拓展新列 ——税率 self.products_table.verticalHeader().setVisible(False) self.products_table.setSelectionBehavior(QAbstractItemView.SelectRows) self.products_table.setSelectionMode(QAbstractItemView.SingleSelection) self.products_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeToContents) self.products_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch) self.products_table.horizontalHeader().setSectionResizeMode(3, QHeaderView.Stretch) self.products_table.setSelectionMode(QAbstractItemView.MultiSelection) self.products_table.setSelectionBehavior(QAbstractItemView.SelectRows) layout.addWidget(self.products_table) # 按钮区域 button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) button_box.accepted.connect(self.on_accept) button_box.rejected.connect(self.reject) layout.addWidget(button_box) self.setLayout(layout) # 初始加载数据 self.load_products() def load_products(self, search_term=None): """加载物料数据并应用搜索过滤""" try: products = self.db.get_products(status='approved') # 应用搜索过滤 if search_term: search_term = search_term.lower().strip() products = [p for p in products if search_term in p[1].lower() or # 物料编码 search_term in p[2].lower()] # 物料名称 self.products_table.setRowCount(len(products)) for row, product in enumerate(products): # 选择框 select_item = QTableWidgetItem() select_item.setCheckState(Qt.Unchecked) self.products_table.setItem(row, 0, select_item) # 物料数据 self.products_table.setItem(row, 1, QTableWidgetItem(str(product[0]))) # ID self.products_table.setItem(row, 2, QTableWidgetItem(product[1])) # 编码 self.products_table.setItem(row, 3, QTableWidgetItem(product[2])) # 名称 self.products_table.setItem(row, 4, QTableWidgetItem(product[4])) # 规格 self.products_table.setItem(row, 5, QTableWidgetItem(product[5])) # 单位 self.products_table.setItem(row, 6, QTableWidgetItem(f"{product[6]:.2f}")) # 进价 self.products_table.setItem(row, 7, QTableWidgetItem(f"{product[7]:.2f}")) # 税率 # 设置整行选择 self.products_table.setSelectionBehavior(QAbstractItemView.SelectRows) except Exception as e: print(f"加载物料错误: {str(e)}") def on_accept(self): """处理确定按钮点击""" self.selected_products = [] for row in range(self.products_table.rowCount()): if self.products_table.item(row, 0).checkState() == Qt.Checked: product = { "id": int(self.products_table.item(row, 1).text()), # 索引1 "code": self.products_table.item(row, 2).text(), # 索引2 "name": self.products_table.item(row, 3).text(), # 索引3 "specification": self.products_table.item(row, 4).text(), # 索引4 "unit": self.products_table.item(row, 5).text(), # 索引5 "purchase_price": float(self.products_table.item(row, 6).text()), # 索引6 "tax_rate": float(self.products_table.item(row, 7).text()) # 索引7 } self.selected_products.append(product) # 测试输出 - 可以保留用于调试 print("01 - Selected Products:") print(self.selected_products) if not self.selected_products: QMessageBox.warning(self, "选择错误", "请至少选择一个物料") return self.accept() class PurchaseOrderManager(QWidget): def __init__(self, current_user, db, parent=None): super().__init__(parent) self.current_user = current_user # 确保传入正确的数据库对象 if db is None or not hasattr(db, 'get_purchase_orders'): # 创建正确的数据库实例 self.db = PurchaseDB() print(f"创建新的 PurchaseDB 实例: {type(self.db)}") else: self.db = db print(f"使用传入的 PurchaseDB 实例: {type(self.db)}") self.basic_db = BasicDataDB() self.init_ui() self.load_orders() def init_ui(self): layout = QVBoxLayout() # 顶部按钮区域 button_layout = QHBoxLayout() self.new_btn = QPushButton("新建订单") self.new_btn.clicked.connect(self.show_new_form) self.edit_btn = QPushButton("修改订单") self.edit_btn.clicked.connect(self.show_edit_form) self.submit_btn = QPushButton("提交订单") self.submit_btn.clicked.connect(self.submit_order) self.approve_btn = QPushButton("审核订单") self.approve_btn.clicked.connect(self.approve_order) self.refresh_btn = QPushButton("刷新") self.refresh_btn.clicked.connect(self.load_orders) button_layout.addWidget(self.new_btn) button_layout.addWidget(self.edit_btn) button_layout.addWidget(self.submit_btn) button_layout.addWidget(self.approve_btn) button_layout.addStretch() button_layout.addWidget(self.refresh_btn) # 搜索区域 search_layout = QHBoxLayout() self.status_combo = QComboBox() self.status_combo.addItem("全部状态", "") self.status_combo.addItem("草稿", "draft") self.status_combo.addItem("已提交", "submitted") self.status_combo.addItem("已审核", "approved") self.status_combo.addItem("已收货", "received") self.status_combo.addItem("已取消", "canceled") self.status_combo.currentIndexChanged.connect(self.load_orders) self.supplier_combo = QComboBox() self.populate_suppliers() self.supplier_combo.currentIndexChanged.connect(self.load_orders) self.date_from = QDateEdit(QDate.currentDate().addMonths(-1)) self.date_from.setCalendarPopup(True) self.date_from.dateChanged.connect(self.load_orders) self.date_to = QDateEdit(QDate.currentDate()) self.date_to.setCalendarPopup(True) self.date_to.dateChanged.connect(self.load_orders) search_layout.addWidget(QLabel("状态:")) search_layout.addWidget(self.status_combo) search_layout.addWidget(QLabel("供应商:")) search_layout.addWidget(self.supplier_combo) search_layout.addWidget(QLabel("日期从:")) search_layout.addWidget(self.date_from) search_layout.addWidget(QLabel("到:")) search_layout.addWidget(self.date_to) # 订单表格 self.orders_table = QTableWidget() self.orders_table.setColumnCount(8) self.orders_table.setHorizontalHeaderLabels([ "ID", "订单编号", "供应商", "订单日期", "交货日期", "总金额", "状态", "创建人" ]) self.orders_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) self.orders_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch) self.orders_table.setSelectionBehavior(QTableWidget.SelectRows) self.orders_table.setEditTriggers(QTableWidget.NoEditTriggers) self.orders_table.doubleClicked.connect(self.show_order_details) # 订单明细表格 self.items_table = QTableWidget() self.items_table.setColumnCount(7) self.items_table.setHorizontalHeaderLabels([ "物料编码", "物料名称", "规格", "单位", "数量", "单价", "金额" ]) self.items_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) self.items_table.setEditTriggers(QTableWidget.NoEditTriggers) # 使用分割器 splitter = QSplitter(Qt.Vertical) splitter.addWidget(self.orders_table) splitter.addWidget(self.items_table) splitter.setSizes([300, 200]) layout.addLayout(button_layout) layout.addLayout(search_layout) layout.addWidget(splitter) self.setLayout(layout) # 根据用户角色设置按钮可见性 if self.current_user.role != "root": self.approve_btn.setVisible(False) def populate_suppliers(self): """填充供应商下拉框""" try: suppliers = self.basic_db.get_suppliers(status='approved') self.supplier_combo.clear() self.supplier_combo.addItem("全部供应商", -1) for supplier in suppliers: self.supplier_combo.addItem(f"{supplier[1]} - {supplier[2]}", supplier[0]) except Exception as e: print(f"加载供应商错误: {str(e)}") def load_orders(self): """加载采购订单""" try: status = self.status_combo.currentData() supplier_id = self.supplier_combo.currentData() if supplier_id == -1: supplier_id = None start_date = self.date_from.date().toString("yyyy-MM-dd") end_date = self.date_to.date().toString("yyyy-MM-dd") orders = self.db.get_purchase_orders( status=status, supplier_id=supplier_id, start_date=start_date, end_date=end_date ) self.orders_table.setRowCount(len(orders)) for row, order in enumerate(orders): self.orders_table.setItem(row, 0, QTableWidgetItem(str(order[0]))) # ID self.orders_table.setItem(row, 1, QTableWidgetItem(order[1])) # 订单编号 self.orders_table.setItem(row, 2, QTableWidgetItem(order[11])) # 供应商名称 self.orders_table.setItem(row, 3, QTableWidgetItem(order[3])) # 订单日期 self.orders_table.setItem(row, 4, QTableWidgetItem(order[4] or "")) # 交货日期 self.orders_table.setItem(row, 5, QTableWidgetItem(f"{order[5]:.2f}")) # 总金额 # 状态列 status_item = QTableWidgetItem(order[6]) if order[6] == "draft": status_item.setForeground(QBrush(QColor("gray"))) elif order[6] == "submitted": status_item.setForeground(QBrush(QColor("blue"))) elif order[6] == "approved": status_item.setForeground(QBrush(QColor("green"))) elif order[6] == "received": status_item.setForeground(QBrush(QColor("darkgreen"))) elif order[6] == "canceled": status_item.setForeground(QBrush(QColor("red"))) self.orders_table.setItem(row, 6, status_item) self.orders_table.setItem(row, 7, QTableWidgetItem(order[8])) # 创建人 # 清除明细表格 self.items_table.setRowCount(0) except Exception as e: print(f"加载订单错误: {str(e)}") QMessageBox.critical(self, "错误", f"加载订单失败: {str(e)}") # 添加更详细的错误信息 import traceback traceback.print_exc() QMessageBox.critical(self, "错误", f"加载订单失败: {str(e)}") def get_selected_order_id(self): """获取选中的订单ID""" selected = self.orders_table.selectedItems() if not selected: return None return int(self.orders_table.item(selected[0].row(), 0).text()) def show_new_form(self): """显示新建订单表单""" dialog = PurchaseOrderFormDialog(self) if dialog.exec_() == QDialog.Accepted: try: order_data = dialog.get_order_data() order_id = self.db.create_purchase_order(order_data, self.current_user.username) QMessageBox.information(self, "成功", f"采购订单 #{order_id} 创建成功!") self.load_orders() except Exception as e: QMessageBox.critical(self, "错误", f"创建采购订单失败: {str(e)}") def show_edit_form(self): """显示编辑订单表单""" order_id = self.get_selected_order_id() if not order_id: QMessageBox.warning(self, "选择错误", "请先选择一个订单") return # 获取订单详情 orders = self.db.get_purchase_orders() order_data = next((o for o in orders if o[0] == order_id), None) if not order_data: QMessageBox.warning(self, "错误", "找不到选中的订单") return # 检查订单状态 if order_data[6] not in ["draft", "submitted"]: QMessageBox.warning(self, "操作受限", "只能编辑草稿或已提交状态的订单") return # 创建订单对象 order = PurchaseOrder( id=order_data[0], order_number=order_data[1], supplier_id=order_data[2], supplier_name=order_data[11], order_date=order_data[3], expected_delivery_date=order_data[4], total_amount=order_data[5], status=order_data[6], created_by=order_data[8], created_at=order_data[9] ) dialog = PurchaseOrderFormDialog(self, order) if dialog.exec_() == QDialog.Accepted: try: update_data = dialog.get_order_data() self.db.update_purchase_order(order_id, update_data, self.current_user.username) QMessageBox.information(self, "成功", "采购订单更新成功!") self.load_orders() except Exception as e: QMessageBox.critical(self, "错误", f"更新采购订单失败: {str(e)}") def submit_order(self): """提交订单""" order_id = self.get_selected_order_id() if not order_id: QMessageBox.warning(self, "选择错误", "请先选择一个订单") return # 检查订单状态 orders = self.db.get_purchase_orders() order_data = next((o for o in orders if o[0] == order_id), None) if not order_data: QMessageBox.warning(self, "错误", "找不到选中的订单") return if order_data[6] != "draft": QMessageBox.warning(self, "操作受限", "只能提交草稿状态的订单") return try: self.db.submit_purchase_order(order_id, self.current_user.username) QMessageBox.information(self, "成功", "采购订单提交成功!") self.load_orders() except Exception as e: QMessageBox.critical(self, "错误", f"提交采购订单失败: {str(e)}") def approve_order(self): """审核订单""" order_id = self.get_selected_order_id() if not order_id: QMessageBox.warning(self, "选择错误", "请先选择一个订单") return # 检查订单状态 orders = self.db.get_purchase_orders() order_data = next((o for o in orders if o[0] == order_id), None) if not order_data: QMessageBox.warning(self, "错误", "找不到选中的订单") return if order_data[6] != "submitted": QMessageBox.warning(self, "操作受限", "只能审核已提交状态的订单") return try: self.db.approve_purchase_order(order_id, self.current_user.username) QMessageBox.information(self, "成功", "采购订单审核通过!") self.load_orders() except Exception as e: QMessageBox.critical(self, "错误", f"审核采购订单失败: {str(e)}") def show_order_details(self, index): """显示订单明细""" row = index.row() order_id = int(self.orders_table.item(row, 0).text()) try: items = self.db.get_purchase_order_details(order_id) self.items_table.setRowCount(len(items)) for row, item in enumerate(items): self.items_table.setItem(row, 0, QTableWidgetItem(item['product_code'])) self.items_table.setItem(row, 1, QTableWidgetItem(item['product_name'])) self.items_table.setItem(row, 2, QTableWidgetItem(item['specification'])) self.items_table.setItem(row, 3, QTableWidgetItem(item['unit'])) self.items_table.setItem(row, 4, QTableWidgetItem(str(item['quantity']))) self.items_table.setItem(row, 5, QTableWidgetItem(f"{item['unit_price']:.2f}")) self.items_table.setItem(row, 6, QTableWidgetItem(f"{item['total_price']:.2f}")) except Exception as e: print(f"加载订单明细错误: {str(e)}")

import sys import json import csv import os import time import re import requests import concurrent.futures from urllib.parse import urlparse, urljoin from requests.exceptions import RequestException from PyQt5.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QTabWidget, QLabel, QLineEdit, QPushButton, QTextEdit, QTableWidget, QTableWidgetItem, QTreeWidget, QTreeWidgetItem, QHeaderView, QFileDialog, QMessageBox, QSplitter, QSpinBox, QAction, QDialog, QFormLayout, QDialogButtonBox, QProgressBar, QGroupBox ) from PyQt5.QtCore import Qt, QThread, pyqtSignal from PyQt5.QtGui import QIcon # 忽略SSL证书验证的警告信息 requests.packages.urllib3.disable_warnings() class FingerprintManager: def __init__(self): self.fingerprints = [] self.default_file_path = os.path.join(os.path.expanduser("~"), "cms_fingerprints.json") if not self.load_from_default_file(): self.load_default_fingerprints() def load_default_fingerprints(self): # 优化默认指纹库,确保正则表达式正确 self.fingerprints = [ { "cms": "WordPress", "version": "", "confidence": 0, "http_headers": [ {"header": "X-Powered-By", "pattern": "PHP/.*", "score": 10, "type": "general"} ], "html_content": [ {"pattern": "<meta name=\"generator\" content=\"WordPress ([\\d.]+)\"", "score": 150, "type": "core", "version_group": 1}, {"pattern": "wp-content/themes/([^/]+)", "score": 80, "type": "specific"}, {"pattern": "wp-includes/js/wp-util.js", "score": 90, "type": "specific"} ], "url_paths": [ {"path": "/wp-admin", "score": 80, "type": "specific"}, {"path": "/wp-login.php", "score": 100, "type": "core"} ] }, { "cms": "示例站点", "version": "", "confidence": 0, "html_content": [ {"pattern": "恭喜, 站点创建成功!", "score": 120, "type": "core"}, {"pattern": "这是默认index.html,本页面由系统自动生成", "score": 100, "type": "core"} ], "url_paths": [] }, { "cms": "Nginx", "version": "", "confidence": 0, "http_headers": [ {"header": "Server", "pattern": "nginx/([\\d.]+)", "score": 90, "type": "core", "version_group": 1} ], "html_content": [ {"pattern": "If you see this page, the nginx web server is successfully installed", "score": 120, "type": "core"} ] }, { "cms": "Drupal", "version": "", "html_content": [ {"pattern": "<meta name=\"generator\" content=\"Drupal ([\\d.]+)\"", "score": 150, "type": "core", "version_group": 1}, {"pattern": "sites/default/files", "score": 70, "type": "specific"} ], "url_paths": [ {"path": "/sites/all", "score": 80, "type": "specific"} ] }, { "cms": "ThinkPHP", "version": "", "html_content": [ {"pattern": "think\\\\Exception", "score": 100, "type": "core"}, {"pattern": "app\\\\controller", "score": 80, "type": "specific"} ] }, { "cms": "Yii", "version": "", "html_content": [ {"pattern": "yii\\\\base\\\\Exception", "score": 100, "type": "core"}, {"pattern": "yii\\\\web\\\\HttpException", "score": 90, "type": "specific"} ] }, { "cms": "Phalcon", "version": "", "html_content": [ {"pattern": "Phalcon\\\\Exception", "score": 100, "type": "core"} ] }, { "cms": "FuelPHP", "version": "", "html_content": [ {"pattern": "Fuel\\\\Exception", "score": 100, "type": "core"} ] }, { "cms": "Habari", "version": "", "html_content": [ {"pattern": "Habari\\\\Core\\\\Exception", "score": 100, "type": "core"} ] }, { "cms": "帝国CMS", "version": "", "html_content": [ {"pattern": "ecmsinfo\\(", "score": 100, "type": "core"} ] } ] self.save_to_default_file() def load_from_default_file(self): try: if os.path.exists(self.default_file_path): with open(self.default_file_path, 'r', encoding='utf-8') as f: loaded_data = json.load(f) valid_fingerprints = [] for fp in loaded_data: if self._is_valid_fingerprint(fp): cleaned_fp = self._clean_fingerprint(fp) valid_fingerprints.append(cleaned_fp) else: print(f"跳过无效指纹: {fp}") self.fingerprints = valid_fingerprints return True return False except Exception as e: print(f"从默认文件加载指纹失败: {e}") return False def _clean_fingerprint(self, fp): """清理指纹中的正则表达式,修复常见错误""" for header in fp.get('http_headers', []): if 'pattern' in header: header['pattern'] = self._fix_regex_pattern(header['pattern']) for html in fp.get('html_content', []): if 'pattern' in html: html['pattern'] = self._fix_regex_pattern(html['pattern']) for url in fp.get('url_paths', []): if 'pattern' in url: url['pattern'] = self._fix_regex_pattern(url['pattern']) return fp def _fix_regex_pattern(self, pattern): """修复常见的正则表达式错误""" if not pattern: return "" # 修复未转义的反斜杠 fixed = re.sub(r'(?<!\\)\\(?!["\\/])', r'\\\\', pattern) # 修复未闭合的括号 open_count = fixed.count('(') close_count = fixed.count(')') if open_count > close_count: fixed += ')' * (open_count - close_count) # 修复不完整的字符类 if '[' in fixed and ']' not in fixed: fixed += ']' return fixed def _is_valid_fingerprint(self, fp): required_fields = ["cms"] for field in required_fields: if field not in fp: return False if not fp["cms"].strip(): return False for key in ["http_headers", "html_content", "url_paths"]: if key not in fp: fp[key] = [] return True def save_to_default_file(self): try: dir_path = os.path.dirname(self.default_file_path) if not os.path.exists(dir_path): os.makedirs(dir_path) with open(self.default_file_path, 'w', encoding='utf-8') as f: json.dump(self.fingerprints, f, indent=4, ensure_ascii=False) return True except Exception as e: print(f"保存指纹到默认文件失败: {e}") return False def add_fingerprint(self, fingerprint): if self._is_valid_fingerprint(fingerprint): cleaned = self._clean_fingerprint(fingerprint) self.fingerprints.append(cleaned) self.save_to_default_file() return True print(f"无法添加无效指纹: {fingerprint}") return False def remove_fingerprint(self, index): if 0 <= index < len(self.fingerprints): self.fingerprints.pop(index) self.save_to_default_file() def update_fingerprint(self, index, fingerprint): if 0 <= index < len(self.fingerprints) and self._is_valid_fingerprint(fingerprint): cleaned = self._clean_fingerprint(fingerprint) self.fingerprints[index] = cleaned self.save_to_default_file() return True return False def clear_fingerprints(self): self.fingerprints = [] self.save_to_default_file() return True def restore_default_fingerprints(self): self.load_default_fingerprints() return True def get_fingerprints(self): return self.fingerprints def export_fingerprints(self, filename): try: with open(filename, 'w', encoding='utf-8') as f: json.dump(self.fingerprints, f, indent=4, ensure_ascii=False) return True except Exception as e: print(f"导出失败: {e}") return False def import_fingerprints(self, filename): try: with open(filename, 'r', encoding='utf-8') as f: imported_data = json.load(f) valid_fingerprints = [] for fp in imported_data: if self._is_valid_fingerprint(fp): cleaned = self._clean_fingerprint(fp) valid_fingerprints.append(cleaned) else: print(f"导入时跳过无效指纹: {fp}") if valid_fingerprints: self.fingerprints = valid_fingerprints self.save_to_default_file() return True print("导入的指纹全部无效") return False except Exception as e: print(f"导入失败: {e}") return False class DetectionWorker(QThread): progress_signal = pyqtSignal(int, int, str) result_signal = pyqtSignal(dict) log_signal = pyqtSignal(str) finished_signal = pyqtSignal() def __init__(self, urls, fingerprints, max_threads=10, retry_count=2): super().__init__() self.urls = urls self.fingerprints = fingerprints self.max_threads = max_threads self.running = True self.retry_count = retry_count self.timeout = 15 # 超时时间(秒) # 缓存响应以提高性能 self.response_cache = {} def run(self): self.log_signal.emit("开始检测...") total = len(self.urls) for i, url in enumerate(self.urls): if not self.running: break self.progress_signal.emit(i+1, total, url) result = self.detect_cms(url) self.result_signal.emit(result) self.log_signal.emit("检测完成!") self.finished_signal.emit() def stop(self): self.running = False def preprocess_html(self, html): """优化HTML预处理:保留标签结构,不过度压缩""" processed = re.sub(r'\n\s+', '\n', html) processed = re.sub(r'>\s+<', '><', processed) return processed.strip() def escape_special_chars(self, pattern): """安全转义正则特殊字符""" if not pattern: return "" safe_pattern = re.sub(r'\\(?![\\.*+?^${}()|[\]sSdDwWtnbfvr])', r'\\\\', pattern) return safe_pattern def validate_regex(self, pattern): """验证正则表达式是否有效""" if not pattern: return True, pattern try: re.compile(pattern) return True, pattern except re.error as e: fixed = pattern if "bad escape" in str(e): fixed = re.sub(r'(?<!\\)\\(?!["\\/])', r'\\\\', pattern) elif "unterminated subpattern" in str(e): open_count = pattern.count('(') close_count = pattern.count(')') if open_count > close_count: fixed = pattern + ')' * (open_count - close_count) try: re.compile(fixed) self.log_signal.emit(f"自动修复正则表达式: {pattern} -> {fixed}") return True, fixed except re.error: return False, pattern def extract_version(self, content, pattern, group_idx): """从匹配结果中提取版本号""" if not pattern or group_idx is None: return "" try: match = re.search(pattern, content, re.IGNORECASE) if match and len(match.groups()) >= group_idx: return match.group(group_idx).strip() except re.error as e: self.log_signal.emit(f"版本提取正则错误 {pattern}: {str(e)}") return "" def fetch_url_content(self, url): """带重试机制的URL内容获取""" # 检查缓存 if url in self.response_cache: return self.response_cache[url] headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9' } for attempt in range(self.retry_count + 1): try: response = requests.get( url, headers=headers, allow_redirects=True, verify=False, timeout=self.timeout ) response.encoding = response.apparent_encoding # 缓存响应 self.response_cache[url] = response return response except RequestException as e: self.log_signal.emit(f"请求尝试 {attempt+1} 失败: {str(e)}") if attempt >= self.retry_count: return None time.sleep(1) return None def build_full_url(self, base_url, path): """构建完整的URL""" if not path.startswith('/'): path = '/' + path parsed = urlparse(base_url) return f"{parsed.scheme}://{parsed.netloc}{path}" def check_url_path(self, base_url, path, pattern, item_score, weight): """检查URL路径特征 - 主动访问并验证""" full_url = self.build_full_url(base_url, path) feature_desc = f"URL路径: {full_url}" # 尝试获取响应 response = self.fetch_url_content(full_url) if response and response.status_code == 200: # 如果有正则模式,检查内容 if pattern: is_valid, fixed_pattern = self.validate_regex(pattern) if is_valid: try: if re.search(fixed_pattern, response.text, re.IGNORECASE): return True, feature_desc, item_score * weight except re.error as e: self.log_signal.emit(f"URL路径正则错误: {str(e)}") # 如果没有正则模式,只要状态200就算匹配 else: return True, feature_desc, item_score * weight return False, feature_desc, 0 def detect_cms(self, url): original_url = url if not url.startswith(('http://', 'https://')): urls_to_try = [f'http://{url}', f'https://{url}'] else: urls_to_try = [url] response = None for test_url in urls_to_try: response = self.fetch_url_content(test_url) if response: url = test_url break if not response: return { "url": original_url, "status": -1, "results": [{"cms": "无法访问", "version": "", "confidence": 0, "judgment_basis": ["无法建立连接"]}], "primary": {"cms": "无法访问", "version": "", "confidence": 0} } status_code = response.status_code headers = response.headers html_content = response.text final_url = response.url processed_html = self.preprocess_html(html_content) self.log_signal.emit(f"获取内容: {final_url} (状态码: {status_code})") cms_matches = [] min_score_threshold = 50 for cms in self.fingerprints: total_score = 0 version = "" # 记录详细的判断依据 judgment_basis = [] matched_features = [] unmatched_features = [] # 1. 匹配HTTP头特征 for header_item in cms.get('http_headers', []): header_name = header_item.get('header', '').lower() pattern = header_item.get('pattern', '') item_score = header_item.get('score', 0) feature_type = header_item.get('type', 'general') if not header_name or not pattern: continue is_valid, fixed_pattern = self.validate_regex(pattern) if not is_valid: self.log_signal.emit(f"跳过无效HTTP头正则: {pattern}") continue weight = 2 if feature_type == 'core' else 1 adjusted_score = item_score * weight feature_desc = f"HTTP头[{header_name}]匹配模式[{fixed_pattern}]" if header_name in headers: header_value = str(headers[header_name]) try: if re.search(fixed_pattern, header_value, re.IGNORECASE): total_score += adjusted_score matched_features.append(f"{feature_desc} (+{adjusted_score})") judgment_basis.append(f"✓ {feature_desc},匹配成功,加{adjusted_score}分") if 'version_group' in header_item: version = self.extract_version( header_value, fixed_pattern, header_item['version_group'] ) or version else: unmatched_features.append(f"{feature_desc} (未匹配)") judgment_basis.append(f"✗ {feature_desc},未匹配") except re.error as e: self.log_signal.emit(f"HTTP头正则执行错误 {fixed_pattern}: {str(e)}") else: unmatched_features.append(f"{feature_desc} (Header不存在)") judgment_basis.append(f"✗ {feature_desc},Header不存在") # 2. 匹配HTML内容特征 for html_item in cms.get('html_content', []): pattern = html_item.get('pattern', '').strip() item_score = html_item.get('score', 0) feature_type = html_item.get('type', 'general') if not pattern: continue is_valid, fixed_pattern = self.validate_regex(pattern) if not is_valid: self.log_signal.emit(f"跳过无效HTML正则: {pattern}") continue weight = 2.5 if feature_type == 'core' else (1.5 if feature_type == 'specific' else 1) adjusted_score = int(item_score * weight) feature_desc = f"HTML内容匹配模式[{fixed_pattern[:50]}{'...' if len(fixed_pattern)>50 else ''}]" try: if '<' in fixed_pattern and '>' in fixed_pattern: escaped_pattern = self.escape_special_chars(fixed_pattern) flexible_pattern = re.sub(r'\s+', r'\\s+', escaped_pattern) match_found = re.search(flexible_pattern, processed_html, re.IGNORECASE | re.DOTALL) else: match_found = re.search(fixed_pattern, processed_html, re.IGNORECASE | re.DOTALL) if match_found: total_score += adjusted_score matched_features.append(f"{feature_desc} (+{adjusted_score})") judgment_basis.append(f"✓ {feature_desc},匹配成功,加{adjusted_score}分") if 'version_group' in html_item: version = self.extract_version( processed_html, fixed_pattern, html_item['version_group'] ) or version else: unmatched_features.append(f"{feature_desc} (未匹配)") judgment_basis.append(f"✗ {feature_desc},未匹配") except re.error as e: self.log_signal.emit(f"HTML正则执行错误 {fixed_pattern}: {str(e)}") # 3. 匹配URL路径特征 - 使用线程池并发处理 url_path_tasks = [] with concurrent.futures.ThreadPoolExecutor(max_workers=min(5, self.max_threads)) as executor: for url_item in cms.get('url_paths', []): path = url_item.get('path', '') pattern = url_item.get('pattern', '') item_score = url_item.get('score', 0) feature_type = url_item.get('type', 'general') if not path: continue weight = 2 if feature_type == 'core' else 1 adjusted_score = item_score * weight # 提交任务到线程池 task = executor.submit( self.check_url_path, final_url, path, pattern, item_score, weight ) url_path_tasks.append((task, adjusted_score, path)) # 处理URL路径特征结果 for task, adjusted_score, path in url_path_tasks: try: matched, desc, score = task.result() if matched: total_score += score matched_features.append(f"{desc} (+{score})") judgment_basis.append(f"✓ {desc},访问成功,加{score}分") else: unmatched_features.append(f"{desc} (访问失败或未匹配)") judgment_basis.append(f"✗ {desc},访问失败或未匹配") except Exception as e: self.log_signal.emit(f"URL路径检查出错: {str(e)}") # 计算置信度 max_possible = sum( (h.get('score', 0) * (2 if h.get('type') == 'core' else 1)) for h in cms.get('http_headers', []) ) + sum( (h.get('score', 0) * (2.5 if h.get('type') == 'core' else 1)) for h in cms.get('html_content', []) ) + sum( (u.get('score', 0) * (2 if u.get('type') == 'core' else 1)) for u in cms.get('url_paths', []) ) confidence = min(100, int((total_score / max_possible) * 100)) if max_possible > 0 else 0 # 汇总判断依据 if matched_features: judgment_basis.insert(0, f"匹配到{len(matched_features)}个特征,总分{total_score}") else: judgment_basis.insert(0, f"未匹配到任何特征,总分0") if total_score >= min_score_threshold: cms_matches.append({ "cms": cms['cms'], "version": version or cms.get('version', ''), "score": total_score, "confidence": confidence, "judgment_basis": judgment_basis, # 存储详细判断依据 "features": matched_features }) cms_matches.sort(key=lambda x: (-x['confidence'], -x['score'])) filtered_results = [] if cms_matches: max_score = cms_matches[0]['score'] for match in cms_matches: if match['score'] >= max_score * 0.8 or match['confidence'] >= 70: filtered_results.append(match) # 如果没有匹配到任何结果,添加一个默认结果并说明原因 if not filtered_results: filtered_results.append({ "cms": "未知", "version": "", "confidence": 0, "judgment_basis": ["未匹配到任何已知CMS的特征", "请检查指纹库是否完整或添加新指纹"] }) primary_result = filtered_results[0] if filtered_results else { "cms": "未知", "version": "", "confidence": 0 } return { "url": final_url, "status": status_code, "results": filtered_results, "primary": primary_result } class AddFingerprintDialog(QDialog): def __init__(self, parent=None, fingerprint=None): super().__init__(parent) self.fingerprint = fingerprint self.setWindowTitle("编辑指纹" if fingerprint else "添加指纹") self.setGeometry(300, 300, 600, 500) self.init_ui() def init_ui(self): layout = QVBoxLayout() form_layout = QFormLayout() self.cms_input = QLineEdit() self.version_input = QLineEdit() form_layout.addRow("CMS名称*:", self.cms_input) form_layout.addRow("默认版本:", self.version_input) regex_help = QLabel("正则表达式提示: 反斜杠需要输入两次(\\\\),特殊字符(如. * + ?)需要转义") regex_help.setStyleSheet("color: #2980b9; font-size: 12px;") form_layout.addRow(regex_help) type_note = QLabel("特征类型说明: core(核心特征,权重高) > specific(特定特征) > general(通用特征)") type_note.setStyleSheet("color: #666; font-size: 12px;") form_layout.addRow(type_note) layout.addLayout(form_layout) # HTTP头特征表格 http_group = QWidget() http_layout = QVBoxLayout(http_group) http_layout.addWidget(QLabel("HTTP头特征:")) self.http_table = QTableWidget(0, 4) self.http_table.setHorizontalHeaderLabels(["Header", "Pattern", "Score", "Type(core/general)"]) self.http_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) http_btn_layout = QHBoxLayout() add_http_btn = QPushButton("添加") add_http_btn.clicked.connect(lambda: self.add_row(self.http_table, ["", "", "50", "general"])) remove_http_btn = QPushButton("移除") remove_http_btn.clicked.connect(lambda: self.remove_row(self.http_table)) http_btn_layout.addWidget(add_http_btn) http_btn_layout.addWidget(remove_http_btn) http_layout.addWidget(self.http_table) http_layout.addLayout(http_btn_layout) layout.addWidget(http_group) # HTML内容特征表格 html_group = QWidget() html_layout = QVBoxLayout(html_group) html_layout.addWidget(QLabel("HTML内容特征:")) self.html_table = QTableWidget(0, 4) self.html_table.setHorizontalHeaderLabels(["Pattern", "Score", "Type(core/specific)", "版本提取组(可选)"]) self.html_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) html_btn_layout = QHBoxLayout() add_html_btn = QPushButton("添加") add_html_btn.clicked.connect(lambda: self.add_row(self.html_table, ["", "80", "specific", ""])) remove_html_btn = QPushButton("移除") remove_html_btn.clicked.connect(lambda: self.remove_row(self.html_table)) html_btn_layout.addWidget(add_html_btn) html_btn_layout.addWidget(remove_html_btn) html_layout.addWidget(self.html_table) html_layout.addLayout(html_btn_layout) layout.addWidget(html_group) # URL路径特征表格 url_group = QWidget() url_layout = QVBoxLayout(url_group) url_layout.addWidget(QLabel("URL路径特征 (将主动访问这些路径):")) self.url_table = QTableWidget(0, 4) self.url_table.setHorizontalHeaderLabels(["Path", "Pattern(可选)", "Score", "Type(core/specific)"]) self.url_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) url_btn_layout = QHBoxLayout() add_url_btn = QPushButton("添加") add_url_btn.clicked.connect(lambda: self.add_row(self.url_table, ["", "", "60", "specific"])) remove_url_btn = QPushButton("移除") remove_url_btn.clicked.connect(lambda: self.remove_row(self.url_table)) url_btn_layout.addWidget(add_url_btn) url_btn_layout.addWidget(remove_url_btn) url_layout.addWidget(self.url_table) url_layout.addLayout(url_btn_layout) layout.addWidget(url_group) # 测试正则按钮 test_btn = QPushButton("测试选中的正则表达式") test_btn.clicked.connect(self.test_selected_regex) layout.addWidget(test_btn) # 确认按钮 btn_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) btn_box.accepted.connect(self.accept) btn_box.rejected.connect(self.reject) layout.addWidget(btn_box) self.setLayout(layout) self.load_fingerprint_data() def test_selected_regex(self): current_table = None pattern = "" if self.http_table.currentRow() >= 0: current_table = self.http_table item = self.http_table.item(self.http_table.currentRow(), 1) if item: pattern = item.text() elif self.html_table.currentRow() >= 0: current_table = self.html_table item = self.html_table.item(self.html_table.currentRow(), 0) if item: pattern = item.text() if not pattern: QMessageBox.information(self, "测试结果", "请选择一个正则表达式进行测试") return try: re.compile(pattern) QMessageBox.information(self, "测试结果", f"正则表达式有效:\n{pattern}") except re.error as e: fixed = pattern if "bad escape" in str(e): fixed = re.sub(r'(?<!\\)\\(?!["\\/])', r'\\\\', pattern) elif "unterminated subpattern" in str(e): open_count = pattern.count('(') close_count = pattern.count(')') if open_count > close_count: fixed = pattern + ')' * (open_count - close_count) try: re.compile(fixed) QMessageBox.information( self, "修复成功", f"原表达式无效: {str(e)}\n修复后表达式: {fixed}" ) if current_table == self.http_table: self.http_table.item(self.http_table.currentRow(), 1).setText(fixed) else: self.html_table.item(self.html_table.currentRow(), 0).setText(fixed) except re.error as e2: QMessageBox.warning( self, "测试失败", f"正则表达式无效: {str(e2)}\n表达式: {pattern}" ) def add_row(self, table, default_values): row = table.rowCount() table.insertRow(row) for col, val in enumerate(default_values): table.setItem(row, col, QTableWidgetItem(val)) def remove_row(self, table): row = table.currentRow() if row >= 0: table.removeRow(row) def load_fingerprint_data(self): if not self.fingerprint: return self.cms_input.setText(self.fingerprint.get("cms", "")) self.version_input.setText(self.fingerprint.get("version", "")) for header in self.fingerprint.get("http_headers", []): self.add_row(self.http_table, [ header.get("header", ""), header.get("pattern", ""), str(header.get("score", 50)), header.get("type", "general") ]) for html in self.fingerprint.get("html_content", []): self.add_row(self.html_table, [ html.get("pattern", ""), str(html.get("score", 80)), html.get("type", "specific"), str(html.get("version_group", "")) if "version_group" in html else "" ]) for path in self.fingerprint.get("url_paths", []): self.add_row(self.url_table, [ path.get("path", ""), path.get("pattern", ""), str(path.get("score", 60)), path.get("type", "specific") ]) def validate_regex(self, pattern): try: if pattern: re.compile(pattern) return True except re.error as e: QMessageBox.warning(self, "正则错误", f"模式 '{pattern}' 无效: {str(e)}\n请使用测试按钮修复") return False def get_fingerprint(self): cms_name = self.cms_input.text().strip() if not cms_name: QMessageBox.warning(self, "输入错误", "CMS名称不能为空") return None for row in range(self.html_table.rowCount()): pattern_item = self.html_table.item(row, 0) if pattern_item and not self.validate_regex(pattern_item.text().strip()): return None fingerprint = { "cms": cms_name, "version": self.version_input.text().strip(), "confidence": 0, "http_headers": [], "html_content": [], "url_paths": [] } for row in range(self.http_table.rowCount()): header = self.http_table.item(row, 0).text().strip() if self.http_table.item(row, 0) else "" pattern = self.http_table.item(row, 1).text().strip() if self.http_table.item(row, 1) else "" score = int(self.http_table.item(row, 2).text() or 50) f_type = self.http_table.item(row, 3).text().strip() or "general" if header and pattern: fingerprint["http_headers"].append({ "header": header, "pattern": pattern, "score": score, "type": f_type }) for row in range(self.html_table.rowCount()): pattern = self.html_table.item(row, 0).text().strip() if self.html_table.item(row, 0) else "" score = int(self.html_table.item(row, 1).text() or 80) f_type = self.html_table.item(row, 2).text().strip() or "specific" version_group = self.html_table.item(row, 3).text().strip() if pattern: item = { "pattern": pattern, "score": score, "type": f_type } if version_group and version_group.isdigit(): item["version_group"] = int(version_group) fingerprint["html_content"].append(item) for row in range(self.url_table.rowCount()): path = self.url_table.item(row, 0).text().strip() if self.url_table.item(row, 0) else "" pattern = self.url_table.item(row, 1).text().strip() if self.url_table.item(row, 1) else "" score = int(self.url_table.item(row, 2).text() or 60) f_type = self.url_table.item(row, 3).text().strip() or "specific" if path: fingerprint["url_paths"].append({ "path": path, "pattern": pattern, "score": score, "type": f_type }) return fingerprint class JudgmentBasisDialog(QDialog): """判断依据展示对话框""" def __init__(self, parent=None, result=None): super().__init__(parent) self.result = result self.setWindowTitle(f"识别依据 - {result['url']}") self.setGeometry(400, 200, 800, 600) self.init_ui() def init_ui(self): layout = QVBoxLayout() # 基本信息 basic_info = QLabel(f""" URL: {self.result['url']} 状态码: {self.result['status']} """) layout.addWidget(basic_info) # 识别结果 results_group = QGroupBox("识别结果汇总") results_layout = QVBoxLayout() for i, res in enumerate(self.result['results']): is_primary = (i == 0) # 第一个结果是主要结果 result_label = QLabel(f""" {'★ ' if is_primary else ''}{res['cms']} v{res['version']} 置信度: {res['confidence']}% """) results_layout.addWidget(result_label) results_group.setLayout(results_layout) layout.addWidget(results_group) # 详细判断依据 basis_group = QTabWidget() for res in self.result['results']: text_edit = QTextEdit() text_edit.setReadOnly(True) # 显示所有判断依据 text_edit.setText("\n".join(res['judgment_basis'])) basis_group.addTab(text_edit, f"{res['cms']} (置信度{res['confidence']}%)") layout.addWidget(basis_group) # 关闭按钮 btn_box = QDialogButtonBox(QDialogButtonBox.Ok) btn_box.accepted.connect(self.accept) layout.addWidget(btn_box) self.setLayout(layout) class CMSDetectorApp(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("多CMS识别工具 (带判断依据)") self.setGeometry(100, 100, 1200, 800) self.fingerprint_manager = FingerprintManager() self.results = [] self.create_menu() self.init_ui() self.apply_styles() def create_menu(self): menubar = self.menuBar() file_menu = menubar.addMenu("文件") import_action = QAction("导入网站列表", self) import_action.triggered.connect(self.import_urls) file_menu.addAction(import_action) export_action = QAction("导出结果", self) export_action.triggered.connect(self.export_results) file_menu.addAction(export_action) file_menu.addSeparator() exit_action = QAction("退出", self) exit_action.setShortcut("Ctrl+Q") exit_action.triggered.connect(self.close) file_menu.addAction(exit_action) fingerprint_menu = menubar.addMenu("指纹库") add_fingerprint_action = QAction("添加指纹", self) add_fingerprint_action.triggered.connect(self.add_fingerprint) fingerprint_menu.addAction(add_fingerprint_action) import_fingerprint_action = QAction("导入指纹库", self) import_fingerprint_action.triggered.connect(self.import_fingerprints) fingerprint_menu.addAction(import_fingerprint_action) export_fingerprint_action = QAction("导出指纹库", self) export_fingerprint_action.triggered.connect(self.export_fingerprints) fingerprint_menu.addAction(export_fingerprint_action) clear_fingerprint_action = QAction("清空指纹库", self) clear_fingerprint_action.triggered.connect(self.clear_fingerprints) fingerprint_menu.addAction(clear_fingerprint_action) restore_default_action = QAction("恢复默认指纹库", self) restore_default_action.triggered.connect(self.restore_default_fingerprints) fingerprint_menu.addAction(restore_default_action) help_menu = menubar.addMenu("帮助") about_action = QAction("关于", self) about_action.triggered.connect(self.show_about) help_menu.addAction(about_action) def init_ui(self): main_widget = QWidget() main_layout = QVBoxLayout() self.tabs = QTabWidget() self.detection_tab = self.create_detection_tab() self.fingerprint_tab = self.create_fingerprint_tab() self.tabs.addTab(self.detection_tab, "网站检测") self.tabs.addTab(self.fingerprint_tab, "指纹库管理") main_layout.addWidget(self.tabs) main_widget.setLayout(main_layout) self.setCentralWidget(main_widget) self.status_bar = self.statusBar() self.status_label = QLabel("就绪") self.status_bar.addWidget(self.status_label) self.detection_thread = None def apply_styles(self): self.setStyleSheet(""" QMainWindow { background-color: #f0f0f0; } QTabWidget::pane { border: 1px solid #cccccc; background: white; } QTableWidget { background-color: white; alternate-background-color: #f8f8f8; gridline-color: #e0e0e0; } QHeaderView::section { background-color: #e0e0e0; padding: 4px; border: 1px solid #d0d0d0; } QPushButton { background-color: #4a86e8; color: white; border: none; padding: 5px 10px; border-radius: 4px; } QPushButton:hover { background-color: #3a76d8; } QPushButton:pressed { background-color: #2a66c8; } QPushButton:disabled { background-color: #a0a0a0; } QPushButton#clearBtn { background-color: #e74c3c; } QPushButton#clearBtn:hover { background-color: #c0392b; } QPushButton#restoreBtn { background-color: #27ae60; } QPushButton#restoreBtn:hover { background-color: #219653; } """) def create_detection_tab(self): tab = QWidget() layout = QVBoxLayout() # URL输入区域 control_layout = QHBoxLayout() self.url_input = QLineEdit() self.url_input.setPlaceholderText("输入网站URL (例如: example.com 或 http://example.com)") add_url_btn = QPushButton("添加URL") add_url_btn.clicked.connect(self.add_single_url) import_btn = QPushButton("导入URL列表") import_btn.clicked.connect(self.import_urls) clear_btn = QPushButton("清空列表") clear_btn.clicked.connect(self.clear_urls) control_layout.addWidget(self.url_input, 4) control_layout.addWidget(add_url_btn, 1) control_layout.addWidget(import_btn, 1) control_layout.addWidget(clear_btn, 1) layout.addLayout(control_layout) # URL列表区域 url_list_layout = QVBoxLayout() url_list_layout.addWidget(QLabel("待检测网站列表:")) self.url_list = QTextEdit() self.url_list.setPlaceholderText("每行一个URL") self.url_list.setMinimumHeight(80) url_list_layout.addWidget(self.url_list) layout.addLayout(url_list_layout) # 检测控制区域 detection_control_layout = QHBoxLayout() self.thread_spin = QSpinBox() self.thread_spin.setRange(1, 20) self.thread_spin.setValue(5) self.thread_spin.setPrefix("线程数: ") self.retry_spin = QSpinBox() self.retry_spin.setRange(0, 3) self.retry_spin.setValue(1) self.retry_spin.setPrefix("重试次数: ") self.timeout_spin = QSpinBox() self.timeout_spin.setRange(5, 60) self.timeout_spin.setValue(15) self.timeout_spin.setPrefix("超时时间(秒): ") self.detect_btn = QPushButton("开始检测") self.detect_btn.clicked.connect(self.start_detection) self.stop_btn = QPushButton("停止检测") self.stop_btn.clicked.connect(self.stop_detection) self.stop_btn.setEnabled(False) detection_control_layout.addWidget(self.thread_spin) detection_control_layout.addWidget(self.retry_spin) detection_control_layout.addWidget(self.timeout_spin) detection_control_layout.addStretch() detection_control_layout.addWidget(self.detect_btn) detection_control_layout.addWidget(self.stop_btn) layout.addLayout(detection_control_layout) # 进度条 self.progress_bar = QProgressBar() self.progress_bar.setRange(0, 100) self.progress_bar.setTextVisible(True) layout.addWidget(self.progress_bar) # 结果展示区域 splitter = QSplitter(Qt.Vertical) self.result_table = QTableWidget(0, 6) # 增加一列显示操作 self.result_table.setHorizontalHeaderLabels(["URL", "状态", "CMS类型", "版本", "置信度(%)", "操作"]) self.result_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch) self.result_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeToContents) self.result_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch) self.result_table.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeToContents) self.result_table.horizontalHeader().setSectionResizeMode(4, QHeaderView.ResizeToContents) self.result_table.horizontalHeader().setSectionResizeMode(5, QHeaderView.ResizeToContents) self.result_table.setAlternatingRowColors(True) self.log_area = QTextEdit() self.log_area.setReadOnly(True) self.log_area.setMinimumHeight(150) splitter.addWidget(self.result_table) splitter.addWidget(self.log_area) splitter.setSizes([400, 150]) layout.addWidget(splitter, 1) tab.setLayout(layout) return tab def create_fingerprint_tab(self): tab = QWidget() layout = QVBoxLayout() btn_layout = QHBoxLayout() add_btn = QPushButton("添加指纹") add_btn.clicked.connect(self.add_fingerprint) edit_btn = QPushButton("编辑指纹") edit_btn.clicked.connect(self.edit_fingerprint) remove_btn = QPushButton("删除指纹") remove_btn.clicked.connect(self.remove_fingerprint) clear_btn = QPushButton("清空指纹库") clear_btn.setObjectName("clearBtn") clear_btn.clicked.connect(self.clear_fingerprints) restore_btn = QPushButton("恢复默认") restore_btn.setObjectName("restoreBtn") restore_btn.clicked.connect(self.restore_default_fingerprints) import_btn = QPushButton("导入指纹库") import_btn.clicked.connect(self.import_fingerprints) export_btn = QPushButton("导出指纹库") export_btn.clicked.connect(self.export_fingerprints) btn_layout.addWidget(add_btn) btn_layout.addWidget(edit_btn) btn_layout.addWidget(remove_btn) btn_layout.addWidget(clear_btn) btn_layout.addWidget(restore_btn) btn_layout.addStretch() btn_layout.addWidget(import_btn) btn_layout.addWidget(export_btn) layout.addLayout(btn_layout) self.fingerprint_tree = QTreeWidget() self.fingerprint_tree.setHeaderLabels(["CMS名称", "版本", "核心特征数", "总特征数"]) self.fingerprint_tree.setColumnWidth(0, 200) self.fingerprint_tree.setSortingEnabled(True) self.populate_fingerprint_tree() layout.addWidget(self.fingerprint_tree, 1) tab.setLayout(layout) return tab def populate_fingerprint_tree(self): self.fingerprint_tree.clear() fingerprints = self.fingerprint_manager.get_fingerprints() for i, fp in enumerate(fingerprints): try: cms_name = fp["cms"] version = fp.get("version", "") core_features = 0 total_features = 0 for h in fp.get("http_headers", []): total_features += 1 if h.get("type") == "core": core_features += 1 for h in fp.get("html_content", []): total_features += 1 if h.get("type") == "core": core_features += 1 for u in fp.get("url_paths", []): total_features += 1 if u.get("type") == "core": core_features += 1 item = QTreeWidgetItem([ cms_name, version, str(core_features), str(total_features) ]) item.setData(0, Qt.UserRole, i) self.fingerprint_tree.addTopLevelItem(item) except Exception as e: self.log(f"处理指纹时出错: {e},已跳过") def add_single_url(self): url = self.url_input.text().strip() if url: current_text = self.url_list.toPlainText() new_text = current_text + (("\n" + url) if current_text else url) self.url_list.setPlainText(new_text) self.url_input.clear() def import_urls(self): file_path, _ = QFileDialog.getOpenFileName( self, "导入URL列表", "", "文本文件 (*.txt);;所有文件 (*)" ) if file_path: try: with open(file_path, 'r', encoding='utf-8') as f: urls = [line.strip() for line in f if line.strip()] self.url_list.setPlainText("\n".join(urls)) self.log(f"成功导入 {len(urls)} 个URL") except Exception as e: QMessageBox.critical(self, "导入错误", f"导入失败: {str(e)}") def clear_urls(self): self.url_list.clear() def start_detection(self): urls_text = self.url_list.toPlainText().strip() if not urls_text: QMessageBox.warning(self, "警告", "请先添加要检测的URL") return urls = [url.strip() for url in urls_text.splitlines() if url.strip()] if not urls: QMessageBox.warning(self, "警告", "没有有效的URL") return self.result_table.setRowCount(0) self.results = [] max_threads = self.thread_spin.value() retry_count = self.retry_spin.value() timeout = self.timeout_spin.value() self.detection_thread = DetectionWorker( urls, self.fingerprint_manager.get_fingerprints(), max_threads, retry_count ) self.detection_thread.timeout = timeout self.detection_thread.progress_signal.connect(self.update_progress) self.detection_thread.result_signal.connect(self.add_result) self.detection_thread.log_signal.connect(self.log) self.detection_thread.finished_signal.connect(self.detection_finished) self.detect_btn.setEnabled(False) self.stop_btn.setEnabled(True) self.progress_bar.setRange(0, len(urls)) self.progress_bar.setValue(0) self.detection_thread.start() def stop_detection(self): if self.detection_thread and self.detection_thread.isRunning(): self.detection_thread.stop() self.log("检测已停止") self.detection_finished() def detection_finished(self): self.detect_btn.setEnabled(True) self.stop_btn.setEnabled(False) self.status_label.setText("检测完成") def update_progress(self, current, total, url): self.progress_bar.setMaximum(total) self.progress_bar.setValue(current) self.status_label.setText(f"正在检测: {url} ({current}/{total})") def show_judgment_basis(self, result): """显示判断依据对话框""" dialog = JudgmentBasisDialog(self, result) dialog.exec_() def add_result(self, result): self.results.append(result) row = self.result_table.rowCount() self.result_table.insertRow(row) # URL url_item = QTableWidgetItem(result["url"]) url_item.setFlags(url_item.flags() ^ Qt.ItemIsEditable) self.result_table.setItem(row, 0, url_item) # 状态码 status = result["status"] status_item = QTableWidgetItem(str(status)) status_item.setFlags(status_item.flags() ^ Qt.ItemIsEditable) if status == 200: status_item.setForeground(Qt.darkGreen) elif 400 <= status < 500: status_item.setForeground(Qt.darkRed) elif status >= 500: status_item.setForeground(Qt.darkMagenta) self.result_table.setItem(row, 1, status_item) # CMS类型(主结果) primary = result["primary"] cms_item = QTableWidgetItem(primary["cms"]) cms_item.setFlags(cms_item.flags() ^ Qt.ItemIsEditable) self.result_table.setItem(row, 2, cms_item) # 版本 version_item = QTableWidgetItem(primary["version"]) version_item.setFlags(version_item.flags() ^ Qt.ItemIsEditable) self.result_table.setItem(row, 3, version_item) # 置信度 confidence = primary["confidence"] confidence_item = QTableWidgetItem(f"{confidence}%") confidence_item.setFlags(confidence_item.flags() ^ Qt.ItemIsEditable) if confidence >= 90: confidence_item.setForeground(Qt.darkGreen) elif confidence >= 70: confidence_item.setForeground(Qt.darkBlue) elif confidence >= 50: confidence_item.setForeground(Qt.darkOrange) else: confidence_item.setForeground(Qt.darkGray) self.result_table.setItem(row, 4, confidence_item) # 查看依据按钮 view_btn = QPushButton("查看依据") # 使用lambda表达式传递当前result view_btn.clicked.connect(lambda checked, res=result: self.show_judgment_basis(res)) self.result_table.setCellWidget(row, 5, view_btn) def add_fingerprint(self): dialog = AddFingerprintDialog(self) if dialog.exec_() == QDialog.Accepted: fingerprint = dialog.get_fingerprint() if fingerprint and self.fingerprint_manager.add_fingerprint(fingerprint): self.populate_fingerprint_tree() self.log(f"已添加指纹: {fingerprint['cms']}") def edit_fingerprint(self): selected_items = self.fingerprint_tree.selectedItems() if not selected_items: QMessageBox.warning(self, "警告", "请选择一个指纹进行编辑") return item = selected_items[0] index = item.data(0, Qt.UserRole) fingerprints = self.fingerprint_manager.get_fingerprints() if index is None or not (0 <= index < len(fingerprints)): QMessageBox.warning(self, "错误", "无效的指纹索引") return fingerprint = fingerprints[index] dialog = AddFingerprintDialog(self, fingerprint) if dialog.exec_() == QDialog.Accepted: updated = dialog.get_fingerprint() if updated and self.fingerprint_manager.update_fingerprint(index, updated): self.populate_fingerprint_tree() self.log(f"已更新指纹: {updated['cms']}") def remove_fingerprint(self): selected_items = self.fingerprint_tree.selectedItems() if not selected_items: QMessageBox.warning(self, "警告", "请选择要删除的指纹") return item = selected_items[0] cms_name = item.text(0) index = item.data(0, Qt.UserRole) reply = QMessageBox.question( self, "确认删除", f"确定要删除 '{cms_name}' 的指纹吗?", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: self.fingerprint_manager.remove_fingerprint(index) self.populate_fingerprint_tree() self.log(f"已删除指纹: {cms_name}") def clear_fingerprints(self): if not self.fingerprint_manager.get_fingerprints(): QMessageBox.information(self, "提示", "指纹库已为空") return reply = QMessageBox.question( self, "确认清空", "确定要清空所有指纹吗?此操作不可恢复!", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: self.fingerprint_manager.clear_fingerprints() self.populate_fingerprint_tree() self.log("已清空所有指纹") def restore_default_fingerprints(self): reply = QMessageBox.question( self, "确认恢复", "确定要恢复默认指纹库吗?当前指纹将被替换!", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: self.fingerprint_manager.restore_default_fingerprints() self.populate_fingerprint_tree() self.log("已恢复默认指纹库") def import_fingerprints(self): file_path, _ = QFileDialog.getOpenFileName( self, "导入指纹库", "", "JSON文件 (*.json);;所有文件 (*)" ) if file_path and self.fingerprint_manager.import_fingerprints(file_path): self.populate_fingerprint_tree() self.log(f"成功导入指纹库: {file_path}") def export_fingerprints(self): file_path, _ = QFileDialog.getSaveFileName( self, "导出指纹库", "cms_fingerprints.json", "JSON文件 (*.json)" ) if file_path and self.fingerprint_manager.export_fingerprints(file_path): self.log(f"成功导出指纹库: {file_path}") def export_results(self): if not self.results: QMessageBox.warning(self, "警告", "没有结果可导出") return file_path, _ = QFileDialog.getSaveFileName( self, "导出结果", "", "CSV文件 (*.csv);;JSON文件 (*.json)" ) if not file_path: return try: if file_path.endswith(".csv"): with open(file_path, 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f) writer.writerow(["URL", "状态", "CMS类型", "版本", "置信度(%)"]) for result in self.results: primary = result["primary"] writer.writerow([ result["url"], result["status"], primary["cms"], primary["version"], primary["confidence"] ]) elif file_path.endswith(".json"): # 导出完整结果,包括判断依据 with open(file_path, 'w', encoding='utf-8') as f: json.dump(self.results, f, indent=4, ensure_ascii=False) self.log(f"结果已导出到: {file_path}") except Exception as e: QMessageBox.critical(self, "导出错误", f"导出失败: {str(e)}") def log(self, message): timestamp = time.strftime("%H:%M:%S") self.log_area.append(f"[{timestamp}] {message}") def show_about(self): about_text = """ 多CMS识别工具 (带判断依据) 版本: 2.3.0 功能特点: 显示详细的识别判断依据 URL路径特征主动访问验证 并发检测提高效率 核心特征加权识别,准确率高 支持正则表达式测试和验证 可自定义超时时间和重试次数 使用说明: 点击结果中的"查看依据"按钮可查看详细的识别依据 """ QMessageBox.about(self, "关于", about_text) def closeEvent(self, event): if self.detection_thread and self.detection_thread.isRunning(): reply = QMessageBox.question( self, "检测中", "检测仍在进行中,确定要退出吗?", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: self.detection_thread.stop() event.accept() else: event.ignore() else: event.accept() if __name__ == "__main__": if hasattr(Qt, 'AA_EnableHighDpiScaling'): QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) if hasattr(Qt, 'AA_UseHighDpiPixmaps'): QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True) app = QApplication(sys.argv) app.setStyle("Fusion") window = CMSDetectorApp() window.show() sys.exit(app.exec_()) 修改代码提高验证效率 其他无需修改 完整输出

exe
Windows 系统修复工具主要用于解决 Windows 11/10 系统中的各种常见问题,具有操作简单、功能全面等特点: 文件资源管理器修复:可解决文件资源管理器卡死、崩溃、无响应等问题,能终止崩溃循环。还可修复右键菜单无响应或选项缺失问题,以及重建缩略图缓存,让图片、视频等文件的缩略图正常显示,此外,还能处理桌面缺少回收站图标、回收站损坏等问题。 互联网和连接修复:能够刷新 DNS 缓存,加速网页加载速度,减少访问延迟。可重置 TCP/IP 协议栈,增强网络连接稳定性,减少网络掉线情况,还能还原 Hosts 文件,清除恶意程序对网络设置的篡改,保障网络安全,解决电脑重装系统后网络无法连接、浏览器主页被篡改等问题。 系统修复:集成系统文件检查器(SFC),可自动扫描并修复受损的系统文件。能解决 Windows 激活状态异常的问题,还可重建 DLL 注册库,恢复应用程序兼容性,解决部分软件无法正常运行的问题,同时也能处理如 Windows 沙箱无法启动、Windows 将 JPG 或 JPEG 保存为 JFIF 等系统问题。 系统工具维护:提供启动管理器、服务管理器和进程管理器等工具,用户可控制和管理启动程序、系统服务和当前运行的进程,提高系统的启动和运行速度,防止不必要的程序和服务占用系统资源。还能查看系统规格,如处理器线程数、最大显示分辨率等。 故障排除:集成超过 20 个微软官方诊断工具,可对系统问题进行专业排查,还能生成硬件健康状态报告。能解决搜索和索引故障、邮件和日历应用程序崩溃、设置应用程序无法启动等问题,也可处理打印机、网络适配器、Windows 更新等相关故障。 其他修复功能:可以重置组策略设置、catroot2 文件夹、记事本等多种系统设置和组件,如重置 Windows 应用商店缓存、Windows 防火墙设置等。还能添加重建图标缓存支持,恢复粘滞便笺删除

最新推荐

recommend-type

2022年网站美工个人年度工作总结(1).doc

2022年网站美工个人年度工作总结(1).doc
recommend-type

财务软件销售实习报告格式范文-实习报告格式(1).doc

财务软件销售实习报告格式范文-实习报告格式(1).doc
recommend-type

【航迹关联】基于标准 Hough 变换、修正 Hough 变换和序列 Hough 变换实现航迹起始算法研究Matlab代码.rar

【航迹关联】基于标准 Hough 变换、修正 Hough 变换和序列 Hough 变换实现航迹起始算法研究Matlab代码
recommend-type

Windows系统修复工具

Windows 系统修复工具主要用于解决 Windows 11/10 系统中的各种常见问题,具有操作简单、功能全面等特点: 文件资源管理器修复:可解决文件资源管理器卡死、崩溃、无响应等问题,能终止崩溃循环。还可修复右键菜单无响应或选项缺失问题,以及重建缩略图缓存,让图片、视频等文件的缩略图正常显示,此外,还能处理桌面缺少回收站图标、回收站损坏等问题。 互联网和连接修复:能够刷新 DNS 缓存,加速网页加载速度,减少访问延迟。可重置 TCP/IP 协议栈,增强网络连接稳定性,减少网络掉线情况,还能还原 Hosts 文件,清除恶意程序对网络设置的篡改,保障网络安全,解决电脑重装系统后网络无法连接、浏览器主页被篡改等问题。 系统修复:集成系统文件检查器(SFC),可自动扫描并修复受损的系统文件。能解决 Windows 激活状态异常的问题,还可重建 DLL 注册库,恢复应用程序兼容性,解决部分软件无法正常运行的问题,同时也能处理如 Windows 沙箱无法启动、Windows 将 JPG 或 JPEG 保存为 JFIF 等系统问题。 系统工具维护:提供启动管理器、服务管理器和进程管理器等工具,用户可控制和管理启动程序、系统服务和当前运行的进程,提高系统的启动和运行速度,防止不必要的程序和服务占用系统资源。还能查看系统规格,如处理器线程数、最大显示分辨率等。 故障排除:集成超过 20 个微软官方诊断工具,可对系统问题进行专业排查,还能生成硬件健康状态报告。能解决搜索和索引故障、邮件和日历应用程序崩溃、设置应用程序无法启动等问题,也可处理打印机、网络适配器、Windows 更新等相关故障。 其他修复功能:可以重置组策略设置、catroot2 文件夹、记事本等多种系统设置和组件,如重置 Windows 应用商店缓存、Windows 防火墙设置等。还能添加重建图标缓存支持,恢复粘滞便笺删除
recommend-type

高中信息技术《算法与程序设计》练习(1).doc

高中信息技术《算法与程序设计》练习(1).doc
recommend-type

获取本机IP地址的程序源码分析

从给定文件信息中我们可以提取出的关键知识点是“取本机IP”的实现方法以及与之相关的编程技术和源代码。在当今的信息技术领域中,获取本机IP地址是一项基本技能,广泛应用于网络通信类的软件开发中,下面将详细介绍这一知识点。 首先,获取本机IP地址通常需要依赖于编程语言和操作系统的API。不同的操作系统提供了不同的方法来获取IP地址。在Windows操作系统中,可以通过调用Windows API中的GetAdaptersInfo()或GetAdaptersAddresses()函数来获取网络适配器信息,进而得到IP地址。在类Unix操作系统中,可以通过读取/proc/net或是使用系统命令ifconfig、ip等来获取网络接口信息。 在程序设计过程中,获取本机IP地址的源程序通常会用到网络编程的知识,比如套接字编程(Socket Programming)。网络编程允许程序之间进行通信,套接字则是在网络通信过程中用于发送和接收数据的接口。在许多高级语言中,如Python、Java、C#等,都提供了内置的网络库和类来简化网络编程的工作。 在网络通信类中,IP地址是区分不同网络节点的重要标识,它是由IP协议规定的,用于在网络中唯一标识一个网络接口。IP地址可以是IPv4,也可以是较新的IPv6。IPv4地址由32位二进制数表示,通常分为四部分,每部分由8位构成,并以点分隔,如192.168.1.1。IPv6地址则由128位二进制数表示,其表示方法与IPv4有所不同,以冒号分隔的8组16进制数表示,如2001:0db8:85a3:0000:0000:8a2e:0370:7334。 当编写源代码以获取本机IP地址时,通常涉及到以下几个步骤: 1. 选择合适的编程语言和相关库。 2. 根据目标操作系统的API或系统命令获取网络接口信息。 3. 分析网络接口信息,提取出IP地址。 4. 将提取的IP地址转换成适合程序内部使用的格式。 5. 在程序中提供相应功能,如显示IP地址或用于网络通信。 例如,在Python中,可以使用内置的socket库来获取本机IP地址。一个简单的示例代码如下: ```python import socket # 获取主机名 hostname = socket.gethostname() # 获取本机IP local_ip = socket.gethostbyname(hostname) print("本机IP地址是:", local_ip) ``` 在实际应用中,获取本机IP地址通常是为了实现网络通信功能,例如建立客户端与服务器的连接,或者是在开发涉及到IP地址的其他功能时使用。 关于文件名称“getIP”,这是一个自解释的文件名,明显表示该文件或程序的作用是获取本机的IP地址。从标签“控件 源码 网络通信类 资源”中我们可以看出,这个文件很可能是一个可以嵌入其他软件中的代码片段,用以实现网络通信功能的一部分,具有较高的灵活性和重用性。 综上所述,获取本机IP地址是一个涉及到操作系统API、网络编程以及特定编程语言知识的技能。它不仅需要对编程语言提供的库函数有深入的理解,还要对操作系统的网络配置和IP协议有足够的认识。通过阅读和分析相关的源代码,如“getIP”,可以加深对网络编程实现细节的理解,并能够在实际开发中快速地应用这一技术。
recommend-type

【权威指南】:Win11笔记本上RTX4080驱动的安装与调试秘籍

# 1. Win11笔记本与RTX4080显卡介绍 随着技术的不断发展,笔记本电脑已不再是仅仅满足办公需求的移动设备,而是逐渐成为可以胜任复杂图形处理和游戏任务的强大工具。微软推出的Windows 11操作系统,以其全新的用户界面和改进的系统性能,为用户提供了更为流畅的操作体验。而在硬件方面,NVIDIA的RTX 4080显卡以其卓越的图形处理能力和AI支持,为笔记本电脑带来了前所未有的性能提升。本章节将带您初步了解Windows 11笔记本与NVIDIA RTX 4080显卡的基础信息,为进一步深入探讨它们的优化和调试技巧奠定基础。 ## 1.1 Win11笔记本的优势 Windows
recommend-type

windows环境举例

<think>好的,用户现在想在Windows环境下创建C函数库,需要具体的方法和示例代码。我需要结合之前的回答和用户提供的引用内容来给出合适的指导。 首先回顾之前的回答,用户已经了解了在Linux下创建静态库和动态库的方法,现在需要切换到Windows环境。根据引用[2],Windows下的C标准库和动态链接库的处理与Linux不同,比如使用dlfcn.h在Linux,而Windows可能需要其他方式。另外,引用[1]提到了在Windows下配置gcc环境(MinGW-w64),这可能是一个关键点,因为用户可能需要使用MinGW来编译库。 用户提供的引用[3]提到了使用MSVC编译器,这
recommend-type

QQ自动发送/回复系统源代码开放

根据提供的文件信息,我们可以了解到以下几点关键的知识点: ### 标题:“qqhelp” 1. **项目类型**: 标题“qqhelp”暗示这是一个与QQ相关的帮助工具或项目。QQ是中国流行的即时通讯软件,因此这个标题表明项目可能提供了对QQ客户端功能的辅助或扩展。 2. **用途**: “help”表明此项目的主要目的是提供帮助或解决问题。由于它提到了QQ,并且涉及“autosend/reply”功能,我们可以推测该项目可能用于自动化发送消息回复,或提供某种形式的自动回复机制。 ### 描述:“I put it to my web, but nobody sendmessage to got the source, now I public it. it supply qq,ticq autosend/reply ,full sourcecode use it as you like” 1. **发布情况**: 描述提到该项目原先被放置在某人的网站上,并且没有收到请求源代码的消息。这可能意味着项目不够知名或者需求不高。现在作者决定公开发布,这可能是因为希望项目能够被更多人了解和使用,或是出于开源共享的精神。 2. **功能特性**: 提到的“autosend/reply”表明该项目能够实现自动发送和回复消息。这种功能对于需要进行批量或定时消息沟通的应用场景非常有用,例如客户服务、自动化的营销通知等。 3. **代码可用性**: 作者指出提供了“full sourcecode”,意味着源代码完全开放,用户可以自由使用,无论是查看、学习还是修改,用户都有很大的灵活性。这对于希望学习编程或者有特定需求的开发者来说是一个很大的优势。 ### 标签:“综合系统类” 1. **项目分类**: 标签“综合系统类”表明这个项目可能是一个多功能的集成系统,它可能不仅限于QQ相关的功能,还可能包含了其他类型的综合服务或特性。 2. **技术范畴**: 这个标签可能表明该项目的技术实现比较全面,可能涉及到了多个技术栈或者系统集成的知识点,例如消息处理、网络编程、自动化处理等。 ### 压缩包子文件的文件名称列表: 1. **Unit1.dfm**: 这是一个Delphi或Object Pascal语言的窗体定义文件,用于定义应用程序中的用户界面布局。DFM文件通常用于存储组件的属性和位置信息,使得开发者可以快速地进行用户界面的设计和调整。 2. **qqhelp.dpr**: DPR是Delphi项目文件的扩展名,包含了Delphi项目的核心设置,如程序入口、使用的单元(Units)等。这个文件是编译和构建Delphi项目的起点,它能够帮助开发者了解项目的组织结构和编译指令。 3. **Unit1.pas**: PAS是Delphi或Object Pascal语言的源代码文件。这个文件可能包含了与QQ帮助工具相关的核心逻辑代码,例如处理自动发送和回复消息的算法等。 4. **readme.txt**: 这是一个常见的文本文件,包含项目的基本说明和使用指导,帮助用户了解如何获取、安装、运行和定制该项目。README文件通常是用户与项目首次交互时首先阅读的文件,因此它对于一个开源项目的用户友好度有着重要影响。 通过以上分析,我们可以看出“qqhelp”项目是一个针对QQ通讯工具的自动化消息发送与回复的辅助工具。项目包含完整的源代码,用户可以根据自己的需要进行查看、修改和使用。它可能包含Delphi语言编写的窗体界面和后端逻辑代码,具有一定的综合系统特性。项目作者出于某种原因将其开源,希望能够得到更广泛的使用和反馈。
recommend-type

【7步打造Win11深度学习利器】:Tensorflow-GPU与RTX4080终极优化指南

# 1. 深度学习与Windows 11的融合 在人工智能时代,深度学习已渗透到生活的方方面面,而Windows 11作为最新一代的操作系统,为深度学习提供了一个高效的工作平台。本章节将探讨深度学习技术如何与Windows 11系统融合,以及这一