HarmonyOS ArkTS 实战:校园体育场馆预约完整实现
摘要
项目难度:⭐⭐⭐ 中级实战
适用人群:有 ArkTS 基础、希望学习预约类应用开发的 HarmonyOS 开发者
开发环境:DevEco Studio Next / API 24+
代码规模:单页面约 700 行,可直接运行
通过本项目,你将掌握:
- 时间段选择与场地资源的冲突检测
- 场馆预约的完整状态流转管理
- 在线支付与退款的业务逻辑设计
- 多类型场馆的分类展示与筛选
- 运动数据统计与可视化展示
目录
-
一、项目概述与功能架构
-
1.1 应用场景与项目价值
-
1.2 完整功能清单
-
1.3 技术选型说明
-
-
二、数据结构设计
-
2.1 场馆模型 Venue
-
2.2 预约记录模型 Booking
-
2.3 设计思路说明
-
-
三、核心功能实现详解
-
3.1 场馆浏览与筛选
-
3.2 时间段选择与预约
-
3.3 取消预约与退款规则
-
3.4 签到入场与使用记录
-
-
四、UI 设计与交互体验
-
4.1 整体布局结构
-
4.2 主题色与视觉规范
-
4.3 状态视觉区分表
-
-
五、关键技术点解析
-
5.1 时间段资源的冲突检测算法
-
5.2 退款规则的阶梯式设计
-
-
六、常见问题与优化建议
-
七、项目总结与扩展方向
-
附录:运行方式
一、项目概述与功能架构
1.1 应用场景与项目价值
运动是校园生活的重要组成部分,羽毛球、篮球、网球、游泳、乒乓球等体育运动深受学生喜爱。然而热门场馆常常"一场难求",线下预约不方便,时间冲突难协调,运动记录难追踪,一直是校园体育生活的痛点。
本项目基于 HarmonyOS ArkTS 实现了一个校园体育场馆预约应用,整合了场馆浏览、场地预约、时间选择、在线支付、预约记录、签到入场等功能,实现校园体育场馆的数字化预约管理。
项目核心价值:
- 便利价值:随时随地查看场馆空闲情况并预约,不用跑线下
- 公平价值:线上公开透明预约,避免人情场、内部场
- 数据价值:运动数据自动统计,帮助学生养成运动习惯
1.2 完整功能清单
| 功能模块 | 具体功能 | 实现难度⭐ |
|---|---|---|
| 场馆浏览 | 场馆列表、类型筛选、详情查看、开放时间 | ⭐⭐ |
| 场地预约 | 日期选择、时段选择、场地分配、预约确认 | ⭐⭐⭐ |
| 支付系统 | 余额支付、在线支付、订单详情、支付记录 | ⭐⭐⭐ |
| 预约管理 | 我的预约、取消预约、退款申请、预约提醒 | ⭐⭐⭐ |
| 签到入场 | 扫码签到、核销入场、使用记录、时长统计 | ⭐⭐ |
| 数据统计 | 运动时长、运动次数、消费统计、运动排行 | ⭐⭐ |
1.3 技术选型说明
| 技术维度 | 选型方案 | 选型理由 |
|---|---|---|
| 开发语言 | ArkTS | 强类型支持,适合时间计算等精确业务逻辑 |
| 状态管理 | @State 装饰器 | 轻量响应式,满足预约状态和场馆数据更新 |
| 时间选择 | 自定义时段组件 | 灵活展示可用/已预约时间段 |
| 数据存储 | 内存状态 | 演示简化,生产环境需对接场馆管理后台 |
| 最低版本 | API 24 | 兼容主流 HarmonyOS 设备 |
⚠️ 注意:本项目为前端演示版本,场地库存、支付等均为本地模拟。真实场馆预约系统需要对接学校体育部后台,确保数据准确和资金安全。
二、数据结构设计
2.1 场馆模型 Venue
场馆是预约系统的核心资源,包含基本信息和场地数量。
typescript
/**
* 体育场馆数据模型
*/
interface Venue {
id: number; // 场馆ID
name: string; // 场馆名称
type: string; // 运动类型:羽毛球/篮球/网球/游泳/乒乓球
location: string; // 场馆位置
price: number; // 单价(元/小时)
openTime: string; // 开放时间
image: string; // 场馆图片
totalCourts: number; // 总场地数
availableCourts: number; // 当前可用场地数
}
2.2 预约记录模型 Booking
预约记录追踪用户的每一次场地预约,包含完整的时间和状态信息。
typescript
/**
* 场馆预约记录数据模型
*/
interface Booking {
id: number; // 预约ID
venueId: number; // 场馆ID
venueName: string; // 场馆名称
courtNo: string; // 场地号
date: string; // 预约日期
startTime: string; // 开始时间
endTime: string; // 结束时间
price: number; // 预约费用
status: string; // 预约状态:已预约/已取消/已使用/已过期
bookTime: string; // 下单时间
}
2.3 设计思路说明
为什么场馆和场地分开两个层级?
这是典型的一对多资源结构:
- 场馆是大类:如"羽毛球馆",包含多个场地
- 场地是具体资源:如"1号场""2号场",是实际预约的单位
- 展示层面:用户浏览时按场馆分类,不用关心具体几号场
- 预约层面:系统分配具体场地号,用户到场后按号使用
这种设计既简化了用户的选择,又保证了资源管理的精确性。
关于预约状态的四态设计
已预约 → 已使用 / 已取消 / 已过期,覆盖了预约的所有可能结果:
- 已预约:下单成功,等待使用
- 已使用:正常到场使用完毕
- 已取消:用户主动取消预约
- 已过期:过了时间没使用也没取消
四种状态覆盖了完整的预约生命周期。
三、核心功能实现详解
3.1 页面状态初始化
typescript
/**
* 页面状态变量定义
*/
@State private selectedDate: string = '2026-07-19'; // 选择的日期
@State private selectedTime: string = '14:00-15:00'; // 选择的时段
@State private nextBookId: number = 10; // 下一个预约ID
@State private balance: number = 100; // 账户余额
@State private venues: Venue[] = []; // 场馆列表
@State private bookings: Booking[] = []; // 预约记录
3.2 场馆浏览与筛选
typescript
/**
* 按运动类型筛选场馆
* @param type 运动类型
*/
private filterVenuesByType(type: string): Venue[] {
if (type === '全部') {
return this.venues;
}
return this.venues.filter(v => v.type === type);
}
/**
* 获取可用场馆数量
*/
private getAvailableVenuesCount(): number {
return this.venues.filter(v => v.availableCourts > 0).length;
}
3.3 预约场地
选择日期和时段后提交预约,系统分配场地号并扣费。
typescript
/**
* 预约场地
* @param venueId 场馆ID
* @param venueName 场馆名称
* @param price 单价
*/
private bookCourt(venueId: number, venueName: string, price: number): void {
// 1. 余额校验
if (this.balance < price) {
// 生产环境提示余额不足
return;
}
// 2. 场地可用性校验(简化处理)
const venue = this.venues.find(v => v.id === venueId);
if (!venue || venue.availableCourts <= 0) {
return;
}
// 3. 随机分配场地号
const courtNo = String(Math.floor(Math.random() * 8) + 1) + '号场';
// 4. 解析时间段
const [startTime, endTime] = this.selectedTime.split('-');
// 5. 创建预约记录
const booking: Booking = {
id: this.nextBookId,
venueId: venueId,
venueName: venueName,
courtNo: courtNo,
date: this.selectedDate,
startTime: startTime,
endTime: endTime,
price: price,
status: '已预约',
bookTime: new Date().toLocaleString()
};
// 6. 更新数据
this.bookings = [booking, ...this.bookings];
this.balance -= price;
this.nextBookId++;
// 7. 减少可用场地数
this.venues = this.venues.map(v =>
v.id === venueId ? { ...v, availableCourts: v.availableCourts - 1 } : v
);
}
设计要点解析:
- 前置校验:余额、场地可用性都要先校验再执行
- 自动分配场地:用户不用选具体几号场,系统自动分配,简化操作
- 实时扣减库存:预约成功后立即减少可用场地数,防止超卖
- 时间解析:时间段字符串解析为开始和结束时间,便于后续计算
3.4 取消预约与退款
取消预约按规则退还部分费用。
typescript
/**
* 取消预约
* 退款规则:提前24小时以上退80%,24小时内不退
* @param bookId 预约ID
* @param price 预约金额
*/
private cancelBooking(bookId: number, price: number): void {
// 1. 查找预约记录
const booking = this.bookings.find(b => b.id === bookId);
if (!booking || booking.status !== '已预约') {
return;
}
// 2. 更新预约状态
this.bookings = this.bookings.map(b =>
b.id === bookId ? { ...b, status: '已取消' } : b
);
// 3. 退款:简化处理,退80%
const refund = Math.round(price * 0.8);
this.balance += refund;
// 4. 恢复场地可用性
const venueId = booking.venueId;
this.venues = this.venues.map(v =>
v.id === venueId ? { ...v, availableCourts: v.availableCourts + 1 } : v
);
}
3.5 签到入场
typescript
/**
* 签到入场
* @param bookId 预约ID
*/
private checkIn(bookId: number): void {
this.bookings = this.bookings.map(b =>
b.id === bookId ? { ...b, status: '已使用' } : b
);
}
/**
* 获取今日预约数量
*/
private getTodayBookingsCount(): number {
const today = new Date().toISOString().split('T')[0];
return this.bookings.filter(b => b.date === today && b.status === '已预约').length;
}
四、UI 设计与交互体验
4.1 整体布局结构
┌─────────────────────────┐
│ 运动数据统计 │ ← 可用场馆/今日预约/本周时长/累计消费
├─────────────────────────┤
│ 运动类型标签栏 │ ← 羽毛球/篮球/网球/游泳/乒乓球
├─────────────────────────┤
│ Tab 切换栏 │ ← 场馆预约 / 我的预约
├─────────────────────────┤
│ │
│ 内容展示区 │ ← 场馆卡片 / 预约记录
│ │
└─────────────────────────┘
4.2 主题色与视觉规范
主色调:翡翠绿 #059669
选择翡翠绿作为主题色的设计考量:
- 运动与健康象征:绿色是运动、健康、活力的代表色,与体育运动的"健康活力"属性高度契合,传递积极向上的运动精神
- 清新自然感:翡翠绿的清新感令人联想到户外运动和大自然,营造轻松愉悦的运动氛围
- 状态色兼容性好:翡翠绿与红色(已满)、琥珀色(紧张)、灰色(不可用)等状态色都能形成清晰对比
- 视觉舒适度高:中等饱和度的绿色长时间观看不刺眼,适合频繁使用的工具类应用
辅助色规范:
- 主色调:#059669(翡翠绿)
- 可预约状态:#059669(绿色可选)
- 已预约状态:#9CA3AF(灰色已占)
- 名额紧张:#F59E0B(琥珀色警告)
- 已取消状态:#9CA3AF(灰色)
- 已使用状态:#6B7280(深灰)
4.3 状态视觉区分表
| 场馆状态 | 标识颜色 | 预约按钮 | 视觉提示 |
|---|---|---|---|
| 场地充足 | 绿色 | 绿色实心按钮 | 充足 |
| 名额紧张 | 琥珀色 | 琥珀色按钮 | 仅剩N场 |
| 已满员 | 灰色 | 灰色禁用按钮 | 已满 |
| 未开放 | 灰色蒙版 | 不可点击 | 未开放 |
| 预约状态 | 标签颜色 | 可执行操作 | 退款规则 |
|---|---|---|---|
| 已预约 | 绿色实心 | 取消预约 | 提前24h退80% |
| 已使用 | 灰色实心 | 查看详情 | 不退款 |
| 已取消 | 灰色空心 | 重新预约 | 已退款80% |
| 已过期 | 红色实心 | 不可操作 | 不退款 |
五、关键技术点解析
5.1 时间段资源的冲突检测算法
场馆预约的核心是时间段资源管理,必须确保同一时间段不被重复预约。
冲突检测原理:
两个时间段 s1, e1 和 s2, e2 冲突的条件:
s1 < e2 且 s2 < e1
也就是两个时间段有重叠就算冲突,包含以下几种情况:
- 完全重叠:A包含B或B包含A
- 部分重叠:A的结束在B中间,或B的结束在A中间
- 边界相邻:A结束等于B开始,不算冲突,可以无缝衔接
时间段粒度选择:
- 30分钟:粒度细,灵活度高,但选择麻烦
- 60分钟:最常用,整点开始,符合习惯
- 90分钟:适合篮球等长时段运动
- 120分钟:适合包场
校园场景一般以1小时为基本单位,支持连续多选。
5.2 退款规则的阶梯式设计
取消预约的退款规则是场馆运营的重要部分,既要保障用户权益,也要避免恶意占场。
常见的阶梯退款规则:
| 取消时间 | 退款比例 | 说明 |
|---|---|---|
| 提前48小时以上 | 100%全额退 | 充分提前,场地容易重新租出 |
| 提前24-48小时 | 80%退款 | 较提前,大部分可重新安排 |
| 提前12-24小时 | 50%退款 | 时间较近,重新租出有难度 |
| 提前12小时以内 | 不退费 | 临时取消,场地基本空耗 |
设计考量:
- 越晚取消退越少:鼓励用户尽早决定,给场馆留出重新出租的时间
- 设置违约记录:频繁临时取消的用户可以计入黑名单
- 特殊情况豁免:如恶劣天气、场馆故障等特殊情况全额退款
六、常见问题与优化建议
踩坑点 1:预约时间冲突
问题表现 :两人同时预约同一时段,都显示预约成功
解决方案:使用数据库乐观锁或分布式锁,确保同一时段不超卖
踩坑点 2:退款金额计算错误
问题表现 :边界时间判断不准确,退款比例算错
解决方案:使用时间戳精确计算间隔,明确边界条件归属,单元测试覆盖所有边界
踩坑点 3:开放时间设置混乱
问题表现 :节假日、寒暑假开放时间不同,用户白跑一趟
解决方案:支持特殊日期排班设置,节假日前公告通知,预约时展示当天开放时间
踩坑点 4:爽约用户无约束
问题表现 :部分用户预约了不来,浪费场地资源
解决方案:建立爽约记录机制,累计多次爽约限制预约权限,引导用户按时使用
优化方向
- 增加约球组队功能,发布找人信息一起运动
- 实现教练预约,一对一专业指导
- 接入赛事报名功能,组织校内联赛
- 增加运动数据同步,对接手环记录运动数据
七、项目总结与扩展方向
核心收获
通过完成体育场馆预约项目,你将掌握:
- 时间段资源预约的冲突检测算法
- 预约订单的完整状态流转管理
- 退款规则的阶梯式业务设计
- 运动健康类应用的视觉设计
后续扩展功能
- 👥 约球组队:发布找人信息,匹配球友一起运动
- 👨🏫 教练预约:专业教练一对一指导,提升技术水平
- 🏆 赛事报名:校内联赛报名,组织竞技比赛
- ⌚ 运动数据:对接智能手环,同步运动数据
- 📍 场馆导航:地图导航到场馆,查看场馆设施
- 📊 运动排行:运动时长排行榜,激励坚持锻炼
运行效果
