Shiny应用中selectLoansLiquidateModal未按预期弹出求助
Shiny应用贷款清算模态框不弹出问题修复
问题背景
我正在编写一个测试用Shiny游戏应用,用于验证主游戏应用的辅助函数。测试参数:取款金额withdrawals=1860,现金余额vals$cashbalance=1400。核心逻辑是:当现金余额加上所有贷款价值的70%仍小于取款金额时,弹出selectLoansLiquidateModal让玩家选择清算贷款类型及数量,将对应贷款价值转换为现金并从loanData中移除。
目前遇到的问题:明明参数符合模态框触发条件,但selectLoansLiquidateModal始终不弹出,不过单独测试LiquidateLoans()功能正常。
问题根源
- 反应式变量引用错误:在判断是否需要弹窗的条件中,使用了全局的
loanData而非反应式值vals$loanData,导致计算的贷款总价值错误,条件不成立,无法进入弹窗分支。 - 逻辑执行顺序错误:弹窗弹出后立刻执行了贷款清算逻辑,但此时玩家还未选择要清算的贷款,应该将清算逻辑移到弹窗确认按钮的响应事件中,等待玩家确认选择后再执行。
修复后的完整代码
# 导入依赖 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)
关键修改说明
- 修正反应式变量引用:将判断条件中的
sum(loanData$loanValue)改为sum(vals$loanData$loanValue),确保使用最新的贷款数据进行计算。 - 调整逻辑执行顺序:将贷款清算的逻辑从
nextmonth的点击事件中移到loanliquidatesubmission的点击事件中,等待玩家确认选择后再执行清算。 - 增加清算金额预校验:在玩家确认选择后,先计算清算后可获得的现金是否足够覆盖取款,不足则提示重新选择并再次弹出模态框。
- 优化
getMaxLoan函数:增加判断避免贷款类型不足时返回NULL,确保模态框的数值输入框max参数为有效数值。
内容的提问来源于stack exchange,提问作者Lyxcoder
相关产品推荐
相关产品推荐

