基于 HarmonyOS 的 AI 聊天破冰器——从对齐到评估的全流程技术实践

一、对齐阶段(Align):从模糊需求到精确规范

1.1 项目上下文分析

在当今社交场景日益多元化的背景下,人们面对陌生社交场合时往往需要借助工具来打破尴尬、开启对话。AI聊天破冰器正是为解决这一痛点而生——它旨在帮助用户在不同社交场景下,根据与对方的关系和当前氛围,智能生成合适的开场白、话题建议和对话策略。

本项目基于 HarmonyOS 生态,采用 ArkTS 语言和 ArkUI 声明式框架进行开发,是鸿蒙原生 AI 应用矩阵中的一个子应用。整个项目采用统一的 MVVM 三层架构模式,包含数据模型层(Model)、业务逻辑层(Service)、UI 展示层(Page),并集成在统一的应用入口(Index)中,通过路由机制实现页面跳转。

从技术栈层面来看,项目使用以下关键技术:

  • ArkTS 语言:HarmonyOS 原生应用开发语言,基于 TypeScript 但做了严格的静态类型约束,不支持 any、unknown、动态属性访问等特性
  • ArkUI 框架:声明式 UI 构建体系,采用 @State 装饰器驱动数据绑定和 UI 刷新
  • @kit.ArkUI 模块:提供基础组件(Column、Row、Text、TextInput、Button、Scroll 等)和路由能力(router)
  • @kit.ArkTS 模块:提供工具类 API,如 TextDecoder 等
    在这里插入图片描述

1.2 需求规格确认

在深入分析后,我们明确了 AI 聊天破冰器的核心需求。用户通过输入三个关键维度——社交场景、双方关系、当前氛围——来定制化生成破冰建议。系统需要输出结构化的破冰方案,包括开场白话题、具体开场语句、跟进问题、深度级别、应避免话题、话题转换技巧和退出策略。

输入字段定义:

字段 类型 说明 示例值
场景 string 社交场合类型 聚会、约会、商务、线上、旅行、面试
关系 string 与对话对象的关系 陌生人、同事、朋友的朋友、相亲、新邻居
氛围 string 当前对话氛围 轻松、正式、幽默、深度

输出字段定义:

字段 类型 说明
icebreakers string[] 多个破冰话题建议
topic string 推荐话题
opener string 具体开场白语句
follow_up string[] 跟进问题列表
depth_level string 对话深度级别(浅/中/深)
avoid_topics string[] 应避免谈论的话题
transition_tips string 话题转换技巧
exit_strategy string 优雅结束对话的方式

1.3 边界确认与约束条件

在 ArkTS 环境下开发,我们需要注意以下语法约束,这些约束直接影响代码编写方式:

  • 不支持对象类型的调用签名,必须使用 class 来实现
  • 不支持解构赋值和展开运算符(数组场景除外)
  • 不支持动态属性访问(obj[“field”]),必须使用 obj.field 语法
  • 不支持在独立函数和静态方法中使用 this,this 只能在实例方法中使用
  • 不支持索引签名,必须使用数组替代
  • 不支持 for…in 遍历对象,必须使用常规 for 循环

这些约束使得我们在设计数据模型和业务逻辑时,必须采用更传统的面向对象风格,而非 TypeScript 中灵活的动态特性。

二、架构阶段(Architect):从需求到系统设计

2.1 整体架构设计

基于 HarmonyOS 的 ArkTS/ArkUI 技术栈,AI聊天破冰器采用经典的三层架构:Model 层负责数据建模,Service 层负责业务逻辑与 AI 数据生成,Page 层负责 UI 展示与用户交互。这种分层设计使得各层职责清晰、耦合度低,便于后续维护和扩展。

┌─────────────────────────────────────────────┐
│              View 层 (Page)                  │
│   ArkUI 声明式组件 + @State 状态管理         │
│   TextInput(输入) → Button(触发) → 结果展示  │
├─────────────────────────────────────────────┤
│             Service 层                       │
│   业务逻辑编排 + AI Prompt 构建 + 数据生成    │
│   generateData(input) → 返回 AI聊天破冰器Data │
├─────────────────────────────────────────────┤
│             Model 层                         │
│   数据实体定义 + 字段约束 + 类型安全          │
│   AI聊天破冰器Data { icebreakers, topic, ... } │
└─────────────────────────────────────────────┘

