吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2313|回复: 39
收起左侧

[Python 原创] pip图形化管理工具

  [复制链接]
asdf1233124 发表于 2025-4-20 10:17
AAA.png
001.png
002.png


pip图形化管理工具功能说明文档

1. 概述

pip图形化管理工具是一个基于PyQt5开发的图形界面应用程序,用于简化Python包(pip)的管理操作。它提供了直观的界面来安装、卸载、升级Python包,并支持镜像源切换功能。

2. 主要功能

2.1 包管理功能

  • 包列表展示:显示当前环境中已安装的所有Python包及其版本
  • 包搜索功能:支持本地包搜索和远程PyPI包搜索
  • 包安装:提供对话框输入包名和版本号进行安装
  • 包卸载:可卸载选中的Python包
  • 包升级:可升级选中的Python包到最新版本
  • 包详情查看:显示选中包的详细信息(pip show)

2.2 镜像源管理

  • 预设镜像源:内置清华、阿里云、腾讯云和官方源等多个镜像源
  • 自定义镜像源:支持输入自定义镜像源URL
  • 镜像源切换:可随时切换不同的镜像源
  • 镜像源状态显示:在状态栏显示当前使用的镜像源

2.3 其他功能

  • 实时输出显示:执行pip命令时实时显示输出信息
  • 错误处理:友好的错误提示和异常处理

3. 界面说明

3.1 主界面布局

  • 左侧面板:包列表和搜索框
  • 右侧面板:包详情显示区域
  • 顶部菜单栏:文件、设置、帮助菜单
  • 工具栏:常用操作按钮(安装、卸载、升级等)
  • 状态栏:显示状态信息和当前镜像源

3.2 右键菜单

在包列表项上右键点击可显示上下文菜单,提供快捷操作:

  • 安装选中包
  • 卸载选中包
  • 升级选中包
  • 查看包详情

4. 使用说明

4.1 基本操作流程

  1. 启动程序后自动加载已安装包列表
  2. 在搜索框中输入包名可搜索本地或远程包
  3. 右键点击包名可进行安装/卸载/升级等操作
  4. 通过"设置镜像源"按钮可切换下载源
  5. 点击工具栏的安装包按钮可以安装包

4.2 系统要求

  • Python 3.6+
  • PyQt5库
  • requests库(用于远程搜索功能)

5. 技术特点

  • 使用PyQt5构建图形界面
  • 通过subprocess调用系统pip命令
  • 使用JSON格式存储配置
  • 支持多线程操作避免界面卡顿
  • 完善的错误处理和用户反馈

6. 注意事项

  • 需要管理员/root权限才能安装/卸载系统级Python包
  • 某些操作可能需要网络连接
  • 首次使用建议安装requests库以获得完整功能



[Python] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
# -*- coding: utf-8 -*-
"""
pip图形化管理工具主程序
功能:提供图形化界面管理Python包,支持安装/卸载/升级和镜像源切换
"""
 
# 系统模块导入
import sys  # 系统相关功能
import subprocess  # 执行外部命令
import json  # JSON配置文件处理
import os  # 操作系统接口
from datetime import datetime  # 时间处理
 
# PyQt5模块导入
from PyQt5.QtWidgets import *
 
