AI塔罗占卜:HarmonyOS 智能应用全流程开发实战

摘要:本文以"AI塔罗占卜"应用为案例,详细阐述在 HarmonyOS 生态下,从需求对齐到最终交付的全流程开发实践。文章遵循"对齐→架构→原子化→审批→自动化执行→评估"六阶段方法论,涵盖 ArkTS 语法约束、ArkUI 声明式 UI 开发、@State 状态管理、数据模型设计、服务层抽象、Hvigor 构建系统、自动化测试等核心技术主题,并配以完整的代码示例和工程实践心得。全文约 10000 字,适合 HarmonyOS 应用开发者、移动端架构师和技术管理者阅读。


在这里插入图片描述

一、对齐阶段(Align)

在软件开发中,对齐阶段的目标是将模糊需求转化为精确规范。这一阶段如果做得不够充分,后续的架构设计和编码实现就会不断返工,造成时间和资源的大量浪费。对于"AI塔罗占卜"这一应用,我们需要从项目上下文、需求理解、技术可行性三个维度进行深度对齐。

1.1 项目上下文分析

在开始任何开发工作之前,理解项目所处的上下文环境至关重要。本项目是一个运行在 HarmonyOS 操作系统上的 AI 智能助手集合应用,代码仓库位于 c:\Users\l\DevEcoStudioProjects\MyApplication。整个应用以"AI 智能助手"为品牌定位,通过首页网格(Index.ets)聚合了多个 AI 应用,涵盖健康生活、工作效率、创意娱乐、学习成长、职业发展六大类别。这种架构模式使得每个应用可以独立开发、独立部署,同时又通过统一的首页入口为用户提供一站式的 AI 服务体验。

"AI塔罗占卜"应用被归类为"创意娱乐"类别,在 apps.json 中的注册信息如下:

{
  "icon": "🔮",
  "title": "AI塔罗占卜",
  "subtitle": "塔罗占卜",
  "color": "#EC4899",
  "bg": "#FDF2F8",
  "border": "#FBCFE8",
  "page": "apps/AI塔罗占卜/AI塔罗占卜Page",
  "cat": "创意娱乐"
}

从技术栈上看,项目采用 HarmonyOS 的 ArkTS 语言、ArkUI 声明式框架、@kit.ArkUI@kit.ArkTS 核心 Kit 包。项目构建系统为 Hvigor,依赖管理通过 oh-package.json5 完成。所有页面以 @Entry 装饰器标记为入口,通过 router API 实现页面间导航。值得注意的是,项目的 oh-package.json5dependencies 为空,这说明项目完全依赖 HarmonyOS 系统的内置 Kit 能力,无需引入任何第三方依赖库,这大大降低了依赖管理和版本兼容性的复杂度。

1.2 需求理解与边界确认

"AI塔罗占卜"的核心需求是:用户输入问题类型和抽牌数,点击占卜按钮后,系统模拟塔罗牌占卜流程,展示占卜结果。结果应包含卡牌列表、卡牌名称、位置、正位/逆位含义、解读、总体评价、建议和免责声明。

经过需求分析,我们明确了以下关键点:

  • 用户输入:问题类型(字符串)、抽牌数(字符串)
  • 业务逻辑:根据输入生成塔罗占卜数据,当前阶段使用 Mock 数据,后续可接入真实 AI 模型
  • 输出展示:以结构化方式展示占卜结果,包含卡牌列表、各字段含义和解读
  • UI 风格:神秘学视觉风格,深紫色背景配合金色文字,营造占卜仪式的沉浸感
  • 交互方式:点击占卜按钮触发计算,结果区域通过条件渲染控制显隐

1.3 技术约束对齐

在 HarmonyOS 的 ArkTS 环境下,有若干重要的语法约束需要在开发前对齐。这些约束与标准 TypeScript 存在显著差异,如果开发团队之前没有 ArkTS 的开发经验,这些约束可能会成为开发过程中的主要障碍。

