官网地址:https://atomcode.atomgit.com/

打开PowerShell

点击开始按钮,输入powershell即可看到,我们使用管理员身份打开。

安装atomcode

点击下载最新版本windows的安装包。

https://file-cdn.gitcode.com/9709354/releases/untagger_26cc21bc121b482294e72cd770bfaf03/atomcode-v4.23.2-windows-x64.exe?auth_key=1779941573-f9453abc5a74408cb4f1d2bae4585212-0-dc868bf9c566614f08c1b91703237649ebf4557e6b808d203228de991ec2b462

下载完毕后双击打开即可。

登录授权

登录成功

最终效果

方便使用方法

修改名称:

DevEco Studio使用atomcode

重新打开:DevEco Studio

成功进入:

atomcode示例

通过arkTS语言创建一个登录窗口

修改主题为科技风。

换成秋季思雨风格。

命令

/codingplan 推荐 — 一条命令登录 + 申领 AtomGit CodingPlan 免费额度 + 自动根据额度模型列表写好 provider 配置。首次启动最省事的入口
/login 仅完成 AtomGit OAuth 身份认证,不动 provider 配置(给已有 API Key 但想用 /issue 等 AtomGit 集成的用户)
/resume 打开会话选择器,恢复任意一个之前的持久化会话(含消息、目录和模型状态)
/session 创建一个全新的、干净的会话
/bg 将当前会话放到后台并打开新的前台会话;子命令: /bg list/bg <N>/bg drop <N>/bg help。详见 后台会话
/background <task> 兼容入口:在一个 /bg 槽位中启动一次性后台任务(详解)
/rename <新名字> 重命名当前会话(影响 /resume 选择器里的显示名)
/provider 打开 provider 管理界面,增删改切换
/model 在当前 provider 下切换模型,或跨 provider 切换
/cd 切换工作目录,并写回 default_workdir。也可以直接输入 cd /path(无需斜杠前缀)

博客撰写

写一篇对应的博客文章,要求markdown格式的,8000~12000字,要细致,丰富,代码明确。写到md文件中。  

复制博客内容去发布文章:

# 鸿蒙 ArkTS 实战:从零构建一个秋思风格的登录窗口

> 作者:AtomCode  
> 时间:2026 年 5 月  
> 框架:HarmonyOS ArkTS (Stage Model)  
> SDK:HarmonyOS 6.0 (API 23)

---

## 一、引言

2025 年的今天,鸿蒙生态已经日趋成熟。作为一名前端 / 移动端开发者,掌握 ArkTS(Ark TypeScript)——鸿蒙原生开发语言——已经成为必备技能之一。

本文将带您完整体验 **从项目创建到登录页面完成** 的全流程。我们不仅会实现一个功能完整的登录窗口(包含输入校验、按钮交互、页面跳转),还会深度剖析 ArkTS 的声明式 UI 语法、状态管理机制、组件化设计思想,以及如何通过主题配色传递产品情感——我们将使用 **「秋季思雨」** 风格,让登录页在功能之外也有温度。

无论您是初次接触鸿蒙开发,还是有一定经验想深入了解 ArkTS 的细节,这篇文章都值得一读。全文约 10000 字,代码完整可运行。

---

## 二、项目初始化与环境准备

### 2.1 开发环境

开始之前,请确保您已安装以下工具:

