diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/BusBillofquantitiesVersionsController.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/BusBillofquantitiesVersionsController.java index b265a712..e681f537 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/BusBillofquantitiesVersionsController.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/BusBillofquantitiesVersionsController.java @@ -1,8 +1,15 @@ package org.dromara.design.controller; import cn.dev33.satoken.annotation.SaCheckPermission; +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.ExcelWriter; +import com.alibaba.excel.write.metadata.WriteSheet; +import com.alibaba.excel.write.metadata.style.WriteCellStyle; +import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; +import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; +import org.apache.poi.ss.usermodel.IndexedColors; import org.dromara.common.core.domain.R; import org.dromara.common.idempotent.annotation.RepeatSubmit; import org.dromara.common.log.annotation.Log; @@ -10,17 +17,17 @@ import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.web.core.BaseController; -import org.dromara.design.domain.bo.ImportExcelFileReq; -import org.dromara.design.domain.bo.ObtainAllVersionNumbersReq; -import org.dromara.design.domain.bo.CoryObtainTheListReq; -import org.dromara.design.domain.bo.SheetListReq; +import org.dromara.design.domain.bo.*; import org.dromara.design.domain.vo.*; +import org.dromara.design.exportUtil.bill.*; import org.dromara.design.service.IBusBillofquantitiesVersionsService; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import java.util.List; +import java.io.IOException; +import java.net.URLEncoder; +import java.util.*; /** * 工程量清单版本 @@ -103,6 +110,74 @@ public class BusBillofquantitiesVersionsController extends BaseController { return R.ok(busBillofquantitiesVersionsService.obtainAllClassification(bo)); } + /** + * 导出工程量清单版本列表 + */ + @Log(title = "工程量清单版本", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(String versions,Long projectId, HttpServletResponse response) throws IOException { + Map> sheetDataMap = busBillofquantitiesVersionsService.export(versions,projectId); + + if (sheetDataMap.isEmpty()) { + response.getWriter().write("无数据可导出"); + return; + } + + // 2. 设置响应头(同上) + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("UTF-8"); + String fileName = URLEncoder.encode( "工程量清单", "UTF-8").replaceAll("\\+", "%20"); + response.setHeader("Content-disposition", "attachment;filename*=UTF-8''" + fileName + ".xlsx"); + + // 3. 构建Excel写入器 + ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build(); + try { + // 4. 遍历每个Sheet分组,创建Sheet并写入数据 + for (Map.Entry> entry : sheetDataMap.entrySet()) { + String sheetName = entry.getKey(); + List dataList = entry.getValue(); + + WriteCellStyle headWriteCellStyle = new WriteCellStyle(); + // 背景设置为红色 + headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex()); + + HorizontalCellStyleStrategy horizontalCellStyleStrategy = + new HorizontalCellStyleStrategy(headWriteCellStyle, new WriteCellStyle()); + + // 定义Sheet(同时注册标题处理器和冻结窗格处理器) + WriteSheet writeSheet = EasyExcel.writerSheet(sheetName) + .registerWriteHandler(new FreezePaneWriteHandler()) // 冻结前2行 + .registerWriteHandler(new ColumnWidthWriteHandler()) // 调整列宽 + .registerWriteHandler(new CustomRowStyleHandler()) // 新增:边框和标题样式 + .registerWriteHandler(horizontalCellStyleStrategy) + .head(head(sheetName)) + .build(); + + // 写入数据 + excelWriter.write(dataList, writeSheet); + } + } finally { + excelWriter.finish(); + } + } + + + private List> head(String sheetName) { + List> list = new ArrayList>(); + List list1 = Arrays.asList(sheetName,"编号"); + List list2 = Arrays.asList(sheetName, "名称"); + List list3 = Arrays.asList(sheetName, "规格"); + List list4 = Arrays.asList(sheetName, "单位"); + List list5 = Arrays.asList(sheetName, "数量"); + List list6 = Arrays.asList(sheetName, "备注"); + list.add(list1); + list.add(list2); + list.add(list3); + list.add(list4); + list.add(list5); + list.add(list6); + return list; + } // /** // * 导入物资设备清单 diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/DesCollectFileController.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/DesCollectFileController.java index 62b083d9..d81700be 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/DesCollectFileController.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/DesCollectFileController.java @@ -7,6 +7,7 @@ import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.*; import cn.dev33.satoken.annotation.SaCheckPermission; import org.dromara.design.domain.bo.DesCollectFileBo; +import org.dromara.design.domain.dto.ExportDto; import org.dromara.design.domain.vo.DesCollectFileVo; import org.dromara.design.service.IDesCollectFileService; import org.springframework.web.bind.annotation.*; @@ -117,4 +118,11 @@ public class DesCollectFileController extends BaseController { @NotNull(message = "请先选择项目")Long projectId) { return toAjax(desCollectFileService.addFile(file, catalogueId, projectId)); } + + + + @PostMapping("/exportZip") + public void exportZip(ExportDto dto, HttpServletResponse response) throws Exception { + desCollectFileService.exportAsZip(dto, response); + } } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/DesConstructionSchedulePlanController.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/DesConstructionSchedulePlanController.java index 66c47ffa..9388df81 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/DesConstructionSchedulePlanController.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/DesConstructionSchedulePlanController.java @@ -22,6 +22,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.util.List; /** @@ -143,4 +144,10 @@ public class DesConstructionSchedulePlanController extends BaseController { @PathVariable Long[] ids) { return toAjax(desConstructionSchedulePlanService.deleteByIds(List.of(ids))); } + + @PostMapping("/exportSchedule") + public void exportSchedule(HttpServletResponse response, Long projectId) throws IOException { + desConstructionSchedulePlanService.exportSchedule(response,projectId); + } + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/DesPrelimSchemeController.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/DesPrelimSchemeController.java index bb0447a3..47a8d198 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/DesPrelimSchemeController.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/DesPrelimSchemeController.java @@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.*; import cn.dev33.satoken.annotation.SaCheckPermission; +import org.dromara.design.domain.dto.ExportDto; import org.springframework.web.bind.annotation.*; import org.springframework.validation.annotation.Validated; import org.dromara.common.idempotent.annotation.RepeatSubmit; @@ -128,4 +129,9 @@ public class DesPrelimSchemeController extends BaseController { return toAjax(desPrelimSchemeService.updateFile(file, projectId,id)); } + @PostMapping("/exportZipWithStatus") + public void exportZipWithStatus(ExportDto dto, HttpServletResponse response) throws Exception { + desPrelimSchemeService.exportAsZipWithStatusPrefix(dto, response); + } + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/DesSchemeController.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/DesSchemeController.java index c39485ac..56108ec9 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/DesSchemeController.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/controller/DesSchemeController.java @@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.*; import cn.dev33.satoken.annotation.SaCheckPermission; +import org.dromara.design.domain.dto.ExportDto; import org.springframework.web.bind.annotation.*; import org.springframework.validation.annotation.Validated; import org.dromara.common.idempotent.annotation.RepeatSubmit; @@ -126,4 +127,10 @@ public class DesSchemeController extends BaseController { public R updateFile(MultipartFile file, Long projectId, @NotNull(message = "主键不能为空")@PathVariable Long id) { return toAjax(desSchemeService.updateFile(file, projectId,id)); } + + + @PostMapping("/exportZipWithStatus") + public void exportZipWithStatus(ExportDto dto, HttpServletResponse response) throws Exception { + desSchemeService.exportAsZipWithStatusPrefix(dto, response); + } } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/dto/ExportDto.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/dto/ExportDto.java new file mode 100644 index 00000000..75fea794 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/dto/ExportDto.java @@ -0,0 +1,11 @@ +package org.dromara.design.domain.dto; + +import lombok.Data; + +import java.util.List; + +@Data +public class ExportDto { + private Long projectId; + private List ids; +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/vo/DesSubcontractVo.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/vo/DesSubcontractVo.java index 57adee17..d3fc2f35 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/vo/DesSubcontractVo.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/domain/vo/DesSubcontractVo.java @@ -1,5 +1,7 @@ package org.dromara.design.domain.vo; +import com.alibaba.excel.annotation.ExcelIgnore; +import com.alibaba.excel.annotation.format.DateTimeFormat; import org.dromara.design.domain.DesSubcontract; import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; import com.alibaba.excel.annotation.ExcelProperty; @@ -31,13 +33,13 @@ public class DesSubcontractVo implements Serializable { /** * 主键ID */ - @ExcelProperty(value = "主键ID") + @ExcelIgnore private Long id; /** * 项目id */ - @ExcelProperty(value = "项目id") + @ExcelIgnore private Long projectId; /** @@ -49,7 +51,13 @@ public class DesSubcontractVo implements Serializable { /** * 分包要求 */ + @ExcelProperty(value = "说明") private String requirement; + /** + * 创建时间 + */ + @DateTimeFormat("yyyy-MM-dd") + @ExcelProperty(value = "创建时间") private Date createTime; } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/bill/BillOfQuantitiesExport.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/bill/BillOfQuantitiesExport.java new file mode 100644 index 00000000..428e4221 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/bill/BillOfQuantitiesExport.java @@ -0,0 +1,33 @@ +package org.dromara.design.exportUtil.bill; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.format.NumberFormat; +import com.alibaba.excel.annotation.write.style.HeadStyle; +import com.alibaba.excel.enums.poi.FillPatternTypeEnum; +import lombok.Data; + +import java.math.BigDecimal; + +@Data +@HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 9) +public class BillOfQuantitiesExport { + @ExcelProperty(value = "编号", index = 0) + private String num; + + @ExcelProperty(value = "名称及规格", index = 1) + private String name; + + @ExcelProperty(value = "规格", index = 2) + private String specification; + + @ExcelProperty(value = "单位", index = 3) + @NumberFormat("#") // 若单位为文本,可去掉此注解 + private String unit; + + @ExcelProperty(value = "数量", index = 4) + @NumberFormat("#,##0.00") // 数量格式化(保留两位小数) + private BigDecimal quantity; + + @ExcelProperty(value = "备注", index = 5) + private String remark; +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/bill/ColumnWidthWriteHandler.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/bill/ColumnWidthWriteHandler.java new file mode 100644 index 00000000..595e07dd --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/bill/ColumnWidthWriteHandler.java @@ -0,0 +1,23 @@ +package org.dromara.design.exportUtil.bill; + +import com.alibaba.excel.write.handler.SheetWriteHandler; +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; +import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder; +import org.apache.poi.ss.usermodel.Sheet; + +/** + * 调整指定列的宽度(第二列和第五列) + */ +public class ColumnWidthWriteHandler implements SheetWriteHandler { + + @Override + public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { + Sheet sheet = writeSheetHolder.getSheet(); + + // 设置列宽(单位:字符数,1字符≈256个单位,这里按实际需求调整) + // 第二列(索引1):例如设置宽度为30个字符 + sheet.setColumnWidth(1, 30 * 256); + // 第五列(索引4):例如设置宽度为15个字符 + sheet.setColumnWidth(4, 15 * 256); + } +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/bill/CustomRowStyleHandler.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/bill/CustomRowStyleHandler.java new file mode 100644 index 00000000..94934a0c --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/bill/CustomRowStyleHandler.java @@ -0,0 +1,51 @@ +package org.dromara.design.exportUtil.bill; + +import com.alibaba.excel.write.handler.RowWriteHandler; +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; +import com.alibaba.excel.write.metadata.holder.WriteTableHolder; +import org.apache.poi.ss.usermodel.*; + +/** + * 行样式处理器: + * 1. 第二行(索引1)及以后所有行添加边框 + * 2. 第二行(索引1)的第二列(索引1)标题栏加粗加大 + */ +import com.alibaba.excel.write.handler.RowWriteHandler; +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; +import com.alibaba.excel.write.metadata.holder.WriteTableHolder; +import org.apache.poi.ss.usermodel.*; + +public class CustomRowStyleHandler implements RowWriteHandler { + + @Override + public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) { + // 关键修改:通过 writeSheetHolder 获取 Workbook(兼容所有版本) + Workbook workbook = writeSheetHolder.getParentWriteWorkbookHolder().getWorkbook(); + int rowIndex = row.getRowNum(); + + // 1. 第二行(索引1)及以后的行添加边框 + if (rowIndex >= 1) { + CellStyle borderStyle = ExcelStyleUtils.createBorderStyle(workbook); + + for (int i = 0; i <= 5; i++) { + Cell cell = row.getCell(i); + if (cell == null) { + cell = row.createCell(i); + } + cell.setCellStyle(borderStyle); + } + + // 2. 第二行(索引1)的第二列(索引1)标题栏加粗加大 + if (rowIndex == 1) { + for (int i = 0; i <= 5; i++) { + Cell secondColumnCell = row.getCell(i); + if (secondColumnCell != null) { + CellStyle headerStyle = ExcelStyleUtils.createSecondColumnHeaderStyle(workbook); + secondColumnCell.setCellStyle(headerStyle); + } + } + + } + } + } +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/bill/ExcelStyleUtils.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/bill/ExcelStyleUtils.java new file mode 100644 index 00000000..6c4c8520 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/bill/ExcelStyleUtils.java @@ -0,0 +1,57 @@ +package org.dromara.design.exportUtil.bill; + +import org.apache.poi.ss.usermodel.*; + +/** + * 样式工具类(定义边框、字体等样式) + */ +public class ExcelStyleUtils { + // 边框样式(细实线) + public static final BorderStyle BORDER_STYLE = BorderStyle.THIN; + // 边框颜色(黑色) + public static final short BORDER_COLOR = IndexedColors.BLACK.getIndex(); + + /** + * 创建带边框的单元格样式 + */ + public static CellStyle createBorderStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + // 上下左右边框 + style.setBorderTop(BORDER_STYLE); + style.setBorderBottom(BORDER_STYLE); + style.setBorderLeft(BORDER_STYLE); + style.setBorderRight(BORDER_STYLE); + // 边框颜色 + style.setTopBorderColor(BORDER_COLOR); + style.setBottomBorderColor(BORDER_COLOR); + style.setLeftBorderColor(BORDER_COLOR); + style.setRightBorderColor(BORDER_COLOR); + // 单元格内容居中(可选) + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + return style; + } + + /** + * 创建第二列标题栏样式(加粗、字号加大) + */ + public static CellStyle createSecondColumnHeaderStyle(Workbook workbook) { + CellStyle style = createBorderStyle(workbook); // 继承边框样式 + Font font = workbook.createFont(); + font.setBold(true); // 加粗 + font.setFontHeightInPoints((short) 12); // 字号加大(默认11,这里设12) + style.setFont(font); + return style; + } + + /** + * 创建第一行(合并标题行)的白色背景样式 + */ + public static CellStyle createFirstRowWhiteStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + // 设置白色背景 + style.setFillForegroundColor(IndexedColors.WHITE.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); // 纯色填充 + return style; + } +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/bill/FreezePaneWriteHandler.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/bill/FreezePaneWriteHandler.java new file mode 100644 index 00000000..d5ecbc7d --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/bill/FreezePaneWriteHandler.java @@ -0,0 +1,20 @@ +package org.dromara.design.exportUtil.bill; + +import com.alibaba.excel.write.handler.SheetWriteHandler; +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; +import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder; +import org.apache.poi.ss.usermodel.Sheet; + +/** + * 冻结前3行的处理器(滚动时前3行固定) + */ +public class FreezePaneWriteHandler implements SheetWriteHandler { + + @Override + public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { + Sheet sheet = writeSheetHolder.getSheet(); + // 参数说明:冻结0列,冻结2行,从第0列、第2行开始滚动 + // 前2行(索引0和1)将固定在顶部 + sheet.createFreezePane(0, 2, 0, 2); + } +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/bill/SheetHeaderWriteHandler.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/bill/SheetHeaderWriteHandler.java new file mode 100644 index 00000000..12fb55dd --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/bill/SheetHeaderWriteHandler.java @@ -0,0 +1,41 @@ +package org.dromara.design.exportUtil.bill; + +import com.alibaba.excel.write.handler.SheetWriteHandler; +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; +import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.util.CellRangeAddress; + +public class SheetHeaderWriteHandler implements SheetWriteHandler { + + private final String sheetName; + + public SheetHeaderWriteHandler( String sheetName) { + this.sheetName = sheetName; + } + + @Override + public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { + Sheet sheet = writeSheetHolder.getSheet(); + Workbook workbook = writeWorkbookHolder.getWorkbook(); + + // 第一行(索引0):合并标题 + 白色背景 + Row titleRow = sheet.createRow(0); + Cell titleCell = titleRow.createCell(0); + titleCell.setCellValue(sheetName); + // 应用白色背景样式 + titleCell.setCellStyle(ExcelStyleUtils.createFirstRowWhiteStyle(workbook)); + // 合并第一行的0-5列 + sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 5)); + + // 第2行(索引1):字段标题栏(手动指定,覆盖实体类默认表头) + Row headerRow = sheet.createRow(1); + String[] headers = {"编号", "名称", "规格", "单位", "数量", "备注"}; + for (int i = 0; i < headers.length; i++) { + headerRow.createCell(i).setCellValue(headers[i]); + } + } +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/plan/ConstructionScheduleExport.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/plan/ConstructionScheduleExport.java new file mode 100644 index 00000000..4f37f35d --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/plan/ConstructionScheduleExport.java @@ -0,0 +1,48 @@ +package org.dromara.design.exportUtil.plan; + +import com.alibaba.excel.annotation.ExcelIgnore; +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.format.DateTimeFormat; +import com.alibaba.excel.converters.localdate.LocalDateStringConverter; +import lombok.Data; +import org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.excel.convert.ExcelDictConvert; + +import java.time.LocalDate; + +@Data +public class ConstructionScheduleExport { + @ExcelProperty(value = "编号", index = 0) + private String levelCode; // 层级编号(如1、1.1、1.1.1) + + @ExcelProperty(value = "节点名称", index = 1) + private String nodeName; + + + @DateTimeFormat("yyyy-MM-dd") + @ExcelProperty(value = "预计开始时间", index = 2) + private LocalDate planStartDate; + + @DateTimeFormat("yyyy-MM-dd") + @ExcelProperty(value = "预计结束时间", index = 3) + private LocalDate planEndDate; + + @DateTimeFormat("yyyy-MM-dd") + @ExcelProperty(value = "实际开始时间", index = 4) + private LocalDate practicalStartDate; + + @DateTimeFormat("yyyy-MM-dd") + @ExcelProperty(value = "实际结束时间", index = 5) + private LocalDate practicalEndDate; + + @ExcelProperty(value = "状态", index = 6,converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "project_construction_status") + private String status; + + // 用于构建层级关系的临时字段(不导出) + @ExcelIgnore + private Long id; + + @ExcelIgnore + private Long parentId; +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/plan/ScheduleHeaderWriteHandler.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/plan/ScheduleHeaderWriteHandler.java new file mode 100644 index 00000000..d29226ed --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/exportUtil/plan/ScheduleHeaderWriteHandler.java @@ -0,0 +1,25 @@ +package org.dromara.design.exportUtil.plan; + +import com.alibaba.excel.write.handler.SheetWriteHandler; +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; +import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.util.CellRangeAddress; + +public class ScheduleHeaderWriteHandler implements SheetWriteHandler { + @Override + public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { + Sheet sheet = writeSheetHolder.getSheet(); + + // 第一行直接作为标题栏 + Row titleRow = sheet.createRow(0); + String[] titles = {"编号", "节点名称", "预计开始时间", "预计结束时间", "实际开始时间", "实际结束时间", "状态"}; + for (int i = 0; i < titles.length; i++) { + Cell cell = titleRow.createCell(i); + cell.setCellValue(titles[i]); + } + } +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IBusBillofquantitiesVersionsService.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IBusBillofquantitiesVersionsService.java index 16ab69a4..5fc14edd 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IBusBillofquantitiesVersionsService.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IBusBillofquantitiesVersionsService.java @@ -3,14 +3,15 @@ package org.dromara.design.service; import com.baomidou.mybatisplus.extension.service.IService; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; -import org.dromara.design.domain.BusBillofquantities; import org.dromara.design.domain.BusBillofquantitiesVersions; import org.dromara.design.domain.bo.*; import org.dromara.design.domain.vo.*; +import org.dromara.design.exportUtil.bill.BillOfQuantitiesExport; import org.springframework.web.multipart.MultipartFile; import java.util.Collection; import java.util.List; +import java.util.Map; /** * 工程量清单版本Service接口 @@ -100,4 +101,7 @@ public interface IBusBillofquantitiesVersionsService extends IService queryMaterialTotalListByProject(Long projectId); List obtainAllClassification(ObtainAllVersionNumbersReq bo); + + + Map> export(String versions,Long projectId); } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IDesCollectFileService.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IDesCollectFileService.java index 6feb8f03..5f90a310 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IDesCollectFileService.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IDesCollectFileService.java @@ -1,12 +1,14 @@ package org.dromara.design.service; +import jakarta.servlet.http.HttpServletResponse; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.mybatis.core.page.PageQuery; import com.baomidou.mybatisplus.extension.service.IService; import org.dromara.design.domain.DesCollectFile; import org.dromara.design.domain.bo.DesCollectFileBo; +import org.dromara.design.domain.dto.ExportDto; import org.dromara.design.domain.vo.DesCollectFileVo; import org.springframework.web.multipart.MultipartFile; @@ -79,5 +81,6 @@ public interface IDesCollectFileService extends IService{ Boolean addFile(MultipartFile file, Long catalogueId, Long projectId); + void exportAsZip(ExportDto dto, HttpServletResponse response) throws Exception; } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IDesConstructionSchedulePlanService.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IDesConstructionSchedulePlanService.java index 0c95b991..23f855c1 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IDesConstructionSchedulePlanService.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IDesConstructionSchedulePlanService.java @@ -8,6 +8,7 @@ import org.dromara.design.domain.dto.constructionscheduleplan.*; import org.dromara.design.domain.vo.DesConstructionSchedulePlanVo; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.util.Collection; import java.util.List; @@ -114,4 +115,7 @@ public interface IDesConstructionSchedulePlanService extends IService convertToEntities(List excelList); + + + void exportSchedule(HttpServletResponse response, Long projectId) throws IOException; } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IDesPrelimSchemeService.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IDesPrelimSchemeService.java index 8c3abbe2..c568c6b9 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IDesPrelimSchemeService.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IDesPrelimSchemeService.java @@ -1,6 +1,7 @@ package org.dromara.design.service; -import jakarta.validation.constraints.NotNull; +import jakarta.servlet.http.HttpServletResponse; +import org.dromara.design.domain.dto.ExportDto; import org.dromara.design.domain.vo.DesPrelimSchemeVo; import org.dromara.design.domain.bo.DesPrelimSchemeBo; import org.dromara.design.domain.DesPrelimScheme; @@ -8,7 +9,6 @@ import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.mybatis.core.page.PageQuery; import com.baomidou.mybatisplus.extension.service.IService; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.multipart.MultipartFile; import java.util.Collection; @@ -81,4 +81,7 @@ public interface IDesPrelimSchemeService extends IService{ * 修改文件 */ Boolean updateFile(MultipartFile file, Long projectId, Long id); + + + void exportAsZipWithStatusPrefix(ExportDto dto, HttpServletResponse response) throws Exception; } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IDesSchemeService.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IDesSchemeService.java index 32748bf1..c0d332b5 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IDesSchemeService.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/IDesSchemeService.java @@ -1,7 +1,9 @@ package org.dromara.design.service; +import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.NotNull; import org.dromara.common.core.domain.R; +import org.dromara.design.domain.dto.ExportDto; import org.dromara.design.domain.vo.DesSchemeVo; import org.dromara.design.domain.bo.DesSchemeBo; import org.dromara.design.domain.DesScheme; @@ -82,4 +84,7 @@ public interface IDesSchemeService extends IService{ * 修改文件 */ Boolean updateFile(MultipartFile file, Long projectId, Long id); + + + void exportAsZipWithStatusPrefix(ExportDto dto, HttpServletResponse response) throws Exception; } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/BusBillofquantitiesVersionsServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/BusBillofquantitiesVersionsServiceImpl.java index 10bb90ba..c61158c2 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/BusBillofquantitiesVersionsServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/BusBillofquantitiesVersionsServiceImpl.java @@ -2,7 +2,7 @@ package org.dromara.design.service.impl; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; -import com.alibaba.fastjson2.util.BeanUtils; +import cn.hutool.core.collection.CollectionUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -28,12 +28,11 @@ import org.dromara.common.excel.coryUtils.ExcelReader; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.utils.BatchNumberGenerator; -import org.dromara.common.utils.excel.ExcelDynamicReader; import org.dromara.design.domain.BusBillofquantities; import org.dromara.design.domain.BusBillofquantitiesVersions; import org.dromara.design.domain.bo.*; -import org.dromara.design.domain.dto.MaterialsAndEquipmentExcelDto; import org.dromara.design.domain.vo.*; +import org.dromara.design.exportUtil.bill.BillOfQuantitiesExport; import org.dromara.design.mapper.BusBillofquantitiesVersionsMapper; import org.dromara.design.service.IBusBillofquantitiesService; import org.dromara.design.service.IBusBillofquantitiesVersionsService; @@ -363,6 +362,34 @@ public class BusBillofquantitiesVersionsServiceImpl extends ServiceImpl> export(String versions,Long projectId) { + // 1. 从数据库查询指定版本的所有数据 + List dbList = busBillofquantitiesService.list(Wrappers.lambdaQuery() + .eq(BusBillofquantities::getVersions, versions) + .eq(BusBillofquantities::getProjectId, projectId) + ); + if (CollectionUtil.isEmpty(dbList)) { + return Collections.emptyMap(); + } + + // 2. 转换为导出实体并按sheet分组 + Map> sheetMap = new HashMap<>(); + for (BusBillofquantities dbItem : dbList) { + BillOfQuantitiesExport exportItem = new BillOfQuantitiesExport(); + BeanUtil.copyProperties(dbItem, exportItem); + // 处理编号、名称等字段(若需格式化可在此补充) + exportItem.setNum(dbItem.getNum()); + exportItem.setName(dbItem.getName()); + + // 按sheet分组 + String sheetName = dbItem.getSheet(); + sheetMap.computeIfAbsent(sheetName, k -> new ArrayList<>()).add(exportItem); + } + return sheetMap; + } + /** * 递归构建树形结构 * diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/DesCollectFileServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/DesCollectFileServiceImpl.java index b3f78a4d..26a6c23b 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/DesCollectFileServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/DesCollectFileServiceImpl.java @@ -2,8 +2,11 @@ package org.dromara.design.service.impl; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.convert.Convert; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FileUtils; import org.dromara.common.core.domain.event.ProcessDeleteEvent; import org.dromara.common.core.domain.event.ProcessEvent; import org.dromara.common.core.domain.event.ProcessTaskEvent; @@ -18,10 +21,13 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import org.dromara.design.domain.DesCollect; +import org.dromara.design.domain.DesCollectCatalogue; import org.dromara.design.domain.DesCollectFile; import org.dromara.design.domain.bo.DesCollectFileBo; +import org.dromara.design.domain.dto.ExportDto; import org.dromara.design.domain.vo.DesCollectFileVo; import org.dromara.design.mapper.DesCollectFileMapper; +import org.dromara.design.service.IDesCollectCatalogueService; import org.dromara.design.service.IDesCollectFileService; import org.dromara.system.domain.vo.SysOssUploadVo; import org.dromara.system.domain.vo.SysOssVo; @@ -31,10 +37,22 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Collection; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.time.Duration; +import java.util.*; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import static org.dromara.common.constant.MinioPathConstant.ContactNoticeTemplate; @@ -53,6 +71,8 @@ public class DesCollectFileServiceImpl extends ServiceImpl files = baseMapper.selectList(Wrappers.lambdaQuery() + .eq(DesCollectFile::getProjectId, dto.getProjectId()) + .in(CollectionUtil.isNotEmpty(dto.getIds()),DesCollectFile::getId, dto.getIds()) + ); + if (files.isEmpty()) { + throw new RuntimeException("没有可导出的收资文件"); + } + + // 2. 提取所有catalogueId,查询对应的目录名称 + List catalogueIds = files.stream() + .map(DesCollectFile::getCatalogueId) + .distinct() + .collect(Collectors.toList()); + Map catalogueNameMap = getCatalogueNames(catalogueIds); // 获取名称映射 + + // 3. 按catalogueId分组 + Map> catalogueGroup = files.stream() + .collect(Collectors.groupingBy(DesCollectFile::getCatalogueId)); + + // 4. 创建临时根目录 + File tempRootDir = File.createTempFile("collect_file_", "_temp"); + if (!tempRootDir.delete() || !tempRootDir.mkdirs()) { + throw new RuntimeException("创建临时根目录失败"); + } + + try { + // 5. 按分组创建文件夹(用目录名称)并下载文件 + for (Map.Entry> entry : catalogueGroup.entrySet()) { + Long catalogueId = entry.getKey(); + List fileList = entry.getValue(); + + // 获取目录名称(若查询不到,用catalogueId作为默认名称) + String catalogueName = catalogueNameMap.getOrDefault(catalogueId, String.valueOf(catalogueId)); + // 处理名称中的特殊字符(避免创建文件夹失败) + String safeCatalogueName = catalogueName.replaceAll("[\\\\/:*?\"<>|]", "_"); + + // 创建以目录名称命名的文件夹 + File catalogueDir = new File(tempRootDir, safeCatalogueName); + if (!catalogueDir.exists() && !catalogueDir.mkdirs()) { + throw new RuntimeException("创建文件夹[" + safeCatalogueName + "]失败"); + } + + // 下载文件(命名格式:审核状态-原文件名) + for (DesCollectFile file : fileList) { + String newFileName = BusinessStatusEnum.getByStatus(file.getStatus()).getDesc() + "-" + file.getFileName(); + File destFile = new File(catalogueDir, newFileName); + downloadFile(file.getFileUrl(), destFile); // 复用之前的下载方法 + } + } + + // 6. 压缩并导出(逻辑不变) + response.setContentType("application/zip"); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + String zipFileName = URLEncoder.encode("收资文件汇总", StandardCharsets.UTF_8.name()) + ".zip"; + response.setHeader("Content-Disposition", "attachment;filename*=UTF-8''" + zipFileName); + + try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) { + zipDirectory(tempRootDir, tempRootDir.getName(), zos); + } + + } finally { + FileUtils.deleteQuietly(tempRootDir); + } + } + + /** + * 查询catalogueId对应的目录名称(从数据库获取) + * @param catalogueIds 多个catalogueId + * @return key: catalogueId, value: 目录名称 + */ + public Map getCatalogueNames(List catalogueIds) { + if (catalogueIds.isEmpty()) { + return Collections.emptyMap(); + } + // 实际实现:从目录表查询,例如 + List desCollectCatalogues = collectCatalogueService.listByIds(catalogueIds); + return desCollectCatalogues.stream() + .collect(Collectors.toMap(DesCollectCatalogue::getId, DesCollectCatalogue::getCatalogueName)); + } + + /** + * 下载文件(Java 21兼容,使用HttpClient替代URL) + * @param fileUrl 文件URL(远程地址或本地路径) + * @param destFile 目标文件 + */ + private void downloadFile(String fileUrl, File destFile) throws Exception { + + + // 远程URL文件,使用HttpClient下载 + HttpClient client = HttpClient.newBuilder() + .connectTimeout(Duration.ofMinutes(5)) + .build(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(fileUrl)) + .timeout(Duration.ofMinutes(10)) + .GET() + .build(); + HttpResponse response = client.send( + request, + HttpResponse.BodyHandlers.ofFile(destFile.toPath()) + ); + if (response.statusCode() != 200) { + throw new RuntimeException("文件下载失败:" + fileUrl + ",状态码:" + response.statusCode()); + } + } + + /** + * 递归压缩目录到ZIP流 + * @param sourceDir 源目录 + * @param baseName ZIP内的基础路径(避免包含系统临时目录路径) + * @param zos ZIP输出流 + */ + private void zipDirectory(File sourceDir, String baseName, ZipOutputStream zos) throws IOException { + File[] files = sourceDir.listFiles(); + if (files == null) return; + + for (File file : files) { + String entryName = baseName + File.separator + file.getName(); + if (file.isDirectory()) { + // 递归压缩子目录 + zipDirectory(file, entryName, zos); + } else { + // 压缩文件 + zos.putNextEntry(new ZipEntry(entryName)); + try (InputStream in = new FileInputStream(file)) { + byte[] buffer = new byte[1024]; + int len; + while ((len = in.read(buffer)) != -1) { + zos.write(buffer, 0, len); + } + } + zos.closeEntry(); + } + } + } } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/DesConstructionSchedulePlanServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/DesConstructionSchedulePlanServiceImpl.java index 3484934d..8540b20c 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/DesConstructionSchedulePlanServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/DesConstructionSchedulePlanServiceImpl.java @@ -1,7 +1,10 @@ package org.dromara.design.service.impl; +import cn.hutool.core.collection.CollectionUtil; +import com.alibaba.excel.EasyExcel; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; @@ -14,15 +17,19 @@ import org.dromara.common.core.constant.HttpStatus; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.utils.ObjectUtils; import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.excel.utils.ExcelUtil; import org.dromara.design.domain.DesConstructionSchedulePlan; import org.dromara.design.domain.bo.DesUserBo; import org.dromara.design.domain.dto.constructionscheduleplan.*; import org.dromara.design.domain.vo.DesConstructionSchedulePlanVo; +import org.dromara.design.exportUtil.plan.ConstructionScheduleExport; +import org.dromara.design.exportUtil.plan.ScheduleHeaderWriteHandler; import org.dromara.design.mapper.DesConstructionSchedulePlanMapper; import org.dromara.design.service.IDesConstructionSchedulePlanService; import org.dromara.project.service.IBusProjectService; import org.dromara.system.domain.vo.SysDictDataVo; +import org.dromara.system.domain.vo.SysUserExportVo; import org.dromara.system.service.ISysDictDataService; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; @@ -36,6 +43,7 @@ import java.nio.charset.StandardCharsets; import java.time.LocalDate; import java.time.ZoneId; import java.util.*; +import java.util.stream.Collectors; /** * 施工进度计划Service业务层处理 @@ -624,4 +632,97 @@ public class DesConstructionSchedulePlanServiceImpl extends ServiceImpl dataList = buildExportData(projectId); + // 2. 设置响应头 + ExcelUtil.exportExcel(dataList, "用户数据", ConstructionScheduleExport.class, response); + } + + + + + public List buildExportData(Long projectId) { + // 1. 查询项目下所有里程碑节点 + List allNodes = baseMapper.selectList(Wrappers.lambdaQuery() + .eq(DesConstructionSchedulePlan::getProjectId, projectId)); + if (CollectionUtil.isEmpty(allNodes)) { + return new ArrayList<>(); + } + + // 2. 转换为导出模型并缓存ID映射 + Map nodeMap = new HashMap<>(); + List exportList = new ArrayList<>(); + for (DesConstructionSchedulePlan node : allNodes) { + ConstructionScheduleExport export = new ConstructionScheduleExport(); + BeanUtils.copyProperties(node, export); + nodeMap.put(node.getId(), export); + exportList.add(export); + } + + // 3. 构建树形结构并生成层级编号 + List rootNodes = new ArrayList<>(); + for (ConstructionScheduleExport export : exportList) { + if (export.getParentId() == 0) { // 父ID为0的是根节点 + rootNodes.add(export); + } + } + + // 4. 递归生成层级编号(根节点从1开始) + int rootIndex = 1; + for (ConstructionScheduleExport root : rootNodes) { + root.setLevelCode(String.valueOf(rootIndex)); + buildChildrenCode(root, nodeMap); + rootIndex++; + } + + // 5. 按层级顺序排序(确保1.1在1之后,1.1.1在1.1之后) + return sortByLevelCode(exportList); + } + + // 递归为子节点生成编号(如父节点1 -> 子节点1.1、1.2) + private void buildChildrenCode(ConstructionScheduleExport parent, Map nodeMap) { + List children = nodeMap.values().stream() + .filter(node -> parent.getId().equals(node.getParentId())) + .collect(Collectors.toList()); + + int childIndex = 1; + for (ConstructionScheduleExport child : children) { + child.setLevelCode(parent.getLevelCode() + "." + childIndex); + buildChildrenCode(child, nodeMap); // 递归处理孙子节点 + childIndex++; + } + } + + // 按层级编号排序(字符串自然排序) + private List sortByLevelCode(List list) { + return list.stream() + .sorted(Comparator.comparing(ConstructionScheduleExport::getLevelCode)) + .collect(Collectors.toList()); + } + + + + + + + + + + + + + + + + + + + + + + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/DesPrelimSchemeServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/DesPrelimSchemeServiceImpl.java index 10302d3d..b0c1a267 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/DesPrelimSchemeServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/DesPrelimSchemeServiceImpl.java @@ -1,8 +1,11 @@ package org.dromara.design.service.impl; +import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.convert.Convert; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FileUtils; import org.dromara.common.core.domain.event.ProcessDeleteEvent; import org.dromara.common.core.domain.event.ProcessEvent; import org.dromara.common.core.domain.event.ProcessTaskEvent; @@ -15,13 +18,9 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; -import org.dromara.design.domain.DesScheme; -import org.dromara.message.domain.MsgConfig; -import org.dromara.message.domain.bo.MsgNoticeBo; -import org.dromara.message.domain.dto.SendMsgDto; +import org.dromara.design.domain.dto.ExportDto; import org.dromara.message.service.IMsgConfigService; import org.dromara.message.service.IMsgNoticeService; -import org.dromara.system.domain.vo.SysOssUploadVo; import org.dromara.system.domain.vo.SysOssVo; import org.dromara.system.service.ISysOssService; import org.springframework.context.event.EventListener; @@ -33,8 +32,18 @@ import org.dromara.design.mapper.DesPrelimSchemeMapper; import org.dromara.design.service.IDesPrelimSchemeService; import org.springframework.web.multipart.MultipartFile; +import java.io.*; +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.time.Duration; import java.util.*; -import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import static org.dromara.common.constant.MinioPathConstant.ContactNoticeTemplate; @@ -189,6 +198,105 @@ public class DesPrelimSchemeServiceImpl extends ServiceImpl0; } + /** + * 文件名添加审核状态前缀后压缩为ZIP导出 + * @param response 响应对象 + */ + @Override + public void exportAsZipWithStatusPrefix(ExportDto dto, HttpServletResponse response) throws Exception { + // 1. 查询数据 + List schemes = baseMapper.selectList(Wrappers.lambdaQuery() + .eq(DesPrelimScheme::getProjectId, dto.getProjectId()) + .in(CollectionUtil.isNotEmpty(dto.getIds()), DesPrelimScheme::getId, dto.getIds()) + ); + if (schemes.isEmpty()) { + throw new RuntimeException("没有可导出的初步设计方案文件"); + } + + // 2. 创建临时目录(存放重命名后的文件) + File tempDir = File.createTempFile("prelim_scheme_", "_temp"); + if (!tempDir.delete()) { // 删除临时文件,创建同名目录 + throw new RuntimeException("创建临时目录失败"); + } + if (!tempDir.mkdirs()) { + throw new RuntimeException("创建临时目录失败"); + } + + try { + // 3. 下载文件并以“审核状态-原文件名”命名 + for (DesPrelimScheme scheme : schemes) { + String status = BusinessStatusEnum.getByStatus(scheme.getStatus()).getDesc(); // 审核状态(如draft、approved) + String originalFileName = scheme.getFileName(); // 原文件名 + String newFileName = status + "-" + originalFileName; // 新文件名:状态-原文件名 + + // 临时文件路径 + File tempFile = new File(tempDir, newFileName); + + // 下载文件到临时目录(根据实际存储类型实现,此处以URL为例) + downloadFile(scheme.getFileUrl(), tempFile); + } + + // 4. 压缩临时目录中的所有文件为ZIP并写入响应 + response.setContentType("application/zip"); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + String zipFileName = URLEncoder.encode("初步设计方案", StandardCharsets.UTF_8.name()) + ".zip"; + response.setHeader("Content-Disposition", "attachment;filename*=UTF-8''" + zipFileName); + + // 压缩文件到响应流 + try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) { + File[] tempFiles = tempDir.listFiles(); + if (tempFiles != null) { + for (File file : tempFiles) { + // ZIP条目名称为新文件名(不含临时目录路径) + zos.putNextEntry(new ZipEntry(file.getName())); + try (InputStream in = new FileInputStream(file)) { + byte[] buffer = new byte[1024]; + int len; + while ((len = in.read(buffer)) != -1) { + zos.write(buffer, 0, len); + } + } + zos.closeEntry(); + } + } + } + + } finally { + // 5. 清理临时目录 + FileUtils.deleteQuietly(tempDir); + } + } + + /** + * 下载文件到本地临时目录(根据实际存储类型调整,如OSS、本地文件系统) + */ + private void downloadFile(String fileUrl, File destFile) throws Exception { + // 示例:从URL下载(若为OSS,使用OSS SDK的getObject方法) + // 创建HTTP客户端(设置超时时间) + HttpClient client = HttpClient.newBuilder() + .connectTimeout(Duration.ofMinutes(5)) // 连接超时 + .build(); + + // 创建HTTP请求 + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(fileUrl)) // 转换为URI(比URL更推荐) + .timeout(Duration.ofMinutes(10)) // 响应超时 + .GET() + .build(); + + // 发送请求并将响应体写入目标文件 + HttpResponse response = client.send( + request, + HttpResponse.BodyHandlers.ofFile(destFile.toPath()) // 直接写入文件路径 + ); + + // 检查响应状态(200表示成功) + if (response.statusCode() != 200) { + throw new RuntimeException("文件下载失败,URL: " + fileUrl + ",状态码: " + response.statusCode()); + } + } + + /** * 总体流程监听(例如: 草稿,撤销,退回,作废,终止,已完成,单任务完成等) * 正常使用只需#processEvent.flowCode=='leave1' diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/DesSchemeServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/DesSchemeServiceImpl.java index 0b5c5cb6..c0cb1edf 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/DesSchemeServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/design/service/impl/DesSchemeServiceImpl.java @@ -1,8 +1,11 @@ package org.dromara.design.service.impl; +import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.convert.Convert; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FileUtils; import org.dromara.common.core.domain.event.ProcessDeleteEvent; import org.dromara.common.core.domain.event.ProcessEvent; import org.dromara.common.core.domain.event.ProcessTaskEvent; @@ -19,6 +22,7 @@ import org.dromara.common.oss.core.OssClient; import org.dromara.common.oss.factory.OssFactory; import org.dromara.design.domain.DesCollectFile; import org.dromara.design.domain.DesPrelimScheme; +import org.dromara.design.domain.dto.ExportDto; import org.dromara.system.domain.vo.SysOssUploadVo; import org.dromara.system.domain.vo.SysOssVo; import org.dromara.system.service.ISysOssService; @@ -31,10 +35,23 @@ import org.dromara.design.mapper.DesSchemeMapper; import org.dromara.design.service.IDesSchemeService; import org.springframework.web.multipart.MultipartFile; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.time.Duration; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Collection; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import static org.dromara.common.constant.MinioPathConstant.ContactNoticeTemplate; @@ -183,6 +200,101 @@ public class DesSchemeServiceImpl extends ServiceImpl0; } + + @Override + public void exportAsZipWithStatusPrefix(ExportDto dto, HttpServletResponse response) throws Exception { + // 1. 查询数据 + List schemes = baseMapper.selectList(Wrappers.lambdaQuery() + .eq(DesScheme::getProjectId, dto.getProjectId()) + .in(CollectionUtil.isNotEmpty(dto.getIds()), DesScheme::getId, dto.getIds()) + ); + if (schemes.isEmpty()) { + throw new RuntimeException("没有可导出的初步设计方案文件"); + } + + // 2. 创建临时目录(存放重命名后的文件) + File tempDir = File.createTempFile("prelim_scheme_", "_temp"); + if (!tempDir.delete()) { // 删除临时文件,创建同名目录 + throw new RuntimeException("创建临时目录失败"); + } + if (!tempDir.mkdirs()) { + throw new RuntimeException("创建临时目录失败"); + } + + try { + // 3. 下载文件并以“审核状态-原文件名”命名 + for (DesScheme scheme : schemes) { + String status = BusinessStatusEnum.getByStatus(scheme.getStatus()).getDesc(); // 审核状态(如draft、approved) + String originalFileName = scheme.getFileName(); // 原文件名 + String newFileName = status + "-" + originalFileName; // 新文件名:状态-原文件名 + + // 临时文件路径 + File tempFile = new File(tempDir, newFileName); + + // 下载文件到临时目录(根据实际存储类型实现,此处以URL为例) + downloadFile(scheme.getFileUrl(), tempFile); + } + + // 4. 压缩临时目录中的所有文件为ZIP并写入响应 + response.setContentType("application/zip"); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + String zipFileName = URLEncoder.encode("初步设计方案", StandardCharsets.UTF_8.name()) + ".zip"; + response.setHeader("Content-Disposition", "attachment;filename*=UTF-8''" + zipFileName); + + // 压缩文件到响应流 + try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) { + File[] tempFiles = tempDir.listFiles(); + if (tempFiles != null) { + for (File file : tempFiles) { + // ZIP条目名称为新文件名(不含临时目录路径) + zos.putNextEntry(new ZipEntry(file.getName())); + try (InputStream in = new FileInputStream(file)) { + byte[] buffer = new byte[1024]; + int len; + while ((len = in.read(buffer)) != -1) { + zos.write(buffer, 0, len); + } + } + zos.closeEntry(); + } + } + } + + } finally { + // 5. 清理临时目录 + FileUtils.deleteQuietly(tempDir); + } + } + + /** + * 下载文件到本地临时目录(根据实际存储类型调整,如OSS、本地文件系统) + */ + private void downloadFile(String fileUrl, File destFile) throws Exception { + // 示例:从URL下载(若为OSS,使用OSS SDK的getObject方法) + // 创建HTTP客户端(设置超时时间) + HttpClient client = HttpClient.newBuilder() + .connectTimeout(Duration.ofMinutes(5)) // 连接超时 + .build(); + + // 创建HTTP请求 + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(fileUrl)) // 转换为URI(比URL更推荐) + .timeout(Duration.ofMinutes(10)) // 响应超时 + .GET() + .build(); + + // 发送请求并将响应体写入目标文件 + HttpResponse response = client.send( + request, + HttpResponse.BodyHandlers.ofFile(destFile.toPath()) // 直接写入文件路径 + ); + + // 检查响应状态(200表示成功) + if (response.statusCode() != 200) { + throw new RuntimeException("文件下载失败,URL: " + fileUrl + ",状态码: " + response.statusCode()); + } + } + /** * 总体流程监听(例如: 草稿,撤销,退回,作废,终止,已完成,单任务完成等) * 正常使用只需#processEvent.flowCode=='leave1'