当前位置: 首页 > article >正文

鸿蒙NEXT项目实战-百得知识库04

代码仓地址,大家记得点个star

IbestKnowTeach: 百得知识库基于鸿蒙NEXT稳定版实现的一款企业级开发项目案例。 本案例涉及到多个鸿蒙相关技术知识点: 1、布局 2、配置文件 3、组件的封装和使用 4、路由的使用 5、请求响应拦截器的封装 6、位置服务 7、三方库的使用和封装 8、头像上传 9、应用软件更新等https://gitee.com/xt1314520/IbestKnowTeach

我的页面开发

设计图

需求分析

华为账号登录功能开发(需要真机)

注意:这个功能需要真机,所以东林在gitee代码仓里面的代码是普通账号密码登录得,没有使用华为账号登录

我们之前封装的响应拦截器里面有判断,根据接口返回的状态码判断是否有权限,如果没有权限会跳转到登录页面

我们也可以点击我的页面头像区域可以到登录页面

1、登录界面整体布局

从上到下的整体布局所以我们使用Column进行包裹组件,整体可以拆分出五块区域

2、刨去华为账号登录的代码
import { CommonConstant } from '../contants/CommonConstant';
import { router } from '@kit.ArkUI';
import { RouterConstant } from '../contants/RouterConstant';


@Entry
  @Component
  struct LoginPage {
    // 多选框状态
    @State multiSelectStatus: boolean = false


    build() {
      Column() {
        Column({ space: 15 }) {
          Image($r('app.media.app_icon'))
            .width($r('app.float.common_width_big')).aspectRatio(1)
            .borderRadius(15)
          Text($r('app.string.application_name'))
            .fontSize($r('app.float.common_font_size_huge'))
            .fontWeight(FontWeight.Medium)
          Text($r('app.string.app_description'))
            .fontSize($r('app.float.common_font_size_small'))
            .fontColor($r('app.color.common_gray'))
          // 用户协议和隐私协议
          Row() {
            Checkbox()
              .select($$this.multiSelectStatus)
              .width(18)
              .selectedColor("#FA6D1D")
            Text() {
              Span("已阅读并同意")
              Span(" 用户协议 ")
                .fontColor(Color.Black)
                .onClick(() => {
                  router.pushUrl({ url: RouterConstant.PAGE_USER_POLICY })
                })
              Span("和")
              Span(" 隐私政策 ")
                .fontColor(Color.Black)
                .onClick(() => {
                  router.pushUrl({ url: RouterConstant.PAGE_PRIVACY_POLICY })
                })
            }.fontSize($r('app.float.common_font_size_small'))
              .fontColor($r('app.color.common_gray'))
          }
        }.height('50%')
      }
      .padding($r('app.float.common_padding'))
        .height(CommonConstant.HEIGHT_FULL)
        .width(CommonConstant.WIDTH_FULL)
        .justifyContent(FlexAlign.Center)
        .backgroundImage($r('app.media.background'))
        .backgroundImageSize({ width: CommonConstant.WIDTH_FULL, height: CommonConstant.HEIGHT_FULL })
        .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])
    }
  }

3、集成华为账号登录

参考东林的鸿蒙应用开发-高级课里面有个章节叫华为账号服务

大概分为以下几个步骤

1、在AGC上创建项目和应用

2、本地创建应用工程

3、本地生成签名文件

4、将签名放在AGC上生成证书

5、复制Client_ID放在本地项目中

6、本地完成签名

import { LoginWithHuaweiIDButton, loginComponentManager } from '@kit.AccountKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { CommonConstant } from '../contants/CommonConstant';
import { router } from '@kit.ArkUI';
import { RouterConstant } from '../contants/RouterConstant';
import { authentication } from '@kit.AccountKit';
import { util } from '@kit.ArkTS';
import { Logger } from '../utils/Logger';
import { showToast } from '../utils/Toast';
import userApi from '../api/UserApi';
import { PreferencesUtil } from '../utils/PreferencesUtil';

