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

Shiny应用中selectLoansLiquidateModal未按预期弹出求助

Shiny应用贷款清算模态框不弹出问题修复

问题背景

我正在编写一个测试用Shiny游戏应用,用于验证主游戏应用的辅助函数。测试参数:取款金额withdrawals=1860,现金余额vals$cashbalance=1400。核心逻辑是:当现金余额加上所有贷款价值的70%仍小于取款金额时,弹出selectLoansLiquidateModal让玩家选择清算贷款类型及数量,将对应贷款价值转换为现金并从loanData中移除。

目前遇到的问题:明明参数符合模态框触发条件,但selectLoansLiquidateModal始终不弹出,不过单独测试LiquidateLoans()功能正常。

问题根源

  1. 反应式变量引用错误:在判断是否需要弹窗的条件中,使用了全局的loanData而非反应式值vals$loanData,导致计算的贷款总价值错误,条件不成立,无法进入弹窗分支。
  2. 逻辑执行顺序错误:弹窗弹出后立刻执行了贷款清算逻辑,但此时玩家还未选择要清算的贷款,应该将清算逻辑移到弹窗确认按钮的响应事件中,等待玩家确认选择后再执行。

修复后的完整代码

# 导入依赖
source("usePackages.R")
loadPkgs(c("sqldf","shiny","shinyalert","dplyr"))

#### 辅助函数部分
selectLoansLiquidateModal <- function(loan.type.1.min=0, loan.type.1.max=2, 
                                      loan.type.2.min=0, loan.type.2.max=2, 
                                      loan.type.3.min=0, loan.type.3.max=2){
  modalDialog(
    title = "选择要清算的贷款",
    numericInput("loantype1", "选择要清算的$200贷款数量", value = 0, 
                 min = loan.type.1.min, max = loan.type.1.max),
    numericInput("loantype2", "选择要清算的$300贷款数量", value = 0, 
                 min = loan.type.2.min, max = loan.type.2.max),
    numericInput("loantype3", "选择要清算的$600贷款数量", value = 0,
                 min = loan.type.3.min, max = loan.type.3.max),
    
    footer = tagList(
      actionButton("loanliquidatesubmission", "确认清算")
    )
    
  )
}

updateCashBalance <- function(cashbalance, newamt){
  newcashbal <- cashbalance + newamt
  return(newcashbal)
}

SelectLoans <- function(numberofeachtypeofloan,eachtypeofloan){
  loansselected <- data.frame(loan.type = eachtypeofloan, no.of.each.type.of.loan = numberofeachtypeofloan)
  # 要求两个参数都是数值向量
  return(loansselected)
}

LiquidateLoans <- function(cashbalance=1400, withdrawalamount=1860, 
                           loanData=data.frame(loanID = c(1,2,3,4,5), 
                                               loanType=c(1,2,3,2,2), 
                                               loanValue = c(200, 300, 600, 300, 300), 
                                               durationToMaturity = c(3,1,2,2,3)), 
                           loansselected=SelectLoans(c(1,2,0),c(1,2,3)), percentage=0.7){
  
  # 记录清算贷款的总价值
  removed_loans_value = 0
  
  for (i in 1:nrow(loansselected)){
    loan_type <- loansselected$loan.type[i]
    num_rows_to_remove <- loansselected$no.of.each.type.of.loan[i]
    
    # 筛选对应类型的贷款
    filtered_rows <- loanData[loanData$loanType == loan_type, ]
    
    if (num_rows_to_remove != 0 & nrow(filtered_rows) >= num_rows_to_remove) {
      # 按剩余期限降序排序,优先清算期限长的
      sorted_rows <- filtered_rows[order(filtered_rows$durationToMaturity, decreasing = TRUE), ]
      
      # 确定要移除的贷款行
      rows_to_remove <- sorted_rows[1:num_rows_to_remove, ]
      print(rows_to_remove) # 调试用
      
      # 累加清算贷款的价值
      removed_loans_value <- sum(rows_to_remove$loanValue) + removed_loans_value
      print(removed_loans_value) # 调试用
      
      # 从贷款数据中移除对应行
      loanData <- loanData[!(loanData$loanID %in% rows_to_remove$loanID), ]
    }
    
    if(nrow(filtered_rows) < num_rows_to_remove){
      stop(sprintf("你清算的%d类型贷款数量超过了持有量",i))
    }
  }
  
  # 更新现金余额:清算贷款到账金额减去取款金额
  cashbalance <- updateCashBalance(cashbalance, percentage*removed_loans_value-withdrawalamount)
  result_list <- list(resultloanData = loanData, resultcashbalance = cashbalance, removed_loans_value = removed_loans_value)
  return(result_list)
}

getMaxLoan <- function(loanData){
  loan_type_counts <- loanData %>% group_by(loanType) %>% tally()
  # 确保返回的数量是数值类型,避免NULL问题
  result_list <- list(
    one = ifelse(nrow(loan_type_counts)>=1, loan_type_counts[1,2]$n, 0),
    two = ifelse(nrow(loan_type_counts)>=2, loan_type_counts[2,2]$n, 0),
    three = ifelse(nrow(loan_type_counts)>=3, loan_type_counts[3,2]$n, 0)
  )
  return(result_list)
}

