【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,通过状态变量管理页面的状态,通过异步方法处理登录、权限获取和页面跳转等操作。

相关推荐
Back~~1 小时前
MFC1(note)
学习
神夜大侠1 小时前
VUE 实现公告无缝循环滚动
前端·javascript·vue.js
明辉光焱1 小时前
【Electron】Electron Forge如何支持Element plus?
前端·javascript·vue.js·electron·node.js
engchina1 小时前
Oracle ADB 导入 BANK_GRAPH 的学习数据
数据库·学习·oracle·graph
柯南二号2 小时前
HarmonyOS ArkTS 下拉列表组件
前端·javascript·数据库·harmonyos·arkts
wyy72932 小时前
v-html 富文本中图片使用element-ui image-viewer组件实现预览,并且阻止滚动条
前端·ui·html
前端郭德纲2 小时前
ES6的Iterator 和 for...of 循环
前端·ecmascript·es6
王解2 小时前
【模块化大作战】Webpack如何搞定CommonJS与ES6混战(3)
前端·webpack·es6
欲游山河十万里2 小时前
(02)ES6教程——Map、Set、Reflect、Proxy、字符串、数值、对象、数组、函数
前端·ecmascript·es6
明辉光焱2 小时前
【ES6】ES6中,如何实现桥接模式?
前端·javascript·es6·桥接模式