news 2026/9/16 3:50:59

用Qt Installer Framework制作Windows离线安装包:从零到实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用Qt Installer Framework制作Windows离线安装包:从零到实战

把Qt程序发给别人用,最怕听到什么?十有八九是这句:“我双击了,但是启动报错,少了个Qt6Core.dll。”这种事发生一次,我就决定不再用绿色版文件夹交付项目了。后来我换成了Qt官方出的 Qt Installer Framework,在win10上做一个离线安装包,客户拿到一个exe,双击就进入熟悉的安装向导,装完有桌面图标,有开始菜单,控制面板能卸载,感觉立刻“正式”了不少。这篇文章就从这个场景出发,讲讲我用 Qt Installer Framework 4.6.1 做 win10 + 离线安装包 的最基础流程。我尽量不用太深的术语,把目录结构、配置文件、打包命令和踩过的坑一次说清楚,适合刚接触QIFW的Qt开发者,也适合那些已经用CMake或qmake编译出exe、但还没想好怎么交付给同事或客户的人。

1. 先回答一个基础问题:为什么我不用“绿色版文件夹”交付

1.1 绿包的三个老问题

网上经常有人推荐“把整个release文件夹压缩发过去”,也就是所谓的绿色版。自己开发时这样确实方便,但要交付给别人的时候,麻烦就来了。

第一,缺运行库。Qt程序不是一个exe单独就能跑的,它依赖Qt6Core.dll、Qt6Gui.dll、Qt6Widgets.dll,还依赖platforms/qwindows.dll这些插件。windeployqt可以帮你把这些库拷全,但如果你只是在开发机上直接压缩文件夹,很可能漏掉某些插件,目标机器上就会报“找不到Qt平台插件”或“无法定位程序输入点”之类的错。这个问题极其常见,而且对不懂技术的人来说简直是灾难,他根本不知道该怎么办。

第二,没有卸载入口。绿色版如果用户不想要了,只能手动删文件夹。删不干净还好说,如果安装到了C盘Program Files目录下,普通用户根本没有权限乱删,到时候还会怪你的软件“装上了就卸不掉”。

第三,升级困难。绿包发下一个版本,用户要手动覆盖文件,配置文件、数据库文件、旧版本残留很容易互相干扰。我见过不少项目因为绿包覆盖升级造成配置错乱,最后连日志都找不回来。

所以,但凡你是认真要交付一个桌面软件,安装器几乎是必须的。它帮你做了文件拷贝、目录选择、权限提升、快捷方式创建、卸载清理这么多事,不是“多个文件而已”。

1.2 Qt Installer Framework在打包方案里的位置

当时我摆在面前的选择有这么几类:NSIS、Inno Setup、Qt Installer Framework,以及CMake里的CPack。

  • NSIS:很老牌,脚本灵活,但写起来反人类,而且界面完全要靠插件美化,离Qt的“那种感觉”很远。
  • Inno Setup:上手快,Pascal脚本也简单,做普通Windows安装器完全够用,但它不是为组件化、在线更新设计的。
  • CPack:用CMake的话可以直接生成NSIS或Inno的安装器,胜在集成,但遇到自定义安装流程还是绕不开上面的脚本。
  • Qt Installer Framework:Qt官方出品,最大的特点是天生为Qt程序服务,支持组件化安装、模块依赖关系、在线/离线更新、维护工具(也就是安装后那个MaintenanceTool)。如果你还会做Qt Widgets/QML程序,QIFW的整套逻辑你会觉得很熟悉。

当然,QIFW不是没有缺点。它的自定义界面能力比较弱,默认向导样式偏Linux风格,想在Windows上做出特别现代的安装界面需要改很多代码。但对我们大多数人来说,“官方、稳定、组件化、支持离线”这几个点已经足够了。尤其Qt Installer Framework 4.6.1这个版本,配合Qt 6环境已经相当成熟,不用再跟自己较劲选哪个脚本语言。

2. 准备QIFW 4.6.1:下载、目录、Win10下的环境细节

2.1 从Qt官方渠道拿到工具

