Files
TransPyC/ViperQtProject/App/main.vp
T
TermiNexus ab068df348 feat(ViperQtProject): 安装向导完善(许可协议/磁盘统计/安装日志/安装选项)
- 新增许可协议步骤:协议文本 + 勾选接受后才可继续
- 组件选择页:磁盘占用实时统计 + 安装选项(快捷方式/PATH/桌面图标)
- 安装进度页:阶段文案 + 实时日志输出(QPlainTextEdit)
- 安装按钮点击后禁用防重复,完成页汇总安装信息
2026-09-02 10:01:44 +08:00

504 lines
20 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# main.vp — ViperQtProject:VS Installer 风格安装向导演示
#
# 纯界面演示(无真实安装逻辑):
# 步骤0 欢迎 → 步骤1 许可协议 → 步骤2 组件选择 → 步骤3 安装进度 → 步骤4 完成
#
# 完善功能:
# - 许可协议页:协议文本 + "我接受" 勾选后才可继续
# - 组件选择:磁盘占用实时统计 + 安装选项(快捷方式/PATH/桌面图标)
# - 安装进度:进度条 + 阶段文案 + 实时日志(QPlainTextEdit)
# - 安装按钮点击后禁用,防重复
#
# 说明:
# - 回调签名统一 def Cb(slot_id: int, data: t.CPtr)
# - 跨模块 FFI 调用避免返回指针的函数(用 *_utf8 便捷版 + 整数返回函数)
# - 模块级全局只存 t.CPtr 指针(不存对象)
import t
import stdlib
import viperlib
import vqt6
# ============================================================
# 全局指针(回调里访问)
# ============================================================
app_ptr: t.CPtr = None
stack_ptr: t.CPtr = None
nav0_ptr: t.CPtr = None
nav1_ptr: t.CPtr = None
nav2_ptr: t.CPtr = None
nav3_ptr: t.CPtr = None
nav4_ptr: t.CPtr = None
progress_ptr: t.CPtr = None
progress_status_ptr: t.CPtr = None
log_ptr: t.CPtr = None
path_label_ptr: t.CPtr = None
summary_ptr: t.CPtr = None
disk_ptr: t.CPtr = None
finish_summary_ptr: t.CPtr = None
timer_ptr: t.CPtr = None
comp0_ptr: t.CPtr = None
comp1_ptr: t.CPtr = None
comp2_ptr: t.CPtr = None
comp3_ptr: t.CPtr = None
comp4_ptr: t.CPtr = None
agree_cb_ptr: t.CPtr = None
license_next_ptr: t.CPtr = None
install_btn_ptr: t.CPtr = None
opt0_ptr: t.CPtr = None
opt1_ptr: t.CPtr = None
opt2_ptr: t.CPtr = None
# 全局状态
cur_step: int = 0
progress_val: int = 0
comp_count: int = 0
install_path: str = "C:/Program Files/Viper6"
# ============================================================
# 深色主题 QSS(VS Installer 风格,单行字面量)
# ============================================================
dark_qss: str = "QWidget { background-color: #1e1e1e; color: #d4d4d4; font-size: 13px; } QPushButton { background-color: #333333; border: 1px solid #555555; border-radius: 4px; padding: 7px 16px; color: #ffffff; } QPushButton:hover { background-color: #454545; } QPushButton:pressed { background-color: #2a6da8; } QPushButton:disabled { background-color: #2a2a2a; color: #666666; border-color: #3a3a3a; } QLineEdit { background-color: #252526; border: 1px solid #555555; border-radius: 3px; padding: 5px 8px; color: #ffffff; } QCheckBox { color: #d4d4d4; spacing: 10px; font-size: 14px; } QCheckBox::indicator { width: 18px; height: 18px; } QProgressBar { background-color: #252526; border: 1px solid #555555; border-radius: 5px; text-align: center; color: #ffffff; font-size: 13px; } QProgressBar::chunk { background-color: #4a86c8; border-radius: 4px; } QPlainTextEdit { background-color: #1a1a1a; border: 1px solid #444444; color: #cccccc; }"
# 导航项样式
nav_active_qss: str = "font-size: 15px; color: #ffffff; font-weight: bold; background-color: #2a6da8; padding: 10px 14px; border-radius: 4px;"
nav_idle_qss: str = "font-size: 15px; color: #8a8a8a; padding: 10px 14px;"
# 许可协议文本
license_text: str = "VIPER 6.0 最终用户许可协议\n\n重要 - 请仔细阅读:本最终用户许可协议(以下简称\"协议\")是您与 Viper Software Foundation 之间就 Viper 6.0 软件达成的法律协议。\n\n1. 许可授予。在遵守本协议条款的前提下,您被授予一份非独占、不可转让的许可,允许在您的设备上安装和使用本软件用于开发与测试目的。\n\n2. 使用限制。您不得对本软件进行反向工程、反编译或反汇编(适用法律明确允许的除外);不得删除或修改软件中的版权声明。\n\n3. 开源组件。本软件包含根据 MIT、BSD 与 LGPL 许可发布的第三方开源组件,相关许可文本随附于软件文档中。\n\n4. 免责声明。本软件按\"现状\"提供,不附带任何明示或默示的担保,包括但不限于适销性、特定用途适用性及不侵权的担保。\n\n5. 责任限制。在任何情况下,Viper Software Foundation 均不对因使用或无法使用本软件而产生的任何间接、偶然、特殊或后果性损害承担责任。\n\n6. 协议终止。若您违反本协议的任何条款,本协议将自动终止,您应停止使用并删除本软件的全部副本。\n\n请阅读以上条款。要安装 Viper 6.0,必须接受这些条款并勾选下方的\"我接受许可协议中的条款\"。"
# ============================================================
# 工具:切换步骤 + 导航高亮
# ============================================================
def SetNavStyle(ptr: t.CPtr, qss: str):
vqt6.vqt6_qwidget_set_stylesheet_utf8(ptr, qss)
def GotoStep(step: int):
global cur_step
cur_step = step
vqt6.vqt6_qstackedwidget_set_current_index(stack_ptr, step)
# 导航高亮
SetNavStyle(nav0_ptr, nav_active_qss if step == 0 else nav_idle_qss)
SetNavStyle(nav1_ptr, nav_active_qss if step == 1 else nav_idle_qss)
SetNavStyle(nav2_ptr, nav_active_qss if step == 2 else nav_idle_qss)
SetNavStyle(nav3_ptr, nav_active_qss if step == 3 else nav_idle_qss)
SetNavStyle(nav4_ptr, nav_active_qss if step == 4 else nav_idle_qss)
# ============================================================
# 步骤0:欢迎页
# ============================================================
def OnNext(slot_id: int, data: t.CPtr):
GotoStep(1)
# ============================================================
# 步骤1:许可协议
# ============================================================
def OnLicenseToggled(slot_id: int, data: t.CPtr):
"""勾选"我接受"后启用下一步"""
checked: int = vqt6.vqt6_qcheckbox_is_checked(agree_cb_ptr)
vqt6.vqt6_qwidget_set_enabled(license_next_ptr, checked)
def OnLicenseNext(slot_id: int, data: t.CPtr):
GotoStep(2)
def OnLicenseBack(slot_id: int, data: t.CPtr):
GotoStep(0)
# ============================================================
# 步骤2:组件选择
# ============================================================
def OnBack(slot_id: int, data: t.CPtr):
GotoStep(1)
def OnBrowse(slot_id: int, data: t.CPtr):
global install_path
p: str = vqt6.QFileDialog.getExistingDirectory(None, "选择安装目录", "C:/")
if p is not None and p != "":
install_path = p
vqt6.vqt6_qlabel_set_text_utf8(path_label_ptr, p)
def UpdateDiskInfo():
"""按组件勾选统计磁盘占用"""
total_mb: int = 0
if vqt6.vqt6_qcheckbox_is_checked(comp0_ptr) != 0: total_mb += 850
if vqt6.vqt6_qcheckbox_is_checked(comp1_ptr) != 0: total_mb += 1200
if vqt6.vqt6_qcheckbox_is_checked(comp2_ptr) != 0: total_mb += 350
if vqt6.vqt6_qcheckbox_is_checked(comp3_ptr) != 0: total_mb += 220
if vqt6.vqt6_qcheckbox_is_checked(comp4_ptr) != 0: total_mb += 90
buf: bytes = stdlib.malloc(96)
viperlib.snprintf(buf, 96, "所需磁盘空间: %d MB / 可用 51200 MB", total_mb)
vqt6.vqt6_qlabel_set_text_utf8(disk_ptr, buf)
stdlib.free(buf)
def OnCompToggled(slot_id: int, data: t.CPtr):
"""组件勾选变化:重算勾选数 + 磁盘占用"""
global comp_count
n: int = 0
if vqt6.vqt6_qcheckbox_is_checked(comp0_ptr) != 0: n += 1
if vqt6.vqt6_qcheckbox_is_checked(comp1_ptr) != 0: n += 1
if vqt6.vqt6_qcheckbox_is_checked(comp2_ptr) != 0: n += 1
if vqt6.vqt6_qcheckbox_is_checked(comp3_ptr) != 0: n += 1
if vqt6.vqt6_qcheckbox_is_checked(comp4_ptr) != 0: n += 1
comp_count = n
buf: bytes = stdlib.malloc(64)
viperlib.snprintf(buf, 64, "已选择 %d 个组件", n)
vqt6.vqt6_qlabel_set_text_utf8(summary_ptr, buf)
stdlib.free(buf)
UpdateDiskInfo()
def OnInstall(slot_id: int, data: t.CPtr):
"""进入安装页并启动模拟进度"""
global progress_val
vqt6.vqt6_qwidget_set_enabled(install_btn_ptr, 0)
GotoStep(3)
progress_val = 0
vqt6.vqt6_qprogressbar_set_value(progress_ptr, 0)
vqt6.vqt6_qlabel_set_text_utf8(progress_status_ptr, "正在准备安装环境...")
vqt6.vqt6_qplaintextedit_append_utf8(log_ptr, "== 开始安装 Viper 6.0 ==\n")
vqt6.vqt6_qtimer_start(timer_ptr, 80)
# ============================================================
# 步骤3:安装进度(QTimer 模拟)
# ============================================================
def OnProgressTick(slot_id: int, data: t.CPtr):
global progress_val
progress_val += 4
vqt6.vqt6_qprogressbar_set_value(progress_ptr, progress_val)
if progress_val == 4:
vqt6.vqt6_qplaintextedit_append_utf8(log_ptr, "[1/4] 正在准备安装环境...\n")
elif progress_val == 28:
vqt6.vqt6_qlabel_set_text_utf8(progress_status_ptr, "正在复制核心文件...")
vqt6.vqt6_qplaintextedit_append_utf8(log_ptr, "[2/4] 正在复制核心文件...\n")
elif progress_val == 52:
vqt6.vqt6_qlabel_set_text_utf8(progress_status_ptr, "正在配置 Qt6 组件...")
vqt6.vqt6_qplaintextedit_append_utf8(log_ptr, "[3/4] 正在配置 Qt6 组件...\n")
elif progress_val == 76:
vqt6.vqt6_qlabel_set_text_utf8(progress_status_ptr, "正在注册系统组件...")
vqt6.vqt6_qplaintextedit_append_utf8(log_ptr, "[4/4] 正在注册系统组件...\n")
elif progress_val >= 100:
vqt6.vqt6_qtimer_stop(timer_ptr)
vqt6.vqt6_qplaintextedit_append_utf8(log_ptr, "安装完成!\n")
# 完成页摘要(含安装选项)
buf: bytes = stdlib.malloc(512)
viperlib.snprintf(buf, 512, "安装位置: %s\n已安装组件: %d 个\nViper 6.0 已成功安装到您的系统。", install_path, comp_count)
vqt6.vqt6_qlabel_set_text_utf8(finish_summary_ptr, buf)
stdlib.free(buf)
GotoStep(4)
# ============================================================
# 步骤4:完成页
# ============================================================
def OnFinish(slot_id: int, data: t.CPtr):
vqt6.vqt6_qapplication_quit(app_ptr)
def main() -> int:
global app_ptr, stack_ptr
global nav0_ptr, nav1_ptr, nav2_ptr, nav3_ptr, nav4_ptr
global progress_ptr, progress_status_ptr, log_ptr, path_label_ptr
global summary_ptr, disk_ptr, finish_summary_ptr, timer_ptr
global comp0_ptr, comp1_ptr, comp2_ptr, comp3_ptr, comp4_ptr
global agree_cb_ptr, license_next_ptr, install_btn_ptr
global opt0_ptr, opt1_ptr, opt2_ptr
app = vqt6.QApplication(t.CPtr(0))
app_ptr = t.CPtr(app._ptr)
# 主窗口
win = vqt6.QMainWindow()
win.setWindowTitle("Viper 6.0 Installer")
win.resize(920, 600)
central = vqt6.QWidget()
central.setStyleSheet(dark_qss)
win.setCentralWidget(central._ptr)
root = vqt6.QHBoxLayout(central)
# ===== 左导航面板 =====
nav_panel = vqt6.QWidget()
vqt6.vqt6_qwidget_set_fixed_size(nav_panel._ptr, 210, 600)
nav_box = vqt6.QVBoxLayout(nav_panel)
nav_box.setContentsMargins(10, 16, 10, 10)
nav_box.setSpacing(6)
title_lb = vqt6.QLabel("Viper 6.0")
vqt6.vqt6_qwidget_set_stylesheet_utf8(title_lb._ptr, "font-size: 20px; color: #4a86c8; font-weight: bold; padding: 6px 14px;")
nav_box.addWidget(title_lb._ptr)
nav0 = vqt6.QLabel("欢迎")
nav0.setStyleSheet(nav_active_qss)
nav_box.addWidget(nav0._ptr)
nav0_ptr = t.CPtr(nav0._ptr)
nav1 = vqt6.QLabel("许可协议")
nav1.setStyleSheet(nav_idle_qss)
nav_box.addWidget(nav1._ptr)
nav1_ptr = t.CPtr(nav1._ptr)
nav2 = vqt6.QLabel("组件选择")
nav2.setStyleSheet(nav_idle_qss)
nav_box.addWidget(nav2._ptr)
nav2_ptr = t.CPtr(nav2._ptr)
nav3 = vqt6.QLabel("安装进度")
nav3.setStyleSheet(nav_idle_qss)
nav_box.addWidget(nav3._ptr)
nav3_ptr = t.CPtr(nav3._ptr)
nav4 = vqt6.QLabel("完成")
nav4.setStyleSheet(nav_idle_qss)
nav_box.addWidget(nav4._ptr)
nav4_ptr = t.CPtr(nav4._ptr)
nav_box.addStretch(1)
ver_lb = vqt6.QLabel("v6.0.1 · 2026")
vqt6.vqt6_qwidget_set_stylesheet_utf8(ver_lb._ptr, "color: #666666; padding: 6px 14px;")
nav_box.addWidget(ver_lb._ptr)
root.addWidget(nav_panel._ptr)
# ===== 右侧步骤容器 =====
stack = vqt6.QStackedWidget()
root.addWidget(stack._ptr)
stack_ptr = t.CPtr(stack._ptr)
# ---------- 页0:欢迎 ----------
page0 = vqt6.QWidget()
p0 = vqt6.QVBoxLayout(page0)
p0.setContentsMargins(36, 40, 36, 24)
p0.setSpacing(12)
big = vqt6.QLabel("Viper 6.0")
vqt6.vqt6_qwidget_set_stylesheet_utf8(big._ptr, "font-size: 34px; color: #ffffff; font-weight: bold;")
p0.addWidget(big._ptr)
sub = vqt6.QLabel("为 Viper 语言平台安装工具链、Qt6 运行时与开发组件")
vqt6.vqt6_qwidget_set_stylesheet_utf8(sub._ptr, "font-size: 16px; color: #9cdcfe;")
p0.addWidget(sub._ptr)
desc = vqt6.QLabel("本向导将引导您完成 Viper 6.0 的安装。您可以自定义要安装的组件\n和安装位置,安装过程由模拟进度与日志展示(演示界面,无真实安装行为)。")
vqt6.vqt6_qwidget_set_stylesheet_utf8(desc._ptr, "font-size: 13px; color: #9a9a9a;")
p0.addWidget(desc._ptr)
p0.addStretch(1)
next_btn = vqt6.QPushButton("下一步 >")
p0.addWidget(next_btn._ptr)
next_btn.clicked(OnNext, 0)
stack.addWidget(page0._ptr)
# ---------- 页1:许可协议 ----------
page1 = vqt6.QWidget()
p1 = vqt6.QVBoxLayout(page1)
p1.setContentsMargins(36, 32, 36, 24)
p1.setSpacing(10)
lh = vqt6.QLabel("许可协议")
vqt6.vqt6_qwidget_set_stylesheet_utf8(lh._ptr, "font-size: 22px; color: #ffffff; font-weight: bold;")
p1.addWidget(lh._ptr)
license_box = vqt6.QPlainTextEdit()
license_box.setPlainText(license_text)
license_box.setReadOnly(1)
p1.addWidget(license_box._ptr)
agree_cb = vqt6.QCheckBox("我接受许可协议中的条款")
p1.addWidget(agree_cb._ptr)
agree_cb_ptr = t.CPtr(agree_cb._ptr)
agree_cb.toggled(OnLicenseToggled, 0)
lbrow = vqt6.QHBoxLayout()
p1.addLayout(lbrow._ptr)
lbrow.addStretch(1)
lic_back = vqt6.QPushButton("< 上一步")
lbrow.addWidget(lic_back._ptr)
lic_back.clicked(OnLicenseBack, 0)
lic_next = vqt6.QPushButton("下一步 >")
lic_next.setEnabled(0)
lbrow.addWidget(lic_next._ptr)
license_next_ptr = t.CPtr(lic_next._ptr)
lic_next.clicked(OnLicenseNext, 0)
stack.addWidget(page1._ptr)
# ---------- 页2:组件选择 ----------
page2 = vqt6.QWidget()
p2 = vqt6.QVBoxLayout(page2)
p2.setContentsMargins(36, 28, 36, 20)
p2.setSpacing(8)
h2 = vqt6.QLabel("选择要安装的组件")
vqt6.vqt6_qwidget_set_stylesheet_utf8(h2._ptr, "font-size: 22px; color: #ffffff; font-weight: bold;")
p2.addWidget(h2._ptr)
h2b = vqt6.QLabel("勾选您需要的工作负载与组件,取消勾选可减少安装体积与磁盘占用。")
vqt6.vqt6_qwidget_set_stylesheet_utf8(h2b._ptr, "font-size: 13px; color: #9a9a9a;")
p2.addWidget(h2b._ptr)
comp0 = vqt6.QCheckBox("Viper 核心工具链(编译器 + 运行时)[850 MB]")
comp0.setChecked(1)
p2.addWidget(comp0._ptr)
comp0_ptr = t.CPtr(comp0._ptr)
comp1 = vqt6.QCheckBox("Qt6 桌面开发(GUI 框架 + vqt6 桥接库)[1200 MB]")
comp1.setChecked(1)
p2.addWidget(comp1._ptr)
comp1_ptr = t.CPtr(comp1._ptr)
comp2 = vqt6.QCheckBox("Viper 标准库与 includes 包 [350 MB]")
comp2.setChecked(1)
p2.addWidget(comp2._ptr)
comp2_ptr = t.CPtr(comp2._ptr)
comp3 = vqt6.QCheckBox("网络与调试工具(QtNetwork + TPV)[220 MB]")
comp3.setChecked(0)
p2.addWidget(comp3._ptr)
comp3_ptr = t.CPtr(comp3._ptr)
comp4 = vqt6.QCheckBox("示例项目与文档 [90 MB]")
comp4.setChecked(0)
p2.addWidget(comp4._ptr)
comp4_ptr = t.CPtr(comp4._ptr)
comp0.toggled(OnCompToggled, comp0._ptr)
comp1.toggled(OnCompToggled, comp1._ptr)
comp2.toggled(OnCompToggled, comp2._ptr)
comp3.toggled(OnCompToggled, comp3._ptr)
comp4.toggled(OnCompToggled, comp4._ptr)
summary = vqt6.QLabel("已选择 3 个组件")
vqt6.vqt6_qwidget_set_stylesheet_utf8(summary._ptr, "font-size: 13px; color: #9cdcfe;")
p2.addWidget(summary._ptr)
summary_ptr = t.CPtr(summary._ptr)
disk = vqt6.QLabel("所需磁盘空间: 2400 MB / 可用 51200 MB")
vqt6.vqt6_qwidget_set_stylesheet_utf8(disk._ptr, "font-size: 13px; color: #9cdcfe;")
p2.addWidget(disk._ptr)
disk_ptr = t.CPtr(disk._ptr)
# 安装选项
oh = vqt6.QLabel("安装选项")
vqt6.vqt6_qwidget_set_stylesheet_utf8(oh._ptr, "font-size: 15px; color: #ffffff; font-weight: bold; padding-top: 6px;")
p2.addWidget(oh._ptr)
opt0 = vqt6.QCheckBox("创建开始菜单快捷方式")
opt0.setChecked(1)
p2.addWidget(opt0._ptr)
opt0_ptr = t.CPtr(opt0._ptr)
opt1 = vqt6.QCheckBox("将 viper 命令添加到系统 PATH")
opt1.setChecked(0)
p2.addWidget(opt1._ptr)
opt1_ptr = t.CPtr(opt1._ptr)
opt2 = vqt6.QCheckBox("创建桌面图标")
opt2.setChecked(0)
p2.addWidget(opt2._ptr)
opt2_ptr = t.CPtr(opt2._ptr)
# 安装位置行
path_row = vqt6.QHBoxLayout()
p2.addLayout(path_row._ptr)
path_lb = vqt6.QLabel("安装位置:")
path_row.addWidget(path_lb._ptr)
path_lb2 = vqt6.QLabel(install_path)
vqt6.vqt6_qwidget_set_stylesheet_utf8(path_lb2._ptr, "background-color: #252526; border: 1px solid #555555; border-radius: 3px; padding: 5px 8px; color: #ffffff;")
path_row.addWidget(path_lb2._ptr)
path_label_ptr = t.CPtr(path_lb2._ptr)
browse_btn = vqt6.QPushButton("浏览...")
path_row.addWidget(browse_btn._ptr)
browse_btn.clicked(OnBrowse, 0)
p2.addStretch(1)
btn_row2 = vqt6.QHBoxLayout()
p2.addLayout(btn_row2._ptr)
btn_row2.addStretch(1)
back_btn = vqt6.QPushButton("< 上一步")
btn_row2.addWidget(back_btn._ptr)
back_btn.clicked(OnBack, 0)
install_btn = vqt6.QPushButton("安装")
vqt6.vqt6_qwidget_set_stylesheet_utf8(install_btn._ptr, "background-color: #2a6da8; border: 1px solid #2a6da8; border-radius: 4px; padding: 8px 22px; color: #ffffff; font-size: 14px; font-weight: bold;")
btn_row2.addWidget(install_btn._ptr)
install_btn_ptr = t.CPtr(install_btn._ptr)
install_btn.clicked(OnInstall, 0)
stack.addWidget(page2._ptr)
# ---------- 页3:安装进度 ----------
page3 = vqt6.QWidget()
p3 = vqt6.QVBoxLayout(page3)
p3.setContentsMargins(36, 36, 36, 24)
p3.setSpacing(12)
h3 = vqt6.QLabel("正在安装 Viper 6.0...")
vqt6.vqt6_qwidget_set_stylesheet_utf8(h3._ptr, "font-size: 22px; color: #ffffff; font-weight: bold;")
p3.addWidget(h3._ptr)
prog = vqt6.QProgressBar()
prog.setRange(0, 100)
prog.setValue(0)
p3.addWidget(prog._ptr)
progress_ptr = t.CPtr(prog._ptr)
pstat = vqt6.QLabel("正在准备安装环境...")
vqt6.vqt6_qwidget_set_stylesheet_utf8(pstat._ptr, "font-size: 13px; color: #9a9a9a;")
p3.addWidget(pstat._ptr)
progress_status_ptr = t.CPtr(pstat._ptr)
log = vqt6.QPlainTextEdit()
log.setReadOnly(1)
p3.addWidget(log._ptr)
log_ptr = t.CPtr(log._ptr)
stack.addWidget(page3._ptr)
# ---------- 页4:完成 ----------
page4 = vqt6.QWidget()
p4 = vqt6.QVBoxLayout(page4)
p4.setContentsMargins(36, 40, 36, 24)
p4.setSpacing(14)
ok = vqt6.QLabel("安装完成")
vqt6.vqt6_qwidget_set_stylesheet_utf8(ok._ptr, "font-size: 30px; color: #4ec9b0; font-weight: bold;")
p4.addWidget(ok._ptr)
fsum = vqt6.QLabel("")
vqt6.vqt6_qwidget_set_stylesheet_utf8(fsum._ptr, "font-size: 14px; color: #d4d4d4;")
p4.addWidget(fsum._ptr)
finish_summary_ptr = t.CPtr(fsum._ptr)
p4.addStretch(1)
finish_btn = vqt6.QPushButton("完成")
vqt6.vqt6_qwidget_set_stylesheet_utf8(finish_btn._ptr, "background-color: #2a6da8; border-radius: 4px; padding: 8px 22px; color: #ffffff; font-size: 14px; font-weight: bold;")
p4.addWidget(finish_btn._ptr)
finish_btn.clicked(OnFinish, 0)
stack.addWidget(page4._ptr)
# ===== 定时器(模拟安装进度) =====
timer = vqt6.QTimer()
timer.timeout(OnProgressTick, 0)
timer_ptr = t.CPtr(timer._ptr)
GotoStep(0)
win.show()
return app.exec()