鸿蒙特色实战3共享单车案例

news2025/5/12 6:22:21

1 案例截图

在这里插入图片描述

2 开发准备

本项目需要一台NEXT真机,并完成以下准备工作。

在AppGallery Connect(简称AGC)上,参考创建项目和创建应用完成HarmonyOS应用的创建,从而使用各类服务。

添加应用包名要注意:与新建DevEco Studio的工程Bundle Name要保持一致。

在这里插入图片描述

3 创建工程

使用DevEco Studio开发工具选择以下其中一种方式创建坚果单车工程。

  • 方式一:当前未打开任何工程,可以在DevEco Studio的欢迎页,选择Create Project打开新工程创建向导。
  • 方式二:已打开新工程,可以在菜单栏选择“File > New > Create Project”打开新工程创建向导。

在这里插入图片描述

注:创建工程时Bundle name需与AGC平台创建应用时的应用包名一致,坚果单车为com.nutpi.bicycle(此处可替换为你在AGC上项目的包名)。

3.1 添加公钥指纹

使用华为账号服务(Account Kit)、地图服务(Map Kit)、推送服务(Push Kit),为了正常调试运行应用,需要预先添加公钥指纹(生效时间为10分钟)。

添加公钥指纹步骤:

  • 工程进行自动签名。
  • 在AGC上对应的应用里,添加公钥指纹。

3.2 配置Client ID

坚果单车使用华为账号服务(Account Kit)、地图服务(Map Kit)、推送服务(Push Kit),需要登录AGC平台,在“我的项目”中选择目标应用,获取“项目设置 > 常规 > 应用”的Client ID。

在这里插入图片描述

注:需要获取应用的Client ID,而不是项目的Client ID。

在工程entry模块中的module.json5配置文件中,新增metadata标签,配置nameclient_idvalue为Client ID的值。

在这里插入图片描述

4.1 配置scope权限

坚果单车需要用到华为账号服务(Account Kit),需要登录开发者联盟,选择“管理中心 > API服务 > 授权管理”,选择目标应用的应用名称,服务选择“华为账号服务”,选择“敏感权限”,再根据应用的需要,选择对应的权限,点击“申请”。点击申请后选择对应“服务类型”选项,根据应用实际情况填写使用场景,使用场景类型和业务描述类型参见表1。提交申请成功后,查看状态“待审核”,5个工作日内审核结果会通过站内消息的形式发送到消息中心,请注意查收。

在这里插入图片描述

4.2 开通地图服务

登录AGC平台,选择“我的项目”,在项目列表中找到目标项目,在项目下的应用列表中选择需要打开地图服务的应用。选择API管理,找到地图服务开关,打开开关。

在这里插入图片描述

4.3 开通推送服务

登录AGC平台,选择“我的项目”,在项目列表中找到目标项目,在项目下的应用列表中选择需要打开地图服务的应用。在左侧导航栏选择“增长 > 推送服务”,点击“立即开通”,在弹出的提示框中点击“确定”。

在这里插入图片描述

5 向用户申请权限

  • 位置服务(Location Kit)需要申请ohos.permission.LOCATIONohos.permission.APPROXIMATELY_LOCATION权限(静态配置),并封装PermissionsUtil用户动态向用户申请权限。
// module.json5配置文件中配置权限
{
  "module": {
    "name": "entry",
    "type": "entry",
    ...
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      },
      {
        "name": "ohos.permission.LOCATION",
        "reason": "$string:location_reason",
        "usedScene": {
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.APPROXIMATELY_LOCATION",
        "reason": "$string:location_reason",
        "usedScene": {
          "when": "inuse"
        }
      }
    ]
  }
}
// PermissionsUtil.ets
import { abilityAccessCtrl, bundleManager, common, PermissionRequestResult, Permissions } from '@kit.AbilityKit'
import { BusinessError } from '@kit.BasicServicesKit';
import { NBConstants } from '../constants/NBConstants';
import { JSON } from '@kit.ArkTS';

const context = getContext(this) as common.UIAbilityContext;

export class PermissionsUtil {
  // 检查是否授权
  static checkAccessToken(permission: Permissions): abilityAccessCtrl.GrantStatus {
    const atManager = abilityAccessCtrl.createAtManager();
    let grantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
    let tokenId: number = 0;
    try {
      const bundleInfo = bundleManager
        .getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
      const appInfo = bundleInfo.appInfo;
      tokenId = appInfo.accessTokenId;
      grantStatus = atManager.checkAccessTokenSync(tokenId, permission);
    } catch (error) {
      const err = error as BusinessError;
      console.error(`${NBConstants.TAG} checkAccessToken Failed. Cause: ${JSON.stringify(err)}`);
    }
    return grantStatus;
  }

  // 动态申请权限
  static async reqPermissionsFromUser(permissions: Permissions[]): Promise<number[]> {
    console.info(`${NBConstants.TAG} reqPermissionsFromUser start.`);
    const atManager = abilityAccessCtrl.createAtManager();
    let result: PermissionRequestResult = { permissions: [], authResults: [] };
    try {
      result = await atManager.requestPermissionsFromUser(context, permissions);
    } catch (error) {
      const err = error as BusinessError;
      console.error(`${NBConstants.TAG} reqPermissionsFromUser Failed. Cause: ${JSON.stringify(err)}`);
    }
    return result.authResults;
  }
}

