package com.ruoyi.device.service.impl; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.device.dto.DeviceRecordDto; import com.ruoyi.device.mapper.DeviceRecordMapper; import com.ruoyi.device.pojo.DeviceRecord; import com.ruoyi.device.service.DeviceRecordService; import com.ruoyi.system.domain.vo.SysRoleVo; import com.ruoyi.system.mapper.SysRoleMenuMapper; import org.apache.poi.xwpf.usermodel.ParagraphAlignment; import org.apache.poi.xwpf.usermodel.TableRowAlign; import org.apache.poi.xwpf.usermodel.UnderlinePatterns; import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.apache.poi.xwpf.usermodel.XWPFParagraph; import org.apache.poi.xwpf.usermodel.XWPFRun; import org.apache.poi.xwpf.usermodel.XWPFTable; import org.apache.poi.xwpf.usermodel.XWPFTableCell; import org.apache.poi.xwpf.usermodel.XWPFTableRow; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBorder; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPageMar; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPageSz; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRow; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblBorders; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblCellMar; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblGrid; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblLayoutType; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblPr; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblWidth; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTrPr; import org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder; import org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge; import org.openxmlformats.schemas.wordprocessingml.x2006.main.STPageOrientation; import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblLayoutType; import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth; import org.springframework.stereotype.Service; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.OutputStream; import java.math.BigInteger; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** *

* cnas设备使用记录表 服务实现类 *

