鸿蒙Next · ArkTS疫苗预约应用开发详解

运行截图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

一、项目概述

随着移动互联网与智慧医疗的深度融合,在线疫苗预约已成为公共卫生服务数字化转型的重要组成部分。传统的疫苗接种流程通常需要用户线下排队、电话预约或通过第三方平台操作,流程繁琐且效率低下。为解决这一痛点,本项目基于 HarmonyOS Next 平台,使用 ArkTS 语言开发了一款疫苗预约应用,旨在为用户提供便捷、直观的在线预约体验。

本应用的核心功能包括:以列表形式展示可预约的疫苗信息,支持用户点选疫苗后在网格中选择合适的接种时间段,最终完成预约确认。整个交互流程简洁明了,从疫苗选择到时间预约再到确认提交,三步即可完成一次完整的预约操作。

本文将从项目架构、数据模型、UI组件实现、状态管理、交互逻辑等多个维度,对该应用进行全方位的技术解析,帮助开发者深入理解 ArkTS 声明式UI开发范式以及 HarmonyOS Next 应用开发的最佳实践。

二、技术架构与开发环境

2.1 技术栈

技术项 说明
开发平台 HarmonyOS Next
开发语言 ArkTS(Ark TypeScript)
UI范式 声明式UI(ArkUI)
IDE DevEco Studio
目标设备 手机(phone)

ArkTS 是 HarmonyOS Next 的主力开发语言,它在 TypeScript 的基础上扩展了声明式UI能力,使得开发者能够通过简洁的语法描述界面结构和状态变化。与传统的命令式UI开发不同,ArkTS 采用声明式范式:开发者只需描述"界面应该是什么样的",框架会自动处理视图的创建、更新和销毁。

2.2 项目结构

ArkTSVaccine/
├── AppScope/                    # 应用级配置和资源
│   ├── app.json5                # 应用配置
│   └── resources/               # 应用级资源
├── entry/                       # 主模块
│   └── src/main/
│       ├── ets/                 # ArkTS源码
│       │   ├── entryability/    # 入口Ability
│       │   └── pages/
│       │       └── Index.ets    # 主页面(核心代码)
│       ├── resources/           # 模块资源
│       │   ├── base/            # 基础资源
│       │   └── dark/            # 深色模式资源
│       └── module.json5         # 模块配置
└── build-profile.json5          # 构建配置

整个应用的核心逻辑集中在一个页面 Index.ets 中,采用单页面架构设计。这种设计对于功能相对聚焦的工具类应用来说非常合适,既降低了代码复杂度,又保证了用户体验的连贯性。

三、数据模型设计

3.1 疫苗信息模型

interface VaccineInfo {
  id: number;          // 疫苗唯一标识
  name: string;        // 疫苗名称
  description: string; // 疫苗说明
  manufacturer: string;// 生产厂家
  dose: string;        // 接种剂次
  price: string;       // 价格信息
  available: boolean;  // 是否可预约
}

VaccineInfo 接口定义了疫苗的完整信息结构。其中 id 字段用于唯一标识每种疫苗,作为后续选择逻辑的关联键;available 字段则控制疫苗是否可被选中——当疫苗库存不足或暂时缺货时,该字段为 false,用户点击时不会触发选择逻辑,同时界面上会显示橙色的"暂缺"标签进行提示。

3.2 时间段模型

interface TimeSlot {
  id: number;       // 时间段唯一标识
  time: string;     // 时间范围(如 "08:00-09:00")
  available: boolean; // 该时段是否可预约
}

TimeSlot 接口用于描述可预约的时间段。与疫苗模型类似,available 字段控制时段的可选性。已满或不可用的时间段在界面上会以半透明灰色显示,且不可点击。

3.3 数据定义方式

在 ArkTS 的组件中,接口定义位于组件外部,数据则通过 private 属性在组件内部初始化。这种写法将数据结构与组件逻辑紧密关联,适用于单页面应用的快速开发:

private vaccines: VaccineInfo[] = [
  {
    id: 1,
    name: '新冠灭活疫苗',
    description: '用于预防新型冠状病毒感染,适用于3岁及以上人群,需接种2剂次。',
    manufacturer: '国药集团',
    dose: '2剂次',
    price: '免费',
    available: true
  },
  // ...更多疫苗数据
];