7 开启“我的位置”按钮

确保应用可以获取用户定位,即ohos.permission.LOCATIONohos.permission.APPROXIMATELY_LOCATION权限在module.json5配置文件中声明。

 private locationPermissions: Array<Permissions> =
    ['ohos.permission.LOCATION', 'ohos.permission.APPROXIMATELY_LOCATION'];
    
// 确保权限声明,并在获得用户授权后开启“我的位置”功能 Index.ets > aboutToAppear()
// 地图初始化回调
this.callback = async (err, mapController) => {
  if (!err) {
    ...
    const grantStatus = await this.checkPermissions();
    if (!grantStatus) {
      await PermissionsUtil.reqPermissionsFromUser(this.locationPermissions);
      this.mapController?.setMyLocationEnabled(true);
    }
  }
}


// 校验应用是否被授予定位权限
async checkPermissions(): Promise<boolean> {
  for (const permission of this.locationPermissions) {
    const grantStatus: abilityAccessCtrl.GrantStatus = PermissionsUtil.checkAccessToken(permission);
    if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
      this.mapController?.setMyLocationEnabled(true);
      this.mapController?.setMyLocationControlsEnabled(true);
      return true;
    }
  }
  return false;
}

7 开启“我的位置”按钮

确保应用可以获取用户定位,即ohos.permission.LOCATIONohos.permission.APPROXIMATELY_LOCATION权限在module.json5配置文件中声明。

 private locationPermissions: Array<Permissions> =
    ['ohos.permission.LOCATION', 'ohos.permission.APPROXIMATELY_LOCATION'];
    
// 确保权限声明,并在获得用户授权后开启“我的位置”功能 Index.ets > aboutToAppear()
// 地图初始化回调
this.callback = async (err, mapController) => {
  if (!err) {
    ...
    const grantStatus = await this.checkPermissions();
    if (!grantStatus) {
      await PermissionsUtil.reqPermissionsFromUser(this.locationPermissions);
      this.mapController?.setMyLocationEnabled(true);
    }
  }
}


// 校验应用是否被授予定位权限
async checkPermissions(): Promise<boolean> {
  for (const permission of this.locationPermissions) {
    const grantStatus: abilityAccessCtrl.GrantStatus = PermissionsUtil.checkAccessToken(permission);
    if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
      this.mapController?.setMyLocationEnabled(true);
      this.mapController?.setMyLocationControlsEnabled(true);
      return true;
    }
  }
  return false;
}

在这里插入图片描述

我的位置

Map Kit默认使用系统的连续定位能力,如果开发者希望定制显示频率或者精准度,可以调用geoLocationManager相关接口获取用户位置坐标(WGS84坐标系)

注意访问设备的位置信息,必须申请ohos.permission.LOCATIONohos.permission.APPROXIMATELY_LOCATION权限,并且获得用户授权。在获取到用户坐标后,调用mapController对象的setMyLocation设置用户的位置。
// LocationUtil.ets

// 导入geoLocationManager模块
import { geoLocationManager } from '@kit.LocationKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { NBConstants } from '../constants/NBConstants';
import { JSON } from '@kit.ArkTS';

export class LocationUtil {

  // 获取当前位置
  static async currentLocation(): Promise<geoLocationManager.Location | undefined> {
    const request: geoLocationManager.SingleLocationRequest = {
      'locatingPriority': geoLocationManager.LocatingPriority.PRIORITY_LOCATING_SPEED,
      'locatingTimeoutMs': 10000
    };
    let location: geoLocationManager.Location | undefined = undefined;
    try {
      location = await geoLocationManager.getCurrentLocation(request);
      console.log(`${NBConstants.TAG} getLastLocation succeeded. Data: ${JSON.stringify(location)}`);
    } catch (error) {
      const err = error as BusinessError;
      console.error(`${NBConstants.TAG} getLastLocation failed. Cause: ${JSON.stringify(err)}`);
    }
    return location;
  }
}


// 获取我的位置 Index.ets
async getMyLocation() {
  const location: geoLocationManager.Location | undefined = await LocationUtil.currentLocation();
  if (location !== undefined) {
    this.mapController?.setMyLocation(location);
    this.mapController?.animateCamera(map.newLatLng({
      latitude: location.latitude,
      longitude: location.longitude
    }, 15), 200)
  }
}

// 监听“我的位置”按钮点击事件 Index.ets > aboutToAppear()
this.mapController?.on('myLocationButtonClick', () => {
  this.getMyLocation();
});

我的位置

Map Kit默认使用系统的连续定位能力,如果开发者希望定制显示频率或者精准度,可以调用geoLocationManager相关接口获取用户位置坐标(WGS84坐标系)

注意访问设备的位置信息,必须申请ohos.permission.LOCATIONohos.permission.APPROXIMATELY_LOCATION权限,并且获得用户授权。在获取到用户坐标后,调用mapController对象的setMyLocation设置用户的位置。
// LocationUtil.ets