* * @author 江苏鵷雏网络科技有限公司 * @since 2024-09-21 11:06:47 */ @Service public class DeviceRecordServiceImpl extends ServiceImpl implements DeviceRecordService { private static final String FORM_TITLE = "仪器设备运行记录"; private static final String RECORD_NUMBER = "LMSY-CX07-JL03"; private static final String FONT_NAME = "宋体"; private static final int[] COLUMN_WIDTHS = {1622, 4168, 1288, 1107, 1137, 1076, 940, 1182, 1288, 1350}; private static final String[] HEADER_FIRST_ROW = {"使用日期", "样品编号", "时间 24 小时制", "", "设备状况", "", "环境条件", "", "使用人", "备注"}; private static final String[] HEADER_SECOND_ROW = {"", "", "开始", "结束", "使用前", "使用后", "℃", "%RH", "", ""}; private static final int[] COMBINED_COLUMN_WIDTHS = {1300, 2000, 1500, 1600, 2000, 900, 900, 800, 800, 650, 750, 950, 1008}; private static final String[] COMBINED_HEADER_FIRST_ROW = {"使用日期", "设备名称", "设备型号", "设备编号", "样品编号", "时间 24 小时制", "", "设备状况", "", "环境条件", "", "使用人", "备注"}; private static final String[] COMBINED_HEADER_SECOND_ROW = {"", "", "", "", "", "开始", "结束", "使用前", "使用后", "℃", "%RH", "", ""}; private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); @Resource private SysRoleMenuMapper sysRoleMenuMapper; @Override public IPage deviceRecordPage(Integer deviceId, Page page, String sampleCode, String managementNumber) { // todo:仅看自己 Long userId = SecurityUtils.getUserId(); // 获取权限判断是否是仅看我 SysRoleVo sysRoleVo = sysRoleMenuMapper.selectRoleMenu("get:device:record", userId); Integer testUserId = null; //没有仅看我权限跳过 if (sysRoleVo != null && sysRoleVo.getIsRersonal() != null && sysRoleVo.getIsRersonal().equals(1)) { testUserId = SecurityUtils.getUserId().intValue(); } return baseMapper.deviceRecordPage(deviceId, page, sampleCode, managementNumber, testUserId); } @Override public void exportUseRecord(Integer deviceId, String sampleCode, String managementNumber, String exportDate, HttpServletResponse response) { List records = baseMapper.selectExportList(deviceId, sampleCode, managementNumber, exportDate); if (records == null || records.isEmpty()) { throw new ServiceException(exportDate == null || exportDate.isEmpty() ? "没有可导出的使用记录,无法导出" : exportDate + " 没有可导出的使用记录,无法导出"); } // 单台设备沿用截图表头; 多台设备合并到一张表, 每行带设备信息 Map> deviceRecords = new LinkedHashMap<>(); for (DeviceRecordDto record : records) { deviceRecords.computeIfAbsent(record.getDeviceId(), key -> new ArrayList<>()).add(record); } try (XWPFDocument document = new XWPFDocument()) { setupPage(document); if (deviceRecords.size() == 1) { List group = deviceRecords.values().iterator().next(); renderForm(document, group.get(0), group); } else { renderCombinedForm(document, records); } response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); String fileName = URLEncoder.encode(FORM_TITLE, StandardCharsets.UTF_8).replace("+", "%20"); response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".docx"); try (OutputStream os = response.getOutputStream()) { document.write(os); os.flush(); } } catch (IOException e) { throw new RuntimeException("导出失败", e); } } private void setupPage(XWPFDocument document) { CTBody body = document.getDocument().getBody(); CTSectPr sectPr = body.isSetSectPr() ? body.getSectPr() : body.addNewSectPr(); CTPageSz pageSz = sectPr.isSetPgSz() ? sectPr.getPgSz() : sectPr.addNewPgSz(); pageSz.setW(BigInteger.valueOf(16838)); pageSz.setH(BigInteger.valueOf(11906)); pageSz.setOrient(STPageOrientation.LANDSCAPE); CTPageMar pageMar = sectPr.isSetPgMar() ? sectPr.getPgMar() : sectPr.addNewPgMar(); pageMar.setTop(BigInteger.valueOf(680)); pageMar.setBottom(BigInteger.valueOf(680)); pageMar.setLeft(BigInteger.valueOf(794)); pageMar.setRight(BigInteger.valueOf(794)); pageMar.setHeader(BigInteger.valueOf(851)); pageMar.setFooter(BigInteger.valueOf(992)); pageMar.setGutter(BigInteger.ZERO); } private void renderForm(XWPFDocument document, DeviceRecordDto device, List records) { XWPFParagraph recordNumber = addParagraph(document, "记录编号:" + RECORD_NUMBER, 10.5, false, ParagraphAlignment.LEFT); recordNumber.setSpacingAfter(240); XWPFParagraph title = addParagraph(document, FORM_TITLE, 22, true, ParagraphAlignment.CENTER); title.setSpacingAfter(240); renderDeviceInfo(document, device); document.createParagraph(); XWPFTable table = document.createTable(2 + records.size(), COLUMN_WIDTHS.length); configureTable(table, COLUMN_WIDTHS); for (int i = 0; i < table.getNumberOfRows(); i++) { XWPFTableRow row = table.getRow(i); row.setHeight(i < 2 ? 560 : 520); for (int j = 0; j < COLUMN_WIDTHS.length; j++) { setCellWidth(row.getCell(j), COLUMN_WIDTHS[j]); } setRowProperties(row, i < 2); } XWPFTableRow firstHeader = table.getRow(0); for (int i = 0; i < HEADER_FIRST_ROW.length; i++) { setCellText(firstHeader.getCell(i), HEADER_FIRST_ROW[i]); } XWPFTableRow secondHeader = table.getRow(1); for (int i = 0; i < HEADER_SECOND_ROW.length; i++) { setCellText(secondHeader.getCell(i), HEADER_SECOND_ROW[i]); } mergeHorizontal(firstHeader, COLUMN_WIDTHS, 6, 2); mergeHorizontal(firstHeader, COLUMN_WIDTHS, 4, 2); mergeHorizontal(firstHeader, COLUMN_WIDTHS, 2, 2); mergeVertical(firstHeader.getCell(0), secondHeader.getCell(0)); mergeVertical(firstHeader.getCell(1), secondHeader.getCell(1)); mergeVertical(firstHeader.getCell(5), secondHeader.getCell(8)); mergeVertical(firstHeader.getCell(6), secondHeader.getCell(9)); for (int i = 0; i < records.size(); i++) { XWPFTableRow row = table.getRow(2 + i); String[] values = buildRowValues(records.get(i)); for (int j = 0; j < values.length; j++) { setCellText(row.getCell(j), values[j]); } } } private void renderCombinedForm(XWPFDocument document, List records) { XWPFParagraph recordNumber = addParagraph(document, "记录编号:" + RECORD_NUMBER, 10.5, false, ParagraphAlignment.LEFT); recordNumber.setSpacingAfter(240); XWPFParagraph title = addParagraph(document, FORM_TITLE, 22, true, ParagraphAlignment.CENTER); title.setSpacingAfter(240); XWPFTable table = document.createTable(2 + records.size(), COMBINED_COLUMN_WIDTHS.length); configureTable(table, COMBINED_COLUMN_WIDTHS); for (int i = 0; i < table.getNumberOfRows(); i++) { XWPFTableRow row = table.getRow(i); row.setHeight(i < 2 ? 560 : 520); for (int j = 0; j < COMBINED_COLUMN_WIDTHS.length; j++) { setCellWidth(row.getCell(j), COMBINED_COLUMN_WIDTHS[j]); } setRowProperties(row, i < 2); } XWPFTableRow firstHeader = table.getRow(0); for (int i = 0; i < COMBINED_HEADER_FIRST_ROW.length; i++) { setCellText(firstHeader.getCell(i), COMBINED_HEADER_FIRST_ROW[i]); } XWPFTableRow secondHeader = table.getRow(1); for (int i = 0; i < COMBINED_HEADER_SECOND_ROW.length; i++) { setCellText(secondHeader.getCell(i), COMBINED_HEADER_SECOND_ROW[i]); } mergeHorizontal(firstHeader, COMBINED_COLUMN_WIDTHS, 9, 2); mergeHorizontal(firstHeader, COMBINED_COLUMN_WIDTHS, 7, 2); mergeHorizontal(firstHeader, COMBINED_COLUMN_WIDTHS, 5, 2); mergeVertical(firstHeader.getCell(0), secondHeader.getCell(0)); mergeVertical(firstHeader.getCell(1), secondHeader.getCell(1)); mergeVertical(firstHeader.getCell(2), secondHeader.getCell(2)); mergeVertical(firstHeader.getCell(3), secondHeader.getCell(3)); mergeVertical(firstHeader.getCell(4), secondHeader.getCell(4)); mergeVertical(firstHeader.getCell(8), secondHeader.getCell(11)); mergeVertical(firstHeader.getCell(9), secondHeader.getCell(12)); for (int i = 0; i < records.size(); i++) { XWPFTableRow row = table.getRow(2 + i); String[] values = buildCombinedRowValues(records.get(i)); for (int j = 0; j < values.length; j++) { setCellText(row.getCell(j), values[j]); } } } private XWPFParagraph addParagraph(XWPFDocument document, String text, double fontSize, boolean bold, ParagraphAlignment alignment) { XWPFParagraph paragraph = document.createParagraph(); paragraph.setAlignment(alignment); addRun(paragraph, text, fontSize, bold); return paragraph; } private void addDeviceField(XWPFParagraph paragraph, String label, String value) { addRun(paragraph, label, 10.5, false); XWPFRun valueRun = addRun(paragraph, padUnderline(value), 10.5, false); valueRun.setUnderline(UnderlinePatterns.SINGLE); } private String padUnderline(String value) { String content = value == null ? "" : value.trim(); int width = 0; for (int i = 0; i < content.length(); i++) { width += content.charAt(i) >= 0x2E80 ? 2 : 1; } StringBuilder text = new StringBuilder(content); for (int i = width; i < 20; i++) { text.append(' '); } return text.toString(); } private XWPFRun addRun(XWPFParagraph paragraph, String text, double fontSize, boolean bold) { XWPFRun run = paragraph.createRun(); run.setFontFamily(FONT_NAME); run.setFontFamily(FONT_NAME, XWPFRun.FontCharRange.eastAsia); run.setFontSize(fontSize); run.setBold(bold); run.setText(text); return run; } private void renderDeviceInfo(XWPFDocument document, DeviceRecordDto device) { String[] labels = {"设备名称:", "设备型号:", "设备编号:"}; String[] values = { device == null ? "" : device.getDeviceName(), device == null ? "" : device.getSpecificationModel(), device == null ? "" : device.getManagementNumber() }; int[] widths = {5052, 5053, 5053}; XWPFTable table = document.createTable(1, labels.length); CTTbl ctTbl = table.getCTTbl(); CTTblPr tblPr = ctTbl.getTblPr() != null ? ctTbl.getTblPr() : ctTbl.addNewTblPr(); CTTblWidth tblWidth = tblPr.isSetTblW() ? tblPr.getTblW() : tblPr.addNewTblW(); tblWidth.setType(STTblWidth.DXA); tblWidth.setW(BigInteger.valueOf(totalWidth(COLUMN_WIDTHS))); CTTblLayoutType layoutType = tblPr.isSetTblLayout() ? tblPr.getTblLayout() : tblPr.addNewTblLayout(); layoutType.setType(STTblLayoutType.FIXED); setNoBorders(tblPr); CTTblCellMar cellMar = tblPr.isSetTblCellMar() ? tblPr.getTblCellMar() : tblPr.addNewTblCellMar(); setZeroWidth(cellMar.isSetLeft() ? cellMar.getLeft() : cellMar.addNewLeft()); setZeroWidth(cellMar.isSetRight() ? cellMar.getRight() : cellMar.addNewRight()); CTTblGrid grid = ctTbl.getTblGrid() != null ? ctTbl.getTblGrid() : ctTbl.addNewTblGrid(); while (grid.sizeOfGridColArray() > 0) { grid.removeGridCol(0); } for (int width : widths) { grid.addNewGridCol().setW(BigInteger.valueOf(width)); } XWPFTableRow row = table.getRow(0); for (int i = 0; i < labels.length; i++) { XWPFTableCell cell = row.getCell(i); setCellWidth(cell, widths[i]); cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER); XWPFParagraph paragraph = cell.getParagraphs().isEmpty() ? cell.addParagraph() : cell.getParagraphs().get(0); paragraph.setAlignment(ParagraphAlignment.LEFT); paragraph.setSpacingBefore(0); paragraph.setSpacingAfter(0); addDeviceField(paragraph, labels[i], values[i]); } } private void setNoBorders(CTTblPr tblPr) { CTTblBorders borders = tblPr.isSetTblBorders() ? tblPr.getTblBorders() : tblPr.addNewTblBorders(); setNoBorder(borders.isSetTop() ? borders.getTop() : borders.addNewTop()); setNoBorder(borders.isSetLeft() ? borders.getLeft() : borders.addNewLeft()); setNoBorder(borders.isSetBottom() ? borders.getBottom() : borders.addNewBottom()); setNoBorder(borders.isSetRight() ? borders.getRight() : borders.addNewRight()); setNoBorder(borders.isSetInsideH() ? borders.getInsideH() : borders.addNewInsideH()); setNoBorder(borders.isSetInsideV() ? borders.getInsideV() : borders.addNewInsideV()); } private void setNoBorder(CTBorder border) { border.setVal(STBorder.NIL); border.setSz(BigInteger.ZERO); border.setSpace(BigInteger.ZERO); border.setColor("auto"); } private void setZeroWidth(CTTblWidth width) { width.setType(STTblWidth.DXA); width.setW(BigInteger.ZERO); } private void configureTable(XWPFTable table, int[] widths) { table.setTableAlignment(TableRowAlign.CENTER); CTTbl ctTbl = table.getCTTbl(); CTTblPr tblPr = ctTbl.getTblPr() != null ? ctTbl.getTblPr() : ctTbl.addNewTblPr(); CTTblWidth tblWidth = tblPr.isSetTblW() ? tblPr.getTblW() : tblPr.addNewTblW(); tblWidth.setType(STTblWidth.DXA); tblWidth.setW(BigInteger.valueOf(totalWidth(widths))); CTTblLayoutType layoutType = tblPr.isSetTblLayout() ? tblPr.getTblLayout() : tblPr.addNewTblLayout(); layoutType.setType(STTblLayoutType.FIXED); CTTblBorders borders = tblPr.isSetTblBorders() ? tblPr.getTblBorders() : tblPr.addNewTblBorders(); setBorder(borders.isSetTop() ? borders.getTop() : borders.addNewTop()); setBorder(borders.isSetLeft() ? borders.getLeft() : borders.addNewLeft()); setBorder(borders.isSetBottom() ? borders.getBottom() : borders.addNewBottom()); setBorder(borders.isSetRight() ? borders.getRight() : borders.addNewRight()); setBorder(borders.isSetInsideH() ? borders.getInsideH() : borders.addNewInsideH()); setBorder(borders.isSetInsideV() ? borders.getInsideV() : borders.addNewInsideV()); CTTblGrid grid = ctTbl.getTblGrid() != null ? ctTbl.getTblGrid() : ctTbl.addNewTblGrid(); while (grid.sizeOfGridColArray() > 0) { grid.removeGridCol(0); } for (int width : widths) { grid.addNewGridCol().setW(BigInteger.valueOf(width)); } } private int totalWidth(int[] widths) { int total = 0; for (int width : widths) { total += width; } return total; } private void setBorder(CTBorder border) { border.setVal(STBorder.SINGLE); border.setSz(BigInteger.valueOf(4)); border.setSpace(BigInteger.ZERO); border.setColor("000000"); } private void setCellWidth(XWPFTableCell cell, int width) { CTTcPr tcPr = cell.getCTTc().isSetTcPr() ? cell.getCTTc().getTcPr() : cell.getCTTc().addNewTcPr(); CTTblWidth tcW = tcPr.isSetTcW() ? tcPr.getTcW() : tcPr.addNewTcW(); tcW.setType(STTblWidth.DXA); tcW.setW(BigInteger.valueOf(width)); } private void setCellText(XWPFTableCell cell, String text) { cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER); XWPFParagraph paragraph = cell.getParagraphs().isEmpty() ? cell.addParagraph() : cell.getParagraphs().get(0); paragraph.setAlignment(ParagraphAlignment.CENTER); paragraph.setSpacingBefore(0); paragraph.setSpacingAfter(0); addRun(paragraph, text == null ? "" : text, 10.5, false); } private void setRowProperties(XWPFTableRow row, boolean repeatHeader) { CTRow ctRow = row.getCtRow(); CTTrPr trPr = ctRow.isSetTrPr() ? ctRow.getTrPr() : ctRow.addNewTrPr(); trPr.addNewCantSplit(); if (repeatHeader) { trPr.addNewTblHeader(); } } private void mergeHorizontal(XWPFTableRow row, int[] widths, int firstIndex, int span) { int width = 0; for (int i = firstIndex; i < firstIndex + span; i++) { width += widths[i]; } XWPFTableCell first = row.getCell(firstIndex); setCellWidth(first, width); CTTcPr tcPr = first.getCTTc().isSetTcPr() ? first.getCTTc().getTcPr() : first.getCTTc().addNewTcPr(); (tcPr.isSetGridSpan() ? tcPr.getGridSpan() : tcPr.addNewGridSpan()).setVal(BigInteger.valueOf(span)); for (int i = firstIndex + span - 1; i > firstIndex; i--) { row.removeCell(i); } } private void mergeVertical(XWPFTableCell first, XWPFTableCell second) { setVMerge(first, STMerge.RESTART); setVMerge(second, STMerge.CONTINUE); } private void setVMerge(XWPFTableCell cell, STMerge.Enum value) { CTTcPr tcPr = cell.getCTTc().isSetTcPr() ? cell.getCTTc().getTcPr() : cell.getCTTc().addNewTcPr(); (tcPr.isSetVMerge() ? tcPr.getVMerge() : tcPr.addNewVMerge()).setVal(value); } private String[] buildRowValues(DeviceRecordDto record) { return new String[]{ formatDate(record.getUseStartDate()), valueOrEmpty(record.getSampleCode()), formatTime(record.getUseStartDate()), formatTime(record.getUseEndDate()), statusText(record.getUseBefore()), statusText(record.getUseAfter()), valueOrEmpty(record.getTemperature()), valueOrEmpty(record.getHumidity()), valueOrEmpty(record.getUsePerson()), valueOrEmpty(record.getRemark()) }; } private String[] buildCombinedRowValues(DeviceRecordDto record) { return new String[]{ formatDate(record.getUseStartDate()), valueOrEmpty(record.getDeviceName()), valueOrEmpty(record.getSpecificationModel()), valueOrEmpty(record.getManagementNumber()), valueOrEmpty(record.getSampleCode()), formatTime(record.getUseStartDate()), formatTime(record.getUseEndDate()), statusText(record.getUseBefore()), statusText(record.getUseAfter()), valueOrEmpty(record.getTemperature()), valueOrEmpty(record.getHumidity()), valueOrEmpty(record.getUsePerson()), valueOrEmpty(record.getRemark()) }; } private String formatDate(LocalDateTime value) { return value == null ? "" : value.format(DATE_FORMATTER); } private String formatTime(LocalDateTime value) { return value == null ? "" : value.format(TIME_FORMATTER); } private String statusText(Integer value) { if (value == null) { return ""; } return value == 0 ? "异常" : "良好"; } private String valueOrEmpty(String value) { return value == null ? "" : value; } }