不支持索引访问类型:这是 ArkTS 中最具约束力的规则之一。标准 TypeScript 中,我们可以通过 obj["field"] 的方式动态访问对象属性,这在处理 JSON 数据或动态配置时非常方便。但在 ArkTS 中,必须使用显式类型名称,通过 obj.field 语法访问。这要求开发者在设计阶段就明确所有字段名称,无法在运行时动态添加或访问属性。

不支持 any 和 unknown 类型:所有变量必须有显式类型标注。例如,Record<string, Object> 是合法的泛型容器类型,但不能使用 any。这意味着在处理不确定类型的数据时,需要借助类型断言或联合类型来解决问题。

不支持解构赋值:标准 TypeScript 中常见的 const { a, b } = obj 语法在 ArkTS 中不可用,必须创建临时变量逐字段操作。这虽然增加了代码量,但使数据流动路径更加清晰。

不支持 Function.bind/apply/call:在 ArkTS 中,this 的语义被限制为传统的 OOP 风格,禁止在独立函数中使用 this。这意味着所有依赖 this 的上下文操作都必须通过类的实例方法来完成,不能通过函数式编程中的 bind 或 call 来动态绑定 this。

不支持 for…in 遍历对象:对于数组,必须使用常规的 for 循环或 ForEach 组件进行迭代。这是因为 ArkTS 在编译时就已经确定了对象的布局,运行时遍历属性没有意义。

不支持对象字面量直接作为类型声明:必须显式声明类和接口,然后通过构造函数创建实例。这要求所有数据结构都有明确的类型定义。

这些约束直接影响代码写法,在后续的架构和编码阶段必须严格遵守。在团队开发中,建议将这些约束编写为 ESLint 规则或代码审查清单,确保每一位开发者都遵循相同的编码规范。


二、架构阶段(Architect)

基于对齐阶段达成的共识,我们进入架构设计阶段。目标是设计出一套与现有系统架构一致、可扩展、易维护的技术方案。架构设计是软件开发中最关键的环节之一,它直接决定了代码的可维护性、可测试性和可扩展性。

2.1 整体架构设计

"AI塔罗占卜"应用采用经典的三层架构模式:

┌─────────────────────────────────────┐
│           UI 表现层 (Page)           │
│    AI塔罗占卜Page.ets                │
│   - 用户输入采集                      │
│   - 占卜结果展示                      │
│   - 交互动画反馈                      │
│   - 状态变量管理                      │
├─────────────────────────────────────┤
│           业务服务层 (Service)        │
│    AI塔罗占卜Service.ets              │
│   - 数据生成逻辑                      │
│   - AI 模型调用封装                   │
│   - 输入参数校验                      │
│   - 数据转换与格式化                  │
├─────────────────────────────────────┤
│           数据模型层 (Model)          │
│    AI塔罗占卜Model.ets                │
│   - 数据实体定义                      │
│   - 属性类型约束                      │
│   - 数据完整性保证                    │
│   - 默认值初始化                      │
└─────────────────────────────────────┘

这种分层架构与项目中其他应用的架构模式保持一致,确保代码风格统一、易于理解和维护。每一层都有自己的明确定义和职责边界:

  • UI 表现层:负责用户的交互体验,包括输入采集、结果展示、动画反馈等。这一层只关注"如何展示"和"如何交互",不关心"数据从哪里来"和"业务逻辑是什么"。
  • 业务服务层:负责核心业务逻辑的实现,包括数据生成、AI 模型调用、参数校验等。这一层是应用的"大脑",处理所有业务规则。
  • 数据模型层:负责数据实体的定义和约束,确保数据的完整性和一致性。这一层是应用的数据"骨架"。

2.2 数据模型层设计

数据模型层是整个应用的基石。在 ArkTS 中,由于不支持 interface 的动态特性和 any 类型,我们使用 class 来定义数据实体。选择 class 而非 interface 的原因在于:ArkTS 中 interface 主要用于编译时的类型检查,而 class 既可以作为类型又可以作为值的构造函数,更适合在运行时创建数据实例。

文件路径:entry/src/main/ets/apps/AI塔罗占卜/AI塔罗占卜Model.ets

