
做 UniApp 多端开发这几年我手里大部分 Vue3 项目都是“非 CLI 项目”——也就是直接在 HBuilderX 里创建没有独立 src 目录和 package.json 管理的那套。最近为鸿蒙HarmonyOS适配 Deep Linking恰恰这种非 CLI 形态最容易翻车HBuilderX 一编译鸿蒙工程里很多配置都是生成即覆盖的状态外部链接要精准唤起 App 并跳到指定页面配置必须从三处同时下手。这篇文章把我在 HarmonyOS NEXT 上配置深链的完整过程写出来从解开 URL Scheme 与 App Linking 的概念到非 CLI 项目里改哪几个文件、怎么写 module.json5、怎么在 UniApp 侧拿到参数再到真机验证方案适合正在做鸿蒙版适配、又被深链折腾到头疼的 UniApp 同学参考。1. 为什么要给 UniApp 的鸿蒙产物单独配置 Deep Linking1.1 “深链”到底解决什么问题Deep Linking 说人话就是把一个链接变成打开 App 内部页面的钥匙。用户点营销短信里“领取优惠券”系统并不只是把链接丢给浏览器而是找到手机里已安装的对应 App直接唤醒后跳到券包页如果你没装 App它还可以跳到应用市场或者一个 H5 落地页兜底。这个场景在鸿蒙生态里越来越刚需因为鸿蒙应用分发的入口不只有桌面图标还会从华为的扫码、小艺建议、AppGallery 推广位、短信邮件等路径进来没有深链就意味着所有外部流量都被挡在“安装 App”这一步根本无法复用到应用内的转化闭环。具体到业务上常见的使用场景有这么几类短信营销和 PUSH 通知用户点击追踪链接直接进入商品详情或活动页。扫码场景线下物料二维码扫完不打开网页而是拉起 App 并对参数做鉴权。广告投放广告平台回传的点击 URL 需要落地到 App 指定页面。应用内互相跳转比如从手机厂商的全局搜索、语音助手识别的某个业务跳回自己的应用内部。它的核心动作只有两个第一步系统解析链接第二步把解析出来的 scheme、host、path、query 交给 App由 App 决定路由到哪个页面。听起来简单真配置起来你会发现在不同端上根本不是一回事尤其是跨端框架 新操作系统的组合往往要跨好几个工具链来回试。1.2 非 CLI 项目在哪一层配置深链很多教程默认你用的是 CLI 创建的 UniApp 工程里面的 HarmonyOS 打包目录是可控的改完代码提交 Git 就完事。但非 CLI 项目不一样。非 CLI 项目是 HBuilderX 图形化管理的工程它没有外层 package.json、没有 scripts 脚本manifest.json 和 pages.json 才是入口。每次构建HBuilderX 都会重新生成unpackage/dist/build/下的鸿蒙工程产物。也就是说如果你只改生成产物里的module.json5下一次 Build 就会被工具静默覆盖。所以正确姿势应该是以 manifest.json 为配置入口以生成产物为校验点两边配合。manifest.json 里能表达的字段先写进 manifest工具支持不了的部分再在生成工程里补技能配置并且把“重构建后需要复查”这个动作固定到流程里避免同事一提交就说深链失效了。这也是为什么我特别想写一篇“非 CLI 项目”专属指南因为很多坑都是工程形态带来的跟 HarmonyOS 平台本身反而关系不大。我把非 CLI 和 CLI 的关键差异列了个表方便快速建立概念对比维度CLI 工程非 CLI 工程HBuilderX 创建代码组织有 src、package.json可配置自定义构建脚本以 HBuilderX 内建结构为准src 在外层不可见鸿蒙工程产物通常由自定义脚本输出到可控目录可入库生成在 unpackage/dist/build/ 下重新构建会被覆盖深链主要配置入口manifest.json 自定义鸿蒙工程文件manifest.json 生成产物里的 module.json5 补改版本管理影响稳定改动可追溯生成目录通常不进版本库需要额外写复核文档搞清楚这个背景后面所有操作就比较有目的性了既要让工具“认识”你的深链又要保证工具重新生成后配置不丢。2. HarmonyOS 里 Deep Linking 的两个关键概念2.1 从 URL Scheme 到 App LinkingHarmonyOS 上做深链最底层的能力是“技能配置”skill。你可以把 skill 理解为 Android 里的 intent-filter它声明了当前应用能够响应哪些系统行为。具体到深链就是在一个能力Ability里声明skills数组数组里写actions、entities、uris三组字段系统在收到一个协议链接时会拿着链接的协议名、域名、路径去遍历所有应用的 skills匹配上的就被唤起来。这里有两个梯度的方案第一个是自定义 URL Scheme比如myapp://h5/purchase/detail。这种方案完全自主适合应用内互跳、测试环境联调、内部工具类 App。缺点是外部生态不会自动认识你的 scheme需要在链接分发时明确告知用户“复制链接到浏览器”或“打开 XXApp”。第二个是 App Linking基于 HTTPS 标准的https://yourdomain/path。它比自定义 scheme 更正规落地页天然可用系统也更容易识别和信任。副作用是必须要有可控域名并且需要到华为 AppGallery Connect 里配置 SHA 指纹、域名校验文件整体链路多了一道云端服务。对大多数 UniApp 项目我建议先做自定义 scheme把接收链路跑通再根据线上投放需求升级到 App Linking。因为深链配置的难点不在于“选哪个协议”而在于“传参对不对、跳转准不准”先把地基打好更重要。2.2 配置前必须备好的物料清单在动手改配置前把下面物料准备齐能省掉八成报错HBuilderX 版本建议 4.x 以上并选择包含鸿蒙编译能力的版本确认 Manifest 可视化界面里能看到“鸿蒙配置”相关入口。DevEco Studio华为官方 IDE用于打开、调试和签名鸿蒙工程版本建议跟着 HarmonyOS SDK API 12对应 HarmonyOS NEXT 5.0.0(12)走。真机或模拟器强烈建议真机因为 skill 匹配和系统弹窗交互在模拟器上表现有差异。签名文件Debug 调试包用工程的自动签名即可发布版必须确认证书、Profile 都正确关联。域名和 HTTPS 证书仅 App Linking 需要如果只做自定义 scheme这条可跳过。一个固定的产品 bundleName深链跳转后要回到对应应用bundleName 错一个字母都匹配不上。另外提醒一句鸿蒙 SDK 版本迭代很快所有配置文件都以你本机 DevEco 关联的 SDK 版本为准。标题里提到harmonyos next sdk(api 12 / 5.0.0(12))我这边就是按这个版本跑通的API 版本不同时字段名可能有微调但配置的嵌套结构基本一致。3. 实操UniAppVue3 非 CLI鸿蒙深链配置全过程3.1 第一步确认工程能跑通鸿蒙板千万别跳过这步。我见过有人在深链配置上折腾一下午最后发现是 HBuilderX 的鸿蒙编译目标没启用压根没安装到真机。先在 HBuilderX 里打开你的 Vue3 非 CLI 项目进入 manifest.json 的可视化界面找到“鸿蒙配置”或者“HarmonyOS”相关的 Tab确认是否勾选开启鸿蒙编译。然后点击菜单栏“运行 — 运行到手机或模拟器 — 运行到鸿蒙设备”让 UniApp 自动把 js、pages、manifest 配置生成一份鸿蒙原生工程再通过 DevEco 编译安装到真机。首次跑通会有几个前置条件本机已经安装了 DevEco Studio、真机开启了开发者模式并信任调试电脑、HBuilderX 的鸿蒙插件识别到了 DevEco 的 SDK 路径。这些做完你能在手机上看到自己的 App 图标才算拿到深链调试的最低基线。这一步还有个很好的副产品HBuilderX 会输出一个鸿蒙工程目录你可以在 DevEco 里直接打开它后续三步都在这个工程里操作。非 CLI 项目不需要手动创建任何工程别自己在 DevEco 里 New Project否则会跟 HBuilderX 生成的工程冲突。3.2 第二步在 manifest.json 里预留深链配置为了让非 CLI 项目在重构建时不至于丢失所有自定义信息我会先在 manifest.json 里做一层“占位”。以 UniApp 当前鸿蒙适配的实现为例可以在app-plus节点下新增distribute的子节点harmony里面用deepLink做配置入口{ app-plus: { distribute: { harmony: { deepLink: { scheme: ruoyiuni, host: h5, path: /purchase/detail } } } } }不过要特别说明不同版本的 HBuilderX 对这个字段的解析深度不一样。老版本可能只在可视化界面生成 scheme不负责把 skills 完整写入 module.json5新版本则可能直接干预生成逻辑。所以这段配置只能算“上游声明”真正生效还要看第三步生成的 module.json5 是否出现了对应的技能块。如果你在 manifest.json 里写完这段发现构建后的 module.json5 没有任何变化不用慌。说明你当前 HBuilderX 版本还未支持该字段自动生成那就直接按第三步手改产物然后把“构建后需要复核”记录进团队文档即可。3.3 第三步正确改写 module.json5 的 skills 块这是整个深链配置的核心。用 DevEco Studio 打开 HBuilderX 生成的鸿蒙工程找到入口模块的module.json5一般在entry/src/main目录下。先理解它的结构module 下面会挂一个abilities数组数组里每个 Ability 代表一个页面入口能力深链的 skills 必须挂在“能够被外部拉起”的那个 Ability 上通常是EntryAbility。打开文件定位到abilities数组找到入口 Ability在里面加一个skills数组{ module: { name: entry, type: entry, description: $string:module_desc, mainElement: EntryAbility, deviceTypes: [ phone, tablet ], deliveryWithInstall: true, installationFree: false, abilities: [ { name: EntryAbility, srcEntry: ./ets/entryability/EntryAbility.ets, description: $string:EntryAbility_desc, icon: $media:app_icon, label: $string:EntryAbility_label, startWindowIcon: $media:startIcon, startWindowBackground: $color:start_window_background, exported: true, skills: [ { actions: [ ohos.want.action.viewData ], entities: [ entity.system.browsable ], uris: [ { scheme: ruoyiuni, host: h5, path: /purchase/detail }, { scheme: https, host: app.yourdomain.cn, path: /purchase/detail } ] } ] } ] } }这段配置的作用是告诉系统这个 Ability 可以处理“查看数据类”的意图并且在浏览器场景下可见。entity.system.browsable很关键加上它系统才认为可以在链接唤起时把应用列为可选项。uris数组可以写多个scheme、host、path 各有侧重一边留自定义 scheme 服务内跳转一边留 https 给 App Linking 兜底。改完保存后用 DevEco 重新编译安装。如果配置写得不合法编译阶段就会报错这其实是好事总比运行时静默失败好定位。验证方式可以看编译产物中是否存在skills块也可以直接用下面第五步的方式从外面拉起试试。注意不要把这个 skills 块写到 module 顶层。我见过不少人照着 Android 的 intent-filter 习惯去写结果把 skills 放到 module 根目录编译通过但链接死活唤不起。HarmonyOS 的 skills 必须挂在abilities数组的具体ability节点下尤其是你已经定义了 mainElement 的入口 Ability。3.4 第四步在 UniApp 侧接收参数并跳转到目标页面工程配置只是让系统愿意拉起 App拉起之后怎么跳转就要靠 UniApp 的页面生命周期来接手。以 Vue3 的非 CLI 项目为例你可以在App.vue中写入接收逻辑。先建立一个通用的 URL 参数解析函数function parseDeepLink(url) { if (!url) return null try { const decoded decodeURIComponent(url) const [base, queryString] decoded.split(?) const match /^([a-z]):\/\/([^/])(\/[^?]*)?/.exec(base) const result { scheme: match ? match[1] : , host: match ? match[2] : , path: match match[3] ? match[3] : /, query: {} } if (queryString) { queryString.split().forEach((item) { const [key, value] item.split() if (key) result.query[key] value || }) } return result } catch (e) { console.error(deep link parse error, e) return null } }然后在生命周期里去读取参数。这里有个关键点冷启动和热启动拿参数的位置不一样。杀进程后的首次启动onLaunch能拿到但如果 App 已经在后台系统再次用深链唤回onLaunch不会再触发必须去onShow里补一次。import { onLaunch, onShow } from dcloudio/uni-app onLaunch((options) { const link parseDeepLink(options options.query options.query.url) if (link link.path /purchase/detail) { uni.navigateTo({ url: /pages/purchase/detail?id${link.query.id || } }) } }) onShow((options) { const link parseDeepLink(options options.query options.query.url) if (link link.path /purchase/detail) { uni.redirectTo({ url: /pages/purchase/detail?id${link.query.id || } }) } })实际开发中你会遇到一个现象鸿蒙桥接层传给 UniApp 的 options.query 可能不是扁平对象而是一个query字符串或者url字段里才放着完整地址。所以我强烈建议你在 onLaunch 第一步先把options完整打出来看一遍结构再决定是从options.query.id取还是从options.query.url里解析。上面代码里的写法兼容了“完整地址放在 query.url”的情况。如果要在页面内更优雅地接收也可以把这段逻辑抽成一个 composable 或者 mixin在首页调用一次即可不必污染全局 App.vue。但深链跳转往往发生在用户还没有进入业务首页的状态所以我还是倾向于在 App 生命周期统一处理。3.5 第五步真机验证一条龙配置完不能只看编译通过真正要验证的是从系统层面拉起这个动作。推荐的做法是把链接通过手机自带的备忘录、短信或者一个简单的 HTML 页面发出来点击后用系统弹窗确认能否唤起。我的完整验证路径是这样先用 DevEco 安装 Debug 包到鸿蒙真机。确保手机上图标已经出现且应用能正常打开。在备忘录里输入ruoyiuni://h5/purchase/detail?id10086长按识别为链接。点击该链接观察系统是否弹出“打开 ruoyiuni 应用”的浮层如果没有弹窗八成是 skills 没生效。点击唤起后在 DevEco 的 Log 窗口里过滤关键词deepLink或query确认 UniApp 侧是否打印出解析后的参数。再测一次热启动保持 App 在后台重新从备忘录点链接确认onShow分支能正常跳转。如果手头没有消息应用可发链接用hdc命令行也可以模拟一部分外部唤起的流程。比如hdc shell aa start -d ruoyiuni://h5/purchase/detail?id10086如果你能看到应用被拉起就说明系统侧的 skills 匹配已经成功。不同 SDK 版本的 aa 命令参数会有差异最稳的还是靠一个真实可点击的链接来测。注意整个验证过程中如果 App 一直处于调试模式系统对 scheme 的处理可能和 Release 模式有细微差异。所以上线前我还会打一个 Release 包在系统浏览器里做一遍全链路回归防止 Debug 签名下的行为误导排查方向。4. 四类高频坑与排查方案4.1 scheme 配了但系统不识别这个问题出现的频率最高症状就是点链接没有弹窗就像没配过一样。按顺序排查以下四件事skills是否嵌在abilities数组里的入口 Ability 下而不是 module 顶层。exported字段是否为true。深链唤起的 Ability 如果没有导出外部系统没有权限访问配置再多也没用。entity.system.browsable是否写对。字段多一个字符、少一个字符都会导致匹配失败。装饰过的 scheme 是否和链接完全一致注意大小写。系统匹配是区分大小写的。我这里有个通用检查法在 DevEco 里打开已安装应用的配置把module.json5中的 skills 块和应用安装后的实际配置比对看两者是否一致。如果实际配置里没有 skills那问题大概率出在 HBuilderX 构建覆盖了你的手改内容需要回到 3.2 里的 manifest.json 兜底方案。4.2 拿着参数却拿不到 onLaunch 的 query有时 App 能正常被拉起但 onLaunch 里的 options 是空的或者只有一堆看不懂的序列化字符串。这种情况通常不是配置问题而是参数传递方式差异。如果链接是ruoyiuni://h5/purchase/detail?id10086UniApp 多数情况会把id10086作为 query 传下来。如果链接是ruoyiuni://h5/purchase/detail?id10086sourcescan有些桥接层会对参数顺序敏感需要你 decode 后再取值。如果链接是ruoyiuni://h5?page/purchase/detailid10086那就不要指望 path 里带信息所有业务参数都在 query。我的建议是不要在 onLaunch 里赌一个字段名而是统一把链接原始地址打出来再交给 parseDeepLink 解析。这里再强调一次热启动必须在onShow里再取一次参数这是 Uniapp 多端框架通病鸿蒙也不会例外。4.3 非 CLI 项目的构建覆盖问题非 CLI 项目手改的生成产物大概率会在下次构建时被冲掉。这种问题最阴间的点在于它不是立即报错的而是某天你把手机上的应用卸载重装突然发现深链又挂了。针对覆盖问题我的经验是按阶段做好三件事构建前把所有需要写入 module.json5 的自定义深链配置先记录到 manifest.json 的app-plus.distribute.harmony.deepLink节点能识别就让工具自动写省得每次都手动改。构建后用 DevEco 全局搜索“uris”快速确认 skills 是否还在不在就立刻补写并做一次完整验证。版本管理上把“深链复核”做成发布清单里的检查项而不是随缘检查。团队规模一大靠人肉记忆一定会漏。这也是我在这篇里反复强调“manifest 为入口、产物为校验点”的原因。非 CLI 项目没法像 CLI 那样把整个鸿蒙工程纳入 Git 管理和 CI 构建但通过固定流程是可以保证配置稳定性的。4.4 真机调试签名与环境不一致深链配置本身没问题但真机上就是打不开往往是签名问题。DevEco 里自动签名时给的指纹和手机设备端实际注入的开发证书指纹不一致就会把应用拦在外面。排查方法很直接在 DevEco 的 Signing Configs 里确认当前用的证书、Profile、bundleName 三者匹配然后重新执行签名同步。我遇到过一次签名后应用能正常打开但系统在按链接匹配应用时根本不到桌面图标换了个调试证书再签一次就好了。如果换了证书还不行就把应用卸载重装排除系统缓存了旧证书信息的可能。另外不要在鸿蒙深链调试阶段混用多个华为开发者账号。每个账号生成的证书指纹不同应用一旦安装过 A 账号签名的版本再用 B 账号覆盖安装很容易出现“可打开但不可外部唤起”的诡异状态。5. 一点私货非 CLI 项目配鸿蒙深链的简化路径根据我这段时间的实操给你一条我验证过最顺的路径先用自定义 scheme 把整条链路跑通再决定要不要上 App Linking。不要一上来就搞 HTTPS 域名、AppGallery Connect 配置那样排查环节会成倍增加特别是非 CLI 项目本身就已经多了一层“生成产物覆盖”的变量新手很容易陷入怪圈不知道是业务链路问题还是平台配置问题。具体执行顺序我会固定成确认能运行到鸿蒙真机 → manifest.json 里留下 deepLink 声明 → 在生成工程里补 skills → 在 App.vue 里解析并跳转 → 真机验证冷热启动 → 重构建一次再验证深链是否稳定。这套顺序把风险点拆得很小每一步都只引入一个变量出问题能快速定位。最后想提醒的是HBuilderX 和 DevEco 都在高频迭代你找到的教程版本号如果和手头不一样不要觉得是自己抄错了。字段名变了、生成目录变了、可视化入口换了这些在鸿蒙适配期都是常态。关键是把配置逻辑吃透然后用我前面的“复核三步法”去验证现状比死记一段配置更靠谱。