如何保存learnr教程的练习答案至.Rmd文件以用于评分?
解决方案:在learnr教程中本地保存用户答案到Rmd
嘿,这个需求完全可行!不用依赖Publish功能,咱们可以通过自定义Shiny逻辑,让用户把填写的答案直接保存为带内容的新Rmd文件,或者导出答案数据后你再合并到原教程里。下面是具体实现步骤:
核心思路
learnr内置了获取用户交互状态的函数,我们可以用它提取所有问题的答案,再把这些内容写入新的Rmd文件(避免覆盖原教程),或者让用户导出答案数据后你再合并。
方法一:添加“保存答案到Rmd”按钮
在你的learnr教程Rmd中,添加一个自定义的保存按钮和逻辑,用户完成所有问题后点击按钮就能生成带答案的新文件。
具体代码示例
在教程的最后一个章节(比如“完成与提交”)插入以下代码块:
library(shiny) library(learnr) # 自动设置工作目录为当前Rmd所在路径,避免文件读写路径错误 setwd(dirname(rmarkdown::metadata$path)) # 定义保存答案到新Rmd的函数 save_answers_to_rmd <- function() { # 获取所有用户输入的答案(包括代码题、选择题等) user_answers <- get_all_state() # 读取原教程的Rmd内容 original_content <- readLines("your_tutorial.Rmd") # 替换成你的教程文件名 # 准备要添加的答案章节内容 answer_section <- c( "\n---\n", "## 用户提交的答案\n", "### 所有问题的解答如下:\n" ) # 遍历每个问题,格式化答案内容 for (question_id in names(user_answers)) { # 区分代码题和选择题的答案格式 if (!is.null(user_answers[[question_id]]$code)) { # 代码题:提取用户编写的代码 answer_text <- paste0("```r\n", user_answers[[question_id]]$code, "\n```\n") } else if (!is.null(user_answers[[question_id]]$answer)) { # 选择题:提取用户选择的答案 answer_text <- paste0("- 选择答案:", user_answers[[question_id]]$answer, "\n") } else { answer_text <- "- 无有效答案输入\n" } # 添加问题标题和答案到章节 answer_section <- c( answer_section, paste0("#### 问题:", question_id, "\n"), answer_text ) } # 合并原内容和答案,写入新的Rmd文件 writeLines(c(original_content, answer_section), "your_tutorial_with_answers.Rmd") # 给用户弹出成功提示 showNotification("✅ 答案已保存到 your_tutorial_with_answers.Rmd!", type = "message") } # 添加保存按钮 actionButton( inputId = "save_btn", label = "保存我的答案到新Rmd", onclick = "Shiny.setInputValue('save_trigger', Math.random());" ) # 监听按钮点击,触发保存逻辑 observeEvent(input$save_trigger, { save_answers_to_rmd() })
使用说明
- 把代码中的
your_tutorial.Rmd替换成你实际的教程文件名。 - 用户点击“Run Document”完成所有问题后,点击“保存我的答案到新Rmd”按钮,就会在同一目录下生成带答案的新文件。
- 你拿到这个新Rmd后,直接打开运行就能看到用户填写的所有答案。
方法二:导出答案为RDS文件(更安全)
如果担心用户误操作覆盖原文件,可以让用户先导出答案为RDS数据文件,你再用脚本把答案合并到原教程里:
用户端导出代码
在教程中添加导出按钮:
actionButton("export_btn", "导出我的答案为RDS", onclick = "Shiny.setInputValue('export_trigger', Math.random());") observeEvent(input$export_trigger, { user_answers <- get_all_state() saveRDS(user_answers, "user_answers.rds") showNotification("✅ 答案已导出为 user_answers.rds!", type = "message") })
你端合并代码
拿到用户的user_answers.rds后,运行以下脚本生成带答案的Rmd:
library(learnr) # 读取用户答案和原教程内容 user_answers <- readRDS("user_answers.rds") original_content <- readLines("your_tutorial.Rmd") # 生成答案章节(和方法一中的逻辑一致) answer_section <- c( "\n---\n", "## 用户提交的答案\n", "### 所有问题的解答如下:\n" ) for (question_id in names(user_answers)) { if (!is.null(user_answers[[question_id]]$code)) { answer_text <- paste0("```r\n", user_answers[[question_id]]$code, "\n```\n") } else if (!is.null(user_answers[[question_id]]$answer)) { answer_text <- paste0("- 选择答案:", user_answers[[question_id]]$answer, "\n") } else { answer_text <- "- 无有效答案输入\n" } answer_section <- c( answer_section, paste0("#### 问题:", question_id, "\n"), answer_text ) } # 写入新文件 writeLines(c(original_content, answer_section), "your_tutorial_with_answers.Rmd")
注意事项
- 确保用户运行教程时,工作目录是Rmd所在的文件夹(代码中已经用
setwd自动处理了大部分情况)。 get_all_state()会获取所有交互式问题的状态,包括未完成的问题,你可以在代码中添加判断,只保存已完成的答案。- 如果你的教程有特殊类型的问题(比如填空题),可以调整
answer_text的格式化逻辑来匹配。
内容的提问来源于stack exchange,提问作者Codrin Mironiuc
相关产品推荐
相关产品推荐

