From 20215143af67ce52b031845840f4c6f892bd44ce Mon Sep 17 00:00:00 2001 From: 6666 <2855471171@qq.com> Date: Sat, 9 May 2026 22:09:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=99=BB=E9=99=86=E6=B3=A8?= =?UTF-8?q?=E5=86=8C=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- entry/src/main/ets/api/AuthApi.ets | 70 +++++ entry/src/main/ets/config/AppConfig.ets | 10 + .../main/ets/entryability/EntryAbility.ets | 5 + entry/src/main/ets/pages/LoginPage.ets | 46 +-- entry/src/main/ets/pages/RegisterPage.ets | 278 +++++++++++------- entry/src/main/ets/types/Auth.ets | 68 +++++ entry/src/main/ets/utils/HttpClient.ets | 115 ++++++++ entry/src/main/ets/utils/TokenStore.ets | 74 +++++ entry/src/main/module.json5 | 8 + 9 files changed, 549 insertions(+), 125 deletions(-) create mode 100644 entry/src/main/ets/api/AuthApi.ets create mode 100644 entry/src/main/ets/config/AppConfig.ets create mode 100644 entry/src/main/ets/types/Auth.ets create mode 100644 entry/src/main/ets/utils/HttpClient.ets create mode 100644 entry/src/main/ets/utils/TokenStore.ets diff --git a/entry/src/main/ets/api/AuthApi.ets b/entry/src/main/ets/api/AuthApi.ets new file mode 100644 index 0000000..641986f --- /dev/null +++ b/entry/src/main/ets/api/AuthApi.ets @@ -0,0 +1,70 @@ +import { HttpClient } from '../utils/HttpClient'; +import { TokenStore } from '../utils/TokenStore'; +import { + RegisterRequest, + LoginRequest, + RefreshTokenRequest, + AuthResponseData, + TokenPair, + AuthMeData, + MessageResponse +} from '../types/Auth'; + +/** + * Auth 模块接口 + * 对应文档: /api/auth/* + */ +export class AuthApi { + /** + * POST /api/auth/register + * 用户注册 + */ + static async register(req: RegisterRequest): Promise { + const data = await HttpClient.post('/api/auth/register', req, false); + await TokenStore.saveTokens(data.accessToken, data.refreshToken, data.user.id); + return data; + } + + /** + * POST /api/auth/login + * 用户登录 + */ + static async login(req: LoginRequest): Promise { + const data = await HttpClient.post('/api/auth/login', req, false); + await TokenStore.saveTokens(data.accessToken, data.refreshToken, data.user.id); + return data; + } + + /** + * POST /api/auth/refresh + * 使用 refreshToken 刷新令牌 + */ + static async refresh(): Promise { + const refreshToken = await TokenStore.getRefreshToken(); + const req: RefreshTokenRequest = { refreshToken }; + const data = await HttpClient.post('/api/auth/refresh', req, false); + await TokenStore.saveTokens(data.accessToken, data.refreshToken); + return data; + } + + /** + * POST /api/auth/logout + * 用户登出 + */ + static async logout(): Promise { + try { + const result = await HttpClient.post('/api/auth/logout', undefined, true); + return result; + } finally { + await TokenStore.clear(); + } + } + + /** + * GET /api/auth/me + * 获取当前认证用户信息 + */ + static getMe(): Promise { + return HttpClient.get('/api/auth/me', true); + } +} diff --git a/entry/src/main/ets/config/AppConfig.ets b/entry/src/main/ets/config/AppConfig.ets new file mode 100644 index 0000000..b681923 --- /dev/null +++ b/entry/src/main/ets/config/AppConfig.ets @@ -0,0 +1,10 @@ +/** + * 应用配置 + * 注意:模拟器中无法直接访问主机的 localhost,需要将下面的 BASE_URL 改为: + * - 鸿蒙模拟器:使用主机局域网 IP,如 http://192.168.x.x:3000 + * - 真机调试:手机与电脑在同一局域网下,使用电脑的局域网 IP + */ +export class AppConfig { + static readonly BASE_URL: string = 'http://localhost:3000'; + static readonly REQUEST_TIMEOUT: number = 10000; +} diff --git a/entry/src/main/ets/entryability/EntryAbility.ets b/entry/src/main/ets/entryability/EntryAbility.ets index 7cf2da2..53b12fe 100644 --- a/entry/src/main/ets/entryability/EntryAbility.ets +++ b/entry/src/main/ets/entryability/EntryAbility.ets @@ -1,6 +1,7 @@ import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { window } from '@kit.ArkUI'; +import { TokenStore } from '../utils/TokenStore'; const DOMAIN = 0x0000; @@ -11,6 +12,10 @@ export default class EntryAbility extends UIAbility { } catch (err) { hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err)); } + // 初始化 Token 存储 + TokenStore.init(this.context).catch((err: Error) => { + hilog.error(DOMAIN, 'testTag', 'TokenStore init failed: %{public}s', err.message); + }); hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); } diff --git a/entry/src/main/ets/pages/LoginPage.ets b/entry/src/main/ets/pages/LoginPage.ets index f22e51e..c3a0f3d 100644 --- a/entry/src/main/ets/pages/LoginPage.ets +++ b/entry/src/main/ets/pages/LoginPage.ets @@ -1,9 +1,11 @@ import { promptAction, router } from '@kit.ArkUI'; +import { AuthApi } from '../api/AuthApi'; +import { HttpError } from '../utils/HttpClient'; @Entry @Component struct LoginPage { - @State username: string = ''; + @State email: string = ''; @State password: string = ''; @State isLoading: boolean = false; @@ -29,21 +31,22 @@ struct LoginPage { // 登录表单 Column() { - // 用户名输入框 + // 邮箱输入框 Column() { - Text('账号') + Text('邮箱') .fontSize(14) .fontColor($r('app.color.text_primary')) .margin({ bottom: 8 }) - TextInput({ placeholder: '请输入手机号/邮箱', text: this.username }) + TextInput({ placeholder: '请输入邮箱', text: this.email }) .height(48) .fontSize(16) .backgroundColor($r('app.color.input_background')) .borderRadius(8) .padding({ left: 16, right: 16 }) + .type(InputType.Email) .onChange((value: string) => { - this.username = value; + this.email = value; }) } .width('100%') @@ -93,8 +96,8 @@ struct LoginPage { .backgroundColor($r('app.color.primary_color')) .borderRadius(24) .margin({ top: 32 }) - .enabled(!this.isLoading && this.username.length > 0 && this.password.length > 0) - .opacity(this.username.length > 0 && this.password.length > 0 ? 1 : 0.6) + .enabled(!this.isLoading && this.email.length > 0 && this.password.length > 0) + .opacity(this.email.length > 0 && this.password.length > 0 ? 1 : 0.6) .onClick(() => { this.handleLogin(); }) @@ -125,20 +128,27 @@ struct LoginPage { .backgroundColor($r('app.color.card_background')) } - private handleLogin(): void { - if (this.username.length === 0 || this.password.length === 0) { + private async handleLogin(): Promise { + if (this.email.length === 0 || this.password.length === 0) { return; } this.isLoading = true; - - setTimeout(() => { - if (this.username === 'admin' && this.password === 'admin123') { - router.replaceUrl({ url: 'pages/HomePage' }); - } else { - this.isLoading = false; - promptAction.showToast({ message: '账号或密码错误', duration: 2000 }); - } - }, 500); + try { + await AuthApi.login({ + email: this.email, + password: this.password + }); + promptAction.showToast({ message: '登录成功', duration: 1500 }); + router.replaceUrl({ url: 'pages/HomePage' }); + } catch (err) { + const httpErr = err as HttpError; + const message = httpErr.code === 401 + ? '邮箱或密码错误' + : (httpErr.message || '登录失败,请稍后重试'); + promptAction.showToast({ message, duration: 2000 }); + } finally { + this.isLoading = false; + } } } diff --git a/entry/src/main/ets/pages/RegisterPage.ets b/entry/src/main/ets/pages/RegisterPage.ets index 627de7a..74d6448 100644 --- a/entry/src/main/ets/pages/RegisterPage.ets +++ b/entry/src/main/ets/pages/RegisterPage.ets @@ -1,9 +1,13 @@ -import { router } from '@kit.ArkUI'; +import { promptAction, router } from '@kit.ArkUI'; +import { AuthApi } from '../api/AuthApi'; +import { HttpError } from '../utils/HttpClient'; @Entry @Component struct RegisterPage { @State username: string = ''; + @State email: string = ''; + @State nickname: string = ''; @State password: string = ''; @State confirmPassword: string = ''; @State isLoading: boolean = false; @@ -37,114 +41,162 @@ struct RegisterPage { .alignItems(VerticalAlign.Center) // 表单区域 - Column() { - // 账号 + Scroll() { Column() { - Text('账号') - .fontSize(14) - .fontColor($r('app.color.text_primary')) - .margin({ bottom: 8 }) + // 用户名 + Column() { + Text('用户名') + .fontSize(14) + .fontColor($r('app.color.text_primary')) + .margin({ bottom: 8 }) - TextInput({ placeholder: '请输入手机号/邮箱', text: this.username }) - .height(48) - .fontSize(16) - .backgroundColor($r('app.color.input_background')) - .borderRadius(8) - .padding({ left: 16, right: 16 }) - .onChange((value: string) => { - this.username = value; - }) - } - .width('100%') - .alignItems(HorizontalAlign.Start) - - // 密码 - Column() { - Text('密码') - .fontSize(14) - .fontColor($r('app.color.text_primary')) - .margin({ bottom: 8 }) - - TextInput({ placeholder: '请输入密码(至少6位)', text: this.password }) - .height(48) - .fontSize(16) - .backgroundColor($r('app.color.input_background')) - .borderRadius(8) - .padding({ left: 16, right: 16 }) - .type(InputType.Password) - .onChange((value: string) => { - this.password = value; - }) - } - .width('100%') - .alignItems(HorizontalAlign.Start) - .margin({ top: 20 }) - - // 确认密码 - Column() { - Text('确认密码') - .fontSize(14) - .fontColor($r('app.color.text_primary')) - .margin({ bottom: 8 }) - - TextInput({ placeholder: '请再次输入密码', text: this.confirmPassword }) - .height(48) - .fontSize(16) - .backgroundColor($r('app.color.input_background')) - .borderRadius(8) - .padding({ left: 16, right: 16 }) - .type(InputType.Password) - .onChange((value: string) => { - this.confirmPassword = value; - }) - } - .width('100%') - .alignItems(HorizontalAlign.Start) - .margin({ top: 20 }) - - // 密码不一致提示 - if (this.confirmPassword.length > 0 && this.password !== this.confirmPassword) { - Text('两次输入的密码不一致') - .fontSize(12) - .fontColor('#FF4D4F') - .margin({ top: 8 }) - } - - // 注册按钮 - Button(this.isLoading ? '注册中...' : '注册') + TextInput({ placeholder: '请输入用户名', text: this.username }) + .height(48) + .fontSize(16) + .backgroundColor($r('app.color.input_background')) + .borderRadius(8) + .padding({ left: 16, right: 16 }) + .onChange((value: string) => { + this.username = value; + }) + } .width('100%') - .height(48) - .fontSize(16) - .fontColor(Color.White) - .backgroundColor($r('app.color.primary_color')) - .borderRadius(24) - .margin({ top: 36 }) - .enabled(!this.isLoading && this.isFormValid()) - .opacity(this.isFormValid() ? 1 : 0.6) - .onClick(() => { - this.handleRegister(); - }) + .alignItems(HorizontalAlign.Start) - // 返回登录 - Row() { - Text('已有账号?') - .fontSize(14) - .fontColor($r('app.color.text_hint')) - Text('返回登录') - .fontSize(14) - .fontColor($r('app.color.primary_color')) - .fontWeight(FontWeight.Medium) + // 邮箱 + Column() { + Text('邮箱') + .fontSize(14) + .fontColor($r('app.color.text_primary')) + .margin({ bottom: 8 }) + + TextInput({ placeholder: '请输入邮箱', text: this.email }) + .height(48) + .fontSize(16) + .backgroundColor($r('app.color.input_background')) + .borderRadius(8) + .padding({ left: 16, right: 16 }) + .type(InputType.Email) + .onChange((value: string) => { + this.email = value; + }) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ top: 20 }) + + // 昵称(可选) + Column() { + Text('昵称(可选)') + .fontSize(14) + .fontColor($r('app.color.text_primary')) + .margin({ bottom: 8 }) + + TextInput({ placeholder: '请输入昵称', text: this.nickname }) + .height(48) + .fontSize(16) + .backgroundColor($r('app.color.input_background')) + .borderRadius(8) + .padding({ left: 16, right: 16 }) + .onChange((value: string) => { + this.nickname = value; + }) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ top: 20 }) + + // 密码 + Column() { + Text('密码') + .fontSize(14) + .fontColor($r('app.color.text_primary')) + .margin({ bottom: 8 }) + + TextInput({ placeholder: '请输入密码(至少6位)', text: this.password }) + .height(48) + .fontSize(16) + .backgroundColor($r('app.color.input_background')) + .borderRadius(8) + .padding({ left: 16, right: 16 }) + .type(InputType.Password) + .onChange((value: string) => { + this.password = value; + }) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ top: 20 }) + + // 确认密码 + Column() { + Text('确认密码') + .fontSize(14) + .fontColor($r('app.color.text_primary')) + .margin({ bottom: 8 }) + + TextInput({ placeholder: '请再次输入密码', text: this.confirmPassword }) + .height(48) + .fontSize(16) + .backgroundColor($r('app.color.input_background')) + .borderRadius(8) + .padding({ left: 16, right: 16 }) + .type(InputType.Password) + .onChange((value: string) => { + this.confirmPassword = value; + }) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ top: 20 }) + + // 密码不一致提示 + if (this.confirmPassword.length > 0 && this.password !== this.confirmPassword) { + Text('两次输入的密码不一致') + .fontSize(12) + .fontColor('#FF4D4F') + .margin({ top: 8 }) + } + + // 注册按钮 + Button(this.isLoading ? '注册中...' : '注册') + .width('100%') + .height(48) + .fontSize(16) + .fontColor(Color.White) + .backgroundColor($r('app.color.primary_color')) + .borderRadius(24) + .margin({ top: 36 }) + .enabled(!this.isLoading && this.isFormValid()) + .opacity(this.isFormValid() ? 1 : 0.6) .onClick(() => { - router.back(); + this.handleRegister(); }) + + // 返回登录 + Row() { + Text('已有账号?') + .fontSize(14) + .fontColor($r('app.color.text_hint')) + Text('返回登录') + .fontSize(14) + .fontColor($r('app.color.primary_color')) + .fontWeight(FontWeight.Medium) + .onClick(() => { + router.back(); + }) + } + .width('100%') + .justifyContent(FlexAlign.Center) + .margin({ top: 20 }) + .padding({ bottom: 24 }) } .width('100%') - .justifyContent(FlexAlign.Center) - .margin({ top: 20 }) + .padding({ left: 32, right: 32 }) + .margin({ top: 24 }) } - .width('100%') - .padding({ left: 32, right: 32 }) - .margin({ top: 24 }) + .layoutWeight(1) + .scrollBar(BarState.Off) } .width('100%') .height('100%') @@ -153,22 +205,34 @@ struct RegisterPage { private isFormValid(): boolean { return this.username.length > 0 + && this.email.length > 0 && this.password.length >= 6 && this.password === this.confirmPassword; } - private handleRegister(): void { + private async handleRegister(): Promise { if (!this.isFormValid()) { return; } this.isLoading = true; - - // 模拟注册请求 - setTimeout(() => { + try { + await AuthApi.register({ + username: this.username, + email: this.email, + password: this.password, + nickname: this.nickname.length > 0 ? this.nickname : undefined + }); + promptAction.showToast({ message: '注册成功', duration: 1500 }); + router.replaceUrl({ url: 'pages/HomePage' }); + } catch (err) { + const httpErr = err as HttpError; + const message = httpErr.code === 409 + ? '用户名或邮箱已被注册' + : (httpErr.message || '注册失败,请稍后重试'); + promptAction.showToast({ message, duration: 2000 }); + } finally { this.isLoading = false; - // 注册成功,返回登录页 - router.back(); - }, 1500); + } } } diff --git a/entry/src/main/ets/types/Auth.ets b/entry/src/main/ets/types/Auth.ets new file mode 100644 index 0000000..9b7e271 --- /dev/null +++ b/entry/src/main/ets/types/Auth.ets @@ -0,0 +1,68 @@ +/** + * Auth 相关请求/响应类型定义 + */ + +// 注册请求 +export interface RegisterRequest { + username: string; + email: string; + password: string; + nickname?: string; +} + +// 登录请求 +export interface LoginRequest { + email: string; + password: string; +} + +// 刷新令牌请求 +export interface RefreshTokenRequest { + refreshToken: string; +} + +// 用户基本信息(登录/注册返回) +export interface AuthUser { + id: string; + username: string; + email: string; +} + +// 认证响应数据(登录/注册返回的 data 部分) +export interface AuthResponseData { + user: AuthUser; + accessToken: string; + refreshToken: string; +} + +// 令牌对(刷新接口返回的 data 部分) +export interface TokenPair { + accessToken: string; + refreshToken: string; +} + +// 当前认证用户信息(/auth/me 返回的 data 部分) +export interface AuthMeData { + id: string; + username: string; + email: string; + nickname: string | null; + avatar: string | null; + bio: string | null; + status: string; + createdAt: string; + roles: string[]; +} + +// 通用消息响应 +export interface MessageResponse { + message: string; +} + +// 通用包装响应 +export interface ApiResponse { + data?: T; + error?: string; + code?: string; + message?: string; +} diff --git a/entry/src/main/ets/utils/HttpClient.ets b/entry/src/main/ets/utils/HttpClient.ets new file mode 100644 index 0000000..da46c2c --- /dev/null +++ b/entry/src/main/ets/utils/HttpClient.ets @@ -0,0 +1,115 @@ +import { http } from '@kit.NetworkKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { AppConfig } from '../config/AppConfig'; +import { TokenStore } from './TokenStore'; + +/** + * HTTP 请求异常 + */ +export class HttpError extends Error { + code: number; + errorCode: string; + + constructor(message: string, code: number, errorCode: string = '') { + super(message); + this.code = code; + this.errorCode = errorCode; + } +} + +/** + * HTTP 客户端,统一处理请求/响应、Token 注入、错误转换 + */ +export class HttpClient { + /** + * 发送请求 + * @param path 接口路径,如 /api/auth/login + * @param method 请求方法 + * @param body 请求体(对象会自动序列化为 JSON) + * @param needAuth 是否需要 Bearer Token(默认 false) + * @returns 解析后的 data 字段 + */ + static async request( + path: string, + method: http.RequestMethod, + body?: object, + needAuth: boolean = false + ): Promise { + const httpRequest = http.createHttp(); + try { + const url = AppConfig.BASE_URL + path; + const header: Record = { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }; + + if (needAuth) { + const token = await TokenStore.getAccessToken(); + if (token.length > 0) { + header['Authorization'] = 'Bearer ' + token; + } + } + + const options: http.HttpRequestOptions = { + method: method, + header: header, + connectTimeout: AppConfig.REQUEST_TIMEOUT, + readTimeout: AppConfig.REQUEST_TIMEOUT, + expectDataType: http.HttpDataType.STRING + }; + + if (body !== undefined && body !== null) { + options.extraData = JSON.stringify(body); + } + + const response: http.HttpResponse = await httpRequest.request(url, options); + const status = response.responseCode; + const text = response.result as string; + + let parsed: Record = {}; + if (text && text.length > 0) { + try { + parsed = JSON.parse(text) as Record; + } catch (e) { + throw new HttpError('响应解析失败: ' + text, status); + } + } + + if (status >= 200 && status < 300) { + // 成功响应统一格式 { data: ... } + if (parsed['data'] !== undefined) { + return parsed['data'] as T; + } + return parsed as T; + } + + // 错误响应 + const errorMessage = (parsed['error'] as string) || (parsed['message'] as string) || '请求失败'; + const errorCode = (parsed['code'] as string) || ''; + throw new HttpError(errorMessage, status, errorCode); + } catch (err) { + if (err instanceof HttpError) { + throw err as HttpError; + } + const businessErr = err as BusinessError; + throw new HttpError( + businessErr.message || '网络请求异常', + businessErr.code || -1 + ); + } finally { + httpRequest.destroy(); + } + } + + static get(path: string, needAuth: boolean = false): Promise { + return HttpClient.request(path, http.RequestMethod.GET, undefined, needAuth); + } + + static post(path: string, body?: object, needAuth: boolean = false): Promise { + return HttpClient.request(path, http.RequestMethod.POST, body, needAuth); + } + + static delete(path: string, needAuth: boolean = false): Promise { + return HttpClient.request(path, http.RequestMethod.DELETE, undefined, needAuth); + } +} diff --git a/entry/src/main/ets/utils/TokenStore.ets b/entry/src/main/ets/utils/TokenStore.ets new file mode 100644 index 0000000..8758dc5 --- /dev/null +++ b/entry/src/main/ets/utils/TokenStore.ets @@ -0,0 +1,74 @@ +import { preferences } from '@kit.ArkData'; +import { common } from '@kit.AbilityKit'; + +/** + * Token 持久化存储工具,基于 @ohos.data.preferences 实现 + */ +export class TokenStore { + private static readonly PREF_NAME: string = 'auth_pref'; + private static readonly KEY_ACCESS_TOKEN: string = 'access_token'; + private static readonly KEY_REFRESH_TOKEN: string = 'refresh_token'; + private static readonly KEY_USER_ID: string = 'user_id'; + + private static prefs: preferences.Preferences | null = null; + + // 内存中的 Token 缓存,用于同步获取 + private static accessTokenCache: string = ''; + + static async init(context: common.UIAbilityContext | Context): Promise { + if (TokenStore.prefs) { + return; + } + TokenStore.prefs = await preferences.getPreferences(context, TokenStore.PREF_NAME); + TokenStore.accessTokenCache = await TokenStore.prefs.get(TokenStore.KEY_ACCESS_TOKEN, '') as string; + } + + static async saveTokens(accessToken: string, refreshToken: string, userId?: string): Promise { + if (!TokenStore.prefs) { + throw new Error('TokenStore 未初始化,请先调用 TokenStore.init(context)'); + } + await TokenStore.prefs.put(TokenStore.KEY_ACCESS_TOKEN, accessToken); + await TokenStore.prefs.put(TokenStore.KEY_REFRESH_TOKEN, refreshToken); + if (userId) { + await TokenStore.prefs.put(TokenStore.KEY_USER_ID, userId); + } + await TokenStore.prefs.flush(); + TokenStore.accessTokenCache = accessToken; + } + + static async getAccessToken(): Promise { + if (!TokenStore.prefs) { + return ''; + } + const value = await TokenStore.prefs.get(TokenStore.KEY_ACCESS_TOKEN, '') as string; + TokenStore.accessTokenCache = value; + return value; + } + + static async getRefreshToken(): Promise { + if (!TokenStore.prefs) { + return ''; + } + return await TokenStore.prefs.get(TokenStore.KEY_REFRESH_TOKEN, '') as string; + } + + static getAccessTokenSync(): string { + return TokenStore.accessTokenCache; + } + + static async clear(): Promise { + if (!TokenStore.prefs) { + return; + } + await TokenStore.prefs.delete(TokenStore.KEY_ACCESS_TOKEN); + await TokenStore.prefs.delete(TokenStore.KEY_REFRESH_TOKEN); + await TokenStore.prefs.delete(TokenStore.KEY_USER_ID); + await TokenStore.prefs.flush(); + TokenStore.accessTokenCache = ''; + } + + static async isLoggedIn(): Promise { + const token = await TokenStore.getAccessToken(); + return token.length > 0; + } +} diff --git a/entry/src/main/module.json5 b/entry/src/main/module.json5 index ec778ae..ca240c0 100644 --- a/entry/src/main/module.json5 +++ b/entry/src/main/module.json5 @@ -11,6 +11,14 @@ "deliveryWithInstall": true, "installationFree": false, "pages": "$profile:main_pages", + "requestPermissions": [ + { + "name": "ohos.permission.INTERNET" + }, + { + "name": "ohos.permission.GET_NETWORK_INFO" + } + ], "abilities": [ { "name": "EntryAbility",