【HarmonyOS NEXT】实战——登录页面
在本文中,我们将深入探讨如何使用HarmonyOS NEXT来实现一个功能完备的登录页面。通过这个实战案例,你将结合页面布局、数据本地化存储、网络请求等多方面了解到HarmonyOS NEXT在构建现代应用时的强大能力和灵活性。
@TOC
1. 整体结构
定义了一个 LoginPage
组件,该组件使用了 @Entry
和 @Component
装饰器来标记它是一个入口组件和可复用的 UI 组件。LoginPage
组件包含了一些状态变量(@State
)和方法(build
、handleLogin
、handleForgotPassword
)。
2. 状态变量
account
: 用户名输入框的值,默认为空字符串。password
: 密码输入框的值,默认为空字符串。text
: 顶部的欢迎文字,默认为空字符串。loading
: 是否正在加载,默认为false
。rememberPassword
: 是否记住密码,默认为false
。stopLogin
: 是否停止自动登录,默认为false
。
3. 方法
aboutToAppear
: 组件即将显示时的生命周期方法,用于初始化状态变量。build
: 组件的构建方法,定义了页面的布局和组件。handleLogin
: 处理登录逻辑的方法。handleForgotPassword
: 处理忘记密码逻辑的方法。
4. 代码解析
4.1 aboutToAppear
方法
async aboutToAppear() {
const params = router.getParams() as JumpParams;
this.stopLogin = params.stopLogin || false;
this.rememberPassword = await PreferencesUtils.get('rememberPassword') as boolean;
if (this.rememberPassword) {
this.account = await PreferencesUtils.get("account") as string;
this.password = await PreferencesUtils.get("password") as string;
}
if (this.account && this.password && !this.stopLogin) {
this.handleLogin();
}
}
router.getParams()
: 获取路由传递的参数,类型为JumpParams
。这里主要是为了通过路由获取参数,来判断是否进行自动登录操作。默认用户正常进入登陆页的时候需要自动登录,但是如果是点击了退出登录来到了登陆页则不进行自动登录。或许还有更多的特殊情况,这一点可以通过路由传参实现。PreferencesUtils.get
: 从本地存储中获取数据,这里主要是获取用户是否记住密码的状态,还有记录下的账户与密码。条件判断:
- 如果用户选择了记住密码,从本地存储中读取用户名和密码。
- 如果用户名和密码都已存在且 路由参数
stopLogin
为false
,自动调用handleLogin
方法进行登录。
4.2 build
方法
build() {
Stack() {
Column() {
// 顶部的欢迎文字
Text(this.text)
Column() {
RelativeContainer() {
Text('您好!')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor(Color.Red)
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
center: { anchor: '__container__', align: VerticalAlign.Center }
})
.width('100%');
Text('欢迎使用xx系统!')
.fontSize(22)
.fontColor(Color.Black)
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
center: { anchor: '__container__', align: VerticalAlign.Center }
})
.width('100%')
.margin({ top: 80 });
}
.height('30%')
.width('100%')
.padding({ left: 32, right: 32 })
.backgroundColor(Color.White);
// 用户名和密码输入框
Column() {
// 用户名输入框
Column() {
TextInput({ placeholder: '请输入用户名', text: this.account })
.onChange((value: string) => {
this.account = value;
})
.fontSize(18)
.fontColor(Color.Black)
.width('100%')
.height(50)
.padding({ left: 10, right: 10 });
Divider().height(1).color('#194487fe'); // 下划线
}
// 密码输入框
Column() {
TextInput({ placeholder: '请输入密码', text: this.password })
.onChange((value: string) => {
this.password = value;
})
.fontSize(18)
.fontColor(Color.Black)
.width('100%')
.height(50)
.padding({ left: 10, right: 10 })
.type(InputType.Password);
Divider().height(1).color('#194487fe'); // 下划线
}.margin({ top: 32 });
}
.width('100%')
.padding({ left: 32, right: 32 })
.backgroundColor(Color.White);
// 记住密码与忘记密码行
Row() {
Row() {
// 根据用户是否选择记住密码,显示不同的图标
Image($r(this.rememberPassword ? 'app.media.radio_normal_checkmark' : 'app.media.radio_normal'))
.width(20)
.height(20)
.onClick(async () => {
// 切换记住密码的状态
this.rememberPassword = !this.rememberPassword;
await PreferencesUtils.put("rememberPassword", this.rememberPassword);
});
Text('记住密码')
.fontSize(14)
.fontColor(Color.Red)
.margin({ left: 10.5 })
.onClick(async () => {
// 也可以点击文字时切换记住密码的状态
this.rememberPassword = !this.rememberPassword;
await PreferencesUtils.put("rememberPassword", this.rememberPassword);
if (!this.rememberPassword) {
await PreferencesUtils.put("account", '');
await PreferencesUtils.put("password", '');
}
});
}
Text('忘记密码')
.fontSize(14)
.fontColor(Color.Gray)
.onClick(() => {
// 实现忘记密码功能,跳转或弹出窗口
this.handleForgotPassword();
});
}
.justifyContent(FlexAlign.SpaceBetween)
.margin({ top: 21 })
.padding({ left: 32, right: 32 })
.width('100%');
// 登录按钮
Button('登 录')
.width('60%')
.height(50)
.backgroundColor(Color.Red)
.fontSize(18)
.fontColor(Color.White)
.onClick(() => {
// 处理登录逻辑,并且在用户选择记住密码时保存密码
this.handleLogin();
})
.margin({ top: 56 });
// 底部图片
Image($r('app.media.login_bottom'))
.width('100%');
}
.height('100%')
.width('100%')
.backgroundColor(Color.White)
.justifyContent(FlexAlign.SpaceBetween);
if (this.loading) {
LoadingProgress().height(180).color('#cd0401');
}
}
}
}
Stack
: 容器组件,用于堆叠其他组件。Column
: 垂直布局组件。Text
: 文本组件,用于显示文本内容。RelativeContainer
: 相对布局容器,用于精确控制子组件的位置。TextInput
: 输入框组件,用于输入用户名和密码。onChange
: 当输入框内容发生变化时的回调函数。type(InputType.Password)
: 设置输入框为密码输入类型。
Divider
: 分割线组件,用于在输入框下方添加下划线。Row
: 水平布局组件。Image
: 图像组件,用于显示记住密码的图标。onClick
: 点击图标时切换记住密码的状态,并保存到本地存储。
Button
: 按钮组件,用于触发登录操作。LoadingProgress
: 加载进度组件,当loading
为true
时显示。4.2.1 总体结构
这段代码定义了一个
build
方法,用于构建一个登录界面。界面包含以下几个主要部分:- 顶部的欢迎文字
- 用户名和密码输入框
- 记住密码和忘记密码选项
- 登录按钮
- 底部图片
- 加载进度条(可选)
代码定义了一个登录界面,包含欢迎文字、用户名和密码输入框、记住密码和忘记密码选项、登录按钮和底部图片。通过使用 Stack
、Column
和 Row
布局组件,以及 Text
、TextInput
、Image
和 Button
等 UI 组件,构建了一个功能完整的登录页面。加载进度条部分是可选的,用于在登录过程中显示加载状态。
4.2.2 根部局
build() {
Stack() {
// 页面内容
}
}
Stack
是 HarmonyOS 中的一种布局组件,它可以将子组件叠放在一起。Stack
作为根布局,包含整个页面的所有内容。
4.2.3 顶部的欢迎文字
Column() {
// 顶部的欢迎文字
Text(this.text)
Column() {
RelativeContainer() {
Text('您好!')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor(Color.Red)
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
center: { anchor: '__container__', align: VerticalAlign.Center }
})
.width('100%');
Text('欢迎使用xx系统!')
.fontSize(22)
.fontColor(Color.Black)
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
center: { anchor: '__container__', align: VerticalAlign.Center }
})
.width('100%')
.margin({ top: 80 });
}
.height('30%')
.width('100%')
.padding({ left: 32, right: 32 })
.backgroundColor(Color.White);
}
}
Column
是一个垂直布局组件,用于将子组件垂直排列。Text(this.text)
显示一个变量this.text
,可能是动态的欢迎文字。- 内部的
Column
包含一个RelativeContainer
,用于更灵活地对齐子组件。 RelativeContainer
是一个相对布局组件,可以使用alignRules
来指定子组件的对齐方式。Text('您好!')
设置了字体大小、加粗、字体颜色和对齐方式。Text('欢迎使用xx系统!')
设置了字体大小、字体颜色和对齐方式,并且设置了上边距。
RelativeContainer
设置了高度、宽度、内边距和背景颜色。
4.2.4 用户名和密码输入框
Column() {
// 用户名输入框
Column() {
TextInput({ placeholder: '请输入用户名', text: this.account })
.onChange((value: string) => {
this.account = value;
})
.fontSize(18)
.fontColor(Color.Black)
.width('100%')
.height(50)
.padding({ left: 10, right: 10 });
Divider().height(1).color('#194487fe'); // 下划线
}
// 密码输入框
Column() {
TextInput({ placeholder: '请输入密码', text: this.password })
.onChange((value: string) => {
this.password = value;
})
.fontSize(18)
.fontColor(Color.Black)
.width('100%')
.height(50)
.padding({ left: 10, right: 10 })
.type(InputType.Password);
Divider().height(1).color('#194487fe'); // 下划线
}.margin({ top: 32 });
}
.width('100%')
.padding({ left: 32, right: 32 })
.backgroundColor(Color.White);
- 外部的
Column
包含用户名和密码输入框。 内部的
Column
用于单独包装用户名输入框和密码输入框,以便更好地控制样式。TextInput
是一个输入框组件,用于用户输入文本。placeholder
是输入框的占位符文本。text
是输入框当前显示的文本,绑定到this.account
和this.password
。onChange
是输入框的值改变时的回调函数,用于更新this.account
和this.password
。fontSize
、fontColor
、width
、height
和padding
设置了输入框的样式。type(InputType.Password)
将输入框类型设置为密码输入框。
Divider
是一个分隔线组件,用于在输入框下方添加一条线,模拟下划线效果。
4.2.5 记住密码与忘记密码行
Row() {
Row() {
// 根据用户是否选择记住密码,显示不同的图标
Image($r(this.rememberPassword ? 'app.media.radio_normal_checkmark' : 'app.media.radio_normal'))
.width(20)
.height(20)
.onClick(async () => {
// 切换记住密码的状态
this.rememberPassword = !this.rememberPassword;
await PreferencesUtils.put("rememberPassword", this.rememberPassword);
});
Text('记住密码')
.fontSize(14)
.fontColor(Color.Red)
.margin({ left: 10.5 })
.onClick(async () => {
// 也可以点击文字时切换记住密码的状态
this.rememberPassword = !this.rememberPassword;
await PreferencesUtils.put("rememberPassword", this.rememberPassword);
if (!this.rememberPassword) {
await PreferencesUtils.put("account", '');
await PreferencesUtils.put("password", '');
}
});
}
Text('忘记密码')
.fontSize(14)
.fontColor(Color.Gray)
.onClick(() => {
// 实现忘记密码功能,跳转或弹出窗口
this.handleForgotPassword();
});
}
.justifyContent(FlexAlign.SpaceBetween)
.margin({ top: 21 })
.padding({ left: 32, right: 32 })
.width('100%');
Row
是一个水平布局组件,用于将子组件水平排列。内部的
Row
包含记住密码的图标和文本。Image
是一个图像组件,根据this.rememberPassword
的值显示不同的图标。onClick
是点击事件的回调函数,用于切换记住密码的状态并保存到偏好设置中。Text('记住密码')
显示记住密码的文本,设置了字体大小、颜色和左边距,并绑定了点击事件。
- 外部的
Row
包含忘记密码的文本,设置了字体大小、颜色,并绑定了点击事件。 justifyContent(FlexAlign.SpaceBetween)
使子组件在水平方向上均匀分布。margin
和padding
设置了外边距和内边距。width
设置了组件的宽度。
4.2.6 登录按钮
Button('登 录')
.width('60%')
.height(50)
.backgroundColor(Color.Red)
.fontSize(18)
.fontColor(Color.White)
.onClick(() => {
// 处理登录逻辑,并且在用户选择记住密码时保存密码
this.handleLogin();
})
.margin({ top: 56 });
Button
是一个按钮组件,用于触发登录操作。width
和height
设置了按钮的宽度和高度。backgroundColor
和fontColor
设置了按钮的背景颜色和字体颜色。onClick
是按钮的点击事件回调函数,用于处理登录逻辑。margin
设置了按钮的上边距。
4.2.7 底部图片
Image($r('app.media.login_bottom'))
.width('100%');
Image
是一个图像组件,用于显示底部图片。width
设置了图片的宽度。$r
是资源引用函数,用于引用应用中的资源。
4.2.8 加载进度条(可选)
if (this.loading) {
LoadingProgress().height(180).color('#cd0401');
}
LoadingProgress
是一个加载进度条组件,用于显示加载状态。height
和color
设置了进度条的高度和颜色。if (this.loading)
控制加载进度条的显示,只有当this.loading
为true
时才会显示加载进度条。
4.3 handleLogin
方法
async handleLogin() {
if (this.rememberPassword) {
// 如果用户选择了记住密码,保存账户和密码到本地存储
await PreferencesUtils.put("account", this.account);
await PreferencesUtils.put("password", this.password);
}
// 执行登录逻辑
this.loading = true;
Login<LoginResponse>({
username: this.account,
password: this.password,
uuid: '',
code: '',
}).then(async (res) => {
if (res.code !== 0) {
promptAction.showToast({
message: res.msg,
duration: 1000,
});
this.loading = false;
} else {
// 保存用户数据和 token
await PreferencesUtils.put("userData", res.data);
await PreferencesUtils.put("token", res.data.token);
// 获取权限
const permission = await GetPermission<PermissionResponse>();
await PreferencesUtils.put("permission", permission);
await PreferencesUtils.put("SystemUser_Permission", permission.data.dataPermission);
// 获取菜单权限
const menuRes = await GetMenu<PermissionResponse>();
await PreferencesUtils.put("User_Manage", menuRes.data || []);
// 跳转到主页
router.replaceUrl({ url: 'pages/MainPage' }).then(() => {
console.info('Succeeded in jumping to the pages/MainPage.');
}).catch((err: BusinessError) => {
console.error(`Failed to jump to the second page. Code is ${err.code}, message is ${err.message}`);
});
this.loading = false;
}
}).catch((err: string) => {
promptAction.showToast({
message: err,
duration: 1000,
});
this.loading = false;
console.error(`Failed to login, message is ${err}`);
});
}
保存用户名和密码:
- 如果用户选择了记住密码,使用
PreferencesUtils.put
方法将用户名和密码保存到本地存储。
- 如果用户选择了记住密码,使用
执行登录逻辑:
- 设置
loading
为true
,显示加载进度。 - 调用
Login
API 进行登录,传入用户名、密码、UUID 和验证码。 成功回调:
- 如果
res.code
不为0
,显示错误提示。 - 如果
res.code
为0
,保存用户数据和 token 到本地存储。 - 调用
GetPermission
API 获取权限,并保存到本地存储。 - 调用
GetMenu
API 获取菜单权限,并保存到本地存储。 - 使用
router.replaceUrl
方法跳转到主页,并处理跳转成功和失败的情况。
- 如果
失败回调:
- 显示错误提示,并设置
loading
为false
。
- 显示错误提示,并设置
- 设置
4.4 handleForgotPassword
方法
handleForgotPassword() {
// 前往忘记密码页面
router.pushUrl({ url: 'pages/Forgot' });
}
跳转到忘记密码页面:
- 使用
router.pushUrl
方法跳转到忘记密码页面。
- 使用
5. 关键函数
PreferencesUtils.get
和PreferencesUtils.put
: 用于从本地存储中读取和保存数据。router.getParams
: 用于获取路由传递的参数。router.replaceUrl
和router.pushUrl
: 用于在页面之间进行跳转。promptAction.showToast
: 用于显示短暂的提示信息。
7. 页面效果
进入页面:
登录接口请求中:
6. 总结
这段代码实现了一个完整的登录页面,包括用户名和密码的输入、记住密码功能、忘记密码功能以及登录逻辑。它使用了 HarmonyOS NEXT 的组件和 API,通过状态变量管理页面的状态,通过异步方法处理登录、权限获取和页面跳转等操作。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。