From 32076839d167c513e3d61e0f04f6bfb0c3d55b22 Mon Sep 17 00:00:00 2001 From: dqb-dev Date: Fri, 2 Oct 2026 09:00:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=93=E5=8C=85=E6=8A=80=E8=83=BD=E5=85=A5?= =?UTF-8?q?=E4=BB=93=E5=BA=93:winforms-designer-package(=E5=90=AB=E6=88=90?= =?UTF-8?q?=E5=A5=97=E6=8A=A5=E4=BB=B7=E8=BD=AF=E4=BB=B6=208=20=E6=AD=A5?= =?UTF-8?q?=E5=8F=91=E7=89=88=E4=B8=93=E7=AB=A0:=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7=E9=80=92=E5=A2=9E/=E7=BB=84=E8=A3=85/ISCC/=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=8C=85/git/Gitea=20Release/=E9=AA=8C=E6=94=B6/?= =?UTF-8?q?=E6=95=85=E9=9A=9C=E9=80=9F=E6=9F=A5)=E6=94=BE=E5=85=A5?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=20.agents\skills,=E9=9A=8F=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E8=B5=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../skills/sd-enhanced-components/SKILL.md | 14 + .../skills/winforms-designer-package/SKILL.md | 330 ++++++++++++++++++ .../references/icon-generation.md | 169 +++++++++ 3 files changed, 513 insertions(+) create mode 100644 .agents/skills/winforms-designer-package/SKILL.md create mode 100644 .agents/skills/winforms-designer-package/references/icon-generation.md diff --git a/.agents/skills/sd-enhanced-components/SKILL.md b/.agents/skills/sd-enhanced-components/SKILL.md index f532622..29c67dd 100644 --- a/.agents/skills/sd-enhanced-components/SKILL.md +++ b/.agents/skills/sd-enhanced-components/SKILL.md @@ -74,6 +74,20 @@ description: SharpDevelop 5.x WinForms 设计器**全部工具箱**(5 分类 8 - **中文路径显示八进制转义**(`\346\210\220...`):git 默认 quotepath,排查时加 `-c core.quotepath=false`,如 `git -c core.quotepath=false ls-tree -r --name-only HEAD`。 - **LF→CRLF warning**:无害,忽略。 - **push 后验证**:`git ls-remote origin` 的 HEAD 与本地 `git log -1` 哈希一致才算成功。 +9. **打包发版完整流程(2026-10-01 定稿,用户强制:打包必须按此技能走)**。发版 = 8 步固定链,顺序不可换,每步有验证点: + ``` + ① 递增版本号 → ② 清进程+编译 → ③ make_package 组装 → ④ ISCC 安装包 + → ⑤ make_update_package 更新包 → ⑥ git 提交推送 → ⑦ Gitea Release → ⑧ 验收 + ``` + - **① 递增版本号**:`powershell -File tools\递增版本号.ps1`(默认修订号+1;大版本传参如 `1.1.0`)。输出应显示 AssemblyInfo 与 setup.iss 两处同步成功。 + - **② 清进程+编译**:`Get-Process | Where-Object { $_.Path -like '*成套报价软件*' -or $_.Path -like '*SharpDevelop Projects*' } | Stop-Process -Force`(先杀占用,否则组装/替换文件报"正在使用");然后**工作目录切到 `成套报价软件\` 子目录**(rsp 相对路径基于它,在仓库根跑必报 CS2001):`& "C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe" "@..\tools\verify_build.rsp" /warn:1`,ExitCode=0 且无 warning 才继续。 + - **③ 组装发布目录**:`python tools\make_package.py`(仓库根跑)。核对输出:"DLL 复制完成,缺失: 无"、cadbt 段只有 node+CAD-Viewer(独立 exe 已退役 2026-10-01)、无 "!!" 警告。 + - **④ 安装包**:`& "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" "release\成套报价软件\setup.iss"`。lzma2 压缩约 2 分钟,产物 `release\成套报价软件\Output\成套报价软件_Setup_<版本>.exe`,"Successful compile" 才算成;文件名必须带版本号。 + - **⑤ 更新包**:先改 `tools\make_update_package.py` 两处——`PKG` 目录名带版本(如 `更新包_1.0.4`)+ `说明` 本期文案(只写用户可感知的变化,使用方法要写清覆盖哪个文件、验证什么);再 `python tools\make_update_package.py`。当前更新包只含 成套报价软件.exe+使用说明(独立 pdfbt.exe 已退役不随包)。 + - **⑥ git**:`git add -A` + `git commit -m "发版 v<版本>:..."` + `git push`。 + - **⑦ Gitea Release**:tag=`v<版本>` 与 AssemblyInfo 一致。API 创建(PowerShell:`Invoke-RestMethod -Headers @{Authorization="token <令牌>"}` POST `/api/v1/repos/0999we/ctbjrj/releases`,body 含 tag_name/name/body,**中文 body 必须转字节** `[Text.Encoding]::UTF8.GetBytes($body)` 否则乱码);附件上传用 `System.Net.Http.HttpClient`+`MultipartFormDataContent`(字段名 `attachment`),PS5.1 的 Invoke-RestMethod 无 -Form。Release 说明与更新包说明同源。 + - **⑧ 验收**:附件大小合理(Setup ~120MB、更新包 <1MB);提醒用户装机验证:关于软件页显示 v<版本>、扒图窗口可用。 + - **故障速查**:ISCC 报文件找不到→make_package 没跑或发布目录被清;附件 401→令牌失效(见第 8 条);组装报"正在使用"→②的进程没杀干净(node/AI 引擎)。 ## 一、项目前提(缺一不可) diff --git a/.agents/skills/winforms-designer-package/SKILL.md b/.agents/skills/winforms-designer-package/SKILL.md new file mode 100644 index 0000000..a6330d6 --- /dev/null +++ b/.agents/skills/winforms-designer-package/SKILL.md @@ -0,0 +1,330 @@ +--- +name: winforms-designer-package +description: 把编译好的 .NET Framework 4.8 WinForms 项目(exe + 依赖 DLL + 图标)用 Inno Setup 6 打包成可分发的单文件安装包(setup.exe)。当用户说"打包""安装包""setup""发布""做成安装程序""Inno Setup""生成 setup.exe""打安装包""做成 exe 安装包""封装发布""更新包",或要求把 F:\pycode\wpf\src\WinFormsDesigner.Fx48 下 Samples\COOO\scr\<项目名> 里编译好的 exe 打包成安装包,或对"成套报价软件"项目发版打包(含版本号递增/Gitea Release/更新包的完整 8 步发版链,见文末专章)时,必须使用本技能。即使用户没说"Inno Setup",只要是要把编译产物做成可分发/可安装的形式就触发。涵盖:识别要打包的文件、写 setup.iss 脚本、中文语言包安装、编译安装包、图标嵌入、Program Files 权限处理、卸载清理等完整发布流程。 +--- + +# WinForms 设计器项目打包器 + +把 winforms-designer-compile 技能编译出的 `<项目名>.exe` + 依赖 DLL + 图标,用 **Inno Setup 6** 打包成一个**单文件 setup.exe** 安装包,带中文安装向导、开始菜单/桌面快捷方式、控制面板卸载项。 + +> 本技能流程在 GCS 铜排壳体计算项目中**完整实战验证过**,每个步骤都真实跑通。 + +## ⛔ 铁律:不做测试安装,直接交付正式版(用户强制,2026-09-24) + +**禁止 AI 自己做任何安装测试**——包括但不限于: +- 编译"测试变体"安装包(降权限 PrivilegesRequired=lowest / 改临时目录)再静默安装验证 +- 把 setup.exe 装到本机临时目录、虚拟机去验证启动/卸载 +- 安装后自动运行程序验证功能 + +**正确做法**:按需求直接编译**正式版** setup.exe(含正确的默认安装目录、权限、快捷方式),**立即交付给用户**,由**用户本人**安装和测试。测试中发现的任何问题由用户反馈后在下轮修复。 + +**原因**(xuanxing 项目实测教训):AI 侧测试变体要在用户桌面弹 UAC/错误框(如降权限后写公共桌面快捷方式报 `IPersistFile::Save 失败 0x80070005 拒绝访问`)、留残留目录和卸载表项,还要反复等待静默安装——比用户直接装一遍正式版**更慢更吵**。打包环节唯一要"验证"的只有 ISCC 编译输出 `Successful compile` 和产物存在。 + +## 前提:这是编译之后的环节 + +本技能接在 winforms-designer-compile 之后。进本技能前,`scr\<项目名>\` 目录里应该已经有: +- `<项目名>.exe`(csc 编译产物) +- 第三方 `.dll`(如 `AdvancedDataGridView.dll`) +- 可选 `app.ico`(程序图标) + +如果还没有 exe,先去用 winforms-designer-compile 技能编译。 + +## 触发前提 + +- Inno Setup 6 已安装(默认在 `C:\Program Files (x86)\Inno Setup 6\`)。没装的话提示用户先装。 +- 项目目录约定在 `F:\pycode\wpf\src\WinFormsDesigner.Fx48\bin\Debug\net48\Samples\COOO\scr\<项目名>\`。 + +--- + +## 📋 完整打包流程(5 步) + +### 第 1 步:识别需要打包的文件 + +进项目目录 `scr\<项目名>\`,按下表分类: + +| 文件类型 | 来源 | 是否打包 | +|---------|------|---------| +| `<项目名>.exe` | csc 编译产物 | ✅ 必需 | +| 第三方 `.dll`(如 `AdvancedDataGridView.dll`) | 设计器/代码依赖 | ✅ 必需(缺了程序跑不起来) | +| `app.ico`(若有) | 自制图标 | ✅ 建议带(用于快捷方式) | +| `.cs` / `.ui.cs` / `.xml` 源码 | — | ❌ 不打包 | +| `build.bat` / `build.rsp` / `*.log` | — | ❌ 不打包 | +| `单价.cfg` / `*.cfg` 配置 | 运行时生成 | ⚠️ 一般不打包(程序用默认值重建) | + +**判断要打包哪些 DLL**:读 `<窗体名>.dependencies.txt`(设计器生成的权威清单),或直接 `ls *.dll` 看项目里实际存在的 DLL。 + +### 第 2 步:建发布目录 + 复制运行时文件 + +在 `Samples\COOO\` 下建 `release\<项目名>\`,**只复制运行时必需文件**(源码和构建文件不进): + +```bash +mkdir -p 'F:\pycode\wpf\src\WinFormsDesigner.Fx48\bin\Debug\net48\Samples\COOO\release\<项目名>' +cp 'F:\...\scr\<项目名>\<项目名>.exe' 'F:\...\release\<项目名>\/' +cp 'F:\...\scr\<项目名>\AdvancedDataGridView.dll' 'F:\...\release\<项目名>\/' # 按实际依赖 +cp 'F:\...\scr\<项目名>\app.ico' 'F:\...\release\<项目名>\/' # 若有图标 +``` + +### 第 3 步:检查 Inno Setup 环境 + 中文语言包 + +**检查 Inno Setup**: + +```bash +ls 'C:\Program Files (x86)\Inno Setup 6\ISCC.exe' 2>&1 && echo 'Inno Setup 已安装' || echo '未安装,需先装 Inno Setup 6' +``` + +**中文语言包**:Inno Setup 6 **默认不带中文简体语言包**。要做中文界面必须下载 `ChineseSimplified.isl` 放到 `C:\Program Files (x86)\Inno Setup 6\Languages\`(需管理员权限)。下载用 kira-96 社区维护版(已验证可用): + +```bash +# 1. 下载到可写位置 +curl -sL -o ChineseSimplified.isl 'https://raw.githubusercontent.com/kira-96/Inno-Setup-Chinese-Simplified-Translation/main/ChineseSimplified.isl' + +# 2. 需管理员权限复制到 Program Files +powershell -NoProfile -Command "Start-Process cmd -ArgumentList '/c copy /Y ChineseSimplified.isl \"C:\Program Files (x86)\Inno Setup 6\Languages\\\"' -Verb RunAs -Wait" + +# 3. 验证 +ls 'C:\Program Files (x86)\Inno Setup 6\Languages\ChineseSimplified.isl' +``` + +> ⚠️ **下载的语言包必须带 UTF-8 BOM**。下载后检查,没有 BOM 就补一个,否则中文界面会乱码: +> ```bash +> python -c " +> import os +> p=r'C:\Program Files (x86)\Inno Setup 6\Languages\ChineseSimplified.isl' +> d=open(p,'rb').read() +> if d[:3]!=b'\xef\xbb\xbf': open(p,'wb').write(b'\xef\xbb\xbf'+d); print('已补 BOM') +> " +> ``` +> (Program Files 需要管理员权限写入,可能要提权 PowerShell 跑。) + +### 第 4 步:编写 setup.iss 脚本 + +在 `release\<项目名>\` 目录下建 `setup.iss`。下面是**实战验证过的完整模板**(GCS 铜排壳体计算项目),按需替换 `<...>` 占位: + +```iss +; ============================================================ +; <项目名> 安装包脚本 — Inno Setup 6 +; 文件必须保存为 UTF-8 with BOM 编码(中文才能正确显示) +; ============================================================ + +[Setup] +AppName=<项目显示名> +AppVersion=1.0 +AppPublisher=<作者或公司> +DefaultDirName=C:\<项目目录名> +DefaultGroupName=<项目显示名> +DisableProgramGroupPage=yes +OutputDir=Output +OutputBaseFilename=<项目名>_Setup +Compression=lzma2 +SolidCompression=yes +WizardStyle=modern +ArchitecturesAllowed=x64 +ArchitecturesInstallIn64BitMode=x64 +PrivilegesRequired=admin + +; 安装包自身图标(用程序的 app.ico) +SetupIconFile=app.ico +UninstallDisplayIcon={app}\app.ico +UninstallDisplayName=<项目显示名> + +[Languages] +Name: "chinesesimp"; MessagesFile: "compiler:Languages\ChineseSimplified.isl" + +[Tasks] +Name: "desktopicon"; Description: "创建桌面快捷方式(&D)"; GroupDescription: "附加图标:" + +[Files] +Source: "<项目名>.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "<第三方DLL>.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "app.ico"; DestDir: "{app}"; Flags: ignoreversion + +[Icons] +; 开始菜单快捷方式 +Name: "{group}\<项目显示名>"; Filename: "{app}\<项目名>.exe"; IconFilename: "{app}\app.ico"; Comment: "<项目描述>" +Name: "{group}\卸载 <项目显示名>"; Filename: "{uninstallexe}" +; 桌面快捷方式 +Name: "{commondesktop}\<项目显示名>"; Filename: "{app}\<项目名>.exe"; IconFilename: "{app}\app.ico"; Tasks: desktopicon + +[Run] +; 安装完成可选启动 +Filename: "{app}\<项目名>.exe"; Description: "立即启动 <项目显示名>"; Flags: nowait postinstall skipifsilent + +[UninstallDelete] +; 卸载时彻底清理 {app} 目录 +Type: filesandordirs; Name: "{app}" +``` + +#### setup.iss 段语法说明 + +| 段 | 作用 | +|----|------| +| `[Setup]` | 安装包基本信息、压缩、平台、权限。`DefaultDirName` = 默认安装位置(`{pf}` = Program Files,或直接写 `C:\xxx`)| +| `[Languages]` | 界面语言。中文用 `chinesesimp` + `ChineseSimplified.isl` | +| `[Tasks]` | 可勾选任务。桌面快捷方式任务默认勾选(**不要加 `Flags: checked`**,新版不认)| +| `[Files]` | 要打包的文件。`Flags: ignoreversion` = 不比对版本直接覆盖 | +| `[Icons]` | 快捷方式。`{group}` = 开始菜单文件夹,`{app}` = 安装目录,`{commondesktop}` = 公共桌面 | +| `[Run]` | 安装后动作。`postinstall` = 勾选"立即启动",`skipifsilent` = 静默安装时不启动 | +| `[UninstallDelete]` | 卸载时额外删除。`filesandordirs` 删整个目录及内容 | + +#### ⚠️ setup.iss 的两个必做细节 + +**细节 1:必须 UTF-8 with BOM 编码**。中文(AppName、说明等)才能正确显示,否则装界面是乱码。写完 .iss 后检查并补 BOM: + +```bash +python -c " +data = open('setup.iss','rb').read() +if data[:3] != b'\xef\xbb\xbf': + open('setup.iss','wb').write(b'\xef\xbb\xbf' + data) + print('已加 UTF-8 BOM') +" +``` + +**细节 2:`[Tasks]` 桌面快捷方式不要写 `Flags: checked`**。新版 Inno Setup 不认这个 flag,编译报 "Parameter Flags includes an unknown flag"。任务默认就是勾选的,直接省略 Flags。 + +### 第 5 步:编译安装包 + +```bash +cd 'F:\pycode\wpf\src\WinFormsDesigner.Fx48\bin\Debug\net48\Samples\COOO\release\<项目名>' +'C:\Program Files (x86)\Inno Setup 6\ISCC.exe' setup.iss 2>&1 | tail -5 +``` + +**成功标志**:末尾输出 `Successful compile`,并在 `Output\<项目名>_Setup.exe` 生成安装包(通常 1~3 MB,LZMA2 压缩后远小于源文件总和)。 + +> 编译时 `Warning: A message named "..." has not been defined for "chinesesimp"` 是**正常的**——中文语言包版本与 Inno Setup 主程序版本不完全同步,缺失的消息自动用英文 Default.isl 回退,不影响功能。 + +--- + +## 发布目录最终结构 + +``` +Samples\COOO\release\<项目名>\ +├── <项目名>.exe ← 编译产物 +├── <第三方DLL>.dll ← 依赖 +├── app.ico ← 图标 +├── setup.iss ← Inno Setup 脚本(源文件,留着方便改版重打包) +└── Output\ + └── <项目名>_Setup.exe ← 最终交付的安装包 +``` + +**交付物就是 `Output\<项目名>_Setup.exe` 这一个文件**,用户双击即可安装,自带中文向导、快捷方式、卸载项。 + +--- + +## 图标制作(可选,让 exe 有专属图标) + +如果想给程序做专属图标嵌入 exe,详见 `references/icon-generation.md`(Pillow 纯 Python 绘制多尺寸 ICO 的完整模板,零原生依赖)。 + +简要流程: +1. 用 Pillow 绘制 512×512 图标 → 存成多尺寸 `app.ico` +2. 编译时在 `build.rsp` 加 `/win32icon:app.ico` +3. 窗体构造函数加 `LoadAppIcon()`(`Icon.ExtractAssociatedIcon`),让窗口/任务栏也显示图标 + +> 图标嵌入 exe 是**编译环节**做的事(/win32icon 是 csc 参数),打包环节只是把已有的 app.ico 一起带进安装包用于快捷方式。两环节配合。 + +--- + +## 常见问题排查 + +| 现象 | 原因 | 解决 | +|------|------|------| +| 安装界面中文乱码 | setup.iss 不是 UTF-8 BOM 编码 | 按第 4 步"细节 1"补 BOM 后重新编译 | +| `Couldn't open include file "...ChineseSimplified.isl"` | 没装中文语言包 | 按第 3 步下载 ChineseSimplified.isl 到 Inno Setup 的 Languages 目录 | +| 中文语言包装了还乱码 | 语言包 .isl 没 UTF-8 BOM | 给 .isl 文件补 BOM(需管理员权限)| +| `Parameter "Flags" includes an unknown flag` | `[Tasks]` 写了 `Flags: checked` | 删掉 `Flags: checked`,任务默认就勾选 | +| 安装到 Program Files 后程序保存配置报错 | exe 目录无写入权限 | 程序里配置文件改存 `%APPDATA%\<项目名>\`(用 `Environment.SpecialFolder.ApplicationData`),并在 `[UninstallDelete]` 加 `{userappdata}\<项目名>` 卸载时清理 | +| 装的 exe 双击没反应 | 缺少第三方 DLL | 检查 `[Files]` 是否把所有依赖 DLL 都打包了 | +| 卸载后配置文件残留 | `[UninstallDelete]` 没覆盖配置目录 | 加 `Type: filesandordirs; Name: "{userappdata}\<项目名>"` | +| `error CS0016: 未能写入输出文件`(重新编译 exe 时) | exe 被安装包运行的进程占用 | 先关掉运行中的程序再重新编译 | + +--- + +## 发布前的完整流程(汇总) + +``` +1. [编译] 改完 .cs → 用 winforms-designer-compile 编译出 exe +2. [识别] 读 dependencies.txt 确定依赖 DLL +3. [建目录] 建 release\<项目名>\,复制 exe + DLL + ico +4. [写脚本] 写 setup.iss(用模板,改占位)+ 确保有 UTF-8 BOM +5. [语言包] 确认装了中文语言包(没有就下载,记得带 BOM) +6. [编译包] ISCC.exe setup.iss → 生成 Output\<项目名>_Setup.exe +7. [交付] 不做任何测试安装(见顶部铁律)——把 Output\<项目名>_Setup.exe 直接交给用户,由用户自行安装测试 +``` + +--- + +## 🏭 项目专章:成套报价软件 完整发版体系(2026-10-01 定稿,用户强制) + +> 仓库根:`C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件`。 +> 该项目发版**不走上面的通用 5 步**,走下面的 **8 步固定链**(多了版本号递增、更新包、git、Gitea Release 环节)。 +> 用户说"打包/发版/上传到 git"针对本项目时,必须按本节执行,顺序不可换。 + +``` +① 递增版本号 → ② 清进程+编译 → ③ make_package 组装 → ④ ISCC 安装包 +→ ⑤ make_update_package 更新包 → ⑥ git 提交推送 → ⑦ Gitea Release → ⑧ 验收 +``` + +### ① 递增版本号(版本号铁律:每次发版必须递增) + +```powershell +powershell -File tools\递增版本号.ps1 # 修订号+1;大版本: tools\递增版本号.ps1 1.1.0 +``` +唯一事实源 = `成套报价软件\Properties\AssemblyInfo.cs` 的 AssemblyVersion(脚本同步 setup.iss 的 AppVersion 与安装包文件名)。关于软件页运行时自动读程序集版本。输出必须显示两处 `[OK]`。 + +### ② 清进程 + 编译 + +```powershell +Get-Process | Where-Object { $_.Path -like '*成套报价软件*' -or $_.Path -like '*SharpDevelop Projects*' } | Stop-Process -Force +cd 成套报价软件 # ★ rsp 相对路径基于此子目录,在仓库根跑必报 CS2001 +& "C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe" "@..\tools\verify_build.rsp" /warn:1 +``` +ExitCode=0 且无 warning 才继续(rsp 默认 /warn:0 压警告,排查必须 /warn:1)。 + +### ③ 组装发布目录 + +```powershell +python tools\make_package.py # 仓库根跑 +``` +核对输出:`DLL 复制完成,缺失: 无`;cadbt 段只有 node+CAD-Viewer(独立扒图 exe 已于 2026-10-01 退役);无 `!!` 警告。 + +### ④ ISCC 安装包 + +```powershell +& "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" "release\成套报价软件\setup.iss" +``` +lzma2 约 2 分钟;产物 `release\成套报价软件\Output\成套报价软件_Setup_<版本>.exe`(约 120MB);`Successful compile` 才算成。中文语言包已装(chinesesimp 消息缺失 Warning 属正常)。 + +### ⑤ 更新包 + +先改 `tools\make_update_package.py` 两处:`PKG` 目录名带版本(如 `更新包_1.0.4`)+ `说明` 本期文案(只写用户可感知变化;使用方法写清覆盖哪个文件、怎么验证版本)。再跑 `python tools\make_update_package.py`。当前只含 成套报价软件.exe+使用说明(<1MB)。 + +### ⑥ git 提交推送 + +`git add -A` → `git commit -m "发版 v<版本>:..."` → `git push`(remote 已内嵌令牌)。 + +### ⑦ Gitea Release + +tag=`v<版本>`(与 AssemblyInfo 一致)。PowerShell API 模式(令牌与仓库地址见 sd-enhanced-components 技能第 8 条或项目记忆): +- 建 Release:`Invoke-RestMethod -Method Post -Headers @{Authorization="token <令牌>"} -ContentType 'application/json; charset=utf-8' -Body ([Text.Encoding]::UTF8.GetBytes($body))`——**中文 body 必须 UTF8.GetBytes 否则乱码** +- 传附件:`System.Net.Http.HttpClient` + `MultipartFormDataContent`(字段名 `attachment`,PS5.1 无 -Form) +- Release 说明与更新包说明同源 + +### ⑧ 验收 + +附件大小合理(Setup ~120MB、更新包 <1MB);提醒用户装机验证:**关于软件页显示 v<版本>**、CAD/PDF 扒图窗口可用。 + +### 故障速查(本项目专属) + +| 现象 | 处置 | +|------|------| +| 组装/替换文件报"正在使用" | ② 的进程没杀干净(查 node/AI 引擎/主程序) | +| 编译报 CS2001 全部源文件找不到 | 工作目录错了——必须 `成套报价软件\` 子目录 | +| ISCC 报文件找不到 | make_package 没跑或发布目录被清,回到 ③ | +| Release 附件上传 401 | 令牌失效——请用户在 Gitea 设置→应用重新生成(git remote 和 API 同步换) | +| push 被拒 fetch first | 用户网页传过文件——`git pull --rebase` 再 push,绝不 force push | + +--- + +## 配套技能 + +- **winforms-designer-compile**:本技能的上游。先用它把 .cs + .ui.cs 编译成 exe,再交给本技能打包。 +- **winforms-designer-xml**:生成/校验窗体 XML 布局,是整个流水线(XML → 编译 → 打包)的起点。 diff --git a/.agents/skills/winforms-designer-package/references/icon-generation.md b/.agents/skills/winforms-designer-package/references/icon-generation.md new file mode 100644 index 0000000..c7bf6a0 --- /dev/null +++ b/.agents/skills/winforms-designer-package/references/icon-generation.md @@ -0,0 +1,169 @@ +# 图标生成(Pillow 纯 Python 绘制 → 多尺寸 ICO) + +用 Pillow 直接绘制图标 → 多尺寸 `.ico`,嵌入 exe。**纯 Python,零原生依赖**(不依赖 cairo/svglib,那些需要 `libcairo-2.dll`,Windows 上通常没有)。 + +> 本模板在 GCS 铜排壳体计算项目中验证过,产出 `app.ico`(16/32/48/64/128/256 六尺寸),通过 `/win32icon:app.ico` 嵌入 exe,并在所有窗体构造函数里 `ExtractAssociatedIcon` 设置窗口/任务栏图标。 + +## 为什么不用 SVG→ICO + +常规路径 cairosvg / svglib+reportlab 都依赖原生 `libcairo-2.dll`,Windows 默认没有,装起来麻烦。**Pillow 直接画** 是零依赖、最稳的方案,缺点是只能画几何图形(渐变、滤镜要自己用代码模拟)。 + +## 核心思路 + +1. **高分辨率绘制**:在 512×512 画布上画完整图标(细节清晰) +2. **Pillow 自动缩放**:保存 ICO 时传 `sizes=[(16,16),(32,32),...]`,Pillow 自动从大图缩出各尺寸 +3. **模拟渐变**:逐行/逐列插值颜色(`lerp_color`),Pillow 没有原生渐变填充 +4. **圆角矩形**:用临时 mask 层 `rounded_rectangle` 做遮罩,再 paste + +## 完整模板(铜排+闪电图标,可直接改色改形) + +```python +# -*- coding: utf-8 -*- +"""用 Pillow 绘制图标 -> 多尺寸 app.ico。纯 Python,零原生依赖。 +设计:深蓝→青蓝渐变圆角背景 + 三根铜排 + 接线端子 + 闪电(电流)。""" +import os +from PIL import Image, ImageDraw + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +ICO_PATH = os.path.join(SCRIPT_DIR, "app.ico") +SIZES = [16, 32, 48, 64, 128, 256] +RENDER_SIZE = 512 # 高分辨率绘制 + + +def lerp_color(c1, c2, t): + """线性插值两个 (r,g,b) 颜色,t∈[0,1]。""" + return ( + int(c1[0] + (c2[0] - c1[0]) * t), + int(c1[1] + (c2[1] - c1[1]) * t), + int(c1[2] + (c2[2] - c1[2]) * t), + ) + + +def draw_gradient_rect(img, box, c_top, c_mid, c_bot, radius): + """带垂直渐变的圆角矩形。用逐行填充近似渐变 + 圆角 mask。""" + x0, y0, x1, y1 = box + w, h = x1 - x0, y1 - y0 + grad = Image.new("RGBA", (w, h), (0, 0, 0, 0)) + gp = grad.load() + for y in range(h): + t = y / max(h - 1, 1) + if t < 0.55: + c = lerp_color(c_top, c_mid, t / 0.55) + else: + c = lerp_color(c_mid, c_bot, (t - 0.55) / 0.45) + for x in range(w): + gp[x, y] = (c[0], c[1], c[2], 255) + mask = Image.new("L", (w, h), 0) + ImageDraw.Draw(mask).rounded_rectangle([0, 0, w - 1, h - 1], radius=radius, fill=255) + img.paste(grad, (x0, y0), mask) + + +def draw_copper_bar(img, box, radius): + """画一根铜排:铜橙→金黄渐变 + 左侧高光条。box=(x,y,w,h)。""" + bx, by, bw, bh = box + c_top, c_mid, c_bot = (255, 210, 127), (232, 163, 61), (181, 101, 29) + grad = Image.new("RGBA", (bw, bh), (0, 0, 0, 0)) + gp = grad.load() + for y in range(bh): + t = y / max(bh - 1, 1) + c = lerp_color(c_top, c_mid, t / 0.45) if t < 0.45 else lerp_color(c_mid, c_bot, (t - 0.45) / 0.55) + for x in range(bw): + gp[x, y] = (c[0], c[1], c[2], 255) + mask = Image.new("L", (bw, bh), 0) + ImageDraw.Draw(mask).rounded_rectangle([0, 0, bw - 1, bh - 1], radius=radius, fill=255) + img.paste(grad, (bx, by), mask) + # 高光条 + shine_w = max(bw // 4, 6) + shine = Image.new("RGBA", (shine_w, bh - 10), (0, 0, 0, 0)) + sp = shine.load() + for y in range(shine.height): + a = int(180 * (1 - y / shine.height)) + for x in range(shine_w): + sp[x, y] = (255, 255, 255, a) + smask = Image.new("L", (shine_w, shine.height), 0) + ImageDraw.Draw(smask).rounded_rectangle([0, 0, shine_w - 1, shine.height - 1], radius=max(radius - 2, 2), fill=255) + img.paste(shine, (bx + bw // 6, by + 5), smask) + + +def draw_lightning(img, cx, cy, scale): + """画一道闪电。金黄色,带白色描边。""" + pts = [(4, -34), (-10, 4), (2, 4), (-8, 30), (16, -4), (4, -4)] + pts = [(cx + p[0] * scale, cy + p[1] * scale) for p in pts] + d = ImageDraw.Draw(img) + for dx, dy in [(-2, 0), (2, 0), (0, -2), (0, 2), (-2, -2), (2, 2), (-2, 2), (2, -2)]: + d.polygon([(p[0] + dx, p[1] + dy) for p in pts], fill=(255, 255, 255, 255)) + d.polygon(pts, fill=(255, 224, 102, 255)) + + +def draw_terminal(img, cx, cy, r): + """铜排顶部接线端子。""" + d = ImageDraw.Draw(img) + d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=(244, 228, 188, 255), + outline=(139, 90, 43, 255), width=2) + d.ellipse([cx - r // 3, cy - r // 3, cx + r // 3, cy + r // 3], fill=(58, 36, 16, 255)) + + +def draw_icon(size): + s = size + img = Image.new("RGBA", (s, s), (0, 0, 0, 0)) + margin = int(s * 0.03) + radius = int(s * 0.17) + # 背景渐变 + draw_gradient_rect(img, (margin, margin, s - margin, s - margin), + c_top=(15, 61, 94), c_mid=(27, 108, 168), c_bot=(42, 157, 143), + radius=radius) + # 三根铜排(长短递减) + bar_w = int(s * 0.133) + bar_radius = max(int(s * 0.023), 3) + bars = [ + (int(s * 0.227), int(s * 0.203), int(s * 0.594)), # 最长 + (int(s * 0.434), int(s * 0.281), int(s * 0.516)), + (int(s * 0.641), int(s * 0.375), int(s * 0.422)), # 最短 + ] + term_r = int(s * 0.027) + for bx, by, bh in bars: + draw_terminal(img, bx + bar_w // 2, by - term_r + 2, term_r) + draw_copper_bar(img, (bx, by, bar_w, bh), bar_radius) + # 闪电 + draw_lightning(img, int(s * 0.512), int(s * 0.613), s / 512.0) + return img + + +def main(): + hi = draw_icon(RENDER_SIZE) + hi.save(ICO_PATH, format="ICO", sizes=[(sz, sz) for sz in SIZES]) + print("已生成:", ICO_PATH, "尺寸:", SIZES) + +if __name__ == "__main__": + main() +``` + +## 改造成其他图标 + +要换图标主题时,改 3 处: +1. **背景色**:`draw_gradient_rect` 的 `c_top/c_mid/c_bot`(RGB 三元组) +2. **主体元素**:替换 `draw_copper_bar` / `draw_lightning` 为你要画的元素(用 `ImageDraw` 的 ellipse/rectangle/polygon/line) +3. **布局坐标**:`draw_icon` 里各元素的 x/y/w/h(按 `s * 比例` 计算,这样任意尺寸都自适应) + +## 嵌入 exe 的两步(配合 SKILL.md 主文档) + +**1) 编译加参数**:`build.rsp` 或 csc 命令加 `/win32icon:app.ico` + +**2) 窗体加图标加载代码**(exe 嵌入图标后,Form 默认不自动用它): + +```csharp +private void LoadAppIcon() +{ + try { this.Icon = System.Drawing.Icon.ExtractAssociatedIcon( + System.Windows.Forms.Application.ExecutablePath); } + catch { } +} +// 在每个窗体构造函数 InitializeComponent() 之后调用 LoadAppIcon(); +``` + +这样 exe 文件图标、窗口标题栏、任务栏、Alt+Tab 都显示这个图标。 + +## 小尺寸清晰度技巧 + +- 16×16 这种小尺寸,复杂图案会糊成一片。设计时**主体元素要大、要少**(1~3 个),避免细节。 +- Pillow 从 512 缩到 16 用的是抗锯齿,比手画 16 清晰。如果还是糊,可以 `draw_icon(16)` 单独画一版简化图标(只保留最核心的轮廓)再合并到 ICO。