拿到QIFW最正规的方式是打开Qt在线安装器,在“Developer and Designer Tools”分类下找到“Qt Installer Framework 4.6.1”,勾选后安装。这里要提醒一件事:很多人会把“Qt离线安装包”和“Qt Installer Framework”弄混。网上搜“qt离线安装包下载5.14”,出来的是老版本Qt库的离线安装文件,那是给编译环境用的;而QIFW是一个帮你生成安装器的工具包,它的产物才是最终给别人用的离线安装包。两个东西完全不是一个概念。

如果你不想走在线安装器,Qt官方其实也分发了独立的QIFW压缩包,解压就能用。我建议直接装进一个专门的目录,比如D:\Qt\Tools\QtInstallerFramework\4.6.1,后面所有命令都对着这个路径来,避免和环境变量乱七八糟的冲突。

2.2 bin目录里那几样工具,分别干什么

装完之后,打开bin目录你会发现有好几个exe。对新手来说,先认识四个足够:

工具作用我实际使用的频率
binarycreator.exe根据config和packages打包生成安装器exe每次打包都用
installerbase.exe安装器运行时的底层程序,通常由binarycreator调用一般不用直接碰
archivegen.exe把一个或多个文件压缩成7z格式的资源包组件较多时用
repogen.exe根据packages生成一个可发布的软件仓库目录做在线更新时用

第一次接触的人看到这么多工具确实会头大,但记住一句话就够了:最基础的离线安装包,你只动binarycreator一个工具,其他都是为“更复杂的分发方式”准备的。第4章我会专门讲它们为什么存在。

2.3 建议先把环境变量和目录结构规划好

我推荐在环境变量PATH里加上D:\Qt\Tools\QtInstallerFramework\4.6.1\bin,这样命令行里直接敲binarycreator就行。不加也没问题,每次写全路径就好,但打包命令会显得很长,而且容易打着打着找不到目录。

Windows 10下面还有个小细节:如果你把QIFW装在C盘且目录权限管得严,第一次跑binarycreator可能会触发UAC弹窗,或者被Defender/SmartScreen拦一下。这是工具本身没问题的状态下也会遇到的情况,不用慌,确认是Qt官方文件就行。但如果你是用在线安装器装的QIFW,它会装在用户目录或指定目录,一般不会碰权限问题。

我自己的习惯是单独建一个D:\myinstaller工作目录,里面只放打包相关的config目录和packages目录,编译好的程序最终也是拷到这里来。这样项目源码、构建产物、安装器配置三者分开,后面排查问题会比较清爽。

3. 跑通最简离线安装器:config.xml、package.xml与binarycreator

3.1 先搭一个干净的目录骨架

QIFW的输入目录结构是固定的,我开始接触时也抄错过。最基本的骨架是这样:

D:\myinstaller\ ├─ config\ │ └─ config.xml └─ packages\ └─ com.example.myapp\ ├─ meta\ │ ├─ package.xml │ └─ installscript.qs └─ data\ └─ ... 这里放你程序的真实文件

我习惯把com.example.myapp作为包名,它类似Java包名的规则,目的是全局唯一。如果你只做内部软件,叫myapp也能跑,但一旦以后要做在线更新、多组件依赖,包名的唯一性就很重要了。data目录里放的是要安装到本机的文件,meta目录里放的是描述和控制安装流程的元数据。记住这个区分,后面所有逻辑都想得通。

3.2 config.xml:安装器的“门面”配置

config.xml描述的是整个安装器,而不是某一个组件。我这份是一个能直接跑的最小示例:

<?xml version="1.0" encoding="UTF-8"?> <Installer> <Name>MyApp</Name> <Version>1.0.0</Version> <Title>MyApp 安装程序</Title> <Publisher>MyCompany</Publisher> <ProductUrl>https://example.com</ProductUrl> <InstallerApplicationIcon>icon.ico</InstallerApplicationIcon> <InstallerWindowIcon>icon.ico</InstallerWindowIcon> <StartMenuDir>MyApp</StartMenuDir> <TargetDir>@ApplicationsDir@/MyApp</TargetDir> <RequireAdmin>true</RequireAdmin> <MaintenanceToolName>MaintenanceTool.exe</MaintenanceToolName> </Installer>

