知识付费在线学习实战:HarmonyOS API 24下知了学堂靛蓝学术主题、5Tab差异化布局与音频听书笔记增删改的完整拆解
知识付费在线学习实战:HarmonyOS API 24下知了学堂靛蓝学术主题、5Tab差异化布局与音频听书笔记增删改的完整拆解
在信息过载的时代,持续学习已经成为职场人的核心竞争力,而一个好的知识付费App能让碎片化时间变成系统性成长。本文完整拆解一个基于鸿蒙 HarmonyOS API 24 与 ArkUI 声明式框架构建的「知了学堂」知识付费在线教育应用,覆盖靛蓝学术主题配色系统、学习头部连续学习天数三卡、三种靛蓝渐变视觉语言、5Tab差异化布局(首页名师横滑+分类网格+精品课列表/课程大卡列表/听书音频书+章节列表/学习时长柱状图+笔记列表/我的已购+证书)、双动画状态联动、分类六色动态着色、课程价格红字+原价删除线、已购课程动态进度条、音频听书播放控制、笔记增删改三大弹窗、弹窗共享editIdx索引、分类跳转与学习报表跳转等核心技术点,附完整源码与运行效果截图。
一、引言:知识付费时代的碎片化学习
随着移动互联网的深度普及,知识付费已经从"新鲜事物"变成了职场人的日常刚需。然而,课程质量参差不齐、学习路径不清晰、碎片化时间难以利用、学完即忘缺乏沉淀、音频听书体验不佳,正成为知识付费用户的现实痛点。一个优秀的知识付费App,不仅需要覆盖从"发现课程"到"学习沉淀"再到"证书成就"的完整业务闭环,还要在名师推荐、分类导航、精品课程、音频听书、学习数据、笔记管理、会员证书等维度做到精准而高效。
正是在这样的背景下,知了学堂类知识付费在线教育应用应运而生,并迅速成为职场人自我提升的必备工具。一个优秀的知识付费应用,需要以首页为核心入口,横向扩展出课程、听书、学习、我的五大功能模块,每个模块采用完全不同的布局风格——首页=名师横滑+分类网格+精品课列表、课程=大卡课程列表、听书=音频书+章节列表、学习=时长柱状图+笔记列表、我的=已购+证书,形成了一个完整闭环的知识付费学习生态。它不仅是一个功能完备的演示项目,更是一份典型的 ArkUI 工程实践范本,涵盖了状态管理、组件化设计、Stack弹窗叠加、双动画状态联动、靛蓝学术视觉语言、分类六色动态着色、音频听书播放控制、笔记增删改三大弹窗、弹窗共享editIdx索引等关键技术点。

二、应用整体架构概览
整个应用采用了"单入口 + 5Tab单排差异化布局 + Stack弹窗叠加"的知识付费应用架构。入口组件 Page819 作为整个应用的根容器,负责管理当前激活的 Tab 状态、双动画状态以及三个弹窗的显隐状态。
应用一共划分为五个一级页面:首页(HOME)、课程(COURSE)、听书(AUDIO)、学习(STUDY)以及我的(MINE)。与之前项目"7Tab两排"不同,本应用回归"5Tab单排"的经典底部导航布局——首页🎓/课程📚/听书🎧/学习📈/我的👤,底部 tabBar 激活态 indigo 靛蓝字加粗,非激活 text3 灰字,通过 ForEach 遍历 TAB_LIST 构建,每个 Tab layoutWeight 1 等分宽度。五个Tab每个布局完全不同——首页采用名师横滑+分类网格+精品课列表、课程采用分类横滑+大卡课程列表、听书采用音频书大卡+章节列表+换一本听、学习采用学习数据三卡+每日时长柱状图+笔记列表+学习成就、我的采用用户卡+已购课程进度条+证书,这种"一页一型"的差异化设计最大程度还原了真实知识付费App的交互体验。
在数据层面,应用定义了一套完整的类型系统,包括色彩主题、名师元数据、课程元数据、音频书元数据、学习笔记元数据、已购课程元数据,并通过常量数组将数据与 UI 表现层解耦。所有业务数据以 @Observed 装饰的可观察类为载体(MasterItem/CourseItem/AudioBook/StudyNote/MyCourse),配合 5 位名师、8 门课程、4 本音频书、6 条学习笔记、4 门已购课程的模拟数据,模拟了一个真实的知识付费数据环境。所有数据类都带有 constructor 构造函数,使用 new ClassName(...) 方式实例化。应用还定义了 8 个工具函数(catColor分类六色/courseLevelTag课程等级标签/noteIcon笔记图标动态/studentsStr人数格式化/courseRank课程排名/chapterDur章节时长/studyBarH柱状图高度/updateNote更新笔记splice替换对象),将颜色映射、数据计算、增删改逻辑从 UI 层抽离,实现数据与逻辑分离。
在交互层面,应用通过 Stack 布局叠加实现了三类弹窗的显示:新增笔记、编辑笔记、删除笔记。与之前项目"每个弹窗独立 Stack 叠加"一致,本应用三个弹窗各自独立 Stack 叠加,共享 modalOverlay 统一遮罩封装(全屏 mask 遮罩 + 点击关闭)。特别的是,本应用的弹窗数据状态设计十分精细——addModal 新增笔记包含 addTitle/addContent 两个输入状态,editModal 编辑笔记包含 editIdx/editTitle/editContent 状态(editIdx 记录要编辑的笔记索引,editTitle/editContent 预填原笔记标题和内容),delModal 删除笔记复用 editIdx 状态记录要删除的笔记索引,三个弹窗共享 editIdx 状态实现笔记索引的传递,减少了重复状态定义。新增笔记确认后 noteList.unshift 新 StudyNote(Date.now id,标题默认今日一得,内容默认坚持每天记录一点,tag默认职场,time 08-21,likes 0),编辑笔记确认后调用 updateNote(splice替换对象确保响应式刷新,保留原id/tag/time/likes,更新title/content),删除笔记确认后 noteList.splice(editIdx,1) 按索引直接删除,形成了完整的笔记管理增删改闭环。
在视觉层面,本应用独创了"靛蓝学术主题"设计——bg #EEF0FB 靛蓝学术底,indigo #3D4EB5 靛蓝为主色,indigoD #28337D 深靛蓝为深色,gold #C9A227 鎏金为辅助色,title #1E2249 深蓝而非纯黑,与靛蓝学术底形成高对比度的同时带有学术沉稳质感。应用的渐变设计是色彩系统的核心亮点——首页 Banner 使用 linearGradient 135 度从 indigoD #28337D 深靛蓝到 indigo #3D4EB5 靛蓝的两色渐变,我的用户卡使用 linearGradient 135 度从 indigoD #28337D 深靛蓝到 purple #8A5FC0 紫的两色渐变(与首页 Banner 的深靛蓝→靛蓝不同,我的卡用深靛蓝→紫,增加视觉变化),学习页每日学习时长柱状图使用 linearGradient 180 度从 indigo #3D4EB5 靛蓝到 indigoD #28337D 深靛蓝的垂直渐变,三种不同角度和配色的靛蓝渐变贯穿整个应用,形成了统一而丰富的靛蓝渐变视觉语言,是本应用色彩设计的核心特色。学习头部采用连续学习天数三卡(156天靛蓝字chip底/286h鎏金字白底/12本绿字白底)+今日课程横条(今日上新《置身事内》精读版靛蓝字+满200减40鎏金字),是本应用头部的特色设计。应用还独创了"双动画状态联动"设计——aboutToAppear 中 setInterval 650ms 同时切换 breath 和 wave 两个布尔动画状态,breath 用于课程页"加入学习"按钮 opacity 动画(0.75/1,duration 500ms),wave 用于首页 Banner 📚 图标 animation(duration 650ms EaseInOut)、听书页播放按钮 animation(duration 600ms)、笔记 ❤️ 点赞 animation(duration 500ms),两个动画状态各司其职驱动四种不同的动效,形成整体协调的联动动效体系。

下面逐页展示应用的五大功能模块运行效果。
三、首页:88好书节Banner、名师横滑与分类网格
首页以"发现学习"为核心,独创"活动Banner+名师横滑+分类网格+精品课列表"的四层复合布局。

页面顶部第一部分是 88 好书节活动 Banner,Column 包裹 space 8,padding 16,圆角 18,linearGradient 135 度从 indigoD #28337D 深靛蓝到 indigo #3D4EB5 靛蓝的两色渐变:
- 活动信息行:Row space 10,活动信息列(layoutWeight 1,左对齐):"88好书节 · 全场 5 折起"白字15号加粗 + "薛兆丰·刘擎·万维钢 大师课同价"白字0.85透明度9号 + 📚图标34号,带 wave animation(duration 650ms EaseInOut),书本图标随 wave 状态微动,营造活动氛围。
- 参与领券行:Row space 8,"已有 120 万人参加"白字0.85透明度9号 + Column layoutWeight 1 + “领券购买 >”#FFE082鎏金字10号加粗。
第二部分是名师大家横滑,标题"⭐ 名师大家"加粗13号 + Scroll horizontal 遍历 5 位名师,每位名师 Column 包裹 space 6,width 92,padding 10,白底圆角14,border 1px line 色,alignItems Center,包含:
- 头像:avatar 32号,width 66 height 66 圆角33 dark底 textAlign Center。
- 姓名:name title深蓝字11号加粗。
- 头衔:title sub灰字8号 maxLines 1。
- 关注按钮:已关注时 text3灰字9号加粗 darkL底,未关注时白字9号加粗 indigo底,padding 左右12上下4 圆角10。
5 位名师:薛兆丰(北大经济学教授,6门课,48.2万学生,已关注)、梁宁(产品思维导师,3门课,32.7万,已关注)、刘擎(华东师大哲学教授,4门课,27.5万,未关注)、香帅(金融学者,5门课,21.3万,未关注)、万维钢(科学作家,8门课,39.8万,未关注)。名师横滑让用户快速发现领域大咖,关注按钮两态(已关注灰/未关注靛蓝)清晰区分关注状态,是本应用首页交互设计的亮点。
第三部分是全部分类网格,标题"🗂️ 全部分类"加粗13号 + Flex wrap 遍历 8 个分类(职场/投资/心理/科技/人文/管理/语言/亲子),每个分类 Column 包裹 space 6,width 23%,padding 10,白底圆角14,margin 4,alignItems Center,包含:
- 分类图标:noteIcon© 24号(职场💼/投资📈/心理🧠/科技🤖/其他📖),width 46 height 46 圆角14 dark底 textAlign Center。
- 分类名:c title深蓝字10号加粗。
- 点击事件:onClick(() => { this.tabIdx = 1; }),点击任意分类跳转到课程页(tabIdx=1),实现分类导航跳转。
8 个分类覆盖职场/投资/心理/科技/人文/管理/语言/亲子八大学习领域,分类图标动态映射(noteIcon函数),点击分类跳课程页,是本应用首页导航设计的核心亮点。
第四部分是精品畅销榜,标题行 Row space 8:"🏆 精品畅销榜"加粗13号 layoutWeight 1 + "完整榜单 >"indigo靛蓝字9号 + 遍历 8 门课程,每门课程横向布局,padding 12,白底圆角14,border 1px line 色,alignItems Center,包含:
- 课程图标:icon 30号,width 60 height 60 圆角16 dark底 textAlign Center。
- 课程信息列(layoutWeight 1,左对齐,space 5):
- 标题排名行:Row space 6,课程名(title深蓝字11号加粗 maxLines 1 layoutWeight 1)+ 排名 courseRank(c.id)(NO.X,id<=3时gold鎏金字否则text3灰字,9号加粗)。
- 讲师信息:c.teacher + " · " + c.lessons + "讲 · " + c.rating + “分”(sub灰字9号)。
- 标签行:Row space 6,分类标签(catColor(c.cat)动态着色8号,chip底圆角6 padding 左右6上下2)+ 等级标签(level text3灰字8号,dark底圆角6 padding 左右6上下2)。
- 价格列(右对齐,space 4):现价(red红字13号加粗)+ 原价(text3灰字8号,decoration LineThrough删除线)+ 学过人数(studentsStr(c.students) + “人学过”,text3灰字8号)。
8 门课程覆盖六大分类:薛兆丰的经济学课(投资入门,¥199/¥365,48.2万人,367讲,4.9分,NO.1鎏金)、产品思维30讲(职场进阶,¥99/¥199,32.7万,30讲,4.8分,NO.2鎏金)、西方现代思想40讲(人文入门,¥129/¥259,27.5万,40讲,4.9分,NO.3鎏金)、香帅中国财富报告(投资进阶,¥199/¥399,21.3万,52讲,4.7分,NO.4灰)、精英日课·第六季(科技高级,¥199/¥365,39.8万,300讲,4.9分,NO.5灰)、沟通心理学实战(心理入门,¥69/¥129,18.6万,24讲,4.6分,NO.6灰)、AI大模型通关指南(科技进阶,¥159/¥299,15.4万,45讲,4.8分,NO.7灰)、高效能人士的7个习惯精讲(职场入门,¥89/¥179,24.1万,28讲,4.7分,NO.8灰)。价格从¥69到¥199,学过人数从15.4万到48.2万,课程排名前3鎏金高亮,分类六色动态着色(职场靛蓝/投资鎏金/心理紫/科技蓝/人文粉/其他绿),现价红字+原价删除线,是本应用首页课程列表设计的核心亮点。
四、课程页:分类横滑与大卡课程列表
课程页以"全部课程"为核心,采用"分类横滑+大卡课程列表"的布局。