@Entry
  @Component
  struct LoginPage {
    // 多选框状态
    @State multiSelectStatus: boolean = false
    // 构造LoginWithHuaweiIDButton组件的控制器
    controller: loginComponentManager.LoginWithHuaweiIDButtonController =
    new loginComponentManager.LoginWithHuaweiIDButtonController()
    .onClickLoginWithHuaweiIDButton((error: BusinessError, response: loginComponentManager.HuaweiIDCredential) => {
      // 判断是否勾选用户协议和隐私政策
      if (!this.multiSelectStatus) {
        showToast('请勾选用户协议和隐私政策')
        return
      }
      if (error) {
        Logger.error(`Failed to onClickLoginWithHuaweiIDButton. Code: ${error.code}, message: ${error.message}`);
        showToast('华为账号登录失败')
        return;
      }
      // 登录成功
      if (response) {
        Logger.info('Succeeded in getting response.')
        // 创建授权请求,并设置参数
        const authRequest = new authentication.HuaweiIDProvider().createAuthorizationWithHuaweiIDRequest();
        // 获取头像昵称需要传如下scope
        authRequest.scopes = ['profile'];
        // 用户是否需要登录授权,该值为true且用户未登录或未授权时,会拉起用户登录或授权页面
        authRequest.forceAuthorization = true;
        // 用于防跨站点请求伪造
        authRequest.state = util.generateRandomUUID();
        // 执行授权请求
        try {
          const controller = new authentication.AuthenticationController(getContext(this));
          controller.executeRequest(authRequest).then((data) => {
            const authorizationWithHuaweiIDResponse = data as authentication.AuthorizationWithHuaweiIDResponse;
            const state = authorizationWithHuaweiIDResponse.state;
            if (state != undefined && authRequest.state != state) {
              Logger.error(`Failed to authorize. The state is different, response state: ${state}`);
              showToast('华为账号登录失败')
              return;
            }
            Logger.info('Succeeded in authentication.');
            const authorizationWithHuaweiIDCredential = authorizationWithHuaweiIDResponse.data!;
            // 头像
            const avatarUri = authorizationWithHuaweiIDCredential.avatarUri;
            // 昵称
            const nickName = authorizationWithHuaweiIDCredential.nickName;
            // 唯一id
            const unionID = authorizationWithHuaweiIDCredential.unionID;
            // 登录接口
            login(unionID, nickName, avatarUri)
          }).catch((err: BusinessError) => {
            showToast('华为账号登录失败')
              Logger.error(`Failed to auth. Code: ${err.code}, message: ${err.message}`);
            });
          } catch (error) {
            showToast('华为账号登录失败')
            Logger.error(`Failed to auth. Code: ${error.code}, message: ${error.message}`);
          }

        }
      });

  build() {
    Column() {
      Column({ space: 15 }) {
        Image($r('app.media.app_icon'))
          .width($r('app.float.common_width_big')).aspectRatio(1)
          .borderRadius(15)
        Text($r('app.string.application_name'))
          .fontSize($r('app.float.common_font_size_huge'))
          .fontWeight(FontWeight.Medium)
        Text($r('app.string.app_description'))
          .fontSize($r('app.float.common_font_size_small'))
          .fontColor($r('app.color.common_gray'))
        // 用户协议和隐私协议
        Row() {
          Checkbox()
            .select($$this.multiSelectStatus)
            .width(18)
            .selectedColor("#FA6D1D")
          Text() {
            Span("已阅读并同意")
            Span(" 用户协议 ")
              .fontColor(Color.Black)
              .onClick(() => {
                router.pushUrl({ url: RouterConstant.PAGE_USER_POLICY })
              })
            Span("和")
            Span(" 隐私政策 ")
              .fontColor(Color.Black)
              .onClick(() => {
                router.pushUrl({ url: RouterConstant.PAGE_PRIVACY_POLICY })
              })
          }.fontSize($r('app.float.common_font_size_small'))
          .fontColor($r('app.color.common_gray'))
        }
      }.height('50%')

      Column() {
        LoginWithHuaweiIDButton({
          params: {
            // LoginWithHuaweiIDButton支持的样式
            style: loginComponentManager.Style.BUTTON_RED,
            // 账号登录按钮在登录过程中展示加载态
            extraStyle: {
              buttonStyle: new loginComponentManager.ButtonStyle().loadingStyle({
                show: true
              })
            },
            // LoginWithHuaweiIDButton的边框圆角半径
            borderRadius: 24,
            // LoginWithHuaweiIDButton支持的登录类型
            loginType: loginComponentManager.LoginType.ID,
            // LoginWithHuaweiIDButton支持按钮的样式跟随系统深浅色模式切换
            supportDarkMode: true,
            // verifyPhoneNumber:如果华为账号用户在过去90天内未进行短信验证,是否拉起Account Kit提供的短信验证码页面
            verifyPhoneNumber: true,
          },
          controller: this.controller,
        })
      }.width('80%')
      .height(40)
    }
    .padding($r('app.float.common_padding'))
    .height(CommonConstant.HEIGHT_FULL)
    .width(CommonConstant.WIDTH_FULL)
    .justifyContent(FlexAlign.Center)
    .backgroundImage($r('app.media.background'))
    .backgroundImageSize({ width: CommonConstant.WIDTH_FULL, height: CommonConstant.HEIGHT_FULL })
    .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])
  }
}

