解题指南
课程表应用 — 模板补全解题指南
一、解题方法:四步拆解法
第一步:先看数据模型,搞清楚"有什么"
模板底部有 CourseDay 和 CourseItem 两个类,字段都是空的。但注释已经告诉你每个字段的用途。先把数据模型填上,因为所有上层代码都在操作这些字段。填完之后 course.name、course.time 这些属性就是合法的,写 UI 代码时心里有底。
这一步是很多人忽略的。一上来就看 UI 模板发懵,是因为你连数据长什么样都不知道。
第二步:看已有代码,反推缺失部分
模板不是完全空的,它留了大量线索。比如:
ForEach( //写入代码 => {
你不需要猜 ForEach 要遍历什么。往上看——buildWeekNavigation 是"星期导航构建器",courseData 是一周的数据,那遍历的肯定是 this.courseData。
规律:模板里已经写出来的代码,就是你最大的提示。 已有的 .fontSize(16)、.borderRadius(20) 这些链式调用告诉你这是什么组件,你只需要补参数。
第三步:数注释,不要数"写入代码"
每个 //写入代码 旁边几乎都有中文注释,这不是装饰,是告诉你这里写什么。比如:
.backgroundColor(//写入代码 ) // 选中状态样式
注释说"选中状态样式",逻辑就是:选中一个颜色,没选中另一个——三元表达式。
你做的不是"从零创作",是"根据提示填空"。
第四步:参考答案只用来"验证",不要用来"背"
- 自己先根据注释和已有代码尝试写
- 写不出来再看参考答案那一行对应的是什么
- 理解为什么是这个写法,然后合上答案自己重写
二、14 处填空逐个拆解
填空 1:状态变量声明(第 282-286 行)
模板:
//写入代码
// 当前选中的星期索引
// 课程数据数组
// 是否显示删除对话框
// 正在删除的课程项
填入:
@State currentDayIndex: number = 0
@State courseData: CourseDay[] = []
@State isShowDeleteDialog: boolean = false
@State deletingCourse: CourseItem | null = null
思路: 注释直接告诉你四个变量名和用途,@State 是 ArkTS 状态装饰器,类型根据注释推断。
填空 2:aboutToAppear 调用(第 289-291 行)
模板:
aboutToAppear() {
//写入代码
// 组件出现时初始化课程数据
}
填入:
this.initCourseData()
思路: 注释说"初始化课程数据",下面已经定义了 initCourseData() 方法,调用它就行。
填空 3:initCourseData 数据填充(第 297-301 行)
模板:
initCourseData() {
this.courseData = [// 初始化一周的课程数据
//写入代码
]
}
填入: 参考答案第 17-68 行的完整数组内容,即 7 天的课程数据。结构如下:
{
day: '周一',
date: '12月25日',
courses: [
{ id: '1', name: '高等数学', time: '08:00-09:40', classroom: '教学楼A201', teacher: '张教授' },
{ id: '2', name: '大学英语', time: '10:00-11:40', classroom: '外语楼B101', teacher: '李老师' },
{ id: '3', name: '计算机基础', time: '14:00-15:40', classroom: '实验楼C301', teacher: '王教授' }
]
},
// ... 周二到周日,参考答案完整数据
思路: 字段名和数据模型 CourseDay、CourseItem 对应。数据内容直接从参考答案搬。
填空 4:build 主界面补全(第 307-320 行)
模板:
build() {
Column() {
//写入代码
// 顶部标题
Text("我的课程表").fontSize(24).fontWeight(900).fontColor('#000000')
// 星期导航
this.buildWeekNavigation()
// 课程列表区域
}
// 宽度100%
// 高度100%
// 背景色
// 内边距
}
填入:
标题后面加 margin:
.margin({ top: 20, bottom: 10 })
星期导航和课程列表之间加:
this.buildWeekNavigation()
this.buildCourseList()
Column 属性链(注释已经告诉你每个属性是什么):
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
.padding(16)
思路: 注释已经列出了每个属性的含义,直接按注释写属性值。
填空 5:ForEach 参数(第 328 行)
模板:
ForEach( //写入代码 => {
填入:
ForEach(this.courseData, (dayItem: CourseDay, index: number) => {
思路: buildWeekNavigation 是星期导航,遍历的是一周数据 this.courseData,回调参数是每一天的数据项和索引。
填空 6:星期导航内部 Text(第 330-337 行)
模板:
Column() {
//写入代码
}
填入:
Text(dayItem.day)
.fontSize(16)
.fontColor(index === this.currentDayIndex ? '#FFFFFF' : '#666666')
Text(dayItem.date)
.fontSize(12)
.fontColor(index === this.currentDayIndex ? '#FFFFFF' : '#999999')
.margin({ top: 2 })
思路: 星期导航要显示"星期"和"日期"两行文字,选中时白色、未选中时灰色。三元表达式判断 index === this.currentDayIndex。
填空 7:选中状态背景色(第 340 行)
模板:
.backgroundColor(//写入代码 ) // 选中状态样式
填入:
.backgroundColor(index === this.currentDayIndex ? '#007DFF' : '#FFFFFF')
思路: 注释说"选中状态样式",选中蓝色 #007DFF,未选中白色 #FFFFFF。
填空 8:onClick 更新索引(第 343 行)
模板:
.onClick(() => {
//写入代码 // 更新选中状态
})
填入:
this.currentDayIndex = index
思路: 注释说"更新选中状态",点击哪个就把它设为当前选中。
填空 9:scrollable 方向(第 350 行)
模板:
.scrollable( //写入代码) // 设置水平滚动
填入:
.scrollable(ScrollDirection.Horizontal)
思路: 注释说"水平滚动",枚举值就是 ScrollDirection.Horizontal。
填空 10:课程列表 ForEach(第 356-362 行)
模板:
if ( //写入代码 ) {
List({ space: 10 }) {
ForEach(//写入代码 ) => {
ListItem() {
this.buildCourseItem(course, index)
}
}, //写入代码 // 键值生成器
}
填入:
if (this.courseData[this.currentDayIndex].courses.length > 0) {
ForEach(this.courseData[this.currentDayIndex].courses, (course: CourseItem, index: number) => {
}, (course: CourseItem) => course.id)
思路:
- if 条件:当前天有课程才显示列表
- ForEach 第一个参数:当前天的课程数组
- 键值生成器:用课程 id 做唯一标识
填空 11:空状态居中(第 377-379 行)
模板:
.layoutWeight(1)
//写入代码
// 垂直居中
// 水平居中
填入:
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
思路: 注释说"垂直居中"和"水平居中",对应 Flex 布局的两个属性。
填空 12:时间标签 + 课程信息(第 394-417 行)
时间标签 Column 内部:
Text(course.time.split('-')[0])
.fontSize(14)
.fontColor('#007DFF')
Text('|')
.fontSize(12)
.fontColor('#007DFF')
.margin({ top: 2, bottom: 2 })
Text(course.time.split('-')[1])
.fontSize(14)
.fontColor('#007DFF')
课程信息 Column 内部:
Text(course.name)
.fontSize(18)
.fontColor('#333333')
.fontWeight(FontWeight.Medium)
Text(`${course.classroom} | ${course.teacher}`)
.fontSize(14)
.fontColor('#666666')
.margin({ top: 4 })
思路: course.time 格式是 "08:00-09:40",用 split('-') 拆成开始和结束时间。竖线 | 做分隔。课程信息用模板字符串拼接教室和教师。
填空 13:背景/圆角/阴影 + 手势(第 425-438 行)
Row 属性链:
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({ radius: 2, color: '#1A000000', offsetX: 0, offsetY: 1 })
手势参数:
PanGesture({
direction: PanDirection.Left,
distance: 50
})
onActionEnd 内部:
this.deletingCourse = course
this.showDeleteConfirm()
思路: 注释已经标注了"白色背景""圆角""阴影效果""左滑手势""滑动距离阈值"。左滑结束时记录要删除的课程,弹出确认框。
填空 14:showCourseDetail + deleteCourse + 数据模型(第 451-513 行)
showCourseDetail 补全:
AlertDialog.show({
title: course.name,
message: `⏰ 时间: ${course.time}\n🏫 教室: ${course.classroom}\n👨🏫 教师: ${course.teacher}`,
confirm: {
value: '确定',
action: () => {}
},
alignment: DialogAlignment.Center
})
deleteCourse 补全:
const currentDayCourses = this.courseData[this.currentDayIndex].courses
const index = currentDayCourses.findIndex(course => course.id === this.deletingCourse!.id)
if (index !== -1) {
this.courseData[this.currentDayIndex].courses.splice(index, 1)
this.courseData = JSON.parse(JSON.stringify(this.courseData))
}
CourseDay 类:
class CourseDay {
day: string = ''
date: string = ''
courses: CourseItem[] = []
}
CourseItem 类:
class CourseItem {
id: string = ''
name: string = ''
time: string = ''
classroom: string = ''
teacher: string = ''
}
思路: AlertDialog 的参数参考注释中的 emoji 提示。deleteCourse 用 findIndex 找到课程索引,splice 删除,JSON.parse(JSON.stringify()) 触发 UI 更新。
三、评分标准速查(100 分)
| 分类 | 分值 | 考点 |
|---|---|---|
| 项目结构和数据模型 | 20 分 | 状态变量声明 + 数据模型类定义 |
| 主界面布局 | 35 分 | 整体布局 + 星期导航 + 课程列表 |
| 课程布局 | 20 分 | 时间标签 + 课程信息 + 样式 + 滑动删除 |
| 交互功能 | 15 分 | 课程详情对话框 + 删除并更新 UI |
| 界面美观度 | 10 分 | 颜色/字体/间距/阴影/交互反馈 |
四、练习建议
- 把模板的
//写入代码全部删掉,变成空行 - 只看注释和已有代码,自己尝试填
- 填不出来的标记一下,看参考答案理解
- 合上答案,从头再填一遍
- 重复 2-3 遍,直到不看参考答案也能填完