更新记录

1.0.0(2025-11-20)

初步提交


平台兼容性

uni-app(4.71)

Vue2 Vue3 Vue2插件版本 Chrome Safari app-vue app-vue插件版本 app-nvue Android iOS 鸿蒙 鸿蒙插件版本
- 1.0.0 × × 1.0.0 - - - 5.0.0 1.0.0
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 快应用-华为 快应用-联盟
× × × × × × × × × × ×

uni-app x(4.71)

Chrome Safari Android iOS 鸿蒙 微信小程序
- - - - - -

harmony-auth

华为账号登录 UTS 插件 - 支持 HarmonyOS


📦 插件信息

  • 插件ID: harmony-auth
  • 版本: 1.0.0
  • 平台支持: HarmonyOS (app-harmony)
  • 类型: UTS 原生插件

🚀 功能特性

  1. 获取匿名手机号

    • 获取华为账号绑定的脱敏手机号(如:138****1234)
    • 用于登录页面展示
    • 使用 HuaweiIDProvider API
  2. 华为账号一键登录

    • 获取华为账号授权码
    • 支持后端验证登录
    • 使用 account_authentication API
  3. 协议校验与用户体验

    • 登录前协议同意检查
    • 防重复登录保护
    • 友好的错误提示

🔧 集成与配置

1. 安装插件

将插件放入 uni_modules/harmony-auth 目录。

2. 华为开发者配置

在华为开发者后台配置应用指纹和包名。


📖 使用方法

1. 导入插件

// 推荐方式:ES6 导入
import { getQuickLoginAnonymousPhone, getAuthorizationCode } from '@/uni_modules/harmony-auth';

// 或使用工具类封装
import HarmonyLoginUtil from '@/utils/harmonyLoginUtil.js';

2. 获取匿名手机号

异步方式(推荐):

// 在页面加载时获取
const loadHarmonyPhoneNumber = async () => {
  try {
    const phone = await getQuickLoginAnonymousPhone();
    if (phone) {
      console.log('匿名手机号:', phone); // 138****1234
      // 显示在登录页面
    }
  } catch (error) {
    console.error('获取失败:', error);
  }
};

使用工具类:

import HarmonyLoginUtil from '@/utils/harmonyLoginUtil.js';

const phone = await HarmonyLoginUtil.getAnonymousPhone();

3. 页面集成原生登录按钮

Vue 页面示例(login.vue):

<template>
  <view>
    <!-- 显示匿名手机号 -->
    <view v-if="quickLoginAnonymousPhone">
      {{ quickLoginAnonymousPhone }}
    </view>

    <!-- 华为一键登录按钮 -->
    <harmonyButton 
      :checked="agreementVal" 
      :triggerLogin="triggerHarmonyLogin"
      :enableNetworkCheck="false"
      @harmonyLogin="handleHarmonyLogin" 
    />

    <!-- 协议组件 -->
    <agree 
      v-model="agreementVal" 
      ref="agreeRef" 
      @confirm="handleAgreeConfirm" 
    />
  </view>
</template>

<script setup>
import harmonyButton from "./components/harmonyButton.vue";
import agree from "./components/agree.vue";
import HarmonyLoginUtil from "@/utils/harmonyLoginUtil.js";
import useUserStore from "@/stores/user.js";

const quickLoginAnonymousPhone = ref("");
const agreementVal = ref(false);
const agreeRef = ref(null);
const triggerHarmonyLogin = ref(false);
const isLoggingIn = ref(false);

// 页面加载时获取匿名手机号
onLoad(() => {
  loadHarmonyPhoneNumber();
});

const loadHarmonyPhoneNumber = async () => {
  const phone = await HarmonyLoginUtil.getAnonymousPhone();
  if (phone) {
    quickLoginAnonymousPhone.value = phone;
  }
};

// 用户同意协议后触发登录
const handleAgreeConfirm = () => {
  isLoggingIn.value = true;
  triggerHarmonyLogin.value = true;

  setTimeout(() => {
    triggerHarmonyLogin.value = false;
  }, 1000);
};

// 处理登录结果
const handleHarmonyLogin = async (result) => {
  if (result.needAgreement) {
    agreeRef.value.openPopup();
    return;
  }

  if (result.success && result.authCode) {
    // 调用后端 API
    await useUserStore().harmonyOneTouchLogin(result.authCode);
    uni.switchTab({ url: '/pages/index/index' });
  } else {
    uni.showToast({
      title: result.error || '登录失败',
      icon: 'none'
    });
  }

  isLoggingIn.value = false;
};
</script>

4. 封装工具类(harmonyLoginUtil.js)

