瀏覽代碼

报销功能调整(报销发票张数错误bug修复)

徐滕 2 周之前
父節點
當前提交
9289281135
共有 51 個文件被更改,包括 8056 次插入86 次删除
  1. 256 0
      src/main/java/com/jeeplus/common/utils/excel/utils/ExcelCommonReader.java
  2. 414 0
      src/main/java/com/jeeplus/common/utils/excel/utils/ExcelImportBySheetName.java
  3. 19 0
      src/main/java/com/jeeplus/common/utils/excel/utils/ExcelRowConverter.java
  4. 14 12
      src/main/java/com/jeeplus/modules/areaStaff/service/AreaStaffService.java
  5. 87 0
      src/main/java/com/jeeplus/modules/projectStatement/dao/ProjectFinalAccountsDao.java
  6. 75 0
      src/main/java/com/jeeplus/modules/projectStatement/dao/ProjectSubstationDao.java
  7. 148 0
      src/main/java/com/jeeplus/modules/projectStatement/entity/ProjectFinalAccounts.java
  8. 129 0
      src/main/java/com/jeeplus/modules/projectStatement/entity/ProjectFinalAccountsImport.java
  9. 171 0
      src/main/java/com/jeeplus/modules/projectStatement/entity/ProjectSubstationImport.java
  10. 724 0
      src/main/java/com/jeeplus/modules/projectStatement/service/ProjectFinalAccountsService.java
  11. 639 0
      src/main/java/com/jeeplus/modules/projectStatement/service/ProjectSubstationService.java
  12. 26 0
      src/main/java/com/jeeplus/modules/projectStatement/utils/BigDecimalTwoDecimalSerializer.java
  13. 971 0
      src/main/java/com/jeeplus/modules/projectStatement/web/ProjectFinalAccountsController.java
  14. 335 0
      src/main/java/com/jeeplus/modules/projectStatement/web/ProjectSubstationController.java
  15. 45 10
      src/main/java/com/jeeplus/modules/ruralprojectrecords/service/RuralProjectMessageElectronicSealService.java
  16. 2 2
      src/main/java/com/jeeplus/modules/ruralprojectrecords/service/RuralProjectRecordsService.java
  17. 42 0
      src/main/java/com/jeeplus/modules/ruralprojectrecords/web/RuralCostProjectRecordsController.java
  18. 19 3
      src/main/java/com/jeeplus/modules/signatureManagement/businessSignature/controller/BusinessSignatureController.java
  19. 19 1
      src/main/java/com/jeeplus/modules/signatureManagement/electronicSignature/controller/ElectronicSignatureController.java
  20. 7 0
      src/main/java/com/jeeplus/modules/signatureManagement/electronicSignature/dao/DistrictDirectorApplicationDao.java
  21. 19 0
      src/main/java/com/jeeplus/modules/signatureManagement/electronicSignature/entity/DistrictDirectorApplication.java
  22. 99 19
      src/main/java/com/jeeplus/modules/signatureManagement/electronicSignature/service/DistrictDirectorApplicationService.java
  23. 12 2
      src/main/java/com/jeeplus/modules/workreimbursement/entity/ReimbursementVATTax.java
  24. 12 0
      src/main/java/com/jeeplus/modules/workreimbursement/entity/WorkReimbursement.java
  25. 1 0
      src/main/java/com/jeeplus/modules/workreimbursement/service/WorkReimbursementAllService.java
  26. 15 12
      src/main/java/com/jeeplus/modules/workreimbursement/service/WorkReimbursementService.java
  27. 10 1
      src/main/java/com/jeeplus/modules/workreimbursement/web/WorkReimbursementAllController.java
  28. 11 1
      src/main/java/com/jeeplus/modules/workreimbursement/web/WorkReimbursementController.java
  29. 339 0
      src/main/resources/mappings/modules/projectStatement/ProjectAccountsDao.xml
  30. 297 0
      src/main/resources/mappings/modules/projectStatement/substation/ProjectSubstationDao.xml
  31. 1 0
      src/main/resources/mappings/modules/ruralprojectrecords/RuralProjectRecordsDao.xml
  32. 15 4
      src/main/resources/mappings/modules/signatureManagement/electronicSignature/DistrictDirectorApplicationDao.xml
  33. 2 1
      src/main/resources/mappings/modules/workreimbursement/WorkReimbursementDao.xml
  34. 104 0
      src/main/webapp/WEB-INF/tags/sys/reimburtreeselectUsers.tag
  35. 2 2
      src/main/webapp/WEB-INF/tags/table/fileUpload.tag
  36. 58 0
      src/main/webapp/WEB-INF/tags/table/importStatementExcel.tag
  37. 906 0
      src/main/webapp/webpage/modules/projectStatement/projectAccountsAddForm.jsp
  38. 723 0
      src/main/webapp/webpage/modules/projectStatement/projectAccountsList.jsp
  39. 138 0
      src/main/webapp/webpage/modules/projectStatement/projectAccountsTwoForm.jsp
  40. 132 0
      src/main/webapp/webpage/modules/projectStatement/projectAccountsView.jsp
  41. 730 0
      src/main/webapp/webpage/modules/projectStatement/substation/projectSubstationList.jsp
  42. 156 0
      src/main/webapp/webpage/modules/projectStatement/substation/projectSubstationTwoForm.jsp
  43. 20 1
      src/main/webapp/webpage/modules/signatureManagement/electronicSignature/directorApplicationAudit.jsp
  44. 32 1
      src/main/webapp/webpage/modules/signatureManagement/electronicSignature/directorApplicationForm.jsp
  45. 34 1
      src/main/webapp/webpage/modules/signatureManagement/electronicSignature/directorApplicationModify.jsp
  46. 5 0
      src/main/webapp/webpage/modules/signatureManagement/electronicSignature/districtDirectorApplicationList.jsp
  47. 10 4
      src/main/webapp/webpage/modules/workreimbursement/treeForm/all/workReimbursementAllFormAdd.jsp
  48. 9 2
      src/main/webapp/webpage/modules/workreimbursement/treeForm/all/workReimbursementAllModifyApply.jsp
  49. 10 3
      src/main/webapp/webpage/modules/workreimbursement/treeForm/new/workReimbursementNewFormAdd.jsp
  50. 10 2
      src/main/webapp/webpage/modules/workreimbursement/treeForm/new/workReimbursementNewModifyApply.jsp
  51. 2 2
      src/main/webapp/webpage/modules/workreimbursement/treeForm/specific/workReimbursementSpecificAudit.jsp

+ 256 - 0
src/main/java/com/jeeplus/common/utils/excel/utils/ExcelCommonReader.java

@@ -0,0 +1,256 @@
+package com.jeeplus.common.utils.excel.utils;
+
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.usermodel.DateUtil;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 通用Excel读取组件(基于POI 3.16)
+ * 支持根据工作表名读取数据,并转换为任意实体类列表
+ */
+public class ExcelCommonReader {
+
+    // 默认日期格式(可通过方法参数自定义)
+    private static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
+
+    /**
+     * 核心方法:读取Excel指定工作表,并转换为实体类列表(支持指定表头行和数据起始行)
+     * @param filePath Excel文件路径
+     * @param sheetName 工作表名称
+     * @param converter 行数据转换器
+     * @param requiredHeaders 必要的表头
+     * @param headerRowIndex 表头所在行索引(从0开始,默认0)
+     * @param dataStartRowIndex 数据开始行索引(默认headerRowIndex + 1)
+     * @param <T> 目标实体类型
+     * @return 转换后的实体类列表
+     */
+    public static <T> List<T> readAndConvert(String filePath, String sheetName,
+                                             ExcelRowConverter<T> converter,
+                                             String[] requiredHeaders,
+                                             int headerRowIndex,
+                                             int dataStartRowIndex) {
+        // 1. 读取Excel原始数据(所有行,包含标题、表头、数据等)
+        List<List<String>> excelData = readExcelData(filePath, sheetName);
+        if (excelData.isEmpty()) {
+            throw new ExcelReadException("Excel文件为空");
+        }
+
+        // 2. 验证行索引合法性
+        validateRowIndexes(excelData.size(), headerRowIndex, dataStartRowIndex);
+
+        // 3. 解析表头(从指定的表头行获取)
+        List<String> headerRow = excelData.get(headerRowIndex);
+        Map<String, Integer> headerMap = parseHeaderMap(headerRow);
+        validateRequiredHeaders(headerMap, requiredHeaders);
+
+        // 4. 遍历数据行(从指定的dataStartRowIndex开始)
+        List<T> resultList = new ArrayList<>();
+        for (int rowIndex = dataStartRowIndex; rowIndex < excelData.size(); rowIndex++) {
+            List<String> rowData = excelData.get(rowIndex);
+            if (rowData.isEmpty()) {
+                continue; // 跳过空行
+            }
+            // 转换时传入原始行索引(用于异常提示,用户看到的行号=索引+1)
+            T entity = converter.convert(rowData, headerMap, rowIndex);
+            resultList.add(entity);
+        }
+        return resultList;
+    }
+
+    /**
+     * 重载方法:使用默认行索引(表头行0,数据行1)
+     */
+    public static <T> List<T> readAndConvert(String filePath, String sheetName,
+                                             ExcelRowConverter<T> converter,
+                                             String[] requiredHeaders) {
+        return readAndConvert(filePath, sheetName, converter, requiredHeaders, 0, 1);
+    }
+
+    /**
+     * 验证表头行和数据行索引的合法性
+     */
+    private static void validateRowIndexes(int totalRows, int headerRowIndex, int dataStartRowIndex) {
+        if (headerRowIndex < 0 || headerRowIndex >= totalRows) {
+            throw new ExcelReadException("表头行索引无效(超出范围):" + headerRowIndex);
+        }
+        if (dataStartRowIndex < 0 || dataStartRowIndex >= totalRows) {
+            throw new ExcelReadException("数据起始行索引无效(超出范围):" + dataStartRowIndex);
+        }
+        if (dataStartRowIndex <= headerRowIndex) {
+            throw new ExcelReadException("数据起始行必须在表头行之后(dataStartRowIndex > headerRowIndex)");
+        }
+    }
+
+    /**
+     * 读取Excel指定工作表的原始数据(所有单元格值转换为字符串)
+     */
+    private static List<List<String>> readExcelData(String filePath, String sheetName) {
+        List<List<String>> result = new ArrayList<>();
+        FileInputStream fis = null;
+        Workbook workbook = null;
+
+        try {
+            // 验证文件存在性
+            File excelFile = new File(filePath);
+            if (!excelFile.exists()) {
+                throw new ExcelReadException("文件不存在:" + filePath);
+            }
+
+            // 读取文件(POI 3.16兼容xls和xlsx)
+            fis = new FileInputStream(excelFile);
+            workbook = WorkbookFactory.create(fis);
+
+            // 定位工作表
+            Sheet sheet = workbook.getSheet(sheetName);
+            if (sheet == null) {
+                throw new ExcelReadException("未找到工作表:" + sheetName);
+            }
+
+            // 遍历行和单元格
+            int lastRowNum = sheet.getLastRowNum();
+            for (int rowIdx = 0; rowIdx <= lastRowNum; rowIdx++) {
+                Row row = sheet.getRow(rowIdx);
+                if (row == null) {
+                    result.add(new ArrayList<>()); // 空行添加空列表
+                    continue;
+                }
+
+                List<String> rowData = new ArrayList<>();
+                int lastCellNum = row.getLastCellNum();
+                for (int cellIdx = 0; cellIdx < lastCellNum; cellIdx++) {
+                    Cell cell = row.getCell(cellIdx);
+                    rowData.add(getCellValue(cell, DEFAULT_DATE_FORMAT));
+                }
+                result.add(rowData);
+            }
+
+        } catch (Exception e) {
+            throw new ExcelReadException("读取Excel失败:" + e.getMessage(), e);
+        } finally {
+            // POI 3.16手动关闭资源
+            if (workbook != null) {
+                try { workbook.close(); } catch (IOException e) { e.printStackTrace(); }
+            }
+            if (fis != null) {
+                try { fis.close(); } catch (IOException e) { e.printStackTrace(); }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 解析表头映射(表头文本→列索引)
+     */
+    private static Map<String, Integer> parseHeaderMap(List<String> headerRow) {
+        Map<String, Integer> headerMap = new HashMap<>();
+        for (int i = 0; i < headerRow.size(); i++) {
+            String header = headerRow.get(i).trim();
+            if (!header.isEmpty()) { // 跳过空表头
+                headerMap.put(header, i);
+            }
+        }
+        return headerMap;
+    }
+
+    /**
+     * 验证必要表头是否存在
+     */
+    private static void validateRequiredHeaders(Map<String, Integer> headerMap, String[] requiredHeaders) {
+        for (String header : requiredHeaders) {
+            if (!headerMap.containsKey(header)) {
+                throw new ExcelReadException("缺少必要表头:" + header);
+            }
+        }
+    }
+
+    /**
+     * 解析单元格值(POI 3.16适配)
+     * @param cell 单元格对象
+     * @param dateFormat 日期格式(如yyyy-MM-dd)
+     * @return 单元格值(字符串)
+     */
+    public static String getCellValue(Cell cell, String dateFormat) {
+        if (cell == null) {
+            return "";
+        }
+
+        String value = "";
+        switch (cell.getCellType()) {
+            case Cell.CELL_TYPE_STRING:
+                value = cell.getStringCellValue().trim();
+                break;
+            case Cell.CELL_TYPE_NUMERIC:
+                if (DateUtil.isCellDateFormatted(cell)) {
+                    // 日期类型
+                    value = new SimpleDateFormat(dateFormat).format(cell.getDateCellValue());
+                } else {
+                    // 数字类型(避免整数显示为123.0)
+                    double num = cell.getNumericCellValue();
+                    value = (num == (long) num) ? String.valueOf((long) num) : String.valueOf(num);
+                }
+                break;
+            case Cell.CELL_TYPE_BOOLEAN:
+                value = String.valueOf(cell.getBooleanCellValue());
+                break;
+            case Cell.CELL_TYPE_FORMULA:
+                // 公式类型:计算结果
+                Workbook workbook = cell.getSheet().getWorkbook();
+                FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
+                CellValue evaluatedValue = evaluator.evaluate(cell);
+                value = getEvaluatedCellValue(evaluatedValue, dateFormat);
+                break;
+            default:
+                value = "";
+        }
+        return value;
+    }
+
+    /**
+     * 解析公式计算结果(POI 3.16适配)
+     */
+    private static String getEvaluatedCellValue(CellValue cellValue, String dateFormat) {
+        if (cellValue == null) {
+            return "";
+        }
+        switch (cellValue.getCellType()) {
+            case Cell.CELL_TYPE_STRING:
+                return cellValue.getStringValue().trim();
+            case Cell.CELL_TYPE_NUMERIC:
+                double num = cellValue.getNumberValue();
+                return (num == (long) num) ? String.valueOf((long) num) : String.valueOf(num);
+            case Cell.CELL_TYPE_BOOLEAN:
+                return String.valueOf(cellValue.getBooleanValue());
+            default:
+                return "";
+        }
+    }
+
+    /**
+     * 根据表头获取单元格值(工具方法,供转换器使用)
+     */
+    public static String getValueByHeader(List<String> rowData, Map<String, Integer> headerMap, String header) {
+        if (!headerMap.containsKey(header)) {
+            return ""; // 表头不存在返回空
+        }
+        int colIdx = headerMap.get(header);
+        return (colIdx < rowData.size()) ? rowData.get(colIdx) : "";
+    }
+
+
+    // 自定义异常:统一Excel读取相关错误
+    public static class ExcelReadException extends RuntimeException {
+        public ExcelReadException(String message) {
+            super(message);
+        }
+        public ExcelReadException(String message, Throwable cause) {
+            super(message, cause);
+        }
+    }
+}

+ 414 - 0
src/main/java/com/jeeplus/common/utils/excel/utils/ExcelImportBySheetName.java

@@ -0,0 +1,414 @@
+package com.jeeplus.common.utils.excel.utils;
+
+import com.jeeplus.common.utils.excel.annotation.ExcelField;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellAddress;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.math.BigDecimal;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * 支持复杂表头(多行+合并列)的Excel导入工具类(适配自定义@ExcelField注解)
+ * 特性:基于注解title匹配表头、自动处理合并单元格、兼容xls/xlsx
+ */
+public class ExcelImportBySheetName {
+
+    private final Workbook workbook;          // Excel工作簿
+    private final Sheet sheet;                // 目标工作表
+    private final int headerStartRowIndex;    // 表头起始行索引(0-based)
+    private final int headerEndRowIndex;      // 表头结束行索引(0-based)
+    private final int dataStartRowIndex;      // 数据起始行索引(表头结束行+1)
+    private final List<List<String>> excelData; // 解析后的所有行数据(含表头)
+    private final Map<String, CellAddress> mergedRegionMap; // 合并区域映射
+
+    /**
+     * 构造方法(支持多行表头)
+     * @param file 上传文件
+     * @param headerStartNum 表头起始行号(Excel行号,从1开始)
+     * @param headerEndNum 表头结束行号(Excel行号,≥起始行)
+     * @param sheetName 工作表名称
+     */
+    public ExcelImportBySheetName(MultipartFile file, int headerStartNum, int headerEndNum, String sheetName) throws IOException {
+        // 入参校验
+        validateParams(file, headerStartNum, headerEndNum, sheetName);
+
+        // 转换行号为0-based索引
+        this.headerStartRowIndex = headerStartNum - 1;
+        this.headerEndRowIndex = headerEndNum - 1;
+        this.dataStartRowIndex = this.headerEndRowIndex + 1;
+
+        // 初始化工作簿
+        this.workbook = initWorkbook(file);
+
+        // 定位工作表
+        this.sheet = workbook.getSheet(sheetName);
+        if (this.sheet == null) {
+            throw new IllegalArgumentException("未找到工作表:【" + sheetName + "】");
+        }
+
+        // 初始化合并区域映射(处理合并单元格)
+        this.mergedRegionMap = initMergedRegions();
+
+        // 解析所有行数据(含表头,自动处理合并单元格)
+        this.excelData = parseAllRows();
+    }
+
+    /**
+     * 入参合法性校验
+     */
+    private void validateParams(MultipartFile file, int headerStartNum, int headerEndNum, String sheetName) {
+        if (file.isEmpty()) {
+            throw new IllegalArgumentException("导入文件不能为空");
+        }
+        if (headerStartNum < 1 || headerEndNum < headerStartNum) {
+            throw new IllegalArgumentException("表头参数错误:起始行≥1,且结束行≥起始行");
+        }
+        if (sheetName == null || sheetName.trim().isEmpty()) {
+            throw new IllegalArgumentException("工作表名称不能为空");
+        }
+    }
+
+    /**
+     * 初始化Excel工作簿(兼容xls/xlsx)
+     */
+    private Workbook initWorkbook(MultipartFile file) throws IOException {
+        try (InputStream is = file.getInputStream()) {
+            return WorkbookFactory.create(is);
+        } catch (InvalidFormatException e) {
+            throw new RuntimeException("Excel文件格式错误:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 初始化合并区域映射:记录合并单元格关联关系
+     */
+    private Map<String, CellAddress> initMergedRegions() {
+        Map<String, CellAddress> map = new HashMap<>(16);
+        List<CellRangeAddress> mergedRegions = sheet.getMergedRegions();
+
+        for (CellRangeAddress region : mergedRegions) {
+            int firstRow = region.getFirstRow();
+            int lastRow = region.getLastRow();
+            int firstCol = region.getFirstColumn();
+            int lastCol = region.getLastColumn();
+
+            // 合并区域内所有单元格映射到首单元格
+            for (int row = firstRow; row <= lastRow; row++) {
+                for (int col = firstCol; col <= lastCol; col++) {
+                    map.put(row + "," + col, new CellAddress(firstRow, firstCol));
+                }
+            }
+        }
+        return map;
+    }
+
+    /**
+     * 解析所有行数据(含表头),自动处理合并单元格
+     */
+    private List<List<String>> parseAllRows() {
+        List<List<String>> result = new ArrayList<>();
+        int lastRowIdx = sheet.getLastRowNum();
+
+        for (int rowIdx = 0; rowIdx <= lastRowIdx; rowIdx++) {
+            Row row = sheet.getRow(rowIdx);
+            if (row == null) {
+                result.add(new ArrayList<>());
+                continue;
+            }
+
+            List<String> rowData = new ArrayList<>();
+            int lastCellNum = row.getLastCellNum();
+            for (int colIdx = 0; colIdx < lastCellNum; colIdx++) {
+                rowData.add(getMergedCellValue(rowIdx, colIdx));
+            }
+            result.add(rowData);
+        }
+        return result;
+    }
+
+    /**
+     * 获取单元格值(处理合并单元格:返回首单元格值)
+     */
+    private String getMergedCellValue(int rowIdx, int colIdx) {
+        // 检查是否在合并区域
+        String cellKey = rowIdx + "," + colIdx;
+        CellAddress firstCellAddr = mergedRegionMap.get(cellKey);
+        if (firstCellAddr != null) {
+            rowIdx = firstCellAddr.getRow();
+            colIdx = firstCellAddr.getColumn();
+        }
+
+        // 读取实际值
+        Row row = sheet.getRow(rowIdx);
+        if (row == null) return "";
+
+        Cell cell = row.getCell(colIdx);
+        return parseCellValue(cell);
+    }
+
+    /**
+     * 解析单元格值为字符串(适配POI 3.16)
+     */
+    private String parseCellValue(Cell cell) {
+        if (cell == null) return "";
+
+        String value = "";
+        switch (cell.getCellType()) {
+            case Cell.CELL_TYPE_STRING:
+                value = cell.getStringCellValue().trim();
+                break;
+            case Cell.CELL_TYPE_NUMERIC:
+                if (DateUtil.isCellDateFormatted(cell)) {
+                    value = new SimpleDateFormat("yyyy-MM-dd").format(cell.getDateCellValue());
+                } else {
+                    double num = cell.getNumericCellValue();
+                    value = (num == (long) num) ? String.valueOf((long) num) : String.valueOf(num);
+                }
+                break;
+            case Cell.CELL_TYPE_BOOLEAN:
+                value = String.valueOf(cell.getBooleanCellValue());
+                break;
+            case Cell.CELL_TYPE_FORMULA:
+                FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
+                CellValue evalValue = evaluator.evaluate(cell);
+                value = parseEvaluatedValue(evalValue);
+                break;
+            default:
+                value = "";
+        }
+        return value;
+    }
+
+    /**
+     * 解析公式计算结果
+     */
+    private String parseEvaluatedValue(CellValue evalValue) {
+        if (evalValue == null) return "";
+
+        switch (evalValue.getCellType()) {
+            case Cell.CELL_TYPE_STRING:
+                return evalValue.getStringValue().trim();
+            case Cell.CELL_TYPE_NUMERIC:
+                double num = evalValue.getNumberValue();
+                return (num == (long) num) ? String.valueOf((long) num) : String.valueOf(num);
+            case Cell.CELL_TYPE_BOOLEAN:
+                return String.valueOf(evalValue.getBooleanValue());
+            default:
+                return "";
+        }
+    }
+
+    /**
+     * 核心方法:转换为实体类列表
+     */
+    public <T> List<T> toEntityList(Class<T> entityClass) {
+        try {
+            // 校验表头行范围
+            if (headerEndRowIndex >= excelData.size()) {
+                throw new RuntimeException("表头结束行超出Excel总行数:" + excelData.size());
+            }
+            if (dataStartRowIndex >= excelData.size()) {
+                throw new RuntimeException("无有效数据行(数据起始行≥总行数)");
+            }
+
+            // 解析多行表头,生成完整标识(如"金额/合计")
+            List<String> fullHeaderList = parseFullHeaderList();
+
+            // 构建表头映射(完整标识→列索引)
+            Map<String, Integer> headerMap = buildHeaderMap(fullHeaderList);
+
+            // 校验必要表头(基于@ExcelField.title)
+            validateRequiredHeaders(headerMap, entityClass);
+
+            // 转换数据行为实体
+            return convertDataRowsToEntities(headerMap, entityClass);
+        } finally {
+            // 释放资源
+            if (workbook != null) {
+                try {
+                    workbook.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * 解析多行表头,组合生成完整标识(用“/”分隔多行文本)
+     */
+    private List<String> parseFullHeaderList() {
+
+        // 打印表头行的原始数据(第3行和第4行)
+        System.out.println("第3行原始数据:" + excelData.get(headerStartRowIndex)); // headerStartRowIndex=2(3-1)
+        System.out.println("第4行原始数据:" + excelData.get(headerStartRowIndex + 1)); // 第4行索引=3
+        // 获取表头区域最大列数
+        int maxColCount = 0;
+        for (int rowIdx = headerStartRowIndex; rowIdx <= headerEndRowIndex; rowIdx++) {
+            if (rowIdx >= excelData.size()) break;
+            List<String> rowData = excelData.get(rowIdx);
+            maxColCount = Math.max(maxColCount, rowData.size());
+        }
+
+        // 组合每列的多行表头文本
+        List<String> fullHeaderList = new ArrayList<>(maxColCount);
+        for (int colIdx = 0; colIdx < maxColCount; colIdx++) {
+            StringBuilder headerSb = new StringBuilder();
+            for (int rowIdx = headerStartRowIndex; rowIdx <= headerEndRowIndex; rowIdx++) {
+                if (rowIdx >= excelData.size()) break;
+                List<String> rowData = excelData.get(rowIdx);
+                String cellValue = (colIdx < rowData.size()) ? rowData.get(colIdx).trim() : "";
+                if (!cellValue.isEmpty()) {
+                    if (headerSb.length() > 0) {
+                        headerSb.append("/"); // 层级分隔符
+                    }
+                    headerSb.append(cellValue);
+                }
+            }
+            fullHeaderList.add(headerSb.toString());
+        }
+        return fullHeaderList;
+    }
+
+    /**
+     * 构建表头映射:完整标识→列索引
+     */
+    private Map<String, Integer> buildHeaderMap(List<String> fullHeaderList) {
+        Map<String, Integer> headerMap = new HashMap<>(16);
+        for (int i = 0; i < fullHeaderList.size(); i++) {
+            String header = fullHeaderList.get(i).trim();
+            if (!header.isEmpty()) {
+                headerMap.put(header, i);
+            }
+        }
+        return headerMap;
+    }
+
+    /**
+     * 校验Excel是否包含实体类所需表头(基于@ExcelField.title)
+     */
+    private void validateRequiredHeaders(Map<String, Integer> headerMap, Class<?> entityClass) {
+        List<String> requiredTitles = getRequiredTitlesFromAnnotation(entityClass);
+        for (String title : requiredTitles) {
+            if (!headerMap.containsKey(title)) {
+                throw new RuntimeException("Excel缺少必要表头:【" + title + "】");
+            }
+        }
+    }
+
+    /**
+     * 从@ExcelField注解提取需要的表头title(type=0或2)
+     */
+    private List<String> getRequiredTitlesFromAnnotation(Class<?> entityClass) {
+        List<String> titles = new ArrayList<>();
+        for (Field field : entityClass.getDeclaredFields()) {
+            if (field.isAnnotationPresent(ExcelField.class)) {
+                ExcelField excelField = field.getAnnotation(ExcelField.class);
+                if (excelField.type() == 0 || excelField.type() == 2) { // 导入或导入导出类型
+                    titles.add(excelField.title().trim()); // 关键:使用注解的title属性
+                }
+            }
+        }
+        return titles;
+    }
+
+    /**
+     * 数据行转换为实体列表
+     */
+    private <T> List<T> convertDataRowsToEntities(Map<String, Integer> headerMap, Class<T> entityClass) {
+        List<T> entities = new ArrayList<>();
+        for (int rowIdx = dataStartRowIndex; rowIdx < excelData.size(); rowIdx++) {
+            List<String> rowData = excelData.get(rowIdx);
+            if (rowData.isEmpty()) continue;
+
+            // 单行转换为实体(rowIdx+1为Excel实际行号)
+            T entity = mapRowToEntity(rowData, headerMap, rowIdx + 1, entityClass);
+            entities.add(entity);
+        }
+        return entities;
+    }
+
+    /**
+     * 单行数据映射为实体对象
+     */
+    private <T> T mapRowToEntity(List<String> rowData, Map<String, Integer> headerMap,
+                                 int excelRowNum, Class<T> entityClass) {
+        try {
+            T entity = entityClass.newInstance(); // 需无参构造
+
+            for (Field field : entityClass.getDeclaredFields()) {
+                if (!field.isAnnotationPresent(ExcelField.class)) continue;
+
+                ExcelField excelField = field.getAnnotation(ExcelField.class);
+                if (excelField.type() == 1) continue; // 跳过仅导出字段
+
+                // 获取表头对应的单元格值
+                String title = excelField.title().trim();
+                String cellValue = getCellValueByTitle(rowData, headerMap, title);
+                if (cellValue.isEmpty()) continue;
+
+                // 确定目标类型(优先注解的fieldType,否则用字段类型)
+                Class<?> targetType = excelField.fieldType() != Class.class
+                        ? excelField.fieldType()
+                        : field.getType();
+
+                // 类型转换
+                Object fieldValue = convertValue(cellValue, targetType, excelField, field.getName(), excelRowNum);
+
+                // 赋值
+                field.setAccessible(true);
+                field.set(entity, fieldValue);
+            }
+            return entity;
+        } catch (InstantiationException | IllegalAccessException e) {
+            throw new RuntimeException("第" + excelRowNum + "行实体创建失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 根据表头title获取单元格值
+     */
+    private String getCellValueByTitle(List<String> rowData, Map<String, Integer> headerMap, String title) {
+        if (!headerMap.containsKey(title)) return "";
+        int colIdx = headerMap.get(title);
+        return (colIdx < rowData.size()) ? rowData.get(colIdx).trim() : "";
+    }
+
+    /**
+     * 单元格值转换为目标类型
+     */
+    private Object convertValue(String cellValue, Class<?> targetType,
+                                ExcelField excelField, String fieldName, int excelRowNum) {
+        try {
+            if (targetType == String.class) {
+                return cellValue;
+            }
+            if (targetType == BigDecimal.class) {
+                return new BigDecimal(cellValue.replace(",", "")); // 处理千分符
+            }
+            if (targetType == Integer.class || targetType == int.class) {
+                return Integer.parseInt(cellValue);
+            }
+            if (targetType == Long.class || targetType == long.class) {
+                return Long.parseLong(cellValue);
+            }
+            if (targetType == Date.class) {
+                String format = excelField.format().isEmpty() ? "yyyy-MM-dd" : excelField.format();
+                return new SimpleDateFormat(format).parse(cellValue);
+            }
+            throw new RuntimeException("不支持的字段类型:" + targetType.getName());
+        } catch (ParseException e) {
+            throw new RuntimeException("第" + excelRowNum + "行【" + fieldName + "】日期格式错误(期望:" + excelField.format() + ")", e);
+        } catch (NumberFormatException e) {
+            throw new RuntimeException("第" + excelRowNum + "行【" + fieldName + "】数字格式错误:" + cellValue, e);
+        }
+    }
+}

+ 19 - 0
src/main/java/com/jeeplus/common/utils/excel/utils/ExcelRowConverter.java

@@ -0,0 +1,19 @@
+package com.jeeplus.common.utils.excel.utils;
+
+import java.util.Map;
+import java.util.List;
+
+/**
+ * Excel行数据转换器接口(将一行Excel数据转换为实体对象)
+ * @param <T> 目标实体类类型
+ */
+public interface ExcelRowConverter<T> {
+    /**
+     * 将Excel行数据转换为实体对象
+     * @param rowData 一行Excel数据(单元格值列表)
+     * @param headerMap 表头映射(键:表头文本,值:列索引)
+     * @param rowIndex 行索引(从0开始,用于异常提示)
+     * @return 转换后的实体对象
+     */
+    T convert(List<String> rowData, Map<String, Integer> headerMap, int rowIndex);
+}

+ 14 - 12
src/main/java/com/jeeplus/modules/areaStaff/service/AreaStaffService.java

@@ -465,19 +465,20 @@ public class AreaStaffService  extends TreeService<AreaStaffDao, AreaStaffInfo>
      */
     public Map<String,Object> disposeAreaMaster(DistrictDirectorApplication directorApplication){
         Map<String,Object> map = new HashMap<>();
+        Map<String,Object> map1 = new HashMap<>();
         //判定是否存在部门信息,若存在则先验证该部门是否存在在表中
         if(null ==directorApplication.getOffice() || StringUtils.isBlank(directorApplication.getOffice().getId())
                 || null ==directorApplication.getCreateBy() || StringUtils.isBlank(directorApplication.getCreateBy().getId())){
-            map.put("success",false);
-            map.put("message","申请人、申请部门或申请地区错误,无法添加地区管理员");
-            return map;
+            map1.put("success",false);
+            map1.put("message","申请人、申请部门或申请地区错误,无法添加地区管理员");
+            return map1;
         }
         //获取申请人信息
         User user = UserUtils.get(directorApplication.getCreateBy().getId());
         if(null == user){
-            map.put("success",false);
-            map.put("message","申请人信息错误,无法添加地区管理员");
-            return map;
+            map1.put("success",false);
+            map1.put("message","申请人信息错误,无法添加地区管理员");
+            return map1;
         }
 
         if(null != directorApplication.getOffice() && StringUtils.isNotBlank(directorApplication.getOffice().getId())){
@@ -486,9 +487,10 @@ public class AreaStaffService  extends TreeService<AreaStaffDao, AreaStaffInfo>
                 //如果部门不为空,则验证该部门下 的地区是否存在
                 List<AreaStaffInfo> areaInfo = dao.getAreaStaffInfoByParentIdAndAreaManagement(directorApplication.getAreaDirector(), officeInfo.getId());
                 if(null != areaInfo && areaInfo.size()>0){
-                    map.put("success",false);
-                    map.put("message","该地区已存在,无法重复添加");
-                    return map;
+                    map1.put("success",false);
+                    map1.put("areaInfo",areaInfo);
+                    map1.put("message","该地区已存在,无法重复添加");
+                    return map1;
                 }else{
                     //如果地区为空则进行地区添加和地区负责人添加
                     String areaId = "";
@@ -510,9 +512,9 @@ public class AreaStaffService  extends TreeService<AreaStaffDao, AreaStaffInfo>
                 AreaStaffInfo areaStaffInfo = this.saveAreaType(areaOfficeInfo, areaId,directorApplication);
             }
         }
-        map.put("success",true);
-        map.put("message","添加成功");
-        return map;
+        map1.put("success",true);
+        map1.put("message","添加成功");
+        return map1;
     }
 
     /**

+ 87 - 0
src/main/java/com/jeeplus/modules/projectStatement/dao/ProjectFinalAccountsDao.java

@@ -0,0 +1,87 @@
+package com.jeeplus.modules.projectStatement.dao;
+
+
+
+import com.jeeplus.common.persistence.CrudDao;
+import com.jeeplus.common.persistence.annotation.MyBatisDao;
+import com.jeeplus.modules.projectStatement.entity.ProjectFinalAccounts;
+import com.jeeplus.modules.projectStatement.entity.ProjectFinalAccountsImport;
+import com.jeeplus.modules.projectStatement.entity.ProjectSubstationImport;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+
+/**
+ * 材料库Dao接口
+ */
+@MyBatisDao
+public interface ProjectFinalAccountsDao extends CrudDao<ProjectFinalAccounts> {
+
+
+    /**
+     * 查询项目名称
+     * @param projectMaterialStorage
+     * @return
+     */
+    public String getProjectName(ProjectFinalAccounts projectMaterialStorage);
+
+    /**
+     * 查询同项目名称、价格、材料名称的数量
+     */
+    public Integer selectCountAboutProjectMaterialStorage(ProjectFinalAccountsImport projectMaterialStorage);
+
+    /**
+     * 根据信息查询数据
+     * @param projectMaterialStorage
+     * @return
+     */
+    List<ProjectFinalAccounts> getByInfo(ProjectFinalAccounts projectMaterialStorage);
+
+
+
+    /**
+     * 材料库处理的批量插入
+     */
+    Integer batchInsert(@Param("projectMaterialStorageImportList")List<ProjectFinalAccountsImport> projectMaterialStorageImportList);
+
+
+    void deleteListById(@Param("idList")List<String> idList);
+
+    List<ProjectFinalAccounts> getByIdList(@Param("idList")List<String> idList,@Param("createBy")String createBy);
+
+
+    /**
+     * 根据项目id查询数据
+     * @param projectId
+     * @return
+     */
+    List<ProjectFinalAccountsImport> getByProjectId(String projectId);
+
+
+    /**
+     * 根据项目id查询数据
+     * @param projectId
+     * @return
+     */
+    List<ProjectSubstationImport> getSubstationByProjectId(String projectId);
+
+
+    /**
+     * 材料库处理的批量插入
+     */
+    Integer saveUniqueInfo(ProjectFinalAccountsImport projectMaterialStorageImport);
+
+
+    /**
+     * 材料库处理的批量插入
+     */
+    Integer saveSubstationUniqueInfo(ProjectSubstationImport projectMaterialStorageImport);
+
+    /**
+     * 删除
+     * @param projectMaterialStorage
+     * @return
+     */
+    Integer deleteById(ProjectFinalAccounts projectMaterialStorage);
+}

+ 75 - 0
src/main/java/com/jeeplus/modules/projectStatement/dao/ProjectSubstationDao.java

@@ -0,0 +1,75 @@
+package com.jeeplus.modules.projectStatement.dao;
+
+
+
+import com.jeeplus.common.persistence.CrudDao;
+import com.jeeplus.common.persistence.annotation.MyBatisDao;
+import com.jeeplus.modules.projectStatement.entity.ProjectFinalAccounts;
+import com.jeeplus.modules.projectStatement.entity.ProjectFinalAccountsImport;
+import com.jeeplus.modules.projectStatement.entity.ProjectSubstationImport;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+
+/**
+ * 材料库Dao接口
+ */
+@MyBatisDao
+public interface ProjectSubstationDao extends CrudDao<ProjectSubstationImport> {
+
+
+    /**
+     * 查询项目名称
+     * @param projectMaterialStorage
+     * @return
+     */
+    public String getProjectName(ProjectSubstationImport projectMaterialStorage);
+
+    /**
+     * 查询同项目名称、价格、材料名称的数量
+     */
+    public Integer selectCountAboutProjectMaterialStorage(ProjectSubstationImport projectMaterialStorage);
+
+    /**
+     * 根据信息查询数据
+     * @param projectMaterialStorage
+     * @return
+     */
+    List<ProjectSubstationImport> getByInfo(ProjectSubstationImport projectMaterialStorage);
+
+
+
+    /**
+     * 材料库处理的批量插入
+     */
+    Integer batchInsert(@Param("projectMaterialStorageImportList")List<ProjectSubstationImport> projectMaterialStorageImportList);
+
+
+    void deleteListById(@Param("idList")List<String> idList);
+
+    List<ProjectSubstationImport> getByIdList(@Param("idList")List<String> idList,@Param("createBy")String createBy);
+
+
+    /**
+     * 根据项目id查询数据
+     * @param projectId
+     * @return
+     */
+    List<ProjectSubstationImport> getByProjectId(String projectId);
+
+
+
+    /**
+     * 材料库处理的批量插入
+     */
+    Integer saveUniqueInfo(ProjectSubstationImport projectMaterialStorageImport);
+
+
+    /**
+     * 删除
+     * @param projectMaterialStorage
+     * @return
+     */
+    Integer deleteById(ProjectSubstationImport projectMaterialStorage);
+}

+ 148 - 0
src/main/java/com/jeeplus/modules/projectStatement/entity/ProjectFinalAccounts.java

@@ -0,0 +1,148 @@
+package com.jeeplus.modules.projectStatement.entity;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.collect.Lists;
+import com.jeeplus.common.persistence.DataEntity;
+import com.jeeplus.common.utils.excel.annotation.ExcelField;
+import com.jeeplus.modules.projectStatement.utils.BigDecimalTwoDecimalSerializer;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+public class ProjectFinalAccounts extends DataEntity<ProjectFinalAccounts> {
+    // title="序号":对应Excel表头;type=0:允许导入导出;fieldType指定为String
+    @ExcelField(title = "序号", type = 0, fieldType = String.class)
+    private String number;
+
+    // title="项目或费用名称":对应Excel表头
+    @ExcelField(title = "项目或费用名称", type = 0)
+    private String projectOrCostName;
+
+    // title="金额":对应Excel表头;format可留空(数字无需格式)
+    @ExcelField(title = "金额", type = 0, fieldType = BigDecimal.class)
+    @JsonSerialize(using = BigDecimalTwoDecimalSerializer.class) // 关键:指定序列化逻辑
+    private BigDecimal settlementSum;
+
+    // title="备注":对应Excel表头;type=2:仅导入(可选)
+    @ExcelField(title = "备注", type = 2)
+    private String remarks;
+
+    private String projectNumber;   //项目编号
+    private String errorMessage ; //错误原因
+    private String projectId ; //项目id
+    private String projectName ; //项目名称
+    private String reportNumber ; //报告号
+
+    //材料信息数据表集合
+    private List<ProjectFinalAccountsImport> projectFinalAccountsList = Lists.newArrayList();
+    private List<ProjectSubstationImport> projectSubstationList = Lists.newArrayList();
+
+    public String getNumber() {
+        return number;
+    }
+
+    public void setNumber(String number) {
+        this.number = number;
+    }
+
+    public String getProjectName() {
+        return projectName;
+    }
+
+    public void setProjectName(String projectName) {
+        this.projectName = projectName;
+    }
+
+    public BigDecimal getSettlementSum() {
+        return settlementSum;
+    }
+
+    public void setSettlementSum(BigDecimal settlementSum) {
+        this.settlementSum = settlementSum;
+    }
+
+    @Override
+    public String getRemarks() {
+        return remarks;
+    }
+
+    @Override
+    public void setRemarks(String remarks) {
+        this.remarks = remarks;
+    }
+
+
+    public ProjectFinalAccounts(String number, String projectOrCostName, BigDecimal settlementSum, String remarks) {
+        this.number = number;
+        this.projectOrCostName = projectOrCostName;
+        this.settlementSum = settlementSum;
+        this.remarks = remarks;
+    }
+
+    public ProjectFinalAccounts(String id, String number, String projectOrCostName, BigDecimal settlementSum, String remarks) {
+        super(id);
+        this.number = number;
+        this.projectOrCostName = projectOrCostName;
+        this.settlementSum = settlementSum;
+        this.remarks = remarks;
+    }
+
+    public ProjectFinalAccounts() {
+    }
+
+    public List<ProjectFinalAccountsImport> getProjectFinalAccountsList() {
+        return projectFinalAccountsList;
+    }
+
+    public void setProjectFinalAccountsList(List<ProjectFinalAccountsImport> projectFinalAccountsList) {
+        this.projectFinalAccountsList = projectFinalAccountsList;
+    }
+
+    public String getProjectNumber() {
+        return projectNumber;
+    }
+
+    public void setProjectNumber(String projectNumber) {
+        this.projectNumber = projectNumber;
+    }
+
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+
+    public void setErrorMessage(String errorMessage) {
+        this.errorMessage = errorMessage;
+    }
+
+    public String getProjectId() {
+        return projectId;
+    }
+
+    public void setProjectId(String projectId) {
+        this.projectId = projectId;
+    }
+
+    public String getProjectOrCostName() {
+        return projectOrCostName;
+    }
+
+    public void setProjectOrCostName(String projectOrCostName) {
+        this.projectOrCostName = projectOrCostName;
+    }
+
+    public List<ProjectSubstationImport> getProjectSubstationList() {
+        return projectSubstationList;
+    }
+
+    public void setProjectSubstationList(List<ProjectSubstationImport> projectSubstationList) {
+        this.projectSubstationList = projectSubstationList;
+    }
+
+    public String getReportNumber() {
+        return reportNumber;
+    }
+
+    public void setReportNumber(String reportNumber) {
+        this.reportNumber = reportNumber;
+    }
+}

+ 129 - 0
src/main/java/com/jeeplus/modules/projectStatement/entity/ProjectFinalAccountsImport.java

@@ -0,0 +1,129 @@
+package com.jeeplus.modules.projectStatement.entity;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.collect.Lists;
+import com.jeeplus.common.persistence.DataEntity;
+import com.jeeplus.common.utils.excel.annotation.ExcelField;
+import com.jeeplus.modules.projectStatement.utils.BigDecimalTwoDecimalSerializer;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+public class ProjectFinalAccountsImport extends DataEntity<ProjectFinalAccountsImport> {
+    // title="序号":对应Excel表头;type=0:允许导入导出;fieldType指定为String
+    @ExcelField(title = "序号", type = 0, fieldType = String.class)
+    private String number;
+
+    // title="项目或费用名称":对应Excel表头
+    @ExcelField(title = "项目或费用名称", type = 0)
+    private String projectOrCostName;
+
+    // title="金额":对应Excel表头;format可留空(数字无需格式)
+    @ExcelField(title = "金额", type = 0, fieldType = BigDecimal.class)
+    @JsonSerialize(using = BigDecimalTwoDecimalSerializer.class) // 关键:指定序列化逻辑
+    private BigDecimal settlementSum;
+
+    // title="备注":对应Excel表头;type=2:仅导入(可选)
+    @ExcelField(title = "备注", type = 2)
+    private String remarks;
+
+    private String projectNumber;   //项目编号
+    private String errorMessage ; //错误原因
+    private String projectId ; //项目id
+    private String projectName ; //项目名称
+    private String distinctStr ; //用于去重用的临时参数
+
+
+    public String getNumber() {
+        return number;
+    }
+
+    public void setNumber(String number) {
+        this.number = number;
+    }
+
+    public String getProjectName() {
+        return projectName;
+    }
+
+    public void setProjectName(String projectName) {
+        this.projectName = projectName;
+    }
+
+    public BigDecimal getSettlementSum() {
+        return settlementSum;
+    }
+
+    public void setSettlementSum(BigDecimal settlementSum) {
+        this.settlementSum = settlementSum;
+    }
+
+    @Override
+    public String getRemarks() {
+        return remarks;
+    }
+
+    @Override
+    public void setRemarks(String remarks) {
+        this.remarks = remarks;
+    }
+
+
+    public ProjectFinalAccountsImport(String number, String projectOrCostName, BigDecimal settlementSum, String remarks) {
+        this.number = number;
+        this.projectOrCostName = projectOrCostName;
+        this.settlementSum = settlementSum;
+        this.remarks = remarks;
+    }
+
+    public ProjectFinalAccountsImport(String id, String number, String projectOrCostName, BigDecimal settlementSum, String remarks) {
+        super(id);
+        this.number = number;
+        this.projectOrCostName = projectOrCostName;
+        this.settlementSum = settlementSum;
+        this.remarks = remarks;
+    }
+
+    public ProjectFinalAccountsImport() {
+    }
+
+    public String getProjectNumber() {
+        return projectNumber;
+    }
+
+    public void setProjectNumber(String projectNumber) {
+        this.projectNumber = projectNumber;
+    }
+
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+
+    public void setErrorMessage(String errorMessage) {
+        this.errorMessage = errorMessage;
+    }
+
+    public String getProjectId() {
+        return projectId;
+    }
+
+    public void setProjectId(String projectId) {
+        this.projectId = projectId;
+    }
+
+    public String getProjectOrCostName() {
+        return projectOrCostName;
+    }
+
+    public void setProjectOrCostName(String projectOrCostName) {
+        this.projectOrCostName = projectOrCostName;
+    }
+
+    public String getDistinctStr() {
+        return distinctStr;
+    }
+
+    public void setDistinctStr(String distinctStr) {
+        this.distinctStr = distinctStr;
+    }
+}

+ 171 - 0
src/main/java/com/jeeplus/modules/projectStatement/entity/ProjectSubstationImport.java

@@ -0,0 +1,171 @@
+package com.jeeplus.modules.projectStatement.entity;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.jeeplus.common.persistence.DataEntity;
+import com.jeeplus.common.utils.excel.annotation.ExcelField;
+import com.jeeplus.modules.projectStatement.utils.BigDecimalTwoDecimalSerializer;
+
+import java.math.BigDecimal;
+
+public class ProjectSubstationImport extends DataEntity<ProjectSubstationImport> {
+    private static final long serialVersionUID = 1L;
+
+    // 关键:第3行“序号” + 第4行“序号” → 完整表头“序号/序号”
+    @ExcelField(title = "序号/序号", type = 0, fieldType = String.class)
+    private String number;
+
+    // 第3行“项目或费用名称” + 第4行“项目或费用名称” → 完整表头“项目或费用名称/项目或费用名称”
+    @ExcelField(title = "项目或费用名称/项目或费用名称", type = 0)
+    private String projectOrCostName;
+
+    // 第3行“金额” + 第4行“其中:直接工程费” → 完整表头“金额/其中:直接工程费”
+    @ExcelField(title = "金额/其中:直接工程费", type = 0, fieldType = BigDecimal.class)
+    @JsonSerialize(using = BigDecimalTwoDecimalSerializer.class) // 关键:指定序列化逻辑
+    private BigDecimal engineeringCost;
+
+    // 第3行“金额” + 第4行“其中:人工费” → 完整表头“金额/其中:人工费”
+    @ExcelField(title = "金额/其中:人工费", type = 0, fieldType = BigDecimal.class)
+    @JsonSerialize(using = BigDecimalTwoDecimalSerializer.class) // 关键:指定序列化逻辑
+    private BigDecimal laborCost;
+
+    // 第3行“金额” + 第4行“其中:暂估价材料费” → 完整表头“金额/其中:暂估价材料费”
+    @ExcelField(title = "金额/其中:暂估价材料费", type = 0, fieldType = BigDecimal.class)
+    @JsonSerialize(using = BigDecimalTwoDecimalSerializer.class) // 关键:指定序列化逻辑
+    private BigDecimal estimatedMaterialCost;
+
+    // 第3行“金额” + 第4行“合计” → 完整表头“金额/合计”
+    @ExcelField(title = "金额/合计", type = 0, fieldType = BigDecimal.class)
+    @JsonSerialize(using = BigDecimalTwoDecimalSerializer.class) // 关键:指定序列化逻辑
+    private BigDecimal totalCost;
+
+    // 第3行“备注” + 第4行“备注” → 完整表头“备注/备注”
+    @ExcelField(title = "备注/备注", type = 2)
+    private String remarks;
+
+
+    private String projectNumber;   //项目编号
+    private String errorMessage ; //错误原因
+    private String projectId ; //项目id
+    private String projectName ; //项目名称
+    private String distinctStr ; //用于去重用的临时参数
+    private String reportNumber ; //用于去重用的临时参数
+
+    public ProjectSubstationImport() {
+    }
+
+    public ProjectSubstationImport(String number, String projectOrCostName, BigDecimal engineeringCost, BigDecimal laborCost, BigDecimal estimatedMaterialCost, BigDecimal totalCost, String remarks) {
+        this.number = number;
+        this.projectOrCostName = projectOrCostName;
+        this.engineeringCost = engineeringCost;
+        this.laborCost = laborCost;
+        this.estimatedMaterialCost = estimatedMaterialCost;
+        this.totalCost = totalCost;
+        this.remarks = remarks;
+    }
+
+    public String getNumber() {
+        return number;
+    }
+
+    public void setNumber(String number) {
+        this.number = number;
+    }
+
+    public String getProjectOrCostName() {
+        return projectOrCostName;
+    }
+
+    public void setProjectOrCostName(String projectOrCostName) {
+        this.projectOrCostName = projectOrCostName;
+    }
+
+    public BigDecimal getEngineeringCost() {
+        return engineeringCost;
+    }
+
+    public void setEngineeringCost(BigDecimal engineeringCost) {
+        this.engineeringCost = engineeringCost;
+    }
+
+    public BigDecimal getLaborCost() {
+        return laborCost;
+    }
+
+    public void setLaborCost(BigDecimal laborCost) {
+        this.laborCost = laborCost;
+    }
+
+    public BigDecimal getEstimatedMaterialCost() {
+        return estimatedMaterialCost;
+    }
+
+    public void setEstimatedMaterialCost(BigDecimal estimatedMaterialCost) {
+        this.estimatedMaterialCost = estimatedMaterialCost;
+    }
+
+    public BigDecimal getTotalCost() {
+        return totalCost;
+    }
+
+    public void setTotalCost(BigDecimal totalCost) {
+        this.totalCost = totalCost;
+    }
+
+    @Override
+    public String getRemarks() {
+        return remarks;
+    }
+
+    @Override
+    public void setRemarks(String remarks) {
+        this.remarks = remarks;
+    }
+
+    public String getProjectNumber() {
+        return projectNumber;
+    }
+
+    public void setProjectNumber(String projectNumber) {
+        this.projectNumber = projectNumber;
+    }
+
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+
+    public void setErrorMessage(String errorMessage) {
+        this.errorMessage = errorMessage;
+    }
+
+    public String getProjectId() {
+        return projectId;
+    }
+
+    public void setProjectId(String projectId) {
+        this.projectId = projectId;
+    }
+
+    public String getProjectName() {
+        return projectName;
+    }
+
+    public void setProjectName(String projectName) {
+        this.projectName = projectName;
+    }
+
+    public String getDistinctStr() {
+        return distinctStr;
+    }
+
+    public void setDistinctStr(String distinctStr) {
+        this.distinctStr = distinctStr;
+    }
+
+    public String getReportNumber() {
+        return reportNumber;
+    }
+
+    public void setReportNumber(String reportNumber) {
+        this.reportNumber = reportNumber;
+    }
+}

+ 724 - 0
src/main/java/com/jeeplus/modules/projectStatement/service/ProjectFinalAccountsService.java

@@ -0,0 +1,724 @@
+package com.jeeplus.modules.projectStatement.service;
+
+
+import com.alibaba.fastjson.JSON;
+import com.google.common.collect.Lists;
+import com.jeeplus.common.persistence.Page;
+import com.jeeplus.common.service.CrudService;
+import com.jeeplus.common.utils.IdGen;
+import com.jeeplus.common.utils.JedisUtils;
+import com.jeeplus.common.utils.StringUtils;
+import com.jeeplus.modules.projectStatement.dao.ProjectFinalAccountsDao;
+import com.jeeplus.modules.projectStatement.entity.ProjectFinalAccounts;
+import com.jeeplus.modules.projectStatement.entity.ProjectFinalAccountsImport;
+import com.jeeplus.modules.projectStatement.entity.ProjectSubstationImport;
+import com.jeeplus.modules.ruralprojectrecords.dao.RuralProjectRecordsDao;
+import com.jeeplus.modules.sys.entity.User;
+import com.jeeplus.modules.sys.utils.UserUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import redis.clients.jedis.Jedis;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.*;
+
+/**
+ * 项目表Service
+ * @author gx
+ * @version 2022-6-28
+ */
+@Service
+@Transactional(readOnly = true)
+public class ProjectFinalAccountsService extends CrudService<ProjectFinalAccountsDao, ProjectFinalAccounts> {
+
+    @Autowired
+    private ProjectFinalAccountsDao projectFinalAccountsDao;
+    @Autowired
+    private RuralProjectRecordsDao ruralProjectRecordsDao;
+
+    public ProjectFinalAccounts get(String id){
+        ProjectFinalAccounts projectMaterialStorage = super.get(id);
+        processHtmlEntities(projectMaterialStorage);
+        return projectMaterialStorage;
+    }
+
+
+    public Integer update(ProjectFinalAccounts projectMaterialStorage){
+       return projectFinalAccountsDao.update(projectMaterialStorage);
+    }
+
+    /**
+     * 获取项目名字
+     */
+    public  String getProjectName(ProjectFinalAccounts projectMaterialStorage){
+       return projectFinalAccountsDao.getProjectName(projectMaterialStorage);
+    }
+
+    /**
+     * 查询同项目名称、价格、材料名称的数量是否只有一个
+     */
+    public Map qureyCountAboutProjectMaterialStorage(List<ProjectFinalAccountsImport> projectMaterialStorageImports){
+        int i =0;
+        HashMap map = new HashMap();
+        StringBuilder stringBuilder = new StringBuilder();
+        List<String> list = new ArrayList();
+        Set set = new HashSet();
+        Boolean flag = false;
+        //材料列表自身重复判断
+        for (ProjectFinalAccountsImport projectMaterialStorageImport: projectMaterialStorageImports){
+            StringBuilder stringBuilder2 = new StringBuilder();
+            String str = new String();
+            str = projectMaterialStorageImport.getProjectOrCostName()+projectMaterialStorageImport.getSettlementSum()+projectMaterialStorageImport.getNumber()+projectMaterialStorageImport.getRemarks();
+            list.add(str);
+            stringBuilder2.append(projectMaterialStorageImport.getProjectOrCostName()).append(projectMaterialStorageImport.getSettlementSum())
+                    .append(projectMaterialStorageImport.getNumber()).append(projectMaterialStorageImport.getRemarks());
+            set.add(str);
+            i++;
+        }
+//        如果列表长度和set长度不符合,代表有不符合条件的参数
+        if(projectMaterialStorageImports.size() != set.size()){
+            stringBuilder.append(" 数据中存在重复材料,无法进行添加");
+            map.put("failure",stringBuilder);
+            return map;
+        }
+//        数据库判断
+        List<String> materialNameList = Lists.newArrayList();
+        for(int j=0;j<projectMaterialStorageImports.size();j++){
+            //材料列表数据库重复判断
+            ProjectFinalAccountsImport projectMaterialStorageImport = projectMaterialStorageImports.get(j);
+            if(projectFinalAccountsDao.selectCountAboutProjectMaterialStorage(projectMaterialStorageImport)!=0){
+                materialNameList.add(projectMaterialStorageImport.getProjectOrCostName());
+                flag = true;
+            }
+        }
+        if(!flag){
+            //如果重复,重复的材料名称以String形式抛出到前端  join方法
+            map.put("successful",stringBuilder);
+            return  map;
+        }else {
+            String join = StringUtils.join(materialNameList, ",");
+            stringBuilder.append("材料 " + join + " 已存在,请勿重复添加");
+            map.put("failure",stringBuilder);
+            return map;
+        }
+    }
+
+
+
+    /**
+     * 查询同项目名称、价格、材料名称的数量是否只有一个
+     */
+    public Map<String, List<ProjectFinalAccountsImport>> distinctProjectMaterialStorage(
+            List<ProjectFinalAccountsImport> projectMaterialStorageImports, String projectId) {
+
+        HashMap<String, List<ProjectFinalAccountsImport>> map = new HashMap<>();
+
+        // 第一步:对本次导入数据进行去重(自身重复)
+        Set<String> seen = new HashSet<>();
+        List<ProjectFinalAccountsImport> uniqueList = new ArrayList<>();      // 本次去重后的有效值
+        List<ProjectFinalAccountsImport> duplicateList = new ArrayList<>();   // 本次自身重复值
+
+        for (ProjectFinalAccountsImport item : projectMaterialStorageImports) {
+            String distinctStr = item.getDistinctStr();
+
+            // 处理null值(视为重复)
+            if (distinctStr == null) {
+                duplicateList.add(item);
+                continue;
+            }
+
+            if (!seen.contains(distinctStr)) {
+                seen.add(distinctStr);
+                uniqueList.add(item);
+            } else {
+                duplicateList.add(item);
+            }
+        }
+
+
+        // 第二步:获取历史数据并处理其distinctStr
+        List<ProjectFinalAccountsImport> historyInfoList = dao.getByProjectId(projectId);
+        for (ProjectFinalAccountsImport info : historyInfoList) {
+            // 组合历史数据的distinctStr(材料名称+规格+含税价格)
+            String number = (info.getNumber() != null) ? info.getNumber() : "";
+            String name = (info.getProjectOrCostName() != null) ? info.getProjectOrCostName() : "";
+            String spec = (info.getSettlementSum() != null) ? info.getSettlementSum().toString() : "";
+            String remarks = (info.getRemarks() != null) ? info.getRemarks() : "";
+
+            info.setDistinctStr(number + "," + name + "," + spec + "," + remarks);
+        }
+
+        // 第三步:筛选历史数据中的distinctStr,用于比对
+        Set<String> historyDistinctSet = new HashSet<>();
+        for (ProjectFinalAccountsImport historyItem : historyInfoList) {
+            String historyDistinctStr = historyItem.getDistinctStr();
+            if (historyDistinctStr != null) {
+                historyDistinctSet.add(historyDistinctStr);
+            }
+        }
+
+        // 第四步:从uniqueList中移除与历史数据重复的项,并加入重复列表
+        // 使用迭代器支持边遍历边删除
+        Iterator<ProjectFinalAccountsImport> iterator = uniqueList.iterator();
+        while (iterator.hasNext()) {
+            ProjectFinalAccountsImport currentItem = iterator.next();
+            String currentDistinctStr = currentItem.getDistinctStr();
+
+            // 处理null值(从uniqueList移除并加入重复列表)
+            if (currentDistinctStr == null) {
+                iterator.remove();
+                duplicateList.add(currentItem);
+                continue;
+            }
+
+            // 与历史数据重复:从uniqueList移除并加入重复列表
+            if (historyDistinctSet.contains(currentDistinctStr)) {
+                iterator.remove();
+                duplicateList.add(currentItem);
+            }
+        }
+
+        // 1. 提取duplicateList中所有数据的唯一标识(distinctStr)
+        Set<String> duplicateDistinctSet = new HashSet<>();
+        for (ProjectFinalAccountsImport item : duplicateList) {
+            String distinctStr = item.getDistinctStr();
+            if (distinctStr != null) {
+                duplicateDistinctSet.add(distinctStr);
+            }
+        }
+
+        // 最终返回处理后的列表
+        map.put("uniqueList", uniqueList);      // 仅包含本次新增且不与历史重复的数据
+        map.put("duplicateList", duplicateList); // 包含自身重复和与历史重复的数据
+        return map;
+    }
+
+
+
+    /**
+     * 将列表存储到Redis的ZSET中
+     * @param key Redis键名
+     * @param list 要存储的ProjectMaterialStorageImport列表
+     */
+    public void saveSubstationListToZSet(String key, List<ProjectSubstationImport> list) {
+        if (list == null || list.isEmpty()) {
+            return;
+        }
+
+        Jedis jedis = null;
+        try {
+            jedis = getJedis();
+            // 先删除可能存在的旧数据
+            jedis.del(key);
+
+            // 准备要存储的元素和分数
+            Map<String, Double> elements = new HashMap<>(list.size());
+
+            // 以元素在列表中的索引作为score,保持原有顺序
+            for (int i = 0; i < list.size(); i++) {
+                ProjectSubstationImport item = list.get(i);
+                // 将实体类序列化为JSON字符串
+                String jsonStr = JSON.toJSONString(item);
+                // 使用索引作为score,确保顺序
+                elements.put(jsonStr, (double) i);
+            }
+
+            // 批量添加到ZSET
+            jedis.zadd(key, elements);
+
+            // 设置24小时过期时间
+            jedis.expire(key, EXPIRATION_SECONDS);
+        } finally {
+            if (jedis != null) {
+                jedis.close();
+            }
+        }
+    }
+
+    /**
+     * 查询同项目名称、价格、材料名称的数量是否只有一个
+     */
+    public Map<String, List<ProjectSubstationImport>> distinctProjectSubstation(List<ProjectSubstationImport> projectMaterialStorageImports, String projectId) {
+
+        HashMap<String, List<ProjectSubstationImport>> map = new HashMap<>();
+
+        // 第一步:对本次导入数据进行去重(自身重复)
+        Set<String> seen = new HashSet<>();
+        List<ProjectSubstationImport> uniqueList = new ArrayList<>();      // 本次去重后的有效值
+        List<ProjectSubstationImport> duplicateList = new ArrayList<>();   // 本次自身重复值
+
+        for (ProjectSubstationImport item : projectMaterialStorageImports) {
+            String distinctStr = item.getDistinctStr();
+
+            // 处理null值(视为重复)
+            if (distinctStr == null) {
+                duplicateList.add(item);
+                continue;
+            }
+
+            if (!seen.contains(distinctStr)) {
+                seen.add(distinctStr);
+                uniqueList.add(item);
+            } else {
+                duplicateList.add(item);
+            }
+        }
+
+
+        // 第二步:获取历史数据并处理其distinctStr
+        List<ProjectSubstationImport> historyInfoList = dao.getSubstationByProjectId(projectId);
+        for (ProjectSubstationImport info : historyInfoList) {
+            // 组合历史数据的distinctStr(材料名称+规格+含税价格)
+            String number = (info.getNumber() != null) ? info.getNumber() : "";
+            String name = (info.getProjectOrCostName() != null) ? info.getProjectOrCostName() : "";
+            String remarks = (info.getRemarks() != null) ? info.getRemarks() : "";
+
+            info.setDistinctStr(number + "," + name + "," + remarks);
+        }
+
+        // 第三步:筛选历史数据中的distinctStr,用于比对
+        Set<String> historyDistinctSet = new HashSet<>();
+        for (ProjectSubstationImport historyItem : historyInfoList) {
+            String historyDistinctStr = historyItem.getDistinctStr();
+            if (historyDistinctStr != null) {
+                historyDistinctSet.add(historyDistinctStr);
+            }
+        }
+
+        // 第四步:从uniqueList中移除与历史数据重复的项,并加入重复列表
+        // 使用迭代器支持边遍历边删除
+        Iterator<ProjectSubstationImport> iterator = uniqueList.iterator();
+        while (iterator.hasNext()) {
+            ProjectSubstationImport currentItem = iterator.next();
+            String currentDistinctStr = currentItem.getDistinctStr();
+
+            // 处理null值(从uniqueList移除并加入重复列表)
+            if (currentDistinctStr == null) {
+                iterator.remove();
+                duplicateList.add(currentItem);
+                continue;
+            }
+
+            // 与历史数据重复:从uniqueList移除并加入重复列表
+            if (historyDistinctSet.contains(currentDistinctStr)) {
+                iterator.remove();
+                duplicateList.add(currentItem);
+            }
+        }
+
+        // 1. 提取duplicateList中所有数据的唯一标识(distinctStr)
+        Set<String> duplicateDistinctSet = new HashSet<>();
+        for (ProjectSubstationImport item : duplicateList) {
+            String distinctStr = item.getDistinctStr();
+            if (distinctStr != null) {
+                duplicateDistinctSet.add(distinctStr);
+            }
+        }
+
+        // 最终返回处理后的列表
+        map.put("uniqueList", uniqueList);      // 仅包含本次新增且不与历史重复的数据
+        map.put("duplicateList", duplicateList); // 包含自身重复和与历史重复的数据
+        return map;
+    }
+
+
+    /**
+     * 修改项目表中的材料库状态信息
+     */
+    @Transactional(readOnly = false)
+    public Map<String,Object> saveSubstationUniqueInfo(ProjectSubstationImport info){
+        Map<String,Object> map = new HashMap<>();
+        try{
+            info.setId(IdGen.uuid());
+            info.preInsert();
+            projectFinalAccountsDao.saveSubstationUniqueInfo(info);
+            map.put("success",true);
+            map.put("message","材料保存成功");
+        }catch (Exception e){
+            info.setErrorMessage(e.getMessage());
+            map.put("success",false);
+            map.put("message",e.getMessage());
+        }
+        return map;
+    }
+
+
+
+    /**
+     * 逻辑删除
+     */
+    @Transactional(readOnly = false)
+    public Integer delectBylogic(ProjectFinalAccounts projectMaterialStorage){
+        return dao.deleteByLogic(projectMaterialStorage);
+    }
+
+    /**
+     * 逻辑删除
+     */
+    @Transactional(readOnly = false)
+    public Integer deleteById(ProjectFinalAccounts projectMaterialStorage){
+        return dao.deleteById(projectMaterialStorage);
+    }
+
+    /**
+     * 修改项目表中的材料库状态信息
+     */
+    @Transactional(readOnly = false)
+    public Map<String,Object> saveInfo(ProjectFinalAccounts projectMaterialStorage){
+        Map<String,Object> map = new HashMap<>();
+        //根据数据信息查询是否存在重复项
+        List<ProjectFinalAccounts> infoList = projectFinalAccountsDao.getByInfo(projectMaterialStorage);
+        if(infoList.size()>0){
+            map.put("success",false);
+            map.put("message","该材料已存在,请勿重复上传");
+            return map;
+        }else{
+            super.save(projectMaterialStorage);
+        }
+        map.put("success",true);
+        map.put("message","材料保存成功");
+        return map;
+    }
+
+    /**
+     * 修改项目表中的材料库状态信息
+     */
+    @Transactional(readOnly = false)
+    public Map<String,Object> saveUniqueInfo(ProjectFinalAccountsImport info){
+        Map<String,Object> map = new HashMap<>();
+        try{
+            info.setId(IdGen.uuid());
+            info.preInsert();
+            projectFinalAccountsDao.saveUniqueInfo(info);
+            map.put("success",true);
+            map.put("message","材料保存成功");
+        }catch (Exception e){
+            info.setErrorMessage(e.getMessage());
+            map.put("success",false);
+            map.put("message",e.getMessage());
+        }
+        return map;
+    }
+
+
+    /**
+     * 批量保存或更新
+     * @param
+     */
+    @Transactional(readOnly = false)
+    public void batchSave(List<ProjectFinalAccountsImport> projectMaterialStorageImports) {
+        for(ProjectFinalAccountsImport projectMaterialStorageImport:projectMaterialStorageImports){
+            projectMaterialStorageImport.setId(IdGen.uuid());
+            projectMaterialStorageImport.preInsert();
+        }
+        projectFinalAccountsDao.batchInsert(projectMaterialStorageImports);
+    }
+
+    /**
+     *分页方法
+     * @param page
+     * @param
+     * @return
+     */
+    @Override
+    public Page<ProjectFinalAccounts> findPage(Page<ProjectFinalAccounts> page, ProjectFinalAccounts projectMaterialStorage) {
+        int count = dao.queryCount(projectMaterialStorage);
+        page.setCount(count);
+        page.setCountFlag(false);
+        projectMaterialStorage.setPage(page);
+        List<ProjectFinalAccounts> projectMaterialStorageList = dao.findList(projectMaterialStorage);
+        for (ProjectFinalAccounts projectMaterialStorage1:projectMaterialStorageList){
+            User user=UserUtils.get(projectMaterialStorage1.getCreateBy().getId());
+            projectMaterialStorage1.setCreateBy(user);
+            processHtmlEntities(projectMaterialStorage1);
+        }
+        page.setList(projectMaterialStorageList);
+        return page;
+    }
+
+    /**
+     * 多条数据删除
+     * @param idList
+     */
+    @Transactional(readOnly = false)
+    public void deleteListById(List<String> idList){
+        dao.deleteListById(idList);
+    }
+
+    /**
+     * 根据id查询信息
+     * @param idList
+     */
+    public List<ProjectFinalAccounts> getByIdList(List<String> idList){
+        return dao.getByIdList(idList,UserUtils.getUser().getId());
+    }
+
+    // 获取Jedis实例(假设你的工具类是JedisUtils)
+    private Jedis getJedis() {
+        return JedisUtils.getResource();
+    }
+
+
+    // 过期时间:24小时(单位:秒)
+    private static final int EXPIRATION_SECONDS = 24 * 60 * 60;
+
+    /**
+     * 将列表存储到Redis的ZSET中
+     * @param key Redis键名
+     * @param list 要存储的ProjectMaterialStorageImport列表
+     */
+    public void saveListToZSet(String key, List<ProjectFinalAccountsImport> list) {
+        if (list == null || list.isEmpty()) {
+            return;
+        }
+
+        Jedis jedis = null;
+        try {
+            jedis = getJedis();
+            // 先删除可能存在的旧数据
+            jedis.del(key);
+
+            // 准备要存储的元素和分数
+            Map<String, Double> elements = new HashMap<>(list.size());
+
+            // 以元素在列表中的索引作为score,保持原有顺序
+            for (int i = 0; i < list.size(); i++) {
+                ProjectFinalAccountsImport item = list.get(i);
+                // 将实体类序列化为JSON字符串
+                String jsonStr = JSON.toJSONString(item);
+                // 使用索引作为score,确保顺序
+                elements.put(jsonStr, (double) i);
+            }
+
+            // 批量添加到ZSET
+            jedis.zadd(key, elements);
+
+            // 设置24小时过期时间
+            jedis.expire(key, EXPIRATION_SECONDS);
+        } finally {
+            if (jedis != null) {
+                jedis.close();
+            }
+        }
+    }
+
+    /**
+     * 从Redis的ZSET中获取列表
+     * @param key Redis键名
+     * @return 解析后的ProjectMaterialStorageImport列表
+     */
+    public List<ProjectFinalAccountsImport> getListFromZSet(String key) {
+        Jedis jedis = null;
+        try {
+            jedis = getJedis();
+            // 获取所有元素(按score排序,即原列表顺序)
+            List<String> jsonList = (List<String>) jedis.zrange(key, 0, -1);
+
+            // 反序列化为实体类列表
+            return jsonList.stream()
+                    .map(jsonStr -> JSON.parseObject(jsonStr, ProjectFinalAccountsImport.class))
+                    .collect(java.util.stream.Collectors.toList());
+        } finally {
+            if (jedis != null) {
+                jedis.close();
+            }
+        }
+    }
+
+    /**
+     * 从Redis获取指定key的ZSET数据并转换为List<ProjectFinalAccounts>
+     * @return 转换后的实体类列表
+     */
+    /**
+     * 从Redis获取指定key的ZSET数据并转换为List<ProjectFinalAccounts>
+     * 增加了HTML实体转换处理(如m&sup3; -> m³)
+     */
+    public List<ProjectFinalAccounts> convertFromRedis(String redisKey) {
+        Jedis jedis = null;
+        try {
+            // 验证参数
+            if (redisKey == null || redisKey.trim().isEmpty()) {
+                throw new IllegalArgumentException("Redis key不能为空");
+            }
+
+            // 获取Jedis实例
+            jedis = JedisUtils.getResource();
+
+            // 检查key是否存在
+            if (!jedis.exists(redisKey)) {
+                System.out.println("指定的Redis key不存在: " + redisKey);
+                return new ArrayList<>();
+            }
+
+            // 从ZSET中获取所有元素(按分数排序,保持原列表顺序)
+            Set<String> jsonSet = jedis.zrange(redisKey, 0, -1);
+            if (jsonSet == null || jsonSet.isEmpty()) {
+                System.out.println("Redis key对应的集合为空: " + redisKey);
+                return new ArrayList<>();
+            }
+
+            // 转换为List<ProjectFinalAccounts>并处理特殊字符
+            List<ProjectFinalAccounts> resultList = new ArrayList<>(jsonSet.size());
+            for (String jsonStr : jsonSet) {
+                // 跳过空字符串
+                if (jsonStr == null || jsonStr.trim().isEmpty()) {
+                    continue;
+                }
+
+                // 将JSON字符串反序列化为实体类
+                ProjectFinalAccounts material = JSON.parseObject(jsonStr, ProjectFinalAccounts.class);
+                if (material != null) {
+                    // 处理HTML实体转换
+                    processHtmlEntities(material);
+                    resultList.add(material);
+                }
+            }
+
+            return resultList;
+        } catch (Exception e) {
+            System.err.println("转换Redis数据出错: " + e.getMessage());
+            e.printStackTrace();
+            return new ArrayList<>();
+        } finally {
+            // 释放资源
+            if (jedis != null) {
+                try {
+                    jedis.close();
+                } catch (Exception e) {
+                    System.err.println("关闭Jedis连接出错: " + e.getMessage());
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * 从Redis获取指定key的ZSET数据并转换为List<ProjectFinalAccounts>
+     * @return 转换后的实体类列表
+     */
+    /**
+     * 从Redis获取指定key的ZSET数据并转换为List<ProjectFinalAccounts>
+     * 增加了HTML实体转换处理(如m&sup3; -> m³)
+     */
+    public List<ProjectSubstationImport> convertFromSubstationRedis(String redisKey) {
+        Jedis jedis = null;
+        try {
+            // 验证参数
+            if (redisKey == null || redisKey.trim().isEmpty()) {
+                throw new IllegalArgumentException("Redis key不能为空");
+            }
+
+            // 获取Jedis实例
+            jedis = JedisUtils.getResource();
+
+            // 检查key是否存在
+            if (!jedis.exists(redisKey)) {
+                System.out.println("指定的Redis key不存在: " + redisKey);
+                return new ArrayList<>();
+            }
+
+            // 从ZSET中获取所有元素(按分数排序,保持原列表顺序)
+            Set<String> jsonSet = jedis.zrange(redisKey, 0, -1);
+            if (jsonSet == null || jsonSet.isEmpty()) {
+                System.out.println("Redis key对应的集合为空: " + redisKey);
+                return new ArrayList<>();
+            }
+
+            // 转换为List<ProjectSubstationImport>并处理特殊字符
+            List<ProjectSubstationImport> resultList = new ArrayList<>(jsonSet.size());
+            for (String jsonStr : jsonSet) {
+                // 跳过空字符串
+                if (jsonStr == null || jsonStr.trim().isEmpty()) {
+                    continue;
+                }
+
+                // 将JSON字符串反序列化为实体类
+                ProjectSubstationImport material = JSON.parseObject(jsonStr, ProjectSubstationImport.class);
+                if (material != null) {
+                    // 处理HTML实体转换
+                    processHtmlSubstationEntities(material);
+                    resultList.add(material);
+                }
+            }
+
+            return resultList;
+        } catch (Exception e) {
+            System.err.println("转换Redis数据出错: " + e.getMessage());
+            e.printStackTrace();
+            return new ArrayList<>();
+        } finally {
+            // 释放资源
+            if (jedis != null) {
+                try {
+                    jedis.close();
+                } catch (Exception e) {
+                    System.err.println("关闭Jedis连接出错: " + e.getMessage());
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * 处理实体类中的HTML实体编码(如m&sup3; -> m³)
+     */
+    public void processHtmlEntities(ProjectFinalAccounts material) {
+        if (material == null) {
+            return;
+        }
+        // 处理材料名称
+        material.setProjectOrCostName(convertHtmlEntity(material.getProjectOrCostName()));
+        // 处理规格型号
+        //material.setSettlementSum(convertHtmlEntity(material.getSettlementSum()));
+        material.setNumber(convertHtmlEntity(material.getNumber()));
+        material.setRemarks(convertHtmlEntity(material.getRemarks()));
+        material.setErrorMessage(convertHtmlEntity(material.getErrorMessage()));
+    }
+
+    /**
+     * 处理实体类中的HTML实体编码(如m&sup3; -> m³)
+     */
+    public void processHtmlSubstationEntities(ProjectSubstationImport material) {
+        if (material == null) {
+            return;
+        }
+        // 处理材料名称
+        material.setProjectOrCostName(convertHtmlEntity(material.getProjectOrCostName()));
+        // 处理规格型号
+        //material.setSettlementSum(convertHtmlEntity(material.getSettlementSum()));
+        material.setNumber(convertHtmlEntity(material.getNumber()));
+        material.setRemarks(convertHtmlEntity(material.getRemarks()));
+        material.setErrorMessage(convertHtmlEntity(material.getErrorMessage()));
+    }
+
+    /**
+     * 转换HTML实体编码为对应的Unicode字符
+     */
+    private String convertHtmlEntity(String input) {
+        if (input == null) {
+            return "";
+        }
+
+        // 替换常见的HTML实体
+        return input.replace("&sup3;", "³")    // 立方符号
+                .replace("&sup2;", "²")    // 平方符号
+                .replace("&amp;", "&")     // &符号
+                .replace("&lt;", "<")      // 小于号
+                .replace("&gt;", ">")      // 大于号
+                .replace("&quot;", "\"")   // 双引号
+                .replace("&apos;", "'");   // 单引号
+    }
+
+    /**
+     * 删除Redis中指定key的数据
+     * @param key 要删除的key
+     */
+    public void deleteRedisKey(String key) {
+        // 获取Jedis实例
+        Jedis jedis = JedisUtils.getResource();
+        jedis.del(key);
+    }
+}
+

+ 639 - 0
src/main/java/com/jeeplus/modules/projectStatement/service/ProjectSubstationService.java

@@ -0,0 +1,639 @@
+package com.jeeplus.modules.projectStatement.service;
+
+
+import com.alibaba.fastjson.JSON;
+import com.google.common.collect.Lists;
+import com.jeeplus.common.persistence.Page;
+import com.jeeplus.common.service.CrudService;
+import com.jeeplus.common.utils.IdGen;
+import com.jeeplus.common.utils.JedisUtils;
+import com.jeeplus.common.utils.StringUtils;
+import com.jeeplus.modules.projectStatement.dao.ProjectFinalAccountsDao;
+import com.jeeplus.modules.projectStatement.dao.ProjectSubstationDao;
+import com.jeeplus.modules.projectStatement.entity.ProjectFinalAccounts;
+import com.jeeplus.modules.projectStatement.entity.ProjectFinalAccountsImport;
+import com.jeeplus.modules.projectStatement.entity.ProjectSubstationImport;
+import com.jeeplus.modules.ruralprojectrecords.dao.RuralProjectRecordsDao;
+import com.jeeplus.modules.sys.entity.User;
+import com.jeeplus.modules.sys.utils.UserUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import redis.clients.jedis.Jedis;
+
+import java.util.*;
+
+/**
+ * 项目表Service
+ * @author gx
+ * @version 2022-6-28
+ */
+@Service
+@Transactional(readOnly = true)
+public class ProjectSubstationService extends CrudService<ProjectSubstationDao, ProjectSubstationImport> {
+
+    @Autowired
+    private ProjectSubstationDao projectSubstationDao;
+    @Autowired
+    private RuralProjectRecordsDao ruralProjectRecordsDao;
+
+    public ProjectSubstationImport get(String id){
+        ProjectSubstationImport projectMaterialStorage = super.get(id);
+        processHtmlSubstationEntities(projectMaterialStorage);
+        return projectMaterialStorage;
+    }
+
+
+    public Integer update(ProjectSubstationImport projectMaterialStorage){
+       return projectSubstationDao.update(projectMaterialStorage);
+    }
+
+    /**
+     * 获取项目名字
+     */
+    public  String getProjectName(ProjectSubstationImport projectMaterialStorage){
+       return projectSubstationDao.getProjectName(projectMaterialStorage);
+    }
+
+    /**
+     * 查询同项目名称、价格、材料名称的数量是否只有一个
+     */
+    public Map qureyCountAboutProjectMaterialStorage(List<ProjectSubstationImport> projectMaterialStorageImports){
+        int i =0;
+        HashMap map = new HashMap();
+        StringBuilder stringBuilder = new StringBuilder();
+        List<String> list = new ArrayList();
+        Set set = new HashSet();
+        Boolean flag = false;
+        //材料列表自身重复判断
+        for (ProjectSubstationImport projectMaterialStorageImport: projectMaterialStorageImports){
+            StringBuilder stringBuilder2 = new StringBuilder();
+            String str = new String();
+            str = projectMaterialStorageImport.getProjectOrCostName();
+            list.add(str);
+            stringBuilder2.append(projectMaterialStorageImport.getProjectOrCostName());
+            set.add(str);
+            i++;
+        }
+//        如果列表长度和set长度不符合,代表有不符合条件的参数
+        if(projectMaterialStorageImports.size() != set.size()){
+            stringBuilder.append(" 数据中存在重复材料,无法进行添加");
+            map.put("failure",stringBuilder);
+            return map;
+        }
+//        数据库判断
+        List<String> materialNameList = Lists.newArrayList();
+        for(int j=0;j<projectMaterialStorageImports.size();j++){
+            //材料列表数据库重复判断
+            ProjectSubstationImport projectMaterialStorageImport = projectMaterialStorageImports.get(j);
+            if(projectSubstationDao.selectCountAboutProjectMaterialStorage(projectMaterialStorageImport)!=0){
+                materialNameList.add(projectMaterialStorageImport.getProjectOrCostName());
+                flag = true;
+            }
+        }
+        if(!flag){
+            //如果重复,重复的材料名称以String形式抛出到前端  join方法
+            map.put("successful",stringBuilder);
+            return  map;
+        }else {
+            String join = StringUtils.join(materialNameList, ",");
+            stringBuilder.append("材料 " + join + " 已存在,请勿重复添加");
+            map.put("failure",stringBuilder);
+            return map;
+        }
+    }
+
+
+
+    /**
+     * 查询同项目名称、价格、材料名称的数量是否只有一个
+     */
+    public Map<String, List<ProjectSubstationImport>> distinctProjectMaterialStorage(
+            List<ProjectSubstationImport> projectMaterialStorageImports, String projectId) {
+
+        HashMap<String, List<ProjectSubstationImport>> map = new HashMap<>();
+
+        // 第一步:对本次导入数据进行去重(自身重复)
+        Set<String> seen = new HashSet<>();
+        List<ProjectSubstationImport> uniqueList = new ArrayList<>();      // 本次去重后的有效值
+        List<ProjectSubstationImport> duplicateList = new ArrayList<>();   // 本次自身重复值
+
+        for (ProjectSubstationImport item : projectMaterialStorageImports) {
+            String distinctStr = item.getDistinctStr();
+
+            // 处理null值(视为重复)
+            if (distinctStr == null) {
+                duplicateList.add(item);
+                continue;
+            }
+
+            if (!seen.contains(distinctStr)) {
+                seen.add(distinctStr);
+                uniqueList.add(item);
+            } else {
+                duplicateList.add(item);
+            }
+        }
+
+
+        // 第二步:获取历史数据并处理其distinctStr
+        List<ProjectSubstationImport> historyInfoList = dao.getByProjectId(projectId);
+        for (ProjectSubstationImport info : historyInfoList) {
+            // 组合历史数据的distinctStr(材料名称+规格+含税价格)
+            String number = (info.getNumber() != null) ? info.getNumber() : "";
+            String name = (info.getProjectOrCostName() != null) ? info.getProjectOrCostName() : "";
+            String remarks = (info.getRemarks() != null) ? info.getRemarks() : "";
+
+            info.setDistinctStr(number + "," + name + "," + remarks);
+        }
+
+        // 第三步:筛选历史数据中的distinctStr,用于比对
+        Set<String> historyDistinctSet = new HashSet<>();
+        for (ProjectSubstationImport historyItem : historyInfoList) {
+            String historyDistinctStr = historyItem.getDistinctStr();
+            if (historyDistinctStr != null) {
+                historyDistinctSet.add(historyDistinctStr);
+            }
+        }
+
+        // 第四步:从uniqueList中移除与历史数据重复的项,并加入重复列表
+        // 使用迭代器支持边遍历边删除
+        Iterator<ProjectSubstationImport> iterator = uniqueList.iterator();
+        while (iterator.hasNext()) {
+            ProjectSubstationImport currentItem = iterator.next();
+            String currentDistinctStr = currentItem.getDistinctStr();
+
+            // 处理null值(从uniqueList移除并加入重复列表)
+            if (currentDistinctStr == null) {
+                iterator.remove();
+                duplicateList.add(currentItem);
+                continue;
+            }
+
+            // 与历史数据重复:从uniqueList移除并加入重复列表
+            if (historyDistinctSet.contains(currentDistinctStr)) {
+                iterator.remove();
+                duplicateList.add(currentItem);
+            }
+        }
+
+        // 1. 提取duplicateList中所有数据的唯一标识(distinctStr)
+        Set<String> duplicateDistinctSet = new HashSet<>();
+        for (ProjectSubstationImport item : duplicateList) {
+            String distinctStr = item.getDistinctStr();
+            if (distinctStr != null) {
+                duplicateDistinctSet.add(distinctStr);
+            }
+        }
+
+        // 最终返回处理后的列表
+        map.put("uniqueList", uniqueList);      // 仅包含本次新增且不与历史重复的数据
+        map.put("duplicateList", duplicateList); // 包含自身重复和与历史重复的数据
+        return map;
+    }
+
+
+
+    /**
+     * 将列表存储到Redis的ZSET中
+     * @param key Redis键名
+     * @param list 要存储的ProjectMaterialStorageImport列表
+     */
+    public void saveSubstationListToZSet(String key, List<ProjectSubstationImport> list) {
+        if (list == null || list.isEmpty()) {
+            return;
+        }
+
+        Jedis jedis = null;
+        try {
+            jedis = getJedis();
+            // 先删除可能存在的旧数据
+            jedis.del(key);
+
+            // 准备要存储的元素和分数
+            Map<String, Double> elements = new HashMap<>(list.size());
+
+            // 以元素在列表中的索引作为score,保持原有顺序
+            for (int i = 0; i < list.size(); i++) {
+                ProjectSubstationImport item = list.get(i);
+                // 将实体类序列化为JSON字符串
+                String jsonStr = JSON.toJSONString(item);
+                // 使用索引作为score,确保顺序
+                elements.put(jsonStr, (double) i);
+            }
+
+            // 批量添加到ZSET
+            jedis.zadd(key, elements);
+
+            // 设置24小时过期时间
+            jedis.expire(key, EXPIRATION_SECONDS);
+        } finally {
+            if (jedis != null) {
+                jedis.close();
+            }
+        }
+    }
+
+    /**
+     * 查询同项目名称、价格、材料名称的数量是否只有一个
+     */
+    public Map<String, List<ProjectSubstationImport>> distinctProjectSubstation(List<ProjectSubstationImport> projectMaterialStorageImports, String projectId) {
+
+        HashMap<String, List<ProjectSubstationImport>> map = new HashMap<>();
+
+        // 第一步:对本次导入数据进行去重(自身重复)
+        Set<String> seen = new HashSet<>();
+        List<ProjectSubstationImport> uniqueList = new ArrayList<>();      // 本次去重后的有效值
+        List<ProjectSubstationImport> duplicateList = new ArrayList<>();   // 本次自身重复值
+
+        for (ProjectSubstationImport item : projectMaterialStorageImports) {
+            String distinctStr = item.getDistinctStr();
+
+            // 处理null值(视为重复)
+            if (distinctStr == null) {
+                duplicateList.add(item);
+                continue;
+            }
+
+            if (!seen.contains(distinctStr)) {
+                seen.add(distinctStr);
+                uniqueList.add(item);
+            } else {
+                duplicateList.add(item);
+            }
+        }
+
+
+        // 第二步:获取历史数据并处理其distinctStr
+        List<ProjectSubstationImport> historyInfoList = dao.getByProjectId(projectId);
+        for (ProjectSubstationImport info : historyInfoList) {
+            // 组合历史数据的distinctStr(材料名称+规格+含税价格)
+            String number = (info.getNumber() != null) ? info.getNumber() : "";
+            String name = (info.getProjectOrCostName() != null) ? info.getProjectOrCostName() : "";
+            String remarks = (info.getRemarks() != null) ? info.getRemarks() : "";
+
+            info.setDistinctStr(number + "," + name + "," + remarks);
+        }
+
+        // 第三步:筛选历史数据中的distinctStr,用于比对
+        Set<String> historyDistinctSet = new HashSet<>();
+        for (ProjectSubstationImport historyItem : historyInfoList) {
+            String historyDistinctStr = historyItem.getDistinctStr();
+            if (historyDistinctStr != null) {
+                historyDistinctSet.add(historyDistinctStr);
+            }
+        }
+
+        // 第四步:从uniqueList中移除与历史数据重复的项,并加入重复列表
+        // 使用迭代器支持边遍历边删除
+        Iterator<ProjectSubstationImport> iterator = uniqueList.iterator();
+        while (iterator.hasNext()) {
+            ProjectSubstationImport currentItem = iterator.next();
+            String currentDistinctStr = currentItem.getDistinctStr();
+
+            // 处理null值(从uniqueList移除并加入重复列表)
+            if (currentDistinctStr == null) {
+                iterator.remove();
+                duplicateList.add(currentItem);
+                continue;
+            }
+
+            // 与历史数据重复:从uniqueList移除并加入重复列表
+            if (historyDistinctSet.contains(currentDistinctStr)) {
+                iterator.remove();
+                duplicateList.add(currentItem);
+            }
+        }
+
+        // 1. 提取duplicateList中所有数据的唯一标识(distinctStr)
+        Set<String> duplicateDistinctSet = new HashSet<>();
+        for (ProjectSubstationImport item : duplicateList) {
+            String distinctStr = item.getDistinctStr();
+            if (distinctStr != null) {
+                duplicateDistinctSet.add(distinctStr);
+            }
+        }
+
+        // 最终返回处理后的列表
+        map.put("uniqueList", uniqueList);      // 仅包含本次新增且不与历史重复的数据
+        map.put("duplicateList", duplicateList); // 包含自身重复和与历史重复的数据
+        return map;
+    }
+
+
+    /**
+     * 修改项目表中的材料库状态信息
+     */
+    @Transactional(readOnly = false)
+    public Map<String,Object> saveSubstationUniqueInfo(ProjectSubstationImport info){
+        Map<String,Object> map = new HashMap<>();
+        try{
+            info.setId(IdGen.uuid());
+            info.preInsert();
+            projectSubstationDao.saveUniqueInfo(info);
+            map.put("success",true);
+            map.put("message","材料保存成功");
+        }catch (Exception e){
+            info.setErrorMessage(e.getMessage());
+            map.put("success",false);
+            map.put("message",e.getMessage());
+        }
+        return map;
+    }
+
+
+
+    /**
+     * 逻辑删除
+     */
+    @Transactional(readOnly = false)
+    public Integer delectBylogic(ProjectSubstationImport projectMaterialStorage){
+        return dao.deleteByLogic(projectMaterialStorage);
+    }
+
+    /**
+     * 逻辑删除
+     */
+    @Transactional(readOnly = false)
+    public Integer deleteById(ProjectSubstationImport projectMaterialStorage){
+        return dao.deleteById(projectMaterialStorage);
+    }
+
+    /**
+     * 修改项目表中的材料库状态信息
+     */
+    @Transactional(readOnly = false)
+    public Map<String,Object> saveInfo(ProjectSubstationImport projectMaterialStorage){
+        Map<String,Object> map = new HashMap<>();
+        //根据数据信息查询是否存在重复项
+        List<ProjectSubstationImport> infoList = projectSubstationDao.getByInfo(projectMaterialStorage);
+        if(infoList.size()>0){
+            map.put("success",false);
+            map.put("message","该材料已存在,请勿重复上传");
+            return map;
+        }else{
+            super.save(projectMaterialStorage);
+        }
+        map.put("success",true);
+        map.put("message","材料保存成功");
+        return map;
+    }
+
+    /**
+     * 修改项目表中的材料库状态信息
+     */
+    @Transactional(readOnly = false)
+    public Map<String,Object> saveUniqueInfo(ProjectSubstationImport info){
+        Map<String,Object> map = new HashMap<>();
+        try{
+            info.setId(IdGen.uuid());
+            info.preInsert();
+            projectSubstationDao.saveUniqueInfo(info);
+            map.put("success",true);
+            map.put("message","材料保存成功");
+        }catch (Exception e){
+            info.setErrorMessage(e.getMessage());
+            map.put("success",false);
+            map.put("message",e.getMessage());
+        }
+        return map;
+    }
+
+
+    /**
+     * 批量保存或更新
+     * @param
+     */
+    @Transactional(readOnly = false)
+    public void batchSave(List<ProjectSubstationImport> projectMaterialStorageImports) {
+        for(ProjectSubstationImport projectMaterialStorageImport:projectMaterialStorageImports){
+            projectMaterialStorageImport.setId(IdGen.uuid());
+            projectMaterialStorageImport.preInsert();
+        }
+        projectSubstationDao.batchInsert(projectMaterialStorageImports);
+    }
+
+    /**
+     *分页方法
+     * @param page
+     * @param
+     * @return
+     */
+    @Override
+    public Page<ProjectSubstationImport> findPage(Page<ProjectSubstationImport> page, ProjectSubstationImport projectMaterialStorage) {
+        int count = dao.queryCount(projectMaterialStorage);
+        page.setCount(count);
+        page.setCountFlag(false);
+        projectMaterialStorage.setPage(page);
+        List<ProjectSubstationImport> projectMaterialStorageList = dao.findList(projectMaterialStorage);
+        for (ProjectSubstationImport projectMaterialStorage1:projectMaterialStorageList){
+            User user=UserUtils.get(projectMaterialStorage1.getCreateBy().getId());
+            projectMaterialStorage1.setCreateBy(user);
+            processHtmlSubstationEntities(projectMaterialStorage1);
+        }
+        page.setList(projectMaterialStorageList);
+        return page;
+    }
+
+    /**
+     * 多条数据删除
+     * @param idList
+     */
+    @Transactional(readOnly = false)
+    public void deleteListById(List<String> idList){
+        dao.deleteListById(idList);
+    }
+
+    /**
+     * 根据id查询信息
+     * @param idList
+     */
+    public List<ProjectSubstationImport> getByIdList(List<String> idList){
+        return dao.getByIdList(idList,UserUtils.getUser().getId());
+    }
+
+    // 获取Jedis实例(假设你的工具类是JedisUtils)
+    private Jedis getJedis() {
+        return JedisUtils.getResource();
+    }
+
+
+    // 过期时间:24小时(单位:秒)
+    private static final int EXPIRATION_SECONDS = 24 * 60 * 60;
+
+    /**
+     * 将列表存储到Redis的ZSET中
+     * @param key Redis键名
+     * @param list 要存储的ProjectMaterialStorageImport列表
+     */
+    public void saveListToZSet(String key, List<ProjectSubstationImport> list) {
+        if (list == null || list.isEmpty()) {
+            return;
+        }
+
+        Jedis jedis = null;
+        try {
+            jedis = getJedis();
+            // 先删除可能存在的旧数据
+            jedis.del(key);
+
+            // 准备要存储的元素和分数
+            Map<String, Double> elements = new HashMap<>(list.size());
+
+            // 以元素在列表中的索引作为score,保持原有顺序
+            for (int i = 0; i < list.size(); i++) {
+                ProjectSubstationImport item = list.get(i);
+                // 将实体类序列化为JSON字符串
+                String jsonStr = JSON.toJSONString(item);
+                // 使用索引作为score,确保顺序
+                elements.put(jsonStr, (double) i);
+            }
+
+            // 批量添加到ZSET
+            jedis.zadd(key, elements);
+
+            // 设置24小时过期时间
+            jedis.expire(key, EXPIRATION_SECONDS);
+        } finally {
+            if (jedis != null) {
+                jedis.close();
+            }
+        }
+    }
+
+    /**
+     * 从Redis的ZSET中获取列表
+     * @param key Redis键名
+     * @return 解析后的ProjectMaterialStorageImport列表
+     */
+    public List<ProjectSubstationImport> getListFromZSet(String key) {
+        Jedis jedis = null;
+        try {
+            jedis = getJedis();
+            // 获取所有元素(按score排序,即原列表顺序)
+            List<String> jsonList = (List<String>) jedis.zrange(key, 0, -1);
+
+            // 反序列化为实体类列表
+            return jsonList.stream()
+                    .map(jsonStr -> JSON.parseObject(jsonStr, ProjectSubstationImport.class))
+                    .collect(java.util.stream.Collectors.toList());
+        } finally {
+            if (jedis != null) {
+                jedis.close();
+            }
+        }
+    }
+
+
+    /**
+     * 从Redis获取指定key的ZSET数据并转换为List<ProjectSubstationImport>
+     * @return 转换后的实体类列表
+     */
+    /**
+     * 从Redis获取指定key的ZSET数据并转换为List<ProjectSubstationImport>
+     * 增加了HTML实体转换处理(如m&sup3; -> m³)
+     */
+    public List<ProjectSubstationImport> convertFromSubstationRedis(String redisKey) {
+        Jedis jedis = null;
+        try {
+            // 验证参数
+            if (redisKey == null || redisKey.trim().isEmpty()) {
+                throw new IllegalArgumentException("Redis key不能为空");
+            }
+
+            // 获取Jedis实例
+            jedis = JedisUtils.getResource();
+
+            // 检查key是否存在
+            if (!jedis.exists(redisKey)) {
+                System.out.println("指定的Redis key不存在: " + redisKey);
+                return new ArrayList<>();
+            }
+
+            // 从ZSET中获取所有元素(按分数排序,保持原列表顺序)
+            Set<String> jsonSet = jedis.zrange(redisKey, 0, -1);
+            if (jsonSet == null || jsonSet.isEmpty()) {
+                System.out.println("Redis key对应的集合为空: " + redisKey);
+                return new ArrayList<>();
+            }
+
+            // 转换为List<ProjectSubstationImport>并处理特殊字符
+            List<ProjectSubstationImport> resultList = new ArrayList<>(jsonSet.size());
+            for (String jsonStr : jsonSet) {
+                // 跳过空字符串
+                if (jsonStr == null || jsonStr.trim().isEmpty()) {
+                    continue;
+                }
+
+                // 将JSON字符串反序列化为实体类
+                ProjectSubstationImport material = JSON.parseObject(jsonStr, ProjectSubstationImport.class);
+                if (material != null) {
+                    // 处理HTML实体转换
+                    processHtmlSubstationEntities(material);
+                    resultList.add(material);
+                }
+            }
+
+            return resultList;
+        } catch (Exception e) {
+            System.err.println("转换Redis数据出错: " + e.getMessage());
+            e.printStackTrace();
+            return new ArrayList<>();
+        } finally {
+            // 释放资源
+            if (jedis != null) {
+                try {
+                    jedis.close();
+                } catch (Exception e) {
+                    System.err.println("关闭Jedis连接出错: " + e.getMessage());
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * 处理实体类中的HTML实体编码(如m&sup3; -> m³)
+     */
+    public void processHtmlSubstationEntities(ProjectSubstationImport material) {
+        if (material == null) {
+            return;
+        }
+        // 处理材料名称
+        material.setProjectOrCostName(convertHtmlEntity(material.getProjectOrCostName()));
+        // 处理规格型号
+        //material.setSettlementSum(convertHtmlEntity(material.getSettlementSum()));
+        material.setNumber(convertHtmlEntity(material.getNumber()));
+        material.setRemarks(convertHtmlEntity(material.getRemarks()));
+        material.setErrorMessage(convertHtmlEntity(material.getErrorMessage()));
+    }
+
+    /**
+     * 转换HTML实体编码为对应的Unicode字符
+     */
+    private String convertHtmlEntity(String input) {
+        if (input == null) {
+            return "";
+        }
+
+        // 替换常见的HTML实体
+        return input.replace("&sup3;", "³")    // 立方符号
+                .replace("&sup2;", "²")    // 平方符号
+                .replace("&amp;", "&")     // &符号
+                .replace("&lt;", "<")      // 小于号
+                .replace("&gt;", ">")      // 大于号
+                .replace("&quot;", "\"")   // 双引号
+                .replace("&apos;", "'");   // 单引号
+    }
+
+    /**
+     * 删除Redis中指定key的数据
+     * @param key 要删除的key
+     */
+    public void deleteRedisKey(String key) {
+        // 获取Jedis实例
+        Jedis jedis = JedisUtils.getResource();
+        jedis.del(key);
+    }
+}
+

+ 26 - 0
src/main/java/com/jeeplus/modules/projectStatement/utils/BigDecimalTwoDecimalSerializer.java

@@ -0,0 +1,26 @@
+package com.jeeplus.modules.projectStatement.utils;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+/**
+ * 自定义BigDecimal序列化器,非空时保留两位小数(四舍五入)
+ */
+public class BigDecimalTwoDecimalSerializer extends JsonSerializer<BigDecimal> {
+
+    @Override
+    public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+        if (value != null) {
+            // 保留两位小数,使用四舍五入模式(HALF_UP)
+            BigDecimal scaledValue = value.setScale(2, RoundingMode.HALF_UP);
+            gen.writeNumber(scaledValue);
+        } else {
+            // 为空时直接返回null
+            gen.writeNull();
+        }
+    }
+}

+ 971 - 0
src/main/java/com/jeeplus/modules/projectStatement/web/ProjectFinalAccountsController.java

@@ -0,0 +1,971 @@
+package com.jeeplus.modules.projectStatement.web;
+
+
+import com.google.common.collect.Lists;
+import com.jeeplus.common.config.Global;
+import com.jeeplus.common.json.AjaxJson;
+import com.jeeplus.common.persistence.Page;
+import com.jeeplus.common.utils.*;
+import com.jeeplus.common.utils.excel.ExportMultipleTabsExcel;
+import com.jeeplus.common.utils.excel.utils.ExcelImportBySheetName;
+import com.jeeplus.common.web.BaseController;
+import com.jeeplus.modules.projectStatement.entity.ProjectFinalAccounts;
+import com.jeeplus.modules.projectStatement.entity.ProjectFinalAccountsImport;
+import com.jeeplus.modules.projectStatement.entity.ProjectSubstationImport;
+import com.jeeplus.modules.projectStatement.service.ProjectFinalAccountsService;
+import com.jeeplus.modules.projectrecord.entity.ProjectRecords;
+import com.jeeplus.modules.projectrecord.enums.ProjectStatusEnum;
+import com.jeeplus.modules.projectrecord.service.ProjectRecordsService;
+import com.jeeplus.modules.ruralprojectrecords.entity.RuralProjectRecords;
+import com.jeeplus.modules.ruralprojectrecords.service.RuralProjectRecordsService;
+import com.jeeplus.modules.sys.entity.MainDictDetail;
+import com.jeeplus.modules.sys.entity.User;
+import com.jeeplus.modules.sys.utils.DictUtils;
+import com.jeeplus.modules.sys.utils.UserUtils;
+import com.jeeplus.modules.workreceiptsregister.entity.ResponseEntity;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.*;
+
+
+@Controller
+@RequestMapping(value = "${adminPath}/project/projectFinalAccounts")
+public class ProjectFinalAccountsController extends BaseController {
+    @Autowired
+    private ProjectFinalAccountsService projectFinalAccountsService;
+    @Autowired
+    private RuralProjectRecordsService ruralProjectRecordsService;
+    @Autowired
+    protected ProjectRecordsService projectRecordsService;
+
+    @ModelAttribute
+    public ProjectFinalAccounts get(@RequestParam(required = false) String id){
+//        创建一个ProjectMaterialStorage
+        ProjectFinalAccounts entity = null;
+//        对于id进行是否为空的判断,如果不会空,就通过get方法获取材料库对象
+        if(StringUtils.isNotBlank(id)){
+            entity = projectFinalAccountsService.get(id);
+        }
+        if(entity == null){
+            entity = new ProjectFinalAccounts();
+        }
+//        返回ProjectMaterialStorage
+        return entity;
+
+    }
+
+
+    /**
+     * 材料库列表页面
+     */
+    @RequiresPermissions("project:projectFinalAccounts:list")
+    @RequestMapping(value = {"list",""})
+    public String list(ProjectFinalAccounts projectFinalAccounts, HttpServletRequest request, HttpServletResponse response, Model model){
+        //进行查询之后进行任何操作,返回还是查询之后的数据页面
+        if (StringUtils.isNotBlank(projectFinalAccounts.getToflag())){
+            if (projectFinalAccounts.getToflag().equals("1")){
+                request.getSession().removeAttribute("projectFinalAccounts");
+                ProjectFinalAccounts search=projectFinalAccounts;
+                request.getSession().setAttribute("projectFinalAccounts",search);
+            }
+        }else{
+            if (request.getSession().getAttribute("projectFinalAccounts")!=null){
+                projectFinalAccounts= (ProjectFinalAccounts) request.getSession().getAttribute("projectFinalAccounts");
+                model.addAttribute("projectFinalAccounts", projectFinalAccounts);
+            }
+        }
+        Page<ProjectFinalAccounts> page =  projectFinalAccountsService.findPage(new Page<ProjectFinalAccounts>(request,response),projectFinalAccounts);
+        model.addAttribute("page",page);
+        model.addAttribute("projectFinalAccounts",projectFinalAccounts);
+
+        return "modules/projectStatement/projectAccountsList";
+    }
+
+
+    //    @RequiresPermissions(value={"project:projectStatement:add","project:projectStatement:edit"},logical = Logical.OR)
+    @RequestMapping(value = "save")
+    public String save(ProjectFinalAccounts projectFinalAccounts, Model model, RedirectAttributes redirectAttributes, String pageId) throws Exception {
+        int i = 1;
+        List<ProjectFinalAccountsImport> projectMaterialStorageList = new ArrayList<ProjectFinalAccountsImport>();
+        List<ProjectFinalAccountsImport> projectMaterialStorageList2 = projectFinalAccounts.getProjectFinalAccountsList();
+
+        //        对于list的长度进行判断,判断小于0就根据来的位置进行返回
+        if(projectMaterialStorageList2.size()<=0){
+            addMessage(redirectAttributes, "项目名称、材料名称、价格等获取失败,请重试");
+//            pageId 1代表这是从工程咨询进来的 2是从造价审核进来的
+            if("1".equals(pageId)){
+                return "redirect:"+Global.getAdminPath()+"/ruralProject/ruralProjectMessageAll/?repage";
+            }
+            return "redirect:"+Global.getAdminPath()+"/project/projectFinalAccounts/?repage";
+        }
+//        此处需要修改为迭代器形式
+        Iterator<ProjectFinalAccountsImport> iterator = projectMaterialStorageList2.iterator();
+        while (iterator.hasNext()){
+            ProjectFinalAccountsImport projectMaterialStorageImport = iterator.next();
+            if(projectMaterialStorageImport.getNumber() == null || projectMaterialStorageImport.getProjectOrCostName() == null || projectMaterialStorageImport.getSettlementSum()== null || "1".equals(projectMaterialStorageImport.getDelFlag())){
+                iterator.remove();
+            }else {
+                projectMaterialStorageList.add(projectMaterialStorageImport);
+            }
+        }
+        for(int k=0;k<projectMaterialStorageList.size();k++){
+            projectMaterialStorageList.get(k).setIsNewRecord(true);
+            //       判断项目名称、材料名称、价格是否为空
+            if(projectMaterialStorageList.get(k).getNumber()==null || projectMaterialStorageList.get(k).getProjectOrCostName() ==null || projectMaterialStorageList.get(k).getSettlementSum() == null){
+                addMessage(redirectAttributes, "第"+i+"个项目名称、材料名称、规格型号、单位等获取失败,请重试");
+
+                if("1".equals(pageId)){
+                    return "redirect:"+Global.getAdminPath()+"/ruralProject/ruralProjectMessageAll/?repage";
+                }
+                return "redirect:"+Global.getAdminPath()+"/project/projectFinalAccounts/?repage";
+            }
+        }
+        Map map = projectFinalAccountsService.qureyCountAboutProjectMaterialStorage(projectMaterialStorageList);
+        if((map.containsKey("failure"))){
+            addMessage(redirectAttributes, map.get("failure").toString());
+
+            if("1".equals(pageId)){
+                return "redirect:"+Global.getAdminPath()+"/ruralProject/ruralProjectMessageAll/?repage";
+            }
+            return "redirect:"+Global.getAdminPath()+"/project/projectFinalAccounts/?repage";
+        }
+
+        projectFinalAccountsService.batchSave(projectMaterialStorageList);
+
+        if("1".equals(pageId)){
+            return "redirect:"+Global.getAdminPath()+"/ruralProject/ruralProjectMessageAll/?repage";
+        }
+        return "redirect:"+Global.getAdminPath()+"/project/projectFinalAccounts/?repage";
+    }
+
+    @InitBinder
+    public void initBinder(WebDataBinder binder) {
+        // 设置集合自动增长的上限为 1000
+        binder.setAutoGrowCollectionLimit(1000);
+    }
+
+    /**
+     * 材料库新增保存功能
+     * @param projectFinalAccounts
+     * @param model
+     * @return
+     * @throws Exception
+     */
+    @RequestMapping(value = "storageSave")
+    @ResponseBody
+    public Map<String, Object> storageSave(@ModelAttribute ProjectFinalAccounts projectFinalAccounts, String dataBodyType, Model model) throws Exception {
+        // 初始化返回结果Map
+        Map<String, Object> result = new HashMap<>(4);
+        Map<String, Object> stringObjectMap = disposeProjectFinalAccountsList(projectFinalAccounts);
+
+
+        //重复数据
+        List<ProjectFinalAccountsImport> duplicateList = Lists.newArrayList();
+        //错误数据
+        List<ProjectFinalAccountsImport> errorList = Lists.newArrayList();
+        //成功数据
+        List<ProjectFinalAccountsImport> uniqueList = Lists.newArrayList();
+        //元数据
+        List<ProjectFinalAccountsImport> projectMaterialStorageList = Lists.newArrayList();
+        //redis存储信息
+        Map<String, String> redisKeys = new HashMap<>();
+
+        Boolean accountsSuccess = (Boolean) stringObjectMap.get("success");
+        if(accountsSuccess && dataBodyType.contains("工程项目竣工结算汇总表")){
+            duplicateList = (List<ProjectFinalAccountsImport>) stringObjectMap.get("duplicateList");
+            errorList = (List<ProjectFinalAccountsImport>) stringObjectMap.get("errorList");
+            uniqueList = (List<ProjectFinalAccountsImport>) stringObjectMap.get("uniqueList");
+            projectMaterialStorageList = (List<ProjectFinalAccountsImport>) stringObjectMap.get("projectMaterialStorageList");
+            redisKeys = (Map<String, String>) stringObjectMap.get("redisKeys");
+        }
+
+        Map<String, Object> stringSubstationMap = disposeProjectSubstationList(projectFinalAccounts);
+        //重复数据
+        List<ProjectSubstationImport> duplicateSubstationList = Lists.newArrayList();
+        //错误数据
+        List<ProjectSubstationImport> errorSubstationList = Lists.newArrayList();
+        //成功数据
+        List<ProjectSubstationImport> uniqueSubstationList = Lists.newArrayList();
+        //元数据
+        List<ProjectSubstationImport> projectSubstationList = Lists.newArrayList();
+        //redis存储信息
+        Map<String, String> redisSubstationKeys = new HashMap<>();
+
+        Boolean substationSuccess = (Boolean) stringSubstationMap.get("success");
+        if(substationSuccess && dataBodyType.contains("变电站建筑工程费用汇总表")){
+            duplicateSubstationList = (List<ProjectSubstationImport>) stringSubstationMap.get("duplicateList");
+
+            errorSubstationList = (List<ProjectSubstationImport>) stringSubstationMap.get("errorList");
+
+            uniqueSubstationList = (List<ProjectSubstationImport>) stringSubstationMap.get("uniqueList");
+
+            projectSubstationList = (List<ProjectSubstationImport>) stringSubstationMap.get("projectMaterialStorageList");
+
+            redisSubstationKeys = (Map<String, String>) stringSubstationMap.get("redisKeys");
+            redisKeys.putAll(redisSubstationKeys);
+        }
+
+        // 封装返回结果
+        result.put("success", true);
+        result.put("message", "处理完成");
+        //元数据总数据量
+        result.put("metadataCount", projectMaterialStorageList.size() + projectSubstationList.size());
+        //成功数据量
+        result.put("uniqueCount", uniqueList.size() + uniqueSubstationList.size());
+        //错误数据量
+        result.put("errorCount", errorList.size() + errorSubstationList.size());
+        //重复数据量
+        result.put("duplicateCount", duplicateList.size() + duplicateSubstationList.size());
+        result.put("redisKeys", redisKeys);
+        result.put("projectId", projectFinalAccounts.getProjectId());
+
+        return result;
+    }
+
+    public Map<String,Object> disposeProjectFinalAccountsList(ProjectFinalAccounts projectFinalAccounts){
+        // 初始化返回结果Map
+        Map<String, Object> result = new HashMap<>(4);
+
+        List<ProjectFinalAccountsImport> projectMaterialStorageList = Lists.newArrayList();
+        List<ProjectFinalAccountsImport> metadataList = projectFinalAccounts.getProjectFinalAccountsList();
+
+        if(null == metadataList || metadataList.isEmpty()){
+            result.put("success", false);
+            result.put("message", "请填写材料信息");
+            return result;
+        }else{
+            result.put("success", true);
+        }
+//        此处需要修改为迭代器形式
+        Iterator<ProjectFinalAccountsImport> iterator = metadataList.iterator();
+        while (iterator.hasNext()){
+            ProjectFinalAccountsImport projectMaterialStorageImport = iterator.next();
+            if("1".equals(projectMaterialStorageImport.getDelFlag())){
+                iterator.remove();
+            }else if(StringUtils.isBlank(projectMaterialStorageImport.getProjectOrCostName()) || null == projectMaterialStorageImport.getSettlementSum()){
+                iterator.remove();
+            }else{
+
+                if(null !=projectMaterialStorageImport.getSettlementSum()){
+                    BigDecimal scaledValue = projectMaterialStorageImport.getSettlementSum().setScale(2, RoundingMode.HALF_UP);
+                    projectMaterialStorageImport.setSettlementSum(scaledValue);
+                }
+
+                projectMaterialStorageList.add(projectMaterialStorageImport);
+            }
+        }
+
+        //对相对有效的数据进行处理
+        //有效的数据
+        List<ProjectFinalAccountsImport> effectiveList = Lists.newArrayList();
+        //错误的数据
+        List<ProjectFinalAccountsImport> errorList = Lists.newArrayList();
+        Iterator<ProjectFinalAccountsImport> projectMaterialStorageListIterator = projectMaterialStorageList.iterator();
+        while (projectMaterialStorageListIterator.hasNext()){
+            ProjectFinalAccountsImport projectMaterialStorageImport = projectMaterialStorageListIterator.next();
+            if(StringUtils.isNotBlank(projectMaterialStorageImport.getProjectOrCostName()) || null == projectMaterialStorageImport.getNumber()){
+                effectiveList.add(projectMaterialStorageImport);
+            }else {
+                if(StringUtils.isNotBlank(projectMaterialStorageImport.getProjectOrCostName()) && null == projectMaterialStorageImport.getNumber()){
+                    projectMaterialStorageImport.setErrorMessage("数据中缺少编号、项目或费用名称");
+                }else if(StringUtils.isNotBlank(projectMaterialStorageImport.getProjectOrCostName())){
+                    projectMaterialStorageImport.setErrorMessage("数据中缺少项目或费用名称");
+                }else if(null == projectMaterialStorageImport.getNumber()){
+                    projectMaterialStorageImport.setErrorMessage("数据中缺少编号");
+                }
+                errorList.add(projectMaterialStorageImport);
+            }
+        }
+        //对有效的数据进行去重处理
+        for (ProjectFinalAccountsImport info : effectiveList) {
+            // 处理可能的null值,转为空字符串
+            String number = (info.getNumber() != null) ? info.getNumber() : "";
+            String name = (info.getProjectOrCostName() != null) ? info.getProjectOrCostName() : "";
+            String spec = (info.getSettlementSum() != null) ? info.getSettlementSum().toString() : "";
+            String remarks = (info.getRemarks() != null) ? info.getRemarks() : "";
+            // 以逗号分隔组合
+            String resultStr =number + "," + name + "," + spec + "," + remarks;
+            info.setDistinctStr(resultStr);
+        }
+
+        Map<String,List<ProjectFinalAccountsImport>> map = projectFinalAccountsService.distinctProjectMaterialStorage(effectiveList,projectFinalAccounts.getProjectId());
+        //有效数据
+        List<ProjectFinalAccountsImport> uniqueList = map.get("uniqueList");
+        //重复数据
+        List<ProjectFinalAccountsImport> duplicateList = map.get("duplicateList");
+        for (ProjectFinalAccountsImport info : duplicateList) {
+            info.setErrorMessage("重复数据");
+        }
+
+
+        Iterator<ProjectFinalAccountsImport> uniqueIterator = uniqueList.iterator();
+        while (uniqueIterator.hasNext()){
+            ProjectFinalAccountsImport info = uniqueIterator.next();
+            info.setProjectId(projectFinalAccounts.getProjectId());
+            Map<String, Object> saveResultMap = projectFinalAccountsService.saveUniqueInfo(info);
+            Boolean success = (Boolean) saveResultMap.get("success");
+            String message = (String) saveResultMap.get("message");
+            if(!success){
+                info.setErrorMessage(message);
+                errorList.add(info);
+                uniqueIterator.remove();
+            }
+        }
+
+        //有效数据:uniqueList
+        //错误数据:errorList
+        //重复数据:duplicateList
+        //元数据:metadataList
+        //判定,如果 错误数据 中存在数据,则表示前端传过来的数据并没有完全保存。需要将数据转存成exce文档并给下载接口
+        //可先将数据存储到redis中,保存的redis 的key可以按照 项目id + 登陆人id + uniqueList 这种格式
+        Map<String, String> redisKeys = new HashMap<>();
+        if(!duplicateList.isEmpty() || !errorList.isEmpty()){
+            User user = UserUtils.getUser();
+            String uniqueRedisKey = projectFinalAccounts.getProjectId() + "_" + user.getId() + "_accounts_uniqueList";
+            String errorRedisKey = projectFinalAccounts.getProjectId() + "_" + user.getId() + "_accounts_errorList";
+            String duplicateRedisKey = projectFinalAccounts.getProjectId() + "_" + user.getId() + "_accounts_duplicateList";
+            String metadataRedisKey = projectFinalAccounts.getProjectId() + "_" + user.getId() + "_accounts_metadataList";
+
+            // 保存前先删除对应key的原有数据
+            projectFinalAccountsService.deleteRedisKey(errorRedisKey);
+            projectFinalAccountsService.deleteRedisKey(uniqueRedisKey);
+            projectFinalAccountsService.deleteRedisKey(duplicateRedisKey);
+            projectFinalAccountsService.deleteRedisKey(metadataRedisKey);
+
+            // 分别存储各个列表
+            if (!errorList.isEmpty()) {
+                projectFinalAccountsService.saveListToZSet(errorRedisKey, errorList);
+                redisKeys.put("errorRedisKey", errorRedisKey);
+            }
+            if (!uniqueList.isEmpty()) {
+                projectFinalAccountsService.saveListToZSet(uniqueRedisKey, uniqueList);
+                redisKeys.put("uniqueRedisKey", uniqueRedisKey);
+            }
+            if (!duplicateList.isEmpty()) {
+                projectFinalAccountsService.saveListToZSet(duplicateRedisKey, duplicateList);
+                redisKeys.put("duplicateRedisKey", duplicateRedisKey);
+            }
+            if (!projectMaterialStorageList.isEmpty()) {
+                projectFinalAccountsService.saveListToZSet(metadataRedisKey, projectMaterialStorageList);
+                redisKeys.put("metadataRedisKey", metadataRedisKey);
+            }
+
+        }
+
+        result.put("duplicateList", duplicateList);
+        result.put("errorList", errorList);
+        result.put("uniqueList", uniqueList);
+        result.put("projectMaterialStorageList", projectMaterialStorageList);
+        result.put("redisKeys", redisKeys);
+        return  result;
+    }
+
+    public Map<String,Object> disposeProjectSubstationList(ProjectFinalAccounts projectFinalAccounts){
+        // 初始化返回结果Map
+        Map<String, Object> result = new HashMap<>(4);
+
+        List<ProjectSubstationImport> projectMaterialStorageList = Lists.newArrayList();
+        List<ProjectSubstationImport> metadataList = projectFinalAccounts.getProjectSubstationList();
+
+        if(null == metadataList || metadataList.isEmpty()){
+            result.put("success", false);
+            result.put("message", "请填写材料信息");
+            return result;
+        }else{
+            result.put("success", true);
+        }
+//        此处需要修改为迭代器形式
+        Iterator<ProjectSubstationImport> iterator = metadataList.iterator();
+        while (iterator.hasNext()){
+            ProjectSubstationImport projectMaterialStorageImport = iterator.next();
+            if("1".equals(projectMaterialStorageImport.getDelFlag())){
+                iterator.remove();
+            }else if(StringUtils.isBlank(projectMaterialStorageImport.getProjectOrCostName())){
+                iterator.remove();
+            }else{
+                if(null !=projectMaterialStorageImport.getEngineeringCost()){
+                    BigDecimal scaledValue = projectMaterialStorageImport.getEngineeringCost().setScale(2, RoundingMode.HALF_UP);
+                    projectMaterialStorageImport.setEngineeringCost(scaledValue);
+                }
+                if(null !=projectMaterialStorageImport.getLaborCost()){
+                    BigDecimal scaledValue = projectMaterialStorageImport.getLaborCost().setScale(2, RoundingMode.HALF_UP);
+                    projectMaterialStorageImport.setLaborCost(scaledValue);
+                }
+                if(null !=projectMaterialStorageImport.getEstimatedMaterialCost()){
+                    BigDecimal scaledValue = projectMaterialStorageImport.getEstimatedMaterialCost().setScale(2, RoundingMode.HALF_UP);
+                    projectMaterialStorageImport.setEstimatedMaterialCost(scaledValue);
+                }
+                if(null !=projectMaterialStorageImport.getTotalCost()){
+                    BigDecimal scaledValue = projectMaterialStorageImport.getTotalCost().setScale(2, RoundingMode.HALF_UP);
+                    projectMaterialStorageImport.setTotalCost(scaledValue);
+                }
+
+                projectMaterialStorageList.add(projectMaterialStorageImport);
+            }
+        }
+
+        //对相对有效的数据进行处理
+        //有效的数据
+        List<ProjectSubstationImport> effectiveList = Lists.newArrayList();
+        //错误的数据
+        List<ProjectSubstationImport> errorList = Lists.newArrayList();
+        Iterator<ProjectSubstationImport> projectMaterialStorageListIterator = projectMaterialStorageList.iterator();
+        while (projectMaterialStorageListIterator.hasNext()){
+            ProjectSubstationImport projectMaterialStorageImport = projectMaterialStorageListIterator.next();
+            if(StringUtils.isNotBlank(projectMaterialStorageImport.getProjectOrCostName())){
+                effectiveList.add(projectMaterialStorageImport);
+            }else {
+                if(StringUtils.isNotBlank(projectMaterialStorageImport.getProjectOrCostName())){
+                    projectMaterialStorageImport.setErrorMessage("数据中缺少项目或费用名称");
+                }
+                errorList.add(projectMaterialStorageImport);
+            }
+        }
+        //对有效的数据进行去重处理
+        for (ProjectSubstationImport info : effectiveList) {
+            // 处理可能的null值,转为空字符串
+            String number = (info.getNumber() != null) ? info.getNumber() : "";
+            String name = (info.getProjectOrCostName() != null) ? info.getProjectOrCostName() : "";
+            String remarks = (info.getRemarks() != null) ? info.getRemarks() : "";
+            // 以逗号分隔组合
+            String resultStr =number + "," + name + "," + remarks;
+            info.setDistinctStr(resultStr);
+        }
+        //对数据进行去重分类处理
+        Map<String,List<ProjectSubstationImport>> map = projectFinalAccountsService.distinctProjectSubstation(effectiveList,projectFinalAccounts.getProjectId());
+        //有效数据
+        List<ProjectSubstationImport> uniqueList = map.get("uniqueList");
+        //重复数据
+        List<ProjectSubstationImport> duplicateList = map.get("duplicateList");
+        for (ProjectSubstationImport info : duplicateList) {
+            info.setErrorMessage("重复数据");
+        }
+
+
+        Iterator<ProjectSubstationImport> uniqueIterator = uniqueList.iterator();
+        while (uniqueIterator.hasNext()){
+            ProjectSubstationImport info = uniqueIterator.next();
+            info.setProjectId(projectFinalAccounts.getProjectId());
+            Map<String, Object> saveResultMap = projectFinalAccountsService.saveSubstationUniqueInfo(info);
+            Boolean success = (Boolean) saveResultMap.get("success");
+            String message = (String) saveResultMap.get("message");
+            if(!success){
+                info.setErrorMessage(message);
+                errorList.add(info);
+                uniqueIterator.remove();
+            }
+        }
+
+        //有效数据:uniqueList
+        //错误数据:errorList
+        //重复数据:duplicateList
+        //元数据:metadataList
+        //判定,如果 错误数据 中存在数据,则表示前端传过来的数据并没有完全保存。需要将数据转存成exce文档并给下载接口
+        //可先将数据存储到redis中,保存的redis 的key可以按照 项目id + 登陆人id + uniqueList 这种格式
+        Map<String, String> redisKeys = new HashMap<>();
+        if(!duplicateList.isEmpty() || !errorList.isEmpty()){
+            User user = UserUtils.getUser();
+            String uniqueRedisKey = projectFinalAccounts.getProjectId() + "_" + user.getId() + "_substation_uniqueList";
+            String errorRedisKey = projectFinalAccounts.getProjectId() + "_" + user.getId() + "_substation_errorList";
+            String duplicateRedisKey = projectFinalAccounts.getProjectId() + "_" + user.getId() + "_substation_duplicateList";
+            String metadataRedisKey = projectFinalAccounts.getProjectId() + "_" + user.getId() + "_substation_metadataList";
+
+            // 保存前先删除对应key的原有数据
+            projectFinalAccountsService.deleteRedisKey(errorRedisKey);
+            projectFinalAccountsService.deleteRedisKey(uniqueRedisKey);
+            projectFinalAccountsService.deleteRedisKey(duplicateRedisKey);
+            projectFinalAccountsService.deleteRedisKey(metadataRedisKey);
+
+            // 分别存储各个列表
+            if (!errorList.isEmpty()) {
+                projectFinalAccountsService.saveSubstationListToZSet(errorRedisKey, errorList);
+                redisKeys.put("errorRedisKey", errorRedisKey);
+            }
+            if (!uniqueList.isEmpty()) {
+                projectFinalAccountsService.saveSubstationListToZSet(uniqueRedisKey, uniqueList);
+                redisKeys.put("uniqueRedisKey", uniqueRedisKey);
+            }
+            if (!duplicateList.isEmpty()) {
+                projectFinalAccountsService.saveSubstationListToZSet(duplicateRedisKey, duplicateList);
+                redisKeys.put("duplicateRedisKey", duplicateRedisKey);
+            }
+            if (!projectMaterialStorageList.isEmpty()) {
+                projectFinalAccountsService.saveSubstationListToZSet(metadataRedisKey, projectMaterialStorageList);
+                redisKeys.put("metadataRedisKey", metadataRedisKey);
+            }
+
+        }
+
+        result.put("duplicateList", duplicateList);
+        result.put("errorList", errorList);
+        result.put("uniqueList", uniqueList);
+        result.put("projectMaterialStorageList", projectMaterialStorageList);
+        result.put("redisKeys", redisKeys);
+        return  result;
+    }
+
+    /**
+     * 修改材料库
+     */
+    @RequestMapping(value = "Update")
+    public String adminUpdate(ProjectFinalAccounts projectFinalAccounts, RedirectAttributes redirectAttributes){
+        try {
+            ProjectFinalAccounts t = projectFinalAccountsService.get(projectFinalAccounts.getId());//从数据库取出记录的值
+            MyBeanUtils.copyBeanNotNull2Bean(projectFinalAccounts, t);//将编辑表单中的非NULL值覆盖数据库记录中的值
+            projectFinalAccounts.preUpdate();
+            if(projectFinalAccountsService.update(t) == 1){
+                addMessage(redirectAttributes, "修改成功");
+            }
+        }catch (Exception e){
+            logger.error("修改项目异常:",e);
+            addMessage(redirectAttributes, "修改项目:"+e.getMessage());
+        }
+        return "redirect:"+Global.getAdminPath()+"/project/projectFinalAccounts/?repage";
+    }
+    /**
+     * 删除材料库
+     */
+    @RequestMapping(value = "delete")
+    public String delete(ProjectFinalAccounts projectFinalAccounts, RedirectAttributes redirectAttributes) {
+
+        if(projectFinalAccountsService.delectBylogic(projectFinalAccounts) != 1){
+            addMessage(redirectAttributes, "删除失败,请重试");
+        }
+        return "redirect:"+Global.getAdminPath()+"/project/projectFinalAccounts/?repage";
+    }
+
+
+    /**
+     * 下载处理后的文档信息
+     */
+    @RequiresPermissions("project:projectFinalAccounts:export")
+    @RequestMapping(value = "export", method = RequestMethod.POST)
+    public void exportFile(ProjectFinalAccounts projectFinalAccounts, HttpServletRequest request,
+                           HttpServletResponse response, RedirectAttributes redirectAttributes) {
+        try {
+            // 1. 创建多页签导出工具实例
+            ExportMultipleTabsExcel exporter = new ExportMultipleTabsExcel();
+
+            // 2. 获取各类数据(从Redis或数据库)
+            String projectId = projectFinalAccounts.getProjectId();
+            String userId = UserUtils.getUser().getId();
+
+            // 错误数据列表
+            List<ProjectFinalAccounts> errorList = projectFinalAccountsService.convertFromRedis(
+                    projectId + "_" + userId + "_errorList");
+
+            // 重复数据列表
+            List<ProjectFinalAccounts> duplicateList = projectFinalAccountsService.convertFromRedis(
+                    projectId + "_" + userId + "_duplicateList");
+
+            // 有效数据列表
+            List<ProjectFinalAccounts> uniqueList = projectFinalAccountsService.convertFromRedis(
+                    projectId + "_" + userId + "_uniqueList");
+
+            // 原数据列表
+            List<ProjectFinalAccounts> metadataList = projectFinalAccountsService.convertFromRedis(
+                    projectId + "_" + userId + "_metadataList");
+
+            // 处理可能的空列表,避免导出工具报错
+            errorList = errorList == null ? Collections.emptyList() : errorList;
+            duplicateList = duplicateList == null ? Collections.emptyList() : duplicateList;
+            uniqueList = uniqueList == null ? Collections.emptyList() : uniqueList;
+            metadataList = metadataList == null ? Collections.emptyList() : metadataList;
+
+            // 3. 添加Sheet页(页签名称、标题、实体类、数据列表)
+            if (!errorList.isEmpty()) {
+                exporter.addSheet("工程项目竣工结算汇总表-错误数据", "工程项目竣工结算汇总表导入-错误数据", ProjectFinalAccounts.class, errorList);
+            }
+            if (!duplicateList.isEmpty()) {
+                exporter.addSheet("工程项目竣工结算汇总表-重复数据", "工程项目竣工结算汇总表导入-重复数据", ProjectFinalAccounts.class, duplicateList);
+            }
+            if (!uniqueList.isEmpty()) {
+                exporter.addSheet("工程项目竣工结算汇总表-有效数据", "工程项目竣工结算汇总表导入-有效数据", ProjectFinalAccounts.class, uniqueList);
+            }
+            if (!metadataList.isEmpty()) {
+                exporter.addSheet("工程项目竣工结算汇总表-原数据", "工程项目竣工结算汇总表导入-原数据", ProjectFinalAccounts.class, metadataList);
+            }
+
+
+            // 错误数据列表
+            List<ProjectSubstationImport> errorSubstationList = projectFinalAccountsService.convertFromSubstationRedis(
+                    projectId + "_" + userId + "_substation_errorList");
+
+            // 重复数据列表
+            List<ProjectSubstationImport> duplicateSubstationList = projectFinalAccountsService.convertFromSubstationRedis(
+                    projectId + "_" + userId + "_substation_duplicateList");
+
+            // 有效数据列表
+            List<ProjectSubstationImport> uniqueSubstationList = projectFinalAccountsService.convertFromSubstationRedis(
+                    projectId + "_" + userId + "_substation_uniqueList");
+
+            // 原数据列表
+            List<ProjectSubstationImport> metadataSubstationList = projectFinalAccountsService.convertFromSubstationRedis(
+                    projectId + "_" + userId + "_substation_metadataList");
+
+            // 处理可能的空列表,避免导出工具报错
+            errorSubstationList = errorSubstationList == null ? Collections.emptyList() : errorSubstationList;
+            duplicateSubstationList = duplicateSubstationList == null ? Collections.emptyList() : duplicateSubstationList;
+            uniqueSubstationList = uniqueSubstationList == null ? Collections.emptyList() : uniqueSubstationList;
+            metadataSubstationList = metadataSubstationList == null ? Collections.emptyList() : metadataSubstationList;
+
+            // 3. 添加Sheet页(页签名称、标题、实体类、数据列表)
+            if (!errorSubstationList.isEmpty()) {
+                exporter.addSheet("变电站建筑工程费用汇总表-错误数据", "变电站建筑工程费用汇总表导入-错误数据", ProjectSubstationImport.class, errorSubstationList);
+            }
+            if (!duplicateSubstationList.isEmpty()) {
+                exporter.addSheet("变电站建筑工程费用汇总表-重复数据", "变电站建筑工程费用汇总表导入-重复数据", ProjectSubstationImport.class, duplicateSubstationList);
+            }
+            if (!uniqueSubstationList.isEmpty()) {
+                exporter.addSheet("变电站建筑工程费用汇总表-有效数据", "变电站建筑工程费用汇总表导入-有效数据", ProjectSubstationImport.class, uniqueSubstationList);
+            }
+            if (!metadataSubstationList.isEmpty()) {
+                exporter.addSheet("变电站建筑工程费用汇总表-原数据", "变电站建筑工程费用汇总表导入-原数据", ProjectSubstationImport.class, metadataSubstationList);
+            }
+
+            // 4. 导出文件(关键:设置响应头,确保浏览器正确下载)
+            String fileName = "结算表导入数据汇总_" + DateUtils.getDate("yyyyMMddHHmmss") + ".xlsx";
+            // 设置响应类型为Excel
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            // 设置文件名(解决中文乱码)
+            String encodedFileName = URLEncoder.encode(fileName, "UTF-8");
+            response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName);
+            // 禁用缓存
+            response.setHeader("Pragma", "no-cache");
+            response.setHeader("Cache-Control", "no-cache");
+            response.setDateHeader("Expires", 0);
+
+            // 修正:直接传递response对象给write方法(移除错误的类型转换)
+            exporter.write(response, fileName);
+            exporter.dispose(); // 清理资源
+
+        } catch (Exception e) {
+            // 日志记录错误(文件导出失败时,前端可通过弹窗提示)
+            logger.error("多页签导出异常", e);
+            // 若需向前端传递错误信息,可设置响应状态和消息
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            try {
+                // 确保响应未提交时才写入错误信息
+                if (!response.isCommitted()) {
+                    response.getWriter().write("导出失败:" + e.getMessage());
+                }
+            } catch (IOException ex) {
+                logger.error("响应错误信息失败", ex);
+            }
+        }
+    }
+
+
+    /**
+     * 文件上传页面跳转
+     * @param projectRecords
+     * @param model
+     * @return
+     */
+    @RequestMapping(value = "form")
+    public String form(RuralProjectRecords projectRecords, Model model,String pageId) {
+        String storageFlag = projectRecords.getStorageFlag();
+        if (projectRecords != null && StringUtils.isNotBlank(projectRecords.getId())) {
+            projectRecords = ruralProjectRecordsService.getQueryProjectUsers(projectRecords.getId());
+        }
+        projectRecords.setStorageFlag(storageFlag);
+        model.addAttribute("pageId",pageId);
+        model.addAttribute("projectRecords",projectRecords);
+        return "modules/projectStatement/projectStatementForm";
+    }
+
+
+    /**
+     * 导入Excel数据
+     */
+    @ResponseBody
+    @RequestMapping(value = "import", method=RequestMethod.POST)
+    public  Map<String,Object> importFile(MultipartFile file, String dataBodyType, RedirectAttributes redirectAttributes) {
+        Map<String,Object> map = new HashMap<String,Object>();
+        Integer importSuccessCount = 0;
+        try {
+            List<String> list = Lists.newArrayList();
+            // 核心调用:工作表名称为"工程项目竣工结算汇总表",标题行在第2行(Excel行号)
+            if(StringUtils.isNotBlank(dataBodyType)){
+                // 1. 按逗号分割为数组(split参数为正则表达式,需注意转义特殊字符)
+                String[] strArray = dataBodyType.split(",");
+                // 2. 数组转List(若需增删元素,用new ArrayList<>()包装)
+                list = Arrays.asList(strArray);
+            }
+            if(!list.isEmpty()){
+                for (String string : list) {
+
+                    if("变电站建筑工程费用汇总表".equals(string)){
+                        ExcelImportBySheetName importer = new ExcelImportBySheetName(file, 3, 4, string);
+                        List<ProjectSubstationImport> dataList = importer.toEntityList(ProjectSubstationImport.class);
+                        map.put(string,dataList);
+                        importSuccessCount = importSuccessCount + dataList.size();
+                        System.out.println(dataList);
+                    }else{
+
+                        ExcelImportBySheetName importer = new ExcelImportBySheetName(file, 3,3, string);
+                        List<ProjectFinalAccountsImport> dataList = importer.toEntityList(ProjectFinalAccountsImport.class);
+                        map.put(string,dataList);
+                        importSuccessCount = importSuccessCount + dataList.size();
+
+                        System.out.println(dataList);
+                    }
+
+                }
+            }
+
+            // 业务处理(如保存到数据库)
+            // projectService.batchSave(dataList);
+            map.put("code",0);
+            map.put("message","导入成功,共" + importSuccessCount + "条数据");
+        } catch (Exception e) {
+            logger.error("导入失败", e);
+            map.put("code",400);
+            map.put("message","导入失败,共" + e.getMessage());
+        }
+        return map;
+    }
+
+    @RequestMapping(value = "import/template")
+    public String importFileTemplate(HttpServletResponse response, HttpServletRequest request, RedirectAttributes redirectAttributes) {
+        try {
+            ThisLocalityDownloadUtil download = new ThisLocalityDownloadUtil();
+            download.download("材料库处理表模板.xlsx",request,response);
+        } catch (Exception e) {
+            logger.error("材料库处理表模板下载失败!",e);
+        }
+        return "redirect:"+Global.getAdminPath()+"/project/projectFinalAccounts/?repage";
+    }
+
+    /**
+     * 查看,增加,编辑客户管理表单页面
+     */
+    @RequestMapping(value = "formTwoPage")
+    public String form(ProjectFinalAccounts projectFinalAccounts, Model model,String isAdd) {
+        ProjectFinalAccounts projectFinalAccounts2 = new ProjectFinalAccounts();
+        if(StringUtils.isNotBlank(projectFinalAccounts.getId())){
+            projectFinalAccounts2  = projectFinalAccountsService.get(projectFinalAccounts.getId());
+        }else{
+            model.addAttribute("isAdd",isAdd);
+            return "modules/projectStatement/projectAccountsAddForm";
+        }
+        if(StringUtils.isNotBlank(projectFinalAccounts.getProjectNumber())){
+
+            model.addAttribute("projectFinalAccounts", projectFinalAccounts2);
+        }
+
+        return "modules/projectStatement/projectAccountsTwoForm";
+    }
+
+    /**
+     * 查看,增加,编辑客户管理表单页面
+     */
+    @RequestMapping(value = "view")
+    public String view(ProjectFinalAccounts projectFinalAccounts, Model model,String isAdd) {
+        ProjectFinalAccounts projectMaterialStorage2 = new ProjectFinalAccounts();
+        if(StringUtils.isNotBlank(projectFinalAccounts.getId())){
+            projectMaterialStorage2  = projectFinalAccountsService.get(projectFinalAccounts.getId());
+        }
+        if(StringUtils.isNotBlank(projectFinalAccounts.getProjectNumber())){
+
+            model.addAttribute("projectFinalAccounts", projectMaterialStorage2);
+        }
+
+        return "modules/projectStatement/projectAccountsView";
+    }
+
+
+    /**
+     * 选择材料关联项目
+     */
+    @RequestMapping(value = "selectproject")
+    public String selectproject(ProjectRecords project, String url, String fieldLabels, String fieldKeys, String searchLabel, String searchKey, String ids, Integer isProject, Integer isProjectFalg, String details, HttpServletRequest request, HttpServletResponse response, Model model) {
+        project.setCompany(UserUtils.getSelectCompany());
+        project.setProjectStatus(ProjectStatusEnum.SIGNED.getValue());//已签状态
+        Page<ProjectRecords> page = projectRecordsService.ruralFindPageByReimbur(new Page<ProjectRecords>(request, response), project,"");
+        List<ProjectRecords> list = page.getList();
+        try {
+            fieldLabels = URLDecoder.decode(fieldLabels, "UTF-8");
+            fieldKeys = URLDecoder.decode(fieldKeys, "UTF-8");
+            searchLabel = URLDecoder.decode(searchLabel, "UTF-8");
+            searchKey = URLDecoder.decode(searchKey, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();
+            logger.error("Exception e:"+e);
+        }
+        model.addAttribute("labelNames", fieldLabels.split("\\|"));
+        model.addAttribute("labelValues", fieldKeys.split("\\|"));
+        model.addAttribute("fieldLabels", fieldLabels);
+        model.addAttribute("fieldKeys", fieldKeys);
+        model.addAttribute("url", url);
+        model.addAttribute("searchLabel", searchLabel);
+        model.addAttribute("searchKey", searchKey);
+        project.setDetails(details);
+        model.addAttribute("obj", project);
+        model.addAttribute("isProject", isProject);
+        model.addAttribute("isProjectFalg", isProjectFalg);
+        model.addAttribute("page",page);
+        return "modules/sys/gridMaterialProject";
+    }
+
+
+    /**
+     * 工程咨询文件上传页面跳转
+     * @param projectRecords
+     * @param model
+     * @return
+     */
+    @RequestMapping(value = "projectMessageform")
+    public String ProjectMessageform(RuralProjectRecords projectRecords, Model model) {
+        if (projectRecords != null && StringUtils.isNotBlank(projectRecords.getId())) {
+            projectRecords = ruralProjectRecordsService.getQueryProjectUsers(projectRecords.getId());
+        }
+        model.addAttribute("projectRecords",projectRecords);
+        return "modules/ruralprojectrecords/ruralporjectmessage/ruralProjectMessageList";
+    }
+
+
+
+    /**
+     * 造价审核文件上传页面跳转
+     * @param projectRecords
+     * @param model
+     * @return
+     */
+    @RequestMapping(value = "ruralCostProjectMessageForm")
+    public String ruralCostProjectMessageForm(RuralProjectRecords projectRecords, Model model) {
+        if (projectRecords != null && StringUtils.isNotBlank(projectRecords.getId())) {
+            projectRecords = ruralProjectRecordsService.getQueryProjectUsers(projectRecords.getId());
+        }
+        model.addAttribute("projectRecords",projectRecords);
+        return "modules/ruralprojectrecords/cost/ruralCostProjectMessageList";
+    }
+
+    /**
+     * 单对象保存
+     */
+    @RequestMapping(value = "signleSave")
+    public String signleSave(ProjectFinalAccounts projectFinalAccounts, Model model, RedirectAttributes redirectAttributes,String pageId,String isAdd) throws Exception {
+
+//       非1为添加  type为1时修改;
+        if( isAdd.equals("0")){
+            projectFinalAccounts.setIsNewRecord(true);
+            String projectNumber = projectFinalAccounts.getProjectOrCostName().substring(0,projectFinalAccounts.getProjectOrCostName().indexOf(","));
+            projectFinalAccounts.setProjectNumber(projectNumber);
+        }
+        /*if(StringUtils.isBlank(projectFinalAccounts.getProjectNumber()) && StringUtils.isNotBlank(projectFinalAccounts.getId())){
+            projectFinalAccounts = projectFinalAccountsService.get(projectFinalAccounts.getId());
+        }*/
+//      判断项目名称、材料名称、是否为空
+        if(StringUtils.isBlank(projectFinalAccounts.getProjectNumber()) && StringUtils.isBlank(projectFinalAccounts.getProjectOrCostName())){
+            addMessage(redirectAttributes, "项目名称、材料名称等获取失败,请重试");
+            if("1".equals(pageId)){
+                return "redirect:"+Global.getAdminPath()+"/ruralProject/ruralProjectMessageAll/?repage";
+            }
+            return "redirect:"+Global.getAdminPath()+"/project/projectFinalAccounts";
+        }
+//        进行项目名称、材料名称、价格重复的判断
+        if(StringUtils.isBlank(projectFinalAccounts.getProjectNumber()) && StringUtils.isBlank(projectFinalAccounts.getProjectOrCostName())){
+            addMessage(redirectAttributes, "项目名称、材料名称等获取失败,请重试");
+            return "redirect:"+Global.getAdminPath()+"/project/projectFinalAccounts";
+        }
+        //根据项目id查询报告号并重新赋值
+        RuralProjectRecords ruralProjectRecords = ruralProjectRecordsService.get(projectFinalAccounts.getProjectNumber());
+
+        if(null != ruralProjectRecords){
+            projectFinalAccounts.setProjectName(ruralProjectRecords.getProjectName());
+            //projectFinalAccounts.setReportNumber(ruralProjectRecords.getProjectReportNumber());
+        }
+
+        if(StringUtils.isNotBlank(projectFinalAccounts.getProjectNumber())){
+            projectFinalAccounts.setProjectId(projectFinalAccounts.getProjectNumber());
+        }
+        Map<String, Object> returnMap = projectFinalAccountsService.saveInfo(projectFinalAccounts);
+        Boolean success = (Boolean) returnMap.get("success");
+        if(!success){
+            addMessage(redirectAttributes, returnMap.get("message").toString());
+            return "redirect:"+Global.getAdminPath()+"/project/projectFinalAccounts/?repage";
+        }
+        return "redirect:"+Global.getAdminPath()+"/project/projectFinalAccounts";
+    }
+
+    /**
+     * 批量删除
+     * @param listId
+     * @return
+     */
+    @RequestMapping(value = "deleteAll")
+    @ResponseBody
+    public Object deleteAll(String listId){
+        Map<String,Object> map = new HashMap<>();
+        try{
+            List<String> idList = Arrays.asList(listId.split(","));
+            List<ProjectFinalAccounts> byIdList = Lists.newArrayList();
+            //根据id查询自己创建的材料信息数量
+            if(!UserUtils.getUser().isAdmin()){
+                byIdList = projectFinalAccountsService.getByIdList(idList);
+                if(byIdList.size() < idList.size()){
+                    map.put("success",false);
+                    map.put("str","删除的材料信息保存不属于您创建的信息。请确认后重新删除");
+                }else{
+                    projectFinalAccountsService.deleteListById(idList);
+                    map.put("success",true);
+                    map.put("str","材料信息删除成功");
+                }
+            }else{
+                projectFinalAccountsService.deleteListById(idList);
+                map.put("success",true);
+                map.put("str","材料信息删除成功");
+            }
+        }catch (Exception e){
+            map.put("success",false);
+            map.put("str","删除失败");
+        }
+        return map;
+    }
+
+    /**
+     * 根据工作内容查询成果类型
+     * @param achievementParentId
+     * @return
+     */
+    @RequestMapping("getDictList")
+    @ResponseBody
+    public AjaxJson getDictList(String achievementParentId){
+        AjaxJson ajaxJson = new AjaxJson();
+        try {
+            //根据字典key获取字典信息
+            List<MainDictDetail> getAchievementTypeList = DictUtils.getMainDictListOnProjectAdvent("project_statement_type");
+
+            List<Map<String, Object>> mapList = new ArrayList<>();
+            for (int i = 0; i < getAchievementTypeList.size(); i++) {
+                Map<String, Object> map = new HashMap<>();
+                map.put("name", getAchievementTypeList.get(i).getLabel());
+                map.put("value", getAchievementTypeList.get(i).getValue());
+                mapList.add(map);
+            }
+            ajaxJson.getBody().put("list", mapList);
+            ajaxJson.setMsg("获取数据成功");
+        } catch (Exception e) {
+            logger.error("获取数据异常!", e);
+            ajaxJson.setSuccess(false);
+            ajaxJson.setMsg("获取数据异常");
+        }
+        return ajaxJson;
+    }
+}

+ 335 - 0
src/main/java/com/jeeplus/modules/projectStatement/web/ProjectSubstationController.java

@@ -0,0 +1,335 @@
+package com.jeeplus.modules.projectStatement.web;
+
+
+import com.google.common.collect.Lists;
+import com.jeeplus.common.config.Global;
+import com.jeeplus.common.json.AjaxJson;
+import com.jeeplus.common.persistence.Page;
+import com.jeeplus.common.utils.DateUtils;
+import com.jeeplus.common.utils.MyBeanUtils;
+import com.jeeplus.common.utils.StringUtils;
+import com.jeeplus.common.utils.ThisLocalityDownloadUtil;
+import com.jeeplus.common.utils.excel.ExportMultipleTabsExcel;
+import com.jeeplus.common.utils.excel.utils.ExcelImportBySheetName;
+import com.jeeplus.common.web.BaseController;
+import com.jeeplus.modules.projectStatement.entity.ProjectFinalAccounts;
+import com.jeeplus.modules.projectStatement.entity.ProjectFinalAccountsImport;
+import com.jeeplus.modules.projectStatement.entity.ProjectSubstationImport;
+import com.jeeplus.modules.projectStatement.service.ProjectFinalAccountsService;
+import com.jeeplus.modules.projectStatement.service.ProjectSubstationService;
+import com.jeeplus.modules.projectrecord.entity.ProjectRecords;
+import com.jeeplus.modules.projectrecord.enums.ProjectStatusEnum;
+import com.jeeplus.modules.projectrecord.service.ProjectRecordsService;
+import com.jeeplus.modules.ruralprojectrecords.entity.RuralProjectRecords;
+import com.jeeplus.modules.ruralprojectrecords.service.RuralProjectRecordsService;
+import com.jeeplus.modules.sys.entity.MainDictDetail;
+import com.jeeplus.modules.sys.entity.User;
+import com.jeeplus.modules.sys.utils.DictUtils;
+import com.jeeplus.modules.sys.utils.UserUtils;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.*;
+
+
+@Controller
+@RequestMapping(value = "${adminPath}/project/projectSubstation")
+public class ProjectSubstationController extends BaseController {
+    @Autowired
+    private ProjectSubstationService projectSubstationService;
+    @Autowired
+    private RuralProjectRecordsService ruralProjectRecordsService;
+    @Autowired
+    protected ProjectRecordsService projectRecordsService;
+
+    @ModelAttribute
+    public ProjectSubstationImport get(@RequestParam(required = false) String id){
+//        创建一个ProjectMaterialStorage
+        ProjectSubstationImport entity = null;
+//        对于id进行是否为空的判断,如果不会空,就通过get方法获取材料库对象
+        if(StringUtils.isNotBlank(id)){
+            entity = projectSubstationService.get(id);
+        }
+        if(entity == null){
+            entity = new ProjectSubstationImport();
+        }
+//        返回ProjectMaterialStorage
+        return entity;
+
+    }
+
+
+    /**
+     * 材料库列表页面
+     */
+    @RequiresPermissions("project:projectFinalAccounts:list")
+    @RequestMapping(value = {"list",""})
+    public String list(ProjectSubstationImport projectSubstation, HttpServletRequest request, HttpServletResponse response, Model model){
+        //进行查询之后进行任何操作,返回还是查询之后的数据页面
+        if (StringUtils.isNotBlank(projectSubstation.getToflag())){
+            if (projectSubstation.getToflag().equals("1")){
+                request.getSession().removeAttribute("projectSubstation");
+                ProjectSubstationImport search=projectSubstation;
+                request.getSession().setAttribute("projectSubstation",search);
+            }
+        }else{
+            if (request.getSession().getAttribute("projectSubstation")!=null){
+                projectSubstation= (ProjectSubstationImport) request.getSession().getAttribute("projectSubstation");
+                model.addAttribute("projectSubstation", projectSubstation);
+            }
+        }
+        Page<ProjectSubstationImport> page =  projectSubstationService.findPage(new Page<ProjectSubstationImport>(request,response),projectSubstation);
+        model.addAttribute("page",page);
+        model.addAttribute("projectSubstation",projectSubstation);
+
+        return "modules/projectStatement/substation/projectSubstationList";
+    }
+
+
+    //    @RequiresPermissions(value={"project:projectStatement:add","project:projectStatement:edit"},logical = Logical.OR)
+    /*@RequestMapping(value = "save")
+    public String save(ProjectFinalAccounts projectFinalAccounts, Model model, RedirectAttributes redirectAttributes, String pageId) throws Exception {
+        int i = 1;
+        List<ProjectFinalAccountsImport> projectMaterialStorageList = new ArrayList<ProjectFinalAccountsImport>();
+        List<ProjectFinalAccountsImport> projectMaterialStorageList2 = projectFinalAccounts.getProjectFinalAccountsList();
+
+        //        对于list的长度进行判断,判断小于0就根据来的位置进行返回
+        if(projectMaterialStorageList2.size()<=0){
+            addMessage(redirectAttributes, "项目名称、材料名称、价格等获取失败,请重试");
+//            pageId 1代表这是从工程咨询进来的 2是从造价审核进来的
+            if("1".equals(pageId)){
+                return "redirect:"+Global.getAdminPath()+"/ruralProject/ruralProjectMessageAll/?repage";
+            }
+            return "redirect:"+Global.getAdminPath()+"/project/projectFinalAccounts/?repage";
+        }
+//        此处需要修改为迭代器形式
+        Iterator<ProjectFinalAccountsImport> iterator = projectMaterialStorageList2.iterator();
+        while (iterator.hasNext()){
+            ProjectFinalAccountsImport projectMaterialStorageImport = iterator.next();
+            if(projectMaterialStorageImport.getNumber() == null || projectMaterialStorageImport.getProjectOrCostName() == null || projectMaterialStorageImport.getSettlementSum()== null || "1".equals(projectMaterialStorageImport.getDelFlag())){
+                iterator.remove();
+            }else {
+                projectMaterialStorageList.add(projectMaterialStorageImport);
+            }
+        }
+        for(int k=0;k<projectMaterialStorageList.size();k++){
+            projectMaterialStorageList.get(k).setIsNewRecord(true);
+            //       判断项目名称、材料名称、价格是否为空
+            if(projectMaterialStorageList.get(k).getNumber()==null || projectMaterialStorageList.get(k).getProjectOrCostName() ==null || projectMaterialStorageList.get(k).getSettlementSum() == null){
+                addMessage(redirectAttributes, "第"+i+"个项目名称、材料名称、规格型号、单位等获取失败,请重试");
+
+                if("1".equals(pageId)){
+                    return "redirect:"+Global.getAdminPath()+"/ruralProject/ruralProjectMessageAll/?repage";
+                }
+                return "redirect:"+Global.getAdminPath()+"/project/projectFinalAccounts/?repage";
+            }
+        }
+        Map map = projectSubstationService.qureyCountAboutProjectMaterialStorage(projectMaterialStorageList);
+        if((map.containsKey("failure"))){
+            addMessage(redirectAttributes, map.get("failure").toString());
+
+            if("1".equals(pageId)){
+                return "redirect:"+Global.getAdminPath()+"/ruralProject/ruralProjectMessageAll/?repage";
+            }
+            return "redirect:"+Global.getAdminPath()+"/project/projectFinalAccounts/?repage";
+        }
+
+        projectSubstationService.batchSave(projectMaterialStorageList);
+
+        if("1".equals(pageId)){
+            return "redirect:"+Global.getAdminPath()+"/ruralProject/ruralProjectMessageAll/?repage";
+        }
+        return "redirect:"+Global.getAdminPath()+"/project/projectFinalAccounts/?repage";
+    }*/
+
+    @InitBinder
+    public void initBinder(WebDataBinder binder) {
+        // 设置集合自动增长的上限为 1000
+        binder.setAutoGrowCollectionLimit(1000);
+    }
+
+
+
+    /**
+     * 修改材料库
+     */
+    @RequestMapping(value = "Update")
+    public String adminUpdate(ProjectSubstationImport projectFinalAccounts, RedirectAttributes redirectAttributes){
+        try {
+            ProjectSubstationImport t = projectSubstationService.get(projectFinalAccounts.getId());//从数据库取出记录的值
+            MyBeanUtils.copyBeanNotNull2Bean(projectFinalAccounts, t);//将编辑表单中的非NULL值覆盖数据库记录中的值
+            projectFinalAccounts.preUpdate();
+            if(projectSubstationService.update(t) == 1){
+                addMessage(redirectAttributes, "修改成功");
+            }
+        }catch (Exception e){
+            logger.error("修改项目异常:",e);
+            addMessage(redirectAttributes, "修改项目:"+e.getMessage());
+        }
+        return "redirect:"+Global.getAdminPath()+"/project/projectSubstation/?repage";
+    }
+
+    /**
+     * 删除材料库
+     */
+    @RequestMapping(value = "delete")
+    public String delete(ProjectSubstationImport projectFinalAccounts, RedirectAttributes redirectAttributes) {
+
+        if(projectSubstationService.delectBylogic(projectFinalAccounts) != 1){
+            addMessage(redirectAttributes, "删除失败,请重试");
+        }
+        return "redirect:"+Global.getAdminPath()+"/project/projectSubstation/?repage";
+    }
+
+    /**
+     * 文件上传页面跳转
+     * @param projectRecords
+     * @param model
+     * @return
+     */
+    @RequestMapping(value = "form")
+    public String form(RuralProjectRecords projectRecords, Model model,String pageId) {
+        String storageFlag = projectRecords.getStorageFlag();
+        if (projectRecords != null && StringUtils.isNotBlank(projectRecords.getId())) {
+            projectRecords = ruralProjectRecordsService.getQueryProjectUsers(projectRecords.getId());
+        }
+        projectRecords.setStorageFlag(storageFlag);
+        model.addAttribute("pageId",pageId);
+        model.addAttribute("projectRecords",projectRecords);
+        return "modules/projectStatement/projectStatementForm";
+    }
+
+
+    /**
+     * 查看,增加,编辑客户管理表单页面
+     */
+    @RequestMapping(value = "formTwoPage")
+    public String form(ProjectSubstationImport projectSubstationImport, Model model,String isAdd) {
+        ProjectSubstationImport projectSubstation = new ProjectSubstationImport();
+        if(StringUtils.isNotBlank(projectSubstationImport.getId())){
+            projectSubstation  = projectSubstationService.get(projectSubstationImport.getId());
+        }else{
+            model.addAttribute("isAdd",isAdd);
+            return "modules/projectStatement/projectAccountsAddForm";
+        }
+        if(StringUtils.isNotBlank(projectSubstationImport.getProjectNumber())){
+
+            model.addAttribute("projectSubstationImport", projectSubstation);
+        }
+
+        return "modules/projectStatement/substation/projectSubstationTwoForm";
+    }
+
+    /**
+     * 查看,增加,编辑客户管理表单页面
+     */
+    @RequestMapping(value = "view")
+    public String view(ProjectSubstationImport projectFinalAccounts, Model model,String isAdd) {
+        ProjectSubstationImport projectMaterialStorage2 = new ProjectSubstationImport();
+        if(StringUtils.isNotBlank(projectFinalAccounts.getId())){
+            projectMaterialStorage2  = projectSubstationService.get(projectFinalAccounts.getId());
+        }
+        if(StringUtils.isNotBlank(projectFinalAccounts.getProjectNumber())){
+
+            model.addAttribute("projectFinalAccounts", projectMaterialStorage2);
+        }
+
+        return "modules/projectStatement/projectAccountsView";
+    }
+
+
+    /**
+     * 单对象保存
+     */
+    @RequestMapping(value = "signleSave")
+    public String signleSave(ProjectSubstationImport projectFinalAccounts, Model model, RedirectAttributes redirectAttributes,String pageId,String isAdd) throws Exception {
+
+//       非1为添加  type为1时修改;
+        if( isAdd.equals("0")){
+            projectFinalAccounts.setIsNewRecord(true);
+            String projectNumber = projectFinalAccounts.getProjectOrCostName().substring(0,projectFinalAccounts.getProjectOrCostName().indexOf(","));
+            projectFinalAccounts.setProjectNumber(projectNumber);
+        }
+        /*if(StringUtils.isBlank(projectFinalAccounts.getProjectNumber()) && StringUtils.isNotBlank(projectFinalAccounts.getId())){
+            projectFinalAccounts = projectSubstationService.get(projectFinalAccounts.getId());
+        }*/
+//      判断项目名称、材料名称、是否为空
+        if(StringUtils.isBlank(projectFinalAccounts.getProjectNumber()) && StringUtils.isBlank(projectFinalAccounts.getProjectOrCostName())){
+            addMessage(redirectAttributes, "项目名称、材料名称等获取失败,请重试");
+            if("1".equals(pageId)){
+                return "redirect:"+Global.getAdminPath()+"/ruralProject/projectSubstation/?repage";
+            }
+            return "redirect:"+Global.getAdminPath()+"/project/projectSubstation";
+        }
+//        进行项目名称、材料名称、价格重复的判断
+        if(StringUtils.isBlank(projectFinalAccounts.getProjectNumber()) && StringUtils.isBlank(projectFinalAccounts.getProjectOrCostName())){
+            addMessage(redirectAttributes, "项目名称、材料名称等获取失败,请重试");
+            return "redirect:"+Global.getAdminPath()+"/project/projectSubstation";
+        }
+        //根据项目id查询报告号并重新赋值
+        RuralProjectRecords ruralProjectRecords = ruralProjectRecordsService.get(projectFinalAccounts.getProjectNumber());
+
+        if(null != ruralProjectRecords){
+            projectFinalAccounts.setProjectName(ruralProjectRecords.getProjectName());
+            //projectFinalAccounts.setReportNumber(ruralProjectRecords.getProjectReportNumber());
+        }
+
+        if(StringUtils.isNotBlank(projectFinalAccounts.getProjectNumber())){
+            projectFinalAccounts.setProjectId(projectFinalAccounts.getProjectNumber());
+        }
+        Map<String, Object> returnMap = projectSubstationService.saveInfo(projectFinalAccounts);
+        Boolean success = (Boolean) returnMap.get("success");
+        if(!success){
+            addMessage(redirectAttributes, returnMap.get("message").toString());
+            return "redirect:"+Global.getAdminPath()+"/project/projectSubstation/?repage";
+        }
+        return "redirect:"+Global.getAdminPath()+"/project/projectSubstation";
+    }
+
+    /**
+     * 批量删除
+     * @param listId
+     * @return
+     */
+    @RequestMapping(value = "deleteAll")
+    @ResponseBody
+    public Object deleteAll(String listId){
+        Map<String,Object> map = new HashMap<>();
+        try{
+            List<String> idList = Arrays.asList(listId.split(","));
+            List<ProjectSubstationImport> byIdList = Lists.newArrayList();
+            //根据id查询自己创建的材料信息数量
+            if(!UserUtils.getUser().isAdmin()){
+                byIdList = projectSubstationService.getByIdList(idList);
+                if(byIdList.size() < idList.size()){
+                    map.put("success",false);
+                    map.put("str","删除的材料信息保存不属于您创建的信息。请确认后重新删除");
+                }else{
+                    projectSubstationService.deleteListById(idList);
+                    map.put("success",true);
+                    map.put("str","材料信息删除成功");
+                }
+            }else{
+                projectSubstationService.deleteListById(idList);
+                map.put("success",true);
+                map.put("str","材料信息删除成功");
+            }
+        }catch (Exception e){
+            map.put("success",false);
+            map.put("str","删除失败");
+        }
+        return map;
+    }
+}

+ 45 - 10
src/main/java/com/jeeplus/modules/ruralprojectrecords/service/RuralProjectMessageElectronicSealService.java

@@ -3169,14 +3169,14 @@ public class RuralProjectMessageElectronicSealService extends CrudService<RuralP
         if("3".equals(projectReportSignatureInfo.getType())){
             //项目名称
             if(StringUtils.isNotBlank(ruralProjectRecords.getProjectName())){
-                data.put("projectName",ruralProjectRecords.getProjectName());
+                data.put("projectName",escapeFtlSpecialChars(ruralProjectRecords.getProjectName()));
             }else{
                 data.put("projectName","");
             }
             //报告号
             if(StringUtils.isNotBlank(projectReportSignatureInfo.getProjectReportId())){
-                data.put("projectReportId",projectReportSignatureInfo.getProjectReportId());
-                data.put("reportNumber",projectReportSignatureInfo.getProjectReportId());
+                data.put("projectReportId",escapeFtlSpecialChars(projectReportSignatureInfo.getProjectReportId()));
+                data.put("reportNumber",escapeFtlSpecialChars(projectReportSignatureInfo.getProjectReportId()));
             }else{
                 data.put("projectReportId","");
                 data.put("reportNumber","");
@@ -3217,7 +3217,7 @@ public class RuralProjectMessageElectronicSealService extends CrudService<RuralP
             }
             //施工单位考核费
             if(StringUtils.isNotBlank(projectReportSignatureInfo.getConstructionUnitAssessmentFee())){
-                data.put("constructionUnitAssessmentFee",projectReportSignatureInfo.getConstructionUnitAssessmentFee());
+                data.put("constructionUnitAssessmentFee",escapeFtlSpecialChars(projectReportSignatureInfo.getConstructionUnitAssessmentFee()));
             }else{
                 projectReportSignatureInfo.setConstructionUnitAssessmentFee("0");
                 data.put("constructionUnitAssessmentFee","0");
@@ -3239,31 +3239,31 @@ public class RuralProjectMessageElectronicSealService extends CrudService<RuralP
             }
             //施工单位
             if(StringUtils.isNotBlank(projectReportSignatureInfo.getConstructionUnit())){
-                data.put("constructionUnit",projectReportSignatureInfo.getConstructionUnit());
+                data.put("constructionUnit",escapeFtlSpecialChars(projectReportSignatureInfo.getConstructionUnit()));
             }else{
                 data.put("constructionUnit","");
             }
             //核减情况说明
             if(StringUtils.isNotBlank(projectReportSignatureInfo.getDescriptionOfDeduction())){
-                data.put("descriptionOfDeduction",projectReportSignatureInfo.getDescriptionOfDeduction());
+                data.put("descriptionOfDeduction",escapeFtlSpecialChars(projectReportSignatureInfo.getDescriptionOfDeduction()));
             }else{
                 data.put("descriptionOfDeduction","");
             }
             //特殊事项说明
             if(StringUtils.isNotBlank(projectReportSignatureInfo.getDescriptionOfSpecialMatters())){
-                data.put("descriptionOfSpecialMatters",projectReportSignatureInfo.getDescriptionOfSpecialMatters());
+                data.put("descriptionOfSpecialMatters",escapeFtlSpecialChars(projectReportSignatureInfo.getDescriptionOfSpecialMatters()));
             }else{
                 data.put("descriptionOfSpecialMatters","");
             }
             //工程主要内容
             if(StringUtils.isNotBlank(projectReportSignatureInfo.getMainContentsOfProject())){
-                data.put("mainContentsOfProject",projectReportSignatureInfo.getMainContentsOfProject());
+                data.put("mainContentsOfProject",escapeFtlSpecialChars(projectReportSignatureInfo.getMainContentsOfProject()));
             }else{
                 data.put("mainContentsOfProject","");
             }
             //工程审计项目
             if(StringUtils.isNotBlank(projectReportSignatureInfo.getProjectName())){
-                data.put("projectAuditName",projectReportSignatureInfo.getProjectName());
+                data.put("projectAuditName",escapeFtlSpecialChars(projectReportSignatureInfo.getProjectName()));
             }else{
                 data.put("projectAuditName","");
             }
@@ -3295,7 +3295,7 @@ public class RuralProjectMessageElectronicSealService extends CrudService<RuralP
 
             //建设单位
             if(StringUtils.isNotBlank(projectReportSignatureInfo.getDevelopmentOrganization())){
-                data.put("developmentOrganization",projectReportSignatureInfo.getDevelopmentOrganization());
+                data.put("developmentOrganization",escapeFtlSpecialChars(projectReportSignatureInfo.getDevelopmentOrganization()));
             }else{
                 data.put("developmentOrganization","");
             }
@@ -3320,6 +3320,41 @@ public class RuralProjectMessageElectronicSealService extends CrudService<RuralP
     }
 
     /**
+     * 转义字符串中的FTL特殊字符
+     * @param input 原始字符串(可能包含ftl特殊字符)
+     * @return 转义后的字符串(可安全用于ftl模板)
+     */
+    public static String escapeFtlSpecialChars(String input) {
+        if (input == null) {
+            return null;
+        }
+        // 转义顺序:先处理转义符本身,再处理其他符号(避免替换冲突)
+        return input
+                // 1. 处理反斜杠(FreeMarker中需用双反斜杠表示单个反斜杠)
+                .replace("\\", "\\\\")
+                // 2. 处理变量输出标记 ${ 和 }
+                .replace("${", "\\${")  // 在ftl字符串中,$需转义为\$
+                .replace("}", "\\}")    // }需转义为\}
+                // 3. 处理指令标记 <# 和 </#
+                .replace("<#", "<#text><#</#text>")  // 用<#text>包裹避免解析
+                .replace("</#", "<#text></#</#text>")
+                // 4. 处理宏调用标记 <@ 和 </@
+                .replace("<@", "<#text><@</#text>")
+                .replace("</@", "<#text></@</#text>")
+                // 5. 处理注释标记 #--
+                .replace("#--", "<#text>#--</#text>")
+                // 6. 处理单引号(在ftl单引号字符串中需转义为\')
+                .replace("'", "\\'")
+                // 7. 处理双引号(在ftl双引号字符串中需转义为\",Java中需额外转义"为\")
+                .replace("\"", "\\\\\"")
+                // 8. 处理内建函数标记 ?
+                .replace("?", "<#text>?</#text>")
+                // 9. 处理HTML相关符号 < 和 >(Web场景优先用实体,兼容ftl解析)
+                .replace("<", "&lt;")
+                .replace(">", "&gt;");
+    }
+
+    /**
      * 判断字符串是不是double型
      * @param str
      * @return

+ 2 - 2
src/main/java/com/jeeplus/modules/ruralprojectrecords/service/RuralProjectRecordsService.java

@@ -1155,7 +1155,7 @@ public class RuralProjectRecordsService extends CrudService<RuralProjectRecordsD
 			}
 		}
 		//判断项目类型并判断项目类别
-		switch (projectRecords.getProjectType()){
+		/*switch (projectRecords.getProjectType()){
 			case "1":
 				if("8".equals(projectRecords.getAttachmentProjectSort())){
 					projectRecords.setReportedState("10");
@@ -1170,7 +1170,7 @@ public class RuralProjectRecordsService extends CrudService<RuralProjectRecordsD
 					projectRecords.setReportedState(null);
 				}
 				break;
-		}
+		}*/
 
 
 		projectRecords.setCompany(company);

+ 42 - 0
src/main/java/com/jeeplus/modules/ruralprojectrecords/web/RuralCostProjectRecordsController.java

@@ -639,6 +639,48 @@ public class RuralCostProjectRecordsController extends BaseController {
 	public String adminUpdate(RuralProjectRecords projectRecords, RedirectAttributes redirectAttributes){
 		try {
 			RuralProjectRecords t = projectRecordsService.get(projectRecords.getId());//从数据库取出记录的值
+
+			if(!projectRecords.getAttachmentProjectSort().equals( t.getAttachmentProjectSort()) ) {
+				//判断项目类型并判断项目类别
+				switch (projectRecords.getProjectType()){
+					case "1":
+						if("8".equals(projectRecords.getAttachmentProjectSort()) && !"8".equals(t.getAttachmentProjectSort())){
+							projectRecords.setReportedState("10");
+						}else{
+							if("8".equals(t.getAttachmentProjectSort())){
+								projectRecords.setReportedState(null);
+							}
+						}
+						break;
+					case "2":
+						if(
+								("5".equals(projectRecords.getAttachmentProjectSort()) ||
+								"6".equals(projectRecords.getAttachmentProjectSort()) ||
+								"8".equals(projectRecords.getAttachmentProjectSort()) ||
+								"20".equals(projectRecords.getAttachmentProjectSort()) ||
+								"10".equals(projectRecords.getAttachmentProjectSort()))
+										&&
+								(!"5".equals(t.getAttachmentProjectSort()) &&
+								!"6".equals(t.getAttachmentProjectSort()) &&
+								!"8".equals(t.getAttachmentProjectSort()) &&
+								!"20".equals(t.getAttachmentProjectSort()) &&
+								!"10".equals(t.getAttachmentProjectSort()))
+						){
+							projectRecords.setReportedState("10");
+						}else{
+							if("5".equals(t.getAttachmentProjectSort()) ||
+									"6".equals(t.getAttachmentProjectSort()) ||
+									"8".equals(t.getAttachmentProjectSort()) ||
+									"20".equals(t.getAttachmentProjectSort()) ||
+									"10".equals(t.getAttachmentProjectSort())){
+								projectRecords.setReportedState(null);
+							}
+						}
+						break;
+				}
+			}
+
+
 			MyBeanUtils.copyBeanNotNull2Bean(projectRecords, t);//将编辑表单中的非NULL值覆盖数据库记录中的值
 			projectRecordsService.adminUpdateProject(t);//管理员修改项目信息
 			addMessage(redirectAttributes, "管理员修改项目成功");

+ 19 - 3
src/main/java/com/jeeplus/modules/signatureManagement/businessSignature/controller/BusinessSignatureController.java

@@ -15,6 +15,7 @@ import com.jeeplus.modules.ruralprojectrecords.enums.ProjectStatusEnum;
 import com.jeeplus.modules.signatureManagement.businessSignature.entity.BusinessSignatureInfo;
 import com.jeeplus.modules.signatureManagement.businessSignature.service.BusinessSignatureService;
 import com.jeeplus.modules.signatureManagement.electronicSignature.entity.ElectronicSignatureInfo;
+import com.jeeplus.modules.signatureManagement.electronicSignature.service.DistrictDirectorApplicationService;
 import com.jeeplus.modules.sys.entity.MainDictDetail;
 import com.jeeplus.modules.sys.entity.User;
 import com.jeeplus.modules.sys.utils.DictUtils;
@@ -57,6 +58,8 @@ public class BusinessSignatureController extends BaseController {
     private AreaStaffService areaStaffService;
     @Autowired
     private WorkProjectNotifyService workProjectNotifyService;
+    @Autowired
+    private DistrictDirectorApplicationService directorApplicationService;
 
     @ModelAttribute
     public BusinessSignatureInfo get(@RequestParam(required=false) String id) {
@@ -103,9 +106,22 @@ public class BusinessSignatureController extends BaseController {
         //获取当前登录人是否是地区负责人
         User user = UserUtils.getUser();
         if(StringUtils.isNotBlank(user.getId())){
-            AreaStaffInfo masterStateByUserId = areaStaffService.getMasterStateByUserId(user.getId());
-            if(null != masterStateByUserId){
-                model.addAttribute("masterState", "1");
+            //查询是否发起 签章责任人申请。申请的是否为业务签章
+            String applySealTypeByUserId = directorApplicationService.getApplySealTypeByUserId(user.getId());
+            if(StringUtils.isNotBlank(applySealTypeByUserId)){
+                // 1. 按逗号拆分字符串为数组
+                String[] strArray = applySealTypeByUserId.split(",");
+
+                // 2. 将数组转换为List(注意:Arrays.asList()返回的是固定大小的List,不可添加/删除元素)
+                List<String> applySealTypeList = Arrays.asList(strArray);
+
+                for (String applySealType : applySealTypeList) {
+                    if("2".equals(applySealType)){
+                        model.addAttribute("masterState", "1");
+                        break;
+                    }
+                }
+
             }
         }
 

+ 19 - 1
src/main/java/com/jeeplus/modules/signatureManagement/electronicSignature/controller/ElectronicSignatureController.java

@@ -115,7 +115,25 @@ public class ElectronicSignatureController extends BaseController {
         if(StringUtils.isNotBlank(user.getId())){
             AreaStaffInfo masterStateByUserId = areaStaffService.getMasterStateByUserId(user.getId());
             if(null != masterStateByUserId){
-                model.addAttribute("masterState", "1");
+                //如果是地区负责人,并且还需要验证是否是电子章的申请
+
+                //查询是否发起 签章责任人申请。申请的是否为业务签章
+                String applySealTypeByUserId = directorApplicationService.getApplySealTypeByUserId(user.getId());
+                if(StringUtils.isNotBlank(applySealTypeByUserId)){
+                    // 1. 按逗号拆分字符串为数组
+                    String[] strArray = applySealTypeByUserId.split(",");
+
+                    // 2. 将数组转换为List(注意:Arrays.asList()返回的是固定大小的List,不可添加/删除元素)
+                    List<String> applySealTypeList = Arrays.asList(strArray);
+
+                    for (String applySealType : applySealTypeList) {
+                        if("1".equals(applySealType)){
+                            model.addAttribute("masterState", "1");
+                            break;
+                        }
+                    }
+
+                }
             }
         }
 

+ 7 - 0
src/main/java/com/jeeplus/modules/signatureManagement/electronicSignature/dao/DistrictDirectorApplicationDao.java

@@ -15,4 +15,11 @@ public interface DistrictDirectorApplicationDao extends CrudDao<DistrictDirector
     List<DistrictDirectorApplication> findByArea(DistrictDirectorApplication directorApplication);
     //查询当前登录人是否已经提过申请
     List<DistrictDirectorApplication> isApplyArea(DistrictDirectorApplication directorApplication);
+
+    /**
+     * 根据登陆人查询是否有已审核完成的数据的applySealType值
+     * @param userId
+     * @return
+     */
+    public String getApplySealTypeByUserId(String userId);
 }

+ 19 - 0
src/main/java/com/jeeplus/modules/signatureManagement/electronicSignature/entity/DistrictDirectorApplication.java

@@ -6,6 +6,7 @@ import com.jeeplus.modules.sys.entity.Office;
 import org.activiti.engine.runtime.ProcessInstance;
 
 import java.util.Date;
+import java.util.List;
 
 /**
  * 地区负责人申请
@@ -31,6 +32,8 @@ public class DistrictDirectorApplication extends ActEntity<DistrictDirectorAppli
     private Date endDate;   //申请结束时间
     private Integer notifyFlag; //代办判定条件
     private String notifyId; //代办判定条件
+    private String applySealType; //代办判定条件
+    private List<String> applySealTypeList; //代办判定条件
 
     public Integer getNotifyFlag() {
         return notifyFlag;
@@ -162,4 +165,20 @@ public class DistrictDirectorApplication extends ActEntity<DistrictDirectorAppli
     public void setArea(Area area) {
         this.area = area;
     }
+
+    public String getApplySealType() {
+        return applySealType;
+    }
+
+    public void setApplySealType(String applySealType) {
+        this.applySealType = applySealType;
+    }
+
+    public List<String> getApplySealTypeList() {
+        return applySealTypeList;
+    }
+
+    public void setApplySealTypeList(List<String> applySealTypeList) {
+        this.applySealTypeList = applySealTypeList;
+    }
 }

+ 99 - 19
src/main/java/com/jeeplus/modules/signatureManagement/electronicSignature/service/DistrictDirectorApplicationService.java

@@ -87,6 +87,14 @@ public class DistrictDirectorApplicationService extends CrudService<DistrictDire
     public DistrictDirectorApplication get(String id){
         DistrictDirectorApplication directorApplication = new DistrictDirectorApplication();
         directorApplication=super.get(id);
+        if(null != directorApplication && StringUtils.isNotBlank(directorApplication.getApplySealType())){
+            // 1. 按逗号拆分字符串为数组
+            String[] strArray = directorApplication.getApplySealType().split(",");
+
+            // 2. 将数组转换为List(注意:Arrays.asList()返回的是固定大小的List,不可添加/删除元素)
+            List<String> list = Arrays.asList(strArray);
+            directorApplication.setApplySealTypeList(list);
+        }
         return directorApplication;
     }
 
@@ -133,6 +141,9 @@ public class DistrictDirectorApplicationService extends CrudService<DistrictDire
 
     @Transactional(readOnly = false)
     public String saveBranch(DistrictDirectorApplication directorApplication, Map<String, Object> variables, String processInstanceId) {
+        // 用逗号分隔,自动忽略null元素
+        String applySealTypeListStr = StringUtils.join(directorApplication.getApplySealTypeList(), ",");
+        directorApplication.setApplySealType(applySealTypeListStr);
 
         String officeId = UserUtils.getUser().getOffice().getId();
         Office office = officeService.get(officeId);
@@ -144,8 +155,16 @@ public class DistrictDirectorApplicationService extends CrudService<DistrictDire
         directorApplication.setArea(area);
         super.save(directorApplication);
         String str = "";
-        String title = "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"待审批";
-        str = "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"待审批";
+        String title = "";
+        if(applySealTypeListStr.contains("1")){
+            title = "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"待审批";
+            str = "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"待审批";
+        }else{
+            title = directorApplication.getCreateBy().getName()+"-申请业务专用章待审批";
+            str = directorApplication.getCreateBy().getName()+"-申请业务专用章待审批";
+        }
+
+
 
         // 启动流程
         String businessKey = directorApplication.getId().toString();
@@ -230,7 +249,12 @@ public class DistrictDirectorApplicationService extends CrudService<DistrictDire
         }
         variables.put("type", processType);
         variables.put("busId", businessKey);
-        variables.put("title", "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"待审批");//设置标题;
+        if(applySealTypeListStr.contains("1")){
+            variables.put("title", "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"待审批");//设置标题;
+        }else{
+            variables.put("title", directorApplication.getCreateBy().getName()+"-申请业务专用章待审批");//设置标题;
+        }
+
         ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processType, businessKey, variables);
         directorApplication.setProcessInstance(processInstance);
         if (StringUtils.isNotBlank(processInstanceId)) {
@@ -266,8 +290,10 @@ public class DistrictDirectorApplicationService extends CrudService<DistrictDire
 
     @Transactional(readOnly = false)
     public String auditSaveBranch(DistrictDirectorApplication directorApplication, List<User> auditUsers) {
-        String userName = UserUtils.get(directorApplication.getCreateBy().getId()).getName();
-        Office office = officeService.get(directorApplication.getOfficeId());
+        // 用逗号分隔,自动忽略null元素
+        String applySealTypeListStr = StringUtils.join(directorApplication.getApplySealTypeList(), ",");
+        directorApplication.setApplySealType(applySealTypeListStr);
+
         String str =  "地区负责人申请";
         String title = "地区负责人申请";
         // 对不同环节的业务逻辑进行操作
@@ -363,17 +389,43 @@ public class DistrictDirectorApplicationService extends CrudService<DistrictDire
                     exp = "pass";
                     if ("yes".equals(directorApplication.getAct().getFlag())) {
                         //审核通过处理
-                        //根据申请人的隶属部门和申请地区查询是否存在该人员信息,若不存在则进行添加
-                        Map<String, Object> stringObjectMap = areaStaffService.disposeAreaMaster(directorApplication);
-                        Boolean success = (Boolean) stringObjectMap.get("success");
-                        String message = (String) stringObjectMap.get("message");
-                        if(! success){
-                            return message;
+
+
+                        if(applySealTypeListStr.contains("1")){
+                            //根据申请人的隶属部门和申请地区查询是否存在该人员信息,若不存在则进行添加
+                            Map<String, Object> stringObjectMap = areaStaffService.disposeAreaMaster(directorApplication);
+                            Boolean success = (Boolean) stringObjectMap.get("success");
+                            String message = (String) stringObjectMap.get("message");
+                            if(! success){
+                                return message;
+                            }
+
+                            notifyRole = "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"审核完成";
+                        }else{
+
+                            //根据申请人的隶属部门和申请地区查询是否存在该人员信息,若不存在则进行添加
+                            Map<String, Object> stringObjectMap = areaStaffService.disposeAreaMaster(directorApplication);
+                            Boolean success = (Boolean) stringObjectMap.get("success");
+                            String message = (String) stringObjectMap.get("message");
+                            if(!success && "该地区已存在,无法重复添加".equals(message)){
+                                List<AreaStaffInfo> areaInfo = (List<AreaStaffInfo>) stringObjectMap.get("areaInfo");
+                                if(areaInfo.size()>0){
+                                    for (AreaStaffInfo areaStaffInfo : areaInfo) {
+                                        //需要遍历把对应数据都给删除
+                                        areaStaffService.delete(areaStaffInfo);
+                                    }
+                                }
+                            }
+                            notifyRole = directorApplication.getCreateBy().getName()+"-申请业务专用章审核完成";
                         }
-                        notifyRole = "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"审核完成";
+
                         workActivityProcess.setIsApproval("1");
                     } else {
-                        notifyRole = "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"调整申请";
+                        if(applySealTypeListStr.contains("1")){
+                            notifyRole = "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"调整申请";
+                        }else{
+                            notifyRole = directorApplication.getCreateBy().getName()+"-申请业务专用章调整申请";
+                        }
                         workActivityProcess.setIsApproval("2");
                     }
                     break;
@@ -459,8 +511,15 @@ public class DistrictDirectorApplicationService extends CrudService<DistrictDire
         List<User> users = new ArrayList<>();
         List<User> userList = new ArrayList<>();
         if (!state) {
-            str =  "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"申请成功" ;
-            title = "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"申请成功";
+
+            if(applySealTypeListStr.contains("1")){
+                str =  "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"申请成功" ;
+                title = "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"申请成功";
+            }else{
+                str =  directorApplication.getCreateBy().getName()+"-申请业务专用章申请成功" ;
+                title = directorApplication.getCreateBy().getName()+"-申请业务专用章申请成功";
+            }
+
             users.add(directorApplication.getCreateBy());
             if ("yes".equals(directorApplication.getAct().getFlag())) {
                 directorApplication.setStatus("5");
@@ -573,8 +632,15 @@ public class DistrictDirectorApplicationService extends CrudService<DistrictDire
 
             } else {
                 if (!"yes".equals(directorApplication.getAct().getFlag())) {//驳回待办提醒
-                    title = "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"申请被驳回";
-                    str = "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"申请被驳回,请重新申请";
+
+                    if(applySealTypeListStr.contains("1")){
+                        title = "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"申请被驳回";
+                        str = "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"申请被驳回,请重新申请";
+                    }else{
+                        str =  directorApplication.getCreateBy().getName()+"-申请业务专用章申请被驳回" ;
+                        title = directorApplication.getCreateBy().getName()+"-申请业务专用章申请被驳回,请重新申请";
+                    }
+
                     WorkProjectNotify notify = new WorkProjectNotify();
                     notify.setNotifyId(directorApplication.getId());
                     userList = workProjectNotifyService.readByNotifyId(notify);
@@ -593,8 +659,13 @@ public class DistrictDirectorApplicationService extends CrudService<DistrictDire
                     users.add( directorApplication.getCreateBy());
                 } else {
                     if (StringUtils.isNotBlank(enname)) {//驳回重新申请待办
-                        title = "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"重新申请,待审批";
-                        str = "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"重新申请,待审批";
+                        if(applySealTypeListStr.contains("1")){
+                            title = "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"重新申请,待审批";
+                            str = "地区负责人申请-"+directorApplication.getCreateBy().getName()+"-负责区域:"+directorApplication.getTerritory()+"重新申请,待审批";
+                        }else{
+                            str =  directorApplication.getCreateBy().getName()+"-申请业务专用章申请被驳回" ;
+                            title = directorApplication.getCreateBy().getName()+"-申请业务专用章申请被驳回,请重新申请";
+                        }
                         WorkProjectNotify notify = new WorkProjectNotify();
                         notify.setNotifyId(directorApplication.getId());
                         userList = workProjectNotifyService.readByNotifyId(notify);
@@ -768,4 +839,13 @@ public class DistrictDirectorApplicationService extends CrudService<DistrictDire
         }
     }
 
+    /**
+     * 根据登陆人查询是否有已审核完成的数据的applySealType值
+     * @param userId
+     * @return
+     */
+    public String getApplySealTypeByUserId(String userId){
+        return dao.getApplySealTypeByUserId(userId);
+    }
+
 }

+ 12 - 2
src/main/java/com/jeeplus/modules/workreimbursement/entity/ReimbursementVATTax.java

@@ -45,6 +45,7 @@ public class ReimbursementVATTax extends DataEntity<ReimbursementVATTax> {
 	private String reimbursementId;		// 报销id
 	private String processInstanceId;		// 报销流程id
 	private String handlingPerson;		// 处理人员
+	private String paymentDate;		// 付款时间
 
 	private String remarks;
 
@@ -223,7 +224,16 @@ public class ReimbursementVATTax extends DataEntity<ReimbursementVATTax> {
 		this.submitterDate = submitterDate;
 	}
 
-	@ExcelField(title="备注", align=2, sort=14)
+	@ExcelField(title="付款日期", align=2, sort=13)
+	public String getPaymentDate() {
+		return paymentDate;
+	}
+
+	public void setPaymentDate(String paymentDate) {
+		this.paymentDate = paymentDate;
+	}
+
+	@ExcelField(title="备注", align=2, sort=15)
 	@Override
 	public String getRemarks() {
 		return remarks;
@@ -259,7 +269,7 @@ public class ReimbursementVATTax extends DataEntity<ReimbursementVATTax> {
 		this.processInstanceId = processInstanceId;
 	}
 
-	@ExcelField(title="当前节点审核人员", align=2, sort=13)
+	@ExcelField(title="当前节点审核人员", align=2, sort=14)
 	public String getHandlingPerson() {
 		return handlingPerson;
 	}

+ 12 - 0
src/main/java/com/jeeplus/modules/workreimbursement/entity/WorkReimbursement.java

@@ -151,6 +151,7 @@ public class WorkReimbursement extends ActEntity<WorkReimbursement> {
 	private String bank;//开户行
 	private String bankNo;//银行账号
 	private List<WorkAccount> workAccountList = Lists.newArrayList();		// 子表列表
+	private Integer workAccountListCount;		// 子表列表行数
 	private List<ReimbursementVATTax> reimbursementVATTaxes = Lists.newArrayList();		// 子表列表
 	private List<ReimbursementVATTax> reimbursementElectronicInvoiceVATTaxes = Lists.newArrayList();		// 子表列表
 	private List<WorkReimbursementBack> reimbursementBackList = Lists.newArrayList();		// 子表列表
@@ -354,6 +355,14 @@ public class WorkReimbursement extends ActEntity<WorkReimbursement> {
 		this.workAccountList = workAccountList;
 	}
 
+	public Integer getWorkAccountListCount() {
+		return workAccountListCount;
+	}
+
+	public void setWorkAccountListCount(Integer workAccountListCount) {
+		this.workAccountListCount = workAccountListCount;
+	}
+
 	public List<ReimbursementVATTax> getReimbursementVATTaxes() {
 		return reimbursementVATTaxes;
 	}
@@ -696,6 +705,7 @@ public class WorkReimbursement extends ActEntity<WorkReimbursement> {
 		this.pageFlag = pageFlag;
 	}
 
+	@ExcelField(title="付款状态", align=2, sort=12)
 	public String getPaymentStatus() {
 		return paymentStatus;
 	}
@@ -704,6 +714,8 @@ public class WorkReimbursement extends ActEntity<WorkReimbursement> {
 		this.paymentStatus = paymentStatus;
 	}
 
+	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+	@ExcelField(title="付款日期", align=2, sort=13)
 	public Date getPaymentDate() {
 		return paymentDate;
 	}

+ 1 - 0
src/main/java/com/jeeplus/modules/workreimbursement/service/WorkReimbursementAllService.java

@@ -160,6 +160,7 @@ public class WorkReimbursementAllService extends CrudService<WorkReimbursementDa
                     workAccount.getProject().setProjectName(workAccount.getReimburseRemarks());
                 }
             }
+            workReimbursement.setWorkAccountListCount(workAccounts.size());
             workReimbursement.setWorkAccountList(workAccounts);
             List<ReimbursementVATTax> vatTaxDaoList = reimbursementVATTaxDao.findList(new ReimbursementVATTax(workReimbursement));
             List<ReimbursementVATTax> vatTaxList = Lists.newArrayList();

+ 15 - 12
src/main/java/com/jeeplus/modules/workreimbursement/service/WorkReimbursementService.java

@@ -186,6 +186,7 @@ public class WorkReimbursementService extends CrudService<WorkReimbursementDao,
                     workAccount.getProject().setProjectName(workAccount.getReimburseRemarks());
                 }
             }
+            workReimbursement.setWorkAccountListCount(workAccounts.size());
             workReimbursement.setWorkAccountList(workAccounts);
             List<ReimbursementVATTax> vatTaxDaoList = reimbursementVATTaxDao.findList(new ReimbursementVATTax(workReimbursement));
             List<ReimbursementVATTax> vatTaxList = Lists.newArrayList();
@@ -412,18 +413,20 @@ public class WorkReimbursementService extends CrudService<WorkReimbursementDao,
 
             //处理报销人信息
             //添加人员部门id(可能是多个人员信息,此处需要根据,进行分割并重新处理)
-            List<String> reimbursementuserIdlist = Arrays.asList(info.getSubmitterId().split(","));
-            Set<String> submitterOfficeName = new HashSet<>();
-            Set<String> submitterName = new HashSet<>();
-            for (String userId : reimbursementuserIdlist) {
-                User user = UserUtils.get(userId);
-                if(null != user){
-                    submitterName.add(user.getName());
-                    submitterOfficeName.add(user.getOffice().getName());
-                }
-            }
-            info.setSubmitterName(String.join(",", submitterName));
-            info.setSubmitterOfficeName(String.join(",", submitterOfficeName));
+            if(StringUtils.isNotBlank(info.getSubmitterId())){
+                List<String> reimbursementuserIdlist = Arrays.asList(info.getSubmitterId().split(","));
+                Set<String> submitterOfficeName = new HashSet<>();
+                Set<String> submitterName = new HashSet<>();
+                for (String userId : reimbursementuserIdlist) {
+                    User user = UserUtils.get(userId);
+                    if(null != user){
+                        submitterName.add(user.getName());
+                        submitterOfficeName.add(user.getOffice().getName());
+                    }
+                }
+                info.setSubmitterName(String.join(",", submitterName));
+                info.setSubmitterOfficeName(String.join(",", submitterOfficeName));
+            }
 
             WorkAccount workAccount = info.getWorkAccount();
             if(null != workAccount){

+ 10 - 1
src/main/java/com/jeeplus/modules/workreimbursement/web/WorkReimbursementAllController.java

@@ -757,12 +757,21 @@ public class WorkReimbursementAllController extends BaseController {
 	/**
 	 * 导出excel文件
 	 */
-	@RequiresPermissions("workreimbursement:workReimbursementAll:export")
+	//@RequiresPermissions("workreimbursement:workReimbursementAll:export")
 	@RequestMapping(value = "export", method=RequestMethod.POST)
 	public String exportFile(WorkReimbursement workReimbursement, HttpServletRequest request, HttpServletResponse response, RedirectAttributes redirectAttributes) {
 		try {
 			String fileName = "报销单"+DateUtils.getDate("yyyyMMddHHmmss")+".xlsx";
 			Page<WorkReimbursement> page = workReimbursementService.findPage(new Page<WorkReimbursement>(request, response, -1), workReimbursement);
+			for (WorkReimbursement reimbursement : page.getList()) {
+				if(StringUtils.isNotBlank(reimbursement.getPaymentStatus())){
+					if("1".equals(reimbursement.getPaymentStatus())){
+						reimbursement.setPaymentStatus("已付款");
+					}else if("0".equals(reimbursement.getPaymentStatus())){
+						reimbursement.setPaymentStatus("未付款");
+					}
+				}
+			}
 			String agent = request.getHeader("USER-AGENT");
 			new ExportExcel("报销单", WorkReimbursement.class).setDataList(page.getList()).write(response, fileName,agent).dispose();
 			return null;

+ 11 - 1
src/main/java/com/jeeplus/modules/workreimbursement/web/WorkReimbursementController.java

@@ -704,12 +704,22 @@ public class WorkReimbursementController extends BaseController {
     /**
      * 导出excel文件
      */
-    @RequiresPermissions("workreimbursement:workReimbursement:export")
+    //@RequiresPermissions("workreimbursement:workReimbursement:export")
     @RequestMapping(value = "export", method = RequestMethod.POST)
     public String exportFile(WorkReimbursement workReimbursement, HttpServletRequest request, HttpServletResponse response, RedirectAttributes redirectAttributes) {
         try {
             String fileName = "报销单" + DateUtils.getDate("yyyyMMddHHmmss") + ".xlsx";
             Page<WorkReimbursement> page = workReimbursementService.findPage(new Page<WorkReimbursement>(request, response, -1), workReimbursement);
+            for (WorkReimbursement reimbursement : page.getList()) {
+                if(StringUtils.isNotBlank(reimbursement.getPaymentStatus())){
+                    if("1".equals(reimbursement.getPaymentStatus())){
+                        reimbursement.setPaymentStatus("已付款");
+                    }else if("0".equals(reimbursement.getPaymentStatus())){
+                        reimbursement.setPaymentStatus("未付款");
+                    }
+                }
+            }
+
             String agent = request.getHeader("USER-AGENT");
             new ExportExcel("报销单", WorkReimbursement.class).setDataList(page.getList()).write(response, fileName, agent).dispose();
             return null;

+ 339 - 0
src/main/resources/mappings/modules/projectStatement/ProjectAccountsDao.xml

@@ -0,0 +1,339 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.jeeplus.modules.projectStatement.dao.ProjectFinalAccountsDao">
+
+	<sql id="projectMaterialStorageColumns">
+		a.id AS "id",
+		a.create_by AS "createBy.id",
+		a.create_date AS "createDate",
+		a.update_by AS "updateBy.id",
+		a.update_date AS "updateDate",
+		a.del_flag AS "delFlag",
+		a.remarks as "remarks",
+
+		a.number  As "number",
+		a.project_or_cost_name AS "projectOrCostName",
+		a.settlement_sum as "settlementSum",
+		a.project_id as "projectid"
+	</sql>
+	<sql id="projectSubstatColumns">
+		a.id AS "id",
+		a.create_by AS "createBy.id",
+		a.create_date AS "createDate",
+		a.update_by AS "updateBy.id",
+		a.update_date AS "updateDate",
+		a.del_flag AS "delFlag",
+		a.remarks as "remarks",
+
+		a.number As "number",
+		a.project_or_cost_name AS "projectOrCostName",
+		a.engineering_cost  as "engineeringCost",
+		a.labor_cost as "laborCost",
+		a.estimated_material_cost as "estimatedMaterialCost",
+		a.total_cost as "totalCost",
+		a.project_id as "projectid"
+	</sql>
+
+
+
+	<select id="get" resultType="com.jeeplus.modules.projectStatement.entity.ProjectFinalAccounts" >
+		SELECT
+			<include refid="projectMaterialStorageColumns"/>
+		    ,r.project_name as "projectName"
+		FROM project_accounts a
+		left join rural_project_records r on a.project_id = r.id
+		WHERE a.id = #{id}
+	</select>
+
+
+	<select id="getByProjectId" resultType="com.jeeplus.modules.projectStatement.entity.ProjectFinalAccountsImport" >
+		SELECT
+		<include refid="projectMaterialStorageColumns"/>
+		FROM project_accounts a
+		left join rural_project_records r on a.project_id = r.id
+		WHERE r.id = #{projectId} and a.del_flag = 0
+	</select>
+
+	<select id="findList" resultType="com.jeeplus.modules.projectStatement.entity.ProjectFinalAccounts" >
+		SELECT
+		<include refid="projectMaterialStorageColumns"/>
+		/*,r.project_name as "projectName"
+		,prd.number as "reportNumber"*/
+		FROM project_accounts a
+		/*left join rural_project_records r on a.project_id = r.id
+		    left join project_report_data prd on prd.project_id = a.id*/
+		<where>
+			a.del_flag = #{DEL_FLAG_NORMAL}
+
+		</where>
+		<choose>
+			<when test="page !=null and page.orderBy != null and page.orderBy != ''">
+				ORDER BY ${page.orderBy}
+			</when>
+			<otherwise>
+				ORDER BY a.create_date DESC
+			</otherwise>
+		</choose>
+	</select>
+
+	<select id="findAllList" resultType="com.jeeplus.modules.projectStatement.entity.ProjectFinalAccounts" >
+		SELECT
+			<include refid="projectMaterialStorageColumns"/>
+		FROM project a
+		<where>
+			a.del_flag = #{DEL_FLAG_NORMAL}
+		</where>
+		<choose>
+			<when test="page !=null and page.orderBy != null and page.orderBy != ''">
+				ORDER BY ${page.orderBy}
+			</when>
+			<otherwise>
+				ORDER BY a.update_date DESC
+			</otherwise>
+		</choose>
+	</select>
+
+	<insert id="insert">
+		INSERT INTO project_accounts(
+			id,
+			create_by,
+			create_date,
+			update_by,
+			update_date,
+			del_flag,
+			remarks,
+
+			number,
+			project_or_cost_name,
+			settlement_sum,
+			project_id
+		) VALUES (
+		 #{id},
+		 #{createBy.id},
+		 #{createDate},
+		 #{updateBy.id},
+		 #{updateDate},
+		 #{delFlag},
+		 #{remarks},
+
+		 #{number},
+		 #{projectOrCostName},
+		 #{settlementSum},
+		 #{projectId}
+		)
+	</insert>
+
+	<insert id="saveUniqueInfo">
+		INSERT INTO project_accounts(
+			id,
+			create_by,
+			create_date,
+			update_by,
+			update_date,
+			del_flag,
+			remarks,
+
+			number,
+			project_or_cost_name,
+			settlement_sum,
+			project_id
+		) VALUES (
+			#{id},
+			#{createBy.id},
+			#{createDate},
+			#{updateBy.id},
+			#{updateDate},
+			#{delFlag},
+			#{remarks},
+
+			#{number},
+			#{projectOrCostName},
+			#{settlementSum},
+			#{projectId}
+		)
+	</insert>
+
+	<update id="update">
+		UPDATE project_accounts SET
+			update_by = #{updateBy.id},
+			update_date = #{updateDate},
+			del_flag = #{delFlag},
+			remarks = #{remarks},
+
+			number = #{number},
+			project_or_cost_name = #{projectOrCostName},
+			settlement_sum = #{settlementSum},
+			project_id = #{projectId}
+		WHERE id = #{id}
+	</update>
+
+
+	<!--物理删除-->
+	<update id="delete">
+		DELETE FROM project_accounts
+		WHERE id = #{id}
+	</update>
+
+	<!--物理删除-->
+	<update id="deleteById">
+		DELETE FROM project_accounts
+		WHERE id = #{id}
+	</update>
+
+	<!--逻辑删除-->
+	<update id="deleteByLogic">
+		UPDATE project_accounts SET
+			del_flag = #{DEL_FLAG_DELETE}
+		WHERE id = #{id}
+	</update>
+
+	<select id="getProjectName" resultType="string">
+		select DISTINCT(r.project_or_cost_name)
+		 from project_accounts s left join rural_project_records r
+		 on s.project_id = r.id
+		 where s.project_id = #{projectId} and s.del_flag = 0
+	</select>
+
+
+	<select id="selectCountAboutProjectMaterialStorage" resultType="integer">
+		select count(1)
+		from project_accounts
+		<where>
+			del_flag = 0
+			and number = #{number}
+			and project_or_cost_name = #{projectOrCostName}
+			and settlement_sum = #{settlementSum}
+		</where>
+	</select>
+
+
+	<select id="getByInfo" resultType="com.jeeplus.modules.projectStatement.entity.ProjectFinalAccounts">
+		SELECT
+		<include refid="projectMaterialStorageColumns"/>
+		FROM project_accounts a
+		left join rural_project_records r on a.project_id = r.id
+		<where>
+			a.del_flag = 0
+			and number = #{number}
+			and project_or_cost_name = #{projectOrCostName}
+			and settlement_sum = #{settlementSum}
+			<if test="projectId !=null and projectId != ''">
+				AND a.project_id = #{projectId}
+			</if>
+			<if test="id !=null and id != ''">
+				AND a.id != #{id}
+			</if>
+		</where>
+	</select>
+
+
+	<insert id="batchInsert">
+		insert into project_accounts
+		(id,
+		create_by,
+		create_date,
+		update_by,
+		update_date,
+		del_flag,
+		remarks,
+		number,
+		project_or_cost_name,
+		settlement_sum,
+		project_id
+		)
+		values
+		<foreach collection="projectMaterialStorageImportList" item="data" separator=",">
+		(#{data.id},
+		#{data.createBy.id},
+		#{data.createDate},
+		#{data.updateBy.id},
+		#{data.updateDate},
+		#{data.delFlag},
+		#{data.remarks},
+		#{data.number},
+		#{data.projectOrCostName},
+		#{data.settlementSum},
+		#{data.projectId}
+		)
+		</foreach>
+	</insert>
+
+	<select id="queryCount" resultType="int">
+
+		SELECT
+			count(1)
+		FROM project_accounts a
+		<where>
+			a.del_flag = #{DEL_FLAG_NORMAL}
+		</where>
+
+
+	</select>
+
+	<delete id="deleteListById">
+		delete from project_accounts where id in
+		<foreach collection="idList" item="item" index="no" open="(" separator="," close=")">
+			#{item}
+		</foreach>
+	</delete>
+
+	<select id="getByIdList" resultType="com.jeeplus.modules.projectStatement.entity.ProjectFinalAccounts">
+		SELECT
+		<include refid="projectMaterialStorageColumns"/>
+		FROM project_accounts a
+		WHERE a.id in
+		<foreach collection="idList" item="item" index="no" open="(" separator="," close=")">
+			#{item}
+		</foreach>
+		and a.del_flag = 0 and a.create_by = #{createBy}
+	</select>
+
+
+
+	<select id="getSubstationByProjectId" resultType="com.jeeplus.modules.projectStatement.entity.ProjectSubstationImport" >
+		SELECT
+		<include refid="projectSubstatColumns"/>
+		FROM project_substat a
+		left join rural_project_records r on a.project_id = r.id
+		WHERE r.id = #{projectId} and a.del_flag = 0
+	</select>
+
+
+
+	<insert id="saveSubstationUniqueInfo">
+		INSERT INTO project_substat(
+			id,
+			create_by,
+			create_date,
+			update_by,
+			update_date,
+			del_flag,
+			remarks,
+
+			number,
+			project_or_cost_name,
+			engineering_cost,
+			labor_cost,
+			estimated_material_cost,
+			total_cost,
+			project_id
+		) VALUES (
+			 #{id},
+			 #{createBy.id},
+			 #{createDate},
+			 #{updateBy.id},
+			 #{updateDate},
+			 #{delFlag},
+			 #{remarks},
+
+			 #{number},
+			 #{projectOrCostName},
+			 #{engineeringCost},
+			 #{laborCost},
+			 #{estimatedMaterialCost},
+			 #{totalCost},
+			 #{projectId}
+		)
+	</insert>
+
+</mapper>

+ 297 - 0
src/main/resources/mappings/modules/projectStatement/substation/ProjectSubstationDao.xml

@@ -0,0 +1,297 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.jeeplus.modules.projectStatement.dao.ProjectSubstationDao">
+
+	<sql id="projectSubstatColumns">
+		a.id AS "id",
+		a.create_by AS "createBy.id",
+		a.create_date AS "createDate",
+		a.update_by AS "updateBy.id",
+		a.update_date AS "updateDate",
+		a.del_flag AS "delFlag",
+		a.remarks as "remarks",
+
+		a.number As "number",
+		a.project_or_cost_name AS "projectOrCostName",
+		a.engineering_cost  as "engineeringCost",
+		a.labor_cost as "laborCost",
+		a.estimated_material_cost as "estimatedMaterialCost",
+		a.total_cost as "totalCost",
+		a.project_id as "projectid"
+	</sql>
+
+
+
+	<select id="get" resultType="com.jeeplus.modules.projectStatement.entity.ProjectSubstationImport" >
+		SELECT
+			<include refid="projectSubstatColumns"/>
+		    ,r.project_name as "projectName"
+		FROM project_substat a
+		left join rural_project_records r on a.project_id = r.id
+		WHERE a.id = #{id}
+	</select>
+
+
+	<select id="getByProjectId" resultType="com.jeeplus.modules.projectStatement.entity.ProjectSubstationImport" >
+		SELECT
+		<include refid="projectSubstatColumns"/>
+		FROM project_substat a
+		left join rural_project_records r on a.project_id = r.id
+		WHERE r.id = #{projectId} and a.del_flag = 0
+	</select>
+
+	<select id="findList" resultType="com.jeeplus.modules.projectStatement.entity.ProjectSubstationImport" >
+		SELECT
+		<include refid="projectSubstatColumns"/>
+		/*,r.project_name as "projectName"
+		,prd.number as "reportNumber"*/
+		FROM project_substat a
+		/*left join rural_project_records r on a.project_id = r.id
+		    left join project_report_data prd on prd.project_id = a.id*/
+		<where>
+			a.del_flag = #{DEL_FLAG_NORMAL}
+
+		</where>
+		<choose>
+			<when test="page !=null and page.orderBy != null and page.orderBy != ''">
+				ORDER BY ${page.orderBy}
+			</when>
+			<otherwise>
+				ORDER BY a.create_date DESC
+			</otherwise>
+		</choose>
+	</select>
+
+	<select id="findAllList" resultType="com.jeeplus.modules.projectStatement.entity.ProjectSubstationImport" >
+		SELECT
+			<include refid="projectSubstatColumns"/>
+		FROM project a
+		<where>
+			a.del_flag = #{DEL_FLAG_NORMAL}
+		</where>
+		<choose>
+			<when test="page !=null and page.orderBy != null and page.orderBy != ''">
+				ORDER BY ${page.orderBy}
+			</when>
+			<otherwise>
+				ORDER BY a.update_date DESC
+			</otherwise>
+		</choose>
+	</select>
+
+	<insert id="insert">
+		INSERT INTO project_substat(
+			id,
+			create_by,
+			create_date,
+			update_by,
+			update_date,
+			del_flag,
+			remarks,
+
+			number,
+			project_or_cost_name,
+			engineering_cost,
+			labor_cost,
+			estimated_material_cost,
+			total_cost,
+			project_id
+		) VALUES (
+			 #{id},
+			 #{createBy.id},
+			 #{createDate},
+			 #{updateBy.id},
+			 #{updateDate},
+			 #{delFlag},
+			 #{remarks},
+
+			 #{number},
+			 #{projectOrCostName},
+			 #{engineeringCost},
+			 #{laborCost},
+			 #{estimatedMaterialCost},
+			 #{totalCost},
+			 #{projectId}
+		 )
+	</insert>
+
+	<insert id="saveUniqueInfo">
+		INSERT INTO project_substat(
+			id,
+			create_by,
+			create_date,
+			update_by,
+			update_date,
+			del_flag,
+			remarks,
+
+			number,
+			project_or_cost_name,
+			engineering_cost,
+			labor_cost,
+			estimated_material_cost,
+			total_cost,
+			project_id
+		) VALUES (
+			 #{id},
+			 #{createBy.id},
+			 #{createDate},
+			 #{updateBy.id},
+			 #{updateDate},
+			 #{delFlag},
+			 #{remarks},
+
+			 #{number},
+			 #{projectOrCostName},
+			 #{engineeringCost},
+			 #{laborCost},
+			 #{estimatedMaterialCost},
+			 #{totalCost},
+			 #{projectId}
+				 )
+	</insert>
+
+	<update id="update">
+		UPDATE project_substat SET
+			update_by = #{updateBy.id},
+			update_date = #{updateDate},
+			del_flag = #{delFlag},
+			remarks = #{remarks},
+
+			number = #{number},
+			project_or_cost_name = #{projectOrCostName},
+			engineering_cost = #{engineeringCost},
+			labor_cost = #{laborCost},
+			estimated_material_cost = #{estimatedMaterialCost},
+			total_cost = #{totalCost},
+			project_id = #{projectId}
+		WHERE id = #{id}
+	</update>
+
+
+	<!--物理删除-->
+	<update id="delete">
+		DELETE FROM project_substat
+		WHERE id = #{id}
+	</update>
+
+	<!--物理删除-->
+	<update id="deleteById">
+		DELETE FROM project_substat
+		WHERE id = #{id}
+	</update>
+
+	<!--逻辑删除-->
+	<update id="deleteByLogic">
+		UPDATE project_substat SET
+			del_flag = #{DEL_FLAG_DELETE}
+		WHERE id = #{id}
+	</update>
+
+	<select id="getProjectName" resultType="string">
+		select DISTINCT(r.project_or_cost_name)
+		 from project_substat s left join rural_project_records r
+		 on s.project_id = r.id
+		 where s.project_id = #{projectId} and s.del_flag = 0
+	</select>
+
+
+	<select id="selectCountAboutProjectMaterialStorage" resultType="integer">
+		select count(1)
+		from project_substat
+		<where>
+			del_flag = 0
+			and number = #{number}
+			and project_or_cost_name = #{projectOrCostName}
+		</where>
+	</select>
+
+
+	<select id="getByInfo" resultType="com.jeeplus.modules.projectStatement.entity.ProjectSubstationImport">
+		SELECT
+		<include refid="projectSubstatColumns"/>
+		FROM project_substat a
+		left join rural_project_records r on a.project_id = r.id
+		<where>
+			a.del_flag = 0
+			and number = #{number}
+			and project_or_cost_name = #{projectOrCostName}
+			<if test="projectId !=null and projectId != ''">
+				AND a.project_id = #{projectId}
+			</if>
+			<if test="id !=null and id != ''">
+				AND a.id != #{id}
+			</if>
+		</where>
+	</select>
+
+
+	<insert id="batchInsert">
+		insert into project_substat
+		(id,
+		create_by,
+		create_date,
+		update_by,
+		update_date,
+		del_flag,
+		remarks,
+		number,
+		project_or_cost_name,
+		engineering_cost,
+		labor_cost,
+		estimated_material_cost,
+		total_cost,
+		project_id
+		)
+		values
+		<foreach collection="projectMaterialStorageImportList" item="data" separator=",">
+		(#{data.id},
+		#{data.createBy.id},
+		#{data.createDate},
+		#{data.updateBy.id},
+		#{data.updateDate},
+		#{data.delFlag},
+		#{data.remarks},
+		#{data.number},
+		#{data.projectOrCostName},
+		#{data.engineeringCost},
+		#{data.laborCost},
+		#{data.estimatedMaterialCost},
+		#{data.totalCost},
+		#{data.projectId}
+		)
+		</foreach>
+	</insert>
+
+	<select id="queryCount" resultType="int">
+
+		SELECT
+			count(1)
+		FROM project_substat a
+		<where>
+			a.del_flag = #{DEL_FLAG_NORMAL}
+		</where>
+
+
+	</select>
+
+	<delete id="deleteListById">
+		delete from project_substat where id in
+		<foreach collection="idList" item="item" index="no" open="(" separator="," close=")">
+			#{item}
+		</foreach>
+	</delete>
+
+	<select id="getByIdList" resultType="com.jeeplus.modules.projectStatement.entity.ProjectSubstationImport">
+		SELECT
+		<include refid="projectSubstatColumns"/>
+		FROM project_substat a
+		WHERE a.id in
+		<foreach collection="idList" item="item" index="no" open="(" separator="," close=")">
+			#{item}
+		</foreach>
+		and a.del_flag = 0 and a.create_by = #{createBy}
+	</select>
+
+
+</mapper>

+ 1 - 0
src/main/resources/mappings/modules/ruralprojectrecords/RuralProjectRecordsDao.xml

@@ -48,6 +48,7 @@
 		a.construction_unit as "constructionUnit",
 		a.construction_linkman as "constructionLinkman",
 		a.project_type as "projectType",
+		ifnull(a.reported_state,0) as "reportedState",
 		a.over_due_status as "overDueStatus",
 		a.attachment_project_sort as "attachmentProjectSort",
 		a.check_remarks as "checkRemarks",

+ 15 - 4
src/main/resources/mappings/modules/signatureManagement/electronicSignature/DistrictDirectorApplicationDao.xml

@@ -21,7 +21,8 @@
 		su.name as "createBy.name",
 		so.name as "office.name",
 		so.show_name as "office.showName",
-		a.status as "status"
+		a.status as "status",
+		a.apply_seal_type as "applySealType"
 	</sql>
 
 
@@ -153,7 +154,8 @@
 			company_id,
 			area_id,
 			territory,
-			status
+			status,
+			apply_seal_type
 		) VALUES (
 			#{id},
 			#{createBy.id},
@@ -167,7 +169,8 @@
 			#{companyId},
             #{area.id},
             #{territory},
-			#{status}
+			#{status},
+			#{applySealType}
 		)
 	</insert>
 
@@ -182,7 +185,8 @@
 			area_id = #{area.id},
 			territory = #{territory},
 			area_director = #{areaDirector},
-			status = #{status}
+			status = #{status},
+			apply_seal_type = #{applySealType}
 		WHERE id = #{id}
 	</update>
 
@@ -211,6 +215,13 @@
 		order by a.create_date DESC
 	</select>
 
+	<select id="getApplySealTypeByUserId" resultType="java.lang.String">
+		select group_concat(distinct apply_seal_type separator ',') as apply_seal_types
+		from district_director_application
+		where status = 5 and del_flag = 0 and create_by = #{userId}
+
+	</select>
+
 
 
 </mapper>

+ 2 - 1
src/main/resources/mappings/modules/workreimbursement/WorkReimbursementDao.xml

@@ -997,7 +997,8 @@
 		a.status as "status",
 		a.id as "reimbursementId",
 		a.process_instance_id as "processInstanceId",
-		date_format(a.submitter_date,'%Y-%m-%d') as "submitterDate"
+		date_format(a.submitter_date,'%Y-%m-%d') as "submitterDate",
+		date_format( a.payment_date, '%Y-%m-%d' ) AS "paymentDate"
 		FROM reimbursement_vat_tax rvt
 		left join work_reimbursement a on rvt.work_reimbursement_id =a.id
 		left join sys_user su on su.id = a.submitter_id

+ 104 - 0
src/main/webapp/WEB-INF/tags/sys/reimburtreeselectUsers.tag

@@ -0,0 +1,104 @@
+<%@ tag language="java" pageEncoding="UTF-8"%>
+<%@ include file="/webpage/include/taglib.jsp"%>
+<%@ attribute name="id" type="java.lang.String" required="true" description="编号"%>
+<%@ attribute name="name" type="java.lang.String" required="true" description="隐藏域名称(ID)"%>
+<%@ attribute name="value" type="java.lang.String" required="true" description="隐藏域值(ID)"%>
+<%@ attribute name="labelName" type="java.lang.String" required="true" description="输入框名称(Name)"%>
+<%@ attribute name="labelValue" type="java.lang.String" required="true" description="输入框值(Name)"%>
+<%@ attribute name="title" type="java.lang.String" required="true" description="选择框标题"%>
+<%@ attribute name="url" type="java.lang.String" required="true" description="树结构数据地址"%>
+<%@ attribute name="checked" type="java.lang.Boolean" required="false"  description="是否显示复选框,如果不需要返回父节点,请设置notAllowSelectParent为true"%>
+<%@ attribute name="extId" type="java.lang.String" required="false" description="排除掉的编号(不能选择的编号)"%>
+<%@ attribute name="isAll" type="java.lang.Boolean" required="false" description="是否列出全部数据,设置true则不进行数据权限过滤(目前仅对Office有效)"%>
+<%@ attribute name="notAllowSelectRoot" type="java.lang.Boolean" required="false" description="不允许选择根节点"%>
+<%@ attribute name="notAllowSelectParent" type="java.lang.Boolean" required="false" description="不允许选择父节点"%>
+<%@ attribute name="module" type="java.lang.String" required="false" description="过滤栏目模型(只显示指定模型,仅针对CMS的Category树)"%>
+<%@ attribute name="selectScopeModule" type="java.lang.Boolean" required="false" description="选择范围内的模型(控制不能选择公共模型,不能选择本栏目外的模型)(仅针对CMS的Category树)"%>
+<%@ attribute name="allowClear" type="java.lang.Boolean" required="false" description="是否允许清除"%>
+<%@ attribute name="allowInput" type="java.lang.Boolean" required="false" description="文本框可填写"%>
+<%@ attribute name="cssClass" type="java.lang.String" required="false" description="css样式"%>
+<%@ attribute name="cssStyle" type="java.lang.String" required="false" description="css样式"%>
+<%@ attribute name="smallBtn" type="java.lang.Boolean" required="false" description="缩小按钮显示"%>
+<%@ attribute name="hideBtn" type="java.lang.Boolean" required="false" description="是否显示按钮"%>
+<%@ attribute name="disabled" type="java.lang.String" required="false" description="是否限制选择,如果限制,设置为disabled"%>
+<%@ attribute name="dataMsgRequired" type="java.lang.String" required="false" description=""%>
+	<input id="${id}Id" name="${name}" class="${cssClass}" type="hidden" value="${value}"/>
+	<div class="input-group">
+		<input id="${id}Name"placeholder="请选择${title}" name="${labelName}" ${allowInput?'':'readonly="readonly"'}  type="text" value="${labelValue}" data-msg-required="${dataMsgRequired}"
+		class="${cssClass}" style="background-color: #ffffff; ${cssStyle} "/>
+
+    </div>
+	 <label id="${id}Name-error" class="error" for="${id}Name" style="display:none"></label>
+<script type="text/javascript">
+	$("#${id}Button, #${id}Name").click(function(){
+		console.log('allowClear',${allowClear})
+		console.log('checked',${checked})
+		console.log('notAllowSelectParent',${notAllowSelectParent})
+		// 是否限制选择,如果限制,设置为disabled
+		if ($("#${id}Button").hasClass("disabled")){
+			return true;
+		}
+		// 正常打开
+		top.layer.open({
+		    type: 2,
+		    area: ['300px', '420px'],
+		    title:"选择${title}",
+		    ajaxData:{selectIds: $("#${id}Id").val()},
+		    content: "${ctx}/tag/treeselectReimbur?url="+encodeURIComponent("${url}")+"&module=${module}&checked=true&extId=${extId}&isAll=${isAll}" ,
+		    btn: ['确定', '关闭']
+    	       ,yes: function(index, layero){ //或者使用btn1
+						var tree = layero.find("iframe")[0].contentWindow.tree;//h.find("iframe").contents();
+						var ids = [], names = [],officeIds = [], nodes = [];
+						if ("${checked}" == "true"){
+							nodes = tree.getCheckedNodes(true);
+						}else{
+							nodes = tree.getSelectedNodes();
+						}
+						for(var i=0; i<nodes.length; i++) {//<c:if test="${checked && notAllowSelectParent}">
+							if (nodes[i].isParent){
+								continue; // 如果为复选框选择,则过滤掉父节点
+							}//</c:if><c:if test="${notAllowSelectRoot}">
+							if (nodes[i].level == 0){
+								//top.$.jBox.tip("不能选择根节点("+nodes[i].name+")请重新选择。");
+								top.layer.msg("不能选择根节点("+nodes[i].name+")请重新选择。", {icon: 0});
+								return false;
+							}//</c:if><c:if test="${notAllowSelectParent}">
+							if (nodes[i].isParent){
+								//top.$.jBox.tip("不能选择父节点("+nodes[i].name+")请重新选择。");
+								//layer.msg('有表情地提示');
+								top.layer.msg("不能选择父节点("+nodes[i].name+")请重新选择。", {icon: 0});
+								return false;
+							}//</c:if><c:if test="${not empty module && selectScopeModule}">
+							if (nodes[i].module == ""){
+								//top.$.jBox.tip("不能选择公共模型("+nodes[i].name+")请重新选择。");
+								top.layer.msg("不能选择公共模型("+nodes[i].name+")请重新选择。", {icon: 0});
+								return false;
+							}else if (nodes[i].module != "${module}"){
+								//top.$.jBox.tip("不能选择当前栏目以外的栏目模型,请重新选择。");
+								top.layer.msg("不能选择当前栏目以外的栏目模型,请重新选择。", {icon: 0});
+								return false;
+							}//</c:if>
+							ids.push(nodes[i].id);
+							names.push(nodes[i].name);
+							officeIds.push(nodes[i].officeId);
+							//<c:if test="${!checked}">
+							break; // 如果为非复选框选择,则返回第一个选择  </c:if>
+						}
+						if(ids.length>1){
+							top.layer.msg("不能进行多选,请重新选择。", {icon: 0});
+							return false;
+						}
+						$("#${id}Id").val(ids.join(",").replace(/u_/ig,""));
+						$("#${id}Name").val(names.join(","));
+						$("#${id}officeId").val(officeIds.join(","));
+						console.log(officeIds)
+						$("#${id}Name").focus();
+						top.layer.close(index);
+				    	       },
+    	cancel: function(index){ //或者使用btn2
+    	           //按钮【按钮二】的回调
+    	       }
+		});
+
+	});
+</script>

+ 2 - 2
src/main/webapp/WEB-INF/tags/table/fileUpload.tag

@@ -109,9 +109,9 @@
     <%-- 上传进度条(ID已调整) --%>
     <div id="${baseId}_addFile" style="display: none" class="upload-progress">
         <span id="${baseId}_fileName"></span>
-        <b><span id="${baseId}_baifenbi"></span></b>
+        <b><span id="baifenbi_${baseId}"></span></b>
         <div class="progress">
-            <div id="${baseId}_jindutiao" class="progress-bar" style="width: 0%" aria-valuenow="0"></div>
+            <div id="jindutiao_${baseId}" class="progress-bar" style="width: 0%" aria-valuenow="0"></div>
         </div>
     </div>
 

+ 58 - 0
src/main/webapp/WEB-INF/tags/table/importStatementExcel.tag

@@ -0,0 +1,58 @@
+<%@ tag language="java" pageEncoding="UTF-8"%>
+<%@ include file="/webpage/include/taglib.jsp"%>
+<%@ attribute name="url" type="java.lang.String" required="true"%>
+<%-- 使用方法: 1.将本tag写在查询的form之前;2.传入controller的url --%>
+<a id="btnImport" class="nav-btn nav-btn-import" data-toggle="tooltip" data-placement="left" title="导入"><i class="fa fa-folder-open-o"></i> 导入</a>
+<div id="importBox" class="hide">
+	<form id="importForm2" action="${url}" method="post" enctype="multipart/form-data" onsubmit="loading('正在导入,请稍等...');">
+		<br/>
+		<input id="uploadFile" name="file" type="file" style="width:330px"/>导入文件不能超过5M,仅允许导入“xls”或“xlsx”格式文件!<br/>  
+
+	</form>
+</div>
+<script type="text/javascript">
+	$(document).ready(function() {
+		$("#btnImport").click(function(){
+			top.layer.open({
+				type: 1,
+				area: [500, 300],
+				title:"导入数据",
+				content:$("#importBox").html() ,
+				btn: ['下载模板','确定', '关闭'],
+				btn1: function(index, layero){
+					window.location.href='${url}/template';
+				},
+				btn2: function(index, layero){
+					var formData = new FormData();
+					formData.append("file",top.$("#uploadFile")[0].files[0]);
+					var dataBodyType = $("#dataBodyType").val();
+					formData.append("dataBodyType",dataBodyType);
+					$.ajax({
+						type: 'post',
+						url: "${url}",
+						data: formData,
+						dataType: "json",
+						cache: false,
+						processData: false,
+						contentType: false,
+					}).success(function (result) {
+						console.log(result)
+						if(result.code == 0){
+							genRow(result);
+						}else {
+							top.layer.msg("导入文件异常:"+result.message);
+						}
+					}).error(function () {
+						top.layer.msg("导入文件失败!");
+					});
+					top.layer.close(index);
+				},
+				btn3: function(index){
+					top.layer.close(index);
+				}
+			});
+		});
+
+	});
+
+</script>

+ 906 - 0
src/main/webapp/webpage/modules/projectStatement/projectAccountsAddForm.jsp

@@ -0,0 +1,906 @@
+<%@ page contentType="text/html;charset=UTF-8" %>
+<%@ include file="/webpage/include/taglib.jsp"%>
+<html>
+<head>
+	<title>材料库</title>
+	<meta name="decorator" content="default"/>
+	<script type="text/javascript" src="${ctxStatic}/layui/layui.js"></script>
+	<link rel='stylesheet' type="text/css" href="${ctxStatic}/layui/css/layui.css"/>
+	<script src="${ctxStatic}/common/html/js/script.js"></script>
+	<script src="${ctxStatic}/layer-v2.3/layui/xmSelect.js" charset="utf-8"></script>
+	<script type="text/javascript" src="${ctxStatic}/layui/layui.js"></script>
+	<link rel='stylesheet' type="text/css" href="${ctxStatic}/layui/css/layui.css"/>
+	<link href="${ctxStatic}/layer-v2.3/layui/tableTree/treetable.css" rel="stylesheet" />
+	<script src="${ctxStatic}/common/html/js/script.js"></script>
+
+	<%@include file="/webpage/include/treetable.jsp" %>
+	<style>
+		#contractTypeDoc-error{
+			top:80px;
+			left:0;
+		}
+		 /*超过5个汉字,调整label的长度,以下是配套的*/
+		 .layui-item .layui-form-label{
+			 width:90px;
+		 }
+		.form-group .layui-item .layui-input-block,
+		.query .layui-input-block {
+			margin-left: 116px;
+		}
+		#workInvoiceProjectRelationList td{
+			padding-left: 0px;
+			padding-right: 0px;
+		}
+		#projectFinalAccountsList td{
+			padding-left: 0px;
+			padding-right: 0px;
+		}
+		#projectSubstationList td{
+			padding-left: 0px;
+			padding-right: 0px;
+		}
+		/* 确保表头与内容列宽一致 */
+		#contentTables thead tr {
+			display: table;
+			width: 100%;
+			table-layout: fixed; /* 固定列宽 */
+		}
+		#contentTables tbody tr {
+			display: table;
+			width: 100%;
+			table-layout: fixed; /* 与表头保持一致的列宽计算方式 */
+		}
+
+		/* 隔离容器样式 */
+		#substationTableWrapper {
+			width: 1240px; /* 总宽度=80+200+150*4+260+100 */
+			overflow: hidden;
+		}
+
+		/* 表格基础样式 */
+		#substationTableWrapper #substationContentTables {
+			table-layout: fixed; /* 配合colgroup固定列宽 */
+			width: 100%;
+			border-collapse: collapse;
+		}
+
+		/* 表头样式:新增vertical-align实现垂直居中 */
+		#substationTableWrapper #substationContentTables thead th {
+			all: unset !important; /* 清除所有全局样式干扰 */
+			display: table-cell !important; /* 恢复表格单元格特性 */
+			box-sizing: border-box !important;
+
+			/* 核心样式:水平+垂直居中+底色 */
+			background-color: #f5f5f5 !important; /* 浅灰底色 */
+			text-align: center !important; /* 水平居中 */
+			vertical-align: middle !important; /* 垂直居中(新增) */
+			font-weight: bold !important; /* 加粗 */
+			border: 1px solid #ddd !important; /* 边框 */
+			padding: 8px 12px !important; /* 内边距(保证垂直居中效果) */
+			color: #333 !important; /* 文字颜色 */
+			font-size: 14px !important; /* 常规文字大小 */
+		}
+
+		/* 第一行大标题单独设置 */
+		#substationTableWrapper #substationContentTables thead tr:first-child th {
+			font-size: 20px !important; /* 大标题字体 */
+		}
+
+		/* 内容单元格样式(确保列宽对齐) */
+		#substationTableWrapper #substationContentTables tbody td {
+			box-sizing: border-box !important;
+			white-space: nowrap !important;
+			overflow: hidden !important;
+			text-overflow: ellipsis !important;
+			border: 1px solid #ddd !important;
+			vertical-align: middle !important; /* 内容区也可同步垂直居中(可选) */
+		}
+
+
+
+		/* 核心容器样式 - 确保不被外部样式影响 */
+		.tab-wrapper {
+			position: relative;
+			width: 100%;
+			height: 42px; /* 固定高度,避免高度塌陷 */
+			box-sizing: border-box;
+			padding: 0 30px; /* 预留箭头位置 */
+		}
+
+		/* 滚动容器 - 关键样式 */
+		.tab-scroll {
+			width: 100%;
+			height: 100%;
+			overflow-x: auto;
+			overflow-y: hidden;
+			scrollbar-width: none; /* 隐藏滚动条 */
+		}
+		.tab-scroll::-webkit-scrollbar {
+			display: none; /* 隐藏滚动条 */
+		}
+
+		/* 页签列表 - 强制单行 */
+		.list-tabs {
+			display: flex;
+			flex-wrap: nowrap; /* 核心:不换行 */
+			margin: 0;
+			padding: 0;
+			list-style: none;
+			height: 100%;
+		}
+
+		/* 单个页签样式 */
+		.list-tabs li {
+			position: relative;
+			padding: 0 20px;
+			height: 100%;
+			line-height: 42px;
+			cursor: pointer;
+			white-space: nowrap; /* 文字不换行 */
+			color: #333;
+			box-sizing: border-box;
+		}
+		.list-tabs li.active {
+			color: #1E9FFF; /* layui主题色 */
+		}
+		.list-tabs li.active::after {
+			content: '';
+			position: absolute;
+			left: 0;
+			bottom: 0;
+			width: 100%;
+			height: 2px;
+			background-color: #1E9FFF;
+		}
+		.list-tabs li a {
+			color: inherit;
+			text-decoration: none;
+			display: block;
+			height: 100%;
+		}
+
+		/* 箭头按钮 - 固定在两侧 */
+		.tab-btn {
+			position: absolute;
+			top: 0;
+			width: 30px;
+			height: 42px;
+			background: white;
+			border: none;
+			outline: none;
+			cursor: pointer;
+			z-index: 10;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			color: #666;
+			box-shadow: 0 0 5px rgba(0,0,0,0.05);
+		}
+		.tab-btn:hover {
+			color: #1E9FFF;
+		}
+		.tab-btn:disabled {
+			color: #ddd;
+			cursor: not-allowed;
+		}
+		.tab-btn.left {
+			left: 0;
+		}
+		.tab-btn.right {
+			right: 0;
+		}
+	</style>
+	<script type="text/javascript">
+        var validateForm;
+		function doSubmit(obj) {
+			// 返回Promise,让父页面的await可以等待后端响应
+			return new Promise((resolve, reject) => {
+				// 表单验证
+				if (validateForm.form()) {
+					// 检查是否至少有一条材料信息
+					/*if ($("#projectFinalAccountsList tr").length === 0) {
+						top.layer.alert('请至少填写一条信息!', { icon: 0 });
+						reject(new Error('请至少填写一条信息')); // 通知父页面验证失败
+						return;
+					}*/
+
+					// 异步提交表单(使用AJAX代替直接submit)
+					$.ajax({
+						url: $("#inputForm").attr("action"), // 表单提交的后端URL
+						type: $("#inputForm").attr("method") || "post", // 提交方式(默认post)
+						data: $("#inputForm").serialize(), // 序列化表单数据
+						dataType: "json", // 预期后端返回JSON格式(即后端Map转换的JSON)
+						success: function(response) {
+							// 后端处理完成后,将结果传递给父页面
+							resolve(response);
+						},
+						error: function(xhr, status, error) {
+							// 处理请求失败(如网络错误、后端500等)
+							const errorMsg = xhr.responseText || "提交失败,请重试";
+							parent.layer.msg(errorMsg, { icon: 5 });
+							reject(new Error(errorMsg)); // 通知父页面发生错误
+						}
+					});
+
+				} else {
+					// 表单验证失败
+					const errorMsg = "信息未填写完整!";
+					parent.layer.msg(errorMsg, { icon: 5 });
+					reject(new Error(errorMsg)); // 通知父页面验证失败
+				}
+			});
+		}
+
+        $(document).ready(function() {
+
+			layui.use(['form', 'layer'], function () {
+				var form = layui.form;
+			});
+            validateForm = $("#inputForm").validate({
+                submitHandler: function(form){
+                    loading('正在提交,请稍等...');
+                    form.submit();
+                },
+                errorContainer: "#messageBox",
+                errorPlacement: function(error, element) {
+
+                    $("#messageBox").text("输入有误,请先更正。");
+                    if (element.is(":checkbox")||element.is(":radio")||element.parent().is(".input-append")){
+                        error.appendTo(element.parent().parent());
+                    } else {
+                        error.insertAfter(element);
+                    }
+                }
+            });
+
+
+            /*--------------*/
+			$("#attachment_btn").click(function () {
+				$("#attachment_file").click();
+			});
+			laydate.render({
+				elem: '#quotedPriceDate', //目标元素。由于laydate.js封装了一个轻量级的选择器引擎,因此elem还允许你传入class、tag但必须按照这种方式 '#id .class'
+				event: 'focus', //响应事件。如果没有传入event,则按照默认的click
+				type : 'date'
+				, trigger: 'click'
+			});
+
+			contentDetailTypeShow();
+
+
+
+			$(".list-tabs li").click(function(){
+				$(".list-tabs li").each(function(){
+					$(this).removeAttr("class","active");
+					var id='#'+$(this).find("span").html();
+					$(id).attr("class","hide");
+				})
+				console.log('this',this)
+				$(this).attr("class","active");
+				var id='#'+$(this).find("span").html();
+				var idKey=$(this).find("span").html();
+				console.log('idKey',idKey)
+				var substationContainer = document.getElementById(idKey);
+				substationContainer.style.display = 'block';
+				$(id).removeAttr("class","hide");
+
+			})
+
+
+        });
+		function addRow1(list, idx, tpl, row){
+			var idx1 = $("#projectFinalAccountsList tr").length;
+			if(list == '#projectFinalAccountsList'){
+				bornTemplete1(list, idx, tpl, row, idx1);
+				projectFinalAccountsListRowIdx+=1;
+			}
+		}
+
+		function addRow2(list, idx, tpl, row){
+			var idx1 = $("#projectSubstationList tr").length;
+			if(list == '#projectSubstationList'){
+				bornTemplete2(list, idx, tpl, row, idx1);
+				projectSubstationListRowIdx+=1;
+			}
+		}
+
+		function bornTemplete1(list, idx, tpl, row, idx1){
+			// 渲染模板时确保row数据正确传递
+			var rendered = Mustache.render(tpl, {
+				idx: idx,
+				delBtn: true,
+				row: row || {},  // 确保row不为空
+				order: idx1 + 1
+			});
+			$(list).append(rendered);
+		}
+
+		function bornTemplete2(list, idx, tpl, row, idx1){
+			// 渲染模板时确保row数据正确传递
+			var rendered = Mustache.render(tpl, {
+				idx: idx,
+				delBtn: true,
+				row: row || {},  // 确保row不为空
+				order: idx1 + 1
+			});
+			$(list).append(rendered);
+		}
+
+		function newSetPNumber(obj,label,ids,isProject,details){
+			$("#projectId").val(obj)
+			// 根据项目id查询项目信息
+			$.ajax({
+				type:'post',
+				url:'${ctx}/ruralProject/ruralProjectRecords/get?id=' + obj,
+				success:function(data){
+					if(data.projectReportNumber){
+						$("#projectReportNumber").val(data.projectReportNumber)
+						$("#leaderName").val(data.leaderNameStr)
+						$("#projectNoId").val(data.projectId)
+					}
+				}
+			})
+			$("#projectName").val(label)
+		}
+
+		function num(obj){
+
+			obj.value = obj.value.replace(/[^\d.]/g,""); //清除"数字"和"."以外的字符
+			obj.value = obj.value.replace(/^\./g,""); //验证第一个字符是数字
+			obj.value = obj.value.replace(/\.{2,}/g,"."); //只保留第一个, 清除多余的
+			obj.value = obj.value.replace(".","$#$").replace(/\./g,"").replace("$#$",".");
+			obj.value = obj.value.replace(/^(\-)*(\d+)\.(\d\d).*$/,'$1$2.$3'); //只能输入两个小数
+		}
+
+
+		function delRow(obj, prefix) {
+			var id = $(prefix + "_invoiceId");
+			var delFlag = $(prefix + "_delFlag");
+			if (id.val() == "") {
+				$(obj).parent().parent().remove();
+			} else if (delFlag.val() == "0") {
+				delFlag.val("1");
+				$(obj).html("&divide;").attr("title", "撤回删除");
+				$(obj).parent().parent().addClass("error");
+				$(obj).parent().parent().addClass("hide");
+			} else if (delFlag.val() == "1") {
+				delFlag.val("0");
+				$(obj).html("&times;").attr("title", "删除");
+				$(obj).parent().parent().removeClass("error");
+			}
+			var length=$("#projectFinalAccountsList tr").length;
+			var count=length;
+			for (var i=1;i<=length;i++) {
+				var delFlag = $("#projectFinalAccountsList").find("tr").eq(i-1).find("input").eq(1).val();
+				if (delFlag == "1") {
+					count =count-1;
+				}
+			}
+			if(count==1){
+				$("#chargeType").val("2")
+				layui.form.render();
+			}else if (count>1){
+				$("#chargeType").val("1")
+				layui.form.render();
+			}else if(count == 0){
+				$("#projectFlag").val("");
+			}
+		}
+
+		function genRow(data) {
+			console.log(data)
+			console.log(data.变电站建筑工程费用汇总表)
+			console.log(data.工程项目竣工结算汇总表)
+			if(data.变电站建筑工程费用汇总表){
+				for (var i = 0; i < data.变电站建筑工程费用汇总表.length; i++) {
+					addRow2('#projectSubstationList', projectSubstationListRowIdx, projectSubstationListTpl, data.变电站建筑工程费用汇总表[i])
+				}
+			}
+			if(data.工程项目竣工结算汇总表){
+				for (var i = 0; i < data.工程项目竣工结算汇总表.length; i++) {
+					addRow1('#projectFinalAccountsList', projectFinalAccountsListRowIdx, projectFinalAccountsListTpl, data.工程项目竣工结算汇总表[i])
+				}
+			}
+
+
+		}
+	</script>
+</head>
+<body>
+<div class="single-form">
+	<div class="container">
+		<form:form id="inputForm" modelAttribute="projectFinalAccounts" action="${ctx}/project/projectFinalAccounts/storageSave" method="post" class="form-horizontal">
+			<form:hidden path="id"/>
+			<form:hidden path="projectId"/>
+			<input type="hidden" id="dataBodyList" name="dataBodyList" value="">
+			<input type="hidden" id="dataBodyType" name="dataBodyType" value="">
+			<div class="form-group layui-row first">
+				<div class="form-group-label"><h2>项目信息</h2></div>
+				<div class="layui-item layui-col-sm6">
+					<label class="layui-form-label"><span class="require-item invoicetype">*</span>项目名称</label>
+					<div class="layui-input-block">
+						<sys:gridselectprojectmaterial disabled="true" url="${ctx}/project/projectMaterialStorage/selectproject" id="project" name="projectNumber"  value="${projectMaterialStorage.projectName}"  title="选择所属项目" labelName="projectName" cssStyle="background-color: #fff"
+													  labelValue="${projectMaterialStorage.projectName}" cssClass="form-control required layui-input" fieldLabels="项目名称" fieldKeys="projectName" searchLabel="项目名称" searchKey="projectName"  ></sys:gridselectprojectmaterial>
+					</div>
+				</div>
+				<div class="layui-item layui-col-sm6">
+					<label class="layui-form-label">报告号</label>
+					<div class="layui-input-block">
+						<input id="projectReportNumber" htmlEscape="false"  value=""  class="form-control layui-input" readonly="true"/>
+					</div>
+				</div>
+				<div class="layui-item layui-col-sm6">
+					<label class="layui-form-label">项目编号</label>
+					<div class="layui-input-block">
+						<input id="projectNoId" htmlEscape="false"  value=""  class="form-control layui-input" readonly="true"/>
+					</div>
+				</div>
+				<div class="layui-item layui-col-sm6">
+					<label class="layui-form-label">项目责任人</label>
+					<div class="layui-input-block">
+						<input id="leaderName" htmlEscape="false"  value=""  class="form-control layui-input" readonly="true"/>
+					</div>
+				</div>
+				<div class="layui-item layui-col-sm6" id="contentDetailTypeDiv">
+					<label class="layui-form-label">选择类型:</label>
+					<div class="layui-input-block">
+						<div class="input-group">
+							<div >
+								<div id="contentDetailType" class="xm-select-demo" tabindex="0" contenteditable="true"></div>
+							</div>
+							<span class="input-group-btn" onclick="getDetailsNum()">
+								<label class="form-status">生成表格</label>
+							 </span>
+						</div>
+					</div>
+				</div>
+			</div>
+			<div class="form-group layui-row first lw14">
+				<div class="form-group-label"><h2>汇总表信息</h2></div>
+				<div class="list-form-tab contentShadow shadowLTR" id="tabDiv" style="display: none;margin-bottom: 20px">
+					<!-- 页签核心容器 -->
+					<div class="tab-wrapper">
+						<!-- 左箭头 -->
+						<div class="tab-btn left" id="leftBtn">
+							<i class="fa fa-angle-left"></i>
+						</div>
+
+						<!-- 滚动区域 -->
+						<div class="tab-scroll" id="tabScroll">
+							<ul class="list-tabs" id="tabsList">
+								<li style="display: none;" class="active projectFinalAccounts"><a>工程项目竣工结算汇总表</a><span class="hide">projectFinalAccounts</span></li>
+								<li style="display: none;" class=" projectSubstation"><a>变电站建筑工程费用汇总表</a><span class="hide">projectSubstation</span></li>
+								<li style="display: none;" class="projectInfo"><a>现场签证汇总表</a><span class="hide">projectInfo</span></li>
+							</ul>
+						</div>
+
+						<!-- 右箭头(用div替代button) -->
+						<div class="tab-btn right" id="rightBtn">
+							<i class="fa fa-angle-right"></i>
+						</div>
+					</div>
+				</div>
+
+
+				<!-- 外层容器:左浮动 + 默认隐藏 + 微调边距避免重叠 -->
+				<div id="importExcelContainer" style="display: none; float: left; margin-right: 10px;">
+					<table:importStatementExcel url="${ctx}/project/projectFinalAccounts/import"></table:importStatementExcel>
+				</div>
+
+				<div id="projectFinalAccounts" style="display: none;">
+					<div class="layui-item nav-btns">
+						<a class="nav-btn nav-btn-add" onclick="addRow1('#projectFinalAccountsList', projectFinalAccountsListRowIdx, projectFinalAccountsListTpl);projectFinalAccountsListRowIdx = projectFinalAccountsListRowIdx + 1;" title="新增"><i class="fa fa-plus"></i>&nbsp;新增</a>
+					</div>
+
+					<table id="contentTables" class="table table-bordered table-condensed can-edit no-bottom-margin details" style="margin-bottom: 20px">
+						<thead>
+						<tr>
+							<th colspan="5" style="font-size: 20px">工程项目竣工结算汇总表</th>
+						</tr>
+						<tr>
+							<th width="80px"><font color="red">*</font>序号</th>
+							<th><font color="red">*</font>项目或费用名称</th>
+							<th width="160px">金额</th>
+							<th style="width: 260px">备注</th>
+							<th width="100px">操作</th>
+						</tr>
+						</thead>
+						<tbody id="projectFinalAccountsList" style="display: block; max-height: 400px; overflow-y: auto;">
+						</tbody>
+					</table>
+				</div>
+
+				<!-- 外层容器:隔离全局样式干扰 -->
+				<div id="projectSubstation" style="display: none;">
+
+					<div class="layui-item nav-btns" >
+						<a class="nav-btn nav-btn-add" onclick="addRow2('#projectSubstationList', projectSubstationListRowIdx, projectSubstationListTpl);projectSubstationListRowIdx = projectSubstationListRowIdx + 1;" title="新增"><i class="fa fa-plus"></i>&nbsp;新增</a>
+					</div>
+					<!-- 表格整体:仅保留表头,内容区单独抽离 -->
+					<table id="substationContentTables" class="table table-bordered table-condensed can-edit no-bottom-margin details">
+						<!-- 1. 用colgroup强制定义8列宽度 -->
+						<colgroup>
+							<col width="80px"> <!-- 1.序号 -->
+							<col width="200px"> <!-- 2.项目或费用名称 -->
+							<col width="150px"> <!-- 3.金额-合计 -->
+							<col width="150px"> <!-- 4.金额-直接工程费 -->
+							<col width="150px"> <!-- 5.金额-人工费 -->
+							<col width="150px"> <!-- 6.金额-暂估价材料费 -->
+							<col width="260px"> <!-- 7.备注 -->
+							<col width="100px"> <!-- 8.操作 -->
+						</colgroup>
+
+						<!-- 2. 表头部分(固定不滚动) -->
+						<thead>
+						<tr>
+							<th colspan="8" style="font-size: 20px">变电站建筑工程费用汇总表</th>
+						</tr>
+						<tr>
+							<th rowspan="2"><font color="red">*</font>序号</th>
+							<th rowspan="2"><font color="red">*</font>项目或费用名称</th>
+							<th colspan="4">金额</th>
+							<th rowspan="2">备注</th>
+							<th rowspan="2">操作</th>
+						</tr>
+						<tr>
+							<th>合计</th>
+							<th>其中:直接工程费</th>
+							<th>其中:人工费</th>
+							<th>其中:暂估价材料费</th>
+						</tr>
+						</thead>
+					</table>
+
+					<!-- 3. 内容滚动区域(限制高度400px,超过显示滚动条) -->
+					<div id="substationContentScroll" style="max-height: 400px; overflow-y: auto; overflow-x: hidden;">
+						<table id="substationContentBody" class="table table-bordered table-condensed can-edit no-bottom-margin details">
+							<colgroup> <!-- 内容表同步列宽,确保与表头对齐 -->
+								<col width="80px">
+								<col width="200px">
+								<col width="150px">
+								<col width="150px">
+								<col width="150px">
+								<col width="150px">
+								<col width="260px">
+								<col width="100px">
+							</colgroup>
+							<tbody id="projectSubstationList">
+							<!-- 动态生成的行将在这里显示 -->
+							</tbody>
+						</table>
+					</div>
+				</div>
+
+				<script type="text/template" id="projectFinalAccountsListTpl">//<!--
+                    <tr id="projectFinalAccountsList{{idx}}">
+                        <td class="hide">
+							<input id="projectFinalAccountsList{{idx}}_projectId" name="projectFinalAccountsList[{{idx}}].projectId" type="hidden" value="{{row.projectId}}"/>
+							<input id="projectFinalAccountsList{{idx}}_delFlag" name="projectFinalAccountsList[{{idx}}].delFlag" type="hidden" value="0"/>
+                        </td>
+                        <td style="width:80px">
+							<input id="projectFinalAccountsList{{idx}}_number" name = "projectFinalAccountsList[{{idx}}].number"  type="text" value="{{row.number}}"  class="form-control required"/>
+                        </td>
+                        <td>
+							<input id="projectFinalAccountsList{{idx}}_projectOrCostName" name = "projectFinalAccountsList[{{idx}}].projectOrCostName"  type="text" value="{{row.projectOrCostName}}"  class="form-control required"/>
+                        </td>
+                        <td style="width:160px">
+							<input id="projectFinalAccountsList{{idx}}_settlementSum" name = "projectFinalAccountsList[{{idx}}].settlementSum"  type="text" value="{{row.settlementSum}}"  onkeyup="num(this)" class="form-control"/>
+                        </td>
+                        <td style="width:260px">
+							<input id="projectFinalAccountsList{{idx}}_remarks" name = "projectFinalAccountsList[{{idx}}].remarks"  type="text" value="{{row.remarks}}"  class="form-control"/>
+                        </td>
+
+                        <td class="text-center op-td"  style="width:100px">
+                            {{#delBtn}}<span class="op-btn op-btn-delete" onclick="delRow(this, '#projectFinalAccountsList{{idx}}')" title="删除"><i class="glyphicon glyphicon-remove"></i>&nbsp;删除</span>{{/delBtn}}
+                        </td>
+                    </tr>//-->
+				</script>
+				<script type="text/javascript">
+					var projectFinalAccountsListRowIdx = 0, projectFinalAccountsListTpl = $("#projectFinalAccountsListTpl").html().replace(/(\/\/\<!\-\-)|(\/\/\-\->)/g,"");
+					$(document).ready(function() {
+						var data = ${fns:toJson(projectFinalAccounts.projectFinalAccountsList)};
+						for (var i=0; i<data.length; i++){
+							addRow1('#projectFinalAccountsList', projectFinalAccountsListRowIdx, projectFinalAccountsListTpl, data[i])
+							projectFinalAccountsListRowIdx = projectFinalAccountsListRowIdx + 1;
+						}
+					});
+				</script>
+
+
+				<!-- 4. 动态行模板(保持原有逻辑) -->
+				<script type="text/template" id="projectSubstationListTpl">//<!--
+					<tr id="projectSubstationList{{idx}}">
+					  <td class="hide">
+						<input id="projectSubstationList{{idx}}_projectId" name="projectSubstationList[{{idx}}].projectId" type="hidden" value="{{row.projectId}}"/>
+						<input id="projectSubstationList{{idx}}_delFlag" name="projectSubstationList[{{idx}}].delFlag" type="hidden" value="0"/>
+					  </td>
+					  <td>
+						<input id="projectSubstationList{{idx}}_number" name="projectSubstationList[{{idx}}].number" type="text" value="{{row.number}}" class="form-control required"/>
+					  </td>
+					  <td>
+						<input id="projectSubstationList{{idx}}_projectOrCostName" name="projectSubstationList[{{idx}}].projectOrCostName" type="text" value="{{row.projectOrCostName}}" class="form-control required"/>
+					  </td>
+					  <td>
+						<input id="projectSubstationList{{idx}}_totalCost" name="projectSubstationList[{{idx}}].totalCost" type="text" value="{{row.totalCost}}" onkeyup="num(this)" class="form-control"/>
+					  </td>
+					  <td>
+						<input id="projectSubstationList{{idx}}_engineeringCost" name="projectSubstationList[{{idx}}].engineeringCost" type="text" value="{{row.engineeringCost}}" onkeyup="num(this)" class="form-control"/>
+					  </td>
+					  <td>
+						<input id="projectSubstationList{{idx}}_laborCost" name="projectSubstationList[{{idx}}].laborCost" type="text" value="{{row.laborCost}}" onkeyup="num(this)" class="form-control"/>
+					  </td>
+					  <td>
+						<input id="projectSubstationList{{idx}}_estimatedMaterialCost" name="projectSubstationList[{{idx}}].estimatedMaterialCost" type="text" value="{{row.estimatedMaterialCost}}" onkeyup="num(this)" class="form-control"/>
+					  </td>
+					  <td>
+						<input id="projectSubstationList{{idx}}_remarks" name="projectSubstationList[{{idx}}].remarks" type="text" value="{{row.remarks}}" class="form-control"/>
+					  </td>
+					  <td class="text-center op-td">
+						{{#delBtn}}<span class="op-btn op-btn-delete" onclick="delRow(this, '#projectSubstationList{{idx}}')" title="删除"><i class="glyphicon glyphicon-remove"></i>&nbsp;删除</span>{{/delBtn}}
+					  </td>
+					</tr>//-->
+				</script>
+				<script type="text/javascript">
+					var projectSubstationListRowIdx = 0, projectSubstationListTpl = $("#projectSubstationListTpl").html().replace(/(\/\/\<!\-\-)|(\/\/\-\->)/g,"");
+					$(document).ready(function() {
+						var data = ${fns:toJson(projectFinalAccounts.projectSubstationList)};
+						for (var i=0; i<data.length; i++){
+							addRow2('#projectSubstationList', projectSubstationListRowIdx, projectSubstationListTpl, data[i])
+							projectSubstationListRowIdx = projectSubstationListRowIdx + 1;
+						}
+					});
+				</script>
+
+			</div>
+
+
+			<div class="form-group layui-row page-end"></div>
+		</form:form>
+	</div>
+</div>
+<script src="${ctxStatic}/layer-v2.3/layui/layui.all.js" charset="utf-8"></script>
+<script>
+	function contentDetailTypeShow() {
+		var projectContentDataId = '${projectFinalAccounts.id}';
+		$.ajax({
+			type:'post',
+			url:'${ctx}/project/projectFinalAccounts/getDictList',
+			data:{
+				"achievementParentId":projectContentDataId
+			},
+			success:function(data){
+				if(data.success) {
+					if (null != projectContentDataId && "" != projectContentDataId) {
+						if(null !=projectContentDataId && ""!= projectContentDataId) {
+							$.ajax({
+								type: 'post',
+								url: getExistingDataOnPath(projectContentDataId),
+								data: {
+									"contentId": "${projectFinalAccounts.id}"
+								},
+								success: function (tableTypeList) {
+									var dataList = data.body.list;
+									var newDataList = [];
+									var holdDataList = data.body.list;
+									if (0 != dataList.length) {
+										for (i in holdDataList) {
+											newDataList.push(holdDataList[i])
+										}
+									}
+									if (0 != newDataList.length && 0 !=tableTypeList.length){
+										for (i in newDataList) {
+											for (j in tableTypeList) {
+												if (newDataList[i].value == tableTypeList[j]) {
+													var newData = {
+														"name": newDataList[i].name,
+														"value": newDataList[i].value,
+														"selected": true
+													}
+													holdDataList.splice(i,1,newData);
+												}
+												modifyGetDetailsNum(tableTypeList);
+											}
+										}
+										xmSelect.render({
+											el: '#contentDetailType',
+											language: 'zn',
+											data: holdDataList
+										})
+										$("#dataBodyList").val(holdDataList);
+									}else{
+										xmSelect.render({
+											el: '#contentDetailType',
+											language: 'zn',
+											data: dataList
+										})
+										$("#dataBodyList").val(holdDataList);
+									}
+								}
+							})
+						}else{
+							xmSelect.render({
+								el: '#contentDetailType',
+								language: 'zn',
+								data: data.body.list
+							})
+							$("#dataBodyList").val(data.body.list);
+						}
+					}else {
+						xmSelect.render({
+							el: '#contentDetailType',
+							language: 'zn',
+							data: data.body.list
+						})
+						$("#dataBodyList").val(data.body.list);
+					}
+				}
+			}
+		})
+	}
+
+	var contentDetailType = xmSelect.render({
+		el: '#contentDetailType',
+		language: 'zn',
+		data: [
+		]
+	})
+
+	function modifyGetDetailsNum(list) {
+		$("#contentDetailType").empty();
+	}
+
+
+	function getDetailsNum(){
+		console.log(213123)
+		var list = [];
+		//获取当前多选选中的值
+		var selectArr = contentDetailType.getValue();
+		for (var i in selectArr){
+			list.push(selectArr[i].name);
+		}
+		// 核心:将数组用逗号拼接成字符串
+		var listStr = list.join(',');
+		$("#contentDetail").val("");
+		$("#dataBodyType").val(listStr);
+
+		// 处理导入按钮容器:仅当listStr不为空时显示
+		var importContainer = document.getElementById("importExcelContainer");
+		var tabDivContainer = document.getElementById("tabDiv");
+		if (importContainer) {
+			// 若listStr为空(无任何选中项)则隐藏,否则显示
+			importContainer.style.display = listStr !== '' ? "block" : "none";
+		}
+		if (tabDivContainer) {
+			// 若listStr为空(无任何选中项)则隐藏,否则显示
+			tabDivContainer.style.display = listStr !== '' ? "block" : "none";
+		}
+		var showFlag = false;
+
+		// 处理“工程项目竣工结算汇总表”对应的容器
+		var finalAccountsContainer = document.getElementById("projectFinalAccounts");
+		var finalAccountsItem = document.querySelector(".projectFinalAccounts");
+		// 先确保元素存在,避免报错
+		if (finalAccountsContainer && finalAccountsItem) {
+			// 1. 判断finalAccountsItem当前的显示状态
+			// 用getComputedStyle获取最终计算后的样式(兼容内联样式和CSS类控制的样式)
+			var isItemVisible = window.getComputedStyle(finalAccountsItem).display !== 'none';
+
+			// 2. 判断是否需要显示(根据listStr是否包含目标字符串)
+			var needShow = listStr.includes('工程项目竣工结算汇总表');
+
+			// 3. 根据当前状态和需求,决定是否切换显示/隐藏
+			if (needShow && !isItemVisible) {
+				// 需要显示,但当前隐藏 → 切换为显示
+				finalAccountsItem.style.display = 'list-item'; // li推荐用list-item而非block
+				finalAccountsContainer.style.display = 'block';
+				showFlag = true;
+			} else if (!needShow && isItemVisible) {
+				// 需要隐藏,但当前显示 → 切换为隐藏
+				finalAccountsItem.style.display = 'none';
+				finalAccountsContainer.style.display = 'none';
+			}else if (needShow && isItemVisible) {
+				showFlag = true;
+			}
+			// 其他情况(状态一致):不做操作,避免冗余DOM修改
+		}else{
+			finalAccountsItem.style.display = 'none';
+			finalAccountsContainer.style.display = 'none';
+		}
+
+		// 处理“变电站建筑工程费用汇总表”对应的容器和li项
+		var substationContainer = document.getElementById("projectSubstation");
+		var projectSubstationItem = document.querySelector(".projectSubstation");
+
+		// 确保容器和li元素都存在
+		if (substationContainer && projectSubstationItem) {
+			// 1. 判断当前显示状态(容器和li保持一致,同步判断)
+			var isContainerVisible = window.getComputedStyle(substationContainer).display !== 'none';
+			var isItemVisible = window.getComputedStyle(projectSubstationItem).display !== 'none';
+
+			// 2. 判断是否需要显示(根据listStr包含的关键词)
+			var needShow = listStr.includes('变电站建筑工程费用汇总表');
+
+			// 3. 仅在状态不一致时执行切换(避免重复操作)
+			if (needShow && (!isContainerVisible || !isItemVisible)) {
+				// 需要显示,但当前至少有一个元素隐藏 → 同步显示
+				projectSubstationItem.style.display = 'list-item'; // li用list-item更符合语义
+				if(showFlag){
+					substationContainer.style.display = 'none';
+				}else{
+					substationContainer.style.display = 'block';
+				}
+			} else if (!needShow && (isContainerVisible || isItemVisible)) {
+				// 需要隐藏,但当前至少有一个元素显示 → 同步隐藏
+				substationContainer.style.display = 'none';
+				projectSubstationItem.style.display = 'none';
+			}
+		}else{
+			substationContainer.style.display = 'none';
+			projectSubstationItem.style.display = 'none';
+		}
+	}
+
+</script>
+
+<script>
+	layui.use(['jquery'], function() {
+		var $ = layui.jquery;
+
+		// 获取核心元素
+		var tabScroll = document.getElementById('tabScroll'); // 滚动容器(原生DOM,操作更直接)
+		var tabsList = document.getElementById('tabsList');
+		var leftBtn = document.getElementById('leftBtn');
+		var rightBtn = document.getElementById('rightBtn');
+
+		// 滚动步长(每次点击滚动的像素)
+		var scrollStep = 250;
+
+		// 检查箭头状态
+		function updateButtons() {
+			// 容器可滚动的最大距离 = 内容总宽度 - 容器可视宽度
+			var maxScroll = tabsList.scrollWidth - tabScroll.clientWidth;
+
+			// 左箭头:滚动到最左侧时禁用
+			leftBtn.disabled = tabScroll.scrollLeft <= 0;
+
+			// 右箭头:滚动到最右侧时禁用(留5px误差)
+			rightBtn.disabled = tabScroll.scrollLeft >= (maxScroll - 5);
+		}
+
+		// 左滚动
+		leftBtn.addEventListener('click', function() {
+			if (!this.disabled) {
+				tabScroll.scrollLeft -= scrollStep;
+				// 手动触发状态检查(滚动事件可能有延迟)
+				setTimeout(updateButtons, 50);
+			}
+		});
+
+		// 右滚动
+		rightBtn.addEventListener('click', function() {
+			if (!this.disabled) {
+				tabScroll.scrollLeft += scrollStep;
+				// 手动触发状态检查
+				setTimeout(updateButtons, 50);
+			}
+		});
+
+		// 滚动时自动更新箭头状态
+		tabScroll.addEventListener('scroll', updateButtons);
+
+		// 窗口大小改变时重新计算
+		window.addEventListener('resize', updateButtons);
+
+		// 页签切换
+		$('#tabsList li').click(function(e) {
+			e.preventDefault();
+			$(this).addClass('active').siblings().removeClass('active');
+			// 这里可以添加内容切换逻辑
+		});
+
+		// 初始化检查状态
+		updateButtons();
+	});
+</script>
+</body>
+</html>

+ 723 - 0
src/main/webapp/webpage/modules/projectStatement/projectAccountsList.jsp

@@ -0,0 +1,723 @@
+<%@ page import="com.jeeplus.modules.sys.utils.UserUtils" %>
+<%@ page contentType="text/html;charset=UTF-8" %>
+<%@ include file="/webpage/include/taglib.jsp"%>
+<%
+	boolean admin = UserUtils.getUser().isAdmin();
+%>
+<html>
+<head>
+	<title>材料库</title>
+	<meta name="decorator" content="default"/>
+	<script type="text/javascript" src="${ctxStatic}/layui/layuidown.js"></script>
+	<link rel='stylesheet' type="text/css" href="${ctxStatic}/layui/layuidown.css"/>
+	<%--<script src="${ctxStatic}/layer-v2.3/laydate/laydate.js"></script>--%>
+	<script type="text/javascript">
+        $(document).ready(function() {
+			layui.use(['dropdown', 'util', 'layer', 'jquery'], function () {
+				var form = layui.form;
+				var $ = layui.jquery;
+				var layer = layui.layer;
+				// 确保顶层窗口可访问
+				top.layui = layui;
+				$.ajax({
+					type : "POST",
+					url : "${ctx}/ruralProject/ruralProjectRecords/engineeringTreeMenu",
+					//请求成功
+					success : function(result) {
+						var str=jQuery.parseJSON(result);
+						//工程类型树形菜单
+						layui.dropdown.render({
+							elem: '#demo100'
+							,style: 'width: 320px;'
+							,data:str
+							,click: function(item){
+								$("#demo100").val(item.title)
+								$("#demo101").val(item.id)
+							}
+						});
+					}
+				});
+			})
+            //搜索框收放
+            $('#moresee').click(function(){
+                if($('#moresees').is(':visible'))
+                {
+                    $('#moresees').slideUp(0,resizeListWindow2);
+                    $('#moresee i').removeClass("glyphicon glyphicon-menu-up").addClass("glyphicon glyphicon-menu-down");
+                }else{
+                    $('#moresees').slideDown(0,resizeListWindow2);
+                    $('#moresee i').removeClass("glyphicon glyphicon-menu-down").addClass("glyphicon glyphicon-menu-up");
+                }
+            });
+            laydate.render({
+                elem: '#beginDate', //目标元素。由于laydate.js封装了一个轻量级的选择器引擎,因此elem还允许你传入class、tag但必须按照这种方式 '#id .class'
+                event: 'focus', //响应事件。如果没有传入event,则按照默认的click
+                type : 'date'
+, trigger: 'click'
+            });
+            laydate.render({
+                elem: '#endDate', //目标元素。由于laydate.js封装了一个轻量级的选择器引擎,因此elem还允许你传入class、tag但必须按照这种方式 '#id .class'
+                event: 'focus', //响应事件。如果没有传入event,则按照默认的click
+                type : 'date'
+, trigger: 'click'
+            });
+            laydate.render({
+                elem: '#beginQuotedPriceDate', //目标元素。由于laydate.js封装了一个轻量级的选择器引擎,因此elem还允许你传入class、tag但必须按照这种方式 '#id .class'
+                type : 'month'
+, trigger: 'click'
+            });
+            laydate.render({
+                elem: '#endQuotedPriceDate', //目标元素。由于laydate.js封装了一个轻量级的选择器引擎,因此elem还允许你传入class、tag但必须按照这种方式 '#id .class'
+                type : 'month'
+, trigger: 'click'
+            });
+        });
+
+        function reset() {
+            $("#searchForm").resetForm();
+        }
+
+        function openDialog(title,url,width,height,target) {
+
+            if (navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i)) {//如果是移动端,就使用自适应大小弹窗
+                width = 'auto';
+                height = 'auto';
+            } else {//如果是PC端,根据用户设置的width和height显示。
+
+            }
+
+            top.layer.open({
+                type: 2,
+                area: [width, height],
+                title: title,
+                maxmin: true, //开启最大化最小化按钮
+                content: url,
+                skin: 'three-btns',
+				btn: ['提交', '关闭'],
+                /*yes: function (index, layero) {
+                    var body = top.layer.getChildFrame('body', index);
+                    var iframeWin = layero.find('iframe')[0]; //得到iframe页的窗口对象,执行iframe页的方法:iframeWin.method();
+                    var inputForm = body.find('#inputForm');
+                    var top_iframe;
+                    if (target) {
+                        top_iframe = target;//如果指定了iframe,则在改frame中跳转
+                    } else {
+                        top_iframe = top.getActiveTab().attr("name");//获取当前active的tab的iframe
+                    }
+                    inputForm.attr("target", top_iframe);//表单提交成功后,从服务器返回的url在当前tab中展示
+
+                    if (iframeWin.contentWindow.doSubmit()) {
+                        // top.layer.close(index);//关闭对话框。
+                        setTimeout(function () {
+                            top.layer.close(index)
+                        }, 100);//延时0.1秒,对应360 7.1版本bug
+                    }
+
+                },*/
+                btn1: function(index, layero){
+                    var body = top.layer.getChildFrame('body', index);
+                    var iframeWin = layero.find('iframe')[0]; //得到iframe页的窗口对象,执行iframe页的方法:iframeWin.method();
+                    var inputForm = body.find('#inputForm');
+                    var top_iframe;
+                    if(target){
+                        top_iframe = target;//如果指定了iframe,则在改frame中跳转
+                    }else{
+                        top_iframe = top.getActiveTab().attr("name");//获取当前active的tab的iframe
+                    }
+                    inputForm.attr("target",top_iframe);//表单提交成功后,从服务器返回的url在当前tab中展示
+                    if(iframeWin.contentWindow.doSubmit(1) ){
+                        // top.layer.close(index);//关闭对话框。
+                        setTimeout(function(){top.layer.close(index)}, 100);//延时0.1秒,对应360 7.1版本bug
+                    }
+                },
+                btn2: function (index) {
+                }
+            });
+        }
+
+
+		function newSetPNumber(obj,label,ids,isProject,details){
+			$("#projectId").val(obj)
+			$("#projectName").val(label)
+		}
+
+		//打开对话框(查看)
+		function openDialogReportView(title,url,id,width,height){
+
+
+			if(navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i)){//如果是移动端,就使用自适应大小弹窗
+				width='auto';
+				height='auto';
+			}else{//如果是PC端,根据用户设置的width和height显示。
+
+			}
+			$.ajax({
+				async: false,
+				url: "${ctx}/ruralProject/ruralProjectMessage/getReportExist?id="+id,
+				dataType: "json",
+				success: function (data) {
+					if(data.success){
+						top.layer.open({
+							type: 2,
+							skin: 'one-btn',
+							area: [width, height],
+							title: title,
+							maxmin: true, //开启最大化最小化按钮
+							content: url ,
+							btn: ['关闭'],
+							cancel: function(index){
+							}
+						});
+					}else{
+						top.layer.msg("该项目报告信息已删除!", {icon: 0});
+						window.location.reload();
+					}
+				}
+			});
+
+		}
+        function openDialogre(title,url,width,height,target,buttons) {
+
+            if (navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i)) {//如果是移动端,就使用自适应大小弹窗
+                width = 'auto';
+                height = 'auto';
+            } else {//如果是PC端,根据用户设置的width和height显示。
+
+            }
+            var split = buttons.split(",");
+            top.layer.open({
+                type: 2,
+                area: [width, height],
+                title: title,
+                maxmin: true, //开启最大化最小化按钮
+                skin: 'three-btns',
+                content: url,
+                btn: split,
+                btn1: function(index, layero){
+                    var body = top.layer.getChildFrame('body', index);
+                    var iframeWin = layero.find('iframe')[0]; //得到iframe页的窗口对象,执行iframe页的方法:iframeWin.method();
+                    var inputForm = body.find('#inputForm');
+                    var top_iframe;
+                    if(target){
+                        top_iframe = target;//如果指定了iframe,则在改frame中跳转
+                    }else{
+                        top_iframe = top.getActiveTab().attr("name");//获取当前active的tab的iframe
+                    }
+                    inputForm.attr("target",top_iframe);//表单提交成功后,从服务器返回的url在当前tab中展示
+                    if(iframeWin.contentWindow.doSubmit(1) ){
+                        // top.layer.close(index);//关闭对话框。
+                        setTimeout(function(){top.layer.close(index)}, 100);//延时0.1秒,对应360 7.1版本bug
+                    }
+                },
+                btn2:function(index,layero){
+                    if(split.length==2){return}
+                    var body = top.layer.getChildFrame('body', index);
+                    var iframeWin = layero.find('iframe')[0]; //得到iframe页的窗口对象,执行iframe页的方法:iframeWin.method();
+                    var inputForm = body.find('#inputForm');
+                    var top_iframe;
+                    if(target){
+                        top_iframe = target;//如果指定了iframe,则在改frame中跳转
+                    }else{
+                        top_iframe = top.getActiveTab().attr("name");//获取当前active的tab的iframe
+                    }
+                    inputForm.attr("target",top_iframe);//表单提交成功后,从服务器返回的url在当前tab中展示
+                    if(iframeWin.contentWindow.doSubmit(2) ){
+                        // top.layer.close(index);//关闭对话框。
+                        setTimeout(function(){top.layer.close(index)}, 100);//延时0.1秒,对应360 7.1版本bug
+                    }else {
+                        return false;
+                    }
+                },
+                btn3: function (index) {
+                }
+            });
+        }
+	</script>
+<%--	<style>
+		body{
+			background-color:transparent;
+			filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#26FFFFFF, endColorstr=#26FFFFFF);
+			color:#ffffff;
+			background-color:rgba(255,255,255,0);
+			height:100%;
+		}
+	</style>--%>
+	<script>
+
+		function notifyDialogre(title,url,width,height,target){
+			if(navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i)){//如果是移动端,就使用自适应大小弹窗
+				width='auto';
+				height='auto';
+			}else{//如果是PC端,根据用户设置的width和height显示。
+
+			}
+			top.layer.open({
+				type: 2,
+				area: [width, height],
+				title: title,
+				skin:"three-btns",
+				maxmin: true, //开启最大化最小化按钮
+				content: url ,
+				btn: ['通过','驳回','关闭'],
+				btn1: function(index, layero){
+					var body = top.layer.getChildFrame('body', index);
+					var iframeWin = layero.find('iframe')[0]; //得到iframe页的窗口对象,执行iframe页的方法:iframeWin.method();
+					var inputForm = body.find('#inputForm');
+					var top_iframe;
+					if(target){
+						top_iframe = target;//如果指定了iframe,则在改frame中跳转
+					}else{
+						top_iframe = top.getActiveTab().attr("name");//获取当前active的tab的iframe
+					}
+					inputForm.attr("target",top_iframe);//表单提交成功后,从服务器返回的url在当前tab中展示
+					if(iframeWin.contentWindow.doSubmit(1) ){
+						top.layer.close(index);//关闭对话框。
+						setTimeout(function(){top.layer.close(index)}, 100);//延时0.1秒,对应360 7.1版本bug
+					}
+				},
+				btn2:function(index,layero){
+					var body = top.layer.getChildFrame('body', index);
+					var iframeWin = layero.find('iframe')[0]; //得到iframe页的窗口对象,执行iframe页的方法:iframeWin.method();
+					var inputForm = body.find('#inputForm');
+					var top_iframe;
+					if(target){
+						top_iframe = target;//如果指定了iframe,则在改frame中跳转
+					}else{
+						top_iframe = top.getActiveTab().attr("name");//获取当前active的tab的iframe
+					}
+					inputForm.attr("target",top_iframe);//表单提交成功后,从服务器返回的url在当前tab中展示
+					if(iframeWin.contentWindow.doSubmit(2) ){
+						top.layer.close(index);//关闭对话框。
+						setTimeout(function(){top.layer.close(index)}, 100);//延时0.1秒,对应360 7.1版本bug
+					}
+					return false;
+				},
+				btn3: function(index){
+				}
+			});
+
+		}
+		//打开对话框(添加修改)
+		function openDialog(title, url, width, height, target) {
+			if (navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i)) {
+				width = 'auto';
+				height = 'auto';
+			}
+
+			top.layer.open({
+				type: 2,
+				area: [width, height],
+				title: title,
+				maxmin: true,
+				content: url,
+				skin: 'two-btns',
+				btn: ['提交', '关闭'],
+				yes: async function(index, layero) {
+					var body = top.layer.getChildFrame('body', index);
+					var iframeWin = layero.find('iframe')[0].contentWindow;
+					var inputForm = body.find('#inputForm');
+					var top_iframe;
+
+					if (target) {
+						top_iframe = target;
+					} else {
+						top_iframe = top.getActiveTab().attr("name");
+					}
+					inputForm.attr("target", top_iframe);
+
+					try {
+						// 关键:等待子页面doSubmit(已改为异步等待后端响应)
+						const result = await iframeWin.doSubmit();
+						// 此时一定是后端处理完成并返回数据后,才执行以下代码
+						console.log("后端返回数据:", result);
+
+						if (result.success) {
+							//原数据总数据量
+							const metadataCount = result.metadataCount
+							//成功数据量
+							const uniqueCount = result.uniqueCount
+							//错误数据量
+							const errorCount = result.errorCount
+							//重复数据量
+							const duplicateCount = result.duplicateCount
+							if(errorCount === 0 && duplicateCount === 0) {
+								var message = "保存成功!您总共上传了 " + metadataCount + " 条数据,保存成功 " + uniqueCount + " 条数据。";
+								//如果不存在错误数据、重复数据,则直接提示新增成功
+								parent.layer.msg(message, { icon: 0 });
+								top.layer.close(index);
+								sortOrRefresh();
+							}else{
+								var projectId = result.projectId
+								if(!projectId){
+									//对应的项目id不能为空
+									parent.layer.msg("未获取到对应报告信息,无法进行下载错误数据", { icon: 5 });
+								}else{
+									// 显示带确定和关闭按钮的弹窗
+									var message = "保存成功!您总共上传了 <span style='color:red'>" + metadataCount + "</span> 条数据。<br>";
+									message += "其中保存成功了 <span style='color:red'>" + uniqueCount + "</span> 条数据。<br>";
+
+									// 根据错误数据和重复数据拼接信息
+									if (errorCount > 0 && duplicateCount > 0) {
+										message += "有<span style='color:red'>" + errorCount + "</span>条错误数据,<br>";
+										message += "<span style='color:red'>" + duplicateCount + "</span>条重复数据。<br>";
+									} else if (errorCount > 0) {
+										message += "有<span style='color:red'>" + errorCount + "</span>条错误数据。<br>";
+									} else if (duplicateCount > 0) {
+										message += "有<span style='color:red'>" + duplicateCount + "</span>条重复数据。<br>";
+									}
+
+									message += "是否导出数据汇总?";
+
+									top.layer.confirm(
+											message,
+											{
+												title: "操作结果",
+												icon: 1,
+												btn: ['确定导出', '关闭']
+											},
+											function(confirmIndex) {
+												// 关闭确认弹窗
+												//top.layer.close(confirmIndex);
+												top.layer.close(index);
+												var $ = top.layui.jquery;
+												var layer = top.layui.layer;
+												var loading = layer.load(2);
+												// 关键:确保获取到正确的子页面弹窗索引(直接从顶层获取当前弹窗索引)
+												const currentLayerIndex = top.layer.index;
+												let isHandled = false;
+
+												// 准备参数
+												const projectId = result.projectId || '';
+												if (!projectId) {
+													layer.close(loading);
+													layer.msg("缺少projectId参数,无法导出", {icon: 5});
+													return;
+												}
+
+												// 创建表单和iframe
+												const form = $('<form>');
+												form.attr('action', "${ctx}/project/projectFinalAccounts/export");
+												form.attr('method', 'post');
+												form.attr('target', 'exportFrame');
+												form.append($('<input>').attr({
+													type: 'hidden',
+													name: 'projectId',
+													value: projectId
+												}));
+
+												var iframe = $('<iframe name="exportFrame" style="display:none;"></iframe>');
+												$('body', top.document).append(iframe);
+												$('body', top.document).append(form);
+
+												// 监听iframe加载
+												iframe.on('load', function() {
+													if (isHandled) return;
+													isHandled = true;
+
+													layer.close(loading);
+													try {
+														var content = $(this).contents().find('body').text().trim();
+														if (content && content.indexOf('导出失败') !== -1) {
+															layer.msg(content, {icon: 5});
+														} else {
+															layer.msg("文件导出成功", {icon: 1});
+															// 关闭子页面(使用顶层layer的close方法)
+															// 刷新父页面
+															//sortOrRefresh();
+														}
+													} catch (e) {
+														layer.msg("文件导出成功", {icon: 1});
+														//sortOrRefresh();
+													}
+
+													form.remove();
+													iframe.remove();
+												});
+
+												// 超时兜底逻辑
+												setTimeout(function() {
+													if (isHandled) return;
+													isHandled = true;
+
+													layer.close(loading);
+													try {
+														var content = iframe.contents().find('body').text().trim();
+														if (content && content.indexOf('导出失败') !== -1) {
+															layer.msg(content, {icon: 5});
+														} else {
+															layer.msg("文件导出成功", {icon: 1});
+															// 确保关闭子页面
+															//sortOrRefresh();
+														}
+													} catch (e) {
+														layer.msg("文件导出成功", {icon: 1});
+														//sortOrRefresh();
+													}
+
+													//form.remove();
+													//iframe.remove();
+												}, 100); // 延长超时时间到0.1秒,确保操作完成
+
+												// 提交表单
+												form.submit();
+											}
+											,
+											function(confirmIndex) {
+												setTimeout(() => {
+													top.layui.layer.close(index);
+												}, 100);
+												top.layer.close(index);
+												sortOrRefresh();
+											}
+									);
+								}
+							}
+
+
+						} else {
+							top.layer.msg(result.message || "操作失败", {icon: 5});
+						}
+					} catch (error) {
+						top.layer.msg("提交失败: " + error.message, {icon: 5});
+					}
+				},
+				cancel: function(index) {}
+			});
+		}
+
+	</script>
+</head>
+<body>
+<div class="wrapper wrapper-content">
+	<sys:message content="${message}"/>
+	<div class="layui-row">
+		<div class="list-form-tab contentShadow shadowLTR" id="tabDiv">
+			<ul class="list-tabs" >
+				<li class="active"><a href="${ctx}/project/projectFinalAccounts/list">工程项目竣工结算汇总表</a></li>
+				<li><a href="${ctx}/project/projectSubstation/list">变电站建筑工程费用汇总表</a></li>
+			</ul>
+		</div>
+		<div class="full-width fl">
+			<div class="layui-row contentShadow shadowLR" id="queryDiv">
+				<form:form id="searchForm" modelAttribute="projectFinalAccounts" action="${ctx}/project/projectFinalAccounts/" method="post" class="form-inline">
+					<input id="pageNo" name="pageNo" type="hidden" value="${page.pageNo}"/>
+					<input id="pageSize" name="pageSize" type="hidden" value="${page.pageSize}"/>
+					<input id="toflag" name="toflag" type="hidden" value="1"/>
+					<table:sortColumn id="orderBy" name="orderBy" value="${page.orderBy}" callback="sortOrRefresh();"/><!-- 支持排序 -->
+					<div class="commonQuery lw6">
+						<%--<div class="layui-item query athird" style="width: 25%">
+							<label class="layui-form-label">项目名称:</label>
+							<div class="layui-input-block">
+										<sys:gridselectprojectmaterial url="${ctx}/project/projectFinalAccounts/selectproject" id="project" name="projectNumber"  value="${projectFinalAccounts.projectName}"  title="选择所属项目" labelName="projectName" cssStyle="background-color: #fff"
+									   labelValue="${projectFinalAccounts.projectName}" cssClass="form-control required layui-input" fieldLabels="项目名称" fieldKeys="projectName" searchLabel="项目名称" searchKey="projectName"  ></sys:gridselectprojectmaterial>
+
+							</div>
+						</div>--%>
+						<div class="layui-item query athird" style="width: 25%">
+							<label class="layui-form-label">序号:</label>
+							<div class="layui-input-block with-icon">
+								<form:input path="number" htmlEscape="false" placeholder="请输入序号"  maxlength="64"  class=" form-control  layui-input"/>
+							</div>
+						</div>
+						<div class="layui-item query athird" style="width: 25%">
+							<label class="layui-form-label">材料名称:</label>
+							<div class="layui-input-block">
+								<form:input path="projectName" htmlEscape="false" placeholder="请输入材料名称" maxlength="64"  class=" form-control  layui-input" />
+							</div>
+						</div>
+
+						<div class="input-group"  style="width: 25%">
+							<a href="#" id="moresee"><i class="glyphicon glyphicon-menu-down"></i></a>
+							<div class="layui-btn-group search-spacing">
+								<button id="searchQuery" class="layui-btn layui-btn-sm layui-bg-blue" onclick="search()">查询</button>
+								<button id="searchReset" class="layui-btn layui-btn-sm" onclick="resetSearch()">重置</button>
+							</div>
+						</div>
+						<div style="    clear:both;"></div>
+					</div>
+					<div id="moresees" style="clear:both;display:none;" class="lw6">
+
+
+
+						<%--<div class="layui-item query athird" style="width: 25%">
+							<label class="layui-form-label double-line" style="line-height:100%">工程价(元,不含税):</label>
+							<div class="layui-input-block">
+								<input id="beginProjectPriceExcludingTax" value="${projectFinalAccounts.beginProjectPriceExcludingTax}" name="beginProjectPriceExcludingTax" placeholder="请输入工程价(元,不含税)" htmlEscape="false" maxlength="64"  class="query-group form-control  layui-input">
+								<span class="group-sep">-</span>
+								<input id="endProjectPriceExcludingTax" value="${projectFinalAccounts.endProjectPriceExcludingTax}" name="endProjectPriceExcludingTax" placeholder="请输入工程价(元,不含税)" htmlEscape="false" maxlength="64"  class="query-group form-control  layui-input">
+							</div>
+						</div>--%>
+
+
+
+
+
+						<%--<div class="layui-item query athird " style="width: 25%">
+							<label class="layui-form-label">创建时间:</label>
+							<div class="layui-input-block readOnlyFFF">
+								<input id="beginDate" name="beginDate" placeholder="创建开始时间" type="text" readonly="readonly" maxlength="20" class="laydate-icondate form-control layer-date layui-input laydate-icon query-group"
+									   value="<fmt:formatDate value="${projectFinalAccounts.beginDate}" pattern="yyyy-MM-dd"/>"/>
+								</input>
+                                <span class="group-sep">-</span>
+                                <input id="endDate" name="endDate" placeholder="创建结束时间" type="text" readonly="readonly" maxlength="20" class="laydate-icondate form-control layer-date layui-input laydate-icon query-group"
+                                       value="<fmt:formatDate value="${projectFinalAccounts.endDate}" pattern="yyyy-MM-dd"/>"/>
+                                </input>
+							</div>
+						</div>--%>
+
+						<div style="clear:both;"></div>
+					</div>
+				</form:form>
+			</div>
+		</div>
+		<div class="full-width fl">
+			<div class="layui-form contentDetails contentShadow shadowLBR">
+				<div class="nav-btns">
+					<%--此处按钮样式包括 nav-btn-add nav-btn-refresh nav-btn-import nav-btn-export nav-btn-query nav-btn-reset--%>
+					<div class="layui-btn-group">
+						<table:addRow url="${ctx}/project/projectFinalAccounts/formTwoPage?isAdd=0" title="添加材料"></table:addRow><!-- 增加按钮 -->
+						<%--<shiro:hasPermission name="project:pojectMaterialsWarehouse:list">--%>
+							<button type="button" data-toggle="tooltip" data-placement="top" class="layui-btn layui-btn-sm layui-bg-red" id="delProjectFinalAccounts"> 批量删除</button>
+						<%--</shiro:hasPermission>--%>
+						<button class="layui-btn layui-btn-sm" data-toggle="tooltip" data-placement="left" onclick="sortOrRefresh()" title="刷新"> 刷新</button>
+						</div>
+					<%--<shiro:hasPermission name="project:projectFinalAccounts:list">
+						<table:exportExcel url="${ctx}/project/projectFinalAccounts/export"></table:exportExcel><!-- 导出按钮 -->
+					</shiro:hasPermission>--%>
+					<div style="clear: both;"></div>
+				</div>
+				<table class="oa-table layui-table" id="contentTable1"></table>
+
+				<!-- 分页代码 -->
+				<table:page page="${page}"></table:page>
+				<div style="clear: both;"></div>
+			</div>
+		</div>
+	</div>
+	<div id="changewidth"></div>
+</div>
+
+<script>
+
+    layui.use('table', function(){
+        layui.table.render({
+            limit:${ page.pageSize }
+			,id:"checkboxTable"
+            ,elem: '#contentTable1'
+            ,page: false
+            ,cols: [[
+				{checkbox: true, fixed: true},
+                {field:'index',align:'center', title: '序号',width:55}
+				,{field:'number',align:'center', title: '编号',  width:80}
+				/*,{field:'projectName',align:'center', title: '项目名称', minWidth:200,templet:function(d){
+						var xml="";
+						<shiro:hasPermission name="ruralProject:ruralProjectView:listAllView">
+						xml+="<a class=\"attention-info pid\" title=\"" + d.projectName + "\" href=\"javascript:void(0);\" onclick=\"openDialogView('查看项目', '${ctx}/ruralProject/ruralProjectView/view?id=" + d.projectId +"','95%', '95%')\">"
+						</shiro:hasPermission>
+						xml+=d.projectName
+						<shiro:hasPermission name="ruralProject:ruralProjectView:listAllView">
+						xml+="</a>";
+						</shiro:hasPermission>
+						return xml;
+					}}
+				,{field:'reportNumber',align:'center', title: '报告号',  width:180}*/
+				,{field:'projectOrCostName',align:'center', title: '项目或费用名称',  minWidth:200, templet:function(d){
+					return "<a class=\"attention-info\" title=\"" + d.projectOrCostName + "\" href=\"javascript:void(0);\" onclick=\"openDialogView('查看详情', '${ctx}/project/projectFinalAccounts/view?id=" + d.id +"','95%', '95%')\">" + d.projectOrCostName + "</a>";
+				}}
+				,{field:'settlementSum',align:'center', title: '金额',  width:150}
+				,{field:'remarks',align:'center', title: '备注',  width:200}
+				,{field:'createBy',align:'center', title: '创建人',  width:80}
+                ,{field:'createDate',align:'center', title: '创建日期',  width:100}
+                ,{field:'op',align:'center',title:"操作",width:160,templet:function(d){
+                        ////对操作进行初始化
+                        var xml="<div class=\"layui-btn-group\">";
+						if((d.flag != undefined && d.flag =="1") || <%=admin%>){
+							xml+="<a href=\"#\" onclick=\"openDialogre('修改', '${ctx}/project/projectFinalAccounts/formTwoPage?id=" + d.id+"&&isAdd=1" +"','95%', '95%','','提交,关闭')\" class=\"layui-btn layui-btn-xs layui-bg-green\" > 修改</a>";
+							xml+="<a href=\"${ctx}/project/projectFinalAccounts/delete?id=" + d.id + "\" onclick=\"return confirmx('确认要删除该材料信息吗?', this.href)\" class=\"layui-btn layui-btn-xs layui-bg-red\"> 删除</a>";
+						}
+                        xml+="</div>"
+                        return xml;
+
+                    }}
+            ]]
+            ,data: [
+                <c:if test="${ not empty page.list}">
+                <c:forEach items="${page.list}" var="projectFinalAccounts" varStatus="index">
+                <c:if test="${index.index != 0}">,</c:if>
+                {
+                    "index":"${index.index+1}"
+                    ,"id":"${projectFinalAccounts.id}"
+					,"number":"${projectFinalAccounts.number}"
+					,"settlementSum":"${projectFinalAccounts.settlementSum}"
+					,"projectOrCostName":"${projectFinalAccounts.projectOrCostName}"
+					,"remarks":"${projectFinalAccounts.remarks}"
+					,"createBy":"${projectFinalAccounts.createBy.name}"
+					,"projectName":"${projectFinalAccounts.projectName}"
+					,"reportNumber":"${projectFinalAccounts.reportNumber}"
+					,"createDate":"<fmt:formatDate value="${projectFinalAccounts.createDate}" pattern="yyyy-MM-dd"/>"
+					,"flag":
+							<c:choose>
+							<c:when test="${projectFinalAccounts.createBy.id eq fns:getUser().id}">
+							"1"
+					</c:when>
+					<c:otherwise>
+					"0"
+					</c:otherwise>
+					</c:choose>
+                }
+                </c:forEach>
+                </c:if>
+            ]
+            // ,even: true
+            // ,height: 315
+        });
+		$("#delProjectFinalAccounts").bind("click",function () {
+			//获得表格CheckBox已经选中的行的信息
+			var checkList = layui.table.checkStatus('checkboxTable').data;
+			//定义数组存放批量删除的行的id
+			var listId = [];
+			//进行遍历所有选中行数据,拿出每一行的id存储到数组中
+			$.each(checkList, function (i, data) {
+				listId.push(data.id);
+			});
+			if (listId.length <= 0) {
+				layer.msg("请选择需要删除的信息", {icon: 2})
+			} else {
+				layer.confirm('是否删除选择的数据信息?', {
+					btn: ['确认', '取消']
+					,btn1: function(index, layero){
+						$.ajax({
+							type:"post",
+							url:"${ctx}/project/projectFinalAccounts/deleteAll?listId="+ listId,
+							dataType:"json",
+							success:function(data){
+								layer.closeAll();//关闭对话框。
+								if(data.success) {
+									top.layer.msg(data.str, {icon: 1});
+									window.location.reload();
+								}else {
+									top.layer.msg(data.str, {icon: 0});
+								}
+							}
+						})
+					}
+					,btn2: function(index, layero){
+					}
+				});
+
+				return true;
+			}
+		});
+    })
+
+    resizeListTable();
+    $("a").on("click",addLinkVisied);
+</script>
+<script>
+    resizeListWindow2();
+    $(window).resize(function(){
+        resizeListWindow2();
+    });
+</script>
+</body>
+</html>

+ 138 - 0
src/main/webapp/webpage/modules/projectStatement/projectAccountsTwoForm.jsp

@@ -0,0 +1,138 @@
+<%@ page contentType="text/html;charset=UTF-8" %>
+<%@ include file="/webpage/include/taglib.jsp"%>
+<html>
+<head>
+	<title>材料库</title>
+	<meta name="decorator" content="default"/>
+	<script type="text/javascript" src="${ctxStatic}/layui/layui.js"></script>
+	<link rel='stylesheet' type="text/css" href="${ctxStatic}/layui/css/layui.css"/>
+	<script src="${ctxStatic}/common/html/js/script.js"></script>
+	<style>
+		#contractTypeDoc-error{
+			top:80px;
+			left:0;
+		}
+		 /*超过5个汉字,调整label的长度,以下是配套的*/
+		 .layui-item .layui-form-label{
+			 width:90px;
+		 }
+		.form-group .layui-item .layui-input-block,
+		.query .layui-input-block {
+			margin-left: 116px;
+		}
+		#workInvoiceProjectRelationList td{
+			padding-left: 0px;
+			padding-right: 0px;
+		}
+	</style>
+	<script type="text/javascript">
+        var validateForm;
+        function doSubmit(obj){//回调函数,在编辑和保存动作时,供openDialog调用提交表单。
+            //debugger
+            if(validateForm.form()){
+				$("#inputForm").submit();
+                return true;
+            }else {
+				parent.layer.msg("信息未填写完整!", {icon: 5});
+			}
+            return false;
+        }
+
+        $(document).ready(function() {
+
+			layui.use(['form', 'layer'], function () {
+				var form = layui.form;
+			});
+            validateForm = $("#inputForm").validate({
+                submitHandler: function(form){
+                    loading('正在提交,请稍等...');
+                    form.submit();
+                },
+                errorContainer: "#messageBox",
+                errorPlacement: function(error, element) {
+
+                    $("#messageBox").text("输入有误,请先更正。");
+                    if (element.is(":checkbox")||element.is(":radio")||element.parent().is(".input-append")){
+                        error.appendTo(element.parent().parent());
+                    } else {
+                        error.insertAfter(element);
+                    }
+                }
+            });
+
+
+            /*--------------*/
+			$("#attachment_btn").click(function () {
+				$("#attachment_file").click();
+			});
+			laydate.render({
+				elem: '#quotedPriceDate', //目标元素。由于laydate.js封装了一个轻量级的选择器引擎,因此elem还允许你传入class、tag但必须按照这种方式 '#id .class'
+				type : 'date'
+				, trigger: 'click'
+			});
+        });
+
+		function newSetPNumber(obj,label,ids,isProject,details){
+			$("#projectNumber").val(obj)
+			$("#projectName").val(label)
+		}
+
+		function num(obj){
+
+			obj.value = obj.value.replace(/[^\d.]/g,""); //清除"数字"和"."以外的字符
+			obj.value = obj.value.replace(/^\./g,""); //验证第一个字符是数字
+			obj.value = obj.value.replace(/\.{2,}/g,"."); //只保留第一个, 清除多余的
+			obj.value = obj.value.replace(".","$#$").replace(/\./g,"").replace("$#$",".");
+			obj.value = obj.value.replace(/^(\-)*(\d+)\.(\d\d).*$/,'$1$2.$3'); //只能输入两个小数
+		}
+	</script>
+</head>
+<body>
+<div class="single-form">
+	<div class="container">
+		<form:form id="inputForm" modelAttribute="projectFinalAccounts" action="${ctx}/project/projectFinalAccounts/signleSave?isAdd=${isAdd}" method="post" class="layui-form">
+			<form:hidden path="id"/>
+			<form:hidden path="projectNumber"/>
+			<div class="form-group layui-row first">
+				<div class="form-group-label"><h2>材料详情</h2></div>
+				<div class="layui-item layui-col-sm6">
+					<label class="layui-form-label"><span class="require-item invoicetype">*</span>项目名称</label>
+					<div class="layui-input-block">
+						<sys:gridselectprojectmaterial url="${ctx}/project/projectFinalAccounts/selectproject" id="project" name="projectNames"  value="${projectFinalAccounts.projectName}"  title="选择所属项目" labelName="projectName" cssStyle="background-color: #fff"
+													   labelValue="${projectFinalAccounts.projectName}" cssClass="form-control required layui-input" fieldLabels="项目名称" fieldKeys="projectName" searchLabel="项目名称" searchKey="projectName"  ></sys:gridselectprojectmaterial>
+					</div>
+				</div>
+				<div class="layui-item layui-col-sm6">
+					<label class="layui-form-label"><span class="require-item invoicetype">*</span>编号</label>
+					<div class="layui-input-block">
+						<form:input id="number" path="number" htmlEscape="false"  placeholder="请输入编号"  class="form-control layui-input" required="true" maxlength="240"/>
+					</div>
+				</div>
+				<div class="layui-item layui-col-sm6">
+					<label class="layui-form-label"><span class="require-item invoicetype">*</span>材料名称</label>
+					<div class="layui-input-block">
+						<form:input id="projectOrCostName" path="projectOrCostName" htmlEscape="false"  placeholder="请输入材料名称"  class="form-control layui-input" required="true" maxlength="240"/>
+					</div>
+				</div>
+				<div class="layui-item layui-col-sm6">
+					<label class="layui-form-label">金额</label>
+					<div class="layui-input-block">
+						<form:input id="settlementSum" path="settlementSum" htmlEscape="false"  placeholder="请输入金额"  class="form-control layui-input" maxlength="240"/>
+					</div>
+				</div>
+
+				<div class="layui-item layui-col-sm12  with-textarea">
+					<label class="layui-form-label">备注:</label>
+					<div class="layui-input-block">
+						<form:textarea id="remarks" path="remarks" htmlEscape="false" rows="4" placeholder="请输入备注"  class="form-control" maxlength="800"/>
+					</div>
+				</div>
+
+			</div>
+			<div class="form-group layui-row page-end"></div>
+		</form:form>
+	</div>
+</div>
+
+</body>
+</html>

+ 132 - 0
src/main/webapp/webpage/modules/projectStatement/projectAccountsView.jsp

@@ -0,0 +1,132 @@
+<%@ page contentType="text/html;charset=UTF-8" %>
+<%@ include file="/webpage/include/taglib.jsp"%>
+<html>
+<head>
+	<title>材料库</title>
+	<meta name="decorator" content="default"/>
+	<script type="text/javascript" src="${ctxStatic}/layui/layui.js"></script>
+	<link rel='stylesheet' type="text/css" href="${ctxStatic}/layui/css/layui.css"/>
+	<script src="${ctxStatic}/common/html/js/script.js"></script>
+	<style>
+		#contractTypeDoc-error{
+			top:80px;
+			left:0;
+		}
+		 /*超过5个汉字,调整label的长度,以下是配套的*/
+		 .layui-item .layui-form-label{
+			 width:90px;
+		 }
+		.form-group .layui-item .layui-input-block,
+		.query .layui-input-block {
+			margin-left: 116px;
+		}
+		#workInvoiceProjectRelationList td{
+			padding-left: 0px;
+			padding-right: 0px;
+		}
+	</style>
+	<script type="text/javascript">
+        var validateForm;
+        function doSubmit(obj){//回调函数,在编辑和保存动作时,供openDialog调用提交表单。
+            //debugger
+            if(validateForm.form()){
+				$("#inputForm").submit();
+                return true;
+            }else {
+				parent.layer.msg("信息未填写完整!", {icon: 5});
+			}
+            return false;
+        }
+
+        $(document).ready(function() {
+
+			layui.use(['form', 'layer'], function () {
+				var form = layui.form;
+			});
+            validateForm = $("#inputForm").validate({
+                submitHandler: function(form){
+                    loading('正在提交,请稍等...');
+                    form.submit();
+                },
+                errorContainer: "#messageBox",
+                errorPlacement: function(error, element) {
+
+                    $("#messageBox").text("输入有误,请先更正。");
+                    if (element.is(":checkbox")||element.is(":radio")||element.parent().is(".input-append")){
+                        error.appendTo(element.parent().parent());
+                    } else {
+                        error.insertAfter(element);
+                    }
+                }
+            });
+
+
+            /*--------------*/
+			$("#attachment_btn").click(function () {
+				$("#attachment_file").click();
+			});
+        });
+
+		function newSetPNumber(obj,label,ids,isProject,details){
+			$("#projectNumber").val(obj)
+			$("#projectName").val(label)
+		}
+
+		function num(obj){
+
+			obj.value = obj.value.replace(/[^\d.]/g,""); //清除"数字"和"."以外的字符
+			obj.value = obj.value.replace(/^\./g,""); //验证第一个字符是数字
+			obj.value = obj.value.replace(/\.{2,}/g,"."); //只保留第一个, 清除多余的
+			obj.value = obj.value.replace(".","$#$").replace(/\./g,"").replace("$#$",".");
+			obj.value = obj.value.replace(/^(\-)*(\d+)\.(\d\d).*$/,'$1$2.$3'); //只能输入两个小数
+		}
+	</script>
+</head>
+<body>
+<div class="single-form">
+	<div class="container">
+		<form:form id="inputForm" modelAttribute="projectFinalAccounts" method="post" class="layui-form">
+			<form:hidden path="id"/>
+			<form:hidden path="projectNumber"/>
+			<div class="form-group layui-row first">
+				<div class="form-group-label"><h2>材料详情</h2></div>
+				<div class="layui-item layui-col-sm6">
+					<label class="layui-form-label"><span class="require-item invoicetype">*</span>项目名称</label>
+					<div class="layui-input-block">
+						<input class="form-control layui-input" readonly id="projectName" name="projectName" value="${projectFinalAccounts.projectName}">
+					</div>
+				</div>
+				<div class="layui-item layui-col-sm6">
+					<label class="layui-form-label"><span class="require-item invoicetype">*</span>编号</label>
+					<div class="layui-input-block">
+						<form:input id="number" path="number" htmlEscape="false" readonly="true" placeholder="请输入编号"  class="form-control layui-input" required="true" maxlength="64"/>
+					</div>
+				</div>
+				<div class="layui-item layui-col-sm6">
+					<label class="layui-form-label"><span class="require-item invoicetype">*</span>材料名称</label>
+					<div class="layui-input-block">
+						<form:input id="projectOrCostName" path="projectOrCostName" htmlEscape="false" readonly="true" placeholder="请输入材料名称"  class="form-control layui-input" required="true" maxlength="64"/>
+					</div>
+				</div>
+				<div class="layui-item layui-col-sm6">
+					<label class="layui-form-label">金额</label>
+					<div class="layui-input-block">
+						<form:input id="settlementSum" path="settlementSum" htmlEscape="false" readonly="true" placeholder="请输入金额"  class="form-control layui-input" maxlength="64"/>
+					</div>
+				</div>
+
+				<div class="layui-item layui-col-sm12  with-textarea">
+					<label class="layui-form-label">备注:</label>
+					<div class="layui-input-block">
+						<form:textarea id="remarks" path="remarks" style="background-color: #f1f1f1" htmlEscape="false" rows="4" readonly="true" placeholder="请输入备注"  class="form-control" maxlength="800"/>
+					</div>
+				</div>
+
+			</div>
+			<div class="form-group layui-row page-end"></div>
+		</form:form>
+	</div>
+</div>
+
+</body>
+</html>

+ 730 - 0
src/main/webapp/webpage/modules/projectStatement/substation/projectSubstationList.jsp

@@ -0,0 +1,730 @@
+<%@ page import="com.jeeplus.modules.sys.utils.UserUtils" %>
+<%@ page contentType="text/html;charset=UTF-8" %>
+<%@ include file="/webpage/include/taglib.jsp"%>
+<%
+	boolean admin = UserUtils.getUser().isAdmin();
+%>
+<html>
+<head>
+	<title>材料库</title>
+	<meta name="decorator" content="default"/>
+	<script type="text/javascript" src="${ctxStatic}/layui/layuidown.js"></script>
+	<link rel='stylesheet' type="text/css" href="${ctxStatic}/layui/layuidown.css"/>
+	<%--<script src="${ctxStatic}/layer-v2.3/laydate/laydate.js"></script>--%>
+	<script type="text/javascript">
+        $(document).ready(function() {
+			layui.use(['dropdown', 'util', 'layer', 'jquery'], function () {
+				var form = layui.form;
+				var $ = layui.jquery;
+				var layer = layui.layer;
+				// 确保顶层窗口可访问
+				top.layui = layui;
+				$.ajax({
+					type : "POST",
+					url : "${ctx}/ruralProject/ruralProjectRecords/engineeringTreeMenu",
+					//请求成功
+					success : function(result) {
+						var str=jQuery.parseJSON(result);
+						//工程类型树形菜单
+						layui.dropdown.render({
+							elem: '#demo100'
+							,style: 'width: 320px;'
+							,data:str
+							,click: function(item){
+								$("#demo100").val(item.title)
+								$("#demo101").val(item.id)
+							}
+						});
+					}
+				});
+			})
+            //搜索框收放
+            $('#moresee').click(function(){
+                if($('#moresees').is(':visible'))
+                {
+                    $('#moresees').slideUp(0,resizeListWindow2);
+                    $('#moresee i').removeClass("glyphicon glyphicon-menu-up").addClass("glyphicon glyphicon-menu-down");
+                }else{
+                    $('#moresees').slideDown(0,resizeListWindow2);
+                    $('#moresee i').removeClass("glyphicon glyphicon-menu-down").addClass("glyphicon glyphicon-menu-up");
+                }
+            });
+            laydate.render({
+                elem: '#beginDate', //目标元素。由于laydate.js封装了一个轻量级的选择器引擎,因此elem还允许你传入class、tag但必须按照这种方式 '#id .class'
+                event: 'focus', //响应事件。如果没有传入event,则按照默认的click
+                type : 'date'
+, trigger: 'click'
+            });
+            laydate.render({
+                elem: '#endDate', //目标元素。由于laydate.js封装了一个轻量级的选择器引擎,因此elem还允许你传入class、tag但必须按照这种方式 '#id .class'
+                event: 'focus', //响应事件。如果没有传入event,则按照默认的click
+                type : 'date'
+, trigger: 'click'
+            });
+            laydate.render({
+                elem: '#beginQuotedPriceDate', //目标元素。由于laydate.js封装了一个轻量级的选择器引擎,因此elem还允许你传入class、tag但必须按照这种方式 '#id .class'
+                type : 'month'
+, trigger: 'click'
+            });
+            laydate.render({
+                elem: '#endQuotedPriceDate', //目标元素。由于laydate.js封装了一个轻量级的选择器引擎,因此elem还允许你传入class、tag但必须按照这种方式 '#id .class'
+                type : 'month'
+, trigger: 'click'
+            });
+        });
+
+        function reset() {
+            $("#searchForm").resetForm();
+        }
+
+        function openDialog(title,url,width,height,target) {
+
+            if (navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i)) {//如果是移动端,就使用自适应大小弹窗
+                width = 'auto';
+                height = 'auto';
+            } else {//如果是PC端,根据用户设置的width和height显示。
+
+            }
+
+            top.layer.open({
+                type: 2,
+                area: [width, height],
+                title: title,
+                maxmin: true, //开启最大化最小化按钮
+                content: url,
+                skin: 'three-btns',
+				btn: ['提交', '关闭'],
+                /*yes: function (index, layero) {
+                    var body = top.layer.getChildFrame('body', index);
+                    var iframeWin = layero.find('iframe')[0]; //得到iframe页的窗口对象,执行iframe页的方法:iframeWin.method();
+                    var inputForm = body.find('#inputForm');
+                    var top_iframe;
+                    if (target) {
+                        top_iframe = target;//如果指定了iframe,则在改frame中跳转
+                    } else {
+                        top_iframe = top.getActiveTab().attr("name");//获取当前active的tab的iframe
+                    }
+                    inputForm.attr("target", top_iframe);//表单提交成功后,从服务器返回的url在当前tab中展示
+
+                    if (iframeWin.contentWindow.doSubmit()) {
+                        // top.layer.close(index);//关闭对话框。
+                        setTimeout(function () {
+                            top.layer.close(index)
+                        }, 100);//延时0.1秒,对应360 7.1版本bug
+                    }
+
+                },*/
+                btn1: function(index, layero){
+                    var body = top.layer.getChildFrame('body', index);
+                    var iframeWin = layero.find('iframe')[0]; //得到iframe页的窗口对象,执行iframe页的方法:iframeWin.method();
+                    var inputForm = body.find('#inputForm');
+                    var top_iframe;
+                    if(target){
+                        top_iframe = target;//如果指定了iframe,则在改frame中跳转
+                    }else{
+                        top_iframe = top.getActiveTab().attr("name");//获取当前active的tab的iframe
+                    }
+                    inputForm.attr("target",top_iframe);//表单提交成功后,从服务器返回的url在当前tab中展示
+                    if(iframeWin.contentWindow.doSubmit(1) ){
+                        // top.layer.close(index);//关闭对话框。
+                        setTimeout(function(){top.layer.close(index)}, 100);//延时0.1秒,对应360 7.1版本bug
+                    }
+                },
+                btn2: function (index) {
+                }
+            });
+        }
+
+
+		function newSetPNumber(obj,label,ids,isProject,details){
+			$("#projectId").val(obj)
+			$("#projectName").val(label)
+		}
+
+		//打开对话框(查看)
+		function openDialogReportView(title,url,id,width,height){
+
+
+			if(navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i)){//如果是移动端,就使用自适应大小弹窗
+				width='auto';
+				height='auto';
+			}else{//如果是PC端,根据用户设置的width和height显示。
+
+			}
+			$.ajax({
+				async: false,
+				url: "${ctx}/ruralProject/ruralProjectMessage/getReportExist?id="+id,
+				dataType: "json",
+				success: function (data) {
+					if(data.success){
+						top.layer.open({
+							type: 2,
+							skin: 'one-btn',
+							area: [width, height],
+							title: title,
+							maxmin: true, //开启最大化最小化按钮
+							content: url ,
+							btn: ['关闭'],
+							cancel: function(index){
+							}
+						});
+					}else{
+						top.layer.msg("该项目报告信息已删除!", {icon: 0});
+						window.location.reload();
+					}
+				}
+			});
+
+		}
+        function openDialogre(title,url,width,height,target,buttons) {
+
+            if (navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i)) {//如果是移动端,就使用自适应大小弹窗
+                width = 'auto';
+                height = 'auto';
+            } else {//如果是PC端,根据用户设置的width和height显示。
+
+            }
+            var split = buttons.split(",");
+            top.layer.open({
+                type: 2,
+                area: [width, height],
+                title: title,
+                maxmin: true, //开启最大化最小化按钮
+                skin: 'three-btns',
+                content: url,
+                btn: split,
+                btn1: function(index, layero){
+                    var body = top.layer.getChildFrame('body', index);
+                    var iframeWin = layero.find('iframe')[0]; //得到iframe页的窗口对象,执行iframe页的方法:iframeWin.method();
+                    var inputForm = body.find('#inputForm');
+                    var top_iframe;
+                    if(target){
+                        top_iframe = target;//如果指定了iframe,则在改frame中跳转
+                    }else{
+                        top_iframe = top.getActiveTab().attr("name");//获取当前active的tab的iframe
+                    }
+                    inputForm.attr("target",top_iframe);//表单提交成功后,从服务器返回的url在当前tab中展示
+                    if(iframeWin.contentWindow.doSubmit(1) ){
+                        // top.layer.close(index);//关闭对话框。
+                        setTimeout(function(){top.layer.close(index)}, 100);//延时0.1秒,对应360 7.1版本bug
+                    }
+                },
+                btn2:function(index,layero){
+                    if(split.length==2){return}
+                    var body = top.layer.getChildFrame('body', index);
+                    var iframeWin = layero.find('iframe')[0]; //得到iframe页的窗口对象,执行iframe页的方法:iframeWin.method();
+                    var inputForm = body.find('#inputForm');
+                    var top_iframe;
+                    if(target){
+                        top_iframe = target;//如果指定了iframe,则在改frame中跳转
+                    }else{
+                        top_iframe = top.getActiveTab().attr("name");//获取当前active的tab的iframe
+                    }
+                    inputForm.attr("target",top_iframe);//表单提交成功后,从服务器返回的url在当前tab中展示
+                    if(iframeWin.contentWindow.doSubmit(2) ){
+                        // top.layer.close(index);//关闭对话框。
+                        setTimeout(function(){top.layer.close(index)}, 100);//延时0.1秒,对应360 7.1版本bug
+                    }else {
+                        return false;
+                    }
+                },
+                btn3: function (index) {
+                }
+            });
+        }
+	</script>
+<%--	<style>
+		body{
+			background-color:transparent;
+			filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#26FFFFFF, endColorstr=#26FFFFFF);
+			color:#ffffff;
+			background-color:rgba(255,255,255,0);
+			height:100%;
+		}
+	</style>--%>
+	<script>
+
+		function notifyDialogre(title,url,width,height,target){
+			if(navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i)){//如果是移动端,就使用自适应大小弹窗
+				width='auto';
+				height='auto';
+			}else{//如果是PC端,根据用户设置的width和height显示。
+
+			}
+			top.layer.open({
+				type: 2,
+				area: [width, height],
+				title: title,
+				skin:"three-btns",
+				maxmin: true, //开启最大化最小化按钮
+				content: url ,
+				btn: ['通过','驳回','关闭'],
+				btn1: function(index, layero){
+					var body = top.layer.getChildFrame('body', index);
+					var iframeWin = layero.find('iframe')[0]; //得到iframe页的窗口对象,执行iframe页的方法:iframeWin.method();
+					var inputForm = body.find('#inputForm');
+					var top_iframe;
+					if(target){
+						top_iframe = target;//如果指定了iframe,则在改frame中跳转
+					}else{
+						top_iframe = top.getActiveTab().attr("name");//获取当前active的tab的iframe
+					}
+					inputForm.attr("target",top_iframe);//表单提交成功后,从服务器返回的url在当前tab中展示
+					if(iframeWin.contentWindow.doSubmit(1) ){
+						top.layer.close(index);//关闭对话框。
+						setTimeout(function(){top.layer.close(index)}, 100);//延时0.1秒,对应360 7.1版本bug
+					}
+				},
+				btn2:function(index,layero){
+					var body = top.layer.getChildFrame('body', index);
+					var iframeWin = layero.find('iframe')[0]; //得到iframe页的窗口对象,执行iframe页的方法:iframeWin.method();
+					var inputForm = body.find('#inputForm');
+					var top_iframe;
+					if(target){
+						top_iframe = target;//如果指定了iframe,则在改frame中跳转
+					}else{
+						top_iframe = top.getActiveTab().attr("name");//获取当前active的tab的iframe
+					}
+					inputForm.attr("target",top_iframe);//表单提交成功后,从服务器返回的url在当前tab中展示
+					if(iframeWin.contentWindow.doSubmit(2) ){
+						top.layer.close(index);//关闭对话框。
+						setTimeout(function(){top.layer.close(index)}, 100);//延时0.1秒,对应360 7.1版本bug
+					}
+					return false;
+				},
+				btn3: function(index){
+				}
+			});
+
+		}
+		//打开对话框(添加修改)
+		function openDialog(title, url, width, height, target) {
+			if (navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i)) {
+				width = 'auto';
+				height = 'auto';
+			}
+
+			top.layer.open({
+				type: 2,
+				area: [width, height],
+				title: title,
+				maxmin: true,
+				content: url,
+				skin: 'two-btns',
+				btn: ['提交', '关闭'],
+				yes: async function(index, layero) {
+					var body = top.layer.getChildFrame('body', index);
+					var iframeWin = layero.find('iframe')[0].contentWindow;
+					var inputForm = body.find('#inputForm');
+					var top_iframe;
+
+					if (target) {
+						top_iframe = target;
+					} else {
+						top_iframe = top.getActiveTab().attr("name");
+					}
+					inputForm.attr("target", top_iframe);
+
+					try {
+						// 关键:等待子页面doSubmit(已改为异步等待后端响应)
+						const result = await iframeWin.doSubmit();
+						// 此时一定是后端处理完成并返回数据后,才执行以下代码
+						console.log("后端返回数据:", result);
+
+						if (result.success) {
+							//原数据总数据量
+							const metadataCount = result.metadataCount
+							//成功数据量
+							const uniqueCount = result.uniqueCount
+							//错误数据量
+							const errorCount = result.errorCount
+							//重复数据量
+							const duplicateCount = result.duplicateCount
+							if(errorCount === 0 && duplicateCount === 0) {
+								var message = "保存成功!您总共上传了 " + metadataCount + " 条数据,保存成功 " + uniqueCount + " 条数据。";
+								//如果不存在错误数据、重复数据,则直接提示新增成功
+								parent.layer.msg(message, { icon: 0 });
+								top.layer.close(index);
+								sortOrRefresh();
+							}else{
+								var projectId = result.projectId
+								if(!projectId){
+									//对应的项目id不能为空
+									parent.layer.msg("未获取到对应报告信息,无法进行下载错误数据", { icon: 5 });
+								}else{
+									// 显示带确定和关闭按钮的弹窗
+									var message = "保存成功!您总共上传了 <span style='color:red'>" + metadataCount + "</span> 条数据。<br>";
+									message += "其中保存成功了 <span style='color:red'>" + uniqueCount + "</span> 条数据。<br>";
+
+									// 根据错误数据和重复数据拼接信息
+									if (errorCount > 0 && duplicateCount > 0) {
+										message += "有<span style='color:red'>" + errorCount + "</span>条错误数据,<br>";
+										message += "<span style='color:red'>" + duplicateCount + "</span>条重复数据。<br>";
+									} else if (errorCount > 0) {
+										message += "有<span style='color:red'>" + errorCount + "</span>条错误数据。<br>";
+									} else if (duplicateCount > 0) {
+										message += "有<span style='color:red'>" + duplicateCount + "</span>条重复数据。<br>";
+									}
+
+									message += "是否导出数据汇总?";
+
+									top.layer.confirm(
+											message,
+											{
+												title: "操作结果",
+												icon: 1,
+												btn: ['确定导出', '关闭']
+											},
+											function(confirmIndex) {
+												// 关闭确认弹窗
+												//top.layer.close(confirmIndex);
+												top.layer.close(index);
+												var $ = top.layui.jquery;
+												var layer = top.layui.layer;
+												var loading = layer.load(2);
+												// 关键:确保获取到正确的子页面弹窗索引(直接从顶层获取当前弹窗索引)
+												const currentLayerIndex = top.layer.index;
+												let isHandled = false;
+
+												// 准备参数
+												const projectId = result.projectId || '';
+												if (!projectId) {
+													layer.close(loading);
+													layer.msg("缺少projectId参数,无法导出", {icon: 5});
+													return;
+												}
+
+												// 创建表单和iframe
+												const form = $('<form>');
+												form.attr('action', "${ctx}/project/projectFinalAccounts/export");
+												form.attr('method', 'post');
+												form.attr('target', 'exportFrame');
+												form.append($('<input>').attr({
+													type: 'hidden',
+													name: 'projectId',
+													value: projectId
+												}));
+
+												var iframe = $('<iframe name="exportFrame" style="display:none;"></iframe>');
+												$('body', top.document).append(iframe);
+												$('body', top.document).append(form);
+
+												// 监听iframe加载
+												iframe.on('load', function() {
+													if (isHandled) return;
+													isHandled = true;
+
+													layer.close(loading);
+													try {
+														var content = $(this).contents().find('body').text().trim();
+														if (content && content.indexOf('导出失败') !== -1) {
+															layer.msg(content, {icon: 5});
+														} else {
+															layer.msg("文件导出成功", {icon: 1});
+															// 关闭子页面(使用顶层layer的close方法)
+															// 刷新父页面
+															//sortOrRefresh();
+														}
+													} catch (e) {
+														layer.msg("文件导出成功", {icon: 1});
+														//sortOrRefresh();
+													}
+
+													form.remove();
+													iframe.remove();
+												});
+
+												// 超时兜底逻辑
+												setTimeout(function() {
+													if (isHandled) return;
+													isHandled = true;
+
+													layer.close(loading);
+													try {
+														var content = iframe.contents().find('body').text().trim();
+														if (content && content.indexOf('导出失败') !== -1) {
+															layer.msg(content, {icon: 5});
+														} else {
+															layer.msg("文件导出成功", {icon: 1});
+															// 确保关闭子页面
+															//sortOrRefresh();
+														}
+													} catch (e) {
+														layer.msg("文件导出成功", {icon: 1});
+														//sortOrRefresh();
+													}
+
+													//form.remove();
+													//iframe.remove();
+												}, 100); // 延长超时时间到0.1秒,确保操作完成
+
+												// 提交表单
+												form.submit();
+											}
+											,
+											function(confirmIndex) {
+												setTimeout(() => {
+													top.layui.layer.close(index);
+												}, 100);
+												top.layer.close(index);
+												sortOrRefresh();
+											}
+									);
+								}
+							}
+
+
+						} else {
+							top.layer.msg(result.message || "操作失败", {icon: 5});
+						}
+					} catch (error) {
+						top.layer.msg("提交失败: " + error.message, {icon: 5});
+					}
+				},
+				cancel: function(index) {}
+			});
+		}
+
+	</script>
+</head>
+<body>
+<div class="wrapper wrapper-content">
+	<sys:message content="${message}"/>
+	<div class="layui-row">
+		<div class="list-form-tab contentShadow shadowLTR" id="tabDiv">
+			<ul class="list-tabs" >
+				<li><a href="${ctx}/project/projectFinalAccounts/list">工程项目竣工结算汇总表</a></li>
+				<li class="active"><a href="${ctx}/project/projectSubstation/list">变电站建筑工程费用汇总表</a></li>
+			</ul>
+		</div>
+		<div class="full-width fl">
+			<div class="layui-row contentShadow shadowLR" id="queryDiv">
+				<form:form id="searchForm" modelAttribute="projectSubstation" action="${ctx}/project/projectSubstation/" method="post" class="form-inline">
+					<input id="pageNo" name="pageNo" type="hidden" value="${page.pageNo}"/>
+					<input id="pageSize" name="pageSize" type="hidden" value="${page.pageSize}"/>
+					<input id="toflag" name="toflag" type="hidden" value="1"/>
+					<table:sortColumn id="orderBy" name="orderBy" value="${page.orderBy}" callback="sortOrRefresh();"/><!-- 支持排序 -->
+					<div class="commonQuery lw6">
+						<%--<div class="layui-item query athird" style="width: 25%">
+							<label class="layui-form-label">项目名称:</label>
+							<div class="layui-input-block">
+										<sys:gridselectprojectmaterial url="${ctx}/project/projectSubstation/selectproject" id="project" name="projectNumber"  value="${projectSubstation.projectName}"  title="选择所属项目" labelName="projectName" cssStyle="background-color: #fff"
+									   labelValue="${projectSubstation.projectName}" cssClass="form-control required layui-input" fieldLabels="项目名称" fieldKeys="projectName" searchLabel="项目名称" searchKey="projectName"  ></sys:gridselectprojectmaterial>
+
+							</div>
+						</div>--%>
+						<div class="layui-item query athird" style="width: 25%">
+							<label class="layui-form-label">序号:</label>
+							<div class="layui-input-block with-icon">
+								<form:input path="number" htmlEscape="false" placeholder="请输入序号"  maxlength="64"  class=" form-control  layui-input"/>
+							</div>
+						</div>
+						<div class="layui-item query athird" style="width: 25%">
+							<label class="layui-form-label">材料名称:</label>
+							<div class="layui-input-block">
+								<form:input path="projectName" htmlEscape="false" placeholder="请输入材料名称" maxlength="64"  class=" form-control  layui-input" />
+							</div>
+						</div>
+
+						<div class="input-group"  style="width: 25%">
+							<a href="#" id="moresee"><i class="glyphicon glyphicon-menu-down"></i></a>
+							<div class="layui-btn-group search-spacing">
+								<button id="searchQuery" class="layui-btn layui-btn-sm layui-bg-blue" onclick="search()">查询</button>
+								<button id="searchReset" class="layui-btn layui-btn-sm" onclick="resetSearch()">重置</button>
+							</div>
+						</div>
+						<div style="    clear:both;"></div>
+					</div>
+					<div id="moresees" style="clear:both;display:none;" class="lw6">
+
+
+
+						<%--<div class="layui-item query athird" style="width: 25%">
+							<label class="layui-form-label double-line" style="line-height:100%">工程价(元,不含税):</label>
+							<div class="layui-input-block">
+								<input id="beginProjectPriceExcludingTax" value="${projectSubstation.beginProjectPriceExcludingTax}" name="beginProjectPriceExcludingTax" placeholder="请输入工程价(元,不含税)" htmlEscape="false" maxlength="64"  class="query-group form-control  layui-input">
+								<span class="group-sep">-</span>
+								<input id="endProjectPriceExcludingTax" value="${projectSubstation.endProjectPriceExcludingTax}" name="endProjectPriceExcludingTax" placeholder="请输入工程价(元,不含税)" htmlEscape="false" maxlength="64"  class="query-group form-control  layui-input">
+							</div>
+						</div>--%>
+
+
+
+
+
+						<%--<div class="layui-item query athird " style="width: 25%">
+							<label class="layui-form-label">创建时间:</label>
+							<div class="layui-input-block readOnlyFFF">
+								<input id="beginDate" name="beginDate" placeholder="创建开始时间" type="text" readonly="readonly" maxlength="20" class="laydate-icondate form-control layer-date layui-input laydate-icon query-group"
+									   value="<fmt:formatDate value="${projectSubstation.beginDate}" pattern="yyyy-MM-dd"/>"/>
+								</input>
+                                <span class="group-sep">-</span>
+                                <input id="endDate" name="endDate" placeholder="创建结束时间" type="text" readonly="readonly" maxlength="20" class="laydate-icondate form-control layer-date layui-input laydate-icon query-group"
+                                       value="<fmt:formatDate value="${projectSubstation.endDate}" pattern="yyyy-MM-dd"/>"/>
+                                </input>
+							</div>
+						</div>--%>
+
+						<div style="clear:both;"></div>
+					</div>
+				</form:form>
+			</div>
+		</div>
+		<div class="full-width fl">
+			<div class="layui-form contentDetails contentShadow shadowLBR">
+				<div class="nav-btns">
+					<%--此处按钮样式包括 nav-btn-add nav-btn-refresh nav-btn-import nav-btn-export nav-btn-query nav-btn-reset--%>
+					<div class="layui-btn-group">
+						<table:addRow url="${ctx}/project/projectFinalAccounts/formTwoPage?isAdd=0" title="添加材料"></table:addRow><!-- 增加按钮 -->
+						<%--<shiro:hasPermission name="project:pojectMaterialsWarehouse:list">--%>
+							<button type="button" data-toggle="tooltip" data-placement="top" class="layui-btn layui-btn-sm layui-bg-red" id="delProjectFinalAccounts"> 批量删除</button>
+						<%--</shiro:hasPermission>--%>
+						<button class="layui-btn layui-btn-sm" data-toggle="tooltip" data-placement="left" onclick="sortOrRefresh()" title="刷新"> 刷新</button>
+						</div>
+					<%--<shiro:hasPermission name="project:projectSubstation:list">
+						<table:exportExcel url="${ctx}/project/projectSubstation/export"></table:exportExcel><!-- 导出按钮 -->
+					</shiro:hasPermission>--%>
+					<div style="clear: both;"></div>
+				</div>
+				<table class="oa-table layui-table" id="contentTable1"></table>
+
+				<!-- 分页代码 -->
+				<table:page page="${page}"></table:page>
+				<div style="clear: both;"></div>
+			</div>
+		</div>
+	</div>
+	<div id="changewidth"></div>
+</div>
+
+<script>
+
+    layui.use('table', function(){
+        layui.table.render({
+            limit:${ page.pageSize }
+			,id:"checkboxTable"
+            ,elem: '#contentTable1'
+            ,page: false
+            ,cols: [[
+				{checkbox: true, fixed: true},
+                {field:'index',align:'center', title: '序号',width:55}
+				,{field:'number',align:'center', title: '编号',  width:80}
+				/*,{field:'projectName',align:'center', title: '项目名称', minWidth:200,templet:function(d){
+						var xml="";
+						<shiro:hasPermission name="ruralProject:ruralProjectView:listAllView">
+						xml+="<a class=\"attention-info pid\" title=\"" + d.projectName + "\" href=\"javascript:void(0);\" onclick=\"openDialogView('查看项目', '${ctx}/ruralProject/ruralProjectView/view?id=" + d.projectId +"','95%', '95%')\">"
+						</shiro:hasPermission>
+						xml+=d.projectName
+						<shiro:hasPermission name="ruralProject:ruralProjectView:listAllView">
+						xml+="</a>";
+						</shiro:hasPermission>
+						return xml;
+					}}
+				,{field:'reportNumber',align:'center', title: '报告号',  width:180}*/
+				,{field:'projectOrCostName',align:'center', title: '项目或费用名称',  minWidth:200, templet:function(d){
+					return "<a class=\"attention-info\" title=\"" + d.projectOrCostName + "\" href=\"javascript:void(0);\" onclick=\"openDialogView('查看详情', '${ctx}/project/projectSubstation/view?id=" + d.id +"','95%', '95%')\">" + d.projectOrCostName + "</a>";
+				}}
+				,{field:'engineeringCost',align:'center', title: '其中:直接工程费',  width:150}
+				,{field:'laborCost',align:'center', title: '其中:人工费',  width:150}
+				,{field:'estimatedMaterialCost',align:'center', title: '其中:暂估价材料费',  width:150}
+				,{field:'totalCost',align:'center', title: '合计',  width:150}
+				,{field:'remarks',align:'center', title: '备注',  width:200}
+				,{field:'createBy',align:'center', title: '创建人',  width:80}
+                ,{field:'createDate',align:'center', title: '创建日期',  width:100}
+                ,{field:'op',align:'center',title:"操作",width:160,templet:function(d){
+                        ////对操作进行初始化
+                        var xml="<div class=\"layui-btn-group\">";
+						if((d.flag != undefined && d.flag =="1") || <%=admin%>){
+							xml+="<a href=\"#\" onclick=\"openDialogre('修改', '${ctx}/project/projectSubstation/formTwoPage?id=" + d.id+"&&isAdd=1" +"','95%', '95%','','提交,关闭')\" class=\"layui-btn layui-btn-xs layui-bg-green\" > 修改</a>";
+							xml+="<a href=\"${ctx}/project/projectSubstation/delete?id=" + d.id + "\" onclick=\"return confirmx('确认要删除该材料信息吗?', this.href)\" class=\"layui-btn layui-btn-xs layui-bg-red\"> 删除</a>";
+						}
+                        xml+="</div>"
+                        return xml;
+
+                    }}
+            ]]
+            ,data: [
+                <c:if test="${ not empty page.list}">
+                <c:forEach items="${page.list}" var="projectSubstation" varStatus="index">
+                <c:if test="${index.index != 0}">,</c:if>
+                {
+                    "index":"${index.index+1}"
+                    ,"id":"${projectSubstation.id}"
+					,"number":"${projectSubstation.number}"
+					,"engineeringCost":"${projectSubstation.engineeringCost}"
+					,"laborCost":"${projectSubstation.laborCost}"
+					,"estimatedMaterialCost":"${projectSubstation.estimatedMaterialCost}"
+					,"totalCost":"${projectSubstation.totalCost}"
+
+					,"projectOrCostName":"${projectSubstation.projectOrCostName}"
+					,"remarks":"${projectSubstation.remarks}"
+					,"createBy":"${projectSubstation.createBy.name}"
+					,"projectName":"${projectSubstation.projectName}"
+					,"reportNumber":"${projectSubstation.reportNumber}"
+					,"createDate":"<fmt:formatDate value="${projectSubstation.createDate}" pattern="yyyy-MM-dd"/>"
+					,"flag":
+							<c:choose>
+							<c:when test="${projectSubstation.createBy.id eq fns:getUser().id}">
+							"1"
+					</c:when>
+					<c:otherwise>
+					"0"
+					</c:otherwise>
+					</c:choose>
+                }
+                </c:forEach>
+                </c:if>
+            ]
+            // ,even: true
+            // ,height: 315
+        });
+		$("#delProjectFinalAccounts").bind("click",function () {
+			//获得表格CheckBox已经选中的行的信息
+			var checkList = layui.table.checkStatus('checkboxTable').data;
+			//定义数组存放批量删除的行的id
+			var listId = [];
+			//进行遍历所有选中行数据,拿出每一行的id存储到数组中
+			$.each(checkList, function (i, data) {
+				listId.push(data.id);
+			});
+			if (listId.length <= 0) {
+				layer.msg("请选择需要删除的信息", {icon: 2})
+			} else {
+				layer.confirm('是否删除选择的数据信息?', {
+					btn: ['确认', '取消']
+					,btn1: function(index, layero){
+						$.ajax({
+							type:"post",
+							url:"${ctx}/project/projectSubstation/deleteAll?listId="+ listId,
+							dataType:"json",
+							success:function(data){
+								layer.closeAll();//关闭对话框。
+								if(data.success) {
+									top.layer.msg(data.str, {icon: 1});
+									window.location.reload();
+								}else {
+									top.layer.msg(data.str, {icon: 0});
+								}
+							}
+						})
+					}
+					,btn2: function(index, layero){
+					}
+				});
+
+				return true;
+			}
+		});
+    })
+
+    resizeListTable();
+    $("a").on("click",addLinkVisied);
+</script>
+<script>
+    resizeListWindow2();
+    $(window).resize(function(){
+        resizeListWindow2();
+    });
+</script>
+</body>
+</html>

+ 156 - 0
src/main/webapp/webpage/modules/projectStatement/substation/projectSubstationTwoForm.jsp

@@ -0,0 +1,156 @@
+<%@ page contentType="text/html;charset=UTF-8" %>
+<%@ include file="/webpage/include/taglib.jsp"%>
+<html>
+<head>
+	<title>材料库</title>
+	<meta name="decorator" content="default"/>
+	<script type="text/javascript" src="${ctxStatic}/layui/layui.js"></script>
+	<link rel='stylesheet' type="text/css" href="${ctxStatic}/layui/css/layui.css"/>
+	<script src="${ctxStatic}/common/html/js/script.js"></script>
+	<style>
+		#contractTypeDoc-error{
+			top:80px;
+			left:0;
+		}
+		 /*超过5个汉字,调整label的长度,以下是配套的*/
+		 .layui-item .layui-form-label{
+			 width:90px;
+		 }
+		.form-group .layui-item .layui-input-block,
+		.query .layui-input-block {
+			margin-left: 116px;
+		}
+		#workInvoiceProjectRelationList td{
+			padding-left: 0px;
+			padding-right: 0px;
+		}
+	</style>
+	<script type="text/javascript">
+        var validateForm;
+        function doSubmit(obj){//回调函数,在编辑和保存动作时,供openDialog调用提交表单。
+            //debugger
+            if(validateForm.form()){
+				$("#inputForm").submit();
+                return true;
+            }else {
+				parent.layer.msg("信息未填写完整!", {icon: 5});
+			}
+            return false;
+        }
+
+        $(document).ready(function() {
+
+			layui.use(['form', 'layer'], function () {
+				var form = layui.form;
+			});
+            validateForm = $("#inputForm").validate({
+                submitHandler: function(form){
+                    loading('正在提交,请稍等...');
+                    form.submit();
+                },
+                errorContainer: "#messageBox",
+                errorPlacement: function(error, element) {
+
+                    $("#messageBox").text("输入有误,请先更正。");
+                    if (element.is(":checkbox")||element.is(":radio")||element.parent().is(".input-append")){
+                        error.appendTo(element.parent().parent());
+                    } else {
+                        error.insertAfter(element);
+                    }
+                }
+            });
+
+
+            /*--------------*/
+			$("#attachment_btn").click(function () {
+				$("#attachment_file").click();
+			});
+			laydate.render({
+				elem: '#quotedPriceDate', //目标元素。由于laydate.js封装了一个轻量级的选择器引擎,因此elem还允许你传入class、tag但必须按照这种方式 '#id .class'
+				type : 'date'
+				, trigger: 'click'
+			});
+        });
+
+		function newSetPNumber(obj,label,ids,isProject,details){
+			$("#projectNumber").val(obj)
+			$("#projectName").val(label)
+		}
+
+		function num(obj){
+
+			obj.value = obj.value.replace(/[^\d.]/g,""); //清除"数字"和"."以外的字符
+			obj.value = obj.value.replace(/^\./g,""); //验证第一个字符是数字
+			obj.value = obj.value.replace(/\.{2,}/g,"."); //只保留第一个, 清除多余的
+			obj.value = obj.value.replace(".","$#$").replace(/\./g,"").replace("$#$",".");
+			obj.value = obj.value.replace(/^(\-)*(\d+)\.(\d\d).*$/,'$1$2.$3'); //只能输入两个小数
+		}
+	</script>
+</head>
+<body>
+<div class="single-form">
+	<div class="container">
+		<form:form id="inputForm" modelAttribute="projectSubstationImport" action="${ctx}/project/projectSubstation/signleSave?isAdd=${isAdd}" method="post" class="layui-form">
+			<form:hidden path="id"/>
+			<form:hidden path="projectNumber"/>
+			<div class="form-group layui-row first">
+				<div class="form-group-label"><h2>材料详情</h2></div>
+				<div class="layui-item layui-col-sm6">
+					<label class="layui-form-label"><span class="require-item invoicetype">*</span>项目名称</label>
+					<div class="layui-input-block">
+						<sys:gridselectprojectmaterial url="${ctx}/project/projectFinalAccounts/selectproject" id="project" name="projectNames"  value="${projectSubstationImport.projectName}"  title="选择所属项目" labelName="projectName" cssStyle="background-color: #fff"
+													   labelValue="${projectSubstationImport.projectName}" cssClass="form-control required layui-input" fieldLabels="项目名称" fieldKeys="projectName" searchLabel="项目名称" searchKey="projectName"  ></sys:gridselectprojectmaterial>
+					</div>
+				</div>
+				<div class="layui-item layui-col-sm6">
+					<label class="layui-form-label"><span class="require-item invoicetype">*</span>编号</label>
+					<div class="layui-input-block">
+						<form:input id="number" path="number" htmlEscape="false"  placeholder="请输入编号"  class="form-control layui-input" required="true" maxlength="240"/>
+					</div>
+				</div>
+				<div class="layui-item layui-col-sm6">
+					<label class="layui-form-label"><span class="require-item invoicetype">*</span>材料名称</label>
+					<div class="layui-input-block">
+						<form:input id="projectOrCostName" path="projectOrCostName" htmlEscape="false"  placeholder="请输入材料名称"  class="form-control layui-input" required="true" maxlength="240"/>
+					</div>
+				</div>
+				<div class="layui-item layui-col-sm6">
+					<label class="layui-form-label double-line">其中:直接工程费</label>
+					<div class="layui-input-block">
+						<form:input id="engineeringCost" path="engineeringCost" htmlEscape="false"  placeholder="请输入直接工程费"  class="form-control layui-input" maxlength="240"/>
+					</div>
+				</div>
+				<div class="layui-item layui-col-sm6">
+					<label class="layui-form-label double-line">其中:人工费</label>
+					<div class="layui-input-block">
+						<form:input id="laborCost" path="laborCost" htmlEscape="false"  placeholder="请输入人工费"  class="form-control layui-input" maxlength="240"/>
+					</div>
+				</div>
+				<div class="layui-item layui-col-sm6">
+					<label class="layui-form-label double-line">其中:暂估价材料费</label>
+					<div class="layui-input-block">
+						<form:input id="estimatedMaterialCost" path="estimatedMaterialCost" htmlEscape="false"  placeholder="请输入暂估价材料费"  class="form-control layui-input" maxlength="240"/>
+					</div>
+				</div>
+				<div class="layui-item layui-col-sm6">
+					<label class="layui-form-label">合计金额</label>
+					<div class="layui-input-block">
+						<form:input id="totalCost" path="totalCost" htmlEscape="false"  placeholder="请输入合计金额"  class="form-control layui-input" maxlength="240"/>
+					</div>
+				</div>
+
+				<div class="layui-item layui-col-sm12  with-textarea">
+					<label class="layui-form-label">备注:</label>
+					<div class="layui-input-block">
+						<form:textarea id="remarks" path="remarks" htmlEscape="false" rows="4" placeholder="请输入备注"  class="form-control" maxlength="800"/>
+					</div>
+				</div>
+
+			</div>
+			<div class="form-group layui-row page-end"></div>
+		</form:form>
+	</div>
+</div>
+
+</body>
+</html>

+ 20 - 1
src/main/webapp/webpage/modules/signatureManagement/electronicSignature/directorApplicationAudit.jsp

@@ -34,6 +34,17 @@
         $(document).ready(function() {
 			layui.use(['form', 'layer'], function () {
 				var form = layui.form;
+				//复选框监听器
+				form.on('checkbox(chargeCriterionList)', function(data){
+					var ids = [];
+					var zhi=$("#otherDetails").val();
+					$("input[name='chargeCriterionList']:checked").each(function(i){
+						ids.push($(this).val());
+					})
+					if ($(this).prop("checked")==true){
+						zhi+=$(this).val()+";"
+					}
+				});
 			});
 			/*$("#area").on("change",function(){
 						var area = $("#area").val()
@@ -88,7 +99,7 @@
 <body>
 <div class="single-form">
 	<div class="container">
-		<form:form id="inputForm" modelAttribute="districtDirectorApplication" action="${ctx}/districtDirectorApplication/districtDirectorApplication/saveAudit" method="post" class="form-horizontal">
+		<form:form id="inputForm" modelAttribute="districtDirectorApplication" action="${ctx}/districtDirectorApplication/districtDirectorApplication/saveAudit" method="post" class="form-horizontal layui-form">
 			<form:hidden path="id"/>
 			<form:hidden path="home"/>
 			<form:hidden path="act.taskId"/>
@@ -101,6 +112,14 @@
 			<c:set var="status" value="${districtDirectorApplication.act.status}" />
 			<div class="form-group layui-row first lw9">
 				<div class="form-group-label"><h2>基础信息<span style="color: red;font-size: 14px"> (注:签章责任人一般为地区负责人)</span></h2></div>
+
+				<div class="layui-item layui-col-sm12" id="chargeCriterionList" >
+					<label class="layui-form-label"><span class="require-item">*</span>用章类型:</label>
+					<div class="layui-input-block">
+						<form:checkboxes path="applySealTypeList" name="applySealTypeList" disabled="true" lay-filter="sealTypeList" lay-skin="primary" itemLabel="label" itemValue="value" htmlEscape="true" items="${fns:getMainDictList('apply_seal_type')}" />
+					</div>
+				</div>
+
 				<div class="layui-item layui-col-sm6">
 					<label class="layui-form-label"><span class="require-item">*</span>负责区域:</label>
 					<div class="layui-input-block with-icon">

+ 32 - 1
src/main/webapp/webpage/modules/signatureManagement/electronicSignature/directorApplicationForm.jsp

@@ -4,6 +4,15 @@
 <head>
 	<title>签章责任人申请管理</title>
 	<meta name="decorator" content="default"/>
+	<script type="text/javascript" src="${ctxStatic}/layui/layui.js"></script>
+	<link rel='stylesheet' type="text/css" href="${ctxStatic}/layui/css/layui.css"/>
+	<script src="${ctxStatic}/common/html/js/script.js"></script>
+	<style type="text/css">
+		img {width: 50px; height: 50px;}
+	</style>
+	<script type="text/javascript" src="${ctxStatic}/ckeditor/ckeditor.js"></script>
+	<script type="text/javascript" src="${ctxStatic}/layui/layui.js"></script>
+	<link rel='stylesheet' type="text/css" href="${ctxStatic}/layui/css/layui.css"/>
 	<script type="text/javascript">
         var validateForm;
         function doSubmit(obj){//回调函数,在编辑和保存动作时,供openDialog调用提交表单。
@@ -31,6 +40,20 @@
 
             });
 
+			layui.use(['form', 'layer'], function () {
+				var form = layui.form;
+				//复选框监听器
+				form.on('checkbox(chargeCriterionList)', function(data){
+					var ids = [];
+					var zhi=$("#otherDetails").val();
+					$("input[name='chargeCriterionList']:checked").each(function(i){
+						ids.push($(this).val());
+					})
+					if ($(this).prop("checked")==true){
+						zhi+=$(this).val()+";"
+					}
+				});
+			});
         });
 
 		function setAreaValue(area){
@@ -58,10 +81,18 @@
 <body>
 <div class="single-form">
 	<div class="container">
-		<form:form id="inputForm" modelAttribute="districtDirectorApplication" action="${ctx}/districtDirectorApplication/districtDirectorApplication/save" method="post" class="form-horizontal">
+		<form:form id="inputForm" modelAttribute="districtDirectorApplication" action="${ctx}/districtDirectorApplication/districtDirectorApplication/save" method="post" class="form-horizontal layui-form">
 			<form:hidden path="id"/>
 			<div class="form-group layui-row first">
 				<div class="form-group-label"><h2>基础信息<span style="color: red;font-size: 14px"> (注:签章责任人一般为地区负责人)</span></h2></div>
+
+				<div class="layui-item layui-col-sm12" id="chargeCriterionList" >
+					<label class="layui-form-label"><span class="require-item">*</span>用章类型:</label>
+					<div class="layui-input-block">
+						<form:checkboxes path="applySealTypeList" name="applySealTypeList" lay-filter="sealTypeList" lay-skin="primary" itemLabel="label" itemValue="value" htmlEscape="true" items="${fns:getMainDictList('apply_seal_type')}" />
+					</div>
+				</div>
+
 				<div class="layui-item layui-col-sm6">
 					<label class="layui-form-label"><span class="require-item">*</span>负责区域:</label>
 					<div class="layui-input-block with-icon">

+ 34 - 1
src/main/webapp/webpage/modules/signatureManagement/electronicSignature/directorApplicationModify.jsp

@@ -4,6 +4,15 @@
 <head>
 	<title>地区负责人申请管理</title>
 	<meta name="decorator" content="default"/>
+	<script type="text/javascript" src="${ctxStatic}/layui/layui.js"></script>
+	<link rel='stylesheet' type="text/css" href="${ctxStatic}/layui/css/layui.css"/>
+	<script src="${ctxStatic}/common/html/js/script.js"></script>
+	<style type="text/css">
+		img {width: 50px; height: 50px;}
+	</style>
+	<script type="text/javascript" src="${ctxStatic}/ckeditor/ckeditor.js"></script>
+	<script type="text/javascript" src="${ctxStatic}/layui/layui.js"></script>
+	<link rel='stylesheet' type="text/css" href="${ctxStatic}/layui/css/layui.css"/>
 	<script type="text/javascript">
         var validateForm;
         function doSubmit(obj){//回调函数,在编辑和保存动作时,供openDialog调用提交表单。
@@ -64,6 +73,22 @@
 
             });
 
+
+			layui.use(['form', 'layer'], function () {
+				var form = layui.form;
+				//复选框监听器
+				form.on('checkbox(chargeCriterionList)', function(data){
+					var ids = [];
+					var zhi=$("#otherDetails").val();
+					$("input[name='chargeCriterionList']:checked").each(function(i){
+						ids.push($(this).val());
+					})
+					if ($(this).prop("checked")==true){
+						zhi+=$(this).val()+";"
+					}
+				});
+			});
+
         });
 
 	</script>
@@ -71,7 +96,7 @@
 <body>
 <div class="single-form">
 	<div class="container">
-		<form:form id="inputForm" modelAttribute="districtDirectorApplication" action="${ctx}/districtDirectorApplication/districtDirectorApplication/save" method="post" class="form-horizontal">
+		<form:form id="inputForm" modelAttribute="districtDirectorApplication" action="${ctx}/districtDirectorApplication/districtDirectorApplication/save" method="post" class="form-horizontal layui-form">
 			<form:hidden path="id"/>
 			<form:hidden path="home"/>
 			<form:hidden path="act.taskId"/>
@@ -83,6 +108,14 @@
 			<c:set var="status" value="${districtDirectorApplication.act.status}" />
 			<div class="form-group layui-row first lw9">
 				<div class="form-group-label"><h2>基础信息</h2></div>
+
+				<div class="layui-item layui-col-sm12" id="chargeCriterionList" >
+					<label class="layui-form-label"><span class="require-item">*</span>用章类型:</label>
+					<div class="layui-input-block">
+						<form:checkboxes path="applySealTypeList" name="applySealTypeList" lay-filter="sealTypeList" lay-skin="primary" itemLabel="label" itemValue="value" htmlEscape="true" items="${fns:getMainDictList('apply_seal_type')}" />
+					</div>
+				</div>
+
 				<div class="layui-item layui-col-sm6">
 					<label class="layui-form-label"><span class="require-item">*</span>负责区域:</label>
 					<div class="layui-input-block with-icon">

+ 5 - 0
src/main/webapp/webpage/modules/signatureManagement/electronicSignature/districtDirectorApplicationList.jsp

@@ -453,6 +453,10 @@
                         {
                             xml+="<a href=\"${ctx}/districtDirectorApplication/districtDirectorApplication/revoke?id=" + d.id + "&processInstanceId=" + d.procId + "&status="+d.status+"\" onclick=\"return confirmx('确认要撤回该地区申请审批吗?', this.href)\" class=\"layui-btn layui-btn-xs layui-bg-red\" > 撤回</a>";
                         }
+                        if(d.canedit5 != undefined && d.canedit5 =="1")
+                        {
+                            xml+="<a href=\"#\" onclick=\"openDialogre('变更调整申请', '${ctx}/districtDirectorApplication/districtDirectorApplication/form?id=" + d.id + "','95%', '95%','','送审,关闭')\" class=\"layui-btn layui-btn-xs layui-bg-green\" > 变更</a>";
+                        }
                         xml+="</div>"
                         return xml;
                 }}
@@ -476,6 +480,7 @@
                     ,"createName":"<c:out value="${districtDirectorApplication.createBy.name}" escapeXml="false"/>"
                     <c:choose><c:when test="${flag == '1' or fns:getUser().id == districtDirectorApplication.createBy.id}">
                     ,"candel":	<c:choose><c:when test="${('1' == flag && districtDirectorApplication.status == 5)}">"1"</c:when><c:otherwise>"0"</c:otherwise></c:choose>
+                    ,"canedit5":<c:choose><c:when test="${districtDirectorApplication.status == 5 && fns:getUser().id == districtDirectorApplication.createBy.id}">"1"</c:when><c:otherwise>"0"</c:otherwise></c:choose>
                     ,"canedit2":<c:choose><c:when test="${districtDirectorApplication.status == 4 && fns:getUser().id == districtDirectorApplication.createBy.id}">"1"</c:when><c:otherwise>"0"</c:otherwise></c:choose>
                     ,"canrecall":<c:choose><c:when test="${districtDirectorApplication.status == 3 && fns:getUser().id == districtDirectorApplication.createBy.id}">"1"</c:when><c:otherwise>"0"</c:otherwise></c:choose>
                     ,"cancancel":<c:choose><c:when test="${districtDirectorApplication.status == 2 && fns:getUser().id == districtDirectorApplication.createBy.id}">"1"</c:when><c:otherwise>"0"</c:otherwise></c:choose>

+ 10 - 4
src/main/webapp/webpage/modules/workreimbursement/treeForm/all/workReimbursementAllFormAdd.jsp

@@ -311,7 +311,6 @@
 
 
                                 var subsetId="workAccountList_"+ obj.id;
-                                console.log(1111)
                                 $("#workAccountList").append("<tr class='"+obj.id+"' style='display: none;'> <td colspan='16' style='padding: 0px;'>" +
                                     "<table style=\"width: 100%;padding: 0px;margin: 0px;\" class=\"table table-bordered table-condensed details\">" +
                                     "<tbody id='"+subsetId+"'>" +
@@ -358,6 +357,13 @@
                 });
             }
             //根据报销单信息去查询每个报销单下数电发票信息
+
+            // 1. 获取后端渲染的列表字符串(注意:不要加单引号,直接获取模板渲染结果)
+            var listStr = '${workReimbursement.workAccountListCount}';
+            console.log(listStr)
+            if(listStr<1){
+                addRow('#workAccountList', workAccountListRowIdx, workAccountListTpl,'',true)
+            }
         });
 
 
@@ -2389,9 +2395,9 @@
             </div>
             <div class="form-group layui-row">
                 <div class="form-group-label"><h2>报销详情<span style="color: red;font-size: 14px"> (可在报销单后新增数电发票xml格式的附件获取发票信息)</span></h2></div>
-                <div class="layui-item nav-btns">
+                <%--<div class="layui-item nav-btns">
                     <a class="nav-btn nav-btn-add" onclick="addRow('#workAccountList', workAccountListRowIdx, workAccountListTpl,'',true);workAccountListRowIdx = workAccountListRowIdx + 1;" title="新增"><i class="fa fa-plus"></i>&nbsp;新增报销单</a>
-                </div>
+                </div>--%>
                 <div class="layui-table-body layui-item layui-col-xs12 form-table-container"  style="padding:0px">
                     <table id="contentTable" class="table table-bordered table-condensed can-edit no-bottom-margin details tree_table">
                         <thead>
@@ -2593,7 +2599,7 @@
                 </td>
                 <td style="vertical-align: middle; text-align:center;" class="op-td" >
                     <span class="op-btn op-btn-add" onclick="addRowInfoForm(this, '#workAccountList{{idx}}')" title="添加"><i class="fa fa-plus"></i>&nbsp;新增</span>
-                    {{#delBtn}}<span class="op-btn op-btn-delete" onclick="delRowParentNew(this, '#workAccountList{{idx}}')" title="删除"><i class="glyphicon glyphicon-remove"></i>&nbsp;删除</span>{{/delBtn}}
+                    <%--{{#delBtn}}<span class="op-btn op-btn-delete" onclick="delRowParentNew(this, '#workAccountList{{idx}}')" title="删除"><i class="glyphicon glyphicon-remove"></i>&nbsp;删除</span>{{/delBtn}}--%>
                 </td>
                 </tr>//-->
                     </script>

+ 9 - 2
src/main/webapp/webpage/modules/workreimbursement/treeForm/all/workReimbursementAllModifyApply.jsp

@@ -385,6 +385,13 @@
                 });
             }
             //根据报销单信息去查询每个报销单下数电发票信息
+
+            // 1. 获取后端渲染的列表字符串(注意:不要加单引号,直接获取模板渲染结果)
+            var listStr = '${workReimbursement.workAccountListCount}';
+            console.log(listStr)
+            if(listStr<1){
+                addRow('#workAccountList', workAccountListRowIdx, workAccountListTpl,'',true)
+            }
         });
 
 
@@ -2375,9 +2382,9 @@
             </div>
             <div class="form-group layui-row">
                 <div class="form-group-label"><h2>报销详情<span style="color: red;font-size: 14px"> (可在报销单后新增数电发票xml格式的附件获取发票信息)</span></h2></div>
-                <div class="layui-item nav-btns">
+                <%--<div class="layui-item nav-btns">
                     <a class="nav-btn nav-btn-add" onclick="addRow('#workAccountList', workAccountListRowIdx, workAccountListTpl,'',true);workAccountListRowIdx = workAccountListRowIdx + 1;" title="新增"><i class="fa fa-plus"></i>&nbsp;新增报销单</a>
-                </div>
+                </div>--%>
                 <div class="layui-table-body layui-item layui-col-xs12 form-table-container"  style="padding:0px">
                     <table id="contentTable" class="table table-bordered table-condensed can-edit no-bottom-margin details tree_table">
                         <thead>

+ 10 - 3
src/main/webapp/webpage/modules/workreimbursement/treeForm/new/workReimbursementNewFormAdd.jsp

@@ -362,6 +362,13 @@
                 });
             }
             //根据报销单信息去查询每个报销单下数电发票信息
+
+            // 1. 获取后端渲染的列表字符串(注意:不要加单引号,直接获取模板渲染结果)
+            var listStr = '${workReimbursement.workAccountListCount}';
+            console.log(listStr)
+            if(listStr<1){
+                addRow('#workAccountList', workAccountListRowIdx, workAccountListTpl,'',true)
+            }
         });
 
 
@@ -2435,9 +2442,9 @@
         </div>
         <div class="form-group layui-row">
             <div class="form-group-label"><h2>报销详情<span style="color: red;font-size: 14px"> (可在报销单后新增数电发票xml格式的附件获取发票信息)</span></h2></div>
-            <div class="layui-item nav-btns">
+            <%--<div class="layui-item nav-btns">
                 <a class="nav-btn nav-btn-add" onclick="addRow('#workAccountList', workAccountListRowIdx, workAccountListTpl,'',true);workAccountListRowIdx = workAccountListRowIdx + 1;" title="新增"><i class="fa fa-plus"></i>&nbsp;新增报销单</a>
-            </div>
+            </div>--%>
             <div class="layui-table-body layui-item layui-col-xs12 form-table-container"  style="padding:0px">
                 <table id="contentTable" class="table table-bordered table-condensed can-edit no-bottom-margin details tree_table">
                     <thead>
@@ -2639,7 +2646,7 @@
                 </td>
                 <td style="vertical-align: middle; text-align:center;" class="op-td" >
                     <span class="op-btn op-btn-add" onclick="addRowInfoForm(this, '#workAccountList{{idx}}')" title="添加"><i class="fa fa-plus"></i>&nbsp;新增</span>
-                    {{#delBtn}}<span class="op-btn op-btn-delete" onclick="delRowParentNew(this, '#workAccountList{{idx}}')" title="删除"><i class="glyphicon glyphicon-remove"></i>&nbsp;删除</span>{{/delBtn}}
+                    <%--{{#delBtn}}<span class="op-btn op-btn-delete" onclick="delRowParentNew(this, '#workAccountList{{idx}}')" title="删除"><i class="glyphicon glyphicon-remove"></i>&nbsp;删除</span>{{/delBtn}}--%>
                 </td>
                 </tr>//-->
                 </script>

+ 10 - 2
src/main/webapp/webpage/modules/workreimbursement/treeForm/new/workReimbursementNewModifyApply.jsp

@@ -385,6 +385,14 @@
                 });
             }
             //根据报销单信息去查询每个报销单下数电发票信息
+
+
+            // 1. 获取后端渲染的列表字符串(注意:不要加单引号,直接获取模板渲染结果)
+            var listStr = '${workReimbursement.workAccountListCount}';
+            console.log(listStr)
+            if(listStr<1){
+                addRow('#workAccountList', workAccountListRowIdx, workAccountListTpl,'',true)
+            }
         });
 
 
@@ -2372,9 +2380,9 @@
             </div>
             <div class="form-group layui-row">
                 <div class="form-group-label"><h2>报销详情<span style="color: red;font-size: 14px"> (可在报销单后新增数电发票xml格式的附件获取发票信息)</span></h2></div>
-                <div class="layui-item nav-btns">
+                <%--<div class="layui-item nav-btns">
                     <a class="nav-btn nav-btn-add" onclick="addRow('#workAccountList', workAccountListRowIdx, workAccountListTpl,'',true);workAccountListRowIdx = workAccountListRowIdx + 1;" title="新增"><i class="fa fa-plus"></i>&nbsp;新增报销单</a>
-                </div>
+                </div>--%>
                 <div class="layui-table-body layui-item layui-col-xs12 form-table-container"  style="padding:0px">
                     <table id="contentTable" class="table table-bordered table-condensed can-edit no-bottom-margin details tree_table">
                         <thead>

+ 2 - 2
src/main/webapp/webpage/modules/workreimbursement/treeForm/specific/workReimbursementSpecificAudit.jsp

@@ -490,8 +490,8 @@
 					"<th style=\"text-align: center; background-color:#F5F5F5; width:100px;padding:0px\"><font color=\"red\">*</font>税额</th>"+
 					"<th style=\"text-align: center; background-color:#F5F5F5; width:100px;padding:0px\"><font color=\"red\">*</font>价税合计</th>"+
 					"<th style=\"text-align: center; background-color:#F5F5F5; width:150px;padding:0px\"><font color=\"red\">*</font>开票日期</th>"+
-					"<th style=\"text-align: center; background-color:#F5F5F5; width:200px;padding:0px\"><font color=\"red\">*</font>开票单位</th>"+
-					"<th style=\"text-align: center; background-color:#F5F5F5; width:300px; vertical-align: middle; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;;padding:0px\">文件预览</th>"+
+					"<th style=\"text-align: center; background-color:#F5F5F5; padding:0px\"><font color=\"red\">*</font>开票单位</th>"+
+					"<th style=\"text-align: center; background-color:#F5F5F5; vertical-align: middle; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;;padding:0px\">文件预览</th>"+
 					"<th style=\"text-align: center; background-color:#F5F5F5; width:80px;padding:0px\">上传人</th>"+
 					"<th style=\"text-align: center; background-color:#F5F5F5; width:100px;padding:0px\">上传时间</th>"+
 					// 嵌入服务器端生成的列(此时ticketConfirmTh是干净的<th>标签)