东软云医 · 鸿蒙前端

基于 HarmonyOS ArkTS 的智慧云医院 App — 从环境搭建、项目结构、代码逻辑到 AI 管家的完整开发者指南

ArkTS HarmonyOS 6.1 API 23 SSE 流式 AI Spring Boot 3 33 个 .ets 文件
Section 1

项目概览

前端技术栈

ArkTS (TypeScript 严格子集) + @ohos/axios HTTP 库 + @ohos.data.preferences 持久化 + AppStorage 全局状态管理

ArkTS HarmonyOS 33 源文件

后端技术栈

Spring Boot 3 + JWT 鉴权 + MySQL 8.0,7 张数据表,260 名医生,139 个科室,支持 RESTful API

Spring Boot MySQL JWT

核心功能

登录注册 · 10大科室浏览 · 7天排班预约挂号 · 就诊人管理(含身份证校验) · 挂号记录 · AI 管家流式对话

挂号 AI 对话 就诊人
Section 2

前端项目详解

📂 项目文件结构(共 33 个 .ets 源文件)

harmony/ ├── AppScope/ ← App 配置(名称、图标、版本号) ├── entry/src/main/ │ ├── module.json5 ← 模块配置(页面路由注册、网络权限) │ └── ets/ ← ★ 全部源码 │ ├── entryability/ │ │ └── EntryAbility.ets ← App 入口,初始化存储+加载首页 │ ├── util/ │ │ └── StorageUtil.ets ← 登录态本地持久化(单例模式) │ ├── models/ ← ★ 数据模型层 (2文件) │ │ ├── ChatMessage.ets ← 聊天消息 @Observed 响应式模型 │ │ └── UISpec.ets ← 5种 UI 卡片规格定义 │ ├── services/ ← ★ 业务服务层 (6文件) │ │ ├── AIConfigService.ets ← AI 配置持久化(baseUrl/key/model) │ │ ├── KeeperContext.ets ← AI 上下文构建(科室知识+用户档案) │ │ ├── KeeperBridge.ets ← 卡片 Action → 后端 API 桥接 │ │ ├── KeeperStore.ets ← 按用户名的历史记录存储 │ │ ├── LLMService.ets ← SSE 流式请求(requestInStream) │ │ └── StreamParser.ets ← ```ui 代码块状态机解析器 │ ├── pages/ ← ★ 页面层 (13个页面) │ │ ├── Index.ets ← 主框架(底部 4 Tab 切换) │ │ ├── LoginPage.ets ← 登录页(手机号+密码+JWT) │ │ ├── RegisterPage.ets ← 注册页(两次密码确认) │ │ ├── KeeperPage.ets ← AI 管家对话页(800+行核心) │ │ ├── AISettingsPage.ets ← AI 服务配置页 │ │ ├── RegisterAppointmentPage.ets ← 选日期+医生 │ │ ├── AppointmentDetailPage.ets ← 选时段+确认挂号 │ │ ├── PatientListPage.ets ← 就诊人管理列表 │ │ ├── PatientEditPage.ets ← 添加/编辑就诊人 │ │ ├── MedicalRecordPage.ets ← 就诊信息+挂号记录 │ │ └── ProfileDetailPage.ets ← 个人信息(待开放) │ └── components/ ← ★ 组件层 (11个) │ ├── HomeHeader.ets ← 首页轮播+搜索 │ ├── RegistrationView.ets ← 科室分类浏览/搜索 │ ├── ProfileView.ets ← "我的"页面 │ ├── PatientSelectView.ets ← 就诊人弹窗选择器 │ └── keeper/ ← AI 管家 8 个子组件 │ ├── MessageBubble.ets ← 聊天气泡(用户/AI双样式) │ ├── DynamicCard.ets ← 卡片类型分发放器 │ ├── GradientButton.ets ← 渐变按钮+卡片外壳 │ ├── InfoCard.ets ← 信息展示卡片 │ ├── DeptPickerCard.ets ← 科室选择胶囊 │ ├── DoctorListCard.ets ← 医生列表卡片 │ ├── AppointmentFormCard.ets ← 挂号确认表单 │ └── QuickRepliesCard.ets ← 快捷回复气泡

🎨 ArkTS 装饰器与核心概念

@Entry

entry=入口 → 页面装饰器,标记可被 router.pushUrl() 打开的独立页面。一个 .ets 文件可以有多个 @Entry 页面

@Component

component=组件 → UI 组件装饰器,标记可复用的 UI 积木块。Component 可嵌套,但不能单独作为页面入口

@State

state=状态 → 状态变量,变量值变化时 build() 自动重新执行,UI 随之刷新。这是 ArkTS 响应式的核心

@Prop

property=属性 → 父传子,单向数据流。子组件收到后可以读但不能往回写(要双向用 @Link)

@Link

link=链接 → 双向绑定,父子组件共享同一数据源。父用 $变量名 传给子,任何一方改值双方同步

@StorageLink

storage+link → 全局存储链接,双向绑定 AppStorage。任何页面改了 AppStorage 的值,所有 @StorageLink 都会同步

@Watch

watch=观察 → 监听器,变量变化时自动调用指定方法。如 @Watch('onChange') 变量变化→自动执行 onChange()

@Builder

builder=构建者 → UI 片段复用。把重复的 UI 代码抽成 @Builder 方法,在多处调用,避免复制粘贴

📝 ArkTS 页面完整示例(带逐行注释)
// ===== 第1步:导入系统模块 ===== import { router } from '@kit.ArkUI'; // router → 路由器,控制页面跳转 import { promptAction } from '@kit.ArkUI'; // promptAction → Toast轻提示 import axios from '@ohos/axios'; // axios → HTTP请求库(GET/POST/DELETE) // ===== 第2步:定义数据结构 ===== interface ApiResponse { // interface → 类型接口,定义对象形状 code: number; // 状态码,200 = 成功 content: Object; // 响应内容 message: string; // 提示消息 } // ===== 第3步:定义页面组件 ===== @Entry // @Entry = 这是页面入口(可被路由打开) @Component // @Component = 这是 UI 组件 struct LoginPage { // struct = 结构体(组件必须用struct) @State username: string = ''; // @State → 状态变量,变了UI自动刷新 @State password: string = ''; // 密码输入(绑定到TextInput) @State loading: boolean = false; // 加载态,true时按钮变灰+禁止点击 // build() → 构建UI,返回组件树 build() { Column() { // Column → 列布局(垂直排列) Text('东软云医') // Text → 文本组件 .fontSize(28) // 字号28px .fontColor('#5B9BD5') // 字体颜色 TextInput({ placeholder: '手机号', text: this.username }) .type(InputType.Number) // 数字键盘 .maxLength(11) // 最多11位 .onChange((v: string) => { this.username = v; }) Button('登 录') // Button → 按钮组件 .enabled(!this.loading) // loading时禁用 .onClick(() => { this.handleLogin(); }) // 点击调用登录 } } // handleLogin → 处理登录业务逻辑 async handleLogin(): Promise<void> { // async → 函数内可用await等待异步 if (!this.username.trim()) { return; } // trim() → 去首尾空格 this.loading = true; const resp = await axios.post(url, body, config); // await → 等待异步请求返回 if (resp.data.code === 200) { // 登录成功 AppStorage.setOrCreate('token', resp.data.content); // 存token到全局 router.back(); // 返回上一页 } this.loading = false; } }
🏗 前端架构分层设计(MVC 思想)

Model 数据层

models/ChatMessage.ets + models/UISpec.ets — 定义消息、卡片等核心数据结构。ChatMessage 用 @Observed 装饰实现响应式

View 视图层

pages/ + components/ — 13 个页面 + 11 个组件。用 struct + build() 描述 UI,用 .链式方法 设置样式属性

Service 服务层

services/ — 6 个服务文件处理所有业务逻辑:AI配置、流式请求、上下文构建、API桥接、历史存储、内容解析

Section 3

架构与页面导航流程

App 启动 → EntryAbility
→
Index(底部 4 Tab)
→
Tab0 主页
→
Tab1 挂号
→
Tab2 管家
→
Tab3 我的
RegistrationView
→
RegisterAppointment
→
AppointmentDetail
🔗 完整页面跳转流程图(含数据传递)
# 挂号流程(核心业务链路) Index (4 Tab壳) ├── Tab0「主页」 │ └── HomeHeader (轮播 + 搜索图标) │ └── 点搜索 → AppStorage.set('focusSearch', true) + 切到 Tab1 │ ├── Tab1「挂号」 │ └── RegistrationView (10大科室分类 / 搜索) │ └── 点子科室 → router.pushUrl({ │ url: 'pages/RegisterAppointmentPage', │ params: { deptId: number, deptName: string } │ }) │ └── RegisterAppointmentPage (7天日期+号别筛选+医生列表) │ └── 点医生 → router.pushUrl({ │ url: 'pages/AppointmentDetailPage', │ params: { empId, empName, title, deptId, deptName, date } │ }) │ └── AppointmentDetailPage (时段选择+就诊人弹窗→确认) │ ├── Tab2「管家」 │ └── KeeperPage (AI 流式对话 + 5种UI卡片交互) │ ├── 未登录 → loginGate (引导登录) │ ├── 已登录+无历史 → welcomeView (建议问题气泡) │ └── 已登录+有历史 → 对话流 + 卡片操作 │ └── Tab3「我的」 └── ProfileView ├── 未登录 → 弹窗 → LoginPage ↔ RegisterPage ├── 已登录 → ProfileDetailPage (个人信息,待开放) ├── 就诊信息 → MedicalRecordPage │ ├── 就诊人管理 → PatientListPage ↔ PatientEditPage │ └── 挂号记录 (过期浅色/取消/删除) ├── 软件设置 → AISettingsPage └── 退出登录 → 清空 AppStorage + 磁盘 token + AI历史
Section 4

AI 管家 · 小医 — 核心架构

LLMService — SSE 流式请求

使用 HarmonyOS http.requestInStream() 向 AI 服务发送 OpenAI 兼容的 /chat/completions 请求。通过 on('dataReceive') 事件逐行接收增量数据,实现打字机效果

StreamParser — 状态机解析

在 TEXT 和 UI 两种模式之间切换。检测 ```ui / ```json 代码块边界,智能提取 JSON(支持括号匹配、字符串转义处理),跨 chunk 边界安全

DynamicCard — 卡片分发

根据 spec.type 自动渲染:info_card(信息展示)→ dept_picker(科室胶囊)→ doctor_list(医生列表)→ appointment_form(挂号表单)→ quick_replies(快捷回复)

KeeperBridge — API 桥接

卡片操作事件 → 后端 API 的桥接层。dispatch() 根据 action 类型(pickDept/pickDoctor/submitAppointment)分发到对应方法,调后端接口并返回结果

KeeperContext — 上下文构建

加载 139 个科室知识 → 注入当前日期/用户档案/就诊人列表 → 预查匹配科室的医生排班 → 拼成结构化文本,注入 AI 的 system prompt 中

KeeperStore — 历史持久化

按用户名(手机号)隔离存储。退出登录清空当前用户历史,重新登录恢复。不同用户互不干扰。streaming 中的消息不保存

🤖 AI 管家完整对话流程(从发消息到挂号成功)
内容将在展开时加载...
🃏 5 种 UI 卡片完整 JSON 规格与使用场景
内容将在展开时加载...
Section 5

快速入门

1. 安装 DevEco Studio

从华为开发者官网下载。安装时选择 API 23 (HarmonyOS 6.1) 的 SDK

2. 打开项目

DevEco Studio → Open → 选择项目文件夹 → File → Sync Now 同步 ohpm 依赖(自动安装 @ohos/axios)

3. 连接设备

模拟器:Device Manager → 创建 API 23 模拟器。真机:USB 连接 + 开发者模式 + 签名配置

4. 首次运行验证

Run → 首页显示轮播 → 挂号Tab查看科室 → 注册账号 → 添加就诊人 → 挂号全流程

🔄 ArkTS 状态管理核心规则(常见踩坑点)
# ArkTS @State 刷新规则(重要!) # ✅ 正确:整体替换数组/对象 → UI 刷新 this.list = [...this.list, newItem]; // 用展开运算符创建新数组 this.list = this.list.concat([newItem]); // concat 返回新数组 this.obj = { ...this.obj, key: newVal }; // 创建新对象 # ❌ 错误:直接修改 → UI 不刷新 this.list.push(newItem); // push 在原数组上修改 this.obj.key = newVal; // 直接改属性 # 💡 解决方案:touch() 模式(KeeperPage 中的用法) m.version++; // 递增版本号 this.messages = [...this.messages]; // 展开再赋值,强制刷新
Section 6

后端 API 参考

方法端点认证说明前端调用位置
POST/customer/login无登录,返回 JWT tokenLoginPage.ets
POST/customer/add无注册新用户RegisterPage.ets
GET/visitor/listToken获取就诊人列表PatientListPage / MedicalRecordPage
POST/visitor/addToken添加就诊人PatientEditPage.ets
GET/employee/list?deptId=&week=Token某科室某工作日医生列表RegisterAppointmentPage / KeeperBridge
GET/employee/getByIdWithScheduling/{id}Token医生详情 + weekRule 排班AppointmentDetailPage.ets
POST/registerW/addToken提交挂号(date/time/empId/vId/price)AppointmentDetailPage / KeeperBridge
GET/registerW/listToken挂号记录列表MedicalRecordPage / KeeperBridge
DELETE/registerW/delete/{id}Token取消/删除挂号MedicalRecordPage.ets
Section 7

英语词汇速查

API
Application Programming Interface → 应用程序编程接口,后端提供的 URL 地址
async / await
asynchronous=异步的 → async 标记函数可用 await,await 暂停等待异步操作完成
axios
axis=轴 → 基于 Promise 的 HTTP 客户端,用于向后端发送 GET/POST/DELETE 请求
Column / Row
column=列→垂直排列子元素 / row=行→水平排列子元素
interface
interface=接口 → TypeScript 类型定义,约束对象的数据结构形状
JWT
JSON Web Token → JSON 格式的加密身份令牌,登录成功后由后端签发
LLM
Large Language Model → 大语言模型,如 DeepSeek、GPT,用于 AI 对话
preferences
preference=偏好 → 轻量级键值对磁盘存储,类似 Web 的 localStorage
Promise
promise=承诺 → 表示一个尚未完成的异步操作,用 .then() 或 await 获取结果
Record<K,V>
record=记录 → TypeScript 内置类型,表示键类型为 K、值类型为 V 的对象
router
route=路由 → 页面跳转控制器,pushUrl(打开) / back(返回) / getParams(获取参数)
SSE
Server-Sent Events → 服务器向客户端单向推送数据流的技术
Stack
stack=堆叠 → 子元素层叠排列,后面的盖在前面上面
struct
structure=结构体 → ArkTS 中用 struct 定义组件(比 class 更轻量、适合 UI)
token
token=令牌 → 登录后服务器返回的身份凭证,每次请求需在 Header 携带
trim()
trim=修剪 → 去除字符串首尾的空白字符(空格、制表符、换行等)

🚀 准备开始了吗?

用 DevEco Studio 打开项目,Sync 依赖,Run 起来即可体验完整的鸿蒙云医院功能