- 教育
- 后端
- 前端
【免费下载链接】moodle
Moodle - the world's open source learning platform
课程卡片(Course Cards)是 Moodle 中用于在课程集合页面上展示课程摘要的可点击组件,帮助用户通过记忆中的课程封面图或课程名称快速浏览并进入课程主页。本文以 public/admin/tool/componentlibrary/content/moodle/components/coursecards.md 的组件设计规范为骨架,结合仓库内模板、导出器与调用方源码,系统讲解课程卡片的渲染流程、数据字段、使用场景与占位图机制。读完本文,你将掌握如何在自己的 Moodle 页面或插件中复用这套组件,并理解其底层数据如何由course_summary_exporter供给。
课程卡片是什么
课程卡片是 Moodle 的组件库(Component Library)中标记为Available的标准化组件。它用于在课程列表中呈现单个课程的摘要信息,并作为用户导航到课程主页(course home page)的入口。Moodle 的用户仪表盘(user dashboard)、我的课程总览块(myoverview block)与星标课程块(starred courses block)等页面都会通过 JavaScript 或 PHP 加载课程卡片。
卡片由一份共享模板渲染,即course/templates/coursecard.mustache。文档中强调了两条硬性规范:
- 卡片必须始终包含一张图片;若后端未提供课程图片,则使用占位图(placeholder image)兜底;
- 卡片的图片与标题必须可点击,且点击后始终跳转到对应课程页面。
课程卡片必须展示的信息
根据组件文档,每张课程卡片上应当始终展示以下四项内容:
| 信息项 | 说明 |
|---|---|
| Course image | 课程封面图,缺失时回退为占位图 |
| Course full name | 课程全名 |
| Course category | 课程分类 |
| Course favourited 状态 | 该课程是否被用户收藏(星标) |
从 coursecard.mustache 的模板源码看,这些要素对应以下实现:
- 图片区:外层
<a href="{{viewurl}}" tabindex="-1">包裹的.card-img-top容器,通过内联样式background-image: url("{{{courseimage}}}")呈现封面图,并附带visually-hidden的全名文本保证可访问性; - 全名:
.aalink.coursename链接内渲染$coursename区块,同时引入core_course/favouriteicon局部模板显示收藏图标; - 分类:
$coursecategory区块配合text-muted muted样式展示; - 收藏状态:由导出器输出的
isfavourite字段驱动收藏图标渲染。
此外模板还支持若干可选增强字段:
shortname:课程简称,仅在showshortname为真时展示(由站点配置courselistshortnames控制);visible:课程对学生的可见性,不可见时渲染 "hidden from students" 徽章;progress/hasprogress:课程学习进度百分比;menu:卡片操作菜单插槽。
模板根节点<div class="card course-card mx-1" role="listitem"><div class="card-grid mx-0 row row-cols-1 row-cols-sm-2 row-cols-lg-3 {{$classes}}{{/classes}}">{{< core_course/coursecards }} {{$classes}}flex-nowrap overflow-auto{{/classes}} {{$coursename}} {{{fullname}}} {{/coursename}} {{$coursecategory}} {{#showcoursecategory}} <span class="visually-hidden"> {{#str}}aria:coursecategory, core_course{{/str}} </span> <div class="text-truncate">{{{coursecategory}}}</div> {{/showcoursecategory}} {{/coursecategory}} {{$divider}} {{#showcoursecategory}} <div class="px-1">|</div> {{/showcoursecategory}} {{/divider}} {{/ core_course/coursecards }}
从模板注释可知,view-cards正是为 star 课程块的轮播(carousel)设计:使用flex-nowrap overflow-auto实现横向滚动,分类之间以竖线分隔符连接。这就是文档中 "A deck of course cards"(一叠课程卡片)与 "A single card"(单张卡片)的来源对应关系。
数据从哪来:course_summary_exporter
课程卡片的数据结构并非由模板自行拼装,而是由导出器类 course/classes/external/course_summary_exporter.php 统一提供。该类继承自\core\external\exporter,将课程对象(stdClass)序列化为模板可用的上下文数据。
在get_other_values()中可以看到卡片各字段的完整产出逻辑:
protected function get_other_values(renderer_base $output) { global $CFG; $courseimage = self::get_course_image($this->data); if (!$courseimage) { $courseimage = $output->get_generated_image_for_id($this->data->id); } $progress = self::get_course_progress($this->data); $hasprogress = false; if ($progress === 0 || $progress > 0) { $hasprogress = true; } $progress = floor($progress ?? 0); $coursecategory = \core_course_category::get($this->data->category, MUST_EXIST, true); return array( 'fullnamedisplay' => get_course_display_name_for_list($this->data), 'viewurl' => (new moodle_url('/course/view.php', array('id' => $this->data->id)))->out(false), 'courseimage' => $courseimage, 'progress' => $progress, 'hasprogress' => $hasprogress, 'isfavourite' => $this->related['isfavourite'], 'hidden' => boolval(get_user_preferences('block_myoverview_hidden_course_' . $this->data->id, 0)), 'showshortname' => $CFG->courselistshortnames ? true : false, 'coursecategory' => $coursecategory->name ); }关键逻辑逐条解读:
- 课程图片回退链:先通过
get_course_image()从core/course_image缓存读取课程封面图 URL;拿不到(返回false)时,调用$output->get_generated_image_for_id($course->id)生成占位图——这正是文档中占位图机制在真实数据流中的落点; - 跳转链接:
viewurl统一指向/course/view.php?id={$course->id},保证卡片点击总是进入课程主页; - 进度:
get_course_progress()委托\core_completion\progress::get_course_progress_percentage()计算课程完成百分比; - 收藏状态:
isfavourite不是课程本身的属性,而是通过构造函数传入的related参数注入(构造时若未提供则默认false); - 分类:通过
\core_course_category::get()读取课程分类名称; - 隐藏状态:读取用户偏好
block_myoverview_hidden_course_{id},用于我的课程总览块的隐藏课程过滤。
define_properties()定义了从课程对象直接映射的基础属性:id、fullname、shortname、idnumber、summary、summaryformat、startdate、enddate、visible、showactivitydates、showcompletionconditions、pdfexportfont;define_other_properties()则声明了派生属性:fullnamedisplay、viewurl、courseimage、progress、hasprogress、isfavourite、hidden、timeaccess、showshortname、coursecategory。两套属性共同构成模板可用的完整上下文。
值得一提的是,导出器中get_course_pattern()与coursecolor()两个静态方法已标记为3.7 版本起废弃,统一改用$OUTPUT->get_generated_image_for_id()与$OUTPUT->get_generated_color_for_id(),新旧调用均生成基于 id 的可复现 SVG/色值。
示例数据:一张卡片的完整 JSON
组件文档给出了可直接用于渲染调试的 JSON 示例(deck 模式):
{ "courses": [ { "viewurl": "https://moodlesite/course/view.php?id=2", "courseimage": "https://placekitten.com/300/500", "fullname": "Mathematics Year One", "isfavourite": true, "coursecategory": "Category 1", "showcoursecategory": true, "visible": true }, { "viewurl": "https://moodlesite/course/view.php?id=3", "courseimage": "https://placekitten.com/330/500", "fullname": "Health and Safety", "isfavourite": false, "coursecategory": "Business", "showcoursecategory": true, "visible": true }, { "viewurl": "https://moodlesite/course/view.php?id=4", "courseimage": "https://placekitten.com/360/500", "fullname": "French Advanced", "isfavourite": false, "coursecategory": "Languages", "showcoursecategory": true, "visible": true }, { "viewurl": "https://moodlesite/course/view.php?id=4", "courseimage": "https://placekitten.com/360/400", "fullname": "French Year one", "isfavourite": false, "coursecategory": "Languages", "showcoursecategory": true, "visible": true } ] }字段含义与取值说明:
| 字段 | 类型 | 说明 |
|---|---|---|
viewurl | string | 卡片图片与标题的跳转链接,指向课程主页 |
courseimage | string | 课程封面图 URL,缺图时后端会替换为生成的 SVG 占位图 |
fullname | string | 课程全名 |
isfavourite | boolean | 是否已收藏,驱动收藏图标 |
coursecategory | string | 课程分类名称 |
showcoursecategory | boolean | 是否展示分类,控制分类文本与分隔符的渲染 |
visible | boolean | 课程是否对学生可见,为false时显示隐藏徽章 |
真实调用链:star 课程块如何渲染卡片组
文档提到 "The example below show a deck of cards as used on the starred courses block"(下图示例展示星标课程块中使用的卡片组)。查看调用方源码可以还原完整的真实数据链路。
public/blocks/starredcourses/classes/external.php 中的get_starred_courses()是核心服务端函数,流程如下:
- 通过
\core_favourites\service_factory::get_service_for_user_context($usercontext)获取当前用户收藏服务,再调用find_favourites_by_type('core_course', 'courses', $offset, $limit)取回用户的课程收藏(支持limit/offset分页参数); - 用
course_get_enrolled_courses_for_logged_in_user()过滤出用户已选课且被收藏的课程,并按收藏时间倒序排序; - 遍历收藏,对每个课程构建
course_summary_exporter并导出:
$exporter = new course_summary_exporter($course, ['context' => $context, 'isfavourite' => true]); $formattedcourse = $exporter->export($renderer); $formattedcourses[] = $formattedcourse;注意这里isfavourite恒为true,因为列表本身就来源于收藏;同时在导出前通过has_capability('moodle/course:viewhiddencourses', $context)检查隐藏课程的查看权限,$course->visible为假且无权限的课程会被跳过;
- 函数返回值结构由
get_starred_courses_returns()声明为external_multiple_structure(course_summary_exporter::get_read_structure()),即一个course_summary_exporter数组——这保证前端通过 Web Service 拿到的数据与模板上下文严格同构。
前端侧,public/blocks/starredcourses/classes/output/main.php 的export_for_template()为view-cards模板提供userid、nocoursesimg与displaycategories上下文,卡片列表则由 JS 通过上述 Web Service 拉取后填充。同样的 exporter 也被 public/blocks/timeline/classes/output/main.php(时间线块)复用,印证了 "课程卡片可在任何列出课程的地方使用" 的设计意图。
占位图机制:由 id 生成唯一样式 SVG
文档明确指出:"Cards usually don't really look great without images. That's why we show a placeholder image when no course image is provided."(没有图片的卡片通常观感不佳,因此当未提供课程图片时展示占位图),并给出核心调用方式:
$OUTPUT->get_generated_image_for_id($id);该占位图由核心渲染器提供,内部使用一个基于 idnumber(此处实际传入课程 id)生成"独一无二"SVG 的库。其机制要点:
- 对同一
$id始终生成相同的 SVG data URI,保证同一课程的占位图长期稳定一致,不会每次刷新变化; - 输出为可内联的 data URI(文档称其为 "datauri"),可直接作为 CSS
background-image或<img>的src使用; - 组件文档的 Placeholder images 一节展示了九宫格占位图示例,每张占位图均以
background-image: url('data:...')形式渲染在.card容器中,直观呈现了无图课程的实际效果。
在导出器中,占位图正是通过这段代码与课程图片无缝衔接:
$courseimage = self::get_course_image($this->data); if (!$courseimage) { $courseimage = $output->get_generated_image_for_id($this->data->id); }也就是说,"有图用图、无图生成占位图" 这一规范在数据层已经强制执行,模板侧无需再关心缺图分支。若你在自己的渲染代码中手动构造卡片上下文,也应当遵循同样的回退逻辑,否则卡片将违背组件库 "卡片必须始终包含图片" 的规范。
使用课程卡片的设计指南
组件文档在 Usage 一节给出了三条使用准则,这里结合实现进一步展开:
- 保持简单(Keep them simple):卡片承载的信息由
course_summary_exporter严格控制——图片、全名、分类、收藏状态四项核心信息加少量可选增强字段,避免在卡片上堆砌过多内容; - 最小化卡片上的操作数(Minimize the number of actions on a card):卡片默认唯一的交互就是点击跳转到课程页,其余操作(如菜单、进度)通过
$menu、$progress等插槽按需注入,且menu插槽默认为空,保证默认形态足够克制; - 聪明地使用图片(Use images smartly):图片是用户识别课程的第一视觉线索,封面图缺失时用基于 id 生成的稳定占位图填充,避免空白卡片;同时注意
viewurl统一指向/course/view.php,保证视觉一致性与导航可预期性。
若要在自定义页面或插件中复用课程卡片,推荐的做法是:复用core_course/view-cards或core_course/coursecards模板,数据通过course_summary_exporter(或调用get_starred_courses这类现成 Web Service)产出,再配合isfavourite、showcoursecategory、visible等布尔字段控制细节渲染。这样既遵守组件库规范,又最大限度复用核心代码。
参考源码路径速查
| 文件 | 作用 |
|---|---|
| public/admin/tool/componentlibrary/content/moodle/components/coursecards.md | 课程卡片组件规范文档(本文骨架) |
| public/course/templates/coursecard.mustache | 单张课程卡片模板 |
| public/course/templates/coursecards.mustache | 课程卡片网格容器 |
| public/course/templates/view-cards.mustache | 星标课程块使用的横向轮播卡片组 |
| public/course/classes/external/course_summary_exporter.php | 卡片数据导出器 |
| public/blocks/starredcourses/classes/external.php | 星标课程块 Web Service(真实调用链示例) |
| public/blocks/starredcourses/classes/output/main.php | 星标课程块模板上下文 |
| public/blocks/timeline/classes/output/main.php | 时间线块中复用课程卡片的示例 |
- 教育
- 后端
- 前端
【免费下载链接】moodle
Moodle - the world's open source learning platform
相关推荐
TiddlyWiki 社区记录与资源体系解析:Community Cards 数据结构、提交规范与卡片渲染机制
TiddlyWiki 社区记录与资源体系解析:Community Cards 数据结构、提交规范与卡片渲染机制 本篇技术指南围绕 TiddlyWiki 5 仓库
前端后端Mushroom Cards 数据流分析:理解卡片与实体之间的通信机制
Mushroom Cards 数据流分析:理解卡片与实体之间的通信机制 Mushroom Cards 是一个专为 Home Assistant 设计的现代化仪表
前端UI组件Penpot 数据结构与形状编辑全链路实战:属性设计、数据迁移、组件同步与导入导出机制解析
Penpot 数据结构与形状编辑全链路实战:属性设计、数据迁移、组件同步与导入导出机制解析 Penpot 的数据结构是整个产品最复杂也最关键的部分之一:设计文件
前端设计系统图形学协同办公
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考