From 5721f1fe9362c9a5b00df6fb967288d3936c3abc Mon Sep 17 00:00:00 2001 From: shy3130 Date: Tue, 23 Jun 2026 22:26:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Windows=20=E6=89=93=E5=8C=85=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=20Inno=20Setup=20=E5=AE=89=E8=A3=85=E5=8C=85=20(Setup?= =?UTF-8?q?.exe)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 packaging/tickflow.iss: Inno Setup 脚本 · 用户目录安装 (不弹 UAC), 桌面+开始菜单快捷方式 · 卸载时询问是否删除用户数据 · 内置简中语言包 (ChineseSimplified.isl, 官方仓库获取) - 新增 packaging/generate_icon.py + icon.ico: 应用图标 · 与 favicon.svg/Logo.tsx 一致 (紫色方括号 + K线) · 用 Pillow 绘制, 多尺寸 (16/32/48/64/128/256) - spec 加 icon: exe 图标 = 安装包图标 = 快捷方式图标 - release.yml: Windows 步骤改为 choco install innosetup + ISCC 编译 - 本地验证通过: 安装→快捷方式→启动→health 0.1.35 全 OK --- .github/workflows/release.yml | 23 +- .gitignore | 4 + README.md | 4 +- packaging/ChineseSimplified.isl | 419 ++++++++++++++++++++++++++++++++ packaging/generate_icon.py | 94 +++++++ packaging/icon.ico | Bin 0 -> 714 bytes packaging/tickflow.iss | 121 +++++++++ packaging/tickflow.spec | 3 +- 8 files changed, 658 insertions(+), 10 deletions(-) create mode 100644 packaging/ChineseSimplified.isl create mode 100644 packaging/generate_icon.py create mode 100644 packaging/icon.ico create mode 100644 packaging/tickflow.iss diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b726771..cfc4e4c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,7 +41,7 @@ jobs: - id: filter run: | REQUESTED="${{ github.event.inputs.platforms }}" - ALL='[{"os":"windows-latest","artifact":"TickFlowStockPanel-win-x64.zip","platform":"windows"},{"os":"macos-latest","artifact":"TickFlowStockPanel-macos.zip","platform":"macos"},{"os":"ubuntu-latest","artifact":"TickFlowStockPanel-linux-x64.tar.gz","platform":"linux"}]' + ALL='[{"os":"windows-latest","artifact":"TickFlowStockPanel-Setup-x64.exe","platform":"windows"},{"os":"macos-latest","artifact":"TickFlowStockPanel-macos.zip","platform":"macos"},{"os":"ubuntu-latest","artifact":"TickFlowStockPanel-linux-x64.tar.gz","platform":"linux"}]' # 按 input 过滤平台 python3 -c " import json, sys @@ -103,12 +103,21 @@ jobs: - name: 打包 (PyInstaller) run: uv run pyinstaller packaging/tickflow.spec --noconfirm - - name: 压缩产物 (Windows) + - name: 安装 Inno Setup (仅 Windows) if: matrix.platform == 'windows' + # Inno Setup 6: 把 PyInstaller 产出封装成单文件安装包 + run: choco install innosetup -y --no-progress + + - name: 生成 Windows 安装包 (Inno Setup) + if: matrix.platform == 'windows' + # 从 frontend/package.json 读版本号传给 ISCC, 保持与 Release tag 一致 run: | - cd dist - 7z a -tzip ../${{ matrix.artifact }} TickFlowStockPanel - shell: bash + $ver = (Get-Content frontend/package.json | ConvertFrom-Json).version + echo "版本号: $ver" + ISCC.exe "/DMyAppVersion=$ver" packaging/tickflow.iss + # 把生成的 Setup.exe 移到工作区根 (Release 上传用) + Move-Item packaging/Output/TickFlowStockPanel-Setup-$ver.exe ${{ matrix.artifact }} -Force + shell: pwsh - name: 压缩产物 (macOS) if: matrix.platform == 'macos' @@ -140,11 +149,11 @@ jobs: ## 桌面客户端 ${{ github.event.inputs.version }} ### 下载 - 选择对应平台的安装包, 解压后双击运行: + 选择对应平台的安装包: | 平台 | 文件 | | :--- | :--- | - | **Windows** | `TickFlowStockPanel-win-x64.zip` → 解压运行 `TickFlowStockPanel.exe` | + | **Windows** | `TickFlowStockPanel-Setup-x64.exe` → 双击安装向导,自动创建桌面/开始菜单快捷方式 | | **macOS** | `TickFlowStockPanel-macos.zip` → 解压后右键打开 (绕过 Gatekeeper) | | **Linux** | `TickFlowStockPanel-linux-x64.tar.gz` → 解压运行 | diff --git a/.gitignore b/.gitignore index ba5deea..666bec7 100644 --- a/.gitignore +++ b/.gitignore @@ -96,3 +96,7 @@ backend/_desktop_run.log backend/TickFlowStockPanel-win-x64.zip backend/TickFlowStockPanel-macos.zip backend/TickFlowStockPanel-linux-x64.tar.gz +packaging/Output/ +packaging/icon_preview.png +packaging/install.log +packaging/installed_run.log diff --git a/README.md b/README.md index 75594a7..98a2260 100644 --- a/README.md +++ b/README.md @@ -174,11 +174,11 @@ docker compose up --build ### 方式 B:桌面客户端(免环境,开箱即用) -下载对应平台的安装包,解压后双击运行即可,**无需 Python / Node 环境**。 +下载对应平台的安装包,运行后按向导安装即可,**无需 Python / Node 环境**。 | 平台 | 文件 | | :--- | :--- | -| **Windows** | `TickFlowStockPanel-win-x64.zip` → 解压后运行 `TickFlowStockPanel.exe` | +| **Windows** | `TickFlowStockPanel-Setup-x64.exe` → 双击安装向导,自动创建桌面/开始菜单快捷方式 | | **macOS** | `TickFlowStockPanel-macos.zip` → 解压后运行 app(首次打开右键→打开,绕过 Gatekeeper) | | **Linux** | `TickFlowStockPanel-linux-x64.tar.gz` → 解压后运行可执行文件 | diff --git a/packaging/ChineseSimplified.isl b/packaging/ChineseSimplified.isl new file mode 100644 index 0000000..d84f6cf --- /dev/null +++ b/packaging/ChineseSimplified.isl @@ -0,0 +1,419 @@ +; *** Inno Setup version 6.5.0+ Chinese Simplified messages *** +; +; To download user-contributed translations of this file, go to: +; https://jrsoftware.org/files/istrans/ +; +; Note: When translating this text, do not add periods (.) to the end of +; messages that didn't have them already, because on those messages Inno +; Setup adds the periods automatically (appending a period would result in +; two periods being displayed). +; +; Maintainer: Zhenghan Yang (Kira) +; Email: 847320916@QQ.com +; Github: https://github.com/kira-96/Inno-Setup-Chinese-Simplified-Translation +; Encoding: UTF-8 +; Translation based on network resource +; + +[LangOptions] +; The following three entries are very important. Be sure to read and +; understand the '[LangOptions] section' topic in the help file. +LanguageName=简体中文 +; If Language Name display incorrect, uncomment next line +; LanguageName=<7B80><4F53><4E2D><6587> +; About LanguageID, to reference link: +; https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c +LanguageID=$0804 +; LanguageCodePage should always be set if possible, even if this file is Unicode +; For English it's set to zero anyway because English only uses ASCII characters +LanguageCodePage=936 +; If the language you are translating to requires special font faces or +; sizes, uncomment any of the following entries and change them accordingly. +;DialogFontName= +;DialogFontSize=9 +;DialogFontBaseScaleWidth=7 +;DialogFontBaseScaleHeight=15 +;WelcomeFontName=Segoe UI +;WelcomeFontSize=14 + +[Messages] + +; *** Application titles +SetupAppTitle=安装 +SetupWindowTitle=安装 - %1 +UninstallAppTitle=卸载 +UninstallAppFullTitle=%1 卸载 + +; *** Misc. common +InformationTitle=信息 +ConfirmTitle=确认 +ErrorTitle=错误 + +; *** SetupLdr messages +SetupLdrStartupMessage=现在将安装 %1。您想要继续吗? +LdrCannotCreateTemp=无法创建临时文件。安装程序已中止 +LdrCannotExecTemp=无法执行临时目录中的文件。安装程序已中止 +HelpTextNote= + +; *** Startup error messages +LastErrorMessage=%1。%n%n错误 %2: %3 +SetupFileMissing=安装目录中缺少文件 %1。请修正这个问题或者获取程序的新副本。 +SetupFileCorrupt=安装文件已损坏。请获取程序的新副本。 +SetupFileCorruptOrWrongVer=安装文件已损坏,或是与这个安装程序的版本不兼容。请修正这个问题或获取新的程序副本。 +InvalidParameter=无效的命令行参数:%n%n%1 +SetupAlreadyRunning=安装程序正在运行。 +WindowsVersionNotSupported=此程序不支持当前计算机运行的 Windows 版本。 +WindowsServicePackRequired=此程序需要 %1 服务包 %2 或更高版本。 +NotOnThisPlatform=此程序不能在 %1 上运行。 +OnlyOnThisPlatform=此程序只能在 %1 上运行。 +OnlyOnTheseArchitectures=此程序只能安装到为下列处理器架构设计的 Windows 版本中:%n%n%1 +WinVersionTooLowError=此程序需要 %1 版本 %2 或更高。 +WinVersionTooHighError=此程序不能安装于 %1 版本 %2 或更高。 +AdminPrivilegesRequired=在安装此程序时您必须以管理员身份登录。 +PowerUserPrivilegesRequired=在安装此程序时您必须以管理员身份或高级用户组身份登录。 +SetupAppRunningError=安装程序发现 %1 当前正在运行。%n%n请先关闭正在运行的程序,然后点击“确定”继续,或点击“取消”退出。 +UninstallAppRunningError=卸载程序发现 %1 当前正在运行。%n%n请先关闭正在运行的程序,然后点击“确定”继续,或点击“取消”退出。 + +; *** Startup questions +PrivilegesRequiredOverrideTitle=选择安装程序模式 +PrivilegesRequiredOverrideInstruction=选择安装模式 +PrivilegesRequiredOverrideText1=%1 可以为所有用户安装(需要管理员权限),或仅为您安装。 +PrivilegesRequiredOverrideText2=%1 可以仅为您安装,或为所有用户安装(需要管理员权限)。 +PrivilegesRequiredOverrideAllUsers=为所有用户安装(&A) +PrivilegesRequiredOverrideAllUsersRecommended=为所有用户安装(&A)(建议选项) +PrivilegesRequiredOverrideCurrentUser=仅为我安装(&M) +PrivilegesRequiredOverrideCurrentUserRecommended=仅为我安装(&M)(建议选项) + +; *** Misc. errors +ErrorCreatingDir=安装程序无法创建目录“%1” +ErrorTooManyFilesInDir=无法在目录“%1”中创建文件,因为里面包含太多文件 + +; *** Setup common messages +ExitSetupTitle=退出安装程序 +ExitSetupMessage=安装程序尚未完成。如果现在退出,将不会安装该程序。%n%n您之后可以再次运行安装程序完成安装。%n%n现在退出安装程序吗? +AboutSetupMenuItem=关于安装程序(&A)... +AboutSetupTitle=关于安装程序 +AboutSetupMessage=%1 版本 %2%n%3%n%n%1 主页:%n%4 +AboutSetupNote= +TranslatorNote=简体中文翻译由 Kira(847320916@qq.com)维护。项目地址:https://github.com/kira-96/Inno-Setup-Chinese-Simplified-Translation + +; *** Buttons +ButtonBack=< 上一步(&B) +ButtonNext=下一步(&N) > +ButtonInstall=安装(&I) +ButtonOK=确定 +ButtonCancel=取消 +ButtonYes=是(&Y) +ButtonYesToAll=全是(&A) +ButtonNo=否(&N) +ButtonNoToAll=全否(&O) +ButtonFinish=完成(&F) +ButtonBrowse=浏览(&B)... +ButtonWizardBrowse=浏览(&R)... +ButtonNewFolder=新建文件夹(&M) + +; *** "Select Language" dialog messages +SelectLanguageTitle=选择安装语言 +SelectLanguageLabel=选择安装时使用的语言。 + +; *** Common wizard text +ClickNext=点击“下一步”继续,或点击“取消”退出安装程序。 +BeveledLabel= +BrowseDialogTitle=浏览文件夹 +BrowseDialogLabel=在下面的列表中选择一个文件夹,然后点击“确定”。 +NewFolderName=新建文件夹 + +; *** "Welcome" wizard page +WelcomeLabel1=欢迎使用 [name] 安装向导 +WelcomeLabel2=即将在您的计算机上安装 [name/ver]。%n%n建议您在继续安装前关闭所有其他应用程序。 + +; *** "Password" wizard page +WizardPassword=密码 +PasswordLabel1=此安装程序需要密码验证。 +PasswordLabel3=请输入密码,然后点击“下一步”继续。密码区分大小写。 +PasswordEditLabel=密码(&P): +IncorrectPassword=您输入的密码不正确,请重新输入。 + +; *** "License Agreement" wizard page +WizardLicense=许可协议 +LicenseLabel=请在继续安装前阅读以下重要信息。 +LicenseLabel3=请仔细阅读下列许可协议。在继续安装前您必须同意这些协议条款。 +LicenseAccepted=我同意此协议(&A) +LicenseNotAccepted=我不同意此协议(&D) + +; *** "Information" wizard pages +WizardInfoBefore=信息 +InfoBeforeLabel=请在继续安装前阅读以下重要信息。 +InfoBeforeClickLabel=准备好继续安装后,点击“下一步”。 +WizardInfoAfter=信息 +InfoAfterLabel=请在继续安装前阅读以下重要信息。 +InfoAfterClickLabel=准备好继续安装后,点击“下一步”。 + +; *** "User Information" wizard page +WizardUserInfo=用户信息 +UserInfoDesc=请输入您的信息。 +UserInfoName=用户名(&U): +UserInfoOrg=组织(&O): +UserInfoSerial=序列号(&S): +UserInfoNameRequired=请输入用户名。 + +; *** "Select Destination Location" wizard page +WizardSelectDir=选择目标位置 +SelectDirDesc=您想将 [name] 安装在哪里? +SelectDirLabel3=安装程序将安装 [name] 到下面的文件夹中。 +SelectDirBrowseLabel=点击“下一步”继续。如果您想选择其他文件夹,点击“浏览”。 +DiskSpaceGBLabel=至少需要有 [gb] GB 的可用磁盘空间。 +DiskSpaceMBLabel=至少需要有 [mb] MB 的可用磁盘空间。 +CannotInstallToNetworkDrive=安装程序无法安装到一个网络驱动器。 +CannotInstallToUNCPath=安装程序无法安装到一个 UNC 路径。 +InvalidPath=您必须输入一个带驱动器卷标的完整路径,例如:%n%nC:\App%n%n或UNC路径:%n%n\\server\share +InvalidDrive=您选定的驱动器或 UNC 共享不存在或不能访问。请选择其他位置。 +DiskSpaceWarningTitle=磁盘空间不足 +DiskSpaceWarning=安装程序至少需要 %1 KB 的可用空间才能安装,但选定驱动器只有 %2 KB 的可用空间。%n%n您一定要继续吗? +DirNameTooLong=文件夹名称或路径太长。 +InvalidDirName=文件夹名称无效。 +BadDirName32=文件夹名称不能包含下列任何字符:%n%n%1 +DirExistsTitle=文件夹已存在 +DirExists=文件夹:%n%n%1%n%n已经存在。您一定要安装到这个文件夹中吗? +DirDoesntExistTitle=文件夹不存在 +DirDoesntExist=文件夹:%n%n%1%n%n不存在。您想要创建此文件夹吗? + +; *** "Select Components" wizard page +WizardSelectComponents=选择组件 +SelectComponentsDesc=您想安装哪些程序组件? +SelectComponentsLabel2=选中您想安装的组件;取消您不想安装的组件。然后点击“下一步”继续。 +FullInstallation=完全安装 +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation=简洁安装 +CustomInstallation=自定义安装 +NoUninstallWarningTitle=组件已存在 +NoUninstallWarning=安装程序检测到下列组件已安装在您的计算机中:%n%n%1%n%n取消选中这些组件不会卸载它们。%n%n确定要继续吗? +ComponentSize1=%1 KB +ComponentSize2=%1 MB +ComponentsDiskSpaceGBLabel=当前选择的组件需要至少 [gb] GB 的磁盘空间。 +ComponentsDiskSpaceMBLabel=当前选择的组件需要至少 [mb] MB 的磁盘空间。 + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=选择附加任务 +SelectTasksDesc=您想要安装程序执行哪些附加任务? +SelectTasksLabel2=选择您想要安装程序在安装 [name] 时执行的附加任务,然后点击“下一步”。 + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=选择开始菜单文件夹 +SelectStartMenuFolderDesc=安装程序应该在哪里放置程序的快捷方式? +SelectStartMenuFolderLabel3=安装程序将在下列“开始”菜单文件夹中创建程序的快捷方式。 +SelectStartMenuFolderBrowseLabel=点击“下一步”继续。如果您想选择其他文件夹,点击“浏览”。 +MustEnterGroupName=您必须输入一个文件夹名称。 +GroupNameTooLong=文件夹名称或路径太长。 +InvalidGroupName=文件夹名称无效。 +BadGroupName=文件夹名称不能包含下列任何字符:%n%n%1 +NoProgramGroupCheck2=不创建开始菜单文件夹(&D) + +; *** "Ready to Install" wizard page +WizardReady=准备安装 +ReadyLabel1=安装程序准备就绪,现在可以开始安装 [name] 到您的计算机。 +ReadyLabel2a=点击“安装”继续此安装程序。如果您想重新考虑或修改任何设置,点击“上一步”。 +ReadyLabel2b=点击“安装”继续此安装程序。 +ReadyMemoUserInfo=用户信息: +ReadyMemoDir=目标位置: +ReadyMemoType=安装类型: +ReadyMemoComponents=已选择组件: +ReadyMemoGroup=开始菜单文件夹: +ReadyMemoTasks=附加任务: + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel2=正在下载文件... +ButtonStopDownload=停止下载(&S) +StopDownload=您确定要停止下载吗? +ErrorDownloadAborted=下载已中止 +ErrorDownloadFailed=下载失败:%1 %2 +ErrorDownloadSizeFailed=获取大小失败:%1 %2 +ErrorProgress=无效的进度:%1 / %2 +ErrorFileSize=文件大小错误:预期 %1,实际 %2 + +; *** TExtractionWizardPage wizard page and ExtractArchive +ExtractingLabel=正在提取文件... +ButtonStopExtraction=停止提取(&S) +StopExtraction=您确定要停止提取吗? +ErrorExtractionAborted=提取已中止 +ErrorExtractionFailed=提取失败:%1 + +; *** Archive extraction failure details +ArchiveIncorrectPassword=密码不正确 +ArchiveIsCorrupted=压缩包已损坏 +ArchiveUnsupportedFormat=不支持的压缩包格式 + +; *** "Preparing to Install" wizard page +WizardPreparing=正在准备安装 +PreparingDesc=安装程序正在准备安装 [name] 到您的计算机。 +PreviousInstallNotCompleted=先前的程序安装或卸载未完成,需要您重启计算机。%n%n在重启计算机后,再次运行安装程序以完成 [name] 的安装。 +CannotContinue=安装程序不能继续。请点击“取消”退出。 +ApplicationsFound=以下应用程序正在使用将由安装程序更新的文件。建议您允许安装程序自动关闭这些应用程序。 +ApplicationsFound2=以下应用程序正在使用将由安装程序更新的文件。建议您允许安装程序自动关闭这些应用程序。安装完成后,安装程序将尝试重新启动这些应用程序。 +CloseApplications=自动关闭应用程序(&A) +DontCloseApplications=不要关闭应用程序(&D) +ErrorCloseApplications=安装程序无法自动关闭所有应用程序。建议您在继续之前,关闭所有在使用需要由安装程序更新的文件的应用程序。 +PrepareToInstallNeedsRestart=安装程序必须重启您的计算机。计算机重启后,请再次运行安装程序以完成 [name] 的安装。%n%n要立即重启吗? + +; *** "Installing" wizard page +WizardInstalling=正在安装 +InstallingLabel=安装程序正在安装 [name] 到您的计算机,请稍候。 + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=[name] 安装完成 +FinishedLabelNoIcons=安装程序已在您的计算机中安装了 [name]。 +FinishedLabel=安装程序已在您的计算机中安装了 [name]。您可以通过已安装的快捷方式运行此应用程序。 +ClickFinish=点击“完成”退出安装程序。 +FinishedRestartLabel=为完成 [name] 的安装,安装程序必须重新启动您的计算机。要立即重启吗? +FinishedRestartMessage=为完成 [name] 的安装,安装程序必须重新启动您的计算机。%n%n要立即重启吗? +ShowReadmeCheck=是,我想查阅自述文件 +YesRadio=是,立即重启计算机(&Y) +NoRadio=否,稍后重启计算机(&N) +; used for example as 'Run MyProg.exe' +RunEntryExec=运行 %1 +; used for example as 'View Readme.txt' +RunEntryShellExec=查阅 %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=安装程序需要下一张磁盘 +SelectDiskLabel2=请插入磁盘 %1 并点击“确定”。%n%n如果这个磁盘中的文件可以在下列文件夹之外的文件夹中找到,请输入正确的路径或点击“浏览”。 +PathLabel=路径(&P): +FileNotInDir2=“%2”中找不到文件“%1”。请插入正确的磁盘或选择其他文件夹。 +SelectDirectoryLabel=请指定下一张磁盘的位置。 + +; *** Installation phase messages +SetupAborted=安装程序未完成安装。%n%n请修正这个问题并重新运行安装程序。 +AbortRetryIgnoreSelectAction=选择操作 +AbortRetryIgnoreRetry=重试(&T) +AbortRetryIgnoreIgnore=忽略错误并继续(&I) +AbortRetryIgnoreCancel=取消安装 +RetryCancelSelectAction=选择操作 +RetryCancelRetry=重试(&T) +RetryCancelCancel=取消 + +; *** Installation status messages +StatusClosingApplications=正在关闭应用程序... +StatusCreateDirs=正在创建目录... +StatusExtractFiles=正在提取文件... +StatusDownloadFiles=正在下载文件... +StatusCreateIcons=正在创建快捷方式... +StatusCreateIniEntries=正在创建 INI 条目... +StatusCreateRegistryEntries=正在创建注册表条目... +StatusRegisterFiles=正在注册文件... +StatusSavingUninstall=正在保存卸载信息... +StatusRunProgram=正在完成安装... +StatusRestartingApplications=正在重启应用程序... +StatusRollback=正在撤销更改... + +; *** Misc. errors +ErrorInternal2=内部错误:%1 +ErrorFunctionFailedNoCode=%1 失败 +ErrorFunctionFailed=%1 失败;错误代码 %2 +ErrorFunctionFailedWithMessage=%1 失败;错误代码 %2.%n%3 +ErrorExecutingProgram=无法执行文件:%n%1 + +; *** Registry errors +ErrorRegOpenKey=打开注册表项时出错:%n%1\%2 +ErrorRegCreateKey=创建注册表项时出错:%n%1\%2 +ErrorRegWriteKey=写入注册表项时出错:%n%1\%2 + +; *** INI errors +ErrorIniEntry=在文件“%1”中创建 INI 条目时出错。 + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=跳过此文件(&S)(不推荐) +FileAbortRetryIgnoreIgnoreNotRecommended=忽略错误并继续(&I)(不推荐) +SourceIsCorrupted=源文件已损坏 +SourceDoesntExist=源文件“%1”不存在 +SourceVerificationFailed=源文件验证失败:%1 +VerificationSignatureDoesntExist=签名文件“%1”不存在 +VerificationSignatureInvalid=签名文件“%1”无效 +VerificationKeyNotFound=签名文件“%1”使用了未知的密钥 +VerificationFileNameIncorrect=文件名不正确 +VerificationFileTagIncorrect=文件标签不正确 +VerificationFileSizeIncorrect=文件大小不正确 +VerificationFileHashIncorrect=文件哈希值不正确 +ExistingFileReadOnly2=无法替换现有文件,它是只读的。 +ExistingFileReadOnlyRetry=移除只读属性并重试(&R) +ExistingFileReadOnlyKeepExisting=保留现有文件(&K) +ErrorReadingExistingDest=尝试读取现有文件时出错: +FileExistsSelectAction=选择操作 +FileExists2=文件已经存在。 +FileExistsOverwriteExisting=覆盖已存在的文件(&O) +FileExistsKeepExisting=保留现有的文件(&K) +FileExistsOverwriteOrKeepAll=为所有冲突文件执行此操作(&D) +ExistingFileNewerSelectAction=选择操作 +ExistingFileNewer2=现有的文件比安装程序将要安装的文件还要新。 +ExistingFileNewerOverwriteExisting=覆盖已存在的文件(&O) +ExistingFileNewerKeepExisting=保留现有的文件(&K)(推荐) +ExistingFileNewerOverwriteOrKeepAll=为所有冲突文件执行此操作(&D) +ErrorChangingAttr=尝试更改下列现有文件的属性时出错: +ErrorCreatingTemp=尝试在目标目录创建文件时出错: +ErrorReadingSource=尝试读取下列源文件时出错: +ErrorCopying=尝试复制下列文件时出错: +ErrorDownloading=尝试下载文件时出错: +ErrorExtracting=尝试提取压缩包时出错: +ErrorReplacingExistingFile=尝试替换现有文件时出错: +ErrorRestartReplace=重启并替换失败: +ErrorRenamingTemp=尝试重命名下列目标目录中的一个文件时出错: +ErrorRegisterServer=无法注册 DLL/OCX:%1 +ErrorRegSvr32Failed=RegSvr32 失败;退出代码 %1 +ErrorRegisterTypeLib=无法注册类库:%1 + +; *** Uninstall display name markings +; used for example as 'My Program (32-bit)' +UninstallDisplayNameMark=%1 (%2) +; used for example as 'My Program (32-bit, All users)' +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32 位 +UninstallDisplayNameMark64Bit=64 位 +UninstallDisplayNameMarkAllUsers=所有用户 +UninstallDisplayNameMarkCurrentUser=当前用户 + +; *** Post-installation errors +ErrorOpeningReadme=尝试打开自述文件时出错。 +ErrorRestartingComputer=安装程序无法重启计算机,请手动重启。 + +; *** Uninstaller messages +UninstallNotFound=文件“%1”不存在。无法卸载。 +UninstallOpenError=文件“%1”不能被打开。无法卸载 +UninstallUnsupportedVer=此版本的卸载程序无法识别卸载日志文件“%1”的格式。无法卸载 +UninstallUnknownEntry=卸载日志中遇到一个未知条目(%1) +ConfirmUninstall=您确认要完全移除 %1 及其所有组件吗? +UninstallOnlyOnWin64=仅允许在 64 位 Windows 中卸载此程序。 +OnlyAdminCanUninstall=仅使用管理员权限的用户能完成此卸载。 +UninstallStatusLabel=正在从您的计算机中移除 %1,请稍候。 +UninstalledAll=已顺利从您的计算机中移除 %1。 +UninstalledMost=%1 卸载完成。%n%n有部分内容未能被删除,但您可以手动删除它们。 +UninstalledAndNeedsRestart=为完成 %1 的卸载,需要重启您的计算机。%n%n要立即重启吗? +UninstallDataCorrupted=文件“%1”已损坏。无法卸载 + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=删除共享的文件吗? +ConfirmDeleteSharedFile2=系统表示下列共享的文件已不再有其他程序使用。您希望卸载程序删除这些共享的文件吗?%n%n如果删除这些文件,但仍有程序在使用这些文件,则这些程序可能出现异常。如果您不能确定,请选择“否”,保留该文件在系统中不会造成任何损害。 +SharedFileNameLabel=文件名: +SharedFileLocationLabel=位置: +WizardUninstalling=卸载状态 +StatusUninstalling=正在卸载 %1... + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=正在安装 %1。 +ShutdownBlockReasonUninstallingApp=正在卸载 %1。 + +; The custom messages below aren't used by Setup itself, but if you make +; use of them in your scripts, you'll want to translate them. + +[CustomMessages] + +NameAndVersion=%1 版本 %2 +AdditionalIcons=附加快捷方式: +CreateDesktopIcon=创建桌面快捷方式(&D) +CreateQuickLaunchIcon=创建快速启动栏快捷方式(&Q) +ProgramOnTheWeb=%1 网站 +UninstallProgram=卸载 %1 +LaunchProgram=运行 %1 +AssocFileExtension=将 %2 文件扩展名与 %1 建立关联(&A) +AssocingFileExtension=正在将 %2 文件扩展名与 %1 建立关联... +AutoStartProgramGroupDescription=启动: +AutoStartProgram=自动启动 %1 +AddonHostProgramNotFound=您选择的文件夹中无法找到 %1。%n%n您要继续吗? diff --git a/packaging/generate_icon.py b/packaging/generate_icon.py new file mode 100644 index 0000000..c7fbaad --- /dev/null +++ b/packaging/generate_icon.py @@ -0,0 +1,94 @@ +"""生成应用图标 .ico — 与前端 favicon.svg / Logo.tsx 保持一致。 + +几何 (32x32 基准, 按比例缩放到各尺寸): + - 紫色方括号 [ ] (brand color #5B21B6) + - 中间一根带 wick 的 K 线 body (实心矩形) + +用途: + - exe 图标 (PyInstaller icon=) + - 安装包图标 (Inno Setup SetupIconFile=) + - 快捷方式图标 + +运行: python packaging/generate_icon.py +产物: packaging/icon.ico (含 16/32/48/64/128/256 多尺寸) +""" +from __future__ import annotations + +from pathlib import Path + +from PIL import Image, ImageDraw + +# 品牌色 (与 frontend/public/favicon.svg 一致) +BRAND = (91, 33, 182, 255) # #5B21B6 +BRAND_DIM = (91, 33, 182, 166) # stroke-opacity 0.65 (wick) + +OUTPUT = Path(__file__).parent / "icon.ico" + + +def draw_logo(size: int) -> Image.Image: + """按 favicon.svg 的几何绘制指定尺寸的图标。 + + 坐标基于 32x32 viewBox, 按比例缩放到 size。 + """ + # 用 4x 超采样再缩放, 抗锯齿 + scale = max(4, 256 // size) + s = size * scale + img = Image.new("RGBA", (s, s), (0, 0, 0, 0)) + d = ImageDraw.Draw(img) + + # 把 32-viewBox 坐标缩放到 s + def x(v: float) -> float: + return v * s / 32 + + def y(v: float) -> float: + return v * s / 32 + + sw = 2.5 # 括号线宽 + bw = 2.0 # wick 线宽 + w = max(1, int(round(sw * scale))) + ww = max(1, int(round(bw * scale))) + + # 左方括号 [ : M10,4 L4,4 L4,28 L10,28 + d.line([(x(10), y(4)), (x(4), y(4))], fill=BRAND, width=w) + d.line([(x(4), y(4)), (x(4), y(28))], fill=BRAND, width=w) + d.line([(x(4), y(28)), (x(10), y(28))], fill=BRAND, width=w) + # 括号转角处用圆点补强 (line 转角有缝隙) + r = w // 2 + 1 + for cx, cy in [(x(4), y(4)), (x(4), y(28))]: + d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=BRAND) + + # 右方括号 ] : M22,4 L28,4 L28,28 L22,28 + d.line([(x(22), y(4)), (x(28), y(4))], fill=BRAND, width=w) + d.line([(x(28), y(4)), (x(28), y(28))], fill=BRAND, width=w) + d.line([(x(28), y(28)), (x(22), y(28))], fill=BRAND, width=w) + for cx, cy in [(x(28), y(4)), (x(28), y(28))]: + d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=BRAND) + + # K 线 wick: x=16, y=7..25 + d.line([(x(16), y(7)), (x(16), y(25))], fill=BRAND_DIM, width=ww) + + # K 线 body: rect (12.5, 11.5) 7x11, rx=0.8 + bx0, by0 = x(12.5), y(11.5) + bx1, by1 = x(12.5) + x(7), y(11.5) + y(11) + radius = x(0.8) + d.rounded_rectangle([bx0, by0, bx1, by1], radius=radius, fill=BRAND) + + # 缩放到目标尺寸 (高质量抗锯齿) + return img.resize((size, size), Image.LANCZOS) + + +def main() -> None: + sizes = [16, 32, 48, 64, 128, 256] + images = [draw_logo(s) for s in sizes] + # 第一个作为主图, 其余作为变体 + images[0].save( + OUTPUT, + format="ICO", + sizes=[(s, s) for s in sizes], + append_images=images[1:], + ) + print(f"生成图标: {OUTPUT} (尺寸 {sizes})") + + +if __name__ == "__main__": + main() diff --git a/packaging/icon.ico b/packaging/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..5c167db211d79aff73cce6f021a9cb57f56ca8e2 GIT binary patch literal 714 zcmV;*0yX^r0096201yxW0000W0JH)C02TlM0EtjeM-2)Z3IG5A4M|8uQUCw|5C8xG z5C{eU001BJ|6u?C0((hBK~#90l~c`2)j99`+&0YCsWp6SAzZE8z8Auz<5g39*R*h=4fJKR2X zMb$0{g&dQE%AWTwX#=oY#GIK2uz+sg*t}g(@C+NSCjS#FvjG(?gUF& zL&VlA#g|b+x5q)GVku*!&T)=*!=`tfcR{4h64QFnU2#-vyd;mHvM6b?UhJB)U%qYK zX4q_g$4_sZ-28)`Twm(T=ZVY{)u76{ZC=HDLPn zQvV)+sezV>uZFTC6Qp(@OEAtqib+G+v2PvO{B45auivZrmnjt>*?+$PT zAVQFgqd&y`fKETla{i_wAeE#6KxeEC^Hfkh2HovKwjbrFB!| wtJjmG;~XWFH=1q)Vduydf|fVOmV0Oa0<=CY=XUsu761SM07*qoM6N<$f>&QeJpcdz literal 0 HcmV?d00001 diff --git a/packaging/tickflow.iss b/packaging/tickflow.iss new file mode 100644 index 0000000..6fbd75b --- /dev/null +++ b/packaging/tickflow.iss @@ -0,0 +1,121 @@ +; =========================================================================== +; TickFlow Stock Panel — Inno Setup 安装包脚本 +; =========================================================================== +; 用途: 把 PyInstaller 产出的 dist/TickFlowStockPanel/ 文件夹封装成 +; 单个 Setup.exe 安装程序 (双击→安装向导→快捷方式→可卸载)。 +; +; 构建 (本地): +; 1. 先跑 PyInstaller: cd backend && uv run pyinstaller ../packaging/tickflow.spec +; 2. 再跑 Inno Setup: ISCC.exe packaging\tickflow.iss +; 3. 产物: packaging\Output\TickFlowStockPanel-Setup-x.x.x.exe +; +; 设计决策: +; - 装到用户目录 {localappdata}\Programs\ (不弹 UAC, 不需管理员) +; - 卸载时询问是否删除用户数据 (%LOCALAPPDATA%\TickFlowStockPanel\) +; - 桌面 + 开始菜单快捷方式 +; - 卸载入口 (控制面板可见) +; =========================================================================== + +#define MyAppName "TickFlow 股票面板" +#define MyAppNameEN "TickFlow Stock Panel" +#define MyAppExeName "TickFlowStockPanel.exe" +#define MyAppPublisher "TickFlow" + +; 版本号: 从 frontend/package.json 读取, 与 Release tag 保持一致 +; 手动指定更可靠 (CI 传入 /DMyAppVersion) +#ifndef MyAppVersion + #define MyAppVersion "0.0.0" +#endif + +[Setup] +; 基本信息 +AppName={#MyAppName} +AppVerName={#MyAppName} {#MyAppVersion} +AppVersion={#MyAppVersion} +AppPublisher={#MyAppPublisher} +DefaultDirName={localappdata}\Programs\TickFlowStockPanel +DefaultGroupName={#MyAppName} +DisableProgramGroupPage=yes +OutputDir=Output +OutputBaseFilename=TickFlowStockPanel-Setup-{#MyAppVersion} + +; 关键: 不需要管理员权限 (用户目录安装) +; PrivilegesRequired=lowest 固定走用户安装, 永不弹 UAC +; 不用 PrivilegesRequiredOverridesAllowed (它在静默模式下路径解析不确定) +PrivilegesRequired=lowest + +; 压缩 +Compression=lzma2/ultra64 +SolidCompression=yes +LZMAUseSeparateProcess=yes + +; 界面 +WizardStyle=modern +DisableWelcomePage=no +DisableReadyPage=no +SetupIconFile=icon.ico +UninstallDisplayIcon={app}\{#MyAppExeName} + +; 卸载相关 +Uninstallable=yes +CreateUninstallRegKey=yes + +[Languages] +; 简中语言包内置在 packaging/ 下 (从 Inno Setup 官方仓库获取), +; 不依赖安装目录是否含该文件 (CI 友好)。 +Name: "chinesesimp"; MessagesFile: "ChineseSimplified.isl" +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkedonce + +[Files] +; 把 PyInstaller 产出的整个文件夹搬进安装目录 +; Source 路径相对于 .iss 文件所在目录 +Source: "..\backend\dist\TickFlowStockPanel\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs + +[Icons] +; 开始菜单 +Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" +Name: "{group}\卸载 {#MyAppName}"; Filename: "{uninstallexe}" + +; 桌面 (可选, 由 Task 控制) +Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon + +[Run] +; 安装完成后启动应用 +Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#MyAppName}}"; Flags: nowait postinstall skipifsilent + +[UninstallRun] +; 卸载前先关闭正在运行的应用 (否则 exe 被占用删不掉) +Filename: "{cmd}"; Parameters: "/C taskkill /F /IM {#MyAppExeName}"; Flags: runhidden; RunOnceId: "KillApp" + +[UninstallDelete] +; 清理安装目录下的残留 (日志等) +Type: filesandordirs; Name: "{app}" + +[Code] +// ── 卸载时询问是否删除用户数据 ───────────────────────────────── +// 用户数据在 %LOCALAPPDATA%\TickFlowStockPanel\ (策略/选股/回测/监控) +// 默认保留 (重装不丢), 但给用户彻底清除的选项 +procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); +var + DataDir: String; +begin + if CurUninstallStep = usPostUninstall then + begin + DataDir := ExpandConstant('{localappdata}\TickFlowStockPanel'); + if DirExists(DataDir) then + begin + if SuppressibleMsgBox( + '是否同时删除用户数据?' + #13#10 + #13#10 + + '位置: ' + DataDir + #13#10 + + '内容: 策略、选股结果、回测记录、监控规则等' + #13#10 + #13#10 + + '选「是」彻底卸载, 选「否」保留数据(重装后可恢复)。', + mbConfirmation, MB_YESNO or MB_DEFBUTTON2, IDNO) = IDYES then + begin + DelTree(DataDir, True, True, True); + end; + end; + end; +end; diff --git a/packaging/tickflow.spec b/packaging/tickflow.spec index 3b6a9e7..6a187dd 100644 --- a/packaging/tickflow.spec +++ b/packaging/tickflow.spec @@ -29,6 +29,7 @@ ROOT = Path(SPECPATH).parent FRONTEND_DIST = str(ROOT / "frontend" / "dist") TIERS_YAML = str(ROOT / "tiers.yaml") BUILTIN_STRATEGIES = str(ROOT / "backend" / "app" / "strategy" / "builtin") +APP_ICON = str(ROOT / "packaging" / "icon.ico") # ── 收集带原生库的依赖 (.libs/ 目录必须完整, 否则启动崩) ───────────── # polars / pyarrow / duckdb / fastexcel 都自带共享库子目录 @@ -151,7 +152,7 @@ exe = EXE( target_arch=None, codesign_identity=None, entitlements_file=None, - icon=None, # TODO: 添加应用图标 (后续设计后填路径) + icon=APP_ICON, # 应用图标 (与 favicon/logo 一致) ) coll = COLLECT(