代碼倉地址,大家記得點個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資源。
官方文檔地址:
文檔中心
檢查更新
應用市場更新功能為開發者提供版本檢測、顯示更新提醒功能。開發者使用應用市場更新功能可以提醒用戶及時更新到最新版本。
當應用啟動完成或用戶在應用中主動檢查應用新版本時,開發者可以通過本服務,來查詢應用是否有可更新的版本。如果存在可更新版本,您可以通過本服務為用戶顯示更新提醒。
- 應用調用檢查更新接口。
- 升級服務API返回是否有新版本。
- 調用顯示升級對話框接口。
- 升級服務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}})}})
}