import { getQuickLoginAnonymousPhone } from '@/uni_modules/harmony-auth';

class HarmonyLoginUtil {
  constructor() {
    this.isLoggingIn = false;
    this.bundleName = 'com.num.phonemanager.parent.hm';
  }

  // 获取匿名手机号
  async getAnonymousPhone() {
    try {
      const phone = await getQuickLoginAnonymousPhone();
      return phone || '';
    } catch (error) {
      console.error('获取匿名手机号异常:', error);
      return '';
    }
  }

  // 重置登录状态
  resetLoginState() {
    this.isLoggingIn = false;
  }
}

export default new HarmonyLoginUtil();

5. 华为登录按钮组件(harmonyButton.vue)

<template>
  <embed 
    tag="quickloginbutton" 
    :options="quickloginparams"
    @quick_login_response="onQuickLoginResponse">
  </embed>
</template>

<script setup>
import { ref, computed, watch } from 'vue';
import { dealAllError, ErrorCode } from '@/uni_modules/harmony-auth';

const props = defineProps({
  checked: { type: Boolean, default: false },
  triggerLogin: { type: Boolean, default: false }
});

const emit = defineEmits(['harmonyLogin']);

const isDisabled = ref(false);
const noNetworkCount = ref(0);
const isContinueLogin = ref(false);

// 监听触发登录
watch(() => props.triggerLogin, (newVal) => {
  if (newVal && props.checked) {
    isContinueLogin.value = true;
    setTimeout(() => {
      isContinueLogin.value = false;
    }, 500);
  }
});

const quickloginparams = computed(() => ({
  noNetCnt: noNetworkCount.value,
  isUserAgree: props.checked,
  isContinueLogin: isContinueLogin.value,
  isDisabled: isDisabled.value
}));

const onQuickLoginResponse = (event) => {
  const detail = event.detail;

  // 登录成功
  if (detail.quickLoginResponse) {
    emit('harmonyLogin', {
      success: true,
      authCode: detail.authCode
    });
  }

  // 登录失败
  if (detail.error) {
    emit('harmonyLogin', {
      success: false,
      error: detail.error.message
    });
  }
};
</script>

6. 后端接口调用(user.js store)

// 华为账号一键登录
harmonyOneTouchLogin(code) {
  if (this.isLoggingIn) {
    return Promise.reject(new Error('登录中,请勿重复操作'));
  }

  this.isLoggingIn = true;

  return new Promise((resolve, reject) => {
    uni.showLoading({ title: '登录中', mask: true });

    harmonyLoginApi({ authCode: code })
      .then(async (res) => {
        if (res.data) {
          this.token = res.data.token;
          this.userInfo = res.data.userInfo;
          uni.setStorageSync('token', this.token);
          uni.setStorageSync('userInfo', this.userInfo);
          resolve(res);
        }
      })
      .catch((error) => {
        reject(error);
      })
      .finally(() => {
        uni.hideLoading();
        this.isLoggingIn = false;
      });
  });
}

📝 接口定义

详见 uni_modules/harmony-auth/utssdk/interface.uts

AuthOptions(授权选项)

type AuthOptions = {
  bundleName?: string;      // 应用包名
  forceLogin?: boolean;     // 是否强制登录
  success?: (res: AuthSuccessResult) => void;
  fail?: (err: AuthErrorResult) => void;
  complete?: () => void;
}

PhoneSuccessResult(手机号结果)

type PhoneSuccessResult = {
  anonymousPhone: string;   // 匿名手机号 (如: 138****1234)
  errCode: number;
  errMsg: string;
}

AuthSuccessResult(授权成功结果)

type AuthSuccessResult = {
  code: string;             // 授权码
  authCode: string;         // 授权码(兼容字段)
  state?: string;
  errCode: number;
  errMsg: string;
}

🔍 错误码与处理

详见 conf/harmonyConfig.js

错误码 含义 处理建议
*** 网络错误 检查网络连接
*** 未同意协议 弹出协议弹窗
*** 未登录华为账号 引导用户登录系统账号
*** 用户取消授权 提示用户可使用其他方式登录
*** 服务异常 提示稍后重试

错误信息获取:

import { getHarmonyErrorMessage } from '@/conf/harmonyConfig.js';

const errorMsg = getHarmonyErrorMessage(errorCode);
uni.showToast({ title: errorMsg, icon: 'none' });

💡 最佳实践

1. 协议校验流程

// 未同意协议时,先弹出协议弹窗
if (!agreementVal.value) {
  agreeRef.value.openPopup();
  return;
}

// 用户点击"同意并继续"后,自动触发登录
const handleAgreeConfirm = () => {
  agreementVal.value = true;
  triggerHarmonyLogin.value = true;
};