预置了 5 款常见疫苗和 12 个时间段数据,模拟了真实的预约场景。其中乙肝疫苗的 available 设置为 false,用于演示缺货状态的 UI 表现。

四、状态管理

4.1 状态变量

ArkTS 的声明式UI核心在于状态驱动视图更新。本应用定义了三个关键状态变量:

@State selectedVaccineId: number = -1;    // 当前选中的疫苗ID
@State selectedTimeSlotId: number = -1;  // 当前选中的时间段ID
@State showConfirm: boolean = false;     // 是否显示预约成功弹窗

@State 装饰器是 ArkTS 状态管理的基础。当被 @State 装饰的变量发生变化时,框架会自动重新执行 build() 方法,触发 UI 的重新渲染。这意味着开发者无需手动操作 DOM 或调用 setState 之类的命令式方法——只需修改状态值,界面就会自动更新。

4.2 状态流转逻辑

整个应用的状态流转遵循清晰的线性流程:

  1. 初始状态:两个选择 ID 均为 -1,表示未做任何选择,时间区域不显示
  2. 选择疫苗selectedVaccineId 更新为对应 ID,时间网格区域条件渲染出现
  3. 选择时间段selectedTimeSlotId 更新为对应 ID,已选摘要和确认按钮出现
  4. 确认预约showConfirm 变为 true,弹窗覆盖层显示
  5. 关闭弹窗:所有状态重置为初始值

这种状态流转设计确保了用户操作的有序性——必须先选疫苗,再选时间,最后才能确认。每一步都有明确的视觉反馈,降低了误操作的可能性。

4.3 选择互斥与重置

当用户切换疫苗选择时,已选的时间段需要重置:

.onClick(() => {
  if (vaccine.available) {
    this.selectedVaccineId = vaccine.id;
    this.selectedTimeSlotId = -1; // 重置时间段选择
  }
})

这是一个重要的交互细节。不同疫苗的可预约时段可能不同,切换疫苗后保留之前的时间选择可能导致逻辑冲突,因此每次切换疫苗时主动清除时间选择是合理的做法。

五、UI组件实现详解

5.1 整体布局架构

应用的布局采用 Stack 作为根容器,包含两个层级:底层是页面主体内容(顶部标题栏 + 可滚动内容区),顶层是条件渲染的预约成功弹窗。

build() {
  Stack() {
    Column() {
      // 顶部标题栏
      Row() { ... }
      // 可滚动内容区
      Scroll() { ... }
    }

    // 预约成功弹窗(条件渲染)
    if (this.showConfirm) {
      Column() { ... }
    }
  }
}

Stack 组件允许子元素层叠放置,非常适合实现弹窗、浮层等覆盖型 UI。弹窗组件通过 if 条件渲染——当 showConfirmtrue 时创建并显示,为 false 时销毁。这种条件渲染方式比显式的 visibility 控制更高效,因为组件在不需要时不会占用渲染资源。

5.2 顶部标题栏

Row() {
  Text('疫苗预约')
    .fontSize(24)
    .fontWeight(FontWeight.Bold)
    .fontColor('#FFFFFF')
}
.width('100%')
.height(56)
.backgroundColor('#4CAF50')
.justifyContent(FlexAlign.Center)

标题栏使用 Row 水平布局,内部仅包含一个居中的 Text 组件。#4CAF50 是一个柔和的绿色,既符合医疗健康类应用的视觉调性,又不会过于刺眼。整个标题栏固定高度 56vp,与系统标准标题栏高度保持一致。

5.3 疫苗列表(List 组件)

疫苗列表是本应用的核心展示组件之一,采用 List + ForEach 的组合实现动态列表渲染:

List({ space: 12 }) {
  ForEach(this.vaccines, (vaccine: VaccineInfo) => {
    ListItem() {
      Row() {
        Column() { /* 疫苗信息 */ }
          .layoutWeight(1)
        Stack() { /* 选中指示器 */ }
      }
      .padding(16)
      .borderRadius(12)
      .backgroundColor(...)
      .border(...)
      .shadow(...)
      .onClick(() => { ... })
    }
  })
}
.width('100%')
.padding({ left: 16, right: 16 })

每个列表项是一个 Row 水平布局,左侧为疫苗详细信息(使用 Column 垂直排列),右侧为选中指示器(使用 Stack 叠加圆形和勾选图标)。

