diff --git a/core/core-backend/src/main/java/io/dataease/chart/charts/impl/DefaultChartHandler.java b/core/core-backend/src/main/java/io/dataease/chart/charts/impl/DefaultChartHandler.java index 4ffe32000f..b5cf59acfa 100644 --- a/core/core-backend/src/main/java/io/dataease/chart/charts/impl/DefaultChartHandler.java +++ b/core/core-backend/src/main/java/io/dataease/chart/charts/impl/DefaultChartHandler.java @@ -154,6 +154,10 @@ public class DefaultChartHandler extends AbstractChartPlugin { if (view.getIsExcelExport()) { Map sourceInfo = ChartDataBuild.transTableNormal(xAxis, yAxis, view, calcResult.getOriginData(), extStack, desensitizationList); sourceInfo.put("sourceData", calcResult.getOriginData()); + // 将汇总计算结果传递到导出数据中 + if (calcResult.getData() != null && calcResult.getData().get("customSumResult") != null) { + sourceInfo.put("customSumResult", calcResult.getData().get("customSumResult")); + } view.setData(sourceInfo); return view; } diff --git a/core/core-backend/src/main/java/io/dataease/chart/server/ChartDataServer.java b/core/core-backend/src/main/java/io/dataease/chart/server/ChartDataServer.java index e56c8e9687..55e4ed7783 100644 --- a/core/core-backend/src/main/java/io/dataease/chart/server/ChartDataServer.java +++ b/core/core-backend/src/main/java/io/dataease/chart/server/ChartDataServer.java @@ -264,13 +264,38 @@ public class ChartDataServer implements ChartDataApi { List details = new ArrayList<>(); Sheet detailsSheet; Integer sheetIndex = 1; + + boolean summaryEnabled = !"dataset".equals(request.getDownloadType()) && isSummaryEnabled(request.getViewInfo()); + SummaryConfig summaryConfig = null; + SummaryAccumulator summaryAcc = null; + List allExportColumns = null; + Map customSumResult = null; + if (summaryEnabled) { + summaryConfig = parseSummaryConfig(request.getViewInfo()); + summaryAcc = new SummaryAccumulator(); + allExportColumns = getAllExportColumns(request.getViewInfo()); + } + request.getViewInfo().getChartExtRequest().setPageSize(Long.valueOf(extractPageSize)); ChartViewDTO chartViewDTO = findExcelData(request); for (long i = 1; i < chartViewDTO.getTotalPage() + 1; i++) { request.getViewInfo().getChartExtRequest().setGoPage(i); - findExcelData(request); + ChartViewDTO pageDto = findExcelData(request); details.addAll(request.getDetails()); + + if (summaryEnabled) { + accumulatePageStats(summaryAcc, request.getDetails(), allExportColumns, summaryConfig); + if (i == chartViewDTO.getTotalPage() && pageDto.getData() != null && pageDto.getData().get("customSumResult") != null) { + customSumResult = (Map) pageDto.getData().get("customSumResult"); + } + } + if ((details.size() + extractPageSize) > sheetLimit || i == chartViewDTO.getTotalPage()) { + if (i == chartViewDTO.getTotalPage() && summaryEnabled && summaryAcc.totalCount > 0) { + Object[] totalRow = buildSummaryRow(allExportColumns, summaryConfig, summaryAcc, customSumResult); + details.add(totalRow); + } + detailsSheet = wb.createSheet("数据" + sheetIndex); Integer[] excelTypes = request.getExcelTypes(); List xAxis = new ArrayList<>(); @@ -865,5 +890,182 @@ public class ChartDataServer implements ChartDataApi { public void exportScreenViewLog(Long id) { } + public static boolean isSummaryEnabled(ChartViewDTO viewInfo) { + if (viewInfo == null || viewInfo.getCustomAttr() == null) return false; + String type = viewInfo.getType(); + if (!StringUtils.equalsAnyIgnoreCase(type, "table-info", "table-normal")) return false; + Map basicStyle = (Map) viewInfo.getCustomAttr().get("basicStyle"); + if (basicStyle == null) return false; + return basicStyle.get("showSummary") != null && (Boolean) basicStyle.get("showSummary"); + } + + public static SummaryConfig parseSummaryConfig(ChartViewDTO viewInfo) { + SummaryConfig config = new SummaryConfig(); + Map basicStyle = (Map) viewInfo.getCustomAttr().get("basicStyle"); + config.summaryLabel = (basicStyle.get("summaryLabel") != null && StringUtils.isNotBlank(basicStyle.get("summaryLabel").toString())) + ? basicStyle.get("summaryLabel").toString() + : (Lang.isChinese() ? "总计" : "Total"); + + List> seriesSummary = basicStyle.get("seriesSummary") != null + ? (List>) basicStyle.get("seriesSummary") : null; + + List summaryFields; + if (viewInfo.getType().equalsIgnoreCase("table-info")) { + summaryFields = viewInfo.getXAxis(); + } else { + summaryFields = new ArrayList<>(); + summaryFields.addAll(viewInfo.getXAxis()); + summaryFields.addAll(viewInfo.getYAxis()); + } + + for (ChartViewFieldDTO field : summaryFields) { + String fName = field.getDataeaseName(); + String sType = "sum"; + boolean sShow = true; + if (seriesSummary != null) { + for (Map s : seriesSummary) { + if (fName.equals(s.get("field"))) { + sType = s.get("summary") == null ? "sum" : s.get("summary").toString(); + sShow = s.get("show") == null || (Boolean) s.get("show"); + break; + } + } + } + config.summaryTypeMap.put(fName, sType); + config.summaryShowMap.put(fName, sShow); + } + return config; + } + + public static void accumulatePageStats(SummaryAccumulator acc, List pageDetails, + List allColumns, SummaryConfig config) { + if (pageDetails == null) return; + for (Object[] row : pageDetails) { + acc.totalCount++; + for (int j = 0; j < allColumns.size() && j < row.length; j++) { + ChartViewFieldDTO field = allColumns.get(j); + String fName = field.getDataeaseName(); + if (!config.summaryShowMap.containsKey(fName) || !config.summaryShowMap.get(fName)) continue; + String sType = config.summaryTypeMap.get(fName); + if (sType == null || "custom".equals(sType)) continue; + Object valObj = row[j]; + if (valObj == null || StringUtils.isBlank(valObj.toString())) continue; + try { + BigDecimal val = new BigDecimal(valObj.toString()); + switch (sType) { + case "max": + BigDecimal curMax = acc.maxMap.get(fName); + if (curMax == null || val.compareTo(curMax) > 0) acc.maxMap.put(fName, val); + break; + case "min": + BigDecimal curMin = acc.minMap.get(fName); + if (curMin == null || val.compareTo(curMin) < 0) acc.minMap.put(fName, val); + break; + default: + acc.sumMap.merge(fName, val, BigDecimal::add); + acc.countMap.merge(fName, 1L, Long::sum); + if ("var_pop".equals(sType) || "stddev_pop".equals(sType)) { + acc.sumOfSquaresMap.merge(fName, val.multiply(val), BigDecimal::add); + } + break; + } + } catch (Exception ignored) { + } + } + } + } + + @SuppressWarnings("unchecked") + public static Object[] buildSummaryRow(List allColumns, SummaryConfig config, + SummaryAccumulator acc, Map customSumResult) { + Object[] totalRow = new Object[allColumns.size()]; + boolean labelSet = false; + for (int j = 0; j < allColumns.size(); j++) { + ChartViewFieldDTO field = allColumns.get(j); + String fName = field.getDataeaseName(); + if (config.summaryShowMap.containsKey(fName) && config.summaryShowMap.get(fName)) { + String sType = config.summaryTypeMap.get(fName); + switch (sType) { + case "custom": + totalRow[j] = customSumResult != null && customSumResult.get(fName) != null + ? customSumResult.get(fName).toPlainString() : null; + break; + case "max": + totalRow[j] = acc.maxMap.get(fName) != null ? acc.maxMap.get(fName).toPlainString() : null; + break; + case "min": + totalRow[j] = acc.minMap.get(fName) != null ? acc.minMap.get(fName).toPlainString() : null; + break; + case "avg": + BigDecimal sum = acc.sumMap.get(fName); + Long cnt = acc.countMap.get(fName); + if (sum != null && cnt != null && cnt > 0) { + totalRow[j] = sum.divide(BigDecimal.valueOf(cnt), 8, java.math.RoundingMode.HALF_UP).toPlainString(); + } + break; + case "sum": + totalRow[j] = acc.sumMap.get(fName) != null ? acc.sumMap.get(fName).toPlainString() : null; + break; + case "var_pop": + totalRow[j] = calcVariance(acc, fName, false); + break; + case "stddev_pop": + totalRow[j] = calcVariance(acc, fName, true); + break; + default: + break; + } + } else if (!labelSet) { + totalRow[j] = config.summaryLabel; + labelSet = true; + } + } + if (!labelSet && totalRow.length > 0) { + totalRow[0] = config.summaryLabel; + } + return totalRow; + } + + private static String calcVariance(SummaryAccumulator acc, String fName, boolean isSqrt) { + Long cnt = acc.countMap.get(fName); + BigDecimal sum = acc.sumMap.get(fName); + BigDecimal sumSq = acc.sumOfSquaresMap.get(fName); + if (cnt == null || cnt < 2 || sum == null || sumSq == null) return null; + BigDecimal mean = sum.divide(BigDecimal.valueOf(cnt), 16, java.math.RoundingMode.HALF_UP); + BigDecimal variance = sumSq.divide(BigDecimal.valueOf(cnt), 16, java.math.RoundingMode.HALF_UP) + .subtract(mean.multiply(mean)); + BigDecimal sampleVariance = variance.multiply(BigDecimal.valueOf(cnt)) + .divide(BigDecimal.valueOf(cnt - 1), 8, java.math.RoundingMode.HALF_UP); + if (isSqrt) { + return BigDecimal.valueOf(Math.sqrt(sampleVariance.doubleValue())) + .setScale(8, java.math.RoundingMode.HALF_UP).toPlainString(); + } + return sampleVariance.toPlainString(); + } + + public static List getAllExportColumns(ChartViewDTO viewInfo) { + List allColumns = new ArrayList<>(); + allColumns.addAll(viewInfo.getXAxis()); + allColumns.addAll(viewInfo.getYAxis()); + allColumns.addAll(viewInfo.getXAxisExt()); + allColumns.addAll(viewInfo.getYAxisExt()); + allColumns.addAll(viewInfo.getExtStack()); + return allColumns; + } + + public static class SummaryConfig { + public String summaryLabel; + public Map summaryTypeMap = new HashMap<>(); + public Map summaryShowMap = new HashMap<>(); + } + + public static class SummaryAccumulator { + public long totalCount = 0; + public Map sumMap = new HashMap<>(); + public Map maxMap = new HashMap<>(); + public Map minMap = new HashMap<>(); + public Map countMap = new HashMap<>(); + public Map sumOfSquaresMap = new HashMap<>(); + } } diff --git a/core/core-backend/src/main/java/io/dataease/exportCenter/manage/ExportCenterDownLoadManage.java b/core/core-backend/src/main/java/io/dataease/exportCenter/manage/ExportCenterDownLoadManage.java index 57836ad830..4532b0aeb5 100644 --- a/core/core-backend/src/main/java/io/dataease/exportCenter/manage/ExportCenterDownLoadManage.java +++ b/core/core-backend/src/main/java/io/dataease/exportCenter/manage/ExportCenterDownLoadManage.java @@ -484,14 +484,38 @@ public class ExportCenterDownLoadManage { Sheet detailsSheet; Integer sheetIndex = 1; if ("dataset".equals(request.getDownloadType()) || request.getViewInfo().getType().equalsIgnoreCase("table-info") || request.getViewInfo().getType().equalsIgnoreCase("table-normal")) { + boolean summaryEnabled = !"dataset".equals(request.getDownloadType()) && ChartDataServer.isSummaryEnabled(request.getViewInfo()); + ChartDataServer.SummaryConfig summaryConfig = null; + ChartDataServer.SummaryAccumulator summaryAcc = null; + List allExportColumns = null; + Map customSumResult = null; + if (summaryEnabled) { + summaryConfig = ChartDataServer.parseSummaryConfig(request.getViewInfo()); + summaryAcc = new ChartDataServer.SummaryAccumulator(); + allExportColumns = ChartDataServer.getAllExportColumns(request.getViewInfo()); + } + request.getViewInfo().getChartExtRequest().setPageSize(Long.valueOf(extractPageSize)); ChartViewDTO chartViewDTO = chartDataServer.findExcelData(request); for (long i = 1; i < chartViewDTO.getTotalPage() + 1; i++) { request.getViewInfo().getChartExtRequest().setGoPage(i); request.getViewInfo().setExtStack(request.getViewInfo().getExtStack().stream().filter(ele -> !ele.isHide()).collect(Collectors.toList())); - chartDataServer.findExcelData(request); + ChartViewDTO pageDto = chartDataServer.findExcelData(request); details.addAll(request.getDetails()); + + if (summaryEnabled) { + ChartDataServer.accumulatePageStats(summaryAcc, request.getDetails(), allExportColumns, summaryConfig); + if (i == chartViewDTO.getTotalPage() && pageDto.getData() != null && pageDto.getData().get("customSumResult") != null) { + customSumResult = (Map) pageDto.getData().get("customSumResult"); + } + } + if (((details.size() + extractPageSize) > sheetLimit) || i == chartViewDTO.getTotalPage()) { + if (i == chartViewDTO.getTotalPage() && summaryEnabled && summaryAcc.totalCount > 0) { + Object[] totalRow = ChartDataServer.buildSummaryRow(allExportColumns, summaryConfig, summaryAcc, customSumResult); + details.add(totalRow); + } + detailsSheet = wb.createSheet("数据" + sheetIndex); Integer[] excelTypes = request.getExcelTypes(); ViewDetailField[] detailFields = request.getDetailFields(); diff --git a/core/core-backend/src/main/java/io/dataease/visualization/manage/CoreVisualizationExportManage.java b/core/core-backend/src/main/java/io/dataease/visualization/manage/CoreVisualizationExportManage.java index f36f480b08..1da2e2591c 100644 --- a/core/core-backend/src/main/java/io/dataease/visualization/manage/CoreVisualizationExportManage.java +++ b/core/core-backend/src/main/java/io/dataease/visualization/manage/CoreVisualizationExportManage.java @@ -10,6 +10,7 @@ import io.dataease.constant.DeTypeConstants; import io.dataease.dataset.server.DatasetFieldServer; import io.dataease.exception.DEException; import io.dataease.exportCenter.util.ExportCenterUtils; +import io.dataease.chart.server.ChartDataServer; import io.dataease.extensions.view.dto.ChartExtFilterDTO; import io.dataease.extensions.view.dto.ChartExtRequest; import io.dataease.extensions.view.dto.ChartViewDTO; @@ -27,6 +28,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import java.io.File; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -174,6 +176,7 @@ public class CoreVisualizationExportManage { boolean rightExist = ObjectUtils.isNotEmpty(chart.get("right")); if (!leftExist && !rightExist) { ExcelSheetModel sheetModel = exportSingleData(chart, title); + appendSummaryToSheet(sheetModel, chartViewDTO, chart); resultList.add(sheetModel); return resultList; } @@ -196,6 +199,75 @@ public class CoreVisualizationExportManage { return sourceNumberStr; } + @SuppressWarnings("unchecked") + private void appendSummaryToSheet(ExcelSheetModel sheetModel, ChartViewDTO chartViewDTO, Map chart) { + if (!ChartDataServer.isSummaryEnabled(chartViewDTO)) return; + ChartDataServer.SummaryConfig config = ChartDataServer.parseSummaryConfig(chartViewDTO); + List allColumns = ChartDataServer.getAllExportColumns(chartViewDTO); + ChartDataServer.SummaryAccumulator acc = new ChartDataServer.SummaryAccumulator(); + + Object objectTableRow = chart.get("tableRow"); + if (objectTableRow == null) objectTableRow = chart.get("sourceData"); + if (objectTableRow == null) return; + + List> tableRow = (List>) objectTableRow; + for (Map row : tableRow) { + acc.totalCount++; + for (int j = 0; j < allColumns.size(); j++) { + ChartViewFieldDTO field = allColumns.get(j); + String fName = field.getDataeaseName(); + if (!config.summaryShowMap.containsKey(fName) || !config.summaryShowMap.get(fName)) continue; + String sType = config.summaryTypeMap.get(fName); + if (sType == null || "custom".equals(sType)) continue; + Object valObj = row.get(fName); + if (valObj == null || StringUtils.isBlank(valObj.toString())) continue; + try { + BigDecimal val = new BigDecimal(valObj.toString()); + switch (sType) { + case "max": + BigDecimal curMax = acc.maxMap.get(fName); + if (curMax == null || val.compareTo(curMax) > 0) acc.maxMap.put(fName, val); + break; + case "min": + BigDecimal curMin = acc.minMap.get(fName); + if (curMin == null || val.compareTo(curMin) < 0) acc.minMap.put(fName, val); + break; + default: + acc.sumMap.merge(fName, val, BigDecimal::add); + acc.countMap.merge(fName, 1L, Long::sum); + if ("var_pop".equals(sType) || "stddev_pop".equals(sType)) { + acc.sumOfSquaresMap.merge(fName, val.multiply(val), BigDecimal::add); + } + break; + } + } catch (Exception ignored) { + } + } + } + + if (acc.totalCount == 0) return; + + Map customSumResult = chart.get("customSumResult") != null + ? (Map) chart.get("customSumResult") : null; + + Object[] totalRowArr = ChartDataServer.buildSummaryRow(allColumns, config, acc, customSumResult); + + List headKeys = new ArrayList<>(); + for (ChartViewFieldDTO field : allColumns) { + headKeys.add(field.getDataeaseName()); + } + + List summaryRow = new ArrayList<>(); + for (int i = 0; i < headKeys.size(); i++) { + if (i < totalRowArr.length && totalRowArr[i] != null) { + summaryRow.add(totalRowArr[i].toString()); + } else { + summaryRow.add(StringUtils.EMPTY); + } + } + sheetModel.getData().add(summaryRow); + } + private final TypeReference>> tokenType = new TypeReference>>() { };