/**
 * 登录
 * @param unionID
 * @param nickname
 * @param avatarUri
 */
async function login(unionID?: string, nickname?: string, avatarUri?: string) {
  if (!nickname) {
    nickname = '小得'
  }
  if (!avatarUri) {
    avatarUri =
      'https://upfile-drcn.platform.hicloud.com/DT4ISbQduGIF5Gz5g_Z9yg.PCj5oenfVYPRaeJp1REFEyac5ctfyoz-bD3L3k5cJTIDkrfDyewIkQaOAEoTWdgIxA_sJ0DD5RITPB85tfWAF7oquqQ6AvE4Jt8dIRUoyic4djriMA.112968985.jpg'
  }
  // 调用服务端登录
  const token = await userApi.userLogin({ unionId: unionID, nickname: nickname, avatarUri: avatarUri });
  // 如果token存在
  if (token) {
    AppStorage.setOrCreate(CommonConstant.TOKEN_NAME, token)
    PreferencesUtil.savaData(CommonConstant.PREFERENCES_NAME, CommonConstant.TOKEN_NAME, token)
    // 获取用户信息
    const userInfo = await userApi.getUserInfo();
    if (!userInfo) {
      showToast(CommonConstant.DEFAULT_LOGIN_ERROR)
    }
    // 存放用户数据
    AppStorage.setOrCreate(CommonConstant.USER_INFO, userInfo)
    PreferencesUtil.savaData(CommonConstant.PREFERENCES_NAME, CommonConstant.USER_INFO, JSON.stringify(userInfo))
    // 登录成功
    showToast('登录成功')
    // 回到首页
    router.pushUrl({
      url: RouterConstant.PAGE_INDEX, params: {
        "currentIndex": 0
      }
    })
  } else {
    showToast(CommonConstant.DEFAULT_LOGIN_ERROR)
  }
}

4、隐私和用户协议页面

新建两个Page页面,然后在resources/rawfile下面新建两个html文件

页面里面使用Web组件来包裹html文件

import { webview } from '@kit.ArkWeb'
import { CommonConstant } from '../contants/CommonConstant'

@Entry
  @Component
  struct PolicyPage {
    webViewController: webview.WebviewController = new webview.WebviewController

    build() {
      Navigation() {
        Web({
          src: $rawfile("PrivacyPolicy.html"),
          controller: this.webViewController
        })
      }
      .height(CommonConstant.HEIGHT_FULL)
        .width(CommonConstant.WIDTH_FULL)
        .title($r('app.string.privacy_policy'))
        .titleMode(NavigationTitleMode.Mini)
        .mode(NavigationMode.Stack)
        .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])
    }
  }

import { webview } from '@kit.ArkWeb'
import { CommonConstant } from '../contants/CommonConstant'

@Entry
  @Component
  struct UserPolicyPage {
    webViewController: webview.WebviewController = new webview.WebviewController

    build() {
      Navigation() {
        Web({
          src: $rawfile("UserPolicy.html"),
          controller: this.webViewController
        })
      }
      .height(CommonConstant.HEIGHT_FULL)
        .width(CommonConstant.WIDTH_FULL)
        .title($r('app.string.user_policy'))
        .titleMode(NavigationTitleMode.Mini)
        .mode(NavigationMode.Stack)
        .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])
    }
  }
5、编写用户接口方法
import http from '../request/Request'
import { LoginParam, UserInfo, UserNicknameUpdateParam } from './UserApi.type'

/**
 * 用户接口
 */
