简介:这是一套面向鸿蒙应用开发初学者与实训教学的全民健身预约系统App源码,移动端为首次发布,配套后台管理思路,可帮助开发者理解预约类应用的完整业务闭环。系统涵盖用户管理、场馆动态、运动常识、预订管理与预约通知等模块,用户可浏览并预约场馆、教练与陪练,并接收即将到来的预约提醒,界面简洁友好,支持多端流转与协同交互,同时注重用户隐私与数据安全。资源包共355个文件,约9.99MB,以99个jpg与49个png图片资源、63个js与25个ets页面逻辑、38个json及10个json5配置、5个ts脚本为主,另含少量xml、md与工程构建文件,结构完整便于二次开发。目前已有528人学习下载,适合作为课程设计、毕业实训或鸿蒙入门练手项目,帮助读者快速掌握ArkTS页面搭建、数据配置与预约流程实现,并在此基础上扩展通知与多端协同能力。
1. 从「约不到场地」说起:这套鸿蒙全民健身预约系统能解决什么
周末想打个球,打开手机发现附近场馆要么电话占线,要么到了现场才被告知满场——这种糟心事,做过场馆运营或者自己爱运动的人都懂。这套基于 HarmonyOS 的全民健身预约系统 App 源码,干的就是把「找场地、看时段、下单、核销」这条链路搬到鸿蒙设备上。它面向两类人:一类是想拿一个完整鸿蒙应用练手、准备做移动应用开发的开发者;另一类是手里有场馆资源、想快速搭一套预约工具的小团队。技术栈上,它走的是 ArkTS + ArkUI 声明式 UI 的路线,配合 HarmonyOS 的分布式能力做多端适配,属于典型的鸿蒙应用开发实战项目。你拿到源码后,能直接看到页面路由、状态管理、网络请求、本地缓存这几块是怎么串起来的,而不是只给你一个空壳。
2. 技术选型与工程结构:ArkTS 声明式 UI 怎么落地
2.1 为什么是 ArkTS 而不是 JS 或 Java
鸿蒙应用开发走到现在,官方主推的就是 ArkTS。它本质上是 TypeScript 的超集,加了声明式 UI 的语法糖和状态装饰器。这套源码选 ArkTS,核心原因是三点:第一,声明式 UI 写起来跟 React 那套心智接近,@State、@Prop、@Link这些装饰器管状态,页面刷新是自动的,不用手动操作 DOM;第二,ArkTS 在编译期能做类型检查,比纯 JS 少踩很多运行时玄学;第三,HarmonyOS 的 API 调用在 ArkTS 里是一等公民,比如分布式数据管理、原子化服务卡片,用 JS 写会别扭。
我一般会先看build-profile.json5和oh-package.json5这两个文件,它们决定了编译目标和依赖。源码里如果compileSdkVersion和compatibleSdkVersion对不上,跑起来就是一堆红。
// build-profile.json5 关键片段 { "app": { "products": [ { "name": "default", "signingConfig": "default", "compileSdkVersion": 11, // 编译用的 SDK 版本 "compatibleSdkVersion": 11, // 最低兼容版本,低于这个跑不起来 "runtimeOS": "HarmonyOS" } ] } }参数说明:compileSdkVersion是你编译时用的 API 版本,compatibleSdkVersion是设备最低要求。这两个值如果比你的 DevEco Studio 自带 SDK 高,编译直接报错。常见做法是先用 DevEco 的 SDK Manager 确认本地装了哪个版本,再回头改这个文件。
2.2 目录结构:一个页面从哪来、到哪去
拿到源码先别急着跑,把目录扫一遍,心里有个地图。典型的鸿蒙工程结构是这样的:
entry/src/main/ets/ ├── entryability/ │ └── EntryAbility.ts // 应用入口,生命周期在这 ├── pages/ │ ├── Index.ets // 首页,场馆列表 │ ├── VenueDetail.ets // 场馆详情 + 时段选择 │ ├── OrderConfirm.ets // 订单确认 │ └── MyOrders.ets // 我的预约 ├── components/ │ ├── VenueCard.ets // 场馆卡片组件 │ └── TimeSlotPicker.ets // 时段选择器 ├── model/ │ ├── Venue.ets // 场馆数据模型 │ └── Order.ets // 订单数据模型 ├── utils/ │ ├── request.ets // 网络请求封装 │ └── storage.ets // 本地缓存封装 └── resources/ // 图片、字符串、颜色这个结构里,pages放页面级组件,components放可复用 UI,model放数据类,utils放工具。判断一个鸿蒙项目规不规范,看components和utils有没有被抽出来就知道——全堆在pages里的,后期改一个样式能改到崩溃。
2.3 路由与状态管理:页面跳转和数据怎么传
鸿蒙的路由用router模块,跳转时通过params传参。源码里从首页跳到详情页,传的是场馆 id,详情页再拿 id 去请求详情数据。这种「传 id 不传对象」的做法是血泪经验——传对象在序列化时容易丢字段,尤其是嵌套结构。
// 首页跳转详情页 import router from '@ohos.router'; function goToDetail(venueId: number) { router.pushUrl({ url: 'pages/VenueDetail', params: { id: venueId } // 只传 id,详情页自己查 }).catch((err: Error) => { console.error('路由跳转失败: ' + err.message); }); } // 详情页接收参数 import router from '@ohos.router'; @Entry @Component struct VenueDetail { @State venueId: number = 0; aboutToAppear() { const params = router.getParams() as Record<string, number>; if (params && params.id) { this.venueId = params.id; this.loadVenueDetail(); // 拿到 id 再请求 } } }逻辑说明:pushUrl是压栈跳转,replaceUrl是替换当前页。aboutToAppear是页面生命周期,相当于 Android 的onCreate。参数用router.getParams()取,返回的是Object,得自己断言类型。注意params里的值只能是基本类型和可序列化对象,传函数或者类实例会静默失败,这个坑我踩过。
3. 核心模块拆解:预约链路从选场到下单
3.1 场馆列表与数据请求封装
首页要展示场馆列表,数据从后端来。源码里utils/request.ets对@ohos.net.http做了一层封装,统一处理 baseUrl、超时、错误码。这样页面里只关心「拿数据」,不关心「怎么拿」。
// utils/request.ets import http from '@ohos.net.http'; const BASE_URL = 'https://api.example.com'; // 换成你自己的后端地址 export async function get<T>(path: string): Promise<T> { const httpRequest = http.createHttp(); try { const resp = await httpRequest.request(BASE_URL + path, { method: http.RequestMethod.GET, connectTimeout: 10000, // 连接超时 10 秒 readTimeout: 10000, // 读取超时 10 秒 header: { 'Content-Type': 'application/json' } }); if (resp.responseCode === 200) { return JSON.parse(resp.result as string) as T; } throw new Error('HTTP ' + resp.responseCode); } finally { httpRequest.destroy(); // 必须销毁,否则连接泄漏 } }参数说明:connectTimeout是建立连接的超时,readTimeout是等响应的超时,两个都设上,不然弱网下页面会一直转圈。httpRequest.destroy()放在finally里,这是硬性要求——不销毁的话,频繁请求会把连接池占满,后面所有请求都卡住。常见做法是把get、post都封一遍,页面里只调get<Venue[]>('/venues')。
3.2 时段选择器:一个容易写错的交互组件
预约的核心是选时段。TimeSlotPicker.ets接收一个时段数组,渲染成网格,点中的高亮。这里的关键是「已约满的时段要置灰且不可点」,逻辑不能只靠 UI 判断,得在数据层就标记好。
// components/TimeSlotPicker.ets @Component export struct TimeSlotPicker { @Prop slots: TimeSlot[] = []; // 父组件传入的时段列表 @Link selectedId: number; // 双向绑定选中的时段 id build() { Grid() { ForEach(this.slots, (slot: TimeSlot) => { GridItem() { Text(slot.label) .fontSize(14) .fontColor(slot.available ? Color.Black : Color.Gray) .backgroundColor(this.selectedId === slot.id ? '#0A59F7' : '#F1F3F5') } .onClick(() => { if (!slot.available) return; // 约满的直接拦截 this.selectedId = slot.id; }) }) } .columnsTemplate('1fr 1fr 1fr') // 三列网格 .rowsGap(8) } }逻辑说明:@Prop是单向传递,父组件变了子组件跟着变;@Link是双向,子组件改了父组件也知道。ForEach渲染列表时必须给唯一 key,这里用slot.id。columnsTemplate控制列数,1fr表示等分。注意onClick里先判断available,别指望置灰就能拦住点击——鸿蒙里置灰只是视觉,事件照样触发,这个坑不写判断就会出「约满还能下单」的 bug。
3.3 订单确认与本地缓存
选完时段进订单确认页,提交前要把订单信息暂存,防止用户返回再进来数据丢了。源码用@ohos.data.preferences做轻量缓存,比关系型数据库轻,适合存这种临时草稿。
// utils/storage.ets import preferences from '@ohos.data.preferences'; let pref: preferences.Preferences; export async function initStorage(context: Context) { pref = await preferences.getPreferences(context, 'order_draft'); } export async function saveDraft(key: string, value: string) { await pref.put(key, value); await pref.flush(); // 必须 flush,否则不落盘 } export async function readDraft(key: string): Promise<string> { return await pref.get(key, '') as string; }参数说明:getPreferences的第二个参数是文件名,不同业务用不同文件,别全塞一个。put只是写内存,flush才落盘,漏了flush杀进程后数据就没了。订单提交成功后记得delete掉草稿,不然下次进来还是旧数据。
4. 跑起来:环境配置与真机调试的完整步骤
4.1 DevEco Studio 环境准备
跑鸿蒙项目,第一步是装 DevEco Studio。版本选择上,源码用的 API 11,你就装对应版本的 IDE,别追最新——新版本 IDE 可能改了构建插件,老项目直接编译不过。装完后在 SDK Manager 里确认HarmonyOS SDK的 API 11 已下载,ohpm和hvigor也一并装上。
# 检查 ohpm 是否可用 ohpm -v # 检查 hvigor 版本 hvigorw -v如果ohpm命令找不到,说明环境变量没配,手动把 SDK 的toolchains目录加到 PATH。这一步卡住的人最多,报错通常是command not found,别怀疑源码,先查环境。
4.2 依赖安装与首次编译
打开工程后,IDE 会提示同步依赖。如果没自动同步,手动执行:
# 在工程根目录执行 ohpm installohpm install会读oh-package.json5里的依赖,从仓库拉包。如果公司网络有限制,配一下.ohpmrc里的 registry。装完依赖后点 Build,第一次编译会慢,因为要生成 ArkTS 的中间产物。编译报错先看Build Output面板,最常见的两类:一是 SDK 版本不匹配,二是某个.ets文件里用了未导入的组件。
4.3 签名与真机运行
鸿蒙应用要跑真机,必须签名。在 DevEco 里点File > Project Structure > Signing Configs,勾选自动签名,登录开发者账号后 IDE 会帮你生成证书和 profile。没有账号的话,用模拟器也能跑,但模拟器跑不了分布式相关的能力。
# 查看已连接设备 hdc list targets # 安装应用到设备 hdc install entry/build/default/outputs/default/entry-default-signed.haphdc是鸿蒙的设备连接工具,类似 adb。list targets能看到设备序列号说明连接正常。install装的是签过名的 hap 包,路径在build目录下。装完在设备桌面找到应用图标点开,如果闪退,用hdc hilog看日志,过滤JSApp关键字。
提示:真机调试时,设备的「开发者选项」和「USB 调试」都要打开,否则
hdc list targets是空的。这个跟 Android 一个道理,但鸿蒙的入口藏得深一点,在「设置 > 系统和更新 > 开发人员选项」里。
5. 避坑与排查:那些让项目跑不起来的常见问题
5.1 编译报错「Cannot find module」
现象:编译时提示某个@ohos.xxx模块找不到。原因通常是 SDK 版本不对,或者oh-package.json5里没声明依赖。解决:先确认compileSdkVersion和本地 SDK 一致,再检查dependencies里有没有漏。如果是三方库,ohpm install重跑一遍。
5.2 页面白屏但无报错
现象:应用能启动,但首页一片白,日志里没明显错误。原因多半是build()里根组件用了Column但没给高度,或者数据请求还没回来就渲染了空列表。解决:给根容器加.width('100%').height('100%'),列表加if (this.list.length > 0)判断,加载中显示LoadingProgress。
5.3 网络请求返回 401 或 403
现象:接口调不通,返回鉴权失败。原因:请求头没带 token,或者 token 过期了。解决:在request.ets里统一加header,从preferences里读 token 塞进去。token 过期的话,加一个拦截逻辑,401 就跳登录页。
5.4 真机安装失败「signature verification failed」
现象:hdc install报签名校验失败。原因:hap 包没签名,或者签名证书和设备不匹配。解决:在 DevEco 里重新走一遍自动签名,确保signingConfigs里的 profile 包含当前设备。模拟器和真机的签名配置不一样,别混用。
5.5 列表滑动卡顿
现象:场馆列表长了之后滑动掉帧。原因:ForEach里每次都重建组件,或者图片没做懒加载。解决:用LazyForEach替代ForEach,配合IDataSource做数据源;图片用Image的syncLoad设为false,让它异步加载。
6. 二次开发:把预约系统改成你自己的业务
6.1 换后端:改一个文件就够
这套源码的网络层是收敛的,所有请求都走utils/request.ets。你要接自己的后端,改BASE_URL和几个接口路径就行。我一般会再抽一层api.ets,把「场馆列表」「时段查询」「下单」这些业务接口单独列出来,页面里只调api.getVenues(),这样后端换了字段,只改api.ets,不动页面。
// utils/api.ets import { get, post } from './request'; import { Venue, TimeSlot, Order } from '../model/types'; export const api = { getVenues: () => get<Venue[]>('/venues'), getSlots: (venueId: number, date: string) => get<TimeSlot[]>(`/venues/${venueId}/slots?date=${date}`), createOrder: (payload: Order) => post<Order>('/orders', payload) };这样分层的好处是,页面组件不关心 URL 长什么样,只关心拿到什么数据。二次开发时,你甚至可以把request.ets换成 mock 实现,前端先跑起来,后端慢慢接。
6.2 加功能:从「我的预约」到「核销码」
源码里MyOrders.ets只展示了订单列表。要加核销功能,思路是:订单详情页生成一个二维码,场馆端扫码核销。鸿蒙里生成二维码可以用@ohos.multimedia.image配合三方库,或者直接调后端接口拿二维码图片。核销状态通过轮询或者 WebSocket 更新。
改造时注意一点:订单状态是后端说了算,前端别自己改状态。我见过有人在本地把订单标记成「已核销」,结果后端没收到,两边数据对不上,排查了半天。状态变更一律走接口,本地只做展示。
6.3 多端适配:手机、平板、折叠屏
鸿蒙的分布式能力让一套代码跑多端成为可能,但「能跑」和「好用」是两回事。手机上是单列列表,平板上就该是双列。用GridRow和GridCol做响应式布局,根据断点调整列数。
// 响应式列数 GridRow({ columns: { sm: 4, md: 8, lg: 12 } }) { GridCol({ span: { sm: 4, md: 4, lg: 3 } }) { VenueCard({ venue: this.venue }) } }sm、md、lg对应不同屏幕宽度断点,span控制占几列。这样手机上一行一个卡片,平板上自动变一行两个。折叠屏展开时断点会变,布局跟着变,不用写两套代码。
6.4 一个我踩过的坑:状态管理别乱用 @State
最后说个血泪教训。刚上手 ArkTS 时,我把所有变量都标@State,结果页面频繁刷新,性能差得离谱。后来才明白,@State只该用在「变了要刷新 UI」的变量上。像venueId这种只在aboutToAppear里赋值、之后不变的,用普通成员变量就行。@Prop和@Link也要按需选,单向传值用@Prop,双向绑定才用@Link。滥用装饰器的后果是,一个无关变量的变化触发整页重绘,列表一长就卡。
从那以后我每次新建组件,都先问自己一句:这个变量变了,UI 要不要跟着变?要,才加装饰器。希望这个习惯能帮到你。
本文还有配套的精品资源,点击获取