You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Shiny中根据downloadHandler完成状态更新按钮状态

解决Shiny下载按钮状态同步与取消识别问题

核心思路

Shiny原生downloadButton的状态反馈机制不支持监听取消/失败事件,我们需要通过自定义按钮+JS通信+后端状态回调来实现精准的状态控制,同时保证文件对话框的一致性。

完整实现代码

library(shiny)
library(officer)

ui <- fluidPage(
  tags$head(
    tags$script(HTML("
      $(document).on('shiny:connected', function() {
        // 监听后端发送的下载状态消息
        Shiny.addCustomMessageHandler('downloadStatus', function(message) {
          const btn = $('#save_report');
          if(message.status === 'start') {
            btn.html('<i class=\"fa fa-spinner fa-spin\"></i> 生成中...');
            btn.prop('disabled', true);
          } else if(message.status === 'success') {
            btn.html('<i class=\"fa fa-check\"></i> 已保存');
            // 3秒后恢复初始状态
            setTimeout(() => {
              btn.html('<i class=\"fa fa-download\"></i> 生成并保存报告');
              btn.prop('disabled', false);
            }, 3000);
          } else if(message.status === 'error') {
            btn.html('<i class=\"fa fa-exclamation-triangle\"></i> 生成失败');
            setTimeout(() => {
              btn.html('<i class=\"fa fa-download\"></i> 生成并保存报告');
              btn.prop('disabled', false);
            }, 3000);
          } else if(message.status === 'cancel') {
            btn.html('<i class=\"fa fa-download\"></i> 生成并保存报告');
            btn.prop('disabled', false);
          }
        });
        
        // 处理按钮点击事件
        $('#save_report').on('click', function() {
          const btn = $(this);
          // 先发送开始请求给后端
          Shiny.setInputValue('trigger_download', Math.random());
          
          // 监听下载对话框取消事件(通过检测焦点恢复+未触发下载完成)
          let downloadCanceled = true;
          setTimeout(() => {
            if(downloadCanceled) {
              Shiny.setInputValue('download_canceled', Math.random());
            }
          }, 1000); // 延迟1秒检测,适配不同浏览器的对话框响应速度
          
          // 监听下载完成事件
          document.addEventListener('download', function() {
            downloadCanceled = false;
          }, { once: true });
        });
      });
    "))
  ),
  
  # 自定义按钮(替代原生downloadButton)
  actionButton(
    inputId = "save_report",
    label = "生成并保存报告",
    icon = icon("download"),
    class = "btn-primary"
  )
)

server <- function(input, output, session) {
  # 触发下载逻辑
  observeEvent(input$trigger_download, {
    # 发送开始状态,更新按钮为加载中
    session$sendCustomMessage("downloadStatus", list(status = "start"))
    
    # 生成officer文档
    doc <- read_docx()
    doc <- body_add_par(doc, "这是测试报告内容", style = "heading 1")
    doc <- body_add_par(doc, "报告生成于:", style = "normal")
    doc <- body_add_par(doc, Sys.time(), style = "strong")
    
    # 创建下载链接
    output$download_report <- downloadHandler(
      filename = function() {
        paste0("报告_", Sys.Date(), ".docx")
      },
      content = function(file) {
        tryCatch({
          print(doc, target = file)
          # 发送成功状态
          session$sendCustomMessage("downloadStatus", list(status = "success"))
        }, error = function(e) {
          # 发送失败状态
          session$sendCustomMessage("downloadStatus", list(status = "error"))
          message("报告生成失败:", e$message)
        })
      }
    )
    
    # 触发下载(通过JS调用下载链接)
    session$sendCustomMessage("triggerDownload", list(url = session$registerDownload("download_report")))
  })
  
  # 处理用户取消下载的情况
  observeEvent(input$download_canceled, {
    session$sendCustomMessage("downloadStatus", list(status = "cancel"))
  })
}

shinyApp(ui, server)

关键解决点说明

  1. 一致显示文件对话框

    • 用actionButton替代原生downloadButton,避免原生按钮的默认跳转行为干扰状态更新;
    • 通过后端session$registerDownload生成合法下载URL,再用JS触发下载,保证所有浏览器都能稳定弹出保存对话框。
  2. 根据实际状态更新按钮

    • 后端在生成报告前、成功、失败时分别发送start/success/error状态消息;
    • 前端通过Shiny.addCustomMessageHandler接收状态,动态修改按钮的图标和标签,同时禁用/启用按钮。
  3. 识别用户取消下载

    • 前端点击按钮后启动延迟检测,若1秒内未触发浏览器的download事件(说明用户关闭了保存对话框),则发送cancel状态给后端;
    • 后端接收取消信号后,发送状态让按钮恢复初始状态。

额外优化

  • 加入了加载动画(fa-spinner),让用户明确感知正在生成报告;
  • 成功/失败状态显示3秒后自动恢复初始按钮状态,提升交互体验;
  • 用tryCatch捕获报告生成过程中的异常,确保失败时能反馈给用户。

内容的提问来源于stack exchange,提问作者AWaddington

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 15:31:16