鸿蒙 ArkTS 实战:从零构建一个秋思风格的登录窗口
本文介绍了基于HarmonyOS 6.0(API 23)使用ArkTS开发登录页面的完整流程。主要内容包括: 开发环境搭建与项目初始化,详细解析了Stage模型下的项目结构及关键配置文件。 ArkTS核心概念讲解,包括声明式UI、@Component组件、@State响应式数据和链式调用等特性。 登录页面实现过程,涵盖UI布局、输入校验、按钮交互和页面跳转等功能开发。 主题配色设计,采用"秋季思雨
时间:2026 年 5 月
框架:HarmonyOS ArkTS (Stage Model)
SDK:HarmonyOS 6.0 (API 23)


一、引言
2025 年的今天,鸿蒙生态已经日趋成熟。作为一名前端 / 移动端开发者,掌握 ArkTS(Ark TypeScript)——鸿蒙原生开发语言——已经成为必备技能之一。
本文将带您完整体验 从项目创建到登录页面完成 的全流程。我们不仅会实现一个功能完整的登录窗口(包含输入校验、按钮交互、页面跳转),还会深度剖析 ArkTS 的声明式 UI 语法、状态管理机制、组件化设计思想,以及如何通过主题配色传递产品情感——我们将使用 「秋季思雨」 风格,让登录页在功能之外也有温度。
无论您是初次接触鸿蒙开发,还是有一定经验想深入了解 ArkTS 的细节,这篇文章都值得一读。全文约 10000 字,代码完整可运行。
二、项目初始化与环境准备
2.1 开发环境
开始之前,请确保您已安装以下工具:
| 工具 | 版本 | 说明 |
|---|---|---|
| DevEco Studio | 6.0+ | 鸿蒙官方 IDE,基于 IntelliJ |
| HarmonyOS SDK | API 23 (6.1.1.24) | 目标 SDK 版本 |
| Node.js | 18+ | 用于 OHPM 包管理 |
| ohpm | 最新 | 鸿蒙包管理器 |
本文项目使用 Stage Model(阶段模型),这是鸿蒙推荐的应用开发模型,API Type 为
stageMode。
2.2 创建项目
在 DevEco Studio 中点击 File → New → Create Project,选择:
- 模板:Empty Ability
- Project Type:Application
- Compile SDK:6.1.1(24)
- Model:Stage
- Language:ArkTS
项目创建完成后,目录结构如下(已移除与本主题无关的文件):
Demo0528/
├── AppScope/
│ ├── app.json5 # 应用级配置(bundleName、版本等)
│ └── resources/
│ ├── base/element/string.json
│ └── base/media/layered_image.json
├── entry/
│ └── src/main/
│ ├── ets/
│ │ ├── entryability/EntryAbility.ets # Ability 入口
│ │ ├── entrybackupability/EntryBackupAbility.ets
│ │ └── pages/
│ │ ├── Index.ets # 首页(入口页面)
│ │ └── LoginPage.ets # 登录页面(本文核心)
│ ├── module.json5 # 模块配置
│ └── resources/
│ ├── base/element/
│ │ ├── color.json
│ │ ├── float.json
│ │ └── string.json
│ ├── base/profile/
│ │ └── main_pages.json # 页面路由配置
│ └── dark/element/color.json
├── build-profile.json5 # 应用级构建配置
├── hvigor/
└── oh-package.json5
2.3 配置文件解读
build-profile.json5 — 应用构建配置
{
"app": {
"products": [
{
"name": "default",
"signingConfig": "default",
"targetSdkVersion": "6.1.1(24)",
"compatibleSdkVersion": "6.1.1(24)",
"runtimeOS": "HarmonyOS",
"buildOption": {
"strictMode": {
"caseSensitiveCheck": true,
"useNormalizedOHMUrl": true
}
}
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [ { "name": "default", "applyToProducts": ["default"] } ]
}
]
}
关键点说明:
compatibleSdkVersion定义了最低兼容版本,targetSdkVersion是目标版本。strictMode.caseSensitiveCheck开启大小写敏感检查,有助于团队规范。- 这里没有配置签名,调试运行使用 DevEco Studio 的自动签名即可。
AppScope/app.json5 — 应用元信息
{
"app": {
"bundleName": "com.shili.Demo0528",
"vendor": "example",
"versionCode": 1000000,
"versionName": "1.0.0",
"icon": "$media:layered_image",
"label": "$string:app_name"
}
}
bundleName是应用的唯一标识,类似 Android 的 package name,需要保证全局唯一。$media:layered_image引用资源文件夹中的图片资源。
module.json5 — 模块配置
{
"module": {
"name": "entry",
"type": "entry",
"mainElement": "EntryAbility",
"deviceTypes": ["phone"],
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": ["entity.system.home"],
"actions": ["ohos.want.action.home"]
}
]
}
]
}
}
这里定义了应用有一个 EntryAbility,它作为启动入口(home 页面)。页面路由由 $profile:main_pages 引用外部 JSON 文件管理。
main_pages.json — 页面注册
{
"src": [
"pages/Index",
"pages/LoginPage"
]
}
这是鸿蒙 Stage Model 的页面路由注册表。每一个新页面都必须在 src 数组中声明,才能被 router.pushUrl() 跳转。路径相对于 ets/ 目录,不包含 .ets 后缀。
注意:如果不添加
"pages/LoginPage",运行时跳转 LoginPage 会报错page not found。这是初学者最容易踩的坑之一。
三、ArkTS 核心概念速览
在写代码之前,了解 ArkTS 的核心理念会帮助您更快地上手。
3.1 声明式 UI
ArkTS 采用 声明式 + 响应式 的 UI 范式。您只需描述 UI 应该是什么样子,框架自动处理 UI 更新。这与 React / SwiftUI / Jetpack Compose 是同一类思想。
// 命令式(传统方式)
text.setText('Hello');
text.setColor('red');
// 声明式(ArkTS 方式)
Text('Hello')
.fontColor('red')
3.2 @Component 与 build()
每一个页面或组件都是一个 @Component 装饰的结构体:
@Component
struct MyComponent {
build() {
// 在这里声明 UI 树
}
}
build()是组件的渲染入口,必须返回一个容器或组件。- 根容器通常是
Column(纵向)或Row(横向)。
3.3 @State 与响应式
@State username: string = '';
@State装饰的变量是响应式数据源。- 当
@State变量变化时,框架自动重新渲染依赖它的 UI 部分。 - 不需要手动调用
setState()或update()。
3.4 链式调用
ArkTS 组件使用链式方法来配置属性,每个方法返回组件自身,可以连续调用:
Text('欢迎登录')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#3D2B1F')
这种风格写起来简洁直观,但注意方法调用的顺序——某些属性可能相互影响(例如先设置颜色再设置字体,颜色不会丢失,因为都是独立设置的)。
3.5 条件渲染
if (this.loginMessage.length > 0) {
Text(this.loginMessage)
.fontColor('#C94A4A')
}
ArkTS 支持 if/else 条件渲染。条件表达式中的变量必须是 @State 或 @Prop 等响应式数据,否则条件判断一次后不再更新。
四、构建首页 Index.ets
首页的作用是作为应用入口,提供一个跳转到登录页的按钮。完整的代码如下:
// entry/src/main/ets/pages/Index.ets
import { router } from '@kit.ArkUI';
@Entry
@Component
struct Index {
build() {
Column() {
Text('Demo0528')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#3D2B1F')
.margin({ bottom: 20 })
Button('进入登录页')
.width(200)
.height(48)
.backgroundColor('#C1793B')
.border({ width: 1, color: '#C1793B' })
.borderRadius(24)
.fontSize(16)
.fontColor('#FFFFFF')
.onClick(() => {
router.pushUrl({
url: 'pages/LoginPage'
});
})
}
.backgroundColor('#F3EFE9')
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
4.1 代码逐行解析
| 代码 | 作用 |
|---|---|
import { router } from '@kit.ArkUI' |
导入页面路由 API |
@Entry |
标记该组件为页面入口(配合 pages 配置使用) |
@Component |
标记为 ArkTS 组件 |
struct Index |
定义组件结构体,名称与文件名无关但与规范推荐一致 |
Column { ... } |
纵向弹性布局容器,子元素从上到下排列 |
.justifyContent(FlexAlign.Center) |
主轴(纵向)居中对齐 |
.alignItems(HorizontalAlign.Center) |
交叉轴(横向)居中对齐 |
router.pushUrl({ url: 'pages/LoginPage' }) |
将 LoginPage 压入页面栈(跳转) |
4.2 @Entry 装饰器
@Entry 是页面的标记者。在 ArkTS 中,只有被 @Entry 装饰的组件才能被路由系统识别为独立页面。一个 .ets 文件中可以有多个 @Component,但最多只能有一个 @Entry。
🔄 回顾:
main_pages.json中注册了"pages/Index",框架在启动时会自动加载Index.ets中@Entry装饰的组件作为启动页面。
五、登录页核心构建 LoginPage.ets
这是本文的核心——一个功能完整的登录窗口。它包含了:
- 品牌 Logo 与标题区域
- 用户名 / 密码输入框
- 登录按钮带阴影效果
- 输入校验逻辑
- 错误提示
- 注册链接入口
5.1 完整代码
我们采用 「秋季思雨」 主题配色,下文会专门讲解配色设计思路。先看完整代码:
// entry/src/main/ets/pages/LoginPage.ets
@Entry
@Component
struct LoginPage {
@State username: string = '';
@State password: string = '';
@State isPasswordVisible: boolean = false;
@State loginMessage: string = '';
build() {
Column() {
// ========== 标题区域 ==========
Column() {
Image($r('app.media.startIcon'))
.width(80)
.height(80)
.borderRadius(40)
.margin({ bottom: 16 })
Text('欢迎登录')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#3D2B1F')
Text('请输入账号和密码')
.fontSize(14)
.fontColor('#8A9BA8')
.margin({ top: 8 })
}
.alignItems(HorizontalAlign.Center)
.width('100%')
.padding({ top: 60, bottom: 40 })
// ========== 输入区域 ==========
Column() {
// ---- 用户名输入框 ----
Row() {
TextInput({ placeholder: '请输入用户名', text: this.username })
.layoutWeight(1)
.fontSize(16)
.placeholderColor('#B0A595')
.placeholderFont({ size: 16 })
.onChange((value: string) => {
this.username = value;
})
}
.padding({ left: 16, right: 16 })
.height(52)
.backgroundColor('#FDFBF9')
.border({ width: 1, color: '#D4C5B0' })
.borderRadius(12)
.margin({ bottom: 16 })
// ---- 密码输入框 ----
Row() {
TextInput({ placeholder: '请输入密码', text: this.password })
.layoutWeight(1)
.fontSize(16)
.placeholderColor('#B0A595')
.placeholderFont({ size: 16 })
.type(InputType.Password)
.showPasswordIcon(true)
.onChange((value: string) => {
this.password = value;
})
}
.padding({ left: 16, right: 16 })
.height(52)
.backgroundColor('#FDFBF9')
.border({ width: 1, color: '#D4C5B0' })
.borderRadius(12)
}
.width('100%')
.padding({ left: 32, right: 32 })
// ========== 登录按钮 ==========
Button('登 录')
.width('80%')
.height(50)
.backgroundColor('#C1793B')
.border({ width: 1, color: '#C1793B' })
.borderRadius(25)
.fontSize(18)
.fontWeight(FontWeight.Medium)
.fontColor('#FFFFFF')
.margin({ top: 40, bottom: 16 })
.shadow({
radius: 8,
offsetX: 0,
offsetY: 4,
color: 'rgba(193, 121, 59, 0.3)'
})
.onClick(() => {
this.handleLogin();
})
// ========== 提示消息(条件渲染) ==========
if (this.loginMessage.length > 0) {
Text(this.loginMessage)
.fontSize(14)
.fontColor('#C94A4A')
.margin({ top: 8 })
}
// ========== 底部注册提示 ==========
Text('还没有账号?立即注册')
.fontSize(14)
.fontColor('#C1793B')
.margin({ top: 40 })
.onClick(() => {
// TODO: 跳转到注册页面
})
}
.width('100%')
.height('100%')
.backgroundColor('#F3EFE9')
.alignItems(HorizontalAlign.Center)
}
// ========== 登录处理逻辑 ==========
handleLogin() {
// 输入校验
if (this.username.trim() === '') {
this.loginMessage = '请输入用户名';
return;
}
if (this.password.trim() === '') {
this.loginMessage = '请输入密码';
return;
}
// 模拟登录验证(后续可替换为真实接口)
if (this.username === 'admin' && this.password === '123456') {
this.loginMessage = '';
console.info('登录成功');
// TODO: 跳转到主页面
} else {
this.loginMessage = '用户名或密码错误';
}
}
}
5.2 UI 组件层级分析
Column (page root)
├── Column (header area)
│ ├── Image ← 品牌 Logo
│ ├── Text ← "欢迎登录" 标题
│ └── Text ← "请输入账号和密码" 副标题
├── Column (input area)
│ ├── Row (username)
│ │ └── TextInput ← 用户名输入框
│ └── Row (password)
│ └── TextInput ← 密码输入框(.type(InputType.Password))
├── Button ← "登 录" 按钮
├── Text (conditional) ← 错误提示消息
└── Text ← "还没有账号?立即注册"
ArkTS 的 UI 就是一个组件树。根节点是 Column(全屏容器),里面按语义划分区域(标题 → 输入 → 按钮 → 提示),每个区域又是一个子 Column 或 Row。
5.3 状态管理详解
我们定义了四个 @State 变量:
@State username: string = ''; // 用户名
@State password: string = ''; // 密码
@State isPasswordVisible: boolean = false; // 密码可见性(预留)
@State loginMessage: string = ''; // 登录提示消息
状态驱动的数据流:
用户输入 → TextInput.onChange → this.username = value
↓
@State username 更新
↓
UI 自动重新渲染
↓
handleLogin() 读取 username
↓
校验 → 通过 → console.info('登录成功')
→ 失败 → this.loginMessage = '错误信息'
↓
条件渲染显示错误 → if (this.loginMessage.length > 0)
💡 关键理解:您不需要手动操作 DOM。改变 @State 变量,框架自动找到依赖这些变量的 UI 部分并增量更新。
5.4 输入组件详解
TextInput 的构造方式
TextInput({ placeholder: '请输入用户名', text: this.username })
placeholder:占位文本,在输入框为空时显示。text:绑定到@State变量,实现受控输入。
受控 vs 非受控:
ArkTS 的 TextInput 有两种模式:
- 受控模式(推荐):传入
text参数,通过onChange回调更新状态。 - 非受控模式:不传
text,只通过onChange读取输入。
受控模式的优点是状态单一来源——this.username 是唯一真相源,便于后续校验和提交。
密码输入框的特殊配置
.type(InputType.Password)
.showPasswordIcon(true)
InputType.Password:将输入内容显示为圆点,保护隐私。.showPasswordIcon(true):在输入框末尾显示一个眼睛图标,用户可以点击切换明文/密文显示。
输入框的视觉设计
Row() {
TextInput({ placeholder: '请输入用户名', text: this.username })
.layoutWeight(1) // 占据所有剩余宽度
.fontSize(16)
.placeholderColor('#B0A595')
.placeholderFont({ size: 16 })
}
.padding({ left: 16, right: 16 })
.height(52)
.backgroundColor('#FDFBF9')
.border({ width: 1, color: '#D4C5B0' })
.borderRadius(12)
设计要点:
- 输入框被包裹在
Row中,Row负责背景色、边框、圆角和内边距。 TextInput本身没有背景色和边框,完全由外层Row控制。这种分层设计使样式更容易统一和复用。.height(52)设置固定行高,保证双端输入框高度一致。.borderRadius(12)提供柔和的圆角,增强亲和力。
5.5 按钮设计
Button('登 录')
.width('80%')
.height(50)
.backgroundColor('#C1793B')
.border({ width: 1, color: '#C1793B' })
.borderRadius(25)
.fontSize(18)
.fontWeight(FontWeight.Medium)
.fontColor('#FFFFFF')
.margin({ top: 40, bottom: 16 })
.shadow({
radius: 8,
offsetX: 0,
offsetY: 4,
color: 'rgba(193, 121, 59, 0.3)'
})
宽度设计:80% 不是全宽,留出左右 10% 的间隙,视觉上更舒适。
圆角按钮:borderRadius(25)(高度 50 的一半),形成经典的胶囊按钮。
阴影效果:
.shadow({
radius: 8, // 阴影模糊半径
offsetX: 0, // 水平偏移
offsetY: 4, // 垂直偏移(向下)
color: 'rgba(193, 121, 59, 0.3)' // 颜色与按钮同色系,降低透明度
})
阴影让按钮有微微浮起的层次感,与秋叶橙的主色呼应。
5.6 登录逻辑 handleLogin()
handleLogin() {
// 输入校验
if (this.username.trim() === '') {
this.loginMessage = '请输入用户名';
return;
}
if (this.password.trim() === '') {
this.loginMessage = '请输入密码';
return;
}
// 模拟登录验证
if (this.username === 'admin' && this.password === '123456') {
this.loginMessage = '';
console.info('登录成功');
// TODO: 跳转到主页面
} else {
this.loginMessage = '用户名或密码错误';
}
}
校验流程:
- 非空校验:先检查用户名,再检查密码。
trim()去除首尾空格。 - 凭证校验:目前是硬编码的
admin / 123456,生产环境应替换为网络请求。 - 成功处理:清空错误消息 → 打印日志 → 预留跳转。
- 失败处理:设置
loginMessage,触发条件渲染显示红色错误提示。
⚠️ 安全提示:在实际项目中,密码校验应在服务端完成,前端仅做格式校验和防抖处理。
5.7 条件渲染错误提示
if (this.loginMessage.length > 0) {
Text(this.loginMessage)
.fontSize(14)
.fontColor('#C94A4A')
.margin({ top: 8 })
}
- 当
loginMessage为空字符串('')时,length > 0为false,Text 不渲染。 - 当登录失败或输入校验不通过时,
loginMessage被赋值为错误文本,Text 自动显示。 - 错误颜色
#C94A4A是一种枫叶红色,与秋季主题呼应,而非刺眼的纯红。
5.8 路由导航
首页通过以下代码跳转到登录页:
import { router } from '@kit.ArkUI';
// 在 onClick 中调用
router.pushUrl({
url: 'pages/LoginPage'
});
router.pushUrl将目标页面推入页面栈,等同于 Android 的startActivity或 iOS 的pushViewController。- 页面路径相对于
ets/目录,不带.ets后缀。 - 返回上一页使用
router.back()。
六、「秋季思雨」主题配色设计
6.1 设计理念
「秋季思雨」是一种融合季节情感的配色方案。它的灵感来源于:
深秋时节,窗外细雨绵绵。银杏叶在雨中飘落,远处的山峦笼罩在烟灰色的薄雾中。屋内一盏暖灯,空气中弥漫着桂花和泥土的湿润气息。
这种场景传递的情感是:温暖、沉静、怀旧、诗意。我们希望登录页不再是冷冰冰的表单,而是一个有温度、有故事的入口。
6.2 调色板
| 色名 | 色值 | 色相 | 象征 |
|---|---|---|---|
| 暖灰米白 | #F3EFE9 |
暖灰 | 秋日阴天的柔和光线,页面背景 |
| 深褐色 | #3D2B1F |
棕 | 老树皮、枯枝,标题文字 |
| 烟雨灰蓝 | #8A9BA8 |
灰蓝 | 雨雾、远山,副标题 |
| 秋叶橙 | #C1793B |
橙 | 枫叶、银杏,主按钮 + 链接 |
| 柔米白 | #FDFBF9 |
米白 | 输入框背景,干净柔和 |
| 淡茶色 | #D4C5B0 |
茶棕 | 输入框边框,低调不抢眼 |
| 浅褐灰 | #B0A595 |
灰褐 | 占位文字,仿佛褪色的记忆 |
| 枫叶红 | #C94A4A |
暗红 | 错误提示,秋叶转红的警示 |
6.3 色彩应用规则
页面背景 ← #F3EFE9(暖灰米白——秋日底色)
标题 ← #3D2B1F(深褐——视觉重心)
副标题 ← #8A9BA8(烟雨灰蓝——不喧宾夺主)
输入框背景 ← #FDFBF9(柔白——高于背景一层)
输入框边框 ← #D4C5B0(淡茶——若有若无)
占位文字 ← #B0A595(浅褐——低调提示)
主按钮 ← #C1793B(秋叶橙——温暖号召)
按钮阴影 ← rgba(193, 121, 59, 0.3)
错误提示 ← #C94A4A(枫叶红——温柔警告)
注册链接 ← #C1793B(呼应主色)
6.4 与常见配色方案对比
| 风格 | 背景 | 主色 | 情感 |
|---|---|---|---|
| 小清新 | 薄荷绿 #E8F5E9 | 翡翠绿 #4CAF50 | 清爽、活力 |
| 科技风 | 深空蓝 #0A0E27 | 电光蓝 #00D4FF | 现代、冰冷 |
| 秋季思雨 | 暖灰米白 #F3EFE9 | 秋叶橙 #C1793B | 温暖、沉静、诗意 |
「秋季思雨」的优势在于:它在温暖与克制之间取得了平衡——不会像糖果色那样甜腻,也不会像深色模式那样冷峻。它给人"坐下来,慢慢输入"的心理暗示,降低用户在登录时的焦虑感。
七、ArkTS 进阶话题
7.1 @Component 的生命周期
每个 ArkTS 组件都有生命周期回调:
@Component
struct LoginPage {
aboutToAppear() {
console.info('页面即将显示');
// 可以在这里初始化数据
}
onPageShow() {
console.info('页面显示完成');
}
onPageHide() {
console.info('页面被覆盖');
}
aboutToDisappear() {
console.info('页面即将销毁');
// 释放资源、取消订阅
}
build() {
// ...
}
}
在登录页场景中,aboutToAppear 可用于检查用户是否已登录(自动跳过登录页)。
7.2 @State 与 @Prop / @Link
除了 @State,ArkTS 还提供了更多装饰器用于组件通信:
| 装饰器 | 作用 | 适用场景 |
|---|---|---|
@State |
组件内部状态 | 当前页面的数据 |
@Prop |
从父组件传入的不可变数据 | 子组件接收配置 |
@Link |
从父组件传入的双向绑定 | 子组件可修改父组件数据 |
@Provide / @Consume |
跨层级传递 | 祖孙组件通信 |
示例:
// 父组件
@Component
struct Parent {
@State count: number = 0;
build() {
Child({ count: this.count })
}
}
// 子组件
@Component
struct Child {
@Prop count: number;
build() {
Text(`${this.count}`)
}
}
7.3 自定义组件复用
我们可以将登录输入框抽取为可复用的组件:
@Component
struct LoginInput {
@Prop placeholder: string;
@Prop inputType: InputType = InputType.Normal;
@Link text: string;
build() {
Row() {
TextInput({ placeholder: this.placeholder, text: this.text })
.layoutWeight(1)
.fontSize(16)
.placeholderColor('#B0A595')
.placeholderFont({ size: 16 })
.type(this.inputType)
.onChange((value: string) => {
this.text = value;
})
}
.padding({ left: 16, right: 16 })
.height(52)
.backgroundColor('#FDFBF9')
.border({ width: 1, color: '#D4C5B0' })
.borderRadius(12)
}
}
然后在 LoginPage 中使用:
LoginInput({ placeholder: '请输入用户名', text: this.username })
LoginInput({ placeholder: '请输入密码', text: this.password, inputType: InputType.Password })
这种抽象带来的好处:
- 样式统一:一处修改,处处生效。
- 减少重复代码:密码和用户名输入框除了类型外完全相同。
- 更易维护:样式变更只需改一个组件。
但注意:对于只有两个实例的场景,是否抽取组件取决于团队的审美偏好。本文为了清晰展示结构,没有抽取,但在生产项目中强烈推荐。
7.4 主题系统
在大型应用中,应该建立统一的主题系统:
// theme.ets
export const Colors = {
background: '#F3EFE9',
primary: '#C1793B',
textPrimary: '#3D2B1F',
textSecondary: '#8A9BA8',
inputBg: '#FDFBF9',
inputBorder: '#D4C5B0',
placeholder: '#B0A595',
error: '#C94A4A',
} as const;
export const Spacing = {
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 40,
} as const;
使用时:
import { Colors, Spacing } from '../common/theme';
Text('欢迎登录')
.fontColor(Colors.textPrimary)
.margin({ bottom: Spacing.md })
这种方法在应用有 5 个以上页面时尤其有用,可以避免"改一个颜色要改十几个文件"的困境。
7.5 页面转场动画
ArkTS 支持自定义页面跳转动画:
router.pushUrl({
url: 'pages/LoginPage'
}, router.RouterMode.Standard, {
duration: 300,
curve: Curve.EaseInOut,
animation: {
translate: { x: '100%', y: 0 }
}
});
这是一个从右侧滑入的动画效果。类似 iOS 的 push 转场,可以让应用显得更流畅自然。
八、常见问题与排查
8.1 页面跳转失败
症状:点击「进入登录页」按钮没有任何反应,或报错 page not found。
排查步骤:
- ✅ 检查
main_pages.json是否注册了目标页面:
{
"src": [
"pages/Index",
"pages/LoginPage"
]
}
- ✅ 检查
router.pushUrl的url路径是否正确——不带.ets后缀,相对于ets/目录:
// 正确
router.pushUrl({ url: 'pages/LoginPage' });
// 错误(多了后缀)
router.pushUrl({ url: 'pages/LoginPage.ets' });
// 错误(路径不对)
router.pushUrl({ url: 'LoginPage' });
- ✅ 检查文件是否存在且被
@Entry装饰。
8.2 @State 更新但 UI 不刷新
症状:loginMessage 变了,但错误文字没有显示。
排查:
- 确保变量确实被
@State装饰,如果少写了@State,ArkTS 不会追踪变化。 - 确保
if条件中的变量是响应式的:
// ✅ 正确
if (this.loginMessage.length > 0) { ... }
// 错误——将响应式变量赋值给局部变量
const msg = this.loginMessage; // 失去响应性
if (msg.length > 0) { ... }
- 不要在
build()或渲染循环中修改@State变量——会导致无限循环。
8.3 输入框样式不生效
症状:设置了 .backgroundColor 但输入框背景没变。
原因:TextInput 的默认样式可能覆盖了部分设置。解决方案是将样式设置在外层容器(Row)上,TextInput 本身保持透明。
// ✅ 推荐——样式放在 Row 上
Row() {
TextInput({ ... })
.layoutWeight(1)
}
.backgroundColor('#FDFBF9')
.border({ width: 1, color: '#D4C5B0' })
.borderRadius(12)
8.4 页面背景色不生效
症状:设置了 .backgroundColor 但背景还是白色。
原因:容器的背景色只在有内容的区域生效。如果容器本身没有撑满父容器,背景色只会覆盖子元素区域。
解决方案:确保容器设置了宽高为 100%:
Column() {
// ... 所有内容
}
.width('100%')
.height('100%')
.backgroundColor('#F3EFE9')
九、完整项目结构总结
Demo0528/
├── AppScope/
│ ├── app.json5 # bundleName: com.shili.Demo0528
│ └── resources/base/media/ # 启动图标、logo
├── entry/
│ ├── build-profile.json5 # apiType: stageMode
│ ├── hvigorfile.ts # Hvigor 构建脚本
│ └── src/main/
│ ├── ets/
│ │ ├── entryability/
│ │ │ └── EntryAbility.ets # Ability 生命周期
│ │ └── pages/
│ │ ├── Index.ets # 首页(入口)
│ │ └── LoginPage.ets # 登录页(核心)
│ ├── module.json5 # 模块配置 + 路由引用
│ └── resources/
│ └── base/
│ ├── element/ # 颜色、字符串等资源
│ └── profile/
│ └── main_pages.json # 页面路由表
├── build-profile.json5 # 应用级构建配置
├── hvigor/ # 构建缓存
└── oh-package.json5 # OHPM 依赖
产出文件清单
| 文件 | 行数 | 核心内容 |
|---|---|---|
Index.ets |
34 | 首页布局+路由跳转 |
LoginPage.ets |
146 | 完整的登录表单+校验逻辑 |
main_pages.json |
6 | 页面注册 |
module.json5 |
50 | 模块信息+Ability配置 |
十、下一步拓展方向
本文实现的登录窗口虽然功能完整,但仍有许多可以延展的方向:
10.1 接入真实后端 API
import { http } from '@kit.Networking';
async handleLogin() {
const httpRequest = http.createHttp();
const response = await httpRequest.request('https://api.example.com/login', {
method: http.RequestMethod.POST,
header: { 'Content-Type': 'application/json' },
extraData: {
username: this.username,
password: this.password
}
});
if (response.responseCode === 200) {
// 登录成功,保存 token
AppStorage.setOrCreate('token', response.result.token);
router.pushUrl({ url: 'pages/HomePage' });
} else {
this.loginMessage = '登录失败,请检查网络或账号密码';
}
httpRequest.destroy();
}
10.2 注册页面
实现「还没有账号?立即注册」的跳转,创建 RegisterPage.ets,在 main_pages.json 中注册。
10.3 记住密码与生物识别
import { preferences } from '@kit.ArkData';
import { userAuth } from '@kit.UserAuthenticationKit';
// 存储账号
const pref = await preferences.getPreferences(this.context, 'loginPrefs');
await pref.put('savedUsername', this.username);
await pref.flush();
// 指纹/面容识别
const authResult = await userAuth.auth();
if (authResult.isSuccess) {
// 自动填充密码并登录
}
10.4 表单验证增强
private validateUsername(): boolean {
if (this.username.trim().length < 3) {
this.loginMessage = '用户名至少3个字符';
return false;
}
if (!/^[a-zA-Z0-9_]+$/.test(this.username)) {
this.loginMessage = '用户名只能包含字母、数字和下划线';
return false;
}
return true;
}
private validatePassword(): boolean {
if (this.password.length < 6) {
this.loginMessage = '密码至少6位';
return false;
}
return true;
}
10.5 多主题切换
@StorageLink('themeMode') themeMode: string = 'autumn';
// 根据主题动态选择颜色
get primaryColor(): string {
const themes = {
autumn: '#C1793B',
tech: '#00D4FF',
fresh: '#4CAF50'
};
return themes[this.themeMode] || themes.autumn;
}
十一、结语
从零开始构建一个登录窗口,我们走过了这样的旅程:
- 理解项目结构:Stage Model 的模块划分、配置文件的作用。
- 掌握 ArkTS 声明式 UI:
@Component、@State、build()的协作。 - 构建完整页面:标题区 → 输入区 → 按钮 → 提示,逐层搭建。
- 实现交互逻辑:onChange 绑定 → 状态驱动 → handleLogin 校验。
- 设计主题配色:「秋季思雨」——用色彩传递产品情感。
- 探索进阶话题:组件通信、生命周期、主题系统、项目优化。
ArkTS 作为鸿蒙原生的声明式框架,其设计理念借鉴了现代前端和移动端框架的最佳实践,同时融合了鸿蒙生态的独特能力。如果您有 React / Vue / SwiftUI / Jetpack Compose 经验,上手会非常顺畅;即使没有,ArkTS 的简洁语法和完整的文档体系也能让您快速进入状态。
最后留一个思考题给您:
如果要将这个登录页改为「多语言支持」(中文/英文/日文),你会如何设计 ArkTS 的国际化方案?欢迎尝试实现并分享你的方案。
更多推荐

所有评论(0)