HarmonyOS开发——uni-app 手机号一键登录鸿蒙端(HarmonyOS NEXT)适配全记录
手机号一键登录是提升用户转化率的关键功能。在 Android / iOS 端,uni-app 通过 uni.login({ provider: 'univerify' }) 即可唤起运营商授权弹窗,用户一键完成登录。但在 HarmonyOS NEXT 上,这套方案从平台开通、配置对齐到 API 调用都经历了完全不同的实现路径。
本文将完整记录鸿蒙端适配一键登录的全过程:从最初的 缺少参数: appid or gyid,到 当前应用AppId尚未开通uni一键登录,再到最终跑通全流程。
一、先开通平台服务:uni-app 一键登录配置
鸿蒙端一键登录能否跑通,第一步不是写代码,而是先把 uni-app 平台侧的一键登录服务开通、计费方式确认、AppID 对齐。否则客户端配置再正确,也很容易遇到:
{ "errSubject": "uni-verify", "errCode": 4000, "errMsg": "缺少参数: appid or gyid" }
或者:
{ "errMsg": "当前应用AppId尚未开通uni一键登录" }
1. 进入 DCloud 开发者中心
登录 DCloud 开发者中心,进入应用管理页面。
这里需要注意:鸿蒙端必须使用独立的鸿蒙应用配置。不能直接把 Android / iOS 的 AppID 拿过来复用,因为鸿蒙原生工程的包名、签名、SDK 读取方式都与 Android / iOS 不同。
建议操作路径:
- 打开 DCloud 开发者中心;
- 找到已有应用,或新建一个鸿蒙端专用应用;
- 确认应用包名与鸿蒙工程
bundleName一致; - 记录该应用的 DCloud AppID,例如
__UNI__XXXXXXX。
[配图:DCloud 应用列表页]
[配图:鸿蒙端应用详情页]
2. 开通 uni 一键登录服务
在应用详情中找到 uni 一键登录 / univerify 相关服务入口,点击开通。
这里要重点确认三件事:
| 检查项 | 说明 | 常见问题 |
|---|---|---|
| 是否已开通 | 未开通时客户端会提示 AppId 未开通 | 最容易忽略 |
| 开通的是哪个 AppID | 必须与鸿蒙端使用的 AppID 一致 | 误开在 Android/iOS 应用下 |
| 计费/套餐状态 | 一键登录通常按成功调用计费 | 欠费或套餐异常会影响调用 |
[配图:uni 一键登录服务开通页]
[配图:计费/套餐状态页]
3. 确认包名与应用绑定
鸿蒙端一键登录依赖原生工程配置,因此包名必须严格一致。
建议核对:
- DCloud 后台应用包名;
- 鸿蒙工程
bundleName; module.json5中的 metadata;- 客户端
manifest.json中的appid; - 云函数
getPhoneNumber中的appid。
只要其中一处不一致,就可能出现参数缺失或应用未开通的错误。
二、鸿蒙端与 Android/iOS 的核心差异
| 维度 | Android/iOS | HarmonyOS NEXT |
|---|---|---|
| 调用方式 | uni.login({ provider: 'univerify' }) |
uni.getUniverifyManager().login() |
| 底层 SDK | DCloud 内置 | @getui/gysdk(个推 SDK) |
| AppID 读取位置 | manifest.json 内置 |
module.json5 metadata 中的 GETUI_APPID |
| 关闭弹窗 | uni.closeAuthView() |
manager.close() |
| 样式支持 | 丰富(按钮颜色、隐私条款、关闭按钮等) | 有限(仅 fullScreen / logoPath / loginBtnText) |
| 预登录 | 隐式(框架内部处理) | 显式(需手动调用 preLogin) |
三、配置清单:四处必须对齐的 AppID
3.1 DCloud 后台
在 DCloud 开发者中心创建鸿蒙应用,包名填鸿蒙原生工程的 bundleName,开通 uni 一键登录 服务,获得新的 DCloud AppID。
重要:鸿蒙端包名与 Android/iOS 不同,必须在 DCloud 后台新建应用,不能复用 Android/iOS 的 AppID。
3.2 manifest.json(uni-app 工程)
{
"appid": "__UNI__YOUR_HARMONY_APPID",
"privacy": {
"provider": "univerify",
"univerify": {
"termsUrl": "https://your-domain.com/userAgreement.html",
"privacyUrl": "https://your-domain.com/privAgreement.htm"
}
},
"app-harmony": {
"distribute": {
"modules": {
"uni-verify": {
"appid": "__UNI__YOUR_HARMONY_APPID"
}
}
}
}
}
[配图:manifest.json 中的 app-harmony.modules.uni-verify.appid]
3.3 module.json5(鸿蒙原生工程)
这是最关键的一步——个推 SDK 从 module.json5 的 metadata 中读取 AppID:
{
module: {
metadata: [
{
name: "GETUI_APPID",
value: "__UNI__YOUR_HARMONY_APPID",
},
],
},
}
踩坑:如果不配置
GETUI_APPID,个推 SDK 初始化时会报错缺少参数: appid or gyid。
[配图:module.json5 中的 GETUI_APPID]
3.4 云函数 getPhoneNumber
'use strict';
exports.main = async (event, context) => {
const res = await uniCloud.getPhoneNumber({
appid: '__UNI__YOUR_HARMONY_APPID',
provider: 'univerify',
access_token: event.access_token,
openid: event.openid,
});
return res;
};
云函数的 package.json 需要声明 uni-cloud-verify 扩展:
{
"name": "getPhoneNumber",
"extensions": {
"uni-cloud-jql": {},
"uni-cloud-verify": {}
}
}
[配图:云函数 getPhoneNumber 配置页]
四、踩坑实录:从报错到跑通
4.1 第一个坑:缺少参数: appid or gyid
现象:调用 preLogin 时直接报错:
{ "errSubject": "uni-verify", "errCode": 4000, "errMsg": "缺少参数: appid or gyid" }
排查过程:
- 最初在编译产物的
manifest.json中添加了distribute.modules.uni-verify.appid→ 不生效 - 深入分析
@getui/gysdk的类型声明文件,发现关键线索:
// GyConsts.d.ets
export declare class GyConsts {
static readonly GETUI_APPID = "GETUI_APPID";
}
- 个推 SDK 是从 HarmonyOS 的
module.json5metadata 中读取GETUI_APPID的,而不是从 manifest.json
修复:在 module.json5 的 metadata 中添加 GETUI_APPID。
4.2 第二个坑:当前应用AppId尚未开通uni一键登录
现象:添加 GETUI_APPID 后,错误变了——说明 SDK 配置已生效,但 DCloud 后台的应用没有开通鸿蒙端的一键登录服务。
修复:在 DCloud 后台为鸿蒙包名新建应用,开通 uni 一键登录服务。
4.3 第三个坑:关闭弹窗 API 不同
Android/iOS 用 uni.closeAuthView(),鸿蒙端必须改用 manager.close()。
五、前端实现:鸿蒙端专属的一键登录流程
5.1 API 差异一览
// Android/iOS:通过 uni.login 一步完成
uni.login({
provider: 'univerify',
univerifyStyle: { /* 丰富的样式配置 */ },
success: (res) => { /* 获取 access_token */ },
});
uni.closeAuthView(); // 关闭弹窗
// 鸿蒙端:通过 manager 实例分步操作
const manager = uni.getUniverifyManager();
manager.preLogin({ success: () => {} }); // 第一步:预登录
manager.login({ // 第二步:登录
univerifyStyle: { fullScreen: false, logoPath: '...', loginBtnText: '...' },
success: (res) => {},
});
manager.close(); // 关闭弹窗
5.2 鸿蒙端完整实现
// #ifdef APP-HARMONY
let univerifyManager: any = null;
const handleHarmonyUniVerifyLogin = async () => {
if (typeof (uni as any).getUniverifyManager !== 'function') {
uni.showToast({ title: '当前设备不支持一键登录', icon: 'none' });
return;
}
univerifyManager = (uni as any).getUniverifyManager();
const closeAuthView = () => univerifyManager?.close();
// 授权成功:通过云函数换取手机号
const handleSuccess = async (res: any) => {
uni.showLoading({ title: '登录中...', mask: true });
try {
const cloudRes = await uniCloud.callFunction({
name: 'getPhoneNumber',
data: {
access_token: res.authResult.access_token,
openid: res.authResult.openid,
},
});
if (cloudRes?.result?.phoneNumber) {
await loginByUniverify({ phone: cloudRes.result.phoneNumber, type: 4 });
await afterAppLogin(resList);
} else {
uni.showToast({ title: '未获取到本机号码', icon: 'none' });
}
} catch (err) {
uni.showToast({ title: '获取手机号失败,请使用验证码登录', icon: 'none' });
} finally {
uni.hideLoading();
closeAuthView();
}
};
// 授权失败处理
const handleFail = (err: any) => {
uni.hideLoading();
if (err?.metadata?.resultCode == '102103') {
uni.showToast({ title: '请先打开移动数据(流量)', icon: 'none' });
}
if (err?.code == '30002') {
toggleLoginType(LoginType.MOBILE);
}
};
uni.showLoading({ title: '加载中...', mask: true });
const doLogin = () => {
univerifyManager!.login({
univerifyStyle: {
fullScreen: false,
logoPath: logoSrc.value,
loginBtnText: '本机号码一键登录',
},
success: handleSuccess,
fail: handleFail,
});
};
// 检查预登录状态
if (univerifyManager!.isPreLoginValid?.()) {
doLogin();
} else {
univerifyManager!.preLogin({
success: () => doLogin(),
fail: (preErr: any) => {
uni.hideLoading();
uni.showToast({
title: preErr?.errMsg ?? '预授权失败,请使用手机号验证码登录',
icon: 'none',
});
setTimeout(() => toggleLoginType(LoginType.MOBILE), 2000);
},
});
}
};
// #endif
5.3 入口方法的条件编译分流
const handleUniVerifyLogin = async () => {
// #ifdef APP-HARMONY
await handleHarmonyUniVerifyLogin();
// #endif
// #ifndef APP-HARMONY
// Android/iOS 保持原有逻辑不变
uni.login({ provider: 'univerify', univerifyStyle: { /* ... */ }, ... });
// #endif
};
[配图:客户端调用成功后的授权弹窗]
六、预登录优化
利用页面加载时间提前预登录,可以显著提升用户点击后的响应速度:
// 页面 onLoad 时提前预登录
// #ifdef APP-HARMONY
if (typeof (uni as any).getUniverifyManager === 'function') {
const manager = (uni as any).getUniverifyManager();
manager.preLogin({
success: () => console.log('[Harmony] preLogin success'),
fail: (err) => console.warn('[Harmony] preLogin fail', JSON.stringify(err)),
});
}
// #endif
七、样式支持对比
鸿蒙端的 univerifyStyle 支持范围远小于 Android/iOS:
| 配置项 | Android/iOS | 鸿蒙端 |
|---|---|---|
fullScreen |
✅ | ✅ |
logoPath |
✅ | ✅ |
loginBtnText |
✅ | ✅ |
backgroundColor |
✅ | ❌ |
phoneNumColor |
✅ | ❌ |
authButton.* |
✅ | ❌ |
closeButtonStyle |
✅ | ❌ |
privacyTerms |
✅ | ❌ |
timeout |
✅ | ❌ |
八、错误码速查表
| errCode | 含义 | 处理方式 |
|---|---|---|
| 4000 | 缺少参数(appid/gyid) | 检查 module.json5 的 GETUI_APPID |
| - | AppId 未开通一键登录 | DCloud 后台开通 uni-verify 服务 |
| 102103 | 未开启移动数据 | 提示用户打开流量 |
| 30002 | 需切换登录方式 | 自动降级到验证码登录 |
九、总结
- AppID 必须新建:鸿蒙包名与 Android/iOS 不同,需在 DCloud 后台新建应用。
- 平台服务要先开通:未开通 uni 一键登录或计费异常,客户端会直接报错。
- metadata 是命门:
GETUI_APPID必须配置在module.json5的 metadata 中。 - API 完全不同:鸿蒙端用
getUniverifyManager()替代uni.login(),用manager.close()替代uni.closeAuthView()。 - 样式大幅缩水:鸿蒙端只支持
fullScreen、logoPath、loginBtnText三个样式属性。 - 预登录是加分项:利用页面加载时间提前预登录,显著提升响应速度。
- 四处 AppID 必须一致:DCloud 后台、
manifest.json、module.json5、云函数必须使用同一个鸿蒙端 AppID。
浙公网安备 33010602011771号