// 导入geoLocationManager模块
import { geoLocationManager } from '@kit.LocationKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { NBConstants } from '../constants/NBConstants';
import { JSON } from '@kit.ArkTS';

export class LocationUtil {

  // 获取当前位置
  static async currentLocation(): Promise<geoLocationManager.Location | undefined> {
    const request: geoLocationManager.SingleLocationRequest = {
      'locatingPriority': geoLocationManager.LocatingPriority.PRIORITY_LOCATING_SPEED,
      'locatingTimeoutMs': 10000
    };
    let location: geoLocationManager.Location | undefined = undefined;
    try {
      location = await geoLocationManager.getCurrentLocation(request);
      console.log(`${NBConstants.TAG} getLastLocation succeeded. Data: ${JSON.stringify(location)}`);
    } catch (error) {
      const err = error as BusinessError;
      console.error(`${NBConstants.TAG} getLastLocation failed. Cause: ${JSON.stringify(err)}`);
    }
    return location;
  }
}


// 获取我的位置 Index.ets
async getMyLocation() {
  const location: geoLocationManager.Location | undefined = await LocationUtil.currentLocation();
  if (location !== undefined) {
    this.mapController?.setMyLocation(location);
    this.mapController?.animateCamera(map.newLatLng({
      latitude: location.latitude,
      longitude: location.longitude
    }, 15), 200)
  }
}

// 监听“我的位置”按钮点击事件 Index.ets > aboutToAppear()
this.mapController?.on('myLocationButtonClick', () => {
  this.getMyLocation();
});

在这里插入图片描述

9 使用华为账号服务(Account Kit)获取头像

Account Kit开放头像昵称授权能力,用户允许应用获取头像昵称后,可快速完成个人信息填写。

// Index.ets
// 默认用户头像
@State avatarUri: ResourceStr = $r('app.media.nutpi_logo');
// 获取用户头像
async getAvatarAndNickName(): Promise<void> {
  // 创建授权请求,并设置参数
  let authRequest = new authentication.HuaweiIDProvider().createAuthorizationWithHuaweiIDRequest();
  // 获取头像昵称需要的参数
  authRequest.scopes = ['profile'];
  // 用户是否需要登录授权,该值为true且用户未登录或未授权时,会拉起用户登录或授权页面
  authRequest.forceAuthorization = true;
  authRequest.state = util.generateRandomUUID();
  try {
    let controller = new authentication.AuthenticationController(getContext(this));
    let response: authentication.AuthorizationWithHuaweiIDResponse = await controller.executeRequest(authRequest);
    if (response) {
      this.avatarUri = response.data?.avatarUri as string;
    }
  } catch (error) {
    console.error('getAvatarAndNickName failed. Cause: ' + JSON.stringify(error));
  }
}
// 头像显示在页面右上角
Stack({ alignContent: Alignment.TopEnd }) {
  Stack({ alignContent: Alignment.Bottom }) {
    // 调用MapComponent组件初始化地图
    MapComponent({
      mapOptions: this.mapOption,
      mapCallback: this.callback
    })
      .width($r('app.string.full_page'))
      .height($r('app.string.full_page'))
  }
  .width($r('app.string.full_page'))
  .height($r('app.string.full_page'))

  Image(this.avatarUri)
    .width(64)
    .height(64)
    .borderRadius(32)
    .margin({ top: 16, right: 16 })
    .onClick(async () => {
      await this.getAvatarAndNickName();
    })
}
.width($r('app.string.full_page'))
.height($r('app.string.full_page'))

9 使用华为账号服务(Account Kit)获取头像

Account Kit开放头像昵称授权能力,用户允许应用获取头像昵称后,可快速完成个人信息填写。

// Index.ets
// 默认用户头像
@State avatarUri: ResourceStr = $r('app.media.nutpi_logo');
// 获取用户头像
async getAvatarAndNickName(): Promise<void> {
  // 创建授权请求,并设置参数
  let authRequest = new authentication.HuaweiIDProvider().createAuthorizationWithHuaweiIDRequest();
  // 获取头像昵称需要的参数
  authRequest.scopes = ['profile'];
  // 用户是否需要登录授权,该值为true且用户未登录或未授权时,会拉起用户登录或授权页面
  authRequest.forceAuthorization = true;
  authRequest.state = util.generateRandomUUID();
  try {
    let controller = new authentication.AuthenticationController(getContext(this));
    let response: authentication.AuthorizationWithHuaweiIDResponse = await controller.executeRequest(authRequest);
    if (response) {
      this.avatarUri = response.data?.avatarUri as string;
    }
  } catch (error) {
    console.error('getAvatarAndNickName failed. Cause: ' + JSON.stringify(error));
  }
}
// 头像显示在页面右上角
Stack({ alignContent: Alignment.TopEnd }) {
  Stack({ alignContent: Alignment.Bottom }) {
    // 调用MapComponent组件初始化地图
    MapComponent({
      mapOptions: this.mapOption,
      mapCallback: this.callback
    })
      .width($r('app.string.full_page'))
      .height($r('app.string.full_page'))
  }
  .width($r('app.string.full_page'))
  .height($r('app.string.full_page'))

  Image(this.avatarUri)
    .width(64)
    .height(64)
    .borderRadius(32)
    .margin({ top: 16, right: 16 })
    .onClick(async () => {
      await this.getAvatarAndNickName();
    })
}
.width($r('app.string.full_page'))
.height($r('app.string.full_page'))

