Pārlūkot izejas kodu

[task-6188] 质量指标看板功能重构

huangws 13 stundas atpakaļ
vecāks
revīzija
e15832485f

+ 21 - 0
ibps-provider-root/modules/provider-business/src/main/java/com/lc/ibps/business/controller/StatisticController.java

@@ -5,6 +5,7 @@ import com.lc.ibps.base.core.util.I18nUtil;
 import com.lc.ibps.business.dto.EquipmentDashBoardDTO;
 import com.lc.ibps.business.dto.LabsDashBoardDTO;
 import com.lc.ibps.business.dto.TrainingDashBoardDTO;
+import com.lc.ibps.business.service.QualityIndicatorService;
 import com.lc.ibps.business.service.StatisticService;
 import com.lc.ibps.cloud.entity.APIResult;
 import com.lc.ibps.cloud.provider.GenericProvider;
@@ -14,6 +15,7 @@ import org.hibernate.validator.constraints.NotBlank;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -25,6 +27,9 @@ public class StatisticController extends GenericProvider {
     @Autowired
     StatisticService statisticService;
 
+    @Autowired
+    QualityIndicatorService qualityIndicatorService;
+
     @ApiOperation("获取风险控制报表")
     @GetMapping("/risk")
     APIResult<List<Map<String, Object>>> getRiskReport(@NotBlank(message = "{com.lc.ibps.cloud.file.attachmentId}")
@@ -86,4 +91,20 @@ public class StatisticController extends GenericProvider {
         }
         return result;
     }
+
+    @ApiOperation("获取指标指标看板")
+    @GetMapping("/qualityIndicatorDashBoard")
+    APIResult<List<Map<String, Object>>> getQualityIndicatorDashBoard(@NotBlank(message = "请求类型不能为空")
+                                                                        @RequestParam(name = "requestType", required = false) String requestType,
+                                                                        @RequestParam(name = "targetName", required = false) String targetName,
+                                                                        @RequestParam(name = "dateRange", required = true) String dateRange) {
+        APIResult<List<Map<String, Object>>> result =  new APIResult<>();
+        try {
+            List<Map<String, Object>> QIDashBoard = qualityIndicatorService.getQualityIndicator(requestType, targetName, dateRange);
+            result.setData(QIDashBoard);
+        } catch (Exception e) {
+            setExceptionResult(result, StateEnum.ILLEGAL_REQUEST.getCode(), I18nUtil.getMessage(StateEnum.ILLEGAL_REQUEST.getCode() + ""), e);
+        }
+        return result;
+    }
 }

+ 10 - 0
ibps-provider-root/modules/provider-business/src/main/java/com/lc/ibps/business/service/QualityIndicatorService.java

@@ -0,0 +1,10 @@
+package com.lc.ibps.business.service;
+
+import java.util.List;
+import java.util.Map;
+
+public interface QualityIndicatorService {
+    List<Map<String, Object>> getQualityIndicator(String requestType, String targetName, String dateRange);
+
+//    List<Map<String, Object>> getQualityIndicator2(String requestType, String targetName, String dateRange);
+}

+ 520 - 0
ibps-provider-root/modules/provider-business/src/main/java/com/lc/ibps/business/service/impl/QualityIndicatorImpl.java