export class AI塔罗占卜Data {
  cards: string[] = []
  name: string = ''
  position: string = ''
  slot: string = ''
  upright_meaning: string = ''
  reversed_meaning: string = ''
  interpretation: string = ''
  overall: string = ''
  advice: string = ''
  disclaimer: string = ''

  constructor() {
    this.cards = []
    this.name = ''
    this.position = ''
    this.slot = ''
    this.upright_meaning = ''
    this.reversed_meaning = ''
    this.interpretation = ''
    this.overall = ''
    this.advice = ''
    this.disclaimer = ''
  }
}

设计考量

  1. 所有字段显式声明:ArkTS 不支持动态字段声明和访问,因此所有字段必须在类中立即声明,并赋予默认值。这与标准 TypeScript 中可以在构造函数中动态添加属性的做法形成鲜明对比。
  2. 构造函数中统一初始化:虽然 ArkTS 不支持在构造函数中声明类字段,但可以在构造函数中为已声明的字段赋值,确保数据完整性。这种"声明与初始化分离"的模式是 ArkTS 的一个重要特点。
  3. 字段类型明确:所有字段均为 stringstring[] 类型,避免使用 anyunknown。如果未来需要支持更多数据类型,可以通过联合类型(如 string | number)来扩展。

2.3 服务层设计

服务层负责业务逻辑的实现,将数据生成逻辑与 UI 表现层解耦。这种设计遵循了"单一职责原则"(Single Responsibility Principle),每个类只负责一个职责。

文件路径:entry/src/main/ets/apps/AI塔罗占卜/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 question_typeVal: string = String(input['question_type'] || '')

    result.cards = ['示例数据1', '示例数据2', '示例数据3']
    result.overall = '生成结果:' + question_typeVal
    result.advice = '生成结果:' + question_typeVal
    result.disclaimer = '生成结果:' + question_typeVal
    return result
  }
}

设计模式应用

  • Service 模式:将业务逻辑从 UI 代码中分离,便于单元测试和后期扩展。如果未来需要接入真实的 AI 模型,只需要修改 Service 层的实现,UI 层完全不需要改动。
  • 依赖注入:通过构造函数注入 Model 实例,降低模块间的耦合度。虽然当前实现中 Service 直接创建了 Model 实例,但后续可以改为通过外部传入的方式,进一步提升可测试性。
  • 工厂方法generateData 方法根据输入参数生成新的数据实例,充当数据工厂的角色。每次调用都会创建一个全新的 AI塔罗占卜Data 实例,避免了对同一实例的重复修改。

2.4 UI 表现层设计

UI 层使用 ArkUI 的声明式框架,通过 @State 装饰器驱动 UI 更新。ArkUI 的声明式 UI 框架与 React、SwiftUI 等现代 UI 框架理念一致,都采用"状态驱动 UI"的编程模型。

文件路径:entry/src/main/ets/apps/AI塔罗占卜/AI塔罗占卜Page.ets

核心状态变量设计

@Entry
@Component
struct AI塔罗占卜Page {
  @State inputData: Record<string, Object> = {}
  @State resultData: AI塔罗占卜Data | null = null
  @State showResult: boolean = false
  @State selectedCard: number = -1
  private service: AI塔罗占卜Service = new AI塔罗占卜Service()
  // ...
}

状态管理策略

  • @State inputData:存储用户输入,使用 Record<string, Object> 泛型容器类型,这是 ArkTS 中推荐的键值对存储方式。Record<K, V> 是 ArkTS 标准库中少数支持索引访问的容器类型之一。
  • @State resultData:存储占卜结果,类型为 AI塔罗占卜Data | null,使用联合类型处理空值状态。在 ArkTS 中,联合类型是处理"可能有值,可能为空"场景的标准方式。
  • @State showResult:控制结果展示区域的显隐,通过布尔值驱动条件渲染。这是一种常见的 UI 状态管理模式,将"是否展示"与"展示什么数据"分离为两个独立的状态变量。
  • @State selectedCard:记录用户当前选中的塔罗牌索引,用于高亮显示。初始值为 -1 表示未选择任何卡牌。