在这里插入图片描述

10 页面底部添加“扫一扫”按钮,用于扫码开锁

// Index.ets
// 为了防止底部信息栏覆盖地图右下角按钮,使用offset属性在y轴方向上移56
Stack({ alignContent: Alignment.Bottom }) {
  // 调用MapComponent组件初始化地图
  MapComponent({
    mapOptions: this.mapOption,
    mapCallback: this.callback
  })
    .width($r('app.string.full_page'))
    .height($r('app.string.full_page'))
    .offset({ y: -56 })

  Row() {
    Column({ space: 8 }) {
      Text('扫码用车')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
      Text('附近有3辆单车可用')
        .fontSize(12)
        .fontWeight(FontWeight.Normal)
    }
    .height($r('app.string.full_page'))
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Start)
    Button() {
      Row({ space: 8 }) {
        Image($r('app.media.ic_line_viewfinder'))
          .width(20)
          .height(20)
          .fillColor(Color.White)
        Text('扫一扫')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor(Color.White)
      }
    }
    .height(40)
    .type(ButtonType.Capsule)
    .padding({ left: 10, right: 10 })
    .linearGradient({
      angle: 45,
      colors: [[0x49c5ef, 0.3], [0x4caefe, 0.8]]
    })
  }
  .width($r('app.string.full_page'))
  .height(64)
  .justifyContent(FlexAlign.SpaceBetween)
  .borderRadius({
    topLeft: 16,
    topRight: 16
  })
  .backgroundColor(Color.White)
  .padding({
    left: 16,
    right: 16
  })
}
.width($r('app.string.full_page'))
.height($r('app.string.full_page'))

10 页面底部添加“扫一扫”按钮,用于扫码开锁

// Index.ets
// 为了防止底部信息栏覆盖地图右下角按钮,使用offset属性在y轴方向上移56
Stack({ alignContent: Alignment.Bottom }) {
  // 调用MapComponent组件初始化地图
  MapComponent({
    mapOptions: this.mapOption,
    mapCallback: this.callback
  })
    .width($r('app.string.full_page'))
    .height($r('app.string.full_page'))
    .offset({ y: -56 })

  Row() {
    Column({ space: 8 }) {
      Text('扫码用车')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
      Text('附近有3辆单车可用')
        .fontSize(12)
        .fontWeight(FontWeight.Normal)
    }
    .height($r('app.string.full_page'))
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Start)
    Button() {
      Row({ space: 8 }) {
        Image($r('app.media.ic_line_viewfinder'))
          .width(20)
          .height(20)
          .fillColor(Color.White)
        Text('扫一扫')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor(Color.White)
      }
    }
    .height(40)
    .type(ButtonType.Capsule)
    .padding({ left: 10, right: 10 })
    .linearGradient({
      angle: 45,
      colors: [[0x49c5ef, 0.3], [0x4caefe, 0.8]]
    })
  }
  .width($r('app.string.full_page'))
  .height(64)
  .justifyContent(FlexAlign.SpaceBetween)
  .borderRadius({
    topLeft: 16,
    topRight: 16
  })
  .backgroundColor(Color.White)
  .padding({
    left: 16,
    right: 16
  })
}
.width($r('app.string.full_page'))
.height($r('app.string.full_page'))

在这里插入图片描述

11 点击“扫一扫”按钮,判断华为账号登录状态

步骤一: 导入authentication模块及相关公共模块。

import { authentication } from '@kit.AccountKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';

步骤二: 创建授权请求并设置参数。

// 创建请求参数
let stateRequest: authentication.StateRequest = {
  idType: authentication.IdType.UNION_ID,
  idValue: 'xxx' // 该值可以通过华为帐号登录接口获取
}

步骤三: 调用getHuaweiIDState方法获取华为账号登录状态。

// 判断华为账号登录状态
async getLoginState() {
  
  if (this.idValue != '') { //如果已经获取过id
    const stateRequest: authentication.StateRequest = {
      idType: authentication.IdType.UNION_ID,
      idValue: this.idValue
    };
    try {
      // 执行获取华为账号登录状态请求
      const result = await new authentication
        .HuaweiIDProvider().getHuaweiIDState(stateRequest);
      if (result.state === authentication.State.UNLOGGED_IN
        || result.state === authentication.State.UNAUTHORIZED) { // 未登录
        this.loginState = false;
      } else {
        this.loginState = true;
      }
    } catch (error) {
      const err = error as BusinessError;
      console.error(`${NBConstants.TAG} getLoginState Failed. Cause: ${JSON.stringify(err)}`);
    }
  }
}