@@ -0,0 +1,520 @@
+package com.lc.ibps.business.service.impl;
+
+import com.lc.ibps.base.framework.table.ICommonDao;
+import com.lc.ibps.business.service.QualityIndicatorService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import org.apache.commons.lang3.StringUtils;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.YearMonth;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static com.lc.ibps.base.framework.repository.IRepository.logger;
+
+@Service
+public class QualityIndicatorImpl implements QualityIndicatorService {
+
+    @Resource
+    private ICommonDao<?> commonDao;
+
+
+    public List<Map<String, Object>> getQualityIndicatorold(String requestType, String targetName, String dateRange) {
+        List<Map<String, Object>> resultList = new ArrayList<>();
+
+        // 准备公共的 X 轴数据和 Y 轴数值
+        List<String> xAxisData = Arrays.asList("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun");
+        List<Integer> dataValues = Arrays.asList(120, 200, 150, 80, 70, 110, 130);
+
+        // --- 构建第一个图表的数据对象 ---
+        Map<String, Object> chartData1 = new LinkedHashMap<>();
+        chartData1.put("title", "测试专用1");
+        chartData1.put("xAxis", xAxisData);
+
+        // 封装 series 数据系列
+        List<Map<String, Object>> seriesList1 = new ArrayList<>();
+        Map<String, Object> seriesItem1 = new LinkedHashMap<>();
+        seriesItem1.put("name", "指标数据"); // 系列名称,方便前端图例展示
+        seriesItem1.put("type", "bar");      // 图表类型
+        seriesItem1.put("data", dataValues); // 具体的数值
+        seriesList1.add(seriesItem1);
+
+        chartData1.put("series", seriesList1);
+
+
+        // --- 构建第二个图表的数据对象 ---
+        Map<String, Object> chartData2 = new LinkedHashMap<>();
+        chartData2.put("title", "测试专用2");
+        chartData2.put("xAxis", xAxisData);
+
+        // 封装 series 数据系列
+        List<Map<String, Object>> seriesList2 = new ArrayList<>();
+        Map<String, Object> seriesItem2 = new LinkedHashMap<>();
+        seriesItem2.put("name", "指标数据");
+        seriesItem2.put("type", "bar");
+        seriesItem2.put("data", dataValues);
+        seriesList2.add(seriesItem2);
+
+        chartData2.put("series", seriesList2);
+
+        // 将两个图表的数据对象放入最终的返回数组中
+        resultList.add(chartData1);
+        resultList.add(chartData2);
+
+        return resultList;
+    }
+
+    /**
+     * 获取质量指标/目标数据
+     *
+     * @param requestType 请求类型 ("mubiao" 或 "zhibiao")
+     * @param targetName  目标名称
+     * @param dateRange   时间范围 (格式: "yyyyMM-yyyyMM",如 "202511-202604")
+     * @return 前端图表所需的数据结构
+     */
+    @Override
+    public List<Map<String, Object>> getQualityIndicator(String requestType, String targetName, String dateRange) {
+        // 1. 入参校验
+        if (StringUtils.isBlank(requestType) || (!"mubiao".equals(requestType) && !"zhibiao".equals(requestType))) {
+            return new ArrayList<>();
+        }
+        // 校验时间格式 yyyyMM-yyyyMM
+        if (StringUtils.isNotBlank(dateRange) && !dateRange.matches("\\d{6}-\\d{6}")) {
+            return new ArrayList<>();
+        }
+
+        // 2. 获取原始数据
+        List<Map<String, Object>> orgData = getOrgData();
+        if (orgData == null || orgData.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 解析时间范围
+        YearMonth startYM = null;
+        YearMonth endYM = null;
+        if (StringUtils.isNotBlank(dateRange)) {
+            startYM = YearMonth.parse(dateRange.substring(0, 6), DateTimeFormatter.ofPattern("yyyyMM"));
+            endYM = YearMonth.parse(dateRange.substring(7), DateTimeFormatter.ofPattern("yyyyMM"));
+        }
+
+        // 3. 初步过滤数据
+        List<Map<String, Object>> filteredData = new ArrayList<>();
+        List<Map<String, Object>> filteredOldData = new ArrayList<>();
+        String finalTargetName = StringUtils.isNotBlank(targetName) ? targetName.trim() : null;
+
+        for (Map<String, Object> row : orgData) {
+            String zhiBiao = (String) row.get("zhi_liang_zhi_bia");
+            String muBiao = (String) row.get("zhi_liang_mu_biao");
+            String bianZhiShiJian = (String) row.get("bian_zhi_shi_jian");
+
+            // 场景一:查质量目标 (zhi_liang_zhi_bia 为空)
+            if ("mubiao".equals(requestType)) {
+                if (StringUtils.isBlank(zhiBiao)) {
+                    filteredData.add(row);
+                }
+            }
+            // 场景二:查质量指标 (zhi_liang_zhi_bia 不为空)
+            else if ("zhibiao".equals(requestType)) {
+                if (StringUtils.isNotBlank(zhiBiao)) {
+                    // 校验目标名
+                    if (finalTargetName != null && !finalTargetName.equals(muBiao)) {
+                        continue;
+                    }
+                    filteredOldData.add(row);
+                    // 校验时间范围
+                    if (startYM != null && endYM != null) {
+                        if (!isTimeInRange(bianZhiShiJian, startYM, endYM)) {
+                            continue;
+                        }
+                    }
+                    filteredData.add(row);
+                }
+            }
+        }
+
+        // 4. 数据分组与排序 (按 名称 + 频率权重 分组)
+        Map<String, Integer> freqWeightMap = new HashMap<>();
+        freqWeightMap.put("每月", 1);
+        freqWeightMap.put("每季度", 2);
+        freqWeightMap.put("每年", 3);
+
+        // 分组 Key: 名称_频率权重
+        Map<String, List<Map<String, Object>>> groupedData = filteredData.stream()
+                .collect(Collectors.groupingBy(row -> {
+                    String name = "mubiao".equals(requestType)
+                            ? (String) row.get("zhi_liang_mu_biao")
+                            : (String) row.get("zhi_liang_zhi_bia");
+                    String freq = (String) row.get("tong_ji_pin_lv_");
+                    int weight = freqWeightMap.getOrDefault(freq, 99);
+                    return name + "_" + weight;
+                }));
+
+        // 5. 构建 Echarts 数据结构
+        List<Map<String, Object>> resultList = new ArrayList<>();
+
+        // 对分组后的 Key 进行排序,保证输出顺序
+        List<String> sortedKeys = groupedData.keySet().stream().sorted().collect(Collectors.toList());
+
+        for (String key : sortedKeys) {
+            List<Map<String, Object>> groupList = groupedData.get(key);
+            if (groupList.isEmpty()) continue;
+
+            // 提取该组的公共信息
+            String title = "mubiao".equals(requestType)
+                    ? (String) groupList.get(0).get("zhi_liang_mu_biao")
+                    : (String) groupList.get(0).get("zhi_liang_zhi_bia");
+            String subtext = "限值:"+groupList.get(groupList.size() - 1).get("yuan_shi_shu_ju_").toString().replace("@","");
+            String freqType = (String) groupList.get(0).get("tong_ji_pin_lv_");
+            String chartType = "mubiao".equals(requestType) ? "bar" : "line";
+
+            // 按时间先后排序组内数据
+            groupList.sort((a, b) -> compareTimeStr((String) a.get("bian_zhi_shi_jian"), (String) b.get("bian_zhi_shi_jian")));
+
+            // 提取 X轴, Y轴(实际值), MarkLine(目标值)
+            List<String> xAxis = new ArrayList<>();
+            List<Number> yAxisData = new ArrayList<>();
+            List<Number> markLineData = new ArrayList<>();
+
+            for (Map<String, Object> row : groupList) {
+                xAxis.add(formatXAxisLabel((String) row.get("bian_zhi_shi_jian")));
+                yAxisData.add(parseNumber(row.get("shi_ji_shu_zhi_")));
+//                Number val = (Number) parseNumber(row.get("shi_ji_shu_zhi_"));
+//                if (val != null) {
+//                    yAxisData.add(val);
+//                }
+                markLineData.add(parseNumber(row.get("zhi_biao_xian_zhi")));
+//                Number val2 = (Number) parseNumber(row.get("zhi_biao_xian_zhi"));
+//                if (val2 != null) {
+//                    markLineData.add(val2);
+//                }
+            }
+
+            // 6. 计算上年均值 (仅月度、季度,且第一个时间为年初)
+            if (("每月".equals(freqType) || "每季度".equals(freqType)) && !xAxis.isEmpty()) {
+                String firstTimeStr = (String) groupList.get(0).get("bian_zhi_shi_jian");
+                int currentYear = extractYear(firstTimeStr);
+                boolean isFirstPeriod = false;
+
+                if ("每月".equals(freqType) && firstTimeStr.contains("1月份")) isFirstPeriod = true;
+                if ("每季度".equals(freqType) && firstTimeStr.contains("第1季度")) isFirstPeriod = true;
+
+                if (isFirstPeriod) {
+                    // 查找上一年同频率的所有数据
+                    List<Number> lastYearValues = new ArrayList<>();
+                    for (Map<String, Object> row : filteredOldData) {
+                        String name = (String) row.get("zhi_liang_zhi_bia");
+                        String rowFreq = (String) row.get("tong_ji_pin_lv_");
+                        String rowTime = (String) row.get("bian_zhi_shi_jian");
+
+                        if (name.equals(title) && rowFreq.equals(freqType) && extractYear(rowTime) == currentYear - 1) {
+                            lastYearValues.add(parseNumber(row.get("shi_ji_shu_zhi_")));
+                        }
+                    }
+                    if (!lastYearValues.isEmpty()) {
+                        double sum = lastYearValues.stream().mapToDouble(Number::doubleValue).sum();
+                        Number lastYearAvg = new BigDecimal(sum / lastYearValues.size())
+                                .setScale(4, RoundingMode.HALF_UP)
+                                .doubleValue();
+                        // 将上年均值插入到第一个位置
+                        yAxisData.add(0, lastYearAvg);
+                        markLineData.add(0, null);
+                        xAxis.add(0, (currentYear - 1) + "年均值");
+                    }
+                }
+            }
+
+            // 7. 计算该图表 Y 轴的最大最小值 (浮动20%取整)
+            double maxVal = yAxisData.stream().filter(Objects::nonNull).mapToDouble(Number::doubleValue).max().orElse(0);
+            double minVal = yAxisData.stream().filter(Objects::nonNull).mapToDouble(Number::doubleValue).min().orElse(0);
+
+            // 加入 markline 的值参与最大最小计算
+            double markMax = markLineData.stream().filter(Objects::nonNull).mapToDouble(Number::doubleValue).max().orElse(0);
+            double markMin = markLineData.stream().filter(Objects::nonNull).mapToDouble(Number::doubleValue).min().orElse(0);
+
+            maxVal = Math.max(maxVal, markMax);
+            minVal = Math.min(minVal, markMin);
+
+            double finalMax = (int) Math.ceil(maxVal * 1.2);
+            //最大值还是小数的被向上取整到1的取消取整
+            if(finalMax==1){
+                finalMax = maxVal* 1.2;
+            }
+            //最大值低于100附近的取100
+            if(maxVal <= 100 && maxVal >= 80){
+                finalMax = 100;
+            }
+            double finalMin = (int) Math.floor(minVal * 0.8);
+            //目标的柱状图最小值取0
+            if(maxVal <= 100 && maxVal >= 80 && "mubiao".equals(requestType)){
+                finalMin = 0;
+            }
+            // 使用 BigDecimal 进行四舍五入并保留两位小数
+            BigDecimal maxBd = BigDecimal.valueOf(finalMax).setScale(2, RoundingMode.HALF_UP);
+            BigDecimal minBd = BigDecimal.valueOf(finalMin).setScale(2, RoundingMode.HALF_UP);
+
+            // 转换回 double 类型
+            finalMax = maxBd.doubleValue();
+            finalMin = minBd.doubleValue();
+            // 柱状图横坐标不足5个,往前自动补齐
+            if ("bar".equals(chartType) && xAxis.size() < 5) {
+                int needFill = 5 - xAxis.size();
+                // 获取当前第一个横坐标的年度,如果取不到则默认为当前系统年份
+                int baseYear = xAxis.isEmpty() ? java.time.Year.now().getValue() : extractYearFromStringLabel(xAxis.get(0));
+
+                for (int i = 0; i < needFill; i++) {
+                    int fillYear = baseYear - (needFill - i); // 依次往前推年份
+                    xAxis.add(0, fillYear + "年度"); // 往前补年度标签
+                    yAxisData.add(0, null);         // 往前补空数据
+                    markLineData.add(0, null);      // 往前补空标线
+                }
+            }
+
+            // 8. 调用封装好的组装方法,生成单个图表 Map
+            Map<String, Object> chartData = buildEchartsChart(title, subtext, xAxis, yAxisData, markLineData, chartType, finalMin, finalMax);
+            resultList.add(chartData);
+        }
+
+        return resultList;
+    }
+
+    /**
+     * 抽取出来的 Echarts 图表组装方法
+     */
+    private Map<String, Object> buildEchartsChart(String title, String subtext, List<String> xAxis,
+                                                  List<Number> yAxisData, List<Number> markLineData,
+                                                  String chartType, double finalMin, double finalMax) {
+
+        // 封装 Series 和 MarkLine
+        List<Map<String, Object>> seriesList = new ArrayList<>();
+        Map<String, Object> seriesItem = new LinkedHashMap<>();
+        seriesItem.put("name", title);
+        // showSymbol: true,
+        //seriesItem.put("showSymbol", true);
+        seriesItem.put("type", chartType);
+        List<Object> formattedYAxisData = new ArrayList<>();
+        for (Number num : yAxisData) {
+            if (num == null) {
+                formattedYAxisData.add(null);
+            } else {
+                double val = num.doubleValue();
+
+                // 1. 先判断是否为极小数(保持原有逻辑)
+                // 使用 BigDecimal 设置最多 4 位小数,采用四舍五入模式
+
+                BigDecimal bd = new BigDecimal(val);
+                formattedYAxisData.add(bd.setScale(4, RoundingMode.HALF_UP).toPlainString());
+            }
+        }
+        // 将处理后的数据放入 series
+        seriesItem.put("data", formattedYAxisData);
+
+        // 封装 markLine (目标值)
+        List<List<Object>> markLineDataArray = new ArrayList<>();
+        for (int i = 0; i < markLineData.size(); i++) {
+            Number currentY = markLineData.get(i);
+            // 跳过 null 值
+            if (currentY == null) {
+                continue;
+            }
+
+            double yValue = currentY.doubleValue();
+            int startIndex = i; // 记录当前相同 Y 值的起始索引
+
+            // 向后查找,合并连续相同的 Y 值
+            while (i + 1 < markLineData.size() && markLineData.get(i + 1) != null
+                    && markLineData.get(i + 1).doubleValue() == yValue) {
+                i++;
+            }
+            // i 指向这一段相同 Y 值的最后一个点的索引
+
+            // 1. 生成横线
+            List<Object> hCoords = new ArrayList<>();
+
+            // 起点:段首
+            Map<String, Object> hStart = new LinkedHashMap<>();
+            hStart.put("coord", Arrays.asList(startIndex, yValue));
+            hCoords.add(hStart);
+
+            // 终点:段尾
+            Map<String, Object> hEnd = new LinkedHashMap<>();
+            hEnd.put("coord", Arrays.asList(i, yValue));
+            hCoords.add(hEnd);
+
+            markLineDataArray.add(hCoords);
+
+
+            // 2. 生成连接下一段的线
+            if (i + 1 < markLineData.size() && markLineData.get(i + 1) != null) {
+                double nextYValue = markLineData.get(i + 1).doubleValue();
+
+                // 只有当下一段的 Y 值与当前 Y 值不同时,才需要画线
+                if (nextYValue != yValue) {
+                    List<Object> vCoords = new ArrayList<>();
+
+                    // 起点:当前段的最后一个点 (i, yValue)
+                    Map<String, Object> vStart = new LinkedHashMap<>();
+                    vStart.put("coord", Arrays.asList(i, yValue));
+                    vCoords.add(vStart);
+
+                    // 终点:下一段的第一个点 (i + 1, nextYValue)
+                    Map<String, Object> vEnd = new LinkedHashMap<>();
+                    vEnd.put("coord", Arrays.asList(i + 1, nextYValue));
+                    vCoords.add(vEnd);
+
+                    markLineDataArray.add(vCoords);
+                }
+            }
+        }
+        Map<String, Object> lineStyle = new LinkedHashMap<>();
+        lineStyle.put("color", "red");
+        lineStyle.put("type", "dashed");
+        Map<String, Object> markLine = new LinkedHashMap<>();
+        markLine.put("data", markLineDataArray);
+        markLine.put("silent", true); // 标线不响应鼠标事件
+        markLine.put("symbol", "none");
+        markLine.put("lineStyle", lineStyle);
+        seriesItem.put("markLine", markLine);
+
+        seriesList.add(seriesItem);
+
+        // 组装最终图表对象
+        Map<String, Object> chartData = new LinkedHashMap<>();
+        chartData.put("title", title);
+        chartData.put("subtext", subtext);
+        chartData.put("xAxis", xAxis);
+        chartData.put("series", seriesList);
+
+        // 将最大最小值返回给前端 (仅折线图需要动态Y轴)
+        if ("line".equals(chartType)) {
+            Map<String, Object> yAxisConfig = new LinkedHashMap<>();
+            yAxisConfig.put("min", finalMin);
+            yAxisConfig.put("max", finalMax);
+            chartData.put("yAxisConfig", yAxisConfig);
+        }
+
+        return chartData;
+    }
+
+    // 判断时间是否在范围内
+    private boolean isTimeInRange(String bianZhiShiJian, YearMonth startYM, YearMonth endYM) {
+        if (StringUtils.isBlank(bianZhiShiJian)) return false;
+
+        int year = extractYear(bianZhiShiJian);
+
+        // 年度判断:只要年份在范围内即可
+        if (bianZhiShiJian.contains("年度")) {
+            return year >= startYM.getYear() && year <= endYM.getYear();
+        }
+
+        // 季度判断:解析出季度,看该季度的任意月份是否与范围有交集
+        if (bianZhiShiJian.contains("季度")) {
+            int quarter = extractQuarter(bianZhiShiJian);
+            YearMonth qStart = YearMonth.of(year, (quarter - 1) * 3 + 1);
+            YearMonth qEnd = YearMonth.of(year, quarter * 3);
+            return !qStart.isAfter(endYM) && !qEnd.isBefore(startYM);
+        }
+
+        // 月度判断
+        if (bianZhiShiJian.contains("月份")) {
+            int month = extractMonth(bianZhiShiJian);
+            YearMonth currentYM = YearMonth.of(year, month);
+            return !currentYM.isAfter(endYM) && !currentYM.isBefore(startYM);
+        }
+
+        return false;
+    }
+
+    // 提取年份 (针对数据库原始格式,如 "2025年11月份")
+    private int extractYear(String timeStr) {
+        Pattern p = Pattern.compile("(\\d{4})年");
+        Matcher m = p.matcher(timeStr);
+        if (m.find()) return Integer.parseInt(m.group(1));
+        return 0;
+    }
+
+    // 提取年份 (针对已经格式化后的 X 轴标签,如 "2025年度" 或 "2025年11月")
+    private int extractYearFromStringLabel(String label) {
+        if (StringUtils.isBlank(label)) return java.time.Year.now().getValue();
+        Pattern p = Pattern.compile("(\\d{4})");
+        Matcher m = p.matcher(label);
+        if (m.find()) return Integer.parseInt(m.group(1));
+        return java.time.Year.now().getValue();
+    }
+
+    // 提取月份
+    private int extractMonth(String timeStr) {
+        Pattern p = Pattern.compile("(\\d{1,2})月份");
+        Matcher m = p.matcher(timeStr);
+        if (m.find()) return Integer.parseInt(m.group(1));
+        return 0;
+    }
+
+    // 提取季度
+    private int extractQuarter(String timeStr) {
+        Pattern p = Pattern.compile("第(\\d{1,2})季度");
+        Matcher m = p.matcher(timeStr);
+        if (m.find()) return Integer.parseInt(m.group(1));
+        return 0;
+    }
+
+    // 比较时间字符串大小,用于排序
+    private int compareTimeStr(String t1, String t2) {
+        int y1 = extractYear(t1);
+        int y2 = extractYear(t2);
+        if (y1 != y2) return Integer.compare(y1, y2);
+
+        if (t1.contains("年度") || t2.contains("年度")) return 0;
+
+        if (t1.contains("季度") && t2.contains("季度")) {
+            return Integer.compare(extractQuarter(t1), extractQuarter(t2));
+        }
+        if (t1.contains("月份") && t2.contains("月份")) {
+            return Integer.compare(extractMonth(t1), extractMonth(t2));
+        }
+        return 0;
+    }
+
+    // 格式化 X 轴标签 (去掉冗余文字)
+    private String formatXAxisLabel(String timeStr) {
+        if (StringUtils.isBlank(timeStr)) return "";
+//        if (timeStr.contains("月份")) return timeStr.substring(timeStr.indexOf("年")+1, timeStr.length()).replace("份", "");
+        if (timeStr.contains("月份")) return timeStr.replace("份", "");
+//        if (timeStr.contains("季度")) return timeStr.substring(timeStr.indexOf("年")+1, timeStr.length());
+        if (timeStr.contains("季度")) return timeStr;
+        if (timeStr.contains("年度")) return timeStr;
+        return timeStr;
+    }
+
+    // 安全转换数字
+    private Number parseNumber(Object obj) {
+        if (obj == null) return null;
+        if (obj instanceof Number) {
+            return (Number) obj;
+        }
+        // 尝试转换字符串
+        String str = obj.toString().trim();
+        // 排除明显的非数字字符串,可以根据实际情况添加更多判断
+        if (str.isEmpty() || "null".equalsIgnoreCase(str) || "N/A".equals(str) || "达标".equals(str)) {
+            return null;
+        }
+
+        try {
+            // 如果需要保留整数类型,可以先尝试 Integer,再尝试 Double
+            return Double.parseDouble(str);
+        } catch (NumberFormatException e) {
+            // 记录日志:无法解析的数值字符串 str
+            return null; // 返回 null,后续流处理会过滤掉
+        }
+    }
+
+    public List<Map<String, Object>> getOrgData() {
+        String sqlStr = "select id_,parent_id_,bian_zhi_shi_jian,zhi_liang_mu_biao,zhi_liang_zhi_bia,zhi_biao_xian_zhi,shi_ji_shu_zhi_,yuan_shi_shu_ju_,zhuang_tai_,tong_ji_pin_lv_ from t_zlzbpjzb where zhuang_tai_='已完成'";
+        List<Map<String, Object>> retList = (List<Map<String, Object>>) commonDao.query(sqlStr);
+        return retList;
+    }
+}