关键设计细节:

  • space: 12:列表项之间的间距为 12vp,配合卡片式设计营造清晰的视觉层次
  • layoutWeight(1):让信息区域占据剩余空间,选择器固定在右侧
  • 条件边框与背景:选中状态的卡片变为浅绿色背景 + 绿色边框,未选中为白色背景 + 浅灰边框
  • 阴影效果shadow 属性为卡片添加微妙的投影,增强层次感
  • 暂缺标签:当疫苗不可用时,在名称后显示橙色"暂缺"标签,使用内联 paddingborderRadius 实现胶囊形状
选中指示器的实现
Stack() {
  Circle()
    .width(22)
    .height(22)
    .fill(this.selectedVaccineId === vaccine.id ? '#4CAF50' : 'transparent')
    .stroke(this.selectedVaccineId === vaccine.id ? '#4CAF50' : '#CCCCCC')
    .strokeWidth(this.selectedVaccineId === vaccine.id ? 0 : 2)

  if (this.selectedVaccineId === vaccine.id) {
    Text('✓')
      .fontSize(14)
      .fontColor('#FFFFFF')
  }
}
.width(24)
.height(24)

选中指示器使用 Stack 叠加一个 Circle 和一个条件渲染的 Text(勾选符号)。未选中时显示灰色空心圆,选中时变为绿色实心圆并叠加白色勾号。Stack 的默认对齐方式为居中,因此勾号自然位于圆形中心,无需额外定位。

5.4 时间段网格(Grid 组件)

时间段选择区域使用 Grid 组件实现 3 列 4 行的网格布局:

Grid() {
  ForEach(this.timeSlots, (slot: TimeSlot) => {
    GridItem() {
      Column() {
        Text(slot.time.split('-')[0])  // 开始时间
        Text(slot.time.split('-')[1])  // 结束时间
      }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
      .borderRadius(8)
      .backgroundColor(...)
      .border(...)
      .opacity(!slot.available ? 0.5 : 1)
      .onClick(() => { ... })
    }
  })
}
.columnsTemplate('1fr 1fr 1fr')
.rowsTemplate('1fr 1fr')
.columnsGap(10)
.rowsGap(10)
.width('100%')
.height(170)

Grid 布局要点:

  • columnsTemplate('1fr 1fr 1fr'):定义三列等宽网格
  • rowsTemplate('1fr 1fr'):定义两行等高网格
  • columnsGap(10)rowsGap(10):网格间距为 10vp
  • 总高度固定 170vp,12 个时间段通过滚动展示

每个网格项展示两行文字——上方为开始时间(较大字号、较粗字体),下方为结束时间(较小字号、灰色)。不可用的时间段通过 opacity(0.5) 降低不透明度,直观地传达"不可选"状态。

5.5 预约成功弹窗

弹窗通过 Stack 覆盖层实现,包含半透明遮罩和居中白色卡片:

if (this.showConfirm) {
  Column() {
    // 遮罩层
    Column()
      .width('100%')
      .height('100%')
      .backgroundColor('rgba(0,0,0,0.5)')
      .onClick(() => { this.showConfirm = false; })

    // 弹窗卡片
    Column() {
      Text('✓')          // 成功图标
      Text('预约成功!')  // 标题
      Text(this.getVaccineName())  // 疫苗名称
      Text(this.getTimeSlot())     // 时间段
      Text('请按时到达接种点,携带有效身份证件。')  // 提示
      Button('确定')     // 关闭按钮
    }
    .width(300)
    .padding(30)
    .borderRadius(16)
    .backgroundColor('#FFFFFF')
  }
  .width('100%')
  .height('100%')
  .justifyContent(FlexAlign.Center)
}

遮罩层铺满全屏,点击可关闭弹窗。弹窗卡片固定宽度 300vp,圆角 16vp,通过 justifyContent(FlexAlign.Center) 在 Stack 中垂直居中。弹窗内容简洁明了:成功图标、预约摘要、温馨提示和确认按钮,层次分明。

5.6 Toast 提示

点击"确认预约"按钮时,通过 promptAction.showToast 弹出轻量级提示:

.onClick(() => {
  promptAction.showToast({
    message: '预约成功!疫苗:' + this.getVaccineName() + ',时间:' + this.getTimeSlot(),
    duration: 2000
  });
  this.showConfirm = true;
})