逐个说下重点:

  • NameVersion是安装器识别自己的关键信息,控制面板卸载时会用到,版本号的格式最好严格点,比如“1.0.0”,否则后续维护工具做更新判断会出问题。
  • Title就是安装向导窗口标题上显示的文字,建议用中文或本地化文案。
  • Publisher是发布者名称,如果以后做在线更新,它会出现在维护工具的界面,尽量固定不要乱改。
  • TargetDir是默认安装目录。@ApplicationsDir@在Windows上通常指向“C:\Program Files”,所以我会配合<RequireAdmin>true</RequireAdmin>。如果你想让普通用户装到自己的目录,也可以换成@HomeDir@/MyApp
  • MaintenanceToolName是安装完成后生成的维护工具名字,如果你不想让用户到处乱点,起名“Uninstall.exe”或“UpdateTool.exe”都行。

很多教程里会写一个<RemoteRepositories>小节,这是在线安装器的配置。对于纯离线包,下面的写法很常见:

<RemoteRepositories> <Repository> <Url></Url> </Repository> </RemoteRepositories>

它的意思是“保留仓库节点的结构,但里面没有地址”。这么做之后,安装器就不会尝试联网找更新源。后文我会结合离线/在线差异继续讲,这里你先记下:离线包要么不写RemoteRepositories,要么就把Url留空。

3.3 package.xml:组件的元数据怎么声明

再来写packages/com.example.myapp/meta/package.xml。这个文件描述的是“包内组件”的元数据:

<?xml version="1.0" encoding="UTF-8"?> <Package> <DisplayName>MyApp 主程序</DisplayName> <Description>MyApp的核心组件,必须安装</Description> <Version>1.0.0</Version> <ReleaseDate>2025-01-01</ReleaseDate> <Default>true</Default> <ForcedInstallation>true</ForcedInstallation> <Licenses> <License name="许可协议" file="license.txt"/> </Licenses> </Package>

关键字段:

  • Default=true表示默认勾选安装。
  • ForcedInstallation=true表示这个组件强制安装,用户在安装向导里不能取消勾选。像主程序这种组件,我一般都会设成强制安装,避免用户勾掉之后整个软件没法用。
  • Licenses指向的是meta目录下的license.txt。如果配置了许可协议,安装向导会多出一个“协议确认”页面,用户必须同意才能继续。对正式交付来说,这一步最好加上。
  • 如果我有多个组件,比如主程序、命令行工具、示例项目,就分别建不同目录,并在各自的package.xml里做依赖声明(比如用<Requires>标签指定依赖另一个包)。这个基础篇先不展开,但你要知道组件化是QIFW相对其他脚本工具的核心优势。

3.4 用binarycreator一次生成exe

目录和配置文件就位后,生成安装包就一条命令:

cd /d D:\myinstaller D:\Qt\Tools\QtInstallerFramework\4.6.1\bin\binarycreator.exe -c config\config.xml -p packages -o MyAppInstaller.exe --offline-only

参数含义:

  • -c指定config目录下的config.xml。
  • -p指定packages目录,binarycreator会扫描这个目录下所有包。
  • -o指定输出的安装器文件名。
  • --offline-only是强制生成离线安装器的参数。实际上当你没配置远程仓库时,生成结果已经是离线的,但加上这个参数等于给一条保险,也让后来维护代码的人一眼知道初衷。

如果生成过程中报错,最可能是XML文件写错了。我通常会在命令行追个--verbose,输出会详细很多。第一次跑通的人,看到当前目录多出一个几MB到几十MB的exe,就算是入门了。直接双击运行,你就能看到一个带“下一步、同意协议、选择目录、安装、完成”的标准安装向导。

4. 离线和在线差异:本地仓库、Updates.xml与那几条配置

4.1 离线包的“离线”到底体现在哪里

很多人刚接触QIFW时会有个疑问:我明明没有写任何在线逻辑,为什么安装器还要在config.xml里处理仓库?这得说清楚QIFW的底层模型。

QIFW本质上相当于把安装内容拆成了一个个“包”,每个包是一些7z压缩资源。当安装器运行时,它会从某一个仓库中读取这些资源列表。在线安装器把仓库放在远程HTTP服务器上,资源按需下载;离线安装器则把仓库直接内嵌在exe里,安装时从exe内部解包。