from PyQt5.QtCore import *  # Qt核心功能
from PyQt5.QtGui import *  # 图标处理
 
 
class PipManager(QMainWindow):
    """
    pip图形化管理工具主窗口类
    提供完整的包管理功能界面和逻辑
    """
 
    def __init__(self):
        super().__init__()
 
        self.is_admin = False
        try:
            import ctypes
            self.is_admin = ctypes.windll.shell32.IsUserAnAdmin()
        except:
            pass
         
        # 窗口基本设置
        title = 'pip图形化管理工具'
        if self.is_admin:
            title += ' (管理员)'
        self.setWindowTitle(title)  # 窗口标题
        self.setGeometry(100, 100, 800, 600# 窗口位置和大小
         
        # 初始化成员变量
        self.current_mirror = None  # 当前使用的镜像源
        self.installed_packages = []  # 已安装包列表缓存
        self.operation_history = []  # 操作历史记录
        self.searched_packages = [] # 搜索到的包列表
        self.requests_available = False  # 初始化requests可用标志
         
        # 检查requests库是否可用
        try:
            import requests
            self.requests_available = True
        except ImportError:
            self.requests_available = False
            QMessageBox.warning(self, '警告', '需要requests库来搜索远程包,请先安装: pip install requests')
         
        # 初始化UI
        self.init_ui()
         
        # 加载配置
        self.load_config()
         
        # 初始化包列表
        self.refresh_package_list()
        # 初始化右键菜单
        self.init_context_menu()
 
    def init_ui(self):
        """初始化用户界面组件"""
         
        # 1. 创建菜单栏
        self.init_menu_bar()
         
        # 2. 创建工具栏
        self.init_tool_bar()
         
        # 3. 创建状态栏
        self.init_status_bar()
         
        # 4. 创建主内容区
        self.init_main_content()
         
        # 5. 创建右键菜单
        self.init_context_menu()
 
    def init_menu_bar(self):
        """初始化菜单栏"""
         
        # 文件菜单
        file_menu = self.menuBar().addMenu('文件(&F)')
         
        # 刷新操作
        refresh_action = QAction('刷新包列表', self)
        refresh_action.triggered.connect(self.refresh_package_list)
        file_menu.addAction(refresh_action)
         
        # 退出操作
        exit_action = QAction('退出', self)
        exit_action.setShortcut('Ctrl+Q')
        exit_action.triggered.connect(self.close)
        file_menu.addAction(exit_action)
         
        # 设置
        edit_menu = self.menuBar().addMenu('设置(&E)')
         
        # 设置镜像源
        mirror_action = QAction('设置镜像源', self)
        mirror_action.triggered.connect(self.show_mirror_dialog)
        edit_menu.addAction(mirror_action)
 
         # 添加以管理员身份运行选项
        self.admin_action = QAction('以管理员身份运行', self)
        self.admin_action.triggered.connect(self.run_as_admin)
        if self.is_admin:
            self.admin_action.setEnabled(False# 已经是管理员则禁用
        edit_menu.addAction(self.admin_action)
         
        # 帮助菜单
        help_menu = self.menuBar().addMenu('帮助(&H)')
         
        # 关于操作
        about_action = QAction('关于', self)
        about_action.triggered.connect(self.show_about)
        help_menu.addAction(about_action)
 
    def run_as_admin(self):
        """以管理员身份重新运行程序"""
        try:
            import ctypes
            import sys
             
            # 检查是否已经是管理员权限
            if ctypes.windll.shell32.IsUserAnAdmin():
                QMessageBox.information(self, '提示', '当前已经是管理员权限')
                return
                 
            # 重新以管理员权限运行
            ctypes.windll.shell32.ShellExecuteW(
                None, "runas", sys.executable, " ".join(sys.argv), None, 1
            )
             
            # 退出当前实例
            self.close()
             
        except Exception as e:
            QMessageBox.critical(self, '错误', f'请求管理员权限失败: {str(e)}')
 
 
    def init_tool_bar(self):
        """初始化工具栏"""
         
        toolbar = QToolBar('主工具栏')
        toolbar.setIconSize(QSize(16, 16))
        self.addToolBar(toolbar)
         
        # 安装按钮
        install_btn = QPushButton('安装包')
        install_btn.clicked.connect(self.install_package)
        toolbar.addWidget(install_btn)
         
        # 卸载按钮
        uninstall_btn = QPushButton('卸载包')
        uninstall_btn.clicked.connect(self.uninstall_package)
        toolbar.addWidget(uninstall_btn)
         
        # 升级按钮
        upgrade_btn = QPushButton('升级包')
        upgrade_btn.clicked.connect(self.upgrade_package)
        toolbar.addWidget(upgrade_btn)
         
        # 刷新按钮
        refresh_btn = QPushButton('刷新')
        refresh_btn.clicked.connect(self.refresh_package_list)
        toolbar.addWidget(refresh_btn)
 
        # 设置镜像源
        jxy_btn = QPushButton('设置镜像源')
        jxy_btn.clicked.connect(self.show_mirror_dialog)
        toolbar.addWidget(jxy_btn)
 
    def init_status_bar(self):
        """初始化状态栏"""
         
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)
         
        # 显示当前镜像源
        self.mirror_label = QLabel('镜像源: 未设置')
        self.status_bar.addPermanentWidget(self.mirror_label)
         
        # 显示状态信息
        self.status_bar.showMessage('准备就绪', 3000)
 
    def init_main_content(self):
        """初始化主内容区域"""
         
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
         
        # 主布局
        main_layout = QHBoxLayout()
        main_widget.setLayout(main_layout)
         
        # 左侧布局 (包列表)
        left_layout = QVBoxLayout()
         
        # 搜索框
        self.search_box = QLineEdit()
        self.search_box.setPlaceholderText('搜索包名...')
        self.search_box.textChanged.connect(self.filter_packages)
        left_layout.addWidget(self.search_box)
         
        # 包列表
        self.package_list = QListWidget()
        self.package_list.setAlternatingRowColors(True)
        left_layout.addWidget(self.package_list)
         
        # 右侧布局 (包详情)
        right_layout = QVBoxLayout()
         
        # 包详情标签
        self.package_detail = QTextEdit()
        self.package_detail.setReadOnly(True)
        right_layout.addWidget(self.package_detail)
         
        # 添加到主布局
        main_layout.addLayout(left_layout, 3# 左侧占3份
        main_layout.addLayout(right_layout, 2# 右侧占2份
 
    def init_context_menu(self):
        """初始化右键菜单"""
         
        # 设置包列表的右键菜单
        self.package_list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.package_list.customContextMenuRequested.connect(self.show_package_context_menu)
         
        # 创建菜单对象
        self.package_menu = QMenu(self)
         
        # 添加菜单项
        install_action = QAction('安装', self)
        install_action.triggered.connect(self.install_selected_package)  # 改为安装选中包
        self.package_menu.addAction(install_action)
         
        uninstall_action = QAction('卸载', self)
        uninstall_action.triggered.connect(self.uninstall_package)
        self.package_menu.addAction(uninstall_action)
         
        upgrade_action = QAction('升级', self)
        upgrade_action.triggered.connect(self.upgrade_package)
        self.package_menu.addAction(upgrade_action)
         
        self.package_menu.addSeparator()
         
        info_action = QAction('查看详情', self)
        info_action.triggered.connect(self.show_package_info)
        self.package_menu.addAction(info_action)
    def install_selected_package(self):
        """安装选中的包"""
        selected_item = self.package_list.currentItem()
        if not selected_item:
            QMessageBox.warning(self, '警告', '请先选择一个包')
            return
             
        pkg_name = selected_item.text().split('==')[0]
         
        # 显示使用的镜像源信息
        mirror_info = ""
        if self.current_mirror:
            mirror_info = f"\n镜像源: {self.get_mirror_name(self.current_mirror)}"
         
        # 确认安装
        confirm = QMessageBox.question(
            self, '确认安装',
            f'确定要安装 {pkg_name} 吗?{mirror_info}',
            QMessageBox.Yes | QMessageBox.No
        )
         
        if confirm == QMessageBox.Yes:
            try:
                self.run_pip_command('install', pkg_name)
            except Exception as e:
                QMessageBox.critical(self, '安装错误', f'安装过程中出错\n{str(e)}')
 
    def load_config(self):
        """加载配置文件"""
         
        config_path = os.path.expanduser('~/.pip-gui-config')
         
        # 默认镜像源配置
        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"
        }
         
        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')
                    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-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 refresh_package_list(self):
        """刷新已安装包列表"""
         
        try:
            # 执行pip list命令获取已安装包
            result = subprocess.run(
                [sys.executable, '-m', 'pip', 'list', '--format=json'],
                capture_output=True, text=True
            )
             
            if result.returncode == 0:
                # 解析JSON结果
                self.installed_packages = json.loads(result.stdout)
                self.update_package_list_display()
                self.status_bar.showMessage('包列表刷新成功', 3000)
            else:
                raise Exception(result.stderr)
                 
        except Exception as 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']}")
            item.setForeground(QColor('black'))  # 本地包用黑色
            self.package_list.addItem(item)
         
        # 添加搜索到的包(如果有)
        for pkg in self.searched_packages:
            item = QListWidgetItem(f"{pkg['name']}=={pkg['version']} (可安装)")
            item.setForeground(QColor('blue'))  # 可安装包用蓝色
            self.package_list.addItem(item)
 
 
    def search_remote_package(self, package_name):
        """搜索远程包"""
        try:
            import requests
            print(f"正在搜索: {package_name}"# 调试输出
             
            # 设置正确的请求头
            headers = {
                "Accept": "application/json",
                "User-Agent": "pip-gui-tool/1.0"
            }
             
            # 使用PyPI的JSON API端点
            response = requests.get(
                f"https://pypi.org/pypi/{package_name}/json",
                headers=headers,
                timeout=10
            )
             
            print(f"响应状态码: {response.status_code}"# 调试输出
            print(f"响应内容: {response.text[:200]}..."# 只打印前200字符
             
            # 检查响应状态
            response.raise_for_status()
                 
            data = response.json()
             
            self.searched_packages = []
            # 解析API返回的数据格式
            if 'info' in data:
                self.searched_packages.append({
                    'name': data['info']['name'],
                    'version': data['info'].get('version', '最新')
                })
                     
            if self.searched_packages:
                self.update_package_list_display()
                self.status_bar.showMessage(f"找到包: {self.searched_packages[0]['name']}", 3000)
            else:
                self.status_bar.showMessage("未找到匹配包", 3000)
                 
        except requests.exceptions.HTTPError as e:
            if e.response.status_code == 404:
                self.status_bar.showMessage(f"未找到包: {package_name}", 3000)
            else:
                error_msg = f"网络请求失败: {str(e)}"
                print(error_msg)
                QMessageBox.critical(self, '错误', error_msg)
        except Exception as e:
            error_msg = f"搜索失败: {str(e)}"
            print(error_msg)
            QMessageBox.critical(self, '错误', error_msg)
 
 
 
    def filter_packages(self):
        """根据搜索框内容过滤包列表"""
        search_text = self.search_box.text().strip()
         
        if not search_text:
            self.searched_packages = []
            self.update_package_list_display()
            return
             
        # 如果requests不可用,直接返回
        if not self.requests_available:
            QMessageBox.warning(self, '警告', 'requests库未安装,无法搜索远程包')
            return
             
        # 先显示本地匹配项
        has_local_match = False
        for i in range(self.package_list.count()):
            item = self.package_list.item(i)
            match = search_text.lower() in item.text().lower()
            item.setHidden(not match)
            if match:
                has_local_match = True
                 
        # 如果没有本地匹配,才进行远程搜索
        if not has_local_match:
            self.search_remote_package(search_text)
 
 
 
 
    def show_package_context_menu(self, pos):
        """显示包列表的右键菜单"""
         
        # 确保有选中的项才显示菜单
        if self.package_list.currentItem():
            self.package_menu.exec_(self.package_list.mapToGlobal(pos))
 
    def show_package_info(self):
        """显示选中包的详细信息"""
         
        selected_item = self.package_list.currentItem()
        if not selected_item:
            return
             
        pkg_name = selected_item.text().split('==')[0]
         
        try:
            # 执行pip show命令获取包详情
            result = subprocess.run(
                [sys.executable, '-m', 'pip', 'show', pkg_name],
                capture_output=True, text=True
            )
             
            if result.returncode == 0:
                self.package_detail.setText(result.stdout)
            else:
                raise Exception(result.stderr)
                 
        except Exception as e:
            QMessageBox.critical(self, '错误', f'获取包信息失败: {str(e)}')
 
    def install_package(self):
        """安装Python包"""
        # 创建自定义对话框
        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.Ok | QDialogButtonBox.Cancel)
        btn_box.accepted.connect(dialog.accept)
        btn_box.rejected.connect(dialog.reject)
        layout.addRow(btn_box)
         
        dialog.setLayout(layout)
         
        if dialog.exec_() == QDialog.Accepted:
            pkg_name = pkg_name_edit.text().strip()
            version = version_edit.text().strip()
             
            if not pkg_name:
                QMessageBox.warning(self, '警告', '包名不能为空')
                return
                 
            if version and not all(c.isdigit() or c == '.' for c in version):
                QMessageBox.warning(self, '错误', '版本号格式不正确,请使用数字和点(.)')
                return
                 
            # 显示使用的镜像源信息
            mirror_info = ""
            if self.current_mirror:
                mirror_info = f"\n镜像源: {self.get_mirror_name(self.current_mirror)}"
             
            # 确认安装
            confirm = QMessageBox.question(
                self, '确认安装',
                f'确定要安装 {pkg_name}{"=="+version if version else ""} 吗?{mirror_info}',
                QMessageBox.Yes | QMessageBox.No
            )
             
            if confirm == QMessageBox.Yes:
                try:
                    self.run_pip_command('install', pkg_name, version)
                except Exception as e:
                    if "Could not find a version" in str(e):
                        QMessageBox.warning(self, '版本错误', f"找不到指定版本的包\n{e}")
                    elif "No matching distribution" in str(e):
                        QMessageBox.warning(self, '包名错误', f"找不到指定的包\n{e}")
                    else:
                        QMessageBox.critical(self, '安装错误', f"安装过程中出错\n{e}")
 
    def uninstall_package(self):
        """卸载Python包"""
         
        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.Yes | QMessageBox.No
        )
         
        if confirm == QMessageBox.Yes:
            self.run_pip_command('uninstall', pkg_name)
 
    def upgrade_package(self):
        """升级Python包"""
         
        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.Yes | QMessageBox.No
        )
         
        if confirm == QMessageBox.Yes:
            self.run_pip_command('install', pkg_name, upgrade=True)
 
    def run_pip_command(self, command, pkg_name, version=None, upgrade=False):
        """执行pip命令的通用方法"""
         
         # 构建命令
        cmd = [sys.executable, '-m', 'pip', command, pkg_name]
         
        # 卸载时自动确认
        if command == 'uninstall':
            cmd.append('--yes')
         
        # 添加版本号
        if version:
            cmd.append(f'=={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])
         
        try:
            # 执行命令并实时输出
            process = subprocess.Popen(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                bufsize=1,
                universal_newlines=True
            )
             
            # 清空并准备显示输出
            self.package_detail.clear()
             
            # 实时读取输出
            while True:
                output = process.stdout.readline()
                if output == '' and process.poll() is not None:
                    break
                if output:
                    self.package_detail.append(output.strip())
             
            # 获取最终返回码
            return_code = process.wait()
             
            # 记录操作历史
            self.operation_history.append({
                'command': ' '.join(cmd),
                'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
                'success': return_code == 0,
                'output': self.package_detail.toPlainText()
            })
             
            # 限制历史记录数量
            if len(self.operation_history) > 20:
                self.operation_history.pop(0)
             
            # 显示结果
            if return_code == 0:
                QMessageBox.information(
                    self, '操作成功',
                    f'操作执行成功'
                )
                self.refresh_package_list()
            else:
                error_output = process.stderr.read()
                self.package_detail.append(f"\n错误信息:\n{error_output}")
                raise Exception(error_output)
                 
        except Exception as e:
            QMessageBox.critical(
                self, '操作失败',
                f'操作执行失败:\n{str(e)}'
            )
            self.package_detail.append(f"\n错误信息:\n{str(e)}")
 
 
    def show_mirror_dialog(self):
        """显示镜像源设置对话框"""
         
        # 创建对话框
        dialog = QDialog(self)
        dialog.setWindowTitle('设置镜像源')
        layout = QVBoxLayout()
         
        # 添加镜像源选项
        mirror_group = QButtonGroup()
        for i, (name, url) in enumerate(self.mirrors.items()):
            radio = QRadioButton(name)
            radio.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()
        # 修改这里:总是显示当前镜像源URL(如果有)
        if self.current_mirror:
            custom_edit.setText(self.current_mirror)
        else:
            custom_edit.setPlaceholderText('输入镜像源URL')
         
        # 设置默认选中状态
        if not self.current_mirror:
            # 如果没有设置镜像源,默认选中第一项
            mirror_group.buttons()[0].setChecked(True)
            custom_edit.setText(mirror_group.buttons()[0].url)
         
        layout.addWidget(custom_edit)
         
        # 添加单选按钮点击事件
        def on_radio_clicked(button):
            if button != custom_radio:
                custom_edit.setText(button.url)
         
        mirror_group.buttonClicked.connect(on_radio_clicked)
         
        # 确定和取消按钮
        btn_box = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel
        )
        btn_box.accepted.connect(dialog.accept)
        btn_box.rejected.connect(dialog.reject)
        layout.addWidget(btn_box)
         
        dialog.setLayout(layout)
         
        # 处理对话框结果
        if dialog.exec_() == QDialog.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.url
            else:
                return
                 
            # 更新UI和配置
            self.mirror_label.setText(f'镜像源: {self.get_mirror_name(self.current_mirror)}')
            self.save_config()
            self.status_bar.showMessage('镜像源设置成功', 3000)
 
 
 
 
 
    def show_about(self):
        """显示关于对话框"""
         
        QMessageBox.about(
            self, '关于 pip图形化管理工具',
            'pip图形化管理工具 v1.0\n\n'
            '一个简单的pip包管理图形界面\n'
            '支持安装/卸载/升级和镜像源切换'
        )
 
    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()  # 只有直接运行时才执行

免费评分

参与人数 18威望 +1 吾爱币 +30 热心值 +15 收起 理由
hzy4619666 + 1 + 1 谢谢@Thanks!
sincos + 1 + 1 谢谢@Thanks!
bluesailor + 1 + 1 谢谢@Thanks!
YihangZhu + 1 我很赞同!
Chesed + 1 + 1 谢谢@Thanks!
HahahahaOO + 1 + 1 谢谢@Thanks!
hwh425 + 1 用心讨论,共获提升!
Gpgg + 1 + 1 谢谢@Thanks!
thornjay + 1 + 1 谢谢@Thanks!
repack9527 + 1 谢谢@Thanks!
RedFox2020 + 1 + 1 求打包文件
ciker_li + 3 + 1 谢谢@Thanks!
xhtdtk + 3 + 1 谢谢@Thanks!
qqy123 + 1 + 1 可以可以
苏紫方璇 + 1 + 10 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
nt0114 + 1 谢谢@Thanks!
hfol85 + 2 + 1 谢谢@Thanks!
Stars313 + 1 虽然用不上,但对于不想打字的人来说应该挺方便的

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

kkpo010 发表于 2025-4-20 18:54
本帖最后由 kkpo010 于 2025-4-20 18:56 编辑

用Visual Studio 2022 转换了  

通过网盘分享的文件:pip.rar
链接: https://pan.baidu.com/s/1vJN0OOFscAvLpGV7nSZXyQ?pwd=7dew 提取码: 7dew

523.jpg
adama 发表于 2025-4-20 14:11
xiaoxino 发表于 2025-4-21 15:23
kkpo010 发表于 2025-4-20 21:13
链接: https://pan.baidu.com/s/1uOhQAAer7hjK6g6PQkBrwA?pwd=busm 提取码: busm

能麻烦你一下这个可以帮忙给编译下吗WINRAR自动查找官方最新中文无广告版并下载脚本

https://www.52pojie.cn/forum.php?mod=viewthread&tid=1998574&highlight=winrar
52shijie 发表于 2025-4-20 10:37
挺好的想法
Yifan2007 发表于 2025-4-20 10:44
能不能打包一下哥,我显示缺环境打包不了,各种库有点不兼容
wyao 发表于 2025-4-20 11:16
这个  可以 顶起啊
hades1998 发表于 2025-4-20 11:22
好东西,能多版本管理吗
大白baymax 发表于 2025-4-20 11:45
打包成exe被杀软杀掉了
ghwanz 发表于 2025-4-20 12:00
求打包文件,我打包没启动
luosu 发表于 2025-4-20 12:31
新手学习血洗,感谢大佬
snakegao 发表于 2025-4-20 15:53
学习学习,感谢楼主
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-5-24 18:31

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表