cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceRecordServiceImpl.java
@@ -11,6 +11,7 @@
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.BreakType;
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
import org.apache.poi.xwpf.usermodel.TableRowAlign;
import org.apache.poi.xwpf.usermodel.UnderlinePatterns;
@@ -71,11 +72,9 @@
    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_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 String FOOTNOTE = "注:设备使用前后一栏,正常划“√”;不正常划“×”并写明情况。";
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
@@ -83,7 +82,9 @@
    private SysRoleMenuMapper sysRoleMenuMapper;
    @Override
    public IPage<DeviceRecordDto> deviceRecordPage(Integer deviceId, Page page, String sampleCode, String managementNumber) {
    public IPage<DeviceRecordDto> deviceRecordPage(Integer deviceId, Page page, String deviceName,
                                                   String specificationModel, String sampleCode,
                                                   String managementNumber) {
        // todo:仅看自己
        Long userId = SecurityUtils.getUserId();
        // 获取权限判断是否是仅看我
@@ -95,19 +96,20 @@
            testUserId = SecurityUtils.getUserId().intValue();
        }
        return baseMapper.deviceRecordPage(deviceId, page, sampleCode, managementNumber, testUserId);
        return baseMapper.deviceRecordPage(deviceId, page, deviceName, specificationModel,
                sampleCode, managementNumber, testUserId);
    }
    @Override
    public void exportUseRecord(Integer deviceId, String sampleCode, String managementNumber, String exportDate, HttpServletResponse response) {
        List<DeviceRecordDto> records = baseMapper.selectExportList(deviceId, sampleCode, managementNumber, exportDate);
    public void exportUseRecord(Integer deviceId, String deviceName, String specificationModel,
                                String managementNumber, String sampleCode, HttpServletResponse response) {
        List<DeviceRecordDto> records = baseMapper.selectExportList(deviceId, deviceName,
                specificationModel, managementNumber, sampleCode);
        if (records == null || records.isEmpty()) {
            throw new ServiceException(exportDate == null || exportDate.isEmpty()
                    ? "没有可导出的使用记录,无法导出"
                    : exportDate + " 没有可导出的使用记录,无法导出");
            throw new ServiceException("没有可导出的使用记录,无法导出");
        }
        // 单台设备沿用截图表头; 多台设备合并到一张表, 每行带设备信息
        // 一台设备一本运行记录,多台设备在同一 Word 内按设备分页
        Map<Integer, List<DeviceRecordDto>> deviceRecords = new LinkedHashMap<>();
        for (DeviceRecordDto record : records) {
            deviceRecords.computeIfAbsent(record.getDeviceId(), key -> new ArrayList<>()).add(record);
@@ -115,11 +117,13 @@
        try (XWPFDocument document = new XWPFDocument()) {
            setupPage(document);
            if (deviceRecords.size() == 1) {
                List<DeviceRecordDto> group = deviceRecords.values().iterator().next();
                renderForm(document, group.get(0), group);
            } else {
                renderCombinedForm(document, records);
            boolean first = true;
            for (List<DeviceRecordDto> group : deviceRecords.values()) {
                if (!first) {
                    addPageBreak(document);
                }
                renderForm(document, group.get(0), mergeSameConditionRecords(group));
                first = false;
            }
            response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
@@ -132,6 +136,49 @@
        } catch (IOException e) {
            throw new RuntimeException("导出失败", e);
        }
    }
    /**
     * 同一设备、同一天、起止时间/温湿度/使用人/使用前后状况相同的记录合并一行,样品编号用“、”拼接。
     */
    private List<DeviceRecordDto> mergeSameConditionRecords(List<DeviceRecordDto> records) {
        Map<String, DeviceRecordDto> merged = new LinkedHashMap<>();
        for (DeviceRecordDto record : records) {
            String key = String.join("|",
                    formatDate(record.getUseStartDate()),
                    formatTime(record.getUseStartDate()),
                    formatTime(record.getUseEndDate()),
                    valueOrEmpty(record.getTemperature()),
                    valueOrEmpty(record.getHumidity()),
                    valueOrEmpty(record.getUsePerson()),
                    String.valueOf(record.getUseBefore()),
                    String.valueOf(record.getUseAfter()));
            DeviceRecordDto existing = merged.get(key);
            if (existing == null) {
                merged.put(key, record);
            } else {
                existing.setSampleCode(appendDistinct(existing.getSampleCode(), record.getSampleCode()));
                existing.setRemark(appendDistinct(existing.getRemark(), record.getRemark()));
            }
        }
        return new ArrayList<>(merged.values());
    }
    private String appendDistinct(String existing, String addition) {
        if (addition == null || addition.trim().isEmpty()) {
            return existing;
        }
        if (existing == null || existing.trim().isEmpty()) {
            return addition;
        }
        List<String> parts = java.util.Arrays.asList(existing.split("、"));
        return parts.contains(addition) ? existing : existing + "、" + addition;
    }
    private void addPageBreak(XWPFDocument document) {
        XWPFParagraph paragraph = document.createParagraph();
        paragraph.setSpacingAfter(0);
        paragraph.createRun().addBreak(BreakType.PAGE);
    }
    private void setupPage(XWPFDocument document) {
@@ -198,54 +245,10 @@
                setCellText(row.getCell(j), values[j]);
            }
        }
    }
    private void renderCombinedForm(XWPFDocument document, List<DeviceRecordDto> 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]);
            }
        }
        XWPFParagraph footnote = addParagraph(document, FOOTNOTE, 10.5, false, ParagraphAlignment.LEFT);
        footnote.setSpacingBefore(120);
        footnote.setSpacingAfter(0);
    }
    private XWPFParagraph addParagraph(XWPFDocument document, String text, double fontSize, boolean bold, ParagraphAlignment alignment) {
@@ -461,24 +464,6 @@
        };
    }
    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);
    }
@@ -491,7 +476,7 @@
        if (value == null) {
            return "";
        }
        return value == 0 ? "异常" : "良好";
        return value == 0 ? "异常" : "正常";
    }
    private String valueOrEmpty(String value) {