2.2 核心数据模型设计

在 Model 层,我们定义了一个纯粹的数据类(Data Class)。根据 ArkTS 的语法约束,每个字段都需要在类声明内部显式声明,并赋予初始值。构造函数中也需要显式初始化所有字段,以确保类型安全和编译通过。

// AI聊天破冰器Model.ets
export class AI聊天破冰器Data {
  icebreakers: string[] = []
  topic: string = ''
  opener: string = ''
  follow_up: string[] = []
  depth_level: string = ''
  avoid_topics: string[] = []
  transition_tips: string = ''
  exit_strategy: string = ''

  constructor() {
    this.icebreakers = []
    this.topic = ''
    this.opener = ''
    this.follow_up = []
    this.depth_level = ''
    this.avoid_topics = []
    this.transition_tips = ''
    this.exit_strategy = ''
  }
}

这个数据模型的设计体现了几个关键考量:

  1. 类型安全:每个字段都有明确的类型,string 或 string[],避免使用 any 或 unknown
  2. 默认值:所有字段在声明时即赋予空值,符合 ArkTS 强制初始化要求
  3. 构造函数冗余:尽管字段声明时已初始化,ArkTS 编译器仍要求构造函数中显式赋值,这是为了确保运行时的确定性

2.3 服务层设计

Service 层封装了 AI 数据生成的核心逻辑。在当前的实现中,我们使用 Mock 数据作为占位,后续可以无缝替换为真实的大模型 API 调用。Service 层接收来自 Page 层的用户输入(Record<string, Object> 类型),处理后返回结构化的 AI聊天破冰器Data 实例。

// AI聊天破冰器Service.ets
import { AI聊天破冰器Data } from './AI聊天破冰器Model'

export class AI聊天破冰器Service {
  private model: AI聊天破冰器Data

  constructor() {
    this.model = new AI聊天破冰器Data()
  }

  // 生成AI聊天破冰器数据
  generateData(input: Record<string, Object>): AI聊天破冰器Data {
    let result: AI聊天破冰器Data = new AI聊天破冰器Data()
    // Mock data generation based on input
    let sceneVal: string = String(input['scene'] || '')

    result.icebreakers = ['示例数据1', '示例数据2', '示例数据3']
    result.avoid_topics = ['示例项1', '示例项2', '示例项3']
    result.transition_tips = '生成结果:' + sceneVal
    result.exit_strategy = '生成结果:' + sceneVal
    return result
  }
}

这里需要注意,由于 ArkTS 不支持动态属性访问(obj[“field”]),但我们使用了 Record<string, Object> 类型,它本质上是一个映射类型,编译器允许通过索引方式访问。这是 ArkTS 中少数允许的索引访问场景。

2.4 数据流向设计

整个应用的数据流遵循"单向数据流"原则:

  1. 用户在 Page 层通过 TextInput 组件输入场景、关系、氛围三个字段
  2. 输入值通过 onChange 事件回调更新到 @State 装饰的 inputData 变量中
  3. 用户点击"发送"按钮,触发 onClick 事件,调用 Service 层的 generateData 方法
  4. Service 层处理输入数据,生成(或调用 AI 生成)结构化的破冰建议
  5. 返回的 AI聊天破冰器Data 实例赋值给 @State 装饰的 resultData 变量
  6. ArkUI 框架自动检测到状态变化,重新渲染 UI,展示结果区域
用户输入 → onChange → @State inputData → onClick → Service.generateData()
                                                              ↓
UI 渲染 ← @State resultData 变化 ← 返回 AI聊天破冰器Data ← AI/Mock 生成

2.5 路由与页面注册

AI聊天破冰器页面需要通过路由系统注册到主应用中。在 main_pages.json 中添加页面路径,同时在 apps.json 中配置应用卡片信息,使其出现在应用列表中。