class UserApi {
  /**
   * 登录接口
   */
  userLogin = (data: LoginParam): Promise<string> => {
    return http.post('/v1/user/login', data)
  }
  /**
  * 获取用户信息
  */
  getUserInfo = (): Promise<UserInfo> => {
    return http.get('/v1/user/info')
  }
  /**
   * 修改用户昵称
   */
  editNickname = (data: UserNicknameUpdateParam) => {
    return http.post('/v1/user/editNickname', data)
  }
}

const userApi = new UserApi();

export default userApi as UserApi;
/**
 * 登录接口的传参
 */
export interface LoginParam {
  /**
   * 华为账号id
   */
  unionId?: string
  /**
   * 昵称
   */
  nickname?: string
  /**
   * 头像
   */
  avatarUri?: string
}


/**
 * 用户信息
 */
export interface UserInfo {
  /**
   * 用户id
   */
  id: number
  /**
  * 华为账号id
  */
  unionId: string
  /**
   * 昵称
   */
  nickname: string
  /**
   * 头像
   */
  avatarUri: string

}

/**
 * 修改用户昵称接口入参
 */
export interface UserNicknameUpdateParam {
  /**
   * 昵称
   */
  nickname: string
}

我的页面整体布局

设计图

需求分析

封装组件
1、标题组件
import { CommonConstant } from '../contants/CommonConstant'

@Component
  export struct TitleComponent {
    // 标题
    @Link title: string

    build() {
      Row() {
        Text(this.title)
          .fontSize($r('app.float.common_font_size_huge'))
          .fontWeight(FontWeight.Medium)
      }.width(CommonConstant.WIDTH_FULL)
        .margin({ top: 10, bottom: 20 })
    }
  }
2、导航组件
import { CommonConstant } from '../contants/CommonConstant'
import { FunctionBarData } from '../models/FunctionBarData'
import { router } from '@kit.ArkUI'
import { PreferencesUtil } from '../utils/PreferencesUtil'
import { RouterConstant } from '../contants/RouterConstant'
import { IBestButton, IBestDialog, IBestDialogUtil } from '@ibestservices/ibest-ui'
import feedbackInfoApi from '../api/FeedbackInfoApi'
import { showToast } from '../utils/Toast'
import { ApplicationCheckUtil } from '../utils/ApplicationCheckUtil'

@Component
  @Entry
  export struct FunctionBarComponent {
    @State functionBarData: FunctionBarData = {
      icon: '',
      text: '',
      router: '',
      eventType: ''
    }
    // 反馈信息
    @State inputValue: string = ''
    @State formInputError: boolean = false
    @State dialogVisible: boolean = false

    @Builder
    formInputContain() {
      Column() {
        TextInput({ 'placeholder': '请输入反馈意见,长度不能超过255字符' })
          .fontSize(14)
          .placeholderFont({ size: 14 })
          .onChange((value) => {
            this.inputValue = value;
            this.formInputError = false
          })
        if (this.formInputError) {
          Text('反馈意见不能为空')
            .width(CommonConstant.WIDTH_FULL)
            .textAlign(TextAlign.Start)
            .margin({
              top: 5,
              left: 5
            })
            .fontColor(Color.Red)
            .fontSize($r('app.float.common_font_size_small'))
            .transition({ type: TransitionType.Insert, opacity: 1 })
            .transition({ type: TransitionType.Delete, opacity: 0 })
        }

      }.width('90%').margin({ top: 15, bottom: 15 })
    }

    build() {
      Row() {
        IBestDialog({
          visible: $dialogVisible,
          title: "反馈意见",
          showCancelButton: true,
          defaultBuilder: (): void => this.formInputContain(),
          beforeClose: async (action) => {
            if (action === 'cancel') {
              return true
            }
            const valueLength = this.inputValue.trim().length;
            this.formInputError = !valueLength;
            if (!this.formInputError) {
              // 添加反馈内容
              await feedbackInfoApi.addFeedbackContent({ content: this.inputValue })
              // 更新用户个人信息
              showToast('添加反馈意见成功')
              this.inputValue = ''
              return true
            }
            return !this.formInputError
          }
        })

        Row({ space: 10 }) {
          if (this.functionBarData.icon != '') {
            Image(this.functionBarData.icon)
              .width(30).aspectRatio(1)
          }
          Text(this.functionBarData.text)
            .fontSize($r('app.float.common_font_size_medium'))
            .fontWeight(FontWeight.Normal)
        }

        Image($r('app.media.icon_arrow'))
          .width(15).aspectRatio(1)

      }
      .width(CommonConstant.WIDTH_FULL)
        .height($r('app.float.common_height_small'))
        .backgroundColor($r('app.color.common_white'))
        .padding(10)
        .justifyContent(FlexAlign.SpaceBetween)
        .borderRadius(5)
        .onClick(() => {
          if (this.functionBarData.router) {
        router.pushUrl({ url: this.functionBarData.router })
      } else if (this.functionBarData.eventType === 'logout') {
        // 退出登录
        logout()
      } else if (this.functionBarData.eventType === 'feedback') {
        // 点击反馈
        this.dialogVisible = true
      } else if (this.functionBarData.eventType === 'checkAppUpdate') {
        // 检查更新
        ApplicationCheckUtil.checkAppUpdate()
      }
    })
  }
}