// 在“扫一扫”点击事件中添加判断华为账号登录状态方法
Button() {
  Row({ space: 8 }) {
    Image($r('app.media.ic_line_viewfinder'))
      .width(20)
      .height(20)
      .fillColor(Color.White)
    Text('扫一扫')
      .fontSize(16)
      .fontWeight(FontWeight.Bold)
      .fontColor(Color.White)
  }
}
.height(40)
.type(ButtonType.Capsule)
.padding({ left: 10, right: 10 })
.linearGradient({
  angle: 45,
  colors: [[0x49c5ef, 0.3], [0x4caefe, 0.8]]
})
.onClick(async () => {
  await this.getLoginState();
})

12 华为账号未登录,使用按钮实现一键登录

在这里插入图片描述

// 在“扫一扫”点击事件中添加华为账号登录状态判断
Stack() {
  Row() {
    Column({ space: 8 }) {
      Text('扫码用车')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
      Text('附近有3辆单车可用')
        .fontSize(12)
        .fontWeight(FontWeight.Normal)
    }
    .height($r('app.string.full_page'))
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Start)
    Button() {
      Row({ space: 8 }) {
        Image($r('app.media.ic_line_viewfinder'))
          .width(20)
          .height(20)
          .fillColor(Color.White)
        Text('扫一扫')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor(Color.White)
      }
    }
    .height(40)
    .type(ButtonType.Capsule)
    .padding({ left: 10, right: 10 })
    .linearGradient({
      angle: 45,
      colors: [[0x49c5ef, 0.3], [0x4caefe, 0.8]]
    })
    .onClick(async () => {  //点击后检查登录并扫码(稍后抽取为函数)
        await this.getLoginState();
        if (this.loginState) {  // 已登录
          this.getAvatarAndNickName() //获取头像
          await this.startScan();
        } else { // 未登录
          // 调用华为账号一键登录
          this.showPanel = true;
        }
    })
  }
  .width($r('app.string.full_page'))
  .height(64)
  .justifyContent(FlexAlign.SpaceBetween)
  .borderRadius({
    topLeft: 16,
    topRight: 16
  })
  .backgroundColor(Color.White)
  .padding({
    left: 16,
    right: 16
  })
}
.width($r('app.string.full_page'))
Stack() {
  LoginPanelComponent({ showPanel: this.showPanel, idValue: this.idValue })
}

//抽取登录和扫码的点击事件为一个函数
  async loginToScan(){
    await this.getLoginState();
    if (this.loginState) {  // 已登录
      this.getAvatarAndNickName() //获取头像
      await this.startScan();
    } else { // 未登录
      // 调用华为账号一键登录
      this.showPanel = true;
    }
  }

  // 用户登录获取的UnionID; 当id发生变化时,触发登录
  @State @Watch('loginToScan') idValue: string = ""

13 一键登录面板的自定义

// LoginPanelCompoent.ets
/**
 * @description 自定义登录组件
 */
import { LoginPanel, loginComponentManager, authentication } from '@kit.AccountKit';
import { JSON, util } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { promptAction } from '@kit.ArkUI';
import { NBConstants } from '../constants/NBConstants';

@Component
export struct LoginPanelComponent {
  // 是否展示LoginPanel组件
  @Link showPanel: boolean;
  // 用户登录获取的UnionID
  @Link idValue: string;

  // 定义LoginPanel展示的隐私文本
  privacyText: loginComponentManager.PrivacyText[] = [{
    text: '已阅读并同意',
    type: loginComponentManager.TextType.PLAIN_TEXT
  }, {
    text: '《用户服务协议》',
    tag: '用户服务协议',
    type: loginComponentManager.TextType.RICH_TEXT
  }];

  // 构造LoginPanel组件的控制器
  controller = new loginComponentManager.LoginPanelController()
    .onClickLoginWithHuaweiIDButton((error: BusinessError, response: loginComponentManager.HuaweiIDCredential) => {
      if (error) {
        console.error(NBConstants.TAG + "onClickLoginWithHuaweiIDButton failed. Cause: " + JSON.stringify(error));
        return;
      }
      console.log(NBConstants.TAG + "onClickLoginWithHuaweiIDButton ==> " + JSON.stringify(response));
      this.idValue = response.unionID;
      this.showPanel = false //登录成功,隐藏登录面板
    })
  @State phoneNum: string = "";
  // 获取华为账号的匿名手机号
  async getQuickLoginAnonymousPhone() {
    // 创建授权请求,并设置参数
    let authRequest = new authentication.HuaweiIDProvider().createAuthorizationWithHuaweiIDRequest();
    // 获取手机号需要传申请的scope
    authRequest.scopes = ['quickLoginAnonymousPhone'];
    // 用于防跨站点请求伪造,非空字符即可
    authRequest.state = util.generateRandomUUID();

    if (this.idValue == '' || this.showPanel) { //未登录 或 展示面板时
      // 用户是否需要登录授权,该值为true且用户未登录或未授权时,会拉起用户登录或授权页面
      authRequest.forceAuthorization = true
    } else {
      authRequest.forceAuthorization = false
    }

    let controller = new authentication.AuthenticationController(getContext(this));
    try {
      let response: authentication.AuthorizationWithHuaweiIDResponse = await controller.executeRequest(authRequest);
      let anonymousPhone = response.data?.extraInfo?.quickLoginAnonymousPhone;
      if (anonymousPhone) {
        this.phoneNum = anonymousPhone as string;
      }
    } catch (error) {
      console.error(NBConstants.TAG + 'getQuickLoginAnonymousPhone failed. Cause: ' + JSON.stringify(error));
    }
  }

