Python之pip图形化(GUI界面)辅助管理工具

Python之pip图形化(GUI界面)辅助管理工具

pip 是 Python 的包管理工具,用于安装、管理、更新和卸载 Python 包(模块)。用于第三方库的安装和管理过程,是 Python 开发中不可或缺的工具。

包的安装、更新、卸载,查看,特别是当用户安装有多个版本的Python时,为特定版本进行这些操作。还可以使用镜像加速安装。

默认情况下,pip 会从 PyPI 下载包。如果你在中国大陆,可能会因为网络问题导致下载速度较慢。可以设置使用国内的镜像源。

有关详情可见:https://blog.csdn.net/cnds123/article/details/104393385

对于新手而言,还是比较麻烦的。

为此,提供一个pip图形化(GUI界面)辅助管理工具

首先搜索出电脑上安装的python版本及路径放在列表中,用户点击哪个就列出哪个版本中已安装的包。所有耗时操作添加了操作前的提示信息,并在操作结束后显示明确的结果。

“列出python”按钮,提供计算机中安装所有python版本(含路径)

“安装包”按钮,提供对话框输入包名进行安装

“包卸载”按钮,可卸载选中的Python包

“包升级”按钮,可升级选中的Python包到最新版本

“镜像源”按钮,出现内置清华、阿里云、腾讯云和官方源等多个镜像源,用于选择

“刷新”按钮,用于列表中显示刚刚安装的包

为了便于用户使用,添加的帮助功能,使用 HTML 格式美化帮助内容。

运行效果如下:

这个工具,利用了多个模块/包/库:

sys(提供与 Python 解释器强相关的功能)、os(提供操作系统相关功能)、subprocess(用于运行外部命令或程序)、json(用于处理 JSON 数据)、shutil(提供文件和文件集合的高级操作)、datetime (用于处理日期和时间),这些都是 Python 的标准库,不需要安装。

PyQt6 是一个功能强大的 GUI 框架,适用于开发复杂的桌面应用程序。通过安装 PyQt6,你可以导入其中的模块来构建用户界面。

源码如下:

import sys
import os
import subprocess
import json
import shutil
from datetime import datetime

# PyQt6模块导入
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QPushButton, QVBoxLayout, QHBoxLayout,
    QListWidget, QListWidgetItem, QLabel, QDialog, QFormLayout, QLineEdit,
    QDialogButtonBox, QMessageBox, QTextEdit, QRadioButton, QButtonGroup,
    QStatusBar, QSplitter, QTabWidget
)
from PyQt6.QtCore import Qt, QSize
from PyQt6.QtGui import QColor, QAction