页面顶部第一部分是标题"📚 全部课程 · 按热度排序"加粗13号 margin 下 12。
第二部分是分类横滑,Scroll horizontal 遍历 6 个分类(全部/入门友好/进阶提升/大师课/拼团中/限时特价),每个分类 Text 10 号,选中(c === ‘全部’)时白字 indigo 靛蓝底,未选中时 sub 灰字白底 border 1px line 色,padding 左右 14 上下 7,圆角 16。分类横滑让用户快速筛选课程类型,选中态靛蓝底高亮。
第三部分是大卡课程列表,遍历 8 门课程,每门课程 Column 包裹 space 10,padding 12,白底圆角16,border 1px line 色,alignItems Start,包含:
- 课程图标:icon 40号,width 100% height 86 圆角14 dark底 textAlign Center。
- 课程名:title title深蓝字13号加粗 maxLines 1。
- 讲师信息:c.teacher + " · " + c.lessons + “讲 · ⭐” + c.rating(sub灰字9号)。
- 标签行:Row space 6,分类标签(catColor(c.cat)动态着色8号,chip底圆角6 padding 左右6上下2)+ 等级 + " · " + studentsStr(c.students) + “人报名”(text3灰字8号)。
- 价格按钮行:Row space 8,现价(red红字15号加粗)+ 原价(text3灰字9号,decoration LineThrough删除线)+ Column layoutWeight 1 + "加入学习"按钮(白字10号加粗,indigo靛蓝底,padding 左右16上下6 圆角12,带 breath 透明度动画 this.breath ? 0.75 : 1,animation duration 500ms)。
8 门课程大卡与首页精品课数据一致,但布局不同——课程页用大卡(图标86px高+课程名+讲师信息+标签+价格按钮行),首页用横向信息卡(图标60px+标题排名+讲师+标签+价格列),两页差异化布局避免重复。"加入学习"按钮的 breath 呼吸动画让核心操作有了"吸引眼球"的动态感,现价红字+原价删除线突出优惠力度,是本应用课程页交互设计的亮点。
五、听书页:音频书大卡与章节列表
听书页以"音频听书"为核心,采用"音频书大卡+播放控制+章节列表+换一本听"的四层布局。

页面顶部第一部分是标题"🎧 听书馆 · 精品解读"加粗13号 margin 下 12。
第二部分是当前音频书大卡,Row space 12,padding 14,白底圆角16,border 1px line 色:
- 书籍图标:AUDIO_LIST[this.audioIdx].icon 42号,width 88 height 112 圆角14 dark底 textAlign Center。
- 书籍信息列(layoutWeight 1,左对齐,space 6):
- 书名:title title深蓝字14号加粗。
- 作者播讲:author + " 著 · " + narrator + " 播"(sub灰字9号)。
- 时长价格:"⏱️ 全书 " + duration + " · " + price(sub灰字9号)。
- 播放控制行:Row space 10,播放/暂停按钮(audioPlaying ? “⏸️ 暂停” : “▶️ 播放”,白字11号加粗,indigo靛蓝底,padding 左右18上下7 圆角16,带 wave animation duration 600ms,点击切换 audioPlaying 状态)+ "已听 46%"green绿字9号加粗。
第三部分是听书设置行,Row space 8:"🎵 倍速 1.25x"indigo靛蓝字9号,chip底圆角8 padding 左右8上下4 + "🌙 定时 30min"purple紫字9号,dark底圆角8 padding 左右8上下4 + Column layoutWeight 1 + "上次听到 第2章 32:14"text3灰字9号。倍速1.25x和定时30min是音频听书的标准功能,上次听到位置记录体现学习连续性。
第四部分是章节列表,标题"📖 章节列表"加粗12号 + 遍历 AUDIO_LIST[this.audioIdx].chapters 章节数组,每章 Row space 12,padding 11,白底圆角12,alignItems Center,包含:
- 章节序号:(ci+1).toString() indigo靛蓝字11号加粗,width 26 height 26 圆角13 chip底 textAlign Center。
- 章节名:ch title深蓝字11号 layoutWeight 1 maxLines 1。
- 状态时长:ci===1时"正在播"green绿字9号加粗,否则 chapterDur(ci)(章节时长,42:18/38:05/45:30/51:12循环,text3灰字9号)。
当前音频(audioIdx=0)为《置身事内》,4章:第一章地方政府的事权(42:18)/第二章财税与政府行为(正在播,38:05)/第三章政府投融资与债务(45:30)/第四章工业化中的政府角色(51:12)。第二章标记"正在播"green绿字加粗,其他章节显示时长,是本应用听书页章节列表设计的亮点。
第五部分是换一本听,标题"🎧 换一本听"加粗12号 + Flex wrap 遍历 4 本音频书,每本 Column 包裹 space 5,width 23%,padding 8,白底圆角12,margin 4,alignItems Center,包含:
- 书籍图标:icon 26号,width 60 height 78 圆角10 dark底 textAlign Center。
- 书名:title title深蓝字9号加粗 maxLines 1。
- 时长:duration text3灰字8号。
- 选中态:borderWidth this.audioIdx === a.id-1 ? 2 : 1,borderColor 选中时indigo靛蓝否则line灰,点击切换 audioIdx = a.id-1。
4 本音频书:置身事内(兰小欢著,梁晨播,18小时,¥45,4章)、穷查理宝典(彼得·考夫曼著,老喻播,26小时,¥59,3章)、思考快与慢(丹尼尔·卡尼曼著,徐幼鱼播,31小时,¥69,3章)、纳瓦尔宝典(埃里克·乔根森著,中书君播,9小时,¥39,3章)。换一本听Flex wrap网格布局,当前音频边框2px靛蓝高亮,点击切换audioIdx,章节列表随audioIdx动态更新(ForEach key含audioIdx),是本应用听书页交互设计的核心亮点。播放按钮的wave animation让播放操作有了"微动"反馈,audioPlaying状态控制播放/暂停文字切换,形成了完整的音频听书交互闭环。
六、学习页:时长柱状图与笔记增删改
学习页以"学习数据+笔记管理"为核心,采用"学习数据三卡+每日时长柱状图+笔记列表+学习成就"的四层布局。