2.5 完整的 UI 组件树分析

"AI塔罗占卜"页面的 UI 组件树结构如下:

Column (根容器)
├── Row (顶部导航栏)
│   ├── Text "← 返回"
│   ├── Blank (弹性空间)
│   ├── Column (标题区域)
│   │   ├── Text "📱 AI塔罗占卜"
│   │   └── Text "✦ 塔罗奥秘 ✦"
│   ├── Blank (弹性空间)
│   └── Text "🔮"
├── Scroll (可滚动内容)
│   └── Column (内容容器)
│       ├── Row (塔罗牌选择器)
│       │   └── ForEach [0, 1, 2]
│       │       └── Column (单张卡牌)
│       │           ├── Text (卡牌图标)
│       │           └── Text (卡牌标签)
│       ├── Column (输入区域)
│       │   ├── Text "问题类型"
│       │   ├── TextInput (问题类型输入框)
│       │   ├── Text "抽牌数"
│       │   └── TextInput (抽牌数输入框)
│       ├── Button "📱 塔罗占卜"
│       └── if (showResult && resultData !== null)
│           └── Column (结果展示区域)
│               ├── Text "🔮 占卜结果"
│               ├── ForEach cards (卡牌列表)
│               ├── Row (name)
│               ├── Row (position)
│               ├── Row (slot)
│               ├── Row (upright_meaning)
│               ├── Row (reversed_meaning)
│               ├── Row (interpretation)
│               ├── Row (overall)
│               ├── Row (advice)
│               └── Row (disclaimer)

2.6 数据流设计

应用的数据流遵循"单向数据流"原则,这是 ArkUI 框架推荐的数据管理模式:

用户输入 → @State inputData 更新(通过 onChange 回调)
    ↓
点击占卜按钮 → onClick 事件处理函数
    ↓
调用 service.generateData(inputData)
    ↓
返回 AI塔罗占卜Data 实例 → 赋值给 @State resultData
    ↓
设置 @State showResult = true
    ↓
ArkUI 响应式系统检测到 @State 变更
    ↓
触发 UI 重新渲染 → 条件渲染结果区域(if 条件变为 true)
    ↓
用户在结果区域交互 → 点击卡牌 → 更新 @State selectedCard
    ↓
UI 重新渲染高亮状态

这种单向数据流模式的优势在于:

  1. 可预测性:数据变更的路径是单向的,不会出现循环更新。
  2. 可调试性:每个状态变更都有明确的触发源,方便定位问题。
  3. 性能优化:ArkUI 框架可以精确追踪哪些组件依赖哪些状态,实现最小化重渲染。

2.7 异常处理策略

在 ArkTS 中,异常处理遵循以下策略:

  1. 输入校验:在 Service 层对输入参数进行校验,确保数据合法性。例如,String(input['question_type'] || '') 确保即使输入为空也能得到一个空字符串而非 undefined。
  2. 空值安全:使用 null 联合类型(如 AI塔罗占卜Data | null)并在模板中通过 if (this.resultData !== null) 进行空值检查。ArkTS 要求显式处理空值,不会出现 JavaScript 中常见的 Cannot read property of undefined 错误。
  3. catch 子句:ArkTS 不支持在 catch 子句中指定类型标注,因此省略类型标注,直接处理异常。这与标准 TypeScript 中 catch (e: unknown) 的写法不同。

三、原子化阶段(Atomize)

原子化阶段将整体任务分解为更小的、可独立执行和验证的原子任务。这有助于提高开发效率、降低复杂度,并确保每个模块的可测试性。原子化分解是敏捷开发中"用户故事拆分"的具体实践,将大需求拆解为小任务,每个任务都有明确的交付标准和验收条件。

3.1 任务分解

"AI塔罗占卜"应用开发可分解为以下原子任务:

任务ID 任务名称 预估工时 前置依赖 验收标准
T-001 数据模型类定义 0.5h AI塔罗占卜Data 类定义完整,所有字段类型正确,默认值初始化正确
T-002 服务层业务逻辑 1h T-001 generateData 方法能正确生成占卜数据,输入参数校验正常
T-003 UI 顶部导航栏 0.5h 返回按钮可点击、标题文字渲染正确、装饰元素显示正常
T-004 塔罗牌选择器 1h 三张卡牌可点击选择,选中状态高亮显示,卡牌文字标签正确
T-005 用户输入表单 1h 问题类型和抽牌数输入框可正常输入,onChange 回调正确更新状态
T-006 占卜按钮与交互 0.5h T-002, T-005 点击按钮调用 Service 并更新结果,按钮样式正确
T-007 结果展示区域 1.5h T-001, T-006 所有结果字段正确展示,卡牌列表渲染正常,条件渲染逻辑正确
T-008 颜色与主题配置 0.5h T-003 深紫色背景、金色文字、紫色辅助色等视觉风格统一
T-009 页面路由注册 0.5h T-003 首页可跳转到本页面,返回按钮可回到首页
T-010 集成测试与验证 1h T-001~T-009 全流程可走通,无编译错误,UI 交互流畅

3.2 依赖关系图

T-001 (Model) ──→ T-002 (Service) ──→ T-006 (Button)
                                              ↑
T-005 (Input) ────────────────────────────────┘
                                              ↓
T-003 (Header) ──→ T-007 (Result) ←── T-006 (Button)
      ↑                    ↑
T-004 (Cards) ─────────────┘
      ↓
T-008 (Theme) ←── T-003, T-004, T-005, T-007
      ↓
T-009 (Router) ←── T-008
      ↓
T-010 (Test)

从依赖关系图中可以清晰地看出,T-001(Model)和 T-005(Input)是基础任务,没有前置依赖,可以并行开发。T-002(Service)和 T-006(Button)形成了一条依赖链,需要按顺序完成。T-007(Result)是依赖最多的任务,需要等前面的任务都完成后才能开始,但这也意味着它是最容易验证整体功能完整性的任务。

3.3 原子任务的 ArkTS 实现要点

T-001:数据模型类定义

在 ArkTS 中定义数据模型时,需要注意 ArkTS 与标准 TypeScript 的几个关键差异:

  1. 不支持接口中的构造函数签名:Model 使用 class 而非 interface 定义,因为 ArkTS 不支持对象类型中的构造函数签名。这意味着所有数据实体都必须定义为 class,而非 interface。
  2. 不支持条件类型别名:不能使用 type MyType<T> = T extends string ? ... : ... 语法,必须显式定义带约束的新类型。这要求开发者在设计阶段就明确所有可能的类型变体。
  3. 不支持映射类型:不能使用 { [K in keyof T]: ... } 语法,需使用常规类实现。这意味着无法通过类型运算动态生成新类型,所有类型必须显式定义。
T-004:塔罗牌选择器

塔罗牌选择器的 ArkTS 实现使用了 ForEach 组件进行列表渲染。这是 ArkUI 中列表渲染的核心组件,类似于 React 中的 Array.map

Row() {
  ForEach([0, 1, 2], (i: number) => {
    Column() {
      Text(i === this.selectedCard ? '🃏' : '⬜')
        .fontSize(32)
        .opacity(i === this.selectedCard ? 1 : 0.6)
      Text(['过去', '现在', '未来'][i])
        .fontSize(9)
        .fontColor('#C4B5FD')
    }
    .margin({ left: 8, right: 8 })
    .onClick(() => { this.selectedCard = i })
  }, (i: number) => i.toString())
}

这里需要注意 ArkTS 的约束:

  • 不支持解构ForEach 的回调参数使用 (i: number) 而非解构形式。标准 TypeScript 中常见的 ({ index, item }) 写法在 ArkTS 中不可用。
  • 不支持 JSX:使用纯 ArkTS 的链式调用构建 UI,而非 JSX 语法。ArkUI 的链式调用 API 设计非常直观,每个方法都返回组件本身,可以连续调用。
  • ForEach 需要 key 生成器:第三个参数 (i: number) => i.toString() 提供唯一的 key 值,用于优化列表的 diff 更新性能。这与 React 中 key prop 的作用类似。