所以,“离线包”的意思是:所有安装数据都被封进了一个exe,安装过程中不访问网络。对很多内网办公环境、客户现场、甚至拿着U盘去演示的场合来说,这是最省心的一种交付形态。

判断一个安装器是否真的离线,我常用的方法是在安装时开着任务管理器或防火墙监控,看有没有对外网络请求。如果你照着前面config.xml里的空仓库配置做出来,安装过程完全是静默的。

4.2 config.xml里的仓库配置怎么影响安装器行为

时间轴回到配置那一步。如果你在config.xml里写了:

<RemoteRepositories> <Repository> <Url>https://update.example.com/repo</Url> </Repository> </RemoteRepositories>

安装器就会认为当前不是纯离线模式。用户安装时,向导会尝试访问这个地址,获取组件列表和资源索引。如果网络不通或者地址失效,安装器会卡住或报错,这就是很多人“明明给了exe,但双击后一直转圈”的原因。

所以做离线包的正确思路很明确:

  • RemoteRepositories不写或留空;
  • binarycreator加--offline-only
  • 运维和交付同事不要把exe当成万能钥匙,离线包就是离线包,别指望它能静默拉取升级内容。

4.3 archivegen和repogen在什么场景下才需要碰

如果你需要把所有组件的源文件交给一个集中仓库,让用户可以从服务器安装/升级,那就需要repogen:

D:\Qt\Tools\QtInstallerFramework\4.6.1\bin\repogen.exe -p packages D:\repo

这样会在D:\repo下生成一个目录,里面有各个组件的7z资源和Updates.xml索引文件。之后再用binarycreator做在线安装器时,把这个目录用IIS或Nginx发布出去,配置好URL就能在线安装。

而archivegen更底层,它负责把单个文件目录打包成7z:

D:\Qt\Tools\QtInstallerFramework\4.6.1\bin\archivegen.exe myapp.7z D:\build\release\*

大多数基础场景下,archivegen不用你手动调用,binarycreator内部已经帮你处理了。但理解这个层级关系很重要,不然你看到“仓库”“索引”“7z包”这些概念会一头雾水。其实一句话总结:archivegen打包单个资源,repogen组织成一个仓库,binarycreator把仓库编译成安装器exe。

5. 把真实Qt程序塞进离线包:windeployqt与快捷方式脚本

5.1 先用windeployqt补齐运行依赖

很多人在这一步卡住,是因为把自己编译的MyApp.exe丢进data目录就算完,结果装完运行还是提示缺DLL。原因很简单:你的开发机上装了Qt,系统环境里能找到Qt6Core.dll,但目标客户机器上不一定有。

所以正确做法是先对你的Release版exe运行windeployqt。以Qt 6.6 + MinGW为例:

cd /d D:\build\release D:\Qt\6.6.0\mingw_64\bin\windeployqt.exe MyApp.exe --release --no-translations

执行完,release目录下会多出大量文件,比如:

  • Qt6Core.dll、Qt6Gui.dll、Qt6Widgets.dll
  • platforms/qwindows.dll
  • styles/qmodernwindowsstyle.dll
  • imageformats/qjpeg.dll等图片格式插件
  • tls/、networkinformation/等网络插件(如果你的程序用了网络功能)

这些文件一个都不能少。它们的相对位置也很讲究,比如platforms必须放在“exe所在目录/platforms”下,Qt是通过相对路径找插件的。

5.2 按“安装后的目录结构”组织data目录

现在你就要把这些依赖文件和exe一起放到packages/com.example.myapp/data里。这里有个重要原则:data目录内的相对路径,就是安装后的相对路径。

举个例子,如果我希望安装后C:\Program Files\MyApp\platforms\qwindows.dll,那么你在data目录下就应该同样有platforms\qwindows.dll。不要把整个release文件夹再套一层,比如data\release\MyApp.exe,那样安装完用户还得进release目录才能启动,非常别扭。

我自己习惯的做法:在packages/com.example.myapp/data下,先建一个顶层目录MyApp还是直接摊开放,取决于软件的安装结构。如果data目录下只有一个exe,直接摊开放没问题;如果程序本身有很多子目录和插件,就按照项目的部署结构摆放。

5.3 用installscript.qs创建桌面和开始菜单快捷方式

