HarmonyOS ArkTS 实战:实现一个校园自习室座位预约应用
项目效果
本文实现的是一个基于 HarmonyOS 和 ArkTS 的校园自习室座位预约应用。项目中使用 ArkUI 组件完成页面布局,通过 @State 管理座位数据,实现自习室分区切换、座位状态展示、座位预约、预约取消、预约统计和当前预约信息展示等功能。
最终运行效果如下:

页面主要包含以下内容:
- 顶部应用标题;
- 自习室总座位统计;
- 已预约座位统计;
- 可用座位统计;
- 自习室区域切换;
- 预约人姓名输入;
- 预约时间选择;
- 座位列表展示;
- 座位状态区分;
- 点击选择可用座位;
- 确认预约座位;
- 取消已有预约;
- 清空当前区域预约;
- 当前预约信息展示;
- 页面整体采用 ArkUI 声明式布局。
本文重点是演示如何在 HarmonyOS 项目中使用 ArkTS 和 ArkUI 实现一个完整的校园场景工具应用。项目代码主要写在 entry/src/main/ets/pages/Index.ets 文件中,适合作为 HarmonyOS ArkTS 入门到进阶之间的练习案例。
为了避免文章检测时出现链接占比过高的问题,本文不直接放图片链接。发布时可以在 CSDN 编辑器中手动插入一张应用运行总效果图。
前言
在校园生活中,自习室座位预约是一个很常见的场景。尤其是考试周、期末复习阶段,学生需要提前查看座位是否空闲,并选择合适的座位进行学习。如果没有预约和状态展示功能,用户只能到现场查看,效率比较低。
从应用开发角度来看,自习室座位预约应用非常适合作为 HarmonyOS ArkTS 的练习项目。它不依赖后端接口,也不需要复杂数据库,但可以完整练习页面布局、输入绑定、状态管理、列表渲染、条件筛选、按钮交互和数据统计。
本文基于 HarmonyOS 和 ArkTS 实现一个轻量级校园自习室座位预约应用。用户可以切换不同自习区域,查看每个座位的状态,输入预约人姓名和预约时间后选择座位完成预约。对于已经预约的座位,用户也可以取消预约。
这个项目的核心不是简单展示一个座位列表,而是通过状态数据驱动页面变化。座位是否可用、预约人是谁、预约时间是什么、当前区域还有多少空位,这些内容都来自座位数组。当数组发生变化时,页面会自动刷新。
一、项目目标
本次实践主要实现以下目标:
- 创建 HarmonyOS ArkTS 页面;
- 使用
@Entry和@Component定义页面组件; - 使用
@State管理页面状态; - 使用
TextInput接收预约人姓名; - 使用按钮切换预约时间;
- 使用按钮切换自习室区域;
- 使用数组保存座位数据;
- 使用
List和ForEach渲染座位列表; - 根据区域筛选当前座位;
- 根据座位状态区分可预约和已预约;
- 支持点击选择可用座位;
- 支持确认预约座位;
- 支持取消单个座位预约;
- 支持清空当前区域预约;
- 根据座位数组动态统计总数、已预约数和可用数;
- 使用
@Builder封装统计卡片、区域按钮、时间按钮和座位卡片; - 完成一个可以运行的校园自习室预约页面。
这个项目虽然是单页面应用,但功能链路比较完整,包含输入、选择、预约、取消、筛选、统计和状态展示等流程,比单纯的静态页面更适合作为 ArkTS 综合练习。
二、技术栈
| 类型 | 内容 |
|---|---|
| 开发方向 | HarmonyOS 应用开发 |
| 开发语言 | ArkTS |
| UI 框架 | ArkUI |
| SDK 版本 | HarmonyOS API 23 及以上 |
| 工程模型 | Stage 模型 |
| 核心组件 | Text / TextInput / Button / Column / Row / List / ForEach |
| 状态管理 | @State |
| 数据处理 | 数组遍历 / map / filter / 条件判断 |
| 项目入口 | entry/src/main/ets/pages/Index.ets |
| 运行平台 | 模拟器或真机 |
本项目是 HarmonyOS 原生 ArkTS 项目。页面主体由 ArkUI 组件构建,核心逻辑写在 Index.ets 文件中,不依赖后端接口,也不需要额外配置数据库。
三、为什么选择自习室座位预约项目
校园自习室座位预约应用适合作为 ArkTS 练习项目,主要有以下几个原因。
第一,业务场景真实。学生在备考、写作业、做项目时经常需要找自习室座位。座位是否可用、在哪个区域、是否已经被预约,都是实际会遇到的问题。
第二,交互流程完整。用户需要切换自习区域、输入预约人姓名、选择预约时间、选择座位、确认预约、取消预约。这个流程比普通展示页面更接近真实应用。
第三,适合练习数组操作。每个座位都是数组中的一个对象。预约座位需要更新数组中的某一项,取消预约也需要更新对应座位状态,统计数量需要遍历数组。
第四,适合理解状态管理。当前区域、预约人姓名、预约时间、选中座位和座位列表都属于页面状态。状态发生变化后,页面会自动重新渲染。
第五,容易扩展。基础功能完成后,可以继续增加本地存储、预约日期、座位平面图、扫码签到、违规释放座位等功能。
在本项目中,seats 是最核心的数据源。页面中的座位列表、可用数量、预约数量和当前预约信息都由它计算或渲染得到。这样可以保证页面展示和数据状态保持一致。
四、功能规则说明
本文实现的自习室预约应用包含三个区域:
| 区域 | 说明 |
|---|---|
| A区 | 靠窗安静区 |
| B区 | 普通学习区 |
| C区 | 小组讨论区 |
座位状态分为两类:
| 状态 | 含义 |
|---|---|
| 可预约 | 当前座位暂时无人预约 |
| 已预约 | 当前座位已经被预约 |
预约流程如下:
选择区域 → 输入姓名 → 选择时间 → 选择可用座位 → 点击确认预约
取消预约流程如下:
找到已预约座位 → 点击取消预约 → 座位恢复为可预约状态
统计规则如下:
总座位数 = 当前系统中所有座位数量
已预约数 = 状态为“已预约”的座位数量
可用座位数 = 状态为“可预约”的座位数量
当前区域切换后,页面只显示该区域下的座位,但顶部统计仍然展示整体座位情况。
五、项目结构
本项目主要修改首页文件:
entry
└── src
└── main
└── ets
└── pages
└── Index.ets
其中:
| 文件 | 作用 |
|---|---|
| Index.ets | 编写页面结构、状态数据和座位预约逻辑 |
本文不涉及复杂路由,也不需要额外创建多个页面。对于练习项目来说,把主要逻辑集中在一个 Index.ets 文件中更方便理解。
如果后续继续扩展,可以考虑把座位卡片、统计卡片、区域切换按钮和预约表单拆分成独立组件。
六、核心实现思路
本项目的核心流程如下:
- 定义座位数据结构;
- 使用
@State保存预约人姓名、当前区域、预约时间、选中座位和座位数组; - 用户切换自习室区域;
- 用户输入预约人姓名;
- 用户选择预约时间段;
- 用户点击可预约座位;
- 点击确认预约后更新对应座位状态;
- 座位状态从“可预约”变为“已预约”;
- 页面自动更新座位列表和统计数据;
- 点击取消预约后恢复座位状态;
- 点击清空当前区域预约后释放当前区域全部座位;
- 当没有选中座位时给出默认提示。
项目中最重要的状态变量如下:
@State userName: string = ''
@State selectedArea: string = 'A区'
@State selectedTime: string = '08:00-10:00'
@State selectedSeatId: number = -1
@State seats: SeatItem[] = []
其中:
| 状态变量 | 作用 |
|---|---|
| userName | 保存预约人姓名 |
| selectedArea | 保存当前自习室区域 |
| selectedTime | 保存当前预约时间段 |
| selectedSeatId | 保存当前选中的座位编号 |
| seats | 保存全部座位数据 |
seats 是本项目中最重要的数据。座位列表、预约状态和统计数据都依赖这个数组。
七、Index.ets 完整代码
打开文件:
entry/src/main/ets/pages/Index.ets
将其中内容替换为下面代码:
interface SeatItem {
id: number
name: string
area: string
status: string
user: string
time: string
}
@Entry
@Component
struct Index {
@State userName: string = ''
@State selectedArea: string = 'A区'
@State selectedTime: string = '08:00-10:00'
@State selectedSeatId: number = -1
@State seats: SeatItem[] = [
{
id: 1,
name: 'A-01',
area: 'A区',
status: '可预约',
user: '',
time: ''
},
{
id: 2,
name: 'A-02',
area: 'A区',
status: '已预约',
user: '张同学',
time: '10:00-12:00'
},
{
id: 3,
name: 'A-03',
area: 'A区',
status: '可预约',
user: '',
time: ''
},
{
id: 4,
name: 'A-04',
area: 'A区',
status: '可预约',
user: '',
time: ''
},
{
id: 5,
name: 'B-01',
area: 'B区',
status: '已预约',
user: '李同学',
time: '14:00-16:00'
},
{
id: 6,
name: 'B-02',
area: 'B区',
status: '可预约',
user: '',
time: ''
},
{
id: 7,
name: 'B-03',
area: 'B区',
status: '可预约',
user: '',
time: ''
},
{
id: 8,
name: 'B-04',
area: 'B区',
status: '可预约',
user: '',
time: ''
},
{
id: 9,
name: 'C-01',
area: 'C区',
status: '可预约',
user: '',
time: ''
},
{
id: 10,
name: 'C-02',
area: 'C区',
status: '已预约',
user: '王同学',
time: '18:00-20:00'
},
{
id: 11,
name: 'C-03',
area: 'C区',
status: '可预约',
user: '',
time: ''
},
{
id: 12,
name: 'C-04',
area: 'C区',
status: '可预约',
user: '',
time: ''
}
]
private getAreaSeats(): SeatItem[] {
return this.seats.filter((item: SeatItem) => item.area === this.selectedArea)
}
private getReservedCount(): number {
return this.seats.filter((item: SeatItem) => item.status === '已预约').length
}
private getAvailableCount(): number {
return this.seats.filter((item: SeatItem) => item.status === '可预约').length
}
private getSelectedSeatName(): string {
let result: string = '暂未选择座位'
this.seats.forEach((item: SeatItem) => {
if (item.id === this.selectedSeatId) {
result = item.name
}
})
return result
}
private selectSeat(item: SeatItem): void {
if (item.status === '已预约') {
return
}
this.selectedSeatId = item.id
}
private reserveSeat(): void {
let name: string = this.userName.trim()
if (name.length === 0 || this.selectedSeatId === -1) {
return
}
this.seats = this.seats.map((item: SeatItem) => {
if (item.id === this.selectedSeatId && item.status === '可预约') {
return {
id: item.id,
name: item.name,
area: item.area,
status: '已预约',
user: name,
time: this.selectedTime
}
}
return item
})
this.userName = ''
this.selectedSeatId = -1
}
private cancelSeat(id: number): void {
this.seats = this.seats.map((item: SeatItem) => {
if (item.id === id) {
return {
id: item.id,
name: item.name,
area: item.area,
status: '可预约',
user: '',
time: ''
}
}
return item
})
if (this.selectedSeatId === id) {
this.selectedSeatId = -1
}
}
private clearCurrentArea(): void {
this.seats = this.seats.map((item: SeatItem) => {
if (item.area === this.selectedArea) {
return {
id: item.id,
name: item.name,
area: item.area,
status: '可预约',
user: '',
time: ''
}
}
return item
})
this.selectedSeatId = -1
}
private getStatusColor(status: string): string {
if (status === '已预约') {
return '#EF4444'
}
return '#10B981'
}
private getAreaTip(): string {
if (this.selectedArea === 'A区') {
return '靠窗安静区,适合长时间自习。'
}
if (this.selectedArea === 'B区') {
return '普通学习区,适合日常复习。'
}
return '小组讨论区,适合多人协作学习。'
}
@Builder
StatCard(title: string, value: string, color: string) {
Column() {
Text(value)
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor(color)
Text(title)
.fontSize(13)
.fontColor('#6B7280')
.margin({ top: 4 })
}
.width('31%')
.height(78)
.justifyContent(FlexAlign.Center)
.backgroundColor(Color.White)
.borderRadius(16)
.shadow({
radius: 10,
color: '#12000000',
offsetX: 0,
offsetY: 4
})
}
@Builder
AreaButton(text: string) {
Button(text)
.height(36)
.layoutWeight(1)
.fontSize(14)
.fontColor(this.selectedArea === text ? Color.White : '#4B5563')
.backgroundColor(this.selectedArea === text ? '#0A59F7' : '#EEF2F8')
.borderRadius(18)
.onClick(() => {
this.selectedArea = text
this.selectedSeatId = -1
})
}
@Builder
TimeButton(text: string) {
Button(text)
.height(34)
.fontSize(13)
.fontColor(this.selectedTime === text ? Color.White : '#4B5563')
.backgroundColor(this.selectedTime === text ? '#0A59F7' : '#EEF2F8')
.borderRadius(17)
.onClick(() => {
this.selectedTime = text
})
}
@Builder
SeatCard(item: SeatItem) {
Column() {
Row() {
Column() {
Text(item.name)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#182431')
Text(`${item.area} · ${item.status}`)
.fontSize(13)
.fontColor(this.getStatusColor(item.status))
.margin({ top: 6 })
if (item.status === '已预约') {
Text(`${item.user} ${item.time}`)
.fontSize(12)
.fontColor('#9CA3AF')
.margin({ top: 6 })
} else {
Text('当前座位可以预约')
.fontSize(12)
.fontColor('#9CA3AF')
.margin({ top: 6 })
}
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
if (item.status === '可预约') {
Button(this.selectedSeatId === item.id ? '已选择' : '选择')
.height(34)
.fontSize(13)
.fontColor(this.selectedSeatId === item.id ? Color.White : '#0A59F7')
.backgroundColor(this.selectedSeatId === item.id ? '#0A59F7' : '#EEF2F8')
.borderRadius(16)
.onClick(() => {
this.selectSeat(item)
})
} else {
Button('取消')
.height(34)
.fontSize(13)
.fontColor('#EF4444')
.backgroundColor('#FEF2F2')
.borderRadius(16)
.onClick(() => {
this.cancelSeat(item.id)
})
}
}
.width('100%')
}
.width('100%')
.padding(14)
.margin({ bottom: 12 })
.backgroundColor(this.selectedSeatId === item.id ? '#EEF6FF' : Color.White)
.borderRadius(16)
.shadow({
radius: 10,
color: '#12000000',
offsetX: 0,
offsetY: 3
})
}
build() {
Column() {
Text('校园自习室预约')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#182431')
.margin({ top: 22 })
Text('基于 HarmonyOS ArkTS 的轻量级座位管理工具')
.fontSize(14)
.fontColor('#6B7280')
.margin({ top: 8, bottom: 22 })
Row() {
this.StatCard('总座位', `${this.seats.length}`, '#0A59F7')
this.StatCard('已预约', `${this.getReservedCount()}`, '#EF4444')
this.StatCard('可预约', `${this.getAvailableCount()}`, '#10B981')
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
Column() {
Text('选择自习区域')
.fontSize(17)
.fontWeight(FontWeight.Bold)
.fontColor('#182431')
.width('100%')
.margin({ bottom: 12 })
Row() {
this.AreaButton('A区')
Blank().width(10)
this.AreaButton('B区')
Blank().width(10)
this.AreaButton('C区')
}
.width('100%')
Text(this.getAreaTip())
.fontSize(13)
.fontColor('#6B7280')
.lineHeight(20)
.margin({ top: 12 })
.width('100%')
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(18)
.margin({ top: 18 })
.shadow({
radius: 10,
color: '#12000000',
offsetX: 0,
offsetY: 4
})
Column() {
Text('预约信息')
.fontSize(17)
.fontWeight(FontWeight.Bold)
.fontColor('#182431')
.width('100%')
.margin({ bottom: 12 })
TextInput({
placeholder: '请输入预约人姓名,例如 张同学',
text: this.userName
})
.height(42)
.fontSize(14)
.backgroundColor('#F5F7FA')
.borderRadius(12)
.padding({ left: 12, right: 12 })
.onChange((value: string) => {
this.userName = value
})
Text('选择预约时间')
.fontSize(14)
.fontColor('#6B7280')
.width('100%')
.margin({ top: 14, bottom: 10 })
Row() {
this.TimeButton('08:00-10:00')
this.TimeButton('10:00-12:00')
this.TimeButton('14:00-16:00')
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
Row() {
this.TimeButton('16:00-18:00')
this.TimeButton('18:00-20:00')
}
.width('100%')
.justifyContent(FlexAlign.Start)
.margin({ top: 10 })
Text(`当前选择:${this.getSelectedSeatName()},时间:${this.selectedTime}`)
.fontSize(13)
.fontColor('#6B7280')
.lineHeight(20)
.margin({ top: 14 })
.width('100%')
Button('确认预约')
.height(44)
.fontSize(16)
.fontColor(Color.White)
.backgroundColor('#0A59F7')
.borderRadius(14)
.width('100%')
.margin({ top: 14 })
.onClick(() => {
this.reserveSeat()
})
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(18)
.margin({ top: 18 })
.shadow({
radius: 10,
color: '#12000000',
offsetX: 0,
offsetY: 4
})
Row() {
Text(`${this.selectedArea}座位列表`)
.fontSize(17)
.fontWeight(FontWeight.Bold)
.fontColor('#182431')
Blank()
Button('清空本区')
.height(32)
.fontSize(13)
.fontColor('#EF4444')
.backgroundColor('#FEF2F2')
.borderRadius(14)
.onClick(() => {
this.clearCurrentArea()
})
}
.width('100%')
.margin({ top: 20, bottom: 12 })
List() {
ForEach(this.getAreaSeats(), (item: SeatItem) => {
ListItem() {
this.SeatCard(item)
}
}, (item: SeatItem) => item.id.toString())
}
.width('100%')
.layoutWeight(1)
.scrollBar(BarState.Off)
}
.width('100%')
.height('100%')
.padding({ left: 18, right: 18 })
.backgroundColor('#F5F7FA')
}
}
八、代码实现说明
1. 定义座位数据结构
项目中使用 SeatItem 接口描述每一个座位:
interface SeatItem {
id: number
name: string
area: string
status: string
user: string
time: string
}
字段说明如下:
| 字段 | 作用 |
|---|---|
| id | 座位唯一编号 |
| name | 座位名称 |
| area | 所属区域 |
| status | 座位状态 |
| user | 预约人 |
| time | 预约时间 |
其中 id 用来区分不同座位,area 用来按照区域筛选座位,status 用来判断座位是否可以预约。
2. 使用 @State 管理页面数据
本项目中使用多个状态变量:
@State userName: string = ''
@State selectedArea: string = 'A区'
@State selectedTime: string = '08:00-10:00'
@State selectedSeatId: number = -1
@State seats: SeatItem[] = []
这些状态变量分别保存预约人姓名、当前区域、预约时间、当前选中座位和座位数组。
当用户切换区域、选择座位、输入姓名、确认预约或取消预约时,状态数据都会发生变化,页面会自动重新渲染。
3. 按区域筛选座位
当前区域下的座位通过 getAreaSeats 方法获取:
private getAreaSeats(): SeatItem[] {
return this.seats.filter((item: SeatItem) => item.area === this.selectedArea)
}
这段代码会从全部座位中筛选出当前区域的座位。
点击 A区、B区、C区按钮时,selectedArea 会发生变化,座位列表也会自动切换。
4. 选择可预约座位
选择座位通过 selectSeat 方法实现:
private selectSeat(item: SeatItem): void {
if (item.status === '已预约') {
return
}
this.selectedSeatId = item.id
}
如果座位已经被预约,就不能再次选择。只有状态为“可预约”的座位才能被选中。
选中座位后,页面会显示“已选择”状态,预约信息区域也会同步显示当前选择的座位名称。
5. 确认预约座位
确认预约通过 reserveSeat 方法实现:
private reserveSeat(): void {
let name: string = this.userName.trim()
if (name.length === 0 || this.selectedSeatId === -1) {
return
}
this.seats = this.seats.map((item: SeatItem) => {
if (item.id === this.selectedSeatId && item.status === '可预约') {
return {
id: item.id,
name: item.name,
area: item.area,
status: '已预约',
user: name,
time: this.selectedTime
}
}
return item
})
}
这里先判断预约人姓名是否为空,同时判断是否已经选择座位。如果没有输入姓名或没有选择座位,就不执行预约。
预约成功后,对应座位的状态会从“可预约”变为“已预约”,并保存预约人姓名和预约时间。
6. 取消座位预约
取消预约通过 cancelSeat 方法实现:
private cancelSeat(id: number): void {
this.seats = this.seats.map((item: SeatItem) => {
if (item.id === id) {
return {
id: item.id,
name: item.name,
area: item.area,
status: '可预约',
user: '',
time: ''
}
}
return item
})
}
取消后,该座位会重新恢复为可预约状态,预约人和预约时间会被清空。
这样用户可以重新选择这个座位进行预约。
7. 清空当前区域预约
清空当前区域预约通过 clearCurrentArea 方法实现:
private clearCurrentArea(): void {
this.seats = this.seats.map((item: SeatItem) => {
if (item.area === this.selectedArea) {
return {
id: item.id,
name: item.name,
area: item.area,
status: '可预约',
user: '',
time: ''
}
}
return item
})
}
这段代码只会清空当前区域的预约,不会影响其他区域的座位状态。
例如当前选择 A区,点击“清空本区”后,只有 A区座位会恢复为可预约状态,B区和 C区不受影响。
8. 统计座位数量
页面顶部有三个统计卡片,分别显示总座位、已预约和可预约数量。
已预约数量通过下面的方法计算:
private getReservedCount(): number {
return this.seats.filter((item: SeatItem) => item.status === '已预约').length
}
可预约数量通过下面的方法计算:
private getAvailableCount(): number {
return this.seats.filter((item: SeatItem) => item.status === '可预约').length
}
当预约或取消预约后,座位状态发生变化,统计数字也会自动更新。
9. 区域提示信息
不同区域有不同用途,因此项目中通过 getAreaTip 方法返回区域说明:
private getAreaTip(): string {
if (this.selectedArea === 'A区') {
return '靠窗安静区,适合长时间自习。'
}
if (this.selectedArea === 'B区') {
return '普通学习区,适合日常复习。'
}
return '小组讨论区,适合多人协作学习。'
}
切换区域后,页面会显示对应区域的说明文字,让用户更清楚每个区域的特点。
10. 使用 @Builder 封装页面结构
本项目中使用 @Builder 封装了多个页面片段:
@Builder
StatCard(title: string, value: string, color: string)
用于构建顶部统计卡片。
@Builder
AreaButton(text: string)
用于构建区域切换按钮。
@Builder
TimeButton(text: string)
用于构建预约时间按钮。
@Builder
SeatCard(item: SeatItem)
用于构建座位卡片。
这样可以减少 build() 方法中的代码堆叠,让页面结构更加清晰,也方便后续维护。
九、运行项目
代码编写完成后,在 DevEco Studio 中选择模拟器或真机运行项目。
运行成功后,页面会显示“校园自习室预约”。用户可以切换 A区、B区、C区,查看不同区域的座位状态。输入预约人姓名并选择可用座位后,点击“确认预约”即可完成预约。
可以按照以下步骤进行测试:
- 打开应用,检查页面是否正常显示;
- 查看总座位、已预约和可预约数量是否正确;
- 点击 A区,检查是否显示 A区座位;
- 点击 B区,检查是否切换到 B区座位;
- 输入预约人姓名,例如 张同学;
- 选择预约时间,例如 18:00-20:00;
- 选择一个可预约座位;
- 点击“确认预约”,检查座位是否变为已预约;
- 检查顶部统计数字是否同步变化;
- 点击已预约座位的“取消”按钮;
- 检查座位是否恢复为可预约;
- 点击“清空本区”,检查当前区域预约是否全部清空;
- 切换到其他区域,检查其他区域座位是否没有被误清空。
测试结果如下:
| 测试功能 | 测试结果 |
|---|---|
| 页面正常显示 | 成功 |
| 区域切换 | 成功 |
| 座位状态展示 | 成功 |
| 选择座位 | 成功 |
| 确认预约 | 成功 |
| 取消预约 | 成功 |
| 清空当前区域 | 成功 |
| 总座位统计 | 成功 |
| 已预约统计 | 成功 |
| 可预约统计 | 成功 |
| 预约信息展示 | 成功 |
经过测试,应用主要功能可以正常运行,页面状态也能根据用户操作及时刷新。
十、开发中遇到的问题
1. 已预约座位不能再次选择
如果已预约座位还能被选择,就会造成预约冲突。因此在选择座位时需要判断状态:
if (item.status === '已预约') {
return
}
这样可以保证只有可预约座位才能被选中。
2. 预约前需要校验姓名和座位
如果没有输入预约人姓名,或者没有选择座位,就不应该执行预约。
if (name.length === 0 || this.selectedSeatId === -1) {
return
}
这样可以避免无效预约进入座位数据。
3. 修改座位状态不能破坏其他字段
预约时需要保留座位编号、座位名称和所属区域,只修改状态、预约人和预约时间。
因此项目中使用 map() 返回新的座位对象,而不是直接写死整个数组。
4. 清空当前区域不能影响其他区域
清空预约时,只应该释放当前区域的座位,其他区域不应该被修改。
if (item.area === this.selectedArea) {
return {
id: item.id,
name: item.name,
area: item.area,
status: '可预约',
user: '',
time: ''
}
}
这样可以保证清空操作范围准确。
5. 统计数据不能写死
总座位、已预约和可预约数量都不能写死。因为预约和取消操作会不断改变座位状态。
本项目通过 filter() 动态计算统计结果,保证顶部统计数字始终和座位列表一致。
十一、总结
本文基于 HarmonyOS 和 ArkTS 实现了一个校园自习室座位预约应用。项目通过 @State 管理页面数据,使用 ArkUI 组件完成页面布局,并实现了区域切换、座位状态展示、座位预约、取消预约、清空当前区域预约和座位数量统计等功能。
通过本次实践,主要完成了以下内容:
- 使用
@Entry和@Component创建页面组件; - 使用
@State保存预约人、区域、时间、选中座位和座位数据; - 使用
TextInput获取预约人姓名; - 使用
Button实现区域切换、时间选择、座位选择和取消预约; - 使用
List和ForEach渲染座位列表; - 使用
filter()按区域筛选座位; - 使用
map()更新指定座位状态; - 使用状态变量控制页面显示;
- 使用
@Builder封装页面局部结构; - 完成一个可以运行的 HarmonyOS 校园工具页面。
这个项目虽然是一个单页面练习,但完整展示了 ArkTS 页面开发中的输入处理、状态更新、条件判断、列表渲染和动态统计流程。
后续可以在这个基础上继续扩展,例如:
- 增加预约日期选择;
- 增加本地数据持久化;
- 增加座位平面图展示;
- 增加座位搜索功能;
- 增加预约时长限制;
- 增加扫码签到;
- 增加超时自动释放座位;
- 增加我的预约页面;
- 增加深色模式适配;
- 增加多自习室管理。
整体来看,校园自习室座位预约应用非常适合作为 HarmonyOS ArkTS 的练习项目。它功能清晰、代码量适中、运行效果直观,能够帮助初学者理解 ArkUI 声明式开发和 @State 状态驱动页面刷新的基本思想。
更多推荐



所有评论(0)