AI塔罗占卜:HarmonyOS 智能应用全流程开发实战
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.json5 中 dependencies 为空,这说明项目完全依赖 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 = ''
}
}
设计考量:
- 所有字段显式声明:ArkTS 不支持动态字段声明和访问,因此所有字段必须在类中立即声明,并赋予默认值。这与标准 TypeScript 中可以在构造函数中动态添加属性的做法形成鲜明对比。
- 构造函数中统一初始化:虽然 ArkTS 不支持在构造函数中声明类字段,但可以在构造函数中为已声明的字段赋值,确保数据完整性。这种"声明与初始化分离"的模式是 ArkTS 的一个重要特点。
- 字段类型明确:所有字段均为
string或string[]类型,避免使用any或unknown。如果未来需要支持更多数据类型,可以通过联合类型(如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 重新渲染高亮状态
这种单向数据流模式的优势在于:
- 可预测性:数据变更的路径是单向的,不会出现循环更新。
- 可调试性:每个状态变更都有明确的触发源,方便定位问题。
- 性能优化:ArkUI 框架可以精确追踪哪些组件依赖哪些状态,实现最小化重渲染。
2.7 异常处理策略
在 ArkTS 中,异常处理遵循以下策略:
- 输入校验:在 Service 层对输入参数进行校验,确保数据合法性。例如,
String(input['question_type'] || '')确保即使输入为空也能得到一个空字符串而非 undefined。 - 空值安全:使用
null联合类型(如AI塔罗占卜Data | null)并在模板中通过if (this.resultData !== null)进行空值检查。ArkTS 要求显式处理空值,不会出现 JavaScript 中常见的Cannot read property of undefined错误。 - 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 的几个关键差异:
- 不支持接口中的构造函数签名:Model 使用 class 而非 interface 定义,因为 ArkTS 不支持对象类型中的构造函数签名。这意味着所有数据实体都必须定义为 class,而非 interface。
- 不支持条件类型别名:不能使用
type MyType<T> = T extends string ? ... : ...语法,必须显式定义带约束的新类型。这要求开发者在设计阶段就明确所有可能的类型变体。 - 不支持映射类型:不能使用
{ [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 中keyprop 的作用类似。
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 并行开发策略
基于原子任务的依赖关系,我们可以制定以下并行开发策略:
- 第一轮(并行):T-001(Model)、T-003(Header)、T-004(Cards)、T-005(Input)可以同时开始,它们之间没有依赖关系。
- 第二轮(依赖等待):T-002(Service)依赖 T-001;T-008(Theme)依赖 T-003。
- 第三轮(集成):T-006(Button)依赖 T-002 和 T-005;T-009(Router)依赖 T-008。
- 第四轮(结果展示):T-007(Result)依赖 T-001 和 T-006。
- 第五轮(验证):T-010(Test)依赖所有前置任务。
四、审批阶段(Approve)
审批阶段是对前面各阶段成果进行审核和批准的关键环节。在本项目中,我们通过代码审查、架构合规性检查、语法约束验证三个维度进行质量把关。审批阶段的目的不是"找茬",而是通过集体智慧发现潜在问题,确保代码质量和项目可持续性。
4.1 代码审查清单
4.1.1 ArkTS 语法合规审查
| 检查项 | 状态 | 说明 |
|---|---|---|
无 any/unknown 类型 |
✅ 通过 | 所有变量使用显式类型标注,如 string、number、boolean 等 |
| 无解构赋值 | ✅ 通过 | 使用临时变量逐字段操作,如 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 架构合规审查
- 分层清晰性:Page(表现层)→ Service(服务层)→ Model(数据层)三层职责明确,无跨层调用。Page 只调用 Service,Service 只操作 Model,不存在 Page 直接操作 Model 或 Service 访问 UI 组件的情况。
- 模块独立性:三个文件位于同一目录
apps/AI塔罗占卜/,通过相对路径导入,无循环依赖。每个文件只导入自己需要的模块,Page.ets导入Model和Service,Service.ets只导入Model。 - 与现有架构一致:与项目中其他应用的架构模式完全一致,均采用 Page + Model + Service 的三层结构。通过对比其他应用的代码结构,可以发现它们都遵循相同的模式。
- 路由注册正确性:在
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 语句中的表达式是对返回类型被省略的函数或方法的调用时
更多推荐

所有评论(0)