HarmonyOS 校园应用系列之鸿蒙原生实战:用 ArkUI 搭自习室首页 —— 可交互柱状图与横向分类栏
HarmonyOS 校园应用系列之鸿蒙原生实战:用 ArkUI 搭自习室首页 —— 可交互柱状图与横向分类栏
应用背景:09 自习室预约(study-lounge)是鸿蒙原生校园工具系列第 9 个应用。本文逐行拆解
HomeTab.ets(193 行),聚焦 HeroCard 渐变统计卡、可交互式 WeekChartCard、横向滚动 CategoryBar 三大核心组件。青绿色主题#14B8A6,视觉清新学术。

一、整体架构概览
自习室首页延续经典 "Hero Card → 快捷入口 → 数据图表 → 分类筛选 → 列表" 架构,三处差异:
- 青绿色主题:
#14B8A6(teal),视觉更清新学术 - HeroCard 统计项改为三列:128 总数/32 今日/86% 完成率
- WeekChart 默认高亮周五(
selectedBar初始值 4)
页面根布局为 Scroll 包裹 Column,.scrollBar(BarState.Off) 隐藏滚动条。

二、HeroCard —— 渐变头部统计卡
2.1 结构分层
Column({ space: 12 }) {
// Row: 标题行(图标+标题+副标题)
Row({ space: 12 }) {
Text('🏠').fontSize(24)
Column({ space: 4 }) {
Text('智能管理').fontSize(20).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
Text('高效便捷 · 智能推荐').fontSize(12).fontColor('rgba(255,255,255,0.85)')
}.alignItems(HorizontalAlign.Start).layoutWeight(1)
}.width('100%')
// Row: 统计数据行(三列)
Row() {
ForEach(this.stats, (s: StatItem, idx: number) => {
Column({ space: 2 }) {
Text(s.value).fontSize(20).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
Text(s.label).fontSize(10).fontColor('rgba(255,255,255,0.8)')
}.alignItems(HorizontalAlign.Start).layoutWeight(1)
if (idx < this.stats.length - 1) {
Column().width(1).height(28).backgroundColor('rgba(255,255,255,0.25)')
}
}, (s: StatItem) => s.label)
}.width('100%')
}
.width('100%')
.padding(18).padding({ top: this.safeTop + 20 })
.linearGradient({ angle: 135, colors: [[C.primary, 0.0], [C.accent, 1.0]] })
.borderRadius({ bottomLeft: D.rLg, bottomRight: D.rLg })
外层 Column 设 .linearGradient(...) 从青绿 #14B8A6(C.primary)过渡到亮青 #2DD4BF(C.accent)。圆角仅设底部——顶部顶到屏幕顶端与状态栏衔接,底部圆角与内容区形成卡片边界。
2.2 内联分隔线统计
HeroCard 的核心亮点是 三项统计数据之间用半透明白色竖线分隔:
if (idx < this.stats.length - 1) {
Column().width(1).height(28).backgroundColor('rgba(255,255,255,0.25)')
}
通过 if (idx < length-1) 在相间插入 1vp 宽、28vp 高的半透明竖向 Column。这种 "内联分隔线" 让统计像仪表盘刻度分区。
2.3 数据驱动渲染
stats 数组定义三项指标:
| 字段 | 值 | 含义 |
|---|---|---|
| value | '128' / '32' / '86%' | 显示值 |
| label | 总数 / 今日 / 完成率 | 标签 |
ForEach 遍历生成三列,每列内部纵向排列 value/label。每列 .layoutWeight(1) 等分宽度。
2.4 渐变配置
.linearGradient({ angle: 135, colors: [[C.primary, 0.0], [C.accent, 1.0]] }) 从青绿 #14B8A6 过渡到亮青 #2DD4BF。135 度对角线方向,符合视觉动线习惯。
三、QuickActionRow —— Emoji 四宫格
四宫格用 Row + ForEach + layoutWeight(1) 实现(非 Grid):
Row() {
ForEach(this.actions, (item: QuickAction) => {
Column({ space: 6 }) {
Row() { Text(item.icon).fontSize(22) }
.width(44).height(44).backgroundColor(C.cardSoft).borderRadius(D.rSm).justifyContent(FlexAlign.Center)
Text(item.title).fontSize(10).fontColor(C.textSub)
}
.layoutWeight(1)
.onClick(() => { promptAction.showToast({ message: item.title }); })
}, (item: QuickAction) => item.title)
}
.width('100%').padding(12).backgroundColor(C.card).borderRadius(D.rMd)
.border({ width: 1, color: C.stroke }).justifyContent(FlexAlign.SpaceAround)
四个入口为 搜索/收藏/统计/消息(🔍⭐📊🔔)。本页用 Emoji 字符 作图标:零资源依赖,代价是无法控制 fillColor。

