From 21174836dec9754fcd66034142b723de350aaa6d Mon Sep 17 00:00:00 2001 From: gaozhaochen <158975971@qq.com> Date: Wed, 26 Nov 2025 16:54:55 +0800 Subject: [PATCH] =?UTF-8?q?update:=20=E8=87=AA=E5=8A=A8=E5=8A=A0=E8=BD=BDx?= =?UTF-8?q?lsx=E7=9A=84=E5=85=A8=E9=83=A8sheet=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inst/app/tools/data/init_load_data.R | 67 +++++++----- radiant.data/inst/app/tools/data/manage.R | 101 +++++++++++++----- 2 files changed, 113 insertions(+), 55 deletions(-) diff --git a/radiant.data/inst/app/tools/data/init_load_data.R b/radiant.data/inst/app/tools/data/init_load_data.R index a7bed85..a31f485 100644 --- a/radiant.data/inst/app/tools/data/init_load_data.R +++ b/radiant.data/inst/app/tools/data/init_load_data.R @@ -8,38 +8,36 @@ observe({ dataset_id <- query[['datasetId']] token <- query[['token']] + # 初始化一个 session 级别的变量来记录已加载的 ID,防止刷新时的重复请求 + if (is.null(session$userData$loaded_datasets)) { + session$userData$loaded_datasets <- c() + } + # 2. 仅当 ID 和 Token 均存在时执行 if (!is.null(dataset_id) && !is.null(token)) { - # 定义 Radiant 内部使用的数据集名称 (例如: data_10086) - # 如果 URL 传了 name 参数就用 name,否则用 id 拼接 - ds_name <- if (!is.null(query[['name']])) query[['name']] else paste0("data_", dataset_id) - - # 3. 检查数据是否已存在 (防止重复加载) - if (is.null(r_data[[ds_name]])) { + # 3. [修改点] 检查 ID 是否已在本次会话中加载过 + # 因为现在数据集名称是动态的 Sheet 名,下载前无法预知,所以改为判断 ID + if (!(dataset_id %in% session$userData$loaded_datasets)) { withProgress(message = '正在从业务系统同步数据...', value = 0.2, { - # 4. 获取环境变量中的 API 基地址 - # api_base <- Sys.getenv("HOST_API_BASE","http://127.0.0.1:11999") - api_base <- Sys.getenv("HOST_API_BASE","https://ds.cixincloud.com/data-search-api") + # 4. 获取环境变量 + api_base <- Sys.getenv("HOST_API_BASE", "https://ds.cixincloud.com/data-search-api") # 5. 拼接完整 API 路径 - # target_url <- paste0(api_base, "/disease-data/data/export/apply/apply/case?applyId=", dataset_id) target_url <- paste0(api_base, "/research-project/generate-project-dataset/", dataset_id) - # 6. 创建临时文件 (明确 .xlsx 后缀) + # 6. 创建临时文件 tmp_file <- tempfile(fileext = ".xlsx") tryCatch({ incProgress(0.3, detail = "正在鉴权并下载...") - # 7. 发起带 Token 的 HTTP 请求 + # 7. HTTP 请求 response <- httr::POST( url = target_url, - # 添加 Bearer Token (或根据你的接口要求修改 Header) httr::add_headers(authentication = paste(token)), - # 将结果写入磁盘 httr::write_disk(tmp_file, overwrite = TRUE) ) @@ -47,34 +45,47 @@ observe({ print(response$request) message("===================") - # 检查 HTTP 状态码 if (httr::status_code(response) != 200) { - stop(paste("下载失败,请手动导入,HTTP状态码:", httr::status_code(response))) + stop(paste("下载失败,HTTP状态码:", httr::status_code(response))) } incProgress(0.7, detail = "解析并导入 Radiant...") - # 8. 复用 Radiant 核心加载函数 (manage_ui.R 中定义) - # 这会自动完成读取、转因子、生成R代码、注册到下拉框等所有动作 + # 8. 调用核心加载函数 + # 注意:这里的 fname 仅用于扩展名识别,实际数据集名称由 Sheet 名决定 load_user_data( - fname = paste0(ds_name, ".xlsx"), # 虚拟文件名 - uFile = tmp_file, # 实际文件路径 + fname = "downloaded_data.xlsx", + uFile = tmp_file, ext = "xlsx", - xlsx_sheet = 1, xlsx_header = TRUE, man_str_as_factor = TRUE ) - # 9. 界面联动:选中数据并跳转到视图 - updateSelectInput(session, "dataset", selected = ds_name) - updateTabsetPanel(session, "nav_radiant", selected = "Data") - updateTabsetPanel(session, "tabs_data", selected = "View") # 或者 "Visualize" + # 9. 界面联动:获取刚才加载的 Sheet 名称并选中第一个 + # 必须再次读取 Sheet 列表并做 make.names 处理,才能匹配到 r_data 中的 Key + loaded_sheets <- try(readxl::excel_sheets(tmp_file), silent = TRUE) + + if (!inherits(loaded_sheets, "try-error") && length(loaded_sheets) > 0) { - showNotification(paste("数据集", ds_name, "加载成功!"), type = "message") + # 必须和 load_user_data 里的逻辑一致,使用 make.names 清洗 + valid_names <- make.names(loaded_sheets) + first_sheet_name <- valid_names[1] # 默认选第一个 + + # 标记为已加载 + session$userData$loaded_datasets <- c(session$userData$loaded_datasets, dataset_id) + + # 更新下拉框并选中第一个 Sheet + updateSelectInput(session, "dataset", selected = first_sheet_name) + updateTabsetPanel(session, "nav_radiant", selected = "Data") + updateTabsetPanel(session, "tabs_data", selected = "View") + + showNotification(paste0("成功加载 ", length(valid_names), " 个数据集 (ID: ", dataset_id, ")"), type = "message") + } else { + showNotification("文件下载成功,但未发现有效的 Sheet", type = "warning") + } }, error = function(e) { - showNotification(paste("数据同步失败,请手动导入数据:", e$message), type = "error", duration = 10) - #调试打印 print(e) + showNotification(paste("数据同步失败:", e$message), type = "error", duration = 10) }) }) } diff --git a/radiant.data/inst/app/tools/data/manage.R b/radiant.data/inst/app/tools/data/manage.R index 583b24a..3cfea63 100644 --- a/radiant.data/inst/app/tools/data/manage.R +++ b/radiant.data/inst/app/tools/data/manage.R @@ -72,13 +72,13 @@ load_user_data <- function(fname, uFile, ext, header = TRUE, fext %in% c("xls", "xlsx") ~ "xlsx", TRUE ~ ext ) - + ## objname is used as the name of the data.frame, make case insensitive objname <- sub(glue("\\.{ext}$"), "", filename, ignore.case = TRUE) ## if ext isn't in the filename nothing was replaced and so ... - if (objname == filename && !fext %in% c("xls", "xlsx")) { + if (objname == filename && !fext %in% c("xls", "xlsx")) { ret <- glue(i18n$t('#### The filename extension "{fext}" does not match the specified \\ file-type "{ext}". Please specify the file type you are trying to upload')) upload_error_handler(objname, ret) @@ -142,35 +142,77 @@ load_user_data <- function(fname, uFile, ext, header = TRUE, } }else if (ext == "xlsx") { if (!requireNamespace("readxl", quietly = TRUE)) { - ret <- i18n$t("#### 读取xlsx文件需要readxl包") - upload_error_handler(objname, ret) - } else { - # 用readxl读取xlsx - robj <- try(readxl::read_excel( + upload_error_handler(objname, i18n$t("#### 需要安装 readxl 包")) + return() + } + + # 1. 获取所有 Sheet + all_sheets <- try(readxl::excel_sheets(path = uFile), silent = TRUE) + + if (inherits(all_sheets, "try-error")) { + upload_error_handler(objname, i18n$t("#### 无法解析 Excel 文件结构")) + return() + } + + loaded_count <- 0 + + # 2. 循环处理 + for (sheet_name in all_sheets) { + + # 2.1 准备合法的变量名 + clean_name <- make.names(sheet_name) + if (clean_name == "") clean_name <- paste0("Sheet_", sample(1000, 1)) + + # 2.2 读取数据 + raw_data <- try(readxl::read_excel( path = uFile, - sheet = xlsx_sheet, # 对应UI的“工作表索引” - col_names = xlsx_header# 对应UI的“第一行为表头” + sheet = sheet_name, + col_names = xlsx_header ), silent = TRUE) - - if (inherits(robj, "try-error")) { - upload_error_handler(objname, i18n$t("#### 读取xlsx文件失败,请检查文件是否损坏或格式正确")) - } else { - # 转换为data.frame并处理因子 - r_data[[objname]] <- as.data.frame(robj, stringsAsFactors = FALSE) %>% - {if (man_str_as_factor) radiant.data::to_fct(.) else .} - # 生成R代码 - cmd <- glue(' - {objname} <- readxl::read_excel( - {pp$rpath}, - sheet = {xlsx_sheet}, - col_names = {xlsx_header} - ) %>% - as.data.frame(stringsAsFactors = FALSE) - {if (man_str_as_factor) paste0(objname, " <- radiant.data::to_fct(", objname, ")") else ""} - register("{objname}") + + # 2.3 卫语句:跳过无效数据(空表或读取错误) + if (inherits(raw_data, "try-error") || !is.data.frame(raw_data) || ncol(raw_data) == 0) { + next + } + + # 2.4 [关键] 立即执行 Radiant 标准数据清洗流程 + # 必须在循环内做,因为我们不会走到函数底部的通用处理逻辑了 + try({ + # 清洗列名 + colnames(raw_data) <- radiant.data::fix_names(colnames(raw_data)) + # 转换因子 + if (man_str_as_factor) raw_data <- radiant.data::to_fct(raw_data) + }, silent = TRUE) + + # 2.5 入库 + r_data[[clean_name]] <- as.data.frame(raw_data, stringsAsFactors = FALSE) + + # 2.6 生成并注册复现代码 (Reproducibility) + # 注意:这里直接注册 register,不需要 cmd 变量在外部累加 + curr_cmd <- glue::glue(' + {clean_name} <- readxl::read_excel("{uFile}", sheet = "{sheet_name}", col_names = {xlsx_header}) %>% + as.data.frame(stringsAsFactors = FALSE) %>% + fix_names() + {if (man_str_as_factor) paste0(clean_name, " <- radiant.data::to_fct(", clean_name, ")") else ""} + register("{clean_name}") ') + + # 2.7 更新系统元数据 (Mimic bottom logic) + if (exists(clean_name, envir = r_data) && !bindingIsActive(as.symbol(clean_name), env = r_data)) { + shiny::makeReactiveBinding(clean_name, env = r_data) } - } + r_info[[glue("{clean_name}_lcmd")]] <- curr_cmd + r_info[["datasetlist"]] <- unique(c(clean_name, r_info[["datasetlist"]])) + + loaded_count <- loaded_count + 1 + } + + if (loaded_count == 0) { + upload_error_handler(objname, i18n$t("#### 文件中未发现有效的表格数据")) + } + + # 直接返回 + return() }else if (ext %in% c("tsv", "csv", "txt")) { r_data[[objname]] <- load_csv( uFile, @@ -197,6 +239,11 @@ load_user_data <- function(fname, uFile, ext, header = TRUE, upload_error_handler(objname, ret) } + # =========================================================================== + # 下面的代码只为单数据集流程(CSV, RDS, parquet)服务 + # XLSX 流程已经在上面 return() 了,不会执行到这里 + # =========================================================================== + if (exists(objname, envir = r_data) && !bindingIsActive(as.symbol(objname), env = r_data)) { shiny::makeReactiveBinding(objname, env = r_data) } -- 2.22.0