  async aboutToAppear() {
    await this.getQuickLoginAnonymousPhone();
  }

  build() {
    if (this.showPanel) {
      // 构造LoginPanel UI组件参数
      Stack({ alignContent: Alignment.Bottom }) {
        LoginPanel({
          show: this.showPanel,
          params: {
            appInfo: {
              appIcon: $r('app.media.nutpi_logo'),
              appName: $r('app.string.app_name'),
              appDescription: $r('app.string.module_desc')
            },
            anonymousPhoneNumber: this.phoneNum,
            privacyText: this.privacyText,
            loginType: loginComponentManager.LoginType.QUICK_LOGIN
          },
          controller: this.controller
        })
      }
      .width('100%')
      .height('100%')
    }
  }
}

14 华为账号已登录,调用统一扫码服务(Scan Kit)进行扫码解锁

步骤一: 导入默认界面扫码模块

import { scanCore, scanBarcode } from '@kit.ScanKit';
// 导入默认界面需要的日志模块和错误码模块
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';

步骤二: 调用startScanForResult方法拉起默认扫码界面

// 启用默认扫码界面
async startScan() {
  console.info(NBConstants.TAG + "默认界面扫码开始。");
  // 定义扫码参数options
  const options: scanBarcode.ScanOptions = {
    scanTypes: [scanCore.ScanType.ALL],
    enableMultiMode: true,
    enableAlbum: true
  };
  try {
    const result = await scanBarcode.startScanForResult(getContext(this), options);
    console.info(NBConstants.TAG + "Succeed. Data: " + JSON.stringify(result));
    promptAction.showToast({
      message: "开锁成功!",
      duration: 5000
    })
  } catch (error) {
    const e: BusinessError = error as BusinessError;
    console.error(NBConstants.TAG + "Failed. Cause: " + JSON.stringify(e));
  }
}

步骤三: 在“扫一扫”点击事件中添加华为账号已登录,开启扫码方法

Button() {
  Row({ space: 8 }) {
    Image($r('app.media.ic_line_viewfinder'))
      .width(20)
      .height(20)
      .fillColor(Color.White)
    Text('扫一扫')
      .fontSize(16)
      .fontWeight(FontWeight.Bold)
      .fontColor(Color.White)
  }
}
.height(40)
.type(ButtonType.Capsule)
.padding({ left: 10, right: 10 })
.linearGradient({
  angle: 45,
  colors: [[0x49c5ef, 0.3], [0x4caefe, 0.8]]
})
.onClick(async () => {
  await this.loginToScan()
})

14 华为账号已登录,调用统一扫码服务(Scan Kit)进行扫码解锁

步骤一: 导入默认界面扫码模块

import { scanCore, scanBarcode } from '@kit.ScanKit';
// 导入默认界面需要的日志模块和错误码模块
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';

步骤二: 调用startScanForResult方法拉起默认扫码界面

// 启用默认扫码界面
async startScan() {
  console.info(NBConstants.TAG + "默认界面扫码开始。");
  // 定义扫码参数options
  const options: scanBarcode.ScanOptions = {
    scanTypes: [scanCore.ScanType.ALL],
    enableMultiMode: true,
    enableAlbum: true
  };
  try {
    const result = await scanBarcode.startScanForResult(getContext(this), options);
    console.info(NBConstants.TAG + "Succeed. Data: " + JSON.stringify(result));
    promptAction.showToast({
      message: "开锁成功!",
      duration: 5000
    })
  } catch (error) {
    const e: BusinessError = error as BusinessError;
    console.error(NBConstants.TAG + "Failed. Cause: " + JSON.stringify(e));
  }
}

步骤三: 在“扫一扫”点击事件中添加华为账号已登录,开启扫码方法

Button() {
  Row({ space: 8 }) {
    Image($r('app.media.ic_line_viewfinder'))
      .width(20)
      .height(20)
      .fillColor(Color.White)
    Text('扫一扫')
      .fontSize(16)
      .fontWeight(FontWeight.Bold)
      .fontColor(Color.White)
  }
}
.height(40)
.type(ButtonType.Capsule)
.padding({ left: 10, right: 10 })
.linearGradient({
  angle: 45,
  colors: [[0x49c5ef, 0.3], [0x4caefe, 0.8]]
})
.onClick(async () => {
  await this.loginToScan()
})

在这里插入图片描述

15 通过AGC平台推送服务向坚果单车应用推送消息

步骤一: 获取Push Token。在应用的UIAbility(例如EntryAbility)的onCreate方法中调用getToken()获取Push Token并上报到开发者的服务端,方便开发者的服务端向终端推送消息。

// 导入pushService模块
import { pushService } from '@kit.PushKit';

