592 lines
12 KiB
Markdown
592 lines
12 KiB
Markdown
|
|
# 开发者指南
|
|||
|
|
|
|||
|
|
本文档为 KeyPressRemark 项目的开发者提供详细的技术指导和代码贡献指南。
|
|||
|
|
|
|||
|
|
## 📋 目录
|
|||
|
|
|
|||
|
|
- [开发环境设置](#开发环境设置)
|
|||
|
|
- [项目架构](#项目架构)
|
|||
|
|
- [核心技术](#核心技术)
|
|||
|
|
- [代码规范](#代码规范)
|
|||
|
|
- [开发流程](#开发流程)
|
|||
|
|
- [测试指南](#测试指南)
|
|||
|
|
- [调试技巧](#调试技巧)
|
|||
|
|
- [性能优化](#性能优化)
|
|||
|
|
- [常见问题](#常见问题)
|
|||
|
|
|
|||
|
|
## 🛠️ 开发环境设置
|
|||
|
|
|
|||
|
|
### 环境要求
|
|||
|
|
|
|||
|
|
- **操作系统**: Windows 10/11 (开发和测试)
|
|||
|
|
- **Python**: 3.11+ (推荐 3.11.x)
|
|||
|
|
- **IDE**: PyCharm / VSCode (推荐)
|
|||
|
|
- **版本控制**: Git
|
|||
|
|
|
|||
|
|
### 开发环境配置
|
|||
|
|
|
|||
|
|
1. **克隆项目**
|
|||
|
|
```bash
|
|||
|
|
git clone <repository-url>
|
|||
|
|
cd KeyPressRemark
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. **创建虚拟环境**
|
|||
|
|
```bash
|
|||
|
|
# 使用 uv (推荐)
|
|||
|
|
uv venv --python 3.11
|
|||
|
|
.venv\Scripts\activate
|
|||
|
|
|
|||
|
|
# 或使用 venv
|
|||
|
|
python -m venv .venv
|
|||
|
|
.venv\Scripts\activate
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. **安装依赖**
|
|||
|
|
```bash
|
|||
|
|
# 开发依赖(包含测试工具)
|
|||
|
|
uv pip install -r requirements.txt
|
|||
|
|
uv add --dev pytest pytest-qt black isort flake8
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
4. **IDE 配置**
|
|||
|
|
- 设置 Python 解释器为虚拟环境中的 Python
|
|||
|
|
- 配置代码格式化工具(Black)
|
|||
|
|
- 启用类型检查(mypy)
|
|||
|
|
|
|||
|
|
### 调试配置
|
|||
|
|
|
|||
|
|
**VSCode launch.json**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"version": "0.2.0",
|
|||
|
|
"configurations": [
|
|||
|
|
{
|
|||
|
|
"name": "KeyPressRemark",
|
|||
|
|
"type": "python",
|
|||
|
|
"request": "launch",
|
|||
|
|
"program": "${workspaceFolder}/src/main.py",
|
|||
|
|
"console": "integratedTerminal",
|
|||
|
|
"justMyCode": true
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🏗️ 项目架构
|
|||
|
|
|
|||
|
|
### 整体架构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
KeyPressRemark/
|
|||
|
|
├── src/ # 源代码
|
|||
|
|
│ ├── core/ # 核心功能层
|
|||
|
|
│ ├── gui/ # 用户界面层
|
|||
|
|
│ ├── utils/ # 工具库层
|
|||
|
|
│ └── main.py # 应用入口
|
|||
|
|
├── resources/ # 资源文件
|
|||
|
|
├── tests/ # 测试代码
|
|||
|
|
└── docs/ # 文档
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 架构原则
|
|||
|
|
|
|||
|
|
1. **分层架构**: 核心功能、界面、工具明确分离
|
|||
|
|
2. **依赖注入**: 通过构造函数传递依赖
|
|||
|
|
3. **单一职责**: 每个模块只负责一个功能域
|
|||
|
|
4. **开闭原则**: 对扩展开放,对修改封闭
|
|||
|
|
|
|||
|
|
### 模块依赖关系
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
main.py
|
|||
|
|
├── gui.main_window (界面层)
|
|||
|
|
│ ├── core.window_selector (窗口选择)
|
|||
|
|
│ ├── core.key_sender (按键发送)
|
|||
|
|
│ └── utils.settings (设置管理)
|
|||
|
|
├── core.admin_check (权限检查)
|
|||
|
|
└── resources (资源文件)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🔧 核心技术
|
|||
|
|
|
|||
|
|
### Windows API 技术
|
|||
|
|
|
|||
|
|
本项目大量使用 Windows API,主要包括:
|
|||
|
|
|
|||
|
|
1. **用户交互 API**
|
|||
|
|
- `win32gui`: 窗口操作、消息发送
|
|||
|
|
- `win32api`: 系统信息获取
|
|||
|
|
- `win32con`: 常量定义
|
|||
|
|
|
|||
|
|
2. **底层输入 API**
|
|||
|
|
- `SendMessage`: 同步消息发送
|
|||
|
|
- `PostMessage`: 异步消息发送
|
|||
|
|
- `SendInput`: 系统级输入模拟
|
|||
|
|
|
|||
|
|
3. **系统权限 API**
|
|||
|
|
- UAC 提升检查
|
|||
|
|
- 进程权限管理
|
|||
|
|
|
|||
|
|
### PyQt5 技术
|
|||
|
|
|
|||
|
|
1. **界面架构**
|
|||
|
|
- QWidget: 基础窗口组件
|
|||
|
|
- QLayout: 布局管理
|
|||
|
|
- QTimer: 定时器功能
|
|||
|
|
|
|||
|
|
2. **事件处理**
|
|||
|
|
- 信号槽机制
|
|||
|
|
- 事件过滤器
|
|||
|
|
- 自定义事件
|
|||
|
|
|
|||
|
|
### 按键发送原理
|
|||
|
|
|
|||
|
|
#### 1. PostMessage 模式
|
|||
|
|
```python
|
|||
|
|
# 异步发送,消息进入目标窗口队列
|
|||
|
|
win32gui.PostMessage(hwnd, WM_KEYDOWN, key_code, lparam)
|
|||
|
|
win32gui.PostMessage(hwnd, WM_KEYUP, key_code, lparam)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2. SendMessage 模式
|
|||
|
|
```python
|
|||
|
|
# 同步发送,等待处理完成
|
|||
|
|
win32gui.SendMessage(hwnd, WM_KEYDOWN, key_code, lparam)
|
|||
|
|
win32gui.SendMessage(hwnd, WM_KEYUP, key_code, lparam)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3. SendInput 模式
|
|||
|
|
```python
|
|||
|
|
# 系统级模拟,最高兼容性
|
|||
|
|
inputs = (INPUT * 2)()
|
|||
|
|
# 设置输入结构...
|
|||
|
|
ctypes.windll.user32.SendInput(2, ctypes.byref(inputs), ctypes.sizeof(INPUT))
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 📝 代码规范
|
|||
|
|
|
|||
|
|
### Python 编码规范
|
|||
|
|
|
|||
|
|
1. **PEP 8 标准**
|
|||
|
|
- 使用 4 个空格缩进
|
|||
|
|
- 行长度不超过 88 字符(Black 标准)
|
|||
|
|
- 函数和类之间用两个空行分隔
|
|||
|
|
|
|||
|
|
2. **命名规范**
|
|||
|
|
- 类名:`PascalCase`
|
|||
|
|
- 函数/变量:`snake_case`
|
|||
|
|
- 常量:`UPPER_CASE`
|
|||
|
|
- 私有成员:`_underscore_prefix`
|
|||
|
|
|
|||
|
|
3. **类型注解**
|
|||
|
|
```python
|
|||
|
|
def send_key(self, key_code: int, mode: str = "post") -> bool:
|
|||
|
|
"""发送按键"""
|
|||
|
|
pass
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 文档规范
|
|||
|
|
|
|||
|
|
1. **模块文档**
|
|||
|
|
```python
|
|||
|
|
"""
|
|||
|
|
模块名 - 简短描述
|
|||
|
|
|
|||
|
|
详细说明模块的功能、用途和技术原理。
|
|||
|
|
|
|||
|
|
作者: xiao liu
|
|||
|
|
版本: v0.1.0
|
|||
|
|
"""
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. **函数文档**
|
|||
|
|
```python
|
|||
|
|
def function_name(param1: type, param2: type) -> return_type:
|
|||
|
|
"""
|
|||
|
|
函数简短描述
|
|||
|
|
|
|||
|
|
详细说明函数的功能、算法或注意事项。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
param1: 参数1说明
|
|||
|
|
param2: 参数2说明
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
返回值说明
|
|||
|
|
|
|||
|
|
Raises:
|
|||
|
|
Exception: 异常情况说明
|
|||
|
|
"""
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. **行内注释**
|
|||
|
|
```python
|
|||
|
|
# 检查目标窗口是否有效
|
|||
|
|
if not self.target_hwnd or not win32gui.IsWindow(self.target_hwnd):
|
|||
|
|
return False
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Git 提交规范
|
|||
|
|
|
|||
|
|
使用 [Conventional Commits](https://www.conventionalcommits.org/) 规范:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
<type>[optional scope]: <description>
|
|||
|
|
|
|||
|
|
[optional body]
|
|||
|
|
|
|||
|
|
[optional footer(s)]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**类型说明**:
|
|||
|
|
- `feat`: 新功能
|
|||
|
|
- `fix`: 修复 bug
|
|||
|
|
- `docs`: 文档更新
|
|||
|
|
- `style`: 代码格式化
|
|||
|
|
- `refactor`: 代码重构
|
|||
|
|
- `test`: 测试相关
|
|||
|
|
- `chore`: 构建过程或辅助工具的变动
|
|||
|
|
|
|||
|
|
**示例**:
|
|||
|
|
```
|
|||
|
|
feat(core): 添加新的按键发送模式
|
|||
|
|
|
|||
|
|
- 实现了 SendInput 模式
|
|||
|
|
- 提高了兼容性
|
|||
|
|
- 添加了相关测试
|
|||
|
|
|
|||
|
|
Closes #123
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🔄 开发流程
|
|||
|
|
|
|||
|
|
### 功能开发流程
|
|||
|
|
|
|||
|
|
1. **需求分析**
|
|||
|
|
- 在 GitHub Issues 中创建需求
|
|||
|
|
- 分析技术可行性
|
|||
|
|
- 设计 API 接口
|
|||
|
|
|
|||
|
|
2. **创建分支**
|
|||
|
|
```bash
|
|||
|
|
git checkout -b feature/new-feature-name
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. **开发实现**
|
|||
|
|
- 编写核心逻辑
|
|||
|
|
- 添加单元测试
|
|||
|
|
- 更新文档
|
|||
|
|
|
|||
|
|
4. **代码检查**
|
|||
|
|
```bash
|
|||
|
|
# 格式化代码
|
|||
|
|
black src/
|
|||
|
|
isort src/
|
|||
|
|
|
|||
|
|
# 静态检查
|
|||
|
|
flake8 src/
|
|||
|
|
mypy src/
|
|||
|
|
|
|||
|
|
# 运行测试
|
|||
|
|
pytest tests/
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
5. **提交代码**
|
|||
|
|
```bash
|
|||
|
|
git add .
|
|||
|
|
git commit -m "feat: 添加新功能"
|
|||
|
|
git push origin feature/new-feature-name
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
6. **创建 Pull Request**
|
|||
|
|
- 填写详细的变更说明
|
|||
|
|
- 添加测试截图(如适用)
|
|||
|
|
- 等待代码审查
|
|||
|
|
|
|||
|
|
### Bug 修复流程
|
|||
|
|
|
|||
|
|
1. **复现问题**
|
|||
|
|
- 记录复现步骤
|
|||
|
|
- 确认影响范围
|
|||
|
|
- 添加回归测试
|
|||
|
|
|
|||
|
|
2. **定位问题**
|
|||
|
|
- 使用调试器定位
|
|||
|
|
- 查看日志信息
|
|||
|
|
- 分析堆栈跟踪
|
|||
|
|
|
|||
|
|
3. **修复实现**
|
|||
|
|
- 最小化修改范围
|
|||
|
|
- 确保不引入新问题
|
|||
|
|
- 验证修复效果
|
|||
|
|
|
|||
|
|
## 🧪 测试指南
|
|||
|
|
|
|||
|
|
### 测试框架
|
|||
|
|
|
|||
|
|
项目使用 `pytest` 和 `pytest-qt` 进行测试:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import pytest
|
|||
|
|
from PyQt5.QtWidgets import QApplication
|
|||
|
|
from src.gui.main_window import KeyPresser
|
|||
|
|
|
|||
|
|
@pytest.fixture
|
|||
|
|
def app():
|
|||
|
|
"""创建 QApplication 实例"""
|
|||
|
|
app = QApplication([])
|
|||
|
|
yield app
|
|||
|
|
app.quit()
|
|||
|
|
|
|||
|
|
def test_window_creation(app):
|
|||
|
|
"""测试窗口创建"""
|
|||
|
|
window = KeyPresser()
|
|||
|
|
assert window is not None
|
|||
|
|
assert window.windowTitle() == "KeyPresser(无钩子版)"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 测试分类
|
|||
|
|
|
|||
|
|
1. **单元测试** (`tests/unit/`)
|
|||
|
|
- 测试单个函数/方法
|
|||
|
|
- 使用 mock 隔离依赖
|
|||
|
|
- 覆盖边界条件
|
|||
|
|
|
|||
|
|
2. **集成测试** (`tests/integration/`)
|
|||
|
|
- 测试模块间交互
|
|||
|
|
- 验证 API 契约
|
|||
|
|
- 测试数据流
|
|||
|
|
|
|||
|
|
3. **界面测试** (`tests/gui/`)
|
|||
|
|
- 使用 pytest-qt
|
|||
|
|
- 模拟用户操作
|
|||
|
|
- 验证界面响应
|
|||
|
|
|
|||
|
|
### 运行测试
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 运行所有测试
|
|||
|
|
pytest
|
|||
|
|
|
|||
|
|
# 运行特定测试文件
|
|||
|
|
pytest tests/test_key_sender.py
|
|||
|
|
|
|||
|
|
# 运行特定测试函数
|
|||
|
|
pytest tests/test_key_sender.py::test_post_message
|
|||
|
|
|
|||
|
|
# 生成覆盖率报告
|
|||
|
|
pytest --cov=src --cov-report=html
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🐛 调试技巧
|
|||
|
|
|
|||
|
|
### 日志记录
|
|||
|
|
|
|||
|
|
使用 Python 标准库 `logging`:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import logging
|
|||
|
|
|
|||
|
|
# 配置日志
|
|||
|
|
logging.basicConfig(
|
|||
|
|
level=logging.DEBUG,
|
|||
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|||
|
|
handlers=[
|
|||
|
|
logging.FileHandler('keypress.log'),
|
|||
|
|
logging.StreamHandler()
|
|||
|
|
]
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
logger = logging.getLogger(__name__)
|
|||
|
|
|
|||
|
|
def send_key(self, key_code: int) -> bool:
|
|||
|
|
logger.debug(f"发送按键: {key_code}")
|
|||
|
|
try:
|
|||
|
|
# 发送逻辑
|
|||
|
|
result = self._do_send_key(key_code)
|
|||
|
|
logger.info(f"按键发送成功: {key_code}")
|
|||
|
|
return result
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"按键发送失败: {key_code}, 错误: {e}")
|
|||
|
|
return False
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 调试 Windows API
|
|||
|
|
|
|||
|
|
1. **检查窗口句柄**
|
|||
|
|
```python
|
|||
|
|
import win32gui
|
|||
|
|
|
|||
|
|
def debug_window_info(hwnd):
|
|||
|
|
if win32gui.IsWindow(hwnd):
|
|||
|
|
title = win32gui.GetWindowText(hwnd)
|
|||
|
|
class_name = win32gui.GetClassName(hwnd)
|
|||
|
|
print(f"窗口: {title}, 类名: {class_name}")
|
|||
|
|
else:
|
|||
|
|
print("无效的窗口句柄")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. **监控消息发送**
|
|||
|
|
```python
|
|||
|
|
def send_key_with_debug(self, key_code: int):
|
|||
|
|
print(f"发送按键: {hex(key_code)} 到窗口: {hex(self.target_hwnd)}")
|
|||
|
|
result = win32gui.PostMessage(self.target_hwnd, WM_KEYDOWN, key_code, 0)
|
|||
|
|
print(f"PostMessage 返回值: {result}")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 性能分析
|
|||
|
|
|
|||
|
|
使用 `cProfile` 进行性能分析:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
python -m cProfile -o profile.stats src/main.py
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
查看结果:
|
|||
|
|
```python
|
|||
|
|
import pstats
|
|||
|
|
p = pstats.Stats('profile.stats')
|
|||
|
|
p.sort_stats('cumulative').print_stats(10)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## ⚡ 性能优化
|
|||
|
|
|
|||
|
|
### 按键发送优化
|
|||
|
|
|
|||
|
|
1. **批量发送**
|
|||
|
|
```python
|
|||
|
|
def send_keys_batch(self, keys: List[int], mode: str = "post"):
|
|||
|
|
"""批量发送按键,减少 API 调用次数"""
|
|||
|
|
for key_code in keys:
|
|||
|
|
self.send_key(key_code, mode)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. **缓存窗口信息**
|
|||
|
|
```python
|
|||
|
|
class KeySender:
|
|||
|
|
def __init__(self):
|
|||
|
|
self._window_cache = {}
|
|||
|
|
|
|||
|
|
def get_window_info(self, hwnd):
|
|||
|
|
if hwnd not in self._window_cache:
|
|||
|
|
self._window_cache[hwnd] = {
|
|||
|
|
'title': win32gui.GetWindowText(hwnd),
|
|||
|
|
'class': win32gui.GetClassName(hwnd)
|
|||
|
|
}
|
|||
|
|
return self._window_cache[hwnd]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 界面响应优化
|
|||
|
|
|
|||
|
|
1. **异步操作**
|
|||
|
|
```python
|
|||
|
|
from PyQt5.QtCore import QThread, pyqtSignal
|
|||
|
|
|
|||
|
|
class KeySendThread(QThread):
|
|||
|
|
finished = pyqtSignal()
|
|||
|
|
|
|||
|
|
def __init__(self, key_sender, key_code):
|
|||
|
|
super().__init__()
|
|||
|
|
self.key_sender = key_sender
|
|||
|
|
self.key_code = key_code
|
|||
|
|
|
|||
|
|
def run(self):
|
|||
|
|
self.key_sender.send_key(self.key_code)
|
|||
|
|
self.finished.emit()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. **减少重绘**
|
|||
|
|
```python
|
|||
|
|
# 批量更新界面
|
|||
|
|
self.setUpdatesEnabled(False)
|
|||
|
|
# 进行多个界面操作...
|
|||
|
|
self.setUpdatesEnabled(True)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## ❓ 常见问题
|
|||
|
|
|
|||
|
|
### Q1: 按键发送不生效
|
|||
|
|
|
|||
|
|
**可能原因**:
|
|||
|
|
1. 目标应用需要管理员权限
|
|||
|
|
2. 应用使用了低级键盘钩子
|
|||
|
|
3. 窗口句柄已失效
|
|||
|
|
|
|||
|
|
**解决方案**:
|
|||
|
|
```python
|
|||
|
|
# 检查窗口是否有效
|
|||
|
|
if not win32gui.IsWindow(self.target_hwnd):
|
|||
|
|
print("窗口句柄已失效,需要重新选择")
|
|||
|
|
|
|||
|
|
# 尝试不同的发送模式
|
|||
|
|
for mode in ["post", "send_message", "send"]:
|
|||
|
|
if self.send_key(key_code, mode):
|
|||
|
|
print(f"模式 {mode} 发送成功")
|
|||
|
|
break
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Q2: 界面冻结
|
|||
|
|
|
|||
|
|
**原因**: 在主线程执行耗时操作
|
|||
|
|
|
|||
|
|
**解决方案**:
|
|||
|
|
```python
|
|||
|
|
# 使用 QTimer 分解操作
|
|||
|
|
def process_large_task(self):
|
|||
|
|
self.timer = QTimer()
|
|||
|
|
self.timer.timeout.connect(self.process_chunk)
|
|||
|
|
self.timer.start(10) # 每10ms处理一小块
|
|||
|
|
|
|||
|
|
def process_chunk(self):
|
|||
|
|
# 处理一小部分数据
|
|||
|
|
if self.is_finished():
|
|||
|
|
self.timer.stop()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Q3: 内存泄漏
|
|||
|
|
|
|||
|
|
**检查点**:
|
|||
|
|
1. Qt 对象是否正确释放
|
|||
|
|
2. 定时器是否正确停止
|
|||
|
|
3. 事件监听器是否移除
|
|||
|
|
|
|||
|
|
**解决方案**:
|
|||
|
|
```python
|
|||
|
|
def cleanup(self):
|
|||
|
|
# 停止所有定时器
|
|||
|
|
for timer in self.timers:
|
|||
|
|
timer.stop()
|
|||
|
|
|
|||
|
|
# 断开信号连接
|
|||
|
|
self.some_signal.disconnect()
|
|||
|
|
|
|||
|
|
# 释放资源
|
|||
|
|
if hasattr(self, 'key_sender'):
|
|||
|
|
del self.key_sender
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Q4: 依赖安装问题
|
|||
|
|
|
|||
|
|
**pyqt5-qt5 版本问题**:
|
|||
|
|
```bash
|
|||
|
|
# 强制安装特定版本
|
|||
|
|
uv pip install PyQt5==5.15.11 pyqt5-qt5==5.15.2 --force-reinstall
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**权限问题**:
|
|||
|
|
```bash
|
|||
|
|
# 以管理员身份运行命令提示符
|
|||
|
|
# 或者使用 --user 参数
|
|||
|
|
pip install --user PyQt5
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📞 获取帮助
|
|||
|
|
|
|||
|
|
- **GitHub Issues**: 报告 bug 和功能请求
|
|||
|
|
- **讨论区**: 技术讨论和经验分享
|
|||
|
|
- **Wiki**: 更多技术文档和示例
|
|||
|
|
|
|||
|
|
欢迎贡献代码和文档!🎉
|