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

news2025/3/31 7:18:54

代码仓地址,大家记得点个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.coloradmin.cn/o/2318823.html

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

相关文章

函数的介绍

1.函数的概念 在C语言中也有函数的概念&#xff0c;有些翻译为&#xff1a;子程序&#xff0c;这种翻译更为准确。C语言的函数就是一个完成某项特定的任务的一小段代码。这段代码是有特殊的写法和调用方法的。 C语言的程序其实是有无数个小的函数组合而成的&#xff0c;也可以…

源自Deformable Convolutional Networks的一种可变形卷积实现解析

衍生记录&#xff1a;深度学习pytorch之简单方法自定义9类卷积即插即用 文章目录 概述1. 可变形卷积的背景2. DeformConv2D概述2.1 构造函数分析2.2 前向传播函数解析2.2.1 偏移量的计算与应用2.2.2 目标位置的计算2.2.3 四个角的插值2.2.4 双线性插值的权重2.2.5 特征图的采样…

【最后203篇系列】020 rocksdb agent

今天还是挺开心的一天&#xff0c;又在工具箱里加了一个工具。嗯&#xff0c;但是快下班的时候也碰到一些不太顺心的事&#xff0c;让我有点恼火。我还真没想到一个专职的前端&#xff0c;加测试&#xff0c;以及其他一堆人&#xff0c;竟然不知道后端返回的markdown,在前端渲染…

mysql-connector-python 报错(0xC0000005)

报错情况&#xff1a; 原因&#xff1a; mysql-connector-python版本不对&#xff0c;我们的mysql版本为sql8.0需要下载mysql-connector-python8.0....的库 方法&#xff1a; pip install mysql-connector-python8.1.0 即可

从零开始实现Stable Diffusion本地部署

1. 依赖安装 文件打包下载地址&#xff08;Stable Diffusion&#xff09; # git &#xff1a; 用于下载源码 https://git-scm.com/downloads/win # Python 作为基础编译环境 https://www.python.org/downloads/ # Nvidia 驱动&#xff0c;用于编译使用GPU显卡硬件 https://ww…

RAG各类方法python源码解读与实践:利用Jupyter对RAG技术综合评测【3万字长文】

检索增强生成&#xff08;RAG &#xff09;是一种结合信息检索与生成模型的混合方法。它通过引入外部知识来提升语言模型的性能&#xff0c;从而提高回答的准确性和事实正确性。为了简单易学&#xff0c;不使用LangChain框架或FAISS向量数据库&#xff0c;而是利用Jupyter Note…

RPA+AI 技术到底好在哪里?

在自动化领域&#xff0c;RPA与生成式AI都是强大的技术&#xff0c;都可以用来实现自动执行重复耗时的任务。 主要区别是&#xff1a;传统RPA擅长处理结构化与规则明确简单的流程&#xff0c;而在非结构化数据处理、动态上下文适应、智能决策等能力上有欠缺&#xff1b;而基于…

flowable适配达梦7 (2.1)

经过第一版的问题解决&#xff0c;后端项目可以启动&#xff0c;前端页面也集成进去。 前端在流程设计页面报错 之后发现主要是组件中modelerStore这个值没有 解决方法:在data增加对象 给component/process/designer.vue 中涉及到的每个子组件传入 :modelerStore“modeler…

基于java的ssm+JSP+MYSQL的母婴用品网站(含LW+PPT+源码+系统演示视频+安装说明)

系统功能 管理员功能模块&#xff1a; 主页 个人中心 用户管理 商品分类管理 商品信息管理 留言板管理 成长交流 系统管理 订单管理 留言管理 用户功能模块&#xff1a; 主页 个人中心 我的收藏管理 订单管理 前台首页功能模块&#xff1a; 首页 商品信息 论…

面试八股 —— Redis篇

重点&#xff1a;缓存 和 分布式锁 缓存&#xff08;穿透&#xff0c;击穿&#xff0c;雪崩&#xff09; 降级可作为系统的保底策略&#xff0c;适用于穿透&#xff0c;击穿&#xff0c;雪崩 1.缓存穿透 2.缓存击穿 3.缓存雪崩 缓存——双写一致性 1.强一致性业务&#xff08…

