代码仓地址,大家记得点个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}})}})
}