// onCreate方法中调用getToken()接口获取Push Token
async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
  hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
  try {
    const pushToken: string = await pushService.getToken();
    // 上报Push Token
    console.info(`${NBConstants.TAG} Push Token: ${pushToken}`);
  } catch (error) {
    const e: BusinessError = error as BusinessError;
    console.error(NBConstants.TAG + "Failed. Cause: " + JSON.stringify(e));
  }
}

步骤二: 请求通知授权

async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
  hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
  try {
    const pushToken: string = await pushService.getToken();
    // 上报Push Token
    console.info(`${NBConstants.TAG} Push Token: ${pushToken}`);
    this.requestNotification();
  } catch (error) {
    const e = error as BusinessError;
    console.error(NBConstants.TAG + "Failed. Cause: " + JSON.stringify(e));
  }
}

// 请求通知授权
async requestNotification() {
  try {
    console.info("requestNotification: 请求通知授权开始。");
    // 查询通知是否授权
    const notificationEnabled: boolean = await notificationManager.isNotificationEnabled();
    console.info("requestNotification: " + (notificationEnabled ? '已' : '未') + "授权");
    if (!notificationEnabled) {
      // 请求通知授权
      await notificationManager.requestEnableNotification();
    }
  } catch (error) {
    const e: BusinessError = error as BusinessError;
    console.error("requestNotification failed. Cause: " + JSON.stringify(e));
  }
}

在这里插入图片描述

16 服务端发推测试

登录AGC平台,在“我的项目”中选择目标应用,点击左侧菜单栏“增长 > 推送服务”,点击页面中“添加通知”按钮,进入推送通知详情页面,填写相关信息,点击“提交”按钮发送。

在这里插入图片描述

步骤四: 下拉通知信息页面,查看是否接受到发送的通知消息。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2255641.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Thonny IDE + MicroPython + ESP32 + 0.96寸OLED(IIC) 显示任意字符

四针脚0.96英寸OLED显示屏模块的具体参数如下表所示。 参数名称 参数特性 分辨率 128x64像素 通信方式 IIC 驱动芯片 SSD1306 屏幕颜色 白色、蓝色或黄蓝双色 元件&#xff1a; 四针脚0.96英寸OLED显示屏模块 ESP32 DEVKIT_C开发板 杜邦线USB Type-C 接线&#xf…

监控易助力IT运维再升级

在当今数字化时代&#xff0c;企业对于IT系统的依赖程度日益加深&#xff0c;IT运维管理的重要性也随之凸显。作为首都的一家知名企业&#xff0c;北京某公司在业务快速发展的同时&#xff0c;也面临着IT系统规模不断扩大、运维管理复杂度不断提升的挑战。为了更好地应对这些挑…

线程(二)——线程安全

如何理解线程安全&#xff1a; 多线程并发执行的时候&#xff0c;有时候会触发一些“bug”&#xff0c;虽然代码能够执行&#xff0c;线程也在工作&#xff0c;但是过程和结果都不符合我们的开发时的预期&#xff0c;所以我们将此类线程称之为“线程安全问题”。 例如&#xff…

prometheusgrafana实现监控告警

Prometheus负责集群数据的监控和采集&#xff0c;然后传递给grafana进行可视化&#xff0c;集成睿象云可实现监控报警&#xff0c;为了方便操作&#xff0c;可以通过iframe嵌套grafana到指定的页面。 文章目录 1.Grafana集成Prometheus2.iframe内嵌grafana3.监控告警 1.Grafana…

双绞线直连两台电脑的方法及遇到的问题

文章目录 前言一、步骤二、问题总结&#xff1a;问题1:遇到ping不通的问题。问题2:访问其他电脑上的共享文件时提示输入网络凭证问题3:局域网共享文件时提示“没有权限访问&#xff0c;请与网络管理员联系请求访问权限” 前言 办公室里有两台电脑&#xff0c;一台装了显卡用于…

如何使用Java编写Jmeter函数

Jmeter 自带有各种功能丰富的函数&#xff0c;可以帮助我们进行测试&#xff0c;但有时候提供的这些函数并不能满足我们的要求&#xff0c;这时候就需要我们自己来编写一个自定义的函数了。例如我们在测试时&#xff0c;有时候需要填入当前的时间&#xff0c;虽然我们可以使用p…

练8:递归

欢迎大家订阅【蓝桥杯Python每日一练】 专栏&#xff0c;开启你的 Python数据结构与算法 学习之旅&#xff01; 1 递归 在 Python 中&#xff0c;递归&#xff08;Recursion&#xff09; 是一种函数调用自身的编程技术。 递归通常用来解决可以分解为类似子问题的问题&#xff…

Python金融大数据分析概述

&#x1f482; 个人网站:【 摸鱼游戏】【神级代码资源网站】【海拥导航】&#x1f485; 想寻找共同学习交流&#xff0c;摸鱼划水的小伙伴&#xff0c;请点击【全栈技术交流群】 金融大数据分析在金融科技领域越来越重要&#xff0c;它涉及从海量数据中提取洞察&#xff0c;为金…

WSL2下如何部署CosyVoice并开启API服务

