news 2026/9/29 2:18:26

Moodle 课程卡片(Course Cards)组件:模板结构、数据导出与占位图机制完全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Moodle 课程卡片(Course Cards)组件:模板结构、数据导出与占位图机制完全解析
  • 教育
  • 后端
  • 前端

【免费下载链接】moodle

Moodle - the world's open source learning platform

项目地址:https://gitcode.com/gh_mirrors/mo/moodle
点击查看免费下载

课程卡片(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 } ] }

字段含义与取值说明:

字段类型说明
viewurlstring卡片图片与标题的跳转链接,指向课程主页
courseimagestring课程封面图 URL,缺图时后端会替换为生成的 SVG 占位图
fullnamestring课程全名
isfavouriteboolean是否已收藏,驱动收藏图标
coursecategorystring课程分类名称
showcoursecategoryboolean是否展示分类,控制分类文本与分隔符的渲染
visibleboolean课程是否对学生可见,为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()是核心服务端函数,流程如下:

  1. 通过\core_favourites\service_factory::get_service_for_user_context($usercontext)获取当前用户收藏服务,再调用find_favourites_by_type('core_course', 'courses', $offset, $limit)取回用户的课程收藏(支持limit/offset分页参数);
  2. 用course_get_enrolled_courses_for_logged_in_user()过滤出用户已选课且被收藏的课程,并按收藏时间倒序排序;
  3. 遍历收藏,对每个课程构建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为假且无权限的课程会被跳过;

  1. 函数返回值结构由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"),可直接作为 CSSbackground-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 一节给出了三条使用准则,这里结合实现进一步展开:

  1. 保持简单(Keep them simple):卡片承载的信息由course_summary_exporter严格控制——图片、全名、分类、收藏状态四项核心信息加少量可选增强字段,避免在卡片上堆砌过多内容;
  2. 最小化卡片上的操作数(Minimize the number of actions on a card):卡片默认唯一的交互就是点击跳转到课程页,其余操作(如菜单、进度)通过$menu、$progress等插槽按需注入,且menu插槽默认为空,保证默认形态足够克制;
  3. 聪明地使用图片(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

项目地址:https://gitcode.com/gh_mirrors/mo/moodle
点击查看免费下载

相关推荐

上一篇:第三方Web智能合约项目指南
下一篇:从零开始掌握智能合约测试:符号执行工具终极指南 🚀

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 2:17:04

Spring AI 开发前必须搞定的 Maven 依赖与环境配置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 2:16:31

解决 Django 非 ORM 模型初始化 request 提示问题

在 Django REST Framework (DRF) 中&#xff0c;自定义序列化器字段时&#xff0c;出现 TypeError: Field.__init__() got an unexpected keyword argument request。该错误通常发生在 get_serializer 方法错误地处理了参数&#xff0c;导致 request 作为不合法的关键字参数传递…

作者头像 李华
网站建设 2026/9/29 2:15:54

Windows 11安装VC++6.0 SP6全流程:老工程编译与HTTP接口访问指南

简介&#xff1a;VC 6.0 with SP6&#xff08;含中英文版、MSDN&#xff09;是一份面向Windows平台C开发者和编程学习者的经典集成开发环境资源包&#xff0c;尤其适合需要维护老旧MFC项目、学习传统Win32编程或体验早期Visual Studio工具的读者。压缩包整体约475.88MB&#xf…

作者头像 李华
网站建设 2026/9/29 2:15:48

计算机视觉数据标注与数据增强基础

图像水平翻转后,汽车到了右侧,标注框却仍留在左侧。图像与标签相互矛盾,训练便会受影响。读完本文,你可以检查标注坐标、验证增强是否同步修改标签,并识别训练集与验证集的近重复泄漏。 本文从宽 100 像素的示意图入手,演示矩形框翻转的坐标变换,并解释划分和标注口径为…

作者头像 李华
网站建设 2026/9/29 2:15:47

【GitHub项目实战】ShareGPT4Video-Gradio 识别视频内容并生成文本描述

ShareGPT4Video旨在让视频制作变得简单高效,让每个人都能释放创意、分享故事。通过不断优化AI技术和用户体验,项目团队希望将ShareGPT4Video打造为视频内容创作的首选工具,推动视频创作的民主化进程。ShareGPT4Video以其创新的技术和用户友好的设计,正在改变我们创作和分享…

作者头像 李华