光把文件装进去还不够,正式软件装完总得有个桌面图标。这个功能要靠meta目录下的installscript.qs脚本实现。名字可以随便起,但要和package.xml里的<Script>标签对应:

<Script>installscript.qs</Script>

然后在installscript.qs里写:

function Component() {} Component.prototype.createOperations = function() { component.createOperations(); if (systemInfo.productType === "windows") { component.addOperation("CreateShortcut", "@TargetDir@/MyApp.exe", "@DesktopDir@/MyApp.lnk", "workingDirectory=@TargetDir@", "iconPath=@TargetDir@/MyApp.exe", "iconId=0"); } };

几个变量解释一下:

  • @TargetDir@是最终安装目录,也就是用户在安装向导里选的那个目录;
  • @DesktopDir@是当前用户桌面路径,QIFW会在安装时解析成实际路径;
  • CreateShortcut是QIFW提供的内置操作,第一个参数是程序路径,第二个参数是快捷方式路径,后面可以带工作目录、图标路径等可选项。

如果你还想在开始菜单里放一个卸载入口,QIFW默认会生成维护工具,并在开始菜单里建一个MaintenanceTool.lnk。这个不用你手动加,只要config.xml里配置了<StartMenuDir>,安装器会自动生成。

5.4 安装日志、卸载入口与注册表信息

安装器不是“把文件复制过去”就完了。它在Windows上安装完成后,会在注册表里写入卸载信息,控制面板的“程序和功能”里能看到“MyApp”,用户点卸载时会启动维护工具。卸载时,维护工具会根据安装记录删除文件、快捷方式、注册的组件信息。

这个机制很关键:为什么要把它放进安装器而不是绿包?因为你每次用QIFW生成安装器后,相当于维护了一个“安装档案”。我遇到过一次用户手动删除程序目录,结果控制面板里残留“MyApp”,再点卸载就报错。这种问题听起来像系统脏了,但其实是因为破坏了安装器自己的记账逻辑。

调试阶段想快速看安装日志的话,安装时可以用命令行加--verbose运行安装器,日志会输出到临时目录,Windows下一般是%TEMP%下的install.log。排查“装到一半闪退”“某个步骤失败”时,这个日志比任何SQLite数据库都有用。

6. Windows 10实测过程中最容易踩的5个坑

6.1 目标目录带中文或空格

QIFW自身对中文路径的兼容还行,但它生成的快捷方式脚本、以及一些外部命令,在目标目录含中文或空格时经常出幺蛾子。比如安装到D:\软件发布\我的程序,桌面快捷方式可能打不开,或者CreateShortcut时传参错误。我自己在Windows 10中文用户名下就踩过:默认@HomeDir@展开后是C:\Users\张三,后面拼接安装路径时一旦处理不好,整个安装器都能崩。

我的建议是:正式交付软件的默认安装目录尽量用纯英文路径;如果用户自定义安装目录非要选中文路径,至少你在脚本里要处理引号,并且用@TargetDir@变量而不是手拼字符串。

6.2 SmartScreen“未知发布者”拦截

新生成的安装器没有代码签名,Windows 10会弹出“Windows已保护你的电脑”,首次运行需要点“更多信息-仍要运行”。这个提示对客户体验非常致命,很多不懂电脑的人看到这个弹窗就以为有病毒,直接放弃安装了。

这个问题的正解是做代码签名,用证书对安装器exe签名后,SmartScreen就不会再拦截。个人开发者没有证书时,至少要在交付说明里提前告诉客户:程序没有签名,Windows会有提示,选择“仍要运行”即可。这不丢人,但千万别假装没有这回事。

6.3 缺管理员权限导致写入失败

如果你在config.xml里用了@ApplicationsDir@作为默认安装目录,但没有设置<RequireAdmin>true</RequireAdmin>,安装器会在写到C:\Program Files时因为没有权限而失败,或者只能写入到虚拟化的重定向目录,用户根本找不到程序装哪去了。

这个坑隐蔽在:程序开发机上的用户是管理员,你怎么装都能成功;换到普通用户机器上就废了。我现在只要目标目录是Program Files,就一定会配<RequireAdmin>true</RequireAdmin>,安装器运行时会主动请求UAC提权,并把安装行为提升到管理员级别。