T-005:用户输入表单

输入表单使用 TextInput 组件,配合 onChange 事件更新状态。TextInput 是 ArkUI 中最常用的输入组件,支持占位符、自定义样式、事件回调等:

TextInput({ placeholder: '请输入问题类型' })
  .fontSize(13)
  .height(40)
  .backgroundColor('rgba(76, 29, 149, 0.3)')
  .borderRadius(8)
  .fontColor('#E0E7FF')
  .placeholderColor('#6D28D9')
  .border({ width: 1, color: 'rgba(196, 181, 253, 0.3)' })
  .padding({ left: 12, right: 12 })
  .onChange((val: string) => { this.inputData['问题类型'] = val })

这里需要注意:ArkTS 不支持通过索引访问对象字段(obj["field"]),但 Record<string, Object> 是标准库中的泛型容器类型,它支持通过 container[key] 语法访问元素,这与 Int32Array 等类型化数组一样是例外情况。这是 ArkTS 中一个容易混淆的地方——同样的 obj["key"] 语法,对于普通对象是编译错误,对于 Record 类型却是合法的。

T-007:结果展示区域

结果展示区域使用条件渲染,通过 if (this.showResult && this.resultData !== null) 控制显隐。条件渲染是 ArkUI 中控制 UI 组件创建和销毁的标准方式:

