HarmonyOS鸿蒙Next华为云AI Agent集成车票查询项目
HarmonyOS鸿蒙Next华为云AI Agent集成车票查询项目
华为云AI Agent集成鸿蒙车票查询项目文档
一、项目概述
1.1 项目背景
随着AI技术与移动应用的深度融合,本项目旨在通过华为云配置AI Agent实现智能车票查询能力,并基于鸿蒙操作系统(HarmonyOS)开发前端应用,为用户提供便捷的列车票务查询服务。
1.2 项目目标
- 在华为云平台配置AI Agent,实现车票查询、车次信息解析等核心能力
- 开发鸿蒙应用,通过前端页面与华为云AI Agent交互,完成车票查询功能
- 实现城市信息解析、令牌管理、数据存储等辅助功能,保障应用稳定性
二、技术栈
- 前端框架:HarmonyOS ArkUI(ETS语言)
- 后端服务:华为云AI Agent
- 网络通信:HTTP/HTTPS、SSE(Server-Sent Events)
- 数据存储:鸿蒙本地存储(Preferences)
- 开发工具:DevEco Studio、华为云控制台
三、华为云AI Agent配置步骤
3.1 登录华为云控制台
- 访问华为云官网(https://www.huaweicloud.com/),登录账号并进入控制台
- 搜索"AI Agent"服务,进入服务管理页面
3.2 创建AI Agent实例
- 点击"创建Agent",配置基本信息(名称、描述、所属区域等)
- 选择"自定义技能",配置车票查询相关能力:
- 定义输入参数:出发城市、到达城市、出发日期
- 定义输出格式:车次列表(包含车次号、出发时间、到达时间、余票信息等)
- 配置API访问凭证(Access Key、Secret Key),记录Agent访问地址(Endpoint)
3.3 测试AI Agent
通过华为云控制台的"在线调试"功能,输入测试参数(如"北京到上海,2025-10-25"),验证Agent是否能正确返回车票信息
四、鸿蒙应用开发架构
4.1 项目目录结构
ets/
├── entryability/ # 应用入口能力
├── pages/ # 页面组件
│ ├── API/ # 网络请求相关
│ │ └── http_ai.ets # 调用华为云AI Agent的HTTP工具
│ ├── Common/ # 公共模型与工具
│ │ └── Model/ # 数据模型定义
│ │ ├── cityModel.ets # 城市信息模型
│ │ ├── ticketModel.ets# 车票信息模型
│ │ └── tokenApi/ # 令牌相关模型
│ │ ├── ReceiveModel.ets # 令牌响应模型
│ │ └── TokenModel.ets # 令牌请求模型
│ └── Utils/ # 工具类
│ ├── getData.ets # 数据获取工具
│ ├── cityParse.ets # 城市信息解析工具
│ ├── getToken.ets # 令牌获取与管理
│ ├── MakeCall.ets # AI Agent调用封装
│ ├── SSEParser.ets # SSE响应解析工具
│ └── TicketDataStore.ets# 车票数据本地存储
├── index.ets # 应用首页
├── TrainHomePage.ets # 车票查询主页
└── TrainDetails.ets # 车次详情页
五、核心文件实现
5.1 entryability(应用入口)
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '[@kit](/user/kit).AbilityKit';
import { hilog } from '[@kit](/user/kit).PerformanceAnalysisKit';
import { window } from '[@kit](/user/kit).ArkUI';
import { util } from '[@kit](/user/kit).ArkTS';
import { BusinessError } from '[@kit](/user/kit).BasicServicesKit';
import { CityListResponse } from '../pages/Common/Model/cityModel';
import json from '@ohos.util.json';
import { notificationManager } from '[@kit](/user/kit).NotificationKit';
const TAG: string = '[PublishOperation]';
const DOMAIN_NUMBER: number = 0xFF00;
const DOMAIN = 0x0000;
export default class EntryAbility extends UIAbility {
private fruits: TextCascadePickerRangeContent[] = []
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
// city.json获取
try {
this.context.resourceManager.getRawFileContent("city.json")
.then((value: Uint8Array) => {
console.log("city初始数据:" + value);
let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true });
console.log("city通过解码器转为UTF-8后的数据:" + json.stringify(textDecoder));
let decodedData = textDecoder.decodeWithStream(value, { stream: false });
console.log("city完成解码后的数据:" + decodedData);
try {
let parseData: CityListResponse = JSON.parse(decodedData);
console.log("city通过JSON工具类解析后的数据:" + json.stringify(parseData));
for (let i = 0; i < parseData.cityList.cities.length; i++) {
this.fruits.push({
text: parseData.cityList.cities[i].name
})
}
console.log("city通过JSON工具类解析后放入数组的数据:" + JSON.stringify(this.fruits));
AppStorage.setOrCreate<TextCascadePickerRangeContent[]>('cityList', this.fruits)
} catch (error) {
console.error("promise getRawFileContent failed, error is " + error);
}
})
.catch((error: BusinessError) => {
console.error("getRawFileContent promise error is " + error);
});
} catch (error) {
let code = (error as BusinessError).code;
let message = (error as BusinessError).message;
console.error(`promise getRawFileContent failed, error code: ${code}, message: ${message}.`);
}
// 请求通知权限
notificationManager.isNotificationEnabled().then((data: boolean) => {
hilog.info(DOMAIN_NUMBER, TAG, "isNotificationEnabled success, data: " + JSON.stringify(data));
if(!data){
notificationManager.requestEnableNotification(this.context).then(() => {
hilog.info(DOMAIN_NUMBER, TAG, `[ANS] requestEnableNotification success`);
}).catch((err: BusinessError) => {
if(1600004 == err.code){
hilog.error(DOMAIN_NUMBER, TAG, `[ANS] requestEnableNotification refused, code is ${err.code}, message is ${err.message}`);
} else {
hilog.error(DOMAIN_NUMBER, TAG, `[ANS] requestEnableNotification failed, code is ${err.code}, message is ${err.message}`);
}
});
}
}).catch((err: BusinessError) => {
hilog.error(DOMAIN_NUMBER, TAG, `isNotificationEnabled fail, code is ${err.code}, message is ${err.message}`);
});
// 发布通知
let notificationRequest: notificationManager.NotificationRequest = {
id: 1,
content: {
notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT, // 普通文本类型通知
normal: {
title: 'test_title',
text: 'test_text',
additionalText: 'test_additionalText',
}
}
};
notificationManager.publish(notificationRequest, (err: BusinessError) => {
if (err) {
hilog.error(DOMAIN_NUMBER, TAG, `Failed to publish notification. Code is ${err.code}, message is ${err.message}`);
return;
}
hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded in publishing notification.');
});
}
onDestroy(): void {
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
});
}
onWindowStageDestroy():
void {
// Main window is destroyed, release UI related resources
hilog
.info
(
DOMAIN,
'testTag',
'%{public}s',
'Ability onWindowStageDestroy'
)
;
}
onForeground():
void {
// Ability has brought to foreground
hilog
.info
(
DOMAIN,
'testTag',
'%{public}s',
'Ability onForeground'
)
;
}
onBackground():
void {
// Ability has back to background
hilog
.info
(
DOMAIN,
'testTag',
'%{public}s',
'Ability onBackground'
)
;
}
}
5.2 页面组件
5.2.1 index.ets(应用首页)
import { router } from '[@kit](/user/kit).ArkUI'
import { getData } from './Utils/getData'
import { common } from '[@kit](/user/kit).AbilityKit'
import { AITicketResponse } from './Common/Model/ticketModel'
[@Entry](/user/Entry)
[@Component](/user/Component)
struct Index {
[@State](/user/State) cityList: TextCascadePickerRangeContent[] = []
[@State](/user/State) fromCityIndex: number = 0 // 出发地选择索引
[@State](/user/State) fromCityString: string = '郑州市' // 出发地
[@State](/user/State) toCityIndex: number = 0 // 目的地选择索引
[@State](/user/State) toCityString: string = '上海市' // 目的地
[@State](/user/State) selectedDate: Date = new Date() // 出发时间
[@State](/user/State) aiResponse: AITicketResponse | null = null
[@State](/user/State) isLoading: boolean = false
build() {
Column({ space: 10 }) {
// 出发地、目的地的选择和显示功能
Row({ space: 10 }) {
// 出发地
Column({ space: 10 }) {
Button('出发地')
.onClick(() => {
this.getUIContext().showTextPickerDialog({
range: AppStorage.get('cityList') as TextCascadePickerRangeContent[],
selected: this.fromCityIndex,
onChange: (value: TextPickerResult) => {
// 使用value属性获取选中的文本
this.fromCityString = value.value as string
},
onAccept: (value: TextPickerResult) => {
this.fromCityIndex = value.index as number
// 同时更新显示的文本
this.fromCityString = value.value as string
}
});
})
Text(this.fromCityString)
}
Text('——').fontWeight(FontWeight.Bold)
// 目的地
Column({ space: 10 }) {
Button('目的地')
.onClick(() => {
this.getUIContext().showTextPickerDialog({
range: AppStorage.get('cityList') as TextCascadePickerRangeContent[],
selected: this.toCityIndex,
onChange: (value: TextPickerResult) => {
// 使用value属性获取选中的文本
this.toCityString = value.value as string
},
onAccept: (value: TextPickerResult) => {
this.toCityIndex = value.index as number
// 同时更新显示的文本
this.toCityString = value.value as string
}
});
})
Text(this.toCityString)
}
}
.width('100%')
.justifyContent(FlexAlign.Center)
// 日期选择功能
Row({ space: 10 }) {
Text(this.selectedDate.toLocaleDateString() + '')
.padding({
left: 15,
right: 15,
top: 10,
bottom: 10
})
.borderRadius(20)
.fontColor('#ffffffff')
.backgroundColor('#007dfe')
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.getUIContext().showDatePickerDialog({
start: new Date(),
selected: this.selectedDate,
onDateChange: (value: Date) => {
this.selectedDate = value
}
})
})
}
// 查询详情标题功能
Button('查询车次')
.onClick(() => {
// 跳转页面详情
router.pushUrl({
url: 'pages/TrainDetails',
params: {
fromCity: this.fromCityString,
toCity: this.toCityString,
selectedDate: this.selectedDate.toLocaleDateString(),
aiResponse: this.aiResponse
}
}, (err) => {
if (err) {
console.error(`Invoke pushUrl failed, code is ${err.code}, message is ${err.message}`);
return;
}
console.info('Invoke pushUrl succeeded.');
})
})
Button('展示AI查询结果')
.onClick(async () => {
this.isLoading = true;
await getData(this.fromCityString, this.toCityString, this.selectedDate.toLocaleDateString(),
getContext(this) as common.UIAbilityContext)
.then((response) => {
this.aiResponse = response;
})
})
Text(this.aiResponse ? JSON.stringify(this.aiResponse) : '暂无数据')
}
.width('100%')
.height('100%')
}
}
5.2.2 TrainHomePage.ets(车票查询主页)
import { webview } from '[@kit](/user/kit).ArkWeb';
[@Entry](/user/Entry)
[@Component](/user/Component)
struct TrainHomePage {
webviewController: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Web({
src: "https://www.12306.cn",
controller: this.webviewController,
})
}
.width('100%')
.height('100%')
}
}
5.2.3 TrainDetails.ets(车次详情页)
import { router } from '[@kit](/user/kit).ArkUI'
import { TicketItem, SeatType, AITicketResponse } from './Common/Model/ticketModel'
import { JSON } from '[@kit](/user/kit).ArkTS'
import { MakeCall } from './Utils/MakeCall'
class TrainDetailsProps {
fromCity?: string
toCity?: string
selectedDate?: string
aiResponse: AITicketResponse | null = null
}
[@Entry](/user/Entry)
[@Component](/user/Component)
struct TrainDetails {
scroller: Scroller = new Scroller();
[@State](/user/State) fromCity: string = '郑州' // 出发城市
[@State](/user/State) toCity: string = '上海' // 目的城市
[@State](/user/State) aiResponse: AITicketResponse | null = null
[@State](/user/State) selectedDateStr: string = '今天 10.19' // 日期显示字符串
[@State](/user/State) ticketList: TicketItem[] = this.aiResponse?.ticketList || []
[@State](/user/State) phoneNumber: string = '400 - 888 - 8888'
aboutToAppear(): void {
const params = router.getParams() as TrainDetailsProps
if (params) {
this.fromCity = params.fromCity || '郑州'
this.toCity = params.toCity || '上海'
this.aiResponse = params.aiResponse
// 日期参数处理(若需动态日期,可扩展此处)
if (params.selectedDate) {
this.selectedDateStr = `今天 ${params.selectedDate}`
}
}
console.log('ai对话返回结果:', JSON.stringify(this.aiResponse))
console.log('ai对话 selectedDate:', JSON.stringify(this.aiResponse))
console.log('ai对话返回列表结果:', JSON.stringify(this.aiResponse?.ticketList))
console.log('ai对话赋值给列表:', this.ticketList.toString())
console.log('ai对话返回的信息中的的温馨提示:', this.aiResponse?.tips.toString())
}
// 查找第一个有余票的席位(只包含"张"的才算有票)
getFirstAvailableSeat(seatTypes: SeatType[]): SeatType | null {
for (let seat of seatTypes) {
if (seat.ticketStatus.includes('张')) {
return seat
}
}
return null
}
//构建车次项
[@Builder](/user/Builder)
TrainItemBuilder(item: TicketItem, index: number) {
// 在[@Builder](/user/Builder)中调用方法获取有余票的席位
// let availableSeat: SeatType | null = this.getFirstAvailableSeat(item.seatTypes)
Column() {
// 车次核心信息行:出发时间/站 + 车次/历时 + 到达时间/站 + 票价
Row({ space: 20 }) {
// 出发信息:时间 + 站名
Column({ space: 5 }) {
Text(item.departureTime).fontSize(18).fontColor(Color.Black)
Text(item.departureStation).fontSize(14).fontColor('#666')
}
// 车次与历时
Column({ space: 5 }) {
Text(item.trainNo).fontSize(16).fontColor(Color.Black)
Text(item.duration).fontSize(14).fontColor('#666')
}
// 到达信息:时间 + 站名
Column({ space: 5 }) {
Text(item.arrivalTime).fontSize(18).fontColor(Color.Black)
Text(item.arrivalStation).fontSize(14).fontColor('#666')
}
// 票价显示:显示第一个有余票的席位价格,如果没有则显示售罄
Column({ space: 5 }) {
// if (availableSeat) {
Row() {
Text('¥').fontSize(14).更多关于HarmonyOS鸿蒙Next华为云AI Agent集成车票查询项目的实战教程也可以访问 https://www.itying.com/category-93-b0.html
HarmonyOS Next通过华为云AI Agent集成车票查询功能,主要基于ArkTS语言开发,利用分布式能力实现多设备协同。系统通过AI Agent调用云端API处理查询请求,结合原子化服务实现卡片式信息展示。数据流转采用统一数据管理框架,保障跨端信息同步。集成过程涉及声明式UI开发、端云协同组件调用及分布式数据对象传递,无需依赖Java或C语言基础模块。
更多关于HarmonyOS鸿蒙Next华为云AI Agent集成车票查询项目的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
这是一个非常完整和专业的HarmonyOS Next车票查询项目实现文档,展示了华为云AI Agent与鸿蒙应用集成的优秀实践。
项目亮点:
-
架构设计清晰:前后端分离,前端使用ArkUI框架,后端依托华为云AI Agent,模块划分合理。
-
技术栈选择恰当:采用ETS语言开发,使用Preferences本地存储,HTTP/HTTPS网络通信,符合HarmonyOS开发规范。
-
AI Agent集成完善:实现了完整的令牌管理机制(Token_management类),包含token获取、缓存、过期判断等逻辑。
-
数据处理规范:定义了完整的车票数据模型(AITicketResponse、TicketItem等),SSE响应解析工具处理AI返回数据。
-
用户体验考虑周到:包含城市选择、日期选择、车次展示、详情页面等完整用户流程。
代码质量方面:
- entryability中正确初始化城市数据并存储到AppStorage
- 页面组件使用了ArkUI的声明式语法,状态管理清晰
- 工具类封装良好,职责单一(SSEParser、TicketDataStore等)
- 错误处理机制完善,网络异常、解析失败等场景都有相应处理
建议优化点:
- 敏感信息(如华为云账号密码)应通过环境变量或配置文件管理,避免硬编码
- 可考虑增加网络状态检测和重试机制
- 界面可进一步优化加载状态和空数据展示
这个项目很好地展示了如何在HarmonyOS Next中集成云服务AI能力,为其他开发者提供了很好的参考范例。