# 测试函数
test <- function(){
  # 测试贷款清算
  loanData <- data.frame(loanID = c(1,2,3,4,5), loanType=c(1,2,3,2,2), loanValue = c(200, 300, 600, 300, 300), durationToMaturity = c(3,1,2,2,3)) # 测试用数据
  numberofeachtypeofloan <- c(1,2,0) # 测试用选择数量
  eachtypeofloan <- c(1,2,3) # 测试用贷款类型
  loansselected <- SelectLoans(numberofeachtypeofloan,eachtypeofloan)
  print(loansselected)
  result_list <- LiquidateLoans(cashbalance=1400, withdrawalamount=1860, loanData, loansselected, percentage=0.7)
  return(result_list)
}

#### UI部分
ui <- fluidPage(
  actionButton("nextmonth", "进入下月")
)

#### 服务器部分
server <- function(input, output, session){
  vals <- reactiveValues(
    cashbalance = 1400, 
    loanData = data.frame(
      loanID = c(1,2,3,4,5), 
      loanType=c(1,2,3,2,2), 
      loanValue = c(200, 300, 600, 300, 300), 
      durationToMaturity = c(3,1,2,2,3)
    ),
    withdrawals = 1860, # 将取款金额存入反应式值,方便后续修改
    percentage=0.7
  )

  observeEvent(input$nextmonth, {
    print("当前现金余额:")
    print(vals$cashbalance)
    print("当前贷款数据:")
    print(vals$loanData)
    
    withdrawals <- vals$withdrawals
    if(withdrawals <= vals$cashbalance){
      # 现金足够,直接扣款
      vals$cashbalance <- updateCashBalance(vals$cashbalance, -1*withdrawals)
      print("无需清算贷款")
    }else{
      # 计算可通过贷款清算获得的最大现金
      max_liquidate_cash <- vals$percentage * sum(vals$loanData$loanValue)
      if(vals$cashbalance + max_liquidate_cash < withdrawals){
        # 贷款清算后仍无法满足取款,游戏结束
        shinyalert("你没有足够的贷款清算来覆盖取款,游戏结束", type = "error")
        stop("无法满足取款需求,游戏结束")
      }else{
        print("进入贷款清算流程")
        max_number_list <- getMaxLoan(vals$loanData)
        
        # 弹出清算选择模态框
        showModal(selectLoansLiquidateModal(
          loan.type.1.min=0, loan.type.1.max=max_number_list$one, 
          loan.type.2.min=0, loan.type.2.max=max_number_list$two, 
          loan.type.3.min=0, loan.type.3.max=max_number_list$three
        ))
      }
    }
  })
  
  observeEvent(input$loanliquidatesubmission, {
    removeModal()
    # 获取玩家选择的清算数量
    numberofeachtypeofloan <- c(input$loantype1, input$loantype2, input$loantype3)
    eachtypeofloan <- c(1,2,3)
    loansselected <- SelectLoans(numberofeachtypeofloan,eachtypeofloan)
    
    print("玩家选择的清算贷款:")
    print(loansselected)
    
    # 计算清算后可获得的现金
    temp_liquidate_value <- sum(loansselected$no.of.each.type.of.loan * c(200,300,600))
    temp_available_cash <- vals$cashbalance + vals$percentage * temp_liquidate_value
    
    if(temp_available_cash < vals$withdrawals){
      # 清算金额不足,提示重新选择
      shinyalert("你选择的贷款清算金额不足以覆盖取款,请选择更多贷款", type = "error")
      # 重新弹出模态框
      max_number_list <- getMaxLoan(vals$loanData)
      showModal(selectLoansLiquidateModal(
        loan.type.1.min=0, loan.type.1.max=max_number_list$one, 
        loan.type.2.min=0, loan.type.2.max=max_number_list$two, 
        loan.type.3.min=0, loan.type.3.max=max_number_list$three
      ))
    } else {
      # 执行贷款清算
      result_list <- LiquidateLoans(vals$cashbalance, vals$withdrawals, vals$loanData, loansselected, vals$percentage)
      
      print("清算结果:")
      print(result_list)
      
      # 更新反应式值
      vals$loanData <- result_list$resultloanData
      vals$cashbalance <- result_list$resultcashbalance
      
      print("更新后的贷款数据:")
      print(vals$loanData)
      print("更新后的现金余额:")
      print(vals$cashbalance)
    }
  })
  
}       

shinyApp(ui, server)

关键修改说明

  1. 修正反应式变量引用:将判断条件中的sum(loanData$loanValue)改为sum(vals$loanData$loanValue),确保使用最新的贷款数据进行计算。
  2. 调整逻辑执行顺序:将贷款清算的逻辑从nextmonth的点击事件中移到loanliquidatesubmission的点击事件中,等待玩家确认选择后再执行清算。
  3. 增加清算金额预校验:在玩家确认选择后,先计算清算后可获得的现金是否足够覆盖取款,不足则提示重新选择并再次弹出模态框。
  4. 优化getMaxLoan函数:增加判断避免贷款类型不足时返回NULL,确保模态框的数值输入框max参数为有效数值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 08:50:53