6.4 杀毒软件把生成的安装器当风险程序

Qt Installer Framework生成的安装器是自解压结构,内部还有7z压缩数据,一些杀毒软件甚至Defender在没有足够信誉信息的情况下,会把全新的未签名安装器判定为“风险程序”或行为可疑。如果你的客户内部装有更严格的EDR,这个概率还会增加。

怎么处理?第一优先级是代码签名;第二是保证每次生成都在干净的可信环境里,避免安装器被二次打包或加壳;第三是给测试机临时加白名单,但这对正式客户没有意义。我不建议为了绕过杀毒去做免杀、加壳、篡改QIFW内部组件,那只会让误报更严重,而且会让客户觉得你心虚。

6.5 4.6.x脚本接口变化让人照老教程写不对

QIFW 4.6.1的脚本API和很多老版本不太一样。网上搜到的旧教程里,可能会出现installer.installerBaseDirectory、旧的component.addOperation("CreateDesktopEntry")等写法。在4.6.x里,某些接口已经调整,windows快捷方式更推荐用CreateShortcut。如果你照抄老代码发现没有效果,不一定是写错了,可能是接口变了。

我的办法是:遇到问题先打开QIFW自带的example目录,Qt官方在安装目录下带了完整示例,比如examples\tutorial,里面是最标准的写法。在新版本里踩坑,与其看网上零散笔记,不如以官方example为基准再改。

7. 收尾:这个系列接下来我会写的方向

这一篇主要把工具链、目录结构、离线exe生成和Win10下的基础避坑讲完了。按照“最基础使用”这个标题,我更希望你先把上面第3章的例子完整跑一遍,哪怕什么都不理解,先看到一个能安装卸载的安装器。等这一步通了,后面很多东西都是在这个基础上的迭代。

我个人在实际项目中的体会是:QIFW虽然看起来配置文件多、概念多,但它最大的价值是“组件化”和“维护工具”这两套机制——一旦你的软件分成主程序、插件、文档多个模块,或者需要在用户机器上保持一个长期可靠的卸载/升级档案,前面这些学习成本就完全值得。下一篇我大概率会聊聊怎么在离线包里做多组件依赖与自定义安装页面,以及如何用repogen把同一个包升级成“可增量更新”的在线仓库。如果你也在研究Qt Installer Framework,欢迎把遇到的问题留在这篇文章后面,我看到了会挑典型的坑继续写。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 3:50:58

n8n读写本地文件实战:Docker部署、节点参数与避坑指南

先把话说在前面&#xff1a;n8n被用得最多的是API对接、Webhook接收、消息推送这类“线上”流程&#xff0c;但在我这一年多的实际项目里&#xff0c;真正帮我解决大问题的&#xff0c;反而是最不起眼的n8n读写本地文件。很多内部系统根本不开放接口&#xff0c;数据交换全靠CS…

作者头像 李华
网站建设 2026/9/16 3:49:50

CGNS静态库编译完全指南:从源码到CMake链接的全流程实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 3:49:44

响应式开发实战:媒体查询与视口单位的正确玩法

搞前端这么多年&#xff0c;我越来越觉得“响应式开发”这个词被说滥了。很多人以为在样式表末尾堆几行媒体查询就算适配了&#xff0c;结果页面一到窄屏&#xff0c;要么横向滚动条冒出来&#xff0c;要么菜单叠成一坨。真正的响应式开发不是“补丁式”修尺寸&#xff0c;而是…

作者头像 李华
网站建设 2026/9/16 3:48:30

基于YOLOv8的PCB缺陷检测实战:从数据标注到边缘端部署

去年接了个PCBA代工厂的预研项目&#xff0c;要用视觉方案检测PCB裸板上的缺陷。设备预算卡得紧&#xff0c;每个工位都上工业级AOI不太现实&#xff0c;于是想到了基于YOLOv8做一套轻量级的PCB缺陷检测方案。折腾了一个多月&#xff0c;从环境配置到数据标注、从模型调优到边缘…

作者头像 李华
网站建设 2026/9/16 3:45:09

虚拟化生态分水岭:ZSvirt开源、VMware订阅制与Proxmox EOL解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华