/**
 * 退出登录
 */
function logout() {
  IBestDialogUtil.open({
    title: "提示",
    message: "是否确认退出登录?",
    showCancelButton: true,
    onConfirm: async () => {
      // 清除登录的缓存数据,userInfo有订阅者删不掉,所以重新赋值空的给userInfo
      AppStorage.setOrCreate(CommonConstant.USER_INFO, {
        nickname: '',
        unionId: '',
        avatarUri: '',
        id: 0
      })
      AppStorage.delete(CommonConstant.TOKEN_NAME)
      await PreferencesUtil.delAllData(CommonConstant.PREFERENCES_NAME, CommonConstant.TOKEN_NAME)
      await PreferencesUtil.delAllData(CommonConstant.PREFERENCES_NAME, CommonConstant.USER_INFO)
      router.clear()
      // 路由到我的页面
      router.replaceUrl({
        url: RouterConstant.PAGE_INDEX, params: {
          "currentIndex": 3
        }
      })
    }
  })
}

头像上传

1、编写工具类
import { common } from '@kit.AbilityKit';
import fs from '@ohos.file.fs';
import request from '@ohos.request';
import { BusinessError } from '@ohos.base';
import { picker } from '@kit.CoreFileKit';
import { Logger } from './Logger';
import { FileData } from '../models/FileData';

let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir
let cacheDir = context.cacheDir

export class FileUtil {
  /**
   * 判断文件是否存在
   */
  static isExist(fileName: string, fileSuffix: string) {
    // 判断文件是否存在,存在就删除
    let path = filesDir + '/' + fileName + '.' + fileSuffix;
    if (fs.accessSync(path)) {
      fs.unlinkSync(path);
    }
  }

  /**
   * 下载文件
   */
  static downloadFile(fileName: string, fileSuffix: string, fileUrl: string): Promise<boolean> {
    return new Promise<boolean>((resolve, reject) => {
      // 判断文件是否已存在
      FileUtil.isExist(fileName, fileSuffix)
      request.downloadFile(context, {
        url: fileUrl,
        filePath: filesDir + '/' + fileName + '.' + fileSuffix
      }).then((downloadTask: request.DownloadTask) => {
        downloadTask.on('complete', () => {
          resolve(true)
        })
      }).catch((err: BusinessError) => {
        console.error(`Invoke downloadTask failed, code is ${err.code}, message is ${err.message}`);
        reject(err)
      });
    })
  }

  /**
   * 选择图片
   */
  static selectImage(): Promise<string> {
    return new Promise<string>((resolve, reject) => {
      try {
        let photoSelectOptions = new picker.PhotoSelectOptions();
        photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
        photoSelectOptions.maxSelectNumber = 1;
        let photoPicker = new picker.PhotoViewPicker(context);
        photoPicker.select(photoSelectOptions).then((photoSelectResult: picker.PhotoSelectResult) => {
          resolve(photoSelectResult.photoUris[0])
        }).catch((err: BusinessError) => {
          reject(err)
        });
      } catch (error) {
        let err: BusinessError = error as BusinessError;
        console.error('PhotoViewPicker failed with err: ' + JSON.stringify(err));
        reject(err)
      }
    })
  }