Toast 提示会在屏幕底部短暂显示 2 秒后自动消失,为用户提供即时的操作反馈,同时不打断主流程。这种轻量级反馈与弹窗形成互补——Toast 做即时确认,弹窗做详细信息展示。

六、交互设计与用户体验

6.1 渐进式披露

应用采用了渐进式披露(Progressive Disclosure)的交互设计原则。初始状态下,页面仅展示疫苗列表。只有当用户选择了某个疫苗后,时间段网格才会出现;只有当用户同时选好了疫苗和时间,确认按钮和已选摘要才会展示。

这种设计减少了初始界面的信息密度,让用户可以专注于当前步骤,降低了认知负担。同时,逐步展开的内容也形成了一种引导式的操作流程,暗示用户按步骤完成预约。

6.2 视觉反馈体系

应用建立了一套完整的视觉反馈体系:

状态 反馈方式
可选 白色背景、灰色边框、正常不透明度
选中 绿色背景、绿色边框、勾选图标
不可用 灰色背景、低不透明度、禁止点击
暂缺 橙色标签、背景不可点击

绿色(#4CAF50)作为主色调贯穿整个应用,既代表"健康"和"通过"的心理暗示,又在视觉上形成统一的品牌感知。

6.3 辅助方法

为了保持模板代码的简洁,将数据查询逻辑封装为私有方法:

private getVaccineName(): string {
  for (const vaccine of this.vaccines) {
    if (vaccine.id === this.selectedVaccineId) {
      return vaccine.name;
    }
  }
  return '';
}

private getTimeSlot(): string {
  for (const slot of this.timeSlots) {
    if (slot.id === this.selectedTimeSlotId) {
      return slot.time;
    }
  }
  return '';
}

这两个方法在弹窗和摘要区域被多次调用,避免了在模板中编写重复的查找逻辑,提升了代码的可读性和可维护性。

七、关键ArkTS特性应用

7.1 条件渲染

应用大量使用了 if 条件渲染来控制组件的显示与隐藏:

// 时间区域在选择疫苗后出现
if (this.selectedVaccineId > 0) {
  // 时间网格和相关UI
}

// 已选摘要在选择时间后出现
if (this.selectedTimeSlotId > 0) {
  // 摘要和确认按钮
}

// 弹窗在确认时出现
if (this.showConfirm) {
  // 弹窗组件
}

ArkTS 的条件渲染与传统的 v-if 或条件样式不同——当条件为 false 时,组件不仅不可见,而且不会被创建到渲染树中,从而节省了内存和渲染开销。

7.2 ForEach 列表渲染

ForEach(this.vaccines, (vaccine: VaccineInfo) => {
  ListItem() { ... }
})

ForEach 是 ArkTS 中处理列表渲染的核心 API。它接收数据源和渲染函数,为每个数据项创建对应的组件实例。当数据源发生变化时,框架会自动进行差异对比并更新视图。

7.3 声明式属性绑定

ArkTS 的属性绑定语法使得样式可以动态响应状态变化:

.backgroundColor(this.selectedVaccineId === vaccine.id ? '#E8F5E9' : '#FFFFFF')
.border({
  width: this.selectedVaccineId === vaccine.id ? 1.5 : 1,
  color: this.selectedVaccineId === vaccine.id ? '#4CAF50' : '#EEEEEE'
})

这种写法将样式逻辑与状态直接绑定,无需手动操作样式类或属性,代码意图清晰明了。

八、总结与展望

本疫苗预约应用通过简洁的架构设计和声明式UI开发范式,在单页面内实现了完整的预约流程。核心技术亮点包括:使用 @State 实现响应式状态管理、List + ForEach 实现动态列表、Grid 实现时间段网格布局、Stack 实现弹窗覆盖层,以及条件渲染实现渐进式交互。

在实际生产环境中,该应用还可以进行以下方向的扩展:接入后端 API 实现真实数据交互、添加用户登录和身份认证、引入预约记录管理、支持多城市/多医院选择、集成日历组件提供更直观的日期选择、以及添加推送通知功能提醒用户按时接种。

ArkTS 的声明式UI开发范式大幅降低了移动端界面开发的门槛,使得开发者能够更加专注于业务逻辑和用户体验的设计,而非繁琐的视图操作。随着 HarmonyOS 生态的不断发展完善,ArkTS 在智慧医疗、公共服务等领域的应用前景将更加广阔。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