页面顶部第一部分是标题"📈 学习数据 · 本周概览"加粗13号 margin 下 12。
第二部分是学习数据三卡,Row space 10,三卡各 layoutWeight 1,padding 12,白底圆角14,alignItems Start:
- 学习时长卡:"470min"indigo靛蓝字18号加粗 + "学习时长"sub灰字9号。
- 打卡率卡:"86%"gold鎏金字18号加粗 + "打卡率"sub灰字9号。
- 学习笔记卡:"32条"green绿字18号加粗 + "学习笔记"sub灰字9号。
三卡用靛蓝/鎏金/绿三色区分学习时长/打卡率/笔记数,与学习头部的连续学习天数三卡(靛蓝/鎏金/绿)色彩呼应,形成统一的学习数据视觉语言。
第三部分是每日学习时长柱状图,Column 包裹 space 10:
- 标题:"⏱️ 每日学习时长(分钟)"加粗11号。
- 柱状图 Row space 10:遍历 WEEK_DAYS 7天(一/二/三/四/五/六/日),每栏 alignItems Center,包含:顶部数值(STUDY_MIN[i],sub灰字8号)+ 柱子(width 14,height = studyBarH(v) = Math.round(v / 120 * 90) 动态计算,圆角 7,linearGradient 180 度从 indigo #3D4EB5 靛蓝到 indigoD #28337D 深靛蓝的垂直渐变)+ 底部星期(text3灰字8号)。柱状图卡片 padding 上下10下12,白底圆角14,justifyContent Center。
7 天学习数据:周一45/周二62/周三30/周四88/周五55/周六120(最高)/周日70。周六120分钟最高(周末学习时间多),周三30分钟最低(工作日忙)。柱子高度 = v/120*90 动态计算,以120分钟为基准映射到90px高度,180度靛蓝→深靛蓝垂直渐变让柱子有了"从上到下由浅入深"的视觉感,是本应用数据可视化的核心亮点。
第四部分是学习笔记列表,标题行 Row space 8:"📝 学习笔记 · " + noteList.length + " 条"加粗12号 layoutWeight 1 + "➕ 记笔记"indigo靛蓝字10号加粗(点击触发 addModal 新增笔记弹窗)。
遍历 noteList 6 条笔记,每条笔记 Column 包裹 space 8,padding 12,白底圆角14,border 1px line 色,alignItems Start,包含:
- 标题行:Row space 8,笔记图标 noteIcon(n.tag) 18号(职场💼/投资📈/心理🧠/科技🤖/其他📖)+ 笔记名(title深蓝字11号加粗 layoutWeight 1)+ 时间(text3灰字8号)。
- 内容:n.content sub灰字10号 maxLines 2。
- 操作行:Row space 8,分类标签(catColor(n.tag)动态着色8号,chip底圆角6 padding 左右6上下2)+ Column layoutWeight 1 + "❤️ " + n.likes(pink粉字9号,带 wave animation duration 500ms,点赞心动效)+ "✏️"编辑12号(点击触发 editModal,editIdx=ni,editTitle/editContent预填原笔记)+ "🗑️"删除12号(点击触发 delModal,editIdx=ni)。
6 条笔记覆盖四大分类:沉没成本不是成本(投资,08-20,128赞)、产品要打痛点不打痒点(职场,08-18,96赞)、系统1与系统2(心理,08-15,214赞)、复利的本质是时间(投资,08-12,305赞)、写作是最好的思考(职场,08-10,156赞)、注意力是最稀缺资源(科技,08-06,89赞)。笔记内容都是经典学习知识点,点赞数从89到305,分类六色动态着色,❤️点赞带wave animation心动效,✏️编辑触发editModal(预填原笔记),🗑️删除触发delModal(二次确认),形成了完整的笔记管理增删改闭环,是本应用学习页交互设计的核心亮点。
第五部分是学习成就,Column 包裹 space 8,padding 14,白底圆角14:
- 标题:"🏅 学习成就"加粗11号。
- 成就标签行:Row space 10,遍历 4 个成就(🥇 百日坚持/📚 12本/🔥 32条/⭐ 5证书),每个成就 indigo靛蓝字9号,dark底圆角10 padding 左右10上下5。
4 个成就标签用emoji+文字形式展示学习成果,百日坚持/12本完读/32条笔记/5证书,与学习数据三卡和连续学习天数呼应,形成完整的学习成就体系。
七、我的页:用户卡渐变与已购课程进度条
我的页以"个人中心+已购课程+证书"为核心,采用"用户卡+已购课程进度条+证书"的三层布局。

页面顶部第一部分是用户卡,Column 包裹 space 10,padding 16,圆角 18,linearGradient 135 度从 indigoD #28337D 深靛蓝到 purple #8A5FC0 紫的两色渐变(与首页 Banner 的深靛蓝→靛蓝不同,我的卡用深靛蓝→紫,增加视觉变化):
- 用户信息行:Row space 12,🧑🎓头像32号(width 58 height 58 圆角29,rgba(255,255,255,0.25)半透明白底 textAlign Center)+ 用户信息列(layoutWeight 1,左对齐):"求知者Leo · 年度会员"白字14号加粗 + "会员至 2027-03-15 · 已连续学习 156 天"白字0.85透明度9号。
- 会员等级行:Row space 8,“学习力 Lv.7”#FFE082鎏金字9号,rgba(255,255,255,0.2)半透明白底圆角10 padding 左右10上下4 + "超越 98% 的同学"白字0.85透明度9号 + Column layoutWeight 1 + “续费 ¥199 >”#FFE082鎏金字10号加粗。
用户卡的 135 度深靛蓝→紫渐变是本应用最具辨识度的视觉元素之一,与首页 Banner 的深靛蓝→靛蓝渐变形成"同色系不同终点"的视觉变化(首页用靛蓝,我的用紫),半透明白底的头像和等级标签在深色渐变上清晰可读,鎏金字的学习力等级和续费按钮突出会员身份,是本应用我的页视觉设计的核心亮点。
第二部分是我的课程标题行,Row space 8:"📖 我的课程 · " + mineList.length + " 门"加粗13号 layoutWeight 1 + "学习报表 >"indigo靛蓝字9号(点击触发 tabIdx=3 跳学习页,实现学习报表跳转)。
第三部分是已购课程列表,遍历 mineList 4 门课程,每门课程 Column 包裹 space 10,padding 12,白底圆角14,border 1px line 色,包含:
- 课程信息行:Row space 12,课程图标26号(width 52 height 52 圆角14 dark底 textAlign Center)+ 课程信息列(layoutWeight 1,左对齐,space 5):
- 课程名:title title深蓝字11号加粗 maxLines 1。
- 讲师进度:m.teacher + " · 已学 " + m.done + “/” + m.lessons + " 讲"(sub灰字9号)。
- 进度条行:Row space 6,背景条(height 5 圆角3 layoutWeight 1 darkL底)+ 进度条(height 5 圆角3,width = m.progress + “%” 动态,背景色 m.progress===100 ? green绿 : indigo靛蓝,已完成绿色否则靛蓝)。
- 进度文字:"进度 " + m.progress + “%”(text3灰字8号)。
- 操作按钮:m.progress===100 ? "已完成"green绿字10号加粗 chip底 : "继续学"白字10号加粗 indigo靛蓝底,padding 左右12上下7 圆角10。
4 门已购课程:薛兆丰的经济学课(68%,250/367讲,靛蓝进度条,继续学)、产品思维30讲(100%,30/30讲,绿色进度条,已完成)、精英日课·第六季(42%,126/300讲,靛蓝进度条,继续学)、AI大模型通关指南(15%,7/45讲,靛蓝进度条,继续学)。进度条width=progress%动态计算,已完成(progress=100)进度条绿色否则靛蓝,操作按钮已完成绿色chip底否则靛蓝白底,两态区分清晰,是本应用我的页进度条设计的核心亮点。"学习报表"按钮点击跳学习页(tabIdx=3),实现页面间导航跳转。
第四部分是我的证书,标题"📜 我的证书"加粗13号 + Flex wrap 遍历 3 个证书(经济学结业/产品思维认证/AI通识课),每个证书 Column 包裹 space 5,width 31%,padding 12,白底圆角14,margin 4,alignItems Center,border 1px line 色,包含:
- 证书图标:🏅26号。
- 证书名:c title深蓝字9号加粗。
- 认证标识:"官方认证"gold鎏金字8号。
3 个证书覆盖经济学/产品思维/AI三大领域,🏅图标+证书名+官方认证鎏金字,形成完整的学习证书体系,与学习成就和连续学习天数呼应,激励用户持续学习。
八、色彩主题系统:靛蓝学术主题与三种渐变视觉语言
8.1 色彩接口定义
任何一款设计精良的应用,背后都有一套严谨的色彩规范。该应用首先通过一个 ColorPalette 接口,将整个应用所需的色彩抽象为一个结构化的契约,共 21 个字段:
interface ColorPalette {
bg: string; card: string; dark: string; darkL: string;
title: string; sub: string; text3: string;
indigo: string; indigoD: string; gold: string;
red: string; green: string; blue: string; purple: string; pink: string;
line: string; tabOn: string; mask: string; chip: string;
}
- bg:页面整体背景色 #EEF0FB,靛蓝学术底,营造知识付费的学术沉稳氛围。
- indigo:主色调 #3D4EB5,靛蓝,用于按钮、强调文字、激活Tab、柱状图起始色、进度条等高优先级视觉元素,是学术沉稳的标志性色彩(理性、专业、可信赖)。
- indigoD:深主色 #28337D,深靛蓝,用于渐变结束色、柱状图底部色、Banner渐变起始色,与 indigo 形成同色系深浅对比。
- gold:辅助色 #C9A227,鎏金,用于累计学习时长、课程排名前3、证书官方认证、会员续费按钮,与靛蓝形成"靛蓝+鎏金"的经典学术配色组合,象征知识的珍贵与价值。
- title:文字主色 #1E2249,深蓝,用于主标题文字,与靛蓝学术底形成高对比度的同时带有学术沉稳质感(非纯黑,更柔和专业)。
- mask:遮罩色 rgba(30,34,73,0.55),深蓝半透明,用于弹窗遮罩,与 title 同色系保证遮罩质感统一。
- chip:标签底色 rgba(61,78,181,0.10),靛蓝 10% 透明度,用于标签、数据卡底色、按钮底色等,与 indigo 同色系保证标签质感统一。

8.2 色彩实例化
const COLORS: ColorPalette = {
bg: '#EEF0FB', card: '#FFFFFF', dark: '#E2E6F7', darkL: '#D2D8F0',
title: '#1E2249', sub: '#5A608A', text3: '#9BA3C6',
indigo: '#3D4EB5', indigoD: '#28337D', gold: '#C9A227',
red: '#E05B4B', green: '#2E9E6C', blue: '#3D7DC8', purple: '#8A5FC0', pink: '#E0719B',
line: '#DFE3F5', tabOn: '#3D4EB5',
mask: 'rgba(30,34,73,0.55)', chip: 'rgba(61,78,181,0.10)'
};
主色 #3D4EB5 是一种沉稳的靛蓝,既契合"知识付费在线学习"主题(学术、理性、专业的标志性色彩),又具有可信赖、高质感的气质。辅助色 #C9A227 鎏金与靛蓝形成"靛蓝+鎏金"的经典学术配色组合,是本应用最具辨识度的色彩组合。bg #EEF0FB 靛蓝学术底与主色同暖色系,营造出知识付费的学术沉稳氛围。title #1E2249 深蓝而非纯黑,与靛蓝学术底形成高对比度的同时带有学术沉稳质感,是本应用色彩设计的细致之处。
值得注意的是,本应用的渐变设计是色彩系统的核心亮点——首页 Banner 使用 linearGradient 135 度从 indigoD #28337D 深靛蓝到 indigo #3D4EB5 靛蓝的两色渐变,我的用户卡使用 linearGradient 135 度从 indigoD #28337D 深靛蓝到 purple #8A5FC0 紫的两色渐变(与首页 Banner 的深靛蓝→靛蓝不同,我的卡用深靛蓝→紫,增加视觉变化),学习页每日学习时长柱状图使用 linearGradient 180 度从 indigo #3D4EB5 靛蓝到 indigoD #28337D 深靛蓝的垂直渐变。三种不同角度和配色的靛蓝渐变贯穿整个应用(135度深靛蓝→靛蓝用于Banner/135度深靛蓝→紫用于用户卡/180度靛蓝→深靛蓝用于柱状图),形成了统一而丰富的靛蓝渐变视觉语言,是本应用色彩设计的核心特色。
本应用的动态颜色逻辑也十分丰富——课程分类按 catColor 分六色着色(职场indigo靛蓝/投资gold鎏金/心理purple紫/科技blue蓝/人文pink粉/其他green绿),课程排名按 courseRank 分两档着色(前3gold鎏金/其他text3灰),已购课程进度条按 progress 分两档着色(已完成100%green绿/未完成indigo靛蓝),名师关注按钮按 follow 分两态着色(已关注text3灰字darkL底/未关注白字indigo底),笔记分类按 catColor 分六色着色(与课程分类一致),形成了"以色定分类、以色定排名、以色定进度、以色定关注、以色定笔记分类"的多层语义着色模式。这种将所有色彩集中管理、配合多维度渐变与动态着色的做法保证了全局一致性,降低了维护成本。

九、类型定义:构建严谨的数据契约
应用通过一组带 constructor 的 @Observed 类,为每一类业务对象建立了明确的数据契约。
9.1 名师、课程、音频书、笔记、已购课程元数据
@Observed export class MasterItem {
id: number; name: string; avatar: string; title: string;
courseCount: number; students: string; follow: boolean;
constructor(...) { ... }
}
@Observed export class CourseItem {
id: number; title: string; teacher: string; cat: string; level: string;
price: string; origin: string; students: number; lessons: number; rating: string; icon: string;
constructor(...) { ... }
}
@Observed export class AudioBook {
id: number; title: string; author: string; narrator: string;
duration: string; price: string; chapters: string[]; icon: string;
constructor(...) { ... }
}
@Observed export class StudyNote {
id: number; title: string; content: string; tag: string; time: string; likes: number;
constructor(...) { ... }
}
@Observed export class MyCourse {
id: number; title: string; icon: string; progress: number;
lessons: number; done: number; teacher: string;
constructor(...) { ... }
}
MasterItem 描述名师的 ID、姓名、头像、头衔、课程数、学生数、关注状态,用 follow 字段实现关注按钮两态(已关注灰/未关注靛蓝)。CourseItem 描述课程的 ID、标题、讲师、分类、等级、现价、原价、学生数、讲数、评分、图标,用 cat 字段配合 catColor 函数实现分类六色着色,用 price/origin 实现现价红字+原价删除线,用 students 字段配合 studentsStr 函数实现人数格式化(≥1万转X.X万)。AudioBook 描述音频书的 ID、书名、作者、播讲人、时长、价格、章节数组、图标,用 chapters 数组字段实现章节列表动态渲染,用 audioIdx 状态控制当前音频书切换。StudyNote 描述学习笔记的 ID、标题、内容、分类标签、时间、点赞数,用 tag 字段配合 catColor/noteIcon 函数实现分类着色和图标动态映射,支持增删改(unshift新增/splice替换更新/splice删除)。MyCourse 描述已购课程的 ID、标题、图标、进度百分比、总讲数、已学讲数、讲师,用 progress 字段实现进度条 width 动态计算和已完成/未完成两态着色(progress===100绿色否则靛蓝)。
9.2 工具函数:颜色映射、数据计算、增删改逻辑
function catColor(cat: string): string { ... }
function courseLevelTag(l: string): string { ... }
function noteIcon(tag: string): string { ... }
function studentsStr(n: number): string { if (n >= 10000) { return (n / 10000).toFixed(1) + '万'; } return n.toString(); }
function courseRank(id: number): string { return 'NO.' + id.toString(); }
function chapterDur(i: number): string { const durations: string[] = ['42:18', '38:05', '45:30', '51:12']; return durations[i % 4]; }
function studyBarH(v: number): number { return Math.round(v / 120 * 90); }
function updateNote(list: StudyNote[], idx: number, title: string, content: string): void {
if (idx < 0 || idx >= list.length) { return; }
const o: StudyNote = list[idx];
list.splice(idx, 1, new StudyNote(o.id, title.length > 0 ? title : o.title, content.length > 0 ? content : o.content, o.tag, o.time, o.likes));
}
8 个工具函数将颜色映射、数据计算、增删改逻辑从 UI 层抽离,实现数据与逻辑分离:
- catColor:分类六色着色,职场indigo/投资gold/心理purple/科技blue/人文pink/其他green。
- courseLevelTag:课程等级标签,入门green/进阶orange/高级red。
- noteIcon:笔记图标动态映射,职场💼/投资📈/心理🧠/科技🤖/其他📖。
- studentsStr:人数格式化,≥10000转(n/10000).toFixed(1)+‘万’,否则返回原数字字符串。
- courseRank:课程排名,返回’NO.'+id,前3鎏金其他灰(在UI层判断id<=3)。
- chapterDur:章节时长,4个时长循环(42:18/38:05/45:30/51:12),i%4取模。
- studyBarH:柱状图高度计算,Math.round(v / 120 * 90),以120分钟为基准映射到90px。
- updateNote:更新笔记,先判断idx越界防御,splice(idx,1,new StudyNote)替换整个对象,保留原id/tag/time/likes,更新title/content(空值保留原值),触发@Observed响应式更新。
所有数据类都带有 constructor 构造函数,使用 new ClassName(...) 方式实例化,配合 @Observed 装饰器实现响应式更新。8 个工具函数将逻辑从 UI 层抽离,实现数据与逻辑分离,特别是 updateNote 采用 splice 替换对象的方式触发 @Observed 响应式更新(直接修改对象属性可能不触发刷新,替换整个对象会触发),是 ArkUI 响应式更新的关键技巧,体现了对框架机制的深入理解。studentsStr 函数实现人数格式化(≥1万转X.X万),studyBarH 函数实现柱状图高度动态计算,chapterDur 函数实现章节时长循环,都是数据计算层的实用工具函数。
十、配置驱动:常量数据与模拟数据规模
10.1 Tab 导航与常量配置
const TAB_LIST: TabMeta[] = [
{ icon: '🎓', label: '首页' }, { icon: '📚', label: '课程' },
{ icon: '🎧', label: '听书' }, { icon: '📈', label: '学习' }, { icon: '👤', label: '我的' }
];
const WEEK_DAYS: string[] = ['一', '二', '三', '四', '五', '六', '日'];
const STUDY_MIN: number[] = [45, 62, 30, 88, 55, 120, 70];
Tab 导航采用 TabMeta 对象数组配置,5 个 Tab(首页🎓/课程📚/听书🎧/学习📈/我的👤),底部 tabBar 通过 ForEach 遍历 TAB_LIST 构建,每个 Tab Column 包裹(图标19号+文字9号,激活态tabOn靛蓝字加粗,非激活text3灰字),layoutWeight 1 等分宽度,padding 上7下6,点击切换tabIdx。底部白底圆角16。5Tab单排布局是知识付费App的经典导航模式,首页发现/课程浏览/听书音频/学习数据/我的中心,五大模块覆盖完整学习闭环。WEEK_DAYS 常量定义7天标签,STUDY_MIN 常量定义每日学习时长数据(45/62/30/88/55/120/70,周六120最高)。
10.2 模拟数据规模
const MASTER_LIST: MasterItem[] = [ new MasterItem(1, '薛兆丰', '👨🏫', '北大经济学教授', 6, '48.2万', true), ... ];
const COURSE_LIST: CourseItem[] = [ new CourseItem(1, '薛兆丰的经济学课', '薛兆丰', '投资', '入门', '¥199', '¥365', 482000, 367, '4.9', '📊'), ... ];
const AUDIO_LIST: AudioBook[] = [ new AudioBook(1, '置身事内', '兰小欢', '梁晨', '18小时', '¥45', ['第一章:地方政府的事权', ...], '📘'), ... ];
const NOTE_INIT: StudyNote[] = [ new StudyNote(1, '沉没成本不是成本', '已经付出且无法收回的时间和金钱...', '投资', '08-20', 128), ... ];
const MINE_INIT: MyCourse[] = [ new MyCourse(1, '薛兆丰的经济学课', '📊', 68, 367, 250, '薛兆丰'), ... ];
五组常量数据承载了知了学堂的全部业务信息:MASTER_LIST 5 位名师(薛兆丰/梁宁/刘擎/香帅/万维钢,课程数3-8门,学生数21.3万-48.2万,2位已关注3位未关注)、COURSE_LIST 8 门课程(价格¥69-¥199,原价¥129-¥399,学生数15.4万-48.2万,讲数24-367,评分4.6-4.9,覆盖投资/职场/人文/心理/科技五大分类)、AUDIO_LIST 4 本音频书(置身事内/穷查理宝典/思考快与慢/纳瓦尔宝典,时长9-31小时,价格¥39-¥69,章节数3-4章)、NOTE_INIT 6 条学习笔记(覆盖投资/职场/心理/科技四大分类,点赞数89-305,时间08-06到08-20)、MINE_INIT 4 门已购课程(进度15%-100%,已学7-250讲,1门已完成3门学习中)。所有 @Observed 类数据使用 new ClassName(...) 构造函数实例化,TabMeta 使用对象字面量。8 个工具函数处理颜色映射、数据计算、增删改逻辑,是本应用唯一的逻辑层。
十一、双动画状态与动态颜色:数据驱动的视觉映射与联动动效
应用通过双动画状态和动态颜色逻辑,将业务数据转化为视觉色彩与动态效果,实现"数据即配色、状态即动效"。
11.1 动态颜色逻辑
本应用的动态颜色逻辑分布在多个维度:
- 课程分类六色着色:
catColor(cat),职场indigo靛蓝/投资gold鎏金/心理purple紫/科技blue蓝/人文pink粉/其他green绿,六色分类着色让用户在课程列表和笔记列表中一眼判断分类领域。 - 课程排名两档着色:
c.id <= 3 ? gold : text3,前3名鎏金高亮,其他灰色,突出畅销榜TOP3。 - 已购课程进度两态着色:
m.progress === 100 ? green : indigo,已完成(progress=100)进度条绿色,未完成靛蓝,两态区分学习状态。 - 名师关注两态着色:
m.follow ? text3灰字darkL底 : 白字indigo底,已关注灰色弱化,未关注靛蓝突出,引导用户关注。 - 笔记分类六色着色:
catColor(n.tag),与课程分类一致,笔记标签六色着色。 - 订单状态四档着色:本应用无订单模块,但课程价格用red红字突出现价,原价text3灰字+删除线。
11.2 双动画状态联动
aboutToAppear(): void {
this.timer = setInterval(() => {
this.breath = !this.breath;
this.wave = !this.wave;
}, 650);
}
aboutToDisappear(): void {
if (this.timer > 0) { clearInterval(this.timer); }
}
aboutToAppear 中启动 setInterval 650ms 同时切换 breath 和 wave 两个布尔动画状态,aboutToDisappear 中判断 timer > 0 时 clearInterval 清理定时器避免内存泄漏。两个动画状态各司其职驱动四种不同的动效:
- breath 用于课程页"加入学习"按钮透明度动画:
this.breath ? 0.75 : 1,animation duration 500ms,所有课程的"加入学习"按钮都有呼吸动画,营造出"核心操作吸引眼球"的动态感。 - wave 用于首页 Banner 📚 图标 animation:
.animation({ duration: 650, iterations: 1, curve: Curve.EaseInOut }),Banner 右侧的📚书本图标随 wave 状态微动,营造活动氛围,与 88 好书节活动主题契合。 - wave 用于听书页播放按钮 animation:
.animation({ duration: 600, iterations: 1 }),听书页的播放/暂停按钮随 wave 状态微动,营造"音频播放"的动感。 - wave 用于笔记 ❤️ 点赞 animation:
.animation({ duration: 500, iterations: 1 }),学习页笔记的❤️点赞文字随 wave 状态微动,营造"心动"的点赞反馈感。
两个动画状态(breath/wave)同时驱动四种动效(按钮呼吸/Banner图标微动/播放按钮微动/点赞心动),650ms 周期同步切换,形成整体协调的联动动效体系。breath 专门驱动课程页按钮透明度动画,wave 同时驱动 Banner 图标/播放按钮/点赞文字三种 animation 动效,两个状态分工明确,让静态的页面有了"鲜活"的动态感,是本应用动效设计的核心亮点。audioPlaying 状态控制听书页播放/暂停文字切换(▶️播放/⏸️暂停),形成了完整的音频听书交互动效闭环。
十二、入口组件:应用的骨架与5Tab差异化布局
12.1 组件声明与状态
@Entry
@Component
struct Page819 {
@State tabIdx: number = 0;
@State breath: boolean = false;
@State wave: boolean = false;
@State addModal: boolean = false;
@State editModal: boolean = false;
@State delModal: boolean = false;
@State editIdx: number = 0;
@State addTitle: string = '';
@State addContent: string = '';
@State editTitle: string = '';
@State editContent: string = '';
@State noteList: StudyNote[] = NOTE_INIT;
@State mineList: MyCourse[] = MINE_INIT;
@State audioIdx: number = 0;
@State audioPlaying: boolean = false;
private timer: number = -1;
}
17 个 @State 状态分六类:导航状态(tabIdx)、动画状态(breath/wave)、弹窗控制状态(addModal/editModal/delModal)、弹窗数据状态(editIdx/addTitle/addContent/editTitle/editContent,editIdx 共享于 editModal 和 delModal 记录笔记索引,addTitle/addContent 用于新增笔记输入,editTitle/editContent 预填原笔记标题和内容)、业务数据状态(noteList/mineList,初始化为常量数组,支持增删改)、音频状态(audioIdx/audioPlaying,控制当前音频书和播放状态)。private 字段(MASTER_LIST/COURSE_LIST/AUDIO_LIST/NOTE_INIT/MINE_INIT/TAB_LIST/WEEK_DAYS/STUDY_MIN/timer)存储静态模拟数据和定时器引用,不触发响应式更新。
12.2 生命周期与双动画联动
aboutToAppear(): void {
this.timer = setInterval(() => {
this.breath = !this.breath;
this.wave = !this.wave;
}, 650);
}
aboutToDisappear(): void {
if (this.timer > 0) { clearInterval(this.timer); }
}
aboutToAppear 中启动 setInterval 650ms 同时切换 breath 和 wave 动画状态,实现双动画联动。aboutToDisappear 中判断 timer > 0 时 clearInterval 清理定时器,避免内存泄漏和重复启动。这种"生命周期管理定时器"的模式是 ArkUI 动效的标准实践。
12.3 学习头部(@Builder header)
顶部头部通过 @Builder header() 抽取,Column 包裹,space 12,padding 左右 14 上下 12:
- 标题行:Row space 10,"🎓"图标 16 号 padding 6 圆角 12 chip 底 + "知了学堂 · 每天听一讲"title 深蓝字 13 号加粗 + Column layoutWeight 1 + "🔍"搜索 18 号 + "🔔"消息 18 号。
- 连续学习天数三卡:Row space 12,三卡各 layoutWeight 1,padding 12,圆角 14,alignItems Start:
- 连续学习卡:"156"indigo靛蓝字26号加粗 + "连续学习(天)"sub灰字9号,chip底。
- 累计学习时长卡:"286h"gold鎏金字26号加粗 + "累计学习时长"sub灰字9号,白底。
- 完读书籍卡:"12本"green绿字26号加粗 + "完读书籍"sub灰字9号,白底。
- 今日课程横条:Row space 8,"📖 今日上新:《置身事内》精读版"indigo靛蓝字9号,dark底圆角8 padding 左右8上下4 + "🎁 满200减40"gold鎏金字9号,chip底圆角8 padding 左右8上下4。
学习头部是本应用最具辨识度的视觉元素,连续学习天数三卡(156天靛蓝/286h鎏金/12本绿)让用户一眼看到学习成果,三卡用靛蓝/鎏金/绿三色区分学习天数/时长/完读数,与学习页的学习数据三卡色彩呼应。今日课程横条(今日上新《置身事内》精读版靛蓝字+满200减40鎏金字)体现课程上新和优惠信息,与整体靛蓝学术主题呼应。
12.4 5Tab单排底部导航与Stack弹窗
底部导航通过 @Builder tabBar() 构建,Row space 0,ForEach 遍历 TAB_LIST 5个Tab,每个Tab Column space 3,layoutWeight 1,padding 上7下6:图标19号+文字9号(激活态tabOn靛蓝字加粗,非激活text3灰字),点击切换tabIdx。底部白底圆角16。
build() 使用 Stack 布局,主 Column 包含 header + Scroll(if-else 切换 5 个 @Builder 页面:tabIdx 0-4 对应 homeTab/courseTab/audioTab/studyTab/mineTab)+ tabBar,width/height 100%,bg 靛蓝学术底。三个弹窗条件渲染:每个弹窗独立 Stack 叠加,共享 modalOverlay 统一遮罩封装(全屏 mask 遮罩 + 点击关闭)。
十三、关键技术特性对比
| 特性维度 | 实现方式 | 技术要点 | 设计价值 |
|---|---|---|---|
| 色彩管理 | ColorPalette 接口 + COLORS 常量 | 集中定义 21 种语义色,含靛蓝学术主题色 | 保证视觉一致性,支持主题切换 |
| 靛蓝学术主题 | bg #EEF0FB + indigo #3D4EB5 + gold #C9A227 | 靛蓝学术底+靛蓝主色+鎏金辅助,title深蓝非纯黑 | 学术沉稳氛围,靛蓝+鎏金经典配色 |
| 三种靛蓝渐变 | 三种不同角度 linearGradient | Banner135度深靛蓝→靛蓝/用户卡135度深靛蓝→紫/柱状图180度靛蓝→深靛蓝 | 统一而丰富的渐变视觉语言 |
| 学习头部 | 连续学习天数三卡+今日课程横条 | 156天靛蓝/286h鎏金/12本绿三卡,今日上新+满减 | 学习成果一目了然,课程上新提醒 |
| 5Tab差异化 | 每页独立 @Builder 布局 | 首页名师横滑+分类网格+精品课/课程大卡列表/听书音频书+章节/学习柱状图+笔记/我的已购+证书 | 一页一型,还原真实知识付费App |
| 名师横滑 | Scroll horizontal + 关注两态 | 5位名师,已关注text3灰darkL底/未关注白字indigo底 | 快速发现领域大咖,关注状态清晰 |
| 分类网格跳转 | Flex wrap + onClick tabIdx=1 | 8个分类,noteIcon动态图标,点击跳课程页 | 分类导航一键跳转 |
| 分类六色着色 | catColor动态函数 | 职场indigo/投资gold/心理purple/科技blue/人文pink/其他green | 以色定分类,一眼判断领域 |
| 课程排名两档 | id<=3 ? gold : text3 | NO.X排名,前3鎏金高亮其他灰 | 突出畅销榜TOP3 |
| 现价红字+删除线 | red价格 + decoration LineThrough | 现价red红字加粗,原价text3灰字删除线 | 突出优惠力度,刺激购买 |
| 人数格式化 | studentsStr函数 | ≥10000转(n/10000).toFixed(1)+‘万’ | 大数字优雅展示 |
| 大卡课程列表 | Column大卡 + 加入学习呼吸按钮 | 图标86px高+课程名+讲师+标签+价格按钮,breath opacity 0.75/1 | 课程信息完整,核心操作呼吸吸引 |
| 音频听书 | audioIdx/audioPlaying状态 | 4本音频书切换,播放/暂停文字切换,章节列表随audioIdx更新 | 完整音频听书交互闭环 |
| 章节列表 | ForEach chapters数组 | 序号+章节名+正在播green/时长text3,chapterDur循环 | 章节结构清晰,播放进度标记 |
| 换一本听 | Flex wrap + 选中态边框 | 4本音频书网格,当前audioIdx边框2px靛蓝,点击切换 | 快速切换音频书,选中态清晰 |
| 学习数据三卡 | 靛蓝/鎏金/绿三色 | 470min靛蓝/86%鎏金/32条绿,与头部三卡色彩呼应 | 学习数据一目了然,色彩统一 |
| 每日时长柱状图 | studyBarH动态计算 + 180度渐变 | v/120*90高度,180度靛蓝→深靛蓝垂直渐变 | 学习趋势直观,渐变视觉统一 |
| 笔记增删改 | addModal/editModal/delModal | 新增unshift新笔记/编辑updateNote splice替换/删除splice按索引 | 完整笔记管理闭环 |
| 弹窗共享editIdx | editModal和delModal共享索引 | editIdx记录要编辑/删除的笔记索引,减少重复状态 | 状态复用,工程化设计 |
| splice替换对象更新 | updateNote用splice替换 | 保留原id/tag/time/likes,更新title/content,替换整个对象触发响应式 | ArkUI响应式更新关键技巧 |
| 笔记❤️点赞动效 | wave animation duration 500ms | 点赞文字随wave状态微动,心动反馈 | 社交交互动效 |
| 已购课程进度条 | width=progress%动态 + 两态着色 | 已完成100%green绿/未完成indigo靛蓝,操作按钮两态 | 学习进度直观,完成状态清晰 |
| 用户卡深靛蓝→紫渐变 | 135度 indigoD→purple | 与Banner深靛蓝→靛蓝不同,我的卡用紫增加视觉变化 | 同色系不同终点,视觉丰富 |
| 学习报表跳转 | onClick tabIdx=3 | 我的页"学习报表"按钮点击跳学习页 | 页面间导航跳转 |
| 双动画状态联动 | breath+wave两个@State | setInterval 650ms同时切换,breath驱动按钮呼吸,wave驱动Banner/播放/点赞微动 | 两状态驱动四动效,协调联动 |
| 三大弹窗 | 独立 Stack 叠加 + modalOverlay | 新增笔记/编辑笔记/删除笔记,共享遮罩样式,editIdx共享索引 | 完整增删改交互闭环 |
| 状态管理 | @State + @Observed + private | 17个@State分六类,private存静态数据不触发更新 | 细粒度状态控制,性能优化 |
| 组件化 | @Component + @Builder | 1个入口组件 + 5个页面@Builder + 3个弹窗 + header/tabBar/modalOverlay | 高内聚低耦合,代码复用率高 |
| 列表渲染 | ForEach + 网格/横滑/列表 | 名师横滑/分类网格/课程列表/音频书网格/章节列表/笔记列表/已购课程/证书网格 | 数据驱动渲染,布局灵活 |
| 数据可视化 | 柱状图 | 每日学习时长180度靛蓝→深靛蓝渐变柱状图 | 学习趋势直观呈现 |
| 文本省略 | maxLines + textOverflow | 课程名maxLines1/笔记内容maxLines2/章节名maxLines1 | 长文本优雅截断,布局整齐 |
| 生命周期 | aboutToAppear/aboutToDisappear | setInterval启动/clearInterval清理,timer判>0 | 健壮性强,无资源泄漏 |
| 布局系统 | Column/Row + Stack + Scroll + Flex | 弹性布局、Stack叠加、Scroll纵向滚动、Flex wrap换行 | 自适应布局,层次分明 |
完整代码
// ============================================================
// 819 知了学堂 · 知识付费在线教育(现代行业:知识付费 / 在线学习)
// 头部样式:学习头部(连续学习天数卡 + 今日课程横条)无动画
// 布局风格:5 个 tab 每个布局完全不同
// 首页=名师横滑+分类网格+精品课列表 / 课程=大卡课程列表
// 听书=音频书+章节列表 / 学习=时长柱状图+笔记列表 / 我的=已购+证书
// 底部 5 tab 单排
// 主题:靛蓝学术底 + 深蓝 + 鎏金
// ============================================================
interface ColorPalette {
bg: string;
card: string;
dark: string;
darkL: string;
title: string;
sub: string;
text3: string;
indigo: string;
indigoD: string;
gold: string;
red: string;
green: string;
blue: string;
purple: string;
pink: string;
line: string;
tabOn: string;
mask: string;
chip: string;
}
const COLORS: ColorPalette = {
bg: '#EEF0FB',
card: '#FFFFFF',
dark: '#E2E6F7',
darkL: '#D2D8F0',
title: '#1E2249',
sub: '#5A608A',
text3: '#9BA3C6',
indigo: '#3D4EB5',
indigoD: '#28337D',
gold: '#C9A227',
red: '#E05B4B',
green: '#2E9E6C',
blue: '#3D7DC8',
purple: '#8A5FC0',
pink: '#E0719B',
line: '#DFE3F5',
tabOn: '#3D4EB5',
mask: 'rgba(30,34,73,0.55)',
chip: 'rgba(61,78,181,0.10)'
};
interface TabMeta {
icon: string;
label: string;
}
const TAB_LIST: TabMeta[] = [
{ icon: '🎓', label: '首页' },
{ icon: '📚', label: '课程' },
{ icon: '🎧', label: '听书' },
{ icon: '📈', label: '学习' },
{ icon: '👤', label: '我的' }
];
const WEEK_DAYS: string[] = ['一', '二', '三', '四', '五', '六', '日'];
const STUDY_MIN: number[] = [45, 62, 30, 88, 55, 120, 70];
function catColor(cat: string): string {
if (cat === '职场') { return COLORS.indigo; }
if (cat === '投资') { return COLORS.gold; }
if (cat === '心理') { return COLORS.purple; }
if (cat === '科技') { return COLORS.blue; }
if (cat === '人文') { return COLORS.pink; }
return COLORS.green;
}
function courseLevelTag(l: string): string {
if (l === '入门') { return 'green'; }
if (l === '进阶') { return 'orange'; }
return 'red';
}
function noteIcon(tag: string): string {
if (tag === '职场') { return '💼'; }
if (tag === '投资') { return '📈'; }
if (tag === '心理') { return '🧠'; }
if (tag === '科技') { return '🤖'; }
return '📖';
}
@Observed export class MasterItem {
id: number;
name: string;
avatar: string;
title: string;
courseCount: number;
students: string;
follow: boolean;
constructor(id: number, name: string, avatar: string, title: string, courseCount: number, students: string, follow: boolean) {
this.id = id; this.name = name; this.avatar = avatar; this.title = title; this.courseCount = courseCount; this.students = students; this.follow = follow;
}
}
@Observed export class CourseItem {
id: number;
title: string;
teacher: string;
cat: string;
level: string;
price: string;
origin: string;
students: number;
lessons: number;
rating: string;
icon: string;
constructor(id: number, title: string, teacher: string, cat: string, level: string, price: string, origin: string, students: number, lessons: number, rating: string, icon: string) {
this.id = id; this.title = title; this.teacher = teacher; this.cat = cat; this.level = level;
this.price = price; this.origin = origin; this.students = students; this.lessons = lessons; this.rating = rating; this.icon = icon;
}
}
@Observed export class AudioBook {
id: number;
title: string;
author: string;
narrator: string;
duration: string;
price: string;
chapters: string[];
icon: string;
constructor(id: number, title: string, author: string, narrator: string, duration: string, price: string, chapters: string[], icon: string) {
this.id = id; this.title = title; this.author = author; this.narrator = narrator;
this.duration = duration; this.price = price; this.chapters = chapters; this.icon = icon;
}
}
@Observed export class StudyNote {
id: number;
title: string;
content: string;
tag: string;
time: string;
likes: number;
constructor(id: number, title: string, content: string, tag: string, time: string, likes: number) {
this.id = id; this.title = title; this.content = content; this.tag = tag; this.time = time; this.likes = likes;
}
}
@Observed export class MyCourse {
id: number;
title: string;
icon: string;
progress: number;
lessons: number;
done: number;
teacher: string;
constructor(id: number, title: string, icon: string, progress: number, lessons: number, done: number, teacher: string) {
this.id = id; this.title = title; this.icon = icon; this.progress = progress; this.lessons = lessons; this.done = done; this.teacher = teacher;
}
}
const MASTER_LIST: MasterItem[] = [
new MasterItem(1, '薛兆丰', '👨🏫', '北大经济学教授', 6, '48.2万', true),
new MasterItem(2, '梁宁', '👩🏫', '产品思维导师', 3, '32.7万', true),
new MasterItem(3, '刘擎', '🧑🏫', '华东师大哲学教授', 4, '27.5万', false),
new MasterItem(4, '香帅', '👩💼', '金融学者', 5, '21.3万', false),
new MasterItem(5, '万维钢', '🧑🔬', '科学作家', 8, '39.8万', false)
];
const COURSE_LIST: CourseItem[] = [
new CourseItem(1, '薛兆丰的经济学课', '薛兆丰', '投资', '入门', '¥199', '¥365', 482000, 367, '4.9', '📊'),
new CourseItem(2, '产品思维30讲', '梁宁', '职场', '进阶', '¥99', '¥199', 327000, 30, '4.8', '🧩'),
new CourseItem(3, '西方现代思想40讲', '刘擎', '人文', '入门', '¥129', '¥259', 275000, 40, '4.9', '🏛️'),
new CourseItem(4, '香帅中国财富报告', '香帅', '投资', '进阶', '¥199', '¥399', 213000, 52, '4.7', '💰'),
new CourseItem(5, '精英日课·第六季', '万维钢', '科技', '高级', '¥199', '¥365', 398000, 300, '4.9', '🔭'),
new CourseItem(6, '沟通心理学实战', '李松蔚', '心理', '入门', '¥69', '¥129', 186000, 24, '4.6', '🧠'),
new CourseItem(7, 'AI大模型通关指南', '卓克', '科技', '进阶', '¥159', '¥299', 154000, 45, '4.8', '🤖'),
new CourseItem(8, '高效能人士的7个习惯精讲', '古典', '职场', '入门', '¥89', '¥179', 241000, 28, '4.7', '🚀')
];
const AUDIO_LIST: AudioBook[] = [
new AudioBook(1, '置身事内', '兰小欢', '梁晨', '18小时', '¥45', ['第一章:地方政府的事权', '第二章:财税与政府行为', '第三章:政府投融资与债务', '第四章:工业化中的政府角色'], '📘'),
new AudioBook(2, '穷查理宝典', '彼得·考夫曼', '老喻', '26小时', '¥59', ['第一讲:芒格的生活学习决策方法', '第二讲:论基本的普世智慧', '第三讲:论学院派经济学'], '📗'),
new AudioBook(3, '思考,快与慢', '丹尼尔·卡尼曼', '徐幼鱼', '31小时', '¥69', ['第一部分:两个系统', '第二部分:启发法与偏见', '第三部分:过度自信与决策错误'], '📕'),
new AudioBook(4, '纳瓦尔宝典', '埃里克·乔根森', '中书君', '9小时', '¥39', ['第一章:积累财富', '第二章:理解幸福', '第三章:学习判断力'], '📙')
];
const NOTE_INIT: StudyNote[] = [
new StudyNote(1, '沉没成本不是成本', '已经付出且无法收回的时间和金钱,不应影响当下决策,向前看才是理性选择。', '投资', '08-20', 128),
new StudyNote(2, '产品要打痛点不打痒点', '梁宁讲的情绪→欲望→恐惧三层模型,恐惧才是最强购买动机。', '职场', '08-18', 96),
new StudyNote(3, '系统1与系统2', '直觉快但易错,理性慢但准确,重要决策强制自己慢三秒。', '心理', '08-15', 214),
new StudyNote(4, '复利的本质是时间', '年化10%坚持30年是17倍,大多数输在中途下车。', '投资', '08-12', 305),
new StudyNote(5, '写作是最好的思考', '写不清楚就是没想清楚,输出倒逼输入。', '职场', '08-10', 156),
new StudyNote(6, '注意力是最稀缺资源', '信息过载时代,过滤信息比获取信息更重要。', '科技', '08-06', 89)
];
const MINE_INIT: MyCourse[] = [
new MyCourse(1, '薛兆丰的经济学课', '📊', 68, 367, 250, '薛兆丰'),
new MyCourse(2, '产品思维30讲', '🧩', 100, 30, 30, '梁宁'),
new MyCourse(3, '精英日课·第六季', '🔭', 42, 300, 126, '万维钢'),
new MyCourse(4, 'AI大模型通关指南', '🤖', 15, 45, 7, '卓克')
];
@Entry
@Component
struct Page819 {
@State tabIdx: number = 0;
@State breath: boolean = false;
@State wave: boolean = false;
@State addModal: boolean = false;
@State editModal: boolean = false;
@State delModal: boolean = false;
@State editIdx: number = 0;
@State addTitle: string = '';
@State addContent: string = '';
@State editTitle: string = '';
@State editContent: string = '';
@State noteList: StudyNote[] = NOTE_INIT;
@State mineList: MyCourse[] = MINE_INIT;
@State audioIdx: number = 0;
@State audioPlaying: boolean = false;
private timer: number = -1;
aboutToAppear(): void {
this.timer = setInterval(() => {
this.breath = !this.breath;
this.wave = !this.wave;
}, 650);
}
aboutToDisappear(): void {
if (this.timer > 0) { clearInterval(this.timer); }
}
@Builder
header() {
Column({ space: 12 }) {
Row({ space: 10 }) {
Text('🎓').fontSize(16).padding(6).borderRadius(12).backgroundColor(COLORS.chip)
Text('知了学堂 · 每天听一讲').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Column().layoutWeight(1)
Text('🔍').fontSize(18)
Text('🔔').fontSize(18)
}
.width('100%')
Row({ space: 12 }) {
Column({ space: 4 }) {
Text('156').fontSize(26).fontColor(COLORS.indigo).fontWeight(FontWeight.Bold)
Text('连续学习(天)').fontSize(9).fontColor(COLORS.sub)
}
.layoutWeight(1).padding(12).borderRadius(14).backgroundColor(COLORS.chip)
.alignItems(HorizontalAlign.Start)
Column({ space: 4 }) {
Text('286h').fontSize(26).fontColor(COLORS.gold).fontWeight(FontWeight.Bold)
Text('累计学习时长').fontSize(9).fontColor(COLORS.sub)
}
.layoutWeight(1).padding(12).borderRadius(14).backgroundColor(COLORS.card)
.alignItems(HorizontalAlign.Start)
Column({ space: 4 }) {
Text('12本').fontSize(26).fontColor(COLORS.green).fontWeight(FontWeight.Bold)
Text('完读书籍').fontSize(9).fontColor(COLORS.sub)
}
.layoutWeight(1).padding(12).borderRadius(14).backgroundColor(COLORS.card)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
Row({ space: 8 }) {
Text('📖 今日上新:《置身事内》精读版').fontSize(9).fontColor(COLORS.indigo)
.padding({ left: 8, right: 8, top: 4, bottom: 4 }).borderRadius(8).backgroundColor(COLORS.dark)
Text('🎁 满200减40').fontSize(9).fontColor(COLORS.gold)
.padding({ left: 8, right: 8, top: 4, bottom: 4 }).borderRadius(8).backgroundColor(COLORS.chip)
}
.width('100%')
}
.width('100%').padding({ left: 14, right: 14, top: 12, bottom: 12 })
}
@Builder
homeTab() {
Column({ space: 12 }) {
Column({ space: 8 }) {
Row({ space: 10 }) {
Column({ space: 4 }) {
Text('88好书节 · 全场 5 折起').fontSize(15).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
Text('薛兆丰·刘擎·万维钢 大师课同价').fontSize(9).fontColor('rgba(255,255,255,0.85)')
}
.alignItems(HorizontalAlign.Start).layoutWeight(1)
Text('📚').fontSize(34)
.animation({ duration: 650, iterations: 1, curve: Curve.EaseInOut })
}
.width('100%')
Row({ space: 8 }) {
Text('已有 120 万人参加').fontSize(9).fontColor('rgba(255,255,255,0.85)')
Column().layoutWeight(1)
Text('领券购买 >').fontSize(10).fontColor('#FFE082').fontWeight(FontWeight.Bold)
}
.width('100%')
}
.width('100%').padding(16).borderRadius(18)
.linearGradient({ angle: 135, colors: [[COLORS.indigoD, 0], [COLORS.indigo, 1]] })
Text('⭐ 名师大家').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Scroll() {
Row({ space: 12 }) {
ForEach(MASTER_LIST, (m: MasterItem) => {
Column({ space: 6 }) {
Text(m.avatar).fontSize(32)
.width(66).height(66).borderRadius(33).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
Text(m.name).fontSize(11).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Text(m.title).fontSize(8).fontColor(COLORS.sub).maxLines(1)
Text(m.follow ? '已关注' : '+ 关注').fontSize(9)
.fontColor(m.follow ? COLORS.text3 : '#FFFFFF').fontWeight(FontWeight.Bold)
.padding({ left: 12, right: 12, top: 4, bottom: 4 }).borderRadius(10)
.backgroundColor(m.follow ? COLORS.darkL : COLORS.indigo)
}
.width(92).padding(10).borderRadius(14).backgroundColor(COLORS.card)
.alignItems(HorizontalAlign.Center).borderWidth(1).borderColor(COLORS.line)
}, (m: MasterItem) => 'm' + m.id.toString())
}
.padding({ left: 4, right: 4 })
}
.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off).width('100%')
Text('🗂️ 全部分类').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(['职场', '投资', '心理', '科技', '人文', '管理', '语言', '亲子'], (c: string) => {
Column({ space: 6 }) {
Text(noteIcon(c)).fontSize(24)
.width(46).height(46).borderRadius(14).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
Text(c).fontSize(10).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
}
.width('23%').padding(10).borderRadius(14).backgroundColor(COLORS.card).margin(4)
.alignItems(HorizontalAlign.Center)
.onClick(() => { this.tabIdx = 1; })
}, (c: string) => 'c' + c)
}
.width('100%')
Row({ space: 8 }) {
Text('🏆 精品畅销榜').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Column().layoutWeight(1)
Text('完整榜单 >').fontSize(9).fontColor(COLORS.indigo)
}
.width('100%')
ForEach(COURSE_LIST, (c: CourseItem) => {
Row({ space: 12 }) {
Text(c.icon).fontSize(30)
.width(60).height(60).borderRadius(16).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
Column({ space: 5 }) {
Row({ space: 6 }) {
Text(c.title).fontSize(11).fontColor(COLORS.title).fontWeight(FontWeight.Bold).maxLines(1).layoutWeight(1)
Text(courseRank(c.id)).fontSize(9).fontColor(c.id <= 3 ? COLORS.gold : COLORS.text3)
.fontWeight(FontWeight.Bold)
}
Text(c.teacher + ' · ' + c.lessons + '讲 · ' + c.rating + '分').fontSize(9).fontColor(COLORS.sub)
Row({ space: 6 }) {
Text(c.cat).fontSize(8).fontColor(catColor(c.cat))
.padding({ left: 6, right: 6, top: 2, bottom: 2 }).borderRadius(6).backgroundColor(COLORS.chip)
Text(c.level).fontSize(8).fontColor(COLORS.text3)
.padding({ left: 6, right: 6, top: 2, bottom: 2 }).borderRadius(6).backgroundColor(COLORS.dark)
}
}
.alignItems(HorizontalAlign.Start).layoutWeight(1)
Column({ space: 4 }) {
Text(c.price).fontSize(13).fontColor(COLORS.red).fontWeight(FontWeight.Bold)
Text(c.origin).fontSize(8).fontColor(COLORS.text3)
.decoration({ type: TextDecorationType.LineThrough })
Text(studentsStr(c.students) + '人学过').fontSize(8).fontColor(COLORS.text3)
}
.alignItems(HorizontalAlign.End)
}
.width('100%').padding(12).borderRadius(14).backgroundColor(COLORS.card)
.alignItems(VerticalAlign.Center).borderWidth(1).borderColor(COLORS.line)
}, (c: CourseItem) => 'cs' + c.id.toString())
}
.width('100%')
}
@Builder
courseTab() {
Column({ space: 12 }) {
Text('📚 全部课程 · 按热度排序').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Scroll() {
Row({ space: 10 }) {
ForEach(['全部', '入门友好', '进阶提升', '大师课', '拼团中', '限时特价'], (c: string) => {
Text(c).fontSize(10).fontColor(c === '全部' ? '#FFFFFF' : COLORS.sub)
.padding({ left: 14, right: 14, top: 7, bottom: 7 }).borderRadius(16)
.backgroundColor(c === '全部' ? COLORS.indigo : COLORS.card)
.borderWidth(c === '全部' ? 0 : 1).borderColor(COLORS.line)
}, (c: string) => c)
}
.padding({ left: 4, right: 4 })
}
.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off).width('100%')
ForEach(COURSE_LIST, (c: CourseItem) => {
Column({ space: 10 }) {
Text(c.icon).fontSize(40)
.width('100%').height(86).borderRadius(14).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
Text(c.title).fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold).maxLines(1)
Text(c.teacher + ' · ' + c.lessons + '讲 · ⭐' + c.rating).fontSize(9).fontColor(COLORS.sub)
Row({ space: 6 }) {
Text(c.cat).fontSize(8).fontColor(catColor(c.cat))
.padding({ left: 6, right: 6, top: 2, bottom: 2 }).borderRadius(6).backgroundColor(COLORS.chip)
Text(c.level + ' · ' + studentsStr(c.students) + '人报名').fontSize(8).fontColor(COLORS.text3)
}
Row({ space: 8 }) {
Text(c.price).fontSize(15).fontColor(COLORS.red).fontWeight(FontWeight.Bold)
Text(c.origin).fontSize(9).fontColor(COLORS.text3)
.decoration({ type: TextDecorationType.LineThrough })
Column().layoutWeight(1)
Column() { Text('加入学习').fontSize(10).fontColor('#FFFFFF').fontWeight(FontWeight.Bold) }
.padding({ left: 16, right: 16, top: 6, bottom: 6 }).borderRadius(12)
.backgroundColor(COLORS.indigo)
.opacity(this.breath ? 0.75 : 1).animation({ duration: 500, iterations: 1 })
}
.width('100%')
}
.width('100%').padding(12).borderRadius(16).backgroundColor(COLORS.card)
.alignItems(HorizontalAlign.Start).borderWidth(1).borderColor(COLORS.line)
}, (c: CourseItem) => 'cl' + c.id.toString())
}
.width('100%')
}
@Builder
audioTab() {
Column({ space: 12 }) {
Text('🎧 听书馆 · 精品解读').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Row({ space: 12 }) {
Text(AUDIO_LIST[this.audioIdx].icon).fontSize(42)
.width(88).height(112).borderRadius(14).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
Column({ space: 6 }) {
Text(AUDIO_LIST[this.audioIdx].title).fontSize(14).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Text(AUDIO_LIST[this.audioIdx].author + ' 著 · ' + AUDIO_LIST[this.audioIdx].narrator + ' 播').fontSize(9).fontColor(COLORS.sub)
Text('⏱️ 全书 ' + AUDIO_LIST[this.audioIdx].duration + ' · ' + AUDIO_LIST[this.audioIdx].price).fontSize(9).fontColor(COLORS.sub)
Row({ space: 10 }) {
Column() {
Text(this.audioPlaying ? '⏸️ 暂停' : '▶️ 播放').fontSize(11)
.fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
}
.padding({ left: 18, right: 18, top: 7, bottom: 7 }).borderRadius(16)
.backgroundColor(COLORS.indigo)
.animation({ duration: 600, iterations: 1 })
.onClick(() => { this.audioPlaying = !this.audioPlaying; })
Text('已听 46%').fontSize(9).fontColor(COLORS.green).fontWeight(FontWeight.Bold)
}
}
.alignItems(HorizontalAlign.Start).layoutWeight(1)
}
.width('100%').padding(14).borderRadius(16).backgroundColor(COLORS.card).borderWidth(1).borderColor(COLORS.line)
Row({ space: 8 }) {
Text('🎵 倍速 1.25x').fontSize(9).fontColor(COLORS.indigo)
.padding({ left: 8, right: 8, top: 4, bottom: 4 }).borderRadius(8).backgroundColor(COLORS.chip)
Text('🌙 定时 30min').fontSize(9).fontColor(COLORS.purple)
.padding({ left: 8, right: 8, top: 4, bottom: 4 }).borderRadius(8).backgroundColor(COLORS.dark)
Column().layoutWeight(1)
Text('上次听到 第2章 32:14').fontSize(9).fontColor(COLORS.text3)
}
.width('100%')
Text('📖 章节列表').fontSize(12).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
ForEach(AUDIO_LIST[this.audioIdx].chapters, (ch: string, ci: number) => {
Row({ space: 12 }) {
Text((ci + 1).toString()).fontSize(11).fontColor(COLORS.indigo).fontWeight(FontWeight.Bold)
.width(26).height(26).borderRadius(13).backgroundColor(COLORS.chip).textAlign(TextAlign.Center)
Text(ch).fontSize(11).fontColor(COLORS.title).layoutWeight(1).maxLines(1)
Text(ci === 1 ? '正在播' : chapterDur(ci)).fontSize(9)
.fontColor(ci === 1 ? COLORS.green : COLORS.text3)
.fontWeight(ci === 1 ? FontWeight.Bold : FontWeight.Normal)
}
.width('100%').padding(11).borderRadius(12).backgroundColor(COLORS.card)
.alignItems(VerticalAlign.Center)
}, (ch: string) => 'ch' + this.audioIdx.toString() + ch)
Text('🎧 换一本听').fontSize(12).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(AUDIO_LIST, (a: AudioBook) => {
Column({ space: 5 }) {
Text(a.icon).fontSize(26)
.width(60).height(78).borderRadius(10).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
Text(a.title).fontSize(9).fontColor(COLORS.title).fontWeight(FontWeight.Bold).maxLines(1)
Text(a.duration).fontSize(8).fontColor(COLORS.text3)
}
.width('23%').padding(8).borderRadius(12).backgroundColor(COLORS.card).margin(4)
.alignItems(HorizontalAlign.Center)
.borderWidth(this.audioIdx === a.id - 1 ? 2 : 1)
.borderColor(this.audioIdx === a.id - 1 ? COLORS.indigo : COLORS.line)
.onClick(() => { this.audioIdx = a.id - 1; })
}, (a: AudioBook) => 'ab' + a.id.toString())
}
.width('100%')
}
.width('100%')
}
@Builder
studyTab() {
Column({ space: 12 }) {
Text('📈 学习数据 · 本周概览').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Row({ space: 10 }) {
Column({ space: 4 }) {
Text('470min').fontSize(18).fontColor(COLORS.indigo).fontWeight(FontWeight.Bold)
Text('学习时长').fontSize(9).fontColor(COLORS.sub)
}
.layoutWeight(1).padding(12).borderRadius(14).backgroundColor(COLORS.card).alignItems(HorizontalAlign.Start)
Column({ space: 4 }) {
Text('86%').fontSize(18).fontColor(COLORS.gold).fontWeight(FontWeight.Bold)
Text('打卡率').fontSize(9).fontColor(COLORS.sub)
}
.layoutWeight(1).padding(12).borderRadius(14).backgroundColor(COLORS.card).alignItems(HorizontalAlign.Start)
Column({ space: 4 }) {
Text('32条').fontSize(18).fontColor(COLORS.green).fontWeight(FontWeight.Bold)
Text('学习笔记').fontSize(9).fontColor(COLORS.sub)
}
.layoutWeight(1).padding(12).borderRadius(14).backgroundColor(COLORS.card).alignItems(HorizontalAlign.Start)
}
.width('100%')
Column({ space: 10 }) {
Text('⏱️ 每日学习时长(分钟)').fontSize(11).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Row({ space: 10 }) {
ForEach(WEEK_DAYS, (d: string, i: number) => {
Column({ space: 6 }) {
Text(STUDY_MIN[i].toString()).fontSize(8).fontColor(COLORS.sub)
Column().width(14).height(studyBarH(STUDY_MIN[i])).borderRadius(7)
.linearGradient({ angle: 180, colors: [[COLORS.indigo, 0], [COLORS.indigoD, 1]] })
Text(d).fontSize(8).fontColor(COLORS.text3)
}
.alignItems(HorizontalAlign.Center)
}, (d: string) => 'sw' + d)
}
.width('100%').padding({ top: 10, bottom: 12 }).borderRadius(14).backgroundColor(COLORS.card)
.justifyContent(FlexAlign.Center)
}
.width('100%')
Row({ space: 8 }) {
Text('📝 学习笔记 · ' + this.noteList.length.toString() + ' 条').fontSize(12).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Column().layoutWeight(1)
Text('➕ 记笔记').fontSize(10).fontColor(COLORS.indigo).fontWeight(FontWeight.Bold)
.onClick(() => { this.addModal = true; })
}
.width('100%')
ForEach(this.noteList, (n: StudyNote, ni: number) => {
Column({ space: 8 }) {
Row({ space: 8 }) {
Text(noteIcon(n.tag)).fontSize(18)
Text(n.title).fontSize(11).fontColor(COLORS.title).fontWeight(FontWeight.Bold).layoutWeight(1)
Text(n.time).fontSize(8).fontColor(COLORS.text3)
}
.width('100%')
Text(n.content).fontSize(10).fontColor(COLORS.sub).maxLines(2)
Row({ space: 8 }) {
Text(n.tag).fontSize(8).fontColor(catColor(n.tag))
.padding({ left: 6, right: 6, top: 2, bottom: 2 }).borderRadius(6).backgroundColor(COLORS.chip)
Column().layoutWeight(1)
Text('❤️ ' + n.likes).fontSize(9).fontColor(COLORS.pink)
.animation({ duration: 500, iterations: 1 })
Text('✏️').fontSize(12)
.onClick(() => {
this.editIdx = ni;
this.editTitle = n.title;
this.editContent = n.content;
this.editModal = true;
})
Text('🗑️').fontSize(12)
.onClick(() => { this.delModal = true; this.editIdx = ni; })
}
.width('100%')
}
.width('100%').padding(12).borderRadius(14).backgroundColor(COLORS.card)
.alignItems(HorizontalAlign.Start).borderWidth(1).borderColor(COLORS.line)
}, (n: StudyNote) => 'n' + n.id.toString())
Column({ space: 8 }) {
Text('🏅 学习成就').fontSize(11).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Row({ space: 10 }) {
ForEach(['🥇 百日坚持', '📚 12本', '🔥 32条', '⭐ 5证书'], (b: string) => {
Text(b).fontSize(9).fontColor(COLORS.indigo)
.padding({ left: 10, right: 10, top: 5, bottom: 5 }).borderRadius(10).backgroundColor(COLORS.dark)
}, (b: string) => b)
}
.width('100%').justifyContent(FlexAlign.Center)
}
.width('100%').padding(14).borderRadius(14).backgroundColor(COLORS.card)
}
.width('100%')
}
@Builder
mineTab() {
Column({ space: 12 }) {
Column({ space: 10 }) {
Row({ space: 12 }) {
Text('🧑🎓').fontSize(32)
.width(58).height(58).borderRadius(29).backgroundColor('rgba(255,255,255,0.25)').textAlign(TextAlign.Center)
Column({ space: 4 }) {
Text('求知者Leo · 年度会员').fontSize(14).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
Text('会员至 2027-03-15 · 已连续学习 156 天').fontSize(9).fontColor('rgba(255,255,255,0.85)')
}
.alignItems(HorizontalAlign.Start).layoutWeight(1)
}
.width('100%')
Row({ space: 8 }) {
Text('学习力 Lv.7').fontSize(9).fontColor('#FFE082')
.padding({ left: 10, right: 10, top: 4, bottom: 4 }).borderRadius(10)
.backgroundColor('rgba(255,255,255,0.2)')
Text('超越 98% 的同学').fontSize(9).fontColor('rgba(255,255,255,0.85)')
Column().layoutWeight(1)
Text('续费 ¥199 >').fontSize(10).fontColor('#FFE082').fontWeight(FontWeight.Bold)
}
.width('100%')
}
.width('100%').padding(16).borderRadius(18)
.linearGradient({ angle: 135, colors: [[COLORS.indigoD, 0], [COLORS.purple, 1]] })
Row({ space: 8 }) {
Text('📖 我的课程 · ' + this.mineList.length.toString() + ' 门').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Column().layoutWeight(1)
Text('学习报表 >').fontSize(9).fontColor(COLORS.indigo)
.onClick(() => { this.tabIdx = 3; })
}
.width('100%')
ForEach(this.mineList, (m: MyCourse) => {
Column({ space: 10 }) {
Row({ space: 12 }) {
Text(m.icon).fontSize(26)
.width(52).height(52).borderRadius(14).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
Column({ space: 5 }) {
Text(m.title).fontSize(11).fontColor(COLORS.title).fontWeight(FontWeight.Bold).maxLines(1)
Text(m.teacher + ' · 已学 ' + m.done + '/' + m.lessons + ' 讲').fontSize(9).fontColor(COLORS.sub)
Row({ space: 6 }) {
Column().height(5).borderRadius(3).layoutWeight(1).backgroundColor(COLORS.darkL)
Column().height(5).borderRadius(3)
.width(m.progress.toString() + '%')
.backgroundColor(m.progress === 100 ? COLORS.green : COLORS.indigo)
}
.width('100%')
Text('进度 ' + m.progress.toString() + '%').fontSize(8).fontColor(COLORS.text3)
}
.alignItems(HorizontalAlign.Start).layoutWeight(1)
Text(m.progress === 100 ? '已完成' : '继续学').fontSize(10).fontWeight(FontWeight.Bold)
.fontColor(m.progress === 100 ? COLORS.green : '#FFFFFF')
.padding({ left: 12, right: 12, top: 7, bottom: 7 }).borderRadius(10)
.backgroundColor(m.progress === 100 ? COLORS.chip : COLORS.indigo)
}
.width('100%')
}
.width('100%').padding(12).borderRadius(14).backgroundColor(COLORS.card).borderWidth(1).borderColor(COLORS.line)
}, (m: MyCourse) => 'mc' + m.id.toString())
Text('📜 我的证书').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(['经济学结业', '产品思维认证', 'AI通识课'], (c: string) => {
Column({ space: 5 }) {
Text('🏅').fontSize(26)
Text(c).fontSize(9).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Text('官方认证').fontSize(8).fontColor(COLORS.gold)
}
.width('31%').padding(12).borderRadius(14).backgroundColor(COLORS.card).margin(4)
.alignItems(HorizontalAlign.Center).borderWidth(1).borderColor(COLORS.line)
}, (c: string) => 'cert' + c)
}
.width('100%')
}
.width('100%')
}
@Builder
tabBar() {
Row({ space: 0 }) {
ForEach(TAB_LIST, (t: TabMeta, i: number) => {
Column({ space: 3 }) {
Text(t.icon).fontSize(19)
Text(t.label).fontSize(9)
.fontColor(this.tabIdx === i ? COLORS.tabOn : COLORS.text3)
.fontWeight(this.tabIdx === i ? FontWeight.Bold : FontWeight.Normal)
}
.layoutWeight(1).padding({ top: 7, bottom: 6 })
.onClick(() => { this.tabIdx = i; })
}, (t: TabMeta, i: number) => t.label + i.toString())
}
.width('100%').backgroundColor(COLORS.card).borderRadius(16)
}
@Builder
modalOverlay(onClose: () => void) {
Column().width('100%').height('100%')
.backgroundColor(COLORS.mask)
.onClick(() => { onClose(); })
}
build() {
Stack({ alignContent: Alignment.Center }) {
Column() {
this.header()
Scroll() {
Column({ space: 10 }) {
if (this.tabIdx === 0) { this.homeTab() }
else if (this.tabIdx === 1) { this.courseTab() }
else if (this.tabIdx === 2) { this.audioTab() }
else if (this.tabIdx === 3) { this.studyTab() }
else { this.mineTab() }
}
.width('100%').padding({ left: 14, right: 14, top: 12, bottom: 16 })
}
.layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top)
this.tabBar()
}
.width('100%').height('100%')
if (this.addModal) {
Stack({ alignContent: Alignment.Center }) {
this.modalOverlay(() => { this.addModal = false; })
Column({ space: 12 }) {
Text('📝 新增学习笔记').fontSize(15).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Text('记录今天最有收获的知识点').fontSize(10).fontColor(COLORS.sub)
TextInput({ placeholder: '笔记标题', text: this.addTitle })
.onChange((v: string) => { this.addTitle = v; })
.fontSize(12).padding(10).borderRadius(10).backgroundColor(COLORS.dark)
TextInput({ placeholder: '笔记内容(一句话总结)', text: this.addContent })
.onChange((v: string) => { this.addContent = v; })
.fontSize(12).padding(10).borderRadius(10).backgroundColor(COLORS.dark)
Row({ space: 10 }) {
Column() { Text('取消').fontSize(12).fontColor(COLORS.sub) }
.layoutWeight(1).padding({ top: 10, bottom: 10 }).borderRadius(10).backgroundColor(COLORS.darkL)
.onClick(() => { this.addModal = false; })
Column() { Text('保存笔记').fontSize(12).fontColor('#FFFFFF').fontWeight(FontWeight.Bold) }
.layoutWeight(1).padding({ top: 10, bottom: 10 }).borderRadius(10).backgroundColor(COLORS.indigo)
.onClick(() => {
this.noteList.unshift(new StudyNote(
Date.now(),
this.addTitle.length > 0 ? this.addTitle : '今日一得',
this.addContent.length > 0 ? this.addContent : '坚持每天记录一点。',
'职场', '08-21', 0));
this.addModal = false;
})
}
.width('100%')
}
.width('82%').constraintSize({ maxHeight: '80%' }).padding(16).borderRadius(16).backgroundColor(COLORS.card)
}
.width('100%').height('100%').zIndex(999)
}
if (this.editModal) {
Stack({ alignContent: Alignment.Center }) {
this.modalOverlay(() => { this.editModal = false; })
Column({ space: 12 }) {
Text('✏️ 编辑笔记').fontSize(15).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
TextInput({ placeholder: '笔记标题', text: this.editTitle })
.onChange((v: string) => { this.editTitle = v; })
.fontSize(12).padding(10).borderRadius(10).backgroundColor(COLORS.dark)
TextInput({ placeholder: '笔记内容', text: this.editContent })
.onChange((v: string) => { this.editContent = v; })
.fontSize(12).padding(10).borderRadius(10).backgroundColor(COLORS.dark)
Row({ space: 10 }) {
Column() { Text('取消').fontSize(12).fontColor(COLORS.sub) }
.layoutWeight(1).padding({ top: 10, bottom: 10 }).borderRadius(10).backgroundColor(COLORS.darkL)
.onClick(() => { this.editModal = false; })
Column() { Text('保存修改').fontSize(12).fontColor('#FFFFFF').fontWeight(FontWeight.Bold) }
.layoutWeight(1).padding({ top: 10, bottom: 10 }).borderRadius(10).backgroundColor(COLORS.indigo)
.onClick(() => {
updateNote(this.noteList, this.editIdx, this.editTitle, this.editContent);
this.editModal = false;
})
}
.width('100%')
}
.width('82%').constraintSize({ maxHeight: '80%' }).padding(16).borderRadius(16).backgroundColor(COLORS.card)
}
.width('100%').height('100%').zIndex(999)
}
if (this.delModal) {
Stack({ alignContent: Alignment.Center }) {
this.modalOverlay(() => { this.delModal = false; })
Column({ space: 14 }) {
Text('🗑️ 删除这条笔记?').fontSize(15).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Text('删除后不可恢复,请谨慎操作。').fontSize(10).fontColor(COLORS.sub)
Row({ space: 10 }) {
Column() { Text('再想想').fontSize(12).fontColor(COLORS.sub) }
.layoutWeight(1).padding({ top: 10, bottom: 10 }).borderRadius(10).backgroundColor(COLORS.darkL)
.onClick(() => { this.delModal = false; })
Column() { Text('确认删除').fontSize(12).fontColor('#FFFFFF') }
.layoutWeight(1).padding({ top: 10, bottom: 10 }).borderRadius(10).backgroundColor(COLORS.red)
.onClick(() => {
this.noteList.splice(this.editIdx, 1);
this.delModal = false;
})
}
.width('100%')
}
.width('78%').constraintSize({ maxHeight: '80%' }).padding(16).borderRadius(16).backgroundColor(COLORS.card)
}
.width('100%').height('100%').zIndex(999)
}
}
.width('100%').height('100%').backgroundColor(COLORS.bg)
}
}
function studentsStr(n: number): string {
if (n >= 10000) {
return (n / 10000).toFixed(1) + '万';
}
return n.toString();
}
function courseRank(id: number): string {
return 'NO.' + id.toString();
}
function chapterDur(i: number): string {
const durations: string[] = ['42:18', '38:05', '45:30', '51:12'];
return durations[i % 4];
}
function studyBarH(v: number): number {
return Math.round(v / 120 * 90);
}
function updateNote(list: StudyNote[], idx: number, title: string, content: string): void {
if (idx < 0 || idx >= list.length) { return; }
const o: StudyNote = list[idx];
list.splice(idx, 1, new StudyNote(
o.id,
title.length > 0 ? title : o.title,
content.length > 0 ? content : o.content,
o.tag, o.time, o.likes));
}
十四、开发环境搭建
14.1 安装 DevEco Studio
DevEco Studio 是 HarmonyOS 应用开发的官方 IDE。下载安装包后双击运行,进入安装欢迎界面,点击「下一步」继续。

