打包技能入仓库:winforms-designer-package(含成套报价软件 8 步发版专章:版本号递增/组装/ISCC/更新包/git/Gitea Release/验收/故障速查)放入项目 .agents\skills,随项目走
This commit is contained in:
@@ -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 引擎)。
|
||||
|
||||
## 一、项目前提(缺一不可)
|
||||
|
||||
|
||||
@@ -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 → 编译 → 打包)的起点。
|
||||
@@ -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。
|
||||
Reference in New Issue
Block a user