  /**
   * 将uri截取转换成固定类型
   */
  static convertFile(uri: string): FileData {
    // 将uri分割成字符串数组
    const array: string[] = uri.split('/');
    // 获取用户文件全名
    const fileFullName = array[array.length-1]
    // 获取文件名字里面.最后出现的索引位置
    let index = fileFullName.lastIndexOf(".");
    // 获取文件后缀名
    const fileSuffix = fileFullName.substring(index + 1)
    // 获取文件名
    const fileName = fileFullName.substring(0, index)
    // 封装文件数据
    const fileData: FileData = { fileFullName: fileFullName, fileSuffix: fileSuffix, fileName: fileName }
    return fileData
  }


  /**
   * 将用户文件转换成缓存目录
   */
  static copyUserFileToCache(uri: string, fileData: FileData): Promise<boolean> {
    return new Promise<boolean>((resolve, reject) => {
      // 缓存目录
      let cachePath = cacheDir + '/' + fileData.fileFullName
      try {
        let files = fs.openSync(uri, fs.OpenMode.READ_ONLY)
        fs.copyFileSync(files.fd, cachePath)
        resolve(true)
      } catch (error) {
        let err: BusinessError = error as BusinessError;
        Logger.error('Error copying file:' + JSON.stringify(err))
        reject(err)
      }
    })
  }
}

2、修改头像
/**
 * 修改头像
 */
async editAvatar() {
  try {
    // 头像上传
    const uri = await FileUtil.selectImage()
    if (!uri) {
      showToast("选择图片失败")
      return
    }
    // 将uri截取转换成固定类型
    const fileData = FileUtil.convertFile(uri)
    // 将用户文件转换成缓存目录
    const data = await FileUtil.copyUserFileToCache(uri, fileData)
    if (!data) {
      showToast("修改头像失败")
      return
    }
    // 上传文件
    await this.uploadImage(fileData)
  } catch (error) {
    showToast("修改头像失败")
  }

}

3、上传头像
/**
 * 上传图片
 */

async uploadImage(fileData: FileData) {
  let files: Array<request.File> = [
    // uri前缀internal://cache 对应cacheDir目录
    {
      filename: fileData.fileFullName,
      name: 'file', // 文件上传的key
      uri: 'internal://cache/' + fileData.fileFullName,
      type: fileData.fileSuffix
    }
  ]
  let uploadConfig: request.UploadConfig = {
    url: 'http://118.31.50.145:9003/v1/user/editAvatar',
    header: {
      "Authorization": AppStorage.get<string>("token")
    },
    method: 'POST',
    files: files,
    data: []
  }
  // 打开上传进度弹窗
  this.dialog.open()
  // 发送请求
  const response = await request.uploadFile(context, uploadConfig)
  // 监听上传进度
  response.on("progress", async (val, size) => {
    Logger.info("头像上传进度:", `${val / size * 100}%`)
    emitter.emit({ eventId: 100 }, { data: { process: `上传进度: ${(val / size * 100).toFixed(0)}%` } })
    if (val === size) {
      this.dialog.close()
      showToast('头像上传成功')
      // 获取用户信息
      const userInfo = await userApi.getUserInfo();
      this.userInfo = userInfo
      // 存放用户数据
      AppStorage.setOrCreate(CommonConstant.USER_INFO, userInfo)
      PreferencesUtil.savaData(CommonConstant.PREFERENCES_NAME, CommonConstant.USER_INFO,
                               JSON.stringify(userInfo))
    }
  })

}
4、自定义上传进度弹窗
// 自定义上传进度弹窗
dialog: CustomDialogController = new CustomDialogController({
  builder: ProgressDialog({ message: `上传进度: 0%` }),
  customStyle: true,
  alignment: DialogAlignment.Center
})

import { emitter } from '@kit.BasicServicesKit'

@CustomDialog
  export struct ProgressDialog {
    @State message: string = ''
    controller: CustomDialogController

    aboutToAppear(): void {
      emitter.on({ eventId: 100 }, (res) => {
        this.message = res.data!["process"]
      })
    }

    build() {
      Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
        LoadingProgress().width(30).height(30).color($r('app.color.common_white'))
        if (this.message) {
          Text(this.message).fontSize((14)).fontColor($r('app.color.common_white'))
        }
      }
      .width($r('app.float.common_width_huge'))
        .height($r('app.float.common_height_small'))
        .padding(10)
        .backgroundColor('rgba(0,0,0,0.5)')
        .borderRadius(8)
    }
  }

Emitter具有同一进程不同线程间,或同一进程同一线程内,发送和处理事件的能力