// main_pages.json 中的路由配置
{
  "src": [
    "pages/Index",
    "apps/AI聊天破冰器/AI聊天破冰器Page",
    // ... 其他页面
  ]
}
// apps.json 中的应用卡片配置
{
  "icon": "💬",
  "title": "AI聊天破冰器",
  "subtitle": "聊天破冰",
  "color": "#EC4899",
  "bg": "#FDF2F8",
  "border": "#FBCFE8",
  "page": "apps/AI聊天破冰器/AI聊天破冰器Page",
  "cat": "创意娱乐"
}

三、原子化阶段(Atomize):任务分解与模块划分

3.1 原子任务分解

将 AI 聊天破冰器的开发任务分解为以下可独立执行、可测试的原子任务:

任务 1:创建数据模型文件

  • 文件:AI聊天破冰器Model.ets
  • 定义 AI聊天破冰器Data 类,包含所有输出字段
  • 确保所有字段有正确类型注解和初始值
  • 构造函数中显式初始化所有字段

任务 2:实现服务层逻辑

  • 文件:AI聊天破冰器Service.ets
  • 导入 Model 层数据类
  • 实现 AI聊天破冰器Service 类,封装 generateData 方法
  • 使用 Mock 数据填充结果,预留 AI 大模型接入接口

任务 3:构建页面 UI

  • 文件:AI聊天破冰器Page.ets
  • 实现顶部导航栏(返回按钮、标题、在线状态指示器)
  • 实现客服对话气泡 UI
  • 实现输入区域(场景、关系、氛围三个 TextInput)
  • 实现发送按钮
  • 实现结果展示区域(条件渲染,仅在 showResult 为 true 时显示)
  • 实现 @State 状态变量管理

任务 4:注册路由配置

  • main_pages.json 中添加页面路径
  • 确保路径格式与项目结构一致

任务 5:集成到应用列表

  • apps.json 中添加应用卡片配置
  • 确保 icon、title、color、page 等字段正确

3.2 依赖关系梳理

这些任务之间存在明确的依赖关系:

任务 1 (Model) ──→ 任务 2 (Service) ──→ 任务 3 (Page)
                                                │
                   任务 4 (路由) ←───────────────┘
                        │
                   任务 5 (应用列表)

任务 1 是基础,任务 2 依赖任务 1,任务 3 依赖任务 1 和任务 2。任务 4 和任务 5 是集成配置,可以在任务 3 完成后并行执行。

3.3 接口契约定义

在原子化分解中,明确各层之间的接口契约至关重要:

Model ↔ Service 接口:

  • Service 层接收 Record<string, Object> 类型的用户输入
  • Service 层返回 AI聊天破冰器Data 类型的结构化数据

Service ↔ Page 接口:

  • Page 层通过 service.generateData(inputData) 调用 Service
  • 返回结果通过 @State resultData: AI聊天破冰erData | null 绑定到 UI

Page ↔ 路由系统接口:

  • 路由路径为 apps/AI聊天破冰器/AI聊天破冰器Page
  • 通过 router.pushUrl({ url: '...' }) 实现页面跳转
  • 通过 router.back() 实现返回

四、审批阶段(Approve):设计审查与质量门控

4.1 代码审查要点

在进入编码阶段之前,需要对架构设计和实现方案进行严格审查,确保质量门控通过。

架构审查清单:

  1. 分层合理性:Model、Service、Page 三层职责是否清晰?是否有跨层调用?

    • 审查结果:通过。Model 仅定义数据结构,Service 仅处理业务逻辑,Page 仅负责 UI 展示,各层职责明确。
  2. 类型安全:是否所有类型都明确声明?是否存在 any/unknown 类型?

    • 审查结果:通过。所有字段都有明确类型,无 any/unknown 使用。
  3. ArkTS 语法合规:是否使用了不支持的语法特性?

    • 审查结果:通过。代码中无解构赋值、无动态属性访问、无函数表达式、无索引签名等不支持的语法。
  4. 状态管理正确性:@State 修饰的变量是否正确?数据流是否单向?

    • 审查结果:通过。@State inputData 和 @State resultData 管理输入和输出,数据流单向清晰。
  5. UI 组件合理性:是否使用了正确的 ArkUI 组件?布局是否合理?

    • 审查结果:通过。Column、Row、Text、TextInput、Button、Scroll、ForEach 等组件使用正确。