环境: WSL2 英伟达4070ti 12G Win10 Ubuntu22.04 问题描述: WSL下如何部署CosyVoice并开启API服务 解决方案: CosyVoice 下载不顺的时候,最好提前开科学 一、部署 1.拉取源码 git clone –recursive https://github.com/FunAudioLLM/CosyVoice.gitwsl下拉取 gi…

室联人形机器人:家政服务任务结构化、技术要点、深入应用FPGA的控制系统框架设计(整合版)

目录&#xff1a; 0 引言 1 人形机器人对室内家政服务任务的结构化 1.1人形机器人在室内家政服务中的比较优势 1.1.1 人形机器人拟人性的7个维度 1.1.2 拟人性在室内家政服务工作中的比较优势 1.1.3 潜在的重要用户&#xff1a;宠物爱好者 1.2 居所室内环境的特征与结构…

基于 Apache Dolphinscheduler3.1.9中的Task 处理流程解析

实现一个调度任务&#xff0c;可能很简单。但是如何让工作流下的任务跑得更好、更快、更稳定、更具有扩展性&#xff0c;同时可视化&#xff0c;是值得我们去思考得问题。 Apache DolphinScheduler是一个分布式和可扩展的开源工作流协调平台&#xff0c;具有强大的DAG可视化界…

物联网接入网关的数据安全和高效传输详解

物联网接入网关&#xff0c;作为连接物联网终端设备与云端或本地服务器的关键环节&#xff0c;不仅负责数据的汇聚与转发&#xff0c;更需确保数据在传输过程中的安全无虞与高效流畅。 一、数据安全&#xff1a;构筑坚实防线 1. 加密技术的应用 天拓四方物联网接入网关内置了…

双指针算法(超详细版)

希望大家多多关注&#xff0c;有三必回 1.双指针 1.1快慢双指针 快慢双指针常用来解决循环问题&#xff0c;或是查找中间节点 1.1.1循环链表&#xff08;141. 环形链表 - 力扣&#xff08;LeetCode&#xff09;&#xff09; 解题思路&#xff1a; 1.定义快慢指针fast和slo…

Rain后台权限管理系统,快速开发

这段时间一直没有更新&#xff0c;因为在写一个脚手架&#xff0c;今天Rain项目终于完工&#xff0c;已经发布到github,免费使用 项目github链接 https://github.com/Rain-hechang/Rain 简介 前端采用Vue3.x、Element UI。 后端采用Spring Boot、Spring Security、Redis &…

欧歌Web电视 1.2|全新修改版,新增更多频道,更稳定

欧歌Web电视App是一款功能强大的电视直播软件&#xff0c;通过WebView二次开发&#xff0c;对内置线路进行了优化和增加&#xff0c;让用户可以看到更多的频道。首次打开如果不会自动全屏&#xff0c;可以进入设置调整画面尺寸。该版本新增了多个地方频道和娱乐内容频道&#x…

嵌入式系统与移动设备开发

文章目录 1 嵌入式系统概述1.1 嵌入式系统基本概念1.1.1 嵌入式系统定义1.1.2 嵌入式系统的发展1.1.3 嵌入式系统的特点 1.2 嵌入式系统分类1.2.1 单个微处理器1.2.2 嵌入式处理器可扩展的系统1.2.3 复杂的嵌入式系统1.2.4 在制造或过程控制中使用的计算机系统 1.3 嵌入式处理器…

使用 Elastic 和 Amazon Bedrock 制作混合地理空间 RAG 应用程序

作者&#xff1a;来自 Elastic Udayasimha Theepireddy (Uday), Srinivas Pendyala, Ayan Ray 借助 Elasticsearch 及其向量数据库&#xff0c;你可以构建可配置的搜索和可信的生成式 AI (GenAI) 体验&#xff0c;这些体验可快速从原型扩展到生产。主要功能包括&#xff1a; 内…

【opencv入门教程】15. 访问像素的十四种方式

文章选自&#xff1a; 一、像素访问 一张图片由许多个点组成&#xff0c;每个点就是一个像素&#xff0c;每个像素包含不同的值&#xff0c;对图像像素操作是图像处理过程中常使用的 二、访问像素 void Samples::AccessPixels1(Mat &image, int div 64) {int nl imag…

Ansys Maxwell使用技巧

1、回到原点 点击Fit All 2、长方体做差 选中两个长方体&#xff0c; 点击Subtracct&#xff0c;就可以得到一个镂空的绕组。 3、电感仿真步骤 3.1 画磁芯 3.2 画绕组 3.3 加激励 选择截面积-右键绕组-Edit-Surface-Section-YZ 选择一个截面添加电流激励 3.4选材料 绕组一…

掌握谈判技巧,达成双赢协议

在当今竞争激烈且合作频繁的社会环境中&#xff0c;谈判成为了我们解决分歧、谋求共同发展的重要手段。无论是商业合作、职场交流&#xff0c;还是国际事务协商&#xff0c;掌握谈判技巧以达成双赢协议都具有极其关键的意义。它不仅能够让各方在利益分配上找到平衡点&#xff0…