From 18be47abe8b3629a7d3b259b44009166c506467c Mon Sep 17 00:00:00 2001 From: shy3130 Date: Wed, 24 Jun 2026 00:18:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=A1=8C=E9=9D=A2=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E7=AB=AF=E5=AE=8C=E5=96=84=20=E2=80=94=20=E5=AE=89=E8=A3=85?= =?UTF-8?q?=E5=8C=85/=E5=9B=BE=E6=A0=87/=E7=B3=BB=E7=BB=9F=E9=80=9A?= =?UTF-8?q?=E7=9F=A5/=E7=89=88=E6=9C=AC=E5=AF=B9=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release.yml | 43 ++-- .gitignore | 5 + README.md | 4 +- backend/app/__init__.py | 2 +- backend/pyproject.toml | 2 +- backend/uv.lock | 2 +- frontend/package.json | 2 +- frontend/public/favicon.svg | 9 +- frontend/src/components/Logo.tsx | 6 +- packaging/ChineseSimplified.isl | 419 +++++++++++++++++++++++++++++++ packaging/generate_icon.py | 90 +++++++ packaging/icon.ico | Bin 0 -> 12205 bytes packaging/tickflow.iss | 143 +++++++++++ packaging/tickflow.spec | 3 +- scripts/bump_version.py | 19 +- 15 files changed, 718 insertions(+), 31 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..fb47ad2 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' @@ -139,17 +148,21 @@ jobs: body: | ## 桌面客户端 ${{ github.event.inputs.version }} - ### 下载 - 选择对应平台的安装包, 解压后双击运行: + ### ⬇️ 下载 - | 平台 | 文件 | - | :--- | :--- | - | **Windows** | `TickFlowStockPanel-win-x64.zip` → 解压运行 `TickFlowStockPanel.exe` | - | **macOS** | `TickFlowStockPanel-macos.zip` → 解压后右键打开 (绕过 Gatekeeper) | - | **Linux** | `TickFlowStockPanel-linux-x64.tar.gz` → 解压运行 | + | 平台 | 文件 | 下载 | + | :--- | :--- | :--- | + | **Windows x64** | `TickFlowStockPanel-Setup-x64.exe` | [⬇️ 立即下载](https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.version }}/TickFlowStockPanel-Setup-x64.exe) | + | **macOS** | `TickFlowStockPanel-macos.zip` | [⬇️ 立即下载](https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.version }}/TickFlowStockPanel-macos.zip) | + | **Linux** | `TickFlowStockPanel-linux-x64.tar.gz` | [⬇️ 立即下载](https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.version }}/TickFlowStockPanel-linux-x64.tar.gz) | - ### 说明 - - 数据存储在用户目录 (Windows `%LOCALAPPDATA%`、mac `~/Library`、Linux `~/.local/share`), 卸载重装不丢数据 - - 含纯 Polars 回测引擎; 不含 vectorbt 回测 (为控制体积) + > 💡 点击「立即下载」直接获取, 无需滚动到底部附件区。 + + ### 📋 使用说明 + - **Windows**: 下载后双击运行, 按安装向导操作(无需管理员权限), 自动创建桌面/开始菜单快捷方式 + - **macOS**: 解压后右键打开(首次绕过 Gatekeeper) + - **Linux**: 解压后运行可执行文件 + - 数据存储在用户目录 (`%LOCALAPPDATA%`), 卸载重装不丢数据 + - 含纯 Polars 回测引擎; 不含 vectorbt 回测(为控制体积) - 系统通知: 设置 → 实时监控 → 系统通知 - 检查更新: 设置 → 系统设置 → 关于 diff --git a/.gitignore b/.gitignore index ba5deea..dbd8d26 100644 --- a/.gitignore +++ b/.gitignore @@ -96,3 +96,8 @@ 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/designs/ +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/backend/app/__init__.py b/backend/app/__init__.py index 219bf01..5084745 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -1,3 +1,3 @@ """TickFlow Stock Panel backend.""" -__version__ = "0.1.31" +__version__ = "0.1.35" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 9a7d287..83a6040 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "tickflow-stock-panel-backend" -version = "0.1.34" +version = "0.1.35" description = "A 股选股 + 监控 + 回测面板 — TickFlow 适配" readme = "../README.md" requires-python = ">=3.11" diff --git a/backend/uv.lock b/backend/uv.lock index 024839c..c6b9e52 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -2470,7 +2470,7 @@ all = [ [[package]] name = "tickflow-stock-panel-backend" -version = "0.1.33" +version = "0.1.35" source = { editable = "." } dependencies = [ { name = "apscheduler" }, diff --git a/frontend/package.json b/frontend/package.json index 993c559..a499d57 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "tickflow-stock-panel-frontend", "private": true, - "version": "0.1.34", + "version": "0.1.35", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg index b329447..504f453 100644 --- a/frontend/public/favicon.svg +++ b/frontend/public/favicon.svg @@ -1,11 +1,12 @@ + - + - + - + - + diff --git a/frontend/src/components/Logo.tsx b/frontend/src/components/Logo.tsx index a39e266..cc4baff 100644 --- a/frontend/src/components/Logo.tsx +++ b/frontend/src/components/Logo.tsx @@ -3,7 +3,7 @@ // 概念: // - 外层 brackets:终端 / 代码 / 引用边界 — 赛博 + quant 气质 // - 中央 wick+body:一根标准 K 线 — 直接的金融指代 -// - body 在垂直中线偏下:体重感,bullish 暗示(上影长 > 下影) +// - body 偏上 + 下影长:bullish 站稳感 (上影短 / 下影长) // // 用 currentColor,继承父级 color 设定,方便切换品牌色。 interface LogoProps { @@ -48,9 +48,9 @@ export function Logo({ className, size = 32, style }: LogoProps) { strokeLinecap="round" strokeOpacity="0.6" /> - {/* K 线 body — 偏下,意味"上影长 / 反弹站住" */} + {/* K 线 body — 偏上,上影短/下影长, bullish 站稳感 */} 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..c9d32d2 --- /dev/null +++ b/packaging/generate_icon.py @@ -0,0 +1,90 @@ +"""生成应用图标 icon.ico — 白底圆角 + 紫色 Logo。 + +设计 (与 frontend Logo.tsx / favicon.svg 一致): + 背景: 白色圆角方块 + 内容: 紫色 #5B21B6 方括号 + K线 (上影短/下影长, bullish 站稳) + +蜡烛几何 (32x32 viewBox): + wick: y=7 ~ y=25 + body: y=9 ~ y=19 (偏上) + → 上影 = 2 (短), 下影 = 6 (长) + +每尺寸独立绘制: 小尺寸加粗线条, 大尺寸精细。 +运行: 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 + +OUT = Path(__file__).parent / "icon.ico" + +BRAND = (91, 33, 182, 255) # #5B21B6 +BRAND_WICK = (91, 33, 182, 153) # 0.6 opacity + + +def _draw(size: int, sw_b: float, sw_w: float, body_w: float) -> Image.Image: + """绘制单尺寸图标 (像素直接映射 32-viewBox)。""" + factor = max(8, 2048 // size) + s = size * factor + img = Image.new("RGBA", (s, s), (255, 255, 255, 0)) + d = ImageDraw.Draw(img) + + # 白色圆角背景 + d.rounded_rectangle([0, 0, s - 1, s - 1], radius=int(s * 0.18), fill=(255, 255, 255, 255)) + + def p(v): + return v * s / 32 + + swb = max(1, int(round(sw_b * factor))) + sww = max(1, int(round(sw_w * factor))) + + # 方括号 [ ] + for pts in [[(10, 4), (4, 4), (4, 28), (10, 28)], [(22, 4), (28, 4), (28, 28), (22, 28)]]: + scaled = [(p(x), p(y)) for x, y in pts] + for i in range(len(scaled) - 1): + d.line([scaled[i], scaled[i + 1]], fill=BRAND, width=swb, joint="curve") + + # wick 影线 (上短下长) + d.line([(p(16), p(7)), (p(16), p(25))], fill=BRAND_WICK, width=sww) + wcap = sww // 2 + 1 + for cy in [7, 25]: + d.ellipse([p(16) - wcap, p(cy) - wcap, p(16) + wcap, p(cy) + wcap], fill=BRAND_WICK) + + # body 实体 (偏上 → 上影短下影长) + d.rounded_rectangle( + [p(16 - body_w / 2), p(9), p(16 + body_w / 2), p(19)], + radius=p(0.5), fill=BRAND, + ) + + return img.resize((size, size), Image.LANCZOS) + + +def draw_logo(size: int) -> Image.Image: + """按尺寸选参数 (小尺寸加粗)。""" + if size <= 16: + return _draw(size, sw_b=4.0, sw_w=3.5, body_w=9) + elif size <= 32: + return _draw(size, sw_b=3.0, sw_w=2.5, body_w=8) + elif size <= 48: + return _draw(size, sw_b=2.5, sw_w=2.0, body_w=7) + else: + return _draw(size, sw_b=2.0, sw_w=1.5, body_w=6) + + +def main() -> None: + sizes = [16, 32, 48, 64, 128, 256] + images = [draw_logo(sz) for sz in sizes] + # 主图 256 (大尺寸优先, 资源管理器大图清晰) + images[-1].save( + OUT, format="ICO", + sizes=[(sz, sz) for sz in sizes], + append_images=images[:-1], + ) + print(f"生成: {OUT} (尺寸 {sizes})") + + +if __name__ == "__main__": + main() diff --git a/packaging/icon.ico b/packaging/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ff835262b57bf5e7fba59ec041239452a55f39b8 GIT binary patch literal 12205 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 tuple[int, int, int]: @@ -54,6 +55,19 @@ def write_pyproject_version(v: str) -> None: PYPROJECT.write_text(text, encoding="utf-8") +def write_app_init_version(v: str) -> None: + """更新 backend/app/__init__.py 的 __version__ (health 接口读这里)。""" + text = APP_INIT.read_text(encoding="utf-8") + text = re.sub( + r'^(__version__\s*=\s*)"[^"]+"', + rf'\1"{v}"', + text, + count=1, + flags=re.MULTILINE, + ) + APP_INIT.write_text(text, encoding="utf-8") + + def main() -> int: # 环境变量跳过 if __import__("os").environ.get("SKIP_VERSION_BUMP") == "1": @@ -62,13 +76,14 @@ def main() -> int: cur = read_pkg_version() new = bump(cur) - # 两个文件版本可能不一致, 统一用 pkg 的为准 + # 三个文件版本可能不一致, 统一用 pkg 的为准 write_pkg_version(new) write_pyproject_version(new) + write_app_init_version(new) # 暂存版本文件改动(并入本次 commit) import subprocess - subprocess.run(["git", "add", str(PKG), str(PYPROJECT)], check=True, cwd=str(ROOT)) + subprocess.run(["git", "add", str(PKG), str(PYPROJECT), str(APP_INIT)], check=True, cwd=str(ROOT)) # 输出新版本号供 hook 读用 print(f"[bump] {cur} -> {new}")