| 工具 | 版本 | 说明 |
|------|------|------|
| DevEco Studio | 5.0+ | 鸿蒙官方 IDE,基于 IntelliJ |
| HarmonyOS SDK | API 12 (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` — 应用构建配置

```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` — 应用元信息

```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` — 模块配置

```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` — 页面注册

```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 是同一类思想。

```typescript
// 命令式(传统方式)
text.setText('Hello');
text.setColor('red');

// 声明式(ArkTS 方式)
Text('Hello')
  .fontColor('red')
```

### 3.2 @Component 与 build()

每一个页面或组件都是一个 `@Component` 装饰的结构体:

```typescript
@Component
struct MyComponent {
  build() {
    // 在这里声明 UI 树
  }
}
```

- `build()` 是组件的渲染入口,必须返回一个容器或组件。
- 根容器通常是 `Column`(纵向)或 `Row`(横向)。

### 3.3 @State 与响应式

```typescript
@State username: string = '';
```

- `@State` 装饰的变量是**响应式数据源**。
- 当 `@State` 变量变化时,框架自动重新渲染依赖它的 UI 部分。
- 不需要手动调用 `setState()` 或 `update()`。

### 3.4 链式调用

ArkTS 组件使用**链式方法**来配置属性,每个方法返回组件自身,可以连续调用:

```typescript
Text('欢迎登录')
  .fontSize(28)
  .fontWeight(FontWeight.Bold)
  .fontColor('#3D2B1F')
```

这种风格写起来简洁直观,但注意**方法调用的顺序**——某些属性可能相互影响(例如先设置颜色再设置字体,颜色不会丢失,因为都是独立设置的)。

### 3.5 条件渲染

```typescript
if (this.loginMessage.length > 0) {
  Text(this.loginMessage)
    .fontColor('#C94A4A')
}
```

ArkTS 支持 `if/else` 条件渲染。条件表达式中的变量必须是 `@State` 或 `@Prop` 等响应式数据,否则条件判断一次后不再更新。

---

## 四、构建首页 Index.ets

首页的作用是作为应用入口,提供一个跳转到登录页的按钮。完整的代码如下:

```typescript
// 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 完整代码

我们采用 **「秋季思雨」** 主题配色,下文会专门讲解配色设计思路。先看完整代码:

```typescript
// 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` 变量:

```typescript
@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 的构造方式

```typescript
TextInput({ placeholder: '请输入用户名', text: this.username })
```

- `placeholder`:占位文本,在输入框为空时显示。
- `text`:绑定到 `@State` 变量,实现**受控输入**。

**受控 vs 非受控**:

ArkTS 的 `TextInput` 有两种模式:
1. **受控模式**(推荐):传入 `text` 参数,通过 `onChange` 回调更新状态。
2. **非受控模式**:不传 `text`,只通过 `onChange` 读取输入。

受控模式的优点是**状态单一来源**——`this.username` 是唯一真相源,便于后续校验和提交。

#### 密码输入框的特殊配置

```typescript
.type(InputType.Password)
.showPasswordIcon(true)
```

- `InputType.Password`:将输入内容显示为圆点,保护隐私。
- `.showPasswordIcon(true)`:在输入框末尾显示一个眼睛图标,用户可以点击切换明文/密文显示。

#### 输入框的视觉设计

```typescript
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 按钮设计

```typescript
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 的一半),形成经典的胶囊按钮。

**阴影效果**:

```typescript
.shadow({
  radius: 8,       // 阴影模糊半径
  offsetX: 0,      // 水平偏移
  offsetY: 4,      // 垂直偏移(向下)
  color: 'rgba(193, 121, 59, 0.3)'  // 颜色与按钮同色系,降低透明度
})
```

阴影让按钮有微微浮起的层次感,与秋叶橙的主色呼应。

### 5.6 登录逻辑 handleLogin()

```typescript
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 = '用户名或密码错误';
  }
}
```

**校验流程**:

1. **非空校验**:先检查用户名,再检查密码。`trim()` 去除首尾空格。
2. **凭证校验**:目前是硬编码的 `admin / 123456`,生产环境应替换为网络请求。
3. **成功处理**:清空错误消息 → 打印日志 → 预留跳转。
4. **失败处理**:设置 `loginMessage`,触发条件渲染显示红色错误提示。

> ⚠️ **安全提示**:在实际项目中,密码校验应在服务端完成,前端仅做格式校验和防抖处理。

### 5.7 条件渲染错误提示

```typescript
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 路由导航

首页通过以下代码跳转到登录页:

```typescript
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 组件都有生命周期回调:

```typescript
@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` | 跨层级传递 | 祖孙组件通信 |

示例:

```typescript
// 父组件
@Component
struct Parent {
  @State count: number = 0;

  build() {
    Child({ count: this.count })
  }
}

// 子组件
@Component
struct Child {
  @Prop count: number;

  build() {
    Text(`${this.count}`)
  }
}
```

### 7.3 自定义组件复用

我们可以将登录输入框抽取为可复用的组件:

```typescript
@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 中使用:

```typescript
LoginInput({ placeholder: '请输入用户名', text: this.username })
LoginInput({ placeholder: '请输入密码', text: this.password, inputType: InputType.Password })
```

这种抽象带来的好处:
1. **样式统一**:一处修改,处处生效。
2. **减少重复代码**:密码和用户名输入框除了类型外完全相同。
3. **更易维护**:样式变更只需改一个组件。

> **但注意**:对于只有两个实例的场景,是否抽取组件取决于团队的审美偏好。本文为了清晰展示结构,没有抽取,但在生产项目中强烈推荐。

### 7.4 主题系统

在大型应用中,应该建立统一的主题系统:

