鴻蒙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@Componentstruct LoginPage {// 多選框狀態@State multiSelectStatus: boolean = falsebuild() {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@Componentstruct 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();// 獲取頭像昵稱需要傳如下scopeauthRequest.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;// 唯一idconst 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@Componentstruct PolicyPage {webViewController: webview.WebviewController = new webview.WebviewControllerbuild() {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@Componentstruct UserPolicyPage {webViewController: webview.WebviewController = new webview.WebviewControllerbuild() {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'@Componentexport struct TitleComponent {// 標題@Link title: stringbuild() {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@Entryexport struct FunctionBarComponent {@State functionBarData: FunctionBarData = {icon: '',text: '',router: '',eventType: ''}// 反饋信息@State inputValue: string = ''@State formInputError: boolean = false@State dialogVisible: boolean = false@BuilderformInputContain() {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有訂閱者刪不掉,所以重新賦值空的給userInfoAppStorage.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.cacheDirexport 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.fileFullNametry {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', // 文件上傳的keyuri: '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'@CustomDialogexport struct ProgressDialog {@State message: string = ''controller: CustomDialogControlleraboutToAppear(): 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有訂閱者刪不掉,所以重新賦值空的給userInfoAppStorage.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.pswp.cn/bicheng/73851.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/73851.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/73851.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

免密登錄遠程服務器shell腳本

一、腳本代碼 #!/bin/bash #提示用戶輸入用戶i名和ip地址 read -p "請輸入遠程服務器的用戶名: " hname read -p "請輸入遠程服務器的IP地址: " fip read -p "請輸入遠程服務器的遠程端口:" sdk #檢查是否配置了免密登錄 function sfmm(){ …

WiFi 定位技術:守護寵物安全的隱形衛士

一、實時追蹤&#xff0c;防患未然 想象一下&#xff0c;活潑好動的貓咪趁你開門瞬間溜出家門&#xff0c;穿梭在樓道雜物間&#xff1b;或是狗狗在戶外玩耍時&#xff0c;被突發聲響驚嚇狂奔&#xff0c;瞬間沒了蹤影。在這些令人揪心的時刻&#xff0c;WiFi 定位技術就像給寵…

《C#上位機開發從門外到門內》3-2::Modbus數據采集系統

文章目錄 **1. 項目概述****1.1 項目背景****1.2 項目目標****1.3 技術棧** **2. 系統架構設計****2.1 系統架構圖****2.2 模塊功能** **3. 數據采集模塊實現****3.1 Modbus協議簡介****3.2 數據采集流程****3.3 代碼實現** **4. 數據存儲模塊實現****4.1 數據庫設計****4.2 數…

Carto 無盡旅圖 for Mac v1.0.7.6 (51528)冒險解謎游戲 支持M、Intel芯片

游戲介紹 《Carto》源于英文"Cartographer"&#xff08;制圖師&#xff09;&#xff0c;卡朵不慎墜入未知世界。這里蜿蜒曲折&#xff0c;地形豐富。作為制圖師卡朵&#xff0c;你將用你自己的神秘力量&#xff0c;操縱地圖顛覆世界&#xff0c;將其翻轉、拼合。當世…

點擊劫持詳細透析

點擊劫持&#xff08;Clickjacking&#xff09;是一種前端安全攻擊手段&#xff0c;攻擊者通過視覺欺騙誘導用戶在不知情的情況下點擊隱藏的頁面元素&#xff0c;從而執行非預期的操作。以下是攻擊過程的詳細說明&#xff1a; 攻擊過程步驟 攻擊者構造惡意頁面 創建一個惡意網頁…

OpenAI--Agent SDK簡介

項目概述 OpenAI Agents SDK 是一個輕量級但功能強大的框架&#xff0c;用于構建多智能體工作流。它主要利用大語言模型&#xff08;LLM&#xff09;&#xff0c;通過配置智能體、交接、護欄和跟蹤等功能&#xff0c;實現復雜的工作流管理。以下是對其各個部分運行過程和代碼流…

【】序列操作

A. Tower 彭教授建造了 n n n 個不同高度的積木塔。其中 i i i 個塔的高度為 a i a_i ai? 。 壽教授不喜歡這些塔&#xff0c;因為它們的高度太隨意了。他決定先移除其中的 m m m 個&#xff0c;然后執行下面的一些操作&#xff08;或不執行&#xff09;&#xff1a; 選…

QwQ-32B 模型結構

QwQ-32B 是一種基于 Transformer 架構 的大型語言模型&#xff08;LLM&#xff09;&#xff0c;由阿里巴巴的 Qwen 團隊開發&#xff0c;專注于推理任務。以下是其核心結構和技術特點&#xff1a; 1. 基礎架構 Transformer 結構&#xff1a;QwQ-32B 采用多層 Transformer 架構…

【Linux】:自定義協議(應用層)

朋友們、伙計們&#xff0c;我們又見面了&#xff0c;本期來給大家帶來應用層自定義協議相關的知識點&#xff0c;如果看完之后對你有一定的啟發&#xff0c;那么請留下你的三連&#xff0c;祝大家心想事成&#xff01; C 語 言 專 欄&#xff1a;C語言&#xff1a;從入門到精通…

【C++】二叉樹和堆的鏈式結構

本篇博客給大家帶來的是用C語言來實現堆鏈式結構和二叉樹的實現&#xff01; &#x1f41f;&#x1f41f;文章專欄&#xff1a;數據結構 &#x1f680;&#x1f680;若有問題評論區下討論&#xff0c;我會及時回答 ??歡迎大家點贊、收藏、分享&#xff01; 今日思想&#xff…

鴻蒙保姆級教學

鴻蒙&#xff08;HarmonyOS&#xff09;是華為推出的一款面向全場景的分布式操作系統&#xff0c;支持手機、平板、智能穿戴、智能家居、車載設備等多種設備。鴻蒙系統的核心特點是分布式架構、一次開發多端部署和高性能。以下是從入門到大神級別的鴻蒙開發深度分析&#xff0c…

關于Docker是否被淘汰虛擬機實現連接虛擬專用網絡Ubuntu 22.04 LTS部署Harbor倉庫全流程

1.今天的第一個主題&#xff1a; 第一個主題是關于Docker是否真的被K8S棄用&#xff0c;還是可以繼續兼容&#xff0c;因為我們知道在去年的時候&#xff0c;由于不可控的原因&#xff0c;docker的所有國內鏡像源都被Ban了&#xff0c;再加上K8S自從V1.20之后&#xff0c;宣布…

八股學習-JUC java并發編程

本文僅供個人學習使用&#xff0c;參考資料&#xff1a;JMM&#xff08;Java 內存模型&#xff09;詳解 | JavaGuide 線程基礎概念 用戶線程&#xff1a;由用戶空間程序管理和調度的線程&#xff0c;運行在用戶空間。 內核線程&#xff1a;由操作系統內核管理和調度的線程&…

遺傳算法+四模型+雙向網絡!GA-CNN-BiLSTM-Attention系列四模型多變量時序預測

遺傳算法四模型雙向網絡&#xff01;GA-CNN-BiLSTM-Attention系列四模型多變量時序預測 目錄 遺傳算法四模型雙向網絡&#xff01;GA-CNN-BiLSTM-Attention系列四模型多變量時序預測預測效果基本介紹程序設計參考資料 預測效果 基本介紹 基于GA-CNN-BiLSTM-Attention、CNN-BiL…

Linux怎樣源碼安裝Nginx

1. 安裝必要的依賴 在編譯 Nginx 之前&#xff0c;你需要安裝一些必要的依賴包&#xff0c;像編譯工具和庫文件等。以 CentOS 系統為例&#xff0c;可借助yum命令來安裝&#xff1a; bash sudo yum install -y gcc pcre-devel zlib-devel openssl-devel要是使用的是 Ubuntu 系…

【入門初級篇】報表基礎操作與功能介紹

【入門初級篇】報表的基本操作與功能介紹 視頻要點 &#xff08;1&#xff09;報表組件的創建 &#xff08;2&#xff09;指標組件的使用&#xff1a;一級、二級指標操作演示 &#xff08;3&#xff09;表格屬性設置介紹 &#xff08;4&#xff09;圖表屬性設置介紹 &#xff0…

【新能源汽車“心臟”賦能:三電系統研發、測試與應用匹配的恒壓恒流源技術秘籍】

新能源汽車“心臟”賦能&#xff1a;三電系統研發、測試與應用匹配的恒壓恒流源技術秘籍 在新能源汽車蓬勃發展的浪潮中&#xff0c;三電系統&#xff08;電池、電機、電控&#xff09;無疑是其核心驅動力。而恒壓源與恒流源&#xff0c;作為電源管理的關鍵要素&#xff0c;在…

在線JSON格式校驗工具站

在線JSON校驗格式化工具&#xff08;Be JSON&#xff09;在線,JSON,JSON 校驗,格式化,xml轉json 工具,在線工具,json視圖,可視化,程序,服務器,域名注冊,正則表達式,測試,在線json格式化工具,json 格式化,json格式化工具,json字符串格式化,json 在線查看器,json在線,json 在線驗…

圖片黑白處理軟件推薦

圖片黑白二值化是一款小巧實用的圖片處理軟件&#xff0c;軟件大小僅268K。 它的操作極其簡單&#xff0c;用戶只需將需要處理的圖片直接拖入軟件&#xff0c;就能實現圖片漂白效果。 從原圖和處理后的圖片對比來看&#xff0c;效果顯著。這種圖片漂白處理在打印時能節省墨水&a…

【AI知識】常見的優化器及其原理:梯度下降、動量梯度下降、AdaGrad、RMSProp、Adam、AdamW

常見的優化器 梯度下降&#xff08;Gradient Descent, GD&#xff09;局部最小值、全局最小值和鞍點凸函數和非凸函數動量梯度下降&#xff08;Momentum&#xff09;自適應學習率優化器AdaGrad&#xff08;Adaptive Gradient Algorithm&#xff09;?RMSProp&#xff08;Root M…