4.2 质量门控标准

门控 1:编译通过

  • 所有 ArkTS 语法约束得到遵守
  • 无类型错误
  • 无未使用的 import

门控 2:功能完整

  • 用户输入场景、关系、氛围三个字段后,点击发送能生成结果
  • 结果展示区域包含所有输出字段
  • 返回按钮能正常返回首页

门控 3:UI 视觉规范

  • 颜色使用 HarmonyOS 设计规范色值
  • 字体大小、间距统一
  • 圆角、阴影等视觉风格一致

门控 4:性能基线

  • 页面渲染流畅,无卡顿
  • @State 状态变更触发的重渲染范围最小化

4.3 潜在风险与应对

风险项 影响 应对措施
ArkTS 语法严格,部分 TS 特性不可用 开发效率降低 提前梳理 ArkTS 约束清单,避免使用不支持的特性
当前使用 Mock 数据,后续接入 AI 大模型 数据结构可能不兼容 Service 层抽象接口,Mock 和 AI 实现可无缝切换
Record<string, Object> 类型安全性不足 运行时类型错误 后续引入更严格的输入类型校验

五、自动化执行阶段(Automate):编码实现与 ArkTS 实战

5.1 数据模型层实现

数据模型层是最基础的部分,定义 AI 聊天破冰器的结构化输出格式。在 ArkTS 中,类的定义需要特别注意语法约束。

// AI聊天破冰器Model.ets
export class AI聊天破冰器Data {
  icebreakers: string[] = []
  topic: string = ''
  opener: string = ''
  follow_up: string[] = []
  depth_level: string = ''
  avoid_topics: string[] = []
  transition_tips: string = ''
  exit_strategy: string = ''

  constructor() {
    this.icebreakers = []
    this.topic = ''
    this.opener = ''
    this.follow_up = []
    this.depth_level = ''
    this.avoid_topics = []
    this.transition_tips = ''
    this.exit_strategy = ''
  }
}

关键 ArkTS 语法要点:

  • 所有字段必须在类声明内部声明,不支持在构造函数中声明字段
  • 每个字段必须显式标注类型,不支持类型推断
  • 字段声明时必须赋予初始值,可以通过 = 赋值
  • 构造函数中仍然需要显式初始化所有字段——这是 ArkTS 的冗余要求,但必须遵守
  • 数组类型使用 string[] 语法,不支持泛型数组 Array<string> 以外的写法

5.2 服务层实现

服务层封装了业务逻辑,当前使用 Mock 数据来模拟 AI 生成结果。后续接入真实 AI 大模型时,只需修改 Service 层,Model 和 Page 层无需改动。

// AI聊天破冰器Service.ets
import { AI聊天破冰器Data } from './AI聊天破冰器Model'

export class AI聊天破冰器Service {
  private model: AI聊天破冰器Data

  constructor() {
    this.model = new AI聊天破冰器Data()
  }

  // 生成AI聊天破冰器数据
  generateData(input: Record<string, Object>): AI聊天破冰器Data {
    let result: AI聊天破冰器Data = new AI聊天破冰器Data()
    // Mock data generation based on input
    let sceneVal: string = String(input['scene'] || '')

    result.icebreakers = ['示例数据1', '示例数据2', '示例数据3']
    result.avoid_topics = ['示例项1', '示例项2', '示例项3']
    result.transition_tips = '生成结果:' + sceneVal
    result.exit_strategy = '生成结果:' + sceneVal
    return result
  }
}

关于 Record<string, Object> 类型:

在 ArkTS 中,Record<K, V> 是少数支持的 TypeScript 实用类型之一。它允许通过索引访问元素,即 input['scene'] 是合法的。但需要注意的是,Record<string, Object> 中的 Object 类型在 ArkTS 中是允许的,用于表示不确定的对象类型。返回时需要通过 String() 显式转换为字符串,因为 ArkTS 不支持隐式类型转换。

5.3 页面层实现

Page 层是 UI 展示和用户交互的核心,使用 ArkUI 声明式语法构建。

// AI聊天破冰器Page.ets
import { AI聊天破冰器Data } from './AI聊天破冰器Model'
import { AI聊天破冰器Service } from './AI聊天破冰器Service'
import { router } from '@kit.ArkUI'

@Entry
@Component
struct AI聊天破冰器Page {
  @State inputData: Record<string, Object> = {}
  @State resultData: AI聊天破冰器Data | null = null
  @State showResult: boolean = false
  private service: AI聊天破冰器Service = new AI聊天破冰器Service()

  build() {
    Column() {
      // 顶部导航栏
      Row() {
        Text('← 返回')
          .fontSize(13)
          .fontColor('#FFFFFF')
          .onClick(() => { router.back() })
        Blank()
        Column() {
          Text('📱 AI聊天破冰器')
            .fontSize(17)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FFFFFF')
          Text('在线客服 · 已连接')
            .fontSize(10)
            .fontColor('#93C5FD')
            .margin({ top: 2 })
        }
        Blank()
        Circle().width(10).height(10).fill('#22C55E')
      }
      .width('100%')
      .padding({ left: 20, right: 20, top: 16, bottom: 14 })
      .backgroundColor('#2563EB')

      Scroll() {
        Column() {
          // 客服对话气泡
          Row() {
            Column() {
              Text('您好!有什么可以帮助您的?')
                .fontSize(13)
                .fontColor('#333333')
                .padding(12)
                .backgroundColor('#FFFFFF')
                .borderRadius({ topLeft: 4, topRight: 16, bottomLeft: 16, bottomRight: 16 })
            }
            .alignItems(HorizontalAlign.Start)
          }
          .width('100%')
          .margin({ top: 12, bottom: 8 })

          // 输入区域
          Column() {
            Text('📝 场景')
              .fontSize(11)
              .fontColor('#4B5563')
              .margin({ top: 6, bottom: 3 })
            TextInput({ placeholder: '请输入场景' })
              .fontSize(13)
              .height(40)
              .backgroundColor('#FFFFFF')
              .borderRadius(8)
              .border({ width: 1, color: '#E5E7EB' })
              .padding({ left: 12, right: 12 })
              .onChange((val: string) => { this.inputData['场景'] = val })

            Text('📝 关系')
              .fontSize(11)
              .fontColor('#4B5563')
              .margin({ top: 6, bottom: 3 })
            TextInput({ placeholder: '请输入关系' })
              .fontSize(13)
              .height(40)
              .backgroundColor('#FFFFFF')
              .borderRadius(8)
              .border({ width: 1, color: '#E5E7EB' })
              .padding({ left: 12, right: 12 })
              .onChange((val: string) => { this.inputData['关系'] = val })

            Text('📝 氛围')
              .fontSize(11)
              .fontColor('#4B5563')
              .margin({ top: 6, bottom: 3 })
            TextInput({ placeholder: '请输入氛围' })
              .fontSize(13)
              .height(40)
              .backgroundColor('#FFFFFF')
              .borderRadius(8)
              .border({ width: 1, color: '#E5E7EB' })
              .padding({ left: 12, right: 12 })
              .onChange((val: string) => { this.inputData['氛围'] = val })
          }
          .width('100%')
          .padding(18)
          .backgroundColor('#FFFFFF')
          .borderRadius(12)
          .margin({ top: 8 })

          // 发送按钮
          Button('📱  发送')
            .width('100%')
            .height(50)
            .backgroundColor('#3B82F6')
            .borderRadius(25)
            .fontColor('#FFFFFF')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .margin({ top: 18, bottom: 14 })
            .onClick(() => {
              this.resultData = this.service.generateData(this.inputData)
              this.showResult = true
            })

          // 结果展示区域(条件渲染)
          if (this.showResult && this.resultData !== null) {
            Row() {
              Column() {
                Text('🤖 AI回复')
                  .fontSize(12)
                  .fontColor('#2563EB')
                  .margin({ bottom: 8 })

                Text('Icebreakers')
                  .fontSize(13)
                  .fontWeight(FontWeight.Bold)
                  .fontColor('#333333')
                  .margin({ top: 10, bottom: 6 })
                if (this.resultData.icebreakers) {
                  ForEach(this.resultData.icebreakers, (item: string, index: number) => {
                    Row() {
                      Text('• ')
                        .fontSize(12)
                        .fontColor('#666666')
                      Text(item)
                        .fontSize(12)
                        .fontColor('#333333')
                    }
                    .width('100%')
                    .padding({ top: 2, bottom: 2 })
                  }, (item: string, index: number) => index.toString())
                }

                Row() {
                  Text('Topic: ')
                    .fontSize(12)
                    .fontWeight(FontWeight.Medium)
                    .fontColor('#666666')
                  Text(this.resultData.topic)
                    .fontSize(12)
                    .fontColor('#333333')
                }
                .width('100%')
                .padding({ top: 4, bottom: 4 })

                Row() {
                  Text('Opener: ')
                    .fontSize(12)
                    .fontWeight(FontWeight.Medium)
                    .fontColor('#666666')
                  Text(this.resultData.opener)
                    .fontSize(12)
                    .fontColor('#333333')
                }
                .width('100%')
                .padding({ top: 4, bottom: 4 })

                Text('Follow up')
                  .fontSize(13)
                  .fontWeight(FontWeight.Bold)
                  .fontColor('#333333')
                  .margin({ top: 10, bottom: 6 })
                if (this.resultData.follow_up) {
                  ForEach(this.resultData.follow_up, (item: string, index: number) => {
                    Row() {
                      Text('• ')
                        .fontSize(12)
                        .fontColor('#666666')
                      Text(item)
                        .fontSize(12)
                        .fontColor('#333333')
                    }
                    .width('100%')
                    .padding({ top: 2, bottom: 2 })
                  }, (item: string, index: number) => index.toString())
                }

                Row() {
                  Text('Depth level: ')
                    .fontSize(12)
                    .fontWeight(FontWeight.Medium)
                    .fontColor('#666666')
                  Text(this.resultData.depth_level)
                    .fontSize(12)
                    .fontColor('#333333')
                }
                .width('100%')
                .padding({ top: 4, bottom: 4 })

                Text('Avoid topics')
                  .fontSize(13)
                  .fontWeight(FontWeight.Bold)
                  .fontColor('#333333')
                  .margin({ top: 10, bottom: 6 })
                if (this.resultData.avoid_topics) {
                  ForEach(this.resultData.avoid_topics, (item: string, index: number) => {
                    Row() {
                      Text('• ')
                        .fontSize(12)
                        .fontColor('#666666')
                      Text(item)
                        .fontSize(12)
                        .fontColor('#333333')
                    }
                    .width('100%')
                    .padding({ top: 2, bottom: 2 })
                  }, (item: string, index: number) => index.toString())
                }

                Row() {
                  Text('Transition tips: ')
                    .fontSize(12)
                    .fontWeight(FontWeight.Medium)
                    .fontColor('#666666')
                  Text(this.resultData.transition_tips)
                    .fontSize(12)
                    .fontColor('#333333')
                }
                .width('100%')
                .padding({ top: 4, bottom: 4 })

                Row() {
                  Text('Exit strategy: ')
                    .fontSize(12)
                    .fontWeight(FontWeight.Medium)
                    .fontColor('#666666')
                  Text(this.resultData.exit_strategy)
                    .fontSize(12)
                    .fontColor('#333333')
                }
                .width('100%')
                .padding({ top: 4, bottom: 4 })
              }
              .padding(14)
              .backgroundColor('#EFF6FF')
              .borderRadius({ topLeft: 16, topRight: 4, bottomLeft: 16, bottomRight: 16 })
            }
            .width('100%')
            .justifyContent(FlexAlign.End)
            .margin({ bottom: 20 })
          }
        }
        .width('100%')
        .padding({ left: 18, right: 18, bottom: 40 })
      }
      .layoutWeight(1)
    }
    .width('100%').height('100%')
    .backgroundColor('#F3F4F6')
  }
}