14.2 选择安装路径
选择目标安装目录,建议安装到非系统盘,确认所需空间后点击「下一步」。

14.3 配置安装选项
设置环境变量,勾选创建桌面快捷方式、将 bin 文件夹添加到 PATH、添加上下文菜单,但是需要重启一下电脑使环境变量生效。

14.4 选择项目模板
新建一个空白模板,在模板市场中选择 Empty Ability。

14.5 配置项目信息
设置 API 为 24 的模板项目,项目名称 kfschool,Compatible SDK 6.1.1(24),设备类型勾选 Phone,点击完成后初始化项目,自动下载相关依赖。

十五、总结
通过对这款知了学堂知识付费在线教育应用的深度拆解,我们可以看到,一个优秀的移动端应用远不止是功能的堆砌,它更是架构设计、数据建模、视觉系统、交互逻辑、动效设计、数据计算等多维度能力的综合体现。
在架构层面,该应用采用了"配置驱动 + 组件化 + 状态管理"的三层设计模式。配置层将所有可变的数据与视觉属性抽离为常量数组和元数据接口,使得业务逻辑与表现层彻底解耦;组件层将五个功能页面拆分为独立的 @Builder 构建器,每个页面采用完全不同的布局风格,实现"一页一型"的差异化设计;状态层利用 ArkUI 的 @State、@Observed 机制,实现了数据变化到 UI 刷新的自动响应,特别是弹窗数据状态的精细设计(editIdx 共享于 editModal 和 delModal 记录笔记索引,addTitle/addContent 用于新增笔记输入,editTitle/editContent 预填原笔记标题和内容),三个弹窗共享 editIdx 状态实现笔记索引的传递,减少了重复状态定义,体现了对状态管理的精准理解和弹窗复用设计的工程化思维。音频状态(audioIdx/audioPlaying)独立管理当前音频书和播放状态,实现了音频听书的完整交互闭环。
在数据层面,六个 @Observed 数据类(MasterItem/CourseItem/AudioBook/StudyNote/MyCourse)以 constructor 构造函数方式实例化,完整描述了知了学堂的全部业务信息。特别的是,应用定义了 8 个工具函数(catColor/courseLevelTag/noteIcon/studentsStr/courseRank/chapterDur/studyBarH/updateNote),将颜色映射、数据计算、增删改逻辑从 UI 层抽离,实现数据与逻辑分离,是本应用工程化设计的核心亮点。studyBarH 函数将学习时长动态映射为柱状图像素高度(v/120*90),studentsStr 函数实现人数格式化(≥1万转X.X万),chapterDur 函数实现章节时长循环(4个时长i%4取模),updateNote 采用 splice 替换对象的方式触发 @Observed 响应式更新(因为直接修改 @Observed 对象属性可能不触发响应式刷新,而替换整个对象会触发),这是 ArkUI 响应式更新的关键技巧,体现了对框架机制的深入理解。updateNote 还保留了原笔记的 id/tag/time/likes 字段,仅更新 title/content(空值保留原值),实现了精准的部分更新。
在视觉层面,本应用独创了"靛蓝学术主题"设计——主色 #3D4EB5 靛蓝搭配辅助色 #C9A227 鎏金,bg #EEF0FB 靛蓝学术底,title #1E2249 深蓝而非纯黑,与靛蓝学术底形成高对比度的同时带有学术沉稳质感。应用的渐变设计是色彩系统的核心亮点——首页 Banner 使用 135 度深靛蓝→靛蓝渐变,我的用户卡使用 135 度深靛蓝→紫渐变(与首页 Banner 的深靛蓝→靛蓝不同,我的卡用深靛蓝→紫,增加视觉变化),学习页每日学习时长柱状图使用 180 度靛蓝→深靛蓝垂直渐变,三种不同角度和配色的靛蓝渐变贯穿整个应用,形成了统一而丰富的靛蓝渐变视觉语言。学习头部采用连续学习天数三卡(156天靛蓝字chip底/286h鎏金字白底/12本绿字白底)+今日课程横条(今日上新《置身事内》精读版靛蓝字+满200减40鎏金字),是本应用头部的特色设计。应用的动态颜色逻辑也十分丰富——课程分类六色着色(职场靛蓝/投资鎏金/心理紫/科技蓝/人文粉/其他绿)、课程排名两档着色(前3鎏金/其他灰)、已购课程进度两态着色(已完成绿/未完成靛蓝)、名师关注两态着色(已关注灰/未关注靛蓝)、笔记分类六色着色(与课程分类一致)、现价红字+原价删除线,形成了"以色定分类、以色定排名、以色定进度、以色定关注、以色定笔记分类、以红定价格"的多层语义着色模式。统一的色彩系统是整个应用的视觉基石,靛蓝主色搭配鎏金辅助色,配合靛蓝学术底和三种靛蓝渐变,营造出既学术沉稳又知识珍贵的在线学习氛围。
在交互层面,应用独创了"双动画状态联动"设计——aboutToAppear 中 setInterval 650ms 同时切换 breath 和 wave 两个布尔动画状态,breath 用于课程页"加入学习"按钮 opacity 动画(0.75/1,duration 500ms),wave 用于首页 Banner 📚 图标 animation(duration 650ms EaseInOut)、听书页播放按钮 animation(duration 600ms)、笔记 ❤️ 点赞 animation(duration 500ms),两个动画状态各司其职驱动四种不同的动效,形成整体协调的联动动效体系。"加入学习"按钮的呼吸动画让核心操作有了"吸引眼球"的动态感,Banner 📚 图标微动营造活动氛围,播放按钮微动营造"音频播放"的动感,笔记❤️点赞微动营造"心动"的点赞反馈感,四种动效各司其职,让静态的页面有了"鲜活"的动态感。音频听书是本应用最具特色的交互模块——audioIdx 状态控制当前音频书切换(换一本听Flex wrap网格,当前音频边框2px靛蓝高亮),audioPlaying 状态控制播放/暂停文字切换,章节列表随 audioIdx 动态更新(ForEach key含audioIdx),第二章标记"正在播"green绿字加粗,倍速1.25x和定时30min设置,上次听到位置记录,形成了完整的音频听书交互闭环。笔记增删改三大弹窗(新增unshift/编辑updateNote splice替换/删除splice按索引)共享 editIdx 索引,新增笔记默认tag职场/time 08-21/likes 0,编辑笔记预填原标题和内容,删除笔记二次确认,形成了完整的笔记管理增删改闭环。分类网格点击跳课程页(tabIdx=1)和我的页"学习报表"按钮跳学习页(tabIdx=3)实现了页面间导航跳转,已购课程进度条 width=progress% 动态计算且已完成绿色/未完成靛蓝两态着色,名师关注按钮两态(已关注灰/未关注靛蓝),课程排名前3鎏金高亮,现价红字+原价删除线,都是本应用交互设计的精细之处。

标签: HarmonyOS · API 24 · ArkTS · 华为 · 知识付费 · 在线学习 · 音频听书 · 学习笔记 · 移动端开发
更多推荐

所有评论(0)