class PipManager(QMainWindow):
    """PIP图形化管理工具主窗口类"""

    def __init__(self):
        super().__init__()
        
        # 窗口基本设置
        self.setWindowTitle('Python PIP图形化管理工具')
        self.setGeometry(100, 100, 800, 600)
        
        # 初始化成员变量
        self.python_installations = []  # 存储Python安装信息
        self.current_python = None      # 当前选中的Python
        self.installed_packages = []    # 当前Python已安装的包
        self.current_mirror = None      # 当前使用的镜像源
        
        # 初始化镜像源配置
        self.mirrors = {
            "清华": "https://pypi.tuna.tsinghua.edu.cn/simple",
            "阿里云": "https://mirrors.aliyun.com/pypi/simple",
            "腾讯云": "https://mirrors.cloud.tencent.com/pypi/simple",
            "官方源": "https://pypi.org/simple"
        }
        
        # 初始化UI
        self.init_ui()
        
        # 加载配置
        self.load_config()
        
        # 显示欢迎信息
        self.info_text.setText("欢迎使用Python PIP图形化管理工具

"
                             "请点击「列出Python」按钮扫描系统中的Python安装
"
                             "或者直接选择上方列表中的Python版本开始管理")
        
        # 加载Python安装列表
        self.find_python_installations()

    def init_ui(self):
        """初始化用户界面组件"""
        # 创建主窗口部件
        main_widget = QWidget()
        self.setCentralWidget(main_widget)

        # 创建菜单栏
        self.init_menu_bar()
    
        # 主布局
        main_layout = QVBoxLayout()
        main_widget.setLayout(main_layout)
        
        # 上方部分 - Python版本列表
        python_group = QWidget()
        python_layout = QVBoxLayout()
        python_group.setLayout(python_layout)
        
        python_label = QLabel('已安装的Python列表')
        python_layout.addWidget(python_label)
        
        self.python_list = QListWidget()
        self.python_list.setMinimumHeight(140)  # 设置最小高度
        self.python_list.setMaximumHeight(200)  # 设置最大高度
        self.python_list.itemClicked.connect(self.on_python_selected)
        python_layout.addWidget(self.python_list)
        
        main_layout.addWidget(python_group)
        
        # 按钮工具栏
        button_layout = QHBoxLayout()
        
        self.list_python_btn = QPushButton('列出Python')
        self.list_python_btn.clicked.connect(self.find_python_installations)
        button_layout.addWidget(self.list_python_btn)
        
        self.install_pkg_btn = QPushButton('安装包')
        self.install_pkg_btn.clicked.connect(self.install_package)
        button_layout.addWidget(self.install_pkg_btn)
        
        self.uninstall_pkg_btn = QPushButton('卸载包')
        self.uninstall_pkg_btn.clicked.connect(self.uninstall_package)
        button_layout.addWidget(self.uninstall_pkg_btn)
        
        self.upgrade_pkg_btn = QPushButton('升级包')
        self.upgrade_pkg_btn.clicked.connect(self.upgrade_package)
        button_layout.addWidget(self.upgrade_pkg_btn)
        
        self.mirror_btn = QPushButton('设置镜像源')
        self.mirror_btn.clicked.connect(self.show_mirror_dialog)
        button_layout.addWidget(self.mirror_btn)
        
        self.refresh_btn = QPushButton('刷新')
        self.refresh_btn.clicked.connect(self.refresh_package_list)
        button_layout.addWidget(self.refresh_btn)
        
        # 添加帮助按钮
        self.help_btn = QPushButton('帮助')
        self.help_btn.clicked.connect(self.show_help)
        button_layout.addWidget(self.help_btn)
        
        main_layout.addLayout(button_layout)
         
        # 下方分割区域:包列表和信息显示
        splitter = QSplitter(Qt.Orientation.Horizontal)
        
        # 左侧 - 已安装包列表
        package_group = QWidget()
        package_layout = QVBoxLayout()
        package_group.setLayout(package_layout)
        
        package_label = QLabel('已安装的包列表')
        package_layout.addWidget(package_label)
        
        self.package_list = QListWidget()
        self.package_list.setMinimumHeight(260)  # 设置最小高度
        self.package_list.itemClicked.connect(self.show_package_info)
        package_layout.addWidget(self.package_list)
        
        splitter.addWidget(package_group)
        
        # 右侧 - 信息日志
        info_group = QWidget()
        info_layout = QVBoxLayout()
        info_group.setLayout(info_layout)
        
        info_label = QLabel('信息日志')
        info_layout.addWidget(info_label)
        
        self.info_text = QTextEdit()
        self.info_text.setMinimumHeight(260)  # 设置最小高度
        self.info_text.setReadOnly(True)
        info_layout.addWidget(self.info_text)
        
        splitter.addWidget(info_group)
        
        # 设置分割区域的初始大小比例
        splitter.setSizes([300, 300])  # 左右两部分初始宽度比例
        
        # 将分割区域添加到主布局,并设置拉伸因子
        main_layout.addWidget(splitter, stretch=1)  # stretch=1表示该部分会占用更多可用空间
        
        # 调整整体窗口大小策略
        main_layout.setStretchFactor(python_group, 1)  # Python列表部分
        main_layout.setStretchFactor(splitter, 3)      # 下半部分(包列表和信息日志)
        
        # 状态栏
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)
        
        # 显示当前镜像源
        self.mirror_label = QLabel('镜像源: 未设置')
        self.status_bar.addPermanentWidget(self.mirror_label)
        
        # 显示当前Python版本
        self.python_label = QLabel('Python: 未选择')
        self.status_bar.addPermanentWidget(self.python_label)
        
        # 初始状态
        self.status_bar.showMessage('准备就绪', 3000)

    def init_menu_bar(self):
        """初始化菜单栏"""
        menu_bar = self.menuBar()
        
        # 文件菜单
        file_menu = menu_bar.addMenu('文件(&F)')
        
        refresh_action = QAction('刷新包列表', self)
        refresh_action.setShortcut('F5')
        refresh_action.triggered.connect(self.refresh_package_list)
        file_menu.addAction(refresh_action)
        
        file_menu.addSeparator()
        
        exit_action = QAction('退出', self)
        exit_action.setShortcut('Ctrl+Q')
        exit_action.triggered.connect(self.close)
        file_menu.addAction(exit_action)
        
        # 工具菜单
        tools_menu = menu_bar.addMenu('工具(&T)')
        
        list_python_action = QAction('列出Python版本', self)
        list_python_action.triggered.connect(self.find_python_installations)
        tools_menu.addAction(list_python_action)
        
        mirror_action = QAction('设置镜像源', self)
        mirror_action.triggered.connect(self.show_mirror_dialog)
        tools_menu.addAction(mirror_action)
        
        # 帮助菜单
        help_menu = menu_bar.addMenu('帮助(&H)')
        
        help_action = QAction('使用帮助', self)
        help_action.setShortcut('F1')
        help_action.triggered.connect(self.show_help)
        help_menu.addAction(help_action)
        
        about_action = QAction('关于', self)
        about_action.triggered.connect(self.show_about)
        help_menu.addAction(about_action)

    def show_help(self):
        """显示帮助对话框"""
        help_dialog = QDialog(self)
        help_dialog.setWindowTitle('使用帮助')
        help_dialog.setMinimumSize(600, 500)
        
        layout = QVBoxLayout()
        
        # 创建选项卡
        tab_widget = QTabWidget()
        
        # 基本使用选项卡
        basic_tab = QWidget()
        basic_layout = QVBoxLayout()
        basic_tab.setLayout(basic_layout)
        
        basic_help = QTextEdit()
        basic_help.setReadOnly(True)
        basic_help.setHtml("""
        <h2>基本使用</h2>
        <p>Python PIP图形化管理工具让您可以轻松管理多个Python环境的包。</p>
        
        <h3>快速入门</h3>
        <ol>
            <li><b>选择Python版本</b>: 启动时工具会自动扫描系统中的Python安装。点击上方列表中的某个Python版本进行选择。</li>
            <li><b>查看已安装的包</b>: 选择Python版本后,下方列表会显示该版本已安装的所有包。</li>
            <li><b>安装新包</b>: 点击"安装包"按钮,输入包名和可选的版本号。</li>
            <li><b>升级或卸载</b>: 在包列表中选择一个包,然后点击相应的按钮。</li>
        </ol>
        
        <h3>按钮功能说明</h3>
        <ul>
            <li><b>列出Python</b>: 扫描系统中所有安装的Python版本</li>
            <li><b>安装包</b>: 安装新的Python包</li>
            <li><b>卸载包</b>: 卸载选中的包</li>
            <li><b>升级包</b>: 将选中的包升级到最新版本</li>
            <li><b>设置镜像源</b>: 选择或自定义PyPI镜像源</li>
            <li><b>刷新</b>: 刷新当前Python环境的包列表</li>
        </ul>
        """)
        basic_layout.addWidget(basic_help)
        
        # 镜像源选项卡
        mirror_tab = QWidget()
        mirror_layout = QVBoxLayout()
        mirror_tab.setLayout(mirror_layout)
        
        mirror_help = QTextEdit()
        mirror_help.setReadOnly(True)
        mirror_help.setHtml("""
        <h2>镜像源设置</h2>
        <p>使用国内镜像源可以大幅提高包的下载速度。本工具内置了以下常用镜像源:</p>
        
        <ul>
            <li><b>清华大学开源软件镜像站</b>: https://pypi.tuna.tsinghua.edu.cn/simple</li>
            <li><b>阿里云</b>: https://mirrors.aliyun.com/pypi/simple</li>
            <li><b>腾讯云</b>: https://mirrors.cloud.tencent.com/pypi/simple</li>
            <li><b>官方源</b>: https://pypi.org/simple (国内访问较慢)</li>
        </ul>
        
        <h3>如何设置镜像源</h3>
        <ol>
            <li>点击"设置镜像源"按钮或从"工具"菜单选择"设置镜像源"</li>
            <li>在弹出的对话框中选择预设镜像源,或选择"自定义"并输入镜像源URL</li>
            <li>点击"确定"保存设置</li>
        </ol>
        
        <p>镜像源设置会被保存,下次启动程序时自动加载。</p>
        """)
        mirror_layout.addWidget(mirror_help)
        
        # 常见问题选项卡
        faq_tab = QWidget()
        faq_layout = QVBoxLayout()
        faq_tab.setLayout(faq_layout)
        
        faq_help = QTextEdit()
        faq_help.setReadOnly(True)
        faq_help.setHtml("""
        <h2>常见问题</h2>
        
        <h3>Q: 为什么我的Python版本没有被检测到?</h3>
        <p>A: 工具会尝试检测常见位置的Python安装。如果您的Python安装在非标准位置,可能需要手动添加。</p>
        
        <h3>Q: 安装包时出现权限错误怎么办?</h3>
        <p>A: 在Windows上,您可能需要以管理员身份运行此工具。在Linux/Mac上,可以尝试使用用户目录下的Python环境或者使用虚拟环境。</p>
        
        <h3>Q: 如何处理版本冲突?</h3>
        <p>A: 建议使用虚拟环境(venv)来隔离不同项目的依赖。可以使用不同的Python解释器创建多个虚拟环境。</p>
        
        <h3>Q: 镜像源设置后没有生效?</h3>
        <p>A: 请确保镜像源URL格式正确,并且该镜像站点可以访问。可以尝试重新设置镜像源或选择其他镜像站点。</p>
        
        <h3>Q: 为什么有些包无法安装?</h3>
        <p>A: 可能是因为该包不支持您的Python版本,或者依赖了一些系统级库。请查看错误信息了解详情。</p>
        """)
        faq_layout.addWidget(faq_help)
        
        # 将选项卡添加到窗口
        tab_widget.addTab(basic_tab, "基本使用")
        tab_widget.addTab(mirror_tab, "镜像源设置")
        tab_widget.addTab(faq_tab, "常见问题")
        
        layout.addWidget(tab_widget)
        
        # 添加关闭按钮
        close_button = QPushButton("关闭")
        close_button.clicked.connect(help_dialog.accept)
        layout.addWidget(close_button)
        
        help_dialog.setLayout(layout)
        help_dialog.exec()

    def show_about(self):
        """显示关于对话框"""
        QMessageBox.about(
            self,
            "关于 Python PIP 图形化管理工具",
            """<h3>Python PIP 图形化管理工具 v1.0.2</h3>
            <p>一个用于管理多个Python版本包的图形界面工具</p>
            <p>主要功能:</p>
            <ul>
                <li>扫描和管理多个Python安装</li>
                <li>安装、卸载和升级包</li>
                <li>支持多种镜像源切换</li>
                <li>查看包详细信息</li>
            </ul>
            <p>&copy; 2025 版权所有</p>"""
        )

    def load_config(self):
        """加载配置文件"""
        config_path = os.path.expanduser('~/.pip-multi-gui-config')
        
        try:
            # 尝试加载配置文件
            if os.path.exists(config_path):
                with open(config_path, 'r', encoding='utf-8') as f:
                    config = json.load(f)
                    self.current_mirror = config.get('current_mirror')
                    if self.current_mirror:
                        self.mirror_label.setText(f'镜像源: {self.get_mirror_name(self.current_mirror)}')
        except Exception as e:
            QMessageBox.warning(self, '配置错误', f'加载配置文件失败: {str(e)}')

    def save_config(self):
        """保存配置文件"""
        config_path = os.path.expanduser('~/.pip-multi-gui-config')
        config = {
            'current_mirror': self.current_mirror
        }
        
        try:
            with open(config_path, 'w', encoding='utf-8') as f:
                json.dump(config, f, ensure_ascii=False, indent=4)
        except Exception as e:
            QMessageBox.warning(self, '配置错误', f'保存配置文件失败: {str(e)}')

    def get_mirror_name(self, url):
        """根据URL获取镜像源名称"""
        for name, mirror_url in self.mirrors.items():
            if mirror_url == url:
                return name
        return '自定义源'

    def _get_startupinfo(self):
        """获取隐藏命令窗口的startupinfo对象"""
        if sys.platform.startswith('win'):
            startupinfo = subprocess.STARTUPINFO()
            startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            startupinfo.wShowWindow = subprocess.SW_HIDE
            return startupinfo
        return None

    def find_python_installations(self):
        """查找系统中安装的所有Python版本"""
        self.python_installations = []
        self.python_list.clear()
        
        # 添加日志提示
        self.info_text.clear()
        self.info_text.append("正在扫描系统中的Python安装,请稍候...
")
        QApplication.processEvents()  # 立即更新UI
        
        try:
            # 在Windows系统上查找Python安装
            if sys.platform == 'win32':
                self._find_windows_pythons()
            # 在Unix/Linux/Mac系统上查找Python安装
            else:
                self._find_unix_pythons()
            
            # 更新UI
            for python in self.python_installations:
                item = QListWidgetItem(f"{python['version']} ({python['path']})")
                self.python_list.addItem(item)
            
            # 添加结果日志
            self.info_text.append(f"扫描完成,找到 {len(self.python_installations)} 个Python安装
")
            for i, python in enumerate(self.python_installations, 1):
                self.info_text.append(f"{i}. {python['version']} - {python['path']}")
            
            # 如果有Python安装,选择第一个
            if self.python_installations:
                self.python_list.setCurrentRow(0)
                self.on_python_selected(self.python_list.item(0))
            
            self.status_bar.showMessage(f'找到 {len(self.python_installations)} 个Python安装', 3000)
            
        except Exception as e:
            self.info_text.append(f"扫描失败: {str(e)}")
            QMessageBox.critical(self, '错误', f'查找Python安装失败: {str(e)}')

    def _find_windows_pythons(self):
        """查找Windows上的Python安装"""
        # 可能的Python安装路径
        possible_paths = []
        
        # 检查Program Files
        program_files = [
            os.environ.get('ProgramFiles', 'C:\Program Files'),
            os.environ.get('ProgramFiles(x86)', 'C:\Program Files (x86)')
        ]
        
        # 添加用户目录下的AppDataLocal
        user_profile = os.environ.get('USERPROFILE', '')
        if user_profile:
            program_files.append(os.path.join(user_profile, 'AppData', 'Local'))
        
        # 查找Python目录
        for base_dir in program_files:
            if os.path.exists(base_dir):
                for item in os.listdir(base_dir):
                    if item.lower().startswith('python'):
                        python_dir = os.path.join(base_dir, item)
                        possible_paths.append(python_dir)
        
        # 添加系统PATH中的Python
        for path in os.environ.get('PATH', '').split(os.pathsep):
            if 'python' in path.lower():
                possible_paths.append(path)
        
        # 检查每个可能的路径
        for path in possible_paths:
            python_exe = os.path.join(path, 'python.exe')
            if os.path.exists(python_exe):
                self._add_python_installation(python_exe)

    def _find_unix_pythons(self):
        """查找Unix/Linux/Mac上的Python安装"""
        # 常见的Python可执行文件名
        python_executables = ['python', 'python3', 'python2']
        
        # 使用which命令查找Python
        for exe in python_executables:
            try:
                result = subprocess.run(['which', exe], 
                                      capture_output=True, text=True, check=False)
                if result.returncode == 0 and result.stdout.strip():
                    self._add_python_installation(result.stdout.strip())
            except Exception:
                pass
        
        # 检查常见安装位置
        common_paths = [
            '/usr/bin', '/usr/local/bin', '/opt/local/bin',
            '/Library/Frameworks/Python.framework/Versions'
        ]
        
        for base_path in common_paths:
            if os.path.exists(base_path):
                for item in os.listdir(base_path):
                    if any(item.startswith(exe) for exe in python_executables):
                        python_path = os.path.join(base_path, item)
                        if os.path.isfile(python_path) and os.access(python_path, os.X_OK):
                            self._add_python_installation(python_path)

    def _add_python_installation(self, python_path):
        """添加Python安装到列表"""
        try:
            # 获取Python版本
            result = subprocess.run(
                [python_path, '--version'],
                capture_output=True, text=True, check=False,
                startupinfo=self._get_startupinfo()
            )
            
            if result.returncode == 0:
                version = result.stdout.strip() or result.stderr.strip()
                
                # 检查是否已存在相同路径
                for existing in self.python_installations:
                    if existing['path'] == python_path:
                        return
                
                self.python_installations.append({
                    'path': python_path,
                    'version': version
                })
        except Exception:
            pass

    def on_python_selected(self, item):
        """处理Python选择事件"""
        if not item:
            return
        
        index = self.python_list.row(item)
        if 0 <= index < len(self.python_installations):
            self.current_python = self.python_installations[index]
            self.python_label.setText(f"Python: {self.current_python['version']}")
            self.refresh_package_list()

    def refresh_package_list(self):
        """刷新当前Python的已安装包列表"""
        if not self.current_python:
            QMessageBox.warning(self, '警告', '请先选择一个Python版本')
            return
        
        # 添加日志提示
        self.info_text.clear()
        self.info_text.append(f"正在获取 {self.current_python['version']} 的已安装包列表...
")
        QApplication.processEvents()  # 立即更新UI
        
        try:
            # 执行pip list命令获取已安装包
            result = subprocess.run(
                [self.current_python['path'], '-m', 'pip', 'list', '--format=json'],
                capture_output=True, text=True, check=False,
                startupinfo=self._get_startupinfo()
            )
            
            if result.returncode == 0:
                # 解析JSON结果
                self.installed_packages = json.loads(result.stdout)
                self.update_package_list_display()
                
                # 更新日志
                self.info_text.append(f"成功获取 {len(self.installed_packages)} 个已安装的包
")
                self.info_text.append("选择左侧列表中的包可查看详细信息")
                
                self.status_bar.showMessage('包列表刷新成功', 3000)
            else:
                error_msg = result.stderr.strip() or '未知错误'
                raise Exception(error_msg)
        except Exception as e:
            self.info_text.append(f"获取包列表失败: {str(e)}")
            QMessageBox.critical(self, '错误', f'获取包列表失败: {str(e)}')
            self.status_bar.showMessage('获取包列表失败', 3000)

    def update_package_list_display(self):
        """更新包列表显示"""
        self.package_list.clear()
        
        # 按名称排序
        sorted_packages = sorted(self.installed_packages, key=lambda x: x['name'].lower())
        
        # 添加到列表控件
        for pkg in sorted_packages:
            item = QListWidgetItem(f"{pkg['name']}=={pkg['version']}")
            self.package_list.addItem(item)

    def show_package_info(self, item):
        """显示选中包的详细信息"""
        if not item:
            return
        
        pkg_name = item.text().split('==')[0]
        
        # 添加日志提示
        self.info_text.clear()
        self.info_text.append(f"正在获取 {pkg_name} 的详细信息...
")
        QApplication.processEvents()  # 立即更新UI
        
        try:
            # 执行pip show命令获取包详情
            result = subprocess.run(
                [self.current_python['path'], '-m', 'pip', 'show', pkg_name],
                capture_output=True, text=True, check=False,
                startupinfo=self._get_startupinfo()
            )
            
            if result.returncode == 0:
                self.info_text.clear()
                self.info_text.append(f"== {pkg_name} 详细信息 ==
")
                self.info_text.append(result.stdout)
            else:
                raise Exception(result.stderr.strip() or '未知错误')
        except Exception as e:
            self.info_text.append(f"获取包信息失败: {str(e)}")

    def install_package(self):
        """安装Python包"""
        if not self.current_python:
            QMessageBox.warning(self, '警告', '请先选择一个Python版本')
            return
        
        # 创建自定义对话框
        dialog = QDialog(self)
        dialog.setWindowTitle('安装包')
        layout = QFormLayout()
        
        # 包名输入框
        pkg_name_edit = QLineEdit()
        layout.addRow('包名:', pkg_name_edit)
        
        # 版本号输入框
        version_edit = QLineEdit()
        version_edit.setPlaceholderText('可选,如: 1.0.0')
        layout.addRow('版本号:', version_edit)
        
        # 确定和取消按钮
        btn_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
        btn_box.accepted.connect(dialog.accept)
        btn_box.rejected.connect(dialog.reject)
        layout.addRow(btn_box)
        
        dialog.setLayout(layout)
        
        if dialog.exec() == QDialog.DialogCode.Accepted:
            pkg_name = pkg_name_edit.text().strip()
            version = version_edit.text().strip()
            
            if not pkg_name:
                QMessageBox.warning(self, '警告', '包名不能为空')
                return
            
            # 显示使用的镜像源信息
            mirror_info = ""
            if self.current_mirror:
                mirror_info = f"
镜像源: {self.get_mirror_name(self.current_mirror)}"
            
            # 确认安装
            confirm = QMessageBox.question(
                self, '确认安装',
                f'确定要安装 {pkg_name}{"=="+version if version else ""} 吗?{mirror_info}',
                QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
            )
            
            if confirm == QMessageBox.StandardButton.Yes:
                self.run_pip_command('install', pkg_name, version=version)

    def uninstall_package(self):
        """卸载Python包"""
        if not self.current_python:
            QMessageBox.warning(self, '警告', '请先选择一个Python版本')
            return
        
        selected_item = self.package_list.currentItem()
        if not selected_item:
            QMessageBox.warning(self, '警告', '请先选择一个包')
            return
        
        pkg_name = selected_item.text().split('==')[0]
        
        # 确认卸载
        confirm = QMessageBox.question(
            self, '确认卸载',
            f'确定要卸载 {pkg_name} 吗?',
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
        )

        if confirm == QMessageBox.StandardButton.Yes:
            self.run_pip_command('uninstall', pkg_name)

    def upgrade_package(self):
        """升级Python包"""
        if not self.current_python:
            QMessageBox.warning(self, '警告', '请先选择一个Python版本')
            return
        
        selected_item = self.package_list.currentItem()
        if not selected_item:
            QMessageBox.warning(self, '警告', '请先选择一个包')
            return
        
        pkg_name = selected_item.text().split('==')[0]
        
        # 确认升级
        confirm = QMessageBox.question(
            self, '确认升级',
            f'确定要升级 {pkg_name} 吗?',
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
        )
        
        if confirm == QMessageBox.StandardButton.Yes:
            self.run_pip_command('install', pkg_name, upgrade=True)

    def run_pip_command(self, command, pkg_name, version=None, upgrade=False):
        """执行pip命令的通用方法"""
        if not self.current_python:
            QMessageBox.warning(self, '警告', '请先选择一个Python版本')
            return
        
        # 构建命令
        cmd = [self.current_python['path'], '-m', 'pip', command, pkg_name]
        
        # 卸载时自动确认
        if command == 'uninstall':
            cmd.append('--yes')
        
        # 添加版本号
        if version:
            cmd[-1] = f"{pkg_name}=={version}"
        
        # 添加升级标志
        if upgrade:
            cmd.append('--upgrade')
        
        # 添加镜像源(卸载操作不需要)
        if self.current_mirror and command != 'uninstall':
            cmd.extend(['-i', self.current_mirror])
            host = self.current_mirror.split('//')[1].split('/')[0]
            cmd.extend(['--trusted-host', host])
        
        # 清空并准备显示输出
        self.info_text.clear()
        self.info_text.append(f"执行命令: {' '.join(cmd)}
")
        self.info_text.append("正在处理,请稍候...
")
        QApplication.processEvents()  # 立即更新UI
        
        try:
            # 创建隐藏窗口的startupinfo对象(仅Windows系统)
            startupinfo = self._get_startupinfo()
            
            # 执行命令并实时输出
            process = subprocess.Popen(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                bufsize=1,
                universal_newlines=True,
                startupinfo=startupinfo  # 添加这个参数来隐藏窗口
            )
            
            # 实时读取输出
            while True:
                output = process.stdout.readline()
                if output == '' and process.poll() is not None:
                    break
                if output:
                    self.info_text.append(output.strip())
                    QApplication.processEvents()  # 更新UI
            
            # 获取最终返回码
            return_code = process.wait()
            
            # 显示结果
            if return_code == 0:
                operation_name = {
                    'install': '安装',
                    'uninstall': '卸载',
                }
                
                op_name = operation_name.get(command, command)
                if upgrade:
                    op_name = '升级'
                
                success_msg = f"
{op_name}操作成功完成!"
                self.info_text.append(success_msg)
                self.status_bar.showMessage(f"{op_name} {pkg_name} 成功", 5000)
                self.refresh_package_list()
            else:
                error_output = process.stderr.read()
                self.info_text.append(f"
错误信息:
{error_output}")
                raise Exception(error_output)
        
        except Exception as e:
            self.status_bar.showMessage(f"操作失败", 5000)
            QMessageBox.critical(
                self, '操作失败',
                f'操作执行失败:
{str(e)}'
            )
            self.info_text.append(f"
错误信息:
{str(e)}")

    def show_mirror_dialog(self):
        """显示镜像源设置对话框"""
        # 创建对话框
        dialog = QDialog(self)
        dialog.setWindowTitle('设置镜像源')
        layout = QVBoxLayout()
        
        # 添加说明
        layout.addWidget(QLabel("选择或输入要使用的PyPI镜像源:"))
        
        # 添加镜像源选项
        mirror_group = QButtonGroup()
        for i, (name, url) in enumerate(self.mirrors.items()):
            radio = QRadioButton(f"{name} ({url})")
            radio.setProperty('url', url)
            if url == self.current_mirror:
                radio.setChecked(True)
            mirror_group.addButton(radio, i)
            layout.addWidget(radio)
        
        # 自定义镜像源输入
        custom_radio = QRadioButton('自定义')
        mirror_group.addButton(custom_radio, len(self.mirrors))
        layout.addWidget(custom_radio)
        
        custom_edit = QLineEdit()
        if self.current_mirror and self.get_mirror_name(self.current_mirror) == '自定义源':
            custom_edit.setText(self.current_mirror)
            custom_radio.setChecked(True)
        else:
            custom_edit.setPlaceholderText('输入镜像源URL,例如: https://pypi.org/simple')
        layout.addWidget(custom_edit)
        
        # 添加单选按钮点击事件
        def on_radio_clicked(button):
            if button != custom_radio:
                url = button.property('url')
                if url:
                    custom_edit.setText(url)
        
        # 连接信号
        for i in range(mirror_group.buttons().__len__()):
            button = mirror_group.button(i)
            if button:
                button.clicked.connect(lambda checked, btn=button: on_radio_clicked(btn))
        
        # 确定和取消按钮
        btn_box = QDialogButtonBox(
            QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
        )
        btn_box.accepted.connect(dialog.accept)
        btn_box.rejected.connect(dialog.reject)
        layout.addWidget(btn_box)
        
        dialog.setLayout(layout)
        
        # 处理对话框结果
        if dialog.exec() == QDialog.DialogCode.Accepted:
            selected = mirror_group.checkedButton()
            if selected == custom_radio and custom_edit.text():
                self.current_mirror = custom_edit.text()
            elif selected != custom_radio:
                self.current_mirror = selected.property('url')
            
            # 更新UI和配置
            self.mirror_label.setText(f'镜像源: {self.get_mirror_name(self.current_mirror)}')
            self.save_config()
            
            # 更新日志
            self.info_text.clear()
            self.info_text.append(f"已设置镜像源: {self.get_mirror_name(self.current_mirror)}")
            self.info_text.append(f"镜像源URL: {self.current_mirror}")
            
            self.status_bar.showMessage('镜像源设置成功', 3000)

    def closeEvent(self, event):
        """重写关闭事件,保存配置"""
        self.save_config()
        event.accept()


def main():
    """主程序入口"""
    app = QApplication(sys.argv)
    window = PipManager()
    window.show()
    sys.exit(app.exec())


if __name__ == '__main__':
    main()

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容