5.4 页面实现的关键技术点解析

(1)@State 装饰器与状态管理

在 ArkUI 中,@State 是驱动 UI 更新的核心机制。被 @State 修饰的变量发生变化时,框架会自动重新渲染依赖于该变量的 UI 部分。

@State inputData: Record<string, Object> = {}        // 存储用户输入
@State resultData: AI聊天破冰器Data | null = null     // 存储AI生成结果
@State showResult: boolean = false                    // 控制结果区域显示

注意 ArkTS 要求 @State 变量必须初始化,不能使用确定性赋值断言(let v!: T)。

(2)条件渲染与空值处理

ArkTS 中,条件渲染使用 if 表达式,且必须显式处理空值:

if (this.showResult && this.resultData !== null) {
  // 渲染结果区域
}

注意这里不能使用 this.resultData?.icebreakers 这种可选链操作符,因为 ArkTS 不支持。必须显式判断 !== null

(3)ForEach 循环渲染

对于数组类型的字段(icebreakers、follow_up、avoid_topics),使用 ForEach 组件进行循环渲染:

ForEach(this.resultData.icebreakers, (item: string, index: number) => {
  // 渲染每一项
}, (item: string, index: number) => index.toString())

第三个参数是 key 生成函数,用于优化列表渲染性能。这里使用 index 作为 key。

