上传文件至 /

This commit is contained in:
2025-10-26 12:17:46 +00:00
parent d5d50f6eec
commit 4ed840aa40
3 changed files with 3699 additions and 0 deletions

957
Setup.py Normal file
View File

@@ -0,0 +1,957 @@
import os
import sys
import time
import requests
import shutil
from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QLineEdit,
QPushButton, QVBoxLayout, QHBoxLayout, QWidget,
QFileDialog, QProgressBar, QMessageBox, QFrame,
QCheckBox, QStackedWidget, QTextEdit, QScrollArea)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer, QUrl
from PyQt5.QtGui import (QFont, QIcon, QPalette, QColor, QPixmap,
QImageReader, QImage, QIcon as QtGuiQIcon)
# --------------------------- 全局样式配置 ---------------------------
GLOBAL_STYLE = """
/* 主按钮样式:科技蓝底色+圆角 */
QPushButton#PrimaryBtn {
background-color: #1E88E5;
color: white;
border: none;
border-radius: 4px;
padding: 8px 16px;
font-family: "微软雅黑", "Segoe UI";
font-size: 10pt;
}
QPushButton#PrimaryBtn:hover {
background-color: #1976D2;
}
QPushButton#PrimaryBtn:disabled {
background-color: #BBDEFB;
}
/* 次要按钮样式:灰色边框+透明底色 */
QPushButton#SecondaryBtn {
background-color: transparent;
color: #333333;
border: 1px solid #CCCCCC;
border-radius: 4px;
padding: 8px 16px;
font-family: "微软雅黑", "Segoe UI";
font-size: 10pt;
}
QPushButton#SecondaryBtn:hover {
background-color: #F5F5F5;
}
/* 进度条样式:蓝色进度+圆角 */
QProgressBar {
border: none;
border-radius: 4px;
background-color: #F5F5F5;
height: 8px;
font-family: "微软雅黑";
font-size: 8pt;
}
QProgressBar::chunk {
background-color: #1E88E5;
border-radius: 4px;
}
/* 输入框样式:细边框+聚焦高亮 */
QLineEdit {
border: 1px solid #CCCCCC;
border-radius: 4px;
padding: 6px 10px;
font-family: "微软雅黑";
font-size: 9pt;
}
QLineEdit:focus {
border-color: #1E88E5;
}
/* 面板样式:圆角+浅灰底色 */
QFrame#CardFrame {
background-color: white;
border-radius: 6px;
border: 1px solid #EEEEEE;
}
/* 标题样式 */
QLabel#TitleLabel {
font-family: "微软雅黑", "Segoe UI";
font-size: 14pt;
font-weight: bold;
color: #212121;
}
/* 副标题样式 */
QLabel#SubtitleLabel, QTextEdit {
font-family: "微软雅黑", "Segoe UI";
font-size: 10pt;
color: #666666;
}
/* 滚动区域样式 */
QScrollArea {
border: none;
}
"""
# --------------------------- 下载线程 ---------------------------
class DownloadThread(QThread):
progress_updated = pyqtSignal(int, str, float) # (进度, 文件名, 下载速度MB/s)
download_finished = pyqtSignal(bool, str, str) # (成功, 消息, 文件名)
def __init__(self, url, save_path, file_name, max_retries=2):
super().__init__()
self.url = url
self.save_path = save_path
self.file_name = file_name
self.max_retries = max_retries # 最大重试次数
self.start_time = None
def run(self):
retry_count = 0
while retry_count <= self.max_retries:
try:
# 确保保存目录存在(固定目录)
save_dir = os.path.dirname(self.save_path)
if not os.path.exists(save_dir):
os.makedirs(save_dir)
# 发送请求(增加超时设置)
self.start_time = time.time()
response = requests.get(
self.url,
stream=True,
timeout=15,
headers={"User-Agent": "EasyUI-Installer/1.0"}
)
response.raise_for_status() # 触发HTTP错误
total_size = int(response.headers.get('content-length', 0))
# 下载文件
with open(self.save_path, 'wb') as file:
downloaded_size = 0
for data in response.iter_content(chunk_size=8192): # 增大缓冲区,提升速度
if data:
file.write(data)
downloaded_size += len(data)
# 计算进度和下载速度
if total_size > 0:
progress = int((downloaded_size / total_size) * 100)
elapsed = time.time() - self.start_time
speed = downloaded_size / (1024 * 1024 * elapsed) if elapsed > 0 else 0
self.progress_updated.emit(progress, self.file_name, round(speed, 2))
# 验证文件完整性
if os.path.exists(self.save_path) and os.path.getsize(self.save_path) > 0:
self.download_finished.emit(True, self.save_path, self.file_name)
else:
raise Exception("文件下载不完整或为空")
break # 成功则退出重试循环
except Exception as e:
retry_count += 1
if retry_count > self.max_retries:
self.download_finished.emit(False, f"重试{self.max_retries}次后失败:{str(e)}", self.file_name)
else:
time.sleep(2) # 重试间隔2秒
# --------------------------- 图标下载线程 ---------------------------
class IconDownloadThread(QThread):
download_finished = pyqtSignal(bool, QIcon, bytes) # (成功, 图标对象, 原始数据)
def __init__(self, icon_url):
super().__init__()
self.icon_url = icon_url
def run(self):
try:
# 下载图标
response = requests.get(
self.icon_url,
timeout=10,
headers={"User-Agent": "EasyUI-Installer/1.0"}
)
response.raise_for_status()
# 将下载的内容转换为QIcon
image = QImage()
if image.loadFromData(response.content):
icon = QIcon(QPixmap.fromImage(image))
self.download_finished.emit(True, icon, response.content)
else:
self.download_finished.emit(False, QIcon(), b'')
except Exception as e:
print(f"图标下载失败: {str(e)}")
self.download_finished.emit(False, QIcon(), b'')
# --------------------------- 多文件下载管理器 ---------------------------
class MultiFileDownloader(QThread):
overall_progress = pyqtSignal(int) # 总体进度
file_progress = pyqtSignal(int, str, float) # (单个进度, 文件名, 速度)
file_finished = pyqtSignal(bool, str, str) # 单个文件完成
all_finished = pyqtSignal() # 所有文件完成
time_remaining = pyqtSignal(str) # 剩余时间格式mm:ss
def __init__(self, downloads):
super().__init__()
self.downloads = downloads # [(url, save_path, file_name), ...]
self.total_files = len(downloads)
self.completed_files = 0
self.current_file_index = 0
self.current_thread = None
self.stopped = False
self.current_speed = 0 # 当前下载速度MB/s
self.remaining_size = 0 # 剩余文件总大小MB
def run(self):
# 预计算总大小(用于剩余时间估算)
self.calc_total_remaining_size()
for i, (url, save_path, file_name) in enumerate(self.downloads):
if self.stopped:
break
self.current_file_index = i
self.file_progress.emit(0, file_name, 0.0)
# 创建当前文件下载线程
self.current_thread = DownloadThread(url, save_path, file_name)
self.current_thread.progress_updated.connect(self.on_file_progress)
self.current_thread.download_finished.connect(self.on_file_complete)
self.current_thread.start()
self.current_thread.wait()
self.all_finished.emit()
def calc_total_remaining_size(self):
"""计算剩余文件总大小MB"""
self.remaining_size = 0
for url, save_path, file_name in self.downloads:
try:
response = requests.head(url, timeout=10)
file_size = int(response.headers.get('content-length', 0)) / (1024 * 1024)
self.remaining_size += file_size
except:
self.remaining_size += 10 # 估算未知大小文件为10MB
def on_file_progress(self, progress, file_name, speed):
self.current_speed = speed
self.file_progress.emit(progress, file_name, speed)
# 计算总体进度
completed_size_ratio = (self.completed_files + progress/100) / self.total_files
overall = int(completed_size_ratio * 100)
self.overall_progress.emit(overall)
# 估算剩余时间(仅当速度>0时
if speed > 0.01:
# 计算当前文件剩余大小 + 未开始文件大小
current_file_remaining = (1 - progress/100) * (self.remaining_size / self.total_files)
unstarted_files_size = (self.total_files - self.current_file_index - 1) * (self.remaining_size / self.total_files)
total_remaining = current_file_remaining + unstarted_files_size
remaining_seconds = int(total_remaining / speed)
minutes = remaining_seconds // 60
seconds = remaining_seconds % 60
self.time_remaining.emit(f"{minutes:02d}:{seconds:02d}")
else:
self.time_remaining.emit("--:--")
def on_file_complete(self, success, message, file_name):
self.file_finished.emit(success, message, file_name)
if success:
self.completed_files += 1
# 更新剩余大小
self.remaining_size -= (self.remaining_size / self.total_files)
def stop(self):
self.stopped = True
if self.current_thread and self.current_thread.isRunning():
self.current_thread.terminate()
# --------------------------- 主安装窗口 ---------------------------
class EasyUIInstaller(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Easy UI 安装程序")
self.setGeometry(300, 200, 700, 500)
self.setMinimumSize(700, 500)
self.setStyleSheet(GLOBAL_STYLE)
# 图标配置 - 使用网络图标
self.icon_url = "https://sunshinetown.oss-cn-shenzhen.aliyuncs.com/eui.ico" # 网络图标路径
self.app_icon = QIcon() # 初始化空图标
self.icon_data = b'' # 存储图标原始数据,用于创建快捷方式
self.download_icon() # 启动图标下载
# 核心数据初始化 - 固定下载路径C:\用户\自动识别用户名\JGZ_YES\EasyUILang\
# 获取当前用户名
self.user_name = os.path.expanduser("~").split(os.sep)[-1]
# 构建固定路径
self.fixed_install_dir = os.path.join("C:", "用户", self.user_name, "JGZ_YES", "EasyUILang")
self.resources = {
"interpreter": {
"name": "Easy UI 解释器",
"url": "https://sunshinetown.oss-cn-shenzhen.aliyuncs.com/easy_ui_interpreter.exe",
"default_path": os.path.join(self.fixed_install_dir, "easy_ui_interpreter.exe")
},
"editor": {
"name": "Easy UI 编辑器",
"url": "https://sunshinetown.oss-cn-shenzhen.aliyuncs.com/EasyUI_Editor.exe",
"default_path": os.path.join(self.fixed_install_dir, "EasyUI_Editor.exe")
}
}
self.download_queue = []
self.download_manager = None
# 初始化分步容器
self.init_stacked_widget()
def download_icon(self):
"""下载网络图标"""
print(f"正在下载图标: {self.icon_url}")
self.icon_thread = IconDownloadThread(self.icon_url)
self.icon_thread.download_finished.connect(self.on_icon_downloaded)
self.icon_thread.start()
def on_icon_downloaded(self, success, icon, data):
"""图标下载完成回调"""
if success and not icon.isNull():
print("图标下载成功")
self.app_icon = icon
self.icon_data = data # 保存图标原始数据
self.setWindowIcon(self.app_icon)
# 更新界面上的图标
if hasattr(self, 'logo_label'):
pixmap = self.app_icon.pixmap(
self.logo_label.width(),
self.logo_label.height(),
mode=QtGuiQIcon.Normal,
state=QtGuiQIcon.On
)
self.logo_label.setPixmap(pixmap)
if hasattr(self, 'complete_icon'):
pixmap = self.app_icon.pixmap(
self.complete_icon.width(),
self.complete_icon.height(),
mode=QtGuiQIcon.Normal,
state=QtGuiQIcon.On
)
self.complete_icon.setPixmap(pixmap)
else:
print("图标下载失败,使用默认图标")
self.app_icon = QIcon.fromTheme("application-x-executable")
self.setWindowIcon(self.app_icon)
def init_stacked_widget(self):
"""创建分步容器"""
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.main_layout = QVBoxLayout(self.central_widget)
self.main_layout.setContentsMargins(20, 20, 20, 20)
self.main_layout.setSpacing(20)
# 1. 顶部Logo区域
self.logo_layout = QHBoxLayout()
self.logo_label = QLabel()
self.logo_label.setFixedSize(40, 40) # 固定Logo大小
# 初始显示默认图标,下载完成后会更新
if self.app_icon.isNull():
palette = QPalette()
palette.setColor(QPalette.Background, QColor("#1E88E5"))
self.logo_label.setAutoFillBackground(True)
self.logo_label.setPalette(palette)
else:
pixmap = self.app_icon.pixmap(
self.logo_label.width(),
self.logo_label.height()
)
self.logo_label.setPixmap(pixmap)
self.app_name_label = QLabel("Easy UI")
self.app_name_label.setStyleSheet("font-size: 16pt; font-weight: bold; color: #1E88E5;")
self.logo_layout.addWidget(self.logo_label)
self.logo_layout.addWidget(self.app_name_label)
self.logo_layout.addStretch()
self.main_layout.addLayout(self.logo_layout)
# 2. 分步内容容器
self.stacked_widget = QStackedWidget()
self.main_layout.addWidget(self.stacked_widget, 1) # 占主要空间
# 3. 底部按钮区域
self.btn_layout = QHBoxLayout()
self.btn_layout.setSpacing(10)
self.main_layout.addLayout(self.btn_layout)
# 初始化各页面
self.init_welcome_page()
self.init_license_page()
self.init_component_page()
self.init_progress_page()
self.init_complete_page()
# 默认显示欢迎页
self.stacked_widget.setCurrentIndex(0)
self.update_buttons_by_page(0)
# --------------------------- 页面1欢迎页 ---------------------------
def init_welcome_page(self):
self.welcome_page = QWidget()
self.welcome_layout = QVBoxLayout(self.welcome_page)
self.welcome_layout.setAlignment(Qt.AlignCenter)
self.welcome_layout.setSpacing(30)
# 标题和副标题
self.welcome_title = QLabel("欢迎使用 Easy UI 安装程序")
self.welcome_title.setObjectName("TitleLabel")
self.welcome_title.setAlignment(Qt.AlignCenter)
self.welcome_subtitle = QLabel("本程序将引导您完成 Easy UI 的安装预计耗时2-5分钟")
self.welcome_subtitle.setObjectName("SubtitleLabel")
self.welcome_subtitle.setAlignment(Qt.AlignCenter)
# 系统要求提示
self.system_require_label = QLabel(f"""
系统要求:
• Windows 10 及以上版本64位
• 至少 100MB 可用磁盘空间
• 稳定的网络连接(用于下载组件)
安装路径:
C:\\用户\\{self.user_name}\\JGZ_YES\\EasyUILang\\
""")
self.system_require_label.setObjectName("SubtitleLabel")
self.system_require_label.setAlignment(Qt.AlignLeft)
self.welcome_layout.addWidget(self.welcome_title)
self.welcome_layout.addWidget(self.welcome_subtitle)
self.welcome_layout.addWidget(self.system_require_label)
self.stacked_widget.addWidget(self.welcome_page)
# --------------------------- 页面2许可协议可滚动版本 ---------------------------
def init_license_page(self):
self.license_page = QWidget()
self.license_layout = QVBoxLayout(self.license_page)
self.license_layout.setSpacing(20)
# 标题
self.license_title = QLabel("软件许可协议")
self.license_title.setObjectName("TitleLabel")
self.license_layout.addWidget(self.license_title)
# 协议内容面板(带滚动功能)
self.license_frame = QFrame()
self.license_frame.setObjectName("CardFrame")
self.license_frame.setFixedHeight(250)
self.license_inner_layout = QVBoxLayout(self.license_frame)
# 添加滚动区域
self.license_scroll = QScrollArea()
self.license_scroll.setWidgetResizable(True)
self.license_scroll_content = QWidget()
self.license_scroll_layout = QVBoxLayout(self.license_scroll_content)
# 协议文本
self.license_text = QTextEdit()
self.license_text.setReadOnly(True)
self.license_text.setText("""
Easy UI 软件许可协议
1. 许可范围
本软件仅供个人非商业使用,禁止未经授权用于商业用途、盈利活动或非法行为。
2. 用户权利
• 免费使用软件的全部功能及更新服务
• 在许可范围内修改个人使用的软件副本
• 获得软件相关的技术支持(限非商业用途)
3. 用户义务
• 不得传播经过篡改、破解或植入恶意代码的软件版本
• 不得利用软件侵犯他人知识产权、隐私或其他合法权益
• 不得违反国家法律法规及相关政策使用软件
4. 免责声明
• 软件按“现状”提供,开发者不保证无故障运行或完全满足用户需求
• 开发者不对软件使用过程中产生的任何直接或间接损失承担责任
• 因用户违反本协议导致的法律风险,由用户自行承担
5. 协议生效与终止
• 用户点击“同意”即表示完全接受本协议所有条款
• 若用户违反本协议,开发者有权终止其使用许可
""")
self.license_scroll_layout.addWidget(self.license_text)
self.license_scroll.setWidget(self.license_scroll_content)
self.license_inner_layout.addWidget(self.license_scroll)
self.license_layout.addWidget(self.license_frame)
# 同意勾选框
self.agree_check = QCheckBox("我已阅读并同意上述许可协议")
self.agree_check.setObjectName("SubtitleLabel")
self.agree_check.setChecked(False)
self.license_layout.addWidget(self.agree_check)
self.license_layout.addStretch()
self.stacked_widget.addWidget(self.license_page)
# --------------------------- 页面3组件选择 ---------------------------
def init_component_page(self):
self.component_page = QWidget()
self.component_layout = QVBoxLayout(self.component_page)
self.component_layout.setSpacing(20)
# 标题和副标题
self.component_title = QLabel("选择安装组件")
self.component_title.setObjectName("TitleLabel")
self.component_layout.addWidget(self.component_title)
self.component_subtitle = QLabel("默认安装所有组件,您可根据需求取消不需要的选项")
self.component_subtitle.setObjectName("SubtitleLabel")
self.component_layout.addWidget(self.component_subtitle)
# 组件选择面板
self.component_frame = QFrame()
self.component_frame.setObjectName("CardFrame")
self.component_inner_layout = QVBoxLayout(self.component_frame)
self.component_inner_layout.setContentsMargins(20, 20, 20, 20)
self.component_inner_layout.setSpacing(25)
# 解释器组件
self.interpreter_group = QWidget()
self.interpreter_group_layout = QVBoxLayout(self.interpreter_group)
self.interpreter_group_layout.setSpacing(10)
self.interpreter_check = QCheckBox(f"{self.resources['interpreter']['name']} (必需组件)")
self.interpreter_check.setObjectName("SubtitleLabel")
self.interpreter_check.setChecked(True)
self.interpreter_check.setEnabled(False)
self.interpreter_group_layout.addWidget(self.interpreter_check)
# 解释器路径选择(固定路径,不可修改)
self.interpreter_path_layout = QHBoxLayout()
self.interpreter_path_label = QLabel("安装位置:")
self.interpreter_path_label.setObjectName("SubtitleLabel")
self.interpreter_path = QLineEdit(self.resources['interpreter']['default_path'])
self.interpreter_path.setReadOnly(True) # 设置为只读,防止修改
self.interpreter_browse_btn = QPushButton("浏览...")
self.interpreter_browse_btn.setObjectName("SecondaryBtn")
self.interpreter_browse_btn.clicked.connect(lambda: self.show_path_info())
self.interpreter_path_layout.addWidget(self.interpreter_path_label)
self.interpreter_path_layout.addWidget(self.interpreter_path, 1)
self.interpreter_path_layout.addWidget(self.interpreter_browse_btn)
self.interpreter_group_layout.addLayout(self.interpreter_path_layout)
self.component_inner_layout.addWidget(self.interpreter_group)
# 编辑器组件
self.editor_group = QWidget()
self.editor_group_layout = QVBoxLayout(self.editor_group)
self.editor_group_layout.setSpacing(10)
self.editor_check = QCheckBox(f"{self.resources['editor']['name']} (推荐组件)")
self.editor_check.setObjectName("SubtitleLabel")
self.editor_check.setChecked(True)
self.editor_group_layout.addWidget(self.editor_check)
# 编辑器路径选择(固定路径,不可修改)
self.editor_path_layout = QHBoxLayout()
self.editor_path_label = QLabel("安装位置:")
self.editor_path_label.setObjectName("SubtitleLabel")
self.editor_path = QLineEdit(self.resources['editor']['default_path'])
self.editor_path.setReadOnly(True) # 设置为只读,防止修改
self.editor_browse_btn = QPushButton("浏览...")
self.editor_browse_btn.setObjectName("SecondaryBtn")
self.editor_browse_btn.clicked.connect(lambda: self.show_path_info())
self.editor_path_layout.addWidget(self.editor_path_label)
self.editor_path_layout.addWidget(self.editor_path, 1)
self.editor_path_layout.addWidget(self.editor_browse_btn)
self.editor_group_layout.addLayout(self.editor_path_layout)
self.component_inner_layout.addWidget(self.editor_group)
self.component_layout.addWidget(self.component_frame)
self.stacked_widget.addWidget(self.component_page)
def show_path_info(self):
"""显示路径信息提示,告知用户路径不可修改"""
QMessageBox.information(
self, "安装路径说明",
f"软件将固定安装到以下路径:\nC:\\用户\\{self.user_name}\\JGZ_YES\\EasyUILang\\"
)
# --------------------------- 页面4安装进度 ---------------------------
def init_progress_page(self):
self.progress_page = QWidget()
self.progress_layout = QVBoxLayout(self.progress_page)
self.progress_layout.setSpacing(20)
# 标题
self.progress_title = QLabel("正在安装")
self.progress_title.setObjectName("TitleLabel")
self.progress_layout.addWidget(self.progress_title)
# 进度面板
self.progress_frame = QFrame()
self.progress_frame.setObjectName("CardFrame")
self.progress_inner_layout = QVBoxLayout(self.progress_frame)
self.progress_inner_layout.setContentsMargins(20, 20, 20, 20)
self.progress_inner_layout.setSpacing(15)
# 当前任务提示
self.current_task_label = QLabel("准备下载安装组件...")
self.current_task_label.setObjectName("SubtitleLabel")
self.progress_inner_layout.addWidget(self.current_task_label)
# 总体进度条
self.overall_progress_bar = QProgressBar()
self.overall_progress_bar.setValue(0)
self.progress_inner_layout.addWidget(self.overall_progress_bar)
# 进度详情
self.progress_detail_layout = QHBoxLayout()
self.speed_label = QLabel("速度0.00 MB/s")
self.speed_label.setObjectName("SubtitleLabel")
self.time_remaining_label = QLabel("剩余时间:--:--")
self.time_remaining_label.setObjectName("SubtitleLabel")
self.progress_detail_layout.addWidget(self.speed_label)
self.progress_detail_layout.addStretch()
self.progress_detail_layout.addWidget(self.time_remaining_label)
self.progress_inner_layout.addLayout(self.progress_detail_layout)
self.progress_layout.addWidget(self.progress_frame)
self.stacked_widget.addWidget(self.progress_page)
# --------------------------- 页面5安装完成 ---------------------------
def init_complete_page(self):
self.complete_page = QWidget()
self.complete_layout = QVBoxLayout(self.complete_page)
self.complete_layout.setSpacing(25)
self.complete_layout.setAlignment(Qt.AlignCenter)
# 完成图标
self.complete_icon = QLabel()
self.complete_icon.setFixedSize(80, 80)
# 初始显示默认图标
if self.app_icon.isNull():
palette = QPalette()
palette.setColor(QPalette.Background, QColor("#4CAF50"))
self.complete_icon.setAutoFillBackground(True)
self.complete_icon.setPalette(palette)
self.complete_icon.setStyleSheet("border-radius: 40px;")
else:
pixmap = self.app_icon.pixmap(
self.complete_icon.width(),
self.complete_icon.height()
)
self.complete_icon.setPixmap(pixmap)
self.complete_layout.addWidget(self.complete_icon, alignment=Qt.AlignCenter)
# 完成标题
self.complete_title = QLabel("安装完成!")
self.complete_title.setObjectName("TitleLabel")
self.complete_layout.addWidget(self.complete_title, alignment=Qt.AlignCenter)
# 完成提示
self.complete_subtitle = QLabel(f"Easy UI 已成功安装到:\nC:\\用户\\{self.user_name}\\JGZ_YES\\EasyUILang\\")
self.complete_subtitle.setObjectName("SubtitleLabel")
self.complete_subtitle.setAlignment(Qt.AlignCenter)
self.complete_layout.addWidget(self.complete_subtitle, alignment=Qt.AlignCenter)
# 后续操作选项
self.complete_options_layout = QVBoxLayout()
self.run_check = QCheckBox("立即运行 Easy UI 编辑器")
self.run_check.setObjectName("SubtitleLabel")
self.run_check.setChecked(True)
self.desktop_shortcut_check = QCheckBox("创建桌面快捷方式")
self.desktop_shortcut_check.setObjectName("SubtitleLabel")
self.desktop_shortcut_check.setChecked(True)
self.complete_options_layout.addWidget(self.run_check)
self.complete_options_layout.addWidget(self.desktop_shortcut_check)
self.complete_layout.addLayout(self.complete_options_layout)
self.stacked_widget.addWidget(self.complete_page)
# --------------------------- 核心交互逻辑 ---------------------------
def update_buttons_by_page(self, page_index):
"""根据当前页面更新底部按钮"""
# 清空现有按钮
for i in range(self.btn_layout.count()):
widget = self.btn_layout.itemAt(i).widget()
if widget:
widget.deleteLater()
# 根据页面索引添加按钮
if page_index == 0: # 欢迎页:仅“下一步”
self.next_btn = QPushButton("下一步")
self.next_btn.setObjectName("PrimaryBtn")
self.next_btn.clicked.connect(lambda: self.switch_page(1))
self.btn_layout.addStretch()
self.btn_layout.addWidget(self.next_btn)
elif page_index == 1: # 许可协议:“上一步”+“下一步”
self.prev_btn = QPushButton("上一步")
self.prev_btn.setObjectName("SecondaryBtn")
self.prev_btn.clicked.connect(lambda: self.switch_page(0))
self.next_btn = QPushButton("下一步")
self.next_btn.setObjectName("PrimaryBtn")
self.next_btn.setEnabled(False)
self.next_btn.clicked.connect(lambda: self.switch_page(2))
# 勾选框联动按钮状态
self.agree_check.stateChanged.connect(
lambda state: self.next_btn.setEnabled(state == Qt.Checked)
)
self.btn_layout.addWidget(self.prev_btn)
self.btn_layout.addStretch()
self.btn_layout.addWidget(self.next_btn)
elif page_index == 2: # 组件选择:“上一步”+“安装”
self.prev_btn = QPushButton("上一步")
self.prev_btn.setObjectName("SecondaryBtn")
self.prev_btn.clicked.connect(lambda: self.switch_page(1))
self.install_btn = QPushButton("开始安装")
self.install_btn.setObjectName("PrimaryBtn")
self.install_btn.clicked.connect(self.start_install)
self.btn_layout.addWidget(self.prev_btn)
self.btn_layout.addStretch()
self.btn_layout.addWidget(self.install_btn)
elif page_index == 3: # 安装进度:“取消”
self.cancel_btn = QPushButton("取消安装")
self.cancel_btn.setObjectName("SecondaryBtn")
self.cancel_btn.clicked.connect(self.cancel_install)
self.btn_layout.addStretch()
self.btn_layout.addWidget(self.cancel_btn)
elif page_index == 4: # 安装完成:“完成”
self.finish_btn = QPushButton("完成")
self.finish_btn.setObjectName("PrimaryBtn")
self.finish_btn.clicked.connect(self.finish_install)
self.btn_layout.addStretch()
self.btn_layout.addWidget(self.finish_btn)
def switch_page(self, target_index):
"""切换页面并更新按钮"""
self.stacked_widget.setCurrentIndex(target_index)
self.update_buttons_by_page(target_index)
def start_install(self):
"""验证组件并开始安装"""
# 构建下载队列(固定路径)
self.download_queue = []
if self.interpreter_check.isChecked():
inter_path = self.resources['interpreter']['default_path']
self.download_queue.append((
self.resources['interpreter']['url'],
inter_path,
self.resources['interpreter']['name']
))
if self.editor_check.isChecked():
edit_path = self.resources['editor']['default_path']
self.download_queue.append((
self.resources['editor']['url'],
edit_path,
self.resources['editor']['name']
))
# 验证路径权限
if not self.verify_path_permission():
return
# 跳转到进度页并启动下载
self.switch_page(3)
self.download_manager = MultiFileDownloader(self.download_queue)
self.download_manager.overall_progress.connect(self.overall_progress_bar.setValue)
self.download_manager.file_progress.connect(self.update_progress_detail)
self.download_manager.time_remaining.connect(self.time_remaining_label.setText)
self.download_manager.file_finished.connect(self.on_file_finished)
self.download_manager.all_finished.connect(self.on_all_finished)
self.download_manager.start()
def verify_path_permission(self):
"""验证安装路径是否有写入权限"""
for url, path, name in self.download_queue:
try:
test_dir = os.path.dirname(path)
if not os.path.exists(test_dir):
os.makedirs(test_dir)
# 测试写入
test_file = os.path.join(test_dir, "test_permission.tmp")
with open(test_file, 'w') as f:
f.write("test")
os.remove(test_file)
except Exception as e:
QMessageBox.warning(
self, "路径权限错误",
f"无法写入{name}的安装路径:\n{str(e)}\n请检查路径权限或以管理员身份运行安装程序。"
)
return False
return True
def update_progress_detail(self, progress, file_name, speed):
"""更新进度详情"""
self.current_task_label.setText(f"正在安装 {file_name}... ({progress}%)")
self.speed_label.setText(f"速度:{speed:.2f} MB/s")
def on_file_finished(self, success, message, file_name):
"""单个文件安装完成回调"""
if not success:
QMessageBox.warning(self, f"{file_name}安装失败", message)
def on_all_finished(self):
"""所有文件安装完成"""
# 创建桌面快捷方式(如果勾选)
if self.desktop_shortcut_check.isChecked():
self.create_desktop_shortcut()
self.switch_page(4)
def create_desktop_shortcut(self):
"""创建桌面快捷方式"""
editor_path = self.resources['editor']['default_path']
if not os.path.exists(editor_path):
QMessageBox.warning(self, "创建失败", "未找到Easy UI编辑器可执行文件无法创建快捷方式")
return
# 尝试创建快捷方式
try:
import pythoncom
import win32com.client
from winshell import desktop
# 初始化COM
pythoncom.CoInitialize()
# 获取桌面路径
desktop_path = desktop()
shortcut_path = os.path.join(desktop_path, "Easy UI 编辑器.lnk")
# 保存图标到本地临时文件
icon_temp_path = os.path.join(self.fixed_install_dir, "eui_icon.ico")
if self.icon_data:
with open(icon_temp_path, 'wb') as f:
f.write(self.icon_data)
# 创建快捷方式
shell = win32com.client.Dispatch("WScript.Shell")
shortcut = shell.CreateShortCut(shortcut_path)
shortcut.TargetPath = editor_path
shortcut.WorkingDirectory = self.fixed_install_dir
shortcut.Description = "Easy UI 编辑器"
shortcut.Hotkey = "Ctrl+Alt+E"
# 设置图标
if os.path.exists(icon_temp_path):
shortcut.IconLocation = icon_temp_path
else:
shortcut.IconLocation = editor_path
shortcut.save()
pythoncom.CoUninitialize()
return
except ImportError:
# 备用方案使用PowerShell命令
try:
import subprocess
# 获取桌面路径
desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
shortcut_path = os.path.join(desktop_path, "Easy UI 编辑器.lnk")
# 保存图标
icon_temp_path = os.path.join(self.fixed_install_dir, "eui_icon.ico")
if self.icon_data:
with open(icon_temp_path, 'wb') as f:
f.write(self.icon_data)
# PowerShell命令
ps_command = f'''
$WshShell = New-Object -ComObject WScript.Shell
$shortcut = $WshShell.CreateShortcut("{shortcut_path}")
$shortcut.TargetPath = "{editor_path}"
$shortcut.WorkingDirectory = "{self.fixed_install_dir}"
$shortcut.Description = "Easy UI 编辑器"
{f'$shortcut.IconLocation = "{icon_temp_path}"' if os.path.exists(icon_temp_path) else ''}
$shortcut.Save()
'''
subprocess.run(
["powershell", "-Command", ps_command],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
return
except Exception as e:
print(f"创建快捷方式失败:{str(e)}")
QMessageBox.warning(
self, "创建失败",
"无法自动创建桌面快捷方式,您可以手动创建:\n"
f"1. 找到文件:{editor_path}\n"
"2. 右键点击文件,选择'发送到'->'桌面快捷方式'"
)
def cancel_install(self):
"""取消安装"""
if self.download_manager and self.download_manager.isRunning():
confirm = QMessageBox.question(
self, "确认取消",
"取消安装将终止当前下载,已下载的文件可能不完整,是否继续?",
QMessageBox.Yes | QMessageBox.No
)
if confirm == QMessageBox.Yes:
self.download_manager.stop()
self.switch_page(2) # 回到组件选择页
def finish_install(self):
"""完成安装"""
# 运行程序(如果勾选)
if self.run_check.isChecked():
editor_path = self.resources['editor']['default_path']
if os.path.exists(editor_path):
os.startfile(editor_path)
# 关闭安装程序
self.close()
# 程序入口
if __name__ == "__main__":
# 先设置高DPI属性
if hasattr(Qt, 'AA_EnableHighDpiScaling'):
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
if hasattr(Qt, 'AA_UseHighDpiPixmaps'):
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
# 确保中文显示正常
app = QApplication(sys.argv)
app.setFont(QFont("微软雅黑", 9))
window = EasyUIInstaller()
window.show()
sys.exit(app.exec_())