鸿蒙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.mzph.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…