(4)TextInput 的回调处理

TextInput 组件的 onChange 回调接收一个字符串参数,这里我们将其赋值到 inputData 中:

TextInput({ placeholder: '请输入场景' })
  .onChange((val: string) => { this.inputData['场景'] = val })

注意这里使用字符串索引访问 Record 对象,这是 ArkTS 中 Record 类型的特殊允许行为。

5.5 路由与集成配置

在完成页面编码后,需要将页面注册到路由系统和应用列表中。

路由注册(main_pages.json):

{
  "src": [
    "pages/Index",
    "apps/AI聊天破冰器/AI聊天破冰器Page"
  ]
}

应用列表注册(apps.json):

{
  "icon": "💬",
  "title": "AI聊天破冰器",
  "subtitle": "聊天破冰",
  "color": "#EC4899",
  "bg": "#FDF2F8",
  "border": "#FBCFE8",
  "page": "apps/AI聊天破冰器/AI聊天破冰器Page",
  "cat": "创意娱乐"
}

5.6 ArkTS 语法实战避坑指南

在开发过程中,我们总结了一些 ArkTS 语法的关键注意事项:

避坑 1:不支持解构赋值

// ❌ 错误写法
let { scene, relationship } = input

// ✅ 正确写法
let sceneVal: string = String(input['场景'] || '')
let relationshipVal: string = String(input['关系'] || '')

避坑 2:不支持函数表达式,必须使用箭头函数

// ❌ 错误写法
.onChange(function(val: string) { ... })

// ✅ 正确写法
.onChange((val: string) => { ... })

避坑 3:不支持对象字面量直接作为类型

// ❌ 错误写法
let obj: { name: string, age: number } = { name: 'test', age: 20 }

// ✅ 正确写法 - 使用 class 或 Record
class Person {
  name: string = ''
  age: number = 0
}
let obj: Person = new Person()

避坑 4:不支持动态属性访问

// ❌ 错误写法
let field = 'scene'
let val = inputData[field]

// ✅ 正确写法
let val = inputData['scene']  // Record 类型允许索引访问

避坑 5:不支持在独立函数中使用 this

// ❌ 错误写法:在独立函数中使用 this
function processData() {
  return this.inputData  // 编译错误
}

// ✅ 正确写法:在实例方法中使用 this
processData(): void {
  let data = this.inputData  // 正确
}
Logo

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

更多推荐