```typescript
// 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;
```

使用时:

```typescript
import { Colors, Spacing } from '../common/theme';

Text('欢迎登录')
  .fontColor(Colors.textPrimary)
  .margin({ bottom: Spacing.md })
```

这种方法在应用有 5 个以上页面时尤其有用,可以避免"改一个颜色要改十几个文件"的困境。

### 7.5 页面转场动画

ArkTS 支持自定义页面跳转动画:

```typescript
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`。

**排查步骤**:

1. ✅ 检查 `main_pages.json` 是否注册了目标页面:

```json
{
  "src": [
    "pages/Index",
    "pages/LoginPage"
  ]
}
```

2. ✅ 检查 `router.pushUrl` 的 `url` 路径是否正确——**不带 `.ets` 后缀**,相对于 `ets/` 目录:

```typescript
// 正确
router.pushUrl({ url: 'pages/LoginPage' });

// 错误(多了后缀)
router.pushUrl({ url: 'pages/LoginPage.ets' });

// 错误(路径不对)
router.pushUrl({ url: 'LoginPage' });
```

3. ✅ 检查文件是否存在且被 `@Entry` 装饰。

### 8.2 @State 更新但 UI 不刷新

**症状**:`loginMessage` 变了,但错误文字没有显示。

**排查**:

1. 确保变量确实被 `@State` 装饰,如果少写了 `@State`,ArkTS 不会追踪变化。
2. 确保 `if` 条件中的变量是响应式的:

```typescript
// ✅ 正确
if (this.loginMessage.length > 0) { ... }

// 错误——将响应式变量赋值给局部变量
const msg = this.loginMessage;  // 失去响应性
if (msg.length > 0) { ... }
```

3. 不要在 `build()` 或渲染循环中修改 `@State` 变量——会导致无限循环。

### 8.3 输入框样式不生效

**症状**:设置了 `.backgroundColor` 但输入框背景没变。

**原因**:`TextInput` 的默认样式可能覆盖了部分设置。解决方案是将样式设置在外层容器(`Row`)上,`TextInput` 本身保持透明。

```typescript
// ✅ 推荐——样式放在 Row 上
Row() {
  TextInput({ ... })
    .layoutWeight(1)
}
.backgroundColor('#FDFBF9')
.border({ width: 1, color: '#D4C5B0' })
.borderRadius(12)
```

### 8.4 页面背景色不生效

**症状**:设置了 `.backgroundColor` 但背景还是白色。

**原因**:容器的背景色只在**有内容**的区域生效。如果容器本身没有撑满父容器,背景色只会覆盖子元素区域。

**解决方案**:确保容器设置了宽高为 100%:

```typescript
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

```typescript
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 记住密码与生物识别

```typescript
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 表单验证增强

```typescript
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 多主题切换

```typescript
@StorageLink('themeMode') themeMode: string = 'autumn';

// 根据主题动态选择颜色
get primaryColor(): string {
  const themes = {
    autumn: '#C1793B',
    tech: '#00D4FF',
    fresh: '#4CAF50'
  };
  return themes[this.themeMode] || themes.autumn;
}
```

---

## 十一、结语

从零开始构建一个登录窗口,我们走过了这样的旅程:

1. **理解项目结构**:Stage Model 的模块划分、配置文件的作用。
2. **掌握 ArkTS 声明式 UI**:`@Component`、`@State`、`build()` 的协作。
3. **构建完整页面**:标题区 → 输入区 → 按钮 → 提示,逐层搭建。
4. **实现交互逻辑**:onChange 绑定 → 状态驱动 → handleLogin 校验。
5. **设计主题配色**:「秋季思雨」——用色彩传递产品情感。
6. **探索进阶话题**:组件通信、生命周期、主题系统、项目优化。

ArkTS 作为鸿蒙原生的声明式框架,其设计理念**借鉴了现代前端和移动端框架的最佳实践**,同时融合了鸿蒙生态的独特能力。如果您有 React / Vue / SwiftUI / Jetpack Compose 经验,上手会非常顺畅;即使没有,ArkTS 的简洁语法和完整的文档体系也能让您快速进入状态。

最后留一个思考题给您:

> 如果要将这个登录页改为「多语言支持」(中文/英文/日文),你会如何设计 ArkTS 的国际化方案?欢迎尝试实现并分享你的方案。

---

**全文完**

*本文代码运行环境:HarmonyOS 5.0 (API 12),DevEco Studio 5.0+。代码已通过编译验证,可直接运行。*

发布文章:

文章发布成功:

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