2. 防重复登录

// 在 store 中使用标识
if (this.isLoggingIn) {
  return Promise.reject(new Error('登录中,请勿重复操作'));
}
this.isLoggingIn = true;

// 登录结束后重置
.finally(() => {
  this.isLoggingIn = false;
});

3. 按钮禁用控制

// 登录期间禁用所有登录按钮
const isLoggingIn = ref(false);

// 在模板中
<uv-button 
  :disabled="isLoggingIn"
  @click="handleLogin">
</uv-button>

4. 错误处理

const handleHarmonyLogin = async (result) => {
  if (result.needAgreement) {
    agreeRef.value.openPopup();
    return;
  }

  if (!result.success) {
    uni.showToast({
      title: result.error || '登录失败',
      icon: 'none'
    });
    return;
  }

  // 调用后端 API
  await useUserStore().harmonyOneTouchLogin(result.authCode);
};

🔧 技术实现

核心 API

  • @kit.AccountKit - 华为账号服务
  • HuaweiIDProvider - 华为ID提供者
  • AuthenticationController - 认证控制器
  • LoginWithHuaweiIDButton - 原生登录按钮组件

项目结构

uni_modules/harmony-auth/
├── utssdk/
│   ├── app-harmony/
│   │   ├── index.uts          # 主要 API 实现
│   │   └── button.ets         # 原生登录按钮组件
│   └── interface.uts          # 类型定义
├── readme.md                  # 使用文档
└── changelog.md               # 更新日志

项目集成文件:
├── utils/harmonyLoginUtil.js  # 工具类封装
├── conf/harmonyConfig.js      # 配置与错误码
├── pagesMy/login/
│   ├── login.vue              # 登录页面
│   └── components/
│       ├── harmonyButton.vue  # 华为登录按钮组件
│       └── agree.vue          # 协议组件
└── stores/user.js             # 状态管理

📚 参考文档


⚠️ 注意事项

必要条件

  1. 平台限制

    • 仅支持 HarmonyOS 平台
    • 需使用 #ifdef APP-HARMONY 条件编译
  2. 权限配置

    • manifest.json 中配置 ohos.permission.INTERNET
  3. 开发者配置

    • 在华为开发者后台配置应用指纹
    • 配置正确的包名(bundleName)
  4. 用户要求

    • 用户必须在设备上登录华为账号
    • 首次使用需用户同意用户协议

开发建议

  1. 协议校验

    • 登录前必须检查用户是否同意协议
    • 未同意时弹出协议弹窗,不自动触发登录
  2. 防重复登录

    • 使用 isLoggingIn 标识防止重复提交
    • 登录期间禁用所有登录按钮
  3. 错误处理

    • 使用 getHarmonyErrorMessage() 获取友好提示
    • 区分不同错误类型,给予不同处理方案
  4. 网络检测配置

    • enableNetworkCheck: false(推荐):使用 Vue 层的全局提示框 $jd_toast,UI 风格统一
    • enableNetworkCheck: true:使用原生层的对话框 showAlertDialog,系统原生风格
    • 建议关闭原生网络检测,避免重复提示
  5. 用户体验

    • 页面加载时获取并显示匿名手机号
    • 登录失败时提供明确的错误提示
    • 提供其他登录方式作为备选

常见问题

Q: 获取不到匿名手机号?

  • 检查用户是否登录华为账号
  • 确认应用指纹配置正确

Q: 登录时提示未同意协议?

  • 检查 isUserAgree 参数是否正确传递
  • 确保用户点击"同意并继续"后再触发登录

Q: 未勾选协议点击登录,同意协议后网络异常怎么办?

  • ✅ 已正确处理:网络异常会弹出提示,并重置登录状态
  • ✅ 用户可以在网络恢复后重新点击登录按钮
  • ✅ 不需要重新勾选协议(协议状态已保存)

Q: 授权码获取失败?

  • 使用原生 LoginWithHuaweiIDButton 组件方式
  • 不要直接调用 getAuthorizationCode() 方法

Q: 如何测试?

  • 使用真机,确保已登录华为账号
  • 在开发者后台配置测试环境应用指纹

📋 更新日志

详见 uni_modules/harmony-auth/changelog.md


📞 技术支持

如需更多接口和参数说明,请:

  1. 查阅插件源码
  2. 参考官方文档
  3. 查看项目中的实际调用示例

版本: 1.0.0 | 更新日期: 2024-01-18

隐私、权限声明

1. 本插件需要申请的系统权限列表:

ohos.permission.INTERNET ohos.permission.GET_NETWORK_INFO

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

暂无用户评论。