【HarmonyOS NEXT】实战——登录页面

【HarmonyOS NEXT】实战------登录页面

在本文中,我们将深入探讨如何使用HarmonyOS NEXT来实现一个功能完备的登录页面。通过这个实战案例,你将结合页面布局、数据本地化存储、网络请求等多方面了解到HarmonyOS NEXT在构建现代应用时的强大能力和灵活性。

目录

  • [【HarmonyOS NEXT】实战------登录页面](#【HarmonyOS NEXT】实战——登录页面)
      • [1. 整体结构](#1. 整体结构)
      • [2. 状态变量](#2. 状态变量)
      • [3. 方法](#3. 方法)
      • [4. 代码解析](#4. 代码解析)
        • [4.1 `aboutToAppear` 方法](#4.1 aboutToAppear 方法)
        • [4.2 `build` 方法](#4.2 build 方法)
          • [4.2.1 总体结构](#4.2.1 总体结构)
          • [4.2.2 根部局](#4.2.2 根部局)
          • [4.2.3 顶部的欢迎文字](#4.2.3 顶部的欢迎文字)
          • [4.2.4 用户名和密码输入框](#4.2.4 用户名和密码输入框)
          • [4.2.5 记住密码与忘记密码行](#4.2.5 记住密码与忘记密码行)
          • [4.2.6 登录按钮](#4.2.6 登录按钮)
          • [4.2.7 底部图片](#4.2.7 底部图片)
          • [4.2.8 加载进度条(可选)](#4.2.8 加载进度条(可选))
        • [4.3 `handleLogin` 方法](#4.3 handleLogin 方法)
        • [4.4 `handleForgotPassword` 方法](#4.4 handleForgotPassword 方法)
      • [5. 关键函数](#5. 关键函数)
      • [7. 页面效果](#7. 页面效果)
      • [6. 总结](#6. 总结)

1. 整体结构

定义了一个 LoginPage 组件,该组件使用了 @Entry@Component 装饰器来标记它是一个入口组件和可复用的 UI 组件。LoginPage 组件包含了一些状态变量(@State)和方法(buildhandleLoginhandleForgotPassword)。

2. 状态变量

  • account: 用户名输入框的值,默认为空字符串。
  • password: 密码输入框的值,默认为空字符串。
  • text: 顶部的欢迎文字,默认为空字符串。
  • loading : 是否正在加载,默认为 false
  • rememberPassword : 是否记住密码,默认为 false
  • stopLogin : 是否停止自动登录,默认为 false

3. 方法

  • aboutToAppear: 组件即将显示时的生命周期方法,用于初始化状态变量。
  • build: 组件的构建方法,定义了页面的布局和组件。
  • handleLogin: 处理登录逻辑的方法。
  • handleForgotPassword: 处理忘记密码逻辑的方法。

4. 代码解析

4.1 aboutToAppear 方法
typescript 复制代码
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: 从本地存储中获取数据,这里主要是获取用户是否记住密码的状态,还有记录下的账户与密码。
  • 条件判断 :
    • 如果用户选择了记住密码,从本地存储中读取用户名和密码。
    • 如果用户名和密码都已存在且 路由参数stopLoginfalse,自动调用 handleLogin 方法进行登录。
4.2 build 方法
typescript 复制代码
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 : 加载进度组件,当 loadingtrue 时显示。
4.2.1 总体结构

这段代码定义了一个 build 方法,用于构建一个登录界面。界面包含以下几个主要部分:

  1. 顶部的欢迎文字
  2. 用户名和密码输入框
  3. 记住密码和忘记密码选项
  4. 登录按钮
  5. 底部图片
  6. 加载进度条(可选)

代码定义了一个登录界面,包含欢迎文字、用户名和密码输入框、记住密码和忘记密码选项、登录按钮和底部图片。通过使用 StackColumnRow 布局组件,以及 TextTextInputImageButton 等 UI 组件,构建了一个功能完整的登录页面。加载进度条部分是可选的,用于在登录过程中显示加载状态。

4.2.2 根部局
typescript 复制代码
build() {
  Stack() {
    // 页面内容
  }
}
  • Stack 是 HarmonyOS 中的一种布局组件,它可以将子组件叠放在一起。
  • Stack 作为根布局,包含整个页面的所有内容。
4.2.3 顶部的欢迎文字
typescript 复制代码
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 用户名和密码输入框
typescript 复制代码
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.accountthis.password
    • onChange 是输入框的值改变时的回调函数,用于更新 this.accountthis.password
    • fontSizefontColorwidthheightpadding 设置了输入框的样式。
    • type(InputType.Password) 将输入框类型设置为密码输入框。
  • Divider 是一个分隔线组件,用于在输入框下方添加一条线,模拟下划线效果。
4.2.5 记住密码与忘记密码行
typescript 复制代码
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) 使子组件在水平方向上均匀分布。
  • marginpadding 设置了外边距和内边距。
  • width 设置了组件的宽度。
4.2.6 登录按钮
typescript 复制代码
Button('登 录')
  .width('60%')
  .height(50)
  .backgroundColor(Color.Red)
  .fontSize(18)
  .fontColor(Color.White)
  .onClick(() => {
    // 处理登录逻辑,并且在用户选择记住密码时保存密码
    this.handleLogin();
  })
  .margin({ top: 56 });
  • Button 是一个按钮组件,用于触发登录操作。
  • widthheight 设置了按钮的宽度和高度。
  • backgroundColorfontColor 设置了按钮的背景颜色和字体颜色。
  • onClick 是按钮的点击事件回调函数,用于处理登录逻辑。
  • margin 设置了按钮的上边距。
4.2.7 底部图片
typescript 复制代码
Image($r('app.media.login_bottom'))
  .width('100%');
  • Image 是一个图像组件,用于显示底部图片。
  • width 设置了图片的宽度。
  • $r 是资源引用函数,用于引用应用中的资源。
4.2.8 加载进度条(可选)
typescript 复制代码
if (this.loading) {
  LoadingProgress().height(180).color('#cd0401');
}
  • LoadingProgress 是一个加载进度条组件,用于显示加载状态。
  • heightcolor 设置了进度条的高度和颜色。
  • if (this.loading) 控制加载进度条的显示,只有当 this.loadingtrue 时才会显示加载进度条。
4.3 handleLogin 方法
typescript 复制代码
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 方法将用户名和密码保存到本地存储。
  • 执行登录逻辑 :
    • 设置 loadingtrue,显示加载进度。
    • 调用 Login API 进行登录,传入用户名、密码、UUID 和验证码。
    • 成功回调 :
      • 如果 res.code 不为 0,显示错误提示。
      • 如果 res.code0,保存用户数据和 token 到本地存储。
      • 调用 GetPermission API 获取权限,并保存到本地存储。
      • 调用 GetMenu API 获取菜单权限,并保存到本地存储。
      • 使用 router.replaceUrl 方法跳转到主页,并处理跳转成功和失败的情况。
    • 失败回调 :
      • 显示错误提示,并设置 loadingfalse
4.4 handleForgotPassword 方法
typescript 复制代码
handleForgotPassword() {
  // 前往忘记密码页面
  router.pushUrl({ url: 'pages/Forgot' });
}
  • 跳转到忘记密码页面 :
    • 使用 router.pushUrl 方法跳转到忘记密码页面。

5. 关键函数

  • PreferencesUtils.getPreferencesUtils.put: 用于从本地存储中读取和保存数据。
  • router.getParams: 用于获取路由传递的参数。
  • router.replaceUrlrouter.pushUrl: 用于在页面之间进行跳转。
  • promptAction.showToast: 用于显示短暂的提示信息。

7. 页面效果

进入页面:

登录接口请求中:

6. 总结

这段代码实现了一个完整的登录页面,包括用户名和密码的输入、记住密码功能、忘记密码功能以及登录逻辑。它使用了 HarmonyOS NEXT 的组件和 API,通过状态变量管理页面的状态,通过异步方法处理登录、权限获取和页面跳转等操作。

相关推荐
Nan_Shu_614几秒前
学习: Threejs (2)
前端·javascript·学习
G_G#8 分钟前
纯前端js插件实现同一浏览器控制只允许打开一个标签,处理session变更问题
前端·javascript·浏览器标签页通信·只允许一个标签页
程序猿追17 分钟前
【鸿蒙PC桌面端实战】从零构建 ArkTS 高性能图像展示器:DevEco Studio 调试与 HDC 命令行验证全流程
华为·harmonyos
@大迁世界24 分钟前
TypeScript 的本质并非类型,而是信任
开发语言·前端·javascript·typescript·ecmascript
GIS之路33 分钟前
GDAL 实现矢量裁剪
前端·python·信息可视化
是一个Bug36 分钟前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu1213838 分钟前
React面向组件编程
开发语言·前端·javascript
学历真的很重要39 分钟前
LangChain V1.0 Context Engineering(上下文工程)详细指南
人工智能·后端·学习·语言模型·面试·职场和发展·langchain
持续升级打怪中1 小时前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化
GIS之路1 小时前
GDAL 实现矢量合并
前端