四、WeekChartCard —— 可交互式渐变柱状图
这是本页技术含量最高的组件——柱体点击选中后切换渐变色:
4.1 柱体动态样式
ForEach(this.weekBars, (b: WeekBar, idx: number) => {
Column({ space: 6 }) {
Column()
.width(18)
.height(Math.max(6, b.value / 7 * 80))
.borderRadius(4)
.linearGradient({
angle: 180,
colors: idx === this.selectedBar
? [[C.primary, 0.0], [C.accent, 1.0]]
: [[C.primarySoft, 0.0], [C.primary, 1.0]]
})
Text(b.day).fontSize(10)
.fontColor(idx === this.selectedBar ? C.primary : C.textDim)
.fontWeight(idx === this.selectedBar ? FontWeight.Bold : FontWeight.Normal)
}.layoutWeight(1).alignItems(HorizontalAlign.Center)
.onClick(() => { this.selectedBar = idx; })
}, (b: WeekBar) => b.day)
选中态由 selectedBar 决定:三元切换渐变与字色,默认 4(周五)高亮。键值生成器用 b.day 而非索引,保证数据变化时正确复用。
4.2 高度映射公式
Math.max(6, b.value/7*80):原值(07)映射 080vp。
4.3 底部标签联动
Text(b.day).fontSize(10)
.fontColor(idx === this.selectedBar ? C.primary : C.textDim)
.fontWeight(idx === this.selectedBar ? FontWeight.Bold : FontWeight.Normal)
标签同步响应——选中态主色加粗,未选中次要色常规。柱体与标签同在 Column({ space: 6 }) 内纵向排布。
4.4 WeekChartCard 容器
Column({ space: 12 }) {
Row() {
Text('📊 本周数据').fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text)
Blank()
Text('30 次').fontSize(13).fontWeight(FontWeight.Bold).fontColor(C.primary)
}.width('100%')
// 柱状图区域
Row({ space: 4 }) {
ForEach(...) { /* 柱体+标签 */ }
}.width('100%')
}
.width('100%').padding(14).backgroundColor(C.card).borderRadius(D.rMd)
.border({ width: 1, color: C.stroke })
标题行是一个 Text('📊 本周数据',Bold 字重)加右侧硬编码的汇总数值 '30 次'(无 totalWeek 变量)。柱图区 Row({ space: 4 }),每根柱 .layoutWeight(1) 等分宽度。

五、CategoryBar —— 横向滚动分类栏
Scroll() {
Row({ space: 8 }) {
ForEach(this.cats, (cat: string, idx: number) => {
Text(cat).fontSize(13)
.fontColor(this.activeCat === idx ? '#FFFFFF' : C.textSub)
.backgroundColor(this.activeCat === idx ? C.primary : C.card)
.borderRadius(D.rSm).border({ width: 1, color: this.activeCat === idx ? C.primary : C.stroke })
.padding({ left: 14, right: 14, top: 8, bottom: 8 })
.onClick(() => { this.activeCat = idx; })
}, (cat: string) => cat)
}
}
.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off).width('100%')
注意 scrollable 是链式属性而非构造参数。外层 Scroll 水平滚动;选中态"反色+描边"双重标记——border 始终 width: 1,仅颜色在 C.primary/C.stroke 间切换。四个分类:全部 / 推荐 / 热门 / 最新。

六、ItemList —— 推荐列表
列表项包含三层信息:
- 首行:图标 + 标题 + StatusTag(HOT 红/NEW 绿/精选 青)
- 次行:描述文字 + 参与人数
- 进度条:Stack 轨道+填充,自动对齐左边缘
StatusTag 条件着色:item.tag === 'HOT' ? C.danger : item.tag === 'NEW' ? C.ok : C.accent(红/绿/青对应热门/新上线/精选)。
进度条用 Stack 实现——轨道+填充,自动对齐左边缘。

七、Theme.ets 双类设计令牌
Theme.ets 用 D 类 集中管理尺寸令牌:
| 令牌 | 值 | 用途 |
|---|---|---|
| D.rLg | 20 | 大圆角(HeroCard 底部圆角、头像) |
| D.rMd | 14 | 中圆角(卡片、列表项) |
| D.rSm | 10 | 小圆角(图标底、胶囊、标签) |
| D.pad | 16 | 页面水平边距 |
安全区偏移不走 D 类——safeTop/safeBottom 由 EntryAbility 写入 AppStorage,各页用 @StorageProp 读取。
颜色/尺寸双类分离 是企业级标准,换肤改 C 类,调密度改 D 类,互不干扰。
八、总结
自习室首页的三大技术要点:
- HeroCard 内联分隔线:
if (idx < length-1)+ 1vp 宽半透明 Column 实现仪表盘式统计区 - 可交互式柱状图:
@State selectedBar驱动渐变/字色/字重三重联动,默认选中周五(idx 4) - CategoryBar 横向 Scroll:选中态反色 + 自动隐藏滚动条
可交互式柱状图 最具学习价值——@State 与视觉效果的深度绑定。
更多推荐





所有评论(0)