python自动安装库的工具

查看 116|回复 11
作者:wfk521   
下载地址
个人使用了还是不错的,分享给大家也使用,发了好几次都没有成功,第一次发帖,不知道怎么发,如果有什么问题可以说一下,
https://wwtk.lanzoue.com/iXYvf32tr2ed密码:52pj
https://pan.baidu.com/s/17Gp-REIckl9WbmIatMryAA?pwd=52pj 提取码: 52pj
[Python] 纯文本查看 复制代码import sys
import subprocess
import logging
import time
import os
import glob
from typing import List, Dict, Optional
from PyQt5.QtWidgets import (QApplication, QMainWindow, QTabWidget, QWidget, QVBoxLayout,
                             QHBoxLayout, QLabel, QPushButton, QListWidget, QListWidgetItem,
                             QTextEdit, QProgressBar, QFileDialog, QLineEdit, QGroupBox,
                             QCheckBox, QMessageBox, QScrollArea, QSplitter, QComboBox,
                             QAction, QMenuBar, QMenu, QTextBrowser)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer
from PyQt5.QtGui import QFont, QIcon, QPalette, QColor, QBrush
# ==================== 配置与常量定义 ====================
# 日志配置
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler("package_installer_gui.log", encoding='utf-8'),
    ]
)
# 国内镜像源
MIRRORS = {
    "清华": "https://pypi.tuna.tsinghua.edu.cn/simple",
    "阿里": "https://mirrors.aliyun.com/pypi/simple/",
    "中科大": "https://pypi.mirrors.ustc.edu.cn/simple/",
    "默认": None
}
# 安装配置
MAX_RETRIES = 3  # 最大重试次数
RETRY_DELAY = 3  # 重试延迟(秒)
MAX_WORKERS = 5  # 最大线程数
# 样式常量
PRIMARY_COLOR = "#2C6ECB"  # 主色调:蓝色
SECONDARY_COLOR = "#4A90E2"  # 次要色:浅蓝色
ACCENT_COLOR = "#5CB85C"  # 强调色:绿色(成功)
WARNING_COLOR = "#F0AD4E"  # 警告色:橙色
DANGER_COLOR = "#D9534F"  # 危险色:红色(错误/取消)
LIGHT_COLOR = "#F8F9FA"  # 浅色背景
DARK_COLOR = "#292B2C"  # 深色文本
GRAY_COLOR = "#E9ECEF"  # 灰色背景
BORDER_COLOR = "#DEE2E6"  # 边框色
# ==================== 核心安装功能 ====================
class InstallWorker(QThread):
    progress_updated = pyqtSignal(int, str)  # 进度值, 状态文本
    log_updated = pyqtSignal(str)  # 日志文本
    install_finished = pyqtSignal(dict)  # 安装结果
    def __init__(self, packages: List[str], python_path: str):
        super().__init__()
        self.packages = packages
        self.python_path = python_path  # 接收Python路径参数
        self.is_running = True
    def run(self):
        results = {
            "success": [],
            "failed": [],
            "skipped": [],
            "total": len(self.packages)
        }
        if not self.packages:
            self.log_updated.emit("没有需要安装的包")
            self.install_finished.emit(results)
            return
        # 验证Python路径有效性并在日志中显示
        self.log_updated.emit(f"当前使用的Python安装路径: {self.python_path}")
        self.log_updated.emit(f"Python可执行文件位置: {os.path.join(self.python_path, 'python.exe')}")
        self.log_updated.emit(f"pip可执行文件位置: {os.path.join(self.python_path, 'Scripts', 'pip.exe')}")
        if not self.verify_python_path():
            self.install_finished.emit(results)
            return
        if not self.check_and_upgrade_pip():
            self.install_finished.emit(results)
            return
        total = len(self.packages)
        for i, package in enumerate(self.packages):
            if not self.is_running:
                self.log_updated.emit("安装已取消")
                break
            progress = int((i + 1) / total * 100)
            self.progress_updated.emit(progress, f"正在处理: {package}")
            result = self.install_single_package(package)
            status = result["status"]
            reason = result["reason"]
            if status == "success":
                results["success"].append(package)
                self.log_updated.emit(f"✅ 成功安装: {package}")
            elif status == "skipped":
                results["skipped"].append(package)
                self.log_updated.emit(f"⏭️ 已安装跳过: {package}")
            else:
                results["failed"].append(f"{package} ({reason[:50]})")
                self.log_updated.emit(f"❌ 安装失败: {package} ({reason[:50]})")
        self.install_finished.emit(results)
    def stop(self):
        self.is_running = False
    def verify_python_path(self) -> bool:
        """验证Python路径是否有效"""
        if not self.python_path:
            self.log_updated.emit("错误:未指定Python路径")
            return False
        python_exe = os.path.join(self.python_path, "python.exe")
        pip_exe = os.path.join(self.python_path, "Scripts", "pip.exe")
        if not os.path.exists(python_exe):
            self.log_updated.emit(f"错误:Python可执行文件不存在 - {python_exe}")
            return False
        if not os.path.exists(pip_exe):
            self.log_updated.emit(f"警告:未找到pip可执行文件 - {pip_exe}")
            self.log_updated.emit("尝试修复pip环境...")
        return True
    def run_command(self, command: List[str], description: str) -> (bool, str):
        try:
            result = subprocess.run(
                command,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                encoding='utf-8',
                timeout=300
            )
            if result.returncode == 0:
                self.log_updated.emit(f"{description} 成功")
                return (True, result.stdout)
            else:
                error_msg = result.stderr.strip()[:500]
                self.log_updated.emit(f"{description} 失败: {error_msg[:100]}")
                return (False, error_msg)
        except subprocess.TimeoutExpired:
            msg = f"{description} 超时(超过5分钟)"
            self.log_updated.emit(msg)
            return (False, msg)
        except Exception as e:
            msg = f"执行命令时发生错误: {str(e)}"
            self.log_updated.emit(msg)
            return (False, msg)
    def check_and_upgrade_pip(self) -> bool:
        self.log_updated.emit("===== 检查pip环境 =====")
        # 使用指定路径的Python和pip
        success, _ = self.run_command(
            [os.path.join(self.python_path, "python.exe"), "-m", "pip", "--version"],
            "检查pip可用性"
        )
        if not success:
            self.log_updated.emit("pip不可用,尝试修复...")
            for i in range(MAX_RETRIES):
                fix_success, _ = self.run_command(
                    [os.path.join(self.python_path, "python.exe"), "-m", "ensurepip", "--upgrade"],
                    f"修复pip(尝试 {i + 1}/{MAX_RETRIES})"
                )
                if fix_success:
                    success = True
                    break
                time.sleep(RETRY_DELAY)
            if not success:
                self.log_updated.emit("pip修复失败,无法继续安装")
                return False
        self.log_updated.emit("升级pip到最新版本...")
        self.run_command(
            [os.path.join(self.python_path, "python.exe"), "-m", "pip", "install", "--upgrade", "pip"],
            "升级pip"
        )
        return True
    def is_package_installed(self, package: str) -> bool:
        pure_package = package.split("[")[0]
        success, _ = self.run_command(
            [os.path.join(self.python_path, "python.exe"), "-m", "pip", "show", pure_package],
            f"检查 {pure_package} 是否已安装"
        )
        return success
    def install_single_package(self, package: str) -> Dict:
        result = {
            "package": package,
            "status": "failed",
            "reason": ""
        }
        if self.is_package_installed(package):
            result["status"] = "skipped"
            result["reason"] = "已安装"
            return result
        pure_package = package.split("[")[0]
        for retry in range(MAX_RETRIES):
            if not self.is_running:
                result["reason"] = "安装已取消"
                return result
            for mirror_name, mirror_url in MIRRORS.items():
                install_cmd = [
                    os.path.join(self.python_path, "python.exe"),
                    "-m", "pip", "install", package
                ]
                if mirror_url:
                    host = mirror_url.split("//")[-1].split("/")[0]
                    install_cmd.extend(["-i", mirror_url, "--trusted-host", host])
                desc = f"安装 {package}(镜像:{mirror_name},重试:{retry + 1}/{MAX_RETRIES})"
                success, output = self.run_command(install_cmd, desc)
                if success and self.is_package_installed(pure_package):
                    result["status"] = "success"
                    result["reason"] = "安装成功"
                    return result
                elif success:
                    result["reason"] = f"安装命令成功,但验证失败"
                else:
                    result["reason"] = f"安装失败"
            if retry  str:
        """获取当前运行环境的Python路径"""
        try:
            return os.path.dirname(sys.executable)
        except:
            return ""
    def browse_python_path(self):
        """浏览选择Python安装目录"""
        default_dir = self.python_path if self.python_path else "C:\\"
        python_dir = QFileDialog.getExistingDirectory(
            self, "选择Python安装目录", default_dir,
            QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks
        )
        if python_dir:
            # 验证是否包含python.exe
            if os.path.exists(os.path.join(python_dir, "python.exe")):
                self.python_path_edit.setText(python_dir)
                self.python_path = python_dir
                # 更新日志显示
                self.append_python_path_to_log()
            else:
                QMessageBox.warning(
                    self, "路径无效",
                    "所选目录不包含python.exe,请选择正确的Python安装目录"
                )
    def find_python_installations(self):
        """自动查找系统中的Python安装"""
        self.log_text.append("正在查找系统中的Python安装...")
        # 常见的Python安装路径
        search_paths = [
            "C:\\Python*",
            "C:\\Program Files\\Python*",
            "C:\\Program Files (x86)\\Python*",
            os.path.expanduser("~") + "\\AppData\\Local\\Programs\\Python\\Python*",
            "D:\\Python*",  # 可能的其他盘符
            "E:\\Python*"
        ]
        python_paths = []
        # 保留当前选择的路径(如果有效)
        current_path = self.python_path_edit.text()
        if current_path and os.path.exists(os.path.join(current_path, "python.exe")):
            python_paths.append(current_path)
        # 搜索常见路径
        for path_pattern in search_paths:
            for path in glob.glob(path_pattern):
                if os.path.exists(os.path.join(path, "python.exe")):
                    if path not in python_paths:
                        python_paths.append(path)
        # 添加环境变量中的Python路径
        try:
            env_python = subprocess.check_output(
                ["where", "python"],
                shell=True,
                text=True
            ).splitlines()
            for path in env_python:
                if path and os.path.exists(path):
                    python_dir = os.path.dirname(path)
                    if python_dir not in python_paths:
                        python_paths.append(python_dir)
        except:
            pass
        # 更新下拉列表
        self.python_versions_combo.clear()
        for path in python_paths:
            # 获取Python版本信息
            try:
                version = subprocess.check_output(
                    [os.path.join(path, "python.exe"), "--version"],
                    stderr=subprocess.STDOUT,
                    text=True
                ).strip()
                self.python_versions_combo.addItem(f"{version} - {path}", path)
            except:
                self.python_versions_combo.addItem(f"Python - {path}", path)
        self.log_text.append(f"已找到 {len(python_paths)} 个Python安装位置")
        # 显示当前选中的Python路径
        self.append_python_path_to_log()
    def on_python_version_selected(self, index):
        """选择下拉列表中的Python版本"""
        if index >= 0:
            python_path = self.python_versions_combo.itemData(index)
            self.python_path_edit.setText(python_path)
            self.python_path = python_path
            # 更新日志显示
            self.append_python_path_to_log()
    def on_python_path_changed(self):
        """当Python路径发生变化时更新日志"""
        self.python_path = self.python_path_edit.text().strip()
        self.append_python_path_to_log()
    def append_python_path_to_log(self):
        """将Python路径信息添加到日志区域"""
        if self.python_path and hasattr(self, 'log_text'):
            # 检查路径有效性
            python_exe_exists = os.path.exists(os.path.join(self.python_path, "python.exe"))
            pip_exe_exists = os.path.exists(os.path.join(self.python_path, "Scripts", "pip.exe"))
            status = "有效" if python_exe_exists else "无效"
            self.log_text.append(f"当前Python安装路径 [{status}]: {self.python_path}")
            if python_exe_exists:
                self.log_text.append(f"Python可执行文件: {os.path.join(self.python_path, 'python.exe')}")
                pip_status = "存在" if pip_exe_exists else "不存在"
                self.log_text.append(
                    f"pip可执行文件 [{pip_status}]: {os.path.join(self.python_path, 'Scripts', 'pip.exe')}")
            else:
                self.log_text.append("警告: 所选路径不包含python.exe,无法执行安装操作")
    def apply_styles(self):
        """应用样式表美化界面"""
        self.setStyleSheet(f"""
            /* 主窗口样式 */
            QMainWindow, QWidget {{
                background-color: {LIGHT_COLOR};
                color: {DARK_COLOR};
            }}
            /* 标题标签 */
            QLabel#titleLabel {{
                color: {PRIMARY_COLOR};
                padding: 5px;
            }}
            /* 标签页样式 */
            QTabWidget::pane {{
                border: 1px solid {BORDER_COLOR};
                border-radius: 6px;
                background-color: white;
                padding: 10px;
            }}
            QTabBar::tab {{
                background-color: {LIGHT_COLOR};
                color: {DARK_COLOR};
                padding: 8px 16px;
                border: 1px solid {BORDER_COLOR};
                border-bottom-color: {BORDER_COLOR};
                border-radius: 6px 6px 0 0;
                margin-right: 2px;
            }}
            QTabBar::tab:selected {{
                background-color: white;
                border-color: {BORDER_COLOR};
                border-bottom-color: white;
                font-weight: bold;
            }}
            QTabBar::tab:hover:!selected {{
                background-color: {GRAY_COLOR};
                border-color: {SECONDARY_COLOR};
            }}
            /* 分组框样式 */
            QGroupBox {{
                border: 1px solid {BORDER_COLOR};
                border-radius: 6px;
                margin-top: 10px;
                padding: 10px;
                background-color: white;
            }}
            QGroupBox::title {{
                color: {PRIMARY_COLOR};
                subcontrol-origin: margin;
                left: 10px;
                padding: 0 5px 0 5px;
                font-weight: bold;
            }}
            /* 按钮样式 */
            QPushButton {{
                background-color: {PRIMARY_COLOR};
                color: white;
                border: none;
                border-radius: 4px;
                padding: 6px 12px;
                font-size: 14px;
            }}
            QPushButton:hover {{
                background-color: {SECONDARY_COLOR};
            }}
            QPushButton:pressed {{
                background-color: {PRIMARY_COLOR};
                padding: 7px 11px 5px 13px;
            }}
            QPushButton:disabled {{
                background-color: {GRAY_COLOR};
                color: #999;
            }}
            /* 特殊按钮样式 */
            QPushButton#installBtn {{
                background-color: {ACCENT_COLOR};
            }}
            QPushButton#installBtn:hover {{
                background-color: #4CAE4C;
            }}
            QPushButton#cancelBtn {{
                background-color: {DANGER_COLOR};
            }}
            QPushButton#cancelBtn:hover {{
                background-color: #C9302C;
            }}
            /* 列表控件样式 */
            QListWidget {{
                border: 1px solid {BORDER_COLOR};
                border-radius: 4px;
                background-color: white;
                padding: 5px;
                alternate-background-color: {GRAY_COLOR};
            }}
            QListWidget::item {{
                padding: 3px;
                border-radius: 2px;
            }}
            QListWidget::item:selected {{
                background-color: {SECONDARY_COLOR};
                color: white;
            }}
            QListWidget::item:hover {{
                background-color: {GRAY_COLOR};
            }}
            /* 进度条样式 */
            QProgressBar {{
                border: 1px solid {BORDER_COLOR};
                border-radius: 4px;
                text-align: center;
                height: 20px;
            }}
            QProgressBar::chunk {{
                background-color: {ACCENT_COLOR};
                border-radius: 3px;
            }}
            /* 文本编辑框(日志)样式 */
            QTextEdit, QTextBrowser {{
                border: 1px solid {BORDER_COLOR};
                border-radius: 4px;
                background-color: white;
                padding: 5px;
                font-family: "Consolas", "Monaco", monospace;
                font-size: 12px;
            }}
            /* 输入框样式 */
            QLineEdit {{
                border: 1px solid {BORDER_COLOR};
                border-radius: 4px;
                padding: 5px;
                background-color: white;
            }}
            QLineEdit:focus {{
                border-color: {SECONDARY_COLOR};
                outline: none;
            }}
            /* 复选框样式 */
            QCheckBox {{
                padding: 3px;
            }}
            QCheckBox:hover {{
                color: {PRIMARY_COLOR};
            }}
            QCheckBox::indicator:checked {{
                color: {PRIMARY_COLOR};
            }}
            /* 分割器样式 */
            QSplitter::handle {{
                background-color: {BORDER_COLOR};
            }}
            QSplitter::handle:hover {{
                background-color: {SECONDARY_COLOR};
            }}
            /* 下拉框样式 */
            QComboBox {{
                border: 1px solid {BORDER_COLOR};
                border-radius: 4px;
                padding: 5px;
                background-color: white;
            }}
            QComboBox:focus {{
                border-color: {SECONDARY_COLOR};
            }}
            /* 滚动区域样式 */
            QScrollArea {{
                border: 1px solid {BORDER_COLOR};
                border-radius: 6px;
                background-color: white;
            }}
            QScrollArea::viewport {{
                background-color: transparent;
            }}
            /* 菜单栏样式 */
            QMenuBar {{
                background-color: {LIGHT_COLOR};
                color: {DARK_COLOR};
            }}
            QMenuBar::item {{
                background-color: {LIGHT_COLOR};
                padding: 4px 8px;
            }}
            QMenuBar::item:selected {{
                background-color: {SECONDARY_COLOR};
                color: white;
            }}
            QMenu {{
                background-color: white;
                border: 1px solid {BORDER_COLOR};
            }}
            QMenu::item {{
                padding: 4px 20px;
            }}
            QMenu::item:selected {{
                background-color: {SECONDARY_COLOR};
                color: white;
            }}
        """)
        # 设置按钮对象名以便应用特殊样式
        self.install_btn.setObjectName("installBtn")
        self.cancel_btn.setObjectName("cancelBtn")
    def create_category_tab(self):
        tab = QWidget()
        layout = QVBoxLayout(tab)
        layout.setSpacing(8)
        splitter = QSplitter(Qt.Vertical)
        layout.addWidget(splitter)
        # 左侧类别列表(增加滚动功能)
        scroll_area = QScrollArea()  # 滚动区域
        scroll_area.setWidgetResizable(True)  # 自适应大小
        splitter.addWidget(scroll_area)
        # 滚动区域内部的容器
        scroll_content = QWidget()
        scroll_layout = QVBoxLayout(scroll_content)
        scroll_area.setWidget(scroll_content)
        # 库类别选择分组框(放入滚动区域)
        category_group = QGroupBox("选择库类别 (可多选)")
        category_layout = QVBoxLayout(category_group)
        category_layout.setSpacing(5)
        self.category_checkboxes = {}
        for category in CATEGORIES.keys():
            cb = QCheckBox(category)
            self.category_checkboxes[category] = cb
            category_layout.addWidget(cb)
        # 添加"全选"和"全不选"按钮
        btn_layout = QHBoxLayout()
        select_all_btn = QPushButton("全选")
        select_all_btn.clicked.connect(lambda: self.check_all_categories(True))
        deselect_all_btn = QPushButton("全不选")
        deselect_all_btn.clicked.connect(lambda: self.check_all_categories(False))
        btn_layout.addWidget(select_all_btn)
        btn_layout.addWidget(deselect_all_btn)
        category_layout.addLayout(btn_layout)
        scroll_layout.addWidget(category_group)  # 将分组框加入滚动区域
        # 右侧选中的库列表
        self.selected_libs_list = QListWidget()
        self.selected_libs_list.setAlternatingRowColors(True)
        splitter.addWidget(self.selected_libs_list)
        # 添加更新按钮
        update_btn = QPushButton("更新选中的库列表")
        update_btn.clicked.connect(self.update_selected_libs)
        layout.addWidget(update_btn)
        self.tabs.addTab(tab, "按类别选择")
    def create_manual_tab(self):
        tab = QWidget()
        layout = QVBoxLayout(tab)
        layout.setSpacing(8)
        # 输入框
        input_group = QGroupBox("输入库名称 (用逗号分隔)")
        input_layout = QVBoxLayout(input_group)
        self.manual_input = QLineEdit()
        self.manual_input.setPlaceholderText("例如: numpy,pandas,requests,flask")
        input_layout.addWidget(self.manual_input)
        layout.addWidget(input_group)
        # 添加按钮
        add_btn = QPushButton("添加到安装列表")
        add_btn.clicked.connect(self.add_manual_libs)
        layout.addWidget(add_btn)
        # 已添加的库列表
        list_group = QGroupBox("已添加的库")
        list_layout = QVBoxLayout(list_group)
        self.manual_libs_list = QListWidget()
        self.manual_libs_list.setAlternatingRowColors(True)
        list_layout.addWidget(self.manual_libs_list)
        layout.addWidget(list_group)
        # 添加移除按钮
        remove_btn = QPushButton("移除选中项")
        remove_btn.clicked.connect(self.remove_manual_lib)
        layout.addWidget(remove_btn)
        self.tabs.addTab(tab, "手动输入")
    def create_file_tab(self):
        tab = QWidget()
        layout = QVBoxLayout(tab)
        layout.setSpacing(8)
        # 文件选择区域
        file_group = QGroupBox("从文件导入库列表")
        file_layout = QVBoxLayout(file_group)
        file_path_layout = QHBoxLayout()
        self.file_path_edit = QLineEdit()
        self.file_path_edit.setReadOnly(True)
        browse_btn = QPushButton("浏览...")
        browse_btn.clicked.connect(self.browse_file)
        file_path_layout.addWidget(self.file_path_edit, 3)
        file_path_layout.addWidget(browse_btn, 1)
        load_btn = QPushButton("加载文件")
        load_btn.clicked.connect(self.load_file_libs)
        file_layout.addLayout(file_path_layout)
        file_layout.addWidget(load_btn)
        layout.addWidget(file_group)
        # 库列表显示
        list_group = QGroupBox("从文件加载的库")
        list_layout = QVBoxLayout(list_group)
        self.file_libs_list = QListWidget()
        self.file_libs_list.setAlternatingRowColors(True)
        list_layout.addWidget(self.file_libs_list)
        layout.addWidget(list_group)
        self.tabs.addTab(tab, "文件导入")
    def create_preset_tab(self):
        tab = QWidget()
        layout = QVBoxLayout(tab)
        layout.setSpacing(8)
        # 预设组合选择
        preset_group = QGroupBox("选择预设组合 (可多选)")
        preset_layout = QVBoxLayout(preset_group)
        preset_layout.setSpacing(5)
        self.preset_checkboxes = {}
        for preset in PRESETS.keys():
            cb = QCheckBox(preset)
            self.preset_checkboxes[preset] = cb
            preset_layout.addWidget(cb)
        # 添加"全选"和"全不选"按钮
        btn_layout = QHBoxLayout()
        select_all_btn = QPushButton("全选")
        select_all_btn.clicked.connect(lambda: self.check_all_presets(True))
        deselect_all_btn = QPushButton("全不选")
        deselect_all_btn.clicked.connect(lambda: self.check_all_presets(False))
        btn_layout.addWidget(select_all_btn)
        btn_layout.addWidget(deselect_all_btn)
        preset_layout.addLayout(btn_layout)
        layout.addWidget(preset_group)
        # 选中的预设库列表
        self.preset_libs_list = QListWidget()
        self.preset_libs_list.setAlternatingRowColors(True)
        layout.addWidget(self.preset_libs_list)
        # 添加更新按钮
        update_btn = QPushButton("更新选中的预设库列表")
        update_btn.clicked.connect(self.update_preset_libs)
        layout.addWidget(update_btn)
        self.tabs.addTab(tab, "预设组合")
    def create_help_tab(self):
        """创建帮助标签页"""
        tab = QWidget()
        layout = QVBoxLayout(tab)
        # 创建带滚动功能的文本浏览器显示帮助内容
        scroll_area = QScrollArea()
        scroll_area.setWidgetResizable(True)
        layout.addWidget(scroll_area)
        help_content = QWidget()
        help_layout = QVBoxLayout(help_content)
        scroll_area.setWidget(help_content)
        # 使用QTextBrowser显示富文本帮助信息
        self.help_browser = QTextBrowser()
        self.help_browser.setOpenExternalLinks(True)  # 允许打开外部链接
        help_layout.addWidget(self.help_browser)
        # 设置帮助内容
        self.set_help_content()
        self.tabs.addTab(tab, "使用帮助")
    def set_help_content(self):
        """设置帮助内容"""
        help_text = """
        Python库自动安装工具 - 使用帮助
        1. 概述
        本工具旨在帮助用户快速、便捷地安装各种Python库。支持多种选择方式,并自动处理安装过程中的常见问题。
        2. 选择Python路径
        在使用工具前,需要指定Python安装路径:
        
            
  • 工具会自动查找系统中已安装的Python版本
                
  • 可从下拉列表中选择需要使用的Python版本
                
  • 也可通过"浏览..."按钮手动指定Python安装目录(需包含python.exe)
                
  • 点击"查找Python"按钮可重新扫描系统中的Python安装
            

            3. 选择库的方式
            工具提供四种选择库的方式,通过顶部标签页切换:
            3.1 按类别选择
            从预设的类别中选择需要安装的库类别:
            
                
  • 勾选需要安装的库类别(可多选)
                
  • 点击"全选"可选择所有类别,点击"全不选"可取消所有选择
                
  • 点击"更新选中的库列表"可查看所选类别包含的所有库
            

            3.2 手动输入
            手动输入需要安装的库名称:
            
                
  • 在输入框中用逗号分隔多个库名称(例如:numpy,pandas,requests)
                
  • 点击"添加到安装列表"将库添加到待安装列表
                
  • 可在列表中选择库并点击"移除选中项"删除不需要的库
            

            3.3 文件导入
            从文本文件导入库列表:
            
                
  • 点击"浏览..."选择包含库列表的文本文件
                
  • 文本文件中每行一个库名称,#号开头的行为注释
                
  • 点击"加载文件"将库导入到列表中
            

            3.4 预设组合
            选择预设的库组合:
            
                
  • 预设组合是针对特定场景(如数据科学、Web开发)的常用库集合
                
  • 勾选需要的预设组合(可多选)
                
  • 点击"更新选中的预设库列表"可查看所选组合包含的所有库
            

            4. 开始安装
            选择好需要安装的库后,点击底部的"开始安装"按钮:
            
                
  • 安装过程中可在日志区域查看详细进度
                
  • 进度条显示整体安装进度
                
  • 可点击"取消安装"中止安装过程
                
  • 安装完成后会显示汇总结果,包括成功、失败和跳过的库
            

            5. 其他功能
            
                
  • 点击"清空日志"可清除当前日志内容
                
  • 安装日志会同时保存到package_installer_gui.log文件中
                
  • 工具会自动尝试多个国内镜像源以提高安装成功率
                
  • 已安装的库会被自动跳过,无需手动处理
            

            6. 常见问题
            Q: 提示"Python可执行文件不存在"怎么办?
            A: 请确保所选路径包含python.exe文件,通常位于Python安装目录的根目录。
            Q: 安装失败的库如何处理?
            A: 可尝试单独安装失败的库,或检查网络连接,也可尝试更换Python环境。
            Q: 工具支持虚拟环境吗?
            A: 支持,只需在Python路径选择中指定虚拟环境的Python目录即可。
            """
            self.help_browser.setHtml(help_text)
        def create_progress_area(self, main_layout):
            # 进度条区域
            progress_group = QGroupBox("安装进度")
            progress_layout = QVBoxLayout(progress_group)
            # 进度信息
            status_layout = QHBoxLayout()
            self.progress_label = QLabel("准备就绪")
            self.progress_label.setMinimumWidth(200)
            self.progress_bar = QProgressBar()
            self.progress_bar.setValue(0)
            status_layout.addWidget(self.progress_label)
            status_layout.addWidget(self.progress_bar)
            progress_layout.addLayout(status_layout)
            main_layout.addWidget(progress_group)
            # 日志区域
            log_group = QGroupBox("安装日志")
            log_layout = QVBoxLayout(log_group)
            self.log_text = QTextEdit()
            self.log_text.setReadOnly(True)
            self.log_text.setLineWrapMode(QTextEdit.WidgetWidth)
            log_layout.addWidget(self.log_text)
            main_layout.addWidget(log_group)
        def create_buttons_area(self, main_layout):
            btn_layout = QHBoxLayout()
            btn_layout.setSpacing(10)
            self.install_btn = QPushButton("开始安装")
            self.install_btn.clicked.connect(self.start_installation)
            self.cancel_btn = QPushButton("取消安装")
            self.cancel_btn.clicked.connect(self.cancel_installation)
            self.cancel_btn.setEnabled(False)
            self.clear_log_btn = QPushButton("清空日志")
            self.clear_log_btn.clicked.connect(self.clear_log)
            btn_layout.addWidget(self.install_btn)
            btn_layout.addWidget(self.cancel_btn)
            btn_layout.addStretch()
            btn_layout.addWidget(self.clear_log_btn)
            main_layout.addLayout(btn_layout)
        # ==================== 帮助相关函数 ====================
        def show_help(self):
            """显示帮助内容,切换到帮助标签页"""
            self.tabs.setCurrentIndex(4)  # 帮助标签页的索引
        def show_about(self):
            """显示关于对话框"""
            about_text = """
            Python库自动安装工具 v1.0
            一个便捷的Python库批量安装工具,支持多种选择方式,
            自动处理安装过程中的常见问题,提高工作效率。
            功能特点:
            - 多方式选择需要安装的库
            - 自动查找系统中的Python环境
            - 支持国内镜像源,提高安装速度
            - 自动跳过已安装的库
            - 详细的安装日志和结果统计
            """
            QMessageBox.about(self, "关于", about_text)
        # ==================== 界面交互函数 ====================
        def check_all_categories(self, check: bool):
            for cb in self.category_checkboxes.values():
                cb.setChecked(check)
        def update_selected_libs(self):
            self.selected_libs_list.clear()
            selected_libs = []
            for category, cb in self.category_checkboxes.items():
                if cb.isChecked() and category in CATEGORIES:
                    selected_libs.extend(CATEGORIES[category])
            unique_libs = list(dict.fromkeys(selected_libs))
            for lib in unique_libs:
                self.selected_libs_list.addItem(lib)
            self.log_text.append(f"已更新选中的库,共 {len(unique_libs)} 个")
        def add_manual_libs(self):
            text = self.manual_input.text().strip()
            if not text:
                QMessageBox.warning(self, "提示", "请输入库名称")
                return
            libs = [lib.strip() for lib in text.split(',') if lib.strip()]
            if not libs:
                QMessageBox.warning(self, "提示", "请输入有效的库名称")
                return
            current_libs = [self.manual_libs_list.item(i).text()
                            for i in range(self.manual_libs_list.count())]
            added_count = 0
            for lib in libs:
                if lib not in current_libs:
                    self.manual_libs_list.addItem(lib)
                    current_libs.append(lib)
                    added_count += 1
            self.log_text.append(f"已添加 {added_count} 个库到手动安装列表")
            self.manual_input.clear()
        def remove_manual_lib(self):
            selected_items = self.manual_libs_list.selectedItems()
            if not selected_items:
                QMessageBox.warning(self, "提示", "请先选择要移除的库")
                return
            for item in selected_items:
                row = self.manual_libs_list.row(item)
                self.manual_libs_list.takeItem(row)
            self.log_text.append(f"已移除 {len(selected_items)} 个库")
        def browse_file(self):
            file_path, _ = QFileDialog.getOpenFileName(
                self, "选择库列表文件", "", "文本文件 (*.txt);;所有文件 (*)"
            )
            if file_path:
                self.file_path_edit.setText(file_path)
        def load_file_libs(self):
            file_path = self.file_path_edit.text()
            if not file_path or not os.path.exists(file_path):
                QMessageBox.warning(self, "错误", "请选择有效的文件")
                return
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    lines = f.readlines()
                libs = [line.strip() for line in lines
                        if line.strip() and not line.strip().startswith('#')]
                if not libs:
                    QMessageBox.information(self, "提示", "文件中没有有效的库名称")
                    return
                self.file_libs_list.clear()
                for lib in libs:
                    self.file_libs_list.addItem(lib)
                self.log_text.append(f"从文件加载了 {len(libs)} 个库")
            except Exception as e:
                QMessageBox.critical(self, "错误", f"读取文件失败: {str(e)}")
        def check_all_presets(self, check: bool):
            for cb in self.preset_checkboxes.values():
                cb.setChecked(check)
        def update_preset_libs(self):
            self.preset_libs_list.clear()
            selected_libs = []
            for preset, cb in self.preset_checkboxes.items():
                if cb.isChecked() and preset in PRESETS:
                    selected_libs.extend(PRESETS[preset])
            unique_libs = list(dict.fromkeys(selected_libs))
            for lib in unique_libs:
                self.preset_libs_list.addItem(lib)
            self.log_text.append(f"已更新选中的预设库,共 {len(unique_libs)} 个")
        # ==================== 安装控制函数 ====================
        def get_selected_packages(self) -> List[str]:
            tab_index = self.tabs.currentIndex()
            packages = []
            if tab_index == 0:  # 按类别选择
                for category, cb in self.category_checkboxes.items():
                    if cb.isChecked() and category in CATEGORIES:
                        packages.extend(CATEGORIES[category])
            elif tab_index == 1:  # 手动输入
                packages = [self.manual_libs_list.item(i).text()
                            for i in range(self.manual_libs_list.count())]
            elif tab_index == 2:  # 文件导入
                packages = [self.file_libs_list.item(i).text()
                            for i in range(self.file_libs_list.count())]
            elif tab_index == 3:  # 预设组合
                for preset, cb in self.preset_checkboxes.items():
                    if cb.isChecked() and preset in PRESETS:
                        packages.extend(PRESETS[preset])
            return list(dict.fromkeys(packages))
        def start_installation(self):
            # 验证Python路径
            self.python_path = self.python_path_edit.text().strip()
            if not self.python_path:
                QMessageBox.warning(self, "路径错误", "请指定Python安装路径")
                return
            if not os.path.exists(os.path.join(self.python_path, "python.exe")):
                QMessageBox.warning(
                    self, "路径无效",
                    f"所选路径不包含python.exe: {self.python_path}"
                )
                return
            packages = self.get_selected_packages()
            if not packages:
                QMessageBox.warning(self, "提示", "请先选择要安装的库")
                return
            reply = QMessageBox.question(
                self, "确认安装",
                f"即将使用 {os.path.basename(self.python_path)} 安装 {len(packages)} 个库,是否继续?",
                QMessageBox.Yes | QMessageBox.No, QMessageBox.No
            )
            if reply != QMessageBox.Yes:
                return
            self.install_btn.setEnabled(False)
            self.cancel_btn.setEnabled(True)
            self.progress_bar.setValue(0)
            self.log_text.append(f"\n===== 开始安装 {len(packages)} 个库 =====")
            self.log_text.append(f"使用Python路径: {self.python_path}")
            # 传递Python路径给安装线程
            self.install_worker = InstallWorker(packages, self.python_path)
            self.install_worker.progress_updated.connect(self.update_progress)
            self.install_worker.log_updated.connect(self.append_log)
            self.install_worker.install_finished.connect(self.installation_finished)
            self.install_worker.start()
        def cancel_installation(self):
            if self.install_worker and self.install_worker.isRunning():
                reply = QMessageBox.question(
                    self, "确认取消",
                    "确定要取消安装吗?",
                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No
                )
                if reply == QMessageBox.Yes:
                    self.log_text.append("正在取消安装...")
                    self.install_worker.stop()
                    self.cancel_btn.setEnabled(False)
        def update_progress(self, value: int, text: str):
            self.progress_bar.setValue(value)
            self.progress_label.setText(text)
        def append_log(self, text: str):
            self.log_text.append(text)
            self.log_text.moveCursor(self.log_text.textCursor().End)
        def clear_log(self):
            self.log_text.clear()
            # 清空日志后重新显示当前Python路径信息
            self.append_python_path_to_log()
        def installation_finished(self, results: Dict):
            self.install_btn.setEnabled(True)
            self.cancel_btn.setEnabled(False)
            self.progress_label.setText("安装完成")
            self.log_text.append("\n===== 安装结果汇总 =====")
            self.log_text.append(f"总任务数:{results['total']}")
            self.log_text.append(f"成功安装:{len(results['success'])}")
            self.log_text.append(f"已安装跳过:{len(results['skipped'])}")
            self.log_text.append(f"安装失败:{len(results['failed'])}")
            if results['failed']:
                self.log_text.append("\n失败的库(前10个):")
                for fail in results['failed'][:10]:
                    self.log_text.append(f"  - {fail}")
                if len(results['failed']) > 10:
                    self.log_text.append(f"  - ... 还有 {len(results['failed']) - 10} 个失败项")
            if results['failed']:
                QMessageBox.information(
                    self, "安装完成",
                    f"安装已完成,成功 {len(results['success'])} 个,失败 {len(results['failed'])} 个"
                )
            else:
                QMessageBox.information(
                    self, "安装完成",
                    "所有库均安装成功!"
                )
    if __name__ == "__main__":
        # 确保中文显示正常
        font = QFont("SimHei")
        app = QApplication(sys.argv)
        app.setFont(font)
        window = LibraryInstallerGUI()
        sys.exit(app.exec_())
    自动安装库GUI版0.5版.zip

    路径, 样式

  • wyf0728   

    我用AI写了一个代码,提示我电脑没有安装Python和所需的库
    提示如下:
    正确运行Python脚本的步骤
    1. 安装必要的库
    首先确保电脑已安装Python和所需的库(如果未安装):
    打开 命令提示符(CMD) 或 终端,输入以下命令:
    Bash
    pip install pandas openpyxl
    2. 准备文件
    将Python脚本保存为 import_data_with_formulas.py(确保文件扩展名为 .py,而非 .txt 或 .xls)。
    将 原始数据.txt 和 原始数据计算.xls 与脚本放在同一文件夹中。
    3. 运行脚本
    打开 命令提示符(CMD) 或 终端。
    导航到脚本所在的文件夹(例如:cd C:\你的文件夹路径)。
    输入以下命令运行脚本:
    Bash
      python import_data_with_formulas.py
    4. 查看结果
    脚本运行完成后,会显示 数据已成功导入到 原始数据计算.xls。
    此时打开 原始数据计算.xls,即可看到导入的数据和保留的公式。
    请问如何操作
    wfk521
    OP
      

    1.增加了判断python版本
    2.增加了库更新功能
    3.增加了几个镜像源

    [color=]下载地址:

    https://wwtk.lanzoue.com/inuF93345nyd 密码:52pj
    https://pan.baidu.com/s/1MnHoWYa1l2FHV9O_tFC62g?pwd=52pj 提取码: 52pj
    [Python] 纯文本查看 复制代码import sys
    import subprocess
    import logging
    import time
    import os
    import glob
    from typing import List, Dict, Optional, Tuple
    from PyQt5.QtWidgets import (QApplication, QMainWindow, QTabWidget, QWidget, QVBoxLayout,
                                 QHBoxLayout, QLabel, QPushButton, QListWidget, QListWidgetItem,
                                 QTextEdit, QProgressBar, QFileDialog, QLineEdit, QGroupBox,
                                 QCheckBox, QMessageBox, QScrollArea, QSplitter, QComboBox,
                                 QAction, QMenuBar, QMenu, QTextBrowser, QTableWidget,
                                 QTableWidgetItem, QHeaderView, QAbstractItemView, QFrame,
                                 QRadioButton)
    from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer
    from PyQt5.QtGui import QFont, QColor, QIcon, QPalette
    # ==================== 配置与常量定义 ====================
    # 日志配置
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(levelname)s - %(message)s',
        handlers=[
            logging.FileHandler("package_installer_gui.log", encoding='utf-8'),
        ]
    )
    # 扩展所有国内镜像源
    MIRRORS = {
        "默认(PyPI)": None,
        "清华": "https://pypi.tuna.tsinghua.edu.cn/simple",
        "阿里": "https://mirrors.aliyun.com/pypi/simple/",
        "腾讯云": "https://mirrors.cloud.tencent.com/pypi/simple/",
        "中科大": "https://pypi.mirrors.ustc.edu.cn/simple/",
        "华中科大": "http://pypi.hustunique.com/",
        "豆瓣": "http://pypi.douban.com/simple/"
    }
    # 安装配置
    MAX_RETRIES = 3  # 最大重试次数
    RETRY_DELAY = 3  # 重试延迟(秒)
    # 样式常量 - 现代美观的配色
    PRIMARY_COLOR = "#165DFF"  # 主色调:现代蓝色
    SECONDARY_COLOR = "#69b1ff"  # 次要色:浅蓝色
    ACCENT_COLOR = "#36D399"  # 强调色:绿色(成功)
    WARNING_COLOR = "#FFAB00"  # 警告色:橙色
    DANGER_COLOR = "#F87272"  # 危险色:红色(错误/取消)
    LIGHT_COLOR = "#F9FAFB"  # 浅色背景(更浅的灰色)
    DARK_COLOR = "#1F2937"  # 深色文本(深灰而非纯黑)
    GRAY_COLOR = "#E5E7EB"  # 灰色背景
    BORDER_COLOR = "#D1D5DB"  # 边框色(更浅的灰色)
    # ==================== 核心安装与更新功能 ====================
    class InstallWorker(QThread):
        progress_updated = pyqtSignal(int, str)  # 进度值, 状态文本
        log_updated = pyqtSignal(str)  # 日志文本
        install_finished = pyqtSignal(dict)  # 安装结果
        def __init__(self, packages: List[str], python_path: str, update_mode: bool = False):
            super().__init__()
            self.packages = packages
            self.python_path = python_path  # 接收Python路径参数
            self.update_mode = update_mode  # 是否为更新模式
            self.is_running = True
        def run(self):
            results = {
                "success": [],
                "failed": [],
                "skipped": [],
                "total": len(self.packages)
            }
            if not self.packages:
                self.log_updated.emit("没有需要处理的包")
                self.install_finished.emit(results)
                return
            # 验证Python路径有效性并在日志中显示
            self.log_updated.emit(f"当前使用的Python安装路径: {self.python_path}")
            self.log_updated.emit(f"Python可执行文件位置: {os.path.join(self.python_path, 'python.exe')}")
            self.log_updated.emit(f"pip可执行文件位置: {os.path.join(self.python_path, 'Scripts', 'pip.exe')}")
            if not self.verify_python_path():
                self.install_finished.emit(results)
                return
            if not self.check_and_upgrade_pip():
                self.install_finished.emit(results)
                return
            total = len(self.packages)
            for i, package in enumerate(self.packages):
                if not self.is_running:
                    self.log_updated.emit("操作已取消")
                    break
                progress = int((i + 1) / total * 100)
                action = "更新" if self.update_mode else "安装"
                self.progress_updated.emit(progress, f"正在{action}: {package}")
                result = self.process_single_package(package)
                status = result["status"]
                reason = result["reason"]
                if status == "success":
                    results["success"].append(package)
                    self.log_updated.emit(f"✅ 成功{action}: {package}")
                elif status == "skipped":
                    results["skipped"].append(package)
                    self.log_updated.emit(f"⏭️ 跳过{action}: {package}")
                else:
                    results["failed"].append(f"{package} ({reason[:50]})")
                    self.log_updated.emit(f"❌ {action}失败: {package} ({reason[:50]})")
            self.install_finished.emit(results)
        def stop(self):
            self.is_running = False
        def verify_python_path(self) -> bool:
            """验证Python路径是否有效"""
            if not self.python_path:
                self.log_updated.emit("错误:未指定Python路径")
                return False
            python_exe = os.path.join(self.python_path, "python.exe")
            pip_exe = os.path.join(self.python_path, "Scripts", "pip.exe")
            if not os.path.exists(python_exe):
                self.log_updated.emit(f"错误:Python可执行文件不存在 - {python_exe}")
                return False
            if not os.path.exists(pip_exe):
                self.log_updated.emit(f"警告:未找到pip可执行文件 - {pip_exe}")
                self.log_updated.emit("尝试修复pip环境...")
            return True
        def run_command(self, command: List[str], description: str) -> (bool, str):
            try:
                result = subprocess.run(
                    command,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    text=True,
                    encoding='utf-8',
                    timeout=300
                )
                if result.returncode == 0:
                    self.log_updated.emit(f"{description} 成功")
                    return (True, result.stdout)
                else:
                    error_msg = result.stderr.strip()[:500]
                    self.log_updated.emit(f"{description} 失败: {error_msg[:100]}")
                    return (False, error_msg)
            except subprocess.TimeoutExpired:
                msg = f"{description} 超时(超过5分钟)"
                self.log_updated.emit(msg)
                return (False, msg)
            except Exception as e:
                msg = f"执行命令时发生错误: {str(e)}"
                self.log_updated.emit(msg)
                return (False, msg)
        def check_and_upgrade_pip(self) -> bool:
            self.log_updated.emit("===== 检查pip环境 =====")
            # 使用指定路径的Python和pip
            success, _ = self.run_command(
                [os.path.join(self.python_path, "python.exe"), "-m", "pip", "--version"],
                "检查pip可用性"
            )
            if not success:
                self.log_updated.emit("pip不可用,尝试修复...")
                for i in range(MAX_RETRIES):
                    fix_success, _ = self.run_command(
                        [os.path.join(self.python_path, "python.exe"), "-m", "ensurepip", "--upgrade"],
                        f"修复pip(尝试 {i + 1}/{MAX_RETRIES})"
                    )
                    if fix_success:
                        success = True
                        break
                    time.sleep(RETRY_DELAY)
                if not success:
                    self.log_updated.emit("pip修复失败,无法继续操作")
                    return False
            self.log_updated.emit("升级pip到最新版本...")
            self.run_command(
                [os.path.join(self.python_path, "python.exe"), "-m", "pip", "install", "--upgrade", "pip"],
                "升级pip"
            )
            return True
        def is_package_installed(self, package: str) -> bool:
            pure_package = package.split("[")[0]
            success, _ = self.run_command(
                [os.path.join(self.python_path, "python.exe"), "-m", "pip", "show", pure_package],
                f"检查 {pure_package} 是否已安装"
            )
            return success
        def get_package_versions(self, package: str) -> Tuple[str, str]:
            """获取包的当前版本和最新版本"""
            pure_package = package.split("[")[0]
            current_version = "未知"
            latest_version = "未知"
            # 获取当前版本
            try:
                result = subprocess.run(
                    [os.path.join(self.python_path, "python.exe"), "-m", "pip", "show", pure_package],
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    text=True,
                    encoding='utf-8',
                    timeout=10
                )
                if result.returncode == 0:
                    for line in result.stdout.splitlines():
                        if line.startswith("Version:"):
                            current_version = line.split(":", 1)[1].strip()
                            break
            except Exception as e:
                logging.error(f"获取 {pure_package} 当前版本时出错: {str(e)}")
            # 获取最新版本
            try:
                result = subprocess.run(
                    [os.path.join(self.python_path, "python.exe"), "-m", "pip", "index", "versions", pure_package],
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    text=True,
                    encoding='utf-8',
                    timeout=15
                )
                if result.returncode == 0:
                    lines = result.stdout.splitlines()
                    for line in lines:
                        if "可用版本:" in line:
                            versions = line.split("可用版本:")[1].strip()
                            latest_version = versions.split(",")[0].strip()
                            break
            except Exception as e:
                logging.warning(f"获取 {pure_package} 最新版本时出错: {str(e)}")
            return (current_version, latest_version)
        def process_single_package(self, package: str) -> Dict:
            """处理单个包的安装或更新,尝试所有镜像源"""
            result = {
                "package": package,
                "status": "failed",
                "reason": ""
            }
            pure_package = package.split("[")[0]
            # 对于更新模式,检查是否已安装
            if self.update_mode and not self.is_package_installed(package):
                result["status"] = "skipped"
                result["reason"] = "未安装,无法更新"
                return result
            # 对于安装模式,检查是否已安装
            if not self.update_mode and self.is_package_installed(package):
                result["status"] = "skipped"
                result["reason"] = "已安装,如需更新请使用更新功能"
                return result
            for retry in range(MAX_RETRIES):
                if not self.is_running:
                    result["reason"] = "操作已取消"
                    return result
                # 尝试所有镜像源
                for mirror_name, mirror_url in MIRRORS.items():
                    # 构建命令,更新模式使用--upgrade
                    if self.update_mode:
                        install_cmd = [
                            os.path.join(self.python_path, "python.exe"),
                            "-m", "pip", "install", "--upgrade", pure_package
                        ]
                    else:
                        install_cmd = [
                            os.path.join(self.python_path, "python.exe"),
                            "-m", "pip", "install", package
                        ]
                    if mirror_url:
                        host = mirror_url.split("//")[-1].split("/")[0]
                        install_cmd.extend(["-i", mirror_url, "--trusted-host", host])
                    action = "更新" if self.update_mode else "安装"
                    desc = f"{action} {package}(镜像:{mirror_name},重试:{retry + 1}/{MAX_RETRIES})"
                    success, output = self.run_command(install_cmd, desc)
                    # 验证操作是否成功
                    if success and self.is_package_installed(pure_package):
                        result["status"] = "success"
                        result["reason"] = f"{action}成功(使用镜像:{mirror_name})"
                        return result
                    elif success:
                        result["reason"] = f"{action}命令成功,但验证失败"
                    else:
                        result["reason"] = f"{action}失败"
                if retry  Tuple[bool, str, str]:
            """检查指定包是否安装及版本,可选检查最新版本"""
            pure_package = package.split("[")[0]
            version = "未知"
            latest_version = "未知"
            try:
                # 使用pip show命令检查包信息
                result = subprocess.run(
                    [os.path.join(self.python_path, "python.exe"), "-m", "pip", "show", pure_package],
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    text=True,
                    encoding='utf-8',
                    timeout=10
                )
                if result.returncode == 0:
                    # 从输出中提取版本信息
                    for line in result.stdout.splitlines():
                        if line.startswith("Version:"):
                            version = line.split(":", 1)[1].strip()
                            break
                    # 检查最新版本
                    if self.check_updates:
                        try:
                            result = subprocess.run(
                                [os.path.join(self.python_path, "python.exe"), "-m", "pip", "index", "versions",
                                 pure_package],
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE,
                                text=True,
                                encoding='utf-8',
                                timeout=15
                            )
                            if result.returncode == 0:
                                lines = result.stdout.splitlines()
                                for line in lines:
                                    if "可用版本:" in line:
                                        versions = line.split("可用版本:")[1].strip()
                                        latest_version = versions.split(",")[0].strip()
                                        break
                        except Exception as e:
                            logging.warning(f"获取 {pure_package} 最新版本时出错: {str(e)}")
                    return (True, version, latest_version)
                else:
                    return (False, version, latest_version)
            except Exception as e:
                logging.error(f"检查包 {pure_package} 时出错: {str(e)}")
                return (False, version, latest_version)
    # ==================== Python检测线程 ====================
    class PythonDetectionWorker(QThread):
        detection_finished = pyqtSignal(list)  # 检测到的Python安装列表
        def run(self):
            """检测系统中安装的Python版本,增强版检测逻辑"""
            python_paths = []
            # 常见的Python安装路径,扩展更多可能的位置
            search_paths = [
                "C:\\Python*",
                "C:\\Program Files\\Python*",
                "C:\\Program Files (x86)\\Python*",
                os.path.expanduser("~") + "\\AppData\\Local\\Programs\\Python\\Python*",
                os.path.expanduser("~") + "\\Anaconda*",
                os.path.expanduser("~") + "\\Miniconda*",
                "C:\\ProgramData\\Anaconda*",
                "C:\\ProgramData\\Miniconda*",
                "D:\\Python*",  # 其他盘符
                "E:\\Python*",
                "F:\\Python*"
            ]
            # 搜索常见路径
            for path_pattern in search_paths:
                for path in glob.glob(path_pattern):
                    # 检查是否包含python.exe
                    if os.path.exists(os.path.join(path, "python.exe")):
                        if path not in python_paths:
                            python_paths.append(path)
            # 检查环境变量中的Python路径
            try:
                env_python = subprocess.check_output(
                    ["where", "python"],
                    shell=True,
                    text=True,
                    timeout=10
                ).splitlines()
                for path in env_python:
                    if path and os.path.exists(path):
                        python_dir = os.path.dirname(path)
                        if python_dir not in python_paths:
                            python_paths.append(python_dir)
            except:
                pass
            # 检查py launcher (py.exe) 能找到的Python版本
            try:
                py_versions = subprocess.check_output(
                    ["py", "-0p"],
                    shell=True,
                    text=True,
                    timeout=10
                ).splitlines()
                for line in py_versions:
                    if "Python" in line and "Installed at" in line:
                        path = line.split("Installed at:")[-1].strip()
                        if path and os.path.exists(os.path.join(path, "python.exe")) and path not in python_paths:
                            python_paths.append(path)
            except:
                pass
            self.detection_finished.emit(python_paths)
    # ==================== 库列表定义 ====================
    BASIC_UTILS = [
        "requests", "beautifulsoup4", "lxml", "pandas", "numpy", "matplotlib",
        "seaborn", "scipy", "scikit-learn", "tensorflow", "torch", "nltk",
        "spacy", "opencv-python", "jupyter", "notebook", "pyarrow", "fastparquet",
        "sqlalchemy", "psycopg2-binary", "mysql-connector-python", "redis",
        "pymongo", "kafka-python", "pyspark", "dash", "flask", "django",
        "fastapi", "uvicorn", "pytest", "coverage", "black", "flake8",
        "mypy", "pydantic","PyInstaller"
    ]
    DATA_SCIENCE = [
        "statsmodels", "prophet", "pmdarima", "tsfresh", "sktime", "lifetimes",
        "mlfinlab", "yfinance", "ta-lib", "zipline", "backtrader", "pyfolio",
        "quandl", "pandas-datareader", "plotly", "plotly-express", "altair",
        "bokeh", "holoviews", "panel", "hvplot", "datashader"
    ]
    COMPUTER_VISION = [
        "opencv-contrib-python", "pillow", "scikit-image", "imutils", "mahotas",
        "simpleitk", "pydicom", "nipype", "nibabel", "vtk", "mayavi", "itk",
        "open3d", "trimesh", "pyvista", "face_recognition", "deepface", "dlib",
        "mediapipe", "easyocr", "paddleocr", "pytesseract"
    ]
    NLP = [
        "gensim", "sumy", "textblob", "pattern", "flair", "allennlp",
        "transformers", "sentence-transformers", "langchain", "llama-index",
        "chromadb", "pinecone-client", "weaviate-client", "openai",
        "anthropic", "huggingface_hub"
    ]
    WEB_DEV = [
        "selenium", "playwright", "requests-html", "scrapy", "pyppeteer",
        "webdriver-manager", "undetected-chromedriver", "fastapi-restful",
        "djangorestframework", "flask-socketio", "websockets", "python-multipart",
        "aiohttp", "sanic", "tornado", "bottle", "web.py", "falcon", "quart",
        "pyramid", "cherrypy"
    ]
    DATABASES = [
        "pymysql", "sqlite3", "elasticsearch", "sqlglot", "tabula-py", "xlwings",
        "pydub", "python-telegram-bot", "discord.py", "praw", "tweepy",
        "facebook-sdk", "spotipy", "pyrogram", "qrcode", "pyqrcode", "pyzbar"
    ]
    GUI = [
        "PyQt5", "PyQt6", "PySide6", "tkinter", "wxPython", "flet", "kivy",
        "customtkinter", "pysimplegui", "pygame", "pyautogui", "screeninfo", "mss"
    ]
    TESTING = [
        "pytest-cov", "tox", "pre-commit", "behave", "robotframework", "coverage",
        "mock", "factory_boy", "hypothesis", "pytest-xdist", "pytest-mock"
    ]
    DEVOPS = [
        "pyinstaller", "cx_Freeze", "poetry", "pipenv", "docker-compose",
        "kubernetes-client", "fabric", "paramiko", "ansible", "invoke", "pyyaml",
        "toml", "python-dotenv", "configparser"
    ]
    SCIENCE = [
        "sympy", "cvxpy", "pyomo", "astropy", "biopython", "rdkit", "openbabel",
        "pymol", "mdtraj", "nglview", "pyro", "pymc3", "stan", "arviz", "emcee"
    ]
    GEO = [
        "geopandas", "folium", "cartopy", "pydeck", "osmnx", "shapely", "fiona",
        "rasterio", "pyproj", "geopy", "utm", "earthpy", "sentinelsat",
        "landsatxplore", "planet", "eo-learn", "solaris"
    ]
    LLM = [
        "llama-cpp-python", "gpt4all", "llama-index", "langchain", "chromadb",
        "pinecone-client", "weaviate-client", "faiss-cpu", "sentence-transformers",
        "transformers", "diffusers", "huggingface_hub", "openai", "anthropic",
        "llmkira", "llmware"
    ]
    # 常用库检查列表(综合各类别中的常用库)
    COMMON_LIBRARIES = [
        # 基础工具
        "requests", "pandas", "numpy", "matplotlib", "scipy", "scikit-learn",
        # 数据科学
        "plotly", "statsmodels",
        # Web开发
        "flask", "django", "fastapi",
        # 数据库
        "sqlalchemy",
        # GUI
        "PyQt5", "tkinter",
        # 测试
        "pytest",
        # AI相关
        "tensorflow", "torch", "transformers",
        # 其他
        "opencv-python", "jupyter", "pillow"
    ]
    PRESETS = {
        "数据科学基础包": BASIC_UTILS + DATA_SCIENCE,
        "Web开发全家桶": BASIC_UTILS + WEB_DEV,
        "AI与大语言模型": LLM + NLP + COMPUTER_VISION,
        "自动化测试套装": TESTING + DEVOPS,
        "全栈开发环境": BASIC_UTILS + WEB_DEV + GUI + DATABASES,
        "科学计算环境": SCIENCE + GEO + DATA_SCIENCE
    }
    CATEGORIES = {
        "基础工具库": BASIC_UTILS,
        "数据科学与机器学习": DATA_SCIENCE,
        "计算机视觉与图像处理": COMPUTER_VISION,
        "自然语言处理": NLP,
        "Web开发与自动化": WEB_DEV,
        "数据库与数据存储": DATABASES,
        "GUI与桌面应用": GUI,
        "测试与CI/CD": TESTING,
        "DevOps与部署": DEVOPS,
        "科学计算与工程": SCIENCE,
        "地理空间分析": GEO,
        "大语言模型与AI应用": LLM
    }
    # ==================== 主界面实现 ====================
    class LibraryInstallerGUI(QMainWindow):
        def __init__(self):
            super().__init__()
            self.install_worker = None
            self.check_worker = None
            self.detection_worker = None
            self.python_path = self.get_default_python_path()  # 默认Python路径
            self.init_ui()
            self.apply_styles()  # 应用样式
            self.start_python_detection()  # 启动Python检测
            self.python_installed = False  # Python安装状态
            self.detected_python_versions = {}  # 存储检测到的Python版本信息
        def init_ui(self):
            """初始化UI界面,包含增强的Python检测功能"""
            self.setWindowTitle("Python库自动安装工具")
            self.setGeometry(100, 100, 1100, 850)  # 窗口初始大小
            self.setMinimumSize(950, 900)  # 最小窗口大小
            # 创建菜单栏和帮助菜单
            self.create_menu_bar()
            # 创建主部件和布局
            central_widget = QWidget()
            self.setCentralWidget(central_widget)
            main_layout = QVBoxLayout(central_widget)
            main_layout.setContentsMargins(8, 8, 8, 8)
            main_layout.setSpacing(8)
            # 添加Python安装状态提示标签 - 增强版
            self.python_status_label = QLabel("正在检测Python安装...")
            self.python_status_label.setAlignment(Qt.AlignCenter)
            self.python_status_label.setStyleSheet(f"padding: 5px; border-radius: 4px;")
            main_layout.addWidget(self.python_status_label)
            # 添加Python路径选择区域
            python_group = QGroupBox("Python安装路径 (需指向包含python.exe的目录)")
            python_layout = QVBoxLayout(python_group)
            python_layout.setContentsMargins(8, 8, 8, 8)
            python_layout.setSpacing(5)
            # 路径选择布局
            path_layout = QHBoxLayout()
            path_layout.setSpacing(5)
            self.python_path_edit = QLineEdit()
            self.python_path_edit.setText(self.python_path)
            # 路径变化时更新日志显示
            self.python_path_edit.textChanged.connect(self.on_python_path_changed)
            browse_btn = QPushButton("浏览...")
            browse_btn.setMinimumHeight(28)
            browse_btn.clicked.connect(self.browse_python_path)
            find_btn = QPushButton("查找Python")
            find_btn.setMinimumHeight(28)
            find_btn.clicked.connect(self.start_python_detection)
            path_layout.addWidget(self.python_path_edit, 5)
            path_layout.addWidget(browse_btn, 1)
            path_layout.addWidget(find_btn, 1)
            # 已找到的Python版本列表
            version_layout = QHBoxLayout()
            version_layout.setSpacing(5)
            version_layout.addWidget(QLabel("已检测到的Python版本:"))
            self.python_versions_combo = QComboBox()
            self.python_versions_combo.setToolTip("选择已检测到的Python版本")
            self.python_versions_combo.setMinimumWidth(350)
            self.python_versions_combo.currentIndexChanged.connect(self.on_python_version_selected)
            version_layout.addWidget(self.python_versions_combo)
            python_layout.addLayout(path_layout)
            python_layout.addLayout(version_layout)
            main_layout.addWidget(python_group)
            # 添加分隔线增强视觉区分
            line = QFrame()
            line.setFrameShape(QFrame.HLine)
            line.setFrameShadow(QFrame.Sunken)
            line.setStyleSheet(f"background-color: {BORDER_COLOR}; margin: 5px 0;")
            main_layout.addWidget(line)
            # 创建标题标签
            title_label = QLabel("Python库自动安装工具")
            title_label.setAlignment(Qt.AlignCenter)
            title_font = title_label.font()
            title_font.setPointSize(14)
            title_font.setBold(True)
            title_label.setFont(title_font)
            title_label.setStyleSheet(f"color: {PRIMARY_COLOR}; margin: 5px 0;")
            main_layout.addWidget(title_label)
            # 创建标签页控件
            self.tabs = QTabWidget()
            self.tabs.setElideMode(Qt.ElideRight)  # 标签文本过长时省略
            self.tabs.setDocumentMode(True)  # 文档模式,更现代
            self.tabs.setMinimumHeight(400)
            # 添加标签页
            self.create_category_tab()  # 按类别选择
            self.create_manual_tab()  # 手动输入
            self.create_file_tab()  # 文件导入
            self.create_preset_tab()  # 预设组合
            self.create_check_tab()  # 库检查
            self.create_update_tab()  # 库更新
            self.create_mirror_tab()  # 镜像源管理
            self.create_help_tab()  # 帮助标签页
            main_layout.addWidget(self.tabs)
            # 创建进度区域
            progress_group = QGroupBox("操作进度")
            progress_layout = QVBoxLayout(progress_group)
            progress_layout.setContentsMargins(8, 8, 8, 8)
            progress_layout.setSpacing(5)
            # 进度信息
            status_layout = QHBoxLayout()
            status_layout.setSpacing(5)
            self.progress_label = QLabel("准备就绪")
            self.progress_label.setMinimumWidth(150)
            self.progress_bar = QProgressBar()
            self.progress_bar.setMinimumHeight(22)
            self.progress_bar.setValue(0)
            status_layout.addWidget(self.progress_label)
            status_layout.addWidget(self.progress_bar)
            progress_layout.addLayout(status_layout)
            main_layout.addWidget(progress_group)
            # 创建日志区域
            log_group = QGroupBox("操作日志")
            log_layout = QVBoxLayout(log_group)
            log_layout.setContentsMargins(8, 8, 8, 8)
            log_layout.setSpacing(5)
            self.log_text = QTextEdit()
            self.log_text.setReadOnly(True)
            self.log_text.setLineWrapMode(QTextEdit.WidgetWidth)
            self.log_text.setMinimumHeight(120)
            log_layout.addWidget(self.log_text)
            main_layout.addWidget(log_group)
            # 创建底部按钮区域
            buttons_layout = QHBoxLayout()
            buttons_layout.setSpacing(8)
            self.install_btn = QPushButton("开始安装")
            self.install_btn.setMinimumHeight(30)
            self.install_btn.setMinimumWidth(90)
            self.install_btn.setEnabled(False)  # 初始禁用,检测到Python后启用
            self.cancel_btn = QPushButton("取消操作")
            self.cancel_btn.setMinimumHeight(30)
            self.cancel_btn.setMinimumWidth(90)
            self.cancel_btn.setEnabled(False)
            self.clear_log_btn = QPushButton("清空日志")
            self.clear_log_btn.setMinimumHeight(30)
            self.clear_log_btn.setMinimumWidth(90)
            buttons_layout.addWidget(self.install_btn)
            buttons_layout.addWidget(self.cancel_btn)
            buttons_layout.addStretch()
            buttons_layout.addWidget(self.clear_log_btn)
            # 连接按钮信号
            self.install_btn.clicked.connect(self.start_installation)
            self.cancel_btn.clicked.connect(self.cancel_installation)
            self.clear_log_btn.clicked.connect(self.clear_log)
            main_layout.addLayout(buttons_layout)
            self.show()
        def create_menu_bar(self):
            """创建菜单栏,包含帮助选项"""
            menubar = self.menuBar()
            # 创建帮助菜单
            help_menu = menubar.addMenu('帮助')
            # 使用帮助动作
            usage_action = QAction('使用帮助', self)
            usage_action.triggered.connect(self.show_help)
            help_menu.addAction(usage_action)
            # 检查更新动作
            check_updates_action = QAction('检查库更新', self)
            check_updates_action.triggered.connect(self.show_update_tab)
            help_menu.addAction(check_updates_action)
            # 关于动作
            about_action = QAction('关于', self)
            about_action.triggered.connect(self.show_about)
            help_menu.addAction(about_action)
            # Python下载动作 - 新增
            python_download_action = QAction('下载Python', self)
            python_download_action.triggered.connect(self.open_python_download)
            help_menu.addAction(python_download_action)
        def get_default_python_path(self) -> str:
            """获取当前运行环境的Python路径"""
            try:
                return os.path.dirname(sys.executable)
            except:
                return ""
        def start_python_detection(self):
            """启动增强版Python检测线程"""
            self.log_text.append("开始检测系统中的Python安装...")
            self.python_status_label.setText("正在检测Python安装...")
            self.python_status_label.setStyleSheet(
                f"background-color: {WARNING_COLOR}; color: white; padding: 5px; border-radius: 4px;")
            # 禁用相关按钮
            self.python_versions_combo.setEnabled(False)
            # 启动检测线程
            self.detection_worker = PythonDetectionWorker()
            self.detection_worker.detection_finished.connect(self.handle_detection_result)
            self.detection_worker.start()
        def handle_detection_result(self, python_paths: list):
            """处理Python检测结果,增强版提示"""
            self.log_text.append(f"Python检测完成,共发现 {len(python_paths)} 个安装")
            self.detected_python_versions = {}
            # 更新下拉列表
            self.python_versions_combo.clear()
            if not python_paths:
                # 未检测到Python安装 - 增强提示
                self.python_installed = False
                self.python_status_label.setText("未检测到Python安装,请先安装Python")
                self.python_status_label.setStyleSheet(
                    f"background-color: {DANGER_COLOR}; color: white; padding: 5px; border-radius: 4px;")
                self.log_text.append("警告: 未检测到Python安装,请先安装Python")
                # 显示详细的安装提示对话框
                reply = QMessageBox.warning(
                    self, "未安装Python",
                    "未检测到Python安装。\n\n"
                    "请从官网下载并安装Python:\n"
                    "https://www.python.org/downloads/\n\n"
                    "安装时请勾选'Add Python to PATH'选项,"
                    "这将使Python在命令行中可直接访问。\n\n"
                    "需要打开Python官网下载页面吗?",
                    QMessageBox.Yes | QMessageBox.No
                )
                if reply == QMessageBox.Yes:
                    import webbrowser
                    webbrowser.open("https://www.python.org/downloads/")
                # 禁用需要Python的功能按钮
                self.install_btn.setEnabled(False)
                self.start_check_btn.setEnabled(False)
                self.check_updates_btn.setEnabled(False)
            else:
                # 检测到Python安装
                self.python_installed = True
                self.python_status_label.setText(f"已检测到 {len(python_paths)} 个Python安装")
                self.python_status_label.setStyleSheet(
                    f"background-color: {ACCENT_COLOR}; color: white; padding: 5px; border-radius: 4px;")
                # 获取每个Python路径的详细版本信息
                for path in python_paths:
                    try:
                        # 执行python --version命令获取版本
                        version_output = subprocess.check_output(
                            [os.path.join(path, "python.exe"), "--version"],
                            stderr=subprocess.STDOUT,  # python --version输出到stderr
                            text=True,
                            encoding='utf-8',
                            timeout=10
                        ).strip()
                        # 执行python -V获取更详细的版本信息
                        detailed_version = subprocess.check_output(
                            [os.path.join(path, "python.exe"), "-V"],
                            stderr=subprocess.STDOUT,
                            text=True,
                            encoding='utf-8',
                            timeout=10
                        ).strip()
                        # 存储版本信息
                        full_version = f"{version_output} ({detailed_version})"
                        self.detected_python_versions[path] = full_version
                        # 添加到下拉列表
                        self.python_versions_combo.addItem(f"{full_version} - {path}", path)
                        self.log_text.append(f"发现Python版本: {version_output} 在 {path}")
                    except Exception as e:
                        self.log_text.append(f"获取Python版本信息失败 for {path}: {str(e)}")
                        self.python_versions_combo.addItem(f"Python (版本未知) - {path}", path)
                # 尝试自动选择一个Python版本
                if self.python_path and self.python_path in self.detected_python_versions:
                    # 找到当前路径在下拉列表中的索引
                    for i in range(self.python_versions_combo.count()):
                        if self.python_versions_combo.itemData(i) == self.python_path:
                            self.python_versions_combo.setCurrentIndex(i)
                            break
                else:
                    # 默认选择第一个
                    self.python_versions_combo.setCurrentIndex(0)
                    if self.python_versions_combo.count() > 0:
                        self.python_path = self.python_versions_combo.itemData(0)
                        self.python_path_edit.setText(self.python_path)
                # 启用相关按钮
                self.install_btn.setEnabled(True)
                self.start_check_btn.setEnabled(True)
                self.check_updates_btn.setEnabled(True)
            # 启用下拉列表
            self.python_versions_combo.setEnabled(True)
            self.detection_worker = None
            # 显示Python路径信息
            self.append_python_path_to_log()
        def browse_python_path(self):
            """浏览选择Python安装目录"""
            default_dir = self.python_path if self.python_path else "C:\\"
            python_dir = QFileDialog.getExistingDirectory(
                self, "选择Python安装目录", default_dir,
                QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks
            )
            if python_dir:
                # 验证是否包含python.exe
                if os.path.exists(os.path.join(python_dir, "python.exe")):
                    self.python_path_edit.setText(python_dir)
                    self.python_path = python_dir
                    # 检查这个路径是否已经在下拉列表中
                    path_exists = False
                    for i in range(self.python_versions_combo.count()):
                        if self.python_versions_combo.itemData(i) == python_dir:
                            self.python_versions_combo.setCurrentIndex(i)
                            path_exists = True
                            break
                    # 如果不在下拉列表中,添加它
                    if not path_exists:
                        try:
                            version_output = subprocess.check_output(
                                [os.path.join(python_dir, "python.exe"), "--version"],
                                stderr=subprocess.STDOUT,
                                text=True,
                                encoding='utf-8',
                                timeout=10
                            ).strip()
                            self.python_versions_combo.addItem(f"{version_output} - {python_dir}", python_dir)
                            self.python_versions_combo.setCurrentIndex(self.python_versions_combo.count() - 1)
                            self.detected_python_versions[python_dir] = version_output
                        except:
                            self.python_versions_combo.addItem(f"Python - {python_dir}", python_dir)
                            self.python_versions_combo.setCurrentIndex(self.python_versions_combo.count() - 1)
                    # 更新日志显示
                    self.append_python_path_to_log()
                else:
                    QMessageBox.warning(
                        self, "路径无效",
                        "所选目录不包含python.exe,请选择正确的Python安装目录"
                    )
        def on_python_version_selected(self, index):
            """选择下拉列表中的Python版本"""
            if index >= 0 and self.python_versions_combo.count() > 0:
                python_path = self.python_versions_combo.itemData(index)
                self.python_path_edit.setText(python_path)
                self.python_path = python_path
                # 显示版本信息
                version = self.detected_python_versions.get(python_path, "未知版本")
                self.python_status_label.setText(f"当前选择: {version}")
                self.python_status_label.setStyleSheet(
                    f"background-color: {ACCENT_COLOR}; color: white; padding: 5px; border-radius: 4px;")
                # 更新日志显示
                self.append_python_path_to_log()
        def on_python_path_changed(self):
            """当Python路径发生变化时更新日志"""
            self.python_path = self.python_path_edit.text().strip()
            self.append_python_path_to_log()
            # 检查路径有效性
            if os.path.exists(os.path.join(self.python_path, "python.exe")):
                self.python_status_label.setText("Python路径有效")
                self.python_status_label.setStyleSheet(
                    f"background-color: {ACCENT_COLOR}; color: white; padding: 5px; border-radius: 4px;")
                self.install_btn.setEnabled(True)
                self.start_check_btn.setEnabled(True)
                self.check_updates_btn.setEnabled(True)
            else:
                self.python_status_label.setText("Python路径无效")
                self.python_status_label.setStyleSheet(
                    f"background-color: {DANGER_COLOR}; color: white; padding: 5px; border-radius: 4px;")
                self.install_btn.setEnabled(False)
                self.start_check_btn.setEnabled(False)
                self.check_updates_btn.setEnabled(False)
        def append_python_path_to_log(self):
            """将Python路径信息添加到日志区域"""
            if self.python_path and hasattr(self, 'log_text'):
                # 检查路径有效性
                python_exe_exists = os.path.exists(os.path.join(self.python_path, "python.exe"))
                pip_exe_exists = os.path.exists(os.path.join(self.python_path, "Scripts", "pip.exe"))
                status = "有效" if python_exe_exists else "无效"
                self.log_text.append(f"当前Python安装路径 [{status}]: {self.python_path}")
                if python_exe_exists:
                    # 获取并显示Python版本
                    try:
                        version_output = subprocess.check_output(
                            [os.path.join(self.python_path, "python.exe"), "--version"],
                            stderr=subprocess.STDOUT,
                            text=True,
                            encoding='utf-8',
                            timeout=10
                        ).strip()
                        self.log_text.append(f"Python版本: {version_output}")
                    except:
                        self.log_text.append("无法获取Python版本信息")
                    self.log_text.append(f"Python可执行文件: {os.path.join(self.python_path, 'python.exe')}")
                    pip_status = "存在" if pip_exe_exists else "不存在"
                    self.log_text.append(
                        f"pip可执行文件 [{pip_status}]: {os.path.join(self.python_path, 'Scripts', 'pip.exe')}")
                else:
                    self.log_text.append("警告: 所选路径不包含python.exe,无法执行安装操作")
        def apply_styles(self):
            """应用样式表美化界面"""
            self.setStyleSheet(f"""
                /* 主窗口样式 */
                QMainWindow, QWidget {{
                    background-color: {LIGHT_COLOR};
                    color: {DARK_COLOR};
                    font-size: 12px;
                }}
                /* 标签页样式 */
                QTabWidget::pane {{
                    border: 1px solid {BORDER_COLOR};
                    border-radius: 6px;
                    background-color: white;
                    padding: 10px;
                    box-shadow: inset 0 1px 3px rgba(0,0,0,0.05);
                }}
                QTabBar::tab {{
                    background-color: {LIGHT_COLOR};
                    color: {DARK_COLOR};
                    padding: 6px 14px;
                    border: 1px solid {BORDER_COLOR};
                    border-bottom-color: {BORDER_COLOR};
                    border-radius: 4px 4px 0 0;
                    margin-right: 1px;
                    font-size: 12px;
                }}
                QTabBar::tab:selected {{
                    background-color: white;
                    border-color: {BORDER_COLOR};
                    border-bottom-color: white;
                    font-weight: bold;
                }}
                QTabBar::tab:hover:!selected {{
                    background-color: {GRAY_COLOR};
                    border-color: {SECONDARY_COLOR};
                }}
                /* 分组框样式 */
                QGroupBox {{
                    border: 1px solid {BORDER_COLOR};
                    border-radius: 6px;
                    margin-top: 8px;
                    padding: 8px 10px;
                    background-color: white;
                    box-shadow: 0 1px 2px rgba(0,0,0,0.05);
                }}
                QGroupBox::title {{
                    color: {PRIMARY_COLOR};
                    subcontrol-origin: margin;
                    left: 5px;
                    padding: 0 3px 0 3px;
                    font-weight: bold;
                    font-size: 12px;
                }}
                /* 按钮样式 */
                QPushButton {{
                    background-color: {PRIMARY_COLOR};
                    color: white;
                    border: none;
                    border-radius: 4px;
                    padding: 5px 12px;
                    font-size: 12px;
                    transition: all 0.2s ease;
                }}
                QPushButton:hover {{
                    background-color: {SECONDARY_COLOR};
                    transform: translateY(-1px);
                    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
                }}
                QPushButton:pressed {{
                    background-color: {PRIMARY_COLOR};
                    transform: translateY(0);
                    box-shadow: none;
                }}
                QPushButton:disabled {{
                    background-color: {GRAY_COLOR};
                    color: #999;
                }}
                /* 特殊按钮样式 */
                QPushButton#installBtn {{
                    background-color: {ACCENT_COLOR};
                }}
                QPushButton#installBtn:hover {{
                    background-color: #2DB886;
                }}
                QPushButton#cancelBtn {{
                    background-color: {DANGER_COLOR};
                }}
                QPushButton#cancelBtn:hover {{
                    background-color: #E6645F;
                }}
                QPushButton#updateBtn {{
                    background-color: #FFAB00;
                }}
                QPushButton#updateBtn:hover {{
                    background-color: #E69E00;
                }}
                /* 列表和表格样式 */
                QListWidget, QTableWidget {{
                    border: 1px solid {BORDER_COLOR};
                    border-radius: 4px;
                    background-color: white;
                    padding: 5px;
                    alternate-background-color: {LIGHT_COLOR};
                    font-size: 12px;
                }}
                QListWidget::item {{
                    padding: 3px;
                    border-radius: 2px;
                    min-height: 20px;
                }}
                QListWidget::item:selected, QTableWidget::item:selected {{
                    background-color: rgba(22, 93, 255, 0.15);
                    color: {PRIMARY_COLOR};
                    border-radius: 2px;
                }}
                QListWidget::item:hover, QTableWidget::item:hover {{
                    background-color: {GRAY_COLOR};
                }}
                /* 进度条样式 */
                QProgressBar {{
                    border: 1px solid {BORDER_COLOR};
                    border-radius: 4px;
                    text-align: center;
                    height: 20px;
                    font-size: 11px;
                }}
                QProgressBar::chunk {{
                    background-color: {PRIMARY_COLOR};
                    border-radius: 3px;
                    width: 10px;
                    margin: 0.5px;
                }}
                /* 文本编辑框样式 */
                QTextEdit, QTextBrowser {{
                    border: 1px solid {BORDER_COLOR};
                    border-radius: 4px;
                    background-color: white;
                    padding: 6px;
                    font-family: "Consolas", "Monaco", monospace;
                    font-size: 11px;
                    line-height: 1.5;
                }}
                /* 输入框样式 */
                QLineEdit {{
                    border: 1px solid {BORDER_COLOR};
                    border-radius: 4px;
                    padding: 5px 8px;
                    background-color: white;
                    font-size: 12px;
                    transition: border-color 0.2s ease;
                }}
                QLineEdit:focus {{
                    border-color: {PRIMARY_COLOR};
                    outline: none;
                    box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.2);
                }}
                /* 复选框样式 */
                QCheckBox {{
                    padding: 3px;
                    font-size: 12px;
                }}
                QCheckBox:hover {{
                    color: {PRIMARY_COLOR};
                }}
                /* 单选按钮样式 */
                QRadioButton {{
                    padding: 3px;
                    font-size: 12px;
                }}
                QRadioButton:hover {{
                    color: {PRIMARY_COLOR};
                }}
                /* 分割器样式 */
                QSplitter::handle {{
                    background-color: {BORDER_COLOR};
                }}
                QSplitter::handle:hover {{
                    background-color: {SECONDARY_COLOR};
                }}
                /* 下拉框样式 */
                QComboBox {{
                    border: 1px solid {BORDER_COLOR};
                    border-radius: 4px;
                    padding: 5px 8px;
                    background-color: white;
                    font-size: 12px;
                    transition: border-color 0.2s ease;
                }}
                QComboBox:focus {{
                    border-color: {PRIMARY_COLOR};
                    box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.2);
                }}
                /* 滚动条样式 */
                QScrollBar:vertical {{
                    border: none;
                    background: {LIGHT_COLOR};
                    width: 8px;
                    margin: 0px;
                }}
                QScrollBar::handle:vertical {{
                    background: {SECONDARY_COLOR};
                    min-height: 15px;
                    border-radius: 4px;
                }}
                QScrollBar:horizontal {{
                    border: none;
                    background: {LIGHT_COLOR};
                    height: 8px;
                    margin: 0px;
                }}
                QScrollBar::handle:horizontal {{
                    background: {SECONDARY_COLOR};
                    min-width: 15px;
                    border-radius: 4px;
                }}
                /* 菜单栏样式 */
                QMenuBar {{
                    background-color: {LIGHT_COLOR};
                    color: {DARK_COLOR};
                    font-size: 12px;
                }}
                QMenuBar::item {{
                    background-color: {LIGHT_COLOR};
                    padding: 2px 6px;
                }}
                QMenuBar::item:selected {{
                    background-color: {SECONDARY_COLOR};
                    color: white;
                }}
                QMenu {{
                    background-color: white;
                    border: 1px solid {BORDER_COLOR};
                    font-size: 12px;
                }}
                QMenu::item {{
                    padding: 2px 15px;
                }}
                QMenu::item:selected {{
                    background-color: {SECONDARY_COLOR};
                    color: white;
                }}
            """)
            # 设置按钮对象名以便应用特殊样式
            if hasattr(self, 'install_btn'):
                self.install_btn.setObjectName("installBtn")
            if hasattr(self, 'cancel_btn'):
                self.cancel_btn.setObjectName("cancelBtn")
            if hasattr(self, 'update_btn'):
                self.update_btn.setObjectName("updateBtn")
        def create_category_tab(self):
            tab = QWidget()
            layout = QVBoxLayout(tab)
            layout.setContentsMargins(8, 8, 8, 8)
            layout.setSpacing(8)
            splitter = QSplitter(Qt.Vertical)
            layout.addWidget(splitter)
            # 左侧类别列表
            scroll_area = QScrollArea()
            scroll_area.setWidgetResizable(True)
            splitter.addWidget(scroll_area)
            # 滚动区域内部的容器
            scroll_content = QWidget()
            scroll_layout = QVBoxLayout(scroll_content)
            scroll_layout.setContentsMargins(0, 0, 0, 0)
            scroll_layout.setSpacing(5)
            scroll_area.setWidget(scroll_content)
            # 库类别选择分组框
            category_group = QGroupBox("选择库类别 (可多选)")
            category_layout = QVBoxLayout(category_group)
            category_layout.setContentsMargins(8, 8, 8, 8)
            category_layout.setSpacing(3)
            self.category_checkboxes = {}
            for category in CATEGORIES.keys():
                cb = QCheckBox(category)
                self.category_checkboxes[category] = cb
                category_layout.addWidget(cb)
            # 添加"全选"和"全不选"按钮
            btn_layout = QHBoxLayout()
            btn_layout.setSpacing(5)
            select_all_btn = QPushButton("全选")
            select_all_btn.setMinimumHeight(28)
            select_all_btn.clicked.connect(lambda: self.check_all_categories(True))
            deselect_all_btn = QPushButton("全不选")
            deselect_all_btn.setMinimumHeight(28)
            deselect_all_btn.clicked.connect(lambda: self.check_all_categories(False))
            btn_layout.addWidget(select_all_btn)
            btn_layout.addWidget(deselect_all_btn)
            category_layout.addLayout(btn_layout)
            scroll_layout.addWidget(category_group)
            # 右侧选中的库列表
            list_scroll = QScrollArea()
            list_scroll.setWidgetResizable(True)
            list_container = QWidget()
            list_layout = QVBoxLayout(list_container)
            list_layout.setContentsMargins(0, 0, 0, 0)
            self.selected_libs_list = QListWidget()
            self.selected_libs_list.setAlternatingRowColors(True)
            list_layout.addWidget(self.selected_libs_list)
            list_scroll.setWidget(list_container)
            splitter.addWidget(list_scroll)
            # 添加更新按钮
            update_btn = QPushButton("更新选中的库列表")
            update_btn.setMinimumHeight(28)
            update_btn.clicked.connect(self.update_selected_libs)
            layout.addWidget(update_btn)
            self.tabs.addTab(tab, "按类别选择")
        def create_manual_tab(self):
            tab = QWidget()
            layout = QVBoxLayout(tab)
            layout.setContentsMargins(8, 8, 8, 8)
            layout.setSpacing(8)
            # 输入框区域
            input_group = QGroupBox("输入库名称 (用逗号分隔)")
            input_layout = QVBoxLayout(input_group)
            input_layout.setContentsMargins(8, 8, 8, 8)
            input_layout.setSpacing(5)
            self.manual_input = QLineEdit()
            self.manual_input.setPlaceholderText("例如: numpy,pandas,requests,flask")
            input_layout.addWidget(self.manual_input)
            layout.addWidget(input_group)
            # 添加按钮
            add_btn = QPushButton("添加到安装列表")
            add_btn.setMinimumHeight(28)
            add_btn.clicked.connect(self.add_manual_libs)
            layout.addWidget(add_btn)
            # 已添加的库列表
            list_group = QGroupBox("已添加的库")
            list_inner_layout = QVBoxLayout(list_group)
            list_inner_layout.setContentsMargins(8, 8, 8, 8)
            self.manual_libs_list = QListWidget()
            self.manual_libs_list.setAlternatingRowColors(True)
            list_inner_layout.addWidget(self.manual_libs_list)
            layout.addWidget(list_group)
            # 添加移除按钮
            remove_btn = QPushButton("移除选中项")
            remove_btn.setMinimumHeight(28)
            remove_btn.clicked.connect(self.remove_manual_lib)
            layout.addWidget(remove_btn)
            self.tabs.addTab(tab, "手动输入")
        def create_file_tab(self):
            tab = QWidget()
            layout = QVBoxLayout(tab)
            layout.setContentsMargins(8, 8, 8, 8)
            layout.setSpacing(8)
            # 文件选择区域
            file_group = QGroupBox("从文件导入库列表")
            file_layout = QVBoxLayout(file_group)
            file_layout.setContentsMargins(8, 8, 8, 8)
            file_layout.setSpacing(5)
            file_path_layout = QHBoxLayout()
            file_path_layout.setSpacing(5)
            self.file_path_edit = QLineEdit()
            self.file_path_edit.setReadOnly(True)
            browse_btn = QPushButton("浏览...")
            browse_btn.setMinimumHeight(28)
            browse_btn.clicked.connect(self.browse_file)
            file_path_layout.addWidget(self.file_path_edit, 3)
            file_path_layout.addWidget(browse_btn, 1)
            load_btn = QPushButton("加载文件")
            load_btn.setMinimumHeight(28)
            load_btn.clicked.connect(self.load_file_libs)
            file_layout.addLayout(file_path_layout)
            file_layout.addWidget(load_btn)
            layout.addWidget(file_group)
            # 库列表显示
            list_group = QGroupBox("从文件加载的库")
            list_inner_layout = QVBoxLayout(list_group)
            list_inner_layout.setContentsMargins(8, 8, 8, 8)
            self.file_libs_list = QListWidget()
            self.file_libs_list.setAlternatingRowColors(True)
            list_inner_layout.addWidget(self.file_libs_list)
            layout.addWidget(list_group)
            self.tabs.addTab(tab, "文件导入")
        def create_preset_tab(self):
            tab = QWidget()
            layout = QVBoxLayout(tab)
            layout.setContentsMargins(8, 8, 8, 8)
            layout.setSpacing(8)
            # 预设组合选择
            preset_group = QGroupBox("选择预设组合 (可多选)")
            preset_inner_layout = QVBoxLayout(preset_group)
            preset_inner_layout.setContentsMargins(8, 8, 8, 8)
            preset_inner_layout.setSpacing(3)
            self.preset_checkboxes = {}
            for preset in PRESETS.keys():
                cb = QCheckBox(preset)
                self.preset_checkboxes[preset] = cb
                preset_inner_layout.addWidget(cb)
            # 添加"全选"和"全不选"按钮
            btn_layout = QHBoxLayout()
            btn_layout.setSpacing(5)
            select_all_btn = QPushButton("全选")
            select_all_btn.setMinimumHeight(28)
            select_all_btn.clicked.connect(lambda: self.check_all_presets(True))
            deselect_all_btn = QPushButton("全不选")
            deselect_all_btn.setMinimumHeight(28)
            deselect_all_btn.clicked.connect(lambda: self.check_all_presets(False))
            btn_layout.addWidget(select_all_btn)
            btn_layout.addWidget(deselect_all_btn)
            preset_inner_layout.addLayout(btn_layout)
            layout.addWidget(preset_group)
            # 选中的预设库列表
            self.preset_libs_list = QListWidget()
            self.preset_libs_list.setAlternatingRowColors(True)
            layout.addWidget(self.preset_libs_list)
            # 添加更新按钮
            update_btn = QPushButton("更新选中的预设库列表")
            update_btn.setMinimumHeight(28)
            update_btn.clicked.connect(self.update_preset_libs)
            layout.addWidget(update_btn)
            self.tabs.addTab(tab, "预设组合")
        def create_check_tab(self):
            """创建库检查标签页"""
            tab = QWidget()
            layout = QVBoxLayout(tab)
            layout.setContentsMargins(8, 8, 8, 8)
            layout.setSpacing(8)
            # 检查选项区域
            options_group = QGroupBox("库检查选项")
            options_layout = QVBoxLayout(options_group)
            options_layout.setContentsMargins(8, 8, 8, 8)
            options_layout.setSpacing(5)
            # 检查范围选择
            scope_layout = QHBoxLayout()
            scope_layout.setSpacing(5)
            self.check_common_radio = QRadioButton("检查常用库")
            self.check_common_radio.setChecked(True)
            self.check_all_radio = QRadioButton("检查所有库")
            scope_layout.addWidget(self.check_common_radio)
            scope_layout.addWidget(self.check_all_radio)
            options_layout.addLayout(scope_layout)
            # 检查按钮
            check_btn_layout = QHBoxLayout()
            check_btn_layout.setSpacing(5)
            self.start_check_btn = QPushButton("开始检查")
            self.start_check_btn.setMinimumHeight(28)
            self.start_check_btn.clicked.connect(self.start_library_check)
            self.cancel_check_btn = QPushButton("取消检查")
            self.cancel_check_btn.setMinimumHeight(28)
            self.cancel_check_btn.clicked.connect(self.cancel_library_check)
            self.cancel_check_btn.setEnabled(False)
            check_btn_layout.addWidget(self.start_check_btn)
            check_btn_layout.addWidget(self.cancel_check_btn)
            options_layout.addLayout(check_btn_layout)
            # 检查进度
            progress_layout = QHBoxLayout()
            progress_layout.setSpacing(5)
            self.check_progress_label = QLabel("准备就绪")
            self.check_progress_label.setMinimumWidth(120)
            self.check_progress_bar = QProgressBar()
            self.check_progress_bar.setMinimumHeight(20)
            self.check_progress_bar.setValue(0)
            progress_layout.addWidget(self.check_progress_label)
            progress_layout.addWidget(self.check_progress_bar)
            options_layout.addLayout(progress_layout)
            layout.addWidget(options_group)
            # 结果统计
            stats_layout = QHBoxLayout()
            stats_layout.setSpacing(10)
            self.installed_count_label = QLabel("已安装: 0")
            self.not_installed_count_label = QLabel("未安装: 0")
            stats_layout.addWidget(self.installed_count_label)
            stats_layout.addWidget(self.not_installed_count_label)
            stats_layout.addStretch()
            layout.addLayout(stats_layout)
            # 检查结果区域(使用分割器分为已安装和未安装两部分)
            splitter = QSplitter(Qt.Vertical)
            splitter.setSizes([300, 300])  # 设置初始大小
            # 已安装的库
            installed_group = QGroupBox("已安装的库")
            installed_inner_layout = QVBoxLayout(installed_group)
            installed_inner_layout.setContentsMargins(5, 5, 5, 5)
            self.installed_list = QListWidget()
            self.installed_list.setAlternatingRowColors(True)
            installed_inner_layout.addWidget(self.installed_list)
            splitter.addWidget(installed_group)
            # 未安装的库
            not_installed_group = QGroupBox("未安装的库")
            not_installed_inner_layout = QVBoxLayout(not_installed_group)
            not_installed_inner_layout.setContentsMargins(5, 5, 5, 5)
            self.not_installed_list = QListWidget()
            self.not_installed_list.setAlternatingRowColors(True)
            not_installed_inner_layout.addWidget(self.not_installed_list)
            splitter.addWidget(not_installed_group)
            layout.addWidget(splitter)
            # 安装未安装库的按钮
            self.install_missing_btn = QPushButton("安装所有未安装的库")
            self.install_missing_btn.setMinimumHeight(28)
            self.install_missing_btn.clicked.connect(self.install_missing_libraries)
            self.install_missing_btn.setEnabled(False)
            layout.addWidget(self.install_missing_btn)
            self.tabs.addTab(tab, "库检查")
        def create_update_tab(self):
            """创建库更新标签页"""
            tab = QWidget()
            layout = QVBoxLayout(tab)
            layout.setContentsMargins(8, 8, 8, 8)
            layout.setSpacing(8)
            # 更新选项区域
            options_group = QGroupBox("库更新选项")
            options_layout = QVBoxLayout(options_group)
            options_layout.setContentsMargins(8, 8, 8, 8)
            options_layout.setSpacing(5)
            # 更新范围选择
            scope_layout = QHBoxLayout()
            scope_layout.setSpacing(5)
            self.update_common_radio = QRadioButton("检查常用库更新")
            self.update_common_radio.setChecked(True)
            self.update_all_radio = QRadioButton("检查所有库更新")
            scope_layout.addWidget(self.update_common_radio)
            scope_layout.addWidget(self.update_all_radio)
            options_layout.addLayout(scope_layout)
            # 检查更新按钮
            check_btn_layout = QHBoxLayout()
            check_btn_layout.setSpacing(5)
            self.check_updates_btn = QPushButton("检查可更新的库")
            self.check_updates_btn.setMinimumHeight(28)
            self.check_updates_btn.clicked.connect(self.check_for_updates)
            self.cancel_update_check_btn = QPushButton("取消检查")
            self.cancel_update_check_btn.setMinimumHeight(28)
            self.cancel_update_check_btn.clicked.connect(self.cancel_update_check)
            self.cancel_update_check_btn.setEnabled(False)
            check_btn_layout.addWidget(self.check_updates_btn)
            check_btn_layout.addWidget(self.cancel_update_check_btn)
            options_layout.addLayout(check_btn_layout)
            # 检查进度
            progress_layout = QHBoxLayout()
            progress_layout.setSpacing(5)
            self.update_progress_label = QLabel("准备就绪")
            self.update_progress_label.setMinimumWidth(120)
            self.update_progress_bar = QProgressBar()
            self.update_progress_bar.setMinimumHeight(20)
            self.update_progress_bar.setValue(0)
            progress_layout.addWidget(self.update_progress_label)
            progress_layout.addWidget(self.update_progress_bar)
            options_layout.addLayout(progress_layout)
            layout.addWidget(options_group)
            # 结果统计
            stats_layout = QHBoxLayout()
            stats_layout.setSpacing(10)
            self.needs_update_count_label = QLabel("需要更新: 0")
            self.up_to_date_count_label = QLabel("已是最新: 0")
            stats_layout.addWidget(self.needs_update_count_label)
            stats_layout.addWidget(self.up_to_date_count_label)
            stats_layout.addStretch()
            layout.addLayout(stats_layout)
            # 可更新库表格
            self.updates_table = QTableWidget()
            self.updates_table.setColumnCount(4)
            self.updates_table.setHorizontalHeaderLabels(["库名称", "当前版本", "最新版本", "选择更新"])
            self.updates_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
            self.updates_table.setEditTriggers(QAbstractItemView.NoEditTriggers)
            self.updates_table.verticalHeader().setDefaultSectionSize(22)
            layout.addWidget(self.updates_table)
            # 全选/取消选择按钮
            select_layout = QHBoxLayout()
            select_layout.setSpacing(5)
            self.select_all_updates_btn = QPushButton("全选")
            self.select_all_updates_btn.setMinimumHeight(28)
            self.select_all_updates_btn.clicked.connect(lambda: self.select_all_updates(True))
            self.deselect_all_updates_btn = QPushButton("全不选")
            self.deselect_all_updates_btn.setMinimumHeight(28)
            self.deselect_all_updates_btn.clicked.connect(lambda: self.select_all_updates(False))
            select_layout.addWidget(self.select_all_updates_btn)
            select_layout.addWidget(self.deselect_all_updates_btn)
            layout.addLayout(select_layout)
            # 更新按钮
            self.update_btn = QPushButton("更新所选库")
            self.update_btn.setMinimumHeight(28)
            self.update_btn.clicked.connect(self.update_selected_libraries)
            self.update_btn.setEnabled(False)
            layout.addWidget(self.update_btn)
            self.tabs.addTab(tab, "库更新")
        def create_mirror_tab(self):
            """创建镜像源管理标签页 - 新增"""
            tab = QWidget()
            layout = QVBoxLayout(tab)
            layout.setContentsMargins(8, 8, 8, 8)
            layout.setSpacing(8)
            # 镜像源信息
            mirror_group = QGroupBox("可用镜像源")
            mirror_layout = QVBoxLayout(mirror_group)
            mirror_layout.setContentsMargins(8, 8, 8, 8)
            # 镜像源列表
            self.mirror_list = QTableWidget()
            self.mirror_list.setColumnCount(2)
            self.mirror_list.setHorizontalHeaderLabels(["镜像源名称", "URL地址"])
            self.mirror_list.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
            self.mirror_list.setEditTriggers(QAbstractItemView.NoEditTriggers)
            # 填充镜像源数据
            self.mirror_list.setRowCount(len(MIRRORS))
            for row, (name, url) in enumerate(MIRRORS.items()):
                name_item = QTableWidgetItem(name)
                url_item = QTableWidgetItem(url if url else "https://pypi.org/simple/")
                self.mirror_list.setItem(row, 0, name_item)
                self.mirror_list.setItem(row, 1, url_item)
            mirror_layout.addWidget(self.mirror_list)
            # 镜像源说明
            info_label = QLabel("""
            镜像源说明:
            本工具会自动尝试所有镜像源来安装Python库,以提高安装成功率。
            国内镜像源通常比官方源速度更快,尤其是在中国大陆地区。
            如果某个镜像源暂时不可用,工具会自动切换到下一个镜像源重试。
            """)
            info_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
            mirror_layout.addWidget(info_label)
            layout.addWidget(mirror_group)
            self.tabs.addTab(tab, "镜像源管理")
        def create_help_tab(self):
            """创建帮助标签页"""
            tab = QWidget()
            layout = QVBoxLayout(tab)
            layout.setContentsMargins(8, 8, 8, 8)
            layout.setSpacing(8)
            # 使用QTextBrowser显示帮助内容
            self.help_browser = QTextBrowser()
            self.help_browser.setOpenExternalLinks(True)  # 允许打开外部链接
            self.help_browser.setMinimumHeight(300)
            layout.addWidget(self.help_browser)
            # 设置帮助内容
            self.set_help_content()
            self.tabs.addTab(tab, "使用帮助")
        def set_help_content(self):
            """设置帮助内容"""
            help_text = """
            Python库自动安装工具 - 使用帮助
            1. 概述
            本工具旨在帮助用户快速、便捷地安装和更新各种Python库。支持多种选择方式,并自动处理安装过程中的常见问题。
            2. 选择Python路径
            在使用工具前,需要指定Python安装路径:
            
                
  • 工具会自动检测系统中安装的Python版本
                
  • 可以从下拉列表中选择已检测到的Python版本
                
  • 也可以通过"浏览"按钮手动选择Python安装目录
                
  • 如果未安装Python,可以通过"帮助"菜单中的"下载Python"选项访问官网下载
            

            3. 镜像源说明
            本工具已集成多个国内镜像源,包括:
            
                
  • 清华镜像源
                
  • 阿里云镜像源
                
  • 腾讯云镜像源
                
  • 中科大镜像源
                
  • 华中科大镜像源
                
  • 豆瓣镜像源
            

            安装过程中会自动尝试所有镜像源,提高安装成功率。
            4. 库选择方式
            可以通过以下几种方式选择要安装的库:
            
                
  • 按类别选择:从预设的类别中选择需要的库类别
                
  • 手动输入:直接输入库名称,多个库用逗号分隔
                
  • 文件导入:从requirements.txt或其他文本文件导入库列表
                
  • 预设组合:选择预设的常用库组合,如"数据科学基础包"
            

            5. 库检查与更新
            
                
  • 库检查:可以检查系统中已安装和未安装的库
                
  • 库更新:可以检查可更新的库并选择更新
            

            6. 常见问题
            Q: 安装失败怎么办?
            A: 查看日志区域的错误信息,工具会自动尝试多个镜像源和重试,如仍失败可尝试手动安装。
            Q: 如何查看安装日志?
            A: 日志会显示在底部的日志区域,同时也会保存到package_installer_gui.log文件中。
            Q: 工具支持虚拟环境吗?
            A: 支持,只需在Python路径选择中选择虚拟环境的Python可执行文件所在目录即可。
            """
            self.help_browser.setHtml(help_text)
        # ==================== 事件处理函数 ====================
        def check_all_categories(self, check: bool):
            """全选或全不选类别"""
            for cb in self.category_checkboxes.values():
                cb.setChecked(check)
        def update_selected_libs(self):
            """更新选中的库列表"""
            selected_libs = set()
            for category, cb in self.category_checkboxes.items():
                if cb.isChecked() and category in CATEGORIES:
                    selected_libs.update(CATEGORIES[category])
            self.selected_libs_list.clear()
            for lib in sorted(selected_libs):
                self.selected_libs_list.addItem(lib)
            self.log_text.append(f"已更新选中的库列表,共 {len(selected_libs)} 个库")
        def add_manual_libs(self):
            """添加手动输入的库"""
            text = self.manual_input.text().strip()
            if not text:
                QMessageBox.warning(self, "输入为空", "请输入库名称")
                return
            # 分割输入的库名
            libs = [lib.strip() for lib in text.split(',') if lib.strip()]
            if not libs:
                QMessageBox.warning(self, "输入无效", "请输入有效的库名称")
                return
            # 添加到列表(去重)
            current_items = set()
            for i in range(self.manual_libs_list.count()):
                current_items.add(self.manual_libs_list.item(i).text())
            added_count = 0
            for lib in libs:
                if lib not in current_items:
                    self.manual_libs_list.addItem(lib)
                    current_items.add(lib)
                    added_count += 1
            self.log_text.append(f"已添加 {added_count} 个库到手动安装列表")
            self.manual_input.clear()
        def remove_manual_lib(self):
            """移除选中的手动添加库"""
            selected_items = self.manual_libs_list.selectedItems()
            if not selected_items:
                QMessageBox.information(self, "未选择", "请先选择要移除的库")
                return
            for item in selected_items:
                row = self.manual_libs_list.row(item)
                self.manual_libs_list.takeItem(row)
            self.log_text.append(f"已移除 {len(selected_items)} 个库")
        def browse_file(self):
            """浏览选择文件"""
            file_path, _ = QFileDialog.getOpenFileName(
                self, "选择包含库列表的文件", "", "文本文件 (*.txt);;所有文件 (*)"
            )
            if file_path:
                self.file_path_edit.setText(file_path)
        def load_file_libs(self):
            """从文件加载库列表"""
            file_path = self.file_path_edit.text().strip()
            if not file_path or not os.path.exists(file_path):
                QMessageBox.warning(self, "文件无效", "请选择有效的文件")
                return
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    content = f.read()
                # 解析文件内容(支持requirements.txt格式)
                libs = []
                for line in content.splitlines():
                    line = line.strip()
                    # 忽略注释和空行
                    if not line or line.startswith('#'):
                        continue
                    # 提取库名(忽略版本限制)
                    lib_name = line.split('==')[0].split('>=')[0].split(' 0:
                self.install_missing_btn.setEnabled(True)
        def check_finished(self):
            """检查完成"""
            self.check_progress_bar.setValue(100)
            self.check_progress_label.setText("检查完成")
            self.start_check_btn.setEnabled(True)
            self.cancel_check_btn.setEnabled(False)
            self.check_worker = None
            self.log_text.append("库检查完成")
        def cancel_library_check(self):
            """取消库检查"""
            if self.check_worker and self.check_worker.isRunning():
                self.log_text.append("正在取消库检查...")
                self.check_worker.stop()
                self.cancel_check_btn.setEnabled(False)
        def install_missing_libraries(self):
            """安装所有未安装的库"""
            missing_libs = []
            for i in range(self.not_installed_list.count()):
                missing_libs.append(self.not_installed_list.item(i).text())
            if not missing_libs:
                QMessageBox.information(self, "无未安装库", "没有需要安装的未安装库")
                return
            # 将要安装的库添加到手动列表并切换到手动标签页
            self.manual_libs_list.clear()
            for lib in missing_libs:
                self.manual_libs_list.addItem(lib)
            self.tabs.setCurrentIndex(1)  # 切换到手动输入标签页
            self.log_text.append(f"准备安装 {len(missing_libs)} 个未安装的库")
        def check_for_updates(self):
            """检查可更新的库"""
            # 确定要检查的库范围
            if self.update_all_radio.isChecked():
                # 获取当前环境中所有已安装的库
                try:
                    result = subprocess.run(
                        [os.path.join(self.python_path, "python.exe"), "-m", "pip", "list", "--outdated"],
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        text=True,
                        encoding='utf-8',
                        timeout=30
                    )
                    outdated_libs = []
                    for line in result.stdout.splitlines()[2:]:  # 跳过前两行标题
                        parts = line.split()
                        if len(parts) >= 1:
                            outdated_libs.append(parts[0])
                    packages = outdated_libs
                except Exception as e:
                    QMessageBox.critical(self, "获取库列表失败", f"获取已安装库列表时出错: {str(e)}")
                    self.log_text.append(f"获取已安装库列表失败: {str(e)}")
                    return
            else:
                # 只检查常用库
                packages = COMMON_LIBRARIES.copy()
            if not packages:
                QMessageBox.information(self, "无库可检查", "没有需要检查更新的库")
                return
            # 检查Python路径
            if not self.verify_python_path_for_operation():
                return
            self.log_text.append(f"开始检查 {len(packages)} 个库的更新状态...")
            # 初始化UI状态
            self.check_updates_btn.setEnabled(False)
            self.cancel_update_check_btn.setEnabled(True)
            self.updates_table.setRowCount(0)
            self.needs_update_count_label.setText("需要更新: 0")
            self.up_to_date_count_label.setText("已是最新: 0")
            self.update_progress_bar.setValue(0)
            self.update_btn.setEnabled(False)
            # 启动检查线程(启用更新检查模式)
            self.check_worker = CheckWorker(packages, self.python_path, check_updates=True)
            self.check_worker.check_progress.connect(self.update_update_progress)
            self.check_worker.check_result.connect(self.handle_update_result)
            self.check_worker.check_finished.connect(self.update_check_finished)
            self.check_worker.start()
        def update_update_progress(self, progress: int, package: str):
            """更新更新检查进度"""
            self.update_progress_bar.setValue(progress)
            self.update_progress_label.setText(f"正在检查: {package}")
        def handle_update_result(self, results: list):
            """处理更新检查结果"""
            needs_update_count = 0
            up_to_date_count = 0
            # 清空表格
            self.updates_table.setRowCount(0)
            for result in results:
                if not result['installed']:
                    continue  # 只处理已安装的库
                if result['needs_update']:
                    # 添加到更新表格
                    row = self.updates_table.rowCount()
                    self.updates_table.insertRow(row)
                    # 库名称
                    name_item = QTableWidgetItem(result['package'])
                    self.updates_table.setItem(row, 0, name_item)
                    # 当前版本
                    current_version_item = QTableWidgetItem(result['version'])
                    self.updates_table.setItem(row, 1, current_version_item)
                    # 最新版本
                    latest_version_item = QTableWidgetItem(result['latest_version'])
                    self.updates_table.setItem(row, 2, latest_version_item)
                    # 选择框
                    checkbox = QTableWidgetItem()
                    checkbox.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
                    checkbox.setCheckState(Qt.Checked)
                    self.updates_table.setItem(row, 3, checkbox)
                    needs_update_count += 1
                else:
                    up_to_date_count += 1
            # 更新统计信息
            self.needs_update_count_label.setText(f"需要更新: {needs_update_count}")
            self.up_to_date_count_label.setText(f"已是最新: {up_to_date_count}")
            # 如果有需要更新的库,启用更新按钮
            if needs_update_count > 0:
                self.update_btn.setEnabled(True)
        def update_check_finished(self):
            """更新检查完成"""
            self.update_progress_bar.setValue(100)
            self.update_progress_label.setText("检查完成")
            self.check_updates_btn.setEnabled(True)
            self.cancel_update_check_btn.setEnabled(False)
            self.check_worker = None
            self.log_text.append("库更新检查完成")
        def cancel_update_check(self):
            """取消更新检查"""
            if self.check_worker and self.check_worker.isRunning():
                self.log_text.append("正在取消更新检查...")
                self.check_worker.stop()
                self.cancel_update_check_btn.setEnabled(False)
        def select_all_updates(self, select: bool):
            """全选或全不选更新"""
            for row in range(self.updates_table.rowCount()):
                item = self.updates_table.item(row, 3)
                if item:
                    item.setCheckState(Qt.Checked if select else Qt.Unchecked)
        def update_selected_libraries(self):
            """更新所选库"""
            # 收集要更新的库
            to_update = []
            for row in range(self.updates_table.rowCount()):
                item = self.updates_table.item(row, 3)
                if item and item.checkState() == Qt.Checked:
                    lib_name = self.updates_table.item(row, 0).text()
                    to_update.append(lib_name)
            if not to_update:
                QMessageBox.information(self, "未选择", "请先选择要更新的库")
                return
            # 检查Python路径
            if not self.verify_python_path_for_operation():
                return
            self.log_text.append(f"准备更新 {len(to_update)} 个库...")
            # 初始化UI状态
            self.install_btn.setEnabled(False)
            self.cancel_btn.setEnabled(True)
            self.progress_bar.setValue(0)
            # 启动安装线程(更新模式)
            self.install_worker = InstallWorker(to_update, self.python_path, update_mode=True)
            self.install_worker.progress_updated.connect(self.update_install_progress)
            self.install_worker.log_updated.connect(self.append_log)
            self.install_worker.install_finished.connect(self.handle_install_finished)
            self.install_worker.start()
        def start_installation(self):
            """开始安装选中的库"""
            # 收集所有要安装的库
            to_install = set()
            # 从类别标签页收集
            for i in range(self.selected_libs_list.count()):
                to_install.add(self.selected_libs_list.item(i).text())
            # 从手动输入标签页收集
            for i in range(self.manual_libs_list.count()):
                to_install.add(self.manual_libs_list.item(i).text())
            # 从文件导入标签页收集
            for i in range(self.file_libs_list.count()):
                to_install.add(self.file_libs_list.item(i).text())
            # 从预设组合标签页收集
            for i in range(self.preset_libs_list.count()):
                to_install.add(self.preset_libs_list.item(i).text())
            to_install = list(to_install)
            if not to_install:
                QMessageBox.information(self, "未选择库", "请先选择要安装的库")
                return
            # 检查Python路径
            if not self.verify_python_path_for_operation():
                return
            self.log_text.append(f"准备安装 {len(to_install)} 个库...")
            # 初始化UI状态
            self.install_btn.setEnabled(False)
            self.cancel_btn.setEnabled(True)
            self.progress_bar.setValue(0)
            # 启动安装线程
            self.install_worker = InstallWorker(to_install, self.python_path)
            self.install_worker.progress_updated.connect(self.update_install_progress)
            self.install_worker.log_updated.connect(self.append_log)
            self.install_worker.install_finished.connect(self.handle_install_finished)
            self.install_worker.start()
        def update_install_progress(self, progress: int, status: str):
            """更新安装进度"""
            self.progress_bar.setValue(progress)
            self.progress_label.setText(status)
        def append_log(self, text: str):
            """添加日志内容"""
            self.log_text.append(text)
            # 自动滚动到底部
            self.log_text.moveCursor(self.log_text.textCursor().End)
            # 同时写入日志文件
            logging.info(text)
        def handle_install_finished(self, results: dict):
            """处理安装完成事件"""
            self.progress_bar.setValue(100)
            self.progress_label.setText("操作完成")
            self.install_btn.setEnabled(True)
            self.cancel_btn.setEnabled(False)
            self.install_worker = None
            # 显示安装结果摘要
            summary = (f"操作完成: 成功 {len(results['success'])}, "
                       f"失败 {len(results['failed'])}, "
                       f"跳过 {len(results['skipped'])}, "
                       f"总计 {results['total']}")
            self.log_text.append(summary)
            # 显示详细结果对话框
            self.show_installation_summary(results)
        def show_installation_summary(self, results: dict):
            """显示安装结果摘要对话框"""
            msg = QMessageBox(self)
            msg.setWindowTitle("操作完成")
            if results['total'] == 0:
                msg.setText("没有处理任何库")
                msg.exec_()
                return
            details = []
            if results['success']:
                details.append(f"✅ 成功 ({len(results['success'])}):")
                details.append(", ".join(results['success'][:10]) + ("..." if len(results['success']) > 10 else ""))
            if results['failed']:
                details.append(f"\n❌ 失败 ({len(results['failed'])}):")
                details.append(", ".join(results['failed'][:10]) + ("..." if len(results['failed']) > 10 else ""))
            if results['skipped']:
                details.append(f"\n⏭️ 跳过 ({len(results['skipped'])}):")
                details.append(", ".join(results['skipped'][:10]) + ("..." if len(results['skipped']) > 10 else ""))
            msg.setText(
                f"操作已完成。成功: {len(results['success'])}, 失败: {len(results['failed'])}, 跳过: {len(results['skipped'])}")
            msg.setDetailedText("\n".join(details))
            msg.setIcon(QMessageBox.Information)
            msg.exec_()
        def cancel_installation(self):
            """取消安装"""
            if self.install_worker and self.install_worker.isRunning():
                self.log_text.append("正在取消安装...")
                self.install_worker.stop()
                self.cancel_btn.setEnabled(False)
                self.progress_label.setText("正在取消...")
        def clear_log(self):
            """清空日志"""
            self.log_text.clear()
            self.log_text.append("日志已清空")
        def verify_python_path_for_operation(self) -> bool:
            """验证Python路径是否有效,用于操作前检查"""
            if not self.python_path:
                QMessageBox.warning(self, "路径未设置", "请先设置Python安装路径")
                return False
            python_exe = os.path.join(self.python_path, "python.exe")
            if not os.path.exists(python_exe):
                QMessageBox.warning(self, "路径无效", f"Python可执行文件不存在: {python_exe}")
                return False
            return True
        def show_help(self):
            """显示帮助内容"""
            self.tabs.setCurrentIndex(6)  # 切换到帮助标签页
        def show_about(self):
            """显示关于对话框"""
            QMessageBox.about(self, "关于",
                              "Python库自动安装工具\n\n"
                              "版本: 1.0.0\n\n"
                              "一个方便的Python库管理工具,支持多镜像源自动切换,\n"
                              "可快速安装、更新和管理各种Python库。")
        def show_update_tab(self):
            """显示更新标签页"""
            self.tabs.setCurrentIndex(5)  # 切换到更新标签页
        def open_python_download(self):
            """打开Python下载页面"""
            import webbrowser
            webbrowser.open("https://www.python.org/downloads/")
            self.log_text.append("已打开Python官网下载页面")
        def closeEvent(self, event):
            """窗口关闭事件处理"""
            # 如果有正在运行的线程,先停止
            if self.install_worker and self.install_worker.isRunning():
                reply = QMessageBox.question(
                    self, "确认关闭",
                    "有操作正在进行中,确定要关闭吗?",
                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No
                )
                if reply == QMessageBox.Yes:
                    self.install_worker.stop()
                    event.accept()
                else:
                    event.ignore()
            elif self.check_worker and self.check_worker.isRunning():
                reply = QMessageBox.question(
                    self, "确认关闭",
                    "有检查操作正在进行中,确定要关闭吗?",
                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No
                )
                if reply == QMessageBox.Yes:
                    self.check_worker.stop()
                    event.accept()
                else:
                    event.ignore()
            else:
                event.accept()
    if __name__ == "__main__":
        # 确保中文显示正常
        import matplotlib
        matplotlib.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
        app = QApplication(sys.argv)
        # 设置应用风格
        app.setStyle("Fusion")
        window = LibraryInstallerGUI()
        sys.exit(app.exec_())
  • shx20   

    谢谢分享
    pplus   

    试了下,不错。谢谢
    sd7895   

    好物,收藏了,感谢分享
    magiclyan   

    "

    超链不对,请重新生成超链;如果不会用可直接放出网盘文本链接
    dnine999   

    感谢分享
    radarer   

    非常感谢,下载试用一下。
    jtq1234   

    请问这样装完,有多大?
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部