# HarmonyOS NEXT API 24 实战:打造一个有温度的 AI 旅游规划助手


本文基于 HarmonyOS NEXT API 24(SDK 6.1.1),完整记录了一款"旅游规划助手"AI 应用的开发全过程。不同于传统的问答工具,本应用通过拟人化角色设定、趣味交互细节和丰富的动效反馈,让 AI 对话"活"了起来。涵盖 Stage 模型、ArkTS 声明式 UI、SSE 流式解析、状态管理、定时轮播、自定义 Builder 组件等核心技术点。
一、从工具到伙伴:AI 应用的"温度"设计
1.1 为什么需要"有温度"的 AI 交互
在移动应用日益同质化的今天,用户对 AI 助手的需求已经从"能回答问题"升级为"愿意和它聊天"。一个冷冰冰的对话框很难留住用户,而一个有性格、有情绪、会开玩笑的 AI 伙伴却能让人产生情感连接。
本项目将 AI 设定为 “旅行小太阳 🌞”——一个热情、贴心、还有点幽默的旅行规划师。她不是机械地输出攻略,而是像朋友一样聊天、推荐、给建议。
1.2 项目技术选型
| 技术层 | 选型 | 版本/说明 |
|---|---|---|
| 操作系统 | HarmonyOS NEXT | API 24 (SDK 6.1.1) |
| 开发语言 | ArkTS | Huawei 声明式语言 |
| UI 框架 | ArkUI | 声明式 UI + @State 响应式 |
| 应用模型 | Stage 模型 | 推荐标准模型 |
| 网络请求 | @kit.NetworkKit |
原生 HTTP 库 |
| AI 模型 | DeepSeek-V3 | GitCode API 接入 |
| 通信协议 | SSE | Server-Sent Events 流式推送 |
| 构建工具 | hvigor | 6.24.2 版本 |
1.3 项目目录结构
Demo022/
├── AppScope/
│ ├── app.json5 # 应用配置(bundleName、版本等)
│ └── resources/base/element/
│ └── string.json # "旅游规划助手" 应用名
├── entry/
│ ├── build-profile.json5 # 模块构建配置
│ ├── oh-package.json5 # 依赖声明
│ └── src/main/
│ ├── ets/
│ │ ├── AIChatService.ets # 核心网络层(SSE + 流式解析)
│ │ ├── entryability/
│ │ │ └── EntryAbility.ets # Ability 生命周期
│ │ └── pages/
│ │ └── Index.ets # 主界面(507行,含全部交互逻辑)
│ ├── module.json5 # 模块注册
│ └── resources/
├── hvigor/ # 构建配置
└── build-profile.json5 # 全局构建(targetSdk: 24)
二、Stage 模型与 Ability 生命周期
2.1 Stage 模型简介
HarmonyOS NEXT 的 Stage 模型将应用逻辑拆分为 Ability 和 AbilityStage 两层。每个 Ability 代表一个独立的功能入口,拥有完整的生命周期方法:
onCreate → onWindowStageCreate → onForeground → 运行中
↑ ↓
└──── onBackground ← onWindowStageDestroy ← onDestroy
2.2 EntryAbility 实现
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
const DOMAIN = 0x0000;
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 跟随系统深浅色模式
try {
this.context.getApplicationContext()
.setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
} catch (err) {
hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode: %{public}s', JSON.stringify(err));
}
}
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(DOMAIN, 'testTag', 'Failed: %{public}s', JSON.stringify(err));
return;
}
});
}
}
关键点:
setColorMode(COLOR_MODE_NOT_SET)让应用跟随系统自动切换深色/浅色模式loadContent('pages/Index', callback)加载主页面- 使用
hilog进行分级日志输出,DOMAIN 参数用于日志分类
三、AI 网络层深度解析 — AIChatService
3.1 类型系统设计
良好的类型定义是 ArkTS 代码质量的基石:
// 消息结构
export interface ChatMessage {
role: string; // "system" | "user" | "assistant"
content: string;
}
// 请求体
export interface ChatCompletionRequest {
model: string;
messages: ChatMessage[];
stream: boolean;
max_tokens: number;
temperature: number;
top_p: number;
frequency_penalty: number;
thinking_budget: number;
}
// 回调接口 — 解耦网络层与 UI 层
export interface AICallbacks {
onData: (text: string) => void; // 流式 token 到达
onDone: () => void; // 响应结束
onError: (errMsg: string) => void; // 错误处理
}
设计亮点:通过 AICallbacks 接口将网络层与 UI 层完全解耦,网络层无需关心 UI 如何渲染,UI 层只需实现三个回调方法。
3.2 提示词工程 — 塑造"旅行小太阳"人格
系统提示词(System Prompt)是决定 AI 回复质量和风格的灵魂。本项目设计了基于角色扮演的提示词:
const SYSTEM_PROMPT: string =
'你是一位充满热情的旅行规划专家,叫做"旅行小太阳",擅长为用户打造独一无二的旅行体验。' +
'你熟知全球热门和小众目的地,对不同季节、不同人群的旅行需求有独到见解。\n\n' +
'以下是你的5大超能力:\n' +
'1. 🌟 梦想解码:通过几个简单问题就能精准捕捉用户的旅行幻想\n' +
'2. 🗺️ 路线魔法:设计每日行程时像讲故事一样,有起承转合、有惊喜彩蛋\n' +
'3. 💰 预算炼金术:不管预算多少都能变出最物超所值的方案\n' +
'4. 🎯 隐藏宝藏雷达:推荐本地人私藏的秘境和小众体验\n' +
'5. 📋 贴心小棉袄:主动提醒天气、穿搭、签证等\n\n' +
'回复风格:用热情活泼的语气,适当加入😊✈️🏖️🎒等表情符号';
提示词工程技巧:
- 角色命名:“旅行小太阳”——有温度、有记忆点的名字
- 超能力框架:5 个能力用 🌟🗺️💰🎯📋 符号化,易于 AI 理解和记忆
- 风格约束:“像和朋友聊天一样轻松有趣”,控制回复语气
- 结构建议:“小标题、emoji 和换行”,控制排版风格
3.3 SSE 流式协议与解析
SSE (Server-Sent Events) 是 AI API 的标准流式传输协议。服务器将数据以 data: {...}\n\n 格式逐行推送:
data: {"choices":[{"delta":{"content":"你"}}]}
data: {"choices":[{"delta":{"content":"好"}]}]
data: [DONE]
逐行解析器
function parseSSEDataLine(line: string): string | null {
const jsonStr = line.slice(5).trim(); // 去掉 "data:" 前缀
if (!jsonStr) return null;
try {
const parsed = JSON.parse(jsonStr) as Record<string, Object>;
const choices = parsed.choices as Object[];
if (choices && choices.length > 0) {
const choice = choices[0] as Record<string, Object>;
// 兼容 delta(流式)和 message(非流式)
const delta = choice.delta as Record<string, Object>;
if (delta) return delta.content as string;
const message = choice.message as Record<string, Object>;
if (message) return message.content as string;
}
} catch (_) { /* 跳过非法行 */ }
return null;
}
关键细节:
line.slice(5)精确移除 “data:” 前缀- 同时兼容
delta(流式逐 token)和message(非流式全量)两种格式 - 异常捕获保证单行解析失败不影响后续处理
三层回退容错
考虑到 HarmonyOS 不同版本对 SSE 事件的支持差异,实现了 3 层容错:
| 优先级 | 机制 | 触发条件 | 体验 |
|---|---|---|---|
| 1(首选) | dataReceive 事件 |
框架支持 SSE 逐事件推送 | 打字机效果 ✅ |
| 2(次选) | 完整响应按 SSE 解析 | dataReceive 未触发 |
一次显示全文 |
| 3(兜底) | 完整响应按 JSON 解析 | 服务端返回非流式格式 | 一次显示全文 |
// 非流式回退机制
if (!receivedAnyData && resp.result) {
const bodyStr = typeof resp.result === 'string'
? resp.result
: arrayBufferToString(resp.result as ArrayBuffer);
// 优先解析 SSE 格式
const sseContent = parseFullSSEBody(bodyStr);
if (sseContent) {
callbacks.onData(sseContent);
} else {
// 回退到 JSON 格式
const jsonContent = parseNonStreamingBody(bodyStr);
if (jsonContent) {
callbacks.onData(jsonContent);
} else {
callbacks.onError(`无法解析响应`);
return;
}
}
}
3.4 请求取消与资源管理
let httpRequestTask: http.HttpRequest | null = null;
export function cancelAI(): void {
if (httpRequestTask) {
httpRequestTask.destroy(); // 释放网络资源
httpRequestTask = null;
}
}
每次发起新请求前自动取消上一次请求,避免多请求并发导致 UI 状态错乱。
四、有趣的 UI 设计 — Index.ets 全解析
本章是核心亮点,将详细介绍如何让 AI 对话界面"有趣"起来。
4.1 整体布局架构
Stack (根容器,支持背景叠加)
├── Column (背景渐变层 — 浅天蓝→爱丽丝蓝→暖米白)
└── Column (前景内容层)
├── Column (顶部标题栏)
│ ├── Row (✈️ 小太阳旅行规划 🌞 — 标题文字)
│ └── Text (轮播旅行Tips)
├── Scroll (消息列表)
│ └── Column
│ ├── ForEach → MessageBubble(item) [消息气泡]
│ ├── if → AIBubble() [流式内容] / LoadingIndicator() [加载]
│ └── if → QuickTagRow() [快捷标签,仅对话开始时显示]
└── Row (底部输入区)
├── TextInput (圆角输入框)
├── Blank (间距)
└── Button (✈️ 发送/取消)
4.2 渐变背景与色彩体系
// 背景渐变
Column()
.linearGradient({
direction: GradientDirection.Bottom,
colors: [
['#E8F4FD', 0.0], // 浅天蓝
['#F0F8FF', 0.5], // 爱丽丝蓝
['#FFF8F0', 1.0], // 暖米白
],
})
// 顶部栏渐变(天空蓝 → 日落橙)
.linearGradient({
direction: GradientDirection.Right,
colors: [
['#4A90D9', 0.0], // 天空蓝
['#5BA0E8', 0.5], // 亮蓝
['#FF8C5A', 1.0], // 日落橙
],
})
配色心理学:蓝色系传递"天空、旅行、自由"的感觉,暖橙色渐变代表"日落、温暖、温度",整体营造出轻松愉快的旅行氛围。
4.3 轮播旅行 Tips — setInterval 定时器
const FUN_TIPS: string[] = [
'💡 淡季出行不仅省钱,游玩体验翻倍哦!',
'💡 当地菜市场是体验地道文化的最佳地点!',
'💡 周二下午买机票通常最便宜 ✈️',
'💡 用一瓶水就能拍出绝美倒影照 📸',
'💡 旅行中迷路往往是最难忘的经历 😄',
];
@State tipIndex: number = 0;
private tipTimer: number = -1;
aboutToAppear(): void {
this.startTipRotation();
}
aboutToDisappear(): void {
this.stopTipRotation();
}
startTipRotation(): void {
this.tipTimer = setInterval(() => {
this.tipIndex = (this.tipIndex + 1) % FUN_TIPS.length;
}, 5000); // 每 5 秒切换一条
}
stopTipRotation(): void {
if (this.tipTimer !== -1) {
clearInterval(this.tipTimer);
this.tipTimer = -1;
}
}
生命周期管理要点:
aboutToAppear中启动定时器(页面可见时)aboutToDisappear中停止定时器(页面不可见时)- 防止页面隐藏后定时器仍在执行,浪费性能和导致状态异常
4.4 快捷提问标签系统 — 降低用户门槛
6 个旅行主题标签,点击即问,无需输入:
const QUICK_TAGS: QuickTag[] = [
{ icon: '🏖️', text: '周末去哪玩' },
{ icon: '🎓', text: '毕业旅行' },
{ icon: '👨👩👧👦', text: '亲子游推荐' },
{ icon: '💰', text: '穷游攻略' },
{ icon: '🌸', text: '赏花好去处' },
{ icon: '🏔️', text: '小众秘境' },
];
标签网格布局
ArkUI 的 ForEach 不支持直接二维渲染,通过 chunkArray 将 6 个标签分为 2 行 × 3 列:
@Builder QuickTagRow() {
Column() {
Text('💬 试试问我这些:')
.fontSize(13).fontColor('#888888')
.margin({ bottom: 8 })
ForEach(this.chunkArray(QUICK_TAGS, 3), (row: QuickTag[]) => {
Row({ space: 8 }) {
ForEach(row, (tag: QuickTag) => {
Button() {
Text(`${tag.icon} ${tag.text}`)
.fontSize(13).fontColor('#4A90D9')
}
.height(36).borderRadius(18)
.backgroundColor('#E8F2FF')
.border({ width: 1, color: '#B8D4F0' })
.layoutWeight(1)
.onClick(() => this.handleQuickTag(tag.text))
})
}
.width('100%').margin({ bottom: 6 })
})
}
}
// 数组分块工具函数
chunkArray(arr: QuickTag[], size: number): QuickTag[][] {
const result: QuickTag[][] = [];
for (let i = 0; i < arr.length; i += size) {
result.push(arr.slice(i, i + size));
}
return result;
}
设计决策:
- 标签只在
messages.length <= 2时显示(即未开始或刚发送一条消息),避免干扰后续对话 - 点击后
showQuickTags = false隐藏标签,为用户输入腾出空间 - 蓝色边框(
#B8D4F0)+ 浅蓝背景(#E8F2FF),与整体旅行主题一致
4.5 消息气泡设计
用户消息 — 暖橙色右对齐
if (item.role === 'user') {
Blank() // 占位,右对齐
Column() {
Text(item.content).fontSize(15).lineHeight(22)
}
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
.backgroundColor('#FFE0B2') // 暖橙色
.borderRadius({ topLeft: 18, topRight: 4, bottomLeft: 18, bottomRight: 18 })
.constraintSize({ maxWidth: '75%' })
.shadow({ radius: 2, color: '#15000000', offsetY: 1 })
}
AI 消息 — 白色左对齐 + 头像
Column() {
Row() {
Text('🌞').fontSize(20) // 小太阳头像
Text(' 旅行小太阳').fontSize(13).fontColor('#4A90D9')
}
.margin({ bottom: 6 })
Text(item.content).fontSize(15).lineHeight(24)
}
.padding({ left: 16, right: 16, top: 12, bottom: 14 })
.backgroundColor('#FFFFFF')
.borderRadius({ topLeft: 4, topRight: 18, bottomLeft: 18, bottomRight: 18 })
.constraintSize({ maxWidth: '82%' })
.shadow({ radius: 3, color: '#15000000', offsetY: 1 })
气泡造型技巧:
- 非对称
borderRadius:靠近对方的一侧小圆角(4px),营造"气泡尾"效果 - 左下角圆形(用户) / 左上角圆形(AI),视觉上形成对话感的对仗
shadow阴影增加立体层次感constraintSize({ maxWidth })限制气泡最大宽度,避免文字过长
4.6 趣味加载动画
10 条随机加载语
const LOADING_TIPS: string[] = [
'正在翻看旅行攻略书... 📚',
'给小行李箱装知识... 🧳',
'飞机正在起飞中... ✈️',
'向本地人打听隐藏景点... 🗣️',
'用地图画出一条有趣的路线... 🗺️',
'查询当地天气和穿搭建议... 🌤️',
'搜索地道美食清单... 🍜',
'计算最佳出行时间... ⏰',
'偷偷加了一些小众彩蛋... 🎁',
'给行程配上BGM... 🎵',
];
加载动画组件
@Builder LoadingIndicator() {
Row() {
Column() {
Row() {
Text('🌞').fontSize(20)
Text(' 旅行小太阳').fontSize(13).fontColor('#4A90D9')
}
.margin({ bottom: 8 })
Row() {
Text('✈️') .fontSize(16).rotate({ angle: -15 }).margin({ right: 4 })
LoadingProgress().width(20).height(20).color('#4A90D9')
Text(LOADING_TIPS[this.loadingTipIndex]).fontSize(13).fontColor('#666666')
}
}
.padding(...).backgroundColor('#FFFFFF')
.borderRadius({ topLeft: 4, topRight: 18, ... })
.shadow(...)
Blank()
}
}
有趣设计点:
- 小飞机
✈️添加rotate({ angle: -15 })倾斜效果,模拟飞行姿态 LoadingProgress是 HarmonyOS 原生的旋转加载组件- 每次调用 AI 前执行
getRandomLoadingTip()随机选取一条,每次加载都有新鲜感
4.7 发送按钮状态机
发送按钮有 3 种状态,通过 @State 驱动:
| 状态 | 条件 | 外观 | 行为 |
|---|---|---|---|
| 空闲 + 有输入 | inputText.trim() && !isLoading |
蓝色背景 + ✈️ | 点击发送 |
| 空闲 + 无输入 | !inputText.trim() |
灰色背景 + ✈️ | 禁用 |
| 加载中 | isLoading |
蓝色背景 + LoadingProgress |
点击取消请求 |
Button() {
if (this.isLoading) {
LoadingProgress().width(22).height(22).color('#FFFFFF')
} else {
Text('✈️').fontSize(20)
}
}
.backgroundColor(
this.inputText.trim() && !this.isLoading ? '#4A90D9' : '#CCCCCC'
)
.enabled(!!this.inputText.trim() && !this.isLoading)
4.8 发送消息完整流程
sendMessage(): void {
const text = this.inputText.trim();
if (!text || this.isLoading) return;
// 1. 隐藏快捷标签
this.showQuickTags = false;
// 2. 添加用户消息并清空输入
this.messages.push({ role: 'user', content: text });
this.inputText = '';
// 3. 滚动到底部
setTimeout(() => this.scroller.scrollEdge(Edge.Bottom), 100);
// 4. 切换加载状态
this.isLoading = true;
this.currentAIResponse = '';
this.getRandomLoadingTip();
// 5. 构建历史消息(不含 system prompt)
const chatHistory: ChatMessage[] = this.messages.map<ChatMessage>((m) => ({
role: m.role,
content: m.content,
}));
// 6. 调用 AI
queryAI({
onData: (text: string) => {
this.currentAIResponse += text;
setTimeout(() => this.scroller.scrollEdge(Edge.Bottom), 50);
},
onDone: () => {
if (this.currentAIResponse) {
this.messages.push({ role: 'assistant', content: this.currentAIResponse });
}
this.isLoading = false;
this.currentAIResponse = '';
setTimeout(() => this.scroller.scrollEdge(Edge.Bottom), 100);
},
onError: (errMsg: string) => {
this.messages.push({
role: 'assistant',
content: '🌧️ 哎呀,旅行遇到了一点小状况:' + errMsg + '\n\n再试一次吧~'
});
this.isLoading = false;
this.currentAIResponse = '';
},
}, chatHistory);
}
关键设计决策:
map<ChatMessage>()显式泛型,满足 ArkTS 严格类型检查setTimeout延迟 50-100ms 再滚屏,等待 DOM 更新完成onDone中将currentAIResponse作为完整消息推入列表,而非逐 token 推入- 错误友好提示:“🌧️ 旅行遇到小状况” 保持角色一致性
五、ArkTS 严格模式与避坑指南
5.1 常见编译错误及解决方案
错误 1:arkts-no-untyped-obj-literals
对象字面量必须对应显式声明的接口
// ❌ 错误
this.messages.map((m) => ({ role: m.role, content: m.content }));
// ✅ 正确
this.messages.map<ChatMessage>((m) => ({
role: m.role, content: m.content,
}));
错误 2:maxWidth 在 ColumnAttribute 上不存在
// ❌ 错误
Column().maxWidth('75%')
// ✅ 正确
Column().constraintSize({ maxWidth: '75%' })
constraintSize 是 ArkUI 的通用尺寸约束属性,支持 minWidth、maxWidth、minHeight、maxHeight。
错误 3:on 方法已弃用
// 当前版本可用但产生警告
httpRequest.on('dataReceive', callback);
5.2 编译输出解读
hvigorw assembleHap --mode module -p module=entry@default -p product=default
成功输出示例:
BUILD SUCCESSFUL in 2s 379ms
WARN: ArkTS:WARN - 'on' has been deprecated. # 可忽略,兼容旧API
WARN: ArkTS:WARN - need INTERNET permission # 提示性警告
WARN: Will skip sign 'hos_hap' # 开发模式无签名
六、性能优化与编码实践
6.1 状态管理最佳实践
- 最小化 @State:只有影响 UI 渲染的变量才标记为 @State,避免不必要重绘
- 条件渲染优先:使用
if而非Visibility控制组件显隐,减少组件树复杂度 - ForEach key:第三个参数
(item, index) => index.toString()提供稳定 key,帮助框架复用节点
6.2 网络优化
- 连接取消:每次新请求前销毁旧连接,防止内存泄漏
- 超时控制:
connectTimeout: 30s、readTimeout: 120s - 数据解码优化:
arrayBufferToString使用String.fromCharCode逐字节转换,避免大字符串拼接性能问题
6.3 内存管理
- 定时器生命周期:
aboutToAppear启动,aboutToDisappear停止 - 流式缓冲区清空:
onDone/onError中将currentAIResponse置空 - HTTP 资源释放:
httpRequest.destroy()及时释放网络资源
6.4 ArkTS 编码规范
| 规范 | 说明 | 示例 |
|---|---|---|
| 显式类型 | 所有变量、参数标注类型 | const text: string = '' |
| 接口先行 | 先定义接口再实现 | interface AICallbacks { ... } |
| 模块化 | 网络层与 UI 层分离 | AIChatService ↔ Index.ets |
| 常量提取 | 魔法数字/字符串提取为常量 | LOADING_TIPS, FUN_TIPS |
七、构建与部署
7.1 开发构建
hvigorw assembleHap --mode module -p module=entry@default -p product=default
7.2 真机安装
hdc install entry/build/default/outputs/default/entry-default-unsigned.hap
7.3 常见问题排查
| 问题 | 原因 | 解决方案 |
|---|---|---|
on 弃用警告 |
API 24 中 on() 标记弃用 |
可忽略,向下兼容 |
| INTERNET 权限 | 未声明网络权限 | module.json5 添加 ohos.permission.INTERNET |
| 签名警告 | 未配置签名证书 | 开发模式可忽略 |
| SSE 不触发 | 部分 HarmonyOS 版本兼容性 | 非流式回退机制自动处理 |
八、总结与扩展方向
8.1 项目特色总结
- 拟人化 AI 角色:“旅行小太阳 🌞” 让 AI 不再冷冰冰,5 大超能力设定让回复更有趣
- 三层 SSE 容错:适配不同 HarmonyOS 版本的网络行为差异
- 快捷标签降低门槛:6 个旅行主题标签,一键提问
- 轮播 Tips 增加粘性:5 秒切换实用旅行小贴士,让用户有意外收获
- 趣味加载语:10 条随机加载语 + 小飞机 ✈️ 倾斜动效,等待不再无聊
- 暖色调视觉体系:天空蓝 → 日落橙渐变 + 暖橙气泡,传递"温暖旅行"的品牌感知
8.2 可扩展方向
- 多轮上下文优化:当前历史消息完整传递,可针对性优化上下文窗口
- 图片/位置分享:利用 HarmonyOS 分布式能力分享景点图片和地图定位
- 离线知识库:集成本地旅行攻略,无网络也能提供基础建议
- 语音交互:集成
@kit.VoiceKit,支持语音输入旅行需求 - 多设备协同:手机规划 → 手表查看 → 平板展示,全场景覆盖
8.3 写在最后
本项目的核心启示是:AI 应用的竞争力不在技术有多复杂,而在细节有多用心。从"旅行小太阳"的角色设定,到轮播 Tips 的趣味选择,再到每次加载时不同的小彩蛋——正是这些看似不起眼的细节,让一个普通的问答工具变成了一个有温度、有性格的旅行伙伴。
HarmonyOS NEXT 的 ArkTS 和 ArkUI 提供了现代化声明式开发体验,希望本文能为 HarmonyOS 开发者带来实用的参考和启发。
本文基于 HarmonyOS NEXT API 24(SDK 6.1.1)编写,于 2026 年 6 月完成。部分 API 可能随版本更新而变化,请以华为官方文档为准。
更多推荐


所有评论(0)