if (this.showResult && this.resultData !== null) {
  Column() {
    Text('🔮 占卜结果')
      .fontSize(16)
      .fontWeight(FontWeight.Bold)
      .fontColor('#FFD700')
      .margin({ bottom: 12 })

    // 卡牌列表
    if (this.resultData.cards) {
      ForEach(this.resultData.cards, (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('Name: ').fontSize(12).fontWeight(FontWeight.Medium).fontColor('#666666')
      Text(this.resultData.name).fontSize(12).fontColor('#333333')
    }
    .width('100%')
    .padding({ top: 4, bottom: 4 })
    // ... position, slot, upright_meaning, reversed_meaning,
    //     interpretation, overall, advice, disclaimer 等字段类似
  }
  // ...
}

3.4 并行开发策略

基于原子任务的依赖关系,我们可以制定以下并行开发策略:

  1. 第一轮(并行):T-001(Model)、T-003(Header)、T-004(Cards)、T-005(Input)可以同时开始,它们之间没有依赖关系。
  2. 第二轮(依赖等待):T-002(Service)依赖 T-001;T-008(Theme)依赖 T-003。
  3. 第三轮(集成):T-006(Button)依赖 T-002 和 T-005;T-009(Router)依赖 T-008。
  4. 第四轮(结果展示):T-007(Result)依赖 T-001 和 T-006。
  5. 第五轮(验证):T-010(Test)依赖所有前置任务。

四、审批阶段(Approve)

审批阶段是对前面各阶段成果进行审核和批准的关键环节。在本项目中,我们通过代码审查、架构合规性检查、语法约束验证三个维度进行质量把关。审批阶段的目的不是"找茬",而是通过集体智慧发现潜在问题,确保代码质量和项目可持续性。

4.1 代码审查清单

4.1.1 ArkTS 语法合规审查
检查项 状态 说明
any/unknown 类型 ✅ 通过 所有变量使用显式类型标注,如 stringnumberboolean
无解构赋值 ✅ 通过 使用临时变量逐字段操作,如 let val = obj.field 而非 let { field } = obj
Function.bind/call/apply ✅ 通过 使用传统 OOP 风格的 this 语义,所有回调使用箭头函数
for...in 遍历 ✅ 通过 使用 ForEach 组件和常规 for 循环进行迭代
无索引签名 ✅ 通过 使用 Record<string, Object> 容器替代索引签名
as const 断言 ✅ 通过 使用显式类型标注,如 let x: string = 'hello' 而非 let x = 'hello' as const
无对象字面量作为类型声明 ✅ 通过 使用显式 class 声明,如 class AI塔罗占卜Data { ... }
import 在文件开头 ✅ 通过 所有 import 语句在文件顶部,符合 ArkTS 编译要求
# 私有标识符 ✅ 通过 使用 private 关键字,如 private service: AI塔罗占卜Service
Function.apply/call ✅ 通过 使用传统 OOP 风格的方法调用
4.1.2 架构合规审查
  1. 分层清晰性:Page(表现层)→ Service(服务层)→ Model(数据层)三层职责明确,无跨层调用。Page 只调用 Service,Service 只操作 Model,不存在 Page 直接操作 Model 或 Service 访问 UI 组件的情况。
  2. 模块独立性:三个文件位于同一目录 apps/AI塔罗占卜/,通过相对路径导入,无循环依赖。每个文件只导入自己需要的模块,Page.ets 导入 ModelServiceService.ets 只导入 Model
  3. 与现有架构一致:与项目中其他应用的架构模式完全一致,均采用 Page + Model + Service 的三层结构。通过对比其他应用的代码结构,可以发现它们都遵循相同的模式。
  4. 路由注册正确性:在 apps.json 中注册的 page 路径与实际文件路径一致,确保 router 可正确跳转。"page": "apps/AI塔罗占卜/AI塔罗占卜Page" 与文件系统中 entry/src/main/ets/apps/AI塔罗占卜/AI塔罗占卜Page.ets 的路径对应。
4.1.3 视觉设计审查

视觉设计遵循神秘学风格,具体审查要点:

  • 色彩体系:深紫色背景(#1A0A2E)营造神秘氛围,金色文字(#FFD700)突出关键信息,紫色调辅助色(#C4B5FD#7C3AED)保持视觉统一。整个页面使用不超过 5 种颜色,确保视觉风格的一致性。
  • 排版布局:信息层级清晰,标题、输入区、按钮、结果区依次排列,符合用户操作流程。顶部导航栏使用 Row + Blank 实现左右对称布局,内容区域使用 Scroll 包裹确保在内容过多时可滚动。
  • 交互反馈:按钮使用阴影效果(shadow({ radius: 10, color: '#7C3AED66', offsetY: 4 }))提供立体感,卡牌使用透明度变化(opacity)表示选中状态。这些细节提升了用户体验,使交互反馈更加直观。

4.2 编译时验证

ArkTS 是静态类型语言,编译时验证是确保代码质量的关键环节。以下是在开发过程中遇到的典型编译错误及解决方案:

问题 1:Record 类型索引访问

// 错误写法(如果用于非 Record 类型)
this.inputData['问题类型'] = val  // 编译错误:不支持索引访问

// 正确写法
// 使用 Record<string, Object> 类型,这是标准库支持的容器
this.inputData['问题类型'] = val  // 正确:Record 类型支持索引访问

关键理解:ArkTS 不支持通过索引访问对象字段(obj["field"]),但 Record<string, Object> 作为标准库中的泛型容器类型,是例外情况。同样,Int32Array 等类型化数组也支持 container[index] 语法。这个例外机制的设计意图是:Record 和类型化数组本质上就是为索引访问而设计的数据结构,而普通对象的字段在编译时就已经确定,无需运行时索引访问。

问题 2:构造函数中声明字段

// 错误写法
class AI塔罗占卜Data {
  constructor() {
    this.cards: string[] = []  // 编译错误:不支持在构造函数中声明类字段
  }
}

// 正确写法
class AI塔罗占卜Data {
  cards: string[] = []  // 在类声明内部声明字段
  constructor() {
    this.cards = []  // 在构造函数中为已声明的字段赋值
  }
}

问题 3:函数返回类型推断

// 可能导致编译错误
generateData(input: Record<string, Object>) {
  return new AI塔罗占卜Data()  // 如果返回类型被省略,可能发生编译时错误
}

// 正确写法:显式指定返回类型
generateData(input: Record<string, Object>): AI塔罗占卜Data {
  return new AI塔罗占卜Data()
}

ArkTS 的规则是:当 return 语句中的表达式是对返回类型被省略的函数或方法的调用时

Logo

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

更多推荐