鸿蒙 HarmonyOS 校园风登录页面开发实战 —— 基于 ArkTS 的 Stage 模型完整教程
鸿蒙 HarmonyOS 校园风登录页面开发实战 —— 基于 ArkTS 的 Stage 模型完整教程
一、前言
随着国产操作系统的蓬勃发展,华为 HarmonyOS(鸿蒙系统)已经逐渐成为移动端、平板端乃至物联网设备的重要选择。对于前端开发者而言,鸿蒙提供了 ArkTS(方舟 TypeScript)作为主力开发语言,它基于 TypeScript 语法并扩展了声明式 UI 描述能力,让页面开发既高效又优雅。
在校园类应用开发中,登录页面 是用户接触产品的第一张面孔。一个设计精良、风格统一的登录页面不仅能提升用户体验,更能传达校园文化的精神内核。本文将从零到一,手把手带你使用 HarmonyOS 的 ArkTS 语言和 Stage 模型,打造一个 清新校园风 的登录窗口。
无论你是刚刚接触鸿蒙开发,还是有一定经验想要系统了解登录页面的最佳实践,这篇文章都将给你带来干货满满的内容。全文约 10000 字,涵盖环境搭建、项目结构、UI 设计、路由跳转、状态管理、样式细节等方方面面,所有代码均可直接运行。
二、HarmonyOS 与 ArkTS 简介
2.1 什么是 HarmonyOS
HarmonyOS(鸿蒙系统)是华为开发的一款面向全场景的分布式操作系统。它的核心理念是 “一生万物,万物归一”,通过统一的开发框架,让应用可以在手机、平板、手表、智慧屏、车机等多种设备上无缝运行。
2.2 Stage 模型
从 HarmonyOS 3.0 开始,Stage 模型成为主推的应用开发模型。与早期的 FA(Feature Ability)模型相比,Stage 模型有以下显著优势:
| 特性 | FA 模型 | Stage 模型 |
|---|---|---|
| 组件管理 | Ability 即页面 | Ability + 页面分离 |
| 生命周期 | 较简单 | 更丰富精细 |
| 共享数据 | 使用 GlobalThis | 使用 AppScope + 共享上下文 |
| 模块化 | 较弱 | 支持 HAP 分包,模块化强 |
| 推荐程度 | 已逐渐弃用 | 当前主流推荐 |
2.3 ArkTS 语言
ArkTS 是鸿蒙生态的主力开发语言,它在 TypeScript 的基础上做了以下增强:
- 声明式 UI:使用
@Component+build()描述界面结构 - 状态管理:使用
@State、@Prop、@Link等装饰器管理组件状态 - 双向数据绑定:支持
$$语法实现数据的快速绑定 - 强类型约束:继承了 TypeScript 的静态类型检查,减少运行时错误
- 性能优化:ArkTS 编译器会对代码进行 AOT(Ahead-of-Time)编译,运行时性能接近原生
一个简单的 ArkTS 组件结构如下:
@Component
struct MyComponent {
@State count: number = 0
build() {
Column() {
Text(`计数: ${this.count}`)
.fontSize(24)
Button('点击 +1')
.onClick(() => {
this.count++
})
}
.width('100%')
.height('100%')
}
}
三、项目环境搭建
3.1 开发工具:DevEco Studio
鸿蒙应用开发的首选 IDE 是 DevEco Studio,它是华为基于 IntelliJ IDEA 定制开发的集成开发环境。建议下载最新版本(当前推荐 5.0+)。
3.2 创建项目
打开 DevEco Studio 后,按以下步骤创建项目:
- 点击 File → New → Create Project
- 选择模板:Empty Ability(空模板)
- 填写项目信息:
- Project Name:
CampusLoginDemo - Bundle Name:
com.example.campuslogindemo - Save Location:自定义
- Compile SDK:选择 4.0 或更高版本
- Model:选择 Stage 模型
- Language:选择 ArkTS
- Project Name:
- 点击 Finish,等待项目初始化完成
项目创建后的目录结构如下:
CampusLoginDemo/
├── AppScope/ # 应用级配置
│ └── app.json5 # 应用全局配置
├── entry/ # 主 Entry 模块
│ ├── src/
│ │ ├── main/
│ │ │ ├── ets/ # ArkTS 源码
│ │ │ │ ├── entryability/
│ │ │ │ │ └── EntryAbility.ets
│ │ │ │ └── pages/
│ │ │ │ └── Index.ets
│ │ │ ├── resources/ # 资源文件
│ │ │ │ ├── base/
│ │ │ │ │ ├── element/
│ │ │ │ │ ├── media/
│ │ │ │ │ ├── profile/
│ │ │ │ │ │ └── main_pages.json
│ │ │ │ │ └── string.json
│ │ │ │ └── ...
│ │ │ └── module.json5 # 模块配置
│ │ └── ...
│ └── build-profile.json5
├── oh_modules/ # 依赖模块
├── build-profile.json5 # 全局构建配置
└── hvigorfile.ts # 构建脚本
3.3 关键配置文件说明
module.json5 —— 模块配置
{
"module": {
"name": "entry",
"type": "entry",
"mainElement": "EntryAbility",
"deviceTypes": ["phone"],
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"exported": true,
"skills": [
{
"entities": ["entity.system.home"],
"actions": ["ohos.want.action.home"]
}
]
}
]
}
}
这里的关键点是:
srcEntry指向 Ability 的入口文件pages指向$profile:main_pages,即resources/base/profile/main_pages.jsonskills中的entity.system.home+ohos.want.action.home表示这是桌面首页入口
main_pages.json —— 页面路由表
{
"src": [
"pages/Index",
"pages/Login"
]
}
这个文件定义了应用中的所有页面路由路径。每当新增一个页面时,都必须在这里注册,否则应用无法找到对应的页面资源导致跳转失败。
四、校园风登录页面设计思路
4.1 什么是"校园风"
在动手写代码之前,先来明确"校园风"的设计语言。校园风格的核心关键词是:
| 关键词 | 设计体现 |
|---|---|
| 🌿 青春 | 明亮的色彩、圆润柔和的形状 |
| 📚 学术 | 学士帽、书本元素、校训文字 |
| 🏫 校园 | 绿色调(象征树木和草坪) |
| ✨ 清新 | 留白充足、卡片式布局、柔和阴影 |
| 🤝 亲切 | 友好的文案、引导性交互 |
4.2 配色方案
我们为校园风登录页面选择了以下配色:
主色: #4CAF50 —— 草木绿,代表校园的自然气息
深绿: #2E7D32 —— 用于主标题,稳重
浅绿: #81C784 —— 辅助文字、链接
背景: #E8F5E9 —— 极浅绿色,给人宁静、舒适感
输入框: #F1F8E9 —— 极浅米绿,区分卡片背景
按钮禁用:#C8E6C9 —— 按钮不可用状态的浅绿
卡片阴影:#1A4CAF50 —— 带透明度的绿色阴影
这套配色方案的核心原则:
- 低饱和度 —— 避免刺眼的高饱和色,校园风追求柔和
- 同色系渐变 —— 从
#E8F5E9到#4CAF50到#2E7D32,色彩层次分明但不跳脱 - 绿色为主,白色为辅 —— 和自然校园环境吻合
4.3 布局结构
页面采用自上而下的垂直布局,共分为三个层次:
┌──────────────────────────────────┐
│ 顶部徽章区域 │ ← 校徽 + 校名 + 校训
│ │
│ ┌────────────────────────────┐ │
│ │ 表单卡片区域 │ │ ← 白色卡片,悬浮效果
│ │ ┌──────────────────────┐ │ │
│ │ │ 学号 / 用户名输入框 │ │ │
│ │ ├──────────────────────┤ │ │
│ │ │ 密码输入框 + 可见切换 │ │ │
│ │ ├──────────────────────┤ │ │
│ │ │ 忘记密码链接 │ │ │
│ │ ├──────────────────────┤ │ │
│ │ │ 登录按钮 │ │ │
│ │ ├──────────────────────┤ │ │
│ │ │ 注册引导文字 │ │ │
│ │ └──────────────────────┘ │ │
│ └────────────────────────────┘ │
└──────────────────────────────────┘
层的优点:
- 视觉聚焦:用户的视线从顶部校徽 → 中间表单 → 底部按钮,路径清晰
- 操作流畅:从上到下依次填写表单,符合自然操作流程
- 适配性好:使用
Scroll包裹,在屏幕较小的设备上也能正常显示全部内容
4.4 交互细节
好的登录页面不止好看,还要好"用"。以下是本页面实现的交互细节:
- 输入框聚焦态:未设计复杂的边框变化,但浅绿背景在输入时能自然引导
- 密码可见切换:点击 👁 图标切换密码明文/密文显示,提升输入体验
- 按钮状态联动:用户名和密码都非空时按钮才高亮可用,直观反馈
- 按钮置灰禁用:不符合条件时按钮呈浅灰色,视觉上不可点击
五、代码实现 —— 登录页面详解
5.1 完整代码
@Entry
@Component
struct Login {
@State username: string = '';
@State password: string = '';
@State passwordVisible: boolean = false;
build() {
Column() {
Scroll() {
Column() {
// ===== 顶部校园徽章区域 =====
Column() {
// 校徽图案(用形状拼一个学士帽风格图标)
Stack() {
Circle()
.width(80)
.height(80)
.fill('#4CAF50')
Column() {
Text('🎓')
.fontSize(36)
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
.width(80)
.height(80)
.margin({ bottom: 16 })
Text('智慧校园')
.fontSize(26)
.fontWeight(FontWeight.Bold)
.fontColor('#2E7D32')
Text('立德树人 · 知行合一')
.fontSize(13)
.fontColor('#81C784')
.margin({ top: 6 })
.letterSpacing(4)
}
.width('100%')
.margin({ top: 60, bottom: 50 })
// ===== 表单卡片 =====
Column() {
// 表单标题
Text('账号登录')
.fontSize(20)
.fontWeight(FontWeight.Medium)
.fontColor('#1a1a1a')
.width('100%')
.margin({ bottom: 28 })
// 学号 / 用户名
Column() {
Row() {
Text('📖')
.fontSize(16)
.margin({ right: 6 })
Text('学号 / 用户名')
.fontSize(14)
.fontColor('#558B2F')
}
.margin({ bottom: 8 })
TextInput({ placeholder: '请输入学号或用户名', text: this.username })
.onChange((value: string) => {
this.username = value
})
.fontSize(16)
.placeholderColor('#bdbdbd')
.padding({ left: 16, right: 16 })
.width('100%')
.height(48)
.backgroundColor('#F1F8E9')
.borderRadius(12)
}
.width('100%')
.margin({ bottom: 20 })
// 密码
Column() {
Row() {
Text('🔒')
.fontSize(16)
.margin({ right: 6 })
Text('密码')
.fontSize(14)
.fontColor('#558B2F')
}
.margin({ bottom: 8 })
Row() {
TextInput({ placeholder: '请输入密码', text: this.password })
.type(InputType.Password)
.onChange((value: string) => {
this.password = value
})
.fontSize(16)
.placeholderColor('#bdbdbd')
.layoutWeight(1)
Text(this.passwordVisible ? '🙈' : '👁')
.fontSize(18)
.onClick(() => {
this.passwordVisible = !this.passwordVisible
})
}
.padding({ left: 16, right: 16 })
.width('100%')
.height(48)
.backgroundColor('#F1F8E9')
.borderRadius(12)
}
.width('100%')
.margin({ bottom: 12 })
// 忘记密码
Row() {
Blank()
Text('忘记密码?')
.fontSize(13)
.fontColor('#81C784')
.onClick(() => {
console.log('跳转到忘记密码')
})
}
.width('100%')
.margin({ bottom: 32 })
// 登录按钮
Button('登 录')
.type(ButtonType.Capsule)
.width('100%')
.height(50)
.backgroundColor(this.username.length > 0 && this.password.length > 0
? '#4CAF50' : '#C8E6C9')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.fontColor(Color.White)
.enabled(this.username.length > 0 && this.password.length > 0)
.onClick(() => {
console.log(`登录:学号=${this.username}, 密码=${this.password}`)
})
// 注册引导
Row() {
Text('还没有校园账号?')
.fontSize(14)
.fontColor('#9E9E9E')
Text('立即注册')
.fontSize(14)
.fontColor('#4CAF50')
.fontWeight(FontWeight.Medium)
.margin({ left: 4 })
.onClick(() => {
console.log('跳转到注册')
})
}
.width('100%')
.justifyContent(FlexAlign.Center)
.margin({ top: 24 })
}
.width('100%')
.padding(28)
.backgroundColor(Color.White)
.borderRadius(20)
.shadow({
radius: 20,
color: '#1A4CAF50',
offsetY: 4
})
}
.width('100%')
.padding({ left: 28, right: 28 })
}
.width('100%')
.scrollBar(BarState.Off)
}
.width('100%')
.height('100%')
.backgroundColor('#E8F5E9')
}
}
5.2 代码分块详解
下面逐块分析代码的核心设计。
5.2.1 组件定义与状态管理
@Entry
@Component
struct Login {
@State username: string = '';
@State password: string = '';
@State passwordVisible: boolean = false;
@Entry:标记该组件为页面的入口组件,每个页面有且只有一个 @Entry 组件@Component:声明这是一个自定义组件@State:状态装饰器。当被修饰的变量值发生变化时,UI 会自动重新渲染。这里三个状态变量分别管理:username:绑定用户名输入框的文本password:绑定密码输入框的文本passwordVisible:控制密码是否明文显示
注意:ArkTS 中
@State只能修饰简单类型(string, number, boolean)或简单对象/数组,不支持复杂嵌套对象的深度监听。
5.2.2 根布局
build() {
Column() {
Scroll() {
// ...
}
.scrollBar(BarState.Off)
}
.width('100%')
.height('100%')
.backgroundColor('#E8F5E9')
}
Column:垂直布局容器,所有子组件从上到下排列Scroll:滚动容器。当内容高度超过屏幕高度时,用户可以向下滑动查看被遮挡的内容。这在登录页中非常重要,因为某些小屏设备可能无法一屏显示完整的表单。BarState.Off:隐藏滚动条,保持界面整洁- 根背景色
#E8F5E9:极浅的绿色,奠定校园清新的基调
5.2.3 顶部徽章区域
Column() {
Stack() {
Circle()
.width(80).height(80)
.fill('#4CAF50')
Column() {
Text('🎓').fontSize(36)
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
.width(80).height(80)
.margin({ bottom: 16 })
Text('智慧校园')
.fontSize(26)
.fontWeight(FontWeight.Bold)
.fontColor('#2E7D32')
Text('立德树人 · 知行合一')
.fontSize(13)
.fontColor('#81C784')
.margin({ top: 6 })
.letterSpacing(4)
}
设计细节:
Stack层叠布局:将绿色圆形(Circle)作为底层,🎓 Emoji 作为顶层居中显示,组合成校徽效果- 标题
#2E7D32:深绿色传递稳重、学术的感觉 - 校训
#81C784:浅绿色不抢眼,但letterSpacing(4)增加字符间距,提升文字品质感 - “立德树人 · 知行合一”:这是常用的校训句式,中间用
·分隔,既古雅又现代
为什么要用 Emoji 做校徽? 在原型阶段,使用 Emoji 可以快速验证布局效果,无需额外准备图标资源。正式发布时,可以替换为设计团队提供的 PNG/SVG 校徽图片。
5.2.4 表单卡片
Column() {
// ...
}
.padding(28)
.backgroundColor(Color.White)
.borderRadius(20)
.shadow({
radius: 20,
color: '#1A4CAF50',
offsetY: 4
})
表单卡片的设计细节:
borderRadius(20):20px 圆角,让卡片看起来柔和亲切shadow:阴影效果,参数解释:radius: 20—— 阴影模糊半径,值越大阴影越柔和color: '#1A4CAF50'—— 绿色透明度 10%(1A是十六进制透明度值),让阴影带有环境色而不仅仅是灰色offsetY: 4—— 垂直向下偏移 4px,模拟自然光照从上方照射的效果
这种 “漂浮卡片” 设计是现代移动端 UI 的经典手法,它通过阴影将内容"抬离"背景,形成清晰的视觉层次。
5.2.5 输入框组件
我们以用户名输入框为例:
Column() {
// 标签行
Row() {
Text('📖').fontSize(16).margin({ right: 6 })
Text('学号 / 用户名').fontSize(14).fontColor('#558B2F')
}
.margin({ bottom: 8 })
TextInput({ placeholder: '请输入学号或用户名', text: this.username })
.onChange((value: string) => {
this.username = value
})
.fontSize(16)
.placeholderColor('#bdbdbd')
.padding({ left: 16, right: 16 })
.width('100%')
.height(48)
.backgroundColor('#F1F8E9')
.borderRadius(12)
}
设计考量:
-
标签与输入框分离:标签在上方(带小图标),输入框在下方。这种上下结构在移动端比左右结构更适合小屏幕阅读。
-
TextInput属性:placeholder:占位提示文字,引导用户输入onChange:输入变化回调,同步更新this.usernamebackgroundColor('#F1F8E9'):输入框使用比卡片背景略深的米绿色,形成层次感borderRadius(12):12px 圆角,配合卡片的 20px 圆角形成视觉节奏
-
双向数据绑定:
{ text: this.username }将状态变量绑定到输入框的显示文本;onChange将用户输入写回状态变量。这是"单向数据流 + 事件回调"的模式,也是 ArkTS 推荐的做法。
5.2.6 密码输入与可见切换
密码输入框与用户名输入框类似,但有三个特殊之处:
// 1. 输入类型设为密码
TextInput({ placeholder: '请输入密码', text: this.password })
.type(InputType.Password)
// 2.「忘记密码」链接
Row() {
Blank()
Text('忘记密码?')
// ...
}
// 3. 密码可见切换按钮
Text(this.passwordVisible ? '🙈' : '👁')
.fontSize(18)
.onClick(() => {
this.passwordVisible = !this.passwordVisible
})
InputType.Password:输入内容显示为圆点,保护隐私Blank():占位撑开右侧空间,让"忘记密码?"文字靠右对齐- 密码切换:通过
passwordVisible布尔值切换显示图标(👁/🙈),但注意这里只是视觉上的切换图标,密码的明文/密文切换需要通过修改TextInput的type属性实现。完整的实现可以这样改进:
TextInput({ placeholder: '请输入密码', text: this.password })
.type(this.passwordVisible ? InputType.Normal : InputType.Password)
当 passwordVisible 为 true 时输入框类型为 Normal(明文显示),否则为 Password(密文显示)。
5.2.7 登录按钮的状态联动
Button('登 录')
.type(ButtonType.Capsule)
.width('100%')
.height(50)
.backgroundColor(this.username.length > 0 && this.password.length > 0
? '#4CAF50' : '#C8E6C9')
.fontColor(Color.White)
.enabled(this.username.length > 0 && this.password.length > 0)
.onClick(() => {
console.log(`登录:学号=${this.username}, 密码=${this.password}`)
})
这是整个页面最关键的用户体验细节——按钮状态联动:
- 条件判断:
this.username.length > 0 && this.password.length > 0 - 视觉联动:条件满足时按钮为亮绿色
#4CAF50,否则为浅灰色#C8E6C9 - 行为联动:
enabled()属性控制按钮是否可点击,条件不满足时禁用点击
这种设计有两大好处:
- 防止空表单提交:用户在未填写完整信息时无法提交,减少无效请求
- 即时反馈:用户每输入一个字符,按钮状态就变化一次,交互感强
进阶思考:实际项目中,还可以增加对输入格式的校验,比如学号是否为数字、密码长度是否≥6位等,进一步提升体验。
5.2.8 注册引导
Row() {
Text('还没有校园账号?')
.fontSize(14)
.fontColor('#9E9E9E')
Text('立即注册')
.fontSize(14)
.fontColor('#4CAF50')
.fontWeight(FontWeight.Medium)
.margin({ left: 4 })
.onClick(() => {
console.log('跳转到注册')
})
}
.width('100%')
.justifyContent(FlexAlign.Center)
.margin({ top: 24 })
这是一个典型的 “引导 + 行动号召” 模式:
- 灰色文字降低存在感,不会干扰主要登录流程
- 绿色"立即注册"作为号召性用语,视觉突出
FlexAlign.Center居中显示,置于按钮下方,符合阅读顺序
六、页面路由配置
6.1 首页(Index.ets)
要让登录页面能被访问到,我们需要在首页添加一个跳转按钮。
import router from '@ohos.router';
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
RelativeContainer() {
Text(this.message)
.id('HelloWorld')
.fontSize($r('app.float.page_text_font_size'))
.fontWeight(FontWeight.Bold)
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
.onClick(() => {
this.message = 'Welcome';
})
Button('登录')
.type(ButtonType.Capsule)
.height(48)
.width(120)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.backgroundColor('#007aff')
.fontColor(Color.White)
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
.margin({ top: 80 })
.onClick(() => {
router.pushUrl({ url: 'pages/Login' })
.catch((err: Error) => {
console.error(`跳转失败: ${err.message}`)
})
})
}
.height('100%')
.width('100%')
}
}
关键点:
import router from '@ohos.router':导入路由器模块router.pushUrl({ url: 'pages/Login' }):使用pushUrl方法跳转到 Login 页面catch错误处理:如果跳转失败(比如路由表中没有 Login 页面),会在控制台输出错误信息
6.2 路由表配置
路由表文件位于 entry/src/main/resources/base/profile/main_pages.json:
{
"src": [
"pages/Index",
"pages/Login"
]
}
重要提醒:每新增一个页面,必须在此文件中注册。路径规则:
- 路径相对于
ets/目录 "pages/Login"对应ets/pages/Login.ets- 文件名不带
.ets后缀
如果不注册路由表,编译阶段不会报错,但运行时 router.pushUrl 会抛出类似以下错误:
跳转失败: The page URI is not exist in the page route list.
6.3 路由跳转方式对比
| 方法 | 说明 | 适用场景 |
|---|---|---|
router.pushUrl |
压栈跳转,保留当前页面 | 页面层次导航(如首页→登录→详情) |
router.replaceUrl |
替换当前页面,不保留 | 登录成功后替换首页 |
router.back |
返回上一页 | 取消/返回操作 |
router.clear |
清空页面栈 | 退出登录回到首页 |
对于登录页面的场景,通常使用 pushUrl 跳转到登录页,登录成功后再使用 replaceUrl 跳转到主界面(这样用户按返回键不会回到登录页)。
七、样式设计的深度解析
7.1 颜色体系
校园风的颜色体系基于 绿色色轮 进行设计:
#2E7D32 (深绿 - 标题)
↑ 更暗
#C8E6C9 (禁用按钮) ← #4CAF50 (主色 - 校徽/按钮)
↓ 更亮
#81C784 (辅助文字/链接)
↓ 更亮
#F1F8E9 (输入框背景)
↓ 更亮
#E8F5E9 (页面背景)
通过调整绿色的明度和饱和度,我们可以用单一色相营造出丰富的视觉层次,既有统一感,又有清晰的信息层级。
7.2 间距系统
一个好的页面需要"呼吸感",间距至关重要。本页面采用的间距体系:
| 位置 | 间距值 | 设计意图 |
|---|---|---|
| 顶部与徽章 | 60px | 让徽章靠上但不贴顶,有仪式感 |
| 徽章与表单 | 50px | 两个区域的明显分隔 |
| 表单内部 | 28px padding | 内容不贴卡边 |
| 输入框之间 | 20px | 足够的垂直间距避免误触 |
| 标签与输入框 | 8px | 紧密的关联性 |
7.3 字体系统
| 元素 | 字号 | 字重 | 颜色 |
|---|---|---|---|
| 校名标题 | 26px | Bold | #2E7D32 |
| 校训 | 13px | Regular | #81C784 |
| 表单标题 | 20px | Medium | #1a1a1a |
| 输入框标签 | 14px | Regular | #558B2F |
| 输入内容 | 16px | Regular | 默认 |
| 占位文字 | 16px | Regular | #bdbdbd |
| 按钮文字 | 18px | Medium | #FFFFFF |
| 忘记密码 | 13px | Regular | #81C784 |
| 注册引导 | 14px | Regular | #9E9E9E |
字号的递进关系(13 → 14 → 16 → 18 → 20 → 26)形成了清晰的视觉节奏,让用户一眼就能区分内容的层级。
7.4 圆角系统
圆形校徽: 100% 圆形 (80x80)
表单卡片: 20px
输入框: 12px
登录按钮: Capsule (胶囊 = 完全圆角)
圆角的"递进"也是有规律的:卡片 20px > 输入框 12px > 校徽完全圆形。最外层最圆润,内层略小,形成视觉上的嵌套关系和统一感。
八、「忘记密码」和「密码可见切换」完善方案
在浏览器的 Demo 中,密码可见切换的图标虽然变了,但 TextInput 的类型没有联动变化。下面是完善后的版本:
// 密码输入框区域 —— 完善版
Column() {
Row() {
Text('🔒').fontSize(16).margin({ right: 6 })
Text('密码').fontSize(14).fontColor('#558B2F')
}
.margin({ bottom: 8 })
Row() {
TextInput({ placeholder: '请输入密码', text: this.password })
.type(this.passwordVisible ? InputType.Normal : InputType.Password) // ← 联动
.onChange((value: string) => {
this.password = value
})
.fontSize(16)
.placeholderColor('#bdbdbd')
.layoutWeight(1)
Text(this.passwordVisible ? '🙈' : '👁')
.fontSize(18)
.onClick(() => {
this.passwordVisible = !this.passwordVisible // ← 切换状态
})
}
.padding({ left: 16, right: 16 })
.width('100%')
.height(48)
.backgroundColor('#F1F8E9')
.borderRadius(12)
}
而"忘记密码"通常需要跳转到专门的找回密码页面。我们可以提前在路由表中注册 pages/ForgetPassword,然后在点击事件中执行跳转:
Text('忘记密码?')
.fontSize(13)
.fontColor('#81C784')
.onClick(() => {
router.pushUrl({ url: 'pages/ForgetPassword' })
.catch((err: Error) => {
console.error(`跳转忘记密码页面失败: ${err.message}`)
})
})
九、让校徽更精致:使用 SVG 或 Media 资源
前面我们使用 Emoji(🎓)作为校徽图标,但在生产环境中通常需要替换为真实的校徽图片。
9.1 准备图片资源
将校徽图片(建议使用 PNG 或 SVG 格式)放入项目的资源目录:
entry/src/main/resources/base/media/
├── app_icon.png # 应用图标
├── logo_school.png # 校徽图片(新建)
└── ...
9.2 替换校徽代码
// 使用 Image 组件替换 Stack + Emoji
Stack() {
Circle()
.width(80)
.height(80)
.fill('#4CAF50')
Image($r('app.media.logo_school')) // 引用资源文件
.width(48)
.height(48)
.objectFit(ImageFit.Contain)
}
.width(80)
.height(80)
.margin({ bottom: 16 })
$r('app.media.logo_school') 是鸿蒙的资源引用语法,编译时会自动解析文件路径并对不同 DPI 设备做适配。
9.3 资源适配
如果应用需要适配不同分辨率的设备,可以在 media 目录下提供多套资源:
media/
├── logo_school.png # 默认(160 dpi)
├── ldpi/
│ └── logo_school.png # 120 dpi
├── mdpi/
│ └── logo_school.png # 160 dpi
├── hdpi/
│ └── logo_school.png # 240 dpi
├── xhdpi/
│ └── logo_school.png # 320 dpi
└── xxhdpi/
└── logo_school.png # 480 dpi
十、扩展:添加表单校验
生产环境中,登录页面通常需要做输入校验。下面展示如何在现有代码基础上增加校验逻辑。
10.1 增加错误状态
@Entry
@Component
struct Login {
@State username: string = '';
@State password: string = '';
@State passwordVisible: boolean = false;
@State usernameError: string = ''; // 用户名错误提示
@State passwordError: string = ''; // 密码错误提示
}
10.2 校验函数
validate(): boolean {
let valid = true
// 清空之前的错误
this.usernameError = ''
this.passwordError = ''
// 校验学号:必须为数字且长度≥6
if (this.username.length === 0) {
this.usernameError = '请输入学号'
valid = false
} else if (!/^\d{6,}$/.test(this.username)) {
this.usernameError = '学号必须为6位以上数字'
valid = false
}
// 校验密码:长度≥6
if (this.password.length === 0) {
this.passwordError = '请输入密码'
valid = false
} else if (this.password.length < 6) {
this.passwordError = '密码长度不能少于6位'
valid = false
}
return valid
}
10.3 在登录按钮中使用校验
Button('登 录')
// ... 其他属性不变
.onClick(() => {
if (this.validate()) {
console.log(`登录:学号=${this.username}, 密码=${this.password}`)
// 执行实际的登录请求...
}
})
10.4 在 UI 中显示错误
TextInput({ placeholder: '请输入学号或用户名', text: this.username })
// ... 其他属性
.onChange((value: string) => {
this.username = value
this.usernameError = '' // 用户重新输入时清除错误
})
// 显示用户名错误
if (this.usernameError.length > 0) {
Text(this.usernameError)
.fontSize(12)
.fontColor('#F44336') // 红色醒目标识
.margin({ top: 4, left: 4 })
}
这样用户在提交时就能获得明确的错误反馈,而不是无响应或闪退。
十一、常见问题与排错
11.1 路由跳转失败
现象:点击按钮后控制台输出 跳转失败: The page URI is not exist...
原因:登录页面未在 main_pages.json 中注册。
解决:检查 main_pages.json 是否包含 "pages/Login"。
{
"src": [
"pages/Index",
"pages/Login"
]
}
11.2 按钮始终禁用
现象:输入用户名和密码后按钮颜色不变化
原因:状态变量未正确绑定或 onChange 回调未更新状态
检查:
- 确认
@State装饰器已添加 - 确认
TextInput的onChange回调中更新了状态变量
@State username: string = ''
// ...
TextInput({
placeholder: '请输入学号或用户名',
text: this.username // 绑定了状态变量的值
})
.onChange((value: string) => {
this.username = value // 更新状态变量
})
11.3 密码可见切换不生效
现象:点击 👁 图标后图标变了,但密码仍然显示为圆点
原因:TextInput 的 type 属性未随状态变化
解决:确保 type 属性使用状态变量的条件表达式:
TextInput({ placeholder: '请输入密码', text: this.password })
.type(this.passwordVisible ? InputType.Normal : InputType.Password)
11.4 灰色阴影看不到
原因:shadow 的 color 属性使用了带透明度的颜色(如 #1A4CAF50),在浅色背景下可能不够明显。
解决:增大颜色透明度值(如将 #1A 改为 #33),或增大 radius 值:
.shadow({
radius: 30, // 增大模糊半径
color: '#334CAF50', // 增大不透明度
offsetY: 6 // 增大偏移量
})
十二、总结
本文从零开始,完整地实现了一个鸿蒙 HarmonyOS 校园风登录页面。从项目初始化到页面设计,从状态管理到路由跳转,从交互细节到表单校验,覆盖了 ArkTS 开发登录页面的全流程。
本文知识点回顾
| 知识点 | 详细内容 |
|---|---|
| ArkTS 组件基础 | @Entry, @Component, build() |
| 状态管理 | @State 装饰器的使用 |
| 布局容器 | Column, Row, Stack, Scroll |
| 基础组件 | Text, TextInput, Button, Image, Circle |
| 样式设计 | 颜色、圆角、阴影、间距系统 |
| 路由跳转 | router.pushUrl, main_pages.json 配置 |
| 交互设计 | 按钮状态联动、密码可见切换、输入校验 |
| 资源管理 | $r() 引用图片、多 DPI 适配 |
设计哲学
好的校园风设计应该是 “润物细无声” 的——绿色调让人联想到绿树成荫的校园小径,圆角让界面像校园氛围一样柔和亲切,清晰的校徽和校训传达着学校的文化传承。技术服务于设计,设计服务于体验,这正是 UI 开发的魅力所在。
下一步可以做什么?
完成登录页面后,你可以继续扩展:
- 注册页面:与登录页面风格保持一致,添加更多表单字段
- 忘记密码页面:实现验证码发送和密码重置流程
- 记住我功能:使用 PersistStorage 进行本地存储
- 生物识别登录:调用系统指纹/面部识别 API
- 多语言适配:使用 $r() 引用字符串资源,支持中英文切换
鼓励的话
鸿蒙生态正在蓬勃发展,学习 ArkTS 开发不仅是掌握一门技术,更是参与国产操作系统生态建设的过程。希望这篇文章能帮助你在鸿蒙开发的道路上走得更远。
如果你觉得本文对你有帮助,欢迎收藏和分享,也欢迎在评论区留下你的建议和问题。让我们一起,用代码构建更美好的校园数字生活!
本文所有代码基于 HarmonyOS 4.0 + Stage 模型 + ArkTS,已在 DevEco Studio 5.0 中验证通过。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)