gradle-8.13

gradle-8.13 稍微看了下&#xff0c;基于Maven改造的 https://gradle.org/install/https://github.com/gradle/gradle-distributions/releaseshttps://github.com/gradle/gradle-distributions/releases/download/v8.13.0/gradle-8.13-all.zip https://github.com/gradle/gra…

不使用负压电源,ADC如何测量正负压?

电路图来自ZLinear的开源数据采集板卡DL8884_RFN&#xff0c;是一个比较常见的电压偏置采集法 对电路进行分析&#xff0c;所以说可以先看下采集卡的模拟输入部分的参数如下&#xff1a; 通道数量: 8通道单端输入/4通道差分输入 分辨率: 16位逐次逼近型(SAR) ADC 采样速率: 40…

SinoSteel生产企业ERP实施建议书final(143页PPT)(文末有下载方式)

资料解读&#xff1a;SinoSteel 生产企业 ERP 实施建议书 final 详细资料请看本解读文章的最后内容。 在当今竞争激烈的商业环境中&#xff0c;企业的信息化建设对于提升竞争力和实现可持续发展至关重要。中钢集团作为一家大型跨国企业集团&#xff0c;在钢铁行业占据重要地位。…

贴片陶瓷天线和蓝牙天线的layout注意事项

板载天线&#xff0c;也有封装成器件进行使用&#xff1a; 看到这里&#xff0c;细心的人发现&#xff0c;天线接入芯片的时候&#xff0c;旁边也直接接地了&#xff1a; F型天线&#xff08;Inverted F Antenna, IFA&#xff09;的一端接地&#xff0c;看起来像是“短路”&am…

关于波士顿动力2025年3月的人形机器人最新视频

这是完整的视频&#xff1a; 波士顿动力最新逆天表演-机器人Atlas行走、奔跑、爬行、杂技_哔哩哔哩_bilibili 至少从目前来看&#xff0c;综合对比运动的幅度、各关节的协调性、整体的顺遂性、动作的拟人程度&#xff0c;波士顿动力是已知人形机器人中最好的。 尤其需要关注…

Wi-Fi NAN 架构(Wi-Fi Aware Specification v4.0,第2章:2.3~2.6)

1. NAN 数据通信架构 1.1 单播支持 要在两个NAN设备之间启动单播数据通信&#xff0c;服务需发起一个NAN数据路径&#xff08;NDP&#xff0c;NAN Data Path&#xff09;请求。这对NAN设备之间会建立一个NAN设备链路&#xff08;NDL&#xff0c;NAN Device Link&#xff09;&…

在Oracle Linux 7上安装Oracle 11gr2数据库

好久没有安装Oracle 11g了&#xff0c;虽然是老版本&#xff0c;但是还是有很多公司在用&#xff0c;自从有了oracle linux感觉安装变简单了。 1.安装先决条件包,此包会配置系统参数,建立oracle用户等: yum install oracle-rdbms-server-11gR2-preinstall 安装完这个oracle自…

python爬虫概述

0x00 python爬虫概述 以豆瓣的选电影模块为例&#xff0c;当查看源代码搜索猫猫的奇幻漂流瓶是搜不到的 这时服务器的工作方式应该是这样的 客户端浏览器第一次访问其实服务器端是返回的一个框架(html代码) 当客户端浏览器第二次通过脚本等方式进行访问时服务器端才返回的数据…

【C++】STL库面试常问点

STL库 什么是STL库 C标准模板库&#xff08;Standard Template Libiary&#xff09;基于泛型编程&#xff08;模板&#xff09;&#xff0c;实现常见的数据结构和算法&#xff0c;提升代码的复用性和效率。 STL库有哪些组件 STL库由以下组件构成&#xff1a; ● 容器&#xf…

Qt 控件概述 QWdiget 1.1

目录 qrc机制 qrc使用 1.在项目中创建一个 qrc 文件 2.将图片导入到qrc文件中 windowOpacity&#xff1a; cursor 光标 cursor类型 自定义Cursor font tooltip focusPolicy styleSheet qrc机制 之前提到使用相对路径的方法来存放资源&#xff0c;还有一种更好的方式…