Emitter用于同一进程内相同线程或不同线程间的事件处理,事件异步执行。使用时需要先订阅一个事件,然后发布该事件,发布完成后Emitter会将已发布的事件分发给订阅者,订阅者就会执行该事件订阅时设置的回调方法。当不需要订阅该事件时应及时取消订阅释放Emitter资源。

官方文档地址:

文档中心

检查更新

应用市场更新功能为开发者提供版本检测、显示更新提醒功能。开发者使用应用市场更新功能可以提醒用户及时更新到最新版本。

当应用启动完成或用户在应用中主动检查应用新版本时,开发者可以通过本服务,来查询应用是否有可更新的版本。如果存在可更新版本,您可以通过本服务为用户显示更新提醒。

  1. 应用调用检查更新接口。
  2. 升级服务API返回是否有新版本。
  3. 调用显示升级对话框接口。
  4. 升级服务API向应用返回显示结果。

import { updateManager } from '@kit.StoreKit';
import type { common } from '@kit.AbilityKit';
import { Logger } from './Logger';
import { showToast } from './Toast';

let context: common.UIAbilityContext = getContext() as common.UIAbilityContext;

export class ApplicationCheckUtil {
  /**
   * 检测新版本
   */
  static async checkAppUpdate() {
    try {
      const checkResult = await updateManager.checkAppUpdate(context);
      if (checkResult.updateAvailable === 0) {
        showToast('当前应用版本已经是最新');
        return;
      }
      // 存在新版本,显示更新对话框
      const resultCode = await updateManager.showUpdateDialog(context);
      if (resultCode === 1) {
        showToast("检查更新失败,请稍后重试,或者在我的界面直接点击反馈将信息反馈给开发者")
        return
      }
    } catch (error) {
      Logger.error('TAG', `检查更新出错: code is ${error.code}, message is ${error.message}`);
      showToast("检查更新失败,请稍后重试,或者在我的界面直接点击反馈将信息反馈给开发者")
    }
  }
}

退出登录

/**
 * 退出登录
 */
function logout() {
  IBestDialogUtil.open({
    title: "提示",
    message: "是否确认退出登录?",
    showCancelButton: true,
    onConfirm: async () => {
      // 清除登录的缓存数据,userInfo有订阅者删不掉,所以重新赋值空的给userInfo
      AppStorage.setOrCreate(CommonConstant.USER_INFO, {
        nickname: '',
        unionId: '',
        avatarUri: '',
        id: 0
      })
      AppStorage.delete(CommonConstant.TOKEN_NAME)
      await PreferencesUtil.delAllData(CommonConstant.PREFERENCES_NAME, CommonConstant.TOKEN_NAME)
      await PreferencesUtil.delAllData(CommonConstant.PREFERENCES_NAME, CommonConstant.USER_INFO)
      router.clear()
      // 路由到我的页面
      router.replaceUrl({
        url: RouterConstant.PAGE_INDEX, params: {
          "currentIndex": 3
        }
      })
    }
  })
}

http://www.kler.cn/a/592774.html

相关文章:

  • 网络爬虫【爬虫库request】
  • Rust学习之实现命令行小工具minigrep(一)
  • 关于HAL库的知识1----MSP函数
  • [解决] PDF转图片,中文乱码或显示方框的解决方案
  • 华为ipd流程华为流程体系管理华为数字化转型流程数字化管理解决方案介绍81页精品PPT
  • gralloc usage flags
  • dns实现主服务器
  • 如何解析返回的商品信息?
  • 深度解析扣减系统设计:从架构到实践
  • HAL库编程知识点---Can.c和Driver_can.c分层开发
  • 【论文阅读】Availability Attacks Create Shortcuts
  • Noe.js 原生 http 模块 vs Express 框架对比
  • c语言基础编程入门练习题
  • 蓝桥杯2023年第十四届省赛真题-子矩阵
  • 基于springboot医疗平台系统(源码+lw+部署文档+讲解),源码可白嫖!
  • 麒麟V10 arm cpu aarch64 下编译 RocketMQ-Client-CPP 2.2.0
  • Oracle 19c更换undo表空间操作步骤
  • TCP、UDP协议的应用、ServerSocket和Socket、DatagramSocket和DatagramPacket
  • 2025年PHP框架推荐及对比
  • 【HarmonyOS Next】鸿蒙应用实现弹框DialogHub详解